Browse Source

proxy: rename + simplify + backpressure

- tcp_proxy/remote_proxy → tcp_proxy_client/tcp_proxy_server
- Все proxy файлы перенесены в src/proxy/
- Конфиг: [tcp_proxy]→[tcp_proxy_client], [remote_proxy]→[tcp_proxy_server]
- Убран subcmd CONNECTED (0x02) — клиент шлёт DATA сразу после CONNECT
- Backpressure: etcp_router_waiter_register/cancel через ll_queue threshold waiter
- Клиент: recv_cb send fail → tx_buf+waiter, не tcp_recved() (окно lwIP=0)
- Сервер: read_cb send fail → pause_buf+waiter, пауза read_id
- CLOSE/ERROR отправляется только после drain всех очередей
- Fail: conn не найден/closed → send ERROR вместо CLOSE/silent drop
- PROTOCOL.md: полная архитектура с диаграммами состояний и потоков
etcp-inflight-fix
Evgeny 4 months ago
parent
commit
1f4fe37fe5
  1. 121
      lib/ll_queue.c
  2. 76
      lib/ll_queue.h
  3. 8
      src/Makefile.am
  4. 58
      src/config_parser.c
  5. 26
      src/config_parser.h
  6. 72
      src/control_server.c
  7. 1
      src/etcp.c
  8. 2
      src/etcp_api.h
  9. 16
      src/etcp_router.c
  10. 6
      src/etcp_router.h
  11. 2
      src/lwip_tcp/lwip_tcp.h
  12. 6
      src/pkt_normalizer.c
  13. 3
      src/pkt_normalizer.h
  14. 477
      src/proxy/PROTOCOL.md
  15. 8
      src/proxy/icmp_proxy.c
  16. 0
      src/proxy/icmp_proxy.h
  17. 315
      src/proxy/tcp_proxy_client.c
  18. 40
      src/proxy/tcp_proxy_client.h
  19. 271
      src/proxy/tcp_proxy_server.c
  20. 44
      src/proxy/tcp_proxy_server.h
  21. 6
      src/proxy/udp_proxy.c
  22. 0
      src/proxy/udp_proxy.h
  23. 52
      src/utun_instance.c
  24. 12
      src/utun_instance.h
  25. 22
      tests/Makefile.am
  26. 2
      tests/tcp_proxy_full/client.conf
  27. 2
      tests/tcp_proxy_full/exit.conf
  28. 10
      tests/test_icmp_proxy.c
  29. 56
      tests/test_intensive_memory_pool.c
  30. 163
      tests/test_ll_queue.c
  31. 36
      tests/test_memory_pool_and_config.c
  32. 20
      tests/test_tcp_proxy_client.c
  33. 22
      tests/test_tcp_proxy_remote.c
  34. 74
      tests/test_tcp_proxy_server.c
  35. 12
      tests/test_udp_proxy.c
  36. 65
      tools/etcpmon/etcpmon_client.c
  37. 10
      tools/etcpmon/etcpmon_client.h
  38. 152
      tools/etcpmon/etcpmon_gui.c
  39. 15
      tools/etcpmon/etcpmon_gui.h
  40. 19
      tools/etcpmon/etcpmon_protocol.h
  41. 10
      utun.conf.sample

121
lib/ll_queue.c

@ -46,8 +46,6 @@ static inline void queue_check_thread(struct ll_queue* q) {
// Предварительные объявления внутренних функций
static void queue_resume_timeout_cb(void* arg);
static void check_waiters(struct ll_queue* q);
static void add_to_hash(struct ll_queue* q, struct ll_entry* entry);
static void remove_from_hash(struct ll_queue* q, struct ll_entry* entry);
// ==================== Управление очередью ====================
@ -60,6 +58,8 @@ struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, uint16_t index_o
q->name = name;
q->ua = ua;
q->size_limit = -1; // Без ограничения по умолчанию
q->threshold_max_packets = 0; // По умолчанию: ждать пустой очереди
q->threshold_max_bytes = 0; // По умолчанию: не проверять байты
q->hash_size = hash_size;
q->index_offset = index_offset;
q->index_size = index_size;
@ -112,6 +112,17 @@ void queue_free(struct ll_queue* q) {
// ВАЖНО: Не освобождаем элементы в очереди - они должны быть извлечены отдельно
// Это упрощает архитектуру и предотвращает double-u_free
// Освободить список waiters (сбросить handle->internal)
struct queue_waiter* w = q->waiter_head;
while (w) {
struct queue_waiter* next = w->next;
if (w->handle) w->handle->internal = NULL;
u_free(w);
w = next;
}
q->waiter_head = NULL;
q->waiter_tail = NULL;
// Освободить хеш-таблицу
if (q->hash_table) {
u_free(q->hash_table);
@ -266,24 +277,27 @@ static uint32_t make_hash(const void* data, uint16_t len) {// алгоритм F
return hash;
}
// Проверить и запустить ожидающие коллбэки
// Проверить и запустить первый ожидающий коллбэк (FIFO / round-robin)
static void check_waiters(struct ll_queue* q) {
if (!q) return;
if (!q || !q->waiter_head) return;
// DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: checking waiters, count=%d, bytes=%zu", q->count, q->total_bytes);
const int cond = q->count <= q->threshold_max_packets
&& (q->threshold_max_bytes == 0 || q->total_bytes <= q->threshold_max_bytes);
if (!cond) return;
struct queue_waiter* waiter = &q->waiter;
struct queue_waiter* waiter = q->waiter_head;
q->waiter_head = waiter->next;
if (!q->waiter_head) q->waiter_tail = NULL;
if (waiter->callback) {
// Проверить условие: не больше max_packets и не больше max_bytes
// max_bytes = 0 означает "не проверять байты"
if (q->count <= waiter->max_packets && (waiter->max_bytes == 0 || q->total_bytes <= waiter->max_bytes)) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition met, calling callback, count=%d<=%d, bytes=%zu<=%zu (max_bytes_check=%s)",
q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes,
waiter->max_bytes == 0 ? "disabled" : "enabled");
waiter->callback(q, waiter->callback_arg);
memset(waiter, 0, sizeof(*waiter));
}
struct queue_waiter_handle* h = waiter->handle;
if (h) h->internal = NULL;
u_free(waiter);
if (h) {
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: waking head waiter, count=%d<=%d, bytes=%zu<=%zu",
q->count, q->threshold_max_packets, q->total_bytes, q->threshold_max_bytes);
h->callback(q, h->callback_arg);
}
}
@ -532,34 +546,73 @@ int queue_check_consistency(struct ll_queue* q) {
return 0;
}
// ==================== Асинхронное ожидание ====================
// ==================== Пороговое ожидание (backpressure) ====================
void queue_set_threshold(struct ll_queue* q, int max_packets, size_t max_bytes) {
if (!q) return;
q->threshold_max_packets = max_packets;
q->threshold_max_bytes = max_bytes;
}
void queue_waiter_handle_init(struct queue_waiter_handle* h,
queue_threshold_callback_fn callback, void* arg) {
if (!h) return;
h->internal = NULL;
h->callback = callback;
h->callback_arg = arg;
}
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes,
queue_threshold_callback_fn callback, void* arg) {
if (!q || !callback) return NULL;
int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h) {
if (!q || !h) return 1;
struct queue_waiter* waiter = &q->waiter;
if (h->internal) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] queue_waiter_wait: waiter already registered", q->name);
return 1;
}
// Проверить условие немедленно
if (q->count <= max_packets && (max_bytes == 0 || q->total_bytes <= max_bytes)) {
// Условие уже выполнено - вызвать коллбэк немедленно
callback(q, arg);
return NULL;
if (q->count <= q->threshold_max_packets
&& (q->threshold_max_bytes == 0 || q->total_bytes <= q->threshold_max_bytes)) {
h->callback(q, h->callback_arg);
return 1;
}
struct queue_waiter* waiter = u_malloc(sizeof(struct queue_waiter));
if (!waiter) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] queue_waiter_wait: u_malloc failed", q->name);
return -1;
}
waiter->next = NULL;
waiter->handle = h;
h->internal = waiter;
// Установить waiter для отложенного вызова
waiter->max_packets = max_packets;
waiter->max_bytes = max_bytes;
waiter->callback = callback;
waiter->callback_arg = arg;
if (q->waiter_tail) q->waiter_tail->next = waiter; else q->waiter_head = waiter;
q->waiter_tail = waiter;
return waiter;
return 0;
}
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter) {
if (!q || !waiter || waiter != &q->waiter) return;
void queue_waiter_cancel(struct ll_queue* q, struct queue_waiter_handle* h) {
if (!q || !h || !h->internal) return;
struct queue_waiter* waiter = h->internal;
struct queue_waiter* prev = NULL;
struct queue_waiter* curr = q->waiter_head;
while (curr && curr != waiter) {
prev = curr;
curr = curr->next;
}
if (!curr) {
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] queue_waiter_cancel: waiter not in list", q->name);
h->internal = NULL;
return;
}
if (prev) prev->next = waiter->next; else q->waiter_head = waiter->next;
if (waiter == q->waiter_tail) q->waiter_tail = prev;
memset(waiter, 0, sizeof(*waiter));
h->internal = NULL;
u_free(waiter);
}
// ==================== Поиск и удаление по ID ====================

76
lib/ll_queue.h

@ -26,7 +26,7 @@
* - Обязательный вызов queue_resume_callback после обработки элемента
* - Поддержка пулов памяти для entry и отдельно для dgram
* - Хеш-таблица для быстрого поиска по ID (опционально)
* - Встроенный одиночный waiter для ожидания освобождения очереди до заданного порога
* - Связный список waiters для backpressure c общим порогом (round-robin)
* - Использует uasync для отложенного возобновления callback'ов (без рекурсии в стеке)
*
* @note Важные правила использования автозабора (queue_set_callback):
@ -37,13 +37,15 @@
* 5. queue_data_get() автоматически suspend'ит коллбэки для предотвращения рекурсии
*
* @note Память:
* - queue_free() освобождает ТОЛЬКО структуру очереди и хеш-таблицу
* - queue_free() освобождает структуру очереди, хеш-таблицу и список waiters
* (handle->internal каждого ожидающего сбрасывается в NULL)
* - Все элементы должны быть извлечены через queue_data_get() и освобождены через queue_entry_free()
* - dgram освобождается отдельно через queue_dgram_free() или кастомную dgram_free_fn
*/
// Forward declaration
// Forward declarations
struct ll_queue;
struct queue_waiter_handle;
/**
* @struct ll_entry
@ -84,7 +86,7 @@ typedef void (*queue_callback_fn)(struct ll_queue* q, void* arg);
/**
* @typedef queue_threshold_callback_fn
* @brief Одноразовый коллбэк при достижении порога очереди (queue_wait_threshold).
* @brief Одноразовый коллбэк при достижении порога очереди.
* @param q указатель на очередь
* @param arg пользовательский аргумент
*/
@ -92,13 +94,27 @@ typedef void (*queue_threshold_callback_fn)(struct ll_queue* q, void* arg);
/**
* @struct queue_waiter
* @brief Описание ожидания освобождения места в очереди (встроен в ll_queue — только один).
* @brief Внутренний узел в связном списке ожидающих (очередь владеет, аллоцирует/освобождает сама).
*/
struct queue_waiter {
int max_packets; ///< Максимально допустимое количество элементов
size_t max_bytes; ///< Максимально допустимый объём данных (0 = не проверять)
queue_threshold_callback_fn callback; ///< Коллбэк (вызывается один раз)
void* callback_arg; ///< Аргумент коллбэка
struct queue_waiter* next; ///< Следующий в списке ожидающих
struct queue_waiter_handle* handle; ///< Обратная ссылка на публичную структуру
};
/**
* @struct queue_waiter_handle
* @brief Публичная управляющая структура (встраивается в структуру вызывающей стороны).
*
* Вызывающая сторона:
* 1. Объявляет поле struct queue_waiter_handle в своей структуре
* 2. Вызывает queue_waiter_handle_init() один раз при инициализации
* 3. Вызывает queue_waiter_wait() когда хочет дождаться освобождения очереди
* 4. Вызывает queue_waiter_cancel() при деинициализации для отмены ожидания
*/
struct queue_waiter_handle {
struct queue_waiter* internal; ///< NULL = не ждём; иначе — внутренний узел
queue_threshold_callback_fn callback;
void* callback_arg;
};
/**
@ -120,7 +136,10 @@ struct ll_queue {
void* resume_timeout_id; // ID таймера uasync для отложенного resume
struct UASYNC* ua; // Экземпляр uasync (обязателен для таймеров)
struct queue_waiter waiter; // Встроенный waiter (только один)
struct queue_waiter* waiter_head; // Голова списка ожидающих
struct queue_waiter* waiter_tail; // Хвост списка ожидающих
int threshold_max_packets; // Общий порог: макс. кол-во элементов (по умолчанию 0)
size_t threshold_max_bytes; // Общий порог: макс. объём данных (0 = не проверять)
struct ll_entry** hash_table; // Хеш-таблица для поиска по id (если hash_size > 0)
size_t hash_size; // Размер хеш-таблицы
@ -190,26 +209,45 @@ void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg)
*/
void queue_resume_callback(struct ll_queue* q);
/* ==================== Пороговое ожидание ==================== */
/* ==================== Пороговое ожидание (backpressure) ==================== */
/**
* @brief Регистрирует одноразовый коллбэк, который сработает, когда очередь освободится до заданного порога.
* @brief Устанавливает общий порог освобождения очереди.
* @param q очередь
* @param max_packets максимальное количество элементов
* @param max_packets максимальное количество элементов (условие: count <= max_packets)
* @param max_bytes максимальный объём данных (0 = не проверять)
*
* Порог — общий на всю очередь. Все waiters в связном списке используют этот порог.
* При достижении порога вызывается следующий waiter из списка (FIFO / round-robin).
* По умолчанию: max_packets=0, max_bytes=0 (пустая очередь).
*/
void queue_set_threshold(struct ll_queue* q, int max_packets, size_t max_bytes);
/**
* @brief Инициализирует публичную управляющую структуру waiter.
* @param h указатель на handle (встраивается в структуру вызывающей стороны)
* @param callback функция, вызываемая при достижении порога
* @param arg аргумент коллбэка
* @return указатель на waiter (для отмены) или NULL, если условие уже выполнено (коллбэк вызван сразу)
*/
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes,
queue_threshold_callback_fn callback, void* arg);
void queue_waiter_handle_init(struct queue_waiter_handle* h,
queue_threshold_callback_fn callback, void* arg);
/**
* @brief Отменяет ранее установленное ожидание порога.
* @brief Регистрирует ожидание освобождения очереди до общего порога.
* @param q очередь
* @param waiter указатель, возвращённый queue_wait_threshold()
* @param h указатель на инициализированный handle
* @return 1 — условие уже выполнено (callback вызван немедленно), 0 — зарегистрирован в списке
*/
int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h);
/**
* @brief Отменяет ожидание (удаляет из списка, если зарегистрирован).
* @param q очередь
* @param h указатель на handle
*
* Безопасно вызывать в любом состоянии: если waiter не зарегистрирован — ничего не делает.
*/
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter);
void queue_waiter_cancel(struct ll_queue* q, struct queue_waiter_handle* h);
/* ==================== Работа с данными ==================== */

8
src/Makefile.am

@ -34,11 +34,11 @@ utun_CORE_SOURCES = \
eim_nat.c \
nat_transport.c \
dummynet.c \
tcp_proxy.c \
proxy/tcp_proxy_client.c \
etcp_router.c \
remote_proxy.c \
udp_proxy.c \
icmp_proxy.c \
proxy/tcp_proxy_server.c \
proxy/udp_proxy.c \
proxy/icmp_proxy.c \
lwip_tcp/lwip_pbuf.c \
lwip_tcp/lwip_tcp.c \
lwip_tcp/lwip_tcp_in.c \

58
src/config_parser.c

@ -45,8 +45,8 @@ typedef enum {
SECTION_CONTROL,
SECTION_ALLOWED_KEYS,
SECTION_NAT,
SECTION_TCP_PROXY,
SECTION_REMOTE_PROXY
SECTION_TCP_PROXY_CLIENT,
SECTION_TCP_PROXY_SERVER
} section_type_t;
static char* trim(char *str) {
@ -436,30 +436,30 @@ static int parse_control(const char *key, const char *value, struct global_confi
return -1;
}
static int parse_tcp_proxy(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
static int parse_tcp_proxy_client(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
if (strcmp(key, "enabled") == 0) {
global->tcp_proxy_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
global->tcp_proxy_client_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
return 0;
}
if (strcmp(key, "tun_name") == 0) {
strncpy(global->tcp_proxy_tun_name, value, sizeof(global->tcp_proxy_tun_name) - 1);
strncpy(global->tcp_proxy_client_tun_name, value, sizeof(global->tcp_proxy_client_tun_name) - 1);
return 0;
}
if (strcmp(key, "tun_ip") == 0) {
strncpy(global->tcp_proxy_tun_ip, value, sizeof(global->tcp_proxy_tun_ip) - 1);
strncpy(global->tcp_proxy_client_tun_ip, value, sizeof(global->tcp_proxy_client_tun_ip) - 1);
return 0;
}
if (strcmp(key, "mtu") == 0) {
global->tcp_proxy_mtu = atoi(value);
global->tcp_proxy_client_mtu = atoi(value);
return 0;
}
if (strcmp(key, "via_node") == 0) {
global->tcp_proxy_via_node_id = strtoull(value, NULL, 16);
global->tcp_proxy_client_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);
if (global->tcp_proxy_client_mapping_count >= MAX_TCP_PROXY_CLIENT_MAPPINGS) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Too many tcp_proxy_client forward rules (max %d)", MAX_TCP_PROXY_CLIENT_MAPPINGS);
return -1;
}
char buf[256];
@ -471,29 +471,29 @@ 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)", value);
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client forward format: %s (expected: port -> ip:port)", value);
return -1;
}
int local_port = atoi(local_port_str);
if (local_port <= 0 || local_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward local port: %s", local_port_str); return -1; }
if (local_port <= 0 || local_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client forward local port: %s", local_port_str); return -1; }
char* remote_ip_str = strtok(remote_str, ":");
char* remote_port_str = strtok(NULL, "");
if (!remote_ip_str || !remote_port_str) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy forward remote: %s (expected ip:port)", remote_str);
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client forward remote: %s (expected ip:port)", remote_str);
return -1;
}
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; }
if (remote_port <= 0 || remote_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client forward remote port: %s", remote_port_str); return -1; }
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_mapping_count++;
int idx = global->tcp_proxy_client_mapping_count;
global->tcp_proxy_client_mappings[idx].local_port = (uint16_t)local_port;
strncpy(global->tcp_proxy_client_mappings[idx].remote_ip, remote_ip_str, sizeof(global->tcp_proxy_client_mappings[idx].remote_ip) - 1);
global->tcp_proxy_client_mappings[idx].remote_port = (uint16_t)remote_port;
global->tcp_proxy_client_mapping_count++;
return 0;
}
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown tcp_proxy option '%s'. Valid: enabled, tun_name, tun_ip, mtu, via_node, forward", filename, line_num, key);
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown tcp_proxy_client option '%s'. Valid: enabled, tun_name, tun_ip, mtu, via_node, forward", filename, line_num, key);
return -1;
}
@ -690,8 +690,8 @@ static section_type_t parse_section_header(const char *line, char *name, size_t
if (strcasecmp(section, "control") == 0) return SECTION_CONTROL;
if (strcasecmp(section, "allowed_keys") == 0) return SECTION_ALLOWED_KEYS;
if (strcasecmp(section, "nat") == 0) return SECTION_NAT;
if (strcasecmp(section, "tcp_proxy") == 0) return SECTION_TCP_PROXY;
if (strcasecmp(section, "remote_proxy") == 0) return SECTION_REMOTE_PROXY;
if (strcasecmp(section, "tcp_proxy_client") == 0) return SECTION_TCP_PROXY_CLIENT;
if (strcasecmp(section, "tcp_proxy_server") == 0) return SECTION_TCP_PROXY_SERVER;
char *colon = strchr(section, ':');
if (!colon) return SECTION_UNKNOWN;
@ -861,15 +861,15 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
// parse_nat already printed the error
}
break;
case SECTION_TCP_PROXY:
cfg->global.tcp_proxy_enabled = 1;
if (parse_tcp_proxy(key, value, &cfg->global, filename, line_num) < 0) {
// parse_tcp_proxy already printed the error
case SECTION_TCP_PROXY_CLIENT:
cfg->global.tcp_proxy_client_enabled = 1;
if (parse_tcp_proxy_client(key, value, &cfg->global, filename, line_num) < 0) {
// parse_tcp_proxy_client already printed the error
}
break;
case SECTION_REMOTE_PROXY:
cfg->global.remote_proxy_enabled = 1;
// remote_proxy section has no options — its presence alone enables it
case SECTION_TCP_PROXY_SERVER:
cfg->global.tcp_proxy_server_enabled = 1;
// tcp_proxy_server section has no options — its presence alone enables it
(void)key; (void)value;
break;
default:

26
src/config_parser.h

@ -82,8 +82,8 @@ struct CFG_ALLOWED_KEY {
struct CFG_ALLOWED_KEY *next;
};
#define MAX_TCP_PROXY_MAPPINGS 32
struct tcp_proxy_mapping_config {
#define MAX_TCP_PROXY_CLIENT_MAPPINGS 32
struct tcp_proxy_client_mapping_config {
uint16_t local_port;
char remote_ip[64];
uint16_t remote_port;
@ -150,17 +150,17 @@ struct global_config {
} nat_forwards[MAX_NAT_FORWARDS];
int nat_forward_count;
// TCP proxy configuration ([tcp_proxy] section)
int tcp_proxy_enabled;
char tcp_proxy_tun_name[16];
char tcp_proxy_tun_ip[64];
int tcp_proxy_mtu;
uint64_t tcp_proxy_via_node_id; // через этот узел проксируются все forward-правила
struct tcp_proxy_mapping_config tcp_proxy_mappings[MAX_TCP_PROXY_MAPPINGS];
int tcp_proxy_mapping_count;
// Remote proxy (exit node) configuration ([remote_proxy] section)
int remote_proxy_enabled;
// TCP proxy client configuration ([tcp_proxy_client] section)
int tcp_proxy_client_enabled;
char tcp_proxy_client_tun_name[16];
char tcp_proxy_client_tun_ip[64];
int tcp_proxy_client_mtu;
uint64_t tcp_proxy_client_via_node_id; // через этот узел проксируются все forward-правила
struct tcp_proxy_client_mapping_config tcp_proxy_client_mappings[MAX_TCP_PROXY_CLIENT_MAPPINGS];
int tcp_proxy_client_mapping_count;
// TCP proxy server (exit node) configuration ([tcp_proxy_server] section)
int tcp_proxy_server_enabled;
};
struct utun_config {

72
src/control_server.c

@ -93,6 +93,7 @@ static void close_client(struct control_server* server, struct control_client* c
static void send_conn_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
static void send_socket_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
static void send_metrics(struct control_server* server, struct control_client* client, uint8_t seq_id);
static void send_debug_config(struct control_server* server, struct control_client* client, uint8_t seq_id);
static void send_error(struct control_client* client, uint8_t error_code, const char* msg, uint8_t seq_id);
static struct ETCP_CONN* find_connection_by_peer_id(struct UTUN_INSTANCE* instance, uint64_t peer_id);
@ -613,7 +614,7 @@ static void handle_client_data(struct control_server* server, struct control_cli
close_client(server, client);
return;
}
if (hdr->type < ETCPMON_CMD_LIST_CONN || hdr->type > ETCPMON_CMD_ACTION) {
if (hdr->type < ETCPMON_CMD_LIST_CONN || hdr->type > ETCPMON_CMD_SET_DEBUG_CONFIG) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Invalid command type from client: 0x%02X", hdr->type);
close_client(server, client);
return;
@ -696,6 +697,30 @@ static void handle_client_data(struct control_server* server, struct control_cli
}
break;
case ETCPMON_CMD_GET_DEBUG_CONFIG:
send_debug_config(server, client, req_seq);
break;
case ETCPMON_CMD_SET_DEBUG_CONFIG:
if (payload_size >= 2) {
uint8_t global_lvl = payload[0];
uint8_t cat_count = payload[1];
if (cat_count > ETCPMON_MAX_DEBUG_CATEGORIES) cat_count = ETCPMON_MAX_DEBUG_CATEGORIES;
if (global_lvl <= DEBUG_LEVEL_TRACE) debug_set_level((debug_level_t)global_lvl);
const uint8_t* levels = payload + 2;
for (uint8_t i = 0; i < cat_count && i + 1 < DEBUG_CATEGORY_COUNT; i++) {
if (levels[i] <= DEBUG_LEVEL_TRACE)
debug_set_category_level((debug_category_t)(i + 1), (debug_level_t)levels[i]);
}
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Debug config applied: global=%d categories=%d", global_lvl, cat_count);
if (server->log_file) {
fprintf(server->log_file, "%llu: [LOG] Debug config set: global=%d categories=%d\n",
(unsigned long long)get_timestamp_ms(), global_lvl, cat_count);
fflush(server->log_file);
}
}
break;
case ETCPMON_CMD_DISCONNECT:
if (server->log_file) {
fprintf(server->log_file, "%llu: [LOG] Client requested disconnect\n",
@ -1154,6 +1179,51 @@ static void send_error(struct control_client* client, uint8_t error_code, const
u_free(buffer);
}
static void send_debug_config(struct control_server* server, struct control_client* client, uint8_t seq_id) {
uint8_t cat_count = 0;
char names_buf[512];
names_buf[0] = '\0';
size_t off = 0;
for (int i = 1; i < DEBUG_CATEGORY_COUNT; i++) {
const char* name = debug_get_category_name(i);
size_t name_len = strlen(name);
if (off + name_len + 2 > sizeof(names_buf)) break;
if (cat_count > 0) { names_buf[off++] = ','; }
memcpy(names_buf + off, name, name_len); off += name_len;
cat_count++;
}
names_buf[off] = '\0';
uint16_t names_len = (uint16_t)(off + 1);
uint16_t payload_size = 2 + names_len + cat_count;
uint16_t rsp_size = sizeof(struct etcpmon_msg_header) + payload_size;
uint8_t* buffer = (uint8_t*)u_malloc(rsp_size);
if (!buffer) { DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate debug config buffer"); return; }
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
etcpmon_build_header(hdr, payload_size, ETCPMON_RSP_DEBUG_CONFIG, seq_id);
uint8_t* pay = buffer + sizeof(*hdr);
pay[0] = (uint8_t)g_debug_config.level;
pay[1] = cat_count;
memcpy(pay + 2, names_buf, names_len);
for (uint8_t i = 0; i < cat_count; i++) pay[2 + names_len + i] = (uint8_t)g_debug_config.category_levels[i + 1];
if (server->log_file) {
fprintf(server->log_file, "%llu: [LOG] Sent RSP_DEBUG_CONFIG global=%d categories=%d\n",
(unsigned long long)get_timestamp_ms(), g_debug_config.level, cat_count);
fflush(server->log_file);
}
#ifdef _WIN32
send(client->fd, (const char*)buffer, rsp_size, 0);
#else
send(client->fd, buffer, rsp_size, 0);
#endif
u_free(buffer);
}
/* ============================================================================
* Helper Functions
* ============================================================================ */

1
src/etcp.c

@ -198,6 +198,7 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
etcp->instance = instance;
etcp->input_queue = queue_new(instance->ua, 0, 0, 0, "ETCP input"); // No hash for input_queue
queue_set_threshold(etcp->input_queue, 0, 0); // Backpressure: ждать полного освобождения
etcp->output_queue = queue_new(instance->ua, 0, 0, 0, "ETCP output"); // No hash for output_queue
etcp->input_send_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "input_send_q"); // Hash for send_q
etcp->input_wait_ack = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, 0, 4, "input_wait_ack"); // Hash for wait_ack

2
src/etcp_api.h

@ -25,7 +25,7 @@
#define ETCP_ID_ROUTE_ENTRY 0x01 // Элемент роутинг-таблицы
#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_TCP_PROXY 0x04 // TCP proxy через удаленный узел (tcp_proxy_server)
#define ETCP_ID_UDP_PROXY 0x05 // UDP datagram прокси (client ↔ exit)
#define ETCP_ID_ICMP_PROXY 0x06 // ICMP echo прокси (ping через exit)

16
src/etcp_router.c

@ -515,6 +515,22 @@ int etcp_router_input_q_count(struct UTUN_INSTANCE* inst, uint64_t node_id) {
return queue_entry_count(conn->normalizer->input);
}
void etcp_router_waiter_register(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
struct queue_waiter_handle* h) {
if (!inst || !inst->bgp || !h) return;
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, peer_node_id);
if (!conn || !conn->normalizer || !conn->normalizer->input) return;
queue_waiter_wait(conn->normalizer->input, h);
}
void etcp_router_waiter_cancel(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
struct queue_waiter_handle* h) {
if (!inst || !inst->bgp || !h) return;
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, peer_node_id);
if (!conn || !conn->normalizer || !conn->normalizer->input) return;
queue_waiter_cancel(conn->normalizer->input, h);
}
// ====================================================================
// Seq-connection API
// ====================================================================

6
src/etcp_router.h

@ -91,4 +91,10 @@ void etcp_router_conn_close_all_for_node(struct UTUN_INSTANCE* inst, uint64_t re
// Возвращает количество пакетов в очереди normalizer->input для узла node_id
int etcp_router_input_q_count(struct UTUN_INSTANCE* inst, uint64_t node_id);
// Backpressure: зарегистрировать/отменить waiter на normalizer->input очереди
void etcp_router_waiter_register(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
struct queue_waiter_handle* h);
void etcp_router_waiter_cancel(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
struct queue_waiter_handle* h);
#endif // ETCP_ROUTER_H

2
src/lwip_tcp/lwip_tcp.h

@ -185,7 +185,7 @@ struct tcp_pcb_listen {
tcp_accept_fn accept;
};
// Context for the TCP module (one per tcp_proxy instance)
// Context for the TCP module (one per tcp_proxy_client instance)
struct lwip_tcp_ctx {
struct UASYNC *ua;
tcp_output_fn output;

6
src/pkt_normalizer.c

@ -69,6 +69,8 @@ struct PKTNORM* pn_init(struct ETCP_CONN* etcp) {
pn->recvpart = NULL;
pn->flush_timer = NULL;
queue_waiter_handle_init(&pn->input_waiter_handle, etcp_input_ready_cb, pn);
return pn;
}
@ -120,7 +122,7 @@ void pn_deinit(struct PKTNORM* pn) {
// during etcp_connection_close() when drain_and_free_fragment_queue() is called
if (pn->etcp) {
if (pn->etcp->input_queue) {
queue_cancel_wait(pn->etcp->input_queue, &pn->etcp->input_queue->waiter);
queue_waiter_cancel(pn->etcp->input_queue, &pn->input_waiter_handle);
}
if (pn->etcp->output_queue) {
queue_set_callback(pn->etcp->output_queue, NULL, NULL);
@ -213,7 +215,7 @@ static void packer_cb(struct ll_queue* q, void* arg) {
struct PKTNORM* pn = (struct PKTNORM*)arg;
if (!pn) return;
DEBUG_TRACE(DEBUG_CATEGORY_NORMALIZER, "input_q->pn: waiting etcp input threshold");
queue_wait_threshold(pn->etcp->input_queue, 0, 0, etcp_input_ready_cb, pn);
queue_waiter_wait(pn->etcp->input_queue, &pn->input_waiter_handle);
}
// Helper to send block to ETCP as ETCP_FRAGMENT

3
src/pkt_normalizer.h

@ -36,6 +36,9 @@ struct PKTNORM {
struct ll_entry* pending; // Partial processed input entry
uint16_t pending_in_ptr; // Pointer in pending entry
// backpressure:
struct queue_waiter_handle input_waiter_handle; // waiter на etcp->input_queue
// unpacker:
struct ll_entry* recvpart; // блок ожидающий заполнение
// uint16_t recvpart_rem; // сколько байт осталось собрать (0 = ждём заголовок нового пакета)

477
src/proxy/PROTOCOL.md

@ -0,0 +1,477 @@
## TCP Proxy Protocol — архитектура (v2, без CONNECTED)
### 1. Протокольный формат (общий для client и server)
```
┌──────┬──────┬───────────────────────┬──────────────────────┐
│svc_id│subcmd│ stream_id (4) │ data ... │
│ 1B │ 1B │ │ │
└──────┴──────┴───────────────────────┴──────────────────────┘
HDR = 6 байт
subcmd:
0x01 CONNECT client→server : dest_ip(4)+dest_port(2)
0x03 DATA ↔ bidirectional: payload
0x04 CLOSE ↔ bidirectional: (нет данных)
0x05 ERROR ↔ bidirectional: (нет данных)
```
---
### 2. Диаграмма состояний `tcp_proxy_client_conn`
```
┌──────────────┐
│ ALLOC + │ accept_cb: u_calloc, stream_id++
│ CONNECT │ send CONNECT → exit
│ отправлен │
└──────┬───────┘
│
┌───────────┴───────────┐
│ send успешен? │
└───────────┬───────────┘
Y │ N
┌────────┘ └──────┐
▼ ▼
┌────────────┐ ┌──────────────┐
│ ACTIVE │ │ FREE │
│ traffic │ └──────────────┘
└────┬──┬────┘
│ │
│ └─────────────────────────────────┐
│ │
▼ lwIP FIN (p=NULL) ▼ ERROR от lwIP/exit
┌──────────────┐ ┌──────────────┐
│ TUN_CLOSED │ │ ERROR │
│ tun_closed=1 │ │ error=1 │
│ ↓ send_close │ │ send ERROR→ │
│ после drain │ └──────┬───────┘
│ tx_buf+lwIP │ │
└──────┬───────┘ poll_cb: tcp_abort+free
│
│ recv CLOSE от exit
▼
┌───────────────────┐
│ TUN+REM CLOSED │
│ tun=1 rem=1 │
│ ждём flush буферов│
└────────┬──────────┘
│ poll_cb: pending=0, unsent=NULL, unacked=NULL
│ (или sndbuf==0)
▼
┌──────────────┐
│ CLEANUP │
│ free(pc) │
└──────────────┘
ДОП. СОСТОЯНИЯ:
┌─────────────────┐ send_data в ETCP вернул -1 (normalizer полон)
│ TX_BACKPRESSURE│ не tcp_recved() → окно lwIP=0 → recv_cb остановлен
│ tx_buf != NULL │ etcp_router_waiter_register() → ждём освобождения
│ tx_waiter pend │ waiter_cb: retry send_data → tcp_recved → окно открыто
└─────────────────┘
┌─────────────────┐ send_close/send_error вернул -1
│ CLOSE_PENDING │ → retry в poll_cb
│ close_pending=1│
└─────────────────┘
```
---
### 3. Диаграмма состояний `tcp_proxy_server_conn`
```
┌───────────────────┐
│ CREATING │ recv CONNECT
│ socket() создан │ → неблокирующий connect()
│ sock = неблок. │
└────────┬──────────┘
│
┌────────────┴────────────┐
│ connect() результат? │
└────┬──────────┬──────────┘
EINPROGRESS│ │ошибка сразу
▼ ▼
┌──────────┐ ┌──────────────┐
│CONNECTING│ │ ERROR │ send ERROR→client
│ждём write│ │ FREE │
└────┬─────┘ └──────────────┘
│ write_cb: SO_ERROR==0
▼
┌─────────────┐
│ CONNECTED │ connected=1
│ flush pend │ → flush pending_buf в сокет
└──────┬──────┘
│
┌──────────────┼──────────────┐
│ │ │
client→DATA │ │ ERROR (sock/pipe)
(sock_send) │ ▼
или pending_buf │ ┌──────────────┐
│ │ │ ERROR │ send ERROR→client
│ │ │ FREE │
│ sock EOF (n=0) │ │
│ │ └──────────────┘
│ ▼
│ ┌──────────────────┐
│ │ SOCK_CLOSED │ sock_closed=1
│ │ out_buf/pause? │ если очереди пусты → send_close
│ │ ждём drain │ иначе ждём retry_cb/waiter_cb
│ └────────┬─────────┘
│ │ очереди опустошены → send_close
│ │ если cli_closed → FREE
│ │
client→CLOSE │
│ │
▼ │
┌──────────────┐ │
│ CLI_CLOSED │ │ cli_closed=1
│ out_buf pend? │◄────────┘ если out_buf пуст → shutdown(SHUT_WR)
│ ждём flush │ иначе ждём sock_retry_cb → shutdown
└──────┬───────┘
│
└──────────────┐
▼
┌──────────────────┐
│ cli && sock │ оба закрыты + очереди пусты
│ очереди пусты │
└────────┬─────────┘
▼
┌──────────────┐
│ FREE │ conn_free(rc)
└──────────────┘
ДОП. СОСТОЯНИЯ:
┌─────────────────┐ send_msg в ETCP вернул -1
│ PAUSED │ буферизуем в pause_buf, убираем read_id (пауза чтения)
│ pause_buf>0 │ etcp_router_waiter_register() → ждём освобождения
│ pause_waiter │ waiter_cb: retry send_msg → resume read_id
└─────────────────┘
┌─────────────────┐ CLOSE/ERROR не доставлен через ETCP
│ CLOSE_PENDING │ close_timer с backoff 50..5000 tb → ретрай
│ close_pending=1│
└─────────────────┘
```
---
### 4. DATA FLOW
```
══════════════════════════════════════════════════════════════════════
APP → lwIP → recv_cb → ETCP → handle → socket → DEST
APP ← lwIP ← tcp_write ← ETCP ← read_cb ← socket ← DEST
══════════════════════════════════════════════════════════════════════
CLIENT (узел A) SERVER (узел B)
APP (через TUN) DEST (реальный)
│ │
▼ TCP SYN/ACK (lwIP) ▼ TCP SYN/ACK (OS)
┌──────────┐ ┌──────────┐
│ lwIP │ │ socket │
│ TCP │ │ (неблок) │
└────┬─────┘ └────┬─────┘
│ recv_cb │ read_cb
│ │
┌────▼────────────┐ ┌────▼───────────────┐
│ tcp_proxy │ ETCP │ tcp_proxy │
│ _client │◄═══════════════════════►│ _server │
│ │ │ │
│ ┌────────────┐ │ │ ┌──────────────┐ │
│ │ recv_cb │ │──CONNECT+DATA──► │ │handle_connect│ │
│ │(lwIP→ETCP) │ │ │ │handle_data │ │
│ │ │ │ backpressure: │ └──────┬───────┘ │
│ │ send fail: │ │ waiter на normalizer │ │ sock_send │
│ │ tx_buf+ │ │ │ ┌──────▼───────┐ │
│ │ waiter │ │ │ │ out_buf │ │
│ │ окно lwIP=0│ │ │ │ pending_buf │ │
│ └────────────┘ │ │ └─────────────┘ │
│ │ │ │ send() │
│ ┌────────────┐ │ │ ┌──────▼───────┐ │
│ │handle_data │ │◄────DATA──────── │ │ read_cb │ │
│ │handle_close│ │ │ │(sock→ETCP) │ │
│ └─────┬──────┘ │ │ │ │ │
│ │ │ │ │ send fail: │ │
│ ┌─────▼──────┐ │ │ │ pause_buf+ │ │
│ │ to_lwip │ │ │ │ waiter+ │ │
│ │ (очередь) │ │ │ │ пауза read_id│ │
│ └─────┬──────┘ │ │ └─────────────┘ │
│ │tcp_write│ │ │
│ ▼ │ └───────────────────┘
│ lwIP→TUN→APP │
└─────────────────┘
```
---
### 5. BACKPRESSURE (lwIP→ETCP и socket→ETCP)
```
══════════════════════════════════════════════════════════════════════
Используется встроенный механизм ll_queue threshold waiter:
etcp_router_waiter_register(inst, peer_node_id, &waiter)
→ внутри: queue_waiter_wait(normalizer->input_queue, &waiter)
etcp_router_waiter_cancel(inst, peer_node_id, &waiter)
CLIENT (lwIP → ETCP):
recv_cb(buf, len):
ret = send_data(buf, len)
if ret == 0: tcp_recved(len)
if ret < 0:
tx_buf = copy(buf), tx_len = len
НЕ tcp_recved() → окно lwIP = 0, recv_cb больше не вызывается
etcp_router_waiter_register(&tx_waiter)
tx_waiter callback:
ret = send_data(tx_buf, tx_len)
if ret == 0:
tcp_recved(tx_len) → окно открыто, lwIP возобновляет recv_cb
free(tx_buf)
if tun_closed → send_close()
// если ret < 0 → waiter остаётся, normalizer вызовет снова
SERVER (socket → ETCP):
read_cb(buf, n):
ret = send_msg(DATA, buf, n)
if ret == 0: ok
if ret < 0:
pause_buf = copy(buf), pause_len = n
uasync_remove_socket_t(sock) → пауза чтения с сокета
etcp_router_waiter_register(&pause_waiter)
pause_waiter callback:
ret = send_msg(DATA, pause_buf, pause_len)
if ret == 0:
free(pause_buf)
uasync_add_socket_t(sock) → возобновляем чтение
if sock_closed → send_close()
// если ret < 0 → waiter остаётся
```
---
### 6. Последовательность: HANDSHAKE (упрощённый, без CONNECTED)
```
CLIENT (lwIP) CLIENT PROXY ETCP SERVER PROXY SOCKET
─────────────────────────────────────────────────────────────────────────────────────────
tcp_proxy_client_accept_cb() CONNECT
├─ u_calloc(conn) ────────────────────────────→ tcp_proxy_server_handle_connect()
├─ stream_id++ ├─ socket(AF_INET,SOCK_STREAM)
├─ send_connect() ├─ socket_set_nonblocking()
│ [dest_ip(4)+dest_port(2)] ├─ uasync_add_socket_t(read/write/error)
├─ conn→conns list ├─ connect(неблок.) → EINPROGRESS
│ └─ rc→conns list
▼
ACTIVE — данные идут СРАЗУ (без ожидания CONNECTED)
│
│ DATA ...connect завершён...
├──────────────────────────────────────────────────→ tcp_proxy_server_sock_write_cb()
│ ├─ connected=1
│ DATA └─ flush pending_buf в сокет
├──────────────────────────────────────────────────→
│
│ DATA ←─ tcp_proxy_server_sock_read_cb()
│◄────────────────────────────────────────────── (socket recv → ETCP)
│
└──→ трафик идёт в обе стороны
При ошибке connect:
...connect fail... → tcp_proxy_server_send_error() → client получает ERROR, error=1, cleanup
```
---
### 7. Последовательность: CLOSE (drain очередей → close в конце)
```
CLIENT (lwIP) CLIENT PROXY ETCP SERVER PROXY SOCKET
─────────────────────────────────────────────────────────────────────────────────────────
══ Вариант А: lwIP закрывает первым ══
tcp_proxy_client_recv_cb(p=NULL)
├─ tun_closed=1
│ если tx_buf пуст → send_close CLOSE
│ если tx_buf pend → waiter досылает ──────────→ tcp_proxy_server_handle_close()
│ потом send_close ├─ cli_closed=1
└─ │ если out_buf пуст → shutdown(SHUT_WR)
│ иначе ждём sock_retry_cb
│
...out_buf дослан...
sock_retry_cb:
shutdown(SHUT_WR)
│
...сокет дочитывает остаток...
│
tcp_proxy_server_sock_read_cb(n=0)
├─ sock_closed=1
│ если pause_buf пуст → send_close
│ иначе ждём waiter_cb
│
CLOSE │
tcp_proxy_client_handle_close() ◄────────────────────────┘
└─ rem_closed=1
│
poll_cb: tun=1, rem=1, to_lwip пуст, unsent=NULL, unacked=NULL
├─ tcp_close(pcb)
└─ conn_free()
══ Вариант Б: серверный сокет закрывается первым ══
tcp_proxy_server_sock_read_cb(n=0)
├─ sock_closed=1
│ если out_buf+pause_buf пусты → send_close
│ иначе ждём retry/waiter
CLOSE │
tcp_proxy_client_handle_close() ◄────────────────────────┘
└─ rem_closed=1
│
▼
...приложение закрывает TCP...
│
tcp_proxy_client_recv_cb(p=NULL)
├─ tun_closed=1
│ если tx_buf пуст → send_close
└─
poll_cb: оба закрыты, буферы пусты → cleanup
══ Вариант В: Ошибка / аномалия ══
ERROR
─────────────────────── (или ←) ──────────
error=1
│
▼
poll_cb: tcp_abort(pcb) → немедленная очистка
или: rc→error=1 → conn_free
══ Вариант Г: conn не найден / closed / error ══
Любой пришедший пакет (DATA/CLOSE/ERROR) для conn в закрытом состоянии:
→ tcp_proxy_*_send_msg(ERROR, stream_id)
→ удалённая сторона получает ERROR и немедленно закрывается
```
---
### 8. Диспетчеризация в `tcp_proxy_client_etcp_recv_cb()`
```
┌────────────────────────────────────────────────────────────────────────┐
│ tcp_proxy_client_etcp_recv_cb() │
│ (единая точка входа для обоих направлений) │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ subcmd == CONNECT? │
│ └── tcp_proxy_server_handle_connect() ←── всегда сервер │
│ │
│ stream_id в server.conns? (если server.enabled) │
│ ├── DATA → tcp_proxy_server_handle_data() │
│ ├── CLOSE → tcp_proxy_server_handle_close() │
│ └── ERROR → rc->error=1; conn_free() │
│ │
│ stream_id в client.conns? (если client proxy != NULL) │
│ ├── DATA → tcp_proxy_client_handle_data() │
│ ├── CLOSE → tcp_proxy_client_handle_close() │
│ └── ERROR → tcp_proxy_client_handle_error() │
│ │
│ Приоритет: │
│ 1. CONNECT всегда интерпретируется сервером │
│ 2. Если server.enabled И stream_id найден в server.conns → server │
│ 3. Иначе если client proxy != NULL → client │
│ 4. Иначе drop │
└────────────────────────────────────────────────────────────────────────┘
```
---
### 9. INIT
```
CLIENT SERVER
─────────────────────────────────────────────────────────
utun_instance.c
│
├─ tcp_proxy_client_create() tcp_proxy_server_init()
│ ├─ tun_init_nat() (TUN iface) ├─ memset(ctx,0)
│ ├─ lwip_tcp_init() (TCP стек) ├─ ctx->enabled = config ...
│ ├─ etcp_router_bind( ├─ etcp_router_bind(
│ │ ETCP_ID_TCP_PROXY, │ ETCP_ID_TCP_PROXY,
│ │ tcp_proxy_client_etcp_recv_cb) │ tcp_proxy_client_etcp_recv_cb)
│ ├─ udp_proxy_init() ├─ udp_proxy_init()
│ └─ icmp_proxy_init() └─ icmp_proxy_init() (если client не активен)
│
└─ ОБА слушают ETCP_ID_TCP_PROXY через один обработчик
```
---
### 10. Поля структур
```
tcp_proxy_client_conn:
stream_id — уникальный ID потока
tun_closed — lwIP/FIN (сторона TUN)
rem_closed — CLOSE от exit
error — немедленная очистка
close_sent — CLOSE/ERROR отправлен
close_pending — отправка не удалась, ретрай
to_lwip — очередь DATA от exit → lwIP (feed_from_transport)
pcb — lwIP tcp_pcb
tx_buf — буфер при backpressure lwIP→ETCP (send_data fail)
tx_len — длина tx_buf
tx_waiter — ll_queue waiter на normalizer.input_queue
tcp_proxy_server_conn:
stream_id — уникальный ID потока
cli_closed — клиент прислал CLOSE
sock_closed — сокет получил EOF
error — ошибка
connected — сокет подключился (connect завершён)
close_pending — CLOSE/ERROR не доставлен, ретрай
pending_buf — буфер данных от клиента до завершения connect
out_buf — буфер отправки в сокет (неблокирующий send, EAGAIN retry)
out_off/out_len — offset/размер out_buf
out_timer — таймер повтора send в сокет
out_backoff — backoff для retry send в сокет
pause_buf — буфер при backpressure socket→ETCP (send_msg fail)
pause_len — длина pause_buf
pause_waiter — ll_queue waiter на normalizer.input_queue
close_timer — таймер повтора CLOSE/ERROR
close_backoff — backoff для retry CLOSE/ERROR
sock — OS сокет к адресату
read_id — handle uasync для событий сокета
```
---
### 11. etcp_router waiter API
```
// etcp_router.h
void etcp_router_waiter_register(struct UTUN_INSTANCE* inst,
uint64_t peer_node_id, struct queue_waiter_handle* h);
void etcp_router_waiter_cancel(struct UTUN_INSTANCE* inst,
uint64_t peer_node_id, struct queue_waiter_handle* h);
// Внутри:
// conn = route_bgp_find_conn_for_node(peer_node_id)
// queue_waiter_wait(conn->normalizer->input, h)
// queue_waiter_cancel(conn->normalizer->input, h)
//
// normalizer.input_queue уже имеет threshold=0 (ждёт полного освобождения)
// через queue_set_threshold(input_queue, 0, 0) в etcp.c
```

8
src/icmp_proxy.c → src/proxy/icmp_proxy.c

@ -232,12 +232,12 @@ int icmp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id,
int icmp_proxy_deliver_reply(struct UTUN_INSTANCE* inst,
uint32_t dst_ip, 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) {
if (!inst || !inst->tcp_proxy_client || !inst->tcp_proxy_client->tun) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "icmp_proxy: deliver_reply failed — no %s",
!inst ? "inst" : !inst->tcp_proxy ? "tcp_proxy" : "TUN");
!inst ? "inst" : !inst->tcp_proxy_client ? "tcp_proxy_client" : "TUN");
return -1;
}
struct tun_if* tun = inst->tcp_proxy->tun;
struct tun_if* tun = inst->tcp_proxy_client->tun;
size_t icmp_len = ICMP_MINLEN + payload_len;
size_t pkt_len = 20 + icmp_len;
@ -306,7 +306,7 @@ int icmp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
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;
ctx->is_exit = inst->tcp_proxy_server.enabled;
ctx->test_loopback = 0;
ctx->expire_timer = NULL;
g_icmp_ctx = ctx;

0
src/icmp_proxy.h → src/proxy/icmp_proxy.h

315
src/tcp_proxy.c → src/proxy/tcp_proxy_client.c

@ -1,5 +1,5 @@
// tcp_proxy.c — TCP прокси: стек lwIP TCP → ETCP → удалённый exit узел
#include "tcp_proxy.h"
// tcp_proxy_client.c — TCP прокси-клиент: стек lwIP TCP → ETCP → удалённый exit узел
#include "tcp_proxy_client.h"
#include "lwip_tcp/lwip_tcp.h"
#include "lwip_tcp/lwip_tcp_priv.h"
#include "lwip_tcp/lwip_tcp_opts.h"
@ -9,7 +9,7 @@
#include "etcp.h"
#include "etcp_api.h"
#include "etcp_router.h"
#include "remote_proxy.h"
#include "tcp_proxy_server.h"
#include "udp_proxy.h"
#include "icmp_proxy.h"
#include "../lib/u_async.h"
@ -32,28 +32,29 @@
// ====================================================================
// Предварительные объявления
// ====================================================================
static err_t proxy_recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err);
static err_t proxy_sent_cb(void *arg, struct tcp_pcb *pcb, uint16_t len);
static void proxy_err_cb(void *arg, err_t err);
static err_t proxy_poll_cb(void *arg, struct tcp_pcb *pcb);
static err_t proxy_accept_cb(void *arg, struct tcp_pcb *newpcb, err_t err);
static err_t tcp_output_cb(void *arg, struct pbuf *p, uint32_t src_ip, uint32_t dst_ip);
static void proxy_feed_from_transport(struct proxy_conn *pc);
static struct ll_entry* entry_from_data(struct memory_pool* pool, const uint8_t* data, uint16_t len);
static void proxy_conn_free(struct proxy_conn *pc);
static int proxy_send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd, uint32_t sid, const uint8_t* data, size_t len);
static int send_data(struct proxy_conn* pc, const uint8_t* data, uint16_t len);
static err_t tcp_proxy_client_recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err);
static err_t tcp_proxy_client_sent_cb(void *arg, struct tcp_pcb *pcb, uint16_t len);
static void tcp_proxy_client_err_cb(void *arg, err_t err);
static err_t tcp_proxy_client_poll_cb(void *arg, struct tcp_pcb *pcb);
static err_t tcp_proxy_client_accept_cb(void *arg, struct tcp_pcb *newpcb, err_t err);
static err_t tcp_proxy_client_output_cb(void *arg, struct pbuf *p, uint32_t src_ip, uint32_t dst_ip);
static void tcp_proxy_client_feed_from_transport(struct tcp_proxy_client_conn *pc);
static struct ll_entry* tcp_proxy_client_entry_from_data(struct memory_pool* pool, const uint8_t* data, uint16_t len);
static void tcp_proxy_client_conn_free(struct tcp_proxy_client_conn *pc);
static int tcp_proxy_client_send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd, uint32_t sid, const uint8_t* data, size_t len);
static int tcp_proxy_client_send_data(struct tcp_proxy_client_conn* pc, const uint8_t* data, uint16_t len);
static void tcp_proxy_client_tx_waiter_cb(struct ll_queue* q, void* arg);
// ====================================================================
// Помощник ll_entry
// ====================================================================
static struct ll_entry* entry_from_data(struct memory_pool* pool, const uint8_t* data, uint16_t len) {
static struct ll_entry* tcp_proxy_client_entry_from_data(struct memory_pool* pool, const uint8_t* data, uint16_t len) {
struct ll_entry* e = queue_entry_new_from_pool(pool);
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "entry_from_data: pool exhausted"); return NULL; }
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_entry_from_data: pool exhausted"); return NULL; }
e->len = 0; e->dgram = NULL;
if (len > 0) {
uint8_t* buf = u_malloc(len);
if (!buf) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "entry_from_data: malloc(%u) failed", len); queue_entry_free(e); return NULL; }
if (!buf) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_entry_from_data: malloc(%u) failed", len); queue_entry_free(e); return NULL; }
memcpy(buf, data, len); e->dgram = buf; e->len = len;
}
return e;
@ -62,12 +63,12 @@ static struct ll_entry* entry_from_data(struct memory_pool* pool, const uint8_t*
// ====================================================================
// Протокол: сборка и отправка сообщений прокси через ETCP
// ====================================================================
static int proxy_send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd,
static int tcp_proxy_client_send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd,
uint32_t sid, const uint8_t* data, size_t len) {
struct ll_entry* e = queue_entry_new(0);
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "proxy_send_msg: queue_entry_new failed subcmd=%02x sid=%08x", subcmd, sid); return -1; }
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_send_msg: queue_entry_new failed subcmd=%02x sid=%08x", subcmd, sid); return -1; }
e->dgram = u_malloc(TCP_PROXY_HDR_SIZE + len);
if (!e->dgram) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "proxy_send_msg: malloc(%zu) failed subcmd=%02x sid=%08x", TCP_PROXY_HDR_SIZE + len, subcmd, sid); queue_entry_free(e); return -1; }
if (!e->dgram) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_send_msg: malloc(%zu) failed subcmd=%02x sid=%08x", TCP_PROXY_HDR_SIZE + len, subcmd, sid); queue_entry_free(e); return -1; }
e->dgram[0] = ETCP_ID_TCP_PROXY;
e->dgram[1] = subcmd;
memcpy(e->dgram + 2, &sid, 4);
@ -76,24 +77,24 @@ static int proxy_send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subc
return etcp_route_send(inst, dst, e);
}
static int send_connect(struct proxy_conn* pc) {
static int tcp_proxy_client_send_connect(struct tcp_proxy_client_conn* pc) {
uint8_t buf[6];
memcpy(buf, pc->dest_ip, 4); memcpy(buf + 4, &pc->dest_port, 2);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy: CONNECT sid=%08x to %d.%d.%d.%d:%d via node %016llx",
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client: CONNECT sid=%08x to %d.%d.%d.%d:%d via node %016llx",
pc->stream_id, pc->dest_ip[0], pc->dest_ip[1], pc->dest_ip[2], pc->dest_ip[3],
ntohs(pc->dest_port), (unsigned long long)pc->proxy->via_node_id);
return proxy_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
return tcp_proxy_client_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
TCP_PROXY_SUBCMD_CONNECT, pc->stream_id, buf, 6);
}
static int send_data(struct proxy_conn* pc, const uint8_t* data, uint16_t len) {
static int tcp_proxy_client_send_data(struct tcp_proxy_client_conn* pc, const uint8_t* data, uint16_t len) {
DEBUG_INFO(DEBUG_CATEGORY_TRAFFIC, "PROXY SEND sid=%08x len=%u", pc->stream_id, len);
return proxy_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
return tcp_proxy_client_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
TCP_PROXY_SUBCMD_DATA, pc->stream_id, data, len);
}
static int send_close(struct proxy_conn* pc) {
int ret = proxy_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
static int tcp_proxy_client_send_close(struct tcp_proxy_client_conn* pc) {
int ret = tcp_proxy_client_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
TCP_PROXY_SUBCMD_CLOSE, pc->stream_id, NULL, 0);
if (ret < 0) { pc->close_pending = 1; return -1; }
pc->close_pending = 0;
@ -101,8 +102,8 @@ static int send_close(struct proxy_conn* pc) {
return 0;
}
static int send_error(struct proxy_conn* pc) {
int ret = proxy_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
static int tcp_proxy_client_send_error(struct tcp_proxy_client_conn* pc) {
int ret = tcp_proxy_client_send_msg(pc->proxy->inst, pc->proxy->via_node_id,
TCP_PROXY_SUBCMD_ERROR, pc->stream_id, NULL, 0);
if (ret < 0) { pc->close_pending = 1; return -1; }
pc->close_pending = 0;
@ -113,8 +114,8 @@ static int send_error(struct proxy_conn* pc) {
// ====================================================================
// Вывод: lwIP TCP отправляет IP пакеты через этот callback
// ====================================================================
static err_t tcp_output_cb(void *arg, struct pbuf *p, uint32_t src_ip, uint32_t dst_ip) {
struct tcp_proxy *proxy = (struct tcp_proxy *)arg;
static err_t tcp_proxy_client_output_cb(void *arg, struct pbuf *p, uint32_t src_ip, uint32_t dst_ip) {
struct tcp_proxy_client *proxy = (struct tcp_proxy_client *)arg;
(void)src_ip; (void)dst_ip;
uint16_t len = p->tot_len;
if (len > 2000) return LERR_BUF;
@ -134,7 +135,7 @@ static err_t tcp_output_cb(void *arg, struct pbuf *p, uint32_t src_ip, uint32_t
// ====================================================================
// Обработчик не-TCP (UDP/ICMP прокси)
// ====================================================================
static int tcp_proxy_handle_non_tcp(struct tcp_proxy* p, uint8_t* buf, size_t len) {
static int tcp_proxy_client_handle_non_tcp(struct tcp_proxy_client* 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;
@ -164,7 +165,7 @@ static int tcp_proxy_handle_non_tcp(struct tcp_proxy* p, uint8_t* buf, size_t le
// ====================================================================
// Помощник: передача данных из очереди to_lwip в lwIP TCP
// ====================================================================
static void proxy_feed_from_transport(struct proxy_conn *pc) {
static void tcp_proxy_client_feed_from_transport(struct tcp_proxy_client_conn *pc) {
if (!pc->to_lwip || !pc->pcb || pc->tun_closed) return;
int sent_any = 0;
uint32_t q_pre = queue_entry_count(pc->to_lwip);
@ -196,12 +197,12 @@ static void proxy_feed_from_transport(struct proxy_conn *pc) {
// ====================================================================
// lwIP TCP коллбэки
// ====================================================================
static err_t proxy_accept_cb(void *arg, struct tcp_pcb *newpcb, err_t err) {
struct tcp_proxy *p = (struct tcp_proxy *)arg;
static err_t tcp_proxy_client_accept_cb(void *arg, struct tcp_pcb *newpcb, err_t err) {
struct tcp_proxy_client *p = (struct tcp_proxy_client *)arg;
if (err != LERR_OK || !newpcb) return LERR_ABRT;
struct proxy_conn *pc = u_calloc(1, sizeof(struct proxy_conn));
if (!pc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy: accept alloc failed"); return LERR_MEM; }
struct tcp_proxy_client_conn *pc = u_calloc(1, sizeof(struct tcp_proxy_client_conn));
if (!pc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy client: accept alloc failed"); return LERR_MEM; }
pc->proxy = p; pc->pcb = newpcb; pc->stream_id = ++p->next_stream_id;
int i;
@ -219,24 +220,41 @@ static err_t proxy_accept_cb(void *arg, struct tcp_pcb *newpcb, err_t err) {
}
tcp_arg(newpcb, pc);
tcp_recv(newpcb, proxy_recv_cb);
tcp_sent(newpcb, proxy_sent_cb);
tcp_err(newpcb, proxy_err_cb);
tcp_poll(newpcb, proxy_poll_cb, 2);
tcp_recv(newpcb, tcp_proxy_client_recv_cb);
tcp_sent(newpcb, tcp_proxy_client_sent_cb);
tcp_err(newpcb, tcp_proxy_client_err_cb);
tcp_poll(newpcb, tcp_proxy_client_poll_cb, 2);
tcp_nagle_disable(newpcb);
pc->to_lwip = queue_new(p->ua, 0, 0, 0, "to_lwip");
queue_waiter_handle_init(&pc->tx_waiter, tcp_proxy_client_tx_waiter_cb, pc);
if (send_connect(pc) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy: send_connect failed sid=%08x to %d.%d.%d.%d:%d", pc->stream_id, pc->dest_ip[0], pc->dest_ip[1], pc->dest_ip[2], pc->dest_ip[3], ntohs(pc->dest_port)); u_free(pc); return LERR_MEM; }
if (tcp_proxy_client_send_connect(pc) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy client: send_connect failed sid=%08x to %d.%d.%d.%d:%d", pc->stream_id, pc->dest_ip[0], pc->dest_ip[1], pc->dest_ip[2], pc->dest_ip[3], ntohs(pc->dest_port)); u_free(pc); return LERR_MEM; }
pc->next = p->conns; p->conns = pc; p->conn_count++;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy: new conn sid=%08x local_port=%u -> %d.%d.%d.%d:%d total_conns=%d snd_wnd=%u mss=%u",
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client: new conn sid=%08x local_port=%u -> %d.%d.%d.%d:%d total_conns=%d snd_wnd=%u mss=%u",
pc->stream_id, newpcb->local_port, pc->dest_ip[0], pc->dest_ip[1], pc->dest_ip[2], pc->dest_ip[3], ntohs(pc->dest_port), p->conn_count, newpcb->snd_wnd, newpcb->mss);
return LERR_OK;
}
static err_t proxy_recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) {
struct proxy_conn *pc = (struct proxy_conn *)arg;
// ====================================================================
// Backpressure: waiter callback при освобождении normalizer очереди
// ====================================================================
static void tcp_proxy_client_tx_waiter_cb(struct ll_queue* q, void* arg) {
(void)q;
struct tcp_proxy_client_conn* pc = (struct tcp_proxy_client_conn*)arg;
if (!pc || !pc->tx_buf) return;
int ret = tcp_proxy_client_send_data(pc, pc->tx_buf, pc->tx_len);
if (ret == 0) {
tcp_recved(pc->pcb, pc->tx_len);
u_free(pc->tx_buf); pc->tx_buf = NULL; pc->tx_len = 0;
if (pc->tun_closed && !pc->close_sent && !pc->close_pending)
tcp_proxy_client_send_close(pc);
}
}
static err_t tcp_proxy_client_recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) {
struct tcp_proxy_client_conn *pc = (struct tcp_proxy_client_conn *)arg;
if (!pc) { if (p) pbuf_free(p); return LERR_OK; }
if (p == NULL || err != LERR_OK) {
@ -246,8 +264,8 @@ static err_t proxy_recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t
{ uint16_t wnd_gap = TCP_WND_MAX(pcb) - pcb->rcv_wnd;
if (wnd_gap > 0) tcp_recved(pcb, wnd_gap); }
pc->tun_closed = 1;
if (!pc->close_sent && !pc->close_pending) {
if (send_close(pc) < 0) DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY FIN send_close failed sid=%08x", pc->stream_id);
if (!pc->tx_buf && !pc->close_sent && !pc->close_pending) {
if (tcp_proxy_client_send_close(pc) < 0) DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY FIN send_close failed sid=%08x", pc->stream_id);
}
return LERR_OK;
}
@ -257,49 +275,55 @@ static err_t proxy_recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t
tcp_recved(pcb, len); pbuf_free(p); return LERR_OK;
}
if (pc->tx_buf) { pbuf_free(p); return LERR_OK; }
uint8_t *data = u_malloc(len);
if (data) {
pbuf_copy_partial(p, data, len, 0);
if (send_data(pc, data, len) < 0) DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY recv send_data failed sid=%08x len=%u", pc->stream_id, len);
u_free(data);
DEBUG_INFO(DEBUG_CATEGORY_TRAFFIC, "PROXY RECV client->exit sid=%08x len=%u", pc->stream_id, len);
int ret = tcp_proxy_client_send_data(pc, data, len);
if (ret == 0) { tcp_recved(pcb, len); u_free(data); }
else {
pc->tx_buf = data; pc->tx_len = len;
etcp_router_waiter_register(pc->proxy->inst, pc->proxy->via_node_id, &pc->tx_waiter);
}
} else DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY recv malloc(%u) failed sid=%08x", len, pc->stream_id);
DEBUG_INFO(DEBUG_CATEGORY_TRAFFIC, "PROXY RECV client->exit sid=%08x len=%u", pc->stream_id, len);
tcp_recved(pcb, len); pbuf_free(p);
pbuf_free(p);
return LERR_OK;
}
static err_t proxy_sent_cb(void *arg, struct tcp_pcb *pcb, uint16_t len) {
static err_t tcp_proxy_client_sent_cb(void *arg, struct tcp_pcb *pcb, uint16_t len) {
(void)len;
struct proxy_conn *pc = (struct proxy_conn *)arg;
struct tcp_proxy_client_conn *pc = (struct tcp_proxy_client_conn *)arg;
if (!pc) return LERR_OK;
proxy_feed_from_transport(pc);
tcp_proxy_client_feed_from_transport(pc);
return LERR_OK;
}
static void proxy_err_cb(void *arg, err_t err) {
struct proxy_conn *pc = (struct proxy_conn *)arg;
if (!pc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY proxy_err_cb: pc=NULL err=%d", err); return; }
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy: error %d sid=%08x pcb_state=%u tun_closed=%d rem_closed=%d connected=%d",
err, pc->stream_id, pc->pcb ? pc->pcb->state : 0, pc->tun_closed, pc->rem_closed, pc->connected);
static void tcp_proxy_client_err_cb(void *arg, err_t err) {
struct tcp_proxy_client_conn *pc = (struct tcp_proxy_client_conn *)arg;
if (!pc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY tcp_proxy_client_err_cb: pc=NULL err=%d", err); return; }
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy client: error %d sid=%08x pcb_state=%u tun_closed=%d rem_closed=%d",
err, pc->stream_id, pc->pcb ? pc->pcb->state : 0, pc->tun_closed, pc->rem_closed);
pc->error = 1;
if (send_error(pc) < 0) DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY send_error failed sid=%08x", pc->stream_id);
if (tcp_proxy_client_send_error(pc) < 0) DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY send_error failed sid=%08x", pc->stream_id);
}
static err_t proxy_poll_cb(void *arg, struct tcp_pcb *pcb) {
struct proxy_conn *pc = (struct proxy_conn *)arg;
static err_t tcp_proxy_client_poll_cb(void *arg, struct tcp_pcb *pcb) {
struct tcp_proxy_client_conn *pc = (struct tcp_proxy_client_conn *)arg;
if (!pc) return LERR_OK;
if (pc->error) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY CLEANUP error sid=%08x tun_closed=%d rem_closed=%d connected=%d", pc->stream_id, pc->tun_closed, pc->rem_closed, pc->connected);
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY CLEANUP error sid=%08x tun_closed=%d rem_closed=%d", pc->stream_id, pc->tun_closed, pc->rem_closed);
if (pc->pcb) { tcp_arg(pc->pcb, NULL); tcp_abort(pc->pcb); pc->pcb = NULL; }
proxy_conn_free(pc);
tcp_proxy_client_conn_free(pc);
return LERR_OK;
}
/* Retry pending CLOSE/ERROR if previous send failed */
if (pc->close_pending) {
if (pc->error) send_error(pc);
else send_close(pc);
if (pc->error) tcp_proxy_client_send_error(pc);
else tcp_proxy_client_send_close(pc);
}
if (pc->tun_closed && pc->rem_closed && pc->pcb) {
@ -316,7 +340,7 @@ static err_t proxy_poll_cb(void *arg, struct tcp_pcb *pcb) {
if (wnd_gap > 0) tcp_recved(save, wnd_gap); }
while (save->unsent) { struct tcp_seg *seg = save->unsent; save->unsent = seg->next; u_free(seg); }
tcp_close(save);
proxy_conn_free(pc);
tcp_proxy_client_conn_free(pc);
}
}
return LERR_OK;
@ -325,38 +349,38 @@ static err_t proxy_poll_cb(void *arg, struct tcp_pcb *pcb) {
// ====================================================================
// Обеспечить динамический listen pcb для прозрачного исходящего TCP проксирования
// ====================================================================
static void tcp_proxy_ensure_outbound_listen(struct tcp_proxy* p, uint16_t dport_net) {
static void tcp_proxy_client_ensure_outbound_listen(struct tcp_proxy_client* p, uint16_t dport_net) {
uint16_t dport_host = ntohs(dport_net);
struct tcp_pcb* lp = p->lwip->listen_pcbs;
while (lp) { if (lp->local_port == dport_host) return; lp = lp->next; }
struct tcp_pcb* lpcb = tcp_new(p->lwip);
if (!lpcb) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy: tcp_new failed for dynamic listen port %u", dport_host); return; }
if (!lpcb) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy client: tcp_new failed for dynamic listen port %u", dport_host); return; }
tcp_bind(lpcb, INADDR_ANY, dport_net);
struct tcp_pcb* listen_pcb = tcp_listen(lpcb);
if (listen_pcb) {
tcp_arg(listen_pcb, p); tcp_accept(listen_pcb, proxy_accept_cb);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy: dynamic listen on port %u", dport_host);
tcp_arg(listen_pcb, p); tcp_accept(listen_pcb, tcp_proxy_client_accept_cb);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client: dynamic listen on port %u", dport_host);
}
}
// ====================================================================
// Ввод: IP пакет → lwip_tcp (из TUN output_queue)
// ====================================================================
static void tcp_proxy_tun_input(struct ll_queue* q, void* arg) {
struct tcp_proxy* p = (struct tcp_proxy*)arg;
static void tcp_proxy_client_tun_input(struct ll_queue* q, void* arg) {
struct tcp_proxy_client* p = (struct tcp_proxy_client*)arg;
struct ll_entry* entry = queue_data_get(q);
if (!entry) return;
if (entry->dgram && entry->len > 1) {
uint8_t* ip = entry->dgram + 1; size_t len = entry->len - 1;
if (len > 20 && len <= 2000) {
if (!tcp_proxy_handle_non_tcp(p, ip, len)) {
if (!tcp_proxy_client_handle_non_tcp(p, ip, len)) {
uint8_t proto = ip[9];
if (proto == IPPROTO_TCP) {
uint16_t ip_hdr_len = (ip[0] & 0x0F) * 4;
uint16_t ip_total = ((uint16_t)ip[2] << 8) | ip[3];
if (ip_hdr_len >= 20 && ip_total >= ip_hdr_len && len >= ip_total) {
uint16_t dport_net; memcpy(&dport_net, ip + ip_hdr_len + 2, 2);
tcp_proxy_ensure_outbound_listen(p, dport_net);
tcp_proxy_client_ensure_outbound_listen(p, dport_net);
uint32_t src_ip, dst_ip;
memcpy(&src_ip, ip + 12, 4); memcpy(&dst_ip, ip + 16, 4);
uint16_t tcp_len = ip_total - ip_hdr_len;
@ -371,149 +395,138 @@ static void tcp_proxy_tun_input(struct ll_queue* q, void* arg) {
}
// ====================================================================
// Очистка proxy_conn
// Очистка tcp_proxy_client_conn
// ====================================================================
static void proxy_conn_free(struct proxy_conn *pc) {
static void tcp_proxy_client_conn_free(struct tcp_proxy_client_conn *pc) {
if (!pc) return;
struct tcp_proxy *p = pc->proxy;
struct tcp_proxy_client *p = pc->proxy;
uint32_t pending = pc->to_lwip ? queue_entry_count(pc->to_lwip) : 0;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY FREE sid=%08x total_conns=%d to_lwip_q=%u",
pc->stream_id, p->conn_count, pending);
struct proxy_conn **prev = &p->conns;
struct tcp_proxy_client_conn **prev = &p->conns;
while (*prev) { if (*prev == pc) { *prev = pc->next; p->conn_count--; break; } prev = &(*prev)->next; }
if (pc->to_lwip) {
struct ll_entry *e;
while ((e = queue_data_get(pc->to_lwip))) { queue_dgram_free(e); queue_entry_free(e); }
queue_free(pc->to_lwip); pc->to_lwip = NULL;
}
if (pc->tx_buf) { u_free(pc->tx_buf); pc->tx_buf = NULL; pc->tx_len = 0; }
if (pc->proxy && pc->proxy->inst) etcp_router_waiter_cancel(pc->proxy->inst, pc->proxy->via_node_id, &pc->tx_waiter);
u_free(pc);
}
// ====================================================================
// Обработчики входящих сообщений (сторона клиента)
// ====================================================================
static struct proxy_conn* find_pc_by_stream(struct tcp_proxy* p, uint32_t stream_id) {
struct proxy_conn* pc;
static struct tcp_proxy_client_conn* tcp_proxy_client_find_conn(struct tcp_proxy_client* p, uint32_t stream_id) {
struct tcp_proxy_client_conn* pc;
for (pc = p->conns; pc; pc = pc->next) if (pc->stream_id == stream_id) return pc;
return NULL;
}
static void handle_connected(struct tcp_proxy* p, uint32_t stream_id, struct ll_entry* entry) {
struct proxy_conn* pc = find_pc_by_stream(p, stream_id);
if (!pc) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "PROXY CONNECTED sid=%08x — no conn, drop", stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
if (entry->len >= TCP_PROXY_CONNECTED_HDR_SIZE) {
uint8_t status = entry->dgram[TCP_PROXY_HDR_SIZE + 2];
if (status == TCP_PROXY_CONNECTED_OK) {
pc->connected = 1;
uint16_t local_port = 0; memcpy(&local_port, entry->dgram + TCP_PROXY_HDR_SIZE, 2);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY CONNECTED sid=%08x exit_port=%u", stream_id, ntohs(local_port));
} else {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy: remote refused sid=%08x", stream_id);
pc->rem_closed = 1;
}
}
queue_dgram_free(entry); queue_entry_free(entry);
}
static void handle_data(struct tcp_proxy* p, struct ETCP_CONN* conn, uint32_t stream_id, struct ll_entry* entry) {
struct proxy_conn* pc = find_pc_by_stream(p, stream_id);
if (!pc) {
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "PROXY DATA sid=%08x — нет соединения, шлём CLOSE", stream_id);
if (conn) proxy_send_msg(p->inst, conn->peer_node_id, TCP_PROXY_SUBCMD_CLOSE, stream_id, NULL, 0);
static void tcp_proxy_client_handle_data(struct tcp_proxy_client* p, struct ETCP_CONN* conn, uint32_t stream_id, struct ll_entry* entry) {
struct tcp_proxy_client_conn* pc = tcp_proxy_client_find_conn(p, stream_id);
if (!pc || pc->rem_closed || pc->error || pc->tun_closed) {
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "PROXY DATA sid=%08x — no conn/closed, шлём ERROR", stream_id);
tcp_proxy_client_send_msg(p->inst, p->via_node_id, TCP_PROXY_SUBCMD_ERROR, stream_id, NULL, 0);
queue_dgram_free(entry); queue_entry_free(entry); return;
}
if (pc->rem_closed || pc->error || pc->tun_closed) { queue_dgram_free(entry); queue_entry_free(entry); return; }
size_t data_len = entry->len - TCP_PROXY_HDR_SIZE;
DEBUG_INFO(DEBUG_CATEGORY_TRAFFIC, "PROXY DATA <- sid=%08x len=%zu", stream_id, data_len);
if (data_len > 0) {
struct ll_entry* e = entry_from_data(pc->proxy->entry_pool, entry->dgram + TCP_PROXY_HDR_SIZE, (uint16_t)data_len);
struct ll_entry* e = tcp_proxy_client_entry_from_data(pc->proxy->entry_pool, entry->dgram + TCP_PROXY_HDR_SIZE, (uint16_t)data_len);
if (e) queue_data_put(pc->to_lwip, e);
proxy_feed_from_transport(pc);
tcp_proxy_client_feed_from_transport(pc);
}
queue_dgram_free(entry); queue_entry_free(entry);
}
static void handle_close(struct tcp_proxy* p, uint32_t stream_id) {
struct proxy_conn* pc = find_pc_by_stream(p, stream_id);
static void tcp_proxy_client_handle_close(struct tcp_proxy_client* p, uint32_t stream_id) {
struct tcp_proxy_client_conn* pc = tcp_proxy_client_find_conn(p, stream_id);
if (pc) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY REM_CLOSED sid=%08x tun_closed=%d connected=%d",
stream_id, pc->tun_closed, pc->connected);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "PROXY REM_CLOSED sid=%08x tun_closed=%d",
stream_id, pc->tun_closed);
pc->rem_closed = 1;
} else DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "PROXY CLOSE sid=%08x — no conn", stream_id);
} else {
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "PROXY CLOSE sid=%08x — no conn, шлём ERROR", stream_id);
tcp_proxy_client_send_msg(p->inst, p->via_node_id, TCP_PROXY_SUBCMD_ERROR, stream_id, NULL, 0);
}
}
static void handle_error(struct tcp_proxy* p, uint32_t stream_id) {
struct proxy_conn* pc = find_pc_by_stream(p, stream_id);
static void tcp_proxy_client_handle_error(struct tcp_proxy_client* p, uint32_t stream_id) {
struct tcp_proxy_client_conn* pc = tcp_proxy_client_find_conn(p, stream_id);
if (pc) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY ERROR from exit sid=%08x tun_closed=%d rem_closed=%d connected=%d",
stream_id, pc->tun_closed, pc->rem_closed, pc->connected);
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "PROXY ERROR from exit sid=%08x tun_closed=%d rem_closed=%d",
stream_id, pc->tun_closed, pc->rem_closed);
pc->error = 1;
} else DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "PROXY ERROR sid=%08x — no conn", stream_id);
} else {
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "PROXY ERROR sid=%08x — no conn, шлём ERROR", stream_id);
tcp_proxy_client_send_msg(p->inst, p->via_node_id, TCP_PROXY_SUBCMD_ERROR, stream_id, NULL, 0);
}
}
// ====================================================================
// Единый обработчик etcp_router (диспетчеризация в tcp_proxy или remote_proxy)
// Единый обработчик etcp_router (диспетчеризация в tcp_proxy_client или tcp_proxy_server)
// ====================================================================
void tcp_proxy_etcp_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
void tcp_proxy_client_etcp_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (!entry || !entry->dgram || entry->len < TCP_PROXY_HDR_SIZE) {
if (entry) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy: bad entry len=%u", entry->len); queue_dgram_free(entry); queue_entry_free(entry); }
if (entry) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy client: bad entry len=%u", entry->len); queue_dgram_free(entry); queue_entry_free(entry); }
return;
}
uint8_t subcmd = entry->dgram[1];
uint32_t stream_id; memcpy(&stream_id, entry->dgram + 2, 4);
struct UTUN_INSTANCE* inst = conn ? conn->instance : NULL;
struct tcp_proxy* proxy = inst ? inst->tcp_proxy : NULL;
struct tcp_proxy_client* proxy = inst ? inst->tcp_proxy_client : NULL;
if (subcmd == TCP_PROXY_SUBCMD_CONNECT) {
uint64_t src_node_id = conn ? conn->peer_node_id : (inst ? inst->node_id : 0);
remote_proxy_handle_connect(inst, entry, stream_id, src_node_id);
tcp_proxy_server_handle_connect(inst, entry, stream_id, src_node_id);
return;
}
if (inst && inst->remote_proxy.enabled) {
struct remote_proxy_conn* rc = remote_proxy_find_conn(&inst->remote_proxy, stream_id);
if (inst && inst->tcp_proxy_server.enabled) {
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(&inst->tcp_proxy_server, stream_id);
if (rc) {
if (subcmd == TCP_PROXY_SUBCMD_DATA) { remote_proxy_handle_data(inst, conn, entry, stream_id); return; }
if (subcmd == TCP_PROXY_SUBCMD_CLOSE) { remote_proxy_handle_close(inst, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_ERROR) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "RP ERROR recv sid=%08x", stream_id); rc->error = 1; rp_conn_free(rc); queue_dgram_free(entry); queue_entry_free(entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_DATA) { tcp_proxy_server_handle_data(inst, conn, entry, stream_id); return; }
if (subcmd == TCP_PROXY_SUBCMD_CLOSE) { tcp_proxy_server_handle_close(inst, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_ERROR) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "RP ERROR recv sid=%08x", stream_id); rc->error = 1; tcp_proxy_server_conn_free(rc); queue_dgram_free(entry); queue_entry_free(entry); return; }
}
}
if (proxy) {
if (subcmd == TCP_PROXY_SUBCMD_CONNECTED) { handle_connected(proxy, stream_id, entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_DATA) { handle_data(proxy, conn, stream_id, entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_CLOSE) { handle_close(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_ERROR) { handle_error(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_DATA) { tcp_proxy_client_handle_data(proxy, conn, stream_id, entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_CLOSE) { tcp_proxy_client_handle_close(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_ERROR) { tcp_proxy_client_handle_error(proxy, stream_id); queue_dgram_free(entry); queue_entry_free(entry); return; }
}
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy: unhandled subcmd=%02x sid=%08x", subcmd, stream_id);
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy client: unhandled subcmd=%02x sid=%08x", subcmd, stream_id);
queue_dgram_free(entry); queue_entry_free(entry);
}
// ====================================================================
// Публичное API
// ====================================================================
struct tcp_proxy* tcp_proxy_create(struct UTUN_INSTANCE* inst, struct UASYNC* ua,
struct tcp_proxy_client* tcp_proxy_client_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,
struct tcp_proxy_client_mapping_config* mappings, int mapping_count,
uint64_t via_node_id)
{
if (!ua) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_create: ua is NULL"); return NULL; }
if (!tun_name || !tun_ip) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_create: tun name/ip required"); return NULL; }
if (!ua) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: ua is NULL"); return NULL; }
if (!tun_name || !tun_ip) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: tun name/ip required"); return NULL; }
struct tcp_proxy* p = u_calloc(1, sizeof(struct tcp_proxy));
if (!p) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_create: u_calloc failed"); return NULL; }
struct tcp_proxy_client* p = u_calloc(1, sizeof(struct tcp_proxy_client));
if (!p) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: u_calloc failed"); return NULL; }
p->inst = inst; p->ua = ua; p->next_stream_id = 1;
p->via_node_id = via_node_id;
p->mappings = mappings; p->mapping_count = mapping_count;
p->entry_pool = memory_pool_init(sizeof(struct ll_entry));
if (!p->entry_pool) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_create: memory_pool_init failed"); u_free(p); return NULL; }
if (!p->entry_pool) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: memory_pool_init failed"); u_free(p); return NULL; }
p->tun = tun_init_nat(ua, tun_name, tun_ip, mtu > 0 ? mtu : 1500, test_mode);
if (!p->tun) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "tcp_proxy: failed to create TUN %s", tun_name); memory_pool_destroy(p->entry_pool); u_free(p); return NULL; }
queue_set_callback(p->tun->output_queue, tcp_proxy_tun_input, p);
if (!p->tun) { DEBUG_ERROR(DEBUG_CATEGORY_TUN, "tcp_proxy_client: failed to create TUN %s", tun_name); memory_pool_destroy(p->entry_pool); u_free(p); return NULL; }
queue_set_callback(p->tun->output_queue, tcp_proxy_client_tun_input, p);
p->lwip = lwip_tcp_init(ua, tcp_output_cb, p);
p->lwip = lwip_tcp_init(ua, tcp_proxy_client_output_cb, p);
if (!p->lwip) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_create: lwip_tcp_init failed");
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_client_create: lwip_tcp_init failed");
tun_close(p->tun);
memory_pool_destroy(p->entry_pool); u_free(p); return NULL;
}
@ -527,37 +540,37 @@ struct tcp_proxy* tcp_proxy_create(struct UTUN_INSTANCE* inst, struct UASYNC* ua
struct tcp_pcb *listen_pcb = tcp_listen(lpcb);
if (listen_pcb) {
tcp_arg(listen_pcb, p);
tcp_accept(listen_pcb, proxy_accept_cb);
tcp_accept(listen_pcb, tcp_proxy_client_accept_cb);
}
}
}
if (inst) {
if (etcp_router_bind(inst, ETCP_ID_TCP_PROXY, tcp_proxy_etcp_recv_cb) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy: etcp_router_bind failed");
if (etcp_router_bind(inst, ETCP_ID_TCP_PROXY, tcp_proxy_client_etcp_recv_cb) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy client: etcp_router_bind failed");
} else {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy: etcp_router bind registered for ID=0x%02x", ETCP_ID_TCP_PROXY);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client: etcp_router bind registered for ID=0x%02x", ETCP_ID_TCP_PROXY);
udp_proxy_init(inst, ua);
icmp_proxy_init(inst, ua);
}
}
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TCP proxy created: mappings=%d via_node=%016llx", mapping_count, (unsigned long long)via_node_id);
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TCP proxy client created: mappings=%d via_node=%016llx", mapping_count, (unsigned long long)via_node_id);
return p;
}
void tcp_proxy_destroy(struct tcp_proxy* p) {
void tcp_proxy_client_destroy(struct tcp_proxy_client* p) {
if (!p) return;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy destroying: conns=%d", p->conn_count);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy client destroying: conns=%d", p->conn_count);
if (p->inst) etcp_router_unbind(p->inst, ETCP_ID_TCP_PROXY);
udp_proxy_destroy(p->inst);
icmp_proxy_destroy(p->inst);
if (p->lwip) { lwip_tcp_destroy(p->lwip); p->lwip = NULL; }
struct proxy_conn* pc = p->conns;
struct tcp_proxy_client_conn* pc = p->conns;
while (pc) {
struct proxy_conn* next = pc->next;
struct tcp_proxy_client_conn* next = pc->next;
if (pc->pcb) { tcp_arg(pc->pcb, NULL); tcp_abort(pc->pcb); pc->pcb = NULL; }
if (pc->to_lwip) {
struct ll_entry *e;

40
src/tcp_proxy.h → src/proxy/tcp_proxy_client.h

@ -1,10 +1,11 @@
// tcp_proxy.h — TCP прокси: стек lwIP TCP → ETCP → удалённый exit узел
#ifndef TCP_PROXY_H
#define TCP_PROXY_H
// tcp_proxy_client.h — TCP прокси-клиент: стек lwIP TCP → ETCP → удалённый exit узел
#ifndef TCP_PROXY_CLIENT_H
#define TCP_PROXY_CLIENT_H
#include <stdint.h>
#include <stddef.h>
#include "../lib/socket_compat.h"
#include "../lib/ll_queue.h"
struct UASYNC;
struct UTUN_INSTANCE;
@ -13,16 +14,16 @@ struct tun_if;
struct ll_queue;
struct ll_entry;
struct memory_pool;
struct tcp_proxy_mapping_config;
struct remote_proxy_ctx;
struct tcp_proxy_client_mapping_config;
struct tcp_proxy_server;
struct lwip_tcp_ctx;
struct tcp_pcb;
struct proxy_conn {
struct proxy_conn* next;
struct tcp_proxy* proxy;
struct tcp_pcb* pcb;
struct tcp_proxy_client_conn {
struct tcp_proxy_client_conn* next;
struct tcp_proxy_client* proxy;
struct tcp_pcb* pcb;
uint32_t stream_id;
@ -33,32 +34,35 @@ struct proxy_conn {
uint8_t error; // ошибка, немедленная очистка
uint8_t close_sent; // отправили CLOSE/ERROR в exit
uint8_t close_pending; // CLOSE/ERROR не доставлен, ждём повтора
uint8_t connected; // CONNECTED(OK) получен от exit
uint8_t* tx_buf; // буфер при backpressure (lwIP→ETCP send fail)
uint16_t tx_len;
struct queue_waiter_handle tx_waiter;
uint8_t dest_ip[4];
uint16_t dest_port;
};
struct tcp_proxy {
struct tcp_proxy_client {
struct UTUN_INSTANCE* inst;
struct UASYNC* ua;
struct tun_if* tun;
struct proxy_conn* conns;
struct tcp_proxy_client_conn* conns;
int conn_count;
struct memory_pool* entry_pool;
uint32_t next_stream_id;
uint64_t via_node_id;
struct lwip_tcp_ctx* lwip;
struct tcp_proxy_mapping_config* mappings; // указатель на конфиг
struct tcp_proxy_client_mapping_config* mappings; // указатель на конфиг
int mapping_count;
};
struct tcp_proxy* tcp_proxy_create(struct UTUN_INSTANCE* inst, struct UASYNC* ua,
struct tcp_proxy_client* tcp_proxy_client_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,
struct tcp_proxy_client_mapping_config* mappings, int mapping_count,
uint64_t via_node_id);
void tcp_proxy_destroy(struct tcp_proxy* p);
void tcp_proxy_etcp_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry);
void tcp_proxy_client_destroy(struct tcp_proxy_client* p);
void tcp_proxy_client_etcp_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry);
#endif // TCP_PROXY_H
#endif // TCP_PROXY_CLIENT_H

271
src/remote_proxy.c → src/proxy/tcp_proxy_server.c

@ -1,6 +1,6 @@
// remote_proxy.c — Удаленный TCP прокси (exit node)
#include "remote_proxy.h"
#include "tcp_proxy.h"
// tcp_proxy_server.c — TCP прокси-сервер (exit node)
#include "tcp_proxy_server.h"
#include "tcp_proxy_client.h"
#include "udp_proxy.h"
#include "icmp_proxy.h"
#include "etcp.h"
@ -26,25 +26,23 @@
#define MSG_NOSIGNAL 0
#endif
static struct remote_proxy_ctx* g_rp_ctx = NULL;
static struct tcp_proxy_server* g_tcp_proxy_server_ctx = NULL;
static void rp_sock_read_cb(socket_t sock, void* arg);
static void rp_sock_write_cb(socket_t sock, void* arg);
static void rp_sock_error_cb(socket_t sock, void* arg);
static void rp_sock_retry_cb(void* arg);
static void rp_sock_pause_cb(void* arg);
static void rp_close_retry_cb(void* arg);
static void rp_sock_send(struct remote_proxy_conn* rc, const uint8_t* data, size_t len);
void rp_conn_free(struct remote_proxy_conn* rc);
static void tcp_proxy_server_sock_read_cb(socket_t sock, void* arg);
static void tcp_proxy_server_sock_write_cb(socket_t sock, void* arg);
static void tcp_proxy_server_sock_error_cb(socket_t sock, void* arg);
static void tcp_proxy_server_sock_retry_cb(void* arg);
static void tcp_proxy_server_pause_waiter_cb(struct ll_queue* q, void* arg);
static void tcp_proxy_server_close_retry_cb(void* arg);
static void tcp_proxy_server_sock_send(struct tcp_proxy_server_conn* rc, const uint8_t* data, size_t len);
void tcp_proxy_server_conn_free(struct tcp_proxy_server_conn* rc);
#define RP_NORMALIZER_Q_THRESHOLD 64
static int rp_send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd,
static int tcp_proxy_server_send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd,
uint32_t sid, const uint8_t* data, size_t len) {
struct ll_entry* e = queue_entry_new(0);
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "rp_send_msg: queue_entry_new failed subcmd=%02x sid=%08x", subcmd, sid); return -1; }
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_server_send_msg: queue_entry_new failed subcmd=%02x sid=%08x", subcmd, sid); return -1; }
e->dgram = u_malloc(TCP_PROXY_HDR_SIZE + len);
if (!e->dgram) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "rp_send_msg: malloc(%zu) failed subcmd=%02x sid=%08x", TCP_PROXY_HDR_SIZE + len, subcmd, sid); queue_entry_free(e); return -1; }
if (!e->dgram) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_proxy_server_send_msg: malloc(%zu) failed subcmd=%02x sid=%08x", TCP_PROXY_HDR_SIZE + len, subcmd, sid); queue_entry_free(e); return -1; }
e->dgram[0] = ETCP_ID_TCP_PROXY;
e->dgram[1] = subcmd;
memcpy(e->dgram + 2, &sid, 4);
@ -53,54 +51,47 @@ static int rp_send_msg(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t subcmd,
return etcp_route_send(inst, dst, e);
}
static int rp_send_connected(struct UTUN_INSTANCE* inst, uint64_t dst, uint32_t sid,
uint16_t local_port, uint8_t status) {
uint8_t buf[3];
memcpy(buf, &local_port, 2); buf[2] = status;
return rp_send_msg(inst, dst, TCP_PROXY_SUBCMD_CONNECTED, sid, buf, 3);
}
static void rp_close_retry_cb(void* arg) {
struct remote_proxy_conn* rc = (struct remote_proxy_conn*)arg;
static void tcp_proxy_server_close_retry_cb(void* arg) {
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
if (!rc) return;
rc->close_timer = NULL;
if (!rc->close_pending) return;
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (!inst) return;
uint8_t subcmd = rc->error ? TCP_PROXY_SUBCMD_ERROR : TCP_PROXY_SUBCMD_CLOSE;
if (rp_send_msg(inst, rc->peer_node_id, subcmd, rc->stream_id, NULL, 0) < 0) {
if (tcp_proxy_server_send_msg(inst, rc->peer_node_id, subcmd, rc->stream_id, NULL, 0) < 0) {
rc->close_backoff = rc->close_backoff < 5000 ? rc->close_backoff * 2 : 5000;
rc->close_timer = uasync_set_timeout(rc->ua, rc->close_backoff, rc, rp_close_retry_cb, "rp_close_retry");
rc->close_timer = uasync_set_timeout(rc->ua, rc->close_backoff, rc, tcp_proxy_server_close_retry_cb, "tcp_proxy_server_close_retry");
return;
}
rc->close_pending = 0;
}
static void rp_send_close(struct remote_proxy_conn* rc) {
static void tcp_proxy_server_send_close(struct tcp_proxy_server_conn* rc) {
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (!inst) return;
if (rp_send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_CLOSE, rc->stream_id, NULL, 0) < 0) {
if (tcp_proxy_server_send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_CLOSE, rc->stream_id, NULL, 0) < 0) {
rc->close_pending = 1;
rc->close_backoff = 50;
if (!rc->close_timer)
rc->close_timer = uasync_set_timeout(rc->ua, rc->close_backoff, rc, rp_close_retry_cb, "rp_close_retry");
rc->close_timer = uasync_set_timeout(rc->ua, rc->close_backoff, rc, tcp_proxy_server_close_retry_cb, "tcp_proxy_server_close_retry");
}
}
static void rp_send_error(struct remote_proxy_conn* rc) {
static void tcp_proxy_server_send_error(struct tcp_proxy_server_conn* rc) {
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (!inst) return;
if (rp_send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_ERROR, rc->stream_id, NULL, 0) < 0) {
if (tcp_proxy_server_send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_ERROR, rc->stream_id, NULL, 0) < 0) {
rc->close_pending = 1;
rc->close_backoff = 50;
if (!rc->close_timer)
rc->close_timer = uasync_set_timeout(rc->ua, rc->close_backoff, rc, rp_close_retry_cb, "rp_close_retry");
rc->close_timer = uasync_set_timeout(rc->ua, rc->close_backoff, rc, tcp_proxy_server_close_retry_cb, "tcp_proxy_server_close_retry");
}
}
static int rp_conn_total(struct remote_proxy_conn* rc) {
static int tcp_proxy_server_conn_total(struct tcp_proxy_server_conn* rc) {
if (!rc || !rc->ctx) return 0;
int n = 0; struct remote_proxy_conn* c;
int n = 0; struct tcp_proxy_server_conn* c;
for (c = rc->ctx->conns; c; c = c->next) n++;
return n;
}
@ -108,7 +99,7 @@ static int rp_conn_total(struct remote_proxy_conn* rc) {
// ====================================================================
// Неблокирующий send с EAGAIN и таймером повтора
// ====================================================================
static int rp_sock_try_send(struct remote_proxy_conn* rc) {
static int tcp_proxy_server_sock_try_send(struct tcp_proxy_server_conn* rc) {
if (!rc || rc->sock == SOCKET_INVALID || !rc->out_buf) return 0;
while (rc->out_off < rc->out_len) {
ssize_t n = send(rc->sock, rc->out_buf + rc->out_off, rc->out_len - rc->out_off, MSG_NOSIGNAL);
@ -127,23 +118,26 @@ static int rp_sock_try_send(struct remote_proxy_conn* rc) {
return 0;
}
static void rp_sock_retry_cb(void* arg) {
struct remote_proxy_conn* rc = (struct remote_proxy_conn*)arg;
static void tcp_proxy_server_sock_retry_cb(void* arg) {
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
if (!rc || !rc->out_buf) { rc->out_timer = NULL; return; }
int ret = rp_sock_try_send(rc);
int ret = tcp_proxy_server_sock_try_send(rc);
if (ret == 0) {
rc->out_timer = NULL; rc->out_backoff = 0;
if (rc->cli_closed && rc->sock != SOCKET_INVALID && rc->connected == 1) shutdown(rc->sock, SHUT_WR);
if (rc->sock_closed) tcp_proxy_server_send_close(rc);
if (rc->cli_closed && rc->sock_closed) tcp_proxy_server_conn_free(rc);
} else if (ret == 1) {
rc->out_backoff = rc->out_backoff < 20 ? 20 : rc->out_backoff * 2;
if (rc->out_backoff > 5000) rc->out_backoff = 5000;
rc->out_timer = uasync_set_timeout(rc->ua, rc->out_backoff, rc, rp_sock_retry_cb, "rp_send");
rc->out_timer = uasync_set_timeout(rc->ua, rc->out_backoff, rc, tcp_proxy_server_sock_retry_cb, "tcp_proxy_server_send");
} else {
u_free(rc->out_buf); rc->out_buf = NULL; rc->out_len = rc->out_off = 0;
rc->out_timer = NULL; rc->out_backoff = 0;
}
}
static void rp_sock_send(struct remote_proxy_conn* rc, const uint8_t* data, size_t len) {
static void tcp_proxy_server_sock_send(struct tcp_proxy_server_conn* rc, const uint8_t* data, size_t len) {
if (!rc || !data || len == 0) return;
if (rc->out_buf) {
size_t new_len = rc->out_len + len;
@ -156,87 +150,77 @@ static void rp_sock_send(struct remote_proxy_conn* rc, const uint8_t* data, size
rc->out_buf = u_malloc(len);
if (!rc->out_buf) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:SEND malloc(%zu) failed sid=%08x", len, rc->stream_id); return; }
memcpy(rc->out_buf, data, len); rc->out_len = len; rc->out_off = 0;
int ret = rp_sock_try_send(rc);
int ret = tcp_proxy_server_sock_try_send(rc);
if (ret == 0) return;
if (ret == 1) {
rc->out_backoff = 10;
rc->out_timer = uasync_set_timeout(rc->ua, rc->out_backoff, rc, rp_sock_retry_cb, "rp_send");
rc->out_timer = uasync_set_timeout(rc->ua, rc->out_backoff, rc, tcp_proxy_server_sock_retry_cb, "tcp_proxy_server_send");
} else {
u_free(rc->out_buf); rc->out_buf = NULL; rc->out_len = rc->out_off = 0;
}
}
static void rp_sock_pause_cb(void* arg) {
struct remote_proxy_conn* rc = (struct remote_proxy_conn*)arg;
if (!rc || rc->sock == SOCKET_INVALID) { rc->pause_timer = NULL; return; }
static void tcp_proxy_server_pause_waiter_cb(struct ll_queue* q, void* arg) {
(void)q;
struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
if (!rc || rc->sock == SOCKET_INVALID) return;
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
int qcnt = -1;
if (inst) qcnt = etcp_router_input_q_count(inst, rc->peer_node_id);
if (qcnt > RP_NORMALIZER_Q_THRESHOLD) {
rc->pause_backoff = rc->pause_backoff * 2;
if (rc->pause_backoff > 5000) rc->pause_backoff = 5000;
rc->pause_timer = uasync_set_timeout(rc->ua, rc->pause_backoff, rc, rp_sock_pause_cb, "rp_pause");
return;
}
if (!inst) return;
if (rc->pause_buf && rc->pause_len > 0) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:PAUSE_FLUSH fd=%d sid=%08x len=%zu qcnt=%d", (int)rc->sock, rc->stream_id, rc->pause_len, qcnt);
if (rp_send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_DATA, rc->stream_id, rc->pause_buf, rc->pause_len) < 0) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:PAUSE_FLUSH fd=%d sid=%08x len=%zu", (int)rc->sock, rc->stream_id, rc->pause_len);
if (tcp_proxy_server_send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_DATA, rc->stream_id, rc->pause_buf, rc->pause_len) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:PAUSE_FLUSH send failed — closing sid=%08x", rc->stream_id);
rc->error = 1; u_free(rc->pause_buf); rc->pause_buf = NULL; rc->pause_len = 0; rp_conn_free(rc); return;
rc->error = 1; u_free(rc->pause_buf); rc->pause_buf = NULL; rc->pause_len = 0; tcp_proxy_server_conn_free(rc); return;
}
u_free(rc->pause_buf); rc->pause_buf = NULL; rc->pause_len = 0;
}
rc->read_id = uasync_add_socket_t(rc->ua, rc->sock, rp_sock_read_cb, rp_sock_write_cb, rp_sock_error_cb, rc);
rc->pause_timer = NULL;
rc->read_id = uasync_add_socket_t(rc->ua, rc->sock, tcp_proxy_server_sock_read_cb, tcp_proxy_server_sock_write_cb, tcp_proxy_server_sock_error_cb, rc);
if (rc->sock_closed) tcp_proxy_server_send_close(rc);
}
// ====================================================================
// Socket callbacks
// ====================================================================
static void rp_sock_read_cb(socket_t sock, void* arg) {
(void)sock; struct remote_proxy_conn* rc = (struct remote_proxy_conn*)arg;
static void tcp_proxy_server_sock_read_cb(socket_t sock, void* arg) {
(void)sock; struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
if (!rc || rc->sock == SOCKET_INVALID) return;
uint8_t buf[8192]; ssize_t n = recv(rc->sock, buf, sizeof(buf), 0);
if (n > 0) {
if (rc->cli_closed || rc->error) return;
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:RECV fd=%d sid=%08x — inst is NULL, drop", (int)rc->sock, rc->stream_id); return; }
int qcnt = etcp_router_input_q_count(inst, rc->peer_node_id);
if (qcnt > RP_NORMALIZER_Q_THRESHOLD) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:PAUSE fd=%d sid=%08x len=%zd qcnt=%d — normalizer переполнен, пауза", (int)rc->sock, rc->stream_id, n, qcnt);
if (rc->pause_buf) return;
int ret = tcp_proxy_server_send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_DATA, rc->stream_id, buf, (size_t)n);
if (ret == 0) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:RECV fd=%d sid=%08x len=%zd total=%d", (int)rc->sock, rc->stream_id, n, tcp_proxy_server_conn_total(rc));
} else {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:PAUSE fd=%d sid=%08x len=%zd — backpressure, пауза", (int)rc->sock, rc->stream_id, n);
rc->pause_buf = u_malloc(n);
if (rc->pause_buf) { memcpy(rc->pause_buf, buf, n); rc->pause_len = n; }
else DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:PAUSE malloc(%zd) failed sid=%08x", n, rc->stream_id);
if (rc->read_id) { uasync_remove_socket_t(rc->ua, rc->sock); rc->read_id = NULL; }
rc->pause_backoff = 50;
rc->pause_timer = uasync_set_timeout(rc->ua, rc->pause_backoff, rc, rp_sock_pause_cb, "rp_pause");
return;
}
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:RECV fd=%d sid=%08x len=%zd total=%d", (int)rc->sock, rc->stream_id, n, rp_conn_total(rc));
if (rp_send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_DATA, rc->stream_id, buf, (size_t)n) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:RECV send failed — closing sid=%08x", rc->stream_id);
rc->error = 1; rp_conn_free(rc); return;
etcp_router_waiter_register(inst, rc->peer_node_id, &rc->pause_waiter);
}
} else if (n == 0) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:EOF fd=%d sid=%08x total=%d cli_closed=%d sock_closed=%d",
(int)rc->sock, rc->stream_id, rp_conn_total(rc), rc->cli_closed, rc->sock_closed);
(int)rc->sock, rc->stream_id, tcp_proxy_server_conn_total(rc), rc->cli_closed, rc->sock_closed);
rc->sock_closed = 1;
rp_send_close(rc);
if (!rc->pause_buf && !rc->out_buf) tcp_proxy_server_send_close(rc);
if (rc->read_id) { uasync_remove_socket_t(rc->ua, rc->sock); rc->read_id = NULL; }
if (rc->cli_closed) rp_conn_free(rc);
if (rc->cli_closed && !rc->out_buf && !rc->pause_buf) tcp_proxy_server_conn_free(rc);
} else if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:ERROR fd=%d sid=%08x errno=%d %s total=%d",
(int)rc->sock, rc->stream_id, errno, strerror(errno), rp_conn_total(rc));
(int)rc->sock, rc->stream_id, errno, strerror(errno), tcp_proxy_server_conn_total(rc));
rc->error = 1;
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (inst) rp_send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_ERROR, rc->stream_id, NULL, 0);
if (inst) tcp_proxy_server_send_msg(inst, rc->peer_node_id, TCP_PROXY_SUBCMD_ERROR, rc->stream_id, NULL, 0);
else DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:ERROR fd=%d sid=%08x — inst is NULL, can't send ERROR", (int)rc->sock, rc->stream_id);
rp_conn_free(rc);
tcp_proxy_server_conn_free(rc);
}
}
static void rp_sock_write_cb(socket_t sock, void* arg) {
(void)sock; struct remote_proxy_conn* rc = (struct remote_proxy_conn*)arg;
static void tcp_proxy_server_sock_write_cb(socket_t sock, void* arg) {
(void)sock; struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
if (!rc || rc->sock == SOCKET_INVALID || rc->connected) return;
int err = 0; socklen_t len = sizeof(err);
if (getsockopt(rc->sock, SOL_SOCKET, SO_ERROR, &err, &len) == 0 && err == 0) {
@ -247,39 +231,36 @@ static void rp_sock_write_cb(socket_t sock, void* arg) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:CONN fd=%d sid=%08x local=%d dest=%d.%d.%d.%d:%d total=%d",
(int)rc->sock, rc->stream_id, ntohs(local_port),
rc->dest_ip[0], rc->dest_ip[1], rc->dest_ip[2], rc->dest_ip[3], ntohs(rc->dest_port),
rp_conn_total(rc));
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (inst) rp_send_connected(inst, rc->peer_node_id, rc->stream_id, local_port, TCP_PROXY_CONNECTED_OK);
tcp_proxy_server_conn_total(rc));
if (rc->pending_buf && rc->pending_len > 0) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FLUSH fd=%d sid=%08x len=%zu", (int)rc->sock, rc->stream_id, rc->pending_len);
rp_sock_send(rc, rc->pending_buf, rc->pending_len);
tcp_proxy_server_sock_send(rc, rc->pending_buf, rc->pending_len);
u_free(rc->pending_buf); rc->pending_buf = NULL; rc->pending_len = 0;
}
} else {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:FAIL fd=%d sid=%08x err=%d %s",
(int)rc->sock, rc->stream_id, err, err ? strerror(err) : "unknown");
struct UTUN_INSTANCE* inst = rc->ctx ? rc->ctx->inst : NULL;
if (inst) rp_send_connected(inst, rc->peer_node_id, rc->stream_id, 0, TCP_PROXY_CONNECTED_REFUSED);
rp_conn_free(rc);
tcp_proxy_server_send_error(rc);
tcp_proxy_server_conn_free(rc);
}
}
static void rp_sock_error_cb(socket_t sock, void* arg) {
(void)sock; struct remote_proxy_conn* rc = (struct remote_proxy_conn*)arg;
static void tcp_proxy_server_sock_error_cb(socket_t sock, void* arg) {
(void)sock; struct tcp_proxy_server_conn* rc = (struct tcp_proxy_server_conn*)arg;
if (!rc || rc->sock == SOCKET_INVALID) return;
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "SOCK:ERR fd=%d sid=%08x", (int)rc->sock, rc->stream_id);
rc->error = 1;
rp_send_error(rc);
rp_conn_free(rc);
tcp_proxy_server_send_error(rc);
tcp_proxy_server_conn_free(rc);
}
void rp_conn_free(struct remote_proxy_conn* rc) {
void tcp_proxy_server_conn_free(struct tcp_proxy_server_conn* rc) {
if (!rc) return;
int total = rp_conn_total(rc);
int total = tcp_proxy_server_conn_total(rc);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:FREE fd=%d sid=%08x total=%d cli_closed=%d sock_closed=%d error=%d",
(int)rc->sock, rc->stream_id, total, rc->cli_closed, rc->sock_closed, rc->error);
if (rc->ctx) {
struct remote_proxy_conn** prev = &rc->ctx->conns;
struct tcp_proxy_server_conn** prev = &rc->ctx->conns;
while (*prev) { if (*prev == rc) { *prev = rc->next; rc->ctx->conn_count--; break; } prev = &(*prev)->next; }
}
if (rc->sock != SOCKET_INVALID) {
@ -289,51 +270,52 @@ void rp_conn_free(struct remote_proxy_conn* rc) {
if (rc->pending_buf) { u_free(rc->pending_buf); rc->pending_buf = NULL; }
if (rc->out_timer) { uasync_cancel_timeout(rc->ua, rc->out_timer); rc->out_timer = NULL; }
if (rc->out_buf) { u_free(rc->out_buf); rc->out_buf = NULL; }
if (rc->pause_timer) { uasync_cancel_timeout(rc->ua, rc->pause_timer); rc->pause_timer = NULL; }
if (rc->pause_buf) { u_free(rc->pause_buf); rc->pause_buf = NULL; }
if (rc->pause_buf) { u_free(rc->pause_buf); rc->pause_buf = NULL; rc->pause_len = 0; }
if (rc->close_timer) { uasync_cancel_timeout(rc->ua, rc->close_timer); rc->close_timer = NULL; }
if (rc->ctx && rc->ctx->inst) etcp_router_waiter_cancel(rc->ctx->inst, rc->peer_node_id, &rc->pause_waiter);
u_free(rc);
}
// ====================================================================
// Public API
// ====================================================================
struct remote_proxy_conn* remote_proxy_find_conn(struct remote_proxy_ctx* ctx, uint32_t stream_id) {
struct remote_proxy_conn* c;
struct tcp_proxy_server_conn* tcp_proxy_server_find_conn(struct tcp_proxy_server* ctx, uint32_t stream_id) {
struct tcp_proxy_server_conn* c;
for (c = ctx->conns; c; c = c->next) if (c->stream_id == stream_id) return c;
return NULL;
}
int remote_proxy_handle_connect(struct UTUN_INSTANCE* inst, struct ll_entry* entry,
int tcp_proxy_server_handle_connect(struct UTUN_INSTANCE* inst, struct ll_entry* entry,
uint32_t stream_id, uint64_t src_node_id) {
if (!inst || !inst->remote_proxy.enabled) { if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } return -1; }
struct remote_proxy_ctx* ctx = &inst->remote_proxy;
if (entry->len < TCP_PROXY_CONNECT_HDR_SIZE) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "remote_proxy: CONNECT too short len=%u", entry->len); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
if (!inst || !inst->tcp_proxy_server.enabled) { if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } return -1; }
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
if (entry->len < TCP_PROXY_CONNECT_HDR_SIZE) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: CONNECT too short len=%u", entry->len); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
uint8_t* dest_ip = entry->dgram + TCP_PROXY_HDR_SIZE;
uint16_t dest_port = 0; memcpy(&dest_port, dest_ip + 4, 2);
struct remote_proxy_conn* rc = u_calloc(1, sizeof(struct remote_proxy_conn));
if (!rc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "remote_proxy: u_calloc failed for sid=%08x", stream_id); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
struct tcp_proxy_server_conn* rc = u_calloc(1, sizeof(struct tcp_proxy_server_conn));
if (!rc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: u_calloc failed for sid=%08x", stream_id); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
rc->ctx = ctx; rc->stream_id = stream_id; rc->peer_node_id = src_node_id;
memcpy(rc->dest_ip, dest_ip, 4); rc->dest_port = dest_port;
rc->ua = inst->ua; rc->sock = SOCKET_INVALID;
queue_waiter_handle_init(&rc->pause_waiter, tcp_proxy_server_pause_waiter_cb, rc);
rc->sock = socket(AF_INET, SOCK_STREAM, 0);
if (rc->sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "remote_proxy: socket() failed"); u_free(rc); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
if (rc->sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: socket() failed"); u_free(rc); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
ctx->conn_count++;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:NEW fd=%d sid=%08x dest=%d.%d.%d.%d:%d total=%d",
(int)rc->sock, stream_id, dest_ip[0],dest_ip[1],dest_ip[2],dest_ip[3],ntohs(dest_port), ctx->conn_count);
socket_set_nonblocking(rc->sock);
rc->read_id = uasync_add_socket_t(rc->ua, rc->sock, rp_sock_read_cb, rp_sock_write_cb, rp_sock_error_cb, rc);
if (!rc->read_id) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "remote_proxy: uasync_add_socket_t failed"); u_free(rc); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
rc->read_id = uasync_add_socket_t(rc->ua, rc->sock, tcp_proxy_server_sock_read_cb, tcp_proxy_server_sock_write_cb, tcp_proxy_server_sock_error_cb, rc);
if (!rc->read_id) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: uasync_add_socket_t failed"); u_free(rc); queue_dgram_free(entry); queue_entry_free(entry); return -1; }
struct sockaddr_in addr; memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; memcpy(&addr.sin_addr.s_addr, dest_ip, 4); addr.sin_port = dest_port;
int ret = connect(rc->sock, (struct sockaddr*)&addr, sizeof(addr));
if (ret < 0 && errno != EINPROGRESS) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "remote_proxy: connect() to %d.%d.%d.%d:%d failed: %s",
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy server: connect() to %d.%d.%d.%d:%d failed: %s",
dest_ip[0], dest_ip[1], dest_ip[2], dest_ip[3], ntohs(dest_port), strerror(errno));
rp_send_connected(inst, src_node_id, stream_id, 0, TCP_PROXY_CONNECTED_REFUSED);
tcp_proxy_server_send_msg(inst, src_node_id, TCP_PROXY_SUBCMD_ERROR, stream_id, NULL, 0);
uasync_remove_socket_t(rc->ua, rc->sock); socket_close_wrapper(rc->sock); u_free(rc);
queue_dgram_free(entry); queue_entry_free(entry); return -1;
}
@ -343,81 +325,82 @@ int remote_proxy_handle_connect(struct UTUN_INSTANCE* inst, struct ll_entry* ent
return 0;
}
int remote_proxy_handle_data(struct UTUN_INSTANCE* inst, struct ETCP_CONN* conn, struct ll_entry* entry, uint32_t stream_id) {
int tcp_proxy_server_handle_data(struct UTUN_INSTANCE* inst, struct ETCP_CONN* conn, struct ll_entry* entry, uint32_t stream_id) {
if (!inst) { queue_dgram_free(entry); queue_entry_free(entry); return -1; }
struct remote_proxy_ctx* ctx = &inst->remote_proxy;
struct remote_proxy_conn* rc = remote_proxy_find_conn(ctx, stream_id);
if (!rc || rc->sock == SOCKET_INVALID) {
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "RP handle_data drop: нет соединения для sid=%08x, шлём CLOSE", stream_id);
if (conn) rp_send_msg(inst, conn->peer_node_id, TCP_PROXY_SUBCMD_CLOSE, stream_id, NULL, 0);
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(ctx, stream_id);
if (!rc || rc->sock == SOCKET_INVALID || rc->error) {
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TPS handle_data: нет/error conn для sid=%08x, шлём ERROR", stream_id);
if (conn) tcp_proxy_server_send_msg(inst, conn->peer_node_id, TCP_PROXY_SUBCMD_ERROR, stream_id, NULL, 0);
queue_dgram_free(entry); queue_entry_free(entry); return -1;
}
if (rc->error) { queue_dgram_free(entry); queue_entry_free(entry); return -1; }
size_t data_len = entry->len - TCP_PROXY_HDR_SIZE;
if (rc->connected) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:SEND fd=%d sid=%08x len=%zu total=%d",
(int)rc->sock, stream_id, data_len, rp_conn_total(rc));
if (data_len > 0) rp_sock_send(rc, entry->dgram + TCP_PROXY_HDR_SIZE, data_len);
(int)rc->sock, stream_id, data_len, tcp_proxy_server_conn_total(rc));
if (data_len > 0) tcp_proxy_server_sock_send(rc, entry->dgram + TCP_PROXY_HDR_SIZE, data_len);
} else {
size_t new_len = rc->pending_len + data_len;
uint8_t* new_buf = u_realloc(rc->pending_buf, new_len);
if (new_buf) {
if (data_len > 0) memcpy(new_buf + rc->pending_len, entry->dgram + TCP_PROXY_HDR_SIZE, data_len);
rc->pending_buf = new_buf; rc->pending_len = new_len;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "RP buffer sid=%08x added=%zu total=%zu", stream_id, data_len, new_len);
} else DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "RP buffer realloc(%zu) failed sid=%08x", new_len, stream_id);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TPS buffer sid=%08x added=%zu total=%zu", stream_id, data_len, new_len);
} else DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TPS buffer realloc(%zu) failed sid=%08x", new_len, stream_id);
}
queue_dgram_free(entry); queue_entry_free(entry);
return 0;
}
void remote_proxy_handle_close(struct UTUN_INSTANCE* inst, uint32_t stream_id) {
void tcp_proxy_server_handle_close(struct UTUN_INSTANCE* inst, uint32_t stream_id) {
if (!inst) return;
struct remote_proxy_ctx* ctx = &inst->remote_proxy;
struct remote_proxy_conn** prev = &ctx->conns;
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
struct tcp_proxy_server_conn** prev = &ctx->conns;
while (*prev) {
struct remote_proxy_conn* rc = *prev;
struct tcp_proxy_server_conn* rc = *prev;
if (rc->stream_id == stream_id) {
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSE_RECV fd=%d sid=%08x total=%d sock_closed=%d connected=%d",
(int)rc->sock, stream_id, rp_conn_total(rc), rc->sock_closed, rc->connected);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "SOCK:CLOSE_RECV fd=%d sid=%08x total=%d sock_closed=%d out=%zu",
(int)rc->sock, stream_id, tcp_proxy_server_conn_total(rc), rc->sock_closed,
rc->out_buf ? rc->out_len - rc->out_off : (size_t)0);
rc->cli_closed = 1;
if (rc->sock != SOCKET_INVALID && rc->connected == 1) shutdown(rc->sock, SHUT_WR);
if (rc->sock_closed) rp_conn_free(rc);
if (rc->sock != SOCKET_INVALID && rc->connected == 1 && !rc->out_buf) shutdown(rc->sock, SHUT_WR);
if (rc->sock_closed && !rc->out_buf && !rc->pause_buf) tcp_proxy_server_conn_free(rc);
return;
}
prev = &rc->next;
}
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "remote_proxy: CLOSE sid=%08x — no conn", stream_id);
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "TCP proxy server: CLOSE sid=%08x — no conn, шлём ERROR", stream_id);
tcp_proxy_server_send_msg(inst, inst->node_id, TCP_PROXY_SUBCMD_ERROR, stream_id, NULL, 0);
}
int remote_proxy_init(struct UTUN_INSTANCE* inst) {
int tcp_proxy_server_init(struct UTUN_INSTANCE* inst) {
if (!inst) return -1;
struct remote_proxy_ctx* ctx = &inst->remote_proxy;
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
memset(ctx, 0, sizeof(*ctx));
if (!inst->config) return 0;
ctx->enabled = inst->config->global.remote_proxy_enabled;
ctx->enabled = inst->config->global.tcp_proxy_server_enabled;
ctx->inst = inst;
if (!ctx->enabled) return 0;
g_rp_ctx = ctx;
etcp_router_bind(inst, ETCP_ID_TCP_PROXY, tcp_proxy_etcp_recv_cb);
if (!inst->config->global.tcp_proxy_enabled) {
g_tcp_proxy_server_ctx = ctx;
etcp_router_bind(inst, ETCP_ID_TCP_PROXY, tcp_proxy_client_etcp_recv_cb);
if (!inst->config->global.tcp_proxy_client_enabled) {
udp_proxy_init(inst, inst->ua);
icmp_proxy_init(inst, inst->ua);
}
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "remote_proxy initialized node=%016llx", (unsigned long long)inst->node_id);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy server initialized node=%016llx", (unsigned long long)inst->node_id);
return 0;
}
void remote_proxy_destroy(struct UTUN_INSTANCE* inst) {
void tcp_proxy_server_destroy(struct UTUN_INSTANCE* inst) {
if (!inst) return;
struct remote_proxy_ctx* ctx = &inst->remote_proxy;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "remote_proxy destroying: conn_count=%d", ctx->conn_count);
struct remote_proxy_conn* rc = ctx->conns;
while (rc) { struct remote_proxy_conn* next = rc->next; rp_conn_free(rc); rc = next; }
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy server destroying: conn_count=%d", ctx->conn_count);
struct tcp_proxy_server_conn* rc = ctx->conns;
while (rc) { struct tcp_proxy_server_conn* next = rc->next; tcp_proxy_server_conn_free(rc); rc = next; }
ctx->conns = NULL; ctx->enabled = 0;
if (g_rp_ctx == ctx) g_rp_ctx = NULL;
if (g_tcp_proxy_server_ctx == ctx) g_tcp_proxy_server_ctx = NULL;
udp_proxy_destroy(inst);
icmp_proxy_destroy(inst);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "remote_proxy destroyed");
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy server destroyed");
}

44
src/remote_proxy.h → src/proxy/tcp_proxy_server.h

@ -1,32 +1,27 @@
// remote_proxy.h — Удаленный TCP прокси (exit node)
#ifndef REMOTE_PROXY_H
#define REMOTE_PROXY_H
// tcp_proxy_server.h — TCP прокси-сервер (exit node)
#ifndef TCP_PROXY_SERVER_H
#define TCP_PROXY_SERVER_H
#include <stdint.h>
#include "../lib/socket_compat.h"
#include "../lib/ll_queue.h"
struct UTUN_INSTANCE;
struct UASYNC;
struct ll_entry;
struct ETCP_CONN;
struct remote_proxy_ctx;
#define TCP_PROXY_SUBCMD_CONNECT 0x01
#define TCP_PROXY_SUBCMD_CONNECTED 0x02
#define TCP_PROXY_SUBCMD_DATA 0x03
#define TCP_PROXY_SUBCMD_CLOSE 0x04
#define TCP_PROXY_SUBCMD_ERROR 0x05
#define TCP_PROXY_CONNECTED_OK 0
#define TCP_PROXY_CONNECTED_REFUSED 1
#define TCP_PROXY_HDR_SIZE 6 // svc_id(1)+subcmd(1)+stream_id(4)
#define TCP_PROXY_CONNECT_HDR_SIZE 12 // HDR_SIZE + dest_ip(4)+dest_port(2)
#define TCP_PROXY_CONNECTED_HDR_SIZE 9 // HDR_SIZE + local_port(2)+status(1)
struct remote_proxy_conn {
struct remote_proxy_conn* next;
struct remote_proxy_ctx* ctx;
struct tcp_proxy_server_conn {
struct tcp_proxy_server_conn* next;
struct tcp_proxy_server* ctx;
uint32_t stream_id;
uint64_t peer_node_id;
socket_t sock;
@ -53,28 +48,27 @@ struct remote_proxy_conn {
void* close_timer; // таймер повтора CLOSE/ERROR
int close_backoff; // backoff: 50..5000 tb (5ms..500ms)
uint8_t* pause_buf; // буфер при паузе чтения (normalizer переполнен)
uint8_t* pause_buf; // буфер при backpressure (ETCP send fail)
size_t pause_len;
void* pause_timer; // таймер паузы чтения
int pause_backoff; // backoff: 50..5000 tb (5ms..500ms)
struct queue_waiter_handle pause_waiter;
};
struct remote_proxy_ctx {
struct tcp_proxy_server {
int enabled;
int conn_count;
struct remote_proxy_conn* conns;
struct tcp_proxy_server_conn* conns;
struct UTUN_INSTANCE* inst;
};
int remote_proxy_init(struct UTUN_INSTANCE* inst);
void remote_proxy_destroy(struct UTUN_INSTANCE* inst);
int tcp_proxy_server_init(struct UTUN_INSTANCE* inst);
void tcp_proxy_server_destroy(struct UTUN_INSTANCE* inst);
struct remote_proxy_conn* remote_proxy_find_conn(struct remote_proxy_ctx* ctx, uint32_t stream_id);
void rp_conn_free(struct remote_proxy_conn* rc);
struct tcp_proxy_server_conn* tcp_proxy_server_find_conn(struct tcp_proxy_server* ctx, uint32_t stream_id);
void tcp_proxy_server_conn_free(struct tcp_proxy_server_conn* rc);
int remote_proxy_handle_connect(struct UTUN_INSTANCE* inst, struct ll_entry* entry,
uint32_t stream_id, uint64_t src_node_id);
int remote_proxy_handle_data(struct UTUN_INSTANCE* inst, struct ETCP_CONN* conn, struct ll_entry* entry, uint32_t stream_id);
void remote_proxy_handle_close(struct UTUN_INSTANCE* inst, uint32_t stream_id);
int tcp_proxy_server_handle_connect(struct UTUN_INSTANCE* inst, struct ll_entry* entry,
uint32_t stream_id, uint64_t src_node_id);
int tcp_proxy_server_handle_data(struct UTUN_INSTANCE* inst, struct ETCP_CONN* conn, struct ll_entry* entry, uint32_t stream_id);
void tcp_proxy_server_handle_close(struct UTUN_INSTANCE* inst, uint32_t stream_id);
#endif

6
src/udp_proxy.c → src/proxy/udp_proxy.c

@ -179,7 +179,7 @@ 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;
if (!inst || !inst->tcp_proxy_client || !inst->tcp_proxy_client->tun) return -1;
// Собрать IP/UDP ответный пакет
size_t ip_len = 20 + 8 + payload_len;
@ -208,7 +208,7 @@ int udp_proxy_deliver_reply(struct UTUN_INSTANCE* inst,
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->input_queue, e);
queue_data_put(inst->tcp_proxy_client->tun->input_queue, e);
return 0;
}
@ -242,7 +242,7 @@ int udp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua) {
ctx->inst = inst; ctx->ua = ua;
ctx->flow_timeout_tb = UDP_FLOW_TIMEOUT_TB;
ctx->expire_timer = NULL;
ctx->is_exit = inst->remote_proxy.enabled;
ctx->is_exit = inst->tcp_proxy_server.enabled;
g_udp_ctx = ctx;
etcp_router_bind(inst, ETCP_ID_UDP_PROXY, udp_proxy_recv_cb);

0
src/udp_proxy.h → src/proxy/udp_proxy.h

52
src/utun_instance.c

@ -145,29 +145,29 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
return -1;
}
// Remote proxy (exit node, optional) — must be before tcp_proxy so
// tcp_proxy_create can overwrite the handler if it has remote mappings
if (remote_proxy_init(instance) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "Failed to initialize remote_proxy (non-fatal)");
}
// TCP proxy (from [tcp_proxy] config section)
if (config->global.tcp_proxy_enabled) {
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, g_tun_init_enabled ? 0 : 1,
config->global.tcp_proxy_mappings, config->global.tcp_proxy_mapping_count,
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);
// TCP proxy server (exit node, optional) — must be before tcp_proxy_client so
// tcp_proxy_client_create can overwrite the handler if it has remote mappings
if (tcp_proxy_server_init(instance) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "Failed to initialize tcp_proxy_server (non-fatal)");
}
// TCP proxy client (from [tcp_proxy] config section)
if (config->global.tcp_proxy_client_enabled) {
const char* tun_name = config->global.tcp_proxy_client_tun_name[0] ? config->global.tcp_proxy_client_tun_name : "tun_tcp";
const char* tun_ip = config->global.tcp_proxy_client_tun_ip[0] ? config->global.tcp_proxy_client_tun_ip : "10.99.0.1";
int mtu = config->global.tcp_proxy_client_mtu > 0 ? config->global.tcp_proxy_client_mtu : 1500;
instance->tcp_proxy_client = tcp_proxy_client_create(instance, ua, tun_name, tun_ip, mtu, g_tun_init_enabled ? 0 : 1,
config->global.tcp_proxy_client_mappings, config->global.tcp_proxy_client_mapping_count,
config->global.tcp_proxy_client_via_node_id);
if (instance->tcp_proxy_client) {
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TCP proxy client enabled: TUN=%s IP=%s MTU=%d mappings=%d",
tun_name, tun_ip, mtu, config->global.tcp_proxy_client_mapping_count);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create TCP proxy");
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create TCP proxy client");
return -1;
}
} else {
instance->tcp_proxy = NULL;
instance->tcp_proxy_client = NULL;
}
return 0;
@ -339,15 +339,15 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
instance->tun = NULL;
}
// Cleanup TCP proxy module
if (instance->tcp_proxy) {
DEBUG_INFO(DEBUG_CATEGORY_TUN, "Destroying TCP proxy module");
tcp_proxy_destroy(instance->tcp_proxy);
instance->tcp_proxy = NULL;
// Cleanup TCP proxy client module
if (instance->tcp_proxy_client) {
DEBUG_INFO(DEBUG_CATEGORY_TUN, "Destroying TCP proxy client module");
tcp_proxy_client_destroy(instance->tcp_proxy_client);
instance->tcp_proxy_client = NULL;
}
// Cleanup remote proxy
remote_proxy_destroy(instance);
// Cleanup TCP proxy server
tcp_proxy_server_destroy(instance);
// Cleanup routing module (unbinds from etcp_router before etcp_router_destroy)
routing_destroy(instance);

12
src/utun_instance.h

@ -11,9 +11,9 @@
#include "firewall.h"
#include "eim_nat.h"
#include "nat_transport.h"
#include "tcp_proxy.h"
#include "proxy/tcp_proxy_client.h"
#include "etcp_router.h"
#include "remote_proxy.h"
#include "proxy/tcp_proxy_server.h"
// Forward declarations
struct utun_config;
@ -97,15 +97,15 @@ struct UTUN_INSTANCE {
// Socket initialization status: 0=OK, 1=partial (some sockets failed), -1=error (none created)
int socket_init_status;
// TCP proxy (optional, NULL if not enabled)
struct tcp_proxy* tcp_proxy;
// TCP proxy client (optional, NULL if not enabled)
struct tcp_proxy_client* tcp_proxy_client;
// etcp_router bindings и seq-connections (per-instance service routing)
struct ETCP_ROUTER_BINDINGS router_bindings;
struct ll_queue* router_conns;
// Remote proxy (exit node)
struct remote_proxy_ctx remote_proxy;
// TCP proxy server (exit node)
struct tcp_proxy_server tcp_proxy_server;
};
// Functions

22
tests/Makefile.am

@ -32,11 +32,11 @@ check_PROGRAMS = \
test_nat_engine \
test_nat_transport \
test_nat_stress \
test_tcp_proxy \
test_tcp_proxy_client \
test_lwip_tcp \
test_etcp_router \
test_etcp_router_unit \
test_remote_proxy \
test_tcp_proxy_server \
test_udp_proxy \
test_icmp_proxy \
test_radix \
@ -46,7 +46,7 @@ check_PROGRAMS = \
# Долгие тесты: запускаются только вручную, не включаются в make check
# test_etcp_congestion — DISABLED: старый congestion control удалён, ждёт новых BBR тестов
# test_tcp_proxy_remote — 2-node TCP через etcp, требует fix g_tcp_proxy singleton
# test_tcp_proxy_remote — 2-node TCP через etcp, требует fix tcp_proxy_client singleton
noinst_PROGRAMS =
# test_crypto and test_ecc_encrypt only needed for TinyCrypt (not when using OpenSSL)
@ -119,11 +119,11 @@ ETCP_FULL_OBJS = \
$(top_builddir)/src/utun-control_server.o \
$(TUN_PLATFORM_OBJ) \
$(top_builddir)/src/utun-utun_instance.o \
$(top_builddir)/src/utun-tcp_proxy.o \
$(top_builddir)/src/proxy/utun-tcp_proxy_client.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/proxy/utun-tcp_proxy_server.o \
$(top_builddir)/src/proxy/utun-udp_proxy.o \
$(top_builddir)/src/proxy/utun-icmp_proxy.o \
$(top_builddir)/src/lwip_tcp/utun-lwip_pbuf.o \
$(top_builddir)/src/lwip_tcp/utun-lwip_tcp.o \
$(top_builddir)/src/lwip_tcp/utun-lwip_tcp_in.o \
@ -204,8 +204,8 @@ test_ipv6_sockets_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS
#test_etcp_reinit_inflight_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
#test_etcp_reinit_inflight_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_tcp_proxy_SOURCES = test_tcp_proxy.c
test_tcp_proxy_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_tcp_proxy_client_SOURCES = test_tcp_proxy_client.c
test_tcp_proxy_client_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_lwip_tcp_SOURCES = test_lwip_tcp.c
test_lwip_tcp_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
@ -222,8 +222,8 @@ test_etcp_router_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS)
test_etcp_router_unit_SOURCES = test_etcp_router_unit.c
test_etcp_router_unit_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_remote_proxy_SOURCES = test_remote_proxy.c
test_remote_proxy_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_tcp_proxy_server_SOURCES = test_tcp_proxy_server.c
test_tcp_proxy_server_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)

2
tests/tcp_proxy_full/client.conf

@ -15,7 +15,7 @@ keepalive=1
link=s1:127.0.0.1:15001
peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9
[tcp_proxy]
[tcp_proxy_client]
enabled=yes
tun_name=tun_test_proxy
tun_ip=10.200.30.1

2
tests/tcp_proxy_full/exit.conf

@ -13,7 +13,7 @@ type=public
[allowed_keys]
allow_all=1
[remote_proxy]
[tcp_proxy_server]
enabled=yes
[debug]

10
tests/test_icmp_proxy.c

@ -1,5 +1,5 @@
// test_icmp_proxy.c — 2-node ICMP ping test via etcp_router
// Node1 (client): tcp_proxy → sends ICMP_REQUEST to exit
// Node1 (client): tcp_proxy_client → sends ICMP_REQUEST to exit
// Node2 (exit): receives → raw socket → sends echo → receives reply → sends ICMP_REPLY
#include <stdio.h>
#include <stdlib.h>
@ -21,8 +21,8 @@
#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/proxy/icmp_proxy.h"
#include "../src/proxy/tcp_proxy_server.h"
#include "../src/config_parser.h"
#include "../src/utun_instance.h"
#include "../src/routing.h"
@ -48,7 +48,7 @@ static const char* cfg_node_client(void) {
"[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"
"[tcp_proxy_client]\n"
"enabled=yes\n"
"tun_name=tun_tcp\n"
"tun_ip=10.99.0.1\n"
@ -67,7 +67,7 @@ static const char* cfg_node_exit(void) {
"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");
"[tcp_proxy_server]\nenabled=yes\n");
return buf;
}

56
tests/test_intensive_memory_pool.c

@ -30,31 +30,28 @@ static double test_without_pools(int iterations) {
struct ll_queue* queue = queue_new(ua, 0, 0, 0,"q1"); // Без пулов
for (int cycle = 0; cycle < iterations; cycle++) {
// Создать много waiters
struct queue_waiter* waiters[32];
for (int i = 0; i < 32; i++) {
waiters[i] = queue_wait_threshold(queue, i, 0, intensive_waiter_callback, NULL);
}
// Добавить записи
// Добавить записи (чтобы waiters становились в очередь)
for (int i = 0; i < 10; i++) {
void* data = queue_entry_new(64);
queue_data_put(queue, data); // Используем ID = i
queue_data_put(queue, data);
}
// Удалить записи (триггер waiters)
// Создать много waiters (общий порог 0, count=10 => все в очередь)
struct queue_waiter_handle handles[32];
for (int i = 0; i < 32; i++) {
queue_waiter_handle_init(&handles[i], intensive_waiter_callback, NULL);
queue_waiter_wait(queue, &handles[i]);
}
// Удалить записи (триггер waiters — по одному за get)
for (int i = 0; i < 10; i++) {
void* retrieved = queue_data_get(queue);
if (retrieved) {
queue_entry_free(retrieved);
}
if (retrieved) queue_entry_free(retrieved);
}
// Отменить waiters
// Отменить оставшиеся waiters
for (int i = 0; i < 32; i++) {
if (waiters[i]) {
queue_cancel_wait(queue, waiters[i]);
}
queue_waiter_cancel(queue, &handles[i]);
}
}
@ -81,33 +78,28 @@ static double test_with_pools(int iterations) {
}
for (int cycle = 0; cycle < iterations; cycle++) {
// Создать много waiters
struct queue_waiter* waiters[32];
for (int i = 0; i < 32; i++) {
waiters[i] = queue_wait_threshold(queue, i, 0, intensive_waiter_callback, NULL);
}
// Добавить записи из пула
for (int i = 0; i < 10; i++) {
void* data = queue_entry_new_from_pool(pool);
if (data) {
queue_data_put(queue, data); // Используем ID = i
}
if (data) queue_data_put(queue, data);
}
// Создать много waiters
struct queue_waiter_handle handles[32];
for (int i = 0; i < 32; i++) {
queue_waiter_handle_init(&handles[i], intensive_waiter_callback, NULL);
queue_waiter_wait(queue, &handles[i]);
}
// Удалить записи (триггер waiters)
for (int i = 0; i < 10; i++) {
void* retrieved = queue_data_get(queue);
if (retrieved) {
queue_entry_free(retrieved); // Вернется в пул
}
if (retrieved) queue_entry_free(retrieved); // Вернется в пул
}
// Отменить waiters
// Отменить оставшиеся waiters
for (int i = 0; i < 32; i++) {
if (waiters[i]) {
queue_cancel_wait(queue, waiters[i]);
}
queue_waiter_cancel(queue, &handles[i]);
}
}

163
tests/test_ll_queue.c

@ -73,6 +73,15 @@ static void waiter_cb(struct ll_queue *q, void *arg) {
(*(int*)arg)++; stats.cb_waiter++;
}
static int waiter_order[10];
static int waiter_order_idx;
static void waiter_cb_order(struct ll_queue *q, void *arg) {
(void)q;
waiter_order[waiter_order_idx++] = *(int*)arg;
stats.cb_waiter++;
}
/* ------------------------------------------------------------------ */
static void test_basic(void) {
TEST("basic creation / free");
@ -165,10 +174,13 @@ static void test_waiter(void) {
TEST("wait_threshold + cancel");
struct UASYNC *ua = uasync_create();
struct ll_queue *q = queue_new(ua, 0, 0, 0, "q6");
queue_set_threshold(q, 2, 0);
int called = 0;
struct queue_waiter *w = queue_wait_threshold(q, 2, 0, waiter_cb, &called);
ASSERT(w == NULL && called == 1, "immediate when condition met"); // empty queue
struct queue_waiter_handle h;
queue_waiter_handle_init(&h, waiter_cb, &called);
int ret = queue_waiter_wait(q, &h);
ASSERT(ret == 1 && called == 1, "immediate when condition met"); // empty queue
for (int i = 0; i < 5; i++) {
test_data_t *d = (test_data_t*)queue_entry_new(sizeof(*d)); d->id = i;
@ -176,15 +188,151 @@ static void test_waiter(void) {
}
called = 0;
w = queue_wait_threshold(q, 2, 0, waiter_cb, &called);
ASSERT(w != NULL && called == 0, "");
ret = queue_waiter_wait(q, &h);
ASSERT(ret == 0 && called == 0, "");
for (int i = 0; i < 3; i++) queue_entry_free((struct ll_entry*)queue_data_get(q));
for (int i = 0; i < 15; i++) uasync_poll(ua, 1);
ASSERT_EQ(called, 1, "");
queue_cancel_wait(q, w);
queue_waiter_cancel(q, &h);
queue_free(q); uasync_destroy(ua, 0);
PASS();
}
static void test_waiter_multiple(void) {
TEST("waiter multiple FIFO (round-robin)");
struct UASYNC *ua = uasync_create();
struct ll_queue *q = queue_new(ua, 0, 0, 0, "wm");
queue_set_threshold(q, 2, 0);
for (int i = 0; i < 5; i++) {
test_data_t *d = (test_data_t*)queue_entry_new(sizeof(*d)); d->id = i;
queue_data_put(q, (struct ll_entry*)d);
}
waiter_order_idx = 0;
int ids[4] = {10, 20, 30, 40};
struct queue_waiter_handle handles[4];
for (int i = 0; i < 4; i++) {
queue_waiter_handle_init(&handles[i], waiter_cb_order, &ids[i]);
ASSERT(queue_waiter_wait(q, &handles[i]) == 0, "should queue");
}
for (int i = 0; i < 5; i++) queue_entry_free((struct ll_entry*)queue_data_get(q));
ASSERT_EQ(waiter_order_idx, 3, "3 waiters fired");
ASSERT(waiter_order[0] == 10 && waiter_order[1] == 20 && waiter_order[2] == 30, "FIFO order");
for (int i = 0; i < 4; i++) queue_waiter_cancel(q, &handles[i]);
queue_free(q); uasync_destroy(ua, 0);
PASS();
}
static void test_waiter_cancel(void) {
TEST("waiter cancel middle");
struct UASYNC *ua = uasync_create();
struct ll_queue *q = queue_new(ua, 0, 0, 0, "wc");
queue_set_threshold(q, 3, 0);
for (int i = 0; i < 10; i++) {
test_data_t *d = (test_data_t*)queue_entry_new(sizeof(*d)); d->id = i;
queue_data_put(q, (struct ll_entry*)d);
}
waiter_order_idx = 0;
int ids[3] = {1, 2, 3};
struct queue_waiter_handle handles[3];
for (int i = 0; i < 3; i++) {
queue_waiter_handle_init(&handles[i], waiter_cb_order, &ids[i]);
queue_waiter_wait(q, &handles[i]); // count=10 > 3, all queued
}
queue_waiter_cancel(q, &handles[1]); // cancel middle (ID 2)
for (int i = 0; i < 10; i++) queue_entry_free((struct ll_entry*)queue_data_get(q));
ASSERT_EQ(waiter_order_idx, 2, "2 fired (middle cancelled)");
ASSERT(waiter_order[0] == 1 && waiter_order[1] == 3, "skip cancelled");
for (int i = 0; i < 3; i++) queue_waiter_cancel(q, &handles[i]);
queue_free(q); uasync_destroy(ua, 0);
PASS();
}
static void test_waiter_queue_free(void) {
TEST("queue_free clears waiters");
struct UASYNC *ua = uasync_create();
struct ll_queue *q = queue_new(ua, 0, 0, 0, "wf");
for (int i = 0; i < 3; i++) {
test_data_t *d = (test_data_t*)queue_entry_new(sizeof(*d)); d->id = i;
queue_data_put(q, (struct ll_entry*)d);
}
int called = 0;
struct queue_waiter_handle h;
queue_waiter_handle_init(&h, waiter_cb, &called);
queue_waiter_wait(q, &h);
ASSERT(h.internal != NULL, "waiter is queued");
queue_free(q);
ASSERT(h.internal == NULL, "internal cleared by queue_free");
uasync_destroy(ua, 0);
PASS();
}
static void test_waiter_threshold(void) {
TEST("waiter dynamic threshold change");
struct UASYNC *ua = uasync_create();
struct ll_queue *q = queue_new(ua, 0, 0, 0, "wt");
queue_set_threshold(q, 0, 0); // default, but explicit
for (int i = 0; i < 10; i++) {
test_data_t *d = (test_data_t*)queue_entry_new(sizeof(*d)); d->id = i;
queue_data_put(q, (struct ll_entry*)d);
}
int called = 0;
struct queue_waiter_handle h;
queue_waiter_handle_init(&h, waiter_cb, &called);
int ret = queue_waiter_wait(q, &h);
ASSERT(ret == 0 && called == 0, "queued (10 > 0)");
queue_set_threshold(q, 10, 0); // raise threshold
queue_entry_free((struct ll_entry*)queue_data_get(q)); // count=9, check: 9 <= 10? YES
ASSERT_EQ(called, 1, "fired after threshold raised");
while (queue_entry_count(q)) queue_entry_free((struct ll_entry*)queue_data_get(q));
queue_free(q); uasync_destroy(ua, 0);
PASS();
}
static void test_waiter_reuse(void) {
TEST("waiter handle reuse");
struct UASYNC *ua = uasync_create();
struct ll_queue *q = queue_new(ua, 0, 0, 0, "wr");
int called = 0;
struct queue_waiter_handle h;
queue_waiter_handle_init(&h, waiter_cb, &called);
int ret = queue_waiter_wait(q, &h);
ASSERT(ret == 1 && called == 1, "immediate (empty queue)");
for (int i = 0; i < 3; i++) {
test_data_t *d = (test_data_t*)queue_entry_new(sizeof(*d)); d->id = i;
queue_data_put(q, (struct ll_entry*)d);
}
called = 0;
ret = queue_waiter_wait(q, &h);
ASSERT(ret == 0 && called == 0, "queued");
for (int i = 0; i < 3; i++) queue_entry_free((struct ll_entry*)queue_data_get(q));
ASSERT_EQ(called, 1, "fired again on same handle");
queue_waiter_cancel(q, &h);
ASSERT(h.internal == NULL, "internal cleared");
queue_free(q); uasync_destroy(ua, 0);
PASS();
}
@ -284,6 +432,11 @@ int main(void) {
test_lifo_priority();
test_callback();
test_waiter();
test_waiter_multiple();
test_waiter_cancel();
test_waiter_queue_free();
test_waiter_threshold();
test_waiter_reuse();
test_limits_hash();
test_pool();
test_stress();

36
tests/test_memory_pool_and_config.c

@ -16,11 +16,6 @@
static int test_callback_count = 0;
static void test_callback(struct ll_queue* q, void* data, void* arg) {
(void)q; (void)data; (void)arg;
test_callback_count++;
}
static void test_waiter_callback(struct ll_queue* q, void* arg) {
(void)q; (void)arg;
test_callback_count++;
@ -49,41 +44,32 @@ int main() {
uasync_destroy(ua, 0);
return 1;
}
queue_set_threshold(queue, 2, 0);
// Test multiple waiter allocations to trigger pool usage
struct queue_waiter* waiters[10];
// Test multiple waiters with new linked-list backpressure
struct queue_waiter_handle handles[10];
for (int i = 0; i < 10; i++) {
waiters[i] = queue_wait_threshold(queue, i * 2, 0, test_waiter_callback, NULL);
queue_waiter_handle_init(&handles[i], test_waiter_callback, NULL);
queue_waiter_wait(queue, &handles[i]); // queue empty => immediate callback
}
// Add some entries and trigger waiters
// Add some entries — waiters already fired, none queued
for (int i = 0; i < 5; i++) {
void* data = queue_entry_new(10);
queue_data_put(queue, data); // Используем ID = i
queue_data_put(queue, data);
}
// Remove entries to trigger waiter callbacks
// Remove entries
for (int i = 0; i < 5; i++) {
void* retrieved = queue_data_get(queue);
if (retrieved) {
queue_entry_free(retrieved);
}
if (retrieved) queue_entry_free(retrieved);
}
// Cancel remaining waiters
// Cancel (safe call even if not waiting)
for (int i = 0; i < 10; i++) {
if (waiters[i]) {
queue_cancel_wait(queue, waiters[i]);
}
queue_waiter_cancel(queue, &handles[i]);
}
// Get pool statistics
size_t allocations, reuse_count;
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "Pool statistics: allocations=%zu, reuse_count=%zu", allocations, reuse_count);
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "Pool efficiency: %.1f%%",
allocations > 0 ? (100.0 * reuse_count / allocations) : 0.0);
queue_free(queue);
uasync_destroy(ua, 0);

20
tests/test_tcp_proxy.c → tests/test_tcp_proxy_client.c

@ -1,9 +1,9 @@
// test_tcp_proxy.c — TCP proxy smoke test: create/destroy without leaks
// test_tcp_proxy_client.c — TCP proxy client smoke test: create/destroy without leaks
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "test_utils.h"
#include "../src/tcp_proxy.h"
#include "../src/proxy/tcp_proxy_client.h"
#include "../src/tun_if.h"
#include "../src/config_parser.h"
#include "../lib/u_async.h"
@ -24,12 +24,12 @@ int main(void) {
{
struct UASYNC* ua = uasync_create();
if (!ua) { printf("[FAIL] uasync_create\n"); return 1; }
struct tcp_proxy_mapping_config m = {.local_port = 9090, .remote_ip = "127.0.0.1", .remote_port = 9999};
struct tcp_proxy_client_mapping_config m = {.local_port = 9090, .remote_ip = "127.0.0.1", .remote_port = 9999};
struct tcp_proxy* p = tcp_proxy_create(NULL, ua, "tun_tcp", "10.99.0.1", 1500, 1, &m, 1, 0);
if (!p) { printf("[FAIL] tcp_proxy_create with mapping\n"); uasync_destroy(ua, 0); return 1; }
struct tcp_proxy_client* p = tcp_proxy_client_create(NULL, ua, "tun_tcp", "10.99.0.1", 1500, 1, &m, 1, 0);
if (!p) { printf("[FAIL] tcp_proxy_client_create with mapping\n"); uasync_destroy(ua, 0); return 1; }
tcp_proxy_destroy(p);
tcp_proxy_client_destroy(p);
uasync_destroy(ua, 0);
check_no_leaks("create/destroy with mapping");
}
@ -39,14 +39,14 @@ int main(void) {
struct UASYNC* ua = uasync_create();
if (!ua) { printf("[FAIL] uasync_create\n"); return 1; }
struct tcp_proxy* p = tcp_proxy_create(NULL, ua, "tun_tcp", "10.99.0.1", 1500, 1, NULL, 0, 0);
if (!p) { printf("[FAIL] tcp_proxy_create without mapping\n"); uasync_destroy(ua, 0); return 1; }
struct tcp_proxy_client* p = tcp_proxy_client_create(NULL, ua, "tun_tcp", "10.99.0.1", 1500, 1, NULL, 0, 0);
if (!p) { printf("[FAIL] tcp_proxy_client_create without mapping\n"); uasync_destroy(ua, 0); return 1; }
tcp_proxy_destroy(p);
tcp_proxy_client_destroy(p);
uasync_destroy(ua, 0);
check_no_leaks("create/destroy without mapping");
}
if (g_ok) printf("[PASS] test_tcp_proxy — create/destroy\n");
if (g_ok) printf("[PASS] test_tcp_proxy_client — create/destroy\n");
return g_ok ? 0 : 1;
}

22
tests/test_tcp_proxy_remote.c

@ -1,5 +1,5 @@
// 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
// Architecture: Client_A (active uIP) ↔ socketpair ↔ B (passive, etcp) ↔ Exit (tcp_proxy_server) ↔ echo
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -16,10 +16,10 @@
#include <errno.h>
#include <time.h>
#include "../src/tcp_proxy.h"
#include "../src/proxy/tcp_proxy_client.h"
#include "../src/etcp.h"
#include "../src/etcp_router.h"
#include "../src/remote_proxy.h"
#include "../src/proxy/tcp_proxy_server.h"
#include "../src/config_parser.h"
#include "../src/utun_instance.h"
#include "../src/uip/uip.h"
@ -36,7 +36,7 @@ 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 struct tcp_proxy_client* 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;
@ -75,12 +75,12 @@ 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, g_pair[1], 0xCCCC000000000001ULL);
struct tcp_proxy_client_mapping_config m = {.local_port=9090,.remote_ip="127.0.0.1",.remote_port=g_echo_port};
g_proxy_b = tcp_proxy_client_create(g_b, g_ua, NULL, NULL, 0, 0, &m, 1, 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, g_pair[0], 0);
g_cli = tcp_proxy_client_create(NULL, g_ua, NULL, NULL, 0, 0, NULL, 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);
@ -138,7 +138,7 @@ static const char* cfg_exit(int srv_port) { static char b[1024]; snprintf(b,size
"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; }
"[server:s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n[tcp_proxy_server]\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"
@ -147,7 +147,7 @@ static const char* cfg_b(int srv_port, int cli_port) { static char b[1024]; snpr
"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; }
"[tcp_proxy_server]\nenabled=yes\n", srv_port, cli_port); return b; }
int main(void) {
printf("[SKIP] Active connections API removed, test skipped\n");
@ -189,8 +189,8 @@ int main(void) {
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_cli) tcp_proxy_client_destroy(g_cli);
if (g_proxy_b) tcp_proxy_client_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);

74
tests/test_remote_proxy.c → tests/test_tcp_proxy_server.c

@ -1,4 +1,4 @@
// test_remote_proxy.c — Test remote_proxy: CONNECT → socket → echo → verify
// test_tcp_proxy_server.c — Test tcp_proxy_server: CONNECT → socket → echo → verify
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -15,7 +15,7 @@
#include "../src/etcp.h"
#include "../src/etcp_api.h"
#include "../src/etcp_router.h"
#include "../src/remote_proxy.h"
#include "../src/proxy/tcp_proxy_server.h"
#include "../src/config_parser.h"
#include "../src/utun_instance.h"
#include "../src/routing.h"
@ -37,7 +37,6 @@ static int g_ok = 0, g_done = 0;
static void* g_mon_id = NULL;
static pid_t echo_pid = 0;
static uint8_t send_buf[PAYLOAD_SIZE], recv_buf[PAYLOAD_SIZE];
static int connected_ok = 0;
static uint32_t stream_id = 1;
static const char* cfg =
@ -47,7 +46,7 @@ static const char* cfg =
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n"
"tun_ip=10.99.0.1/24\n"
"tun_ifname=tun99\n"
"[remote_proxy]\n"
"[tcp_proxy_server]\n"
"enabled=yes\n";
static void echo_server(void) {
@ -67,63 +66,46 @@ static void echo_server(void) {
done: close(cli); close(srv);
}
static void test_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
struct UTUN_INSTANCE* i = conn ? conn->instance : inst;
if (!i || !entry || entry->dgram == NULL || entry->len < TCP_PROXY_HDR_SIZE) {
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } return;
}
uint8_t subcmd = entry->dgram[1];
uint32_t sid = 0; memcpy(&sid, entry->dgram + 2, 4);
uint64_t src = conn ? conn->peer_node_id : i->node_id;
if (subcmd == TCP_PROXY_SUBCMD_CONNECT && sid == stream_id) { remote_proxy_handle_connect(i, entry, sid, src); return; }
if (sid != stream_id) { queue_dgram_free(entry); queue_entry_free(entry); return; }
if (subcmd == TCP_PROXY_SUBCMD_CONNECTED) {
uint8_t status = (entry->len >= TCP_PROXY_CONNECTED_HDR_SIZE) ? entry->dgram[TCP_PROXY_HDR_SIZE + 2] : 1;
connected_ok = (status == TCP_PROXY_CONNECTED_OK) ? 1 : -1;
}
queue_dgram_free(entry); queue_entry_free(entry);
}
static void monitor(void* arg) {
(void)arg;
if (g_done) { g_mon_id = NULL; return; }
static int phase = 0;
if (phase == 0) {
phase = 1;
etcp_router_bind(inst, ETCP_ID_TCP_PROXY, test_handler);
struct tcp_proxy_server* ctx = &inst->tcp_proxy_server;
if (!ctx->enabled) {
/* enable if config didn't */
ctx->enabled = 1; ctx->conn_count = 0;
etcp_router_bind(inst, ETCP_ID_TCP_PROXY, tcp_proxy_client_etcp_recv_cb);
}
uint32_t ip = inet_addr("127.0.0.1");
uint16_t port = htons(ECHO_PORT);
uint8_t payload[6]; memcpy(payload, &ip, 4); memcpy(payload + 4, &port, 2);
struct ll_entry* e = queue_entry_new(0);
if (e) {
e->dgram = u_malloc(TCP_PROXY_HDR_SIZE + 6);
e->dgram[0] = ETCP_ID_TCP_PROXY; e->dgram[1] = TCP_PROXY_SUBCMD_CONNECT;
memcpy(e->dgram + 2, &stream_id, 4);
memset(e->dgram + 6, 0, 2);
memcpy(e->dgram + TCP_PROXY_HDR_SIZE, payload, 6);
e->len = TCP_PROXY_HDR_SIZE + 6;
etcp_route_send(inst, inst->node_id, e);
}
e->dgram = u_malloc(TCP_PROXY_HDR_SIZE + 6);
e->dgram[0] = ETCP_ID_TCP_PROXY; e->dgram[1] = TCP_PROXY_SUBCMD_CONNECT;
memcpy(e->dgram + 2, &stream_id, 4);
memcpy(e->dgram + TCP_PROXY_HDR_SIZE, payload, 6);
e->len = TCP_PROXY_HDR_SIZE + 6;
tcp_proxy_server_handle_connect(inst, e, stream_id, inst->node_id);
}
if (phase == 1 && (connected_ok == 1 || connected_ok == -1)) {
if (connected_ok != 1) { printf("[FAIL] connect refused\n"); g_done = -1; return; }
// Get proxy conn, detach from uasync so we can use socket directly
struct remote_proxy_conn* rc = remote_proxy_find_conn(&inst->remote_proxy, stream_id);
if (!rc || rc->sock == SOCKET_INVALID) { printf("[FAIL] no proxy conn\n"); g_done = -1; return; }
uasync_remove_socket_t(rc->ua, rc->sock); rc->read_id = NULL;
// Send data through OS socket directly
for (int i = 0; i < PAYLOAD_SIZE; i++) send_buf[i] = (uint8_t)(rand() & 0xFF);
ssize_t n = send(rc->sock, send_buf, PAYLOAD_SIZE, MSG_NOSIGNAL);
if (n != PAYLOAD_SIZE) { printf("[FAIL] send %zd\n", n); g_done = -1; return; }
phase = 2;
if (phase == 1) {
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(&inst->tcp_proxy_server, stream_id);
if (rc && rc->sock != SOCKET_INVALID && rc->connected) {
uasync_remove_socket_t(rc->ua, rc->sock); rc->read_id = NULL;
for (int i = 0; i < PAYLOAD_SIZE; i++) send_buf[i] = (uint8_t)(rand() & 0xFF);
ssize_t n = send(rc->sock, send_buf, PAYLOAD_SIZE, MSG_NOSIGNAL);
if (n != PAYLOAD_SIZE) { printf("[FAIL] send %zd\n", n); g_done = -1; return; }
phase = 2;
}
}
if (phase == 2) {
struct remote_proxy_conn* rc = remote_proxy_find_conn(&inst->remote_proxy, stream_id);
struct tcp_proxy_server_conn* rc = tcp_proxy_server_find_conn(&inst->tcp_proxy_server, stream_id);
if (rc && rc->sock != SOCKET_INVALID) {
ssize_t n = recv(rc->sock, recv_buf, PAYLOAD_SIZE, 0);
if (n > 0) {
if ((size_t)n == PAYLOAD_SIZE && memcmp(send_buf, recv_buf, PAYLOAD_SIZE) == 0) {
printf("[PASS] test_remote_proxy — %zd bytes echoed\n", n); g_ok = 1;
printf("[PASS] test_tcp_proxy_server — %zd bytes echoed\n", n); g_ok = 1;
} else {
printf("[FAIL] echo mismatch: got %zd expected %d\n", n, PAYLOAD_SIZE);
}
@ -136,7 +118,7 @@ static void monitor(void* arg) {
static void timeout_cb(void* arg) {
(void)arg;
if (!g_done) { printf("[FAIL] timeout: conn=%d\n", connected_ok); g_done = -1; }
if (!g_done) { printf("[FAIL] timeout\n"); g_done = -1; }
if (g_mon_id) { uasync_cancel_timeout(ua, g_mon_id); g_mon_id = NULL; }
}
@ -152,7 +134,7 @@ int main(void) {
if (!f) { kill(echo_pid, SIGTERM); waitpid(echo_pid,NULL,0); test_rmdir(temp_dir); return 1; }
fprintf(f, "%s", cfg); fclose(f);
printf("=== test_remote_proxy ===\n");
printf("=== test_tcp_proxy_server ===\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));

12
tests/test_udp_proxy.c

@ -1,5 +1,5 @@
// test_udp_proxy.c — 2-node UDP echo test via etcp_router
// Node1 (client): tcp_proxy → sends UDP_REQUEST to exit
// Node1 (client): tcp_proxy_client → sends UDP_REQUEST to exit
// Node2 (exit): receives → creates UDP socket → echoes → sends UDP_REPLY
#include <stdio.h>
#include <stdlib.h>
@ -19,8 +19,8 @@
#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/proxy/udp_proxy.h"
#include "../src/proxy/tcp_proxy_server.h"
#include "../src/config_parser.h"
#include "../src/utun_instance.h"
#include "../src/routing.h"
@ -47,7 +47,7 @@ static const char* cfg_node_client(void) {
"[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"
"[tcp_proxy_client]\n"
"enabled=yes\n"
"tun_name=tun_tcp\n"
"tun_ip=10.99.0.1\n"
@ -66,7 +66,7 @@ static const char* cfg_node_exit(void) {
"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");
"[tcp_proxy_server]\nenabled=yes\n");
return buf;
}
@ -126,7 +126,7 @@ static void monitor(void* arg) {
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)
// Bind client handler for UDP replies (overrides what tcp_proxy_client_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);

65
tools/etcpmon/etcpmon_client.c

@ -602,6 +602,31 @@ static void handle_response(struct etcpmon_client* client, uint8_t msg_type,
}
break;
}
case ETCPMON_RSP_DEBUG_CONFIG: {
if (payload_size >= 2) {
client->debug_global_level = payload[0];
client->debug_category_count = payload[1];
uint16_t names_len = (uint16_t)strlen((const char*)(payload + 2)) + 1;
if (2 + names_len <= payload_size) {
size_t copy_len = names_len < sizeof(client->debug_category_names) ? names_len : sizeof(client->debug_category_names) - 1;
memcpy(client->debug_category_names, payload + 2, copy_len);
client->debug_category_names[copy_len] = '\0';
uint16_t levels_off = 2 + names_len;
uint8_t lvl_count = client->debug_category_count;
if (lvl_count > ETCPMON_MAX_DEBUG_CATEGORIES) lvl_count = ETCPMON_MAX_DEBUG_CATEGORIES;
for (uint8_t i = 0; i < lvl_count && levels_off + i < payload_size; i++)
client->debug_category_levels[i] = payload[levels_off + i];
client->debug_config_dirty = 1;
if (client->log_file) {
fprintf(client->log_file, "%llu: [LOG] RSP_DEBUG_CONFIG global=%d categories=%d\n",
(unsigned long long)get_timestamp_ms(), client->debug_global_level, client->debug_category_count);
fflush(client->log_file);
}
}
}
break;
}
}
}
@ -666,7 +691,7 @@ static void handle_response_with_callback(struct etcpmon_client* client, uint8_t
/* === НОВОЕ: ВСЕГДА вызываем legacy обработчики (on_conn_list / on_metrics / on_error) ===
* Это позволяет использовать и старый код, и новый асинхронный send_request одновременно */
if (seq_id == 0 || msg_type == ETCPMON_RSP_CONN_LIST ||
msg_type == ETCPMON_RSP_SOCKET_LIST ||
msg_type == ETCPMON_RSP_SOCKET_LIST || msg_type == ETCPMON_RSP_DEBUG_CONFIG ||
msg_type == ETCPMON_RSP_METRICS || msg_type == ETCPMON_RSP_ERROR) {
handle_response(client, msg_type, payload, payload_size);
}
@ -859,6 +884,44 @@ struct metrics_history* etcpmon_client_get_history(struct etcpmon_client* client
return &client->history;
}
static void debug_config_auto_callback(void* arg, uint8_t msg_type, const uint8_t* payload, uint16_t payload_size) {
(void)arg; (void)msg_type; (void)payload; (void)payload_size;
}
int etcpmon_client_request_debug_config(struct etcpmon_client* client) {
if (!client || client->sock == INVALID_SOCKET) return -1;
return etcpmon_client_send_request(client, ETCPMON_CMD_GET_DEBUG_CONFIG, NULL, 0,
debug_config_auto_callback, NULL);
}
int etcpmon_client_send_debug_config(struct etcpmon_client* client,
uint8_t global_level, uint8_t count, const uint8_t* levels) {
if (!client || client->sock == INVALID_SOCKET || !levels || count == 0 || count > ETCPMON_MAX_DEBUG_CATEGORIES) return -1;
uint8_t payload[2 + ETCPMON_MAX_DEBUG_CATEGORIES];
payload[0] = global_level;
payload[1] = count;
memcpy(payload + 2, levels, count);
uint16_t payload_size = 2 + count;
struct etcpmon_msg_header hdr;
etcpmon_build_header(&hdr, payload_size, ETCPMON_CMD_SET_DEBUG_CONFIG, 0);
if (client->log_file) {
fprintf(client->log_file, "%llu: [LOG] CMD_SET_DEBUG_CONFIG global=%d count=%d\n",
(unsigned long long)get_timestamp_ms(), global_level, count);
fflush(client->log_file);
}
int sent = send(client->sock, (const char*)&hdr, sizeof(hdr), 0);
if (sent != sizeof(hdr)) return -1;
sent = send(client->sock, (const char*)payload, payload_size, 0);
if (sent != payload_size) return -1;
client->debug_global_level = global_level;
for (uint8_t i = 0; i < count; i++) client->debug_category_levels[i] = levels[i];
return 0;
}
void etcpmon_client_add_to_history(struct etcpmon_client* client, struct etcpmon_rsp_metrics* metrics) {
if (!client || !metrics) return;

10
tools/etcpmon/etcpmon_client.h

@ -89,6 +89,13 @@ struct etcpmon_client {
/* Last received response seq_id (for matching) */
uint8_t last_response_seq_id;
/* Debug config cache */
uint8_t debug_global_level;
uint8_t debug_category_count;
uint8_t debug_category_levels[ETCPMON_MAX_DEBUG_CATEGORIES];
char debug_category_names[512];
uint8_t debug_config_dirty;
/* Callbacks */
void (*on_connected)(void* user_data);
void (*on_disconnected)(void* user_data);
@ -189,6 +196,9 @@ void etcpmon_client_add_to_history(struct etcpmon_client* client, struct etcpmon
/* Send action command to server */
int etcpmon_client_send_action(struct etcpmon_client* client, const char* action);
int etcpmon_client_request_debug_config(struct etcpmon_client* client);
int etcpmon_client_send_debug_config(struct etcpmon_client* client, uint8_t global_level, uint8_t count, const uint8_t* levels);
uint64_t get_timestamp_ms(void); // Add this declaration
#ifdef __cplusplus

152
tools/etcpmon/etcpmon_gui.c

@ -20,7 +20,7 @@
//#pragma comment(lib, "user32.lib")
//#pragma comment(lib, "gdi32.lib")
#define WINDOW_WIDTH 1000
#define WINDOW_HEIGHT 1250
#define WINDOW_HEIGHT 1810
#define UPDATE_INTERVAL 10 /* 50ms → 20 samples per second */
/* Global app pointer for callbacks */
static struct etcpmon_app* g_app = NULL;
@ -33,6 +33,8 @@ static void OnConnectionSelect(struct etcpmon_app* app);
static void OnAction(struct etcpmon_app* app);
static void OnTimer(struct etcpmon_app* app);
static void UpdateUIState(struct etcpmon_app* app);
static void OnDebugRadio(struct etcpmon_app* app, int control_id);
static void RefreshDebugUI(struct etcpmon_app* app);
static void AddTooltip(HWND hTip, HWND hCtrl, const char* text);
static void CreateTooltips(struct etcpmon_app* app);
/* Callback functions for client events */
@ -724,6 +726,61 @@ static void CreateControls(struct etcpmon_app* app) {
q_col1 + 85 + i * 65, qy - 2, 60, 18, hWnd, (HMENU)((UINT_PTR)(IDC_EDIT_DEBUG_0 + i)), hInst, NULL);
}
/* ========================================================================
* Debug Levels section
* ======================================================================== */
{
int dy = 1255;
app->hDebugGroupBox = CreateWindowExA(0, "BUTTON", "Debug Levels",
WS_CHILD | WS_VISIBLE | BS_GROUPBOX,
10, dy, WINDOW_WIDTH - 20, 545, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
dy += 20;
const char* global_texts[6] = { "NONE", "ERROR", "WARN", "INFO", "DEBUG", "TRACE" };
const char* header_texts[6] = {
"N\r\nO\r\nN\r\nE", "E\r\nR\r\nR\r\nO\r\nR", "W\r\nA\r\nR\r\nN",
"I\r\nN\r\nF\r\nO", "D\r\nE\r\nB\r\nU\r\nG", "T\r\nR\r\nA\r\nC\r\nE"
};
static const char* cat_names[] = {
"UASYNC","LL_QUEUE","CONNECTION","ETCP","CRYPTO",
"MEMORY","TIMING","CONFIG","TUN","ROUTING",
"TIMERS","NORMALIZER","BGP","SOCKET","CONTROL",
"DUMP","TRAFFIC","DEBUG_CAT","GENERAL","NAT","KEEPALIVE"
};
int ncat = sizeof(cat_names) / sizeof(cat_names[0]);
app->debug_cat_count = ncat;
CreateWindowExA(0, "STATIC", "Global:", WS_CHILD | WS_VISIBLE,
20, dy + 2, 50, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
for (int l = 0; l < 6; l++) {
int lx = 75 + l * 85;
app->hDebugGlobalRadio[l] = CreateWindowExA(0, "BUTTON", global_texts[l],
WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
lx, dy, 80, 20, hWnd, (HMENU)(UINT_PTR)(IDC_DEBUG_GLOBAL_BASE + l), hInst, NULL);
}
dy += 22;
for (int l = 0; l < 6; l++) {
int lx = 75 + l * 85;
app->hDebugHeaderEdit[l] = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", header_texts[l],
WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER | ES_MULTILINE,
lx, dy, 80, 60, hWnd, (HMENU)(UINT_PTR)(IDC_DEBUG_HEADER_BASE + l), hInst, NULL);
}
dy += 65;
for (int r = 0; r < ncat; r++) {
app->hDebugCatLabel[r] = CreateWindowExA(0, "STATIC", cat_names[r],
WS_CHILD | WS_VISIBLE | SS_LEFT | SS_CENTERIMAGE,
20, dy + 1, 50, 16, hWnd, (HMENU)(UINT_PTR)(IDC_DEBUG_NAME_BASE + r), hInst, NULL);
for (int l = 0; l < 6; l++) {
int lx = 75 + l * 85;
app->hDebugCatRadio[r][l] = CreateWindowExA(0, "BUTTON", "",
WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
lx, dy, 80, 16, hWnd, (HMENU)(UINT_PTR)(IDC_DEBUG_ROW_BASE + r * 8 + l), hInst, NULL);
}
dy += 18;
}
}
}
static void AddTooltip(HWND hTip, HWND hCtrl, const char* text)
{
@ -942,21 +999,27 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l
switch (message) {
case WM_COMMAND:
if (app) {
switch (LOWORD(wParam)) {
case IDC_BTN_CONNECT:
OnConnect(app);
break;
case IDC_BTN_DISCONNECT:
OnDisconnect(app);
break;
case IDC_LIST_CONNECTIONS:
if (HIWORD(wParam) == LBN_SELCHANGE) {
OnConnectionSelect(app);
}
break;
case IDC_BTN_ACTION:
OnAction(app);
break;
WORD ctrl_id = LOWORD(wParam);
if ((ctrl_id >= IDC_DEBUG_GLOBAL_BASE && ctrl_id <= IDC_DEBUG_GLOBAL_BASE + 5) ||
(ctrl_id >= IDC_DEBUG_ROW_BASE && ctrl_id < IDC_DEBUG_ROW_BASE + IDC_DEBUG_MAX_CAT * 8)) {
OnDebugRadio(app, ctrl_id);
} else {
switch (ctrl_id) {
case IDC_BTN_CONNECT:
OnConnect(app);
break;
case IDC_BTN_DISCONNECT:
OnDisconnect(app);
break;
case IDC_LIST_CONNECTIONS:
if (HIWORD(wParam) == LBN_SELCHANGE) {
OnConnectionSelect(app);
}
break;
case IDC_BTN_ACTION:
OnAction(app);
break;
}
}
}
break;
@ -1049,6 +1112,57 @@ static void OnAction(struct etcpmon_app* app) {
}
}
static void OnDebugRadio(struct etcpmon_app* app, int control_id) {
static int in_handler = 0;
if (in_handler) return;
if (!app || !app->client) return;
in_handler = 1;
if (control_id >= IDC_DEBUG_GLOBAL_BASE && control_id <= IDC_DEBUG_GLOBAL_BASE + 5) {
int level = control_id - IDC_DEBUG_GLOBAL_BASE;
for (int l = 0; l <= 5; l++)
SendMessage(app->hDebugGlobalRadio[l], BM_SETCHECK, (l == level) ? BST_CHECKED : BST_UNCHECKED, 0);
app->client->debug_global_level = (uint8_t)level;
} else if (control_id >= IDC_DEBUG_ROW_BASE) {
int flat = control_id - IDC_DEBUG_ROW_BASE;
int row = flat / 8;
int level = flat % 8;
if (row >= app->debug_cat_count || level > 5) { in_handler = 0; return; }
for (int l = 0; l <= 5; l++) {
if (app->hDebugCatRadio[row][l])
SendMessage(app->hDebugCatRadio[row][l], BM_SETCHECK, (l == level) ? BST_CHECKED : BST_UNCHECKED, 0);
}
app->client->debug_category_levels[row] = (uint8_t)level;
}
etcpmon_client_send_debug_config(app->client, app->client->debug_global_level,
app->debug_cat_count, app->client->debug_category_levels);
in_handler = 0;
}
static void RefreshDebugUI(struct etcpmon_app* app) {
if (!app || !app->client) return;
struct etcpmon_client* cl = app->client;
for (int l = 0; l <= 5; l++) {
if (app->hDebugGlobalRadio[l])
SendMessage(app->hDebugGlobalRadio[l], BM_SETCHECK,
(l == cl->debug_global_level) ? BST_CHECKED : BST_UNCHECKED, 0);
}
int n = cl->debug_category_count;
if (n > app->debug_cat_count) n = app->debug_cat_count;
for (int r = 0; r < n; r++) {
int level = cl->debug_category_levels[r];
if (level > 5) level = 0;
for (int l = 0; l <= 5; l++) {
if (app->hDebugCatRadio[r][l])
SendMessage(app->hDebugCatRadio[r][l], BM_SETCHECK,
(l == level) ? BST_CHECKED : BST_UNCHECKED, 0);
}
}
}
static void OnTimer(struct etcpmon_app* app) {
if (!app || !app->client) return;
@ -1064,9 +1178,15 @@ static void OnTimer(struct etcpmon_app* app) {
if (app->need_initial_request && app->isConnected) {
etcpmon_client_request_list(app->client);
etcpmon_client_request_socket_list(app->client);
etcpmon_client_request_debug_config(app->client);
app->need_initial_request = 0;
}
if (app->isConnected && app->client->debug_config_dirty) {
RefreshDebugUI(app);
app->client->debug_config_dirty = 0;
}
}
static void UpdateUIState(struct etcpmon_app* app) {

15
tools/etcpmon/etcpmon_gui.h

@ -156,6 +156,13 @@ extern "C" {
#define IDC_EDIT_ACTIVE_TIMEOUTS 700
#define IDC_EDIT_BUSY_MEMORY 701
/* Debug Levels control IDs */
#define IDC_DEBUG_MAX_CAT 64
#define IDC_DEBUG_GLOBAL_BASE 1000 /* +0..5 radio: NONE(0)..TRACE(5) */
#define IDC_DEBUG_HEADER_BASE 1010 /* +0..5 header EDIT */
#define IDC_DEBUG_ROW_BASE 1100 /* + row*8 + level */
#define IDC_DEBUG_NAME_BASE 2000 /* + row: category name label */
/* Graph control ID */
#define IDC_GRAPH 500
@ -315,6 +322,14 @@ struct etcpmon_app {
int graph_cursor_y;
int graph_cursor_active;
/* Debug Levels controls */
HWND hDebugGroupBox;
HWND hDebugGlobalRadio[6]; /* 0=NONE..5=TRACE */
HWND hDebugHeaderEdit[6]; /* вертикальный текст: T\nR\nA\nC\nE итд */
HWND hDebugCatLabel[IDC_DEBUG_MAX_CAT]; /* метки категорий */
HWND hDebugCatRadio[IDC_DEBUG_MAX_CAT][6]; /* [row][level] радио-кнопки */
int debug_cat_count;
/* Client state */
struct etcpmon_client* client;

19
tools/etcpmon/etcpmon_protocol.h

@ -35,6 +35,7 @@ extern "C" {
#define ETCPMON_MAX_CONN_NAME 32
#define ETCPMON_MAX_CONNECTIONS 250
#define ETCPMON_MAX_LINKS 16
#define ETCPMON_MAX_DEBUG_CATEGORIES 64
/* Update interval in milliseconds */
#define ETCPMON_UPDATE_INTERVAL_MS 100
@ -50,11 +51,14 @@ extern "C" {
#define ETCPMON_CMD_DISCONNECT 0x04 /* Disconnect from monitoring */
#define ETCPMON_CMD_LIST_SOCKETS 0x05 /* Request local socket list */
#define ETCPMON_CMD_ACTION 0x06 /* Execute action (text command) */
#define ETCPMON_CMD_GET_DEBUG_CONFIG 0x07 /* Request current debug levels */
#define ETCPMON_CMD_SET_DEBUG_CONFIG 0x08 /* Set debug levels (byte array) */
/* Server -> Client responses */
#define ETCPMON_RSP_CONN_LIST 0x81 /* Connection list response */
#define ETCPMON_RSP_METRICS 0x82 /* Metrics response */
#define ETCPMON_RSP_SOCKET_LIST 0x83 /* Socket list response */
#define ETCPMON_RSP_DEBUG_CONFIG 0x84 /* Debug levels response */
#define ETCPMON_RSP_ERROR 0xFF /* Error response */
/* Error codes for RSP_ERROR */
@ -286,6 +290,21 @@ struct etcpmon_rsp_metrics {
/* Followed by etcp.links_count * struct etcpmon_link_metrics */
};
/* RSP_DEBUG_CONFIG: Server -> Client debug levels */
struct etcpmon_rsp_debug_config {
uint8_t global_level; /* Global debug level (0=NONE..5=TRACE) */
uint8_t category_count; /* Number of categories */
/* Followed by: char names_csv[]; null-terminated CSV: "UASYNC,LL_QUEUE,...\0" */
/* Followed by: uint8_t levels[category_count]; per-category levels */
};
/* CMD_SET_DEBUG_CONFIG: Client -> Server set debug levels */
struct etcpmon_cmd_set_debug {
uint8_t global_level; /* Global debug level (0=NONE..5=TRACE) */
uint8_t category_count; /* Number of categories */
uint8_t levels[ETCPMON_MAX_DEBUG_CATEGORIES]; /* Actual length = category_count */
};
/* RSP_ERROR: Error response */
struct etcpmon_rsp_error {
uint8_t error_code; /* Error code */

10
utun.conf.sample

@ -76,10 +76,10 @@ allow=all
#tun_ip=100.64.1.1/24 # IP клиентского NAT TUN
#nat_via=0xABCD000000000001 # node_id провайдера (у кого запрашивать NAT)
# --- TCP Proxy (локальный TCP/UDP/ICMP прокси) ---
# Проксирует входящие TCP (через uIP), UDP и ICMP ping через указанный via_node.
# --- TCP Proxy Client (локальный TCP/UDP/ICMP прокси) ---
# Проксирует входящие TCP (через lwIP), UDP и ICMP ping через указанный via_node.
# Все три протокола идут через один и тот же удалённый узел.
#[tcp_proxy]
#[tcp_proxy_client]
#enabled=yes
#tun_name=tun_tcp # имя TUN интерфейса (по умолчанию tun_tcp)
#tun_ip=10.99.0.1 # IP адрес TUN интерфейса
@ -89,7 +89,7 @@ allow=all
#forward=8000 -> 10.0.0.50:80
#forward=2222 -> 10.0.0.50:22
# --- Remote Proxy (удаленный exit node) ---
# --- TCP Proxy Server (удаленный exit node) ---
# Принимает CONNECT-запросы от других нод и открывает OS сокеты к адресатам.
#[remote_proxy]
#[tcp_proxy_server]
#enabled=yes

Loading…
Cancel
Save