From 23cec465bf71f3a4444e63b47f49a61ac60d270f Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 24 Oct 2026 12:14:42 +0200 Subject: [PATCH] docs(proxy): specify TCP lifecycle and UDP ownership --- src/proxy/tcp_proxy_client.h | 69 +++++++++++++++++++++--------------- src/proxy/tcp_proxy_server.h | 62 +++++++++++++++++++++----------- src/proxy/udp_proxy.h | 61 +++++++++++++++++-------------- 3 files changed, 117 insertions(+), 75 deletions(-) diff --git a/src/proxy/tcp_proxy_client.h b/src/proxy/tcp_proxy_client.h index c4a6dd7f..79829ca7 100644 --- a/src/proxy/tcp_proxy_client.h +++ b/src/proxy/tcp_proxy_client.h @@ -1,7 +1,9 @@ /* Клиент TCP-прокси: принимает IPv4 с отдельного TUN через lwIP либо TCP от SOCKS/HTTP listener. * Передаёт потоки через UTUN-router на via_node_id; proxy_flow обеспечивает окно и backpressure. * create/destroy и callbacks — в uasync. Владеет созданными TUN/lwIP/listener/потоками, - * заимствует inst/ua/mappings до destroy. Без tun_name/tun_ip может работать только SOCKS/HTTP. */ + * заимствует inst/ua/mappings до destroy. Без пары непустых tun_name/tun_ip работает только SOCKS/HTTP. + * Владелец сам записывает результат create в inst->tcp_proxy_client и обнуляет поле после destroy. + * Клиент принимает TCP-ответы только от via_node_id в TOPO_GROUP_UTUN; UDP/ICMP требуют TUN. */ #ifndef TCP_PROXY_CLIENT_H #define TCP_PROXY_CLIENT_H @@ -32,12 +34,12 @@ struct listen_ctx; struct tcp_pcb; struct tcp_proxy_client_conn { - struct tcp_proxy_client_conn* next; - struct tcp_proxy_client* proxy; - struct tcp_pcb* pcb; + struct tcp_proxy_client_conn* next; // следующий lwIP-поток клиента + struct tcp_proxy_client* proxy; // заимствованный владелец + struct tcp_pcb* pcb; // принадлежит lwIP; NULL после detach/err callback - struct proxy_flow flow; - uint32_t stream_id; + struct proxy_flow flow; // окно и retry CONNECTED/WINDOW/FIN + uint32_t stream_id; // общий генератор с SOCKS/HTTP-потоками struct ll_queue* to_lwip; // DATA от exit → lwIP (управление потоком) @@ -47,54 +49,63 @@ struct tcp_proxy_client_conn { uint8_t rem_closed; // exit отправил CLOSE uint8_t error; // ошибка, немедленная очистка uint8_t close_sent; // отправили CLOSE/ERROR в exit - uint8_t close_pending; // CLOSE/ERROR не доставлен, ждём повтора + uint8_t close_pending; // CLOSE/ERROR не принят router, повтор в poll/free пока pc жив struct ll_queue* tx_queue; // буфер lwIP→ETCP при backpressure (аналог server read_queue) struct queue_waiter_handle tx_waiter; // waiter на send_q (etcp_router_on_send_ready) void* tx_retry_timer; // retry safety-net (force=1), аналог server retry_timer - uint32_t bytes_to_exit; // байт отправлено в exit (из lwIP/TUN) + uint32_t bytes_to_exit; // байт принято router для exit (из lwIP/TUN) uint32_t bytes_from_exit; // байт получено от exit (в lwIP/TUN) uint32_t bp_count; // счётчик backpressure (send fail) - uint8_t dest_ip[4]; - uint16_t dest_port; + uint8_t dest_ip[4]; // IPv4 назначения в сетевом порядке + uint16_t dest_port; // порт назначения в сетевом порядке }; struct tcp_proxy_client { - struct UTUN_INSTANCE* inst; - struct UASYNC* ua; - struct tun_if* tun; - 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 UTUN_INSTANCE* inst; // заимствован; NULL допустим для автономного TUN smoke test + struct UASYNC* ua; // заимствованный event loop + struct tun_if* tun; // собственный TUN или NULL для SOCKS/HTTP без TUN + struct tcp_proxy_client_conn* conns; // список собственных lwIP-потоков + int conn_count; // число lwIP-потоков, без SOCKS/HTTP + struct memory_pool* entry_pool; // собственный пул ll_entry для очередей lwIP-потоков + uint32_t next_stream_id; // общий счётчик TUN/SOCKS/HTTP, init=1, перед выдачей инкрементируется + uint64_t via_node_id; // настроенный exit для всех потоков + struct lwip_tcp_ctx* lwip; // собственный TCP-стек или NULL без TUN struct tcp_proxy_client_mapping_config* mappings; // указатель на конфиг - int mapping_count; + int mapping_count; // число записей mappings, порты конфига в host byte order // SOCKS proxy - int socks_enabled; - struct socks_proxy_conn* socks_conns; - int socks_conn_count; - struct listen_ctx* socks_listen; + int socks_enabled; // запрошен SOCKS5; не гарантирует успешный listener + struct socks_proxy_conn* socks_conns; // собственные SOCKS5-клиенты + int socks_conn_count; // число SOCKS5-клиентов + struct listen_ctx* socks_listen; // собственный listener или NULL при отключении/ошибке - // HTTP CONNECT proxy - int http_proxy_enabled; - struct socks_proxy_conn* http_conns; - int http_conn_count; - struct listen_ctx* http_listen; + // HTTP forward proxy и CONNECT + int http_proxy_enabled; // запрошен HTTP; не гарантирует успешный listener + struct socks_proxy_conn* http_conns; // собственные HTTP-клиенты + int http_conn_count; // число HTTP-клиентов + struct listen_ctx* http_listen; // собственный listener или NULL при отключении/ошибке }; +// Создать клиент; ua обязателен. Непустые tun_name И tun_ip включают TUN/lwIP (mtu<=0 → 1500). +// test_mode передаётся tun_init_nat: ненулевой использует тестовый TUN; mappings заимствуются до destroy. +// SOCKS/HTTP создаются при enabled и непустом IPv4:port; via_node_id — общий exit. +// Результат: собственный p или NULL при критической ошибке/отсутствии режима. +// Ошибки listener/router bind/UDP/ICMP не делают create атомарным: проверять listener и диагностику отдельно. 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_client_mapping_config* mappings, int mapping_count, uint64_t via_node_id, int socks_enabled, const char* socks_addr, int http_proxy_enabled, const char* http_proxy_addr); +// Остановить listeners, освободить потоки/TUN/lwIP и снять router binding; NULL допустим. +// UDP/ICMP сохраняются, пока включён сервер того же inst. Не обнуляет inst->tcp_proxy_client. void tcp_proxy_client_destroy(struct tcp_proxy_client* p); +// Router callback: conn->instance задаёт владельца, entry содержит метаданные router и передаёт владение. +// Освобождает entry/dgram при любом исходе; CLOSE_ALL = пустой payload router, а не TCP-подкоманда. void tcp_proxy_client_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry); diff --git a/src/proxy/tcp_proxy_server.h b/src/proxy/tcp_proxy_server.h index 4c0b60c3..83f968ff 100644 --- a/src/proxy/tcp_proxy_server.h +++ b/src/proxy/tcp_proxy_server.h @@ -1,7 +1,9 @@ /* Exit TCP-прокси: принимает CONNECT/DATA через UTUN-router и открывает обычный TCP к назначению. * Поток определяется парой peer_node_id/stream_id; proxy_flow согласует окна и half-close. * init учитывает tcp_proxy_server_enabled в конфиге; destroy закрывает потоки и общий UDP/ICMP-прокси. - * Состояние принадлежит UTUN_INSTANCE, операции и callbacks выполняются в его uasync-потоке. */ + * Сначала init, затем приём через router callback, в конце destroy; повторный init живого сервера недопустим. + * Состояние принадлежит UTUN_INSTANCE, операции и callbacks выполняются в его uasync-потоке. + * CLOSE дренирует запись и прекращает чтение; ERROR немедленно уничтожает поток, FIN — half-close. */ #ifndef TCP_PROXY_SERVER_H #define TCP_PROXY_SERVER_H @@ -24,50 +26,70 @@ struct ETCP_CONN; #include "proxy_protocol.h" struct tcp_proxy_server_conn { - struct tcp_proxy_server_conn* next; - struct tcp_proxy_server* ctx; - struct tcp_conn* tc; - struct proxy_flow flow; - uint32_t stream_id; - uint64_t peer_node_id; - uint8_t dest_ip[4]; - uint16_t dest_port; + struct tcp_proxy_server_conn* next; // следующий поток exit + struct tcp_proxy_server* ctx; // заимствованный владелец в inst + struct tcp_conn* tc; // собственный OS TCP к назначению + struct proxy_flow flow; // окно, FIN и control retry + uint32_t stream_id; // ID клиента; уникален в паре с peer_node_id + uint64_t peer_node_id; // конечный ingress-узел, не транспортный сосед + uint8_t dest_ip[4]; // IPv4 назначения в сетевом порядке + uint16_t dest_port; // порт назначения в сетевом порядке - uint8_t close_queued; + uint8_t close_queued; // close-маркер уже поставлен в write_queue TCP uint8_t cli_closed; // client sent CLOSE void* diag_timer; // 1-секундный таймер диагностики - uint8_t freed; // 1 = уже освобождён, защита от double-free + uint8_t freed; // логически удалён; сама структура освобождается через call_soon - struct queue_waiter_handle pause_waiter; - void* retry_timer; // fallback timer (500ms) for force=1 retry on stall - uint32_t bytes_sent; // байт записано в destination (реальный TCP) - uint32_t bytes_relayed; // байт отправлено клиенту через ETCP + struct queue_waiter_handle pause_waiter; // ожидание свободного места в router send_q + void* retry_timer; // fallback 500 мс: возобновить read_queue, DATA отправляется с force=0 + uint32_t bytes_sent; // сумма входящих DATA, учитывается до проверки/записи в TCP + uint32_t bytes_relayed; // байт принято router для клиента uint32_t drain_count; // счётчик вызовов read_queue_drain_cb uint32_t data_count; // счётчик входящих DATA от клиента - struct UASYNC* ua; + struct UASYNC* ua; // заимствованный loop для таймеров и отложенного free }; struct tcp_proxy_server { - int enabled; - int conn_count; - struct tcp_proxy_server_conn* conns; - struct UTUN_INSTANCE* inst; + int enabled; // exit обслуживает CONNECT/UDP/ICMP REQUEST + int conn_count; // число потоков в conns + struct tcp_proxy_server_conn* conns; // собственные потоки всех пиров + struct UTUN_INSTANCE* inst; // заимствованный instance, содержащий сервер }; +// Обнулить состояние и учесть config->global.tcp_proxy_server_enabled; NULL inst → -1, иначе 0. +// Без конфига/при отключении не bind-ит сервис. При включении bind SERVER и вызывает UDP/ICMP init. +// 0 не гарантирует успешный bind/UDP/ICMP: их ошибки не пробрасываются вызывающему коду. int tcp_proxy_server_init(struct UTUN_INSTANCE* inst); +// Удалить потоки, выключить сервер, снять binding и уничтожить общие UDP/ICMP; NULL inst допустим. +// Перед этим остановить tcp_proxy_client того же inst; дать ua обработать отложенные освобождения. void tcp_proxy_server_destroy(struct UTUN_INSTANCE* inst); +// Линейный поиск в ненулевом ctx по паре peer/sid; заимствованный указатель или NULL. struct tcp_proxy_server_conn* tcp_proxy_server_find_conn(struct tcp_proxy_server* ctx, uint64_t peer, uint32_t stream_id); +// Немедленно удалить rc из списка, закрыть TCP и отменить таймеры/waiter; NULL допустим. +// Память rc освобождается отложенно в ua; сама функция не отправляет CLOSE/ERROR пиру. void tcp_proxy_server_conn_free(struct tcp_proxy_server_conn* rc); +// Router callback: conn->instance — владелец, peer берётся из ROUTER_SVC_SRC_OFF entry. +// Принимает владение entry/dgram при любом исходе. Пустой payload router удаляет потоки этого peer. void tcp_proxy_server_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry); +// entry — полный приёмный пакет CONNECT с IPv4/портом; принимает владение entry/dgram. +// inst должен быть включён, sid/src — уже разобранные поля. 0 = connect запущен/готов, -1 = ошибка. +// CONNECTED отправляется только после реального connect; таймаут — из connect_timeout_ms (0 = отключён). int tcp_proxy_server_handle_connect(struct UTUN_INSTANCE* inst, struct ll_entry* entry, uint32_t stream_id, uint64_t src_node_id); +// Полный entry DATA не короче RECV_HDR_SIZE; принимает владение entry/dgram. conn сейчас не используется. +// Peer берётся из entry; проверяет окно и ставит данные в OS write_queue. 0 = обработано, -1 = отклонено. +// Ошибка выделения в write_queue может закрыть поток и при возврате 0: это не подтверждение OS send. int tcp_proxy_server_handle_data(struct UTUN_INSTANCE* inst, struct ETCP_CONN* conn, struct ll_entry* entry, uint32_t stream_id); +// CLOSE: прекратить чтение, отбросить read_queue и закрыть TCP после записи уже принятой write_queue. void tcp_proxy_server_handle_close(struct UTUN_INSTANCE* inst, uint64_t peer, uint32_t stream_id); +// ERROR: немедленно удалить поток вместе с непрочитанными/незаписанными данными. void tcp_proxy_server_handle_error(struct UTUN_INSTANCE* inst, uint64_t peer, uint32_t stream_id); +// FIN: поставить shutdown(SHUT_WR) после уже принятых DATA; обратное направление продолжает работать. +// handle_close/error/fin допускают NULL inst и отсутствующую пару peer/sid. void tcp_proxy_server_handle_fin(struct UTUN_INSTANCE* inst, uint64_t peer, uint32_t stream_id); diff --git a/src/proxy/udp_proxy.h b/src/proxy/udp_proxy.h index 937fe5fe..e1676545 100644 --- a/src/proxy/udp_proxy.h +++ b/src/proxy/udp_proxy.h @@ -1,6 +1,9 @@ /* UDP из TUN через UTUN-router: клиент отправляет REQUEST, exit открывает сокет для кортежа адресов * и возвращает REPLY. Потоки exit удаляются по таймауту неактивности. - * init/destroy и callbacks — в uasync; контекст принадлежит inst, роль берётся из tcp_proxy_server.enabled. */ + * init/destroy и callbacks — в uasync; inst владеет контекстом в inst->udp_proxy. + * init сохраняет роль в is_exit для диагностики; приём REQUEST проверяет текущее server.enabled, + * а REPLY — наличие tcp_proxy_client и совпадение peer с via_node_id. Один inst может быть и клиентом, и exit. + * API принимает IP/порты в network byte order, payload заимствован только на время вызова. */ #ifndef UDP_PROXY_H #define UDP_PROXY_H @@ -22,55 +25,61 @@ struct ETCP_CONN; #define UDP_PROXY_SUBCMD_REQUEST 0x01 #define UDP_PROXY_SUBCMD_REPLY 0x02 -// Заголовок сообщения на отправке (включая байт svc_id; src/dst node_id добавляет роутер): +// Сообщение для etcp_route_send (не полный wire packet; метаданные приёма добавляет router): // svc_id(1) + subcmd(1) + src_ip(4) + src_port(2) + dst_ip(4) + dst_port(2) + payload #define UDP_PROXY_HDR_SIZE 14 // Полный заголовок на приёме (после router_deliver): -// svc_id(1) + src_node_id(8) + dst_node_id(8) + subcmd(1) + src_ip(4) + src_port(2) + dst_ip(4) + dst_port(2) -#define UDP_PROXY_RECV_HDR_SIZE (ROUTER_SVC_PAYLOAD_OFF + 13) // 30 +// router header(26: svc/src/dst/flags/group) + subcmd(1) + src_ip(4) + src_port(2) + dst_ip(4) + dst_port(2) +#define UDP_PROXY_RECV_HDR_SIZE (ROUTER_SVC_PAYLOAD_OFF + 13) // 39 // Один UDP поток (сторона exit узла: сопоставляет client_node_id + кортеж адресов с OS сокетом) struct udp_flow { - struct udp_flow* next; - struct udp_proxy_ctx* ctx; - uint64_t client_node_id; - uint32_t src_ip; - uint16_t src_port; - uint32_t dst_ip; - uint16_t dst_port; - socket_t sock; - void* read_id; - struct UASYNC* ua; - uint64_t created_tb; - uint64_t last_activity_tb; + struct udp_flow* next; // следующий flow exit + struct udp_proxy_ctx* ctx; // заимствованный владелец + uint64_t client_node_id; // конечный ingress-узел + uint32_t src_ip; // исходный адрес приложения, network byte order + uint16_t src_port; // исходный порт приложения, network byte order + uint32_t dst_ip; // адрес назначения, network byte order + uint16_t dst_port; // порт назначения, network byte order + socket_t sock; // собственный неблокирующий UDP, bind ANY:0 + void* read_id; // регистрация сокета в ua + struct UASYNC* ua; // заимствованный event loop + uint64_t created_tb; // время создания (tb = 0.1 мс) + uint64_t last_activity_tb; // последний REQUEST или ответ от ожидаемого dst_ip:dst_port }; struct udp_proxy_ctx { - int initialized; - int is_exit; // 1=exit узел (создаёт сокеты), 0=клиент (шлёт через etcp) - struct UTUN_INSTANCE* inst; - struct UASYNC* ua; - struct udp_flow* flows; - int flow_count; - uint64_t flow_timeout_tb; + int initialized; // init завершён, binding запрошен + int is_exit; // снимок server.enabled на момент init, не фильтр recv_cb + struct UTUN_INSTANCE* inst; // заимствованный instance-владелец + struct UASYNC* ua; // заимствованный event loop + struct udp_flow* flows; // собственные flows exit + int flow_count; // число flows + uint64_t flow_timeout_tb; // период проверки и порог неактивности, default=600000 (60 с) void* expire_timer; // периодический таймер очистки истёкших потоков }; +// Создать per-instance контекст и bind UDP service; повторный init существующего контекста даёт 0. +// inst/ua должны жить до destroy. 0 = контекст есть (результат bind не проверяется), -1 = ошибка inst/памяти. int udp_proxy_init(struct UTUN_INSTANCE* inst, struct UASYNC* ua); +// Снять binding, отменить таймер, закрыть flows и обнулить inst->udp_proxy; NULL/нет контекста допустимы. void udp_proxy_destroy(struct UTUN_INSTANCE* inst); // Exit узел: принимает REQUEST, создаёт сокет, шлёт данные -// Сторона клиента: принимает REPLY, доставляет на TUN +// Сторона клиента: принимает REPLY от настроенного exit, доставляет на TUN. +// conn->instance задаёт владельца; callback принимает владение entry/dgram при любом исходе. void udp_proxy_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry); -// Сторона клиента: принять IP/UDP пакет с TUN, отправить через etcp_route_send +// Скопировать payload (0..65507 байт) в REQUEST и отправить в UTUN, force=0/mode=0. +// Требует inst->udp_proxy; при payload_len>0 payload не NULL. 0 = принято router, <0 = ошибка. int udp_proxy_send_to_exit(struct UTUN_INSTANCE* inst, uint64_t exit_node_id, uint32_t src_ip, uint16_t src_port, uint32_t dst_ip, uint16_t dst_port, const uint8_t* payload, size_t payload_len); -// Сторона клиента: доставить ответ на TUN (вызывается из recv_cb) +// Собрать IPv4/UDP src→dst и поставить в tun->input_queue (префикс версии 4); UDP checksum=0. +// Требует inst->tcp_proxy_client->tun; payload=0..65506. 0 = поставлено в очередь, -1 = ошибка. 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,