- etcp_router: SVC_ROUTE_HDR (+8B), ETCP_ROUTER_CONN/TRANSIT_QUEUE хеши расширены под group_id - Все caller'ы (proxy, routing, conn_mgr, nat, chatgui, tests) обновлены - topo_group: глобальный реестр TOPO_NODE по node_id, ref_count=0 при создании - BGP-обмен активируется для всех групп (а не только дефолтной) - Протокол: group_id добавлен в WITHDRAW/TABLE_REQ/TABLE_COMPLETE/ERR_GROUP_MISMATCH - Per-connection коллбэки заменены на instance-level conn_status - msg_transport полностью удалён из проекта 57/57 тестов
1085 lines
57 KiB
Markdown
1085 lines
57 KiB
Markdown
# 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.*
|