From f10a6f70c512fdcf1cd85cf2b5a2916ee950acb8 Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 24 Oct 2026 13:54:40 +0200 Subject: [PATCH] Describe checked datagram startup and bounded TCP HUP handling --- doc/proxy_protocol.md | 48 +++++++++++++++++++++++++----------- src/proxy/socks_proxy_doc.md | 7 ++++++ 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/doc/proxy_protocol.md b/doc/proxy_protocol.md index bf2fd43e..d5466f0e 100644 --- a/doc/proxy_protocol.md +++ b/doc/proxy_protocol.md @@ -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` сверены с реализацией. diff --git a/src/proxy/socks_proxy_doc.md b/src/proxy/socks_proxy_doc.md index d1f794a4..2c7b0079 100644 --- a/src/proxy/socks_proxy_doc.md +++ b/src/proxy/socks_proxy_doc.md @@ -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 и диагностируются, уже доставленные ответы сохраняются.