From 55cc721c9b86f0f965ea71121e50749739ddf792 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 21 Oct 2026 22:54:24 +0200 Subject: [PATCH] Test held NAT mappings under TUN congestion and reject invalid provider input --- tests/test_nat_transport.c | 82 +++++++++++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 11 deletions(-) diff --git a/tests/test_nat_transport.c b/tests/test_nat_transport.c index 863bfc26..b6410e02 100644 --- a/tests/test_nat_transport.c +++ b/tests/test_nat_transport.c @@ -673,37 +673,47 @@ static int test_router_backpressure(int provider) { #ifdef __linux__ /* EAGAIN удерживает первый IP-пакет; второй остаётся в router recv_q. После возобновления оба доставляются по порядку. */ -static int test_tun_write_backpressure(void) { - write_blocked_tun = inst_client->nat_tr.nat_tun; +static int test_tun_write_backpressure(int provider) { + struct UTUN_INSTANCE* receiver = provider ? inst_provider : inst_client; + struct UTUN_INSTANCE* sender = provider ? inst_client : inst_provider; + write_blocked_tun = receiver->nat_tr.nat_tun; for (uint32_t i = 1; i <= 2; i++) { size_t len; - uint8_t* ip = build_udp_pkt(0x08080808, 53, 0x0a0000cd, 44444, &len); + uint8_t* ip = provider ? build_udp_pkt(0x0a0000ab, 40101, 0x08080808, 44444, &len) : + build_udp_pkt(0x08080808, 53, 0x0a0000cd, 44444, &len); memcpy(ip + 28, &i, 4); struct ll_entry* packet = ll_alloc_lldgram(len + 1); if (!packet) { free(ip); write_blocked_tun = NULL; return 0; } packet->len = len + 1; packet->dgram[0] = ETCP_RT_ID_NAT; memcpy(packet->dgram + 1, ip, len); free(ip); - if (etcp_route_send(inst_provider, TOPO_GROUP_UTUN, g_client_node_id, packet, 0, 0)) { write_blocked_tun = NULL; return 0; } + if (etcp_route_send(sender, TOPO_GROUP_UTUN, receiver->node_id, packet, 0, 0)) { write_blocked_tun = NULL; return 0; } } - struct ETCP_ROUTER_CONN* incoming = etcp_router_conn_get(inst_client, TOPO_GROUP_UTUN, g_provider_node_id, ETCP_RT_ID_NAT); + struct ETCP_ROUTER_CONN* incoming = etcp_router_conn_get(receiver, TOPO_GROUP_UTUN, sender->node_id, ETCP_RT_ID_NAT); for (int cycles = 0; cycles < 500 && !test_timed_out && !incoming->receive_blocked; cycles++) uasync_poll(ua, 5); - int ok = inst_client->nat_tr.tun_pending && incoming->receive_blocked; + int ok = receiver->nat_tr.tun_pending && incoming->receive_blocked; + if (provider && receiver->nat_tr.tun_pending_port) { + uint16_t port = receiver->nat_tr.tun_pending_port; + uint64_t deadline = receiver->nat.table[port].last_seen + EIM_NAT_UDP_IDLE_TB; + eim_nat_expire(&receiver->nat, deadline, 65535); + if (!receiver->nat.table[port].holds || receiver->nat.table[port].state != EIM_NAT_ENTRY_ACTIVE) ok = 0; + } else if (provider) ok = 0; write_blocked_tun = NULL; uint32_t received = 0; for (int cycles = 0; cycles < 500 && !test_timed_out && received < 2; cycles++) { uasync_poll(ua, 5); uint8_t data[1500]; ssize_t n; - while ((n = tun_read_packet(inst_client->nat_tr.nat_tun, data, sizeof(data))) > 0) { + while ((n = tun_read_packet(receiver->nat_tr.nat_tun, data, sizeof(data))) > 0) { uint32_t serial = 0; if (n >= 32) memcpy(&serial, data + 28, 4); if (n != 42 || serial != received + 1) ok = 0; received++; } } - if (received != 2 || inst_client->nat_tr.tun_pending || incoming->receive_blocked) ok = 0; - if (ok) DEBUG_INFO(DEBUG_CATEGORY_NAT, "PASS TUN write backpressure: retained packet, router pause and ordered resume"); + if (received != 2 || receiver->nat_tr.tun_pending || receiver->nat_tr.tun_pending_port || incoming->receive_blocked) ok = 0; + if (ok) DEBUG_INFO(DEBUG_CATEGORY_NAT, "PASS %s TUN write backpressure: retained packet, held mapping, router pause and ordered resume", + provider ? "provider" : "client"); else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL TUN write backpressure: received=%u pending=%p blocked=%u", received, - (void*)inst_client->nat_tr.tun_pending, incoming->receive_blocked); + (void*)receiver->nat_tr.tun_pending, incoming->receive_blocked); return ok; } #endif @@ -772,6 +782,51 @@ static int test_global_backpressure(void) { return ok; } +#ifdef __linux__ +/* Stop обязан отменить retry и освободить удержанный mapping до уничтожения TUN/таблицы. */ +static int test_shutdown_pending_write(void) { + if (nat_transport_init(inst_provider)) return 0; + write_blocked_tun = inst_provider->nat_tr.nat_tun; + size_t len; + uint8_t* ip = build_udp_pkt(0x0a000002, 40000, 0x08080808, 53, &len); + struct ll_entry* packet = ll_alloc_lldgram(len + 1); + if (!packet) { free(ip); write_blocked_tun = NULL; return 0; } + packet->len = len + 1; packet->dgram[0] = ETCP_RT_ID_NAT; memcpy(packet->dgram + 1, ip, len); free(ip); + int result = etcp_route_send(inst_provider, TOPO_GROUP_UTUN, inst_provider->node_id, packet, 0, 0); + int ok = !result && inst_provider->nat_tr.tun_pending && inst_provider->nat_tr.tun_pending_port; + nat_transport_destroy(inst_provider); write_blocked_tun = NULL; + uasync_poll(ua, 0); + if (inst_provider->nat_tr.tun_pending || inst_provider->nat_tr.tun_retry_timer || inst_provider->nat.table) ok = 0; + if (ok) DEBUG_INFO(DEBUG_CATEGORY_NAT, "PASS shutdown with held outgoing mapping and active TUN retry"); + else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL shutdown with pending TUN write"); + return ok; +} +#endif + +/* Ни повреждённый IPv4, ни фрагмент не попадает в TUN провайдера и не расходует порты. */ +static int test_provider_rejects_invalid(void) { + uint8_t drain[1500]; + while (tun_read_packet(inst_provider->nat_tr.nat_tun, drain, sizeof(drain)) > 0) {} + uint32_t free_before = inst_provider->nat.free_count; + uint64_t dropped_before = inst_provider->nat_tr.dropped; + for (unsigned int i = 0; i < 3; i++) { + size_t len; + uint8_t* ip = build_udp_pkt(0x0a000002, 40000, 0x08080808, 53, &len); + if (i == 0) ip[0] = 0x65; + if (i == 1) { uint16_t total = htobe16(100); memcpy(ip + 2, &total, 2); } + if (i == 2) ip[6] = 0x20; + struct ll_entry* packet = ll_alloc_lldgram(len + 1); + if (!packet) { free(ip); return 0; } + packet->len = len + 1; packet->dgram[0] = ETCP_RT_ID_NAT; memcpy(packet->dgram + 1, ip, len); free(ip); + if (etcp_route_send(inst_provider, TOPO_GROUP_UTUN, inst_provider->node_id, packet, 0, 0)) return 0; + } + int ok = tun_read_packet(inst_provider->nat_tr.nat_tun, drain, sizeof(drain)) == 0 && + inst_provider->nat.free_count == free_before && inst_provider->nat_tr.dropped == dropped_before + 3; + if (ok) DEBUG_INFO(DEBUG_CATEGORY_NAT, "PASS invalid/fragmented IP rejected before TUN delivery"); + else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL invalid provider input"); + return ok; +} + // ==================== Main ==================== int main(void) { int test_result = 1; @@ -845,13 +900,18 @@ int main(void) { // Реальная доставка после инъекций проверяет восстановление обеих веток без перезапуска NAT. total++; if (test_full_roundtrip()) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: full_roundtrip"); total++; if (test_static_forward_delivery()) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: static_forward"); + total++; if (test_provider_rejects_invalid()) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: provider_invalid"); total++; if (test_router_backpressure(0)) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: client_backpressure"); total++; if (test_router_backpressure(1)) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: provider_backpressure"); #ifdef __linux__ - total++; if (test_tun_write_backpressure()) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: tun_write_backpressure"); + total++; if (test_tun_write_backpressure(0)) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: client_tun_backpressure"); + total++; if (test_tun_write_backpressure(1)) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: provider_tun_backpressure"); #endif total++; if (test_slow_peer_and_shutdown()) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: slow_peer_shutdown"); total++; if (test_global_backpressure()) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: global_backpressure"); +#ifdef __linux__ + total++; if (test_shutdown_pending_write()) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: shutdown_pending_write"); +#endif printf("\n=== NAT Transport Tests: %d/%d passed ===\n", passed, total); test_result = (passed == total) ? 0 : 1;