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.
523 lines
23 KiB
523 lines
23 KiB
// test_etcp_link_stress.c - Multilink test: добавляет линки в 5 вариантах (V1/V2 one-way, |
|
// V3a/b/c коллизия), проверяет per-link трафик и инвариант «нет реинита пока жив хотя бы один линк». |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include "../lib/platform_compat.h" |
|
#include "test_utils.h" |
|
|
|
#include "etcp.h" |
|
#include "etcp_api.h" |
|
#include "etcp_connections.h" |
|
#include "etcp_dump.h" |
|
#include "../src/config_parser.h" |
|
#include "../src/utun_instance.h" |
|
#include "routing.h" |
|
#include "../src/tun_if.h" |
|
#include "secure_channel.h" |
|
#include "../lib/u_async.h" |
|
#include "../lib/ll_queue.h" |
|
#include "../lib/debug_config.h" |
|
#include "../lib/mem.h" |
|
|
|
#define PACKET_SIZE 64 |
|
#define TRAFFIC_PACKETS 200 |
|
#define LINK_UP_TIMEOUT_MS 8000 |
|
#define TRAFFIC_TIMEOUT_MS 8000 |
|
#define LINK_DOWN_TIMEOUT_MS 8000 |
|
#define COLLISION_TIMEOUT_MS 15000 |
|
|
|
enum { COL_A = 0, COL_B, COL_C }; |
|
|
|
static struct UTUN_INSTANCE* server_instance = NULL; |
|
static struct UTUN_INSTANCE* client_instance = NULL; |
|
static struct UASYNC* ua = NULL; |
|
|
|
static char temp_dir[] = "/tmp/utun_test_XXXXXX"; |
|
static char server_config_path[256]; |
|
static char client_config_path[256]; |
|
static int server_port = 0; |
|
static int client_port = 0; |
|
static int next_port = 0; |
|
|
|
static uint32_t packets_sent = 0; |
|
static uint32_t packets_received = 0; |
|
|
|
// globals for generic poll_until conditions |
|
static struct ETCP_LINK* g_link = NULL; |
|
static struct ETCP_SOCKET* g_sock = NULL; |
|
static struct ETCP_CONN* g_conn = NULL; |
|
static uint16_t g_port = 0; |
|
static int traffic_corrupt = 0; |
|
|
|
static int create_temp_configs(void) { |
|
if (test_mkdtemp(temp_dir) != 0) { fprintf(stderr, "Failed to create temp directory\n"); return -1; } |
|
int base_port = 50000 + (getpid() % 15000); |
|
server_port = base_port; |
|
client_port = base_port + 1; |
|
next_port = base_port + 100; |
|
|
|
snprintf(server_config_path, sizeof(server_config_path), "%s/server.conf", temp_dir); |
|
snprintf(client_config_path, sizeof(client_config_path), "%s/client.conf", temp_dir); |
|
|
|
FILE* f = fopen(server_config_path, "w"); |
|
if (!f) { fprintf(stderr, "Failed to create server config file\n"); return -1; } |
|
fprintf(f, |
|
"[global]\n" |
|
"my_node_id=0x1111111111111111\n" |
|
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n" |
|
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n" |
|
"tun_ip=10.99.0.1/24\n" |
|
"tun_ifname=tun99\n" |
|
"keepalive_interval=100\n" |
|
"keepalive_adaptive=0\n" |
|
"\n" |
|
"[server: udp0]\n" |
|
"addr=127.0.0.1:%d\n" |
|
"type=public\n" |
|
"\n" |
|
"[allowed_keys]\n" |
|
"allow_all=1\n", |
|
server_port); |
|
fclose(f); |
|
|
|
f = fopen(client_config_path, "w"); |
|
if (!f) { fprintf(stderr, "Failed to create client config file\n"); test_unlink(server_config_path); return -1; } |
|
fprintf(f, |
|
"[global]\n" |
|
"my_node_id=0x2222222222222222\n" |
|
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n" |
|
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n" |
|
"tun_ip=10.99.0.2/24\n" |
|
"tun_ifname=tun98\n" |
|
"keepalive_interval=100\n" |
|
"keepalive_adaptive=0\n" |
|
"\n" |
|
"[server: udp0]\n" |
|
"addr=127.0.0.1:%d\n" |
|
"type=public\n" |
|
"\n" |
|
"[client: test_client]\n" |
|
"keepalive=1\n" |
|
"peer_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n" |
|
"link=udp0:127.0.0.1:%d\n" |
|
"\n" |
|
"[allowed_keys]\n" |
|
"allow_all=1\n", |
|
client_port, server_port); |
|
fclose(f); |
|
return 0; |
|
} |
|
|
|
static void cleanup_temp_configs(void) { |
|
if (server_config_path[0]) test_unlink(server_config_path); |
|
if (client_config_path[0]) test_unlink(client_config_path); |
|
if (temp_dir[0]) test_rmdir(temp_dir); |
|
} |
|
|
|
// ===== helpers ===== |
|
|
|
static struct ETCP_CONN* get_conn(struct UTUN_INSTANCE* inst) { |
|
if (!inst || !inst->connections || !inst->connections->head) return NULL; |
|
struct conn_queue_entry* ce = (struct conn_queue_entry*)inst->connections->head->data; |
|
return ce ? ce->conn : NULL; |
|
} |
|
|
|
static int conn_up(struct UTUN_INSTANCE* inst) { |
|
struct ETCP_CONN* conn = get_conn(inst); |
|
return conn && conn->initialized && conn->links_up; |
|
} |
|
|
|
static int count_links(struct ETCP_CONN* conn) { |
|
int n = 0; |
|
for (struct ETCP_LINK* l = conn ? conn->links : NULL; l; l = l->next) n++; |
|
return n; |
|
} |
|
|
|
static int count_links_up(struct ETCP_CONN* conn) { |
|
int n = 0; |
|
for (struct ETCP_LINK* l = conn ? conn->links : NULL; l; l = l->next) if (l->link_status) n++; |
|
return n; |
|
} |
|
|
|
static void mk_addr(struct sockaddr_storage* out, uint16_t port) { |
|
memset(out, 0, sizeof(*out)); |
|
struct sockaddr_in* sin = (struct sockaddr_in*)out; |
|
sin->sin_family = AF_INET; |
|
sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
|
sin->sin_port = htons(port); |
|
} |
|
|
|
static int alloc_port(void) { return next_port++; } |
|
|
|
static struct ETCP_SOCKET* add_udp_socket(struct UTUN_INSTANCE* inst, uint16_t port, const char* name) { |
|
struct CFG_SERVER cfg; memset(&cfg, 0, sizeof(cfg)); |
|
snprintf(cfg.name, sizeof(cfg.name), "%s", name); |
|
cfg.type = CFG_SERVER_TYPE_PUBLIC; |
|
cfg.mtu = PACKET_DATA_MAX_MTU; |
|
mk_addr(&cfg.ip, port); |
|
return etcp_socket_add(inst, &cfg); |
|
} |
|
|
|
static struct ETCP_LINK* find_link_by_remote_port(struct ETCP_CONN* conn, uint16_t port) { |
|
for (struct ETCP_LINK* l = conn ? conn->links : NULL; l; l = l->next) { |
|
if (l->remote_addr.ss_family == AF_INET && |
|
ntohs(((struct sockaddr_in*)&l->remote_addr)->sin_port) == port) |
|
return l; |
|
} |
|
return NULL; |
|
} |
|
|
|
static struct ETCP_LINK* first_link_on_socket(struct ETCP_SOCKET* sock) { |
|
if (!sock || !sock->links_queue || !sock->links_queue->head) return NULL; |
|
struct link_queue_entry* lqe = (struct link_queue_entry*)sock->links_queue->head->data; |
|
return lqe ? lqe->link : NULL; |
|
} |
|
|
|
static uint64_t client_link_acked(uint16_t srv_port) { |
|
struct ETCP_LINK* l = find_link_by_remote_port(get_conn(client_instance), srv_port); |
|
return l ? l->acked_packets : 0; |
|
} |
|
|
|
static void dump_state(void) { |
|
printf("--- STATE DUMP ---\n"); |
|
if (server_instance) etcp_dump_all_conns(server_instance); |
|
if (client_instance) etcp_dump_all_conns(client_instance); |
|
printf("--- END DUMP ---\n"); |
|
fflush(stdout); |
|
} |
|
|
|
#define CHECK(cond, ...) do { if (!(cond)) { \ |
|
printf("FAIL %s:%d: ", __func__, __LINE__); \ |
|
printf(__VA_ARGS__); \ |
|
printf("\n"); \ |
|
dump_state(); \ |
|
goto fail; } } while (0) |
|
|
|
// ===== poll conditions ===== |
|
|
|
static int c_conn_up_both(void) { return conn_up(server_instance) && conn_up(client_instance); } |
|
static int c_link_up(void) { return g_link && g_link->initialized && g_link->link_status == 1; } |
|
static int c_link_down(void) { return g_link && g_link->link_status == 0; } |
|
static int c_socket_link_up(void) { struct ETCP_LINK* l = first_link_on_socket(g_sock); return l && l->initialized && l->link_status == 1; } |
|
static int c_link_on_port_up(void) { struct ETCP_LINK* l = find_link_by_remote_port(g_conn, g_port); return l && l->initialized && l->link_status == 1; } |
|
static int c_client_all_down(void) { struct ETCP_CONN* c = get_conn(client_instance); return !c || c->links_up == 0; } |
|
static int c_server_all_down(void) { struct ETCP_CONN* c = get_conn(server_instance); return !c || c->links_up == 0; } |
|
|
|
static int poll_until(int (*cond)(void), uint64_t timeout_ms, const char* what) { |
|
uint64_t deadline = get_time_tb() + timeout_ms * 10; |
|
while (get_time_tb() < deadline) { |
|
if (cond()) return 0; |
|
uasync_poll(ua, 5); |
|
} |
|
printf("TIMEOUT waiting for: %s (%llums)\n", what, (unsigned long long)timeout_ms); |
|
return -1; |
|
} |
|
|
|
// ===== traffic ===== |
|
|
|
static void receive_test_packet(struct ETCP_CONN* conn, struct ll_entry* pkt) { |
|
(void)conn; |
|
uint8_t expected[PACKET_SIZE]; expected[0] = 0x7f; |
|
uint32_t seq = packets_received++; |
|
for (int i = 0; i < 4; i++) expected[1 + i] = (uint8_t)(seq >> (i * 8)); |
|
for (int i = 5; i < PACKET_SIZE; i++) expected[i] = (uint8_t)(seq + i); |
|
if (pkt->len != PACKET_SIZE || memcmp(pkt->dgram, expected, PACKET_SIZE) != 0) { |
|
fprintf(stderr, "FAIL: multilink payload/order mismatch at packet %u len=%u\n", seq, pkt->len); |
|
traffic_corrupt = 1; |
|
} |
|
queue_dgram_free(pkt); queue_entry_free(pkt); |
|
} |
|
|
|
static int run_traffic(int n, uint64_t timeout_ms) { |
|
struct ETCP_CONN* cc = get_conn(client_instance); |
|
if (!cc || !cc->input_queue) return -1; |
|
uint32_t target = packets_sent + (uint32_t)n; |
|
uint64_t deadline = get_time_tb() + timeout_ms * 10; |
|
while (1) { |
|
while (packets_sent < target && queue_entry_count(cc->send_input_q) == 0) { |
|
uint8_t buf[PACKET_SIZE]; |
|
uint32_t s = packets_sent; |
|
buf[0] = 0x7f; |
|
for (int i = 0; i < 4; i++) buf[1 + i] = (uint8_t)(s >> (i * 8)); |
|
for (int i = 5; i < PACKET_SIZE; i++) buf[i] = (uint8_t)(s + i); |
|
struct ll_entry* pkt = ll_alloc_lldgram(PACKET_SIZE); |
|
if (!pkt) return -1; |
|
pkt->len = PACKET_SIZE; |
|
memcpy(pkt->dgram, buf, PACKET_SIZE); |
|
if (etcp_send(cc, pkt) != 0) { queue_dgram_free(pkt); queue_entry_free(pkt); return -1; } |
|
packets_sent++; |
|
} |
|
if (traffic_corrupt || packets_received > target) return -1; |
|
if (packets_received == target && !cc->input_wait_ack->count && !cc->input_send_q->count) return 0; |
|
if (get_time_tb() > deadline) { |
|
printf("traffic timeout: sent=%u recv=%u target=%u\n", packets_sent, packets_received, target); |
|
return -1; |
|
} |
|
uasync_poll(ua, 5); |
|
} |
|
} |
|
|
|
// Проверяет, что конкретный линк (по серверному порту) реально перенёс данные. |
|
// Линк может пропустить короткий burst из-за keepalive-стабилизации link_status, |
|
// поэтому повторяем до нескольких keepalive-тиков. Возвращает 0 если линк нёс трафик. |
|
static int check_link_traffic(uint16_t srv_port, const char* label) { |
|
for (int attempt = 0; attempt < 6; attempt++) { |
|
uint64_t before = client_link_acked(srv_port); |
|
if (run_traffic(TRAFFIC_PACKETS, TRAFFIC_TIMEOUT_MS) != 0) return -1; |
|
if (client_link_acked(srv_port) > before) return 0; |
|
uasync_poll(ua, 50); |
|
} |
|
printf(" [%s] link never carried traffic\n", label); |
|
return -1; |
|
} |
|
|
|
// ===== instance lifecycle ===== |
|
|
|
static int start_instances(void) { |
|
packets_sent = packets_received = 0; traffic_corrupt = 0; |
|
server_instance = utun_instance_create(ua, server_config_path); |
|
if (!server_instance || utun_instance_init(server_instance) < 0) { |
|
printf("failed to create server\n"); return -1; |
|
} |
|
client_instance = utun_instance_create(ua, client_config_path); |
|
if (!client_instance || utun_instance_init(client_instance) < 0) { |
|
printf("failed to create client\n"); return -1; |
|
} |
|
etcp_bind(server_instance, 0x7f, receive_test_packet); |
|
if (poll_until(c_conn_up_both, LINK_UP_TIMEOUT_MS, "initial connection") != 0) { |
|
printf("initial connection timeout\n"); dump_state(); return -1; |
|
} |
|
printf("connection established: server links_up=%d/%d client links_up=%d/%d\n", |
|
count_links_up(get_conn(server_instance)), count_links(get_conn(server_instance)), |
|
count_links_up(get_conn(client_instance)), count_links(get_conn(client_instance))); |
|
return 0; |
|
} |
|
|
|
static void stop_instances(void) { |
|
if (server_instance) { server_instance->running = 0; utun_instance_destroy(server_instance); server_instance = NULL; } |
|
if (client_instance) { client_instance->running = 0; utun_instance_destroy(client_instance); client_instance = NULL; } |
|
} |
|
|
|
// Убить серверный сокет (линк со стороны сервера закрывается сразу), ускорить детект на клиенте. |
|
static void kill_server_socket(struct ETCP_SOCKET* sock, uint16_t srv_port) { |
|
struct ETCP_LINK* cl = find_link_by_remote_port(get_conn(client_instance), srv_port); |
|
if (cl) cl->last_recv_local_time = 0; |
|
etcp_socket_remove(sock); |
|
} |
|
|
|
// ===== Session A: one-way adds + kill failover ===== |
|
|
|
static int session_a(void) { |
|
printf("\n=== Session A: one-way adds (V1/V2) + link kill failover ===\n"); |
|
|
|
if (start_instances() != 0) return -1; |
|
|
|
struct ETCP_CONN* sc = get_conn(server_instance); |
|
struct ETCP_CONN* cc = get_conn(client_instance); |
|
int held = 0; |
|
CHECK(sc && cc, "no conn after start"); |
|
CHECK(etcp_conn_ref_take(sc) == 0 && etcp_conn_ref_take(cc) == 0, "test conn references"); |
|
held = 1; |
|
|
|
uint32_t srv_reinit0 = sc->reinit_count; |
|
uint32_t cli_reinit0 = cc->reinit_count; |
|
struct ETCP_SOCKET* s0 = server_instance->etcp_sockets; // начальный серверный сокет udp0 |
|
struct ETCP_SOCKET* c0 = client_instance->etcp_sockets; // клиентский сокет udp0 (source для V1) |
|
|
|
// -- per-link трафик на исходном линке -- |
|
printf("[A] traffic on initial link...\n"); |
|
CHECK(check_link_traffic(server_port, "initial") == 0, "initial link carried no traffic"); |
|
CHECK(sc->reinit_count == srv_reinit0 && cc->reinit_count == cli_reinit0, "reinit during initial traffic"); |
|
|
|
// -- V1: клиент дозванивается до нового серверного сокета S1 -- |
|
int sp1 = alloc_port(); |
|
struct ETCP_SOCKET* s1 = add_udp_socket(server_instance, sp1, "dyn_s1"); |
|
CHECK(s1, "V1: failed to add server socket S1"); |
|
struct sockaddr_storage s1_addr; mk_addr(&s1_addr, sp1); |
|
struct ETCP_LINK* lc1 = etcp_link_new(cc, c0, &s1_addr, 0); |
|
CHECK(lc1, "V1: failed to create client link"); |
|
printf("[A] V1: client link %d -> server port %d\n", lc1->local_link_id, sp1); |
|
g_link = lc1; |
|
CHECK(poll_until(c_link_up, LINK_UP_TIMEOUT_MS, "V1 link up (client)") == 0, "V1 link did not come up"); |
|
g_sock = s1; |
|
CHECK(poll_until(c_socket_link_up, LINK_UP_TIMEOUT_MS, "V1 link up (server)") == 0, "V1 server link did not come up"); |
|
CHECK(sc->reinit_count == srv_reinit0 && cc->reinit_count == cli_reinit0, "reinit during V1"); |
|
CHECK(check_link_traffic(sp1, "V1") == 0, "V1 link carried no traffic"); |
|
|
|
// -- V2: сервер дозванивается до клиента -- |
|
int sp2 = alloc_port(); |
|
struct ETCP_SOCKET* s2 = add_udp_socket(server_instance, sp2, "dyn_s2"); |
|
CHECK(s2, "V2: failed to add server socket S2"); |
|
struct sockaddr_storage c0_addr; mk_addr(&c0_addr, client_port); |
|
struct ETCP_LINK* ls2 = etcp_link_new(sc, s2, &c0_addr, 0); |
|
CHECK(ls2, "V2: failed to create server outbound link"); |
|
printf("[A] V2: server link %d -> client port %d\n", ls2->local_link_id, client_port); |
|
g_link = ls2; |
|
CHECK(poll_until(c_link_up, LINK_UP_TIMEOUT_MS, "V2 link up (server)") == 0, "V2 link did not come up"); |
|
g_conn = cc; g_port = sp2; |
|
CHECK(poll_until(c_link_on_port_up, LINK_UP_TIMEOUT_MS, "V2 link up (client)") == 0, "V2 client link did not come up"); |
|
CHECK(sc->reinit_count == srv_reinit0 && cc->reinit_count == cli_reinit0, "reinit during V2"); |
|
CHECK(check_link_traffic(sp2, "V2") == 0, "V2 link carried no traffic"); |
|
|
|
printf("[A] after adds: server links_up=%d/%d client links_up=%d/%d\n", |
|
count_links_up(sc), count_links(sc), count_links_up(cc), count_links(cc)); |
|
|
|
// -- kill V2 link: failover, без реинита -- |
|
printf("[A] killing V2 link (server socket %d)...\n", sp2); |
|
kill_server_socket(s2, sp2); |
|
g_link = find_link_by_remote_port(cc, sp2); |
|
CHECK(poll_until(c_link_down, LINK_DOWN_TIMEOUT_MS, "V2 link down") == 0, "V2 link did not go down"); |
|
CHECK(cc->links_up == 1, "client links_up != 1 after V2 kill (got %d)", cc->links_up); |
|
CHECK(sc->links_up == 1, "server links_up != 1 after V2 kill (got %d)", sc->links_up); |
|
CHECK(cc->initialized == 1, "client initialized lost after V2 kill"); |
|
CHECK(sc->reinit_count == srv_reinit0 && cc->reinit_count == cli_reinit0, "reinit after V2 kill"); |
|
CHECK(run_traffic(TRAFFIC_PACKETS, TRAFFIC_TIMEOUT_MS) == 0, "failover traffic after V2 kill failed"); |
|
|
|
// -- kill V1 link -- |
|
printf("[A] killing V1 link (server socket %d)...\n", sp1); |
|
kill_server_socket(s1, sp1); |
|
g_link = find_link_by_remote_port(cc, sp1); |
|
CHECK(poll_until(c_link_down, LINK_DOWN_TIMEOUT_MS, "V1 link down") == 0, "V1 link did not go down"); |
|
CHECK(cc->links_up == 1, "client links_up != 1 after V1 kill (got %d)", cc->links_up); |
|
CHECK(cc->initialized == 1, "client initialized lost after V1 kill"); |
|
CHECK(sc->reinit_count == srv_reinit0 && cc->reinit_count == cli_reinit0, "reinit after V1 kill"); |
|
CHECK(run_traffic(TRAFFIC_PACKETS, TRAFFIC_TIMEOUT_MS) == 0, "failover traffic after V1 kill failed"); |
|
|
|
// -- kill initial (последний) линк: всё вниз, реинита нет -- |
|
printf("[A] killing initial link (server socket %d)...\n", server_port); |
|
kill_server_socket(s0, server_port); |
|
g_link = find_link_by_remote_port(cc, server_port); |
|
CHECK(poll_until(c_link_down, LINK_DOWN_TIMEOUT_MS, "initial link down") == 0, "initial link did not go down"); |
|
CHECK(poll_until(c_client_all_down, LINK_DOWN_TIMEOUT_MS, "client all links down") == 0, "client links not all down"); |
|
CHECK(poll_until(c_server_all_down, LINK_DOWN_TIMEOUT_MS, "server all links down") == 0, "server links not all down"); |
|
CHECK(cc->reinit_count == cli_reinit0, "reinit when last link died (client %u->%u)", cli_reinit0, cc->reinit_count); |
|
CHECK(sc->reinit_count == srv_reinit0, "reinit when last link died (server %u->%u)", srv_reinit0, sc->reinit_count); |
|
|
|
printf("[A] Session A PASSED\n"); |
|
etcp_conn_ref_free(sc); etcp_conn_ref_free(cc); |
|
stop_instances(); |
|
return 0; |
|
|
|
fail: |
|
if (held) { etcp_conn_ref_free(sc); etcp_conn_ref_free(cc); } |
|
stop_instances(); |
|
return -1; |
|
} |
|
|
|
// ===== Session: коллизии ===== |
|
|
|
static int session_collision(int which) { |
|
const char* names[] = { |
|
"V3a: both correct addr (true collision)", |
|
"V3b: client wrong addr (server-only establish)", |
|
"V3c: server wrong addr (client-only establish)", |
|
}; |
|
printf("\n=== Session %s ===\n", names[which]); |
|
|
|
if (start_instances() != 0) return -1; |
|
|
|
struct ETCP_CONN* sc = get_conn(server_instance); |
|
struct ETCP_CONN* cc = get_conn(client_instance); |
|
CHECK(sc && cc, "no conn"); |
|
|
|
uint32_t srv_reinit0 = sc->reinit_count; |
|
uint32_t cli_reinit0 = cc->reinit_count; |
|
uint64_t srv_epoch = sc->reset_id, cli_epoch = cc->reset_id; |
|
CHECK(run_traffic(TRAFFIC_PACKETS, TRAFFIC_TIMEOUT_MS) == 0, "traffic before link add failed"); |
|
|
|
int sp3 = alloc_port(); |
|
int cp1 = alloc_port(); |
|
int dead = alloc_port(); |
|
|
|
struct ETCP_SOCKET* s3 = add_udp_socket(server_instance, sp3, "dyn_s3"); |
|
CHECK(s3, "failed to add server socket S3"); |
|
struct ETCP_SOCKET* c1 = add_udp_socket(client_instance, cp1, "dyn_c1"); |
|
CHECK(c1, "failed to add client socket C1"); |
|
|
|
struct sockaddr_storage s3_addr; mk_addr(&s3_addr, sp3); |
|
struct sockaddr_storage c1_addr; mk_addr(&c1_addr, cp1); |
|
struct sockaddr_storage dead_addr; mk_addr(&dead_addr, dead); |
|
|
|
struct ETCP_LINK* lc3 = NULL; |
|
struct ETCP_LINK* ls3 = NULL; |
|
if (which == COL_A) { |
|
lc3 = etcp_link_new(cc, c1, &s3_addr, 0); // клиент -> S3 (верно) |
|
ls3 = etcp_link_new(sc, s3, &c1_addr, 0); // сервер -> C1 (верно) |
|
} else if (which == COL_B) { |
|
lc3 = etcp_link_new(cc, c1, &dead_addr, 0); // клиент -> мёртвый порт |
|
ls3 = etcp_link_new(sc, s3, &c1_addr, 0); // сервер -> C1 (верно) |
|
} else { |
|
lc3 = etcp_link_new(cc, c1, &s3_addr, 0); // клиент -> S3 (верно) |
|
ls3 = etcp_link_new(sc, s3, &dead_addr, 0); // сервер -> мёртвый порт |
|
} |
|
CHECK(lc3 && ls3, "link creation failed (lc3=%p ls3=%p)", (void*)lc3, (void*)ls3); |
|
|
|
if (which == COL_A) { |
|
CHECK(run_traffic(TRAFFIC_PACKETS, TRAFFIC_TIMEOUT_MS) == 0, "traffic during collision failed"); |
|
g_conn = cc; g_port = sp3; |
|
CHECK(poll_until(c_link_on_port_up, COLLISION_TIMEOUT_MS, "collision client link up") == 0, "client link down"); |
|
g_conn = sc; g_port = cp1; |
|
CHECK(poll_until(c_link_on_port_up, COLLISION_TIMEOUT_MS, "collision server link up") == 0, "server link down"); |
|
struct ETCP_LINK* cl = find_link_by_remote_port(cc, sp3); |
|
struct ETCP_LINK* sl = find_link_by_remote_port(sc, cp1); |
|
CHECK(cl->is_server != sl->is_server, "collision left both ends in the same role"); |
|
CHECK(count_links(sc) == 2 && count_links(cc) == 2, "collision created duplicate links"); |
|
CHECK(check_link_traffic(sp3, "collision") == 0, "new link carried no traffic"); |
|
} else { |
|
struct ETCP_LINK* good = (which == COL_B) ? ls3 : lc3; |
|
g_link = good; |
|
CHECK(poll_until(c_link_up, LINK_UP_TIMEOUT_MS, "good link up") == 0, "good link did not come up"); |
|
uint16_t peer_port = (which == COL_B) ? sp3 : cp1; |
|
g_conn = (which == COL_B) ? cc : sc; |
|
g_port = peer_port; |
|
CHECK(poll_until(c_link_on_port_up, LINK_UP_TIMEOUT_MS, "peer incoming link up") == 0, |
|
"peer incoming link did not come up"); |
|
CHECK(sc->reinit_count == srv_reinit0 && cc->reinit_count == cli_reinit0, |
|
"unexpected reinit (srv %u->%u cli %u->%u)", srv_reinit0, sc->reinit_count, cli_reinit0, cc->reinit_count); |
|
CHECK(run_traffic(TRAFFIC_PACKETS, TRAFFIC_TIMEOUT_MS) == 0, "traffic failed"); |
|
} |
|
|
|
CHECK(sc->reinit_count == srv_reinit0 && cc->reinit_count == cli_reinit0, "link add reset the stream"); |
|
CHECK(sc->reset_id == srv_epoch && cc->reset_id == cli_epoch, "link add changed session epochs"); |
|
printf("[%s] PASSED\n", names[which]); |
|
stop_instances(); |
|
return 0; |
|
|
|
fail: |
|
stop_instances(); |
|
return -1; |
|
} |
|
|
|
int main(void) { |
|
if (create_temp_configs() != 0) return 1; |
|
|
|
debug_config_init(); |
|
debug_set_level(DEBUG_LEVEL_ERROR); |
|
if (getenv("UTUN_TEST_DEBUG")) { |
|
debug_set_level(DEBUG_LEVEL_DEBUG); |
|
debug_set_category_level(DEBUG_CATEGORY_ETCP, DEBUG_LEVEL_DEBUG); |
|
debug_set_category_level(DEBUG_CATEGORY_CONNECTION, DEBUG_LEVEL_DEBUG); |
|
debug_set_category_level(DEBUG_CATEGORY_KEEPALIVE, DEBUG_LEVEL_DEBUG); |
|
debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_DEBUG); |
|
} |
|
|
|
printf("=== ETCP Multilink Test ===\n"); |
|
printf("server_port=%d client_port=%d dyn_ports=%d..\n", server_port, client_port, next_port); |
|
|
|
utun_instance_set_tun_init_enabled(0); |
|
ua = uasync_create(); |
|
|
|
int rc = 0; |
|
rc |= session_a() != 0; |
|
rc |= session_collision(COL_A) != 0; |
|
rc |= session_collision(COL_B) != 0; |
|
rc |= session_collision(COL_C) != 0; |
|
|
|
if (ua) { uasync_poll(ua, 0); uasync_destroy(ua, 0); ua = NULL; } |
|
cleanup_temp_configs(); |
|
|
|
if (rc == 0) { printf("\n=== ALL TESTS PASSED ===\n"); return 0; } |
|
printf("\n=== TESTS FAILED ===\n"); |
|
return 1; |
|
}
|
|
|