docs: align ETCP handshake and epoch descriptions with code
This commit is contained in:
1 parent
f59f64ea48
commit
d644126cac
3 files changed
+81
-47
No files matched your search
+67
-38
@@ -65,8 +65,12 @@ Burst-измерения bandwidth (0x07, 0x08) зарезервированы,
|
||||
|
||||
шифруется строго ВСЁ включая ВСЕ заголовки (кроме обфусцированного publickey в INIT-пакетах).
|
||||
timestamp вставляется ВСЕГДА ВО ВСЕ КОДОГРАММЫ (скрыто в шифрованном заголовке).
|
||||
Итого шифруется: 3 байта заголовка + (data_len - noencrypt_len) байт данных.
|
||||
2. Опциональные секции (одна или несколько) добавляются в etcp.c (внутри data[], без доп. заголовка):
|
||||
Для DATA/ACK после этих 3 байт добавляется обязательный заголовок сессии:
|
||||
[0x09][sender_epoch(8 BE)][receiver_epoch(8 BE)], затем идут секции из etcp.c.
|
||||
Итого шифруется: 3 + 17 + data_len байт для DATA/ACK;
|
||||
для остальных кодограмм — 3 + (data_len - noencrypt_len) байт.
|
||||
Формат и подтверждение эпох описаны в разделе «Подтверждение транспортной сессии» ниже.
|
||||
2. Опциональные секции (одна или несколько) добавляются в etcp.c внутри DATA/ACK после заголовка сессии:
|
||||
а) Подтверждения (ACK) - заголовок 0x01:
|
||||
[0x01][count][last_delivered_id(4 байта LE)][rx_dup_count(2 байта LE)][[seq(4 LE)][recv_ts(2 LE)][dly(2 LE)] × count]
|
||||
- count: количество ACK записей (1 байт; до 32 ограничено размером пакета)
|
||||
@@ -114,56 +118,80 @@ Burst-измерения bandwidth (0x07, 0x08) зарезервированы,
|
||||
- клиент отправляет init запрос и выставляет таймаут
|
||||
- по таймауту повторяет init запросы с экспоненциально растущим интервалом (начальный 500ms, макс 50s, +25% каждые 10 попыток), ведется счетчик запросов init.
|
||||
- когда клиент получает init подтверждение — снимает таймаут, выставляет link_state=connected, запускает keepalive.
|
||||
- сервер при получении init запроса: если линк существует и session_id совпал — отвечает без сброса (0x05); если session_id новый — выполняет etcp_conn_reinit и отвечает со сбросом (0x03).
|
||||
При добавлении нового канала: если link_state=1 (handshake) — посылаем INIT со сбросом (0x02); если link_state=2 (reconnect) — без сброса (0x04).
|
||||
- каждая сторона сообщает в INIT собственный reset_id (64 бита, BE); ответ не повторяет эпоху запроса.
|
||||
- первая неизвестная эпоха пира сохраняется из handshake. Смена уже известной эпохи запускает challenge;
|
||||
только подходящий CONFIRM сбрасывает поток и заменяет эпоху пира, сохраняя собственную.
|
||||
- повтор INIT и коллизия встречных подключений сами по себе не сбрасывают поток.
|
||||
При добавлении нового канала: если link_state=1 (handshake) — посылаем INIT_REQUEST (0x02);
|
||||
если link_state=2 (reconnect) — INIT_REQUEST_NOINIT (0x04). Коды INIT не заменяют подтверждение смены эпохи.
|
||||
link_state: 0=just init, 1=handshake, 2=reconnect, 3=connected.
|
||||
|
||||
**** Формат кодограмм для etcp_connections.c/h ****
|
||||
Кодограммы с этими секциями обрабатываются в etcp_connections (в этих кодограммах всегда одна секция-тип пакета). В обязательном заголовке (timestamp+flag_up) ID не используется.
|
||||
|
||||
Типы пакетов:
|
||||
0x02 — ETCP_INIT_REQUEST (со сбросом etcp сессии)
|
||||
0x03 — ETCP_INIT_RESPONSE (подтверждение со сбросом)
|
||||
0x04 — ETCP_INIT_REQUEST_NOINIT (без сброса)
|
||||
0x05 — ETCP_INIT_RESPONSE_NOINIT (подтверждение без сброса)
|
||||
0x02 — ETCP_INIT_REQUEST (начальный handshake линка)
|
||||
0x03 — ETCP_INIT_RESPONSE (вариант ответа INIT)
|
||||
0x04 — ETCP_INIT_REQUEST_NOINIT (восстановление линка, также разрешение коллизии)
|
||||
0x05 — ETCP_INIT_RESPONSE_NOINIT (вариант ответа INIT)
|
||||
0x06 — ETCP_PING (проверка доступности)
|
||||
0x07 — ETCP_PONG (ответ на пинг)
|
||||
0x08 — ETCP_KEEPALIVE
|
||||
0x09 — ETCP_SESSION_DATA (обёртка секций DATA/ACK с эпохами)
|
||||
0x0a — ETCP_SESSION_HELLO
|
||||
0x0b — ETCP_SESSION_CHALLENGE
|
||||
0x0c — ETCP_SESSION_CONFIRM
|
||||
0x0d — ETCP_TCP_ADDRESS (STCP: code + family(4/6) + observed IP(16), без порта)
|
||||
|
||||
Варианты ответа 0x03/0x05 не определяют смену эпохи: клиент в обоих случаях обрабатывает reset_id одинаково.
|
||||
|
||||
1) Init запрос (0x02 / 0x04):
|
||||
[code(1)] [my_node_id(8 BE)] [session_id(4 BE)] [mtu_local(2)] [keepalive_interval(2)]
|
||||
[recovery_interval/100(2)] [local_link_id(1)] [sock_id(1)] [only_local(1)] [type(1)]
|
||||
[code(1)] [node_id(8 BE)] [reset_id(8 BE)] [mtu(2 BE)] [keepalive(2 BE)]
|
||||
[recovery(2 BE)] [link_id(1)] [socket_id(1)] [only_local(1)] [type(1)]
|
||||
[src_ipv4(4)] [src_port(2 BE)] [collision(1)] [ed25519_pubkey(32)] [device_type(1)]
|
||||
[random_padding...] [salt(SC_PUBKEY_ENC_SALT_SIZE)] [obfuscated_pubkey(SC_PUBKEY_SIZE)]
|
||||
- code: 0x02 (со сбросом) или 0x04 (без сброса)
|
||||
Фиксированное тело до padding — 67 байт (ETCP_INIT_REQUEST_PKT).
|
||||
- code: 0x02 или 0x04, вариант handshake линка
|
||||
- node_id: 64 бита, идентификатор узла (big-endian)
|
||||
- session_id: 32 бита, случайный ID сессии генерируется клиентом (big-endian)
|
||||
- mtu_local: uint16_t локальный MTU
|
||||
- keepalive_interval: uint16_t интервал keepalive в мс
|
||||
- recovery_interval: uint16_t = recovery_interval/100 (в таймбазах 0.1мс / 100)
|
||||
- local_link_id: uint8_t (0-255), назначается отправителем
|
||||
- sock_id: uint8_t, уникальный ID сокета отправителя
|
||||
- reset_id: uint64_t, собственная локальная эпоха потока клиента (big-endian)
|
||||
- mtu: uint16_t локальный MTU
|
||||
- keepalive: uint16_t интервал keepalive в мс
|
||||
- recovery: uint16_t = link->recovery_interval/100 (исходная единица — 0.1 мс)
|
||||
- link_id: uint8_t (0-255), назначается отправителем
|
||||
- socket_id: uint8_t, уникальный ID сокета отправителя
|
||||
- only_local: uint8_t, флаг "только локальные подключения"
|
||||
- type: uint8_t, тип сокета (CFG_SERVER_TYPE_UNKNOWN=0/PUBLIC=1/NAT=2/PRIVATE=3/LOCAL=4)
|
||||
- type: uint8_t, тип сокета (CFG_SERVER_TYPE_UNKNOWN=0/PUBLIC=1/NAT=2/PRIVATE=3/LOCAL=4)
|
||||
- src_ipv4/src_port: адрес интерфейса отправителя; для IPv6 src_ipv4=0, порт сохраняется;
|
||||
при SOCKS5 оба поля нулевые. Это заявленный адрес, а не наблюдение внешнего NAT-адреса.
|
||||
- collision: 1 — отправитель разрешает встречное подключение, заявляя роль master для линка
|
||||
- ed25519_pubkey: публичный ключ Ed25519 отправителя, 32 байта
|
||||
- device_type: CLIENT_TYPE_SERVER/DESKTOP/MOBILE
|
||||
- random_padding: случайные байты до размера handshake (защита от fingerprinting)
|
||||
- salt + obfuscated_pubkey: публичный ключ с обфускацией (не шифруется AES,
|
||||
но скрывается XOR с salt и собственным публичным ключом). Передаётся в конце пакета
|
||||
в noencrypt_len байтах (не попадает под AES-шифрование)
|
||||
Инициирует новый connection для tcp instance. Если tcp instance нет (первое подключение) - создаёт.
|
||||
Между нодами только одно подключение, но можно добавлять каналы.
|
||||
INIT устанавливает UDP-линк: создаёт или переиспользует общий ETCP_CONN к пиру.
|
||||
Между узлами одно ETCP-соединение с несколькими линками; TCP-линк использует STCP handshake.
|
||||
|
||||
2) Init подтверждение (0x03 / 0x05):
|
||||
[code(1)] [my_node_id(8 BE)] [session_id(4 BE)] [mtu_local(2)] [local_link_id(1)]
|
||||
[remote_socket_id(1)] [only_local(1)] [type(1)] [peer_ipv4(4)] [peer_port(2)]
|
||||
[random_padding...]
|
||||
- code: 0x03 (со сбросом) или 0x05 (без сброса)
|
||||
[code(1)] [node_id(8 BE)] [reset_id(8 BE)] [mtu(2 BE)] [link_id(1)]
|
||||
[remote_socket_id(1)] [only_local(1)] [type(1)] [peer_ipv4(4)] [peer_port(2 BE)]
|
||||
[ed25519_pubkey(32)] [device_type(1)] [keepalive(2 BE)] [random_padding...]
|
||||
Фиксированное тело до padding — 64 байта (ETCP_INIT_RESPONSE_PKT).
|
||||
- code: 0x03 или 0x05, вариант ответа INIT; сам по себе не сбрасывает поток клиента
|
||||
- node_id: 64 бита, big-endian
|
||||
- session_id: 32 бита, big-endian (должен совпасть с запросом клиента; если нет — клиент игнорирует)
|
||||
- mtu_local: uint16_t локальный MTU сервера
|
||||
- local_link_id: uint8_t идентификатор канала сервера
|
||||
- reset_id: uint64_t, собственная локальная эпоха потока сервера (big-endian), не эхо запроса
|
||||
- mtu: uint16_t локальный MTU сервера
|
||||
- link_id: uint8_t идентификатор канала сервера
|
||||
- remote_socket_id: uint8_t (ID серверного сокета; ID клиентского сокета известен серверу из запроса)
|
||||
- only_local: uint8_t флаг сервера
|
||||
- type: uint8_t тип сокета сервера
|
||||
- peer_ipv4: 4 байта (IPv4 адрес клиента как его видит сервер — для NAT traversal)
|
||||
- peer_port: 2 байта (порт клиента как его видит сервер)
|
||||
- peer_ipv4: 4 байта (IPv4 адрес клиента как его видит сервер, в сетевом порядке)
|
||||
- peer_port: uint16_t (порт клиента как его видит сервер, big-endian)
|
||||
При IPv6 оба поля нулевые.
|
||||
- ed25519_pubkey: публичный ключ Ed25519 сервера, 32 байта
|
||||
- device_type: CLIENT_TYPE_SERVER/DESKTOP/MOBILE
|
||||
- keepalive: uint16_t интервал keepalive сервера в мс
|
||||
- random_padding: случайные байты
|
||||
|
||||
3) PING (0x06):
|
||||
@@ -183,9 +211,9 @@ link_state: 0=just init, 1=handshake, 2=reconnect, 3=connected.
|
||||
ADDRESS_CHECK (0x08) — аутентифицированное входящее испытание endpoint.
|
||||
Модель NAT и алгоритм владельца: doc/nat_detection.txt.
|
||||
|
||||
При получении init получатель пакета должен:
|
||||
- reset ETCP_LINK с этим ip_port если он есть
|
||||
- создать новый ETCP_CONN с этим node_id. если уже существует подключение с этим node_id - проверить ключи на совпадение.
|
||||
При получении INIT получатель создаёт или переиспользует ETCP_CONN и подходящий линк;
|
||||
для существующего соединения проверяет совпадение ключа пира. Эпоха из INIT учитывается
|
||||
через etcp_conn_apply_peer_reset_id, без немедленного сброса потока при смене известной эпохи.
|
||||
приём кодограмм из UDP выглядит так:
|
||||
uasync select -> etcp_connection.c etcp_connections_read_callback: memory_pool_alloc, decrypt (or init) -> etcp_conn_input (etcp.c/h - сам tcp механизм) -> ETCP_CONN output_queue
|
||||
|
||||
@@ -217,14 +245,14 @@ Padding keepalive, PING/PONG и STCP-handshake:
|
||||
|
||||
Клиент: если все линки =down то начинается процедура восстановления связи:
|
||||
- клиент посылает init без сброса (0x04), link_state=2
|
||||
- сервер как получает init проверяет session_id:
|
||||
- если session_id совпадает — шлёт подтверждение без сброса (0x05)
|
||||
- если session_id изменился (клиент перезапустился) — шлёт подтверждение со сбросом (0x03)
|
||||
- сервер восстанавливает физический линк и отвечает INIT_RESPONSE/INIT_RESPONSE_NOINIT со своей эпохой.
|
||||
- прежняя эпоха пира не меняет поток; смена известной эпохи подтверждается через CHALLENGE/CONFIRM.
|
||||
Код ответа 0x03/0x05 не является подтверждением смены эпохи.
|
||||
|
||||
**** Connection session confirmation (2026-09) ****
|
||||
**** Подтверждение транспортной сессии (etcp_session.c/h) ****
|
||||
Physical UDP INIT / STCP handshake establishes an authenticated link. A stream reset
|
||||
uses the same control protocol on every initialized UDP or TCP link, bypassing the
|
||||
normalizer and reliable DATA/ACK queues. A physical reset does not generate DOWN/UP.
|
||||
normalizer and reliable DATA/ACK queues. A stream reset does not generate physical DOWN/UP.
|
||||
|
||||
Encrypted DATA/ACK plaintext after timestamp(2) + flag_up(1):
|
||||
09 | sender_epoch:u64be | receiver_epoch:u64be | existing ACK/DATA sections
|
||||
@@ -237,7 +265,8 @@ Control frames (all 25 bytes after timestamp/flag):
|
||||
0c CONFIRM | sender_epoch | receiver_epoch | echoed_cookie:u64be
|
||||
|
||||
Each connection owns one pending peer epoch, fresh challenge, and retry timer.
|
||||
HELLO and a changed epoch advertised by INIT only request a challenge. Only a
|
||||
The first nonzero peer epoch from a physical handshake is stored directly if unknown.
|
||||
HELLO and a change to an already known epoch advertised by INIT only request a challenge. Only a
|
||||
CONFIRM for the current local epoch and pending random challenge commits a peer
|
||||
epoch. Changing the peer epoch resets the stream but preserves our own epoch.
|
||||
A local fatal reset generates a new local epoch and blocks DATA until confirmation.
|
||||
|
||||
@@ -203,9 +203,9 @@ struct ETCP_CONN {
|
||||
|
||||
// Flags
|
||||
uint8_t got_initial_pkt; //
|
||||
uint8_t initialized; // 0 - только созданный ETCP, 1 - хотя бы один линк проинициалзирован (обмен ключами произведен)
|
||||
uint8_t initialized; // 1 — поток готов; 0 — создание или reset, в том числе ожидание подтверждения сессии
|
||||
uint64_t reset_id; // локальная эпоха потока (меняется только при локальном reset)
|
||||
uint64_t peer_reset_id; // подтверждённая эпоха потока пира
|
||||
uint64_t peer_reset_id; // эпоха пира: первая из handshake, смена — только после CONFIRM
|
||||
uint64_t session_candidate, session_challenge, session_started; // эпоха-кандидат, challenge, время начала (0.1 ms)
|
||||
void* session_timer; // таймер согласования транспортной сессии
|
||||
uint8_t session_required; // только подтверждение challenge может завершить локальный reset
|
||||
@@ -291,12 +291,16 @@ void etcp_conn_ref_free(struct ETCP_CONN* conn);
|
||||
void etcp_conn_reset(struct ETCP_CONN* etcp);
|
||||
void etcp_links_reset(struct ETCP_CONN* etcp);
|
||||
|
||||
/* Локальный reset: эквивалент fatal_reinit(); etcp — соединение, reason — причина для лога. Возврата нет. */
|
||||
void etcp_conn_reinit(struct ETCP_CONN* etcp, const char* reason);
|
||||
// Реинит с конкретным reset_id (для adopt чужой эпохи) — без генерации нового id
|
||||
/* Сбросить поток etcp с заданной локальной эпохой reset_id; reason — причина для лога. Возврата нет.
|
||||
* Не генерирует эпоху и не запускает подтверждение. При подтверждённой смене эпохи пира передают текущий etcp->reset_id. */
|
||||
void etcp_conn_reinit_id(struct ETCP_CONN* etcp, const char* reason, uint64_t reset_id);
|
||||
// Фатальный ресет (normalizer desync / bad fragment size): генерит новую эпоху reset_id
|
||||
/* Сгенерировать новую локальную эпоху, сбросить поток etcp и запустить подтверждение сессии.
|
||||
* reason — причина для лога; DATA ждёт подтверждения, физические линки сохраняются. Возврата нет. */
|
||||
void etcp_conn_fatal_reinit(struct ETCP_CONN* etcp, const char* reason);
|
||||
// Применение чужого reset_id: при отличии — slave принимает эпоху пира и ресетится, master пересылает свою
|
||||
/* Учесть эпоху peer_reset_id из handshake соединения conn: первую сохранить, смену предложить для challenge.
|
||||
* Нулевая и прежняя эпохи игнорируются. Сброс при смене выполняется после CONFIRM, своя эпоха сохраняется. Возврата нет. */
|
||||
void etcp_conn_apply_peer_reset_id(struct ETCP_CONN* conn, uint64_t peer_reset_id);
|
||||
|
||||
void etcp_connection_ready(struct ETCP_CONN* etcp);// вызывается когда подключение инициализировано
|
||||
|
||||
@@ -79,13 +79,14 @@ struct ETCP_DGRAM {// пакет (незашифрованный)
|
||||
// Размер базового ACK: type(1) + count(1) + last_delivered_id(4) + rx_dup_count(2)
|
||||
#define ETCP_ACK_BASE_SIZE (2 + 4 + 2)
|
||||
|
||||
// --- INIT packet wire formats (inside encrypted payload, before padding) ---
|
||||
// INIT внутри шифрованного тела, до padding: запрос — 67 байт, ответ — 64 байта.
|
||||
// Каждая сторона передаёт свою эпоху reset_id; смена известной эпохи подтверждается через etcp_session.
|
||||
|
||||
// INIT REQUEST: client → server
|
||||
struct ETCP_INIT_REQUEST_PKT {
|
||||
uint8_t code; // 0: ETCP_INIT_REQUEST (0x02) или ETCP_INIT_REQUEST_NOINIT (0x04)
|
||||
uint8_t node_id[8]; // 1: sender node_id (big-endian)
|
||||
uint8_t reset_id[8]; // 9: reset epoch id (big-endian)
|
||||
uint8_t reset_id[8]; // 9: локальная эпоха потока клиента (big-endian)
|
||||
uint8_t mtu[2]; // 17: client MTU (big-endian)
|
||||
uint8_t keepalive[2]; // 19: keepalive interval (big-endian)
|
||||
uint8_t recovery[2]; // 21: recovery interval/100 (big-endian)
|
||||
@@ -108,10 +109,10 @@ struct ETCP_INIT_REQUEST_PKT {
|
||||
struct ETCP_INIT_RESPONSE_PKT {
|
||||
uint8_t code; // 0: ETCP_INIT_RESPONSE (0x03) или ETCP_INIT_RESPONSE_NOINIT (0x05)
|
||||
uint8_t node_id[8]; // 1: server node_id (big-endian)
|
||||
uint8_t reset_id[8]; // 9: reset epoch id (big-endian)
|
||||
uint8_t reset_id[8]; // 9: локальная эпоха потока сервера (big-endian), не эхо запроса
|
||||
uint8_t mtu[2]; // 17: server MTU (big-endian)
|
||||
uint8_t link_id; // 19: server's local link id
|
||||
uint8_t remote_socket_id; // 20: echo of client's socket_id
|
||||
uint8_t remote_socket_id; // 20: ID серверного сокета, отправляющего ответ
|
||||
uint8_t only_local; // 21: server only_local flag
|
||||
uint8_t type; // 22: server socket type (CFG_SERVER_TYPE_*)
|
||||
// V2 fields:
|
||||
|
||||
Reference in new issue
Block a user