From 477d8f995b2e01dbc15de96ba042f8a9edc19242 Mon Sep 17 00:00:00 2001 From: evgeny Date: Fri, 23 Oct 2026 21:59:12 +0200 Subject: [PATCH] Document STCP adapter ownership, callbacks and reconnect lifecycle --- src/transport_layer/stcp_link_doc.md | 114 +++++++++++++-------------- 1 file changed, 55 insertions(+), 59 deletions(-) diff --git a/src/transport_layer/stcp_link_doc.md b/src/transport_layer/stcp_link_doc.md index 8c0de8f4..e09a5274 100644 --- a/src/transport_layer/stcp_link_doc.md +++ b/src/transport_layer/stcp_link_doc.md @@ -1,73 +1,69 @@ -# STCP Link +# STCP как транспорт ETCP -## 1. Назначение -Прослойка-мост между STCP-транспортом (TCP с потоковым шифрованием) и ETCP-уровнем (маршрутизация, балансировка, управление соединениями). Инкапсулирует создание STCP-сервера или клиента, настройку очередей приёма/передачи и диспетчеризацию входящих пакетов в `api_bindings` экземпляра `UTUN_INSTANCE`. По сути — STCP как транспортный линк для ETCP. +Нормативный контракт API находится в `stcp_link.h`, переходы физического линка — в `etcp_connections.h`. +Все операции выполняются в uasync-потоке экземпляра ядра. -## 2. Как пользоваться +## Создание и владение -### Сервер (приём входящих соединений): -```c -struct stcp_link_config cfg = { - .ua = ua, .my_keys = &my_keys, .inst = utun_instance, - .peer_pubkey = NULL, .remote_addr = NULL, -}; -struct stcp_server *srv = stcp_server_listen(&cfg, port, on_link_cb, NULL); -// При входящем соединении вызывается on_link_cb со stcp_link. -// Остановка: stcp_link_server_destroy(srv). -``` +Сервер создаётся через `stcp_server_listen(&cfg, port, on_link, tcp_sock)`. +`cfg` содержит `inst`, `listen_family` (0/AF_INET/AF_INET6) и `reality_enabled`; +ключи, event loop и настройки REALITY берутся из instance. Порт 0 выбирает ОС, +фактическое значение возвращает `stcp_link_server_get_port()`. +После handshake `on_link(link, tcp_sock)` передаёт готовый адаптер владельцу. +Уничтожение слушателя закрывает также принятые им STCP-соединения. -### Клиент (исходящее соединение): -```c -struct stcp_link_config cfg = { - .ua = ua, .my_keys = &my_keys, .inst = utun_instance, - .peer_pubkey = server_pubkey, .peer_pubkey_mode = 0, - .remote_addr = (struct sockaddr_storage *)&addr, .remote_port = port, -}; -struct stcp_link *link = stcp_link_connect(&cfg); -// По готовности: stcp_link_is_ready(link) == 1, или через stcp_link_set_on_ready. -// Отправка: stcp_link_send(link, data, len). -// Закрытие: stcp_link_close(link). -``` +Исходящий адаптер создаёт `stcp_link_connect(etcp_link, remote_addr, port)`. +Родительские `ETCP_CONN` и `UTUN_INSTANCE` уже существуют; ключ пира берётся из +`etcp_link->etcp->crypto_ctx`. Локальный bind задаёт `etcp_link->conn`, NULL означает выбор адреса ОС. +При `port=0` используется порт из `remote_addr`. REALITY и SOCKS выбираются по параметрам линка/instance. +NULL означает отказ запуска без callback. Для возвращённого handle ошибки TCP connect и handshake +также вызывают установленный `on_close`, даже если READY ещё не было. +Callbacks устанавливаются сразу после возврата connect, до следующего poll. -### Ключевые нюансы -- **ETCP_CONN**: каждый `stcp_link` содержит встроенный `struct ETCP_CONN` (`etcp_conn`), который используется для диспетчеризации через `inst->api_bindings`. Поле `transport_link` служит обратным указателем на `stcp_link`. -- **Диспетчеризация RX**: входящие пакеты из `rx_queue` обрабатываются коллбэком `link_rx_cb`, который по первому байту (`id`) демультиплексирует пакет в соответствующий обработчик `api_bindings.callbacks[id]`. Если конкретный обработчик не найден — пакет уходит в `callbacks[0]`. -- **Очередь TX**: `tx_queue` настраивается с `queue_set_waiter_defer(1)` — отправка через пороговое ожидание (backpressure). Очередь связывается с `stcp_conn` через `stcp_conn_set_tx_queue`. -- **Разница сервер/клиент**: сервер при accept создаёт `stcp_link` в коллбэке `server_accept_cb`, клиент — при ready в `client_ready_cb`. Настройка очередей идентична. -- **Закрытие**: `stcp_link_close` корректно очищает `transit_queues` (очереди ETCP-трафика в пути), `rx_queue`, `tx_queue`, уничтожает `stcp_conn` (сервер) или `stcp_client` (клиент). +Адаптер хранит заимствованные указатели на физический линк и соединение, а не встроенный `ETCP_CONN`. +Он владеет RX/TX очередями; низкоуровневый `stcp_conn` только заимствует их. +После закрытия raw STCP указатель на него очищается, поскольку серверный conn освобождается автоматически. +Клиентский raw conn принадлежит `stcp_client` до его уничтожения. -## 3. API +## Данные и готовность -### Структура `struct stcp_link_config` -Конфигурация для создания линка. Содержит: -- `ua` — экземпляр UASYNC (один на поток) -- `my_keys` — ключи этой стороны для ECDH -- `inst` — UTUN_INSTANCE для диспетчеризации через api_bindings -- `peer_pubkey` / `peer_pubkey_mode` — публичный ключ пира (binary или hex) -- `remote_addr` / `remote_port` — адрес пира (только для клиента) +`stcp_link_send(link, data, len)` копирует пакет в TX очередь: 0 при успехе, -1 при отказе. +Успех означает постановку в очередь, а не доставку пиру. ETCP передаёт ограниченные размером своей кодограммы пакеты. +RX очередь ограничена порогами STCP; возобновление потребления снимает backpressure чтения TCP. +Обычный RX восстанавливает `ETCP_DGRAM` и вызывает `etcp_packet_decrypted()` с физическим линком; +проверка сессии и дальнейшая доставка выполняются ETCP. Прямой dispatch в `api_bindings` — резервный путь без физического владельца. -### Структура `struct stcp_link` (opaque) -Содержит ссылки на `stcp_client`/`stcp_conn`, очереди RX/TX, встроенный `ETCP_CONN`, флаг `ready`, коллбэки on_ready/on_close. +STCP READY означает завершённый криптографический handshake, но не завершённое согласование +ETCP-сессии и не READY topo-группы. Клиент сначала вызывает `etcp_link_enter_ready_tcp()`, +затем пользовательский `on_ready`. Если callback перехода закрыл транспорт/соединение, +дальнейшие действия перехода и пользовательский READY прекращаются. +ETCP удерживает соединение на время перехода и после callbacks проверяет, что адаптер всё ещё привязан к тому же линку. -### `stcp_server_listen()` -Оборачивает `stcp_server_create()`, передавая `server_accept_cb` в качестве коллбэка при входящем соединении. При accept создаёт `stcp_link`, настраивает очереди и вызывает пользовательский `on_link`. +## Закрытие и восстановление -### `stcp_link_connect()` -Создаёт `stcp_link`, подготавливает pubkey пира (конвертация hex→bin при необходимости), вызывает `stcp_client_connect()`. При успешном handshake (коллбэк `client_ready_cb`) настраивает очереди RX/TX и вызывает `on_ready_cb`. +`stcp_link_close()` немедленно запрещает send/RX/READY, отвязывает родительский линк, +снимает обработчики очередей и отключает чтение/запись сокета. Он подавляет `on_close`: +это уведомление предназначено для неожиданного разрыва, а не явной отмены владельцем. +Освобождение очередей, raw STCP/client и самого адаптера выполняется через `call_soon`. +Повторный close внутри активного callback безопасен; после возврата в event loop handle использовать нельзя. -### `stcp_link_close()` -Полная очистка: освобождает `transit_queues` (с отменой waiter'ов), `rx_queue`, `tx_queue`, уничтожает `stcp_conn` или `stcp_client`. +Неожиданный разрыв доставляет `on_close` один раз. Владелец освобождает адаптер через `stcp_link_close()`, +в том числе из самого callback. Серверный ETCP_LINK закрывается; исходящий сохраняется для повторной попытки. +ETCP владеет состоянием физического линка и backoff: 1, 2, 4, 8, 16, затем не более 30 секунд. +Синхронный отказ запуска и асинхронный разрыв используют одну последовательность. +Успешный STCP handshake сбрасывает backoff; повторный запрос при уже поставленном таймере не переносит его срок. +Закрытие ETCP_LINK отменяет повторное подключение. Если обязательный таймер поставить невозможно, +ETCP закрывает соединение с диагностикой вместо сохранения зависшего пути. -### `stcp_link_send()` -Отправляет сырые данные через STCP-линк: создаёт `ll_entry` с данными, кладёт в `conn->tx_queue`. Шифруется и отправляется коллбэком `tx_cb` из stcp_client/stcp_server. +`etcp_link_close()` освобождает физический линк синхронно. Callback может закрыть этот линк, +соседний линк или всё соединение; повторный close уже закрываемого линка игнорируется. +Поэтому через callback нельзя сохранять следующий элемент списка и продолжать обход по старому указателю. -### `stcp_link_is_ready()` -Возвращает 1 если линк готов к передаче данных (handshake завершён). +## Проверки -### `stcp_link_get_etcp_conn()` -Возвращает `struct ETCP_CONN *` для передачи в ETCP-уровень (маршрутизатор, балансировщик и т.д.). - -### Коллбэки -- **`stcp_link_set_on_ready()`** — вызывается когда линк переходит в состояние готовности. -- **`stcp_link_set_on_close()`** — вызывается при закрытии линка. -- **`stcp_server_on_link_cb`** — коллбэк сервера при новом входящем соединении. +`test_transport` проверяет передачу, неверный ключ, ранний отказ connect, таймаут handshake, +отмену до первого poll и повторный close из callback. +`test_etcp_transport` проверяет передачу и восстановление после рестарта пира, +закрытие из READY/DOWN для клиента и сервера, plain STCP и REALITY, +отсутствие поздних READY, backoff, его сброс после восстановления и отмену повторного подключения. +Проверяется баланс сокетов, таймеров и учитываемых выделений памяти.