From 0df339c38460da7c15144513db94b2f37c1e791b Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 24 Oct 2026 17:35:41 +0200 Subject: [PATCH] Verify inline filters with real UDP delivery and independent ETCP teardown --- src/transport_layer/dummynet.c | 4 +- tests/test_dummynet_channels.c | 81 +++++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/src/transport_layer/dummynet.c b/src/transport_layer/dummynet.c index 4cf6af91..2cca197d 100644 --- a/src/transport_layer/dummynet.c +++ b/src/transport_layer/dummynet.c @@ -683,8 +683,8 @@ void dummynet_filter_set_loss(struct dummynet_filter* df, uint32_t loss_permille } int dummynet_filter_attach(struct dummynet_filter* df, struct ETCP_LINK* link) { - if (!df || !link || !link->etcp || link->is_tcp || link->closing || link->etcp->close_requested || - link->etcp->instance->ua != df->ua || (link->conn && link->conn->socks_udp)) { + if (!df || !link || !link->etcp || !link->etcp->instance || !link->conn || link->is_tcp || link->closing || + link->etcp->close_requested || link->etcp->state == 2 || link->etcp->instance->ua != df->ua || link->conn->socks_udp) { DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Inline filter requires live native UDP link on its uasync link=%p", (void*)link); return -1; } if (df->link == link && link->send_hook == dummynet_filter_send_hook && link->send_hook_ctx == df) return 0; diff --git a/tests/test_dummynet_channels.c b/tests/test_dummynet_channels.c index 980d2ede..f78bdb10 100644 --- a/tests/test_dummynet_channels.c +++ b/tests/test_dummynet_channels.c @@ -4,6 +4,7 @@ #include #include "dummynet.h" #include "test_clock.h" +#include "etcp.h" #include "../lib/platform_compat.h" #include "../lib/mem.h" @@ -16,6 +17,7 @@ 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; +static socket_t fail_fd = SOCKET_INVALID; 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) { @@ -37,7 +39,7 @@ void* __wrap_u_malloc_impl(uint32_t size, const char* 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; } + if (fail_send && ((dn && fd == dummynet_get_socket(dn)) || fd == fail_fd)) { errno = EIO; return -1; } return __real_socket_sendto(fd, data, size, addr, len); } @@ -165,6 +167,10 @@ static void configuration(void) { 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); + uint64_t count = UINT64_MAX; + dummynet_get_debug_counters(NULL, -1, NULL, &count, NULL, NULL); CHECK(!count); + dummynet_get_debug_counters(dn, 0, NULL, NULL, NULL, NULL); + dummynet_reset_stats(dn, -2); CHECK(dummynet_get_stats(dn, 0)->recv == 2); teardown(); DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS atomic channel configuration, bounds, distinct peers and queued BW changes"); } @@ -209,6 +215,77 @@ static void failures(void) { DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS packet allocation, delay arg/timer, shaper timer and UDP send failures with recovery"); } +/* Проверяет фактическую доставку inline-фильтра, независимо от его счётчиков. */ +static void inline_receive(const uint8_t* expected, ssize_t length) { + uint8_t data[128]; + ssize_t n = socket_recvfrom(peers[1], data, sizeof(data), NULL, NULL); + if (length < 0) CHECK(n < 0 && (socket_get_error() == ERR_WOULDBLOCK || socket_get_error() == ERR_AGAIN)); + else CHECK(n == length && !memcmp(data, expected, (size_t)length)); +} + +/* Настоящий UDP ETCP_LINK: закрытие линка и ядра отвязывает живой фильтр, чужой hook не заменяется. */ +static void inline_filter(void) { + setup(0, 0, 16); + struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua, + "[global]\nauto_sockets=no\n[ntp]\nenabled=no\n[server:udp]\naddr=127.0.0.1:0\ntype=public\n"); + CHECK(inst && inst->etcp_sockets); + struct ETCP_CONN* connection = etcp_connection_create(inst, "inline filter test"); CHECK(connection); + struct sockaddr_storage target = {0}; + struct sockaddr_in* address = (struct sockaddr_in*)⌖ + *address = proxy; address->sin_port = htons(socket_get_bound_port(peers[1])); + struct ETCP_LINK* link = etcp_link_new(connection, inst->etcp_sockets, &target, 1); CHECK(link); + CHECK(!dummynet_filter_create(NULL)); + struct dummynet_filter* filter = dummynet_filter_create(ua); CHECK(filter); + struct dummynet_filter* second = dummynet_filter_create(ua); CHECK(second); + CHECK(!dummynet_filter_attach(filter, link) && !dummynet_filter_attach(filter, link)); + CHECK(dummynet_filter_attach(second, link) == -1 && link->send_hook_ctx == filter); + struct ETCP_LINK* tcp = etcp_link_new(connection, NULL, NULL, 0); CHECK(tcp); + CHECK(dummynet_filter_attach(filter, tcp) == -1 && link->send_hook_ctx == filter); + socket_t fd = inst->etcp_sockets->fd; + uint8_t data[64]; memset(data, 0x5a, sizeof(data)); + CHECK(etcp_udp_send(link, fd, data, sizeof(data), (struct sockaddr*)address, sizeof(*address)) == sizeof(data)); + inline_receive(data, sizeof(data)); + dummynet_filter_set_loss(filter, UINT32_MAX); + CHECK(etcp_udp_send(link, fd, data, sizeof(data), (struct sockaddr*)address, sizeof(*address)) == sizeof(data)); + inline_receive(NULL, -1); + dummynet_filter_set_loss(filter, 0); + for (unsigned i = 0; i < 4; i++) + CHECK(!dummynet_filter_block_addr(filter, htonl(INADDR_LOOPBACK + i), address->sin_port)); + CHECK(!dummynet_filter_block_addr(filter, address->sin_addr.s_addr, address->sin_port)); + CHECK(dummynet_filter_block_addr(filter, htonl(INADDR_LOOPBACK + 4), address->sin_port) == -1); + CHECK(etcp_udp_send(link, fd, data, sizeof(data), (struct sockaddr*)address, sizeof(*address)) == sizeof(data)); + inline_receive(NULL, -1); + CHECK(!dummynet_filter_unblock_addr(filter, address->sin_addr.s_addr, address->sin_port)); + CHECK(!dummynet_filter_unblock_addr(filter, address->sin_addr.s_addr, address->sin_port)); + CHECK(etcp_udp_send(link, fd, data, sizeof(data), (struct sockaddr*)address, sizeof(*address)) == sizeof(data)); + inline_receive(data, sizeof(data)); + CHECK(etcp_udp_send(link, fd, data, 0, (struct sockaddr*)address, sizeof(*address)) == 0); + inline_receive(data, 0); + fail_send = 1; fail_fd = fd; + CHECK(etcp_udp_send(link, fd, data, sizeof(data), (struct sockaddr*)address, sizeof(*address)) < 0); + fail_send = 0; fail_fd = SOCKET_INVALID; + inline_receive(NULL, -1); + CHECK(etcp_udp_send(link, fd, data, sizeof(data), (struct sockaddr*)address, sizeof(*address)) == sizeof(data)); + inline_receive(data, sizeof(data)); + const struct dummynet_stats* stats = dummynet_filter_get_stats(filter); + CHECK(stats->recv == 7 && stats->sent == 4 && stats->lost == 2 && stats->blocked == 1 && stats->dropped == 1); + CHECK(stats->recv_bytes == 6 * sizeof(data) && stats->sent_bytes == 3 * sizeof(data)); + dummynet_filter_detach(filter); + CHECK(!link->send_hook && !link->send_hook_ctx && !link->send_hook_release); + CHECK(!dummynet_filter_attach(filter, link)); + link->active_users++; /* Удержать закрытую память для проверки отказа повторного attach. */ + etcp_link_close(link); + CHECK(dummynet_filter_attach(filter, link) == -1); + etcp_link_release(link); + dummynet_filter_destroy(filter); /* link уже освобождён. */ + link = etcp_link_new(connection, inst->etcp_sockets, &target, 1); CHECK(link); + CHECK(!dummynet_filter_attach(second, link)); + utun_instance_destroy(inst); + dummynet_filter_destroy(second); /* Ядро уже освободило link. */ + teardown(); + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "PASS inline UDP loss/block/zero/send failure, exclusive hook and independent link/core lifetime"); +} + 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); @@ -220,7 +297,7 @@ int main(int argc, char** argv) { hold_drop(); CHECK(u_get_allocated_count() == baseline); } struct { const char* name; void (*run)(void); } cases[] = { - {"configuration", configuration}, {"packet_bounds", packet_bounds}, {"failures", failures} + {"configuration", configuration}, {"packet_bounds", packet_bounds}, {"failures", failures}, {"inline", inline_filter} }; unsigned selected = 0; for (unsigned i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {