Document STCP adapter ownership, callbacks and reconnect lifecycle

This commit is contained in:
evgeny committed 2026-10-23 21:59:12 +02:00
1 parent 03d69cb772
commit 477d8f995b
1 file changed
+55 -59
+55 -59
View File
@@ -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, его сброс после восстановления и отмену повторного подключения.
Проверяется баланс сокетов, таймеров и учитываемых выделений памяти.