146 lines
9.3 KiB
Bash
146 lines
9.3 KiB
Bash
#!/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
|