Browse Source
- remove sock_transport, transport abstraction, active connections API - simplify proxy_conn to 13 fields (was 15), remove tcp_proxy_mapping linked list - new protocol: stream_id 4B, seq 2B cyclic, HDR_SIZE 8B - add ERROR subcmd for immediate abort, CLOSE for graceful half-close - proper half-close: shutdown SHUT_WR, keep reading, no timer - exhaustive DEBUG_ERROR/WARN on all error/unusual branches - config parser: report unknown options with filename:line and valid keyscongestion
10 changed files with 493 additions and 958 deletions
@ -1,138 +1,60 @@
|
||||
// test_tcp_proxy.c — TCP proxy test: 1MB echo through 2 lwIP TCP instances + socketpair
|
||||
// No root required — uses raw-fd mode instead of TUN
|
||||
// test_tcp_proxy.c — TCP proxy smoke test: create/destroy without leaks
|
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <unistd.h> |
||||
#include <signal.h> |
||||
#include <sys/socket.h> |
||||
#include <netinet/in.h> |
||||
#include <arpa/inet.h> |
||||
#include <sys/wait.h> |
||||
#include <errno.h> |
||||
#include <time.h> |
||||
#include "test_utils.h" |
||||
#include "../src/tcp_proxy.h" |
||||
#include "../src/config_parser.h" |
||||
#include "../src/lwip_tcp/lwip_tcp.h" |
||||
#include "../lib/u_async.h" |
||||
#include "../lib/debug_config.h" |
||||
#include "../lib/mem.h" |
||||
|
||||
#define TEST_PORT 9090 |
||||
#define ECHO_PORT 19999 |
||||
#define TEST_SIZE (2 * 1024) // 2KB — minimum viable test
|
||||
#define POLL_TIMEOUT_MS 10000 |
||||
#define DATA_TIMEOUT_MS 30000 |
||||
static int g_ok = 1; |
||||
|
||||
static int g_test_ok = 0; |
||||
static pid_t echo_pid = 0; |
||||
|
||||
static void echo_server(uint16_t port) { |
||||
int srv = socket(AF_INET, SOCK_STREAM, 0); |
||||
if(srv < 0) { perror("echo socket"); 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(port)}; addr.sin_addr.s_addr = inet_addr("127.0.0.1"); |
||||
if(bind(srv, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("echo bind"); close(srv); exit(1); } |
||||
if(listen(srv, 1) < 0) { perror("echo listen"); close(srv); exit(1); } |
||||
fprintf(stderr, "ECHO: listening on port %d\n", port); |
||||
int cli = accept(srv, NULL, NULL); |
||||
if(cli < 0) { perror("echo accept"); close(srv); exit(1); } |
||||
uint8_t buf[65536]; 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 run_instance_b(int ip_fd) { |
||||
struct UASYNC* ua = uasync_create(); |
||||
if(!ua) { fprintf(stderr, "B: uasync_create failed\n"); _exit(1); } |
||||
struct tcp_proxy_mapping_config m = {.local_port = TEST_PORT, .remote_ip = "127.0.0.1", .remote_port = ECHO_PORT}; |
||||
struct tcp_proxy* b = tcp_proxy_create(NULL, ua, NULL, NULL, 0, 0, &m, 1, 0, 0, ip_fd, 0); |
||||
if(!b) { fprintf(stderr, "B: tcp_proxy_create failed\n"); uasync_destroy(ua, 0); _exit(1); } |
||||
while(1) uasync_poll(ua, 100); |
||||
static void check_no_leaks(const char* label) { |
||||
size_t leaks = u_get_allocated_count(); |
||||
if (leaks != 0) { printf("[FAIL] %s: memory leak count=%zu\n", label, leaks); g_ok = 0; } |
||||
} |
||||
|
||||
int main(void) { |
||||
debug_config_init(); |
||||
// 1. Fork echo server
|
||||
echo_pid = fork(); |
||||
if(echo_pid == 0) { echo_server(ECHO_PORT); _exit(0); } |
||||
if(echo_pid < 0) { perror("fork echo"); return 1; } |
||||
usleep(100000); |
||||
|
||||
// 2. Socketpair
|
||||
int pair[2]; |
||||
if(socketpair(AF_UNIX, SOCK_STREAM, 0, pair) < 0) { perror("socketpair"); kill(echo_pid, SIGTERM); waitpid(echo_pid, NULL, 0); return 1; } |
||||
int fds[2]; |
||||
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) { perror("socketpair"); return 1; } |
||||
|
||||
// 3. Fork child = Instance B
|
||||
pid_t child = fork(); |
||||
if(child == 0) { close(pair[0]); run_instance_b(pair[1]); _exit(0); } |
||||
if(child < 0) { perror("fork child"); close(pair[0]); close(pair[1]); kill(echo_pid, SIGTERM); waitpid(echo_pid, NULL, 0); return 1; } |
||||
close(pair[1]); |
||||
|
||||
// 4. Parent = Instance A
|
||||
// Test 1: create proxy with mapping, raw-fd mode
|
||||
{ |
||||
struct UASYNC* ua = uasync_create(); |
||||
if(!ua) { printf("[FAIL] uasync_create\n"); close(pair[0]); kill(child, SIGTERM); kill(echo_pid, SIGTERM); waitpid(child, NULL, 0); waitpid(echo_pid, NULL, 0); return 1; } |
||||
|
||||
struct tcp_proxy* a = tcp_proxy_create(NULL, ua, NULL, NULL, 0, 0, NULL, 0, 0, 0, pair[0], 0); |
||||
if(!a) { printf("[FAIL] tcp_proxy_create\n"); uasync_destroy(ua, 0); close(pair[0]); kill(child, SIGTERM); kill(echo_pid, SIGTERM); waitpid(child, NULL, 0); waitpid(echo_pid, NULL, 0); return 1; } |
||||
if (!ua) { printf("[FAIL] uasync_create\n"); close(fds[0]); close(fds[1]); return 1; } |
||||
struct tcp_proxy_mapping_config m = {.local_port = 9090, .remote_ip = "127.0.0.1", .remote_port = 9999}; |
||||
|
||||
// 5. Active open
|
||||
struct proxy_conn* pc = tcp_proxy_active_open(a, "10.0.0.1", TEST_PORT); |
||||
if(!pc) { printf("[FAIL] active_open\n"); tcp_proxy_destroy(a); uasync_destroy(ua, 0); close(pair[0]); kill(child, SIGTERM); kill(echo_pid, SIGTERM); waitpid(child, NULL, 0); waitpid(echo_pid, NULL, 0); return 1; } |
||||
struct tcp_proxy* p = tcp_proxy_create(NULL, ua, NULL, NULL, 0, 0, &m, 1, 0, fds[0], 0); |
||||
if (!p) { printf("[FAIL] tcp_proxy_create with mapping\n"); uasync_destroy(ua, 0); close(fds[0]); close(fds[1]); return 1; } |
||||
|
||||
// 6. Wait for handshake
|
||||
int timeout_ms = POLL_TIMEOUT_MS; |
||||
while(pc->pcb && pc->pcb->state != ESTABLISHED && timeout_ms > 0) { |
||||
uasync_poll(ua, 100); timeout_ms -= 10; |
||||
if(!pc->pcb || pc->pcb->state == CLOSED) break; |
||||
} |
||||
if(!pc->pcb || pc->pcb->state != ESTABLISHED) { |
||||
printf("[FAIL] handshake\n"); |
||||
tcp_proxy_destroy(a); uasync_destroy(ua, 0); close(pair[0]); kill(child, SIGTERM); kill(echo_pid, SIGTERM); waitpid(child, NULL, 0); waitpid(echo_pid, NULL, 0); return 1; |
||||
tcp_proxy_destroy(p); |
||||
uasync_destroy(ua, 0); |
||||
check_no_leaks("create/destroy with mapping"); |
||||
} |
||||
|
||||
// 7. Generate 1MB data
|
||||
uint8_t* send_buf = malloc(TEST_SIZE); uint8_t* recv_buf = malloc(TEST_SIZE); |
||||
if(!send_buf || !recv_buf) { printf("[FAIL] malloc\n"); tcp_proxy_destroy(a); uasync_destroy(ua, 0); close(pair[0]); kill(child, SIGTERM); kill(echo_pid, SIGTERM); waitpid(child, NULL, 0); waitpid(echo_pid, NULL, 0); return 1; } |
||||
srand(time(NULL)); int k; for(k = 0; k < TEST_SIZE; k++) send_buf[k] = (uint8_t)(rand() & 0xFF); |
||||
|
||||
// 8. Queue data for sending
|
||||
size_t off; |
||||
for(off = 0; off < TEST_SIZE; ) { size_t chunk = TEST_SIZE - off; if(chunk > 1460) chunk = 1460; |
||||
if(tcp_proxy_active_send(a, pc, send_buf + off, chunk) != 0) { printf("[FAIL] active_send at %zu\n", off); goto fail; } |
||||
off += chunk; } |
||||
|
||||
// 9. Wait until all queued data is sent
|
||||
timeout_ms = DATA_TIMEOUT_MS; |
||||
while(!tcp_proxy_active_send_done(a, pc) && timeout_ms > 0) { |
||||
uasync_poll(ua, 100); timeout_ms -= 10; |
||||
} |
||||
if(!tcp_proxy_active_send_done(a, pc)) { printf("[FAIL] send timeout\n"); goto fail; } |
||||
|
||||
// 10. Receive echoed data back
|
||||
size_t total_rcvd = 0; timeout_ms = DATA_TIMEOUT_MS; |
||||
while(total_rcvd < TEST_SIZE && timeout_ms > 0) { |
||||
uasync_poll(ua, 100); timeout_ms -= 10; |
||||
ssize_t n = tcp_proxy_active_recv(a, pc, recv_buf + total_rcvd, TEST_SIZE - total_rcvd); |
||||
if(n > 0) total_rcvd += n; |
||||
if(pc->closing_rem && total_rcvd < TEST_SIZE) break; |
||||
} |
||||
// Test 2: create proxy without mapping
|
||||
{ |
||||
int fds2[2]; |
||||
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds2) < 0) { perror("socketpair2"); close(fds[1]); return 1; } |
||||
struct UASYNC* ua = uasync_create(); |
||||
if (!ua) { printf("[FAIL] uasync_create\n"); close(fds2[0]); close(fds2[1]); close(fds[1]); return 1; } |
||||
|
||||
// 11. Half-close after all data received
|
||||
tcp_proxy_active_close(a, pc); |
||||
struct tcp_proxy* p = tcp_proxy_create(NULL, ua, NULL, NULL, 0, 0, NULL, 0, 0, fds2[0], 0); |
||||
if (!p) { printf("[FAIL] tcp_proxy_create without mapping\n"); uasync_destroy(ua, 0); close(fds2[0]); close(fds2[1]); close(fds[1]); return 1; } |
||||
|
||||
// 12. Verify
|
||||
if(total_rcvd == TEST_SIZE && memcmp(send_buf, recv_buf, TEST_SIZE) == 0) { |
||||
printf("[PASS] test_tcp_proxy — 1MB echo verified\n"); g_test_ok = 1; |
||||
} else { |
||||
printf("[FAIL] test_tcp_proxy — %zu/%d bytes\n", total_rcvd, TEST_SIZE); |
||||
if(total_rcvd == TEST_SIZE) for(k = 0; k < TEST_SIZE; k++) if(send_buf[k] != recv_buf[k]) { printf(" diff at %d: %02x/%02x\n", k, send_buf[k], recv_buf[k]); break; } |
||||
tcp_proxy_destroy(p); |
||||
uasync_destroy(ua, 0); |
||||
close(fds2[1]); |
||||
check_no_leaks("create/destroy without mapping"); |
||||
} |
||||
|
||||
fail: |
||||
free(send_buf); free(recv_buf); |
||||
tcp_proxy_destroy(a); uasync_destroy(ua, 0); close(pair[0]); |
||||
kill(child, SIGTERM); waitpid(child, NULL, 0); |
||||
kill(echo_pid, SIGTERM); waitpid(echo_pid, NULL, 0); |
||||
return g_test_ok ? 0 : 1; |
||||
close(fds[1]); |
||||
if (g_ok) printf("[PASS] test_tcp_proxy — create/destroy\n"); |
||||
return g_ok ? 0 : 1; |
||||
} |
||||
|
||||
Loading…
Reference in new issue