From bed99abbe94020bb76bd04de6b71ae85a76eed03 Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 24 Oct 2026 00:28:58 +0200 Subject: [PATCH] Clarify STCP client ownership, ping lifetime and handshake byte order --- src/transport_layer/stcp_client.h | 16 +++++++- src/transport_layer/stcp_client_doc.md | 54 +++++++++++++++++++------- 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/transport_layer/stcp_client.h b/src/transport_layer/stcp_client.h index 1b955e20..c88d6288 100644 --- a/src/transport_layer/stcp_client.h +++ b/src/transport_layer/stcp_client.h @@ -16,7 +16,15 @@ extern "C" { typedef void (*stcp_ready_cb)(struct stcp_conn *conn, void *arg); typedef void (*stcp_close_cb)(struct stcp_conn *conn, int err, void *arg); -/* inst используется только при запуске для выбора padding без etcp_conn; клиент не хранит ссылку на inst. */ +/* Подключиться к addr:port (getaddrinfo для прямого TCP; SOCKS5 CONNECT при socks!=NULL). + * ua/addr/keys/peer_pubkey[32] обязательны; нужен хотя бы ready_cb либо ping_cb. + * X25519-ключи и необязательный my_ed25519_pubkey[32] копируются. etcp_conn заимствован: + * handshake читает его текущие got_initial_pkt/reset_id; без него используется аргумент got_initial_pkt и эпоха 0. + * device_type/keepalive_interval (мс)/hs_flags передаются в handshake; callbacks получают соответствующие arg. + * local_addr — необязательный bind прямого сокета, reality/socks — необязательные внешние транспорты. + * timeout_ms>0 задаёт таймаут подключения/handshake, иначе STCP_CONNECT_TIMEOUT (10 с). + * inst нужен только при запуске для padding и времени REALITY, клиент не хранит ссылку на него. + * Результат — владеющий handle либо NULL при отказе запуска; последующие ошибки приходят в close_cb. */ struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uint16_t port, struct SC_MYKEYS *keys, const uint8_t *peer_pubkey, const uint8_t *my_ed25519_pubkey, @@ -34,7 +42,9 @@ struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uin struct UTUN_INSTANCE *inst); /* TCP-ping: подключается и шлёт хендшейк с флагом STCP_HANDSHAKE_FLAG_PING. * Ответчик отвечает обычным хендшейк-ответом и мягко закрывает соединение. - * По завершении хендшейка вызывается cb(success=1, rtt, arg); при ошибке/таймауте — cb(0, 0, arg). */ + * По завершении хендшейка вызывается cb(success=1, rtt в 0.1 мс, arg); при ошибке/таймауте — cb(0, 0, arg). + * NULL — отказ запуска без callback. Успешно созданная проба освобождается автоматически; + * возвращённый handle служит для ранней отмены через destroy, после завершения пробы недействителен. */ struct stcp_client *stcp_ping_send(struct UASYNC *ua, const char *addr, uint16_t port, struct SC_MYKEYS *keys, const uint8_t *peer_pubkey, const uint8_t *my_ed25519_pubkey, @@ -42,7 +52,9 @@ struct stcp_client *stcp_ping_send(struct UASYNC *ua, const char *addr, uint16_t int timeout_ms, stcp_ping_cb cb, void *arg, const struct socks_cfg *socks, struct UTUN_INSTANCE *inst, const struct reality_client_config *reality); +/* Отменить dial/закрыть conn и освободить handle; во время close освобождение откладывается. NULL допустим. */ void stcp_client_destroy(struct stcp_client *cli); +/* Заимствованный встроенный conn до destroy, доступен также до DATA и после закрытия I/O; NULL для cli==NULL. */ struct stcp_conn *stcp_client_get_conn(struct stcp_client *cli); diff --git a/src/transport_layer/stcp_client_doc.md b/src/transport_layer/stcp_client_doc.md index 454b093a..acaabd7f 100644 --- a/src/transport_layer/stcp_client_doc.md +++ b/src/transport_layer/stcp_client_doc.md @@ -1,20 +1,24 @@ # STCP Client ## 1. Назначение -TCP-клиент протокола STCP (Streaming TCP). Устанавливает TCP-соединение с STCP-сервером, выполняет X25519-handshake с передачей обфусцированных публичных ключей, согласует сессионные ключи через ECDH, после чего переходит в режим обмена данными (DATA). Каждое сообщение шифруется потоковым XOR-шифром и проверяется CRC32. Является нижним уровнем STCP-стека — поверх него работает `stcp_link`. +Клиент устанавливает прямой TCP или SOCKS5 CONNECT, при необходимости выполняет +внешний REALITY-handshake, затем STCP-handshake с X25519-ключами и эпохами ETCP. +Handshake, padding и DATA шифруются AES-128-CTR; сообщения проверяются CRC32. +Все вызовы выполняются в uasync-потоке. Для ETCP используется адаптер `stcp_link`. ## 2. Как пользоваться ### Типовой сценарий (клиент): ```c -stcp_ready_cb on_ready = (stcp_ready_cb)my_ready_handler; -stcp_close_cb on_close = (stcp_close_cb)my_close_handler; +void on_ready(struct stcp_conn *conn, void *arg); +void on_close(struct stcp_conn *conn, int err, void *arg); struct stcp_client *cli = stcp_client_connect( ua, addr, port, &my_keys, peer_pubkey_bin, my_ed25519_pubkey, 0, NULL, instance->client_type, instance->keepalive_interval, 0, on_ready, ready_ctx, NULL, NULL, on_close, close_ctx, NULL, 0, NULL, NULL, instance ); +if (!cli) return -1; // После handshake — on_ready. // Соединение доступно через stcp_client_get_conn(cli). // Tx: пакеты в conn->tx_queue → tx_cb шифрует и отправляет. @@ -22,35 +26,59 @@ struct stcp_client *cli = stcp_client_connect( // Завершение: stcp_client_destroy(cli). ``` +`cli` — владеющий handle, `stcp_client_get_conn(cli)` — заимствованный указатель на +встроенный conn. После сетевой ошибки handle остаётся владельцу до destroy; +самостоятельно освобождать conn нельзя. Контракт очередей и закрытия — в `stcp.h`. + ### Ключевые нюансы -- **Handshake**: salt(8) + обфусцированный pubkey(32) открыты; следующие 51 байт содержат Ed25519 pubkey, состояние/эпоху ETCP, padding_size, тип устройства, keepalive, flags и CRC32. Они и следующий нулевой padding шифруются одним CTR-потоком. Длина padding выбирается независимо из диапазона ETCP_CONN, иначе instance; диапазон обновляется своим таймером каждые 50–300 секунд. Приём продвигает CTR через padding, при padding_size=0 сразу завершает handshake. +- **Handshake**: salt(8) + обфусцированный pubkey(32) открыты; далее 47 байт тела и CRC32(4 LE), затем padding. + Тело: Ed25519 pubkey(32), got_initial_pkt(1), reset_id(8 BE), padding_size(2 LE), + device_type(1), keepalive_interval(2 BE), flags(1). Обязательная часть — 91 байт в каждом направлении. + Нулевой padding шифруется продолжением того же CTR-потока, отдельно от CRC тела handshake. + При наличии ETCP_CONN длина выбирается из его текущего диапазона. Без ETCP_CONN клиент сохраняет + выбранную длину при запуске; диапазон берётся у существующего пира/instance, без instance выбирается временный. + Сервер выбирает свою длину независимо. При padding_size=0 приём handshake завершается сразу. - **Стрим-шифр**: nonce — первые 12 байт `SHA256(session_key || stream_id_LE || salt(8) || "uTun3-stream")`, последние 4 байта CTR IV нулевые. `STCP_STREAM_CLIENT_SEND` использует соль клиента, `STCP_STREAM_SERVER_SEND` — соль сервера. Отправка запускается с собственной солью, приём — после чтения ответа; CTR продолжается через padding и DATA. - **Фрейминг в DATA**: `len(2 LE)` + данные + `CRC32(4)`. CRC считается от данных, весь кадр вместе с длиной шифруется stream_xor. -- **Очереди**: `tx_queue` через callback `client_tx_queue_cb` шифрует каждое сообщение и отправляет. `rx_queue` получает расшифрованные сообщения для вышележащего слоя. +- **Очереди**: `tx_queue` обслуживает общий `stcp_tx_queue_cb`; `rx_queue` получает отдельные копии расшифрованных сообщений. - **Асинхронность**: все операции через `UASYNC` — сокет в неблокирующем режиме. Поддержка частичной отправки (send_offset) и EAGAIN/WOULDBLOCK. -- **Обработка ошибок**: CRC mismatch, ошибки шифрования, сетевые ошибки — все ведут к `client_do_close` с кодом ошибки. +- **Обработка ошибок**: CRC mismatch, несовпадение ожидаемого ключа сервера, ошибки шифрования или сети закрывают conn через `stcp_conn_do_close` и вызывают close_cb. +- **Время и владение**: timeout_ms>0 задаёт интервал подключения/handshake, иначе используется 10 с. + `inst` нужен только при запуске для выбора padding и временной базы REALITY. ETCP_CONN заимствован для чтения текущих эпохи/флага при handshake. ## 3. API ### Структура `struct stcp_client` -Обёртка над `struct stcp_conn`. Хранит `peer_pubkey`, callback `ready_cb`, ссылку на `UASYNC`. +Содержит встроенный `struct stcp_conn`, ожидаемый ключ пира, callbacks, UASYNC и снимки параметров внешних транспортов. ### `stcp_client_connect()` -Создаёт TCP-сокет, подключается (асинхронно через `EINPROGRESS`), регистрирует write-коллбэк `client_connect_write_cb`. При успешном соединении: проверяет `SO_ERROR`, выводит сессионные ключи через ECDH (`client_derive_session`), отправляет handshake (`client_send_handshake`). +Возвращает владеющий handle либо NULL при отказе запуска. При прямом TCP разрешает +адрес через getaddrinfo, создаёт неблокирующий сокет, при необходимости привязывает +его к local_addr и проверяет SO_ERROR после connect. При SOCKS5 ждёт завершения CONNECT. +Затем выполняет REALITY, если задан, и STCP: выводит общий ключ и отправляет handshake. +Для обычного DATA нужен ready_cb; для пробы используется ping_cb и флаг PING. ### `stcp_client_destroy()` -Закрывает соединение через `client_do_close`, освобождает `stcp_conn`, освобождает память. +Отменяет незавершённый SOCKS5 dial или закрывает conn через `stcp_conn_do_close`. +Освобождение handle после close откладывается через uasync; встроенный conn отдельно не освобождается. +После destroy handle больше не используют. NULL допустим. ### `stcp_client_get_conn()` -Возвращает внутренний `struct stcp_conn *` для доступа к сокету и очередям. +Возвращает заимствованный встроенный `struct stcp_conn *`, также до готовности DATA +и после закрытия I/O. Для cli==NULL возвращает NULL. ### Коллбэки - **`stcp_ready_cb`**: вызывается при успешном handshake (переход в `STCP_STATE_DATA`). - **`stcp_close_cb`**: вызывается при закрытии или ошибке соединения, получает код ошибки. +`stcp_ping_send` создаёт автоматически освобождаемую пробу. Callback вызывается один +раз с успехом и RTT в 0.1 мс либо с `(0, 0, arg)` при ошибке. Возвращённый handle +можно использовать для ранней отмены через destroy; после завершения пробы он недействителен. + ### Внутренние функции - **`client_derive_session()`** — вычисляет session_key через ECDH. Потоки инициализируются после получения соответствующих солей. - **`client_send_handshake()`** — формирует и отправляет handshake-пакет (salt + обфусцированный pubkey + зашифрованный padding+CRC + padding). -- **`process_server_response()`** — деобфусцирует pubkey сервера, расшифровывает и проверяет CRC ответа сервера. -- **`stcp_conn_process_recv()`** — конечный автомат разбора входящих данных: в HS-состоянии ждёт полный ответ, в DATA — разбирает фреймы (len + data + CRC). -- **`encrypt_and_crc()` / `decrypt_and_check()`** — шифрование/расшифровка фрейма с CRC32 (stream_xor + проверка целостности). +- **`client_hs_cb()`** — проверяет ожидаемый ключ сервера, расшифровывает тело ответа и проверяет CRC/размер padding. +- **`client_hs_padding_cb()`** — продвигает приёмный CTR через padding, завершает таймер handshake и сообщает DATA либо результат PING. +- **`stcp_recv_try()` / `stcp_recv_set()`** — общий поэтапный приём handshake и DATA. +- **`stcp_frame_encrypt()` / `stcp_frame_decrypt()`** — общие CRC и CTR для тела кадра; длину DATA шифрует общий TX-код отдельно тем же потоком.