/** * @file test_nat_transport.c * @brief Интеграционный тест NAT transport layer (nat_transport.c/h) * * Создаёт два UTUN_INSTANCE (provider + client), инициализирует NAT транспорт * и тестирует полный цикл: client → provider (egress) → provider TUN (internet) * → provider (ingress) → client (response). * * Из-за особенностей test-mode TUN (tun_write → output_queue), на клиентской * стороне для приёма response используется capture-callback, предотвращая loop. */ #include #include #include #include #include "test_utils.h" #include "etcp.h" #include "etcp_connections.h" #include "../src/config_parser.h" #include "../src/config_updater.h" #include "../src/utun_instance.h" #include "routing.h" #include "topo_group.h" #include "../src/tun_if.h" #include "../src/nat_transport.h" #include "../src/eim_nat.h" #include "etcp_api.h" #include "etcp_router.h" #include "../lib/u_async.h" #include "../lib/debug_config.h" #include "../lib/mem.h" #include "../lib/ll_queue.h" #define TEST_TIMEOUT_MS 15000 static uint64_t g_provider_node_id = 0; static uint64_t g_client_node_id = 0; #define NAT_SVC_HDR_SIZE 9 // svc_id(1) + src_node_id(8) static struct UTUN_INSTANCE* inst_provider = NULL; static struct UTUN_INSTANCE* inst_client = NULL; static struct UASYNC* ua = NULL; static int test_timed_out = 0; static void* test_timeout_id = NULL; static char temp_dir[] = "/tmp/utun_nat_transport_XXXXXX"; static char config_provider[256]; static char config_client[256]; /* Test result tracking */ static struct { int done; uint8_t captured[1500]; size_t captured_len; int capture_count; int provider_egress_entry; uint64_t egress_src_node_id; uint32_t egress_internal_ip; uint16_t egress_internal_port_net; uint16_t egress_external_port; uint8_t egress_proto; int provider_ingress_done; int client_response_done; } test_state; static int write_config(const char* path, const char* content) { FILE* f = fopen(path, "w"); if (!f) return -1; fprintf(f, "%s", content); fclose(f); return 0; } static uint64_t get_node_id_from_config(const char* path) { struct utun_config* cfg = parse_config(path); if (!cfg) return 0; uint64_t nid = cfg->global.my_node_id; free_config(cfg); return nid; } static char* get_pubkey_from_config(const char* path) { struct utun_config* cfg = parse_config(path); if (!cfg) return NULL; char* pub = strdup(cfg->global.my_public_key_hex); free_config(cfg); return pub; } static int create_temp_configs(void) { if (test_mkdtemp(temp_dir) != 0) { fprintf(stderr, "Failed to create temp directory\n"); return -1; } snprintf(config_provider, sizeof(config_provider), "%s/provider.conf", temp_dir); snprintf(config_client, sizeof(config_client), "%s/client.conf", temp_dir); const char* tpl_provider = "[global]\n" "tun_ip=10.100.0.1/24\n" "tun_ifname=tun_provider\n" "tun_test_mode=1\n" "\n" "[server:prov]\n" "addr=127.0.0.1:39101\n" "type=public\n" "\n" "[allowed_keys]\n" "allow_all=1\n" "\n" "[nat]\n" "enabled=1\n" "tun_ifname=tun_nat\n" "tun_ip=100.64.0.1/24\n" "port_start=20000\n" "port_end=20099\n"; if (write_config(config_provider, tpl_provider) != 0) return -1; if (config_ensure_keys_and_node_id(config_provider) != 0) return -1; uint64_t prov_nid = get_node_id_from_config(config_provider); char* pub_prov = get_pubkey_from_config(config_provider); if (!pub_prov) return -1; char tpl_client_full[4096]; snprintf(tpl_client_full, sizeof(tpl_client_full), "[global]\n" "tun_ip=10.200.0.1/24\n" "tun_ifname=tun_client\n" "tun_test_mode=1\n" "\n" "[server:cl]\n" "addr=127.0.0.1:39102\n" "type=public\n" "\n" "[client:to_prov]\n" "keepalive=1\n" "peer_public_key=%s\n" "link=cl:127.0.0.1:39101\n" "\n" "[nat]\n" "enabled=1\n" "tun_ifname=tun_nat_client\n" "tun_ip=100.64.1.1/24\n" "nat_via=0x%016llx\n", pub_prov, (unsigned long long)prov_nid); free(pub_prov); if (write_config(config_client, tpl_client_full) != 0) return -1; if (config_ensure_keys_and_node_id(config_client) != 0) return -1; return 0; } static void cleanup_temp_configs(void) { test_unlink(config_provider); test_unlink(config_client); test_rmdir(temp_dir); } static void test_timeout_cb(void* arg) { (void)arg; test_timed_out = 1; DEBUG_ERROR(DEBUG_CATEGORY_NAT, "test_nat_transport: timeout"); } static struct ETCP_LINK* first_initialized_link(struct UTUN_INSTANCE* inst) { if (!inst || !inst->connections) return NULL; struct ll_entry* entry = inst->connections->head; while (entry) { struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data; struct ETCP_CONN* conn = ce->conn; struct ETCP_LINK* link = conn->links; while (link) { if (link->initialized) return link; link = link->next; } entry = entry->next; } return NULL; } // ==================== Capture callback for client TUN ==================== static void capture_tun_out_cb(struct ll_queue* q, void* arg) { (void)arg; struct ll_entry* pkt = queue_data_get(q); if (pkt && pkt->dgram && pkt->len > 1) { size_t copy = pkt->len - 1; if (copy > sizeof(test_state.captured) - 1) copy = sizeof(test_state.captured) - 1; memcpy(test_state.captured, pkt->dgram + 1, copy); test_state.captured_len = copy; test_state.capture_count++; DEBUG_INFO(DEBUG_CATEGORY_NAT, "capture_tun: %zu bytes, count=%d", copy, test_state.capture_count); } queue_dgram_free(pkt); queue_entry_free(pkt); queue_resume_callback(q); } // ==================== Tests ==================== /* Build raw UDP/IP packet helper */ static uint8_t* build_udp_pkt(uint32_t src_host, uint16_t src_port_host, uint32_t dst_host, uint16_t dst_port_host, size_t* out_len) { const size_t len = 20 + 8 + 14; uint8_t* pkt = calloc(1, len); pkt[0] = 0x45; pkt[1] = 0x00; uint16_t tot = htobe16((uint16_t)len); memcpy(pkt + 2, &tot, 2); pkt[4] = 0x12; pkt[5] = 0x34; memset(pkt + 6, 0, 2); pkt[8] = 64; pkt[9] = IPPROTO_UDP_UINT8; memset(pkt + 10, 0, 2); uint32_t sn = htobe32(src_host), dn = htobe32(dst_host); memcpy(pkt + 12, &sn, 4); memcpy(pkt + 16, &dn, 4); uint16_t sp = htobe16(src_port_host), dp = htobe16(dst_port_host); memcpy(pkt + 20, &sp, 2); memcpy(pkt + 22, &dp, 2); uint16_t ul = htobe16(8 + 14); memcpy(pkt + 24, &ul, 2); memset(pkt + 26, 0, 2); memset(pkt + 28, 0xAB, 14); // Compute IP checksum uint32_t sum = 0; for (int i = 0; i < 10; i++) { uint16_t w; memcpy(&w, pkt + i*2, 2); sum += w; } sum = (sum & 0xFFFF) + (sum >> 16); sum += (sum >> 16); uint16_t cs = (uint16_t)(~sum); memcpy(pkt + 10, &cs, 2); *out_len = len; return pkt; } /* Verify IP checksum */ static int verify_ip_checksum(const uint8_t* ip) { uint32_t sum = 0; for (int i = 0; i < 10; i++) { uint16_t w; memcpy(&w, ip + i*2, 2); sum += w; } sum = (sum & 0xFFFF) + (sum >> 16); sum += (sum >> 16); return (uint16_t)(~sum) == 0; } static int test_init_destroy(void) { /* Already done in main: provider and client NAT transport initialized. Here we just verify that ctx fields are populated. */ int ok = 1; // Check provider if (!inst_provider->nat_tr.initialized) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Provider NAT transport not initialized"); ok = 0; } if (!inst_provider->nat.initialized) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Provider NAT engine not initialized"); ok = 0; } if (inst_provider->nat.gateway_ip == 0) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Provider gateway_ip = 0"); ok = 0; } if (!inst_provider->nat_tr.nat_tun) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Provider NAT TUN not created"); ok = 0; } if (inst_provider->nat_tr.nat_via_node_id != 0) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Provider nat_via_node_id should be 0"); ok = 0; } // Check client if (!inst_client->nat_tr.initialized) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Client NAT transport not initialized"); ok = 0; } if (inst_client->nat_tr.nat_via_node_id != g_provider_node_id) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Client nat_via_node_id mismatch: 0x%016llx", (unsigned long long)inst_client->nat_tr.nat_via_node_id); ok = 0; } return ok; } static int test_provider_egress(void) { /* Inject a raw IP/UDP packet directly into provider via ETCP */ DEBUG_INFO(DEBUG_CATEGORY_NAT, "=== test_provider_egress ==="); // Get the connection from client to provider (for debug info only) struct ETCP_CONN* client_conn = (inst_client->connections && inst_client->connections->head) ? ((struct conn_queue_entry*)inst_client->connections->head->data)->conn : NULL; if (!client_conn) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "No client connections"); return 0; } DEBUG_INFO(DEBUG_CATEGORY_NAT, "Client conn peer_node_id=0x%016llx", (unsigned long long)client_conn->peer_node_id); // Build ETCP_RT_ID_NAT packet via etcp_route_send (new format: svc_id + src_node_id + ip_data) size_t ip_len; uint8_t* raw_ip = build_udp_pkt(0x0A0000FE, 40000, 0x08080808, 53, &ip_len); size_t total = NAT_SVC_HDR_SIZE + ip_len; uint8_t* dgram = u_malloc(total); dgram[0] = ETCP_RT_ID_NAT; memcpy(dgram + 1, &inst_client->nat_tr.self_node_id, 8); memcpy(dgram + 9, raw_ip, ip_len); free(raw_ip); struct ll_entry* entry = queue_entry_new(0); entry->dgram = dgram; entry->len = total; int ret = etcp_route_send(inst_client, TOPO_GROUP_UTUN, g_provider_node_id, entry, 0); if (ret != 0) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "etcp_route_send failed"); queue_dgram_free(entry); queue_entry_free(entry); return 0; } DEBUG_INFO(DEBUG_CATEGORY_NAT, "ETCP_RT_ID_NAT sent from client to provider via etcp_router"); // Poll to let provider process int cycles = 0; while (cycles < 200 && !test_timed_out) { uasync_poll(ua, 5); cycles++; // Stop when we see a NAT entry if (inst_provider->nat.table[inst_provider->nat.port_start].state != EIM_NAT_ENTRY_FREE) break; } // Verify NAT table on provider struct eim_nat_entry* e = &inst_provider->nat.table[inst_provider->nat.port_start]; if (e->state != EIM_NAT_ENTRY_ACTIVE) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Provider: no NAT entry after egress (state=%d)", (int)e->state); return 0; } test_state.provider_egress_entry = 1; test_state.egress_src_node_id = e->src_node_id; test_state.egress_internal_ip = e->internal_ip; test_state.egress_internal_port_net = e->internal_port; test_state.egress_external_port = inst_provider->nat.port_start; test_state.egress_proto = e->proto; DEBUG_INFO(DEBUG_CATEGORY_NAT, "Provider egress: internal=%08x:%u proto=%u ext_port=%u node=%016llx", e->internal_ip, be16toh(e->internal_port), e->proto, inst_provider->nat.port_start, (unsigned long long)e->src_node_id); if (e->src_node_id != g_client_node_id) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "src_node_id mismatch: %016llx vs %016llx", (unsigned long long)e->src_node_id, g_client_node_id); return 0; } if (e->internal_ip != 0x0A0000FE) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "internal_ip mismatch: %08x", e->internal_ip); return 0; } if (e->internal_port != htobe16(40000)) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "internal_port mismatch: %u vs 40000", be16toh(e->internal_port)); return 0; } if (e->proto != IPPROTO_UDP_UINT8) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "proto mismatch: %u", e->proto); return 0; } return 1; } static int test_provider_ingress_response(void) { /* Inject internet response into provider's TUN output_queue. The provider's TUN output callback will do ingress NAT and send to client. */ DEBUG_INFO(DEBUG_CATEGORY_NAT, "=== test_provider_ingress_response ==="); if (!test_state.provider_egress_entry) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Skip: no egress entry from previous test"); return 0; } // Use actual gateway IP from provider's NAT engine uint32_t gw_ip_host = inst_provider->nat.gateway_ip; size_t ip_len; uint8_t* resp_ip = build_udp_pkt(0x08080808, 53, gw_ip_host, test_state.egress_external_port, &ip_len); // Inject into provider's NAT TUN output_queue (simulating internet response) struct tun_if* tun = inst_provider->nat_tr.nat_tun; if (!tun) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "No NAT TUN"); free(resp_ip); return 0; } // Before injecting, set capture callback on client's TUN to prevent loop struct tun_if* client_tun = inst_client->nat_tr.nat_tun; if (client_tun && client_tun->output_queue) { queue_set_callback(client_tun->output_queue, capture_tun_out_cb, NULL); DEBUG_INFO(DEBUG_CATEGORY_NAT, "Client TUN callback set to capture"); } memset(&test_state.captured, 0, sizeof(test_state.captured)); test_state.captured_len = 0; test_state.capture_count = 0; int r = tun_inject_packet(tun, resp_ip, ip_len); free(resp_ip); if (r != 0) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "tun_inject_packet failed"); return 0; } DEBUG_INFO(DEBUG_CATEGORY_NAT, "Injected response into provider TUN output_queue"); // Poll extensively to let the full chain complete int cycles = 0; while (cycles < 500 && !test_timed_out && test_state.capture_count == 0) { uasync_poll(ua, 10); cycles++; } DEBUG_INFO(DEBUG_CATEGORY_NAT, "After poll: capture_count=%d, captured_len=%zu", test_state.capture_count, test_state.captured_len); if (test_state.capture_count == 0) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "No response captured on client after %d cycles", cycles); return 0; } // Restore original callback if (client_tun && client_tun->output_queue) { queue_set_callback(client_tun->output_queue, NULL, NULL); } // Verify the captured response if (test_state.captured_len < 20) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Captured packet too short: %zu", test_state.captured_len); return 0; } if (!verify_ip_checksum(test_state.captured)) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Captured IP checksum invalid"); return 0; } // Check dst IP = original internal IP uint32_t dst_net; memcpy(&dst_net, test_state.captured + 16, 4); uint32_t expected_dst = htobe32(0x0A0000FE); // 10.0.0.254 if (dst_net != expected_dst) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Response dst IP: %08x, expected %08x", be32toh(dst_net), be32toh(expected_dst)); return 0; } // Check dst port = original internal port uint16_t dst_port_net; memcpy(&dst_port_net, test_state.captured + 22, 2); if (dst_port_net != htobe16(40000)) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Response dst port: %u, expected 40000", be16toh(dst_port_net)); return 0; } test_state.client_response_done = 1; return 1; } static int test_full_roundtrip(void) { /* Combined egress + ingress via manual injection. Cannot do auto-roundtrip because tun_write in test mode puts to output_queue instead of real TUN, causing egressed packets to loop back. */ DEBUG_INFO(DEBUG_CATEGORY_NAT, "=== test_full_roundtrip ==="); struct tun_if* client_tun = inst_client->nat_tr.nat_tun; if (!client_tun) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "No client TUN"); return 0; } // Clean NAT table from previous test for (uint16_t p = inst_provider->nat.port_start; p <= inst_provider->nat.port_end; p++) memset(&inst_provider->nat.table[p], 0, sizeof(struct eim_nat_entry)); inst_provider->nat.next_port = inst_provider->nat.port_start; // === Step 1: Send ETCP_RT_ID_NAT from client to provider via etcp_router (egress) === size_t ip_len; uint8_t* raw_ip = build_udp_pkt(0x0A0000CD, 44444, 0x08080808, 80, &ip_len); size_t total = NAT_SVC_HDR_SIZE + ip_len; uint8_t* dgram = u_malloc(total); dgram[0] = ETCP_RT_ID_NAT; memcpy(dgram + 1, &inst_client->nat_tr.self_node_id, 8); memcpy(dgram + 9, raw_ip, ip_len); free(raw_ip); struct ll_entry* entry = queue_entry_new(0); entry->dgram = dgram; entry->len = total; int ret = etcp_route_send(inst_client, TOPO_GROUP_UTUN, g_provider_node_id, entry, 0); if (ret != 0) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "etcp_route_send failed"); queue_dgram_free(entry); queue_entry_free(entry); return 0; } // Poll for provider to process egress int cycles = 0; while (cycles < 200 && !test_timed_out) { uasync_poll(ua, 5); cycles++; if (inst_provider->nat.table[inst_provider->nat.port_start].state != EIM_NAT_ENTRY_FREE) break; } // Verify NAT entry struct eim_nat_entry* e = &inst_provider->nat.table[inst_provider->nat.port_start]; if (e->state != EIM_NAT_ENTRY_ACTIVE || e->internal_ip != 0x0A0000CD || e->internal_port != htobe16(44444)) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Egress entry not found: ip=%08x port=%u state=%d", e->internal_ip, be16toh(e->internal_port), e->state); return 0; } DEBUG_INFO(DEBUG_CATEGORY_NAT, "Egress entry ok: %08x:%u ext=%u proto=%u", e->internal_ip, be16toh(e->internal_port), inst_provider->nat.port_start, e->proto); // === Step 2: Manually inject internet response into provider's TUN === if (client_tun->output_queue) queue_set_callback(client_tun->output_queue, capture_tun_out_cb, NULL); memset(&test_state.captured, 0, sizeof(test_state.captured)); test_state.captured_len = 0; test_state.capture_count = 0; uint32_t gw_ip_host = inst_provider->nat.gateway_ip; size_t rip_len; uint8_t* resp_ip = build_udp_pkt(0x08080808, 80, gw_ip_host, inst_provider->nat.port_start, &rip_len); if (tun_inject_packet(inst_provider->nat_tr.nat_tun, resp_ip, rip_len) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "tun_inject response failed"); free(resp_ip); return 0; } free(resp_ip); cycles = 0; while (cycles < 500 && !test_timed_out && test_state.capture_count == 0) { uasync_poll(ua, 10); cycles++; } if (client_tun->output_queue) queue_set_callback(client_tun->output_queue, NULL, NULL); if (test_state.capture_count == 0) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "No response in roundtrip"); return 0; } // Verify captured response if (!verify_ip_checksum(test_state.captured)) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Captured IP checksum invalid"); return 0; } uint32_t dst_net; memcpy(&dst_net, test_state.captured + 16, 4); if (dst_net != htobe32(0x0A0000CD)) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Roundtrip dst IP mismatch"); return 0; } uint16_t dp_net; memcpy(&dp_net, test_state.captured + 22, 2); if (dp_net != htobe16(44444)) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Roundtrip dst port mismatch"); return 0; } return 1; } // ==================== Main ==================== int main(void) { int test_result = 1; debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); debug_set_categories(DEBUG_CATEGORY_NAT | DEBUG_CATEGORY_ETCP | DEBUG_CATEGORY_BGP | DEBUG_CATEGORY_ROUTING); utun_instance_set_tun_init_enabled(0); if (create_temp_configs() != 0) { fprintf(stderr, "Failed to create temp configs\n"); return 1; } ua = uasync_create(); if (!ua) { cleanup_temp_configs(); return 1; } inst_provider = utun_instance_create(ua, config_provider); inst_client = utun_instance_create(ua, config_client); if (!inst_provider || !inst_client) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Failed to create instances"); goto cleanup; } if (utun_instance_init(inst_provider) != 0 || utun_instance_init(inst_client) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Failed to init instances"); goto cleanup; } g_provider_node_id = inst_provider->node_id; g_client_node_id = inst_client->node_id; test_timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS, NULL, test_timeout_cb, "test_nat_transport"); // Wait for ETCP link between client and provider DEBUG_INFO(DEBUG_CATEGORY_NAT, "Waiting for ETCP link..."); while (!test_timed_out) { if (first_initialized_link(inst_client)) { DEBUG_INFO(DEBUG_CATEGORY_NAT, "Link initialized"); break; } uasync_poll(ua, 10); } if (test_timed_out) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Link timeout"); goto cleanup; } // Wait for BGP exchange (provider learns about client) DEBUG_INFO(DEBUG_CATEGORY_NAT, "Waiting for BGP..."); int bgp_cycles = 0; while (!test_timed_out && bgp_cycles < 500) { if (topo_groups_get_default(inst_provider->topo_groups) && topo_node_find_by_id(topo_groups_get_default(inst_provider->topo_groups), g_client_node_id) && topo_groups_get_default(inst_client->topo_groups) && topo_node_find_by_id(topo_groups_get_default(inst_client->topo_groups), g_provider_node_id)) { DEBUG_INFO(DEBUG_CATEGORY_NAT, "BGP exchanged"); break; } uasync_poll(ua, 10); bgp_cycles++; } if (test_timed_out) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "BGP timeout"); goto cleanup; } // Run tests int passed = 0, total = 0; 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"); 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); test_result = (passed == total) ? 0 : 1; cleanup: if (test_timeout_id) uasync_cancel_timeout(ua, test_timeout_id); if (inst_provider) utun_instance_destroy(inst_provider); if (inst_client) utun_instance_destroy(inst_client); if (ua) uasync_destroy(ua, 0); cleanup_temp_configs(); return test_result; }