add etcp_router service routing layer + remote TCP proxy (uip+socket via router)
etcp_router: routable service packets with dst/src node_id - etcp_router_bind/unbind: per-instance service handler registry - etcp_route_send: send to node by id via BGP routing - multi-hop forwarding through intermediate nodes - loopback optimization (dst==self dispatches directly) remote_proxy: exit node creating OS sockets on demand - handles CONNECT/DATA/CLOSE via etcp_router - non-blocking socket connect with uasync callbacks - echo back response data to requesting node tcp_proxy: etcp_transport for remote proxy - via_node_id in mapping selects between sock/etcp transport - unified handler for both tcp_proxy and remote_proxy roles - config: forward = port -> ip:port via NODE_HEX [remote_proxy] section tests: test_etcp_router (20 pkts bidirectional), test_remote_proxy (echo)
This commit is contained in:
1 parent
5e834b519f
commit
9869cf66f1
15 files changed
+1997
-6
No files matched your search
+16
-2
@@ -31,6 +31,9 @@ check_PROGRAMS = \
|
||||
test_nat_transport \
|
||||
test_nat_stress \
|
||||
test_etcp_reinit_inflight \
|
||||
test_tcp_proxy \
|
||||
test_etcp_router \
|
||||
test_remote_proxy \
|
||||
bench_timeout_heap \
|
||||
bench_uasync_timeouts
|
||||
|
||||
@@ -107,6 +110,10 @@ ETCP_FULL_OBJS = \
|
||||
$(top_builddir)/src/utun-control_server.o \
|
||||
$(TUN_PLATFORM_OBJ) \
|
||||
$(top_builddir)/src/utun-utun_instance.o \
|
||||
$(top_builddir)/src/utun-tcp_proxy.o \
|
||||
$(top_builddir)/src/utun-etcp_router.o \
|
||||
$(top_builddir)/src/utun-remote_proxy.o \
|
||||
$(top_builddir)/src/uip/utun-uip.o \
|
||||
$(ETCP_CORE_OBJS)
|
||||
|
||||
# Windows-specific libraries (advapi32 for CryptGenRandom, ws2_32 for sockets)
|
||||
@@ -177,8 +184,15 @@ test_etcp_reinit_inflight_SOURCES = test_etcp_reinit_inflight.c
|
||||
test_etcp_reinit_inflight_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_etcp_reinit_inflight_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_etcp_minimal_SOURCES = test_etcp_minimal.c
|
||||
test_etcp_minimal_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_tcp_proxy_SOURCES = test_tcp_proxy.c
|
||||
test_tcp_proxy_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_etcp_router_SOURCES = test_etcp_router.c
|
||||
test_etcp_router_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_remote_proxy_SOURCES = test_remote_proxy.c
|
||||
test_remote_proxy_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_etcp_minimal_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_etcp_100_packets_SOURCES = test_etcp_100_packets.c
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
// test_etcp_router.c — Unit test for etcp_router: bind/unbind, send, loopback, forward, reply
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../lib/platform_compat.h"
|
||||
#include "test_utils.h"
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <direct.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <time.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "../src/etcp.h"
|
||||
#include "../src/etcp_connections.h"
|
||||
#include "../src/etcp_api.h"
|
||||
#include "../src/etcp_router.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/route_bgp.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
|
||||
#define TEST_SVC_ID 0x01
|
||||
#define TEST_TIMEOUT_MS 5000
|
||||
#define TOTAL_PACKETS 20
|
||||
#define MAX_PAYLOAD 200
|
||||
|
||||
static char temp_dir[] = "/tmp/utun_test_XXXXXX";
|
||||
static char server_conf[256], client_conf[256];
|
||||
static struct UTUN_INSTANCE* srv = NULL;
|
||||
static struct UTUN_INSTANCE* cli = NULL;
|
||||
static struct UASYNC* ua = NULL;
|
||||
|
||||
static int g_ok = 0;
|
||||
static int g_test_done = 0;
|
||||
static int g_phase = 0; // 0=wait_conn, 1=fwd, 2=reply
|
||||
static void* g_mon_id = NULL;
|
||||
|
||||
// Server config — node 0x1111...
|
||||
static const char* srv_cfg =
|
||||
"[global]\n"
|
||||
"my_node_id=0x1111111111111111\n"
|
||||
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
|
||||
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
|
||||
"tun_ip=10.99.0.1/24\n"
|
||||
"tun_ifname=tun99\n"
|
||||
"[server: s1]\n"
|
||||
"addr=127.0.0.1:9041\n"
|
||||
"type=public\n"
|
||||
"[allowed_keys]\n"
|
||||
"allow_all=1\n";
|
||||
|
||||
// Client config — node 0x2222...
|
||||
static const char* cli_cfg =
|
||||
"[global]\n"
|
||||
"my_node_id=0x2222222222222222\n"
|
||||
"my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2\n"
|
||||
"my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n"
|
||||
"tun_ip=10.99.0.2/24\n"
|
||||
"tun_ifname=tun98\n"
|
||||
"[server: s1]\n"
|
||||
"addr=127.0.0.1:9042\n"
|
||||
"type=public\n"
|
||||
"[client: c1]\n"
|
||||
"keepalive=1\n"
|
||||
"peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
|
||||
"link=s1:127.0.0.1:9041\n";
|
||||
|
||||
// ======================== Test state ========================
|
||||
static int fwd_sent = 0, fwd_rcvd = 0;
|
||||
static int reply_sent = 0, reply_rcvd = 0;
|
||||
static uint8_t expected_data[MAX_PAYLOAD];
|
||||
static uint64_t server_node_id = 0x1111111111111111ULL;
|
||||
static uint64_t client_node_id = 0x2222222222222222ULL;
|
||||
|
||||
// ======================== Helpers ========================
|
||||
static int conn_established(struct UTUN_INSTANCE* inst) {
|
||||
if (!inst) return 0;
|
||||
for (struct ETCP_CONN* c = inst->connections; c; c = c->next) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct ETCP_CONN* first_conn(struct UTUN_INSTANCE* inst) {
|
||||
return inst ? inst->connections : NULL;
|
||||
}
|
||||
|
||||
// ======================== Server handler ========================
|
||||
static void srv_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
(void)conn;
|
||||
if (!entry || !entry->dgram || entry->len < 10) { // svc_id(1) + subcmd(1) + seq(4) + data_len(4)
|
||||
if (entry) { queue_entry_free(entry); queue_dgram_free(entry); }
|
||||
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) { // DATA
|
||||
if (seq != (uint32_t)fwd_rcvd) {
|
||||
printf("[FAIL] server: seq mismatch expected=%u got=%u\n", (uint32_t)fwd_rcvd, seq);
|
||||
g_test_done = -1;
|
||||
} else if (data_len > 0 && memcmp(payload, expected_data, data_len) != 0) {
|
||||
printf("[FAIL] server: data mismatch at seq=%u\n", seq);
|
||||
g_test_done = -1;
|
||||
} else {
|
||||
fwd_rcvd++;
|
||||
}
|
||||
queue_entry_free(entry); queue_dgram_free(entry);
|
||||
|
||||
// Send reply back
|
||||
if (!g_test_done && reply_sent < TOTAL_PACKETS) {
|
||||
uint8_t buf[10 + MAX_PAYLOAD];
|
||||
buf[0] = TEST_SVC_ID;
|
||||
buf[1] = 0x02; // REPLY subcmd
|
||||
uint32_t rseq = reply_sent;
|
||||
memcpy(buf + 2, &rseq, 4);
|
||||
memcpy(buf + 6, &data_len, 4);
|
||||
memcpy(buf + 10, expected_data, data_len);
|
||||
struct ll_entry* re = queue_entry_new(0);
|
||||
if (re) { re->dgram = u_malloc(10 + data_len); memcpy(re->dgram, buf, 10 + data_len); re->len = 10 + data_len;
|
||||
etcp_route_send(srv, client_node_id, re); reply_sent++; }
|
||||
}
|
||||
} else {
|
||||
queue_entry_free(entry); queue_dgram_free(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== Client handler ========================
|
||||
static void cli_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
(void)conn;
|
||||
if (!entry || !entry->dgram || entry->len < 10) {
|
||||
if (entry) { queue_entry_free(entry); queue_dgram_free(entry); }
|
||||
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 == 0x02) { // REPLY
|
||||
if (seq != (uint32_t)reply_rcvd) {
|
||||
printf("[FAIL] client: reply seq mismatch expected=%u got=%u\n", (uint32_t)reply_rcvd, seq);
|
||||
g_test_done = -1;
|
||||
} else if (data_len > 0 && memcmp(payload, expected_data, data_len) != 0) {
|
||||
printf("[FAIL] client: reply data mismatch at seq=%u\n", seq);
|
||||
g_test_done = -1;
|
||||
} else {
|
||||
reply_rcvd++;
|
||||
}
|
||||
}
|
||||
queue_entry_free(entry); queue_dgram_free(entry);
|
||||
}
|
||||
|
||||
// ======================== Loopback test (no ETCP) ========================
|
||||
static int loop_rcvd = 0;
|
||||
static int 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_entry_free(entry); queue_dgram_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); // loopback
|
||||
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;
|
||||
}
|
||||
|
||||
// ======================== Test API bind/unbind ========================
|
||||
static int test_api(void) {
|
||||
if (etcp_router_bind(srv, 0xEE, loop_handler) != 0) { printf("[FAIL] bind\n"); return 1; }
|
||||
if (etcp_router_bind(srv, 0xEE, loop_handler) != 0) { printf("[FAIL] rebind (overwrite)\n"); etcp_router_unbind(srv, 0xEE); return 1; }
|
||||
if (etcp_router_unbind(srv, 0xEE) != 0) { printf("[FAIL] unbind\n"); return 1; }
|
||||
if (etcp_router_unbind(srv, 0xEE) == 0) { printf("[FAIL] double unbind should fail\n"); return 1; }
|
||||
// Invalid args:
|
||||
if (etcp_router_bind(NULL, 0, NULL) == 0) { printf("[FAIL] bind null\n"); return 1; }
|
||||
if (etcp_router_bind(srv, 0xEE, NULL) == 0) { printf("[FAIL] bind null cb\n"); return 1; }
|
||||
printf(" api bind/unbind: OK\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ======================== Monitor & sender ========================
|
||||
static uint64_t dedup_bgp_log = 0;
|
||||
static void monitor(void* arg) {
|
||||
(void)arg;
|
||||
if (g_test_done) { g_mon_id = NULL; return; }
|
||||
|
||||
static int conn_ok = 0, conn_delay = 0;
|
||||
if (!conn_ok) {
|
||||
if (conn_established(srv) && conn_established(cli)) {
|
||||
conn_delay++;
|
||||
if (conn_delay < 40) { g_mon_id = uasync_set_timeout(ua, 10, NULL, monitor, "mon"); return; }
|
||||
conn_ok = 1;
|
||||
printf(" connections established\n");
|
||||
// Run API tests + loopback
|
||||
if (test_api() != 0) { g_test_done = -1; return; }
|
||||
if (test_loopback() != 0) { g_test_done = -1; return; }
|
||||
// Bind handlers
|
||||
etcp_router_bind(srv, TEST_SVC_ID, srv_handler);
|
||||
etcp_router_bind(cli, TEST_SVC_ID, cli_handler);
|
||||
// Generate shared random data
|
||||
for (int i = 0; i < MAX_PAYLOAD; i++) expected_data[i] = (uint8_t)(rand() & 0xFF);
|
||||
g_phase = 1;
|
||||
printf(" sending %d packets client→server...\n", TOTAL_PACKETS);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 1: send forward
|
||||
if (g_phase == 1 && fwd_sent < TOTAL_PACKETS) {
|
||||
int data_len = 16 + (fwd_sent % 32);
|
||||
uint8_t buf[10 + MAX_PAYLOAD];
|
||||
buf[0] = TEST_SVC_ID;
|
||||
buf[1] = 0x01; // DATA subcmd
|
||||
uint32_t seq = fwd_sent;
|
||||
memcpy(buf + 2, &seq, 4);
|
||||
memcpy(buf + 6, &data_len, 4);
|
||||
memcpy(buf + 10, expected_data, data_len);
|
||||
struct ll_entry* e = queue_entry_new(0);
|
||||
if (e) { e->dgram = u_malloc(10 + data_len); memcpy(e->dgram, buf, 10 + data_len); e->len = 10 + data_len;
|
||||
if (etcp_route_send(cli, server_node_id, e) == 0) fwd_sent++;
|
||||
else { queue_entry_free(e); queue_dgram_free(e); }
|
||||
}
|
||||
}
|
||||
|
||||
if (g_phase == 1 && fwd_sent >= TOTAL_PACKETS && fwd_rcvd >= TOTAL_PACKETS && reply_sent >= TOTAL_PACKETS) {
|
||||
g_phase = 2;
|
||||
printf(" forward phase done: sent=%d rcvd=%d reply_sent=%d\n", fwd_sent, fwd_rcvd, reply_sent);
|
||||
}
|
||||
|
||||
if (g_phase == 2 && reply_rcvd >= TOTAL_PACKETS) {
|
||||
printf(" reply phase done: rcvd=%d\n", reply_rcvd);
|
||||
g_test_done = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
g_mon_id = uasync_set_timeout(ua, 10, NULL, monitor, "mon");
|
||||
}
|
||||
|
||||
static void timeout(void* arg) {
|
||||
(void)arg;
|
||||
if (!g_test_done) { printf("[FAIL] timeout: sent=%d rcvd=%d reply_sent=%d reply_rcvd=%d\n",
|
||||
fwd_sent, fwd_rcvd, reply_sent, reply_rcvd);
|
||||
g_test_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; }
|
||||
snprintf(server_conf, sizeof(server_conf), "%s/server.conf", temp_dir);
|
||||
snprintf(client_conf, sizeof(client_conf), "%s/client.conf", temp_dir);
|
||||
|
||||
FILE* f = fopen(server_conf, "w");
|
||||
if (!f) { fprintf(stderr, "fopen fail\n"); test_rmdir(temp_dir); return 1; }
|
||||
fprintf(f, "%s", srv_cfg); fclose(f);
|
||||
f = fopen(client_conf, "w");
|
||||
if (!f) { fprintf(stderr, "fopen fail\n"); test_unlink(server_conf); test_rmdir(temp_dir); return 1; }
|
||||
fprintf(f, "%s", cli_cfg); fclose(f);
|
||||
|
||||
printf("=== test_etcp_router ===\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));
|
||||
|
||||
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; }
|
||||
|
||||
g_mon_id = uasync_set_timeout(ua, 100, NULL, monitor, "mon");
|
||||
void* to_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS * 10, NULL, timeout, "to");
|
||||
|
||||
while (!g_test_done) uasync_poll(ua, 100);
|
||||
|
||||
if (to_id) uasync_cancel_timeout(ua, to_id);
|
||||
if (g_mon_id) uasync_cancel_timeout(ua, g_mon_id);
|
||||
|
||||
if (g_test_done == 1) {
|
||||
// Check all statistics
|
||||
if (fwd_sent == TOTAL_PACKETS && fwd_rcvd == TOTAL_PACKETS &&
|
||||
reply_sent == TOTAL_PACKETS && reply_rcvd == TOTAL_PACKETS) {
|
||||
printf("[PASS] test_etcp_router — %d packets forwarded, %d replied\n", fwd_rcvd, reply_rcvd);
|
||||
g_ok = 1;
|
||||
} else {
|
||||
printf("[FAIL] incomplete: fwd_sent=%d fwd_rcvd=%d reply_sent=%d reply_rcvd=%d\n",
|
||||
fwd_sent, fwd_rcvd, reply_sent, reply_rcvd);
|
||||
}
|
||||
} else {
|
||||
printf("[FAIL] test did not complete\n");
|
||||
}
|
||||
|
||||
etcp_router_unbind(srv, TEST_SVC_ID);
|
||||
etcp_router_unbind(cli, TEST_SVC_ID);
|
||||
|
||||
done:
|
||||
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_ok ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
// test_remote_proxy.c — Test remote_proxy: CONNECT → socket → echo → verify
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/wait.h>
|
||||
#include "../lib/platform_compat.h"
|
||||
#include "test_utils.h"
|
||||
#include "../src/etcp.h"
|
||||
#include "../src/etcp_api.h"
|
||||
#include "../src/etcp_router.h"
|
||||
#include "../src/remote_proxy.h"
|
||||
#include "../src/config_parser.h"
|
||||
#include "../src/utun_instance.h"
|
||||
#include "../src/routing.h"
|
||||
#include "../src/tun_if.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
|
||||
#define ECHO_PORT 19991
|
||||
#define TEST_TIMEOUT_MS 8000
|
||||
#define PAYLOAD_SIZE 64
|
||||
|
||||
static char temp_dir[] = "/tmp/utun_test_XXXXXX";
|
||||
static char cfg_path[256];
|
||||
static struct UTUN_INSTANCE* inst = NULL;
|
||||
static struct UASYNC* ua = NULL;
|
||||
static int g_ok = 0, g_done = 0;
|
||||
static void* g_mon_id = NULL;
|
||||
static pid_t echo_pid = 0;
|
||||
static uint8_t send_buf[PAYLOAD_SIZE], recv_buf[PAYLOAD_SIZE];
|
||||
static int connected_ok = 0;
|
||||
static uint64_t stream_id = 1;
|
||||
|
||||
static const char* cfg =
|
||||
"[global]\n"
|
||||
"my_node_id=0x1111111111111111\n"
|
||||
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
|
||||
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
|
||||
"tun_ip=10.99.0.1/24\n"
|
||||
"tun_ifname=tun99\n"
|
||||
"[remote_proxy]\n"
|
||||
"enabled=yes\n";
|
||||
|
||||
static void echo_server(void) {
|
||||
int srv = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (srv < 0) _exit(1);
|
||||
int opt = 1; setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
struct sockaddr_in addr = {.sin_family = AF_INET, .sin_port = htons(ECHO_PORT)};
|
||||
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
if (bind(srv, (struct sockaddr*)&addr, sizeof(addr)) < 0 || listen(srv, 1) < 0) { close(srv); _exit(1); }
|
||||
int cli = accept(srv, NULL, NULL);
|
||||
if (cli < 0) { close(srv); _exit(1); }
|
||||
uint8_t buf[8192]; ssize_t n;
|
||||
while ((n = recv(cli, buf, sizeof(buf), 0)) > 0) {
|
||||
ssize_t sent = 0;
|
||||
while (sent < n) { ssize_t s = send(cli, buf + sent, n - sent, 0); if (s < 0) goto done; sent += s; }
|
||||
}
|
||||
done: close(cli); close(srv);
|
||||
}
|
||||
|
||||
static void test_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
struct UTUN_INSTANCE* i = conn ? conn->instance : inst;
|
||||
if (!i || !entry || entry->dgram == NULL || entry->len < TCP_PROXY_HDR_SIZE) {
|
||||
if (entry) { queue_entry_free(entry); queue_dgram_free(entry); } return;
|
||||
}
|
||||
uint8_t subcmd = entry->dgram[1];
|
||||
uint64_t sid = 0; memcpy(&sid, entry->dgram + 2, 8);
|
||||
uint64_t src = conn ? conn->peer_node_id : i->node_id;
|
||||
if (subcmd == TCP_PROXY_SUBCMD_CONNECT && sid == stream_id) { remote_proxy_handle_connect(i, entry, sid, src); return; }
|
||||
if (sid != stream_id) { queue_entry_free(entry); queue_dgram_free(entry); return; }
|
||||
if (subcmd == TCP_PROXY_SUBCMD_CONNECTED) {
|
||||
uint8_t status = (entry->len >= TCP_PROXY_CONNECTED_HDR_SIZE) ? entry->dgram[TCP_PROXY_HDR_SIZE + 2] : 1;
|
||||
connected_ok = (status == TCP_PROXY_CONNECTED_OK) ? 1 : -1;
|
||||
}
|
||||
queue_entry_free(entry); queue_dgram_free(entry);
|
||||
}
|
||||
|
||||
static void monitor(void* arg) {
|
||||
(void)arg;
|
||||
if (g_done) { g_mon_id = NULL; return; }
|
||||
static int phase = 0;
|
||||
if (phase == 0) {
|
||||
phase = 1;
|
||||
etcp_router_bind(inst, ETCP_ID_TCP_PROXY, test_handler);
|
||||
uint32_t ip = inet_addr("127.0.0.1");
|
||||
uint16_t port = htons(ECHO_PORT);
|
||||
uint8_t payload[6]; memcpy(payload, &ip, 4); memcpy(payload + 4, &port, 2);
|
||||
struct ll_entry* e = queue_entry_new(0);
|
||||
if (e) {
|
||||
e->dgram = u_malloc(TCP_PROXY_HDR_SIZE + 6);
|
||||
e->dgram[0] = ETCP_ID_TCP_PROXY; e->dgram[1] = TCP_PROXY_SUBCMD_CONNECT;
|
||||
memcpy(e->dgram + 2, &stream_id, 8);
|
||||
memcpy(e->dgram + TCP_PROXY_HDR_SIZE, payload, 6);
|
||||
e->len = TCP_PROXY_HDR_SIZE + 6;
|
||||
etcp_route_send(inst, inst->node_id, e);
|
||||
}
|
||||
}
|
||||
if (phase == 1 && (connected_ok == 1 || connected_ok == -1)) {
|
||||
if (connected_ok != 1) { printf("[FAIL] connect refused\n"); g_done = -1; return; }
|
||||
// Get proxy conn, detach from uasync so we can use socket directly
|
||||
struct remote_proxy_conn* rc = remote_proxy_find_conn(&inst->remote_proxy, stream_id);
|
||||
if (!rc || rc->sock == SOCKET_INVALID) { printf("[FAIL] no proxy conn\n"); g_done = -1; return; }
|
||||
uasync_remove_socket_t(rc->ua, rc->sock); rc->read_id = NULL;
|
||||
// Send data through OS socket directly
|
||||
for (int i = 0; i < PAYLOAD_SIZE; i++) send_buf[i] = (uint8_t)(rand() & 0xFF);
|
||||
ssize_t n = send(rc->sock, send_buf, PAYLOAD_SIZE, MSG_NOSIGNAL);
|
||||
if (n != PAYLOAD_SIZE) { printf("[FAIL] send %zd\n", n); g_done = -1; return; }
|
||||
phase = 2;
|
||||
}
|
||||
if (phase == 2) {
|
||||
struct remote_proxy_conn* rc = remote_proxy_find_conn(&inst->remote_proxy, stream_id);
|
||||
if (rc && rc->sock != SOCKET_INVALID) {
|
||||
ssize_t n = recv(rc->sock, recv_buf, PAYLOAD_SIZE, 0);
|
||||
if (n > 0) {
|
||||
if ((size_t)n == PAYLOAD_SIZE && memcmp(send_buf, recv_buf, PAYLOAD_SIZE) == 0) {
|
||||
printf("[PASS] test_remote_proxy — %zd bytes echoed\n", n); g_ok = 1;
|
||||
} else {
|
||||
printf("[FAIL] echo mismatch: got %zd expected %d\n", n, PAYLOAD_SIZE);
|
||||
}
|
||||
g_done = 1; return;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_mon_id = uasync_set_timeout(ua, 10, NULL, monitor, "mon");
|
||||
}
|
||||
|
||||
static void timeout_cb(void* arg) {
|
||||
(void)arg;
|
||||
if (!g_done) { printf("[FAIL] timeout: conn=%d\n", connected_ok); g_done = -1; }
|
||||
if (g_mon_id) { uasync_cancel_timeout(ua, g_mon_id); g_mon_id = NULL; }
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
echo_pid = fork();
|
||||
if (echo_pid == 0) { echo_server(); _exit(0); }
|
||||
if (echo_pid < 0) { perror("fork"); return 1; }
|
||||
usleep(100000);
|
||||
|
||||
if (test_mkdtemp(temp_dir) != 0) { kill(echo_pid, SIGTERM); waitpid(echo_pid,NULL,0); return 1; }
|
||||
snprintf(cfg_path, sizeof(cfg_path), "%s/test.conf", temp_dir);
|
||||
FILE* f = fopen(cfg_path, "w");
|
||||
if (!f) { kill(echo_pid, SIGTERM); waitpid(echo_pid,NULL,0); test_rmdir(temp_dir); return 1; }
|
||||
fprintf(f, "%s", cfg); fclose(f);
|
||||
|
||||
printf("=== test_remote_proxy ===\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));
|
||||
|
||||
ua = uasync_create();
|
||||
inst = utun_instance_create(ua, cfg_path);
|
||||
if (!inst) { printf("[FAIL] instance create\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, 100);
|
||||
if (to_id) uasync_cancel_timeout(ua, to_id);
|
||||
|
||||
done:
|
||||
if (g_mon_id) uasync_cancel_timeout(ua, g_mon_id);
|
||||
if (inst) { inst->running = 0; utun_instance_destroy(inst); }
|
||||
if (ua) uasync_destroy(ua, 0);
|
||||
test_unlink(cfg_path); test_rmdir(temp_dir);
|
||||
if (echo_pid) { kill(echo_pid, SIGTERM); waitpid(echo_pid, NULL, 0); }
|
||||
return g_ok ? 0 : 1;
|
||||
}
|
||||
Reference in new issue
Block a user