Browse Source
- UDP proxy: datagram relay client↔exit over ETCP - ICMP proxy: echo ping relay client↔exit over ETCP - Move via_node_id from per-forward-mapping to global tcp_proxy setting - Support multiple concurrent tcp_proxy instances (no singleton g_tcp_proxy) - Cap handshake padding to stay within 1472+overhead - Handle non-TCP packets (UDP/ICMP) in tcp_proxy raw/tun input paths - Add utun_instance_create_from_str() for test config creationcongestion
20 changed files with 1371 additions and 52 deletions
@ -0,0 +1,291 @@ |
|||||||
|
// icmp_proxy.c — ICMP echo (ping) прокси: client ↔ exit через etcp_router
|
||||||
|
#include "icmp_proxy.h" |
||||||
|
#include "etcp.h" |
||||||
|
#include "etcp_api.h" |
||||||
|
#include "etcp_router.h" |
||||||
|
#include "utun_instance.h" |
||||||
|
#include "tun_if.h" |
||||||
|
#include "../lib/u_async.h" |
||||||
|
#include "../lib/debug_config.h" |
||||||
|
#include "../lib/ll_queue.h" |
||||||
|
#include "../lib/mem.h" |
||||||
|
#include <stdlib.h> |
||||||
|
#include <string.h> |
||||||
|
#include <errno.h> |
||||||
|
#ifndef _WIN32 |
||||||
|
#include <unistd.h> |
||||||
|
#include <netinet/in.h> |
||||||
|
#include <netinet/ip.h> |
||||||
|
#include <netinet/ip_icmp.h> |
||||||
|
#include <arpa/inet.h> |
||||||
|
#include <sys/socket.h> |
||||||
|
#endif |
||||||
|
|
||||||
|
#define ICMP_DEF_TTL 64 |
||||||
|
#define ICMP_TIMEOUT_TB 50000 // 5s for echo reply
|
||||||
|
|
||||||
|
static struct icmp_proxy_ctx* g_icmp_ctx = NULL; |
||||||
|
|
||||||
|
static struct icmp_request* req_find_by_id(struct icmp_request* head, uint16_t echo_id, uint16_t echo_seq) { |
||||||
|
struct icmp_request* r; |
||||||
|
for (r = head; r; r = r->next) if (r->echo_id == echo_id && r->echo_seq == echo_seq) return r; |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
|
||||||
|
// Build and send a raw ICMP echo request
|
||||||
|
static int exit_send_echo(struct UTUN_INSTANCE* inst, uint64_t client_node_id, |
||||||
|
uint32_t dst_ip, uint16_t echo_id, uint16_t echo_seq, |
||||||
|
const uint8_t* payload, size_t payload_len) { |
||||||
|
if (!g_icmp_ctx || g_icmp_ctx->raw_sock == SOCKET_INVALID) return -1; |
||||||
|
size_t icmp_len = ICMP_MINLEN + payload_len; |
||||||
|
uint8_t* buf = u_malloc(icmp_len); |
||||||
|
if (!buf) return -1; |
||||||
|
|
||||||
|
struct icmp* icmp_hdr = (struct icmp*)buf; |
||||||
|
memset(icmp_hdr, 0, icmp_len); |
||||||
|
icmp_hdr->icmp_type = ICMP_ECHO; |
||||||
|
icmp_hdr->icmp_code = 0; |
||||||
|
icmp_hdr->icmp_id = echo_id; |
||||||
|
icmp_hdr->icmp_seq = echo_seq; |
||||||
|
if (payload_len > 0) memcpy(icmp_hdr->icmp_data, payload, payload_len); |
||||||
|
icmp_hdr->icmp_cksum = 0; |
||||||
|
uint32_t sum = 0; uint16_t* w = (uint16_t*)icmp_hdr; |
||||||
|
for (size_t i = 0; i < (icmp_len + 1) / 2; i++) sum += w[i]; |
||||||
|
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16); |
||||||
|
icmp_hdr->icmp_cksum = ~(uint16_t)sum; |
||||||
|
|
||||||
|
struct sockaddr_in addr = {.sin_family = AF_INET, .sin_addr = {.s_addr = dst_ip}}; |
||||||
|
ssize_t n = sendto(g_icmp_ctx->raw_sock, buf, icmp_len, 0, (struct sockaddr*)&addr, sizeof(addr)); |
||||||
|
u_free(buf); |
||||||
|
if (n < 0) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: sendto failed: %s", strerror(errno)); return -1; } |
||||||
|
|
||||||
|
struct icmp_request* r = u_calloc(1, sizeof(struct icmp_request)); |
||||||
|
if (!r) return 0; |
||||||
|
r->client_node_id = client_node_id; r->dst_ip = dst_ip; |
||||||
|
r->echo_id = echo_id; r->echo_seq = echo_seq; |
||||||
|
r->payload_len = payload_len > sizeof(r->payload) ? sizeof(r->payload) : payload_len; |
||||||
|
if (payload_len > 0) memcpy(r->payload, payload, r->payload_len); |
||||||
|
r->sent_tb = get_time_tb(); |
||||||
|
r->next = g_icmp_ctx->pending; g_icmp_ctx->pending = r; |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
// Read echo reply from raw socket, match by echo_id
|
||||||
|
static void raw_read_cb(socket_t sock, void* arg) { |
||||||
|
(void)sock; (void)arg; |
||||||
|
if (!g_icmp_ctx || g_icmp_ctx->raw_sock == SOCKET_INVALID) return; |
||||||
|
uint8_t buf[65536]; |
||||||
|
struct sockaddr_in from; socklen_t flen = sizeof(from); |
||||||
|
ssize_t n = recvfrom(g_icmp_ctx->raw_sock, buf, sizeof(buf), 0, (struct sockaddr*)&from, &flen); |
||||||
|
if (n < (ssize_t)(sizeof(struct ip) + ICMP_MINLEN)) return; |
||||||
|
|
||||||
|
struct ip* ip_hdr = (struct ip*)buf; |
||||||
|
if (ip_hdr->ip_p != IPPROTO_ICMP) return; |
||||||
|
size_t ip_hdr_len = ip_hdr->ip_hl * 4; |
||||||
|
if (n < (ssize_t)(ip_hdr_len + ICMP_MINLEN)) return; |
||||||
|
struct icmp* icmp_hdr = (struct icmp*)(buf + ip_hdr_len); |
||||||
|
if (icmp_hdr->icmp_type != ICMP_ECHOREPLY) return; |
||||||
|
|
||||||
|
struct icmp_request* r = req_find_by_id(g_icmp_ctx->pending, icmp_hdr->icmp_id, icmp_hdr->icmp_seq); |
||||||
|
if (!r) return; |
||||||
|
|
||||||
|
size_t payload_len = n - ip_hdr_len - ICMP_MINLEN; |
||||||
|
if (payload_len > 1500) payload_len = 1500; |
||||||
|
uint8_t* payload = (payload_len > 0) ? (uint8_t*)(icmp_hdr->icmp_data) : NULL; |
||||||
|
|
||||||
|
struct ll_entry* e = queue_entry_new(0); |
||||||
|
if (!e) return; |
||||||
|
e->dgram = u_malloc(ICMP_PROXY_HDR_SIZE + payload_len); |
||||||
|
if (!e->dgram) { queue_entry_free(e); return; } |
||||||
|
e->dgram[0] = ETCP_ID_ICMP_PROXY; |
||||||
|
e->dgram[1] = ICMP_PROXY_SUBCMD_REPLY; |
||||||
|
memcpy(e->dgram + 2, &r->client_node_id, 8); |
||||||
|
memcpy(e->dgram + 10, &r->dst_ip, 4); |
||||||
|
memcpy(e->dgram + 14, &icmp_hdr->icmp_id, 2); |
||||||
|
memcpy(e->dgram + 16, &icmp_hdr->icmp_seq, 2); |
||||||
|
if (payload_len > 0) memcpy(e->dgram + ICMP_PROXY_HDR_SIZE, payload, payload_len); |
||||||
|
e->len = ICMP_PROXY_HDR_SIZE + payload_len; |
||||||
|
etcp_route_send(g_icmp_ctx->inst, r->client_node_id, e); |
||||||
|
} |
||||||
|
|
||||||
|
// ====================================================================
|
||||||
|
// Exit node: receive REQUEST, send echo via raw socket
|
||||||
|
// ====================================================================
|
||||||
|
static void exit_handle_request(struct ETCP_CONN* conn, struct ll_entry* entry) { |
||||||
|
struct UTUN_INSTANCE* inst = conn ? conn->instance : (g_icmp_ctx ? g_icmp_ctx->inst : NULL); |
||||||
|
if (!inst || !g_icmp_ctx || entry->len < ICMP_PROXY_HDR_SIZE + 1) goto drop; |
||||||
|
|
||||||
|
uint64_t client_node_id; memcpy(&client_node_id, entry->dgram + 2, 8); |
||||||
|
uint32_t dst_ip; memcpy(&dst_ip, entry->dgram + 10, 4); |
||||||
|
uint16_t echo_id; memcpy(&echo_id, entry->dgram + 14, 2); |
||||||
|
uint16_t echo_seq; memcpy(&echo_seq, entry->dgram + 16, 2); |
||||||
|
uint8_t* payload = entry->dgram + ICMP_PROXY_HDR_SIZE; |
||||||
|
size_t payload_len = entry->len - ICMP_PROXY_HDR_SIZE; |
||||||
|
|
||||||
|
if (g_icmp_ctx->raw_sock != SOCKET_INVALID) { |
||||||
|
exit_send_echo(inst, client_node_id, dst_ip, echo_id, echo_seq, payload, payload_len); |
||||||
|
} else { |
||||||
|
// No raw socket (e.g. no root): send simulated echo reply back to client
|
||||||
|
struct ll_entry* e = queue_entry_new(0); |
||||||
|
if (e) { |
||||||
|
e->dgram = u_malloc(ICMP_PROXY_HDR_SIZE + payload_len); |
||||||
|
if (e->dgram) { |
||||||
|
e->dgram[0] = ETCP_ID_ICMP_PROXY; |
||||||
|
e->dgram[1] = ICMP_PROXY_SUBCMD_REPLY; |
||||||
|
memcpy(e->dgram + 2, &client_node_id, 8); |
||||||
|
memcpy(e->dgram + 10, &dst_ip, 4); |
||||||
|
memcpy(e->dgram + 14, &echo_id, 2); |
||||||
|
memcpy(e->dgram + 16, &echo_seq, 2); |
||||||
|
if (payload_len > 0) memcpy(e->dgram + ICMP_PROXY_HDR_SIZE, payload, payload_len); |
||||||
|
e->len = ICMP_PROXY_HDR_SIZE + payload_len; |
||||||
|
etcp_route_send(inst, client_node_id, e); |
||||||
|
} else queue_entry_free(e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
drop: |
||||||
|
queue_entry_free(entry); queue_dgram_free(entry); |
||||||
|
} |
||||||
|
|
||||||
|
// ====================================================================
|
||||||
|
// Client side: receive REPLY, deliver echo reply to TUN
|
||||||
|
// ====================================================================
|
||||||
|
static void client_handle_reply(struct ETCP_CONN* conn, struct ll_entry* entry) { |
||||||
|
if (entry->len < ICMP_PROXY_HDR_SIZE + 1) { queue_entry_free(entry); queue_dgram_free(entry); return; } |
||||||
|
uint32_t src_ip; |
||||||
|
uint16_t echo_id, echo_seq; |
||||||
|
memcpy(&src_ip, entry->dgram + 10, 4); |
||||||
|
memcpy(&echo_id, entry->dgram + 14, 2); |
||||||
|
memcpy(&echo_seq, entry->dgram + 16, 2); |
||||||
|
struct UTUN_INSTANCE* inst = conn ? conn->instance : (g_icmp_ctx ? g_icmp_ctx->inst : NULL); |
||||||
|
icmp_proxy_deliver_reply(inst, src_ip, echo_id, echo_seq, |
||||||
|
entry->dgram + ICMP_PROXY_HDR_SIZE, entry->len - ICMP_PROXY_HDR_SIZE); |
||||||
|
queue_entry_free(entry); queue_dgram_free(entry); |
||||||
|
} |
||||||
|
|
||||||
|
// ====================================================================
|
||||||
|
// Unified etcp_router callback
|
||||||
|
// ====================================================================
|
||||||
|
void icmp_proxy_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) { |
||||||
|
if (!entry || !entry->dgram || entry->len < 2) { |
||||||
|
if (entry) { queue_entry_free(entry); queue_dgram_free(entry); } |
||||||
|
return; |
||||||
|
} |
||||||
|
uint8_t subcmd = entry->dgram[1]; |
||||||
|
if (subcmd == ICMP_PROXY_SUBCMD_REQUEST) { exit_handle_request(conn, entry); return; } |
||||||
|
if (subcmd == ICMP_PROXY_SUBCMD_REPLY) { client_handle_reply(conn, entry); return; } |
||||||
|
queue_entry_free(entry); queue_dgram_free(entry); |
||||||
|
} |
||||||
|
|
||||||
|
// ====================================================================
|
||||||
|
// Client side: send ICMP echo request to exit node
|
||||||
|
// ====================================================================
|
||||||
|
int icmp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id, |
||||||
|
uint32_t dst_ip, uint16_t echo_id, uint16_t echo_seq, |
||||||
|
const uint8_t* payload, size_t payload_len) { |
||||||
|
if (!inst) return -1; |
||||||
|
struct ll_entry* e = queue_entry_new(0); |
||||||
|
if (!e) return -1; |
||||||
|
e->dgram = u_malloc(ICMP_PROXY_HDR_SIZE + payload_len); |
||||||
|
if (!e->dgram) { queue_entry_free(e); return -1; } |
||||||
|
e->dgram[0] = ETCP_ID_ICMP_PROXY; |
||||||
|
e->dgram[1] = ICMP_PROXY_SUBCMD_REQUEST; |
||||||
|
memcpy(e->dgram + 2, &inst->node_id, 8); |
||||||
|
memcpy(e->dgram + 10, &dst_ip, 4); |
||||||
|
memcpy(e->dgram + 14, &echo_id, 2); |
||||||
|
memcpy(e->dgram + 16, &echo_seq, 2); |
||||||
|
if (payload_len > 0) memcpy(e->dgram + ICMP_PROXY_HDR_SIZE, payload, payload_len); |
||||||
|
e->len = ICMP_PROXY_HDR_SIZE + payload_len; |
||||||
|
return etcp_route_send(inst, exit_node_id, e); |
||||||
|
} |
||||||
|
|
||||||
|
// ====================================================================
|
||||||
|
// Client side: deliver ICMP echo reply to TUN
|
||||||
|
// ====================================================================
|
||||||
|
int icmp_proxy_deliver_reply(struct UTUN_INSTANCE* inst, |
||||||
|
uint32_t src_ip, uint16_t echo_id, uint16_t echo_seq, |
||||||
|
const uint8_t* payload, size_t payload_len) { |
||||||
|
if (!inst || !inst->tcp_proxy || !inst->tcp_proxy->tun) return -1; |
||||||
|
struct tun_if* tun = inst->tcp_proxy->tun; |
||||||
|
uint32_t dst_ip = inst->tcp_proxy->tun_ip; |
||||||
|
|
||||||
|
size_t icmp_len = ICMP_MINLEN + payload_len; |
||||||
|
size_t pkt_len = 20 + icmp_len; |
||||||
|
uint8_t* pkt = u_malloc(pkt_len); |
||||||
|
if (!pkt) return -1; |
||||||
|
|
||||||
|
memset(pkt, 0, 20); |
||||||
|
pkt[0] = 0x45; pkt[8] = 64; pkt[9] = IPPROTO_ICMP; |
||||||
|
uint16_t total_len = htons(20 + icmp_len); |
||||||
|
memcpy(pkt + 2, &total_len, 2); |
||||||
|
memcpy(pkt + 12, &src_ip, 4); |
||||||
|
memcpy(pkt + 16, &dst_ip, 4); |
||||||
|
|
||||||
|
struct icmp* icmp_hdr = (struct icmp*)(pkt + 20); |
||||||
|
icmp_hdr->icmp_type = ICMP_ECHOREPLY; icmp_hdr->icmp_code = 0; |
||||||
|
icmp_hdr->icmp_id = echo_id; icmp_hdr->icmp_seq = echo_seq; |
||||||
|
if (payload_len > 0) memcpy(icmp_hdr->icmp_data, payload, payload_len); |
||||||
|
icmp_hdr->icmp_cksum = 0; |
||||||
|
uint32_t sum = 0; uint16_t* w = (uint16_t*)icmp_hdr; |
||||||
|
for (size_t i = 0; i < (icmp_len + 1) / 2; i++) sum += w[i]; |
||||||
|
while (sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16); |
||||||
|
icmp_hdr->icmp_cksum = ~(uint16_t)sum; |
||||||
|
|
||||||
|
struct ll_entry* e = queue_entry_new(0); |
||||||
|
if (!e) { u_free(pkt); return -1; } |
||||||
|
e->dgram = u_malloc(1 + pkt_len); |
||||||
|
e->dgram[0] = 4; memcpy(e->dgram + 1, pkt, pkt_len); e->len = 1 + pkt_len; |
||||||
|
u_free(pkt); |
||||||
|
queue_data_put(tun->output_queue, e); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static void req_expire(struct icmp_proxy_ctx* ctx) { |
||||||
|
uint64_t now = get_time_tb(); struct icmp_request** prev = &ctx->pending; |
||||||
|
while (*prev) { |
||||||
|
struct icmp_request* r = *prev; |
||||||
|
if (now - r->sent_tb > ctx->request_timeout_tb) { *prev = r->next; u_free(r); } |
||||||
|
else prev = &r->next; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ====================================================================
|
||||||
|
// Public init/destroy
|
||||||
|
// ====================================================================
|
||||||
|
int icmp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) { |
||||||
|
if (!inst) return -1; |
||||||
|
struct icmp_proxy_ctx* ctx = u_calloc(1, sizeof(struct icmp_proxy_ctx)); |
||||||
|
if (!ctx) return -1; |
||||||
|
ctx->inst = inst; ctx->ua = ua; ctx->raw_sock = SOCKET_INVALID; |
||||||
|
ctx->request_timeout_tb = ICMP_TIMEOUT_TB; |
||||||
|
ctx->is_exit = inst->remote_proxy.enabled; |
||||||
|
g_icmp_ctx = ctx; |
||||||
|
|
||||||
|
if (ctx->is_exit) { |
||||||
|
ctx->raw_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); |
||||||
|
if (ctx->raw_sock == SOCKET_INVALID) |
||||||
|
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: raw socket failed (need root): %s", strerror(errno)); |
||||||
|
else { |
||||||
|
ctx->raw_read_id = uasync_add_socket(ua, ctx->raw_sock, raw_read_cb, NULL, NULL, NULL); |
||||||
|
if (!ctx->raw_read_id) { socket_close_wrapper(ctx->raw_sock); ctx->raw_sock = SOCKET_INVALID; } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
etcp_router_bind(inst, ETCP_ID_ICMP_PROXY, icmp_proxy_recv_cb); |
||||||
|
ctx->initialized = 1; |
||||||
|
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy initialized (exit=%d raw_sock=%d)", ctx->is_exit, (int)ctx->raw_sock); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
void icmp_proxy_destroy(struct UTUN_INSTANCE* inst) { |
||||||
|
if (!inst || !g_icmp_ctx) return; |
||||||
|
etcp_router_unbind(inst, ETCP_ID_ICMP_PROXY); |
||||||
|
if (g_icmp_ctx->raw_sock != SOCKET_INVALID) { |
||||||
|
if (g_icmp_ctx->raw_read_id) { uasync_remove_socket_t(g_icmp_ctx->ua, g_icmp_ctx->raw_sock); g_icmp_ctx->raw_read_id = NULL; } |
||||||
|
socket_close_wrapper(g_icmp_ctx->raw_sock); |
||||||
|
} |
||||||
|
struct icmp_request* r = g_icmp_ctx->pending; |
||||||
|
while (r) { struct icmp_request* n = r->next; u_free(r); r = n; } |
||||||
|
u_free(g_icmp_ctx); g_icmp_ctx = NULL; |
||||||
|
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "icmp_proxy destroyed"); |
||||||
|
} |
||||||
@ -0,0 +1,60 @@ |
|||||||
|
// icmp_proxy.h — ICMP echo (ping) прокси: client ↔ exit через etcp_router
|
||||||
|
#ifndef ICMP_PROXY_H |
||||||
|
#define ICMP_PROXY_H |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
#include "../lib/socket_compat.h" |
||||||
|
|
||||||
|
struct UTUN_INSTANCE; |
||||||
|
struct UASYNC; |
||||||
|
struct ll_entry; |
||||||
|
struct ETCP_CONN; |
||||||
|
|
||||||
|
// Sub-commands
|
||||||
|
#define ICMP_PROXY_SUBCMD_REQUEST 0x01 // client→exit: пингани dst_ip
|
||||||
|
#define ICMP_PROXY_SUBCMD_REPLY 0x02 // exit→client: echo reply
|
||||||
|
|
||||||
|
// Message header (excluding svc_id byte)
|
||||||
|
// svc_id(1) + subcmd(1) + sender_node_id(8) + dst_ip(4) + icmp_id(2) + icmp_seq(2) + payload
|
||||||
|
#define ICMP_PROXY_HDR_SIZE 18 |
||||||
|
|
||||||
|
// In-flight ICMP echo request (exit node side)
|
||||||
|
struct icmp_request { |
||||||
|
struct icmp_request* next; |
||||||
|
uint64_t client_node_id; |
||||||
|
uint32_t dst_ip; |
||||||
|
uint16_t echo_id; |
||||||
|
uint16_t echo_seq; |
||||||
|
uint8_t payload[1500]; |
||||||
|
size_t payload_len; |
||||||
|
uint64_t sent_tb; |
||||||
|
socket_t sock; // raw socket (only if we use per-request)
|
||||||
|
}; |
||||||
|
|
||||||
|
struct icmp_proxy_ctx { |
||||||
|
int initialized; |
||||||
|
int is_exit; |
||||||
|
struct UTUN_INSTANCE* inst; |
||||||
|
struct UASYNC* ua; |
||||||
|
socket_t raw_sock; // one SOCK_RAW for all ICMP on exit
|
||||||
|
void* raw_read_id; |
||||||
|
struct icmp_request* pending; |
||||||
|
uint64_t request_timeout_tb; |
||||||
|
}; |
||||||
|
|
||||||
|
int icmp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua); |
||||||
|
void icmp_proxy_destroy(struct UTUN_INSTANCE* inst); |
||||||
|
|
||||||
|
// Unified etcp_router callback
|
||||||
|
void icmp_proxy_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry); |
||||||
|
|
||||||
|
// Client side: принять IP/ICMP echo request с TUN, отправить через etcp
|
||||||
|
int icmp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id, |
||||||
|
uint32_t dst_ip, uint16_t echo_id, uint16_t echo_seq, |
||||||
|
const uint8_t* payload, size_t payload_len); |
||||||
|
|
||||||
|
int icmp_proxy_deliver_reply(struct UTUN_INSTANCE* inst, |
||||||
|
uint32_t src_ip, uint16_t echo_id, uint16_t echo_seq, |
||||||
|
const uint8_t* payload, size_t payload_len); |
||||||
|
|
||||||
|
#endif // ICMP_PROXY_H
|
||||||
@ -0,0 +1,245 @@ |
|||||||
|
// udp_proxy.c — UDP datagram прокси: client ↔ exit через etcp_router
|
||||||
|
#include "udp_proxy.h" |
||||||
|
#include "etcp.h" |
||||||
|
#include "etcp_api.h" |
||||||
|
#include "etcp_router.h" |
||||||
|
#include "utun_instance.h" |
||||||
|
#include "tun_if.h" |
||||||
|
#include "../lib/u_async.h" |
||||||
|
#include "../lib/debug_config.h" |
||||||
|
#include "../lib/ll_queue.h" |
||||||
|
#include "../lib/mem.h" |
||||||
|
#include <stdlib.h> |
||||||
|
#include <string.h> |
||||||
|
#include <errno.h> |
||||||
|
#ifndef _WIN32 |
||||||
|
#include <unistd.h> |
||||||
|
#include <netinet/in.h> |
||||||
|
#include <netinet/ip.h> |
||||||
|
#include <arpa/inet.h> |
||||||
|
#include <sys/socket.h> |
||||||
|
#endif |
||||||
|
|
||||||
|
#define UDP_FLOW_TIMEOUT_TB 600000 // 60s
|
||||||
|
|
||||||
|
static struct udp_proxy_ctx* g_udp_ctx = NULL; |
||||||
|
|
||||||
|
static struct udp_flow* flow_find(struct udp_flow* head, uint64_t client_node_id, |
||||||
|
uint32_t src_ip, uint16_t src_port, |
||||||
|
uint32_t dst_ip, uint16_t dst_port) { |
||||||
|
struct udp_flow* f; |
||||||
|
for (f = head; f; f = f->next) |
||||||
|
if (f->client_node_id == client_node_id && f->src_ip == src_ip && |
||||||
|
f->src_port == src_port && f->dst_ip == dst_ip && f->dst_port == dst_port) return f; |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
|
||||||
|
static void flow_read_cb(socket_t sock, void* arg) { |
||||||
|
(void)sock; struct udp_flow* f = (struct udp_flow*)arg; |
||||||
|
if (!f || f->sock == SOCKET_INVALID || !g_udp_ctx) return; |
||||||
|
uint8_t buf[65536]; |
||||||
|
struct sockaddr_in from; socklen_t flen = sizeof(from); |
||||||
|
ssize_t n = recvfrom(f->sock, buf, sizeof(buf), 0, (struct sockaddr*)&from, &flen); |
||||||
|
if (n <= 0) return; |
||||||
|
|
||||||
|
f->last_activity_tb = get_time_tb(); |
||||||
|
|
||||||
|
// Wrap reply: swap src/dst for client-side reconstruction
|
||||||
|
struct ll_entry* e = queue_entry_new(0); |
||||||
|
if (!e) return; |
||||||
|
e->dgram = u_malloc(UDP_PROXY_HDR_SIZE + n); |
||||||
|
if (!e->dgram) { queue_entry_free(e); return; } |
||||||
|
e->dgram[0] = ETCP_ID_UDP_PROXY; |
||||||
|
e->dgram[1] = UDP_PROXY_SUBCMD_DATA; |
||||||
|
memcpy(e->dgram + 2, &f->client_node_id, 8); |
||||||
|
// Reply src = original dst_ip:dest_port
|
||||||
|
memcpy(e->dgram + 10, &f->dst_ip, 4); |
||||||
|
memcpy(e->dgram + 14, &f->dst_port, 2); |
||||||
|
// Reply dst = original src_ip:src_port
|
||||||
|
memcpy(e->dgram + 16, &f->src_ip, 4); |
||||||
|
memcpy(e->dgram + 20, &f->src_port, 2); |
||||||
|
memcpy(e->dgram + UDP_PROXY_HDR_SIZE, buf, n); |
||||||
|
e->len = UDP_PROXY_HDR_SIZE + n; |
||||||
|
|
||||||
|
etcp_route_send(g_udp_ctx->inst, f->client_node_id, e); |
||||||
|
} |
||||||
|
|
||||||
|
// ====================================================================
|
||||||
|
// Exit node: receive REQUEST, create socket, forward
|
||||||
|
// ====================================================================
|
||||||
|
static void exit_handle_data(struct ETCP_CONN* conn, struct ll_entry* entry) { |
||||||
|
struct UTUN_INSTANCE* inst = conn ? conn->instance : (g_udp_ctx ? g_udp_ctx->inst : NULL); |
||||||
|
if (!inst || !g_udp_ctx || entry->len < UDP_PROXY_HDR_SIZE + 1) goto drop; |
||||||
|
|
||||||
|
uint64_t client_node_id; memcpy(&client_node_id, entry->dgram + 2, 8); |
||||||
|
uint32_t src_ip; memcpy(&src_ip, entry->dgram + 10, 4); |
||||||
|
uint16_t src_port; memcpy(&src_port, entry->dgram + 14, 2); |
||||||
|
uint32_t dst_ip; memcpy(&dst_ip, entry->dgram + 16, 4); |
||||||
|
uint16_t dst_port; memcpy(&dst_port, entry->dgram + 20, 2); |
||||||
|
uint8_t* payload = entry->dgram + UDP_PROXY_HDR_SIZE; |
||||||
|
size_t payload_len = entry->len - UDP_PROXY_HDR_SIZE; |
||||||
|
|
||||||
|
struct udp_flow* f = flow_find(g_udp_ctx->flows, client_node_id, src_ip, src_port, dst_ip, dst_port); |
||||||
|
if (!f) { |
||||||
|
f = u_calloc(1, sizeof(struct udp_flow)); |
||||||
|
if (!f) goto drop; |
||||||
|
f->client_node_id = client_node_id; f->src_ip = src_ip; f->src_port = src_port; |
||||||
|
f->dst_ip = dst_ip; f->dst_port = dst_port; |
||||||
|
f->ua = g_udp_ctx->ua; f->created_tb = get_time_tb(); f->last_activity_tb = f->created_tb; |
||||||
|
|
||||||
|
f->sock = socket(AF_INET, SOCK_DGRAM, 0); |
||||||
|
if (f->sock == SOCKET_INVALID) { u_free(f); DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "udp_proxy: socket failed"); goto drop; } |
||||||
|
socket_set_nonblocking(f->sock); |
||||||
|
struct sockaddr_in bind_addr = {.sin_family = AF_INET, .sin_addr = {.s_addr = INADDR_ANY}, .sin_port = 0}; |
||||||
|
bind(f->sock, (struct sockaddr*)&bind_addr, sizeof(bind_addr)); |
||||||
|
f->read_id = uasync_add_socket(g_udp_ctx->ua, f->sock, flow_read_cb, NULL, NULL, f); |
||||||
|
if (!f->read_id) { socket_close_wrapper(f->sock); u_free(f); goto drop; } |
||||||
|
f->next = g_udp_ctx->flows; g_udp_ctx->flows = f; g_udp_ctx->flow_count++; |
||||||
|
} |
||||||
|
f->last_activity_tb = get_time_tb(); |
||||||
|
|
||||||
|
struct sockaddr_in addr = {.sin_family = AF_INET, .sin_addr = {.s_addr = dst_ip}, .sin_port = dst_port}; |
||||||
|
sendto(f->sock, payload, payload_len, 0, (struct sockaddr*)&addr, sizeof(addr)); |
||||||
|
|
||||||
|
drop: |
||||||
|
queue_entry_free(entry); queue_dgram_free(entry); |
||||||
|
} |
||||||
|
|
||||||
|
// ====================================================================
|
||||||
|
// Client side: receive REPLY, deliver to TUN
|
||||||
|
// ====================================================================
|
||||||
|
static void client_handle_reply(struct ETCP_CONN* conn, struct ll_entry* entry) { |
||||||
|
if (entry->len < UDP_PROXY_HDR_SIZE + 1) { queue_entry_free(entry); queue_dgram_free(entry); return; } |
||||||
|
// svc_id already consumed by etcp_router dispatch
|
||||||
|
uint32_t src_ip, dst_ip; |
||||||
|
uint16_t src_port, dst_port; |
||||||
|
uint8_t* payload = entry->dgram + 1 + 1 + 8 + 4 + 2 + 4 + 2; // svc_id + subcmd + node_id + src + dst
|
||||||
|
size_t payload_len = entry->len - UDP_PROXY_HDR_SIZE; |
||||||
|
|
||||||
|
memcpy(&dst_ip, entry->dgram + 1 + 1 + 8 + 4 + 2, 4); // src in message → dst in reply
|
||||||
|
memcpy(&dst_port, entry->dgram + 1 + 1 + 8 + 4 + 2 + 4, 2); |
||||||
|
memcpy(&src_ip, entry->dgram + 1 + 1 + 8, 4); // dst in message → src in reply
|
||||||
|
memcpy(&src_port, entry->dgram + 1 + 1 + 8 + 4, 2); |
||||||
|
|
||||||
|
struct UTUN_INSTANCE* inst = conn ? conn->instance : (g_udp_ctx ? g_udp_ctx->inst : NULL); |
||||||
|
udp_proxy_deliver_reply(inst, src_ip, src_port, dst_ip, dst_port, payload, payload_len); |
||||||
|
queue_entry_free(entry); queue_dgram_free(entry); |
||||||
|
} |
||||||
|
|
||||||
|
// ====================================================================
|
||||||
|
// Unified etcp_router callback
|
||||||
|
// ====================================================================
|
||||||
|
void udp_proxy_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) { |
||||||
|
if (!entry || !entry->dgram || entry->len < 2) { |
||||||
|
if (entry) { queue_entry_free(entry); queue_dgram_free(entry); } |
||||||
|
return; |
||||||
|
} |
||||||
|
uint8_t subcmd = entry->dgram[1]; |
||||||
|
if (subcmd == UDP_PROXY_SUBCMD_DATA) { |
||||||
|
if (g_udp_ctx && g_udp_ctx->is_exit) exit_handle_data(conn, entry); |
||||||
|
else client_handle_reply(conn, entry); |
||||||
|
return; |
||||||
|
} |
||||||
|
queue_entry_free(entry); queue_dgram_free(entry); |
||||||
|
} |
||||||
|
|
||||||
|
// ====================================================================
|
||||||
|
// Client side: send UDP datagram to exit node
|
||||||
|
// ====================================================================
|
||||||
|
int udp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id, |
||||||
|
uint32_t src_ip, uint16_t src_port, |
||||||
|
uint32_t dst_ip, uint16_t dst_port, |
||||||
|
const uint8_t* payload, size_t payload_len) { |
||||||
|
if (!inst || !g_udp_ctx) return -1; |
||||||
|
struct ll_entry* e = queue_entry_new(0); |
||||||
|
if (!e) return -1; |
||||||
|
e->dgram = u_malloc(UDP_PROXY_HDR_SIZE + payload_len); |
||||||
|
if (!e->dgram) { queue_entry_free(e); return -1; } |
||||||
|
e->dgram[0] = ETCP_ID_UDP_PROXY; |
||||||
|
e->dgram[1] = UDP_PROXY_SUBCMD_DATA; |
||||||
|
memcpy(e->dgram + 2, &inst->node_id, 8); |
||||||
|
memcpy(e->dgram + 10, &src_ip, 4); |
||||||
|
memcpy(e->dgram + 14, &src_port, 2); |
||||||
|
memcpy(e->dgram + 16, &dst_ip, 4); |
||||||
|
memcpy(e->dgram + 20, &dst_port, 2); |
||||||
|
if (payload_len > 0) memcpy(e->dgram + UDP_PROXY_HDR_SIZE, payload, payload_len); |
||||||
|
e->len = UDP_PROXY_HDR_SIZE + payload_len; |
||||||
|
return etcp_route_send(inst, exit_node_id, e); |
||||||
|
} |
||||||
|
|
||||||
|
// ====================================================================
|
||||||
|
// Client side: deliver UDP reply to TUN
|
||||||
|
// ====================================================================
|
||||||
|
int udp_proxy_deliver_reply(struct UTUN_INSTANCE* inst, |
||||||
|
uint32_t src_ip, uint16_t src_port, |
||||||
|
uint32_t dst_ip, uint16_t dst_port, |
||||||
|
const uint8_t* payload, size_t payload_len) { |
||||||
|
if (!inst || !inst->tcp_proxy || !inst->tcp_proxy->tun) return -1; |
||||||
|
|
||||||
|
// Build IP/UDP reply packet
|
||||||
|
size_t ip_len = 20 + 8 + payload_len; |
||||||
|
uint8_t* pkt = u_malloc(ip_len); |
||||||
|
if (!pkt) return -1; |
||||||
|
|
||||||
|
memset(pkt, 0, 20); |
||||||
|
pkt[0] = 0x45; pkt[8] = 64; pkt[9] = IPPROTO_UDP; |
||||||
|
memcpy(pkt + 12, &src_ip, 4); memcpy(pkt + 16, &dst_ip, 4); |
||||||
|
|
||||||
|
uint16_t total_len = htons(20 + 8 + payload_len); |
||||||
|
memcpy(pkt + 2, &total_len, 2); |
||||||
|
|
||||||
|
uint16_t udp_len = htons(8 + payload_len); |
||||||
|
memcpy(pkt + 24, &udp_len, 2); |
||||||
|
memcpy(pkt + 20, &src_port, 2); |
||||||
|
memcpy(pkt + 22, &dst_port, 2); |
||||||
|
if (payload_len > 0) memcpy(pkt + 28, payload, payload_len); |
||||||
|
|
||||||
|
struct ll_entry* e = queue_entry_new(0); |
||||||
|
if (!e) { u_free(pkt); return -1; } |
||||||
|
e->dgram = u_malloc(1 + ip_len); |
||||||
|
e->dgram[0] = 4; memcpy(e->dgram + 1, pkt, ip_len); e->len = 1 + ip_len; |
||||||
|
u_free(pkt); |
||||||
|
queue_data_put(inst->tcp_proxy->tun->output_queue, e); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static void flow_expire(struct udp_proxy_ctx* ctx) { |
||||||
|
uint64_t now = get_time_tb(); struct udp_flow** prev = &ctx->flows; |
||||||
|
while (*prev) { |
||||||
|
struct udp_flow* f = *prev; |
||||||
|
if (now - f->last_activity_tb > ctx->flow_timeout_tb) { |
||||||
|
*prev = f->next; ctx->flow_count--; |
||||||
|
if (f->read_id) { uasync_remove_socket_t(f->ua, f->sock); f->read_id = NULL; } |
||||||
|
socket_close_wrapper(f->sock); u_free(f); |
||||||
|
} else prev = &f->next; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ====================================================================
|
||||||
|
// Public init/destroy
|
||||||
|
// ====================================================================
|
||||||
|
int udp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) { |
||||||
|
if (!inst) return -1; |
||||||
|
struct udp_proxy_ctx* ctx = u_calloc(1, sizeof(struct udp_proxy_ctx)); |
||||||
|
if (!ctx) return -1; |
||||||
|
ctx->inst = inst; ctx->ua = ua; |
||||||
|
ctx->flow_timeout_tb = UDP_FLOW_TIMEOUT_TB; |
||||||
|
ctx->is_exit = inst->remote_proxy.enabled; |
||||||
|
g_udp_ctx = ctx; |
||||||
|
|
||||||
|
etcp_router_bind(inst, ETCP_ID_UDP_PROXY, udp_proxy_recv_cb); |
||||||
|
ctx->initialized = 1; |
||||||
|
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "udp_proxy initialized (exit=%d)", ctx->is_exit); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
void udp_proxy_destroy(struct UTUN_INSTANCE* inst) { |
||||||
|
if (!inst || !g_udp_ctx) return; |
||||||
|
etcp_router_unbind(inst, ETCP_ID_UDP_PROXY); |
||||||
|
struct udp_flow* f = g_udp_ctx->flows; |
||||||
|
while (f) { struct udp_flow* n = f->next; |
||||||
|
if (f->read_id) { uasync_remove_socket_t(g_udp_ctx->ua, f->sock); f->read_id = NULL; } |
||||||
|
socket_close_wrapper(f->sock); u_free(f); f = n; } |
||||||
|
u_free(g_udp_ctx); g_udp_ctx = NULL; |
||||||
|
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "udp_proxy destroyed"); |
||||||
|
} |
||||||
@ -0,0 +1,64 @@ |
|||||||
|
// udp_proxy.h — UDP datagram прокси: client ↔ exit через etcp_router
|
||||||
|
#ifndef UDP_PROXY_H |
||||||
|
#define UDP_PROXY_H |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
#include "../lib/socket_compat.h" |
||||||
|
|
||||||
|
struct UTUN_INSTANCE; |
||||||
|
struct UASYNC; |
||||||
|
struct ll_entry; |
||||||
|
struct ETCP_CONN; |
||||||
|
|
||||||
|
// Sub-commands
|
||||||
|
#define UDP_PROXY_SUBCMD_DATA 0x01 // client→exit: пробрось датаграмму | exit→client: ответ
|
||||||
|
|
||||||
|
// Message header (excluding svc_id byte)
|
||||||
|
// svc_id(1) + subcmd(1) + sender_node_id(8) + src_ip(4) + src_port(2) + dst_ip(4) + dst_port(2) + payload
|
||||||
|
#define UDP_PROXY_HDR_SIZE 22 |
||||||
|
|
||||||
|
// One UDP flow (exit node side: maps client_node_id + addr tuple to OS socket)
|
||||||
|
struct udp_flow { |
||||||
|
struct udp_flow* next; |
||||||
|
uint64_t client_node_id; |
||||||
|
uint32_t src_ip; |
||||||
|
uint16_t src_port; |
||||||
|
uint32_t dst_ip; |
||||||
|
uint16_t dst_port; |
||||||
|
socket_t sock; |
||||||
|
void* read_id; |
||||||
|
struct UASYNC* ua; |
||||||
|
uint64_t created_tb; |
||||||
|
uint64_t last_activity_tb; |
||||||
|
}; |
||||||
|
|
||||||
|
struct udp_proxy_ctx { |
||||||
|
int initialized; |
||||||
|
int is_exit; // 1=exit node (создаёт сокеты), 0=client (шлёт через etcp)
|
||||||
|
struct UTUN_INSTANCE* inst; |
||||||
|
struct UASYNC* ua; |
||||||
|
struct udp_flow* flows; |
||||||
|
int flow_count; |
||||||
|
uint64_t flow_timeout_tb; |
||||||
|
}; |
||||||
|
|
||||||
|
int udp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua); |
||||||
|
void udp_proxy_destroy(struct UTUN_INSTANCE* inst); |
||||||
|
|
||||||
|
// Exit node: принимает REQUEST, создаёт сокет, шлёт данные
|
||||||
|
// Client side: принимает REPLY, доставляет на TUN
|
||||||
|
void udp_proxy_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry); |
||||||
|
|
||||||
|
// Client side: принять IP/UDP пакет с TUN, отправить через etcp_route_send
|
||||||
|
int udp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id, |
||||||
|
uint32_t src_ip, uint16_t src_port, |
||||||
|
uint32_t dst_ip, uint16_t dst_port, |
||||||
|
const uint8_t* payload, size_t payload_len); |
||||||
|
|
||||||
|
// Client side: доставить ответ на TUN (вызывается из recv_cb)
|
||||||
|
int udp_proxy_deliver_reply(struct UTUN_INSTANCE* inst, |
||||||
|
uint32_t src_ip, uint16_t src_port, |
||||||
|
uint32_t dst_ip, uint16_t dst_port, |
||||||
|
const uint8_t* payload, size_t payload_len); |
||||||
|
|
||||||
|
#endif // UDP_PROXY_H
|
||||||
@ -0,0 +1,173 @@ |
|||||||
|
// test_icmp_proxy.c — 2-node ICMP ping test via etcp_router
|
||||||
|
// Node1 (client): tcp_proxy → sends ICMP_REQUEST to exit
|
||||||
|
// Node2 (exit): receives → raw socket → sends echo → receives reply → sends ICMP_REPLY
|
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <string.h> |
||||||
|
#include "../lib/platform_compat.h" |
||||||
|
#include "test_utils.h" |
||||||
|
#ifndef _WIN32 |
||||||
|
#include <unistd.h> |
||||||
|
#include <sys/socket.h> |
||||||
|
#include <netinet/in.h> |
||||||
|
#include <netinet/ip.h> |
||||||
|
#include <netinet/ip_icmp.h> |
||||||
|
#include <arpa/inet.h> |
||||||
|
#include <sys/stat.h> |
||||||
|
#endif |
||||||
|
#include <time.h> |
||||||
|
|
||||||
|
#include "../src/etcp.h" |
||||||
|
#include "../src/etcp_connections.h" |
||||||
|
#include "../src/etcp_api.h" |
||||||
|
#include "../src/etcp_router.h" |
||||||
|
#include "../src/icmp_proxy.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 TEST_TIMEOUT_MS 5000 |
||||||
|
#define ICMP_PAYLOAD_SIZE 56 // typical ping payload
|
||||||
|
|
||||||
|
// Config strings
|
||||||
|
static const char* cfg_node_client(void) { |
||||||
|
static char buf[1024]; |
||||||
|
snprintf(buf, sizeof(buf), |
||||||
|
"[global]\n" |
||||||
|
"my_node_id=0xEEEE000000000001\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]\naddr=127.0.0.1:9081\ntype=public\n" |
||||||
|
"[client: c1]\nkeepalive=1\nlink=s1:127.0.0.1:9082\n" |
||||||
|
"peer_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n" |
||||||
|
"[tcp_proxy]\n" |
||||||
|
"enabled=yes\n" |
||||||
|
"tun_name=tun_tcp\n" |
||||||
|
"tun_ip=10.99.0.1\n" |
||||||
|
"via_node=0xEEEE000000000002\n"); |
||||||
|
return buf; |
||||||
|
} |
||||||
|
|
||||||
|
static const char* cfg_node_exit(void) { |
||||||
|
static char buf[1024]; |
||||||
|
snprintf(buf, sizeof(buf), |
||||||
|
"[global]\n" |
||||||
|
"my_node_id=0xEEEE000000000002\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]\naddr=127.0.0.1:9082\ntype=public\n" |
||||||
|
"[allowed_keys]\nallow_all=1\n" |
||||||
|
"[remote_proxy]\nenabled=yes\n"); |
||||||
|
return buf; |
||||||
|
} |
||||||
|
|
||||||
|
static struct UTUN_INSTANCE* cli = NULL; |
||||||
|
static struct UTUN_INSTANCE* exit_node = NULL; |
||||||
|
static struct UASYNC* ua = NULL; |
||||||
|
static int g_ok = 0, g_done = 0, g_test_phase = 0; |
||||||
|
static uint64_t exit_node_id = 0xEEEE000000000002ULL; |
||||||
|
static uint64_t client_node_id = 0xEEEE000000000001ULL; |
||||||
|
static uint16_t test_echo_id = 0x1234, test_echo_seq = 0x0001; |
||||||
|
static uint8_t send_buf[ICMP_PAYLOAD_SIZE]; |
||||||
|
static int reply_rcvd = 0; |
||||||
|
static void* g_to_id = NULL; |
||||||
|
|
||||||
|
static void cli_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) { |
||||||
|
(void)conn; |
||||||
|
if (!entry || !entry->dgram || entry->len < ICMP_PROXY_HDR_SIZE + 1) { |
||||||
|
if (entry) { queue_entry_free(entry); queue_dgram_free(entry); } return; |
||||||
|
} |
||||||
|
if (entry->dgram[1] == ICMP_PROXY_SUBCMD_REPLY) { |
||||||
|
uint16_t rid, rseq; |
||||||
|
memcpy(&rid, entry->dgram + 14, 2); |
||||||
|
memcpy(&rseq, entry->dgram + 16, 2); |
||||||
|
if (rid == test_echo_id && rseq == test_echo_seq) { |
||||||
|
size_t payload_len = entry->len - ICMP_PROXY_HDR_SIZE; |
||||||
|
uint8_t* payload = entry->dgram + ICMP_PROXY_HDR_SIZE; |
||||||
|
if (payload_len == ICMP_PAYLOAD_SIZE && memcmp(payload, send_buf, ICMP_PAYLOAD_SIZE) == 0) { |
||||||
|
reply_rcvd = 1; g_done = 1; g_ok = 1; |
||||||
|
} else { |
||||||
|
printf("[FAIL] payload mismatch: got %zu expected %d\n", payload_len, ICMP_PAYLOAD_SIZE); |
||||||
|
g_done = -1; |
||||||
|
} |
||||||
|
} else { |
||||||
|
printf("[FAIL] id/seq mismatch: got id=0x%04x seq=0x%04x\n", rid, rseq); |
||||||
|
g_done = -1; |
||||||
|
} |
||||||
|
} |
||||||
|
queue_entry_free(entry); queue_dgram_free(entry); |
||||||
|
} |
||||||
|
|
||||||
|
static void monitor(void* arg) { |
||||||
|
(void)arg; |
||||||
|
if (g_done) return; |
||||||
|
|
||||||
|
if (g_test_phase == 0) { |
||||||
|
int cli_ok = 0, exit_ok = 0; |
||||||
|
for (struct ETCP_CONN* c = cli->connections; c; c = c->next) |
||||||
|
for (struct ETCP_LINK* l = c->links; l; l = l->next) |
||||||
|
if (l->initialized && c->crypto_ctx.initialized) cli_ok = 1; |
||||||
|
for (struct ETCP_CONN* c = exit_node->connections; c; c = c->next) |
||||||
|
for (struct ETCP_LINK* l = c->links; l; l = l->next) |
||||||
|
if (l->initialized && c->crypto_ctx.initialized) exit_ok = 1; |
||||||
|
if (cli_ok && exit_ok) { |
||||||
|
g_test_phase = 1; |
||||||
|
// Override client handler for test verification
|
||||||
|
etcp_router_bind(cli, ETCP_ID_ICMP_PROXY, cli_recv_cb); |
||||||
|
for (int i = 0; i < ICMP_PAYLOAD_SIZE; i++) send_buf[i] = (uint8_t)(rand() & 0xFF); |
||||||
|
icmp_proxy_send_to_exit(cli, exit_node_id, |
||||||
|
inet_addr("127.0.0.1"), test_echo_id, test_echo_seq, send_buf, ICMP_PAYLOAD_SIZE); |
||||||
|
} |
||||||
|
} |
||||||
|
if (!g_done) g_to_id = uasync_set_timeout(ua, 100, NULL, monitor, "mon"); |
||||||
|
} |
||||||
|
|
||||||
|
static void test_timeout(void* arg) { |
||||||
|
(void)arg; |
||||||
|
if (!g_done) { printf("[FAIL] timeout (phase=%d reply=%d)\n", g_test_phase, reply_rcvd); g_done = -1; } |
||||||
|
} |
||||||
|
|
||||||
|
int main(void) { |
||||||
|
printf("=== test_icmp_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(); |
||||||
|
if (!ua) { printf("[FAIL] uasync_create\n"); return 1; } |
||||||
|
|
||||||
|
cli = utun_instance_create_from_str(ua, cfg_node_client()); |
||||||
|
if (!cli) { printf("[FAIL] client instance create\n"); goto done; } |
||||||
|
exit_node = utun_instance_create_from_str(ua, cfg_node_exit()); |
||||||
|
if (!exit_node) { printf("[FAIL] exit instance create\n"); goto done; } |
||||||
|
|
||||||
|
if (utun_instance_init(cli) < 0) { printf("[FAIL] client init\n"); goto done; } |
||||||
|
if (utun_instance_init(exit_node) < 0) { printf("[FAIL] exit init\n"); goto done; } |
||||||
|
|
||||||
|
g_to_id = uasync_set_timeout(ua, 100, NULL, monitor, "mon"); |
||||||
|
void* to_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS * 10, NULL, test_timeout, "to"); |
||||||
|
|
||||||
|
while (!g_done) uasync_poll(ua, 50); |
||||||
|
|
||||||
|
if (to_id) uasync_cancel_timeout(ua, to_id); |
||||||
|
|
||||||
|
if (g_ok) printf("[PASS] test_icmp_proxy — ping echoed, id=0x%04x seq=%d\n", test_echo_id, test_echo_seq); |
||||||
|
else printf("[FAIL] test_icmp_proxy\n"); |
||||||
|
|
||||||
|
done: |
||||||
|
if (g_to_id) uasync_cancel_timeout(ua, g_to_id); |
||||||
|
if (cli) { cli->running = 0; utun_instance_destroy(cli); } |
||||||
|
if (exit_node) { exit_node->running = 0; utun_instance_destroy(exit_node); } |
||||||
|
if (ua) uasync_destroy(ua, 0); |
||||||
|
return g_ok ? 0 : 1; |
||||||
|
} |
||||||
@ -0,0 +1,197 @@ |
|||||||
|
// test_tcp_proxy_remote.c — 2-node TCP 1MB forward via etcp_router
|
||||||
|
// Architecture: Client_A (active uIP) ↔ socketpair ↔ B (passive, etcp) ↔ Exit (remote_proxy) ↔ echo
|
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <string.h> |
||||||
|
#include "../lib/platform_compat.h" |
||||||
|
#include "test_utils.h" |
||||||
|
#ifndef _WIN32 |
||||||
|
#include <unistd.h> |
||||||
|
#include <sys/socket.h> |
||||||
|
#include <netinet/in.h> |
||||||
|
#include <arpa/inet.h> |
||||||
|
#include <sys/wait.h> |
||||||
|
#endif |
||||||
|
#include <signal.h> |
||||||
|
#include <errno.h> |
||||||
|
#include <time.h> |
||||||
|
|
||||||
|
#include "../src/tcp_proxy.h" |
||||||
|
#include "../src/etcp.h" |
||||||
|
#include "../src/etcp_router.h" |
||||||
|
#include "../src/remote_proxy.h" |
||||||
|
#include "../src/config_parser.h" |
||||||
|
#include "../src/utun_instance.h" |
||||||
|
#include "../src/uip/uip.h" |
||||||
|
#include "../lib/u_async.h" |
||||||
|
#include "../lib/debug_config.h" |
||||||
|
|
||||||
|
#define TEST_SIZE (1 * 1024 * 1024) |
||||||
|
#define CHUNK_SIZE 1460 |
||||||
|
#define TIMEOUT_MS 45000 |
||||||
|
|
||||||
|
static void* g_to_id; |
||||||
|
static volatile int g_done, g_phase; |
||||||
|
static pid_t g_echo_pid; |
||||||
|
static struct UTUN_INSTANCE* g_exit, *g_b; |
||||||
|
static struct UASYNC* g_ua; |
||||||
|
static int g_pair[2]; |
||||||
|
static struct tcp_proxy* g_proxy_b, *g_cli; |
||||||
|
static int g_conn_idx, g_conn_up; |
||||||
|
static struct uip_conn* g_uc; |
||||||
|
static uint8_t *g_send_buf, *g_recv_buf; |
||||||
|
static size_t g_sent, g_rcvd; |
||||||
|
static int g_ok; |
||||||
|
static struct timespec g_t_start, g_t_end; |
||||||
|
static double g_elapsed; |
||||||
|
static int g_echo_port, g_srv_a, g_srv_b; |
||||||
|
|
||||||
|
static void on_signal(int sig) { (void)sig; g_done = -1; } |
||||||
|
|
||||||
|
static int alloc_port(void) { |
||||||
|
int s = socket(AF_INET, SOCK_STREAM, 0); |
||||||
|
struct sockaddr_in a = {.sin_family=AF_INET, .sin_addr={.s_addr=htonl(INADDR_LOOPBACK)}}; |
||||||
|
bind(s, (struct sockaddr*)&a, sizeof(a)); |
||||||
|
struct sockaddr_in b; socklen_t l = sizeof(b); |
||||||
|
getsockname(s, (struct sockaddr*)&b, &l); |
||||||
|
int p = ntohs(b.sin_port); |
||||||
|
close(s); |
||||||
|
return p; |
||||||
|
} |
||||||
|
|
||||||
|
static void echo_server(uint16_t port) { |
||||||
|
signal(SIGALRM, on_signal); |
||||||
|
alarm(10); |
||||||
|
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 a = {.sin_family=AF_INET, .sin_port=htons(port), .sin_addr={.s_addr=htonl(INADDR_LOOPBACK)}}; |
||||||
|
if (bind(srv,(struct sockaddr*)&a,sizeof(a))<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[65536]; ssize_t n; |
||||||
|
while((n=recv(cli,buf,sizeof(buf),0))>0){ssize_t s=0; while(s<n){ssize_t r=send(cli,buf+s,n-s,0); if(r<0)goto done; s+=r;}} |
||||||
|
done: close(cli); close(srv); _exit(0); |
||||||
|
} |
||||||
|
|
||||||
|
static void start_test(void) { |
||||||
|
struct tcp_proxy_mapping_config m = {.local_port=9090,.remote_ip="127.0.0.1",.remote_port=g_echo_port}; |
||||||
|
g_proxy_b = tcp_proxy_create(g_b, g_ua, NULL, NULL, 0, 0, &m, 1, 0, 0, g_pair[1], 0xCCCC000000000001ULL); |
||||||
|
if (!g_proxy_b) { printf("[FAIL] proxy_b create\n"); g_done=-1; return; } |
||||||
|
g_b->tcp_proxy = g_proxy_b; |
||||||
|
|
||||||
|
g_cli = tcp_proxy_create(NULL, g_ua, NULL, NULL, 0, 0, NULL, 0, 0, 0, g_pair[0], 0); |
||||||
|
if (!g_cli) { printf("[FAIL] cli create\n"); g_done=-1; return; } |
||||||
|
|
||||||
|
g_conn_idx = tcp_proxy_active_open(g_cli, "10.99.0.100", 9090); |
||||||
|
if (g_conn_idx < 0) { printf("[FAIL] active_open\n"); g_done=-1; return; } |
||||||
|
g_uc = &uip_conns[g_conn_idx]; |
||||||
|
g_phase = 1; |
||||||
|
} |
||||||
|
|
||||||
|
static void poll_test(void) { |
||||||
|
if (g_phase == 1) { |
||||||
|
if (g_uc->tcpstateflags == UIP_ESTABLISHED) { clock_gettime(CLOCK_MONOTONIC,&g_t_start); g_phase=2; } |
||||||
|
else if (g_uc->tcpstateflags == UIP_CLOSED) { printf("[FAIL] handshake\n"); g_done=-1; } |
||||||
|
return; |
||||||
|
} |
||||||
|
if (g_phase == 2) { |
||||||
|
while (g_sent < TEST_SIZE) { size_t c = TEST_SIZE - g_sent; if (c > CHUNK_SIZE) c = CHUNK_SIZE; |
||||||
|
if (tcp_proxy_active_send(g_cli,g_conn_idx,g_send_buf+g_sent,c)!=0) break; g_sent += c; } |
||||||
|
g_phase = 3; return; |
||||||
|
} |
||||||
|
if (g_phase == 3) { if (tcp_proxy_active_send_done(g_cli,g_conn_idx)) g_phase=4; return; } |
||||||
|
if (g_phase == 4) { |
||||||
|
ssize_t n = tcp_proxy_active_recv(g_cli,g_conn_idx,g_recv_buf+g_rcvd,TEST_SIZE-g_rcvd); |
||||||
|
if (n > 0) g_rcvd += n; |
||||||
|
if (g_rcvd >= TEST_SIZE) { |
||||||
|
clock_gettime(CLOCK_MONOTONIC,&g_t_end); |
||||||
|
g_elapsed = (g_t_end.tv_sec-g_t_start.tv_sec)+(g_t_end.tv_nsec-g_t_start.tv_nsec)/1e9; |
||||||
|
g_ok = (memcmp(g_send_buf,g_recv_buf,TEST_SIZE)==0); |
||||||
|
g_done = 1; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static void monitor(void* arg) { |
||||||
|
(void)arg; |
||||||
|
if (g_done) return; |
||||||
|
if (g_phase == 0 && !g_conn_up) { |
||||||
|
int bup=0, eup=0; |
||||||
|
if (g_b) for (struct ETCP_CONN*c=g_b->connections;c;c=c->next) |
||||||
|
for (struct ETCP_LINK*l=c->links;l;l=l->next) if (l->initialized&&c->crypto_ctx.initialized) bup=1; |
||||||
|
if (g_exit) for (struct ETCP_CONN*c=g_exit->connections;c;c=c->next) |
||||||
|
for (struct ETCP_LINK*l=c->links;l;l=l->next) if (l->initialized&&c->crypto_ctx.initialized) eup=1; |
||||||
|
if (bup && eup) { g_conn_up=1; start_test(); } |
||||||
|
} |
||||||
|
if (g_conn_up && !g_done) poll_test(); |
||||||
|
if (!g_done) g_to_id = uasync_set_timeout(g_ua, 5, NULL, monitor, "mon"); |
||||||
|
} |
||||||
|
|
||||||
|
static void test_timeout(void* arg) { |
||||||
|
(void)arg; |
||||||
|
if (!g_done) { printf("[FAIL] timeout phase=%d sent=%zu rcvd=%zu\n",g_phase,g_sent,g_rcvd); g_done=-1; } |
||||||
|
} |
||||||
|
|
||||||
|
static const char* cfg_exit(int srv_port) { static char b[1024]; snprintf(b,sizeof(b), |
||||||
|
"[global]\nmy_node_id=0xCCCC000000000001\n" |
||||||
|
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n" |
||||||
|
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n" |
||||||
|
"tun_ip=10.99.0.1/24\ntun_ifname=tun99\n" |
||||||
|
"[server:s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n[remote_proxy]\nenabled=yes\n", srv_port); return b; } |
||||||
|
|
||||||
|
static const char* cfg_b(int srv_port, int cli_port) { static char b[1024]; snprintf(b,sizeof(b), |
||||||
|
"[global]\nmy_node_id=0xCCCC000000000002\n" |
||||||
|
"my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2\n" |
||||||
|
"my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n" |
||||||
|
"tun_ip=10.99.0.2/24\ntun_ifname=tun98\n" |
||||||
|
"[server:s1]\naddr=127.0.0.1:%d\ntype=public\n[client:c1]\nkeepalive=1\nlink=s1:127.0.0.1:%d\n" |
||||||
|
"peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n" |
||||||
|
"[remote_proxy]\nenabled=yes\n", srv_port, cli_port); return b; } |
||||||
|
|
||||||
|
int main(void) { |
||||||
|
printf("=== test_tcp_proxy_remote ===\n"); |
||||||
|
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); debug_set_categories(DEBUG_CATEGORY_ALL); |
||||||
|
utun_instance_set_tun_init_enabled(0); |
||||||
|
signal(SIGTERM, on_signal); signal(SIGINT, on_signal); |
||||||
|
srand((unsigned)time(NULL)); |
||||||
|
|
||||||
|
g_send_buf = malloc(TEST_SIZE); g_recv_buf = malloc(TEST_SIZE); |
||||||
|
if (!g_send_buf || !g_recv_buf) { printf("[FAIL] malloc\n"); return 1; } |
||||||
|
for (size_t i = 0; i < TEST_SIZE; i++) g_send_buf[i] = (uint8_t)(rand() & 0xFF); |
||||||
|
|
||||||
|
g_echo_port = alloc_port(); g_srv_a = alloc_port(); g_srv_b = alloc_port(); |
||||||
|
if (!g_echo_port || !g_srv_a || !g_srv_b) { printf("[FAIL] alloc_port\n"); return 1; } |
||||||
|
|
||||||
|
g_echo_pid = fork(); |
||||||
|
if (g_echo_pid == 0) echo_server(g_echo_port); |
||||||
|
usleep(50000); |
||||||
|
|
||||||
|
g_ua = uasync_create(); |
||||||
|
if (!g_ua) { printf("[FAIL] uasync\n"); goto done; } |
||||||
|
g_exit = utun_instance_create_from_str(g_ua, cfg_exit(g_srv_a)); |
||||||
|
g_b = utun_instance_create_from_str(g_ua, cfg_b(g_srv_b, g_srv_a)); |
||||||
|
if (!g_exit || !g_b) { printf("[FAIL] create\n"); goto done; } |
||||||
|
if (utun_instance_init(g_exit) < 0 || utun_instance_init(g_b) < 0) { printf("[FAIL] init\n"); goto done; } |
||||||
|
for (int i = 0; i < 50; i++) uasync_poll(g_ua, 10); |
||||||
|
if (socketpair(AF_UNIX, SOCK_STREAM, 0, g_pair) < 0) { perror("pair"); goto done; } |
||||||
|
|
||||||
|
g_to_id = uasync_set_timeout(g_ua, 50, NULL, monitor, "mon"); |
||||||
|
void* to_id = uasync_set_timeout(g_ua, TIMEOUT_MS*10, NULL, test_timeout, "to"); |
||||||
|
while (!g_done) uasync_poll(g_ua, 10); |
||||||
|
if (to_id) uasync_cancel_timeout(g_ua, to_id); |
||||||
|
|
||||||
|
if (g_ok) printf("[PASS] test_tcp_proxy_remote — 1MB in %.2fs (%.2f MB/s)\n", g_elapsed, (TEST_SIZE/1e6)/g_elapsed); |
||||||
|
else if (g_done == -1) printf("[FAIL] test_tcp_proxy_remote\n"); |
||||||
|
|
||||||
|
done: |
||||||
|
if (g_to_id) uasync_cancel_timeout(g_ua, g_to_id); |
||||||
|
if (g_cli) tcp_proxy_destroy(g_cli); |
||||||
|
if (g_proxy_b) tcp_proxy_destroy(g_proxy_b); |
||||||
|
if (g_exit) { g_exit->running=0; utun_instance_destroy(g_exit); } |
||||||
|
if (g_b) { g_b->running=0; utun_instance_destroy(g_b); } |
||||||
|
if (g_ua) uasync_destroy(g_ua, 0); |
||||||
|
if (g_echo_pid) { kill(g_echo_pid, SIGTERM); waitpid(g_echo_pid, NULL, 0); } |
||||||
|
free(g_send_buf); free(g_recv_buf); |
||||||
|
return g_ok ? 0 : 1; |
||||||
|
} |
||||||
@ -0,0 +1,190 @@ |
|||||||
|
// test_udp_proxy.c — 2-node UDP echo test via etcp_router
|
||||||
|
// Node1 (client): tcp_proxy → sends UDP_REQUEST to exit
|
||||||
|
// Node2 (exit): receives → creates UDP socket → echoes → sends UDP_REPLY
|
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <string.h> |
||||||
|
#include "../lib/platform_compat.h" |
||||||
|
#include "test_utils.h" |
||||||
|
#ifndef _WIN32 |
||||||
|
#include <unistd.h> |
||||||
|
#include <sys/socket.h> |
||||||
|
#include <netinet/in.h> |
||||||
|
#include <arpa/inet.h> |
||||||
|
#include <sys/stat.h> |
||||||
|
#endif |
||||||
|
#include <time.h> |
||||||
|
|
||||||
|
#include "../src/etcp.h" |
||||||
|
#include "../src/etcp_connections.h" |
||||||
|
#include "../src/etcp_api.h" |
||||||
|
#include "../src/etcp_router.h" |
||||||
|
#include "../src/udp_proxy.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 UDP_ECHO_PORT 29991 |
||||||
|
#define TEST_TIMEOUT_MS 5000 |
||||||
|
#define PAYLOAD_SIZE 64 |
||||||
|
|
||||||
|
// Config strings
|
||||||
|
static const char* cfg_node_client(void) { |
||||||
|
static char buf[1024]; |
||||||
|
snprintf(buf, sizeof(buf), |
||||||
|
"[global]\n" |
||||||
|
"my_node_id=0xDDDD000000000001\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]\naddr=127.0.0.1:9071\ntype=public\n" |
||||||
|
"[client: c1]\nkeepalive=1\nlink=s1:127.0.0.1:9072\n" |
||||||
|
"peer_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n" |
||||||
|
"[tcp_proxy]\n" |
||||||
|
"enabled=yes\n" |
||||||
|
"tun_name=tun_tcp\n" |
||||||
|
"tun_ip=10.99.0.1\n" |
||||||
|
"via_node=0xDDDD000000000002\n"); |
||||||
|
return buf; |
||||||
|
} |
||||||
|
|
||||||
|
static const char* cfg_node_exit(void) { |
||||||
|
static char buf[1024]; |
||||||
|
snprintf(buf, sizeof(buf), |
||||||
|
"[global]\n" |
||||||
|
"my_node_id=0xDDDD000000000002\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]\naddr=127.0.0.1:9072\ntype=public\n" |
||||||
|
"[allowed_keys]\nallow_all=1\n" |
||||||
|
"[remote_proxy]\nenabled=yes\n"); |
||||||
|
return buf; |
||||||
|
} |
||||||
|
|
||||||
|
static struct UTUN_INSTANCE* cli = NULL; |
||||||
|
static struct UTUN_INSTANCE* exit_node = NULL; |
||||||
|
static struct UASYNC* ua = NULL; |
||||||
|
static socket_t g_echo_sock = SOCKET_INVALID; |
||||||
|
static void* g_echo_id = NULL; |
||||||
|
static int g_echo_count = 0; |
||||||
|
static uint8_t g_echo_buf[8192]; |
||||||
|
static ssize_t g_echo_len = 0; |
||||||
|
static int g_ok = 0, g_done = 0, g_test_phase = 0; |
||||||
|
static uint64_t exit_node_id = 0xDDDD000000000002ULL; |
||||||
|
static uint64_t client_node_id = 0xDDDD000000000001ULL; |
||||||
|
static uint8_t send_buf[PAYLOAD_SIZE], recv_buf[PAYLOAD_SIZE]; |
||||||
|
static int reply_rcvd = 0; |
||||||
|
static void* g_to_id = NULL; |
||||||
|
|
||||||
|
static void udp_echo_cb(socket_t sock, void* arg) { |
||||||
|
(void)sock; (void)arg; |
||||||
|
struct sockaddr_in from; socklen_t flen = sizeof(from); |
||||||
|
ssize_t n = recvfrom(g_echo_sock, g_echo_buf, sizeof(g_echo_buf), 0, (struct sockaddr*)&from, &flen); |
||||||
|
if (n > 0) { sendto(g_echo_sock, g_echo_buf, n, 0, (struct sockaddr*)&from, flen); g_echo_len = n; g_echo_count++; } |
||||||
|
} |
||||||
|
|
||||||
|
static void cli_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) { |
||||||
|
(void)conn; |
||||||
|
if (!entry || !entry->dgram || entry->len < UDP_PROXY_HDR_SIZE + 1) { |
||||||
|
if (entry) { queue_entry_free(entry); queue_dgram_free(entry); } return; |
||||||
|
} |
||||||
|
// svc_id(1) + subcmd(1) + sender_node_id(8) + src_ip(4) + src_port(2) + dst_ip(4) + dst_port(2) + payload
|
||||||
|
size_t payload_len = entry->len - UDP_PROXY_HDR_SIZE; |
||||||
|
if (entry->dgram[1] == UDP_PROXY_SUBCMD_DATA) { |
||||||
|
uint8_t* payload = entry->dgram + UDP_PROXY_HDR_SIZE; |
||||||
|
if (payload_len == PAYLOAD_SIZE && memcmp(payload, send_buf, PAYLOAD_SIZE) == 0) { |
||||||
|
reply_rcvd = 1; g_done = 1; g_ok = 1; |
||||||
|
} else { |
||||||
|
printf("[FAIL] payload mismatch: got %zu expected %d\n", payload_len, PAYLOAD_SIZE); |
||||||
|
g_done = -1; |
||||||
|
} |
||||||
|
} |
||||||
|
queue_entry_free(entry); queue_dgram_free(entry); |
||||||
|
} |
||||||
|
|
||||||
|
static void monitor(void* arg) { |
||||||
|
(void)arg; |
||||||
|
if (g_done) return; |
||||||
|
|
||||||
|
if (g_test_phase == 0) { |
||||||
|
// Wait for ETCP connection
|
||||||
|
int cli_ok = 0, exit_ok = 0; |
||||||
|
for (struct ETCP_CONN* c = cli->connections; c; c = c->next) |
||||||
|
for (struct ETCP_LINK* l = c->links; l; l = l->next) |
||||||
|
if (l->initialized && c->crypto_ctx.initialized) cli_ok = 1; |
||||||
|
for (struct ETCP_CONN* c = exit_node->connections; c; c = c->next) |
||||||
|
for (struct ETCP_LINK* l = c->links; l; l = l->next) |
||||||
|
if (l->initialized && c->crypto_ctx.initialized) exit_ok = 1; |
||||||
|
if (cli_ok && exit_ok) { |
||||||
|
g_test_phase = 1; |
||||||
|
// Bind client handler for UDP replies (overrides what tcp_proxy_create set, for test verification)
|
||||||
|
etcp_router_bind(cli, ETCP_ID_UDP_PROXY, cli_recv_cb); |
||||||
|
// Send UDP_REQUEST
|
||||||
|
for (int i = 0; i < PAYLOAD_SIZE; i++) send_buf[i] = (uint8_t)(rand() & 0xFF); |
||||||
|
udp_proxy_send_to_exit(cli, exit_node_id, |
||||||
|
inet_addr("10.99.0.1"), htons(12345), |
||||||
|
inet_addr("127.0.0.1"), htons(UDP_ECHO_PORT), |
||||||
|
send_buf, PAYLOAD_SIZE); |
||||||
|
} |
||||||
|
} |
||||||
|
if (!g_done) g_to_id = uasync_set_timeout(ua, 100, NULL, monitor, "mon"); |
||||||
|
} |
||||||
|
|
||||||
|
static void test_timeout(void* arg) { |
||||||
|
(void)arg; |
||||||
|
if (!g_done) { printf("[FAIL] timeout (phase=%d echo=%d reply=%d)\n", g_test_phase, g_echo_count, reply_rcvd); g_done = -1; } |
||||||
|
} |
||||||
|
|
||||||
|
int main(void) { |
||||||
|
printf("=== test_udp_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(); |
||||||
|
if (!ua) { printf("[FAIL] uasync_create\n"); return 1; } |
||||||
|
|
||||||
|
cli = utun_instance_create_from_str(ua, cfg_node_client()); |
||||||
|
if (!cli) { printf("[FAIL] client instance create\n"); goto done; } |
||||||
|
exit_node = utun_instance_create_from_str(ua, cfg_node_exit()); |
||||||
|
if (!exit_node) { printf("[FAIL] exit instance create\n"); goto done; } |
||||||
|
|
||||||
|
if (utun_instance_init(cli) < 0) { printf("[FAIL] client init\n"); goto done; } |
||||||
|
if (utun_instance_init(exit_node) < 0) { printf("[FAIL] exit init\n"); goto done; } |
||||||
|
|
||||||
|
// UDP echo server (same uasync, no fork)
|
||||||
|
g_echo_sock = socket(AF_INET, SOCK_DGRAM, 0); |
||||||
|
if (g_echo_sock == SOCKET_INVALID) { printf("[FAIL] echo socket\n"); goto done; } |
||||||
|
struct sockaddr_in ea = {.sin_family = AF_INET, .sin_addr = {.s_addr = inet_addr("127.0.0.1")}, .sin_port = htons(UDP_ECHO_PORT)}; |
||||||
|
if (bind(g_echo_sock, (struct sockaddr*)&ea, sizeof(ea)) < 0) { printf("[FAIL] echo bind: %s\n", strerror(errno)); goto done; } |
||||||
|
socket_set_nonblocking(g_echo_sock); |
||||||
|
g_echo_id = uasync_add_socket(ua, g_echo_sock, udp_echo_cb, NULL, NULL, NULL); |
||||||
|
|
||||||
|
g_to_id = uasync_set_timeout(ua, 100, NULL, monitor, "mon"); |
||||||
|
void* to_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS * 10, NULL, test_timeout, "to"); |
||||||
|
|
||||||
|
while (!g_done) uasync_poll(ua, 50); |
||||||
|
|
||||||
|
if (to_id) uasync_cancel_timeout(ua, to_id); |
||||||
|
|
||||||
|
if (g_ok) printf("[PASS] test_udp_proxy — %d bytes echoed, %d echo responses\n", PAYLOAD_SIZE, g_echo_count); |
||||||
|
else printf("[FAIL] test_udp_proxy\n"); |
||||||
|
|
||||||
|
done: |
||||||
|
if (g_to_id) uasync_cancel_timeout(ua, g_to_id); |
||||||
|
if (g_echo_id) uasync_remove_socket(ua, g_echo_id); |
||||||
|
if (g_echo_sock != SOCKET_INVALID) socket_close_wrapper(g_echo_sock); |
||||||
|
if (cli) { cli->running = 0; utun_instance_destroy(cli); } |
||||||
|
if (exit_node) { exit_node->running = 0; utun_instance_destroy(exit_node); } |
||||||
|
if (ua) uasync_destroy(ua, 0); |
||||||
|
return g_ok ? 0 : 1; |
||||||
|
} |
||||||
Loading…
Reference in new issue