You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

651 lines
24 KiB

// test_etcp_router.c — Integration test for etcp_router with dummynet congestion
// 2 ноды в одном UASYNC, dummynet-прокси между ними.
// Фазы: baseline (без congestion) + congestion cycles (fill→push→drain→verify)
// Проверка: bitmap (нет потерь/дубликатов), per-packet payload integrity, порядок.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <inttypes.h>
#include "../lib/platform_compat.h"
#include "../lib/socket_compat.h"
#include "test_utils.h"
#ifdef _WIN32
#include <windows.h>
#include <direct.h>
#else
#include <unistd.h>
#endif
#include "../src/etcp.h"
#include "../src/etcp_connections.h"
#include "../src/etcp_api.h"
#include "../src/etcp_router.h"
#include "../src/dummynet.h"
#include "../src/config_parser.h"
#include "../src/utun_instance.h"
#include "../src/routing.h"
#include "../src/tun_if.h"
#include "../src/secure_channel.h"
#include "../src/pkt_normalizer.h"
#include "../src/topo_group.h"
#include "../lib/u_async.h"
#include "../lib/ll_queue.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
// ======================== Configuration ========================
#define TEST_SVC_ID 0x01
#define BASELINE_PACKETS 20
#define PACKETS_PER_CYCLE 20
#define CONGESTION_CYCLES 5
#define BITMAP_SIZE 2000 // с запасом (baseline + cycles * (fill+push))
#define LOW_BW_KBPS 80 // ~55 pkt/s @ ~180 bytes — медленнее чем можем слать
#define DUMMY_QUEUE_SIZE 300 // чтобы dummynet не дропал
#define MAX_PAYLOAD 200
#define CONNECT_TIMEOUT_MS 10000
#define TEST_TIMEOUT_MS 120000
#define DRAIN_TIMEOUT_MS 30000 // per-cycle drain timeout
// ======================== Globals ========================
static char temp_dir[] = "/tmp/utun_test_XXXXXX";
static char server_conf[512], client_conf[512];
static struct UTUN_INSTANCE* srv = NULL;
static struct UTUN_INSTANCE* cli = NULL;
static struct UASYNC* ua = NULL;
static struct dummynet* dn = NULL;
static int g_dn_port = 0, g_srv_port = 0, g_cli_port = 0;
static uint64_t server_node_id = 0;
static uint64_t client_node_id = 0;
// Bitmap: 0=not received, 1=received
static uint8_t rcvd_bitmap[BITMAP_SIZE];
// Corruption/dup tracking
static int g_fail = 0; // 0=ok, 1=corrupt, 2=dup, 3=out_of_range, 4=seq_mismatch
static uint32_t g_fail_seq = 0;
static uint32_t g_fail_detail = 0;
// Counters
static uint32_t g_total_sent = 0; // total successfully sent (etcp_route_send==0)
static uint32_t g_total_rcvd = 0; // total received by server handler
static uint32_t g_total_drop = 0; // total etcp_route_send returned -1
// Expected next seq for in-order delivery check
static uint32_t g_expected_seq = 0;
// State machine
enum {
ST_WAIT_CONN,
ST_BASELINE_SEND,
ST_BASELINE_WAIT,
ST_CYCLE_FILL,
ST_CYCLE_PUSH,
ST_CYCLE_DRAIN,
ST_FINAL
};
static int g_state = ST_WAIT_CONN;
static int g_cycle = 0;
static uint32_t g_cycle_start_seq = 0;
static uint32_t g_cycle_sent = 0; // packets sent in current cycle (fill+push)
static uint32_t g_cycle_push_ok = 0; // successful pushes in PUSH phase
static int g_backpressure_seen = 0; // saw etcp_route_send=-1 in FILL
// Timing
static struct timespec t_phase_start;
static double t_connect_ms = 0, t_baseline_ms = 0;
static double t_cycle_fill_ms[CONGESTION_CYCLES];
static double t_cycle_push_ms[CONGESTION_CYCLES];
static double t_cycle_drain_ms[CONGESTION_CYCLES];
static uint32_t g_cycle_fill_count[CONGESTION_CYCLES];
static uint32_t g_cycle_drop_count[CONGESTION_CYCLES];
// Monitor
static void* g_mon_id = NULL;
static int g_done = 0; // 0=running, 1=pass, -1=fail
// ======================== Timing helpers ========================
static void tic(struct timespec* t) {
clock_gettime(CLOCK_MONOTONIC, t);
}
static double toc_ms(const struct timespec* start) {
struct timespec end;
clock_gettime(CLOCK_MONOTONIC, &end);
return (end.tv_sec - start->tv_sec) * 1000.0 + (end.tv_nsec - start->tv_nsec) / 1000000.0;
}
// ======================== Port allocation ========================
static int alloc_consecutive_ports(int* base_port) {
for (int attempt = 0; attempt < 200; attempt++) {
socket_t s = socket_create_udp(AF_INET);
if (s == SOCKET_INVALID) return -1;
struct sockaddr_in a;
memset(&a, 0, sizeof(a));
a.sin_family = AF_INET;
a.sin_addr.s_addr = inet_addr("127.0.0.1");
a.sin_port = 0; // auto-assign
if (bind(s, (struct sockaddr*)&a, sizeof(a)) != 0) { socket_close_wrapper(s); continue; }
socklen_t alen = sizeof(a);
getsockname(s, (struct sockaddr*)&a, &alen);
int port = ntohs(a.sin_port);
socket_close_wrapper(s);
// Need port-1, port, port+1 all free
int ok = 1;
for (int p = port - 1; p <= port + 1 && ok; p++) {
if (p < 1024) { ok = 0; break; }
socket_t ts = socket_create_udp(AF_INET);
if (ts == SOCKET_INVALID) { ok = 0; break; }
struct sockaddr_in ta;
memset(&ta, 0, sizeof(ta));
ta.sin_family = AF_INET;
ta.sin_addr.s_addr = inet_addr("127.0.0.1");
ta.sin_port = htons((uint16_t)p);
if (bind(ts, (struct sockaddr*)&ta, sizeof(ta)) != 0) ok = 0;
socket_close_wrapper(ts);
}
if (ok) { *base_port = port; return 0; }
}
return -1;
}
// ======================== Config generation ========================
static const char* srv_priv = "38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68";
static const char* srv_pub = "ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a";
static const char* cli_priv = "704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f";
static const char* cli_pub = "b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01";
static void write_configs(void) {
// Server: listens on g_srv_port
snprintf(server_conf, sizeof(server_conf), "%s/server.conf", temp_dir);
FILE* f = fopen(server_conf, "w");
if (!f) { fprintf(stderr, "fopen server fail\n"); exit(1); }
fprintf(f,
"[global]\n"
"my_private_key=%s\n"
"my_public_key=%s\n"
"tun_ip=10.99.0.1/24\n"
"tun_ifname=tun99\n"
"keepalive_timeout=60000\n"
"keepalive_adaptive=0\n"
"[server: s1]\n"
"addr=127.0.0.1:%d\n"
"type=public\n"
"[allowed_keys]\n"
"allow_all=1\n",
srv_priv, srv_pub, g_srv_port);
fclose(f);
// Client: listens on g_cli_port, connects to dummynet on g_dn_port
snprintf(client_conf, sizeof(client_conf), "%s/client.conf", temp_dir);
f = fopen(client_conf, "w");
if (!f) { fprintf(stderr, "fopen client fail\n"); exit(1); }
fprintf(f,
"[global]\n"
"my_private_key=%s\n"
"my_public_key=%s\n"
"tun_ip=10.99.0.2/24\n"
"tun_ifname=tun98\n"
"keepalive_timeout=60000\n"
"keepalive_adaptive=0\n"
"[server: s1]\n"
"addr=127.0.0.1:%d\n"
"type=public\n"
"[client: c1]\n"
"keepalive=1\n"
"peer_public_key=%s\n"
"link=s1:127.0.0.1:%d\n",
cli_priv, cli_pub, g_cli_port, srv_pub, g_dn_port);
fclose(f);
}
// ======================== Helpers ========================
static int conn_established(struct UTUN_INSTANCE* inst) {
if (!inst || !inst->connections) return 0;
struct ll_entry* entry = inst->connections->head;
while (entry) {
struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data;
struct ETCP_CONN* c = ce->conn;
for (struct ETCP_LINK* l = c->links; l; l = l->next) {
if (l->initialized && c->crypto_ctx.initialized) {
int ok = 0;
for (int i = 0; i < SC_SESSION_KEY_SIZE; i++)
if (c->crypto_ctx.session_key[i] != 0) { ok = 1; break; }
if (ok) return 1;
}
}
entry = entry->next;
}
return 0;
}
static void gen_payload(uint32_t seq, uint8_t* buf, int len) {
for (int i = 0; i < len; i++)
buf[i] = (uint8_t)((i ^ seq ^ 0xAA) & 0xFF);
}
// Build and send one router packet. Returns 0 on success, -1 on failure.
static int send_one_pkt(uint32_t seq, int data_len) {
if (data_len > MAX_PAYLOAD) data_len = MAX_PAYLOAD;
uint8_t buf[10 + MAX_PAYLOAD];
buf[0] = TEST_SVC_ID;
buf[1] = 0x01; // DATA subcmd
memcpy(buf + 2, &seq, 4);
memcpy(buf + 6, &data_len, 4);
gen_payload(seq, buf + 10, data_len);
struct ll_entry* e = queue_entry_new(0);
if (!e) return -1;
e->dgram = u_malloc(10 + data_len);
if (!e->dgram) { queue_entry_free(e); return -1; }
memcpy(e->dgram, buf, 10 + data_len);
e->len = 10 + data_len;
return etcp_route_send(cli, server_node_id, e, 0);
}
// ======================== Server handler ========================
static void srv_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
// Restart notification: conn=NULL, len=9, format [svc_id:1][node_id:8]
if (!conn && entry && entry->dgram && entry->len == 9) {
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "srv_handler: restart notification, resetting expected_seq from %u to 0", g_expected_seq);
g_expected_seq = 0;
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
if (!entry || !entry->dgram || entry->len < 10) {
printf("[FAIL] srv_handler: bad entry len=%zu conn=%p\n", entry ? entry->len : 0, (void*)conn);
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
g_fail = 4; g_done = -1;
return;
}
uint8_t subcmd = entry->dgram[1];
uint32_t seq = 0; memcpy(&seq, entry->dgram + 2, 4);
uint32_t data_len = 0; memcpy(&data_len, entry->dgram + 6, 4);
uint8_t* payload = entry->dgram + 10;
if (subcmd != 0x01) {
printf("[FAIL] srv_handler: unexpected subcmd=%u seq=%u\n", subcmd, seq);
queue_dgram_free(entry); queue_entry_free(entry);
g_fail = 4; g_done = -1;
return;
}
// Check seq in range
if (seq >= BITMAP_SIZE) {
printf("[FAIL] srv_handler: seq=%u out of bitmap range\n", seq);
queue_dgram_free(entry); queue_entry_free(entry);
g_fail = 3; g_fail_seq = seq; g_done = -1;
return;
}
// Check in-order delivery (router should guarantee this)
if (seq != g_expected_seq) {
printf("[FAIL] srv_handler: seq mismatch expected=%u got=%u (loss or reorder)\n",
g_expected_seq, seq);
queue_dgram_free(entry); queue_entry_free(entry);
g_fail = 4; g_fail_seq = seq; g_fail_detail = g_expected_seq;
g_done = -1;
return;
}
// Check payload integrity
if (data_len > 0 && data_len <= MAX_PAYLOAD) {
uint8_t expected[MAX_PAYLOAD];
gen_payload(seq, expected, data_len);
if (memcmp(payload, expected, data_len) != 0) {
printf("[FAIL] srv_handler: data corrupt at seq=%u\n", seq);
queue_dgram_free(entry); queue_entry_free(entry);
g_fail = 1; g_fail_seq = seq;
g_done = -1;
return;
}
}
// Check duplicate
if (rcvd_bitmap[seq] != 0) {
printf("[FAIL] srv_handler: duplicate seq=%u\n", seq);
queue_dgram_free(entry); queue_entry_free(entry);
g_fail = 2; g_fail_seq = seq;
g_done = -1;
return;
}
rcvd_bitmap[seq] = 1;
g_total_rcvd++;
g_expected_seq++;
queue_dgram_free(entry);
queue_entry_free(entry);
}
// ======================== Loopback test (no ETCP) ========================
static int loop_rcvd = 0, loop_ok = 0;
static void loop_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
(void)conn;
if (entry && entry->dgram && entry->len >= 6) {
loop_rcvd++;
if (entry->dgram[1] == 0xAA && entry->dgram[2] == 0xBB && entry->dgram[3] == 0xCC) loop_ok = 1;
}
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
}
static int test_loopback(void) {
loop_rcvd = loop_ok = 0;
etcp_router_bind(srv, 0xF0, loop_handler);
uint8_t data[6] = { 0xF0, 0xAA, 0xBB, 0xCC, 0x00, 0x00 };
struct ll_entry* e = queue_entry_new(0);
e->dgram = u_malloc(6); memcpy(e->dgram, data, 6); e->len = 6;
etcp_route_send(srv, srv->node_id, e, 0);
etcp_router_unbind(srv, 0xF0);
if (loop_rcvd != 1 || !loop_ok) {
printf("[FAIL] loopback: rcvd=%d ok=%d\n", loop_rcvd, loop_ok);
return 1;
}
printf(" loopback: OK\n");
return 0;
}
// ======================== Dummynet setup ========================
static int setup_dummynet(void) {
dn = dummynet_create(ua, "127.0.0.1", (uint16_t)g_dn_port);
if (!dn) { printf("[FAIL] dummynet_create on port %d\n", g_dn_port); return -1; }
// Forward: dummynet → server
dummynet_set_direction(dn, DUMMYNET_FORWARD, 0, 0, 0, DUMMY_QUEUE_SIZE, 0,
"127.0.0.1", (uint16_t)g_srv_port);
// Backward: dummynet → client
dummynet_set_direction(dn, DUMMYNET_BACKWARD, 0, 0, 0, DUMMY_QUEUE_SIZE, 0,
"127.0.0.1", (uint16_t)g_cli_port);
return 0;
}
static void set_bw(uint32_t kbps) {
dummynet_set_direction(dn, DUMMYNET_FORWARD, 0, 0, kbps, DUMMY_QUEUE_SIZE, 0,
"127.0.0.1", (uint16_t)g_srv_port);
dummynet_set_direction(dn, DUMMYNET_BACKWARD, 0, 0, kbps, DUMMY_QUEUE_SIZE, 0,
"127.0.0.1", (uint16_t)g_cli_port);
}
// ======================== Monitor ========================
static int g_conn_ok = 0;
static int g_conn_delay = 0;
static uint32_t g_baseline_sent = 0;
static double g_cycle_drain_start_ms = 0;
static void monitor(void* arg) {
(void)arg;
if (g_done) { g_mon_id = NULL; return; }
// Phase: wait for connection
if (g_state == ST_WAIT_CONN) {
if (conn_established(srv) && conn_established(cli)) {
g_conn_delay++;
if (g_conn_delay < 50) { g_mon_id = uasync_set_timeout(ua, 10, NULL, monitor, "mon"); return; }
g_conn_ok = 1;
t_connect_ms = toc_ms(&t_phase_start);
printf("Phase 1 — Connect: %7.1f ms\n", t_connect_ms);
// Run loopback test
if (test_loopback() != 0) { g_done = -1; return; }
// Bind data handler
etcp_router_bind(srv, TEST_SVC_ID, srv_handler);
// Start baseline
g_state = ST_BASELINE_SEND;
tic(&t_phase_start);
printf("Phase 2 — Baseline (%d packets, no congestion):\n", BASELINE_PACKETS);
}
g_mon_id = uasync_set_timeout(ua, 10, NULL, monitor, "mon");
return;
}
// Phase: baseline send (all at once, no congestion)
if (g_state == ST_BASELINE_SEND) {
while (g_baseline_sent < BASELINE_PACKETS) {
int data_len = 16 + (g_baseline_sent % 32);
if (send_one_pkt(g_total_sent, data_len) == 0) {
g_total_sent++;
g_baseline_sent++;
} else {
printf("[FAIL] baseline: send failed at seq=%u\n", g_total_sent);
g_done = -1;
return;
}
}
g_state = ST_BASELINE_WAIT;
g_mon_id = uasync_set_timeout(ua, 10, NULL, monitor, "mon");
return;
}
// Phase: baseline wait
if (g_state == ST_BASELINE_WAIT) {
if (g_total_rcvd >= BASELINE_PACKETS) {
t_baseline_ms = toc_ms(&t_phase_start);
printf(" baseline: %7.1f ms sent=%u rcvd=%u\n", t_baseline_ms, g_baseline_sent, g_total_rcvd);
// Start congestion cycles
g_cycle = 0;
g_state = ST_CYCLE_FILL;
tic(&t_phase_start);
printf("Phase 3 — Congestion cycles (%d cycles, %d pkt/cycle, bw=%u kbps):\n",
CONGESTION_CYCLES, PACKETS_PER_CYCLE, LOW_BW_KBPS);
}
g_mon_id = uasync_set_timeout(ua, 10, NULL, monitor, "mon");
return;
}
// Phase: cycle fill — send until backpressure
if (g_state == ST_CYCLE_FILL) {
set_bw(LOW_BW_KBPS);
g_cycle_start_seq = g_total_sent;
g_cycle_sent = 0;
g_backpressure_seen = 0;
g_state = ST_CYCLE_FILL + 100; // sub-state: actively filling
tic(&t_phase_start);
g_mon_id = uasync_set_timeout(ua, 1, NULL, monitor, "mon");
return;
}
if (g_state == ST_CYCLE_FILL + 100) {
// Send one packet per tick until backpressure
int data_len = 32 + (g_total_sent % 64);
int ret = send_one_pkt(g_total_sent, data_len);
if (ret == 0) {
g_total_sent++;
g_cycle_sent++;
} else {
// Backpressure hit — inflight+send_q full
g_backpressure_seen = 1;
t_cycle_fill_ms[g_cycle] = toc_ms(&t_phase_start);
g_cycle_fill_count[g_cycle] = g_cycle_sent;
printf(" cycle %d fill: %7.1f ms sent=%u (backpressure)\n",
g_cycle + 1, t_cycle_fill_ms[g_cycle], g_cycle_sent);
// Transition to push phase
g_state = ST_CYCLE_PUSH;
g_cycle_push_ok = 0;
g_cycle_drop_count[g_cycle] = 0;
tic(&t_phase_start);
}
g_mon_id = uasync_set_timeout(ua, 1, NULL, monitor, "mon");
return;
}
// Phase: cycle push — push 20 more packets through congestion
if (g_state == ST_CYCLE_PUSH) {
if (g_cycle_push_ok >= PACKETS_PER_CYCLE) {
t_cycle_push_ms[g_cycle] = toc_ms(&t_phase_start);
printf(" cycle %d push: %7.1f ms pushed=%u drops=%u\n",
g_cycle + 1, t_cycle_push_ms[g_cycle], g_cycle_push_ok,
g_cycle_drop_count[g_cycle]);
// Release shaper
set_bw(0);
g_state = ST_CYCLE_DRAIN;
tic(&t_phase_start);
g_cycle_drain_start_ms = toc_ms(&t_phase_start);
g_mon_id = uasync_set_timeout(ua, 10, NULL, monitor, "mon");
return;
}
// Try to send one packet
int data_len = 32 + (g_total_sent % 64);
int ret = send_one_pkt(g_total_sent, data_len);
if (ret == 0) {
g_total_sent++;
g_cycle_sent++;
g_cycle_push_ok++;
} else {
g_total_drop++;
g_cycle_drop_count[g_cycle]++;
}
g_mon_id = uasync_set_timeout(ua, 10, NULL, monitor, "mon");
return;
}
// Phase: cycle drain — wait for all sent packets to arrive
if (g_state == ST_CYCLE_DRAIN) {
uint32_t cycle_target = g_cycle_start_seq + g_cycle_sent;
if (g_total_rcvd >= cycle_target) {
t_cycle_drain_ms[g_cycle] = toc_ms(&t_phase_start);
printf(" cycle %d drain: %7.1f ms rcvd=%u/%u\n",
g_cycle + 1, t_cycle_drain_ms[g_cycle], g_total_rcvd, cycle_target);
g_cycle++;
if (g_cycle >= CONGESTION_CYCLES) {
g_state = ST_FINAL;
} else {
g_state = ST_CYCLE_FILL;
}
}
g_mon_id = uasync_set_timeout(ua, 10, NULL, monitor, "mon");
return;
}
// Phase: final verification
if (g_state == ST_FINAL) {
printf("Phase 4 — Final verify:\n");
// Scan bitmap for gaps
int gaps = 0;
uint32_t first_gap = 0, last_gap = 0;
for (uint32_t i = 0; i < g_total_sent; i++) {
if (rcvd_bitmap[i] == 0) {
if (gaps == 0) first_gap = i;
last_gap = i;
gaps++;
}
}
// Dummynet stats
const struct dummynet_stats* sf = dummynet_get_stats(dn, DUMMYNET_FORWARD);
const struct dummynet_stats* sb = dummynet_get_stats(dn, DUMMYNET_BACKWARD);
printf(" total: sent=%u rcvd=%u drops=%u\n", g_total_sent, g_total_rcvd, g_total_drop);
printf(" bitmap: gaps=%d (first=%u last=%u)\n", gaps, first_gap, last_gap);
printf(" dummynet fwd: recv=%" PRIu64 " sent=%" PRIu64 " dropped=%" PRIu64 " lost=%" PRIu64 " qmax=%u\n",
sf->recv, sf->sent, sf->dropped, sf->lost, sf->queue_max);
printf(" dummynet bwd: recv=%" PRIu64 " sent=%" PRIu64 " dropped=%" PRIu64 " lost=%" PRIu64 " qmax=%u\n",
sb->recv, sb->sent, sb->dropped, sb->lost, sb->queue_max);
// Router stats
struct ETCP_ROUTER_CONN* rconn = etcp_router_conn_get(cli, server_node_id, TEST_SVC_ID);
if (rconn) {
printf(" router: tx_seq=%u rx_seq=%u tx_acked=%u c_pkts_sent=%u c_pkts_rcvd=%u c_dup_dropped=%u c_oob_dropped=%u\n",
rconn->tx_seq, rconn->rx_seq, rconn->tx_acked,
rconn->c_pkts_sent, rconn->c_pkts_rcvd,
rconn->c_dup_dropped, rconn->c_oob_dropped);
}
// Per-cycle summary
printf(" per-cycle:\n");
double total_fill = 0, total_push = 0, total_drain = 0;
for (int i = 0; i < CONGESTION_CYCLES; i++) {
printf(" cycle %d: fill=%6.1fms push=%6.1fms drain=%7.1fms fill_pkt=%u push_drops=%u\n",
i + 1, t_cycle_fill_ms[i], t_cycle_push_ms[i], t_cycle_drain_ms[i],
g_cycle_fill_count[i], g_cycle_drop_count[i]);
total_fill += t_cycle_fill_ms[i];
total_push += t_cycle_push_ms[i];
total_drain += t_cycle_drain_ms[i];
}
printf(" totals: fill=%6.1fms push=%6.1fms drain=%7.1fms\n", total_fill, total_push, total_drain);
// Verdict
if (g_total_sent != g_total_rcvd) {
printf("[FAIL] sent(%u) != rcvd(%u): %u packets lost\n", g_total_sent, g_total_rcvd, g_total_sent - g_total_rcvd);
g_done = -1;
} else if (gaps > 0) {
printf("[FAIL] %d gaps in bitmap (losses)\n", gaps);
g_done = -1;
} else {
printf("[PASS] test_etcp_router — %u packets, 0 loss, 0 dup, 0 corrupt\n", g_total_sent);
g_done = 1;
}
g_mon_id = NULL;
return;
}
g_mon_id = uasync_set_timeout(ua, 10, NULL, monitor, "mon");
}
// ======================== Timeout ========================
static void timeout_cb(void* arg) {
(void)arg;
if (!g_done) {
printf("[FAIL] TIMEOUT: state=%d cycle=%d sent=%u rcvd=%u drops=%u\n",
g_state, g_cycle, g_total_sent, g_total_rcvd, g_total_drop);
g_done = -1;
}
if (g_mon_id) { uasync_cancel_timeout(ua, g_mon_id); g_mon_id = NULL; }
}
// ======================== Main ========================
int main(void) {
if (test_mkdtemp(temp_dir) != 0) { fprintf(stderr, "mkdtemp fail\n"); return 1; }
// Allocate consecutive ports
int base_port;
if (alloc_consecutive_ports(&base_port) != 0) {
printf("[FAIL] cannot allocate consecutive ports\n");
test_rmdir(temp_dir);
return 1;
}
g_dn_port = base_port;
g_srv_port = base_port + 1;
g_cli_port = base_port - 1;
printf("Ports: dummynet=%d server=%d client=%d\n", g_dn_port, g_srv_port, g_cli_port);
write_configs();
printf("=== test_etcp_router (with dummynet congestion) ===\n");
debug_config_init();
debug_set_level(DEBUG_LEVEL_ERROR);
debug_set_categories(DEBUG_CATEGORY_ALL);
utun_instance_set_tun_init_enabled(0);
srand((unsigned)time(NULL));
tic(&t_phase_start);
ua = uasync_create();
if (!ua) { printf("[FAIL] uasync_create\n"); goto done; }
srv = utun_instance_create(ua, server_conf);
if (!srv || utun_instance_init(srv) < 0) { printf("[FAIL] server create\n"); goto done; }
cli = utun_instance_create(ua, client_conf);
if (!cli || utun_instance_init(cli) < 0) { printf("[FAIL] client create\n"); goto done; }
server_node_id = srv->node_id; client_node_id = cli->node_id;
// Create dummynet between client and server
if (setup_dummynet() != 0) { printf("[FAIL] dummynet setup\n"); goto done; }
g_mon_id = uasync_set_timeout(ua, 100, NULL, monitor, "mon");
void* to_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS * 10, NULL, timeout_cb, "to");
while (!g_done) uasync_poll(ua, 10);
if (to_id) uasync_cancel_timeout(ua, to_id);
if (g_mon_id) uasync_cancel_timeout(ua, g_mon_id);
etcp_router_unbind(srv, TEST_SVC_ID);
done:
if (dn) dummynet_destroy(dn);
if (srv) { srv->running = 0; utun_instance_destroy(srv); }
if (cli) { cli->running = 0; utun_instance_destroy(cli); }
if (ua) uasync_destroy(ua, 0);
test_unlink(server_conf); test_unlink(client_conf); test_rmdir(temp_dir);
return g_done == 1 ? 0 : 1;
}