diff --git a/src/nat_transport.c b/src/nat_transport.c index 5157b04c..7d0fc3cf 100644 --- a/src/nat_transport.c +++ b/src/nat_transport.c @@ -47,11 +47,11 @@ static void nat_transport_client_tun_out_cb(struct ll_queue* q, void* arg) { queue_dgram_free(pkt); queue_entry_free(pkt); queue_resume_callback(q); + // Роутер принимает владение new_entry и dgram при любом результате, включая ошибку. int ret = etcp_route_send(inst, TOPO_GROUP_UTUN, tr->nat_via_node_id, new_entry, 0, 0); if (ret != 0) { - DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT client: etcp_route_send to provider %016llx failed", - (unsigned long long)tr->nat_via_node_id); - queue_dgram_free(new_entry); queue_entry_free(new_entry); + DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT client: etcp_route_send to provider %016llx failed: error=%d ip_bytes=%zu", + (unsigned long long)tr->nat_via_node_id, ret, ip_len); } } @@ -89,11 +89,11 @@ static void nat_transport_provider_tun_out_cb(struct ll_queue* q, void* arg) { queue_dgram_free(pkt); queue_entry_free(pkt); queue_resume_callback(q); + // После передачи роутеру new_entry и dgram больше не принадлежат NAT, даже при ошибке. int send_ret = etcp_route_send(inst, TOPO_GROUP_UTUN, entry->src_node_id, new_entry, 0, 0); if (send_ret != 0) { - DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT provider: etcp_route_send back to node %016llx failed", - (unsigned long long)entry->src_node_id); - queue_dgram_free(new_entry); queue_entry_free(new_entry); + DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT provider: etcp_route_send back to node %016llx failed: error=%d ip_bytes=%zu", + (unsigned long long)entry->src_node_id, send_ret, ip_len); } } diff --git a/tests/Makefile.am b/tests/Makefile.am index 722c1ba0..9c7037d1 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -524,6 +524,9 @@ test_nat_engine_LDADD = $(LIBUTUN) $(COMMON_LIBS) test_nat_transport_SOURCES = test_nat_transport.c test_nat_transport_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib test_nat_transport_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS) +if OS_LINUX +test_nat_transport_LDFLAGS = -Wl,--wrap=etcp_route_send +endif test_nat_stress_SOURCES = test_nat_stress.c test_nat_stress_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib diff --git a/tests/test_nat_transport.c b/tests/test_nat_transport.c index 72b79925..d914f095 100644 --- a/tests/test_nat_transport.c +++ b/tests/test_nat_transport.c @@ -43,6 +43,34 @@ static struct UASYNC* ua = NULL; static int test_timed_out = 0; static void* test_timeout_id = NULL; +#ifdef __linux__ +/* Отказ с реальным освобождением в router API и успешный приём с тем же контрактом владения. */ +static int send_test_mode, send_test_calls, send_test_valid; +static struct UTUN_INSTANCE* send_test_instance; +static uint64_t send_test_destination; +static size_t send_test_ip_len; + +int __real_etcp_route_send(struct UTUN_INSTANCE*, uint64_t, uint64_t, struct ll_entry*, int, int); +int __wrap_etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t group, uint64_t destination, + struct ll_entry* entry, int force, int mode) { + if (!send_test_mode) return __real_etcp_route_send(inst, group, destination, entry, force, mode); + send_test_calls++; + if (inst != send_test_instance || group != TOPO_GROUP_UTUN || destination != send_test_destination || + !entry || !entry->dgram || entry->len != send_test_ip_len + 1 || entry->dgram[0] != ETCP_RT_ID_NAT || force || mode) { + DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Unexpected NAT send in ownership test: destination=%016llx call=%d", + (unsigned long long)destination, send_test_calls); + send_test_valid = 0; + } + if (send_test_mode == 1) { + DEBUG_INFO(DEBUG_CATEGORY_NAT, "Inject NAT router refusal: packet ownership transfers on error"); + // NULL instance вызывает отказ в реальном router API; переданный entry он освобождает сам. + return __real_etcp_route_send(NULL, group, destination, entry, force, mode); + } + queue_dgram_free(entry); queue_entry_free(entry); + return 0; +} +#endif + static char temp_dir[] = "/tmp/utun_nat_transport_XXXXXX"; static char config_provider[256]; static char config_client[256]; @@ -431,6 +459,45 @@ static int test_provider_ingress_response(void) { return 1; } +#ifdef __linux__ +static int test_send_failure_ownership(int provider) { + struct UTUN_INSTANCE* sender = provider ? inst_provider : inst_client; + struct tun_if* tun = sender->nat_tr.nat_tun; + struct eim_nat_entry saved = {0}; + uint16_t external_port = inst_provider->nat.port_start; + if (provider) { + saved = inst_provider->nat.table[external_port]; + inst_provider->nat.table[external_port] = (struct eim_nat_entry){.internal_ip = 0x0a0000cd, + .internal_port = htobe16(44444), .proto = IPPROTO_UDP_UINT8, + .state = EIM_NAT_ENTRY_ACTIVE, .src_node_id = g_client_node_id}; + } + size_t ip_len; + uint8_t* ip = provider ? build_udp_pkt(0x08080808, 80, inst_provider->nat.gateway_ip, external_port, &ip_len) : + build_udp_pkt(0x0a0000cd, 44444, 0x08080808, 80, &ip_len); + send_test_instance = sender; + send_test_destination = provider ? g_client_node_id : g_provider_node_id; + send_test_ip_len = ip_len; + send_test_calls = 0; send_test_valid = 1; + send_test_mode = 2; + // Прогреть пул entry до измерения живых блоков; исходный IP остаётся у вызывающего. + int ok = tun_inject_packet(tun, ip, ip_len) == 0 && send_test_calls == 1; + size_t baseline = u_get_allocated_count(); + send_test_mode = 1; + if (tun_inject_packet(tun, ip, ip_len) != 0 || send_test_calls != 2 || u_get_allocated_count() != baseline) ok = 0; + send_test_mode = 2; + if (tun_inject_packet(tun, ip, ip_len) != 0 || send_test_calls != 3 || u_get_allocated_count() != baseline) ok = 0; + if (!send_test_valid || queue_entry_count(tun->output_queue)) ok = 0; + send_test_mode = 0; send_test_instance = NULL; + free(ip); + if (provider) inst_provider->nat.table[external_port] = saved; + if (ok) DEBUG_INFO(DEBUG_CATEGORY_NAT, "PASS NAT %s send failure: no double free/leak, next packet accepted", + provider ? "provider" : "client"); + else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL NAT %s send failure: calls=%d valid=%d live_blocks=%zu baseline=%zu", + provider ? "provider" : "client", send_test_calls, send_test_valid, u_get_allocated_count(), baseline); + return ok; +} +#endif + static int test_full_roundtrip(void) { /* Полный путь через client socketpair → ETCP → provider TUN и обратно. */ DEBUG_INFO(DEBUG_CATEGORY_NAT, "=== test_full_roundtrip ==="); @@ -563,6 +630,11 @@ int main(void) { total++; if (test_init_destroy()) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: init_destroy"); total++; if (test_provider_egress()) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: provider_egress"); total++; if (test_provider_ingress_response()) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: provider_ingress"); +#ifdef __linux__ + total++; if (test_send_failure_ownership(0)) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: client send ownership"); + total++; if (test_send_failure_ownership(1)) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: provider send ownership"); +#endif + // Реальная доставка после инъекций проверяет восстановление обеих веток без перезапуска NAT. total++; if (test_full_roundtrip()) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: full_roundtrip"); printf("\n=== NAT Transport Tests: %d/%d passed ===\n", passed, total);