Files
utun2/tests/test_etcp_router.c
T
Evgeny 852ce401e7 Добавлен group_id в etcp_router и topo_group, удалён msg_transport
- etcp_router: SVC_ROUTE_HDR (+8B), ETCP_ROUTER_CONN/TRANSIT_QUEUE хеши расширены под group_id
- Все caller'ы (proxy, routing, conn_mgr, nat, chatgui, tests) обновлены
- topo_group: глобальный реестр TOPO_NODE по node_id, ref_count=0 при создании
- BGP-обмен активируется для всех групп (а не только дефолтной)
- Протокол: group_id добавлен в WITHDRAW/TABLE_REQ/TABLE_COMPLETE/ERR_GROUP_MISMATCH
- Per-connection коллбэки заменены на instance-level conn_status
- msg_transport полностью удалён из проекта
57/57 тестов
2026-07-22 15:51:03 +03:00

652 lines
24 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 "etcp.h"
#include "etcp_connections.h"
#include "etcp_api.h"
#include "etcp_router.h"
#include "dummynet.h"
#include "../src/config_parser.h"
#include "../src/utun_instance.h"
#include "routing.h"
#include "../src/tun_if.h"
#include "secure_channel.h"
#include "pkt_normalizer.h"
#include "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, TOPO_GROUP_UTUN, 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, TOPO_GROUP_UTUN, 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, TOPO_GROUP_UTUN, 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;
}