Verify inline filters with real UDP delivery and independent ETCP teardown

This commit is contained in:
evgeny committed 2026-10-24 19:57:24 +02:00
1 parent c341a9b6cc
commit 0df339c384
2 files changed
+81 -4

No files matched your search

+2 -2
View File
@@ -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;
+79 -2
View File
@@ -4,6 +4,7 @@
#include <errno.h>
#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*)&target;
*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++) {