From f5cab5a14e3cadd7e2c5c09a9a2b06ecc2511c99 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 22 Oct 2026 23:19:01 +0200 Subject: [PATCH] Calibrate dummynet bandwidth and verify stalled channel lifecycle --- tests/Makefile.am | 4 + tests/test_dummynet_channels.c | 133 +++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 tests/test_dummynet_channels.c diff --git a/tests/Makefile.am b/tests/Makefile.am index 75752908..b56018e8 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -100,6 +100,7 @@ if OS_LINUX check_PROGRAMS += \ test_ntp_regressions \ test_etcp_windows \ + test_dummynet_channels \ test_stcp_sigpipe \ test_firewall \ test_mem \ @@ -1012,6 +1013,9 @@ test_etcp_session_LDADD = $(LIBUTUN) $(COMMON_LIBS) test_etcp_session_LDFLAGS = $(test_clock_ldflags) test_etcp_windows_SOURCES = test_etcp_windows.c $(test_clock_sources) +test_dummynet_channels_SOURCES = test_dummynet_channels.c $(test_clock_sources) +test_dummynet_channels_LDADD = $(LIBUTUN) $(COMMON_LIBS) +test_dummynet_channels_LDFLAGS = $(test_clock_ldflags) test_stcp_sigpipe_SOURCES = test_stcp_sigpipe.c test_stcp_sigpipe_LDADD = $(LIBUTUN) $(COMMON_LIBS) test_etcp_windows_LDADD = $(LIBUTUN) $(COMMON_LIBS) diff --git a/tests/test_dummynet_channels.c b/tests/test_dummynet_channels.c new file mode 100644 index 00000000..3875024e --- /dev/null +++ b/tests/test_dummynet_channels.c @@ -0,0 +1,133 @@ +/* Калибровка каналов: BWlim смешанных размеров, задержка, ограниченный HOLD, DROP и teardown с таймерами. */ +#include +#include +#include "dummynet.h" +#include "test_clock.h" +#include "../lib/platform_compat.h" +#include "../lib/mem.h" + +#define CHECK(x) do { if (!(x)) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "FAIL %s:%d: %s", __func__, __LINE__, #x); exit(1); } } while (0) +static struct UASYNC* ua; +static struct dummynet* dn; +static socket_t peers[2]; +static struct sockaddr_in proxy; +static unsigned received[2]; +static uint64_t bytes[2], last_rx[2]; +static uint8_t seen[2][512]; + +static void setup(unsigned bw, unsigned delay, unsigned capacity) { + ua = uasync_create(); CHECK(ua); + dn = dummynet_create(ua, "127.0.0.1", 0); CHECK(dn); + memset(received, 0, sizeof(received)); memset(bytes, 0, sizeof(bytes)); memset(seen, 0, sizeof(seen)); + memset(&proxy, 0, sizeof(proxy)); proxy.sin_family = AF_INET; + proxy.sin_addr.s_addr = htonl(INADDR_LOOPBACK); proxy.sin_port = htons(dummynet_get_listen_port(dn)); + for (int i = 0; i < 2; i++) { + peers[i] = socket_create_udp(AF_INET); CHECK(peers[i] != SOCKET_INVALID); + struct sockaddr_in address = proxy; address.sin_port = 0; + CHECK(bind(peers[i], (struct sockaddr*)&address, sizeof(address)) == 0); + CHECK(socket_set_nonblocking(peers[i]) == 0); + int size = 4 * 1024 * 1024; + CHECK(setsockopt(peers[i], SOL_SOCKET, SO_RCVBUF, (const char*)&size, sizeof(size)) == 0); + CHECK(dummynet_set_direction(dn, 1-i, delay, 0, bw, capacity, 0, + "127.0.0.1", socket_get_bound_port(peers[i])) == 0); + } +} + +/* Проверяем каждый байт и уникальность независимо от статистики эмулятора. */ +static void poll_channels(int timeout) { + test_clock_poll(ua, timeout); + for (int i = 0; i < 2; i++) { + uint8_t data[1500]; ssize_t n; + while ((n = socket_recvfrom(peers[i], data, sizeof(data), NULL, NULL)) >= 0) { + CHECK(n >= 4); + unsigned seq = data[0] | (data[1] << 8); CHECK(seq < 512 && !seen[i][seq]); + seen[i][seq] = 1; + for (ssize_t p = 2; p < n; p++) CHECK(data[p] == (uint8_t)(seq + p)); + received[i]++; bytes[i] += n; last_rx[i] = get_time_us(); + } + CHECK(socket_get_error() == ERR_WOULDBLOCK || socket_get_error() == ERR_AGAIN); + } +} + +static void send_packet(int from, unsigned seq, unsigned size) { + uint8_t data[1500]; CHECK(size >= 4 && size <= sizeof(data)); + data[0] = seq; data[1] = seq >> 8; + for (unsigned i = 2; i < size; i++) data[i] = seq + i; + CHECK(socket_sendto(peers[from], data, size, (struct sockaddr*)&proxy, sizeof(proxy)) == (ssize_t)size); + poll_channels(0); +} + +static void run_for(unsigned ms) { + uint64_t end = get_time_us() + ms * 1000ULL; + while (get_time_us() < end) poll_channels(10); +} + +static void teardown(void) { + dummynet_destroy(dn); + for (int i = 0; i < 2; i++) socket_close_wrapper(peers[i]); + uasync_poll(ua, 0); + CHECK(ua->timer_alloc_count == ua->timer_free_count); + uasync_destroy(ua, 0); +} + +static void bandwidth(unsigned bw) { + setup(bw, 20, 512); + uint64_t start = get_time_us(), total = 0; + for (unsigned seq = 0; seq < 256; seq++) { + unsigned len = seq & 1 ? 1500 : 100; + total += len; + send_packet(0, seq, len); send_packet(1, seq, len); + } + uint64_t expected = total * 8000 / bw + 20000, end = start + expected + 2000000; + while ((received[0] < 256 || received[1] < 256) && get_time_us() < end) poll_channels(10); + for (int i = 0; i < 2; i++) { + uint64_t elapsed = last_rx[i] - start; + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "BW calibration: kbps=%u dir=%d bytes=%llu elapsed_us=%llu expected_us=%llu", bw, i, + (unsigned long long)bytes[i], (unsigned long long)elapsed, (unsigned long long)expected); + CHECK(received[i] == 256 && bytes[i] == total); + CHECK(elapsed * 100 >= expected * 95 && elapsed * 100 <= expected * 105 + 5000); + const struct dummynet_stats* stats = dummynet_get_stats(dn, 1-i); + CHECK(stats->sent_bytes == total && !stats->queue_size && !stats->lost && !stats->dropped); + } + teardown(); +} + +static void hold_drop(void) { + setup(80, 20, 8); + CHECK(dummynet_set_mode(dn, 0, DUMMYNET_HOLD) == 0); + for (unsigned seq = 0; seq < 12; seq++) send_packet(0, seq, 1000); + run_for(60); + const struct dummynet_stats* stats = dummynet_get_stats(dn, 0); + CHECK(!received[1] && stats->queue_size == 8 && stats->dropped == 4); + dummynet_reset_stats(dn, 0); CHECK(stats->queue_size == 8 && stats->queue_max == 8); + uint64_t resumed = get_time_us(); + CHECK(dummynet_set_mode(dn, 0, DUMMYNET_RUN) == 0); + run_for(50); CHECK(!received[1]); + run_for(850); CHECK(received[1] == 8 && last_rx[1] - resumed >= 795000); + // DROP удаляет как delay, так и сериализуемую голову; RUN не возвращает потерянное. + send_packet(0, 20, 1000); run_for(5); + CHECK(dummynet_set_mode(dn, 0, DUMMYNET_DROP) == 0); + send_packet(0, 21, 1000); run_for(50); + CHECK(stats->blocked == 2 && !stats->queue_size && received[1] == 8); + CHECK(dummynet_set_mode(dn, 0, DUMMYNET_RUN) == 0); + send_packet(0, 22, 1000); run_for(25); + CHECK(dummynet_set_mode(dn, 0, DUMMYNET_DROP) == 0); + CHECK(stats->blocked == 3 && !stats->queue_size); + CHECK(dummynet_set_mode(dn, 0, DUMMYNET_RUN) == 0); + send_packet(0, 23, 1000); run_for(150); CHECK(received[1] == 9); + // Уничтожение одновременно с delay и shaper: после destroycallbacks не должны обращаться к dn. + send_packet(0, 24, 1000); run_for(25); send_packet(1, 25, 1000); + teardown(); + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS bounded HOLD, paced resume, DROP of pending packets and active teardown"); +} + +int main(void) { + debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); test_clock_init(10); + CHECK(socket_platform_init() == 0); + size_t baseline = u_get_allocated_count(); + bandwidth(2000); CHECK(u_get_allocated_count() == baseline); + bandwidth(8000); CHECK(u_get_allocated_count() == baseline); + hold_drop(); CHECK(u_get_allocated_count() == baseline); + test_clock_finish(); socket_platform_cleanup(); + return 0; +}