18 KiB
tcp_proxy_client
1. Назначение
Клиентский оркестратор TCP прокси. Создаёт TUN-интерфейс, запускает встроенный lwIP TCP стек, перехватывает исходящие TCP-соединения со стороны клиента и туннелирует их через ETCP к удалённому exit-узлу (tcp_proxy_server). Поддерживает три режима перехвата трафика одновременно:
- Прозрачное проксирование через TUN — весь TCP-трафик с TUN-интерфейса попадает в lwIP, который управляет реальными TCP-сессиями. Исходящие соединения прозрачно проксируются на exit-узел. UDP (через
udp_proxy) и ICMP Echo (черезicmp_proxy) также пробрасываются. - SOCKS5 proxy listener — встроенный SOCKS5-сервер, принимающий локальные подключения и туннелирующий их через ETCP.
- HTTP CONNECT proxy listener — встроенный HTTP CONNECT-прокси (работает через тот же механизм SOCKS, что и SOCKS5).
Модуль полностью оркестрирует весь TCP-прокси-пайплайн на стороне клиента: захват трафика → lwIP TCP стек → ETCP-туннелирование → приём ответных данных → отдача в lwIP → запись IP-пакетов обратно в TUN.
2. Как пользоваться
Типовой сценарий
tcp_proxy_client_create(inst, ua, tun_name, tun_ip, mtu, test_mode,
mappings, mapping_count, via_node_id,
socks_enabled, socks_addr, http_proxy_enabled, http_proxy_addr)
→ TUN init (tun_init_nat) — создание TUN-интерфейса с NAT
→ lwIP init (lwip_tcp_init) — запуск встроенного TCP стека
→ Настройка port mappings — прослушивание локальных портов в lwIP
→ SOCKS5/HTTP listen — запуск локальных прокси-слушателей
→ ETCP router bind — регистрация коллбэка tcp_proxy_client_router_recv_cb
→ UDP/ICMP proxy init
Ключевые концепции
TUN interface через tun_init_nat(). IP-пакеты из output_queue TUN направляются в tcp_proxy_client_tun_input() — главную точку входа inbound-трафика.
Встроенный lwIP TCP стек (lwip_tcp_init()) с output-коллбэком tcp_proxy_client_output_cb, который пишет готовые IP-пакеты обратно в TUN (в сторону ОС клиента).
Inbound TUN трафик (tcp_proxy_client_tun_input()): парсинг IP-пакета → по протоколу:
- TCP → вызов
lwip_tcp_input()— пакет попадает в lwIP, где срабатываетaccept_cbдля новых соединений илиrecv_cbдля существующих - UDP →
udp_proxy_send_to_exit()— проброс UDP в exit - ICMP (type 8/Echo) →
icmp_proxy_send_to_exit()— проброс ping в exit
Connection accept (tcp_proxy_client_accept_cb): при новом входящем TCP-соединении в lwIP:
- Выделяется
struct tcp_proxy_client_conn - Определяется destination: поиск по port mapping config
mappings[](local_port → remote_ip:remote_port), иначеlocal_ip:local_portсамого pcb - Назначается
stream_id(инкрементnext_stream_id) - Регистрируются lwIP коллбэки:
recv,sent,err,poll - Создаётся очередь
to_lwipдля данных от exit - Отправляется ETCP CONNECT с dest_ip+dest_port через
etcp_route_send(force=1)
Client-side lwIP — чтение данных (tcp_proxy_client_recv_cb):
- При получении данных от lwIP (клиент что-то отправляет): копируем pbuf →
tcp_proxy_client_send_data()→ ETCP DATA - При backpressure (очередь нормализатора переполнена): сохраняем в
tx_buf, регистрируем waiter черезetcp_router_waiter_register(). При освобождении очередиtcp_proxy_client_tx_waiter_cbдосылает данные и вызываетtcp_recved()для window update - При p==NULL (FIN от lwIP): выставляется
fin_local, отправляется ETCP FIN. Если обе стороны закрылись — ETCP CLOSE
Exit-side data — приём ответных данных (tcp_proxy_client_handle_data()):
- Данные от exit помещаются в очередь
to_lwip tcp_proxy_client_feed_from_transport(): пока есть место вtcp_sndbuf(pcb), читает изto_lwipи пишет черезtcp_write()в lwIP. После записи вызываетtcp_output()для отправки- Trigger на feed: при получении DATA, при
sent_cb(lwIP освободил send buffer)
Full lifecycle:
- CONNECT — установка соединения с exit
- DATA ↔ DATA — двунаправленный обмен данными
- FIN (от клиента или exit) — half-close:
tcp_shutdown(pcb, 0, 1)(закрытие только send-стороны) - CLOSE — полное закрытие после обоих FIN, очистка
tcp_proxy_client_conn_free() - ERROR — аварийное закрытие (ошибка lwIP или exit), немедленная очистка
Per-port mapping конфигурация (tcp_proxy_client_mapping_config):
struct tcp_proxy_client_mapping_config {
uint16_t local_port; // локальный порт для прослушивания
char remote_ip[64]; // IP назначения на exit-узле
uint16_t remote_port; // порт назначения на exit-узле
};
При старте для каждого mapping создаётся listen pcb в lwIP. Также работает динамический listen для любых исходящих портов (tcp_proxy_client_ensure_outbound_listen()).
SOCKS5/HTTP proxy listeners — используют socks_proxy_init_listen() (общий код с SOCKS, флаг is_http различает режимы). SOCKS/HTTP обрабатываются приоритетно в router_recv_cb — им не нужен TUN/lwIP.
Протокол обмена с exit-узлом
Формат сообщения: svc_id(1) + subcmd(1) + stream_id(4) + [data] (заголовок TCP_PROXY_HDR_SIZE = 6 байт).
| Subcmd | Код | Направление | Описание |
|---|---|---|---|
| CONNECT | 0x01 | client→exit | Установка TCP-соединения с destination |
| DATA | 0x03 | ↔ | Данные TCP-потока |
| CLOSE | 0x04 | ↔ | Полное закрытие потока |
| ERROR | 0x05 | ↔ | Ошибка, немедленная очистка |
| FIN | 0x06 | ↔ | Half-close (shutdown write) |
Специальное сообщение: svc_id(1) + peer_id(8) (9 байт) — CLOSE_ALL: очистка всех соединений к указанному peer.
Механизмы надёжности
- Backpressure в нормализатор:
etcp_router_waiter_register()— еслиetcp_route_send()не может отправить (очередь переполнена), данные буферизуются вtx_bufи досылаются при освобождении - Повтор CLOSE/ERROR: если отправка CLOSE/ERROR не удалась, выставляется
close_pending=1. Вpoll_cbповторяется попытка - Очередь to_lwip: буферизует данные от exit до готовности lwIP send buffer, обеспечивая window-based flow control
3. API
Структуры
struct tcp_proxy_client_conn — одно проксируемое TCP-соединение
| Поле | Тип | Назначение |
|---|---|---|
next |
* |
Связный список всех соединений |
proxy |
*tcp_proxy_client |
Обратный указатель на родительский прокси |
pcb |
*tcp_pcb |
lwIP protocol control block для этого соединения |
stream_id |
uint32_t |
Уникальный идентификатор потока в протоколе ETCP |
to_lwip |
*ll_queue |
Очередь данных от exit → lwIP (flow control) |
fin_local |
uint8_t |
Локальная сторона (lwIP) отправила FIN |
fin_remote |
uint8_t |
Удалённая сторона (exit) отправила FIN |
rem_closed |
uint8_t |
Exit прислал CLOSE — прекращаем обработку |
error |
uint8_t |
Ошибка на любом конце — немедленная очистка |
close_sent |
uint8_t |
CLOSE/ERROR отправлен на exit |
close_pending |
uint8_t |
CLOSE/ERROR не доставлен, ждём повтора в poll |
tx_buf |
*uint8_t |
Буфер при backpressure (lwIP→ETCP send fail) |
tx_len |
uint16_t |
Длина данных в tx_buf |
tx_waiter |
queue_waiter_handle |
Дескриптор waiter для backpressure |
dest_ip |
uint8_t[4] |
IP назначения на exit-узле |
dest_port |
uint16_t |
Порт назначения на exit-узле (network byte order) |
struct tcp_proxy_client — состояние прокси-клиента
| Поле | Тип | Назначение |
|---|---|---|
inst |
*UTUN_INSTANCE |
Родительский инстанс uTun |
ua |
*UASYNC |
Асинхронный event loop |
tun |
*tun_if |
TUN-интерфейс для захвата/инжекции трафика |
conns |
*tcp_proxy_client_conn |
Голова связного списка lwIP-соединений |
conn_count |
int |
Количество активных lwIP-соединений |
entry_pool |
*memory_pool |
Пул для struct ll_entry (данные to_lwip) |
next_stream_id |
uint32_t |
Счётчик stream_id (начинается с 1) |
via_node_id |
uint64_t |
Node ID exit-узла для маршрутизации ETCP |
lwip |
*lwip_tcp_ctx |
Контекст встроенного lwIP TCP стека |
mappings |
*mapping_config |
Указатель на массив port mapping конфигурации |
mapping_count |
int |
Количество mapping записей |
socks_enabled |
int |
Флаг включения SOCKS5 прокси |
socks_conns |
*socks_proxy_conn |
Список активных SOCKS5-соединений |
socks_conn_count |
int |
Количество SOCKS5-соединений |
socks_listen |
*listen_ctx |
Слушающий сокет SOCKS5 |
http_proxy_enabled |
int |
Флаг включения HTTP CONNECT прокси |
http_conns |
*socks_proxy_conn |
Список активных HTTP CONNECT соединений |
http_conn_count |
int |
Количество HTTP CONNECT соединений |
http_listen |
*listen_ctx |
Слушающий сокет HTTP CONNECT |
Функции
tcp_proxy_client_create()
Создаёт и инициализирует весь прокси-клиент. Параметры: инстанс uTun, uasync, имя/IP/MTU TUN-интерфейса, test_mode, массив port mapping, via_node_id, флаги/адреса SOCKS и HTTP прокси. Возвращает NULL при ошибке.
tcp_proxy_client_destroy()
Полная очистка: отвязка от ETCP router, остановка UDP/ICMP прокси, закрытие SOCKS/HTTP listeners, освобождение всех соединений (lwIP abort + очистка очередей to_lwip + tx_buf + waiter cancel), уничтожение lwIP стека, закрытие TUN, освобождение memory pool.
tcp_proxy_client_router_recv_cb()
Главный ETCP-коллбэк (зарегистрирован через etcp_router_bind на ETCP_RT_ID_TCP_PROXY=0x04). Обрабатывает входящие сообщения от exit-узла:
- DATA →
tcp_proxy_client_handle_data()— данные в очередь to_lwip и feed в lwIP - CLOSE →
tcp_proxy_client_handle_close()— пометка rem_closed, закрытие lwIP pcb, освобождение соединения - ERROR →
tcp_proxy_client_handle_error()— аварийное закрытие lwIP pcb, освобождение соединения - FIN →
tcp_proxy_client_handle_fin()— half-close через tcp_shutdown(pcb, 0, 1) - CLOSE_ALL (9-байтовый пакет без conn) → очистка всех соединений к peer
- Сначала пробует SOCKS/HTTP обработчики (приоритет — могут работать без TUN)
Внутренние функции
lwIP коллбэки
tcp_proxy_client_output_cb()— output lwIP: запись IP-пакетов в TUN (в сторону ОС клиента)tcp_proxy_client_accept_cb()— новый входящий TCP: созданиеtcp_proxy_client_conn, определение destination из mapping конфига, отправка ETCP CONNECTtcp_proxy_client_recv_cb()— данные от клиента (lwIP→ETCP): копирование pbuf → ETCP DATA с backpressuretcp_proxy_client_sent_cb()— lwIP освободил send buffer: вызов feed_from_transport для передачи накопленных данныхtcp_proxy_client_err_cb()— ошибка lwIP: pcb уже уничтожен стеком, отправка ETCP ERRORtcp_proxy_client_poll_cb()— периодический poll (2 интервала): очистка при error, повтор close_pending
Отправка сообщений в ETCP
tcp_proxy_client_send_msg()— базовая функция: сборка пакетаsvc_id+subcmd+stream_id+data, отправка черезetcp_route_send()tcp_proxy_client_send_connect()— CONNECT с dest_ip+dest_port (6 байт данных), force=1tcp_proxy_client_send_data()— DATA с payload, без force (backpressure через waiter)tcp_proxy_client_send_close()— CLOSE, force=1, при ошибке выставляет close_pendingtcp_proxy_client_send_error()— ERROR, force=1, при ошибке выставляет close_pendingtcp_proxy_client_send_fin()— FIN, force=1
Обработка трафика
tcp_proxy_client_tun_input()— коллбэк очереди output_queue TUN. Парсит IP-пакеты: TCP → lwip_tcp_input (с динамическим listen на целевой порт), UDP/ICMP → соответствующие проксиtcp_proxy_client_handle_non_tcp()— перенаправление UDP вudp_proxy_send_to_exit()и ICMP Echo вicmp_proxy_send_to_exit()tcp_proxy_client_feed_from_transport()— чтение данных из очередиto_lwipи запись в lwIP черезtcp_write()пока есть место в send buffer. После feed вызываетtcp_output()tcp_proxy_client_tx_waiter_cb()— коллбэк backpressure waiter: досылкаtx_bufпри освобождении normalizer очереди, вызовtcp_recved()для window updatetcp_proxy_client_ensure_outbound_listen()— динамическое создание listen pcb в lwIP для заданного порта (если ещё не создан), нужно для прозрачного проксирования любых исходящих TCP-соединений
Управление памятью и соединениями
tcp_proxy_client_entry_from_data()— созданиеll_entryиз пулаentry_poolс копированием данныхtcp_proxy_client_find_conn()— линейный поиск соединения поstream_idtcp_proxy_client_conn_free()— полная очистка одного соединения: досылка pending CLOSE/ERROR, удаление из списка, очистка очереди to_lwip, освобождение tx_buf, cancel waiter, free памяти
Зависимости
tun_if.h— TUN-интерфейс (инициализация, запись, закрытие)lwip_tcp/lwip_tcp.h— встроенный lwIP TCP стек (инициализация, input, управление pcb)etcp.h/etcp_api.h/etcp_router.h— ETCP-маршрутизация и отправка данныхtcp_proxy_server.h— общие константы протокола (TCP_PROXY_SUBCMD_*, TCP_PROXY_HDR_SIZE)socks_proxy.h— SOCKS5/HTTP CONNECT прокси (init_listen, handle_etcp, conn_free_all)udp_proxy.h/icmp_proxy.h— проброс UDP и ICMPconfig_parser.h—tcp_proxy_client_mapping_config(local_port → remote_ip:remote_port)utun_instance.h—struct UTUN_INSTANCEс полемtcp_proxy_clientlib/u_async.h— асинхронный event looplib/ll_queue.h— lock-free очереди (to_lwip, output_queue TUN)lib/memory_pool.h— пул дляstruct ll_entrylib/mem.h— обёртки malloc/free с учётом утечекlib/debug_config.h— логирование