# 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`): ```c 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` — логирование