Clarify STCP client ownership, ping lifetime and handshake byte order
This commit is contained in:
1 parent
d6eecd6f1c
commit
bed99abbe9
2 files changed
+55
-15
No files matched your search
@@ -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);
|
||||
|
||||
|
||||
|
||||
@@ -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-код отдельно тем же потоком.
|
||||
Reference in new issue
Block a user