docs(proxy): reconcile TCP client server and listener behavior
This commit is contained in:
1 parent
27cf6ae365
commit
f003aaa92d
3 files changed
+174
-291
No files matched your search
@@ -22,10 +22,11 @@ struct listen_ctx* listen = socks_proxy_init_listen(
|
||||
`is_http=0` — SOCKS5, `is_http=1` — HTTP CONNECT/прокси.
|
||||
|
||||
### Приём ETCP-данных от exit node
|
||||
Внешний dispatcher (`tcp_proxy_client`) вызывает `socks_proxy_handle_etcp()` при получении ETCP-пакетов с `svc_id=ETCP_RT_ID_TCP_PROXY`:
|
||||
Внешний dispatcher (`tcp_proxy_client`) проверяет peer/group и вызывает `socks_proxy_handle_etcp()`
|
||||
при получении пакетов `ETCP_RT_ID_TCP_PROXY_CLIENT` (0x08). Запросы уходят в `ETCP_RT_ID_TCP_PROXY_SERVER` (0x04):
|
||||
```c
|
||||
int handled = socks_proxy_handle_etcp(&conns, &conn_count, stream_id, subcmd, data, data_len);
|
||||
// handled=1 — найден и обработан, 0 — stream_id не найден
|
||||
// handled=1 — stream_id найден (включая игнорирование команды), 0 — stream_id не найден
|
||||
```
|
||||
Поддерживаемые subcmd: `CONNECTED`, `DATA`, `WINDOW`, `CLOSE`, `ERROR`, `FIN`.
|
||||
|
||||
@@ -37,12 +38,17 @@ struct socks_proxy_conn* c = socks_proxy_find_conn(conns, stream_id);
|
||||
|
||||
### Завершение
|
||||
```c
|
||||
socks_proxy_conn_free_all(&conns, &conn_count); // все соединения
|
||||
socks_proxy_close_listen(ua, listen, &sock_out); // слушатель
|
||||
socks_proxy_conn_free_all(&conns, &conn_count); // все соединения
|
||||
```
|
||||
`inst/ua/conns/count/next_stream_id` заимствуются и должны жить до закрытия listener и всех клиентов.
|
||||
Закрытие listener сохраняет активных клиентов. Вызовы и callbacks выполняются в uasync владельца.
|
||||
Адрес listener — IPv4:port; порт 0 выбирает ОС, фактический порт возвращает `socks_proxy_get_port()`.
|
||||
|
||||
### Ключевые нюансы
|
||||
- **Бэкпрессур (backpressure):** при неудаче `etcp_route_send()` данные сохраняются в `tx_buf/tx_len`, и регистрируется waiter через `etcp_router_on_send_ready()`. Когда ETCP готов к отправке, `tx_waiter_cb` повторяет попытку и возобновляет `read_queue`.
|
||||
- **Backpressure:** DATA ждёт CONNECTED и кредита `proxy_flow`. Неотправленные байты сохраняются
|
||||
в `tx_buf/tx_len` и отправляются порциями до 4096. При доступном окне ставится router send-ready
|
||||
waiter; safety-net 500 мс возобновляет отправку с обычным force=0. Чтение tcp_io ограничено его очередью.
|
||||
- **Несколько соединений:** управляются через связный список `struct socks_proxy_conn*` + счётчик. В HTTP keep-alive каждое сообщение получает новый upstream stream; клиентский TCP-сокет сохраняется. Pipelining обрабатывается последовательно, включая запросы к разным адресатам.
|
||||
- **stream_id** генерируется монотонно (`++next_stream_id`), передаётся в заголовке ETCP-пакетов для мультиплексирования.
|
||||
|
||||
@@ -71,31 +77,32 @@ socks_proxy_close_listen(ua, listen, &sock_out); // слушатель
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `socks_proxy_init_listen(ua, addr, conns, count, next_sid, inst, via_node, is_http)` | Создаёт слушающий TCP-сокет на `addr` (IP:PORT). Возвращает `listen_ctx*`. |
|
||||
| `socks_proxy_init_listen(ua, addr, conns, count, next_sid, inst, via_node, is_http)` | Создаёт IPv4 listener, возвращает собственный ctx или NULL. Не регистрирует router callback. |
|
||||
| `socks_proxy_close_listen(ua, ctx, sock_out)` | Закрывает слушающий сокет, освобождает ctx. |
|
||||
| `socks_proxy_get_port(ctx)` | Фактический порт listener, 0 при ошибке. |
|
||||
| `socks_proxy_handle_etcp(head, count, stream_id, subcmd, data, len)` | Диспетчер ETCP-сообщений от exit node. Обрабатывает DATA (→write_queue клиенту), CLOSE/ERROR (→push_close), FIN (→push_fin). |
|
||||
| `socks_proxy_find_conn(head, stream_id)` | Поиск соединения по stream_id в связном списке. |
|
||||
| `socks_proxy_conn_free(c)` | Освобождает соединение: отправляет отложенный CLOSE/ERROR в ETCP, удаляет из списка, освобождает `tcp_conn`, buffer, waiter. |
|
||||
| `socks_proxy_conn_free(c)` | Удаляет из списка, отменяет DNS/control retry/DATA timer/waiter, освобождает TCP/HTTP/buffers. Активный HTTP upstream отменяется; pending CLOSE/ERROR получает последнюю попытку. |
|
||||
| `socks_proxy_conn_free_all(head, count)` | Освобождает все соединения в списке. |
|
||||
|
||||
### Внутренние функции
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `send_msg(inst, dst, subcmd, sid, data, len, force)` | Формирует ETCP-пакет: `[TCP_PROXY_HDR_SIZE=6] svc_id(1)+subcmd(1)+stream_id(4)` + payload, отправляет через `etcp_route_send`. |
|
||||
| `send_msg(inst, group_id, dst, subcmd, sid, data, len, force)` | Через общий proxy_send копирует payload и отправляет в TOPO_GROUP_UTUN; group_id аргумент игнорируется, mode=0. |
|
||||
| `send_connect(c)` | Отправляет CONNECT (dest_ip[4]+dest_port[2]) на exit node. |
|
||||
| `send_data(c, data, len)` | Отправляет DATA (релей пользовательского трафика). |
|
||||
| `send_close(c)`, `send_error(c)`, `send_fin(c)` | Отправляют управляющие subcmd. |
|
||||
| `send_close(c)` | Попытка CLOSE с force=1; pending повторяется при free. ERROR отправляется через send_msg, FIN — через proxy_flow_flush. |
|
||||
| `write_to_client(c, data, len)` | Запись данных в `tc->write_queue` (отправка клиенту через TCP). |
|
||||
| `process_socks_greeting(c)` | Парсинг SOCKS5 GREETING: ver=5, выбор метода 0x00 (no auth), ответ 05 00. |
|
||||
| `process_socks_request(c)` | Парсинг SOCKS5 CONNECT REQUEST (atyp=1/3/4, IPv4/IPv6/domain). DNS-резолвинг для доменов. Ответ 05 00 00 01 + dummy addr. |
|
||||
| `process_socks_request(c)` | CONNECT к IPv4 или DNS-имени; IPv6 отклоняется с REP=8. Успешный ответ отправляет socks_connected только после CONNECTED. |
|
||||
| `http_read(c)`, `http_receive(c)` | Потоковый разбор клиентского запроса и upstream ответа через HTTP codec. |
|
||||
| `http_finish(c)` | Завершение/отмена upstream, ожидание доставки ответа клиенту и переход к следующему запросу. |
|
||||
| `http_fail(c)`, `http_deadline(c)` | Ошибки и тайм-ауты: собственный HTTP error до final headers, закрытие оборванного сообщения после них. |
|
||||
| `on_accept_cb(sock, arg)` | Callback accept: создаёт `socks_proxy_conn`, `tcp_conn_create`, вешает read callback. |
|
||||
| `on_read_cb(q, arg)` | Callback чтения из TCP: handshake-парсинг или релей данных через ETCP с бэкпрессуром. |
|
||||
| `tx_waiter_cb(q, arg)` | Callback бэкпрессура: повторная отправка отложенных данных при готовности ETCP. |
|
||||
| `on_fin_cb`, `on_error_cb`, `on_closed_cb` | Callback'и жизненного цикла `tcp_conn`: FIN→удалённый FIN, ошибка→send_error+free, закрыт→send_close+free. |
|
||||
| `on_fin_cb`, `on_error_cb`, `on_closed_cb` | FIN ждёт исходящих DATA; ошибка отправляет ERROR, cleanup из TCP callbacks откладывается через call_soon. HTTP вне туннеля использует границы транзакций. |
|
||||
|
||||
## 4. Протокол
|
||||
|
||||
@@ -104,8 +111,9 @@ socks_proxy_close_listen(ua, listen, &sock_out); // слушатель
|
||||
Клиент → uTun: [ver=5][nmethods][methods...]
|
||||
uTun → Клиент: [05][00] (GREETING: no auth)
|
||||
Клиент → uTun: [05][01][00][atyp][addr...][port] (CONNECT REQUEST)
|
||||
uTun → Клиент: [05][00][00][01][0.0.0.0][0] (ответ ок)
|
||||
uTun → exit (ETCP): TCP_PROXY CONNECT[dest_ip+port]
|
||||
exit → uTun (ETCP): CONNECTED (реальный connect завершён)
|
||||
uTun → Клиент: [05][00][00][01][0.0.0.0][0] (ответ ок)
|
||||
Клиент ↔ uTun ↔ exit: RELAY данных через ETCP DATA
|
||||
```
|
||||
|
||||
@@ -121,10 +129,12 @@ uTun → Клиент: HTTP/1.1 200 Connection Established\r\n\r\n
|
||||
### HTTP-прокси (GET/POST, is_http=1)
|
||||
```
|
||||
Клиент → uTun: GET http://host:port/path HTTP/1.1\r\nHost: ...\r\n\r\n
|
||||
uTun → exit (ETCP): TCP_PROXY CONNECT[dest_ip+port] + TCP_PROXY DATA[GET /path HTTP/1.1\r\n...]
|
||||
uTun → exit (ETCP): TCP_PROXY CONNECT[dest_ip+port]
|
||||
exit → uTun (ETCP): CONNECTED
|
||||
uTun → exit (ETCP): TCP_PROXY DATA[GET /path HTTP/1.1\r\n...]
|
||||
exit → uTun: HTTP headers/body через ETCP DATA, затем FIN
|
||||
uTun → Клиент: проверенный и реконструированный HTTP response
|
||||
uTun → exit (ETCP): CLOSE завершённого stream
|
||||
uTun → exit (ETCP): CLOSE завершённого stream (после полного ответа, FIN нужен для тела до EOF)
|
||||
Клиент → uTun: следующий запрос → новый CONNECT с новым stream_id
|
||||
```
|
||||
URL преобразуется в origin-form без потери query; Host строится из absolute URL. Заголовки текущего соединения, включая поля из Connection, и Proxy-Authorization удаляются в обоих направлениях. Framing и Connection формируются заново, добавляется Via. Upstream обычного запроса получает Connection: close; frontend HTTP/1.1 сохраняет keep-alive для ответов с определённой длиной.
|
||||
@@ -139,12 +149,26 @@ ETCP credit возвращается за исходные принятые ба
|
||||
|
||||
ERROR на exit немедленно уничтожает сокет и отложенный upload; CLOSE сохраняет обычное завершение очереди.
|
||||
|
||||
SOCKS greeting принимает только предложенный метод 0 (no auth); иначе отправляется 05 FF.
|
||||
Реализован только CONNECT, без BIND/UDP ASSOCIATE. DNS выполняется на ingress через
|
||||
асинхронный A-resolver, выбирается первый адрес. Хвост совмещённых greeting/request/data сохраняется.
|
||||
Ошибка DNS/connect до установления потока даёт SOCKS REP=4 либо HTTP 502.
|
||||
|
||||
В сыром туннеле FIN локального клиента ждёт tx_buf и read_queue, но не write_queue
|
||||
противоположного направления. FIN от exit ставится в write_queue клиента после предыдущих DATA.
|
||||
После обоих FIN и фактического локального shutdown ставится close-маркер. WINDOW возвращает
|
||||
кредит после опустошения write_queue и write_buf. Общий retry повторяет CONNECTED/WINDOW/FIN,
|
||||
отдельной гарантированной очереди CLOSE/ERROR у listener нет.
|
||||
|
||||
## 5. Зависимости
|
||||
- `tcp_proxy_server.h` — константы subcmd (`TCP_PROXY_SUBCMD_*`) и размер заголовка (`TCP_PROXY_HDR_SIZE=6`)
|
||||
- `etcp.h` / `etcp_api.h` — `etcp_route_send()`, `ETCP_RT_ID_TCP_PROXY`
|
||||
- `proxy_protocol.h` — subcmd, размеры заголовков, proxy_flow и proxy_send
|
||||
- `etcp_api.h` — `ETCP_RT_ID_TCP_PROXY_SERVER/CLIENT`
|
||||
- `etcp_router.h` — `etcp_router_on_send_ready()`, `etcp_router_cancel_send_ready()`
|
||||
- `tcp_io.h` / `tcp_conn` — TCP-соединения (`tcp_conn_create`, `tcp_conn_push_close`, `tcp_conn_push_fin`, write/read очереди)
|
||||
- `u_async.h` — event loop, `uasync_add_socket_t`
|
||||
- `ll_queue.h` — очереди (write_queue, read_queue), waiter механизм
|
||||
- `memory_pool.h` — аллокация из пулов (`entry_pool`, `data_pool`)
|
||||
- `utun_instance.h` — `struct UTUN_INSTANCE`
|
||||
|
||||
Нормативные размеры сообщений и порядок байтов — в [proxy_protocol.md](../../doc/proxy_protocol.md).
|
||||
Контракты чистого HTTP codec (обнуление состояния, emit/source_len, used и EOF) — в [http_proxy.h](http_proxy.h).
|
||||
@@ -1,204 +1,87 @@
|
||||
# tcp_proxy_client
|
||||
# TCP proxy client
|
||||
|
||||
## 1. Назначение
|
||||
`tcp_proxy_client.c/h` объединяет TUN/lwIP, SOCKS5 CONNECT и HTTP forward proxy/CONNECT,
|
||||
передавая потоки к общему exit `via_node_id`. Форматы и общие правила —
|
||||
[doc/proxy_protocol.md](../../doc/proxy_protocol.md), контракт API и полей —
|
||||
[tcp_proxy_client.h](tcp_proxy_client.h), локальные listeners —
|
||||
[socks_proxy_doc.md](socks_proxy_doc.md).
|
||||
|
||||
Клиентский оркестратор 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_proxy_client_create` вызывается в uasync с живым `ua`. TUN/lwIP создаются только
|
||||
при **обоих** непустых `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.
|
||||
|
||||
Модуль полностью оркестрирует весь TCP-прокси-пайплайн на стороне клиента: захват трафика → lwIP TCP стек → ETCP-туннелирование → приём ответных данных → отдача в lwIP → запись IP-пакетов обратно в TUN.
|
||||
`inst/ua/mappings` заимствуются до destroy; результат принадлежит вызывающему коду.
|
||||
Создание само не заполняет `inst->tcp_proxy_client`: это делает владелец, например
|
||||
`utun_instance`. Для автономного TUN smoke test допускается inst=NULL, но для
|
||||
обмена через router нужен instance. Mappings содержат порты в host byte order,
|
||||
адрес назначения — IPv4-строку.
|
||||
|
||||
## 2. Как пользоваться
|
||||
Критическая ошибка TUN/lwIP/пула возвращает NULL. Ошибка отдельного listener или
|
||||
router bind не отменяет создание всего клиента; проверять конкретные listener handles
|
||||
и диагностику. UDP/ICMP инициализируются только для TUN после успешного TCP bind.
|
||||
|
||||
### Типовой сценарий
|
||||
Клиент привязывается к `ETCP_RT_ID_TCP_PROXY_CLIENT` (0x08), сервер — к
|
||||
`ETCP_RT_ID_TCP_PROXY_SERVER` (0x04). Генератор stream_id общий для TUN/SOCKS/HTTP,
|
||||
изначально равен 1, перед выдачей инкрементируется. На exit ключ потока — пара peer/sid.
|
||||
|
||||
```
|
||||
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 → 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.
|
||||
|
||||
**TUN interface** через `tun_init_nat()`. IP-пакеты из output_queue TUN направляются в `tcp_proxy_client_tun_input()` — главную точку входа inbound-трафика.
|
||||
При 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.
|
||||
|
||||
**Встроенный lwIP TCP стек** (`lwip_tcp_init()`) с output-коллбэком `tcp_proxy_client_output_cb`, который пишет готовые IP-пакеты обратно в TUN (в сторону ОС клиента).
|
||||
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 возвращается в начало очереди.
|
||||
|
||||
**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
|
||||
## Exit → TUN
|
||||
|
||||
**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)`
|
||||
Router callback проверяет peer==via_node_id и group==TOPO_GROUP_UTUN, включая пустые
|
||||
уведомления router. Сначала предлагает stream SOCKS/HTTP спискам, затем ищет lwIP-поток.
|
||||
|
||||
**Client-side lwIP** — чтение данных (`tcp_proxy_client_recv_cb`):
|
||||
- При получении данных от lwIP (клиент что-то отправляет): копируем pbuf → `tcp_proxy_client_send_data()` → ETCP DATA
|
||||
- При backpressure (router send_q переполнен): сохраняем в `tx_buf`, регистрируем waiter через `etcp_router_on_send_ready()`. При освобождении очереди `tcp_proxy_client_tx_waiter_cb` досылает данные и вызывает `tcp_recved()` для window update
|
||||
- При p==NULL (FIN от lwIP): выставляется `fin_local`, отправляется ETCP FIN. Если обе стороны закрылись — ETCP CLOSE
|
||||
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`.
|
||||
|
||||
**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), немедленная очистка
|
||||
Локальный 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)`; при неудаче попытка повторяется.
|
||||
|
||||
**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()`).
|
||||
Когда обе половины завершены, PCB отсоединяется от callbacks: lwIP может самостоятельно
|
||||
освободить его по TIME_WAIT без err callback. Поток удаляется после отправленного FIN,
|
||||
полученного FIN, снятого fin_deferred и пустых tx_queue/to_lwip. Штатное завершение
|
||||
не требует дополнительного обмена CLOSE.
|
||||
|
||||
**SOCKS5/HTTP proxy listeners** — используют `socks_proxy_init_listen()` (общий код с SOCKS, флаг `is_http` различает режимы). SOCKS/HTTP обрабатываются приоритетно в `router_recv_cb` — им не нужен TUN/lwIP.
|
||||
Входящий CLOSE прекращает поток через `tcp_close` и очистку, ERROR вызывает abort.
|
||||
Ошибка lwIP обнуляет PCB, который уже освобождает стек; остальная очистка отменяет
|
||||
control retry, DATA timer, router waiter и освобождает обе очереди.
|
||||
CLOSE/ERROR при неудаче отправки могут повторяться через close_pending в poll/free
|
||||
пока поток жив; отдельной сохраняемой очереди этих команд нет.
|
||||
|
||||
### Протокол обмена с exit-узлом
|
||||
Пустое уведомление router (`ROUTER_SVC_HDR_SIZE=26`, без TCP command/sid) удаляет
|
||||
все lwIP/SOCKS/HTTP потоки клиента к настроенному exit. Это не 9-байтовый wire CLOSE_ALL.
|
||||
|
||||
Формат сообщения: `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 на router send_q**: `etcp_router_on_send_ready()` — если `etcp_route_send()` не может отправить (inflight полон), данные буферизуются в `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` при освобождении router send_q, вызов `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` — логирование
|
||||
`destroy` снимает TCP binding, закрывает listeners/клиентов, освобождает lwIP/TUN/пул.
|
||||
Общие UDP/ICMP уничтожаются только при выключенном сервере того же instance.
|
||||
Владелец затем сам обнуляет `inst->tcp_proxy_client`. Все callbacks и очистка выполняются
|
||||
в uasync; уничтожать клиента следует вне его активного callback.
|
||||
@@ -1,109 +1,85 @@
|
||||
# tcp_proxy_server
|
||||
# TCP proxy server (exit)
|
||||
|
||||
## 1. Назначение
|
||||
`tcp_proxy_server.c/h` открывает обычный TCP к IPv4-назначению по CONNECT ingress-узла
|
||||
и передаёт данные через UTUN-router. Общий протокол —
|
||||
[doc/proxy_protocol.md](../../doc/proxy_protocol.md), контракт API —
|
||||
[tcp_proxy_server.h](tcp_proxy_server.h).
|
||||
|
||||
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-сессий).
|
||||
Контекст встроен в `inst->tcp_proxy_server`. `init(inst)` обнуляет его, читает
|
||||
`config->global.tcp_proxy_server_enabled`, при включении регистрирует сервис
|
||||
`ETCP_RT_ID_TCP_PROXY_SERVER` (0x04) и вызывает UDP/ICMP init независимо от наличия клиента.
|
||||
NULL inst даёт -1; отсутствие config или отключённый сервер дают 0 без binding.
|
||||
Результаты bind/UDP/ICMP init не пробрасываются: 0 не гарантирует готовность всех сервисов.
|
||||
Повторный init при живых потоках недопустим.
|
||||
|
||||
При старте сервера автоматически инициализируются `udp_proxy` и `icmp_proxy`, если клиентский TCP прокси не активен (чтобы exit node обслуживал все виды трафика).
|
||||
INI-парсер включает сервер при обработке параметра секции `[tcp_proxy_server]`.
|
||||
Поддержанные ключи: `tcp_recv_buf`, `fib`, `so_mark`, `netif`, `bind_ip`,
|
||||
`connect_timeout`. Ключ `enabled` в этой секции текущим парсером не поддерживается;
|
||||
для включения достаточно, например, `connect_timeout=2000`.
|
||||
Пустая секция без параметров сама по себе флаг не выставляет.
|
||||
|
||||
## 2. Как пользоваться
|
||||
Каждый поток принадлежит серверу, содержит собственный `tcp_conn` и `proxy_flow`.
|
||||
Поиск выполняется по паре `peer_node_id/stream_id`; одинаковые sid разных пиров допустимы.
|
||||
Повторный CONNECT существующей пары отклоняется с ERROR.
|
||||
|
||||
### Включение
|
||||
В конфиге: `tcp_proxy_server_enabled = yes` — сервер регистрирует обработчик `ETCP_RT_ID_TCP_PROXY` через `etcp_router_bind()` и готов принимать команды от клиентов.
|
||||
## Connect и обмен данными
|
||||
|
||||
### Типовой сценарий
|
||||
1. **CONNECT (0x01):** клиент шлёт `dest_ip(4) + dest_port(2)`, сервер создаёт `tcp_proxy_server_conn`, открывает OS TCP сокет, делает `connect()` к destination.
|
||||
2. **DATA (0x03):** клиент шлёт данные → сервер пишет в OS write_queue через pool-аллокацию.
|
||||
3. **Чтение из destination:** данные с OS сокета попадают в `read_queue`, оттуда `read_queue_drain_cb` релеит их клиенту через ETCP.
|
||||
4. **CLOSE (0x04):** клиент закрыл соединение → сервер паузит чтение, сливает read_queue, шлёт shutdown(SHUT_WR) в OS сокет.
|
||||
5. **FIN (0x06):** клиент подтверждает получение FIN от destination → сервер push-ит FIN в OS сокет (завершение полного закрытия).
|
||||
6. **ERROR (0x05):** ошибка на клиенте → сервер форсированно закрывает соединение.
|
||||
7. **Очистка:** по `on_closed_cb` (OS сокет закрыт полностью) → `tcp_proxy_server_conn_free()` освобождает все ресурсы.
|
||||
CONNECT содержит IPv4 и порт в сетевом порядке. Создаётся неблокирующий OS-сокет,
|
||||
применяются bind_ip, SO_MARK, FreeBSD FIB и привязка к интерфейсу там, где поддержаны,
|
||||
затем создаётся tcp_conn и вызывается connect. Эти настройки относятся к TCP-сокетам,
|
||||
UDP/ICMP их не наследуют. Таймаут connect — `connect_timeout` мс,
|
||||
по умолчанию 2000; 0 отключает таймаут. Асинхронная ошибка/таймаут приводит к ERROR и очистке.
|
||||
|
||||
### Ключевые нюансы
|
||||
- **Backpressure:** если `etcp_route_send()` вернула ошибку — данные буферизуются в `tx_buf`, регистрируется `pause_waiter` (ожидание освобождения ETCP канала), запускается fallback `retry_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.
|
||||
Реальный успех connect ставит pending CONNECTED. Только после принятия CONNECTED router
|
||||
flow становится ready. Сервер отправляет ответы в `ETCP_RT_ID_TCP_PROXY_CLIENT` (0x08).
|
||||
Все proxy send используют `TOPO_GROUP_UTUN` и mode=0.
|
||||
|
||||
## 3. API
|
||||
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 у сервера нет.
|
||||
|
||||
| Структура | Назначение |
|
||||
|-----------|-----------|
|
||||
| `tcp_proxy_server` | Корневой контекст: enabled, связный список conns, conn_count, ссылка на UTUN_INSTANCE |
|
||||
| `tcp_proxy_server_conn` | Состояние одного прокси-соединения: stream_id, peer_node_id, dest_ip:port, tcp_conn, буферы/счётчики/таймеры для backpressure и закрытия |
|
||||
## 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-маркер.
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `tcp_proxy_server_init(inst)` | Регистрирует ETCP биндинг на `ETCP_RT_ID_TCP_PROXY`, авто-инициализирует UDP/ICMP прокси если tcp_proxy_client не активен |
|
||||
| `tcp_proxy_server_destroy(inst)` | Освобождает все соединения, уничтожает UDP/ICMP прокси |
|
||||
CLOSE клиента устанавливает cli_closed, паузит OS-чтение и отбрасывает read_queue.
|
||||
При установленном TCP close-маркер ждёт ранее принятые записи write_queue;
|
||||
ещё не подключённый поток освобождается сразу. ERROR немедленно уничтожает поток,
|
||||
не ожидая дренажа потенциально зависшего upload.
|
||||
|
||||
### Обработчики команд (вызываются из `tcp_proxy_server_recv_cb`)
|
||||
Общий proxy_flow повторяет только pending CONNECTED/WINDOW/FIN через 500 мс.
|
||||
CLOSE/ERROR не имеют экспоненциального backoff или собственной очереди server retry.
|
||||
OS error callback удаляет поток и делает отдельную попытку отправки ERROR;
|
||||
conn_free сам по себе не отправляет control-сообщение.
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `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) |
|
||||
## Диспетчеризация и очистка
|
||||
|
||||
### Управление соединениями
|
||||
recv_cb принимает владение entry/dgram, CONNECT/DATA handlers также освобождают их.
|
||||
Peer берётся из метаданных router, а не из физического conn.
|
||||
Уведомление router без payload (26 байт) удаляет только потоки данного peer.
|
||||
Это локальное CLOSE_ALL/restart/close-событие канала, не отдельная 9-байтовая команда proxy.
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `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 |
|
||||
conn_free сразу помечает freed, удаляет поток из списка, отменяет waiter/control retry,
|
||||
DATA retry и диагностический таймер, уничтожает TCP. Память структуры освобождается
|
||||
через uasync_call_soon, чтобы текущая синхронная доставка могла проверить freed.
|
||||
Указатель нельзя использовать как живой поток после conn_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 и структур |
|
||||
destroy выключает сервер, снимает binding и уничтожает общие UDP/ICMP-контексты.
|
||||
Клиент того же instance должен быть остановлен раньше; ua должен обработать отложенные free.
|
||||
Все операции выполняются в uasync. Каждую секунду диагностический таймер пишет
|
||||
размеры TCP-очередей, наличие write_buf, кеши пулов и OS socket buffers на уровне proxy=trace.
|
||||
Reference in new issue
Block a user