Document STCP adapter ownership, callbacks and reconnect lifecycle
This commit is contained in:
1 parent
03d69cb772
commit
477d8f995b
1 file changed
+55
-59
@@ -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, его сброс после восстановления и отмену повторного подключения.
|
||||
Проверяется баланс сокетов, таймеров и учитываемых выделений памяти.
|
||||
Reference in new issue
Block a user