You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

14 KiB

ETCP Connections

1. Назначение

Подмодуль ETCP, обслуживающий UDP/TCP-сокеты для передачи кодограмм и управляющий одним ETCP-соединением (ETCP_CONN) через несколько каналов связи (ETCP_LINK, мультилинк с failover).

Отвечает за:

  • Создание сокетов из конфига (серверных для входящих и клиентских для исходящих)
  • Приём сырых пакетов, дешифровку, диспетчеризацию (INIT/PING/PONG/KEEPALIVE/данные)
  • INIT handshake: обмен node_id, ключами pubkey (X25519 + Ed25519), MTU, keepalive-параметрами
  • Keepalive с адаптивным периодом (200мс → 10с → 200мс при трафике)
  • Управление inflight-лимитами, burst-измерения bandwidth, BBR congestion control per-link
  • Детекцию NAT (DIRECT/EIM/strict), обновление nat_addr при смене адреса

2. Как пользоваться

Инициализация сокетов и соединений

Двухфазная инициализация:

// Фаза 1: создаём listen-сокеты (до topo_group_init)
init_sockets(instance);

// Фаза 2: создаём клиентские соединения из [client] секций конфига
init_connections(instance);

Обе функции берут конфиг из instance->config: серверы из [server], клиенты из [client].

Входящий пакет: полный путь

socket_recvfrom() → etcp_connections_read_callback_socket()
  ├─ Есть линк и session_ready → sc_decrypt() по session_key
  │   ├─ KEEPALIVE → обновить period, выйти
  │   ├─ INIT_RESPONSE → handle_init_response_client()
  │   └─ Данные → etcp_conn_input(pkt) [в ETCP-стек]
  │
  └─ Нет линка/сессии → попытка INIT-дешифровки
      ├─ Извлечь соль + obfuscated pubkey из хвоста пакета
      ├─ sc_obfuscate_pubkey() → восстановить pubkey пира
      ├─ sc_set_peer_public_key() → вычислить session_key
      ├─ sc_decrypt() → расшифровать
      ├─ PING → handle_ping(): ответить PONG, вернуть данные коллбэку
      ├─ PONG → handle_pong(): найти PING_CONTEXT, вызвать cb
      └─ INIT_REQUEST → обработать handshake, отправить INIT_RESPONSE

Отправка зашифрованного пакета

struct ETCP_DGRAM* dgram = memory_pool_alloc(inst->pkt_pool);
dgram->link = link;
dgram->data[0] = ETCP_KEEPALIVE;
dgram->data_len = 1;
dgram->noencrypt_len = 0;  // для INIT — SC_PUBKEY_ENC_SIZE
etcp_encrypt_send(dgram);
memory_pool_free(inst->pkt_pool, dgram);

etcp_encrypt_send делает:

  1. sc_encrypt(timestamp(2) + flag_up(1) + data, ...) → зашифрованный буфер
  2. Копирует noencrypt_len байт из хвоста данных в конец буфера (для pubkey при INIT)
  3. etcp_udp_send() → socket_sendto() (или link->send_hook если установлен)

PING/PONG (one-shot пробники)

etcp_send_ping(instance, peer_pubkey_bin, &addr, timeout_ms,
               my_ping_callback, user_arg,
               user_data, user_data_len);

// Коллбэк:
void my_ping_callback(int success, uint16_t rtt, void* arg, uint64_t nonce,
                      const uint8_t* resp_data, size_t resp_data_len);

Пинг-пакеты идут мимо ETCP_CONN/LINK — через временный sc_context_t, pubkey обфусцируется в хвосте.

Адаптивный keepalive

  • Период ka_period_ms: старт = keepalive_interval из конфига, минимальный 200мс
  • Если был трафик (pkt_sent_since_keepalive=1): период сбрасывается к keepalive_interval
  • Без трафика: период растёт ×1.05 до KA_PERIOD_MAX_MS (10с)
  • Таймаут = period × KA_TIMEOUT_MULT (×10). При превышении → линк падает
  • Сервер не шлёт keepalive при recv_keepalive=0 (линк уже мёртв), клиент шлёт всегда

Протокол смены keepalive-режима (standby/normal, мобильные узлы)

Модуль etcp_keepalive.c/h. Мобильный узел (Android) при смене active mode (передний/фоновый план) сообщает пиру желаемый keepalive-режим через кодограмму ETCP_KEEPALIVE_REQ (0x09); пир применяет согласованный режим и отвечает ETCP_KEEPALIVE_RESP (0x0A). Формат: data[0]=code, data[1]=режим.

Режимы:

  • KA_MODE_NORMAL (0) — активен: интервал KA_MOBILE_ACTIVE_MS (3с), адаптивный (рост 3с → 10с, сброс на трафике).
  • KA_MODE_STANDBY (1) — спит: интервал KA_MOBILE_STANDBY_MS (30с), период зафиксирован (ka_pinned=1), адаптивный рост отключён.

Согласование:

  • Мобильный отвечающий считает agreed = standby, если его или запрошенный режим standby (эквивалент max интервалов), иначе normal.
  • Не-мобильный отвечающий применяет запрошенный режим как есть.
  • Инициатор применяет согласованный режим только после получения RESP.
  • Применение: keepalive_interval = ka_period_ms = ms, keepalive_timeout = ms * KA_TIMEOUT_MULT, ka_pinned по режиму, рестарт keepalive-таймера.

Смена active mode рассылается через событие utun_add_activity_cbk (подписчики: topo_group_on_activity_change, etcp_keepalive_on_activity).

3. API

Ключевые структуры

ETCP_SOCKET — один слушающий/клиентский сокет из конфига:

  • fd — UDP (или TCP через stcp_link)
  • links[] — сортированный массив активных линков (по ip_port_hash), бинарный поиск
  • interface_addr — IP интерфейса (автоопределённый или из конфига)
  • nat_addr, nat_type — детектированный NAT-адрес и тип
  • type — CFG_SERVER_TYPE_PUBLIC/NAT/PRIVATE
  • sock_id — уникальный ID (0-255) для сопоставления при NAT-матчинге

ETCP_LINK — одно динамическое соединение (один путь между двумя узлами):

  • Принадлежит ровно одному ETCP_CONN (родитель) и одному ETCP_SOCKET (сокет)
  • remote_addr — адрес пира; поиск по ip_port_hash (CRC32 от sockaddr)
  • mtu, mtu_local, mtu_remote — согласованный MTU (min)
  • is_server — 0=клиент инициирует, 1=сервер принимает
  • initialized=1 — handshake завершён, соединение готово
  • link_status — итоговый статус: recv_keepalive && remote_keepalive
  • link_state: 0=init, 1=handshake, 2=reconnect, 3=connected
  • init_timer / keepalive_timer / shaper_timer — таймеры uasync
  • remote_ed25519_pubkey — Ed25519-ключ пира из INIT handshake
  • bbr — состояние BBR congestion control per-link
  • Поля burst-измерений bandwidth (sender + receiver)
  • send_hook — перехватчик отправки (для net_emulator)
  • Статистика: encrypt/decrypt/send/recv errors, total_encrypted/decrypted, retransmissions

ETCP_DGRAM — незашифрованная кодограмма:

  • link — куда отправлять / откуда получено
  • data_len — общий размер данных (без timestamp)
  • noencrypt_len — байты с конца, не подлежащие шифрованию (SC_PUBKEY_ENC_SIZE для INIT)
  • timestamp, flag_up — заголовок, шифруется вместе с data

ETCP_INIT_REQUEST_PKT (62 байта) — формат INIT-запроса в зашифрованном payload:

  • code (0x02/0x04), node_id[8], session_id[4], mtu[2], keepalive[2], recovery[2]
  • link_id, socket_id, only_local, type
  • src_ipv4[4], src_port[2] — для NAT_DIRECT-детекции
  • collision — флаг разрешения коллизий
  • ed25519_pubkey[32]

ETCP_INIT_RESPONSE_PKT (57 байт) — ответ сервера:

  • code (0x03/0x05), node_id[8], session_id[4], mtu[2]
  • link_id, remote_socket_id, only_local, type
  • peer_ipv4[4], peer_port[2] — внешний NAT-адрес клиента
  • ed25519_pubkey[32]

Типы кодограмм

Константа Значение Назначение
ETCP_INIT_REQUEST 0x02 INIT-запрос со сбросом ETCP-соединения
ETCP_INIT_RESPONSE 0x03 INIT-ответ со сбросом
ETCP_INIT_REQUEST_NOINIT 0x04 INIT-запрос без сброса
ETCP_INIT_RESPONSE_NOINIT 0x05 INIT-ответ без сброса
ETCP_PING 0x06 One-shot пробник
ETCP_PONG 0x07 Ответ на пробник
ETCP_KEEPALIVE 0x08 keepalive-пакет
ETCP_KEEPALIVE_REQ 0x09 Запрос смены keepalive-режима (standby/normal)
ETCP_KEEPALIVE_RESP 0x0A Ответ: согласованный режим

NAT-типы

Константа Значение Назначение
NAT_TYPE_UNKNOWN 0 Не определён
NAT_TYPE_EIM 1 Endpoint-Independent Mapping
NAT_TYPE_STRICT 2 Address/Restricted или Symmetric
NAT_TYPE_DIRECT 3 Реальный публичный IP (нет NAT)
NAT_VERIFIED_* (4-7) — Верифицированные (опубликованные в nodeinfo)

Основные функции

Функция Назначение
init_sockets(inst) Создать listen-сокеты из [server] конфига (до topo_group_init)
init_connections(inst) Создать клиентские соединения из [client] + сокеты если не созданы
etcp_socket_add(inst, srv) Создать UDP-сокет из CFG_SERVER: bind, non-block, send/recv buffers 4MB
etcp_socket_remove(e_sock) Закрыть сокет, удалить все линки, освободить память
etcp_link_new(etcp, conn, addr, is_server) Создать ETCP_LINK: выделить local_link_id, BBR, вставить в сокет и ETCP_CONN
etcp_link_close(link) Удалить линк: таймеры, удаление из списков сокета и ETCP_CONN
etcp_link_update_inflight_lim(link, new_lim) Обновить лимит inflight (clamped к [8K, max_inflight])
etcp_encrypt_send(dgram) Зашифровать dgram (SC_CCM) + UDP-отправка через etcp_udp_send
etcp_udp_send(link, fd, buf, len, addr, len) Отправка: если send_hook — через него, иначе socket_sendto
etcp_send_ping(inst, pubkey, addr, timeout, cb, arg, data, len) One-shot PING
etcp_send_ping_to_socket(inst, sock, pubkey, addr, ...) PING через конкретный сокет
etcp_link_find_by_addr(e_sock, addr) Найти линк по адресу (бинарный поиск в links[])
etcp_link_find_by_remote_id(conn, remote_link_id) Найти линк по ID пира
etcp_find_free_local_link_id(etcp) Найти свободный local_link_id (0-255, битовая карта)
etcp_link_enter_init(link) Начать INIT handshake (link_state=1)
etcp_link_enter_reinit(link) Начать переподключение (link_state=2, без сброса ETCP)

Burst-измерения

Функция Назначение
etcp_link_burst_start(link) Начать burst-замер bandwidth
etcp_link_burst_check(link) Проверить условия для старта burst
etcp_link_burst_finish(link) Завершить burst, установить таймер ожидания ответа

Константы

Константа Значение Назначение
INFLIGHT_LIM_MIN 8192 Минимальный inflight (8K)
INFLIGHT_LIM_MAX 1048576 Максимальный inflight (1M)
PACKET_DATA_SIZE 1600 Размер буфера пакета
PACKET_DATA_MAX_MTU 1600 Максимальный MTU
KA_PERIOD_MIN_MS 200 Мин. период keepalive
KA_PERIOD_MAX_MS 10000 Макс. период keepalive
KA_TIMEOUT_MULT 10 Множитель таймаута = period×10
ACK_REZERV 100 Резерв байт под ACK
INIT_TIMEOUT_INITIAL 500ms Начальный таймаут INIT
INIT_TIMEOUT_MAX 50000ms Максимальный таймаут INIT