- Renamed TOPO_BGP to TOPO_GROUP with topo_group_id (64bit) - Added TOPO_GROUPS container (ll_queue group_list) in UTUN_INSTANCE - Memory pools moved from TOPO_GROUP to TOPO_GROUPS (instance-level) - Default group: TOPO_GROUP_UTUN = 0x8000000000000000 - topo_groups_get_default() searches by group_id, not just first entry - All topo_bgp_* renamed to topo_group_*; function signatures updated - Files: topo_bgp.h/c -> topo_group.h/c
597 lines
22 KiB
C
597 lines
22 KiB
C
/**
|
|
* @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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include "test_utils.h"
|
|
#include "../src/etcp.h"
|
|
#include "../src/etcp_connections.h"
|
|
#include "../src/config_parser.h"
|
|
#include "../src/config_updater.h"
|
|
#include "../src/utun_instance.h"
|
|
#include "../src/routing.h"
|
|
#include "../src/topo_group.h"
|
|
#include "../src/tun_if.h"
|
|
#include "../src/nat_transport.h"
|
|
#include "../src/eim_nat.h"
|
|
#include "../src/etcp_api.h"
|
|
#include "../src/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
|
|
#define NODE_ID_PROVIDER 0xAAAA000000000001ULL
|
|
#define NODE_ID_CLIENT 0xBBBB000000000001ULL
|
|
#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 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"
|
|
"my_node_id=0xAAAA000000000001\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;
|
|
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"
|
|
"my_node_id=0xBBBB000000000001\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=0xAAAA000000000001\n",
|
|
pub_prov);
|
|
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 ETCP_LINK* link = inst->connections->links;
|
|
while (link) {
|
|
if (link->initialized) return link;
|
|
link = link->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 != NODE_ID_PROVIDER) {
|
|
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;
|
|
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, NODE_ID_PROVIDER, 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 != NODE_ID_CLIENT) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "src_node_id mismatch: %016llx vs %016llx",
|
|
(unsigned long long)e->src_node_id, NODE_ID_CLIENT);
|
|
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, NODE_ID_PROVIDER, 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;
|
|
}
|
|
|
|
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), NODE_ID_CLIENT) &&
|
|
topo_groups_get_default(inst_client->topo_groups) && topo_node_find_by_id(topo_groups_get_default(inst_client->topo_groups), NODE_ID_PROVIDER)) {
|
|
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;
|
|
}
|