- 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 тестов
652 lines
24 KiB
C
652 lines
24 KiB
C
// 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;
|
||
}
|