Document STCP listener arguments, accepted ownership and current handshake phases

This commit is contained in:
evgeny committed 2026-10-24 00:27:56 +02:00
1 parent ecdd767934
commit d6eecd6f1c
2 files changed
+33 -8

No files matched your search

+6
View File
@@ -17,6 +17,11 @@ struct UTUN_INSTANCE;
typedef void (*stcp_connect_cb)(struct stcp_conn *conn, void *arg);
typedef void (*stcp_close_cb)(struct stcp_conn *conn, int err, void *arg);
/* Создать listener на всех адресах family: AF_INET6 — IPv6-only, иначе IPv4; port=0 выбирает ОС.
* ua/keys/connect_cb обязательны; X25519-ключи и необязательный my_ed25519_pubkey[32] копируются.
* inst заимствован до destroy для padding/эпох ETCP; NULL допустим для самостоятельного STCP.
* connect_cb(conn,arg) получает готовый заимствованный conn, close_cb(conn,err,close_arg) — его закрытие.
* Результат — владеющий server handle или NULL при ошибке; принятые conn освобождает серверный STCP. */
struct stcp_server *stcp_server_create(struct UASYNC *ua, uint16_t port,
struct SC_MYKEYS *keys,
const uint8_t *my_ed25519_pubkey,
@@ -26,6 +31,7 @@ struct stcp_server *stcp_server_create(struct UASYNC *ua, uint16_t port,
int family);
// Фактический TCP-порт слушателя (port=0 при create выбирает ОС); 0 при ошибке.
uint16_t stcp_server_get_port(const struct stcp_server *srv);
/* Закрыть listener и все принятые conn с их close callbacks; освободить srv. NULL допустим. */
void stcp_server_destroy(struct stcp_server *srv);
// Включить REALITY-камуфляж на этом TCP-сервере: перед STCP-хендшейком сервер
+27 -8
View File
@@ -10,21 +10,31 @@ STCP Server — серверная сторона протокола STCP. Со
```c
struct stcp_server *srv = stcp_server_create(ua, port, &my_keys,
my_ed25519_pubkey, instance,
on_connect_cb, conn_arg, // вызывается после успешного рукопожатия
on_close_cb, close_arg); // вызывается при закрытии/ошибке
on_close_cb, close_arg, // вызывается при закрытии/ошибке
AF_INET); // AF_INET6 — отдельный IPv6-only listener
if (!srv) return -1;
uint16_t actual_port = stcp_server_get_port(srv); // port=0 выбирает ОС
// ... use ...
stcp_server_destroy(srv);
```
После завершения рукопожатия соединение переходит в `STCP_STATE_DATA`, вызывается `connect_cb`. Вышестоящий код может слать сообщения через `tx_queue` — они будут зашифрованы с CRC32 и отправлены. Входящие расшифрованные сообщения попадают в `rx_queue`.
После завершения обычного рукопожатия соединение переходит в `STCP_STATE_DATA`, вызывается
`connect_cb(conn, arg)`. Он получает заимствованный conn; память отдельно не освобождается вызывающим.
`tx_queue` принимает сообщения для шифрования с CRC32, `rx_queue` — отдельные копии расшифрованных сообщений.
Все операции выполняются в uasync-потоке. Полный контракт очередей и close находится в `stcp.h`.
Ключевые нюансы:
- Сервер принимает клиентский pubkey+salt (обфусцированный), извлекает pubkey через `sc_obfuscate_pubkey`, выполняет ECDH.
- Отправляет ответ: открытый блок salt/pubkey, зашифрованное тело handshake с CRC32 и зашифрованный padding.
- После рукопожатая трафик шифруется потоковым AES-CTR (XOR), для каждого направления — свой stream.
- Рукопожатие обрабатывается в два этапа: `process_client_handshake` (извлечение ключа, дешифровка) → `finish_client_handshake` (отправка ответа, переход в DATA).
- Handshake, padding и последующие кадры DATA используют непрерывный AES-128-CTR stream каждого направления.
- `server_hs_phase1_cb` разбирает обязательные 91 байт, `server_hs_phase2_cb` принимает padding и формирует ответ.
- Флаг `STCP_HANDSHAKE_FLAG_PING` вызывает ответ и мягкое закрытие пробы без `connect_cb` и DATA-сессии.
- `stcp_server_set_reality` включает внешний REALITY-handshake и relay неавторизованных клиентов; параметры — `reality_config`.
## 3. API
@@ -32,13 +42,22 @@ stcp_server_destroy(srv);
Слушающий сокет, ключи сервера, коллбэки `connect_cb` / `close_cb`. Не экспортируется, создаётся только через `stcp_server_create`.
### Функции
- `stcp_server_create(ua, port, keys, connect_cb, arg, close_cb, close_arg)` — создать сервер: открыть TCP-сокет, bind к порту (IPV4, INADDR_ANY), listen(16), зарегистрировать accept-коллбэк. При приёме соединения создаёт `struct stcp_conn` в состоянии `STCP_STATE_HS_SERVER_WAIT`.
- `stcp_server_destroy(srv)` — закрыть слушающий сокет, освободить память.
- `stcp_server_create(ua, port, keys, ed25519_pubkey, inst, connect_cb, arg, close_cb, close_arg, family)` —
создать TCP listener на всех адресах IPv4 или IPv6, `listen(16)`, зарегистрировать accept callback;
результат — владеющий handle либо NULL. Ключи копируются, `ua/inst` заимствуются до destroy.
- `stcp_server_get_port(srv)` — фактический порт listener, 0 при ошибке.
- `stcp_server_destroy(srv)` — закрыть listener и все принятые conn, включая вызовы их close callbacks, затем освободить сервер.
### Протокол рукопожатия (серверная сторона)
1. Сервер читает `SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_CLIENT` байт (открытый salt/pubkey и 51 байт зашифрованного тела).
2. Деобфусцирует клиентский pubkey через XOR с `SHA256(salt || server_pubkey)`.
2. Деобфусцирует 32-байтный клиентский pubkey через XOR с `SHA256(salt || server_pubkey)`.
3. Выполняет ECDH (`sc_init_ctx` + `sc_set_peer_public_key`) → получает `session_key`, запускает `stream_recv` с клиентской солью. Для ответа генерирует собственную соль и запускает `stream_send` с ней; соль входит в вывод CTR IV вместе с ключом и направлением.
4. Расшифровывает тело, проверяет CRC32 и границы padding_size, затем принимает и расшифровывает padding.
5. Формирует ответ: открытые salt2(8) + обфусцированный pubkey(32), затем зашифрованные 51 байт handshake и нулевой padding случайной длины. Диапазон берётся у существующего ETCP_CONN, иначе у instance. Приём padding продвигает CTR; при длине 0 handshake завершается сразу.
6. Переходит в `STCP_STATE_DATA`, вызывает `on_ready` (== `connect_cb`).
6. Для обычного соединения переходит в `STCP_STATE_DATA` и вызывает `connect_cb`; для PING завершает пробу.
47 байт тела handshake: Ed25519 pubkey(32), got_initial_pkt(1), reset_id(8 BE),
padding_size(2 LE), device_type(1), keepalive_interval(2 BE), flags(1).
CRC32(4 LE) считается по этому телу и шифруется вместе с ним. Размер обязательной
части каждого направления — 40 + 47 + 4 = 91 байт; padding не входит в CRC тела handshake.
`reset_id` — собственная эпоха отправителя, подтверждение новой ETCP-сессии выполняет `etcp_session`.