Browse Source

add UDP/ICMP proxy, refactor tcp_proxy via_node to instance-level

- 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 creation
congestion
Evgeny 5 months ago
parent
commit
a6311a17d0
  1. 2
      src/Makefile.am
  2. 11
      src/config_parser.c
  3. 2
      src/config_parser.h
  4. 2
      src/etcp_api.h
  5. 4
      src/etcp_connections.c
  6. 291
      src/icmp_proxy.c
  7. 60
      src/icmp_proxy.h
  8. 8
      src/remote_proxy.c
  9. 112
      src/tcp_proxy.c
  10. 7
      src/tcp_proxy.h
  11. 245
      src/udp_proxy.c
  12. 64
      src/udp_proxy.h
  13. 21
      src/utun_instance.c
  14. 1
      src/utun_instance.h
  15. 16
      tests/Makefile.am
  16. 173
      tests/test_icmp_proxy.c
  17. 4
      tests/test_tcp_proxy.c
  18. 197
      tests/test_tcp_proxy_remote.c
  19. 190
      tests/test_udp_proxy.c
  20. 13
      utun.conf.sample

2
src/Makefile.am

@ -36,6 +36,8 @@ utun_CORE_SOURCES = \
tcp_proxy.c \
etcp_router.c \
remote_proxy.c \
udp_proxy.c \
icmp_proxy.c \
uip/uip.c
# Platform-specific TUN libs (Windows only)

11
src/config_parser.c

@ -459,6 +459,10 @@ static int parse_tcp_proxy(const char *key, const char *value, struct global_con
global->tcp_proxy_eim_timeout = atoi(value);
return 0;
}
if (strcmp(key, "via_node") == 0) {
global->tcp_proxy_via_node_id = strtoull(value, NULL, 16);
return 0;
}
if (strcmp(key, "forward") == 0) {
if (global->tcp_proxy_mapping_count >= MAX_TCP_PROXY_MAPPINGS) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Too many tcp_proxy forward rules (max %d)", MAX_TCP_PROXY_MAPPINGS);
@ -473,7 +477,7 @@ static int parse_tcp_proxy(const char *key, const char *value, struct global_con
char* arrow = strtok(NULL, " ");
char* remote_str = strtok(NULL, "");
if (!local_port_str || !arrow || !remote_str || strcmp(arrow, "->") != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward format: %s (expected: port -> ip:port [via NODE_HEX])", value);
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward format: %s (expected: port -> ip:port)", value);
return -1;
}
int local_port = atoi(local_port_str);
@ -488,15 +492,10 @@ static int parse_tcp_proxy(const char *key, const char *value, struct global_con
int remote_port = atoi(remote_port_str);
if (remote_port <= 0 || remote_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward remote port: %s", remote_port_str); return -1; }
uint64_t via_node_id = 0;
char* via_ptr = strstr(remote_port_str, "via ");
if (via_ptr) via_node_id = strtoull(via_ptr + 4, NULL, 16);
int idx = global->tcp_proxy_mapping_count;
global->tcp_proxy_mappings[idx].local_port = (uint16_t)local_port;
strncpy(global->tcp_proxy_mappings[idx].remote_ip, remote_ip_str, sizeof(global->tcp_proxy_mappings[idx].remote_ip) - 1);
global->tcp_proxy_mappings[idx].remote_port = (uint16_t)remote_port;
global->tcp_proxy_mappings[idx].via_node_id = via_node_id;
global->tcp_proxy_mapping_count++;
return 0;
}

2
src/config_parser.h

@ -87,7 +87,6 @@ struct tcp_proxy_mapping_config {
uint16_t local_port;
char remote_ip[64];
uint16_t remote_port;
uint64_t via_node_id; // 0=локальный прокси, !=0=удаленный прокси через эту ноду
};
struct global_config {
@ -158,6 +157,7 @@ struct global_config {
char tcp_proxy_tun_ip[64];
int tcp_proxy_mtu;
int tcp_proxy_eim_timeout;
uint64_t tcp_proxy_via_node_id; // через этот узел проксируются все forward-правила (0=локально)
struct tcp_proxy_mapping_config tcp_proxy_mappings[MAX_TCP_PROXY_MAPPINGS];
int tcp_proxy_mapping_count;

2
src/etcp_api.h

@ -26,6 +26,8 @@
#define ETCP_ID_NAT 0x02 // NAT трафик между узлами
#define ETCP_ID_SVC_ROUTE 0x03 // Маршрутизируемые сервисные пакеты (etcp_router)
#define ETCP_ID_TCP_PROXY 0x04 // TCP proxy через удаленный узел (remote_proxy)
#define ETCP_ID_UDP_PROXY 0x05 // UDP datagram прокси (client ↔ exit)
#define ETCP_ID_ICMP_PROXY 0x06 // ICMP echo прокси (ping через exit)
// Forward declarations
struct ETCP_CONN;

4
src/etcp_connections.c

@ -151,6 +151,8 @@ static void etcp_link_send_init(struct ETCP_LINK* link, uint8_t reset) {
// padding
int s = rand() % (link->handshake_maxsize - link->handshake_minsize) + link->handshake_minsize;
if (s > link->mtu - dgram->noencrypt_len) s = link->mtu - dgram->noencrypt_len;
// Cap s: final encrypted = s + 46 for REQUEST (noencrypt=72)
if (s > 1472 - 46) s = 1472 - 46; // s ≤ 1426
int to_add=s-offset-UDP_HDR_SIZE - UDP_SC_HDR_SIZE;
if (to_add<0) to_add=0;
@ -1686,6 +1688,8 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
// padding
int s = rand() % (link->handshake_maxsize - link->handshake_minsize) + link->handshake_minsize;
if (s > link->mtu) s = link->mtu;
// Cap s: final encrypted = s - 26 for RESPONSE (noencrypt=0)
if (s > 1472 + 26) s = 1472 + 26; // s ≤ 1498
int to_add=s - xoffset - UDP_HDR_SIZE - UDP_SC_HDR_SIZE;
if (to_add<0) to_add=0;

291
src/icmp_proxy.c

@ -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");
}

60
src/icmp_proxy.h

@ -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

8
src/remote_proxy.c

@ -1,6 +1,8 @@
// remote_proxy.c — Удаленный TCP прокси (exit node)
#include "remote_proxy.h"
#include "tcp_proxy.h"
#include "udp_proxy.h"
#include "icmp_proxy.h"
#include "etcp.h"
#include "etcp_api.h"
#include "etcp_router.h"
@ -214,6 +216,10 @@ int remote_proxy_init(struct UTUN_INSTANCE* inst) {
// Register standalone handler for when tcp_proxy is not using remote mappings
// (tcp_proxy_create will overwrite this handler if it has remote mappings)
etcp_router_bind(inst, ETCP_ID_TCP_PROXY, rp_standalone_recv_cb);
if (!inst->config || !inst->config->global.tcp_proxy_enabled) {
udp_proxy_init(inst, inst->ua);
icmp_proxy_init(inst, inst->ua);
}
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "remote_proxy initialized on node %016llx", (unsigned long long)inst->node_id);
return 0;
}
@ -225,5 +231,7 @@ void remote_proxy_destroy(struct UTUN_INSTANCE* inst) {
while (rc) { struct remote_proxy_conn* next = rc->next; rp_conn_free(rc); rc = next; }
ctx->conns = NULL; ctx->enabled = 0;
if (g_rp_ctx == ctx) g_rp_ctx = NULL;
udp_proxy_destroy(inst);
icmp_proxy_destroy(inst);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "remote_proxy destroyed");
}

112
src/tcp_proxy.c

@ -7,6 +7,8 @@
#include "etcp.h"
#include "etcp_router.h"
#include "remote_proxy.h"
#include "udp_proxy.h"
#include "icmp_proxy.h"
#include "uip/uip.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
@ -36,6 +38,7 @@ static uip_ipaddr_t g_pkt_dest_ip;
static uint16_t g_pkt_dest_port;
static struct tcp_proxy* g_tcp_proxy = NULL;
static int g_timer_period_tb = PROXY_TIMER_TB; // can be shortened for tests
static int g_uip_inited = 0;
// ====================================================================
// Forward declarations
@ -110,6 +113,34 @@ static inline void tcp_proxy_write_output(struct tcp_proxy* p) {
else if (write(p->ip_fd, uip_buf, uip_len)) {}
}
static int tcp_proxy_handle_non_tcp(struct tcp_proxy* p, uint8_t* buf, size_t len) {
if (len < 20) return 0;
uint8_t ip_ver = (buf[0] >> 4) & 0xF;
if (ip_ver != 4) return 0;
uint8_t proto = buf[9];
if (proto == IPPROTO_UDP) {
if (!p->has_remote_mappings) return 0;
if (len < 28) return 0;
uint32_t src_ip, dst_ip; uint16_t src_port, dst_port;
memcpy(&src_ip, buf + 12, 4); memcpy(&dst_ip, buf + 16, 4);
memcpy(&src_port, buf + 20, 2); memcpy(&dst_port, buf + 22, 2);
udp_proxy_send_to_exit(p->inst, p->via_node_id, src_ip, src_port, dst_ip, dst_port, buf + 28, len - 28);
return 1;
}
if (proto == IPPROTO_ICMP) {
if (!p->has_remote_mappings) return 0;
if (len < 28) return 0;
uint8_t icmp_type = buf[20];
if (icmp_type != 8) return 0;
uint32_t dst_ip; memcpy(&dst_ip, buf + 16, 4);
uint16_t icmp_id, icmp_seq;
memcpy(&icmp_id, buf + 24, 2); memcpy(&icmp_seq, buf + 26, 2);
icmp_proxy_send_to_exit(p->inst, p->via_node_id, dst_ip, icmp_id, icmp_seq, buf + 28, len - 28);
return 1;
}
return 0;
}
// ====================================================================
// OS socket transport (passive connections)
// ====================================================================
@ -259,7 +290,7 @@ static struct etcp_transport* etcp_transport_create(struct proxy_conn* pc, struc
if (!et) return NULL;
et->base.ops = &etcp_transport_ops; et->conn = pc; et->inst = inst;
et->remote_node_id = remote_node_id;
et->stream_id = ++g_tcp_proxy->next_stream_id;
et->stream_id = ++pc->proxy->next_stream_id;
pc->remote_stream_id = et->stream_id;
uint8_t conn_buf[6];
memcpy(conn_buf, pc->dest_ip, 4); memcpy(conn_buf + 4, &pc->dest_port, 2);
@ -281,14 +312,14 @@ static struct etcp_transport* etcp_transport_create(struct proxy_conn* pc, struc
// ====================================================================
// Stream lookup helpers
// ====================================================================
static struct proxy_conn* find_pc_by_stream(uint64_t stream_id) {
static struct proxy_conn* find_pc_by_stream(struct tcp_proxy* p, uint64_t stream_id) {
struct proxy_conn* pc;
for (pc = g_tcp_proxy->conns; pc; pc = pc->next) if (pc->remote_stream_id == stream_id) return pc;
for (pc = p->conns; pc; pc = pc->next) if (pc->remote_stream_id == stream_id) return pc;
return NULL;
}
static void handle_connected(uint64_t stream_id, struct ll_entry* entry) {
struct proxy_conn* pc = find_pc_by_stream(stream_id);
static void handle_connected(struct tcp_proxy* p, uint64_t stream_id, struct ll_entry* entry) {
struct proxy_conn* pc = find_pc_by_stream(p, stream_id);
if (!pc || !pc->transport) {
queue_entry_free(entry); queue_dgram_free(entry);
return;
@ -298,7 +329,15 @@ static void handle_connected(uint64_t stream_id, struct ll_entry* entry) {
if (entry->len >= TCP_PROXY_CONNECTED_HDR_SIZE) {
uint8_t status = entry->dgram[TCP_PROXY_HDR_SIZE + 2];
if (status == TCP_PROXY_CONNECTED_OK) { et->connected = 1;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy: remote connected stream=%016llx", (unsigned long long)stream_id); }
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy: remote connected stream=%016llx", (unsigned long long)stream_id);
struct ll_entry* e;
while ((e = queue_data_get(pc->uip_to_transport)) != NULL) {
if (e->len > 0) {
etcp_transport_send(&et->base, e->dgram, e->len);
}
queue_dgram_free(e); queue_entry_free(e);
}
}
else { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy: remote refused stream=%016llx", (unsigned long long)stream_id);
pc->closing = 1; }
}
@ -315,7 +354,8 @@ void tcp_proxy_etcp_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
}
uint8_t subcmd = entry->dgram[1];
uint64_t stream_id; memcpy(&stream_id, entry->dgram + 2, 8);
struct UTUN_INSTANCE* inst = conn ? conn->instance : (g_tcp_proxy ? g_tcp_proxy->inst : NULL);
struct UTUN_INSTANCE* inst = conn ? conn->instance : NULL;
struct tcp_proxy* proxy = inst ? inst->tcp_proxy : NULL;
// CONNECT → remote_proxy
if (subcmd == TCP_PROXY_SUBCMD_CONNECT) {
@ -332,10 +372,10 @@ void tcp_proxy_etcp_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
}
}
// CONNECTED / DATA / CLOSE for tcp_proxy
if (g_tcp_proxy) {
if (subcmd == TCP_PROXY_SUBCMD_CONNECTED) { handle_connected(stream_id, entry); return; }
if (proxy) {
if (subcmd == TCP_PROXY_SUBCMD_CONNECTED) { handle_connected(proxy, stream_id, entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_DATA) {
struct proxy_conn* pc = find_pc_by_stream(stream_id);
struct proxy_conn* pc = find_pc_by_stream(proxy, stream_id);
if (pc) {
size_t data_len = entry->len - TCP_PROXY_HDR_SIZE;
if (data_len > 0) {
@ -345,7 +385,7 @@ void tcp_proxy_etcp_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
}
}
if (subcmd == TCP_PROXY_SUBCMD_CLOSE) {
struct proxy_conn* pc = find_pc_by_stream(stream_id);
struct proxy_conn* pc = find_pc_by_stream(proxy, stream_id);
if (pc) pc->closing = 1;
}
}
@ -391,10 +431,9 @@ void tcp_proxy_appcall(void)
pc->uip_to_transport = queue_new(pc->proxy->ua, 0, 0, 0, "uip_to_transport");
pc->transport_to_uip = queue_new(pc->proxy->ua, 0, 0, 0, "transport_to_uip");
if(!pc->uip_to_transport || !pc->transport_to_uip) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "Failed to create queues"); uip_abort(); return; }
struct tcp_proxy_mapping* m = find_mapping_by_port(pc->proxy, uip_conn->lport);
uint64_t via_node = m ? m->via_node_id : 0;
if(via_node != 0 && g_tcp_proxy && g_tcp_proxy->inst) {
struct etcp_transport* et = etcp_transport_create(pc, g_tcp_proxy->inst, via_node);
uint64_t via_node = pc->proxy->via_node_id;
if(via_node != 0 && pc->proxy->inst && via_node != pc->proxy->inst->node_id) {
struct etcp_transport* et = etcp_transport_create(pc, pc->proxy->inst, via_node);
if(!et) { uip_abort(); return; }
pc->transport = &et->base;
} else {
@ -491,11 +530,14 @@ static void tcp_proxy_raw_read(int fd, void* arg) {
(void)fd; struct tcp_proxy* p = (struct tcp_proxy*)arg;
uint8_t buf[UIP_BUFSIZE]; ssize_t n = read(p->ip_fd, buf, sizeof(buf));
if(n <= 0) return;
if (tcp_proxy_handle_non_tcp(p, buf, n)) return;
struct tcp_proxy* saved = g_tcp_proxy; g_tcp_proxy = p;
memcpy(uip_buf, buf, n); uip_len = n;
uip_ipaddr_copy(g_pkt_dest_ip, BUF->destipaddr); g_pkt_dest_port = BUF->destport;
uip_ipaddr_copy(uip_hostaddr, BUF->destipaddr);
uip_input();
tcp_proxy_write_output(p);
g_tcp_proxy = saved;
}
// ====================================================================
@ -508,11 +550,15 @@ static void tcp_proxy_tun_input(struct ll_queue* q, void* arg) {
if(entry->dgram && entry->len > 1) {
uint8_t* ip = entry->dgram + 1; size_t len = entry->len - 1;
if(len > 0 && len <= UIP_BUFSIZE) {
memcpy(uip_buf, ip, len); uip_len = len;
uip_ipaddr_copy(g_pkt_dest_ip, BUF->destipaddr); g_pkt_dest_port = BUF->destport;
uip_ipaddr_copy(uip_hostaddr, BUF->destipaddr);
uip_input();
tcp_proxy_write_output(p);
if (!tcp_proxy_handle_non_tcp(p, ip, len)) {
struct tcp_proxy* saved = g_tcp_proxy; g_tcp_proxy = p;
memcpy(uip_buf, ip, len); uip_len = len;
uip_ipaddr_copy(g_pkt_dest_ip, BUF->destipaddr); g_pkt_dest_port = BUF->destport;
uip_ipaddr_copy(uip_hostaddr, BUF->destipaddr);
uip_input();
tcp_proxy_write_output(p);
g_tcp_proxy = saved;
}
}
}
queue_dgram_free(entry); queue_entry_free(entry); queue_resume_callback(q);
@ -523,10 +569,13 @@ static void tcp_proxy_tun_input(struct ll_queue* q, void* arg) {
// ====================================================================
static void tcp_proxy_periodic(void* arg) {
struct tcp_proxy* p = (struct tcp_proxy*)arg;
struct tcp_proxy* saved = g_tcp_proxy; g_tcp_proxy = p;
int i;
for(i = 0; i < UIP_CONNS; i++) {
struct proxy_conn* pc = (struct proxy_conn*)uip_conns[i].appstate;
if(pc) uip_ipaddr_copy(uip_hostaddr, pc->tun_ip);
if(!pc) continue;
if(pc->proxy != p) continue;
uip_ipaddr_copy(uip_hostaddr, pc->tun_ip);
uip_periodic(i);
tcp_proxy_write_output(p);
}
@ -557,6 +606,7 @@ static void tcp_proxy_periodic(void* arg) {
}
p->uip_timer_id = uasync_set_timeout(p->ua, g_timer_period_tb, p, tcp_proxy_periodic, "uip_periodic");
g_tcp_proxy = saved;
}
// ====================================================================
@ -565,7 +615,7 @@ static void tcp_proxy_periodic(void* arg) {
struct tcp_proxy* tcp_proxy_create(struct UTUN_INSTANCE* inst, struct UASYNC* ua,
const char* tun_name, const char* tun_ip, int mtu, int test_mode,
struct tcp_proxy_mapping_config* mappings, int mapping_count,
int eim_timeout_sec, int use_tun, int ip_fd)
int eim_timeout_sec, int use_tun, int ip_fd, uint64_t via_node_id)
{
if(!ua) return NULL;
@ -573,6 +623,10 @@ struct tcp_proxy* tcp_proxy_create(struct UTUN_INSTANCE* inst, struct UASYNC* ua
if(!p) return NULL;
p->inst = inst; p->ua = ua; p->ip_fd = -1; p->next_stream_id = 1;
p->eim_timeout_tb = eim_timeout_sec > 0 ? eim_timeout_sec * 10000 : 300000;
p->via_node_id = via_node_id;
p->has_remote_mappings = (via_node_id != 0 && inst && via_node_id != inst->node_id) ? 1 : 0;
p->tun_ip = tun_ip ? inet_addr(tun_ip) : 0;
p->tun_ip = tun_ip ? inet_addr(tun_ip) : 0;
p->entry_pool = memory_pool_init(sizeof(struct ll_entry));
if(!p->entry_pool) { u_free(p); return NULL; }
@ -588,7 +642,7 @@ struct tcp_proxy* tcp_proxy_create(struct UTUN_INSTANCE* inst, struct UASYNC* ua
if(!p->ip_fd_id) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy: uasync_add_socket for ip_fd failed"); memory_pool_destroy(p->entry_pool); u_free(p); return NULL; }
}
uip_init();
if (!g_uip_inited) { uip_init(); g_uip_inited = 1; }
uip_ipaddr_t addr; uip_ipaddr(addr, 127,0,0,1); uip_sethostaddr(addr);
if(mapping_count > 0) {
@ -598,15 +652,12 @@ struct tcp_proxy* tcp_proxy_create(struct UTUN_INSTANCE* inst, struct UASYNC* ua
struct tcp_proxy_mapping* m = u_calloc(1, sizeof(struct tcp_proxy_mapping));
if(m) { m->local_port = port_net; struct in_addr ra; ra.s_addr = inet_addr(mappings[j].remote_ip);
memcpy(m->remote_ip, &ra.s_addr, 4); m->remote_port = htons(mappings[j].remote_port); m->dynamic = 0;
m->via_node_id = mappings[j].via_node_id;
if(m->via_node_id != 0) p->has_remote_mappings = 1;
m->next = p->mappings; p->mappings = m;
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TCP proxy mapping: %d -> %s:%d %s", mappings[j].local_port, mappings[j].remote_ip, mappings[j].remote_port,
m->via_node_id ? "(remote)" : ""); }
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TCP proxy mapping: %d -> %s:%d%s", mappings[j].local_port, mappings[j].remote_ip, mappings[j].remote_port,
p->has_remote_mappings ? " (remote)" : ""); }
}
} else { uip_set_promiscuous(1); uip_listen_all(1); }
g_tcp_proxy = p;
p->uip_timer_id = uasync_set_timeout(ua, g_timer_period_tb, p, tcp_proxy_periodic, "uip_periodic");
// Register etcp_router handler if we have remote proxy mappings
@ -616,6 +667,8 @@ struct tcp_proxy* tcp_proxy_create(struct UTUN_INSTANCE* inst, struct UASYNC* ua
p->has_remote_mappings = 0;
} else {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy: etcp_router bind registered for ID=0x%02x", ETCP_ID_TCP_PROXY);
udp_proxy_init(inst, ua);
icmp_proxy_init(inst, ua);
}
}
@ -626,6 +679,8 @@ struct tcp_proxy* tcp_proxy_create(struct UTUN_INSTANCE* inst, struct UASYNC* ua
void tcp_proxy_destroy(struct tcp_proxy* p) {
if(!p) return;
if(p->has_remote_mappings && p->inst) etcp_router_unbind(p->inst, ETCP_ID_TCP_PROXY);
udp_proxy_destroy(p->inst);
icmp_proxy_destroy(p->inst);
if(p->uip_timer_id) { uasync_cancel_timeout(p->ua, p->uip_timer_id); p->uip_timer_id = NULL; }
struct proxy_conn* pc = p->conns;
while(pc) { struct proxy_conn* next = pc->next;
@ -639,7 +694,6 @@ void tcp_proxy_destroy(struct tcp_proxy* p) {
if(p->ip_fd_id) { uasync_remove_socket(p->ua, p->ip_fd_id); p->ip_fd_id = NULL; }
if(p->tun) tun_close(p->tun);
if(p->entry_pool) memory_pool_destroy(p->entry_pool);
if(g_tcp_proxy == p) g_tcp_proxy = NULL;
u_free(p);
}

7
src/tcp_proxy.h

@ -38,7 +38,6 @@ struct tcp_proxy_mapping {
int dynamic;
uint64_t created_tb;
uint64_t delete_at_tb;
uint64_t via_node_id; // 0=local proxy, !=0=remote via this node
};
// One proxy connection: uIP TCP ↔ ll_queues ↔ transport ↔ destination
@ -76,7 +75,9 @@ struct tcp_proxy {
struct tcp_proxy_mapping* mappings;
int eim_timeout_tb;
uint64_t next_stream_id; // counter for remote proxy stream IDs
int has_remote_mappings; // 1=at least one mapping uses via_node_id
uint64_t via_node_id; // узел через который проксируются все forward (0=локально)
int has_remote_mappings; // 1=via_node_id задан и не равен local node_id
uint32_t tun_ip; // TUN IP (network order) for building reply packets
};
// ========== API ==========
@ -87,7 +88,7 @@ struct tcp_proxy {
struct tcp_proxy* tcp_proxy_create(struct UTUN_INSTANCE* inst, struct UASYNC* ua,
const char* tun_name, const char* tun_ip, int mtu, int test_mode,
struct tcp_proxy_mapping_config* mappings, int mapping_count,
int eim_timeout_sec, int use_tun, int ip_fd);
int eim_timeout_sec, int use_tun, int ip_fd, uint64_t via_node_id);
void tcp_proxy_destroy(struct tcp_proxy* p);

245
src/udp_proxy.c

@ -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");
}

64
src/udp_proxy.h

@ -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

21
src/utun_instance.c

@ -18,6 +18,9 @@
#include <stdio.h>
#include <string.h>
#include <errno.h>
#ifndef _WIN32
#include <unistd.h>
#endif
#include <unistd.h>
#include "../lib/platform_compat.h"
#include "../lib/mem.h"
@ -153,9 +156,9 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
const char* tun_name = config->global.tcp_proxy_tun_name[0] ? config->global.tcp_proxy_tun_name : "tun_tcp";
const char* tun_ip = config->global.tcp_proxy_tun_ip[0] ? config->global.tcp_proxy_tun_ip : "10.99.0.1";
int mtu = config->global.tcp_proxy_mtu > 0 ? config->global.tcp_proxy_mtu : 1500;
instance->tcp_proxy = tcp_proxy_create(instance, ua, tun_name, tun_ip, mtu, 0,
instance->tcp_proxy = tcp_proxy_create(instance, ua, tun_name, tun_ip, mtu, g_tun_init_enabled ? 0 : 1,
config->global.tcp_proxy_mappings, config->global.tcp_proxy_mapping_count,
config->global.tcp_proxy_eim_timeout, 1, -1);
config->global.tcp_proxy_eim_timeout, 1, -1, config->global.tcp_proxy_via_node_id);
if (instance->tcp_proxy) {
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TCP proxy enabled: TUN=%s IP=%s MTU=%d mappings=%d",
tun_name, tun_ip, mtu, config->global.tcp_proxy_mapping_count);
@ -254,6 +257,20 @@ struct UTUN_INSTANCE* utun_instance_create_from_config(struct UASYNC* ua, struct
return instance;
}
// Create instance from config text string (writes to temp file, parses, cleans up)
struct UTUN_INSTANCE* utun_instance_create_from_str(struct UASYNC* ua, const char* config_text) {
if (!config_text) return NULL;
char tmp_path[] = "/tmp/utun_cfg_XXXXXX";
int fd = mkstemp(tmp_path);
if (fd < 0) return NULL;
size_t len = strlen(config_text);
if (write(fd, config_text, len) != (ssize_t)len) { close(fd); unlink(tmp_path); return NULL; }
close(fd);
struct UTUN_INSTANCE* inst = utun_instance_create(ua, tmp_path);
unlink(tmp_path);
return inst;
}
// Destroy instance and cleanup resources
void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
if (!instance) return;

1
src/utun_instance.h

@ -110,6 +110,7 @@ struct UTUN_INSTANCE {
// Functions
struct UTUN_INSTANCE* utun_instance_create(struct UASYNC* ua, const char* config_file);
struct UTUN_INSTANCE* utun_instance_create_from_config(struct UASYNC* ua, struct utun_config* config);
struct UTUN_INSTANCE* utun_instance_create_from_str(struct UASYNC* ua, const char* config_text);
void utun_instance_destroy(struct UTUN_INSTANCE* instance);
int utun_instance_init(struct UTUN_INSTANCE *instance);
struct UTUN_INSTANCE *utun_instance_reload(struct UTUN_INSTANCE *instance, struct UASYNC *ua, const char *config_file);

16
tests/Makefile.am

@ -34,6 +34,8 @@ check_PROGRAMS = \
test_tcp_proxy \
test_etcp_router \
test_remote_proxy \
test_udp_proxy \
test_icmp_proxy \
test_radix \
test_route6_lib \
bench_timeout_heap \
@ -41,7 +43,8 @@ check_PROGRAMS = \
# Долгие тесты: запускаются только вручную, не включаются в make check
# test_etcp_congestion — 25+ секунд, congestion control simulation
noinst_PROGRAMS = test_etcp_congestion
# test_tcp_proxy_remote — 2-node TCP через etcp, требует fix g_tcp_proxy singleton
noinst_PROGRAMS = test_etcp_congestion test_tcp_proxy_remote
# test_crypto and test_ecc_encrypt only needed for TinyCrypt (not when using OpenSSL)
if USE_OPENSSL
@ -115,6 +118,8 @@ ETCP_FULL_OBJS = \
$(top_builddir)/src/utun-tcp_proxy.o \
$(top_builddir)/src/utun-etcp_router.o \
$(top_builddir)/src/utun-remote_proxy.o \
$(top_builddir)/src/utun-udp_proxy.o \
$(top_builddir)/src/utun-icmp_proxy.o \
$(top_builddir)/src/uip/utun-uip.o \
$(ETCP_CORE_OBJS)
@ -182,6 +187,9 @@ test_etcp_congestion_SOURCES = test_etcp_congestion.c
test_etcp_congestion_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_etcp_congestion_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_tcp_proxy_remote_SOURCES = test_tcp_proxy_remote.c
test_tcp_proxy_remote_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
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)
@ -195,6 +203,12 @@ test_etcp_router_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS)
test_remote_proxy_SOURCES = test_remote_proxy.c
test_remote_proxy_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_udp_proxy_SOURCES = test_udp_proxy.c
test_udp_proxy_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_icmp_proxy_SOURCES = test_icmp_proxy.c
test_icmp_proxy_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_radix_SOURCES = test_radix.c
test_radix_CFLAGS = -I$(top_srcdir)/lib
test_radix_LDADD = $(COMMON_LIBS)

173
tests/test_icmp_proxy.c

@ -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;
}

4
tests/test_tcp_proxy.c

@ -45,7 +45,7 @@ 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);
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);
}
@ -71,7 +71,7 @@ int main(void) {
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]);
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; }
// 5. Active open

197
tests/test_tcp_proxy_remote.c

@ -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;
}

190
tests/test_udp_proxy.c

@ -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;
}

13
utun.conf.sample

@ -76,21 +76,18 @@ allow=all
#tun_ip=100.64.1.1/24 # IP клиентского NAT TUN
#nat_via=0xABCD000000000001 # node_id провайдера (у кого запрашивать NAT)
# --- TCP Proxy (локальный TCP прокси) ---
# Проксирует входящие TCP соединения в удалённый TCP адрес.
# Два режима:
# 1. Локально (силами этой ноды):
# forward=LOCAL_PORT -> IP:PORT
# 2. Через удалённую ноду (exit node):
# forward=LOCAL_PORT -> IP:PORT via NODE_ID
# --- TCP Proxy (локальный TCP/UDP/ICMP прокси) ---
# Проксирует входящие TCP (через uIP), UDP и ICMP ping через указанный via_node.
# Все три протокола идут через один и тот же удалённый узел.
#[tcp_proxy]
#enabled=yes
#tun_name=tun_tcp # имя TUN интерфейса (по умолчанию tun_tcp)
#tun_ip=10.99.0.1 # IP адрес TUN интерфейса
#mtu=1500 # MTU
#eim_timeout=300 # таймаут EIM маппингов (сек)
#via_node=0xABCD000000000001 # узел для проксирования (или свой node_id для локального)
#forward=8000 -> 10.0.0.50:80
#forward=2222 -> 10.0.0.50:22 via 0xABCD000000000001
#forward=2222 -> 10.0.0.50:22
# --- Remote Proxy (удаленный exit node) ---
# Принимает CONNECT-запросы от других нод и открывает OS сокеты к адресатам.

Loading…
Cancel
Save