# uTun — Архитектура системы Документ для разработчиков. Описывает внутреннее устройство, дизайн-решения, взаимодействие модулей и поток данных. --- ## 1. Обзор и дизайн-решения uTun — защищённый VPN-туннель с собственным надёжным транспортным протоколом ETCP. Ключевые архитектурные решения: | Решение | Обоснование | |---------|-------------| | **Однопоточный async** | Нет гонок данных, нет оверхеда на мьютексы, детерминированное поведение. Один `UASYNC` на поток | | **Очередь-ориентированный дизайн** | Все взаимодействия между модулями — через lock-free очереди `ll_queue`. Callback-и + backpressure | | **2 уровня надёжности** | ETCP (per-connection, RTT-based retrans) + etcp_router (per-service, 300ms retrans/17 попыток, transit forwarding) | | **Memory pools** | Предварительно выделенные пулы для hot-path объектов (пакеты, ACK, payload). Нет malloc в критическом пути | | **Детерминированный node_id** | `SHA256(privkey) → 63-bit` — глобально уникальный идентификатор без централизованного распределения | | **2-phase cleanup** | Отложенное освобождение ресурсов через `uasync_call_soon` — защита от UAF при вызове close из callback | ### Область применения - Site-to-site и client-to-site VPN - Mesh-сети с автоматическим обнаружением топологии (BGP) - Exit-прокси: SOCKS5, HTTP CONNECT, TCP/UDP/ICMP туннелирование - P2P чат с синхронизацией (chatgui) - Тестирование сетевых протоколов (dummynet — эмуляция потерь/задержек) ### Ограничения - Требует root для TUN и raw socket (ICMP proxy) - Однопоточная модель: CPU-bound операции блокируют event loop - Нет аппаратного ускорения криптографии (OpenSSL софтварный) - Максимум 16 hop-ов в BGP (защита от петель) - Максимум 65535 фрагментов на пакет (нормализатор) --- ## 2. Многослойный протокольный стек ``` ┌─────────────────────────────────────────────────────────────────┐ │ Layer 4: Приложения │ │ routing (IP-форвардинг), proxy (SOCKS5/TCP/UDP/ICMP), chat, │ │ db_sync (P2P БД), NTP, NAT │ ├─────────────────────────────────────────────────────────────────┤ │ Layer 3: etcp_router — сервисная маршрутизация │ │ seq-нумерация, дедупликация, реордеринг, ретрансмиссия, │ │ inflight control, transit forwarding (multi-hop), minRTT │ │ SVC_ROUTE_HDR: [cmd|dst|src|seq|svc_id|flags|timestamp] (25B) │ ├─────────────────────────────────────────────────────────────────┤ │ Layer 2.5: LoadBalancer — multi-link балансировка │ │ выбор линка (min inflight, round-robin), traffic shaper │ │ (token bucket per-link), burst measurement │ ├─────────────────────────────────────────────────────────────────┤ │ Layer 2: ETCP — надёжный транспорт │ │ inflight queues (send_q / wait_ack), ACK processing, │ │ retransmission (K1*rtt + K2*jitter), BBR congestion control, │ │ фрагментация (pkt_normalizer), keepalive, INIT handshake │ ├─────────────────────────────────────────────────────────────────┤ │ Layer 1: secure_channel — криптография │ │ X25519 ECDH → AES-128-CCM (AEAD), Ed25519 signatures, │ │ AES-128-CTR (streaming), pubkey obfuscation (INIT/PING) │ │ Формат: nonce[13] || AES-CCM(plaintext || CRC32) || tag[16] │ ├─────────────────────────────────────────────────────────────────┤ │ Layer 0: Транспорт │ │ UDP (основной), TCP/STCP (fallback), TUN device, │ │ кроссплатформенные абстракции (socket_compat, platform_compat) │ └─────────────────────────────────────────────────────────────────┘ ``` ### Почему два уровня надёжности? **ETCP** обеспечивает надёжную доставку между двумя непосредственно соединёнными узлами (per-connection). Этого достаточно для прямого линка, но недостаточно когда трафик идёт через промежуточные узлы (transit). **etcp_router** добавляет end-to-end надёжность поверх ETCP для multi-hop сценариев: - Свой sequence number — не зависит от ETCP seq (который per-hop) - Transit forwarding: промежуточный узел получает `SVC_ROUTE_HDR`, видит что `dst_node_id != local_node_id` и пересылает дальше - Собственный retransmission таймер (300ms, 17 попыток) — не ждёт ETCP retrans (который срабатывает на каждом hop-е отдельно) - Per-service изоляция: `(remote_node_id, svc_id)` кортеж — разные сервисы имеют независимые seq --- ## 3. Фундамент: однопоточный async (lib/) ### 3.1 UASYNC — событийный цикл Центральный планировщик. Один инстанс на поток. ``` uasync_mainloop(): while (!stop): timeout = timeout_heap_peek() // ближайший таймер epoll_wait/poll(timeout) // ждём события на сокетах process expired timers // сработавшие таймеры process ready sockets // сработавшие сокеты process call_soon queue // отложенные callback (FIFO) process posted tasks // межпоточные callback ``` **Ключевые API:** - `uasync_set_timeout(ua, timeout_tb, arg, cb, name)` — таймер в 0.1ms единицах. Возвращает handle для отмены - `uasync_add_socket(ua, fd, read_cb, write_cb, error_cb, arg)` — регистрация fd в epoll - `uasync_call_soon(ua, arg, cb)` — отложенный вызов на следующей итерации (FIFO). Используется для 2-phase cleanup - `uasync_post(ua, cb, arg)` — межпоточный вызов (thread-safe). Пробуждает epoll через wakeup pipe - `uasync_memsync(ua)` — memory barrier для синхронизации памяти между потоками **Правила:** - Никаких sleep/usleep — только таймеры - Нельзя использовать из нескольких потоков один UASYNC - `uasync_post` — единственный способ вызвать код uTun из другого потока ### 3.2 ll_queue — lock-free очередь Основа всего межмодульного взаимодействия. **Критические правила:** 1. **Callback правило:** `queue_set_callback(q, cb, arg)` → внутри cb обязательно: `queue_data_get(q)` → обработка → `queue_resume_callback(q)`. Без resume очередь навсегда блокируется. 2. **Backpressure:** `queue_set_threshold(q, max_packets, max_bytes)` + `queue_waiter_wait(q, &handle, cb, arg)`. Не забиваем очередь — добавляем только когда waiter сработал. 3. **Поиск по индексу:** `queue_data_put_with_index(q, entry)` — entry должен иметь ключ по смещению `index_offset` размером `index_size`. FNV-1a хеш. 4. **Память:** `queue_entry_new_from_pool(pool)` / `queue_entry_free(entry)` для entry. `queue_dgram_free(entry)` отдельно для dgram. ### 3.3 Memory pools Три пула в UTUN_INSTANCE для hot-path: | Пул | Размер блока | Назначение | |-----|-------------|-----------| | `data_pool` | `PACKET_DATA_SIZE` | Payload данные (фрагменты, recv_q, output_queue) | | `pkt_pool` | `sizeof(ETCP_DGRAM) + PACKET_DATA_SIZE` | Исходящие wire-пакеты | | `ack_pool` | `sizeof(ACK_PACKET)` | Записи ACK в ack_q | Пулы кэшируют до 64 освобождённых блоков (singly-linked free list). При исчерпании — fallback на malloc. Канарейки для детекции переполнения. ### 3.4 Кроссплатформенность | Абстракция | Linux | FreeBSD | Windows | |-----------|-------|---------|---------| | Event loop | epoll | poll | poll | | TUN | /dev/net/tun (ioctl) | /dev/tun (ioctl) | Wintun DLL | | Роутинг | netlink (RTM_NEWROUTE) | routing socket | CreateIpForwardEntry | | Сокеты | socket_t = int | socket_t = int | socket_t = SOCKET | | Random | /dev/urandom | /dev/urandom | BCryptGenRandom | --- ## 4. Сквозной путь данных ### 4.1 Отправка: TUN → сеть ``` Ядро пишет IP-пакет в /dev/tun │ ▼ (epoll: fd готов к чтению) ┌──────────────────────────────────────────────┐ │ tun_read_callback(fd, tun) │ tun_if.c │ tun_platform_read() → сырой IP-пакет │ │ ll_entry = pool_alloc(tun->pool) │ префикс [0x00] (cmd byte) │ queue_data_put(tun->output_queue, entry) │ └──────────────────────┬───────────────────────┘ │ queue callback ▼ ┌──────────────────────────────────────────────┐ │ routing_pkt_from_tun_cb(q, instance) │ routing.c:207 │ queue_data_get() → entry │ │ route_pkt(instance, entry, SELF_NODE_ID) │ │ queue_resume_callback() │ └──────────────────────┬───────────────────────┘ │ ▼ ┌──────────────────────────────────────────────┐ │ route_pkt(instance, entry, src_node_id) │ routing.c:53 │ extract_dst_ip(entry) → dst_ip │ парсинг IPv4 заголовка │ route_lookup(rt, dst_ip) → ROUTE_ENTRY │ longest prefix match │ if v_node_info == NULL (локальный): │ │ → queue_data_put(tun->input_queue) │ доставка себе │ else (удалённый узел): │ │ → etcp_route_send(inst, node_id, entry) │ отправка через ETCP └──────────────────────┬───────────────────────┘ │ если удалённый ▼ ┌──────────────────────────────────────────────┐ │ etcp_route_send(inst, dst, entry) │ etcp_router.c │ rconn = etcp_router_conn_get(dst, svc_id) │ per-(dst,svc) соединение │ if rconn->inflight >= inflight_limit: │ │ → queue_data_put(rconn->send_q) │ backpressure │ return │ │ router_send_one(rconn, data, len): │ │ SVC_ROUTE_HDR hdr = {cmd, dst, src, │ 25 байт │ tx_seq++, svc_id, flags, timestamp} │ │ ll_entry = [hdr || payload] │ │ entry_copy = copy for inflight_q (retrans)│ │ etcp_send(conn, entry) │ │ start retrans_timer (300ms) │ └──────────────────────┬───────────────────────┘ │ ▼ ┌──────────────────────────────────────────────┐ │ etcp_send(conn, entry) │ etcp_api.c │ queue_data_put(conn->send_input_q, entry) │ == normalizer->input └──────────────────────┬───────────────────────┘ │ ▼ ┌──────────────────────────────────────────────┐ │ pkt_normalizer — packer │ pkt_normalizer.c │ waiter на etcp->input_queue │ backpressure: ждём пустую очередь │ etcp_input_ready_cb(): │ │ pop from pn->input │ │ write [total_len:2 LE][data] in pn->data │ буфер аккумуляции │ when buffer >= frag_size: │ │ pn_send_to_etcp() → ETCP_FRAGMENT │ │ queue_data_put(etcp->input_queue) │ │ pn_flush_cb(): flush partial buffer │ └──────────────────────┬───────────────────────┘ │ ▼ ┌──────────────────────────────────────────────┐ │ etcp — inflight processing │ etcp.c │ input_queue_cb(): │ │ pop ETCP_FRAGMENT from input_queue │ │ wrap in INFLIGHT_PACKET (inflight_pool) │ │ assign next_tx_id++ │ │ queue_data_put_with_index(input_send_q) │ хеш-индекс по seq │ │ │ input_send_q_cb → process_send_queue(): │ │ loop: │ │ etcp_request_pkt(etcp): │ │ select link (loadbalancer) │ │ pop from input_send_q │ │ move to input_wait_ack │ │ build wire packet: │ │ [ACK section (0x01)] │ mandatory, always first │ [optional: MEAS_RESP, TIMESTAMP, │ │ MEAS_TS, FILLER] │ │ [PAYLOAD (0x00): seq(4B) + data] │ always last │ etcp_loadbalancer_send(dgram) │ └──────────────────────┬───────────────────────┘ │ ▼ ┌──────────────────────────────────────────────┐ │ LoadBalancer → secure_channel → UDP │ │ select_link(): min inflight_bytes, RR ties │ │ check shaper (token bucket) │ │ etcp_encrypt_send(dgram): │ │ sc_encrypt(header(3) + data) │ AES-128-CCM │ append CRC32 │ │ socket_sendto(sock, ...) │ │ update shaper_load_time_tb │ └──────────────────────────────────────────────┘ ``` ### 4.2 Приём: сеть → TUN ``` UDP socket recvfrom() │ ▼ ┌──────────────────────────────────────────────┐ │ etcp_connections_read_callback_socket() │ etcp_connections.c │ sc_decrypt() → проверка CRC32 │ │ if INIT_REQUEST: process handshake │ │ if INIT_RESPONSE: complete handshake │ │ if KEEPALIVE/PING/PONG: handle │ │ if data (ETCP sections): │ │ → etcp_conn_input(pkt) │ └──────────────────────┬───────────────────────┘ │ ▼ ┌──────────────────────────────────────────────┐ │ etcp_conn_input(pkt) │ etcp.c │ for each section in pkt: │ │ ACK (0x01): │ │ parse cumulative rx_ack_till │ всё до этого seq собрано │ for each individual ACK: │ │ find in input_wait_ack by seq │ │ remove, update BBR (rate_sample) │ │ free data_pool memory │ │ advance tx state if window opens │ │ TIMESTAMP (0x06): │ │ compute RTT, update jitter │ │ PAYLOAD (0x00): │ │ extract seq from data[1..4] │ │ create ACK entry in ack_q │ будет piggybacked в ответ │ if seq == last_delivered_id + 1: │ │ move directly to output_queue │ │ else: │ │ store in recv_q (hash by seq) │ OOSEQ буфер │ etcp_output_try_assembly(): │ │ scan recv_q for contiguous seq │ │ move to output_queue │ └──────────────────────┬───────────────────────┘ │ output_queue callback ▼ ┌──────────────────────────────────────────────┐ │ pkt_normalizer — unpacker │ pkt_normalizer.c │ pn_unpacker_cb(): │ │ pop ETCP_FRAGMENT from output_queue │ │ read [total_len:2 LE] header │ │ accumulate in recvpart buffer │ │ when recvpart complete: │ │ queue_data_put(pn->output, packet) │ └──────────────────────┬───────────────────────┘ │ pn->output callback ▼ ┌──────────────────────────────────────────────┐ │ etcp_int_recv(queue, conn) │ etcp_api.c │ read cmd = data[0] │ │ dispatch: bindings->callbacks[cmd](conn, e)│ │ cmd=0x03 (ETCP_ID_SVC_ROUTE): │ │ → etcp_router_recv_cb() │ │ cmd=0x00 (ETCP_ID_DATA): │ │ → direct routing handler (legacy) │ └──────────────────────┬───────────────────────┘ │ для cmd=0x03 (SVC_ROUTE) ▼ ┌──────────────────────────────────────────────┐ │ etcp_router_recv_cb(conn, entry) │ etcp_router.c │ parse SVC_ROUTE_HDR: │ │ dst_node_id, src_node_id, seq, svc_id │ │ │ │ if dst_node_id != local_node_id: │ │ router_forward_transit(): │ мы — промежуточный узел │ find/create TRANSIT_QUEUE(src,dst) │ │ queue_data_put(transit_q, entry) │ │ drain transit_q → next hop via │ │ waiter on send_input_q │ backpressure │ return │ │ │ │ if payload_len == 0: │ это ACK/CLOSE/RST │ router_handle_ack(rconn, hdr): │ │ tx_acked = hdr->seq │ продвигаем окно │ drain send_q │ освободившиеся слоты │ return │ │ │ │ router_handle_data_packet(rconn, ...): │ │ check bounds, dedup (recv_q lookup) │ │ queue_data_put(rconn->incoming_q) │ │ if hdr->seq == rconn->rx_seq + 1: │ следующий ожидаемый │ router_try_assembly(rconn): │ │ scan recv_q for contiguous seq │ │ deliver to service callback │ └──────────────────────┬───────────────────────┘ │ service callback (ETCP_RT_ID_DATA) ▼ ┌──────────────────────────────────────────────┐ │ routing_pkt_from_etcp_cb(conn, pkt) │ routing.c:180 │ route_pkt(instance, pkt, conn->peer_node) │ │ → route_lookup() → если локальный: │ │ → queue_data_put(tun->input_queue) │ └──────────────────────┬───────────────────────┘ │ ▼ ┌──────────────────────────────────────────────┐ │ tun_input_queue_callback(q, tun) │ tun_if.c:88 │ queue_data_get() → pkt │ │ tun_platform_write(pkt->dgram+1, len) │ пишем в /dev/tun │ queue_dgram_free + queue_entry_free │ │ queue_resume_callback() │ └──────────────────────────────────────────────┘ │ ▼ Ядро читает IP-пакет из /dev/tun → доставка приложению ``` ### 4.3 Retransmission (два уровня) **ETCP retrans** (`ack_timeout_check`): ``` Таймер сканирует input_wait_ack с головы. Для каждого INFLIGHT_PACKET: if now - last_timestamp > timeout: timeout = RTT * K1 + jitter * K2 min 50, max 10000 (0.1ms единицы) → переместить обратно в input_send_q (переотправка) ``` **Router retrans** (`router_retrans_timer_cb`): ``` 300ms начальный таймаут Экспоненциальный backoff (×2 каждые 3 попытки) Максимум 17 попыток (суммарно ~5 секунд) Копии пакетов хранятся в inflight_q (hash по seq) При успешном ACK: удаление из inflight_q ``` --- ## 5. ETCP — надёжный транспорт (Layer 2) ### 5.1 Основные структуры **ETCP_CONN** — одно логическое соединение с удалённым узлом: - Идентичность: `peer_node_id` (uint64_t), `session_id` (uint32_t) - Криптография: `crypto_ctx` (sc_context_t — сессионные ключи) - Фрагментация: `normalizer` (PKTNORM*) - Линки: связный список `ETCP_LINK*` - Счётчик seq: `next_tx_id` (монотонный), `last_rx_id`, `last_delivered_id` - RTT: `rtt_last`, `rtt_avg_10`, `rtt_avg_100`, `jitter`, `rtt_history[10]` - Состояние: 0=pending, 1=ready, 2=deleted (2-phase cleanup) **ETCP_LINK** — один UDP-путь: - Статус: `recv_keepalive` (принимаем ли пакеты), `remote_keepalive` (видит ли нас пир), `link_status` - BBR: `bbr` (struct bbr*), `delivered_bytes`, `pacing_rate`, `bandwidth` - Flow control: `inflight_bytes`, `inflight_packets`, `inflight_lim_bytes` - Shaper: `shaper_load_time_tb`, `shaper_sub_nanotime`, `shaper_timer` - NAT: `nat_type`, `nat_ip`, `nat_port` **Wire-формат пакета:** ``` [encrypted:] [timestamp:2] [flag_up:1] // ETCP_ENCRYPTED_HDR = 3 байта [section_0] [section_1] ... [section_N] // типизированные секции [appended after encryption:] [CRC32:4] Sections: ACK (0x01): [count:1][rx_ack_till:4][rx_dup:2][per-ACK: seq(4)+ts(2)+delay(2)] PAYLOAD (0x00): [seq:4 BE][data...] (always last) TIMESTAMP (0x06): [cur_ts:2][txrx_offset:2] (RTT measurement) MEAS_TS (0x07): [burst_id:2][flags:1][seq:1][ts_us:2][sz:2] (burst) MEAS_RESP (0x08): [burst_id:2][valid:1][gap_avg:4][gap_min:4][pkt_cnt:1] FILLER (0x09): [len:2][zeros...] (burst padding) METRICS (0x0A): [csv_len:2][Ed25519_sig:64][csv...] ``` ### 5.2 INIT handshake Клиент знает pubkey сервера из конфига. Сервер не знает клиента заранее. ``` Client Server │ │ │ 1. ECDH: priv_C × pub_S → session_key │ │ 2. salt = random(8) │ │ 3. obf_pub = XOR(pub_C, │ │ SHA256(salt || pub_S)) │ │ 4. encrypt INIT_REQUEST │ │ (AES-CCM, session_key) │ │ │ │──── INIT_REQ + [salt||obf_pub] ──────→│ │ │ 1. deobf: XOR(obf_pub, │ │ SHA256(salt || pub_S)) │ │ → pub_C recovered │ │ 2. ECDH: priv_S × pub_C │ │ → same session_key │ │ 3. decrypt INIT_REQUEST │ │ (проверка: клиент знает pub_S) │ │ 4. create ETCP_CONN │ │ 5. encrypt INIT_RESPONSE │←────── INIT_RESP + [salt||obf_pub] ───│ │ │ │ decrypt RESPONSE, complete │ │ link_state = connected │ │ │ │←══════ keepalive/данные ═════════════→│ ``` **Ключевое свойство обфускации:** `XOR(XOR(pub_C, SHA256(salt||pub_S)), SHA256(salt||pub_S)) = pub_C`. Обе стороны знают `pub_S` и `salt`, поэтому обе могут восстановить `pub_C`. Пассивный наблюдатель видит только случайный XOR. ### 5.3 BBR congestion control (per-link) Алгоритм управления перегрузкой, работающий на уровне ETCP_LINK. **Режимы:** ``` STARTUP → экспоненциальный рост до обнаружения BDP ↓ (найден BDP) DRAIN → слив избыточного inflight ↓ PROBE_BW → циклическое зондирование bandwidth: CRUISE (без изменений) → REFILL (увеличение) → UP (зондирование вверх) → DOWN (слив) → CRUISE... ↓ (периодически) PROBE_RTT → зондирование минимального RTT (inflight=4) ``` **Оценка bandwidth:** два окна — `bw_hi` (максимальный фильтр, окно 10) и `bw_lo` (минимум из окна 6). Используется fixed-point арифметика (BBR_SCALE=8, BW_SCALE=24). **Pacing rate:** `pacing_rate = bw * pacing_gain`. Пакеты отправляются не чаще чем `pkt_size / pacing_rate`. **Burst measurement:** Когда inflight достигает лимита и >500ms с последнего burst, отправляется 12 пакетов подряд (bypass shaper). Получатель измеряет inter-packet gaps и возвращает min/avg gap. Отправитель вычисляет: `BW = pkt_size * 8000 / gap_min_us`. --- ## 6. etcp_router — сервисная маршрутизация (Layer 3) ### 6.1 Зачем нужен поверх ETCP ETCP обеспечивает надёжность только на одном hop-е. etcp_router добавляет: 1. **End-to-end seq:** не зависит от per-hop ETCP seq 2. **Transit forwarding:** пакет может пройти через несколько промежуточных узлов 3. **Per-service изоляция:** разные сервисы имеют независимые seq и inflight control 4. **Inflight control:** свой congestion window (max_inflight, default 256) 5. **minRTT probing:** периодически снижает inflight до 4 для измерения минимального RTT ### 6.2 Transit forwarding ``` Узел A → Узел B (промежуточный) → Узел C (цель) A: etcp_route_send(inst, C_node_id, data) → topo_group_find_conn_for_node(C_node_id) → conn_AB → router_send_one(rconn_AC): SVC_ROUTE_HDR {dst=C, src=A, seq=5} B: etcp_router_recv_cb(): dst_node_id == C → не нам router_forward_transit(): TRANSIT_QUEUE tq(src=A, dst=C) → queue_data_put drain через conn_BC → etcp_send(conn_BC, entry) (SVC_ROUTE_HDR не модифицируется, только пересылается) C: etcp_router_recv_cb(): dst_node_id == C → нам router_handle_data_packet() → incoming_q → recv_q (reorder) → service callback ``` Backpressure на транзите: если `conn_BC->send_input_q` заполнена, transit queue ждёт освобождения через waiter. ### 6.3 Inflight control и retrans ``` Отправка: tx_seq - tx_acked >= inflight_limit → блокировка → send_q tx_seq - tx_acked < inflight_limit → router_send_one() → inflight_q + retrans_timer ACK: получаем SVC_ROUTE_HDR с seq=N, payload_len=0 → tx_acked = N → удаляем все inflight_q записи с seq <= N → drain send_q (освободившиеся слоты) Retrans: 300ms → 600ms → 1200ms → ... → максимум 17 попыток router_retransmit_one(): копия из inflight_q → etcp_send() если 17 попыток без ACK → закрытие rconn ``` --- ## 7. BGP топология ### 7.1 Модель данных - **TOPO_NODE:** pubkey, ed25519_pubkey, имя, версия, флаги - **TOPO_NODEQ:** узел в группе + пути (TOPO_NODEPATH), подсети, connectivity, transit - **TOPO_NODEPATH:** путь к узлу (next_hop_node_id, hop_count, hop_list до 16) - **TOPO_GROUP:** группа узлов (UTUN — VPN с подсетями, CHAT — чат без подсетей) ### 7.2 Поток синхронизации ``` Новое ETCP соединение установлено │ ▼ topo_group_new_conn(group, conn): 1. topo_group_add_to_senders() // добавить в список BGP-пиров 2. topo_group_send_table_request(conn) // запросить полную таблицу │ ▼ (удалённая сторона получает REQUEST_TABLE) topo_group_handle_request_table(): 1. topo_group_send_nodeinfo(local_node) // отправить информацию о себе 2. topo_group_send_full_table() // все известные узлы ├── for each node in group->nodes: │ if peer NOT in node's hop_list: │ topo_group_send_nodeinfo(node, conn) 3. topo_group_send_table_complete() ``` ### 7.3 Обработка NODEINFO ``` topo_group_process_nodeinfo(group, from_conn, data, len): 1. Проверка типа группы (UTUN/CHAT) 2. Проверка hop_count < MAX_HOPS (16) 3. Проверка версии: if existing_node.version >= new_node.version: только обновить пути (множественные пути к узлу) return 4. Полное обновление: - десериализация TOPO_NODE (адреса, подсети, hop_list) - создание/обновление TOPO_NODEQ в group->nodes (hash queue) - добавление пути: from_conn->peer_node_id в hop_list - ROUTE_INSERT(instance->rt, node) ← ПОДСЕТИ В ТАБЛИЦУ МАРШРУТИЗАЦИИ! - персистентность в SQLite - уведомление control_server + chatgui 5. BROADCAST соседям: for each sender in senders_list: if sender->peer_node_id not in node->hop_list: topo_group_send_nodeinfo(node, sender->conn) ``` ### 7.4 WITHDRAW (удаление узла) ``` topo_group_remove_conn(group, conn): for each node reachable ONLY through this conn: topo_group_process_withdraw(group, node): route_delete(instance->rt, node) ← удаление из таблицы маршрутов broadcast WITHDRAW соседям удаление из group->nodes ``` --- ## 8. conn_mgr — трёхфазное установление соединений ### 8.1 Диаграмма фаз ``` conn_mgr_connect_node(mgr, target_node_id) │ ├── Фаза 1: DIRECT ─────────────────────────┐ │ Для каждого адреса target × наш сокет: │ │ etcp_connection_create() │ │ etcp_link_new() → INIT │ │ таймер 5000ms │ │ │ │ ✓ Успех → CONN_MGR_OK (DIRECT) │ │ ✗ Таймаут: │ │ if (у нас прямой IP) && (!у target): │ │ → Фаза 2 (REVERSE) │ │ else: │ │ → Фаза 3 (INDIRECT) │ │ │ ├── Фаза 2: REVERSE ─────────────────────────┤ │ etcp_route_send(DIRECT_REQ) → через BGP │ │ { наши адреса для подключения } │ │ таймер 15000ms │ │ │ │ Target получает DIRECT_REQ: │ │ etcp_connection_create() │ │ etcp_link_new(REVERSE) → INIT к нам │ │ │ │ ✓ Успех → CONN_MGR_OK (REVERSE) │ │ ✗ Таймаут → Фаза 3 (INDIRECT) │ │ │ └── Фаза 3: INDIRECT ────────────────────────┤ etcp_route_send(EXCHANGE_REQ) → BGP │ { наши top-4 кандидата + RTT } │ таймер 15000ms │ │ Target: probe наши кандидаты │ → EXCHANGE_RESP {свои кандидаты + RTT} │ │ Мы: probe кандидаты target │ cm_compute_intermediaries(): │ union списков │ total_rtt = our_rtt + their_rtt │ sort by total_rtt, select top 3 │ → INTERM_SELECTED │ entry->conn_type = INDIRECT │ CONN_MGR_OK │ ``` ### 8.2 Фоновые процессы - **bg_ping_timer:** каждые ~100ms пингует очередной BGP-узел (RTT измерение), полный цикл ≥10s - **candidate_ping_timer:** каждые ~2s обновляет RTT для best_candidates[3] - **idle_timer:** каждые ~1s проверяет неактивные соединения, дисконнект по idle_timeout ### 8.3 NAT compatibility check При выборе адресов для DIRECT фазы проверяется совместимость NAT типов: - Если у нас EIM NAT: можем соединиться с кем угодно - Если у нас Symmetric NAT и target за NAT: DIRECT невозможен (→ REVERSE/INDIRECT) - NAT тип определяется через сравнение src_ipv4 (что мы о себе сообщаем) и наблюдаемого адреса при INIT --- ## 9. Криптография ### 9.1 Генерация ключей и идентичности ``` X25519 keypair: EVP_PKEY_keygen(EVP_PKEY_X25519) → private_key[32], public_key[32] Node ID (63-bit): SHA256(private_key) → hash[32] node_id = *((uint64_t*)hash) & 0x7FFFFFFFFFFFFFFF Ed25519 keypair (производный от X25519): SHA512(private_key) → hash[64] ed25519_privkey = hash[0..32] ed25519_pubkey = EVP_PKEY_get_raw_public_key(ed25519_privkey) ``` ### 9.2 Сессионный ключ ``` X25519 ECDH: shared_secret = EVP_PKEY_derive(my_priv, peer_pub) // 32 байта Session key (AES-128): SHA256(shared_secret || "uTun-v3-session") → hash[32] session_key = hash[0..16] ``` ### 9.3 Формат зашифрованного пакета ``` Шифрование: nonce = sc_build_nonce(tx_counter) // 13 байт: SHA256(urandom||counter||time) AES-128-CCM(nonce, session_key): encrypt: header[3] + plaintext[N] + CRC32[4] tag = CCM auth tag[16] Пакет: [nonce:13] [ciphertext: N+7] [tag:16] [+ pubkey_block:40 для INIT] Расшифровка: извлечь nonce[13] из начала, tag[16] из конца AES-128-CCM decrypt проверить CRC32 проверить CCM auth tag (EVP_DecryptFinal_ex) ``` ### 9.4 Streaming cipher (STCP) ``` AES-128-CTR: iv = SHA256(session_key || stream_id_LE || "uTun3-stream")[0..16] EVP_aes_128_ctr(session_key, iv) XOR in-place (конфиденциальность без аутентификации) STCP добавляет свой CRC32 на уровне фреймов ``` ### 9.5 Ed25519 подписи Используются для: - Метрик (METRICS section 0x0A) — подписанные CSV - Сообщений чата (MSG_OPT_SIGNED флаг) - db_sync записей (JSON + Ed25519 sig) Стриминговая подпись: `SHA512(SHA512(data))` — обе стороны аккумулируют SHA-512, затем one-shot Ed25519 над финальным хешем. --- ## 10. Прокси-система ### 10.1 Архитектура клиента (tcp_proxy_client) ``` Приложение (браузер и т.д.) │ подключается к SOCKS5 localhost:1080 ▼ ┌──────────────────┐ │ socks_proxy │ SOCKS5/HTTP CONNECT парсинг │ (src/proxy/) │ → резолвинг destination └────────┬─────────┘ │ ETCP CONNECT → tcp_proxy_server на exit node ▼ ┌──────────────────┐ │ lwip_tcp │ Встроенный TCP стек │ (src/lwip_tcp/) │ Завершает TCP на клиенте │ │ tcp_write() → сегменты └────────┬─────────┘ │ lwIP output callback → TUN write ▼ ┌──────────────────┐ │ TUN (nat_tun) │ Локальный виртуальный интерфейс │ │ Перехватывает ВЕСЬ исходящий TCP └────────┬─────────┘ │ для не-TCP: udp_proxy / icmp_proxy │ для TCP: lwip_tcp_input() → tcp_process() ▼ ETCP туннель → exit node ``` **Ключевая особенность:** клиент использует встроенный lwIP TCP стек для терминации TCP-соединений локально. Это позволяет: - Не гонять TCP ACK через туннель (нет TCP-over-TCP проблемы) - Прозрачно обрабатывать потери на пути клиент↔exit (ETCP надёжен) - Поддерживать SOCKS5/HTTP CONNECT без модификации приложений ### 10.2 Архитектура сервера (tcp_proxy_server) ``` ETCP туннель от клиента │ CONNECT subcmd: target_ip:port ▼ ┌──────────────────┐ │ tcp_proxy_server │ Exit node │ │ Создаёт OS TCP сокет к реальному адресату │ │ Релеит данные: ETCP ↔ OS TCP │ │ Backpressure: tx_buf + pause_waiter + retry_timer │ │ FIN handling: отложенный FIN до опустошения read_queue └────────┬─────────┘ │ OS TCP connect() ▼ Целевой сервер в интернете ``` ### 10.3 lwIP TCP стек Встроенный в uTun TCP/IP стек (адаптированный lwIP): - **Context-based:** один `lwip_tcp_ctx` на tcp_proxy_client - **Таймеры:** `tcp_fasttmr()` (delayed ACK) и `tcp_slowtmr()` (retrans, keepalive, TIME_WAIT) — каждые 250ms - **4 списка PCB:** bound, listen, active, time_wait - **10 TCP состояний** (RFC 793): CLOSED → LISTEN → SYN_RCVD → ESTABLISHED → FIN_WAIT_1/2 → TIME_WAIT - **Reno congestion control:** slow start, congestion avoidance, fast retransmit (3 dupack), fast recovery - **Memory pools:** pcb_pool, pcb_listen_pool, seg_pool - **Trace ring buffer:** 384 записи внутренних событий для отладки - **Конфигурация:** MSS=1460, RTO_MIN=3000ms, MSL=60s, TCP_WND=8*MSS --- ## 11. Модель потоков ### 11.1 Основной процесс uTun (однопоточный) ``` Поток 1 (главный): uasync_mainloop() ├── Сокеты: │ ├── UDP listen (все [server] сокеты) │ ├── TCP listen (control_server, STCP) │ └── TUN fd (read/write через uasync callback) ├── Таймеры: │ ├── ETCP: retrans, ack_response, keepalive │ ├── etcp_router: retrans, ACK, no_route_retry, minRTT_probe │ ├── BBR: shaper per-link │ ├── conn_mgr: bg_ping, candidate_ping, idle, connect_timeout │ ├── Нормализатор: flush_timer │ ├── Прокси: диагностика (1s), retry (500ms) │ ├── NTP: resync │ └── db_sync: TTL cleanup ├── call_soon (FIFO): │ └── Отложенные callback (2-phase cleanup, resume) └── post (межпоточные): └── Из chatgui/GUI потока НЕТ: sleep, usleep, pthread_mutex_lock (в главном потоке) ВСЕ: асинхронно через uasync ``` ### 11.2 chatgui (двухпоточный) ``` Поток 1 (Qt main thread): - Рендеринг UI (QPainter) - Обработка событий (клики, ввод) - Чтение из SQLite (read-only) - Отправка команд в uTun через GuiBridge (Qt signals) Поток 2 (uTun networking): - uasync_mainloop() - ETCP соединения - ChatSync (P2P синхронизация каналов) - Запись в SQLite (thread-safe write) - GuiBridge: получение команд через uasync_post + wakeup pipe ``` **Правила межпоточного взаимодействия:** - Из GUI → uTun: `uasync_post(ua, callback, arg)` — thread-safe - Из uTun → GUI: `emit Qt signal` — Qt thread-safe через queued connection - Память: `uasync_memsync(ua)` перед post для видимости изменений - БД: chat_core (uTun поток) пишет, DbManager (GUI поток) читает ### 11.3 Windows TUN (дополнительный поток) На Windows TUN чтение выполняется в отдельном потоке `tun_read_thread_proc` (Wintun API требует dedicated thread). Результаты передаются в главный поток через `uasync_post`. --- ## 12. Жизненный цикл UTUN_INSTANCE ### 12.1 Инициализация ``` utun_instance_create(ua, config_path): config_ensure_keys_and_node_id() // авто-генерация ключей при первом запуске parse_config(config_path) // парсинг INI в utun_config debug_set_output_file() // открытие лог-файла debug_set_level() per config // настройка уровней отладки instance_init_common(instance, ua, config): 1. Копирование name, node_id из конфига 2. sc_init_local_keys() // загрузка X25519 ключей 3. sc_derive_ed25519_pubkey() // деривация Ed25519 ключей 4. queue_new() для networks // хеш-очередь сетей 5. queue_new() для connections // хеш-очередь ETCP соединений 6. memory_pool_init() ×3 // data_pool, pkt_pool, ack_pool 7. routing_create(instance) // таблица маршрутов 8. tun_init() // TUN интерфейс 9. init_sockets(instance) // UDP сокеты из [server] 10. topo_groups_init(instance) // BGP группы + SQLite 11. topo_group_update_my_nodeinfo() // локальный узел 12. conn_mgr_init(instance) // менеджер соединений 13. fw_init() + fw_load_rules() // firewall 14. etcp_router_init(instance) // сервисный роутер 15. routing_bind(instance) // привязка data handler 16. tcp_proxy_server_init() // exit node прокси 17. tcp_proxy_client_create() // клиентский прокси (если включён) utun_instance_init(instance): 1. db_sync_init(instance) // распределённая БД 2. routing_set_tun(instance) // подключение TUN к роутингу 3. nat_transport_init(instance) // NAT транспорт 4. init_connections(instance) // ETCP соединения из [client] 5. control_server_init() // мониторинг 6. running = 1 8. ntp_time_init() // NTP синхронизация 9. ntp_node_time_init() // межузловое время ``` ### 12.2 Завершение (строго обратный порядок) ``` utun_instance_destroy(instance): 1. running = 0 2. Отмена NTP таймеров 3. control_server_destroy() // потом мониторинг 5. Закрытие всех UDP сокетов // etcp_socket_remove() для каждого 6. db_sync_destroy() 7. etcp_connection_close() для всех соединений в connections 8. uasync_poll() до опустошения deferred callbacks 9. Отмена pending_pings 10. tun_route_del_all() + tun_close() // TUN 11. tcp_proxy_client_destroy() 12. tcp_proxy_server_destroy() 13. routing_destroy() 14. nat_transport_destroy() 15. etcp_router_destroy() 16. conn_mgr_destroy() 17. topo_groups_destroy() 18. fw_free() 19. stcp_link_server_destroy() 20. networks queue очистка 21. free_config() // освобождение конфига 22. memory_pool_destroy() ×3 // pkt_pool, ack_pool, data_pool (порядок важен!) 23. u_free(instance) ``` ### 12.3 2-phase cleanup ETCP_CONN ``` etcp_connection_close(etcp): Phase 1: - очистка таймеров - уничтожение линков (etcp_link_free) - уничтожение нормализатора - удаление из instance->connections - state = 2 (deleted) Phase 2 (if ref_count == 0): - uasync_call_soon(etcp_connection_free_deferred) - на следующей итерации event loop: - освобождение всех очередей - освобождение crypto_ctx - u_free(etcp) Зачем 2 фазы: - close может быть вызван из callback того же ETCP_CONN - нельзя освобождать память пока мы ещё в стеке вызова callback - call_soon гарантирует что стек полностью размотается перед free ``` ### 12.4 Reload ``` utun_instance_reload(instance, new_config_path): - парсинг нового конфига - сравнение [server] секций: удалить удалённые, добавить новые - сравнение [client] секций: закрыть ненужные соединения, открыть новые - обновление firewall, NAT, keepalive параметров - замена instance->config → free_config(old) ``` --- ## Приложение A: Полный список очередей в системе | Модуль | Очередь | Тип | Назначение | |--------|---------|-----|-----------| | tun_if | `tun->output_queue` | FIFO | TUN → routing | | tun_if | `tun->input_queue` | FIFO | routing → TUN | | routing | `routing_pkt` (через etcp_router) | — | IP-пакеты на маршрутизацию | | pkt_normalizer | `pn->input` | FIFO | Пакеты на фрагментацию | | pkt_normalizer | `pn->output` | FIFO | Собранные пакеты | | etcp | `send_input_q` | FIFO | Вход от нормализатора | | etcp | `input_queue` | FIFO | Фрагменты → inflight | | etcp | `input_send_q` | hash(seq) | Готовые к отправке | | etcp | `input_wait_ack` | hash(seq) | Отправленные, ждут ACK | | etcp | `recv_q` | hash(seq) | Принятые не по порядку | | etcp | `output_queue` | FIFO | Собранные данные | | etcp | `ack_q` | hash(seq) | Неотправленные ACK | | etcp | `transit_queues` | hash(src,dst) | Транзитные пакеты | | etcp_router | `rconn->incoming_q` | FIFO | Входящие router-пакеты | | etcp_router | `rconn->recv_q` | hash(seq) | Реордеринг router-пакетов | | etcp_router | `rconn->send_q` | FIFO | Заблокированные на отправку | | etcp_router | `rconn->inflight_q` | hash(seq) | Копии для ретрансмиссии | | topo_group | `group->nodes` | hash(node_id) | Все известные узлы | | topo_group | `group->senders_list` | linked list | BGP-пиры | | utun_instance | `instance->connections` | hash(peer_node_id) | Все ETCP_CONN | | utun_instance | `instance->networks` | hash(network_id) | Доверенные сети | | conn_mgr | `mgr->entries[]` | dynamic array | Состояния подключений | ## Приложение B: Основные таймауты и константы | Константа | Значение | Где используется | |-----------|---------|-----------------| | ETCP retrans min | 5ms (50 tb) | etcp ack_timeout_check | | ETCP retrans max | 1000ms (10000 tb) | etcp ack_timeout_check | | ETCP delayed ACK | 2ms (20 tb) | etcp ack_response_timer | | Router retrans init | 300ms | etcp_router retrans_timer | | Router retrans max tries | 17 (~5s) | etcp_router | | Router minRTT probe interval | 10s | etcp_router | | Keepalive interval (active) | 200ms | etcp_connections | | Keepalive interval (idle) | 10s | etcp_connections | | INIT retrans init | 500ms | etcp_connections | | INIT retrans max | 3200ms | etcp_connections | | conn_mgr DIRECT timeout | 5s | conn_mgr | | conn_mgr REVERSE/INDIRECT timeout | 15s | conn_mgr | | Normalizer frag_size | mtu - ACK_RESERVE - UDP_HDR - SC_HDR | ~1553 bytes | | BBR burst packets | 12 | etcp burst measurement | | BBR max cwnd | 1MB (configurable) | etcp_bbr | | lwIP TCP_RTO_MIN | 3000ms | lwip_tcp | | lwIP TCP_MSL | 60s | lwip_tcp | | UDP proxy flow expiry | 60s | udp_proxy | | ICMP proxy request timeout | 5s | icmp_proxy | | db_sync record batch | 32 | db_sync | | BGP max hops | 16 | topo_group | | BGP MAX_ADDR_TYPES | 16 | topo_node | ## Приложение C: Ключевые callback-цепочки ### C.1 Новое ETCP соединение ``` etcp_connection_create() → new_conn_cbks chain (topo_group_etcp_conn_cbk, ...) → topo_group: up/down callbacks on connection → etcp_conn_queue_set_ready() → ready_cbks chain etcp_on_up() → up_cbks chain (topo_group_on_conn_up, conn_mgr notification, ...) → topo_group_on_conn_up: BGP full table sync ``` ### C.2 Получение данных (снизу вверх) ``` UDP recv → sc_decrypt → etcp_conn_input: parse sections, ACK processing, store in recv_q → etcp_output_try_assembly: output_queue → pn_unpacker_cb: reassemble → etcp_int_recv: dispatch by cmd byte → cmd=0x03: etcp_router_recv_cb → router_handle_data_packet → incoming_q callback → router_try_assembly → service callback (routing_pkt_from_etcp_cb, proxy, chat, ...) ``` ### C.3 Отправка данных (сверху вниз) ``` service (routing, proxy, chat) → etcp_route_send / etcp_router_conn_send → router_send_one: SVC_ROUTE_HDR + inflight_q → etcp_send: send_input_q → pkt_normalizer packer: fragment → etcp->input_queue → input_queue_cb: INFLIGHT_PACKET → input_send_q → process_send_queue: etcp_request_pkt → loadbalancer_select_link → etcp_encrypt_send: sc_encrypt → socket_sendto ``` --- *Документ основан на анализе исходного кода uTun. Для каждого модуля доступен отдельный `_doc.md` файл с детальным описанием API.*