7.3 KiB
TCP proxy server (exit)
tcp_proxy_server.c/h открывает обычный TCP к IPv4-назначению по CONNECT ingress-узла
и передаёт данные через UTUN-router. Общий протокол —
doc/proxy_protocol.md, контракт API —
tcp_proxy_server.h.
Инициализация и ресурсы
Контекст встроен в inst->tcp_proxy_server. init(inst) обнуляет его, читает
config->global.tcp_proxy_server_enabled, при включении регистрирует сервис
ETCP_RT_ID_TCP_PROXY_SERVER (0x04).
NULL inst даёт -1; отсутствие config или отключённый сервер дают 0 без binding.
Занятый binding/ошибка bind возвращает -1; enabled выставляется только после успеха.
Повторный init отклоняется. Общие UDP/ICMP создаёт и уничтожает instance независимо от TCP-модулей.
INI-парсер включает сервер при обработке параметра секции [tcp_proxy_server].
Поддержанные ключи: tcp_recv_buf, fib, so_mark, netif, bind_ip,
connect_timeout. Ключ enabled в этой секции текущим парсером не поддерживается;
для включения достаточно, например, connect_timeout=2000.
Пустая секция без параметров сама по себе флаг не выставляет.
Каждый поток принадлежит серверу, содержит собственный tcp_conn и proxy_flow.
Поиск выполняется по паре peer_node_id/stream_id; одинаковые sid разных пиров допустимы.
Повторный CONNECT существующей пары отклоняется с ERROR.
Connect и обмен данными
CONNECT содержит IPv4 и порт в сетевом порядке. Создаётся неблокирующий OS-сокет,
применяются bind_ip, SO_MARK, FreeBSD FIB и привязка к интерфейсу там, где поддержаны,
затем создаётся tcp_conn и вызывается connect. Эти настройки относятся к TCP-сокетам,
UDP/ICMP их не наследуют. Таймаут connect — connect_timeout мс,
по умолчанию 2000; 0 отключает таймаут. Асинхронная ошибка/таймаут приводит к ERROR и очистке.
Реальный успех connect ставит pending CONNECTED. Только после принятия CONNECTED router
flow становится ready. Сервер отправляет ответы в ETCP_RT_ID_TCP_PROXY_CLIENT (0x08).
Все proxy send используют TOPO_GROUP_UTUN и mode=0.
DATA клиента проверяется через proxy_flow_receive, копируется в pool-backed write_queue TCP. Кредит WINDOW возвращается после опустошения и write_queue, и write_buf, то есть после передачи принятой порции в OS TCP, а не при получении ETCP-пакета. Счётчик bytes_sent увеличивается до проверки/постановки в очередь и не является числом реально доставленных destination байтов.
Данные destination берутся из read_queue и отправляются через proxy_flow_send (force=0). При блокировке entry возвращается в начало read_queue. Для ошибки router ставится send-ready waiter; отсутствие ready/кредита (-2) ждёт CONNECTED/WINDOW. Safety-net 500 мс возобновляет drain с обычным force=0, не обходит окно/очередь router. Чтение tcp_io ограничивается его очередью. Отдельных tx_buf/pause_buf у сервера нет.
FIN, CLOSE и ERROR
FIN destination отправляется клиенту только после опустошения read_queue. Ожидание write_queue противоположного направления для этого не требуется. FIN клиента отмечает flow.fin_received и ставит FIN-маркер в OS write_queue: shutdown(SHUT_WR) следует за всеми ранее принятыми DATA. Дальнейшие DATA после FIN отклоняются. Когда FIN отправлен, принят встречный FIN и OS TCP отправил локальный FIN, в write_queue ставится close-маркер.
CLOSE клиента устанавливает cli_closed, паузит OS-чтение и отбрасывает read_queue. При установленном TCP close-маркер ждёт ранее принятые записи write_queue; ещё не подключённый поток освобождается сразу. ERROR немедленно уничтожает поток, не ожидая дренажа потенциально зависшего upload.
Общий proxy_flow повторяет только pending CONNECTED/WINDOW/FIN через 500 мс. CLOSE/ERROR не имеют экспоненциального backoff или собственной очереди server retry. OS error callback удаляет поток и делает отдельную попытку отправки ERROR; conn_free сам по себе не отправляет control-сообщение.
Диспетчеризация и очистка
recv_cb принимает владение entry/dgram, CONNECT/DATA handlers также освобождают их. Peer берётся из метаданных router, а не из физического conn. Уведомление router без payload (26 байт) удаляет только потоки данного peer. Это локальное CLOSE_ALL/restart/close-событие канала, не отдельная 9-байтовая команда proxy.
conn_free сразу помечает freed, удаляет поток из списка, отменяет waiter/control retry, DATA retry и диагностический таймер, уничтожает TCP. Память структуры освобождается через uasync_call_soon, чтобы текущая синхронная доставка могла проверить freed. Указатель нельзя использовать как живой поток после conn_free.
destroy выключает сервер и снимает только его binding; общие UDP/ICMP-контексты сохраняются. Instance останавливает proxy до разборки UTUN-группы и транспорта; ua обрабатывает отложенные free. Все операции выполняются в uasync. Каждую секунду диагностический таймер пишет размеры TCP-очередей, наличие write_buf, кеши пулов и OS socket buffers на уровне proxy=trace.