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)
176 lines
7.2 KiB
C
176 lines
7.2 KiB
C
// 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;
|
|
}
|