From 1472531f2f3cba098a7bfceedaa007ffab13d13c Mon Sep 17 00:00:00 2001 From: Maxim Date: Fri, 17 Jul 2026 09:00:19 +0300 Subject: [PATCH] =?UTF-8?q?=D0=92=20=D0=BF=D1=80=D0=BE=D1=86=D0=B5=D1=81?= =?UTF-8?q?=D1=81=D0=B5=20=D1=80=D0=B0=D0=B7=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- makefile | 13 +++-- scripts/deploy.sh | 2 +- src/udp_probe.c | 137 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+), 5 deletions(-) create mode 100644 src/udp_probe.c diff --git a/makefile b/makefile index 3470402..86d5bd1 100644 --- a/makefile +++ b/makefile @@ -20,7 +20,7 @@ PLUTO_B ?= 192.168.3.1 BIN := build SRC := src -all: $(BIN)/transmitter $(BIN)/receiver $(BIN)/ofdm_bench $(BIN)/udp_gw +all: $(BIN)/transmitter $(BIN)/receiver $(BIN)/ofdm_bench $(BIN)/udp_gw $(BIN)/udp_probe $(BIN): mkdir -p $(BIN) @@ -39,6 +39,10 @@ $(BIN)/ofdm_bench: $(SRC)/ofdm_bench.c | $(BIN) $(BIN)/udp_gw: $(SRC)/udp_gw.c | $(BIN) $(CC) $(CFLAGS) $< -o $@ +# Тестовый генератор/приёмник UDP для бринг-апа udp_gw (busybox без nc/socat). +$(BIN)/udp_probe: $(SRC)/udp_probe.c | $(BIN) + $(CC) $(CFLAGS) $< -o $@ + # Оффлайн-самопроверка GF(256) — нативная сборка (gcc хоста, напр. RPi/aarch64) $(BIN)/gftest: $(SRC)/group_fec.c $(SRC)/group_fec.h | $(BIN) cc -O2 -Wall -DGF_SELFTEST $(SRC)/group_fec.c -o $@ @@ -51,6 +55,7 @@ tx: $(BIN)/transmitter rx: $(BIN)/receiver bench: $(BIN)/ofdm_bench gw: $(BIN)/udp_gw +probe: $(BIN)/udp_probe gftest: $(BIN)/gftest $(BIN)/gftest gwtest: $(BIN)/gwtest @@ -76,8 +81,8 @@ clean: rm -rf $(BIN) help: - @echo "make [all] — собрать transmitter, receiver, ofdm_bench, udp_gw (static armhf)" - @echo "make tx|rx|bench|gw — собрать отдельную цель" + @echo "make [all] — собрать transmitter, receiver, ofdm_bench, udp_gw, udp_probe (static armhf)" + @echo "make tx|rx|bench|gw|probe — собрать отдельную цель" @echo "make gftest — оффлайн-самопроверка group-FEC (нативный cc)" @echo "make gwtest — оффлайн-самопроверка парсера udp_gw (нативный cc)" @echo "make deploy — залить бинарники на обе платы (scp -O в /tmp)" @@ -86,4 +91,4 @@ help: @echo "make clean — удалить build/" @echo "переменные: PLUTO_A=$(PLUTO_A) PLUTO_B=$(PLUTO_B) XARM=$(XARM)" -.PHONY: all tx rx bench gw gftest gwtest deploy test check-env clean help \ No newline at end of file +.PHONY: all tx rx bench gw probe gftest gwtest deploy test check-env clean help \ No newline at end of file diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 493f0f5..f9d1200 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -17,7 +17,7 @@ else SCP=(scp -O) fi -FILES=(build/transmitter build/receiver build/ofdm_bench build/udp_gw) +FILES=(build/transmitter build/receiver build/ofdm_bench build/udp_gw build/udp_probe) TARGETS=() case "${1:-}" in diff --git a/src/udp_probe.c b/src/udp_probe.c new file mode 100644 index 0000000..df25b2f --- /dev/null +++ b/src/udp_probe.c @@ -0,0 +1,137 @@ +// udp_probe.c — минимальный генератор/приёмник UDP для тестирования udp_gw +// (roadmap §10 п.8). НЕ часть штатного тракта — только инструмент бринг-апа +// и E2E-тестов: busybox-rootfs Pluto без nc/socat (проверено на железе), +// а Python на хосте запрещён (CLAUDE.md правило №1) — значит свой маленький +// C-бинарь, тот же кросс-компилятор, что и весь тракт. +// +// send: udp_probe send ip:port РАЗМЕР_ЧАНКА [ПАУЗА_МС] stdin -> UDP +// recv: udp_probe recv ПОРТ ТАЙМАУТ_ПРОСТОЯ_С UDP -> stdout +// +// recv сам завершается после ТАЙМАУТ_ПРОСТОЯ_С секунд без новых датаграмм +// (busybox без `timeout` — та же причина, что и в scripts/test_file_link.sh). + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static volatile sig_atomic_t g_stop = 0; +static void on_signal(int s) { (void)s; g_stop = 1; } + +static int parse_hostport(const char *hostport, struct sockaddr_in *out) { + const char *colon = strrchr(hostport, ':'); + if (!colon || colon == hostport) return -1; + char host[64]; + size_t hlen = (size_t)(colon - hostport); + if (hlen >= sizeof(host)) return -1; + memcpy(host, hostport, hlen); host[hlen] = 0; + + memset(out, 0, sizeof(*out)); + out->sin_family = AF_INET; + out->sin_port = htons((uint16_t)atoi(colon + 1)); + return inet_pton(AF_INET, host, &out->sin_addr) == 1 ? 0 : -1; +} + +// stdin читается чанками по `chunk` байт, каждый чанк — одна UDP-датаграмма. +// Короткий чанк (fread < chunk) — признак EOF (как fread в transmitter.c). +static int mode_send(const char *hostport, int chunk, int pause_ms) { + struct sockaddr_in dst; + if (parse_hostport(hostport, &dst) != 0) { + fprintf(stderr, "udp_probe: не разобрал '%s' (ожидался ip:port)\n", hostport); + return 1; + } + if (chunk <= 0 || chunk > 65000) { + fprintf(stderr, "udp_probe: размер чанка вне диапазона\n"); + return 1; + } + + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { perror("socket"); return 1; } + + uint8_t *buf = malloc((size_t)chunk); + uint64_t sent = 0, bytes = 0; + for (;;) { + size_t n = fread(buf, 1, (size_t)chunk, stdin); + if (n == 0) break; + if (sendto(fd, buf, n, 0, (struct sockaddr *)&dst, sizeof(dst)) < 0) + fprintf(stderr, "udp_probe: sendto: %s\n", strerror(errno)); + else { sent++; bytes += n; } + if (n < (size_t)chunk) break; // короткое чтение = EOF + if (pause_ms > 0) usleep((useconds_t)pause_ms * 1000); + } + fprintf(stderr, "udp_probe send: отправлено %llu датаграмм, %llu байт\n", + (unsigned long long)sent, (unsigned long long)bytes); + free(buf); + close(fd); + return 0; +} + +// Дампит содержимое каждой полученной датаграммы в stdout как есть (порядок +// прихода). Для сверки с исходником через md5sum, как в остальных e2e-тестах +// репозитория — если ничего не потерялось и не переупорядочилось, конкатенация +// совпадёт байт-в-байт. +static int mode_recv(int port, int idle_timeout_s) { + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { perror("socket"); return 1; } + int one = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons((uint16_t)port); + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + perror("bind"); close(fd); return 1; + } + + signal(SIGINT, on_signal); + signal(SIGTERM, on_signal); + + uint8_t buf[65536]; + uint64_t recvd = 0, bytes = 0; + time_t last_rx = time(NULL); + while (!g_stop) { + fd_set rfds; FD_ZERO(&rfds); FD_SET(fd, &rfds); + struct timeval tv = { .tv_sec = 1, .tv_usec = 0 }; + int sel = select(fd + 1, &rfds, NULL, NULL, &tv); + if (sel < 0) { if (errno == EINTR) continue; break; } + if (sel > 0 && FD_ISSET(fd, &rfds)) { + ssize_t n = recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL); + if (n < 0) { if (errno == EINTR) continue; break; } + fwrite(buf, 1, (size_t)n, stdout); + fflush(stdout); + recvd++; bytes += (uint64_t)n; + last_rx = time(NULL); + } + if (idle_timeout_s > 0 && time(NULL) - last_rx >= idle_timeout_s) break; + } + fprintf(stderr, "udp_probe recv: получено %llu датаграмм, %llu байт\n", + (unsigned long long)recvd, (unsigned long long)bytes); + close(fd); + return 0; +} + +static void usage(const char *prog) { + fprintf(stderr, "udp_probe — тестовый генератор/приёмник UDP (busybox без nc)\n\n"); + fprintf(stderr, " %s send ip:port РАЗМЕР_ЧАНКА [ПАУЗА_МС] stdin -> UDP\n", prog); + fprintf(stderr, " %s recv ПОРТ ТАЙМАУТ_ПРОСТОЯ_С UDP -> stdout\n", prog); +} + +int main(int argc, char *argv[]) { + if (argc >= 4 && !strcmp(argv[1], "send")) + return mode_send(argv[2], atoi(argv[3]), argc >= 5 ? atoi(argv[4]) : 0); + if (argc >= 4 && !strcmp(argv[1], "recv")) + return mode_recv(atoi(argv[2]), atoi(argv[3])); + usage(argv[0]); + return 1; +}