7.4 KiB
TCP proxy client
tcp_proxy_client.c/h объединяет TUN/lwIP, SOCKS5 CONNECT и HTTP forward proxy/CONNECT,
передавая потоки к общему exit via_node_id. Форматы и общие правила —
doc/proxy_protocol.md, контракт API и полей —
tcp_proxy_client.h, локальные listeners —
socks_proxy_doc.md.
Создание и владение
tcp_proxy_client_create вызывается в uasync с живым ua. Для TUN нужны оба
непустых tun_name и tun_ip; задание только одного — ошибка. tun_init_nat создаёт
отдельный proxy TUN (test_mode выбирает тестовый интерфейс); MTU<=0 заменяется на 1500,
MTU TUN передаётся в lwIP. Имя tun_init_nat не означает включение eim_nat на ingress.
Без TUN необходим хотя бы флаг SOCKS или HTTP; эти listeners могут работать без lwIP.
inst/ua/mappings заимствуются до destroy; результат принадлежит вызывающему коду.
Создание само не заполняет inst->tcp_proxy_client: это делает владелец, например
utun_instance. Для автономного TUN smoke test допускается inst=NULL, но для
обмена через router нужен instance. Mappings содержат порты в host byte order,
адрес назначения — IPv4-строку.
Все заданные SOCKS/HTTP listeners, TUN mappings, TUN/lwIP/пулы и TCP router binding обязательны. Ошибка любого шага возвращает NULL и освобождает уже созданные ресурсы. Повторное создание клиента или занятой binding отклоняются. UDP/ICMP создаёт владелец instance после успешного создания TCP-модулей, если есть TUN либо включён exit.
Клиент привязывается к ETCP_RT_ID_TCP_PROXY_CLIENT (0x08), сервер — к
ETCP_RT_ID_TCP_PROXY_SERVER (0x04). Генератор stream_id общий для TUN/SOCKS/HTTP,
изначально равен 1, перед выдачей инкрементируется. На exit ключ потока — пара peer/sid.
TUN → exit
Output queue TUN содержит однобайтовый префикс перед IP-пакетом. Обработчик проверяет
IPv4 version/IHL/total length и отвергает фрагменты (MF/offset) с WARN.
Для UDP и ICMP Echo type 8 учитывается IHL, затем вызывается соответствующий proxy.
TCP без IP-заголовка передаётся в lwip_tcp_input; перед этим по порту назначения
при необходимости создаётся динамический listen PCB.
При accept выбирается первый mapping с совпадающим локальным портом; если его нет —
исходные pcb->local_ip/local_port. Создаются tcp_proxy_client_conn, proxy_flow
и очереди tx_queue (lwIP→exit) и to_lwip (exit→lwIP), отправляется CONNECT.
Локальный TCP handshake завершает lwIP; его успех не подтверждает connect назначения.
DATA ожидает CONNECTED от exit.
Recv callback копирует pbuf в блоки до 4096 байт в tx_queue. Drain отправляет через
proxy_flow_send; tcp_recved возвращает окно локальному клиенту только после
успешного принятия DATA router. При -2 ожидаются CONNECTED/WINDOW; при другой ошибке
регистрируется router send-ready waiter. Safety-net каждые 500 мс повторяет отправку
с force=1, сохраняя ограничение окна proxy. Неотправленный entry возвращается в начало очереди.
Exit → TUN
Router callback проверяет peer==via_node_id и group==TOPO_GROUP_UTUN, включая пустые уведомления router. Сначала предлагает stream SOCKS/HTTP спискам, затем ищет lwIP-поток.
CONNECTED открывает flow.ready; WINDOW проверяет и увеличивает кредит, возобновляя
очереди. DATA после проверки окна помещается в to_lwip. feed_from_transport пишет
часть entry по доступному tcp_sndbuf с TCP_WRITE_FLAG_COPY, оставляя хвост в очереди.
Кредит возвращается после успешного tcp_write, затем вызывается tcp_output.
Дальнейший feed запускают DATA, WINDOW/wake, sent и poll callbacks.
Выходные IPv4-пакеты lwIP записываются через tun_write.
Завершение
Локальный FIN устанавливает fin_local. FIN к exit ставится только после опустошения
tx_queue; общий control retry ждёт ready и повторяет неудавшуюся отправку.
Удалённый FIN устанавливает fin_remote/flow.fin_received и fin_deferred.
После дренажа to_lwip выполняется tcp_shutdown(pcb, 0, 1); при неудаче попытка повторяется.
Когда обе половины завершены, PCB отсоединяется от callbacks: lwIP может самостоятельно освободить его по TIME_WAIT без err callback. Поток удаляется после отправленного FIN, полученного FIN, снятого fin_deferred и пустых tx_queue/to_lwip. Штатное завершение не требует дополнительного обмена CLOSE.
Входящий CLOSE прекращает поток через tcp_close и очистку, ERROR вызывает abort.
Ошибка lwIP обнуляет PCB, который уже освобождает стек; остальная очистка отменяет
control retry, DATA timer, router waiter и освобождает обе очереди.
CLOSE/ERROR при неудаче отправки могут повторяться через close_pending в poll/free
пока поток жив; отдельной сохраняемой очереди этих команд нет.
Пустое уведомление router (ROUTER_SVC_HDR_SIZE=26, без TCP command/sid) удаляет
все lwIP/SOCKS/HTTP потоки клиента к настроенному exit. Это не 9-байтовый wire CLOSE_ALL.
destroy снимает только собственный TCP binding, закрывает listeners/клиентов,
освобождает lwIP/TUN/пул. Общие UDP/ICMP принадлежат instance и сохраняются.
Владелец затем сам обнуляет inst->tcp_proxy_client. Все callbacks и очистка выполняются
в uasync; уничтожать клиента следует вне его активного callback.