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
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; |
|
}
|
|
|