From 29d54ed20d8b9daf53b60e505c30d23964f745d6 Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 24 Oct 2026 17:30:40 +0200 Subject: [PATCH] Reproduce dummynet configuration datagram and timer failures --- tests/Makefile.am | 3 +- tests/test_dummynet_channels.c | 111 +++++++++++++++++++++++++++++++-- 2 files changed, 107 insertions(+), 7 deletions(-) diff --git a/tests/Makefile.am b/tests/Makefile.am index 25945d3d..c4a7227d 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1072,7 +1072,8 @@ test_etcp_init_liveness_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_dummynet_channels_LDFLAGS = $(test_clock_ldflags) -Wl,--wrap=uasync_set_timeout -Wl,--wrap=queue_entry_new_from_pool \ + -Wl,--wrap=u_malloc_impl -Wl,--wrap=socket_sendto test_etcp_multipath_SOURCES = test_etcp_multipath.c $(test_clock_sources) test_etcp_multipath_LDADD = $(LIBUTUN) $(COMMON_LIBS) test_etcp_multipath_LDFLAGS = $(test_clock_ldflags) -Wl,--wrap=etcp_dump_pkt_sections -Wl,--wrap=bbr_main \ diff --git a/tests/test_dummynet_channels.c b/tests/test_dummynet_channels.c index 57c6b473..980d2ede 100644 --- a/tests/test_dummynet_channels.c +++ b/tests/test_dummynet_channels.c @@ -1,6 +1,7 @@ /* Калибровка каналов: BWlim смешанных размеров, задержка, ограниченный HOLD, DROP и teardown с таймерами. */ #include #include +#include #include "dummynet.h" #include "test_clock.h" #include "../lib/platform_compat.h" @@ -14,6 +15,31 @@ static struct sockaddr_in proxy; static unsigned received[2]; static uint64_t bytes[2], last_rx[2]; static uint8_t seen[2][512]; +static int fail_delay, fail_shaper, fail_packet, fail_delay_arg, fail_send; + +void* __real_uasync_set_timeout(struct UASYNC*, int, void*, timeout_callback_t, const char*); +void* __wrap_uasync_set_timeout(struct UASYNC* loop, int delay, void* arg, timeout_callback_t fn, const char* name) { + if ((fail_delay && !strcmp(name, "dummynet_delay")) || (fail_shaper && !strcmp(name, "dummynet_shaper"))) return NULL; + return __real_uasync_set_timeout(loop, delay, arg, fn, name); +} + +struct ll_entry* __real_queue_entry_new_from_pool(struct memory_pool*); +struct ll_entry* __wrap_queue_entry_new_from_pool(struct memory_pool* pool) { + if (fail_packet) return NULL; + return __real_queue_entry_new_from_pool(pool); +} + +void* __real_u_malloc_impl(uint32_t, const char*); +void* __wrap_u_malloc_impl(uint32_t size, const char* location) { + if (fail_delay_arg && strstr(location, "dummynet.c")) return NULL; + return __real_u_malloc_impl(size, location); +} + +ssize_t __real_socket_sendto(socket_t, const void*, size_t, const struct sockaddr*, socklen_t); +ssize_t __wrap_socket_sendto(socket_t fd, const void* data, size_t size, const struct sockaddr* addr, socklen_t len) { + if (fail_send && dn && fd == dummynet_get_socket(dn)) { errno = EIO; return -1; } + return __real_socket_sendto(fd, data, size, addr, len); +} static void setup(unsigned bw, unsigned delay, unsigned capacity) { ua = uasync_create(); CHECK(ua); @@ -63,7 +89,7 @@ static void run_for(unsigned ms) { } static void teardown(void) { - dummynet_destroy(dn); + dummynet_destroy(dn); dn = NULL; 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); @@ -122,13 +148,86 @@ static void hold_drop(void) { 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); +/* Ошибка настройки не меняет действующий канал; повторное изменение BW допустимо с очередью. */ +static void configuration(void) { + setup(0, 0, 16); + uint16_t port = socket_get_bound_port(peers[1]); + CHECK(dummynet_set_direction(dn, 0, 123, 456, 1, 0, 1000, "bad address", port) == -1); + send_packet(0, 0, 100); send_packet(1, 1, 100); run_for(5); + CHECK(received[0] == 1 && received[1] == 1); + CHECK(dummynet_set_direction(dn, 0, UINT32_MAX, UINT32_MAX, 0, 16, 0, "127.0.0.1", port) == -1); + CHECK(dummynet_set_direction(dn, 0, 10000, 1, 0, 16, 0, "127.0.0.1", port) == -1); + CHECK(dummynet_set_direction(dn, 0, 0, 0, 0, UINT32_MAX, 0, "127.0.0.1", port) == -1); + CHECK(dummynet_set_direction(dn, 0, 0, 0, 0, 16, 0, "127.0.0.1", socket_get_bound_port(peers[0])) == -1); + CHECK(dummynet_set_direction(dn, 0, 0, 0, 0, 16, 0, "0.0.0.0", port) == -1); + CHECK(dummynet_set_direction(dn, 0, 20, 0, 80, 16, 0, "127.0.0.1", port) == 0); + send_packet(0, 2, 1000); run_for(25); + CHECK(dummynet_set_direction(dn, 0, 0, 0, 8000, 16, 0, "127.0.0.1", port) == 0); + CHECK(dummynet_set_direction(dn, 0, 0, 0, 0, 16, 0, "127.0.0.2", port) == -1); + run_for(150); CHECK(received[1] == 2 && !dummynet_get_stats(dn, 0)->queue_size); + teardown(); + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS atomic channel configuration, bounds, distinct peers and queued BW changes"); +} + +/* Нельзя пересылать усечённую UDP-датаграмму. Пустая датаграмма — допустимый пакет. */ +static void packet_bounds(void) { + setup(0, 0, 16); + uint8_t large[65507]; + for (size_t i = 0; i < sizeof(large); i++) large[i] = (uint8_t)i; + for (int i = 0; i < 2; i++) { + size_t size = i ? sizeof(large) : DUMMYNET_MAX_PKT_SIZE + 1; + CHECK(socket_sendto(peers[0], large, size, (struct sockaddr*)&proxy, sizeof(proxy)) == (ssize_t)size); + test_clock_run_for(ua, 50); + uint8_t reply[1600]; + CHECK(socket_recvfrom(peers[1], reply, sizeof(reply), NULL, NULL) < 0); + } + const struct dummynet_stats* stats = dummynet_get_stats(dn, 0); + CHECK(stats->recv == 2 && stats->dropped == 2 && !stats->sent && !stats->queue_size); + CHECK(stats->recv_bytes == sizeof(large) + DUMMYNET_MAX_PKT_SIZE + 1); + CHECK(socket_sendto(peers[0], large, 0, (struct sockaddr*)&proxy, sizeof(proxy)) == 0); + test_clock_run_for(ua, 50); + CHECK(socket_recvfrom(peers[1], large, sizeof(large), NULL, NULL) == 0); + CHECK(stats->recv == 3 && stats->sent == 1 && !stats->sent_bytes); + teardown(); + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS UDP boundaries: oversized packets discarded intact, zero-byte packet delivered"); +} + +/* Каждый отказ теряет один пакет и оставляет направление пригодным для следующего. */ +static void failures(void) { + setup(80, 20, 16); + int* faults[] = {&fail_packet, &fail_delay_arg, &fail_delay, &fail_shaper, &fail_send}; + for (unsigned i = 0; i < sizeof(faults) / sizeof(faults[0]); i++) { + *faults[i] = 1; + send_packet(0, i * 2, 1000); run_for(150); + *faults[i] = 0; + const struct dummynet_stats* stats = dummynet_get_stats(dn, 0); + CHECK(stats->dropped == i + 1 && !stats->queue_size && received[1] == i); + send_packet(0, i * 2 + 1, 1000); run_for(150); + CHECK(received[1] == i + 1 && !stats->queue_size); + } + teardown(); + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS packet allocation, delay arg/timer, shaper timer and UDP send failures with recovery"); +} + +int main(int argc, char** argv) { + debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_INFO); + 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); + if (argc == 1) { + bandwidth(2000); CHECK(u_get_allocated_count() == baseline); + bandwidth(8000); CHECK(u_get_allocated_count() == baseline); + hold_drop(); CHECK(u_get_allocated_count() == baseline); + } + struct { const char* name; void (*run)(void); } cases[] = { + {"configuration", configuration}, {"packet_bounds", packet_bounds}, {"failures", failures} + }; + unsigned selected = 0; + for (unsigned i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + if (argc > 1 && strcmp(argv[1], cases[i].name)) continue; + selected++; cases[i].run(); CHECK(u_get_allocated_count() == baseline); + } + CHECK(selected && argc <= 2); test_clock_finish(); socket_platform_cleanup(); return 0; }