Files
Pluto-SDR/scripts/test_udp_tunnel.sh

146 lines
9.3 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# test_udp_tunnel.sh — e2e-тест UDP-туннеля (§10 п.8):
# хост-файл → udp_probe send → UDP → udp_gw -l → transmitter → ЭФИР →
# receiver → udp_gw -d → UDP → udp_probe recv → файл → md5-сверка
# Использование: scripts/test_udp_tunnel.sh [ipA] [ipB] [размер_КБ]
# По умолчанию: адреса из toolchain.env, 64 КБ, симплекс 915 МГц.
#
# ВНИМАНИЕ: правило №7 (кабель+аттенюатор) для udp_gw не применимо — шлюз не
# трогает PHY ни на байт (record-слой поверх уже принятого байтового потока),
# его логика проверена `make gwtest` + loopback на плате. Прогон штатно идёт
# на антеннах: TXGAIN=-20 scripts/test_udp_tunnel.sh
set -euo pipefail
source "$(dirname "$0")/../toolchain.env"
A="${1:-$PLUTO_A}" # передатчик
B="${2:-$PLUTO_B}" # приёмник
SIZE_KB="${3:-64}"
FREQ=915000000
RATE=1920000 # НЕ поднимать: бюджет CPU, docs/BENCHMARK.md
BW=1500000
TXGAIN="${TXGAIN:--30}" # OTA: переопредели, напр. TXGAIN=-20 scripts/test_udp_tunnel.sh
FEC="${FEC:-rs8}"
PAUSE="${PAUSE:-3000}" # ≤1 МБ — 3000 чист (§12.6); длинные прогоны — 4000
GROUPFEC="${GROUPFEC:-1}"
[ "$GROUPFEC" = 1 ] && GFLAG="-G" || GFLAG=""
PORT_A="${PORT_A:-6000}" # udp_gw -l на плате A (вход туннеля)
PORT_B="${PORT_B:-6001}" # udp_probe recv на плате B (выход туннеля)
CHUNK="${CHUNK:-1000}" # размер датаграммы; >1016 Б рекорд рвётся на 2 кадра
PACE_MS="${PACE_MS:-15}" # 1000 Б / 15 мс ≈ 65 КБ/сс запасом под потолок
# `-G -p 3000` ≈107 КБ/с. На 10 мс (≈100 КБ/с) вход
# упирается в потолок и шлюз штатно дропает.
IDLE="${IDLE:-30}" # сторожевой таймер udp_probe recv. ВАЖНО: тикает
# с момента СТАРТА, не с первой датаграммы → зазор
# старт-B→передача должен быть скриптованным (~4 с),
# а не ручной вставкой команд. Штатное завершение —
# активный killall из шага [5], не по таймауту.
if command -v sshpass >/dev/null; then
SSH=(sshpass -p "$PLUTO_PASS" ssh -o StrictHostKeyChecking=no)
else
SSH=(ssh)
echo "подсказка: apt install sshpass — и пароль вводить не придётся"
fi
if command -v sshpass >/dev/null; then
SCP=(sshpass -p "$PLUTO_PASS" scp -O -o StrictHostKeyChecking=no)
else
SCP=(scp -O)
fi
echo "=== [1/6] Тестовый файл ${SIZE_KB} КБ (чанк ${CHUNK} Б, темп ${PACE_MS} мс) ==="
dd if=/dev/urandom of=/tmp/probe_in.bin bs=1024 count="$SIZE_KB" status=none
MD5_SRC=$(md5sum /tmp/probe_in.bin | cut -d' ' -f1)
DGRAMS=$(( (SIZE_KB * 1024 + CHUNK - 1) / CHUNK ))
echo "md5: $MD5_SRC | ожидается датаграмм: $DGRAMS"
echo "=== [2/6] Заливка файла на Pluto A ($A) ==="
"${SCP[@]}" /tmp/probe_in.bin "root@$A:/tmp/"
echo "=== [3/6] Старт цепочки на Pluto B ($B): receiver | udp_gw -d → udp_probe recv ==="
# heredoc в `sh -s`: stdin ssh занят скриптом → фоновый ssh не ловит SIGTTIN
# (без этого `ssh ... &` уходит в «Остановлен» при попытке читать терминал).
"${SSH[@]}" "root@$B" 'sh -s' <<EOF &
rm -f /tmp/probe_out.bin /tmp/rx.log /tmp/gwd.log /tmp/probe_rx.log
/tmp/receiver -f $FREQ -r $RATE -b $BW -c $FEC -u local: 2>/tmp/rx.log \
| /tmp/udp_gw -d 127.0.0.1:$PORT_B 2>/tmp/gwd.log &
/tmp/udp_probe recv $PORT_B $IDLE > /tmp/probe_out.bin 2>/tmp/probe_rx.log
killall receiver udp_gw 2>/dev/null || true
EOF
B_PID=$!
sleep 4 # дать приёмнику прогреться до старта TX
echo "=== [4/6] Передача с Pluto A: udp_probe send → udp_gw -l → transmitter ==="
# mkfifo: у пайпа `$!` — только последний процесс, а гейтвей надо убить отдельно
# (его выход = EOF транзмиттеру → штатное завершение передачи).
"${SSH[@]}" "root@$A" 'sh -s' <<EOF
rm -f /tmp/tx.log /tmp/gwl.log /tmp/probe_tx.log /tmp/gw.fifo
mkfifo /tmp/gw.fifo
/tmp/udp_gw -l $PORT_A > /tmp/gw.fifo 2>/tmp/gwl.log &
GWP=\$!
/tmp/transmitter -f $FREQ -r $RATE -b $BW -g $TXGAIN -p $PAUSE \
-c $FEC $GFLAG -u local: < /tmp/gw.fifo 2>/tmp/tx.log &
TXP=\$!
# Ждём готовности транзмиттера, а не спим вслепую: инициализация iio (LO, буферы)
# занимает секунды, а из пайпа он до неё не читает. Пайп 64 КБ ≈ весь тестовый
# файл → шлюз штатно дропает хвост по бэкпрешеру, если начать лить раньше.
i=0
while [ \$i -lt 20 ]; do
grep -q "Ожидание данных" /tmp/tx.log 2>/dev/null && break
i=\$((i+1)); sleep 1
done
/tmp/udp_probe send 127.0.0.1:$PORT_A $CHUNK $PACE_MS \
< /tmp/probe_in.bin 2>/tmp/probe_tx.log
sleep 2
kill \$GWP 2>/dev/null || true
wait \$TXP
rm -f /tmp/gw.fifo
EOF
echo "=== [5/6] Гашение цепочки B ==="
sleep 3 # дохлебать хвост эфира
# ПОРЯДОК ГАШЕНИЯ КРИТИЧЕН. receiver.c:798 — group_flush()+rb_flush_all() стоят
# ПОСЛЕ основного цикла: хвост незавершённой группы (до 16 кадров) уходит в
# stdout только в момент смерти приёмника. Поэтому гасим строго по течению:
# receiver (флашит хвост) → пауза на прохождение через udp_gw -d → udp_probe.
# Убить их разом = потерять последнюю группу (было: доставлено 64, принято 48).
"${SSH[@]}" -n "root@$B" "killall receiver 2>/dev/null || true" || true
sleep 3 # хвост: receiver → пайп → udp_gw -d (выйдет сам по EOF) → UDP → probe
"${SSH[@]}" -n "root@$B" "killall udp_probe udp_gw 2>/dev/null || true" || true
wait "$B_PID" 2>/dev/null || true
echo "=== [6/6] Сверка ==="
MD5_DST=$("${SSH[@]}" -n "root@$B" "md5sum /tmp/probe_out.bin 2>/dev/null | cut -d' ' -f1" || echo ет_файла")
SIZE_DST=$("${SSH[@]}" -n "root@$B" "wc -c < /tmp/probe_out.bin 2>/dev/null" || echo 0)
echo "источник: $MD5_SRC ($((SIZE_KB*1024)) байт)"
echo "туннель: $MD5_DST ($SIZE_DST байт)"
echo
echo "--- A: udp_probe send ---"; "${SSH[@]}" -n "root@$A" "cat /tmp/probe_tx.log" || true
echo "--- A: udp_gw -l (вход) ---"; "${SSH[@]}" -n "root@$A" "tail -2 /tmp/gwl.log" || true
echo "--- A: transmitter ---"; "${SSH[@]}" -n "root@$A" "tail -4 /tmp/tx.log" || true
echo "--- B: receiver ---"; "${SSH[@]}" -n "root@$B" "tail -6 /tmp/rx.log" || true
echo "--- B: udp_gw -d (выход) ---";"${SSH[@]}" -n "root@$B" "tail -2 /tmp/gwd.log" || true
echo "--- B: udp_probe recv ---"; "${SSH[@]}" -n "root@$B" "cat /tmp/probe_rx.log" || true
echo
if [ "$MD5_SRC" = "$MD5_DST" ]; then
echo "РЕЗУЛЬТАТ: OK — туннель прозрачен, $DGRAMS датаграмм доставлены байт-в-байт"
else
echo "РЕЗУЛЬТАТ: FAIL — разбор по счётчикам выше:"
echo " - 'дыр(id)' > 0 или 'ресинк' > 0 при живом потоке → НЕ баг туннеля:"
echo " радиопотери сверх group-FEC 16+2 (на симплексе ARQ нет, техдолг #6)."
echo " Контракт соблюдён: дыры посчитаны, поток рекордов не развалился."
echo " Меры: TXGAIN ближе к 20, антенны разнести/переориентировать."
echo " - 'дропов' > 0 у 'udp_gw -l (вход)' → вход быстрее линка (~107 КБ/с"
echo " на -G -p 3000): подними PACE_MS. Это контрактный дроп, семантика UDP."
echo " Если дропов ровно 1-2 в конце — транзмиттер ещё не читал пайп на"
echo " старте (см. ожидание 'Ожидание данных' в шаге [4])."
echo " - 'доставлено' > принятых probe → гашение обогнало хвост приёмника"
echo " (receiver.c:798 флашит последнюю группу только при выходе)."
echo " - размер 0 и в rx.log 0 кадров → приёмник не работал во время передачи:"
echo " частота/усиление/антенны, либо цепочка B умерла по IDLE (=$IDLE с)."
echo " - размер совпал, md5 нет → искажение полезной нагрузки: техдолг #1/#3."
fi