Remove stale pacing and handshake descriptions from ETCP protocol specification

This commit is contained in:
evgeny committed 2026-10-24 00:32:42 +02:00
1 parent 964f6cb045
commit 9459b43211
2 files changed
+39 -24

No files matched your search

+38 -23
View File
@@ -30,7 +30,9 @@ HELLO/CHALLENGE/CONFIRM и keepalive передаются своим незав
- последовательно сканируем секции и отдаём их на обработку нужным обработчикам:
- ack (0x01): пара seq+attempt_no подтверждает физическую попытку только принявшего ACK линка;
seq отдельно подтверждает логическую доставку. till освобождает логические записи, без BBR/снятия чужого flight.
- timestamp (0x06): обновляем last RTT, пересчитываем RTT avg10 и jitter по каждому линку; обновляем recv_dt_avg для раздельного измерения времени передачи/приёма
- timestamp (0x06): обновляем RTT и EWMA |ΔRTT| пути; recv_dt_avg хранит поправки времени TX/RX в Q16.
У conn rtt_last — среднее RTT линков, rtt_avg_10 — их максимум (имя историческое).
Для BBR/RTO используются отдельные DATA RTT/jitter, измеренные по ACK физических попыток.
- [0x00] payload: сохраняем фрагмент в recv_q либо распознаём уже сохранённый/доставленный seq.
Только после этого ставим ACK с seq+attempt_no в ack_q принявшего линка; OOM до сохранения не подтверждается.
после сканирования проверяем появились ли в linked-list собранные данные которые можно переместить в выходную очередь. и перемещаем если есть.
@@ -115,8 +117,9 @@ pending/частичный буфер normalizer; ограничение окн
**** Формат кодограмм для etcp.c/h ****
Каждая кодограмма состоит из обязательного заголовка и опциональных секций:
1. Обязательный заголовок всего пакета (3 байта) добавляется при передаче, есть во всех пакетах:
[Timestamp high][Timestamp low][flags/flag_up]
- Timestamp: uint16_t, время отправки в единицах 0.1 мс (циклическое). при переповторах время обновляется
[Timestamp:2][flags/flag_up:1]
- Timestamp: uint16_t, время отправки в единицах 0.1 мс (циклическое), при повторе обновляется.
Копируется из памяти в порядке байтов хоста; на используемых little-endian платформах передаётся LE.
- flag_up: uint8_t, bit0 = up/down (recv_keepalive) — признак живой приёмной стороны
шифруется строго ВСЁ включая ВСЕ заголовки (кроме обфусцированного publickey в INIT-пакетах).
@@ -171,20 +174,22 @@ pending/частичный буфер normalizer; ограничение окн
При прохождении трафика через этот модуль подсчитываются метрики (для каждого канала отдельно):
- RTT последнего пакета и время отправки последнего пакета
- общее количество отправленных/принятых байт
- количество перезапросов (по каждому каналу отдельно) в пакетах. когда etcp модуль принял запрос ретрансмиссии он смотрит с какого интерфейса был отправлен этот пакет (это запоминается при отправке) и увеличивает счетчики.
- число фактических повторных отправок; потери для BBR учитываются отдельно при истечении физических попыток.
- обновление текущего количества неподтвержденных данных inflight (в пакетах и байтах) - сколько по этому каналу отправленных и неподтвержденных на текущий момент
Планировщик/балансир каналов (etcp_loadbalancer.c/h):
для каждого канала ограничение полосы с тестированием отклика:
- временную нагрузку shaper считаем по data_len + 2 байта на пакет (текущая реализация).
Ограничение реализуем следующим образом:
- в структуре есть счётчик времени (квантизация стандартная 0.1ms) по которое канал загружен данными. И суб-разряды (nanotime) этого счетчика 0.1ns - 0.1ms (считает по модулю 1000000 и переполнения добавляет в основной счетчик) чтобы при высоких скоростях не было потери точности.
Этим счётчиком контролируется полоса: при передаче к нему добавляется расчётное время передачи текущего ethernet пакета (используя текущий bandwidth считаем время передачи одного байта (float) и умножаем на число байт, переводим в таймбазу nanotime). А если при передаче время меньше current_time - delta_time (delta_time - константа в define, x0.1ms) то он устанавливается как current time-delta_time. (при неактивности обновляем время)
счетчик нужен для контроля можно ли передавать следующий пакет ("расчётное время передачи");
когда etcp пытается отправить пакет через линк, то если в линке не инициализировано подключение и он клиент - то он отбрасывает этот пакет и запускает процесс установки соединения.
также процесс установки соединения инициируется при добавлении канала (в etcp_link_new) если это client.
Окно и pacing проверяются до извлечения DATA из очереди; без допустимого UP-линка фрагмент остаётся в очереди.
У каждого линка собственный shaper по точной bbr_pacing_rate в байтах/с:
- После успешного send списываются только payload_len DATA-байтов, без заголовков и ACK.
- Основной счётчик времени имеет единицу 0.1 мс; sub-разряды — 0.1 нс, модуль 1000000.
Прибавка равна ceil(payload_len * 10000000000 / bbr_pacing_rate), вычисляется целочисленно в uint64.
- Накопленный кредит после простоя ограничивается 1 мс. При смене ставки пересчитывается оставшийся долг;
таймер снимается и при необходимости назначается заново. При нулевой ставке pacing не ограничивает отправку.
- ACK/control и измерительная burst-серия не списывают DATA-pacing. Окно DATA проверяется и в burst.
Конструктор исходящего UDP-линка запускает INIT сразу; TCP использует отдельный STCP connect/handshake.
процесс установки соединения (рукопожатие):
- клиент отправляет init запрос и выставляет таймаут
- по таймауту повторяет init запросы с экспоненциально растущим интервалом (начальный 500ms, макс 50s, +25% каждые 10 попыток), ведется счетчик запросов init.
- по таймауту повторяет INIT: начальный интервал 500 TB = 50 мс, максимум 50000 TB = 5 с;
каждые 10 отправок интервал увеличивается на interval/4 + 1 TB. TB здесь равен 0.1 мс.
- когда клиент получает init подтверждение — снимает таймаут, выставляет link_state=connected, запускает keepalive.
- каждая сторона сообщает в INIT собственный reset_id (64 бита, BE); ответ не повторяет эпоху запроса.
- первая неизвестная эпоха пира сохраняется из handshake. Смена уже известной эпохи запускает challenge;
@@ -225,18 +230,18 @@ link_state: 0=just init, 1=handshake, 2=reconnect, 3=connected.
- mtu: uint16_t локальный MTU
- keepalive: uint16_t интервал keepalive в мс
- recovery: uint16_t = link->recovery_interval/100 (исходная единица — 0.1 мс)
- link_id: uint8_t (0-255), назначается отправителем
- link_id: uint8_t (1-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_*
- 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 и собственным публичным ключом). Передаётся в конце пакета
- salt + obfuscated_pubkey: ключ отправителя XOR SHA256(salt || публичный ключ получателя).
Этот блок не шифруется AES, передаётся в конце пакета
в noencrypt_len байтах (не попадает под AES-шифрование)
INIT устанавливает UDP-линк: создаёт или переиспользует общий ETCP_CONN к пиру.
Между узлами одно ETCP-соединение с несколькими линками; TCP-линк использует STCP handshake.
@@ -250,7 +255,7 @@ link_state: 0=just init, 1=handshake, 2=reconnect, 3=connected.
- node_id: 64 бита, big-endian
- reset_id: uint64_t, собственная локальная эпоха потока сервера (big-endian), не эхо запроса
- mtu: uint16_t локальный MTU сервера
- link_id: uint8_t идентификатор канала сервера
- link_id: uint8_t идентификатор канала сервера, 1..255
- remote_socket_id: uint8_t (ID серверного сокета; ID клиентского сокета известен серверу из запроса)
- only_local: uint8_t флаг сервера
- type: uint8_t тип сокета сервера
@@ -279,7 +284,10 @@ link_state: 0=just init, 1=handshake, 2=reconnect, 3=connected.
ADDRESS_CHECK (0x08) — аутентифицированное входящее испытание endpoint.
Модель NAT и алгоритм владельца: doc/nat_detection.txt.
При получении INIT получатель создаёт или переиспользует ETCP_CONN и подходящий линк;
До изменения транспорта INIT_REQUEST проверяет полное 67-байтовое тело и соответствие node_id
аутентифицированному X25519-ключу. Оба направления INIT отклоняют link_id=0 и MTU меньше
ETCP_MIN_UDP_MTU(family): 191 байт для IPv4, 211 для IPv6. MTU больше 1500 ограничивается сверху.
Получатель создаёт или переиспользует ETCP_CONN и подходящий линк;
для существующего соединения проверяет совпадение ключа пира. Эпоха из INIT учитывается
через etcp_conn_apply_peer_reset_id, без немедленного сброса потока при смене известной эпохи.
Принятый INIT обновляет recv_keepalive, время последнего RX и входной remote_keepalive=flag_up
@@ -291,13 +299,19 @@ link_state: 0=just init, 1=handshake, 2=reconnect, 3=connected.
INIT_RESPONSE должен содержать полное обязательное тело (64 байта без timestamp/flag_up)
и node_id, соответствующий аутентифицированному ключу пира. Усечённый ответ или чужой node_id
отклоняются до изменения живости, эпохи и параметров линка. Дополнительный padding разрешён.
приём кодограмм из 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
Входящий conn с известным ключом индексируется по node_id до callback создания линка.
Отмена входящего линка из callback не закрывает conn, если у него появился другой линк или NCD-владелец.
Close немедленно отключает линк и ресурсы, но память удерживается active_users до выхода внутренних стеков RX/переходов/callbacks.
Приём UDP: uasync -> etcp_connections_read_callback_socket -> etcp_process_packet ->
etcp_packet_decrypted (проверки и снятие SESSION_DATA) -> etcp_conn_input -> normalizer/output_queue.
Keepalive: [0x08] [flags(1)] [period_ms(2 LE)] [sleep_fields?:4] [padding...].
При KA_FLAG_SLEEP добавляются active_remaining(2 LE) и sleep_duration(2 LE), оба в единицах 0.1 с.
Полезное тело занимает 4 байта без SLEEP или 8 байт с SLEEP; остальные байты — padding.
keepalive пакеты шлют и клиент и сервер с заданным интервалом если нет полезного трафика.
Обычные keepalive периодически шлют обе стороны; полезный TX сбрасывает адаптивный период к базовому.
Сон одной из сторон останавливает обычный таймер. UDP-анонс сна подтверждается флагом SLEEP_ACK,
повторяется до ACK/исчерпания попыток; TCP отдельного подтверждения сна не требует.
RX watchdog работает отдельно в normal и standby и не продлевается собственными отправками.
Сервер прекращает слать keepalive если линк потерян (ждёт keepalive от клиента).
flag_up в заголовке устанавливается в значение recv_keepalive (признак что локальная сторона принимает пакеты).
@@ -311,7 +325,8 @@ Padding keepalive, PING/PONG и STCP-handshake:
user_data_len/resp_data_len обозначают только полезные данные. PONG не копирует padding запроса.
- STCP: salt(8)+pubkey(32) открыты, затем 51 байт зашифрованного handshake и padding_size байт
нулевого padding, зашифрованного продолжением того же CTR. Приём продвигает CTR через padding;
padding_size=0 сразу завершает handshake. Параметры берутся у ETCP_CONN, иначе у instance.
padding_size=0 сразу завершает handshake. При ETCP_CONN клиент читает актуальный диапазон при отправке;
без ETCP_CONN сохраняет выбранную длину при запуске. Сервер выбирает длину независимо по пиру/instance.
- Каждый STCP-поток использует соль своего отправителя: клиентскую для client→server, серверную для server→client.
CTR IV = первые 12 байт SHA256(session_key || stream_id_LE || salt(8) || "uTun3-stream") + четыре нулевых байта.
Отправка инициализируется после генерации своей соли, приём — после чтения соли пира, до расшифровки тела.
+1 -1
View File
@@ -40,7 +40,7 @@ ACK_REQUEST и PAYLOAD (размер, seq, attempt_no). Направление:
Пакет не изменяется; это диагностика после снятия защиты, а не проверка подлинности.
Длинная строка безопасно усекается, неизвестная секция завершает разбор. Пример:
```
ETCP PKT: SEND ts=12345 ack=100/0 dup=0 ch_ts=200/150 payload=100(seq=101,try=1)
ETCP PKT: SEND ts=12345 ack=100/0 dup=0 ch_ts=200/150 payload=100(seq=101 attempt=1)
```
### Зависимости