You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

18 KiB

tcp_proxy_client

1. Назначение

Клиентский оркестратор TCP прокси. Создаёт TUN-интерфейс, запускает встроенный lwIP TCP стек, перехватывает исходящие TCP-соединения со стороны клиента и туннелирует их через ETCP к удалённому exit-узлу (tcp_proxy_server). Поддерживает три режима перехвата трафика одновременно:

  1. Прозрачное проксирование через TUN — весь TCP-трафик с TUN-интерфейса попадает в lwIP, который управляет реальными TCP-сессиями. Исходящие соединения прозрачно проксируются на exit-узел. UDP (через udp_proxy) и ICMP Echo (через icmp_proxy) также пробрасываются.
  2. SOCKS5 proxy listener — встроенный SOCKS5-сервер, принимающий локальные подключения и туннелирующий их через ETCP.
  3. 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:

  1. Выделяется struct tcp_proxy_client_conn
  2. Определяется destination: поиск по port mapping config mappings[] (local_port → remote_ip:remote_port), иначе local_ip:local_port самого pcb
  3. Назначается stream_id (инкремент next_stream_id)
  4. Регистрируются lwIP коллбэки: recv, sent, err, poll
  5. Создаётся очередь to_lwip для данных от exit
  6. Отправляется 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:

  1. CONNECT — установка соединения с exit
  2. DATA ↔ DATA — двунаправленный обмен данными
  3. FIN (от клиента или exit) — half-close: tcp_shutdown(pcb, 0, 1) (закрытие только send-стороны)
  4. CLOSE — полное закрытие после обоих FIN, очистка tcp_proxy_client_conn_free()
  5. 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 CONNECT
  • tcp_proxy_client_recv_cb() — данные от клиента (lwIP→ETCP): копирование pbuf → ETCP DATA с backpressure
  • tcp_proxy_client_sent_cb() — lwIP освободил send buffer: вызов feed_from_transport для передачи накопленных данных
  • tcp_proxy_client_err_cb() — ошибка lwIP: pcb уже уничтожен стеком, отправка ETCP ERROR
  • tcp_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=1
  • tcp_proxy_client_send_data() — DATA с payload, без force (backpressure через waiter)
  • tcp_proxy_client_send_close() — CLOSE, force=1, при ошибке выставляет close_pending
  • tcp_proxy_client_send_error() — ERROR, force=1, при ошибке выставляет close_pending
  • tcp_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 update
  • tcp_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_id
  • tcp_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 и ICMP
  • config_parser.h — tcp_proxy_client_mapping_config (local_port → remote_ip:remote_port)
  • utun_instance.h — struct UTUN_INSTANCE с полем tcp_proxy_client
  • lib/u_async.h — асинхронный event loop
  • lib/ll_queue.h — lock-free очереди (to_lwip, output_queue TUN)
  • lib/memory_pool.h — пул для struct ll_entry
  • lib/mem.h — обёртки malloc/free с учётом утечек
  • lib/debug_config.h — логирование