- Router: unified service delivery format [svc_id][src][dst][payload] (end-to-end src/dst from SVC_ROUTE header) - tcp_proxy_client: replace single tx_buf drop-on-backpressure with bounded tx_queue + send_q waiter + retry timer (no data loss, bulk window reopen — removes 62.5ms delayed-ACK stall) - tcp_io: configurable connect timeout (tcp_conn_set_connect_timeout) - tcp_proxy_server: connect_timeout config option (ms, default 2000, 0=off) - check.sh: run tcp_proxy_full integration test with sudo (skip if passwordless sudo unavailable) - test_tcp_io: connect timeout unit test; docs (lwip_nuances.md)
11 KiB
tcp_proxy_server
1. Назначение
TCP прокси-сервер на exit node. Принимает CONNECT-запросы от клиента через ETCP, создаёт OS TCP сокет к реальному адресату, релеит данные в обе стороны: клиент↔ETCP↔сервер↔OS TCP↔destination. Поддерживает backpressure (пауза чтения из OS сокета при заполнении ETCP очереди), graceful CLOSE, экспоненциальный ретрай управляющих сообщений, диагностические таймеры.
Работает в паре с tcp_proxy_client (на ingress node) и lwip_tcp (lwIP-стек для обработки TCP-сессий).
При старте сервера автоматически инициализируются udp_proxy и icmp_proxy, если клиентский TCP прокси не активен (чтобы exit node обслуживал все виды трафика).
2. Как пользоваться
Включение
В конфиге: tcp_proxy_server_enabled = yes — сервер регистрирует обработчик ETCP_RT_ID_TCP_PROXY через etcp_router_bind() и готов принимать команды от клиентов.
Типовой сценарий
- CONNECT (0x01): клиент шлёт
dest_ip(4) + dest_port(2), сервер создаётtcp_proxy_server_conn, открывает OS TCP сокет, делаетconnect()к destination. - DATA (0x03): клиент шлёт данные → сервер пишет в OS write_queue через pool-аллокацию.
- Чтение из destination: данные с OS сокета попадают в
read_queue, оттудаread_queue_drain_cbрелеит их клиенту через ETCP. - CLOSE (0x04): клиент закрыл соединение → сервер паузит чтение, сливает read_queue, шлёт shutdown(SHUT_WR) в OS сокет.
- FIN (0x06): клиент подтверждает получение FIN от destination → сервер push-ит FIN в OS сокет (завершение полного закрытия).
- ERROR (0x05): ошибка на клиенте → сервер форсированно закрывает соединение.
- Очистка: по
on_closed_cb(OS сокет закрыт полностью) →tcp_proxy_server_conn_free()освобождает все ресурсы.
Ключевые нюансы
- Backpressure: если
etcp_route_send()вернула ошибку — данные буферизуются вtx_buf, регистрируетсяpause_waiter(ожидание освобождения ETCP канала), запускается fallbackretry_timer(500ms, force=1). При возобновлении — сначала делается повтор tx_buf, потом продолжается дрейн очереди. - FIN handling: FIN от destination не отправляется клиенту пока есть pending writes в OS сокет (ждём
on_flushed_cb) или pending reads (tx_buf / read_queue не пусты — флагdst_fin_deferred). При опустошении очередей — FIN отправляется немедленно. - Graceful CLOSE: при получении CLOSE от клиента чтение из OS сокета паузится через
tcp_conn_pause_read(), read_queue сливается (данные отбрасываются), push-ится close в OS сокет. - Ретрай CLOSE/ERROR: при неудачной отправке
send_msg()— экспоненциальный backoff (50→100→200→…→5000 tb = 5ms→500ms) черезclose_timer+close_retry_cb. - Диагностический таймер (1s): логирует TCP буферы ОС, размеры read/write очередей, free counts entry/data pool-ов.
- Connect-таймаут:
connect()к destination асинхронный; если заconnect_timeoutмс (конфиг[tcp_proxy_server], по умолчанию 2000,0= без таймаута) соединение не установилось — срабатываетtcp_conn_handle_error(ETIMEDOUT)→ клиенту уходит ERROR, соединение освобождается (защита от «чёрной дыры» и утечки fd). - CLOSE_ALL: специальный 9-байтовый пакет без валидного conn удаляет все соединения для заданного peer.
3. API
Структуры
| Структура | Назначение |
|---|---|
tcp_proxy_server |
Корневой контекст: enabled, связный список conns, conn_count, ссылка на UTUN_INSTANCE |
tcp_proxy_server_conn |
Состояние одного прокси-соединения: stream_id, peer_node_id, dest_ip:port, tcp_conn, буферы/счётчики/таймеры для backpressure и закрытия |
Функции инициализации/завершения
| Функция | Описание |
|---|---|
tcp_proxy_server_init(inst) |
Регистрирует ETCP биндинг на ETCP_RT_ID_TCP_PROXY, авто-инициализирует UDP/ICMP прокси если tcp_proxy_client не активен |
tcp_proxy_server_destroy(inst) |
Освобождает все соединения, уничтожает UDP/ICMP прокси |
Обработчики команд (вызываются из tcp_proxy_server_recv_cb)
| Функция | Описание |
|---|---|
tcp_proxy_server_recv_cb(conn, entry) |
Главный ETCP-коллбэк: диспетчеризует subcmd → handle_connect/data/close/error/fin, обрабатывает CLOSE_ALL |
tcp_proxy_server_handle_connect(inst,entry,sid,src) |
Создаёт OS TCP сокет, connect() к destination, аллоцирует tcp_proxy_server_conn, регистрирует diag_timer и коллбэки tcp_conn |
tcp_proxy_server_handle_data(inst,conn,entry,sid) |
Копирует данные клиента в write_queue OS сокета через pool-аллокацию |
tcp_proxy_server_handle_close(inst, sid) |
Паузит чтение, сливает read_queue, отправляет shutdown в OS сокет, освобождает соединение если не connected |
tcp_proxy_server_handle_error(inst, sid) |
Делегирует в handle_close (обрабатывается идентично CLOSE) |
tcp_proxy_server_handle_fin(inst, sid) |
Push-ит FIN в write_queue OS сокета (клиент подтвердил получение FIN от destination) |
Управление соединениями
| Функция | Описание |
|---|---|
tcp_proxy_server_find_conn(ctx, sid) |
Линейный поиск соединения по stream_id в связном списке |
tcp_proxy_server_conn_free(rc) |
Полное освобождение: защита от double-free, отмена таймеров, отправка pending close/error если надо, удаление из списка, tcp_conn_destroy(), u_free |
Внутренние callback-и (статические)
| Функция | Описание |
|---|---|
send_msg(inst, dst, subcmd, sid, data, len, force) |
Формирует ETCP-пакет (svc_id + subcmd + sid + data) и отправляет через etcp_route_send() |
send_close(rc) |
Отправляет CLOSE клиенту, при неудаче запускает экспоненциальный ретрай через close_timer |
send_error(rc) |
Аналогично send_close, но шлёт ERROR |
send_fin(rc) |
Отправляет FIN клиенту (без ретрая) |
on_fin_cb(tc, arg) |
OS сокет получил FIN → машина состояний: если локальный FIN тоже → close; если write pending → ждать flush; если read pending → отложить FIN |
on_flushed_cb(tc, arg) |
Write очередь OS сокета опустела → отправить FIN клиенту или закрыть если оба FIN получены |
on_error_cb(tc, err, arg) |
Ошибка OS сокета → отправить ERROR клиенту, освободить соединение |
on_closed_cb(tc, arg) |
OS сокет полностью закрыт → освободить соединение |
read_queue_drain_cb(q, arg) |
Дрейн read_queue OS сокета → отправка данных клиенту через ETCP; backpressure: буферизация в tx_buf + pause_waiter + retry_timer |
pause_resume_cb(q, arg) |
ETCP канал освободился → возобновить дрейн read_queue |
retry_timer_cb(arg) |
Fallback-таймер 500ms: повтор tx_buf с force=1, при успехе возобновляет read_queue |
close_retry_cb(arg) |
Экспоненциальный backoff для повторной отправки CLOSE/ERROR (50→5000 tb) |
diag_timer_cb(arg) |
1-секундный таймер: логирует TCP буферы ОС, размеры очередей, free-counts pool-ов |
Константы
| Макрос | Значение | Описание |
|---|---|---|
TCP_PROXY_HDR_SIZE |
6 | svc_id(1) + subcmd(1) + stream_id(4) |
TCP_PROXY_CONNECT_HDR_SIZE |
12 | HDR_SIZE + dest_ip(4) + dest_port(2) |
TCP_PROXY_SUBCMD_CONNECT |
0x01 | Запрос на создание TCP соединения к destination |
TCP_PROXY_SUBCMD_DATA |
0x03 | Данные для отправки в destination |
TCP_PROXY_SUBCMD_CLOSE |
0x04 | Клиент закрывает прокси-соединение |
TCP_PROXY_SUBCMD_ERROR |
0x05 | Ошибка на стороне клиента |
TCP_PROXY_SUBCMD_FIN |
0x06 | Клиент подтвердил FIN от destination |
Зависимости
| Модуль | Использование |
|---|---|
tcp_io.h |
tcp_conn — абстракция OS TCP сокета с read/write очередями и memory pool-ами |
ll_queue.h |
Очереди read_queue/write_queue OS сокета, pause_waiter для backpressure |
etcp_router.h |
etcp_router_bind(), etcp_route_send(), etcp_router_on_send_ready(), etcp_router_cancel_send_ready() |
u_async.h |
Таймеры (uasync_set_timeout, uasync_cancel_timeout) |
udp_proxy.h |
Авто-инициализация при старте сервера |
icmp_proxy.h |
Авто-инициализация при старте сервера |
config_parser.h |
Флаг tcp_proxy_server_enabled |
mem.h |
u_malloc/u_free/u_calloc для tx_buf и структур |