diff --git a/doc/etcp_protocol.txt b/doc/etcp_protocol.txt index e2c70599..257787f4 100644 --- a/doc/etcp_protocol.txt +++ b/doc/etcp_protocol.txt @@ -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") + четыре нулевых байта. Отправка инициализируется после генерации своей соли, приём — после чтения соли пира, до расшифровки тела. diff --git a/src/transport_layer/etcp_debug_doc.md b/src/transport_layer/etcp_debug_doc.md index f877baf6..a72efede 100644 --- a/src/transport_layer/etcp_debug_doc.md +++ b/src/transport_layer/etcp_debug_doc.md @@ -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) ``` ### Зависимости