Describe checked datagram startup and bounded TCP HUP handling

This commit is contained in:
evgeny committed 2026-10-24 13:54:40 +02:00
1 parent e2ee5f2397
commit f10a6f70c5
2 files changed
+41 -14

No files matched your search

+34 -14
View File
@@ -68,6 +68,11 @@ Backpressure router и окно потока независимы. При оши
Force обходит лимит send_q router, но никогда не окно proxy.
При отсутствии CONNECTED/кредита (-2) отправка ждёт ready/WINDOW, а не только router waiter.
OS TCP сохраняет ограничение read_high_water и явную остановку чтения при HUP.
Если HUP не оставляет работы для чтения/записи, tcp_io временно снимает регистрацию fd;
low_water восстанавливает её с новой generation. Сокет и очереди сохраняются, запись
обслуживается независимо от чтения. Ошибка повторной регистрации закрывает соединение.
## Half-close и удаление потока
Владелец выставляет fin_pending только после отправки всех исходящих DATA.
@@ -87,6 +92,7 @@ FIN не должен ждать записи противоположного
Клиентский close_pending может повторяться в poll/free пока поток жив; сервер после
ошибки TCP удаляет поток и делает попытку ERROR. Гарантированного повторения CLOSE/ERROR
после освобождения владельца нет.
Поздний WINDOW отсутствующего потока отбрасывается с debug-диагностикой, без создания потока.
Router close/restart уведомляет сервис entry длиной 26 байт без payload.
Proxy называет это CLOSE_ALL: exit удаляет потоки указанного peer, клиент — все свои
@@ -133,13 +139,22 @@ UDP exit создаёт сокет на tuple client_node_id/src_ip/src_port/dst
проверяет источник ответа, меняет src/dst местами в REPLY. Удаляет неактивные flows
при очередной периодической проверке с порогом >60 с. Клиент восстанавливает IPv4 checksum,
UDP checksum=0 (допустимо для IPv4).
Flow публикуется только после socket/nonblocking/bind/read registration и создания
таймера очистки. Ошибка продления общего таймера закрывает оставшиеся flows.
sendto проверяется по точной длине, включая успешный нулевой payload; EINTR повторяется.
EAGAIN/ENOBUFS и ошибки отдельного пакета логируются без очереди повторов; сломанный
сокет удаляется. Ошибки выделения ответа, router send и доставки в TUN диагностируются.
ICMP exit использует один raw socket; заменяет id/seq token и сопоставляет
wire_id/wire_seq/IP цели с pending. Исходные id/seq восстанавливаются для клиента.
Порог ожидания >5 с проверяется периодически. Raw reply проходит проверку ICMP checksum.
Нечётный хвост checksum обрабатывается без чтения за буфером.
test_loopback даёт виртуальный ответ только при отсутствии raw socket.
Успешный ICMP init не гарантирует raw socket: его ошибка оставляет контекст активным.
`icmp_proxy_init` создаёт общий контекст и binding; `icmp_proxy_enable_exit` отдельно
открывает raw socket. EPERM/EACCES/отсутствие поддержки raw отключают только ICMP exit
с WARN; клиентские REPLY сохраняются. Прочие ошибки запуска прерывают создание instance.
Тестовый `test_loopback` включается явно, закрывает raw socket и даёт виртуальные ответы
независимо от прав процесса. Production его не включает.
Pending raw request создаётся только с рабочим таймером истечения срока ожидания.
Send API принимает payload 0..65507, TUN delivery — 0..65506 из-за однобайтового
префикса в uint16_t длине entry. UDP send требует локальный UDP-контекст, ICMP send
@@ -148,11 +163,13 @@ Send API принимает payload 0..65507, TUN delivery — 0..65506 из-з
## Жизненный цикл, диагностика и проверки
Все операции и callbacks — в uasync владельца. Proxy init запрашивает binding
после router init; client/server имеют разные service IDs. Сервер создаёт UDP/ICMP,
клиент делает это при наличии TUN. Сначала уничтожается клиент, затем сервер:
клиент сохраняет общие UDP/ICMP пока сервер включён, server destroy уничтожает их.
После удаления exit-потоков ua должен обработать отложенные free.
Ошибки listener/bind/подмодулей не всегда пробрасываются init/create — точные контракты в .h.
после router init; client/server имеют разные service IDs. Общие UDP/ICMP создаёт
и уничтожает UTUN_INSTANCE; TCP-модули освобождают только собственные ресурсы.
Контексты нужны для включённого exit либо клиентского TUN; SOCKS/HTTP без TUN их не требуют.
Заданные listeners, TUN mappings и bindings обязательны: ошибка откатывает создание.
Повторный init активного модуля и попытка занять чужой binding отклоняются.
При уничтожении instance TCP-клиент/сервер и общие контексты закрываются до остановки
групп и транспорта; ua обрабатывает отложенные free. Точные контракты — в .h.
Категория proxy: info — подключения/закрытия/транзакции, debug — окна/паузы/повторы,
trace — трафик и секундная диагностика exit TCP-очередей/пулов/OS buffers.
@@ -164,17 +181,20 @@ trace — трафик и секундная диагностика exit TCP-о
| test_http_proxy | HTTP codec, разбиения headers/chunks, framing, лимиты, source credit, Upgrade |
| test_proxy_packets | IPv4/UDP/ICMP, checksum и payload длиной 0..1501 |
| test_proxy_regressions | Handshake/CONNECTED, HTTP pipeline/EOF/deadlines, окна/FIN, peer+sid, reentrancy, TUN options/fragment validation, per-instance UDP/ICMP |
| test_proxy_lifecycle | 25 сценариев отказов/rollback, fd/память/таймеры, владение bindings, ICMP без raw, HUP re-register и destroy из callback |
| test_tcp_proxy_client | Create/destroy с тестовым TUN, без утечек |
| test_tcp_proxy_server | TCP echo через tcp_conn; сам по себе не проверяет протокол exit |
| test_socks_http_proxy | Реальные SOCKS IPv4/domain, HTTP и HTTPS CONNECT, byte-exact загрузки |
| test_udp_proxy / test_icmp_proxy | Два instance через ETCP; ICMP использует raw socket либо тестовый fallback |
| test_socks_http_proxy | Реальные SOCKS IPv4/domain, HTTP и HTTPS CONNECT, byte-exact загрузки; штатный TLS shutdown либо UTUN_TEST_TLS_ABRUPT=1 |
| test_udp_proxy / test_icmp_proxy | Два instance через ETCP; ICMP выбирает явный test_loopback при недоступном raw socket |
| test_tcp_io | 19 сценариев, включая HUP high/low-water на epoll/poll, ручную паузу и независимую запись |
| test_tcp_io_flush | Callback после последнего синхронного send |
Аудит 2026-10-24: перечисленные девять тестовых программ пересобраны и прошли на Linux.
ICMP прошёл через test_loopback: raw socket недоступен без соответствующих прав.
В успешном SOCKS/HTTP-тесте остались EPIPE/ENOTCONN при teardown, а тестовые конфиги
логируют неподдержанный enabled в секции сервера; наблюдения записаны в [tasks.md](tasks.md).
Полный check.sh, реальные TUN/load/burst и Windows/FreeBSD в этом аудите не запускались.
Проверка 2026-10-24: профильные тесты проходят на Linux. ICMP без прав на raw проверен
через явный test_loopback. Штатный TLS shutdown проходит без EPIPE/ENOTCONN;
аварийный сохраняет реальные ошибки записи, все 16 загрузок остаются byte-exact.
Тестовые конфиги используют поддержанный connect_timeout. Итоги полного прогона
и sanitizer-проверок — в [proxy_fix_plan.txt](proxy_fix_plan.txt).
Windows/FreeBSD runtime и настоящий raw ICMP в этом окружении не проверены.
Создание proxy из конфигурации в utun_instance.c исключено под _WIN32.
IPv4-фрагменты на входе proxy TUN отклоняются; сборки фрагментов нет.
Описания модулей: `src/proxy/*_doc.md`; все семь заголовков `src/proxy/*.h` сверены с реализацией.
+7
View File
@@ -44,11 +44,15 @@ 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()`.
При создании через `tcp_proxy_client_create` каждый включённый listener обязателен:
пустой/неверный адрес или ошибка bind/listen откатывают создание всего клиента.
### Ключевые нюансы
- **Backpressure:** DATA ждёт CONNECTED и кредита `proxy_flow`. Неотправленные байты сохраняются
в `tx_buf/tx_len` и отправляются порциями до 4096. При доступном окне ставится router send-ready
waiter; safety-net 500 мс возобновляет отправку с обычным force=0. Чтение tcp_io ограничено его очередью.
HUP сохраняет high/low-water и ручную остановку чтения; временно снятая регистрация fd
восстанавливается после освобождения очереди. Запись не зависит от паузы чтения.
- **Несколько соединений:** управляются через связный список `struct socks_proxy_conn*` + счётчик. В HTTP keep-alive каждое сообщение получает новый upstream stream; клиентский TCP-сокет сохраняется. Pipelining обрабатывается последовательно, включая запросы к разным адресатам.
- **stream_id** генерируется монотонно (`++next_stream_id`), передаётся в заголовке ETCP-пакетов для мультиплексирования.
@@ -172,3 +176,6 @@ SOCKS greeting принимает только предложенный мето
Нормативные размеры сообщений и порядок байтов — в [proxy_protocol.md](../../doc/proxy_protocol.md).
Контракты чистого HTTP codec (обнуление состояния, emit/source_len, used и EOF) — в [http_proxy.h](http_proxy.h).
`test_socks_http_proxy` проверяет 16 byte-exact загрузок и двусторонний TLS shutdown.
`UTUN_TEST_TLS_ABRUPT=1` отдельно воспроизводит обрыв TLS: реальные EPIPE/ENOTCONN
остаются ошибками TCP и диагностируются, уже доставленные ответы сохраняются.