// test_routing_mesh.c - Test routing between 3 instances in mesh topology // Each instance has 2 subnets and sends packets to 2 neighbors // Topology: A <-> B <-> C <-> A (full mesh) // Verification: packet size, destination IP, payload checksum #include #include #include #include #include #include "test_utils.h" #include "../lib/platform_compat.h" #include "../lib/u_async.h" #include "../lib/ll_queue.h" #include "../lib/debug_config.h" #include "../lib/memory_pool.h" #include "../src/utun_instance.h" #include "../src/tun_if.h" #include "../src/etcp.h" #include "../src/routing.h" #include "../src/config_parser.h" #include "../src/etcp_connections.h" #include "../src/secure_channel.h" #define TEST_TIMEOUT_CONNECT_MS 10000 // 10 seconds for connections #define TEST_TIMEOUT_DELIVERY_MS 10000 // 10 seconds for packet delivery #define TEST_PACKET_SIZE 100 // Test packet structure with IP header + payload struct test_packet { uint8_t ip_header[20]; // Minimal IP header uint8_t payload[80]; // Test payload }; // Test state static struct UASYNC* ua = NULL; static struct UTUN_INSTANCE* inst_a = NULL; static struct UTUN_INSTANCE* inst_b = NULL; static struct UTUN_INSTANCE* inst_c = NULL; // Config structures (created programmatically) static struct utun_config* cfg_a = NULL; static struct utun_config* cfg_b = NULL; static struct utun_config* cfg_c = NULL; // Test statistics static int packets_sent = 0; static int packets_received = 0; static int packets_verified = 0; static int test_phase = 0; // 0=connecting, 1=sending, 2=verifying, 3=done // Timeout IDs static void* connect_timeout_id = NULL; static void* delivery_timeout_id = NULL; // ============================================================================ // Helper Functions // ============================================================================ static void bin2hex(const uint8_t* bin, size_t len, char* hex) { const char* hex_chars = "0123456789ABCDEF"; for (size_t i = 0; i < len; i++) { hex[i * 2] = hex_chars[bin[i] >> 4]; hex[i * 2 + 1] = hex_chars[bin[i] & 0x0F]; } hex[len * 2] = '\0'; } // ============================================================================ // Programmatic Config Creation // ============================================================================ static void parse_ip_cidr(const char* cidr, struct IP* ip, uint8_t* netmask) { char buf[64]; strncpy(buf, cidr, sizeof(buf) - 1); buf[sizeof(buf) - 1] = '\0'; char* slash = strchr(buf, '/'); if (slash) { *slash = '\0'; *netmask = atoi(slash + 1); } else { *netmask = 32; } inet_pton(AF_INET, buf, &ip->addr.v4); ip->family = AF_INET; } static void parse_addr_port(const char* addr_port, struct sockaddr_storage* sa) { char buf[64]; strncpy(buf, addr_port, sizeof(buf) - 1); buf[sizeof(buf) - 1] = '\0'; char* colon = strrchr(buf, ':'); if (!colon) return; *colon = '\0'; int port = atoi(colon + 1); struct sockaddr_in* sin = (struct sockaddr_in*)sa; sin->sin_family = AF_INET; sin->sin_port = htons(port); inet_pton(AF_INET, buf, &sin->sin_addr); } // Create server config static struct CFG_SERVER* create_server(const char* name, const char* addr_port, int type) { struct CFG_SERVER* srv = calloc(1, sizeof(struct CFG_SERVER)); if (!srv) return NULL; strncpy(srv->name, name, MAX_CONN_NAME_LEN - 1); parse_addr_port(addr_port, &srv->ip); srv->type = type; srv->netif_index = 0; srv->so_mark = 0; srv->next = NULL; return srv; } // Create client config static struct CFG_CLIENT* create_client(const char* name, const char* link, const char* peer_key) { struct CFG_CLIENT* cli = calloc(1, sizeof(struct CFG_CLIENT)); if (!cli) return NULL; strncpy(cli->name, name, MAX_CONN_NAME_LEN - 1); cli->keepalive = 1; if (peer_key) { strncpy(cli->peer_public_key_hex, peer_key, MAX_KEY_LEN - 1); } // Parse link: "server_name:ip:port" char buf[256]; strncpy(buf, link, sizeof(buf) - 1); char* first_colon = strchr(buf, ':'); if (first_colon) { *first_colon = '\0'; const char* server_name = buf; const char* remote_addr = first_colon + 1; // Find server in global list (will be set later) // For now just create the link structure struct CFG_CLIENT_LINK* clink = calloc(1, sizeof(struct CFG_CLIENT_LINK)); if (clink) { parse_addr_port(remote_addr, &clink->remote_addr); // clink->local_srv will be set later when linking clink->next = NULL; cli->links = clink; } } cli->next = NULL; return cli; } // Create route entry static struct CFG_ROUTE_ENTRY* create_route(const char* cidr) { struct CFG_ROUTE_ENTRY* route = calloc(1, sizeof(struct CFG_ROUTE_ENTRY)); if (!route) return NULL; parse_ip_cidr(cidr, &route->ip, &route->netmask); route->next = NULL; return route; } // Link client to server static void link_client_to_server(struct CFG_CLIENT* client, struct CFG_SERVER* servers) { if (!client || !client->links) return; // Find server by name from link // The server name was stored in the parsing logic above // We need to find it in the servers list struct CFG_CLIENT_LINK* clink = client->links; // Simple approach: assume link name is in client name (to_b -> server b) const char* target = NULL; if (strstr(client->name, "_b")) target = "b"; else if (strstr(client->name, "_a")) target = "a"; else if (strstr(client->name, "_c")) target = "c"; if (target) { struct CFG_SERVER* srv = servers; while (srv) { if (strcmp(srv->name, target) == 0) { clink->local_srv = srv; return; } srv = srv->next; } } } // Create complete instance config static struct utun_config* create_instance_config( uint64_t node_id, const char* tun_ip, const char* tun_ifname, const char** subnets, int num_subnets, const char** servers, int num_servers, const char** clients, int num_clients ) { struct utun_config* cfg = calloc(1, sizeof(struct utun_config)); if (!cfg) return NULL; // Global config cfg->global.my_node_id = node_id; cfg->global.mtu = 1500; strncpy(cfg->global.tun_ifname, tun_ifname, 15); parse_ip_cidr(tun_ip, &cfg->global.tun_ip, &(uint8_t){32}); cfg->global.tun_test_mode = 1; // Servers struct CFG_SERVER* last_srv = NULL; for (int i = 0; i < num_servers; i++) { // Parse "name:addr:port" char buf[256]; strncpy(buf, servers[i], sizeof(buf) - 1); char* colon1 = strchr(buf, ':'); if (!colon1) continue; *colon1 = '\0'; const char* name = buf; const char* addr = colon1 + 1; struct CFG_SERVER* srv = create_server(name, addr, CFG_SERVER_TYPE_PUBLIC); if (last_srv) { last_srv->next = srv; } else { cfg->servers = srv; } last_srv = srv; } // Subnets (routing) struct CFG_ROUTE_ENTRY* last_route = NULL; for (int i = 0; i < num_subnets; i++) { struct CFG_ROUTE_ENTRY* route = create_route(subnets[i]); if (last_route) { last_route->next = route; } else { cfg->my_subnets = route; } last_route = route; } // Clients struct CFG_CLIENT* last_cli = NULL; for (int i = 0; i < num_clients; i++) { // Parse "name:link" (link format: "server:ip:port") char buf[256]; strncpy(buf, clients[i], sizeof(buf) - 1); char* colon = strchr(buf, ':'); if (!colon) continue; *colon = '\0'; const char* name = buf; const char* link = colon + 1; struct CFG_CLIENT* cli = create_client(name, link, NULL); // peer_key will be set later if (last_cli) { last_cli->next = cli; } else { cfg->clients = cli; } last_cli = cli; } // Link clients to servers struct CFG_CLIENT* cli = cfg->clients; while (cli) { link_client_to_server(cli, cfg->servers); cli = cli->next; } return cfg; } // Free config memory static void free_instance_config(struct utun_config* cfg) { if (!cfg) return; // Free servers struct CFG_SERVER* srv = cfg->servers; while (srv) { struct CFG_SERVER* next = srv->next; free(srv); srv = next; } // Free clients and their links struct CFG_CLIENT* cli = cfg->clients; while (cli) { struct CFG_CLIENT* next = cli->next; struct CFG_CLIENT_LINK* link = cli->links; while (link) { struct CFG_CLIENT_LINK* lnext = link->next; free(link); link = lnext; } free(cli); cli = next; } // Free routes struct CFG_ROUTE_ENTRY* route = cfg->my_subnets; while (route) { struct CFG_ROUTE_ENTRY* next = route->next; free(route); route = next; } free(cfg); } // ============================================================================ // Setup peer public keys after auto-generation // ============================================================================ static void setup_peer_keys(void) { printf("[SETUP] Configuring peer public keys from auto-generated keys...\n"); // Build mapping: server_name -> public_key struct { const char* name; const char* pubkey; } key_map[3]; key_map[0].name = "a"; key_map[0].pubkey = inst_a->config->global.my_public_key_hex; key_map[1].name = "b"; key_map[1].pubkey = inst_b->config->global.my_public_key_hex; key_map[2].name = "c"; key_map[2].pubkey = inst_c->config->global.my_public_key_hex; printf(" Generated keys:\n"); printf(" Instance A (a): %.20s...\n", key_map[0].pubkey); printf(" Instance B (b): %.20s...\n", key_map[1].pubkey); printf(" Instance C (c): %.20s...\n", key_map[2].pubkey); struct UTUN_INSTANCE* instances[] = {inst_a, inst_b, inst_c}; for (int i = 0; i < 3; i++) { struct UTUN_INSTANCE* inst = instances[i]; if (!inst || !inst->config) continue; struct CFG_CLIENT* client = inst->config->clients; while (client) { // Determine peer from client name (to_b -> peer b) const char* peer_name = NULL; if (strstr(client->name, "_b") || strstr(client->name, "_B")) { peer_name = "b"; } else if (strstr(client->name, "_a") || strstr(client->name, "_A")) { peer_name = "a"; } else if (strstr(client->name, "_c") || strstr(client->name, "_C")) { peer_name = "c"; } if (peer_name) { for (int j = 0; j < 3; j++) { if (strcmp(key_map[j].name, peer_name) == 0) { strncpy(client->peer_public_key_hex, key_map[j].pubkey, MAX_KEY_LEN - 1); client->peer_public_key_hex[MAX_KEY_LEN - 1] = '\0'; printf(" %s client '%s' -> peer '%s': OK\n", (i == 0 ? "A" : (i == 1 ? "B" : "C")), client->name, peer_name); break; } } } client = client->next; } } printf("[SETUP] Peer keys configured.\n"); } // ============================================================================ // Helper Functions // ============================================================================ static uint32_t calculate_crc32(const uint8_t* data, size_t len) { uint32_t crc = 0xFFFFFFFF; for (size_t i = 0; i < len; i++) { crc ^= data[i]; for (int j = 0; j < 8; j++) { crc = (crc >> 1) ^ (0xEDB88320 & -(crc & 1)); } } return ~crc; } static void build_ip_header(uint8_t* header, uint32_t src_ip, uint32_t dst_ip, uint16_t total_len) { memset(header, 0, 20); header[0] = 0x45; // Version 4, IHL 5 header[1] = 0; header[2] = (total_len >> 8) & 0xFF; header[3] = total_len & 0xFF; header[4] = 0; header[5] = 0; header[6] = 0x40; // Don't fragment header[7] = 0; header[8] = 64; // TTL header[9] = 1; // Protocol: ICMP header[10] = 0; header[11] = 0; // Source IP header[12] = (src_ip >> 24) & 0xFF; header[13] = (src_ip >> 16) & 0xFF; header[14] = (src_ip >> 8) & 0xFF; header[15] = src_ip & 0xFF; // Destination IP header[16] = (dst_ip >> 24) & 0xFF; header[17] = (dst_ip >> 16) & 0xFF; header[18] = (dst_ip >> 8) & 0xFF; header[19] = dst_ip & 0xFF; } // ============================================================================ // Connection Monitoring // ============================================================================ static int count_initialized_links(struct UTUN_INSTANCE* inst) { int count = 0; struct ETCP_CONN* conn = inst->connections; while (conn) { struct ETCP_LINK* link = conn->links; while (link) { if (link->initialized) count++; link = link->next; } conn = conn->next; } return count; } static int are_all_connections_ready(void) { int links_a = count_initialized_links(inst_a); int links_b = count_initialized_links(inst_b); int links_c = count_initialized_links(inst_c); printf("[CONNECT] Links ready: A=%d, B=%d, C=%d (need 2 each)\n", links_a, links_b, links_c); return (links_a >= 2 && links_b >= 2 && links_c >= 2); } // ============================================================================ // Packet Injection and Verification // ============================================================================ #define MAX_TEST_PACKETS 6 struct packet_record { uint64_t sender_node_id; uint64_t receiver_node_id; uint32_t src_ip; uint32_t dst_ip; uint16_t payload_len; uint32_t payload_crc; int sent; int received; int verified; }; static struct packet_record test_packets[MAX_TEST_PACKETS]; static int packet_count = 0; static void send_test_packet(struct UTUN_INSTANCE* sender, struct UTUN_INSTANCE* receiver, uint32_t src_ip, uint32_t dst_ip) { struct test_packet pkt; uint16_t total_len = 20 + TEST_PACKET_SIZE; build_ip_header(pkt.ip_header, src_ip, dst_ip, total_len); // Fill payload with pattern based on sender/receiver uint64_t pattern = sender->node_id ^ receiver->node_id; for (int i = 0; i < TEST_PACKET_SIZE; i++) { pkt.payload[i] = (uint8_t)((pattern >> (i % 8)) ^ i); } uint32_t payload_crc = calculate_crc32(pkt.payload, TEST_PACKET_SIZE); // Inject into sender's TUN output queue (simulates packet from TUN) if (tun_inject_packet(sender->tun, pkt.ip_header, total_len) == 0) { if (packet_count < MAX_TEST_PACKETS) { test_packets[packet_count].sender_node_id = sender->node_id; test_packets[packet_count].receiver_node_id = receiver->node_id; test_packets[packet_count].src_ip = src_ip; test_packets[packet_count].dst_ip = dst_ip; test_packets[packet_count].payload_len = TEST_PACKET_SIZE; test_packets[packet_count].payload_crc = payload_crc; test_packets[packet_count].sent = 1; test_packets[packet_count].received = 0; test_packets[packet_count].verified = 0; packet_count++; } packets_sent++; printf("[SEND] %llX -> %llX (dst=%d.%d.%d.%d)\n", (unsigned long long)sender->node_id, (unsigned long long)receiver->node_id, (dst_ip >> 24) & 0xFF, (dst_ip >> 16) & 0xFF, (dst_ip >> 8) & 0xFF, dst_ip & 0xFF); } } static int verify_packet(struct UTUN_INSTANCE* receiver, struct ETCP_FRAGMENT* pkt) { if (!pkt || !pkt->ll.dgram || pkt->ll.len < 20) { return -1; } // Extract destination IP uint32_t dst_ip = ((uint32_t)pkt->ll.dgram[16] << 24) | ((uint32_t)pkt->ll.dgram[17] << 16) | ((uint32_t)pkt->ll.dgram[18] << 8) | (uint32_t)pkt->ll.dgram[19]; uint16_t ip_total_len = ((uint16_t)pkt->ll.dgram[2] << 8) | pkt->ll.dgram[3]; uint16_t payload_len = ip_total_len - 20; if (payload_len > TEST_PACKET_SIZE) { return -1; } // Verify payload CRC uint32_t crc = calculate_crc32(pkt->ll.dgram + 20, payload_len); for (int i = 0; i < packet_count; i++) { if (!test_packets[i].sent || test_packets[i].received) continue; if (payload_len == test_packets[i].payload_len && crc == test_packets[i].payload_crc) { test_packets[i].received = 1; test_packets[i].verified = 1; packets_received++; packets_verified++; printf("[VERIFY] Packet verified: %llX -> %llX (size=%d)\n", (unsigned long long)test_packets[i].sender_node_id, (unsigned long long)receiver->node_id, payload_len); return 0; } } return -1; } static void check_received_packets(void) { struct UTUN_INSTANCE* instances[] = {inst_a, inst_b, inst_c}; for (int i = 0; i < 3; i++) { struct UTUN_INSTANCE* inst = instances[i]; if (!inst || !inst->tun) continue; struct ll_queue* out_q = tun_get_output_queue(inst->tun); if (!out_q) continue; struct ETCP_FRAGMENT* pkt = (struct ETCP_FRAGMENT*)queue_data_get(out_q); while (pkt) { verify_packet(inst, pkt); if (pkt->ll.dgram) free(pkt->ll.dgram); if (inst->tun && inst->tun->pool) { memory_pool_free(inst->tun->pool, pkt); } pkt = (struct ETCP_FRAGMENT*)queue_data_get(out_q); } } } // ============================================================================ // Test State Machine // ============================================================================ static void start_packet_transmission(void); static void delivery_check_callback(void* arg); static void connection_check_callback(void* arg) { (void)arg; if (test_phase != 0) return; if (are_all_connections_ready()) { printf("\n[PHASE] All connections established! Starting packet transmission...\n\n"); test_phase = 1; if (connect_timeout_id) { uasync_cancel_timeout(ua, connect_timeout_id); connect_timeout_id = NULL; } start_packet_transmission(); delivery_timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_DELIVERY_MS, NULL, delivery_check_callback); } else { connect_timeout_id = uasync_set_timeout(ua, 100, NULL, connection_check_callback); } } static void start_packet_transmission(void) { printf("[PHASE] Sending test packets (mesh topology)...\n"); // A sends to B and C send_test_packet(inst_a, inst_b, (192U << 24) | (168 << 16) | (10 << 8) | 1, (192U << 24) | (168 << 16) | (20 << 8) | 1); send_test_packet(inst_a, inst_c, (192U << 24) | (168 << 16) | (10 << 8) | 1, (192U << 24) | (168 << 16) | (30 << 8) | 1); // B sends to A and C send_test_packet(inst_b, inst_a, (192U << 24) | (168 << 16) | (20 << 8) | 1, (192U << 24) | (168 << 16) | (10 << 8) | 1); send_test_packet(inst_b, inst_c, (192U << 24) | (168 << 16) | (20 << 8) | 1, (192U << 24) | (168 << 16) | (30 << 8) | 1); // C sends to A and B send_test_packet(inst_c, inst_a, (192U << 24) | (168 << 16) | (30 << 8) | 1, (192U << 24) | (168 << 16) | (10 << 8) | 1); send_test_packet(inst_c, inst_b, (192U << 24) | (168 << 16) | (30 << 8) | 1, (192U << 24) | (168 << 16) | (20 << 8) | 1); printf("\n[PHASE] Packets sent: %d, starting verification...\n", packets_sent); test_phase = 2; } static void delivery_check_callback(void* arg) { (void)arg; check_received_packets(); if (packets_verified >= packets_sent && packets_sent > 0) { printf("\n[SUCCESS] All packets verified! (%d/%d)\n", packets_verified, packets_sent); test_phase = 3; return; } delivery_timeout_id = uasync_set_timeout(ua, 50, NULL, delivery_check_callback); } static void connect_timeout_handler(void* arg) { (void)arg; if (test_phase == 0) { printf("\n[TIMEOUT] Connection establishment failed\n"); test_phase = -1; } } // ============================================================================ // Main Test // ============================================================================ int main(void) { printf("========================================\n"); printf(" Routing Mesh Test (3 instances)\n"); printf("========================================\n\n"); // Initialize debug debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); // Create shared uasync ua = uasync_create(); if (!ua) { fprintf(stderr, "Failed to create uasync\n"); return 1; } printf("[INIT] Creating configs programmatically...\n"); // Instance A config const char* subnets_a[] = {"192.168.10.0/24", "192.168.11.0/24"}; const char* servers_a[] = {"b:127.0.0.1:9101", "c:127.0.0.1:9102"}; const char* clients_a[] = {"to_b:b:127.0.0.1:9201", "to_c:c:127.0.0.1:9301"}; cfg_a = create_instance_config(0xAAAAAAAAAAAAAAAA, "10.99.1.1/24", "tun_a", subnets_a, 2, servers_a, 2, clients_a, 2); // Instance B config const char* subnets_b[] = {"192.168.20.0/24", "192.168.21.0/24"}; const char* servers_b[] = {"a:127.0.0.1:9201", "c:127.0.0.1:9202"}; const char* clients_b[] = {"to_a:a:127.0.0.1:9101", "to_c:c:127.0.0.1:9301"}; cfg_b = create_instance_config(0xBBBBBBBBBBBBBBBB, "10.99.2.1/24", "tun_b", subnets_b, 2, servers_b, 2, clients_b, 2); // Instance C config const char* subnets_c[] = {"192.168.30.0/24", "192.168.31.0/24"}; const char* servers_c[] = {"a:127.0.0.1:9302", "b:127.0.0.1:9303"}; const char* clients_c[] = {"to_a:a:127.0.0.1:9102", "to_b:b:127.0.0.1:9202"}; cfg_c = create_instance_config(0xCCCCCCCCCCCCCCCC, "10.99.3.1/24", "tun_c", subnets_c, 2, servers_c, 2, clients_c, 2); if (!cfg_a || !cfg_b || !cfg_c) { fprintf(stderr, "Failed to create configs\n"); return 1; } printf("[INIT] Generating keys for configs...\n"); // Generate keys for each config struct SC_MYKEYS keys_a, keys_b, keys_c; if (sc_generate_keypair(&keys_a) != SC_OK || sc_generate_keypair(&keys_b) != SC_OK || sc_generate_keypair(&keys_c) != SC_OK) { fprintf(stderr, "Failed to generate key pairs\n"); return 1; } // Copy keys to configs (convert binary to hex) bin2hex(keys_a.private_key, SC_PRIVKEY_SIZE, cfg_a->global.my_private_key_hex); bin2hex(keys_a.public_key, SC_PUBKEY_SIZE, cfg_a->global.my_public_key_hex); bin2hex(keys_b.private_key, SC_PRIVKEY_SIZE, cfg_b->global.my_private_key_hex); bin2hex(keys_b.public_key, SC_PUBKEY_SIZE, cfg_b->global.my_public_key_hex); bin2hex(keys_c.private_key, SC_PRIVKEY_SIZE, cfg_c->global.my_private_key_hex); bin2hex(keys_c.public_key, SC_PUBKEY_SIZE, cfg_c->global.my_public_key_hex); printf("[INIT] Creating instances...\n"); // Create instances from configs inst_a = utun_instance_create_from_config(ua, cfg_a); inst_b = utun_instance_create_from_config(ua, cfg_b); inst_c = utun_instance_create_from_config(ua, cfg_c); if (!inst_a || !inst_b || !inst_c) { fprintf(stderr, "Failed to create instances\n"); return 1; } printf(" Instance A: node_id=%016llX\n", (unsigned long long)inst_a->node_id); printf(" Instance B: node_id=%016llX\n", (unsigned long long)inst_b->node_id); printf(" Instance C: node_id=%016llX\n", (unsigned long long)inst_c->node_id); // Setup peer keys after auto-generation setup_peer_keys(); // Initialize connections for each instance printf("\n[INIT] Initializing connections...\n"); if (init_connections(inst_a) < 0) { fprintf(stderr, "Failed to initialize A connections\n"); return 1; } if (init_connections(inst_b) < 0) { fprintf(stderr, "Failed to initialize B connections\n"); return 1; } if (init_connections(inst_c) < 0) { fprintf(stderr, "Failed to initialize C connections\n"); return 1; } printf(" A connections: OK\n"); printf(" B connections: OK\n"); printf(" C connections: OK\n"); // Set up connection monitoring printf("\n[PHASE] Waiting for connections to establish...\n"); connect_timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_CONNECT_MS, NULL, connect_timeout_handler); connection_check_callback(NULL); // Main event loop printf("\n[LOOP] Running event loop...\n"); int elapsed = 0; while (test_phase >= 0 && test_phase < 3 && elapsed < (TEST_TIMEOUT_CONNECT_MS + TEST_TIMEOUT_DELIVERY_MS + 2000)) { uasync_poll(ua, 10); elapsed += 10; } // Final verification check_received_packets(); // Cleanup printf("\n[CLEANUP] Cleaning up...\n"); if (connect_timeout_id) uasync_cancel_timeout(ua, connect_timeout_id); if (delivery_timeout_id) uasync_cancel_timeout(ua, delivery_timeout_id); if (inst_a) utun_instance_destroy(inst_a); if (inst_b) utun_instance_destroy(inst_b); if (inst_c) utun_instance_destroy(inst_c); // Note: cfg_a, cfg_b, cfg_c are freed by utun_instance_destroy uasync_destroy(ua, 0); // Results printf("\n========================================\n"); printf(" RESULTS\n"); printf("========================================\n"); printf("Packets sent: %d/%d\n", packets_sent, MAX_TEST_PACKETS); printf("Packets received: %d\n", packets_received); printf("Packets verified: %d\n", packets_verified); if (test_phase == 3 && packets_verified == MAX_TEST_PACKETS) { printf("\n✓ TEST PASSED\n"); return 0; } else { printf("\n✗ TEST FAILED (phase=%d)\n", test_phase); return 1; } }