From 0381f4df89125922585d845871d5c42517341e3c Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 17 Jul 2026 17:49:15 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A3=D0=B1=D1=80=D0=B0=D0=BD=D1=8B=20TCP=5FTI?= =?UTF-8?q?MESTAMPS=20=D0=B8=20TCP=5FKEEPALIVE=20=D0=B8=D0=B7=20lwip=5Ftcp?= =?UTF-8?q?=5Fopts.h=20(=D0=BD=D0=B5=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=B7=D1=83=D1=8E=D1=82=D1=81=D1=8F,=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BD=D1=84=D0=BB=D0=B8=D0=BA=D1=82=D0=BE=D0=B2=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=20=D1=81=20ws2ipdef.h=20=D0=BD=D0=B0=20Windows)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ARCHITECTURE.md | 1086 +++++++++++++++++++++++++++++ USER_MANUAL.md | 784 +++++++++++++++++++++ _desc.md | 25 + lib/debug_config_doc.md | 112 +++ lib/getmyip_doc.md | 34 + lib/ll_queue_doc.md | 234 +++++++ lib/mem_doc.md | 48 ++ lib/memory_pool_doc.md | 57 ++ lib/platform_compat_doc.md | 81 +++ lib/radix_doc.md | 97 +++ lib/serialize_doc.md | 117 ++++ lib/sha256_doc.md | 32 + lib/socket_compat_doc.md | 99 +++ lib/swm_min_doc.md | 39 ++ lib/tcp_io_doc.md | 155 ++++ lib/timeout_heap_doc.md | 107 +++ lib/u_async_doc.md | 174 +++++ src/config_parser_doc.md | 98 +++ src/config_updater_doc.md | 67 ++ src/conn_mgr_doc.md | 145 ++++ src/control_server_doc.md | 49 ++ src/crc32_doc.md | 35 + src/db_sync_doc.md | 239 +++++++ src/dummynet_doc.md | 86 +++ src/eim_nat_doc.md | 47 ++ src/etcp_api_doc.md | 121 ++++ src/etcp_bbr_doc.md | 95 +++ src/etcp_connect_doc.md | 71 ++ src/etcp_connections_doc.md | 198 ++++++ src/etcp_debug_doc.md | 39 ++ src/etcp_doc.md | 289 ++++++++ src/etcp_dump_doc.md | 59 ++ src/etcp_loadbalancer_doc.md | 51 ++ src/etcp_router_doc.md | 131 ++++ src/firewall_doc.md | 28 + src/lwip_tcp/lwip_pbuf_doc.md | 72 ++ src/lwip_tcp/lwip_tcp_doc.md | 461 ++++++++++++ src/lwip_tcp/lwip_tcp_in_doc.md | 104 +++ src/lwip_tcp/lwip_tcp_opts.h | 2 - src/lwip_tcp/lwip_tcp_out_doc.md | 167 +++++ src/msg_transport_doc.md | 69 ++ src/nat_transport_doc.md | 44 ++ src/ntp_node_time_doc.md | 25 + src/ntp_time_doc.md | 27 + src/packet_dump_doc.md | 59 ++ src/pkt_normalizer_doc.md | 95 +++ src/proxy/icmp_proxy_doc.md | 77 ++ src/proxy/socks_proxy_doc.md | 132 ++++ src/proxy/tcp_proxy_client_doc.md | 204 ++++++ src/proxy/tcp_proxy_server_doc.md | 108 +++ src/proxy/udp_proxy_doc.md | 73 ++ src/route6_lib_doc.md | 50 ++ src/route_connectivity_doc.md | 64 ++ src/route_lib_doc.md | 68 ++ src/route_ping_doc.md | 62 ++ src/routing_doc.md | 44 ++ src/secure_channel_doc.md | 352 ++++++++++ src/stcp_client_doc.md | 55 ++ src/stcp_doc.md | 52 ++ src/stcp_link_doc.md | 73 ++ src/stcp_server_doc.md | 44 ++ src/topo_group_doc.md | 305 ++++++++ src/topo_node_doc.md | 170 +++++ src/topo_node_sqlite_doc.md | 39 ++ src/tun_if_doc.md | 54 ++ src/tun_route_doc.md | 49 ++ src/utun_doc.md | 50 ++ src/utun_instance_doc.md | 116 +++ tools/bping/bping_doc.md | 50 ++ tools/chatgui/chatgui_doc.md | 216 ++++++ tools/etcpmon/etcpmon_doc.md | 120 ++++ tools/proxy/proxy_doc.md | 29 + 72 files changed, 8908 insertions(+), 2 deletions(-) create mode 100644 ARCHITECTURE.md create mode 100644 USER_MANUAL.md create mode 100644 _desc.md create mode 100644 lib/debug_config_doc.md create mode 100644 lib/getmyip_doc.md create mode 100644 lib/ll_queue_doc.md create mode 100644 lib/mem_doc.md create mode 100644 lib/memory_pool_doc.md create mode 100644 lib/platform_compat_doc.md create mode 100644 lib/radix_doc.md create mode 100644 lib/serialize_doc.md create mode 100644 lib/sha256_doc.md create mode 100644 lib/socket_compat_doc.md create mode 100644 lib/swm_min_doc.md create mode 100644 lib/tcp_io_doc.md create mode 100644 lib/timeout_heap_doc.md create mode 100644 lib/u_async_doc.md create mode 100644 src/config_parser_doc.md create mode 100644 src/config_updater_doc.md create mode 100644 src/conn_mgr_doc.md create mode 100644 src/control_server_doc.md create mode 100644 src/crc32_doc.md create mode 100644 src/db_sync_doc.md create mode 100644 src/dummynet_doc.md create mode 100644 src/eim_nat_doc.md create mode 100644 src/etcp_api_doc.md create mode 100644 src/etcp_bbr_doc.md create mode 100644 src/etcp_connect_doc.md create mode 100644 src/etcp_connections_doc.md create mode 100644 src/etcp_debug_doc.md create mode 100644 src/etcp_doc.md create mode 100644 src/etcp_dump_doc.md create mode 100644 src/etcp_loadbalancer_doc.md create mode 100644 src/etcp_router_doc.md create mode 100644 src/firewall_doc.md create mode 100644 src/lwip_tcp/lwip_pbuf_doc.md create mode 100644 src/lwip_tcp/lwip_tcp_doc.md create mode 100644 src/lwip_tcp/lwip_tcp_in_doc.md create mode 100644 src/lwip_tcp/lwip_tcp_out_doc.md create mode 100644 src/msg_transport_doc.md create mode 100644 src/nat_transport_doc.md create mode 100644 src/ntp_node_time_doc.md create mode 100644 src/ntp_time_doc.md create mode 100644 src/packet_dump_doc.md create mode 100644 src/pkt_normalizer_doc.md create mode 100644 src/proxy/icmp_proxy_doc.md create mode 100644 src/proxy/socks_proxy_doc.md create mode 100644 src/proxy/tcp_proxy_client_doc.md create mode 100644 src/proxy/tcp_proxy_server_doc.md create mode 100644 src/proxy/udp_proxy_doc.md create mode 100644 src/route6_lib_doc.md create mode 100644 src/route_connectivity_doc.md create mode 100644 src/route_lib_doc.md create mode 100644 src/route_ping_doc.md create mode 100644 src/routing_doc.md create mode 100644 src/secure_channel_doc.md create mode 100644 src/stcp_client_doc.md create mode 100644 src/stcp_doc.md create mode 100644 src/stcp_link_doc.md create mode 100644 src/stcp_server_doc.md create mode 100644 src/topo_group_doc.md create mode 100644 src/topo_node_doc.md create mode 100644 src/topo_node_sqlite_doc.md create mode 100644 src/tun_if_doc.md create mode 100644 src/tun_route_doc.md create mode 100644 src/utun_doc.md create mode 100644 src/utun_instance_doc.md create mode 100644 tools/bping/bping_doc.md create mode 100644 tools/chatgui/chatgui_doc.md create mode 100644 tools/etcpmon/etcpmon_doc.md create mode 100644 tools/proxy/proxy_doc.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 00000000..4a2b41dd --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,1086 @@ +# 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 БД), msg_transport (IPC), 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, msg_transport, 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. msg_transport_init() // IPC транспорт + 7. running = 1 + 8. ntp_time_init() // NTP синхронизация + 9. ntp_node_time_init() // межузловое время +``` + +### 12.2 Завершение (строго обратный порядок) + +``` +utun_instance_destroy(instance): + 1. running = 0 + 2. Отмена NTP таймеров + 3. msg_transport_destroy() // сначала IPC + 4. 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.* diff --git a/USER_MANUAL.md b/USER_MANUAL.md new file mode 100644 index 00000000..968571ed --- /dev/null +++ b/USER_MANUAL.md @@ -0,0 +1,784 @@ +# uTun — Руководство пользователя + +uTun — децентрализованная VPN-система с шифрованием, надёжной доставкой данных, поддержкой mesh-сетей и встроенным TCP-прокси. Работает поверх UDP (основной транспорт) и TCP (STCP-резерв). Всё шифруется через X25519 + AES-128-CCM. Топология узлов обменивается автоматически (BGP-подобный протокол). + +--- + +## 1. Быстрый старт + +### 1.1. Сборка + +```bash +git clone <репозиторий> && cd utun3 +./build.sh --full -j4 # autoreconf + configure + make +``` + +Зависимости: `gcc`, `make`, `autotools` (autoconf/automake/libtool), `OpenSSL` (libssl-dev). + +### 1.2. Генерация ключей + +Ключи генерируются автоматически, если их нет в конфиге. Достаточно указать `my_node_name` — `config_updater` создаст пару X25519 и node_id: + +```ini +[global] +my_node_name=my-first-node +``` + +После первого запуска ключи пропишутся в конфиг автоматически. + +### 1.3. Первый запуск + +```bash +sudo ./src/utun -f -c myconfig.conf +``` + +- `-f` — foreground (без демонизации, видно логи в консоли) +- `-c myconfig.conf` — путь к конфигу +- `-p /var/run/utun.pid` — PID-файл +- `-l /var/log/utun.log` — лог-файл + +Без `-f` процесс уходит в демон (`fork` + `setsid`). + +### 1.4. Проверка работы + +```bash +ip addr show tun3 # TUN-интерфейс создан? +ping 10.23.1.1 # пингуем TUN-IP узла +ss -uln | grep 1333 # UDP-сокет слушает? +``` + +--- + +## 2. Архитектура (кратко) + +uTun — **однопоточная асинхронная** система. Один event loop (`epoll`/`poll`) на поток. Никаких `sleep()`, никакого блокирующего I/O. + +**Стек протоколов (5 уровней):** + +| Уровень | Назначение | +|---------|------------| +| 1. UDP/TUN/STCP | Физический транспорт: UDP-сокеты, TUN-интерфейс, TCP-резерв | +| 2. secure_channel | X25519 ECDH + AES-128-CCM шифрование + Ed25519 подписи | +| 3. ETCP | Надёжный транспорт (inflight, ACK, ретрансмиссии, BBR congestion control) | +| 4. LoadBalancer | Multi-link балансировка (min inflight + round-robin) | +| 5. etcp_router | Сервисная маршрутизация (seq, dedup, reorder, transit forwarding) | + +Два уровня надёжности: +- **ETCP** — per-connection, RTT-based ретрансмиссии +- **etcp_router** — per-service, 300ms ретрансмиссии с 17 попытками (~5 сек) + +Node identity: `SHA256(private_key)` → первые 8 байт → 63-bit `node_id`. + +--- + +## 3. Конфигурация + +Формат: INI-style. Секции `[section]`, строки `key=value`, комментарии `#`. + +### 3.1. `[global]` — Идентичность и базовые настройки + +```ini +[global] +my_node_name=home-server # имя узла (человекочитаемое) +my_node_id=5f75c7445af88e1f # ID узла (авто-генерируется) +my_private_key=d065b784...4fe441 # X25519 приватный ключ (hex, 64 символа) +my_public_key=86b51a8b...6a2d19 # X25519 публичный ключ (hex, 64 символа) + +tun_enabled=yes # создавать TUN-интерфейс (по умолчанию yes) +tun_ifname=tun3 # имя TUN (по умолчанию tun0) +tun_ip=10.23.1.1 # IP на TUN-интерфейсе +mtu=1500 # MTU для всех соединений + +debug_level=error # глобальный уровень отладки (none/error/warn/info/debug/trace) +log_file=/var/log/utun.log # путь к лог-файлу +``` + +### 3.2. `[server:NAME]` — Локальные UDP-сокеты + +```ini +[server: lan1] +addr=192.168.1.10:1333 # IP:порт для приёма входящих +type=public # public / nat / private / local +transport=udp # udp (по умолчанию) или tcp (STCP) +mtu=1400 # MTU для этого канала +so_mark=100 # SO_MARK для policy routing +netif=eth0 # привязка к интерфейсу +only_local=0 # 1 = только локальные подключения +``` + +Типы адресов: +- `public` — узел на публичном IP (прямая связь) +- `nat` — узел за NAT (нужен NAT traversal) +- `private` — локальная сеть (только соседи в том же сегменте) +- `local` — только loopback/локальные подключения + +### 3.3. `[client:NAME]` — Подключение к удалённым узлам + +```ini +[client: aeza] +link=lan1:85.192.42.96:1333 # server_name:ip:port (можно несколько link=) +link=lan1:85.192.42.96:1433 # второй канал к тому же узлу +peer_public_key=0206dbee...fbcc16 # публичный ключ удалённого узла +keepalive=1 # интервал keepalive (сек) +``` + +Формат `link`: `локальный_сервер:удалённый_ip:удалённый_порт`. Серверная секция должна быть объявлена выше по файлу. + +### 3.4. `[routing]` — Маршруты + +```ini +[routing] +my_subnet=10.23.1.0/24 # своя подсеть — анонсируется соседям через BGP +route_subnet=10.23.0.0/16 # добавляется в системную таблицу маршрутов через TUN +route_subnet=fd00:1234::/32 # IPv6-подсеть +``` + +### 3.5. `[debug]` — Настройка отладки + +Формат: `категория=уровень`. Уровни: `none`, `error`, `warn`, `info`, `debug`, `trace`. + +```ini +[debug] +etcp=trace # всё про ETCP +crypto=info # криптографические операции +bgp=info # обмен топологией +connection=info # сокеты и линки +general=info # общая информация +``` + +Основные категории (всего 26): `uasync`, `ll_queue`, `connection`, `etcp`, `crypto`, `memory`, `timing`, `config`, `tun`, `routing`, `timers`, `normalizer`, `bgp`, `socket`, `control`, `dump`, `traffic`, `debug`, `general`, `nat`, `keepalive`, `etcпroute`, `bbr`, `etcp_dump`. + +### 3.6. `[firewall]` — Правила фильтрации + +```ini +[firewall] +allow=all # разрешить всё (bypass) +allow=192.168.1.0/24 # разрешить подсеть +allow=10.0.0.1:80 # разрешить конкретный IP:порт +``` + +### 3.7. `[nat]` — EIM NAT + +```ini +[nat] +nat_enabled=yes +nat_tun_ifname=tun_external # внешний TUN (выход в интернет) +nat_via=gateway_ip # шлюз +port_start=10000 +port_end=20000 +forward=tcp:10.0.0.5:80:8080 # проброс: протокол:ip:внутр_порт:внеш_порт +``` + +### 3.8. `[tcp_proxy_client]` / `[tcp_proxy_server]` — TCP-прокси + +**Клиент (выход через exit-узел):** + +```ini +[tcp_proxy_client] +enabled=yes +tun_name=tun_proxy # имя TUN для прокси-трафика +tun_ip=10.200.30.1 # IP клиентского TUN +via_node=50e9e88658e91977 # node_id exit-узла + +socks_enabled=yes # встроенный SOCKS5-прокси +socks_addr=127.0.0.1:1082 # слушать SOCKS5 здесь + +http_proxy_enabled=yes # встроенный HTTP CONNECT-прокси +http_proxy_addr=127.0.0.1:8080 +``` + +**Exit-узел (сервер):** + +```ini +[tcp_proxy_server] +enabled=yes +``` + +Клиент настраивает браузер на SOCKS5 `127.0.0.1:1082` — весь TCP-трафик идёт через exit-узел. + +### 3.9. `[control]` — Сервер мониторинга + +```ini +[control] +control_ip=192.168.29.117 # IP для приёма подключений etcpmon +control_port=9090 +control_allow=192.168.0.0/16 # разрешённые подсети (можно несколько) +``` + +### 3.10. `[allowed_keys]` — Ограничение подключений + +```ini +[allowed_keys] +allow_all=1 # разрешить все ключи +# key=86b51a8b...6a2d19 # разрешить конкретный pubkey +``` + +### 3.11. `[ntp]` — Синхронизация времени + +```ini +[ntp] +enabled=yes +server=pool.ntp.org +server=time.google.com +interval=3600 # интервал синхронизации (сек) +``` + +### 3.12. `[network:NAME]` — Именованные сети + +```ini +[network: mynet] +id=5f75c7445af88e1f # 56-bit ID сети +pubkey=86b51a8b...6a2d19 # публичный ключ сети +signing_key=d065b784...4fe441 # ключ подписи (Ed25519) +``` + +### 3.13. Серверный vs клиентский конфиг + +| Параметр | Сервер | Клиент | +|----------|--------|--------| +| `[server]` секция | Обязательна (свои сокеты) | Обязательна (свои сокеты) | +| `[client]` секция | Нет | Обязательна (к кому подключаться) | +| `peer_public_key` | Нет | Обязателен в `[client]` | +| `my_private_key` | Свой | Свой | +| `my_public_key` | Свой | Свой | + +Важно: `[server]` обязателен в **обоих** случаях — это рабочий сокет узла. Клиент дополнительно указывает `[client]` с `peer_public_key` и `link`. + +--- + +## 4. Сценарии использования + +### 4.1. VPN между двумя узлами + +**Сервер** (публичный IP `85.192.42.96`): + +```ini +[global] +my_node_name=server +my_private_key= +my_public_key= +tun_ip=10.23.1.1 + +[server: main] +addr=85.192.42.96:1333 +type=public + +[routing] +my_subnet=10.23.1.0/24 +``` + +**Клиент** (за NAT, подключается к серверу): + +```ini +[global] +my_node_name=client +my_private_key= +my_public_key= +tun_ip=10.23.2.1 + +[server: uplink] +addr=0.0.0.0:1333 # можно 0.0.0.0 — клиенту не нужен фиксированный порт +type=nat + +[client: server] +link=uplink:85.192.42.96:1333 +peer_public_key= +keepalive=1 + +[routing] +my_subnet=10.23.2.0/24 +``` + +**Проверка связности:** + +```bash +# На сервере +ping 10.23.2.1 + +# На клиенте +ping 10.23.1.1 +``` + +### 4.2. Mesh-сеть из нескольких узлов + +Каждый узел = сервер (свои сокеты) + клиент (подключения к соседям). Пример для узла B, который подключается к A и C: + +```ini +[global] +my_node_name=node-b +my_private_key= +my_public_key= +tun_ip=10.23.2.1 + +[server: main] +addr=0.0.0.0:1333 +type=public + +[client: node-a] +link=main:85.192.42.96:1333 +peer_public_key= + +[client: node-c] +link=main:203.0.113.10:1333 +peer_public_key= + +[routing] +my_subnet=10.23.2.0/24 +``` + +Что происходит автоматически: +1. B устанавливает ETCP-соединения с A и C +2. Через BGP A узнаёт о подсетях C (и наоборот) +3. Узел A может отправлять пакеты к подсети C через B (transit forwarding) +4. `etcp_router` обеспечивает надёжную маршрутизацию через промежуточные узлы + +### 4.3. Exit-прокси (выход в интернет через удалённый узел) + +**Exit-узел** (с прямым выходом в интернет): + +```ini +[global] +my_node_name=exit-node +my_private_key= +my_public_key= + +[server: main] +addr=85.192.42.96:1333 +type=public + +[tcp_proxy_server] +enabled=yes +``` + +**Клиент** (выходит в интернет через exit-узел): + +```ini +[global] +my_node_name=client +my_private_key= +my_public_key= + +[server: main] +addr=0.0.0.0:1333 + +[client: exit] +link=main:85.192.42.96:1333 +peer_public_key= + +[tcp_proxy_client] +enabled=yes +tun_name=tun_proxy +tun_ip=10.200.30.1 +via_node= +socks_enabled=yes +socks_addr=127.0.0.1:1082 +``` + +Настройка браузера: SOCKS5 proxy `127.0.0.1:1082`. TCP-трафик идёт: браузер → SOCKS5 → lwIP → ETCP → exit-узел → интернет. + +### 4.4. EIM NAT (раздача интернета через NAT) + +```ini +[nat] +nat_enabled=yes +nat_tun_ifname=eth0 # внешний интерфейс +port_start=20000 +port_end=30000 +forward=tcp:192.168.1.100:80:8080 # проброс порта 8080 → 192.168.1.100:80 +forward=udp:192.168.1.100:53:53 # проброс DNS +``` + +### 4.5. Чат (chatgui) + +Chatgui — десктопный GUI-чат (Qt 6) со встроенным uTun-узлом. Вся связь — напрямую между узлами (P2P), без центральных серверов. + +**Сборка:** + +```bash +sudo apt install librlottie-dev zlib1g-dev qt6-base-dev +cd tools/chatgui && mkdir -p build && cd build +cmake .. && make -j4 +./chatgui +``` + +**Запуск:** + +```bash +setsid env DISPLAY=:1.0 QT_ACCESSIBILITY=1 ./chatgui & disown +``` + +Chatgui работает в двух потоках: +- **GUI-поток** — Qt, отрисовка, чтение из SQLite +- **uasync-поток** — uTun-узел, сеть, криптография, ETCP, запись в SQLite + +**Основные возможности:** +- Создание каналов и групп +- Обмен сообщениями с анимированными эмодзи (TGS) +- Invite-ссылки (`utun://...`) с QR-кодами +- P2P синхронизация через Merkle-деревья +- Авто-подключение к известным узлам из БД + +--- + +## 5. Топология сети + +### 5.1. Типы адресов + +| Тип | Описание | Пример | +|-----|----------|--------| +| `INTERFACE` | Адрес на сетевом интерфейсе | `eth0: 192.168.1.10` | +| `NAT` | Внешний адрес после NAT (детектируется) | `85.192.42.96:54321` | +| `REAL` | Публичный адрес (совпадает interface == NAT) | `85.192.42.96:1333` | + +### 5.2. Как узлы находят друг друга + +1. **Прямое подключение** — через `[client]` секцию с явным IP:портом +2. **BGP-обмен** — при подключении к одному узлу, узнаёшь о всех его соседях +3. **Локальное сканирование** — `conn_mgr` периодически сканирует локальную сеть +4. **Invite-ссылки** — `utun://` ссылки с закодированными адресами и ключами + +### 5.3. NAT traversal + +`conn_mgr` реализует трёхфазное подключение: + +1. **DIRECT** (5 сек) — прямое INIT-рукопожатие со всеми известными адресами +2. **REVERSE** (15 сек) — если клиент за NAT, сервер с прямым IP: клиент шлёт свои адреса через BGP, сервер подключается сам +3. **INDIRECT** (15 сек) — оба за NAT: выбираются общие кандидаты-посредники по min RTT, трафик идёт через `etcp_router` + +Типы NAT, которые определяет uTun: +- **EIM** (Endpoint-Independent Mapping) — один внешний порт для всех назначений +- **Strict** (Address/Restricted/Symmetric) — разные порты + +### 5.4. Группы + +Два типа групп: +- **UTUN** (`group_id=0x8000000000000000`) — VPN-сеть: обмен подсетями, маршруты +- **CHAT** — чат-группа: только узлы, без подсетей, персистентность в SQLite + +Узлы разных групп изолированы. + +--- + +## 6. Мониторинг и отладка + +### 6.1. Уровни отладки + +``` +none < error < warn < info < debug < trace +``` + +Настройка через `-d` в CLI (имеет приоритет) или секцию `[debug]` в конфиге: + +```bash +./src/utun -f -c myconfig.conf -d "etcp=trace,crypto=info" +``` + +Или эквивалент в конфиге: + +```ini +[debug] +etcp=trace +crypto=info +bgp=info +``` + +### 6.2. Категории отладки (26 шт) + +| Категория | Что логирует | +|-----------|-------------| +| `etcp` | Протокол ETCP: очереди, retrans, ACK, RTT | +| `crypto` | Шифрование/дешифрование, ключи, nonce | +| `connection` | Сокеты, линки, INIT handshake, keepalive | +| `bgp` | Обмен топологией (NODEINFO, WITHDRAW) | +| `routing` | Таблица маршрутов, поиск путей | +| `tun` | TUN-интерфейс: чтение/запись | +| `traffic` | Данные трафика | +| `general` | Общая информация | +| `bbr` | BBR congestion control | +| `keepalive` | Адаптивный keepalive | +| `memory` | Выделение/освобождение памяти | +| `dump` | Hex-дампы пакетов | +| `etcp_dump` | Декодирование ETCP-пакетов | + +### 6.3. etcpmon — Windows GUI монитор + +Подключается к `control_server` узла по TCP (порт из `control_port`). Показывает: +- Список соединений и их состояние +- RTT, потери, inflight +- Графики метрик +- Дамп пакетов + +### 6.4. control_server — TCP API + +```ini +[control] +control_ip=0.0.0.0 +control_port=9090 +control_allow=192.168.0.0/16 +``` + +Любой TCP-клиент может подключиться и получать события в реальном времени (JSON-подобный формат). + +### 6.5. Логи и дампы + +Лог-файл указывается через `-l`: + +```bash +./src/utun -f -c myconfig.conf -l /tmp/utun.log +``` + +Hex-дамп пакетов включается категорией `dump=trace`: + +```ini +[debug] +dump=trace +``` + +--- + +## 7. NAT и Firewall + +### 7.1. Как uTun определяет тип NAT + +1. Клиент подключается к серверу +2. Сервер видит внешний IP:порт клиента (из UDP-пакета) +3. Сервер через **третий узел** (посредник) пингует клиента на этот внешний IP:порт +4. Если клиент ответил — NAT EIM (один mapping для всех) +5. Если нет — NAT Strict + +Результат сохраняется в `link->nat_type` и broadcast-ится всем соседям через BGP. + +### 7.2. Firewall + +```ini +[firewall] +allow=all # всё разрешено +allow=10.0.0.0/8 # разрешить подсеть +allow=192.168.1.5:22 # разрешить IP:порт +``` + +По умолчанию без секции `[firewall]` — фильтрация выключена. + +### 7.3. Проброс портов + +```ini +[nat] +forward=tcp:192.168.1.100:80:8080 # входящий порт 8080 → 192.168.1.100:80 +forward=udp:10.0.0.53:53:53 # DNS +``` + +--- + +## 8. Устранение неполадок + +### 8.1. Нет связи — проверка INIT handshake + +Включите: + +```ini +[debug] +connection=debug +crypto=debug +``` + +В логах ищите: +- `INIT_REQUEST sent to ...` / `INIT_RESPONSE received from ...` — handshake +- `session_ready=1` — ключи согласованы, шифрование работает +- `ETCP_KEEPALIVE` — keepalive-пакеты идут +- Если нет — проверьте firewall на портах, доступность IP + +### 8.2. Пакеты теряются — проверка метрик + +```bash +# В логах с etcp=debug смотрите: +grep "retrans" /tmp/utun.log # ретрансмиссии +grep "rtt=" /tmp/utun.log # задержки +grep "lost" /tmp/utun.log # потери +``` + +Каждые 10 минут пишутся гистограммы метрик с RTT-бакетами и loss-бакетами. + +### 8.3. Высокая задержка — keepalive + +Адаптивный keepalive: период растёт от 200ms до 10s при отсутствии трафика, сбрасывается к 200ms при появлении. Таймаут = период × 10. + +Проверьте: + +```ini +[debug] +keepalive=debug +``` + +### 8.4. Утечки памяти — проверка пулов + +```ini +[debug] +memory=debug +``` + +При завершении `u_report_unfreed_blocks()` показывает все неосвобождённые блоки. + +### 8.5. Дамп состояния + +ETCP предоставляет функцию `etcp_dump_all_conns()` для вывода полного состояния всех соединений (очереди, inflight, счётчики). Включается через `etcp_dump=trace`. + +### 8.6. ASAN-сборка (поиск повреждений памяти) + +```bash +./build.sh --asan -j4 +ASAN_OPTIONS=detect_leaks=0:halt_on_error=0 ./src/utun -f -c myconfig.conf +``` + +Краш-логи ASAN пишутся в `/tmp/utun_asan.`. + +--- + +## 9. Производительность + +### 9.1. BBR congestion control + +BBR работает per-link. Основные настройки: + +```ini +[global] +bbr_max_cwnd=1048576 # максимум окна перегрузки (1MB по умолчанию) +``` + +BBR автоматически измеряет пропускную способность через burst-измерения (12 пакетов пачкой, замер inter-packet gap). Периодичность burst — не чаще 500ms. + +### 9.2. Memory pools + +Три пула в `UTUN_INSTANCE`: +- `data_pool` — данные пакетов (payload) +- `pkt_pool` — `struct ETCP_DGRAM` (сетевые пакеты) +- `ack_pool` — `struct ACK_PACKET` (подтверждения) + +Пулы аллоцируются upfront при старте. Статистику можно посмотреть через control_server. + +### 9.3. Multi-link балансировка + +LoadBalancer выбирает линк с минимальным inflight, с round-robin для равных. Traffic shaper ограничивает отправку до `optimal_inflight`: + +``` +timeout = (rtt_avg_10 * 32 + jitter * 32) / 16 +``` + +Оптимальное окно: `optimal_inflight = sum(inflight_lim_bytes)` + +### 9.4. Keepalive + +Адаптивный режим: +- Начальный период = `keepalive` из `[client]` (сек) +- При трафике → сброс к начальному +- Без трафика → растёт ×1.05 до 10 секунд +- Таймаут = период × 10 + +--- + +## 10. CLI-справка + +``` +utun [опции] + +Опции: + -f Foreground (без демонизации) + -c FILE Конфигурационный файл (по умолчанию: utun.conf) + -p FILE PID-файл (по умолчанию: /var/run/utun.pid) + -l FILE Лог-файл (по умолчанию: utun.log) + -d CONFIG Отладка: категория=уровень,... (переопределяет конфиг) + -h Справка +``` + +**Сигналы:** +- `SIGTERM` / `SIGINT` — graceful shutdown (закрытие соединений, освобождение ресурсов) +- `SIGHUP` — reload конфига (селективный: меняются только изменённые сокеты/клиенты; полный — при изменении ключей или TUN) + +--- + +## 11. Типовые конфигурации + +### Минимальный сервер + +```ini +[global] +my_node_name=server +tun_ip=10.23.1.1 + +[server: main] +addr=0.0.0.0:1333 +``` + +### Минимальный клиент + +```ini +[global] +my_node_name=client +tun_ip=10.23.2.1 + +[server: main] +addr=0.0.0.0:1333 + +[client: server] +link=main:85.192.42.96:1333 +peer_public_key= +``` + +### Mesh-узел с несколькими пирами + +```ini +[global] +my_node_name=mesh-node-3 +tun_ip=10.23.3.1 + +[server: main] +addr=0.0.0.0:1333 + +[client: node-1] +link=main:10.0.0.1:1333 +peer_public_key= + +[client: node-2] +link=main:10.0.0.2:1333 +peer_public_key= + +[routing] +my_subnet=10.23.3.0/24 +``` + +### Exit-узел с TCP-прокси + +```ini +[global] +my_node_name=exit-eu +tun_ip=10.23.100.1 + +[server: main] +addr=85.192.42.96:1333 +type=public + +[tcp_proxy_server] +enabled=yes + +[routing] +my_subnet=10.23.100.0/24 +``` + +### Клиент с SOCKS5-прокси + +```ini +[global] +my_node_name=home-laptop + +[server: main] +addr=0.0.0.0:1333 + +[client: exit] +link=main:85.192.42.96:1333 +peer_public_key= + +[tcp_proxy_client] +enabled=yes +socks_enabled=yes +socks_addr=127.0.0.1:1082 +via_node= +``` diff --git a/_desc.md b/_desc.md new file mode 100644 index 00000000..75c6d1be --- /dev/null +++ b/_desc.md @@ -0,0 +1,25 @@ +Задача сделать полное архитектурное описание проекта. +Как делаем + +1. определяем список проектов. продумываем последовательность разбора проектов - зависимости - что от чего зависит, что лучше разбирать в начале. + +Далее по каждому проекту: + +2. разбиваешь весь проект на логические модули. + поручаешь субагентам (параллельно запускаешь до 8 потоков), балансируй нагрузку на агентов (по объему кода): если модули короткие - можно 2-3 простых одному субагенту. + - проанализировать логику работы модуля + - понять как этим пользоваться с позиции пользователя (ты - архитектор и пользователь - типовой сценарий использования модуля с точки зрения удобства и лёгкости использования). + какие есть потенциальные проблемные места, можно ли пользоваться в многопоточной архитектуре, можно ли в асинхронной архитектуре, можно ли делать close из callback (если есть коллбэки и модулю асинхронный). какие есть еще нюансы и ограничения. + - создать (или обновить если есть) описание работы модуля - файл рядом с исходниками называется [module_name]_doc.md + описание должно быть с точки зрения пользователя - в первую очередь как пользоваться и какие нюансы. понятным языком (если используется терминология специфичная для модуля - то должна быть понятна или пояснения). + Схема описания: + 1. назначение модуля, зачем нужен, что делает + 2. как пользоваться, лучше типовой сценарий (с нюансами если есть), + 3. кратко пояснения по апи и нюансы если есть + + далее читаешь полностью все эти описания и формируешь итоговое описание проекта. + Продумай какая структура документации лучше подходит, разбей на главы. + И сделай общее описние проекта (user manual). + +твоя задача на основе сформированной документации разобраться как работает utun. ключевое слово =- хорошо. хорошо понять все модули, их взаимодействое, возможности, облести применимости и +ограничения. понять архитектурно как работает проект собранный из этих модулей. и только после этого написать документацию. diff --git a/lib/debug_config_doc.md b/lib/debug_config_doc.md new file mode 100644 index 00000000..8c72b525 --- /dev/null +++ b/lib/debug_config_doc.md @@ -0,0 +1,112 @@ +# debug_config — Runtime-система отладочного логирования + +## 1. Назначение + +Модуль обеспечивает гибкое управление отладочным выводом без перекомпиляции. Позволяет: +- Задавать **глобальный уровень** логирования (error/warn/info/debug/trace). +- **Покатегорийно** включать/выключать или переопределять уровень (26 категорий: etcp, crypto, tun, routing, bgp, bbr, …). +- Вести **двойной вывод**: в консоль (stdout) и/или в файл. +- Настраивать формат сообщений (временные метки с микросекундами, имя функции, файл:строка). +- Парсить конфигурационную строку вида `"etcp=trace,config=info"` из конфиг-файла. + +Логика вывода: итоговый уровень = **max(глобальный уровень, уровень категории)**. Сообщение печатается, если его уровень ≤ итогового. + +## 2. Как пользоваться + +### 2.1. Инициализация + +```c +#include "debug_config.h" + +debug_config_init(); // уровень по умолчанию: ERROR, консоль включена +debug_set_level(DEBUG_LEVEL_INFO); // поднять глобальный уровень до INFO +debug_enable_file_output("/tmp/utun.log", 1); // писать в файл (truncate=1) +debug_apply_category_config("etcp", "trace"); // для ETCP-категории — всё +``` + +### 2.2. Макросы логирования + +```c +DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "Decrypt failed: len=%d", len); +DEBUG_WARN(DEBUG_CATEGORY_ETCP, "Retransmit timeout, seq=%u", seq); +DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Socket created on port %d", port); +DEBUG_DEBUG(DEBUG_CATEGORY_ROUTING, "Route added: %s -> %s", dst, gw); +DEBUG_TRACE(DEBUG_CATEGORY_TUN, "Packet recv: %zu bytes", n); +``` + +Макросы делают проверку `debug_should_output()` **перед** вызовом `debug_output()`, поэтому дорогой `vsnprintf` не вызывается, если вывод не нужен. + +### 2.3. Конфигурационная строка + +Формат: `"категория=уровень,категория=уровень,..."`. Парсится вызовом `debug_parse_config()`. Пример из конфиг-файла: + +```ini +debug = etcp=trace,config=info,crypto=error +``` + +Категории: `none, uasync, ll_queue, connection, etcp, crypto, memory, timing, config, tun, routing, timers, normalizer, bgp, socket, control, dump, traffic, debug, general, nat, keepalive, etcp_route, bbr, etcp_dump, connectivity, all`. + +Уровни: `disabled, none, error, warn, info, debug, trace`. + +**Важно:** старый формат `ll_queue:debug` (двоеточие) тоже работает через `debug_parse_config`, но в конфиге рекомендуется `=`. + +### 2.4. Двойной вывод (консоль + файл) + +- По умолчанию вывод в **stdout**. +- `debug_enable_file_output(path, truncate)` — перенаправляет вывод в файл, консоль отключается. +- `debug_disable_file_output()` — закрывает файл. +- `debug_reopen_log()` — переоткрывает лог-файл (используется по SIGHUP для ротации). +- `debug_enable_console(1/0)` — ручное управление консольным выводом. + +### 2.5. Вспомогательные утилиты + +```c +log_dump(DEBUG_LEVEL_TRACE, DEBUG_CATEGORY_DUMP, "payload", data, len); // hex-дамп (до 128 байт) + +ip_str_t s = sockaddr_storage_to_str(&addr); // "192.168.1.1:8080" или "[::1]:443" +ip_str_t s = ip_to_str(&addr, AF_INET); // "192.168.1.1" (без порта) +``` + +### 2.6. Нюансы + +- **Потокобезопасность:** модуль не потокобезопасен. Несколько потоков, одновременно вызывающих макросы, могут перемешать вывод (но не упадут). Файловый вывод использует `fflush()` после каждой строки. +- **Буфер:** размер строки лога ограничен 4096 байт (`BUFFER_SIZE`), длинные сообщения обрезаются. +- **`debug_set_output_file`** против `debug_enable_file_output`: первая вызывается один раз (кто первый — того и тапки), вторая позволяет переоткрыть файл. +- **Флаг `-f` (foreground):** в foreground-режиме вывод идёт в stdout, в daemon-режиме — в файл. + +## 3. API + +| Функция/макрос | Назначение | +|---|---| +| `debug_config_init()` | Инициализация: глобальный уровень ERROR, консоль включена, метки времени/функций/файлов — да | +| `debug_set_level(level)` | Задать глобальный уровень (ERROR..TRACE) | +| `debug_set_category_level(cat, level)` | Задать уровень конкретной категории (0 = использовать глобальный) | +| `debug_set_category_level_by_name("etcp", "trace")` | То же, но именами строк | +| `debug_apply_category_config("etcp", "trace")` | Установить уровень категории с логированием результата | +| `debug_apply_global_level("info")` | Установить глобальный уровень по имени строки | +| `debug_parse_config("etcp=trace,config=info")` | Разобрать конфигурационную строку | +| `debug_should_output(level, cat)` | Проверить, нужно ли выводить сообщение данного уровня/категории | +| `debug_output(level, cat, func, file, line, fmt, ...)` | Форматировать и вывести сообщение (вызывается макросами) | +| `debug_enable_file_output(path, truncate)` | Включить вывод в файл (консоль отключается) | +| `debug_disable_file_output()` | Закрыть файл вывода | +| `debug_reopen_log()` | Переоткрыть лог-файл (для SIGHUP/ротации) | +| `debug_enable_console(1/0)` | Включить/выключить консольный вывод | +| `debug_get_level()` | Получить текущий глобальный уровень | +| `get_category_by_name("etcp")` | Получить ID категории по имени строки | +| `debug_level_from_name("trace")` | Получить уровень по имени строки | +| `debug_get_category_name(cat)` | Получить имя категории по ID | +| `log_dump(level, cat, prefix, data, len)` | Hex-дамп данных (первые 128 байт) в лог | +| `ip_to_str(addr, family)` | IP-адрес → строка (без порта). Статический буфер | +| `sockaddr_storage_to_str(addr)` | sockaddr_storage → "ip:port" (IPv6 в скобках). Статический буфер | +| `DEBUG_ERROR/WARN/INFO/DEBUG/TRACE(cat, fmt, ...)` | Макросы логирования с проверкой `debug_should_output()` | +| `DEBUG_OUTPUT(fmt, ...)` | Устаревший макрос совместимости (ERROR, категория ALL) | + +**Глобальная переменная:** `g_debug_config` (тип `debug_config_t`) — хранит всё состояние системы логирования. + +**Структура `debug_config_t`:** +- `level` — глобальный уровень (по умолчанию ERROR) +- `category_levels[26]` — уровни по категориям (NONE = использовать глобальный, DISABLED = никогда не выводить) +- `timestamp_enabled` / `function_name_enabled` / `file_line_enabled` — формат вывода +- `file_output` — FILE* файла (NULL если вывод в консоль) +- `console_enabled` — вывод в stdout +- `thread_marker` — числовой маркер потока (0 = нет) diff --git a/lib/getmyip_doc.md b/lib/getmyip_doc.md new file mode 100644 index 00000000..716530b8 --- /dev/null +++ b/lib/getmyip_doc.md @@ -0,0 +1,34 @@ +# Get My IP (getmyip.h) + +## 1. Назначение +Определение локального IP-адреса, который ОС выберет для исходящего UDP-соединения на заданный удалённый хост. Используется в `etcp_connections.c` при установке соединения: зная remote-адрес, модуль узнаёт, какой source IP ядро назначит пакетам (нужно для проверки NAT и привязки сокета к правильному интерфейсу). + +## 2. Как пользоваться +Передать локальный адрес-подсказку (или `0.0.0.0` для автоопределения) и удалённый адрес. Функция создаст временный UDP-сокет, сделает bind+connect, считает выбранный IP через `getsockname` и вернёт его в `result` (порт = 0, только IP). + +```c +struct sockaddr_storage local_hint; // AF_INET6, sin6_addr = in6addr_any +struct sockaddr_storage remote; // AF_INET6, адрес сервера +struct sockaddr_storage chosen_ip; + +// ... заполнить local_hint и remote ... + +if (get_outgoing_local_ip(&local_hint, &remote, &chosen_ip) == 0) { + // chosen_ip.ss_family == AF_INET6, sin6_addr содержит выбранный source IP + // chosen_ip.sin6_port == 0 +} +``` + +**Нюансы:** +- Функция временно создаёт и закрывает UDP-сокет — нельзя вызывать в hot path +- `local_addr` и `remote_addr` должны быть одного семейства (оба AF_INET или оба AF_INET6) +- Порт в `local_addr` заменяется на 0 (чтобы не конфликтовать с основными сокетами) +- Порт в `result` всегда 0 — только IP + +## 3. API + +### Функция +- **`get_outgoing_local_ip(local_addr, remote_addr, result)`** — создаёт временный UDP-сокет, привязывает к `local_addr` (с портом 0), делает `connect` к `remote_addr` для вычисления маршрута ядром, получает выбранный source IP через `getsockname`. Возвращает 0 при успехе, -1 при ошибке (NULL-аргументы, несовпадение семейств, ошибки сокета). + +### Зависимости +- `socket_compat.h` — кроссплатформенные обёртки: `socket_create_udp`, `SOCKET_INVALID`, `socket_close_wrapper`, `socket_get_error`, `socket_strerror` diff --git a/lib/ll_queue_doc.md b/lib/ll_queue_doc.md new file mode 100644 index 00000000..d8df6aad --- /dev/null +++ b/lib/ll_queue_doc.md @@ -0,0 +1,234 @@ +# ll_queue — Двусвязная FIFO-очередь с автозабором и backpressure + +## 1. Назначение + +Двусвязная очередь элементов (FIFO) с автоматическим вызовом callback при появлении данных, пороговым ожиданием освобождения (backpressure), хеш-индексом для поиска и поддержкой пулов памяти. Используется как основной механизм передачи данных между компонентами внутри одного потока uasyc: TUN ↔ ETCP ↔ routing ↔ normalizer и т.д. + +Работает **строго в одном потоке** (один uasyc). Для многопоточного доступа не предназначена. + +## 2. Как пользоваться + +### 2.1. Создание очереди + +```c +// Без хеша (простая очередь) +struct ll_queue* q = queue_new(ua, 0, 0, 0, "my_queue"); + +// С хешем — для быстрого поиска по ключу внутри data[] +// hash_size=256, ключ лежит в data[] по смещению 0, размер ключа 4 байта +struct ll_queue* q = queue_new(ua, 256, 0, 4, "my_hash_queue"); +``` + +### 2.2. Создание элемента + +Три способа, по убыванию производительности: + +```c +// 1. Из memory pool (самый быстрый) +struct ll_entry* e = queue_entry_new_from_pool(instance->pkt_pool); + +// 2. Через malloc (data[] фиксированного размера одним блоком) +struct ll_entry* e = queue_entry_new(sizeof(struct my_data)); + +// 3. Элемент + отдельный буфер dgram (для переменного размера данных) +struct ll_entry* e = ll_alloc_lldgram(dgram_len); +memcpy(e->dgram, src_data, dgram_len); +e->len = dgram_len; +``` + +### 2.3. Запись в очередь (backpressure) + +**Главное правило: не забиваем очередь!** Добавляем следующий элемент только когда очередь опустела до заданного порога. Для этого используется Пороговое ожидание: + +```c +// 1. Настроить порог (например: ждать пока count<=0, т.е. очередь совсем пуста) +queue_set_threshold(q, 0, 0); + +// 2. В структуре-производителе завести handle: +struct queue_waiter_handle my_waiter = {0}; + +// 3. При готовности отправить — зарегистрировать ожидание: +static void my_send_cb(struct ll_queue* q, void* arg) { + struct my_producer* p = (struct my_producer*)arg; + struct ll_entry* e = ... // создать элемент + e->len = data_len; + queue_data_put_with_index(q, e); + // Снова зарегистрироваться на следующую порцию + queue_waiter_wait(q, &p->waiter, my_send_cb, p); +} + +// Первичная регистрация: +queue_waiter_wait(q, &my_waiter, my_send_cb, producer); + +// 4. При деинициализации — отменить ожидание: +queue_waiter_cancel(q, &my_waiter); +``` + +### 2.4. Чтение из очереди (callback) + +```c +// Установить callback — будет вызываться при появлении элементов +queue_set_callback(q, my_callback, my_data); + +static void my_callback(struct ll_queue* q, void* arg) { + // 1. ИЗВЛЕЧЬ элемент + struct ll_entry* e = queue_data_get(q); + if (!e) { queue_resume_callback(q); return; } + + // 2. ОБРАБОТАТЬ + if (e->dgram && e->len > 0) { + do_something(e->dgram, e->len); + queue_dgram_free(e); // сначала освободить dgram + } + queue_entry_free(e); // потом освободить сам entry + + // 3. ОБЯЗАТЕЛЬНО возобновить callback + queue_resume_callback(q); +} +``` + +**Критически важно:** `queue_data_get()` автоматически приостанавливает callback-и (`callback_suspended = 1`). Без вызова `queue_resume_callback()` очередь **навсегда застрянет** — callback больше никогда не вызовется. + +Во **всех ветках** (включая ранние return по ошибке) должен быть `queue_resume_callback(q)`: + +```c +// ПРАВИЛЬНО: +if (!e) { queue_resume_callback(q); return; } +if (!e->dgram) { queue_dgram_free(e); queue_entry_free(e); queue_resume_callback(q); return; } + +// НЕПРАВИЛЬНО — очередь зависнет: +if (!e) return; +``` + +### 2.5. Поиск по индексу + +```c +// Создать очередь с хешем (ключ 4 байта по смещению 0): +struct ll_queue* q = queue_new(ua, 256, 0, 4, "indexed_q"); + +// Добавлять элементы через _with_index (хеш вычисляется автоматически): +queue_data_put_with_index(q, entry); + +// Искать: +uint32_t key = 42; +struct ll_entry* found = queue_find_data_by_index(q, &key); + +// Если несколько элементов с одним ключом — продолжать поиск: +struct ll_entry* next = queue_find_next_by_index(q, &key, found); +``` + +### 2.6. Освобождение ресурсов + +```c +// 1. Снять callback +queue_set_callback(q, NULL, NULL); + +// 2. Отменить всех waiter-ов +queue_waiter_cancel(q, &my_waiter); + +// 3. Извлечь и освободить ВСЕ оставшиеся элементы +struct ll_entry* e; +while ((e = queue_data_get(q)) != NULL) { + queue_dgram_free(e); + queue_entry_free(e); +} +// После извлечения всех элементов resume не нужен — callback уже снят. + +// 4. Освободить очередь +queue_free(q); +``` + +**Важно:** `queue_free()` **не освобождает элементы**. Их нужно извлечь через `queue_data_get()` и освободить вручную. + +### 2.7. Приоритетная вставка (LIFO) + +```c +// Вставить в начало очереди (высокий приоритет): +queue_data_put_first(q, entry); +queue_data_put_first_with_index(q, entry); +``` + +### 2.8. Удаление из середины + +```c +queue_remove_data(q, entry); // удаляет из очереди, НЕ освобождает память +queue_dgram_free(entry); // освободить dgram отдельно +queue_entry_free(entry); // освободить entry отдельно +``` + +После `queue_remove_data()` элемент больше не принадлежит очереди — вызывающий сам отвечает за его освобождение. + +### 2.9. Callback при опустошении + +```c +static void on_empty(struct ll_queue* q, void* arg) { + // Очередь стала пустой (count==0) + // Вызывается однократно, после вызова сбрасывается +} + +queue_set_empty_callback(q, on_empty, my_data); +``` + +## 3. API + +### Создание / уничтожение + +| Функция | Назначение | +|---------|-----------| +| `queue_new(ua, hash_size, index_offset, index_size, name)` | Создать очередь. `hash_size=0` — без хеша. `ua` обязателен (для uasync_call_soon). | +| `queue_free(q)` | Освободить очередь (НЕ элементы — их надо извлечь и освободить отдельно). | + +### Создание / освобождение элементов + +| Функция | Назначение | +|---------|-----------| +| `queue_entry_new(data_size)` | Выделить entry с data[] через malloc. | +| `queue_entry_new_from_pool(pool)` | Выделить entry из memory pool. | +| `ll_alloc_lldgram(len)` | Выделить entry + отдельный буфер dgram (malloc). | +| `queue_entry_free(entry)` | Освободить entry (возврат в pool или free). НЕ освобождает dgram! | +| `queue_dgram_free(entry)` | Освободить dgram (через dgram_free_fn, pool или free). | + +### Чтение (callback) + +| Функция | Назначение | +|---------|-----------| +| `queue_set_callback(q, cbk_fn, arg)` | Установить callback автозабора. Вызывается при наличии элементов. | +| `queue_data_get(q)` | Извлечь элемент из головы. Приостанавливает callback до `queue_resume_callback()`. | +| `queue_resume_callback(q)` | **Обязательно** вызвать после обработки элемента. Планирует отложенный вызов callback через uasync_call_soon. | + +### Запись + +| Функция | Назначение | +|---------|-----------| +| `queue_data_put(q, entry)` | Добавить в конец (FIFO). Для очередей БЕЗ хеша. | +| `queue_data_put_with_index(q, entry)` | Добавить в конец с хеш-индексом. | +| `queue_data_put_first(q, entry)` | Добавить в начало (LIFO, высокий приоритет). | +| `queue_data_put_first_with_index(q, entry)` | Добавить в начало с хеш-индексом. | + +### Backpressure + +| Функция | Назначение | +|---------|-----------| +| `queue_set_threshold(q, max_packets, max_bytes)` | Задать общий порог. Когда `count <= max_packets` И `total_bytes <= max_bytes`, waiter-ы пробуждаются (FIFO). | +| `queue_waiter_wait(q, h, callback, arg)` | Зарегистрировать ожидание. Возвращает 1 (уже выполнено), 0 (в очереди), -1 (ошибка). | +| `queue_waiter_cancel(q, h)` | Отменить ожидание. Безопасно вызывать в любом состоянии. | +| `queue_set_waiter_defer(q, enable)` | Отложенный вызов waiter-ов (через uasync_call_soon). | +| `queue_set_empty_callback(q, cbk_fn, arg)` | Одноразовый callback при count==0. | +| `queue_set_on_get(q, cbk_fn, arg)` | Callback после каждого queue_data_get (deferred). | + +### Поиск / удаление + +| Функция | Назначение | +|---------|-----------| +| `queue_find_data_by_index(q, index_key)` | Найти по ключу (требуется hash_size>0). Размер ключа = q->index_size. | +| `queue_find_next_by_index(q, index_key, prev_entry)` | Следующий элемент с тем же ключом. | +| `queue_remove_data(q, entry)` | Удалить из очереди (память НЕ освобождает). | + +### Утилиты + +| Функция | Назначение | +|---------|-----------| +| `queue_entry_count(q)` | Текущее количество элементов. | +| `queue_total_bytes(q)` | Суммарный объём данных (сумма len). | +| `queue_set_size_limit(q, lim)` | Ограничение на количество элементов (при превышении новые удаляются). | +| `queue_check_consistency(q)` | Проверка целостности (счётчики, циклы, prev/next). Только для отладки. | diff --git a/lib/mem_doc.md b/lib/mem_doc.md new file mode 100644 index 00000000..4ec24b87 --- /dev/null +++ b/lib/mem_doc.md @@ -0,0 +1,48 @@ +# mem (Memory Wrappers с отслеживанием утечек) + +## 1. Назначение + +Обёртки над стандартными `malloc`/`calloc`/`realloc`/`free`/`strdup` с детекцией: +- **Buffer overflow/underflow** — каждый блок окружён канарейками (`0xDEADBEEF`) и padding-областями, которые проверяются при `u_free` и `u_realloc`. При повреждении — подробный дамп блока и аварийное завершение. +- **Double free** — двусвязный список всех живых аллокаций; повторное освобождение детектируется по отсутствию блока в списке. +- **Утечки памяти** — `u_report_unfreed_blocks()` при завершении выводит все незакрытые блоки с местом аллокации (`файл:строка`), размером и hex-дампом данных. + +Функции потокобезопасны (мьютекс/критическая секция). + +## 2. Как пользоваться + +```c +#include "mem.h" + +// Вместо malloc/calloc/realloc/free/strdup используй соответствующие макросы: +void* buf = u_malloc(data_size); +char* str = u_strdup("hello"); +buf = u_realloc(buf, new_size); +u_free(buf); +u_free(str); + +// В конце main (перед return) — отчёт о незакрытых блоках: +u_report_unfreed_blocks(); + +// Текущее число живых аллокаций (для диагностики): +size_t count = u_get_allocated_count(); +``` + +**Важно:** +- Макросы (`u_malloc`, `u_calloc`, `u_free`, и т.д.) автоматически подставляют `__FILE__:__LINE__` через `LOCATION` — это нужно для диагностики утечек и повреждений. Напрямую вызывай `_impl`-функции только если нужно явно передать location. +- `u_realloc(NULL, size)` эквивалентен `u_malloc(size)`, `u_realloc(ptr, 0)` эквивалентен `u_free(ptr)`. +- Потокобезопасность гарантирована, но аллокации с метаданными заметно дороже обычного `malloc` — используй `memory_pool` для hot-path объектов. +- При обнаружении повреждения (overflow/underflow/double-free) программа завершается через `exit(EXIT_FAILURE)` после вывода отладочной информации. + +## 3. API + +| Функция/макрос | Описание | +|---|---| +| `u_malloc(size)` | Аналог `malloc` с boundary check и трекингом | +| `u_calloc(nmemb, size)` | Аналог `calloc` (выделяет + обнуляет) | +| `u_realloc(ptr, size)` | Аналог `realloc`, с проверкой целостности старого блока | +| `u_free(ptr)` | Освобождение с проверкой canary, double-free и удалением из списка | +| `u_strdup(s)` | Аналог `strdup` через u_malloc | +| `u_report_unfreed_blocks()` | Выводит все незакрытые блоки с дампом; вызывается при shutdown | +| `u_get_allocated_count()` | Возвращает количество живых аллокаций | +| `u_check(ptr, text, location)` | Ручная проверка целостности блока (canary + padding) | diff --git a/lib/memory_pool_doc.md b/lib/memory_pool_doc.md new file mode 100644 index 00000000..fdd5cae8 --- /dev/null +++ b/lib/memory_pool_doc.md @@ -0,0 +1,57 @@ +# memory_pool (Object Pool Allocator) + +## 1. Назначение + +Быстрый аллокатор объектов фиксированного размера. Вместо постоянных `malloc`/`free` хранит до 64 освобождённых объектов в linked list для повторного использования. Применяется для hot-path объектов, которые часто создаются и уничтожаются: пакеты, фрагменты, ACK-пакеты, структуры маршрутизации, TCP-сегменты lwIP и т.д. + +Встроенная защита: +- **Buffer overflow** — канарейка `0xDEADBEAF` после пользовательских данных проверяется при освобождении. При повреждении — бесконечный цикл (hang) для отладки. +- **Double free** — однобайтный счётчик в метаданных; повторное освобождение детектируется и вызывает hang. +- Вся диагностика пишется через `DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, ...)`. + +Не потокобезопасен — каждый пул используется из одного потока (обычно в рамках одного u_async event loop). + +## 2. Как пользоваться + +```c +#include "memory_pool.h" + +// 1. Создать пул (обычно в init-функции инстанса): +struct memory_pool* pkt_pool = memory_pool_init(sizeof(struct ETCP_DGRAM), "pkt_pool"); + +// 2. Выделить объект (если есть свободный — вернёт из пула, иначе u_calloc): +struct ETCP_DGRAM* dgram = memory_pool_alloc(pkt_pool); + +// 3. Освободить объект (возвращается в пул, если там меньше 64 блоков): +memory_pool_free(pkt_pool, dgram); + +// 4. Получить статистику: +size_t allocs, reuse; +memory_pool_get_stats(pkt_pool, &allocs, &reuse); +// reuse много → эффективно; reuse мало → пул слишком мал для нагрузки + +// 5. Проверить, в пуле ли объект (0 — не в пуле, 1 — в пуле): +if (memory_pool_is_freed(pkt_pool, obj)) { /* уже освобождён */ } + +// 6. Уничтожить пул (при shutdown): +memory_pool_destroy(pkt_pool); +// реально освобождает все кэшированные блоки через u_free() и сам пул. +``` + +**Важно:** +- Если объекты пула используются как элементы `ll_queue`, в `object_size` нужно закладывать `sizeof(struct ll_entry)`. Например: `memory_pool_init(sizeof(struct ll_entry) + sizeof(struct dummynet_pkt), "pkt_pool")`. +- `memory_pool_alloc` обнуляет объект перед возвратом (защита от утечки старых данных). +- При заполнении пула (64 свободных блока) лишние `memory_pool_free` реально вызывают `u_free` — блок не кэшируется. +- `memory_pool_destroy` проходит по всем свободным блокам и вызывает `u_free` для каждого, затем освобождает сам `struct memory_pool`. + +## 3. API + +| Функция/макрос | Описание | +|---|---| +| `memory_pool_init(object_size, name)` | Создаёт пул для объектов заданного размера. `name` — для диагностики. | +| `memory_pool_alloc(pool)` | Выделяет объект: из кэша (если есть) или через `u_calloc`. Обнуляет перед возвратом. | +| `memory_pool_free(pool, obj)` | Возвращает объект в пул (до 64) или вызывает `u_free`. Проверяет canary и double-free. | +| `memory_pool_destroy(pool)` | Уничтожает пул: освобождает все кэшированные блоки и сам пул. | +| `memory_pool_get_stats(pool, &allocs, &reuse)` | Статистика: общее число аллокаций и число повторных использований из кэша. | +| `memory_pool_get_total_free_blocks()` | Глобальный счётчик свободных блоков во всех пулах (для диагностики). | +| `memory_pool_is_freed(pool, obj)` | Проверяет, находится ли объект в свободном списке пула (уже освобождён). | diff --git a/lib/platform_compat_doc.md b/lib/platform_compat_doc.md new file mode 100644 index 00000000..b7720461 --- /dev/null +++ b/lib/platform_compat_doc.md @@ -0,0 +1,81 @@ +# platform_compat — Cross-platform compatibility layer + +## 1. Назначение +Унифицирует различия между POSIX (Linux/FreeBSD) и Windows (MSYS2 UCRT64) на уровне системных вызовов и типов. Предоставляет единый API для байтового порядка, строковых функций, энтропии, работы с сетевыми интерфейсами и времени. Полностью заголовочный (inline) для макросов и тривиальных обёрток, `.c` только для нетривиальных реализаций. + +## 2. Как пользоваться +Подключить `"platform_compat.h"` — всё остальное разрешается автоматически в зависимости от `_WIN32`. + +### Байтовый порядок +```c +uint16_t v16 = htobe16(x); // host → big-endian +uint32_t v32 = be32toh(x); // big-endian → host +uint64_t v64 = be64toh(x); // big-endian → host +// На Linux использует , на Windows — _byteswap_* +``` + +### Криптостойкий random +```c +uint8_t salt[8]; +if (random_bytes(salt, sizeof(salt)) != 0) { /* ошибка */ } +// Linux: /dev/urandom, Windows: BCryptGenRandom +``` + +### Сетевые интерфейсы +```c +// Определить интерфейс маршрута по умолчанию +uint32_t ifidx = get_default_route_netif_index(AF_INET); // или AF_INET6 + +// Получить IPv4 адрес интерфейса по индексу +uint32_t ipv4 = get_interface_ip_by_index(ifidx); // network byte order + +// Получить IPv6 адрес (постоянный или временный) +uint8_t ipv6[16]; +if (get_interface_ipv6_by_index(ifidx, 1, ipv6) == 0) { // temporary=1 + // ipv6 содержит 16 байт адреса +} +``` +**Windows**: `get_interface_ip_by_index`, `get_interface_ipv6_by_index`, `get_default_route_netif_index` — заглушки (возвращают 0/-1). + +### Время +```c +struct timeval tv; +utun_gettimeofday(&tv, NULL); // макрос: на Linux → gettimeofday, на Windows → inline-реализация +``` + +### Прочее +- `strcasecmp`/`strncasecmp` — на Windows `_stricmp`/`_strnicmp` +- `memmem` — на Windows inline-реализация `compat_memmem` +- `pipe` — на Windows `_pipe` с `_O_BINARY` +- `poll` — на Windows `WSAPoll`, флаги `POLLIN`/`POLLOUT` и т.д. определены если отсутствуют +- `ssize_t` — определён если отсутствует +- `fcntl` — на Windows упрощённая реализация только для `F_SETFL`/`O_NONBLOCK` +- `utun_mkdir` — кроссплатформенный `mkdir` + +### Ограничения +- Функции сетевых интерфейсов работают только на POSIX, на Windows — заглушки +- `get_default_route_netif_index` создаёт временный UDP-сокет к `8.8.8.8` (IPv4) или `2001:4860:4860::8888` (IPv6) для определения интерфейса +- Только IPv4/IPv6; `family` только `AF_INET` или `AF_INET6` + +## 3. API + +### Байтовый порядок +| Макрос | Назначение | +|--------|------------| +| `htobe16(x)` / `be16toh(x)` | 16-bit host ↔ big-endian | +| `htobe32(x)` / `be32toh(x)` | 32-bit host ↔ big-endian | +| `htobe64(x)` / `be64toh(x)` | 64-bit host ↔ big-endian | + +### Основные функции +| Функция | Назначение | +|---------|------------| +| `random_bytes(buffer, len)` | Криптостойкие случайные байты. 0 — успех, -1 — ошибка | +| `get_interface_ip_by_index(ifindex)` | IPv4 адрес интерфейса в network byte order, 0 при ошибке | +| `get_interface_ipv6_by_index(ifindex, temporary, out)` | IPv6 адрес (temporary=1 — временный privacy-адрес), 0 — успех | +| `get_default_route_netif_index(family)` | ifindex дефолтного маршрута через connect() к внешнему IP | + +### Макросы-заменители (условные) +| Макрос | Назначение | +|--------|------------| +| `utun_gettimeofday(tv, tz)` | gettimeofday (inline на Windows) | +| `utun_mkdir(path, mode)` | mkdir | diff --git a/lib/radix_doc.md b/lib/radix_doc.md new file mode 100644 index 00000000..781b4af7 --- /dev/null +++ b/lib/radix_doc.md @@ -0,0 +1,97 @@ +# Radix Tree (Patricia Trie) для IP-маршрутизации + +## 1. Назначение + +Бинарное radix-дерево (Patricia trie) для хранения маршрутов и поиска Longest Prefix Match (LPM). Производное от BSD `net/radix.c`. Используется в `route_lib.c` / `route6_lib.c` для таблиц маршрутизации IPv4/IPv6. + +Каждый маршрут — пара `(key, mask)`. Ключи — sockaddr-подобные структуры: первый байт хранит длину всей структуры, остальное — IP-адрес. Максимальная длина ключа — 32 байта (`RADIX_MAX_KEY_LEN`). + +Потокобезопасность: замки — заглушки (no-op). Подразумевается однопоточное использование с `u_async`. + +## 2. Как пользоваться + +```c +#include "../lib/radix.h" + +// --- Инициализация --- +struct radix_node_head *rnh = NULL; +rn_inithead((void**)&rnh, 1); // off=1 пропускает байт длины в сравнениях + +// --- Структура данных маршрута --- +struct route_data { + struct radix_node rn_nodes[2]; // ПАМЯТЬ ДЛЯ ДЕРЕВА — первые 2 поля! + uint8_t key[32]; + uint8_t mask[32]; + // ... пользовательские поля ... + int metric; +}; + +// --- Добавление маршрута --- +struct route_data *rd = u_calloc(sizeof(*rd), 1); +rd->key[0] = 17; // длина (1 + 16 байт IPv6) +memcpy(rd->key + 1, addr, 16); +rd->mask[0] = 17; +make_mask(rd->mask + 1, plen); +rn_addroute(rd->key, rd->mask, &rnh->rh, rd->rn_nodes); + +// --- Longest Prefix Match --- +uint8_t search_key[17]; +search_key[0] = 17; +memcpy(search_key + 1, dst_addr, 16); +struct radix_node *leaf = rn_match(search_key, &rnh->rh); +if (leaf && !(leaf->rn_flags & RNF_ROOT)) { + // leaf = &rd->rn_nodes[0] — восстанавливаем указатель на данные: + struct route_data *found = (struct route_data *)( + (char *)leaf - offsetof(struct route_data, rn_nodes)); +} + +// --- Точный поиск (key + mask) --- +struct radix_node *node = rn_lookup(key, mask, &rnh->rh); + +// --- Удаление --- +rn_delete(rd->key, rd->mask, &rnh->rh); + +// --- Обход всех маршрутов --- +static int walk_cb(struct radix_node *rn, void *arg) { + if (rn->rn_flags & RNF_ROOT) return 0; + struct route_data *rd = (struct route_data *)((char *)rn - offsetof(...)); + // ... обработать rd ... + return 0; // 0 = продолжить, !=0 = прервать +} +rn_walktree(&rnh->rh, walk_cb, NULL); + +// --- Обход с нижней границы (mask-фильтр) --- +uint8_t base_key[...], base_mask[...]; +rn_walktree_from(&rnh->rh, base_key, base_mask, walk_cb, NULL); + +// --- Завершение --- +rn_detachhead((void**)&rnh); +``` + +**Ключевые нюансы:** +- `rn_nodes[2]` **должны быть первыми полями** в структуре данных — дерево использует их для хранения. +- `rn_addroute` возвращает `&rd->rn_nodes[0]` при успехе. Для извлечения структуры используйте `offsetof()`. +- `rn_match` возвращает самый специфичный (longest prefix) лист либо `NULL`. Если `RNF_ROOT` — совпадений нет. +- Максимальный размер ключа — 32 байта (включая байт длины). +- Все динамические выделения через `R_Malloc`/`R_Zalloc`/`R_Free` (макросы на `u_malloc`/`u_free`). +- Потокобезопасность не реализована — замки (макросы `RADIX_NODE_HEAD_LOCK` и т.д.) пустые. + +## 3. API + +| Функция | Назначение | +|---------|-----------| +| `rn_inithead(void **head, int off)` | Выделяет и инициализирует `radix_node_head`. `off` — смещение (байт длины sockaddr). | +| `rn_detachhead(void **head)` | Освобождает дерево и все узлы (через `rn_walktree` + `rn_delete`). | +| `rn_match(key, head)` | Longest Prefix Match. Возвращает лист-победитель или NULL. | +| `rn_lookup(key, mask, head)` | Точный поиск (key + mask). Возвращает узел или NULL. | +| `rn_addroute(key, mask, head, nodes[2])` | Добавляет маршрут. `nodes` — память из структуры данных. | +| `rn_delete(key, mask, head)` | Удаляет маршрут. Возвращает удалённый узел для освобождения. | +| `rn_walktree(head, callback, arg)` | Обход всего дерева (in-order), вызывает `callback(rn, arg)` для каждого листа. | +| `rn_walktree_from(head, base_key, base_mask, callback, arg)` | Обход поддерева, ограниченного `base_mask`. | +| `rn_refines(mask1, mask2)` | Проверяет, является ли `mask1` более специфичной, чем `mask2`. | +| `rn_nextprefix(rn)` | Переход к следующему dupedkey-узлу с тем же ключом. | + +**Структуры:** +- `struct radix_node_head` — корень дерева с таблицей функций (`rnh_matchaddr`, `rnh_addaddr`, ...). +- `struct radix_node` — узел: внутренний (bit-offset, left/right) или лист (key, mask, dupedkey). +- `struct radix_mask` — аннотация маски для нетривиальных (non-normal) масок в поддеревьях. diff --git a/lib/serialize_doc.md b/lib/serialize_doc.md new file mode 100644 index 00000000..25153cbb --- /dev/null +++ b/lib/serialize_doc.md @@ -0,0 +1,117 @@ +# Binary Serialization Library + +## 1. Назначение + +Библиотека бинарной сериализации C-структур с динамическими полями (строки, массивы, singly-linked списки) в компактный бинарный буфер и обратно. Предназначена для кодирования/декодирования сообщений сетевого протокола (контрольный сервер, синхронизация БД, обмен конфигурацией). + +Что сериализуется: +- Фиксированные поля (целые, флаги, структуры) — копируются «как есть». +- ASCIIZ-строки (`char*`) — длина определяется через `strlen`, сохраняется с терминальным нулём. +- Динамические массивы (`uint8_t*`) — счётчик элементов в поле `UINT8`/`UINT16`/`UINT32` или ровно 1 элемент (`ARRAY_FIXED`). +- Singly-linked списки — `next`-указатели не сериализуются, сохраняются только `count+data`. + +Формат: header (`header_len` байт, передаётся отдельно) + данные полей. Длина переменных полей кодируется 2 байтами (`uint16_t`, big-endian). + +Потокобезопасность: нет, однопоточное использование. + +## 2. Как пользоваться + +```c +#include "../lib/serialize.h" + +// --- 1. Определяем структуру --- +typedef struct Node { + uint32_t id; + struct Node *next; +} Node; + +typedef struct { + uint8_t version; + uint8_t name_len; + char *name; // ASCIIZ (elem_size=1) + uint16_t addrs_cnt; + uint8_t *addrs; // массив байт (elem_size=1) + Node *list; // linked list +} Message; + +// --- 2. Описываем схему --- +static const struct SerializeField msg_fields[] = { + {offsetof(Message, version), sizeof(uint8_t), SERIALIZE_TYPE_FIXED, 0}, + {offsetof(Message, name), 1, SERIALIZE_TYPE_ARRAY_U8, offsetof(Message, name_len)}, + {offsetof(Message, addrs), 1, SERIALIZE_TYPE_ARRAY_U16, offsetof(Message, addrs_cnt)}, + {offsetof(Message, list), sizeof(Node), SERIALIZE_TYPE_LINKED, offsetof(Node, next)} +}; + +static const struct SerializeSchema msg_schema = { + .field_count = 4, + .struct_size = sizeof(Message), + .max_size = 4096, // 0 = без лимита + .header_len = 4, // байты заголовка (напр. версия протокола) + .fields = msg_fields +}; + +// --- 3. Сериализация --- +Message msg = { .version = 1, .name = "test", .name_len = 4, .list = NULL }; +msg.addrs_cnt = 2; +msg.addrs = u_malloc(2); msg.addrs[0] = 0xAA; msg.addrs[1] = 0xBB; + +uint8_t header[] = {0x01, 0x00, 0x00, 0x00}; // 4-байтный заголовок +uint8_t *buf = NULL; +size_t len; +if (serialize_encode(&msg, &msg_schema, header, &buf, &len) != SERIALIZE_ERR_OK) + goto fail; + +// buf содержит: header (4) + version (1) + [len=2]name(4) + '\0' + [len=2]addrs(2) + +// --- 4. Десериализация (buf без заголовка!) --- +Message *restored = NULL; +uint8_t *data_ptr = buf + msg_schema.header_len; // пропускаем header +if (serialize_decode(data_ptr, len - msg_schema.header_len, &msg_schema, (void**)&restored) != SERIALIZE_ERR_OK) + goto fail; + +// restored->name == "test", restored->name_len == 4 +// restored->addrs[0] == 0xAA, restored->addrs_cnt == 2 +// restored->list == NULL (список пуст — не падает) + +// --- 5. Очистка --- +serialize_free(&msg_schema, (void**)&restored); // освобождает всё: name, addrs, list, структуру +u_free(buf); +``` + +**Ключевые нюансы:** +- Поля в `schema.fields` должны идти в порядке возрастания `offset` в структуре. +- `serialize_decode` принимает буфер **без заголовка** — передаётся `buf + schema.header_len`. +- `serialize_free` освобождает структуру и все вложенные динамические поля (строки, массивы, списки). +- При ошибке `serialize_decode` **автоматически освобождает** всю частично выделенную память → `*structure = NULL`. +- `max_size > 0` — жёсткий лимит, при превышении `SERIALIZE_ERR_SIZE`. +- Длина переменных полей всегда 2 байта (максимум 65535 элементов). +- ASCIIZ-строки (`elem_size=1`) сохраняются с терминальным `\0`; пустая строка → 1 байт (`\0`). +- Для linked list поле `len_offset` указывает смещение `next`-указателя внутри узла. +- Передавать буфер в `serialize_decode` **без заголовка** — он не знает про `header_len`. + +## 3. API + +| Функция | Назначение | +|---------|-----------| +| `serialize_encode(structure, schema, header, &buf, &len)` | Кодирует структуру. `header` копируется в начало `buf`. | +| `serialize_decode(in_buf, in_len, schema, &structure)` | Декодирует буфер **без заголовка**. При ошибке всё освобождает. | +| `serialize_free(schema, &structure)` | Рекурсивно освобождает структуру и динамические поля, затем обнуляет указатель. | + +**Типы полей (`SERIALIZE_TYPE_*`):** + +| Тип | `data` | `elem_size` | `len_offset` | +|-----|--------|-------------|--------------| +| `FIXED` (0) | Встроенные данные | Размер поля | Не исп. | +| `ASCIIZ` (1) | `char*` | 1 | Не исп. (длина = `strlen`) | +| `ARRAY_FIXED` (2) | `uint8_t*` (ровно 1 элемент) | Размер элемента | Не исп. | +| `ARRAY_U8` (3) | `uint8_t*` | Размер элемента | Смещение `uint8_t`-счётчика | +| `ARRAY_U16` (4) | `uint8_t*` | Размер элемента | Смещение `uint16_t`-счётчика | +| `ARRAY_U32` (5) | `uint8_t*` | Размер элемента | Смещение `uint32_t`-счётчика | +| `LINKED` (6) | `void*` (голова списка) | Размер узла | Смещение `next` внутри узла | + +**Коды возврата:** +- `SERIALIZE_ERR_OK (0)` — успех. +- `SERIALIZE_ERR_BUF (-1)` — ошибка выделения памяти. +- `SERIALIZE_ERR_NULL (-2)` — NULL-указатель во входных параметрах. +- `SERIALIZE_ERR_SIZE (-3)` — превышен `max_size`. +- `SERIALIZE_ERR_NOTSUP (-4)` — неподдерживаемый тип поля. diff --git a/lib/sha256_doc.md b/lib/sha256_doc.md new file mode 100644 index 00000000..7dfabbe9 --- /dev/null +++ b/lib/sha256_doc.md @@ -0,0 +1,32 @@ +# SHA-256 (sha256.h) + +## 1. Назначение +Чистая C-реализация SHA-256 по FIPS 180-2, без внешних зависимостей. Используется как fallback при отключенной OpenSSL (`USE_OPENSSL` не определён) — в `secure_channel.c` для деривации session key, stream nonce, хеширования pubkey для обфускации и в `db_sync.c` для контрольных сумм БД. + +## 2. Как пользоваться +Трёхфазный API: init → update (можно много раз) → final. + +```c +SC_SHA256_CTX ctx; +uint8_t hash[SC_SHA256_BLOCK_SIZE]; // 32 байта + +sc_sha256_init(&ctx); +sc_sha256_update(&ctx, data1, len1); +sc_sha256_update(&ctx, data2, len2); // можно добавлять данные порциями +sc_sha256_final(&ctx, hash); // hash — 32 байта, после этого ctx непригоден +``` + +После `sc_sha256_final` контекст нельзя переиспользовать без повторного `sc_sha256_init`. + +## 3. API + +### Структура +- **`SC_SHA256_CTX`** — контекст хеширования: буфер 64 байта, счётчик данных, счётчик бит, 8 слов состояния. + +### Функции +- **`sc_sha256_init(ctx)`** — инициализирует контекст начальными значениями IV (первые 32 бита дробных частей квадратных корней первых 8 простых чисел). +- **`sc_sha256_update(ctx, data, len)`** — добавляет данные в хеш; обрабатывает блоки по 64 байта через `sc_sha256_transform` (внутренняя, раунды SHA-256). +- **`sc_sha256_final(ctx, hash)`** — завершает хеширование: дополняет данные до кратных 512 бит (padding с 0x80 и длиной сообщения), выполняет трансформацию, выдаёт 32-байтовый дайджест в big-endian порядке. + +### Константа +- **`SC_SHA256_BLOCK_SIZE`** (32) — размер выходного дайджеста в байтах. diff --git a/lib/socket_compat_doc.md b/lib/socket_compat_doc.md new file mode 100644 index 00000000..df3e3d31 --- /dev/null +++ b/lib/socket_compat_doc.md @@ -0,0 +1,99 @@ +# socket_compat — Cross-platform socket abstraction + +## 1. Назначение +Унифицирует работу с UDP-сокетами между POSIX и Windows (MSYS2 UCRT64). Скрывает различия в типах дескрипторов (`int` vs `SOCKET`), инициализации подсистемы (WSAStartup/WSACleanup), кодах ошибок и сигнатурах системных вызовов. + +## 2. Как пользоваться + +### Инициализация (однократно при старте) +```c +if (socket_platform_init() != 0) { /* фатальная ошибка */ } +// ... работа с сокетами ... +socket_platform_cleanup(); // при завершении +``` +На Windows WSAStartup/WSACleanup с refcount — можно вызывать init/cleanup вложенно. + +### Создание и настройка сокета +```c +socket_t sock = socket_create_udp(AF_INET); // или AF_INET6 +if (sock == SOCKET_INVALID) { /* ошибка */ } + +socket_set_nonblocking(sock); // обязательно для u_async +socket_set_reuseaddr(sock, 1); +socket_set_buffers(sock, 256*1024, 256*1024); // SO_SNDBUF/SO_RCVBUF +socket_bind_to_device(sock, "eth0"); // только Linux, SO_BINDTODEVICE +socket_set_mark(sock, 42); // только Linux, SO_MARK +``` + +### Отправка и приём +```c +ssize_t sent = socket_sendto(sock, buf, len, (struct sockaddr*)&addr, addr_len); +ssize_t recv = socket_recvfrom(sock, buf, sizeof(buf), (struct sockaddr*)&src, &src_len); + +uint16_t port = ss_get_port(&src); // порт из sockaddr_storage +``` + +### Обработка ошибок +```c +if (sent == SOCKET_ERROR_CODE) { + int err = socket_get_error(); + if (err == ERR_WOULDBLOCK) { /* нормально для неблокирующего */ } + // на POSIX ERR_WOULDBLOCK == EWOULDBLOCK (может совпадать с EAGAIN) + DEBUG_ERROR(..., "%s", socket_strerror(err)); +} +``` + +### Закрытие +```c +socket_close_wrapper(sock); // на POSIX: close() с защитой от fd 0 +``` + +### Ключевые нюансы +- **Всегда вызывать `socket_platform_init()` перед работой** — иначе на Windows сокеты не будут работать +- **Всегда `socket_set_nonblocking()`** — u_async требует неблокирующих сокетов +- `socket_t` — на POSIX `int`, на Windows `SOCKET` (unsigned) +- `SOCKET_INVALID` — константа для невалидного сокета (`-1` на POSIX, `INVALID_SOCKET` на Windows) +- `SOCKET_ERROR_CODE` — код ошибки сокетных вызовов (`-1` на POSIX, `SOCKET_ERROR` на Windows) +- `socket_strerror()` на Windows использует статический буфер — не thread-safe для параллельного использования +- `socket_bind_to_device` и `socket_set_mark` — только Linux, на остальных платформах возвращают -1 с DEBUG-сообщением +- `socket_close_wrapper` на POSIX не закроет fd 0 (защита) + +## 3. API + +### Типы и константы +| Имя | Назначение | +|-----|------------| +| `socket_t` | Кроссплатформенный тип дескриптора сокета (`int` / `SOCKET`) | +| `SOCKET_INVALID` | Невалидный сокет (`-1` / `INVALID_SOCKET`) | +| `SOCKET_ERROR_CODE` | Код ошибки сокетного вызова (`-1` / `SOCKET_ERROR`) | +| `ERR_WOULDBLOCK` / `ERR_AGAIN` / `ERR_INTR` | Кроссплатформенные коды ошибок | + +### Инициализация +| Функция | Назначение | +|---------|------------| +| `socket_platform_init()` | Инициализация сокетной подсистемы (WSAStartup на Windows). 0 — успех | +| `socket_platform_cleanup()` | Деинициализация (WSACleanup на Windows), refcount | + +### Создание и настройка +| Функция | Назначение | +|---------|------------| +| `socket_create_udp(family)` | Создать UDP-сокет (AF_INET или AF_INET6) | +| `socket_set_nonblocking(sock)` | Перевести в неблокирующий режим | +| `socket_set_buffers(sock, snd, rcv)` | Установить SO_SNDBUF и SO_RCVBUF | +| `socket_set_reuseaddr(sock, reuse)` | Установить SO_REUSEADDR | +| `socket_bind_to_device(sock, ifname)` | Привязать к интерфейсу (Linux SO_BINDTODEVICE) | +| `socket_set_mark(sock, mark)` | Установить SO_MARK (Linux) | + +### I/O +| Функция | Назначение | +|---------|------------| +| `socket_sendto(sock, buf, len, dest, dest_len)` | Отправить UDP-датаграмму | +| `socket_recvfrom(sock, buf, len, src, src_len)` | Принять UDP-датаграмму | + +### Утилиты +| Функция | Назначение | +|---------|------------| +| `socket_get_error()` | Текущий код ошибки (inline: `WSAGetLastError()` / `errno`) | +| `socket_strerror(err)` | Текстовое описание ошибки (Windows: статический буфер, не thread-safe) | +| `socket_close_wrapper(sock)` | Закрыть сокет (POSIX: защита от закрытия fd 0) | +| `ss_get_port(addr)` | Извлечь порт в host byte order из `sockaddr_storage` | diff --git a/lib/swm_min_doc.md b/lib/swm_min_doc.md new file mode 100644 index 00000000..f9483a98 --- /dev/null +++ b/lib/swm_min_doc.md @@ -0,0 +1,39 @@ +# Sliding Window Minimum (swm_min.h) + +## 1. Назначение +Структура данных для отслеживания минимума в скользящем окне фиксированного размера. Добавление значения и запрос минимума — O(1) амортизированное время. Память — O(window_size). Предназначена для отслеживания минимального RTT в ETCP/BBR (в кодовой базе зарезервирована, ожидает интеграции). + +## 2. Как пользоваться +Создать окно заданного размера, добавлять значения по мере поступления, запрашивать текущий минимум. + +```c +SlidingWindowMin *swm = swm_create(10); // окно из 10 последних значений + +for (int i = 0; i < 100; i++) { + swm_add(swm, get_new_rtt()); + int min_rtt = swm_get_min(swm); // минимум последних ≤10 значений +} + +swm_destroy(swm); +``` + +**Нюансы:** +- `swm_get_min` на пустом окне возвращает `INT_MAX` +- `window_size` должен быть > 0, иначе `swm_create` вернёт NULL +- Значения — `int`, отрицательные допустимы + +## 3. API + +### Структура +- **`SlidingWindowMin`** — непрозрачная структура (opaque pointer). Содержит кольцевой буфер значений, deque индексов, позицию и размер окна. + +### Функции +- **`swm_create(window_size)`** — выделяет SlidingWindowMin, инициализирует пустой deque и кольцевой буфер размера `window_size`. Возвращает NULL при ошибке выделения памяти. +- **`swm_destroy(swm)`** — освобождает все ресурсы структуры. +- **`swm_add(swm, val)`** — добавляет значение в окно. Автоматически вытесняет индексы, вышедшие за границу окна, и удаляет из deque значения, которые больше текущего (поддерживает монотонный минимум). +- **`swm_get_min(swm)`** — возвращает минимум текущего окна за O(1). Для пустого окна — `INT_MAX`. + +### Алгоритм +Использует монотонный deque: в голове deque всегда индекс минимального элемента текущего окна. При добавлении из хвоста удаляются элементы ≥ нового — они никогда не станут минимумом, пока новый элемент в окне. + +Тест: `tests/test_swm_min.c` diff --git a/lib/tcp_io_doc.md b/lib/tcp_io_doc.md new file mode 100644 index 00000000..7c79427e --- /dev/null +++ b/lib/tcp_io_doc.md @@ -0,0 +1,155 @@ +# tcp_io — Управление TCP-соединением на uasync + ll_queue + +## 1. Назначение + +Асинхронный TCP-коннектор, построенный на event loop `uasync` и lock-free очередях `ll_queue`. +Одна структура `struct tcp_conn` = одно TCP-соединение. +Используется в SOCKS/HTTP-прокси, STCP, TCP-прокси и тестах. + +**Ключевые свойства:** +- Чтение, запись, ошибки и connect — через `uasync` (epoll/kqueue/poll). +- Два независимых `ll_queue`: `read_queue` (входящие данные) и `write_queue` (исходящие). +- Два memory pool: `entry_pool` (struct ll_entry) и `data_pool` (буферы), всё выделение на горячем пути через пулы. +- Backpressure на чтение: high_water/low_water + `queue_waiter_wait`, EPOLLIN вкл/выкл по уровню заполнения очереди. +- Backpressure на запись: порог 32 записи в write_queue + `queue_waiter_wait`, EPOLLOUT вкл/выкл при частичной отправке. +- Graceful shutdown: FIN (shutdown SHUT_WR) и CLOSE (close сокета) — оба как сентинелы в write_queue, все предшествующие данные гарантированно отправлены. + +## 2. Как пользоваться + +### Создание соединения + +```c +struct tcp_conn* tc = tcp_conn_create( + ua, sock, + 1500, // entry_data_size — размер буфера для одного recv + 8192, // write_chunk_size — размер буфера для одной send-операции + 32, // read_high_water — порог приостановки EPOLLIN + 8, // read_low_water — порог возобновления EPOLLIN + 0, // rcvbuf_size — размер буфера сокета (0 = не менять) + on_fin_cb, on_error_cb, arg); +``` + +После создания нужно установить callback на read_queue и включить отложенную обработку: + +```c +queue_set_callback(tc->read_queue, on_read_cb, my_conn); +queue_set_waiter_defer(tc->read_queue, 1); +``` + +### Чтение данных (в on_read_cb) + +```c +static void on_read_cb(struct ll_queue* q, void* arg) { + struct my_conn* c = (struct my_conn*)arg; + struct ll_entry* e = queue_data_get(q); + if (!e) { queue_resume_callback(q); return; } + // обработать e->dgram (e->len байт) + queue_entry_free(e); + queue_resume_callback(q); // обязательно! +} +``` + +### Запись данных + +Пользователь напрямую кладёт entry в `write_queue`, отправка авто (deferred callback сам шлёт когда сокет готов): + +```c +struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool); +uint8_t* buf = memory_pool_alloc(tc->data_pool); +memcpy(buf, data, len); +e->dgram = buf; e->len = (uint16_t)len; +queue_data_put(tc->write_queue, e); +``` + +При заполнении (порог 32) нужно ждать через `queue_waiter_wait`. + +### Graceful shutdown + +```c +tcp_conn_push_fin(tc); // shutdown(SHUT_WR) после отправки всех данных → on_fin_sent +tcp_conn_push_close(tc); // close сокета после отправки всех данных → on_closed +``` + +Можно вызывать из callback-ов (on_fin, on_read_cb и т.д.). +Повторные вызовы игнорируются (fin_local=1/closed=1 — возвращают -1). + +### Уничтожение + +```c +void tcp_conn_destroy(struct tcp_conn* tc); +``` + +Идемпотентен (`destroyed` флаг). Дренирует обе очереди, закрывает сокет, освобождает пулы через отложенный `uasync_call_soon`. + +### Обработка ошибок + +`on_error(tc, err, arg)` вызывается после закрытия сокета. **Реализация on_error обязана вызвать tcp_conn_destroy.** После возврата из on_error `tc` недействителен. + +```c +static void on_error_cb(struct tcp_conn* tc, int err, void* arg) { + struct my_conn* c = (struct my_conn*)arg; + // почистить свои ресурсы + tcp_conn_destroy(tc); // tc после этого использовать нельзя + socket_close_wrapper(other_sock); + u_free(c); +} +``` + +### on_flushed (одноразовый) + +```c +tcp_conn_set_flushed(tc, my_flushed_cb); +// коллбэк вызовется один раз когда write_queue + write_buf полностью опустеют, затем сбросится +``` + +### Приостановка чтения + +```c +tcp_conn_pause_read(tc); // убирает EPOLLIN и отменяет read_waiter +``` + +### Ограничения и многопоточность + +- **Однопоточный.** Всё работает в одном `uasync` event loop. Нельзя вызывать из другого потока. +- **FIN-сентинел в очереди.** После `push_fin` данные продолжат отправляться, сам FIN выполнится только когда все предшествующие данные отправлены. +- **Нельзя вызывать close сокета напрямую.** Только через `tcp_conn_push_close` — иначе нарушится порядок обработки очереди. +- **Можно вызывать push_fin/push_close из callback-ов** — защита от повторного вызова встроена (fin_local/closed флаги). + +## 3. API + +### Структуры + +| Поле | Назначение | +|------|-----------| +| `struct tcp_conn` | Всё состояние TCP-соединения: сокет, очереди, пулы, коллбэки, флаги | + +### Жизненный цикл + +| Функция | Описание | +|---------|----------| +| `tcp_conn_create(ua, sock, entry_data_size, write_chunk_size, read_high_water, read_low_water, rcvbuf_size, on_fin, on_error, arg)` | Создать соединение: аллоцирует tc, два memory pool, два ll_queue, регистрирует сокет в uasync с EPOLLIN+EPOLLOUT. Вовращает NULL при ошибке | +| `tcp_conn_destroy(tc)` | Дренирует read_queue и write_queue, закрывает сокет, освобождает пулы через `uasync_call_soon`. Идемпотентен | + +### Коллбэки (поля struct tcp_conn) + +| Поле | Когда вызывается | +|------|-----------------| +| `on_fin(tc, arg)` | FIN получен от удалённой стороны (recv==0): сразу если read_queue пуста, иначе через `queue_set_empty_callback` после дренажа | +| `on_fin_sent(tc, arg)` | FIN отправлен (`shutdown(SHUT_WR)`) — после отправки всех данных перед FIN-сентинелом | +| `on_error(tc, err, arg)` | Фатальная ошибка сокета. Вызывается после close сокета. **Обязан вызвать tcp_conn_destroy.** После возврата tc недействителен | +| `on_flushed(tc, arg)` | write_queue + write_buf полностью опустели. Одноразовый — после вызова сбрасывается в NULL. Установка: `tcp_conn_set_flushed()` | +| `on_closed(tc, arg)` | Сокет закрыт через CLOSE-сентинел (`tcp_conn_push_close`) после отправки всех предшествующих данных | + +### Graceful shutdown + +| Функция | Описание | +|---------|----------| +| `tcp_conn_push_fin(tc)` | Помещает FIN-сентинел (`dgram=&tcp_fin_sentinel, len=0`) в write_queue. После отправки предшествующих данных: shutdown(SHUT_WR), fin_local=1, вызывает on_fin_sent. Повторный вызов → -1 | +| `tcp_conn_push_close(tc)` | Помещает CLOSE-сентинел (`dgram=NULL, len=0`) в write_queue. После отправки предшествующих данных: close сокета, удаление из uasync, closed=1, вызывает on_closed. Повторный вызов → -1 | + +### Прочее + +| Функция | Описание | +|---------|----------| +| `tcp_conn_set_flushed(tc, cb)` | Установить одноразовый коллбэк на полное опустошение write_queue | +| `tcp_conn_pause_read(tc)` | Принудительно выключить EPOLLIN и отменить ожидание низкого порога | diff --git a/lib/timeout_heap_doc.md b/lib/timeout_heap_doc.md new file mode 100644 index 00000000..a52e88e6 --- /dev/null +++ b/lib/timeout_heap_doc.md @@ -0,0 +1,107 @@ +# timeout_heap — Min-heap для управления таймерами + +## 1. Назначение + +timeout_heap — легковесная реализация min-heap (двоичная куча с минимумом в корне) для хранения таймеров в порядке возрастания времени срабатывания. Используется модулем `u_async` как внутренняя структура `UASYNC::timeout_heap`. + +Ключевая особенность — **ленивое удаление (lazy deletion)**: при отмене таймера элемент не удаляется из кучи, а помечается флагом `deleted = 1`, его `expiration` обнуляется, и он всплывает в корень (bubble-up). Реальное удаление происходит при `peek`/`pop` — корень с `deleted == 1` выкидывается, а данные освобождаются через пользовательский `free_callback`. Это даёт O(log n) на отмену без полного удаления из середины кучи. + +## 2. Как пользоваться + +Модуль используется только внутри `u_async.c`, пользователь напрямую с ним не работает. Приводится для понимания внутреннего устройства. + +### Создание и уничтожение + +```c +TimeoutHeap* h = timeout_heap_create(16); // начальная ёмкость 16 +timeout_heap_set_free_callback(h, ua, timeout_node_free_callback); +// ... использование ... +timeout_heap_destroy(h); // уничтожает все элементы через free_callback +``` + +### Вставка и извлечение + +```c +size_t index; +timeout_heap_push(h, expiration_ms, my_data, &index); +// index обновляется при перемещениях элемента в куче + +TimeoutEntry entry; +if (timeout_heap_peek(h, &entry) == 0) { + // entry.expiration — ближайшее время срабатывания + // entry.data — пользовательские данные +} + +if (timeout_heap_pop(h, &entry) == 0) { + // элемент удалён из кучи, данные нужно освободить вручную или через free_callback +} +``` + +### Отмена + +```c +// O(n) — линейный поиск по expiration+data: +timeout_heap_cancel(h, expiration, data); + +// O(log n) — удаление по индексу (для внешнего отслеживания позиции): +timeout_heap_cancel_at(h, index, data); +``` + +### Ленивое удаление: как это работает + +1. `cancel` / `cancel_at` ставит `deleted = 1`, `expiration = 0`, вызывает `bubble_up` — элемент всплывает в корень. +2. При следующем `peek`/`pop`: если корень `deleted`, он удаляется из кучи (`remove_root`), данные освобождаются через `free_callback`. Повторяется, пока корень не окажется активным. +3. O(log n) как на cancel, так и на последующее удаление. + +## 3. API + +### Жизненный цикл + +| Функция | Описание | +|---------|----------| +| `timeout_heap_create(capacity)` | Создать кучу с начальной ёмкостью. Расширяется автоматически при заполнении. | +| `timeout_heap_destroy(h)` | Уничтожить кучу. Все данные (включая помеченные deleted) освобождаются через `free_callback` или `free()`. | +| `timeout_heap_set_free_callback(h, user_data, cb)` | Установить callback для освобождения данных при удалении deleted-узлов. Если NULL — данные не освобождаются. | + +### Основные операции + +| Функция | Описание | +|---------|----------| +| `timeout_heap_push(h, exp, data, &index)` | Вставить элемент с временем срабатывания `exp`. `index_ptr` (опционально) отслеживает позицию элемента в куче — обновляется при всех перемещениях. O(log n). | +| `timeout_heap_peek(h, &entry)` | Посмотреть ближайший **неудалённый** элемент без удаления. Попутно вычищает deleted-корни через `free_callback`. O(log n) в худшем случае. | +| `timeout_heap_pop(h, &entry)` | Извлечь ближайший неудалённый элемент. Аналогично чистит deleted. O(log n). | + +### Отмена + +| Функция | Описание | +|---------|----------| +| `timeout_heap_cancel(h, exp, data)` | Отменить по совпадению `expiration + data`. Линейный поиск — O(n). Для массового использования предпочитать `cancel_at`. | +| `timeout_heap_cancel_at(h, index, data)` | Отменить по индексу (с проверкой data). O(log n) — ленивое удаление с bubble-up. | + +### Статистика + +| Функция | Описание | +|---------|----------| +| `timeout_heap_get_size(h)` | Текущий размер кучи (включая deleted-элементы). | +| `timeout_heap_get_freed_count(h)` | Количество узлов, освобождённых через `free_callback` (не реализовано в текущей версии — возвращает 0). | + +### Структуры + +```c +typedef struct { + TimeoutTime expiration; // время срабатывания (ключ сортировки) + void *data; // пользовательские данные + size_t *index_ptr; // указатель на внешнюю переменную с индексом (обновляется при перемещениях) + int deleted; // 0 = активен, 1 = помечен на удаление +} TimeoutEntry; + +struct TimeoutHeap { + TimeoutEntry *heap; // динамический массив + size_t size; // текущее количество элементов + size_t capacity; // выделенная ёмкость + void* user_data; // аргумент для free_callback + void (*free_callback)(void*, void*); // освобождение данных deleted-узлов +}; +``` + +Индексация в коде: внешний API использует 0-based индексы в массиве `heap[]`. Внутренние операции (`bubble_up`, `heapify_down`) оперируют 1-based индексами через макросы `PARENT(i)`, `LEFT_CHILD(i)`, `RIGHT_CHILD(i)` для упрощения арифметики кучи. diff --git a/lib/u_async_doc.md b/lib/u_async_doc.md new file mode 100644 index 00000000..5658d1f1 --- /dev/null +++ b/lib/u_async_doc.md @@ -0,0 +1,174 @@ +# u_async — Центральный цикл событий (event loop) + +## 1. Назначение + +u_async — центральный планировщик проекта uTun. Каждый поток, которому нужна асинхронная обработка сокетов, таймеров и отложенных задач, создаёт свой экземпляр `struct UASYNC` и запускает `uasync_mainloop()` / `uasync_poll()`. Модуль объединяет: + +- **Сокеты** — мониторинг fd/socket на чтение/запись/ошибки через Linux epoll (предпочтительно) с fallback на poll/select. +- **Таймеры** — однократные таймеры с точностью 0.1 мс, реализованные на базе `timeout_heap` (min-heap) и `memory_pool`. +- **Немедленное выполнение** — FIFO-очередь `uasync_call_soon` для отложенного запуска callback в ближайшей итерации цикла. +- **Межпоточную связь** — `uasync_post` позволяет другому потоку безопасно запланировать callback в главном потоке (wakeup через pipe/UDP-сокет). + +На Linux используется epoll, на FreeBSD/Windows — poll/select. Платформенная абстракция прозрачна для вызывающего кода. + +## 2. Как пользоваться + +### Правила + +- **Один UASYNC на поток.** Нельзя создать несколько экземпляров и вызывать их из разных потоков. +- **Никаких sleep/usleep.** Если поток заблокирован на sleep, цикл событий стоит. Все ожидания — только через таймеры `uasync_set_timeout`. +- **Закрытие из callback.** Можно вызывать `uasync_destroy` или `uasync_remove_socket` прямо из callback сокета/таймера. Модуль использует локальные копии указателей перед вызовом callback и генерационные счётчики (gen) для защиты от stale epoll-событий. +- **Указатель `user_arg`** передаётся во все callback и позволяет передать контекстную структуру (например, `UTUN_INSTANCE`). + +### Типовой сценарий + +```c +// 1. Создать uasync +struct UASYNC* ua = uasync_create(); +if (!ua) { /* ошибка */ } + +// 2. Зарегистрировать сокет +void* sock_id = uasync_add_socket(ua, fd, my_read_cb, NULL, NULL, my_ctx); +// или для socket_t: +void* sock_id = uasync_add_socket_t(ua, sock, my_read_cb_sock, my_write_cb_sock, NULL, my_ctx); + +// 3. Зарегистрировать таймер (timebase = 0.1 мс, т.е. timeout_tb=100 = 10 мс) +void* t_id = uasync_set_timeout(ua, 100, my_ctx, my_timer_cb, "my_timer"); + +// 4. Отменить таймер (обязательно обнулить дескриптор!) +uasync_cancel_timeout(ua, t_id); +t_id = NULL; + +// 5. Отложенное выполнение в следующей итерации +void* soon_id = uasync_call_soon(ua, my_ctx, deferred_cb); + +// 6. Динамически включить/отключить мониторинг записи на сокете +uasync_set_socket_write(ua, sock_id, 1); // включить +uasync_set_socket_write(ua, sock_id, 0); // отключить + +// 7. Запустить главный цикл (блокирующий, выход через uasync_stop) +uasync_mainloop(ua); + +// 8. Или один шаг с таймаутом (timebase, -1 = бесконечно) +uasync_poll(ua, 500); // ждать до 50 мс или первого события + +// 9. Завершение +uasync_destroy(ua, 1); // close_fds=1 — закрыть все сокеты +``` + +### Таймеры: правильный паттерн + +```c +void* reconnect_timer = NULL; // дескриптор всегда обнуляем + +// При запуске: +if (!reconnect_timer) { + reconnect_timer = uasync_set_timeout(ua, 5000, ctx, reconnect_cb, "reconnect"); +} + +// В callback или при отмене: +void reconnect_cb(void* arg) { + reconnect_timer = NULL; // сработал — обнулили + // ... +} + +// При ручной отмене: +if (reconnect_timer) { + uasync_cancel_timeout(ua, reconnect_timer); + reconnect_timer = NULL; +} +``` + +### Межпоточное взаимодействие (uasync_post) + +```c +// Из другого потока: +void notify_main(void* arg) { + UTUN_INSTANCE* u = arg; + // работаем в главном потоке — можно вызывать любые функции uasync, трогать сокеты и т.д. + uasync_set_timeout(u->ua, 10, u, handle_work, "work"); +} + +void thread_func(UTUN_INSTANCE* u) { + // … + uasync_post(u->ua, notify_main, u); +} + +// Для гарантии видимости памяти из другого потока перед uasync_post: +uasync_memsync(u->ua); +``` + +### Получение времени + +```c +uint64_t now_tb = get_time_tb(); // timebase 0.1 мс (монотонные часы) +uint64_t now_us = get_time_us(); // микросекунды (для burst-измерений) +``` + +## 3. API + +### Жизненный цикл + +| Функция | Описание | +|---------|----------| +| `uasync_create()` | Создать экземпляр: аллоцирует структуру, `socket_array`, `timeout_heap`, `memory_pool`, epoll/poll, wakeup pipe/сокет. | +| `uasync_destroy(ua, close_fds)` | Уничтожить экземпляр. При `close_fds=1` закрывает все зарегистрированные fd. Перед уничтожением выводит диагностику ресурсов и проверяет на утечки (abort при несовпадении аллокаций). | +| `uasync_stop(ua)` | Установить флаг `stop = 1` — на следующей итерации `uasync_mainloop` выйдет. | +| `uasync_mainloop(ua)` | Бесконечный цикл `while(!stop) uasync_poll(ua, -1)`. | +| `uasync_poll(ua, timeout_tb)` | Одна итерация: обработать сокеты + таймеры. `timeout_tb` = максимальное ожидание в timebase; `-1` = ждать следующего таймера (или бесконечно, если нет таймеров). | + +### Таймеры (timebase = 0.1 мс) + +| Функция | Описание | +|---------|----------| +| `uasync_set_timeout(ua, timeout_tb, arg, cb, name)` | Запланировать однократный таймер. Возвращает дескриптор `void*` для отмены. `name` — до 15 символов, используется в логах. | +| `uasync_cancel_timeout(ua, t_id)` | Отменить таймер по дескриптору. После отмены дескриптор нужно обнулить — повторный cancel даст ошибку. | + +### Немедленное выполнение (FIFO) + +| Функция | Описание | +|---------|----------| +| `uasync_call_soon(ua, arg, cb)` | Запланировать callback на ближайшую итерацию цикла. Используется, когда нужно отложить выполнение на «сразу после текущих событий». Узел выделяется из того же `timeout_pool`. | +| `uasync_call_soon_cancel(ua, t_id)` | Отменить — просто зануляет callback, узел будет освобождён при обработке очереди (O(1)). | + +### Сокеты + +| Функция | Описание | +|---------|----------| +| `uasync_add_socket(ua, fd, r_cb, w_cb, e_cb, arg)` | Добавить fd (pipe, file). `r_cb/w_cb/e_cb` могут быть NULL. Возвращает дескриптор `void*`. | +| `uasync_add_socket_t(ua, sock, r_cb, w_cb, e_cb, arg)` | Добавить `socket_t` (кросс-платформенный сокет). | +| `uasync_remove_socket(ua, s_id)` | Удалить сокет по дескриптору. Помечает слот неактивным, не освобождает индексную ячейку (защита от stale epoll-событий через gen). | +| `uasync_remove_socket_t(ua, sock)` | Удалить по значению `socket_t`. | +| `uasync_set_socket_read(ua, s_id, enable)` | Динамически включить/отключить мониторинг чтения (EPOLL_CTL_MOD). | +| `uasync_set_socket_write(ua, s_id, enable)` | Динамически включить/отключить мониторинг записи. | +| `uasync_lookup_socket(ua, fd, &s_id)` | Найти дескриптор сокета по fd (возвращает актуальный указатель даже после realloc). | + +### Межпоточное взаимодействие + +| Функция | Описание | +|---------|----------| +| `uasync_post(ua, cb, arg)` | **Потокобезопасно.** Запланировать callback в главном потоке. Выделяет `posted_task`, добавляет в связный список под мьютексом, будит главный поток через `uasync_wakeup`. | +| `uasync_memsync(ua)` | **Потокобезопасно.** Барьер памяти (lock/unlock `posted_lock`) для гарантии видимости данных, записанных из другого потока перед `uasync_post`. | +| `uasync_wakeup(ua)` | Разбудить `poll`/`epoll_wait` записью байта в wakeup pipe (POSIX) или send в UDP-сокет (Windows). Можно вызывать из обработчика сигналов. | +| `uasync_get_wakeup_fd(ua)` | Получить write-fd wakeup pipe для использования в `signalfd` или кастомных механизмах. | + +### Время + +| Функция | Описание | +|---------|----------| +| `get_time_tb()` | Монотонное время в timebase (0.1 мс). `clock_gettime(CLOCK_MONOTONIC)` на POSIX, `QueryPerformanceCounter` на Windows. | +| `get_time_us()` | Монотонное время в микросекундах (для burst-измерений производительности). | + +### Диагностика + +| Функция | Описание | +|---------|----------| +| `uasync_get_stats(ua, ...)` | Получить счётчики аллокаций/освобождений таймеров и сокетов. | +| `uasync_print_resources(ua, prefix)` | Вывести в лог все активные таймеры (имя, оставшееся время) и сокеты. Вызывается также в `uasync_destroy` перед очисткой. | + +### Внутренние структуры + +- **`struct timeout_node`** — узел таймера: name, arg, callback, expiration_ms, heap_index. Используется и для таймеров в heap, и для FIFO-очереди immediate (через поле `next`). Выделяется из `timeout_pool`. +- **`struct socket_node`** — узел сокета: fd/sock, тип (FD/SOCK), колбэки, user_data, флаги active/enable_read/enable_write, gen (защита от stale epoll-событий после переиспользования fd). +- **`struct socket_array`** — массив сокетов: O(1) доступ по fd через `fd_to_index[]`, обход активных через `active_indices[]`, динамическое расширение. +- **`struct posted_task`** — задача из другого потока: callback + arg + next, защищена `posted_lock`. diff --git a/src/config_parser_doc.md b/src/config_parser_doc.md new file mode 100644 index 00000000..f3040960 --- /dev/null +++ b/src/config_parser_doc.md @@ -0,0 +1,98 @@ +# Config Parser (config_parser.c/h) + +## 1. Назначение + +INI-style парсер конфигурационного файла uTun. Читает текстовый конфиг, разбирает его на структуры и возвращает заполненный `struct utun_config` — корневое представление всех настроек. + +Поддерживает два режима входа: +- `parse_config(filename)` — чтение из файла +- `parse_config_from_buf(buf, len, filename)` — чтение из буфера (для chatgui/in-memory использования) + +Формат: стандартные `.ini`-секции `[section]`, строки `key=value`, комментарии `#`. Именованные секции через двоеточие: `[server:main]`, `[client:peer1]`, `[network:mynet]`. + +## 2. Как пользоваться + +```c +struct utun_config *cfg = parse_config("/etc/utun/config.ini"); +if (!cfg) { /* ошибка — смотри логи DEBUG_CATEGORY_CONFIG */ } + +// Работа с конфигом... +const char *tun_name = cfg->global.tun_ifname; +struct CFG_SERVER *srv = cfg->servers; +while (srv) { + ip_str_t addr = sockaddr_storage_to_str(&srv->ip); + srv = srv->next; +} + +// Освобождение +free_config(cfg); +``` + +**Нюансы:** +- `parse_config` возвращает `NULL` при ошибках открытия/парсинга — проверяй возврат. +- Все строковые поля обрезаются по размерам буфера, переполнение логируется. +- Ключи клиента `link` требуют, чтобы сервер (`[server:имя]`) был объявлен выше по файлу. Иначе ошибка. +- `tun_enabled` по умолчанию `1`; если задан `tun_ifname` или `tun_ip` — генерируется имя `tun0` при отсутствии. Если нет ни одного TUN-параметра — считается выключенным (режим без root). +- IPv6-адреса в `[server:...] addr` поддерживают префиксы `temporary_ipv6:` и `permanent_ipv6:`. + +## 3. API + +### Структуры + +| Структура | Назначение | +|-----------|------------| +| `struct utun_config` | Корневой контейнер: `global` + связные списки `servers`, `clients`, `route_subnets`, `my_subnets`, `networks` | +| `struct global_config` | Глобальные настройки (ключи, TUN, таймауты, firewall, NAT, SOCKS/HTTP-прокси, NTP, debug, allowed_keys...) | +| `struct CFG_SERVER` | Локальный серверный линк: `addr` (ip:port), `netif_index`, `so_mark`, `fib`, `type`, `transport` (udp/tcp), `mtu`, `only_local` | +| `struct CFG_CLIENT` | Удалённый пир: `peer_public_key_hex`, `keepalive`, связный список `links` | +| `struct CFG_CLIENT_LINK` | Привязка клиента к локальному серверу: `local_srv` (указатель на `CFG_SERVER`), `remote_addr` | +| `struct CFG_ROUTE_ENTRY` | Маршрут: `IP` + `netmask` | +| `struct CFG_NETWORK` | Именованная сеть: `id` (56-bit), `pubkey_hex`, `signing_key_hex` | +| `struct CFG_FIREWALL_RULE` | Правило фаервола: `ip` (host order), `port`, `bypass` | +| `struct CFG_ALLOWED_KEY` | Разрешённый публичный ключ (32 байта binary) для контрольного сервера | +| `struct CFG_CONTROL_ALLOW` | Разрешённая подсеть для control-сервера | +| `struct CFG_NTP_SERVER` | NTP-сервер: имя хоста | + +### Секции конфига + +| Секция | Обработчик | Ключевые поля | +|--------|-----------|---------------| +| `[global]` | `parse_global()` | `my_node_name`, `my_private_key`, `my_public_key`, `my_node_id`, `tun_ifname`, `tun_ip`, `mtu`, `keepalive_*`, `bbr_max_cwnd`, `debug_level`, `log_file`, `db_path`, `db_sync_*`, `enable_timestamp`, `enable_colors`, `tun_test_mode` | +| `[server:name]` | `parse_server()` | `addr`, `so_mark`, `fib`, `netif`, `type` (public/nat/private/local), `transport` (udp/tcp), `mtu`, `only_local` | +| `[client:name]` | `parse_client()` | `link` (формат: `server_name:ip:port`), `peer_public_key`, `keepalive` | +| `[network:name]` | `parse_network()` | `id` (hex), `pubkey`, `signing_key` | +| `[routing]` | inline | `route_subnet`, `my_subnet` | +| `[firewall]` | `parse_firewall_rule()` | `allow` (IP:port или `all` для bypass) | +| `[control]` | `parse_control()` | `ip`, `port`, `allow` (подсеть) | +| `[allowed_keys]` | inline | `allow_all`, `key` (64 hex) | +| `[nat]` | `parse_nat()` | `tun_ifname`, `tun_ip`, `nat_via`, `port_start`, `port_end`, `forward` (proto:ip:in_port:ext_port) | +| `[tcp_proxy_client]` | `parse_tcp_proxy_client()` | `enabled`, `tun_name`, `tun_ip`, `mtu`, `via_node`, `socks_*`, `http_proxy_*`, `forward` (port -> ip:port) | +| `[tcp_proxy_server]` | inline | `tcp_recv_buf` | +| `[msg_transport]` | `parse_msg_transport()` | `port` | +| `[debug]` | inline | `категория=уровень` (до 16 категорий) | +| `[ntp]` | inline | `enabled`, `server`, `interval` | +| `[gui]` | игнорируется | (зарезервировано) | + +### Функции + +| Функция | Назначение | +|---------|------------| +| `parse_config(filename)` | Открывает файл, парсит через `parse_config_internal`, возвращает `utun_config*`. `NULL` при ошибке. | +| `parse_config_from_buf(buf, len, filename)` | Парсит конфиг из буфера (in-memory). `filename` используется только для сообщений об ошибках. | +| `free_config(config)` | Рекурсивно освобождает все связные списки и сам `utun_config`. Безопасен для `NULL`. | +| `print_config(config)` | Выводит всё содержимое конфига через `DEBUG_INFO(DEBUG_CATEGORY_CONFIG, ...)`. Полезно для проверки после парсинга. | +| `update_config_keys(filename, priv_key, pub_key)` | **Устарела.** Дописывает ключи в конец файла. Используй `config_ensure_keys_and_node_id()` из `config_updater.h`. | + +### Внутренние хелперы (static) + +| Функция | Назначение | +|---------|------------| +| `trim(str)` | Обрезает пробелы слева и справа (in-place) | +| `parse_key_value(line, ...)` | Разбирает строку `key=value`, удаляет комментарии после `#` | +| `parse_ip_with_netmask(str, ip, netmask)` | Парсит `1.2.3.4/24` или `::1/64` в `struct IP` | +| `parse_sockaddr(addr, port, sockaddr)` | Преобразует адрес+порт в `sockaddr_storage` через `getaddrinfo` | +| `parse_address_and_port(str, sockaddr)` | Разбирает строку `ip:port` → вызов `parse_sockaddr` | +| `get_netif_index(ifname)` | Получает индекс сетевого интерфейса через `if_nametoindex` | +| `parse_firewall_rule(rule_str, global)` | Парсит `allow=IP[:port]` или `all` | +| `parse_control_allow(rule_str, global)` | Парсит `allow=IP[/CIDR]` | +| `hex_to_binary(hex_str, binary, len)` | Конвертит hex-строку в бинарный массив | diff --git a/src/config_updater_doc.md b/src/config_updater_doc.md new file mode 100644 index 00000000..c69b2134 --- /dev/null +++ b/src/config_updater_doc.md @@ -0,0 +1,67 @@ +# Config Updater (config_updater.c/h) + +## 1. Назначение + +Утилита автоматической инициализации и обновления ключей в конфигурационном файле uTun. + +Основная задача — гарантировать, что в конфиге всегда присутствуют корректные и согласованные: +- `my_private_key` (X25519, 64 hex символа) +- `my_public_key` (производный от private, 64 hex) +- `my_node_id` (SHA256-идентификатор узла, 16 hex символов, вычисляется из private key) + +Если чего-то нет или значение неконсистентно — модуль генерирует недостающее через `secure_channel.h` и атомарно перезаписывает конфиг, сохраняя всё остальное содержимое файла нетронутым. + +## 2. Как пользоваться + +```c +#include "config_updater.h" + +int ret = config_ensure_keys_and_node_id("/etc/utun/config.ini"); +if (ret == 0) { + // Конфиг готов: ключи и node_id гарантированно на месте +} else { + // Ошибка — смотри DEBUG_*(DEBUG_CATEGORY_CONFIG) +} +``` + +**Типовой сценарий (первый запуск):** +1. Файла нет → создаётся минимальный `[global]` с `my_node_name`, сгенерированными ключами и `my_node_id`. +2. Файл есть, но нет `my_private_key` → генерируется новая X25519-пара, записываются все три поля. +3. Файл есть с `my_private_key`, но нет `my_public_key` или `my_node_id` → вычисляются из private key, дописываются. +4. Все поля есть, но `my_public_key` не соответствует `my_private_key` или `my_node_id` не совпадает → перезаписываются на корректные. + +**Нюансы:** +- Вычисление pubkey: `sc_compute_public_key_from_private(priv_bin, pub_bin)`. +- Вычисление node_id: `sc_derive_node_id(priv_bin)`. +- Генерация новой пары ключей: `sc_generate_keypair(&mykeys)`. +- Файл читается целиком в память, модифицируется через `insert_or_replace_option`, затем пишется обратно. Это атомарно: либо файл полностью перезаписан, либо остался нетронутым (при ошибке). +- Редактирование in-place через `memmove`: строки не сдвигают остальной конфиг, только вставляются/заменяются нужные опции внутри секции `[global]`. +- Для нового файла создаётся минимальный `[global]` с именем узла из `my_node_name` (или `"utun"` если не задано). + +## 3. API + +### Функции + +| Функция | Назначение | +|---------|------------| +| `config_ensure_keys_and_node_id(filename)` | **Основная функция.** Читает конфиг, проверяет/генерирует ключи и node_id, записывает обратно. Возвращает 0 при успехе, -1 при ошибке. | +| `bytes_to_hex(bytes, len, hex_str, hex_len)` | Вспомогательная: конвертация бинарного массива в hex-строку. Безопасна для нулевых указателей и маленьких буферов. | + +### Внутренние функции (static) + +| Функция | Назначение | +|---------|------------| +| `is_valid_priv_key(key)` | Проверяет, что строка — 64 hex-символа | +| `is_valid_pub_key(key)` | То же для публичного ключа | +| `is_valid_node_id(node_id)` | Проверяет `node_id != 0` | +| `write_mem_to_file(filename, buffer, size)` | Пишет буфер в файл (одной операцией) | +| `find_section_global(buf, buf_len)` | Ищет позицию после `[global]` в текстовом буфере | +| `find_option(buf, buf_len, option)` | Ищет строку `option=...` в буфере | +| `insert_or_replace_option(buf, buf_len, buf_capacity, option, value)` | Заменяет значение опции при наличии, либо вставляет новую строку после `[global]`. Работает с `memmove`/`memcpy`, перевыделяет буфер при необходимости. | + +### Зависимости + +- `secure_channel.h` — `sc_generate_keypair()`, `sc_compute_public_key_from_private()`, `sc_derive_node_id()` +- `config_parser.h` — `parse_config_from_buf()`, `free_config()`, структуры `utun_config`/`global_config` +- `lib/mem.h` — `u_malloc`, `u_realloc`, `u_free` +- `lib/debug_config.h` — `DEBUG_*` diff --git a/src/conn_mgr_doc.md b/src/conn_mgr_doc.md new file mode 100644 index 00000000..6680a756 --- /dev/null +++ b/src/conn_mgr_doc.md @@ -0,0 +1,145 @@ +# conn_mgr — Менеджер установки соединений + +## 1. Назначение +Трёхфазный асинхронный менеджер подключения к удалённым нодам. Отвечает за установку ETCP-соединения с выбором оптимального способа: + +1. **DIRECT** — прямое INIT-рукопожатие со всеми известными IPv4-адресами цели (с проверкой NAT-совместимости) +2. **REVERSE** — если у нас прямой IP, а цель за NAT: отправляем наши адреса через BGP, цель подключается к нам +3. **INDIRECT** — обмениваемся через BGP списками кандидатов-посредников, выбираем общих по минимальной сумме RTT, трафик идёт через `etcp_router` + +Также выполняет фоновые задачи: периодический ping всех BGP-нод, поддержание списка лучших посредников, idle-таймаут для неактивных соединений, локальное сканирование сети. + +Типы соединений (итоговый результат): +- `CONN_TYPE_DIRECT` (1) — прямое соединение +- `CONN_TYPE_REVERSE` (2) — цель подключилась к нам +- `CONN_TYPE_INDIRECT` (3) — через посредника + +## 2. Как пользоваться + +### Инициализация +```c +struct CONN_MGR* mgr = conn_mgr_init(instance); +// Регистрирует обработчик в etcp_router для ETCP_RT_ID_CONN_MGR (0x11) +// Запускает фоновые таймеры: bg_ping, candidate_ping +``` + +### Подключение +```c +static void my_connect_cb(int result, uint64_t node_id, void* arg) { + if (result == CONN_MGR_OK) { /* подключено */ } + else if (result == CONN_MGR_ERR_TIMEOUT) { /* таймаут */ } + else if (result == CONN_MGR_ERR_UNREACHABLE) { /* недостижима */ } +} +conn_mgr_connect_node(mgr, target_node_id, idle_timeout_ms, my_connect_cb, arg); +``` + +### Отправка данных +```c +struct ll_entry* entry = queue_entry_new(0); +entry->dgram = data; entry->len = len; +conn_mgr_send(mgr, node_id, entry); // entry освобождается внутри +``` + +### Alien-ноды +Для нод, которые сами подключились к нам, используется `conn_mgr_add_alien_node()` — не запускает фазы подключения, сразу устанавливает `CONNECTED`. + +### Кандидаты-посредники +Внешний код (обычно `route_connectivity`) вызывает `conn_mgr_update_best_candidates()` для заполнения списка лучших посредников (топ-3 по RTT). Список используется фазой INDIRECT. + +### Нюансы +- Повторный `connect_node` для уже подключённой ноды вернёт `CONN_MGR_ERR_ALREADY_CONNECTED` +- Для ноды в состоянии CONNECTING новый вызов добавит ещё один callback в список ожидания +- Для INDIRECT-соединений `conn_mgr_send` использует round-robin по выбранным посредникам +- Пока существует активный ETCP-линк к ноде, немедленно возвращает CONNECTED + +## 3. API + +### Корневые структуры + +| Структура | Описание | +|-----------|----------| +| `CONN_MGR` | Корневая структура менеджера. Хранит массив `entries[]`, топ-3 `best_candidates`, таймеры (`bg_ping_timer`, `candidate_ping_timer`), списки `reverse_pending`/`exchange_pending`, `next_request_id` | +| `CONN_MGR_ENTRY` | Запись о подключении к одной ноде: `state`, `conn_type`, `alien`, `idle_timeout_ms`, список коллбэков `cb_list`, массив `intermediaries[]`, состояния `local_scan_state`/`main_connect_state`, текущая `main.phase` | +| `CONN_MGR_CANDIDATE` | Пара `{node_id, rtt}` — запись о кандидате-посреднике (packed) | +| `cm_cb_node` | Узел связного списка коллбэков для асинхронного результата подключения | +| `cm_reverse_pending` | Запись об ожидании REVERSE-подключения (связный список) | +| `cm_exchange_pending` | Запись об ожидании INDIRECT-обмена, включает кешированный ответ и таймер probe-повторов | + +### Жизненный цикл + +| Функция | Описание | +|---------|----------| +| `conn_mgr_init(instance)` | Выделяет CONN_MGR, регистрирует `ETCP_RT_ID_CONN_MGR` в маршрутизаторе, запускает bg_ping и candidate_ping таймеры | +| `conn_mgr_destroy(mgr)` | Отменяет таймеры, освобождает все entries, exchange_pending, reverse_pending | + +### Подключение / отключение + +| Функция | Описание | +|---------|----------| +| `conn_mgr_connect_node(mgr, id, timeout, cb, arg)` | Запускает 3-фазное подключение. Если уже подключена — сразу callback с OK. Если в процессе — добавляет callback в список | +| `conn_mgr_disconnect_node(mgr, id)` | Шлёт DISCONNECT пакет, сбрасывает `conn_mgr_type` в `NODEINFO_Q`, удаляет entry | +| `conn_mgr_send(mgr, id, entry)` | Отправляет данные подключённой ноде через `etcp_router`; для INDIRECT — round-robin по посредникам; обновляет `last_traffic_tb` | +| `conn_mgr_set_idle_timeout(mgr, id, ms)` | Устанавливает/снимает idle-таймер для ноды. При неактивности дольше `ms` — автоотключение | +| `conn_mgr_get_status(mgr, id, &state, &type)` | Возвращает текущее состояние и тип соединения | + +### Alien-ноды + +| Функция | Описание | +|---------|----------| +| `conn_mgr_add_alien_node(mgr, data, len)` | Регистрирует чужую ноду (сама подключилась к нам): копирует `TOPO_NODE`, добавляет в группу, помечает `alien=1` | + +### Кандидаты-посредники + +| Функция | Описание | +|---------|----------| +| `conn_mgr_update_best_candidates(mgr, id, rtt)` | Вставляет/обновляет кандидата в отсортированном топ-3 по RTT. Худшие кандидаты вытесняются | + +### Протокольные подкоманды (ETCP_RT_ID_CONN_MGR = 0x11) + +| Константа | Описание | +|-----------|----------| +| `CONN_MGR_SUBCMD_DIRECT_REQ` (0x01) | Фаза 2: "я за NAT, подключись ко мне" — наши адреса | +| `CONN_MGR_SUBCMD_DIRECT_RESP` (0x02) | Ответ на DIRECT_REQ (зарезервирован) | +| `CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ` (0x03) | Фаза 3 инициатор: наш топ-4 кандидатов | +| `CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP` (0x04) | Фаза 3 ответ: свои + чужие кандидаты с RTT с обеих сторон | +| `CONN_MGR_SUBCMD_INTERM_SELECTED` (0x05) | Фаза 3 финал: выбранные посредники | +| `CONN_MGR_SUBCMD_DISCONNECT` (0x06) | Уведомление о разрыве | + +### Пакеты протокола (все packed) + +| Структура | Поля | +|-----------|------| +| `CONN_MGR_DIRECT_REQ` | `cmd, subcmd, request_id, addr_count` + массив адресов `{type, ip[4], port, sock_id}` | +| `CONN_MGR_DIRECT_RESP` | `cmd, subcmd, request_id, accepted` | +| `CONN_MGR_INTERM_EXCHANGE_REQ` | `cmd, subcmd, request_id, candidate_count, candidates[4]` | +| `CONN_MGR_INTERM_EXCHANGE_RESP` | `cmd, subcmd, request_id, my_count, your_count, my_candidates[4], your_candidates[4]` | +| `CONN_MGR_INTERM_SELECTED` | `cmd, subcmd, request_id, count, selected[3]` | +| `CONN_MGR_DISCONNECT` | `cmd, subcmd, node_id` | + +### Коды возврата + +| Код | Значение | +|-----|----------| +| `CONN_MGR_OK` (0) | Успех | +| `CONN_MGR_ERR_NOT_FOUND` (-1) | Нода не найдена в BGP | +| `CONN_MGR_ERR_NO_ADDRESSES` (-2) | Нет адресов для подключения | +| `CONN_MGR_ERR_TIMEOUT` (-3) | Таймаут | +| `CONN_MGR_ERR_UNREACHABLE` (-4) | Недостижима (все фазы) | +| `CONN_MGR_ERR_ALREADY_CONNECTED` (-6) | Уже подключены | +| `CONN_MGR_ERR_INTERNAL` (-7) | Внутренняя ошибка | + +### Ключевые константы + +| Константа | Значение | Смысл | +|-----------|----------|-------| +| `CONN_MGR_MAX_CANDIDATES` | 3 | Макс. число лучших посредников в списке | +| `CONN_MGR_MAX_INTERMEDIARIES` | 3 | Макс. число выбранных посредников для одного соединения | +| `CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS` | 5000 | Таймаут фазы DIRECT | +| `CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS` | 15000 | Таймаут фазы REVERSE | +| `CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS` | 15000 | Таймаут фазы INDIRECT | +| `CONN_MGR_CANDIDATE_STALE_TB` | 300000 | Порог устаревания кандидата (~30 сек) | +| `CONN_MGR_CANDIDATE_PING_TB` | 20000 | Интервал ping кандидатов (~2 сек) | +| `CONN_MGR_BG_PING_INTERVAL_TB` | 1000 | Интервал фонового ping нод (~100 мс) | +| `CONN_MGR_BG_PING_CYCLE_MIN_TB` | 100000 | Мин. длительность цикла обхода всех нод (~10 сек) | +| `CONN_MGR_IDLE_CHECK_INTERVAL_TB` | 10000 | Интервал проверки idle (~1 сек) | +| `CONN_MGR_LOCAL_SCAN_ATTEMPTS` | 3 | Попыток локального сканирования | diff --git a/src/control_server_doc.md b/src/control_server_doc.md new file mode 100644 index 00000000..fae520ff --- /dev/null +++ b/src/control_server_doc.md @@ -0,0 +1,49 @@ +# Control Server (control_server) + +## 1. Назначение +TCP-сервер для мониторинга ETCP-соединений. Принимает подключения от GUI-клиента `etcpmon`, отдаёт по запросу метрики соединений/каналов/TUN/роутера, список узлов BGP-топологии, позволяет менять уровни отладки на лету. Уведомляет подписанных клиентов об изменениях узлов через `control_server_notify_node_change()`. + +## 2. Как пользоваться +1. Вызвать `control_server_init()` — создаёт слушающий TCP-сокет на адресе из конфига `control_sock`, регистрирует accept-колбэк в uasync. +2. Периодически вызывать `control_server_process_updates()` для обработки накопившихся данных клиентов. +3. При изменении/удалении узла BGP-топологии вызывать `control_server_notify_node_change()` / `control_server_notify_node_removed()` — сервер сам разошлёт уведомления подписанным клиентам. +4. При остановке вызвать `control_server_shutdown()`. + +**Нюансы:** +- Доступ по IP контролируется через `control_allow` в конфиге (белый список); без правил доступ запрещён. +- Клиенты отключаются по idle-таймауту (300 секунд без команд). +- Лог пишется в `control_server.log` (полные дампы RX/TX). + +## 3. API + +### Структуры + +| Структура | Назначение | +|-----------|-----------| +| `struct control_server` | Состояние сервера: `listen_fd`, связный список `clients`, uasync-контекст, счётчик клиентов. | +| `struct control_client` | Состояние клиента: `fd`, буфер приёма (`recv_buffer`), `output_queue` для асинхронной отправки, `selected_peer_id`, флаг подписки на узлы. | + +### Функции + +| Функция | Назначение | +|---------|-----------| +| `control_server_init(server, ua, instance, bind_addr, max_clients)` | Создать слушающий сокет, зарегистрировать в uasync. Возвращает 0 / -1. | +| `control_server_shutdown(server)` | Закрыть всех клиентов, освободить слушающий сокет, закрыть лог-файл. | +| `control_server_process_updates(server)` | Обработать накопившиеся данные всех клиентов (вызывать периодически). | +| `control_server_get_client_count(server)` | Вернуть количество подключённых клиентов. | +| `control_server_notify_node_change(server, node)` | Разослать подписанным клиентам сериализованную информацию об узле. | +| `control_server_notify_node_removed(server, node_id)` | Разослать подписанным клиентам уведомление об удалении узла. | + +### Поддерживаемые команды ETCPMON + +| Команда | Ответ | Описание | +|---------|-------|----------| +| `CMD_LIST_CONN` | `RSP_CONN_LIST` | Список активных ETCP-соединений. | +| `CMD_SELECT_CONN` | — | Выбрать соединение по `peer_node_id` для метрик. | +| `CMD_GET_METRICS` | `RSP_METRICS` | Полные метрики: ETCP, TUN, router, все каналы с BBR. | +| `CMD_LIST_SOCKETS` | `RSP_SOCKET_LIST` | Список локальных сокетов с NAT-типом и NAT-адресом. | +| `CMD_ACTION` | `RSP_ACTION_RESULT` | `"nat"` — запрос NAT-проверки; `"nodes"` — дамп BGP-узлов текстом. | +| `CMD_GET_DEBUG_CONFIG` | `RSP_DEBUG_CONFIG` | Текущие уровни отладки (глобальный + по категориям). | +| `CMD_SET_DEBUG_CONFIG` | — | Установить уровни отладки. | +| `CMD_SUBSCRIBE_NODES` | `RSP_NODE_INFO` (список) + `RSP_NODE_INFO_END` | Подписка на изменения узлов + текущий список. | +| `CMD_DISCONNECT` | — | Отключение клиента. | diff --git a/src/crc32_doc.md b/src/crc32_doc.md new file mode 100644 index 00000000..3f4e776e --- /dev/null +++ b/src/crc32_doc.md @@ -0,0 +1,35 @@ +# crc32 + +## 1. Назначение +Модуль вычисления контрольной суммы **CRC-32 (IEEE 802.3)** с полиномом `0xEDB88320`. Используется в подсистемах `secure_channel` и `stcp` для контроля целостности данных при передаче — перед шифрованием к данным добавляется CRC32, при расшифровке CRC32 пересчитывается и сверяется. + +## 2. Как пользоваться + +**Одноразовое вычисление (внутренне инициализирует таблицу при первом вызове):** +```c +// Исходное значение 0xFFFFFFFF, результат XOR-ится с ~0 +uint32_t crc = crc32_calc(data, data_len); +``` + +**Инкрементальное вычисление (например, при получении данных фрагментами):** +```c +uint32_t crc = 0xFFFFFFFF; +while (chunk) { + crc = crc32_update(crc, chunk_data, chunk_len); +} +crc ^= 0xFFFFFFFF; // финальный XOR +``` + +**С произвольным начальным значением (без финального XOR):** +```c +uint32_t crc = crc32_calc_ex(data, len, initial_crc); +``` + +## 3. API + +| Функция | Описание | +|---------|----------| +| `crc32_init()` | Принудительная инициализация lookup-таблицы (256 × uint32_t). Автоматически вызывается при первом использовании, ручной вызов не обязателен. | +| `crc32_calc(data, len)` | CRC32 «в один проход». Авто-инит, начальное значение `0xFFFFFFFF`, финальный `~crc`. При `data == NULL` или `len == 0` возвращает `0xFFFFFFFF`. | +| `crc32_ex(data, len, initial_crc)` | CRC32 с заданным начальным значением, **без** финального XOR. Подходит для инкрементального использования и цепочек вызовов. | +| `crc32_update(crc, data, len)` | Синоним `crc32_ex(data, len, crc)` — инкрементальное обновление. | diff --git a/src/db_sync_doc.md b/src/db_sync_doc.md new file mode 100644 index 00000000..3586aca2 --- /dev/null +++ b/src/db_sync_doc.md @@ -0,0 +1,239 @@ +# db_sync — Distributed content-addressed table with SQLite + P2P sync + +## 1. Назначение + +Децентрализованная реплицируемая таблица JSON-записей, синхронизируемая между всеми узлами сети через ETCP. Каждый узел хранит полную копию данных каждого инстанса. Модуль поддерживает несколько независимых инстансов (таблиц), каждый идентифицируется хешем `SHA256(name || id_be)[0:8]`. + +**Ключевые свойства:** +- **Content-addressed**: целостность цепочки гарантируется `chain_hash` — каскадным SHA256, где `chain_hash[n] = SHA256(chain_hash[n-1] || id || timestamp || author || author_signature)`. +- **Ed25519-подписи**: каждая запись обязательно подписана автором (`Ed25519(timestamp || json_data)`). Записи без подписи или с неверной подписью отвергаются. +- **Multi-instance**: несколько независимых таблиц внутри одного процесса (напр. `("chats", 1)` и `("chats", 2)`). +- **Ordered**: записи упорядочены по `(timestamp, author_signature)`. Первичный ключ — та же пара, определяющая уникальность (дубликаты по тому же автору в ту же миллисекунду невозможны). +- **Append-only**: записи не редактируются и не удаляются явно, только TTL-очистка собственных неотправленных записей. +- **Sync protocol**: 8 типов сообщений (INIT_SYNC → INIT_RESP → REFINE → SEND_DATA → SYNC_DONE, плюс PUSH/ACK_PUSH/ERROR). Используется бинарный поиск расхождений по chain_hash. +- **Push**: новые записи немедленно рассылаются (PUSH) всем синхронизированным пирам. + +## 2. Как пользоваться + +### Типовой сценарий + +``` +1. В конфиге: db_sync_enabled = 1, db_sync_ttl = 86400 +2. db_sync_init(inst) — вызывается автоматически при старте utun_instance +3. struct DB_SYNC_INSTANCE* si = db_sync_instance_add(inst, "chats", 1); + → Создаёт таблицу SQLite, верифицирует цепочку, запускает TTL-таймер. + → Если есть активные ETCP-соединения — автоматически запускает синхронизацию. +4. Вставка записи: + uint64_t ts = db_sync_next_timestamp(si); // монотонно возрастающий timestamp (ms) + uint8_t sig[64]; + Ed25519_sign(my_privkey, ts || my_node_id || json, sig); + db_sync_insert_signed(si, json_str, json_len, sig, 64, ts); + → Подпись ОБЯЗАТЕЛЬНА (sig=NULL → ошибка). Запись автоматически push-ится всем synced пирам. +5. Чтение: + db_sync_select(si, offset, limit, my_callback, ctx); +6. db_sync_instance_remove(si) — деактивировать инстанс (таблица БД не удаляется). +7. db_sync_destroy(inst) — вызывается автоматически при завершении. +``` + +### Ключевые концепции + +#### Multi-instance +Каждый экземпляр `DB_SYNC_INSTANCE` идентифицируется 64-битным хешем `hash = SHA256(name || htobe64(id))[0:8]`. Этот хеш используется как routing key в синхронизационных сообщениях. На приёмной стороне по хешу находится нужный инстанс. + +#### Chain hash +Каскадный хеш цепочки записей: + +``` +chain_hash[0] = SHA256(0x00...00 || id_0 || ts_0 || author_0 || sig_0) +chain_hash[n] = SHA256(chain_hash[n-1] || id_n || ts_n || author_n || sig_n) +``` + +Для протокола синхронизации используются первые 8 байт (`chain_hash8`). Сравнивая эти хеши на разных позициях, узлы находят точку расхождения. + +#### Sync protocol (8 message types) + +| Message | Direction | Описание | +|---------|-----------|----------| +| `DB_MSG_INIT_SYNC (0x01)` | A→B | Инициатор шлёт своё количество записей (`my_count`) | +| `DB_MSG_INIT_RESP (0x02)` | B→A | Truncation point `tp = min(counts)-1`, `chain_hash8[tp]`, + до 16 sparse-хешей на позициях `tp-2^k` | +| `DB_MSG_REFINE (0x03)` | A→B, B→A | Бинарный поиск: до 16 равномерно распределённых хешей в диапазоне расхождения | +| `DB_MSG_SEND_DATA (0x04)` | A→B, B→A | Пакетная передача записей (до 32 за раз). Wire: `[id:8][ts:8][author:8][dlen:4][data][sig_len:1=64][sig:64]` | +| `DB_MSG_PUSH (0x05)` | A→B | Рассылка одной новой записи всем synced-пирам | +| `DB_MSG_ACK_PUSH (0x06)` | B→A | Подтверждение получения PUSH; обновляет delivery_chain и флаг WAS_SENT | +| `DB_MSG_SYNC_DONE (0x07)` | B→A | Финальное подтверждение: peer_count + last_chain_hash8 | +| `DB_MSG_ERROR (0x08)` | B→A | Ошибка. Коды: `0x01` — instance not found, `0x02` — instance disabled | + +**Алгоритм синхронизации:** + +1. `INIT_SYNC`: A → B: `my_count_a`; B вычисляет `tp = min(count_a, count_b) - 1`. +2. `INIT_RESP`: B → A: `tp`, `chain_hash8_b[tp]`, + sparse-хеши на позициях `tp-1, tp-2, tp-4, tp-8, ..., tp-32768`. +3. Если `hash8_b[tp] == hash8_a[tp]` — цепочки совпадают до `tp`. Более длинная сторона шлёт «хвост» (записи после `tp`). +4. Если sparse-хеши показывают расхождение: A определяет диапазон `[ds, de]` где хеши не совпадают. Если `de-ds ≤ 1` — пустой `REFINE` (запрос данных). Иначе — `REFINE` с до 16 хешами, равномерно распределёнными в диапазоне. +5. `REFINE`: B ищет первую позицию, где хеши разошлись, шлёт `SEND_DATA` начиная с этой позиции. +6. `SEND_DATA`: A вставляет полученные записи (с верификацией Ed25519 подписи), пересчитывает каскадный chain_hash, шлёт `SYNC_DONE`. +7. `SYNC_DONE`: если counts/hashes не совпали — реинициируется sync. + +#### Защита от подделок +- Ed25519-подпись автора проверяется для КАЖДОЙ вставляемой записи (и локальной, и от пиров). +- Публичный ключ автора ищется: (1) в своих ключах (если self), (2) в `topo_node_sqlite`, (3) в `peer_ed25519_pubkey` активного ETCP-соединения. +- Если подпись невалидна — запись отвергается с логом "discarding as forgery". + +#### Peer management +- При поднятии ETCP-соединения для каждого инстанса добавляется `SI_PEER` и запускается синхронизация. +- При разрыве соединения `sync_state` пира сбрасывается в 0. +- `peer_check` таймер (каждые 5с) перебирает `topo_group->senders_list` и запускает синхронизацию для пиров с `sync_state == 0`. +- `PUSH` рассылается только пирам в состоянии `sync_state >= 1`. + +#### TTL cleanup +- Периодический таймер (каждые 3600с). +- Удаляет записи authored by self, где НЕ установлен флаг `DB_REC_FLAG_WAS_SENT` (0x01) и `timestamp < now - db_sync_ttl`. +- Чужие записи и свои отправленные не удаляются. + +## 3. API + +### Глобальный жизненный цикл + +```c +int db_sync_init(struct UTUN_INSTANCE* inst); +void db_sync_destroy(struct UTUN_INSTANCE* inst); +``` +- `db_sync_init` — инициализирует DB_SYNC, открывает SQLite по пути `/sync`, биндит ETCP service `0x20`, вешает коллбэки на существующие и новые соединения, стартует `peer_check` таймер. Если `db_sync_enabled = 0` — создаёт структуру в disabled-режиме. +- `db_sync_destroy` — отменяет все таймеры, анбиндит ETCP service, снимает коллбэки со всех соединений, закрывает SQLite, освобождает память. + +### Управление инстансами + +```c +struct DB_SYNC_INSTANCE* db_sync_instance_add(struct UTUN_INSTANCE* inst, const char* name, uint64_t id); +void db_sync_instance_remove(struct DB_SYNC_INSTANCE* si); +``` +- `db_sync_instance_add` — создаёт/регистрирует инстанс. Проверяет валидность имени (`[a-zA-Z0-9_]`, макс 48 символов). Вычисляет hash, создаёт SQLite-таблицу `db_sync__`, верифицирует цепочку хешей, запускает TTL-таймер. Если уже есть активные соединения — автоматически инициирует sync. +- `db_sync_instance_remove` — деактивирует инстанс: `enabled=0`, отменяет TTL-таймер, освобождает peers, удаляет из массива. Таблица БД **не удаляется**. + +### Операции с данными + +```c +int db_sync_insert_signed(struct DB_SYNC_INSTANCE* si, const char* json_data, size_t len, + const uint8_t* sig, size_t sig_len, uint64_t ts); +uint32_t db_sync_count(struct DB_SYNC_INSTANCE* si); +uint64_t db_sync_get_last_timestamp(struct DB_SYNC_INSTANCE* si); +uint64_t db_sync_next_timestamp(struct DB_SYNC_INSTANCE* si); +``` +- `db_sync_insert_signed` — вставляет подписанную запись. `sig` ОБЯЗАТЕЛЬНО 64 байта (Ed25519). Проверяет подпись, проверяет дубликат по `(timestamp, author_signature)`, вычисляет chain_hash, вставляет, каскадно пересчитывает хеши последующих записей, рассылает PUSH всем synced-пирам. Возвращает 0 (успех), 1 (уже существует), -1 (ошибка), -2 (неверная подпись). +- `db_sync_count` — количество записей в локальной БД. +- `db_sync_get_last_timestamp` — последний выданный `db_sync_next_timestamp`. +- `db_sync_next_timestamp` — возвращает монотонно возрастающий timestamp в миллисекундах. Гарантирует `last_timestamp_ms < returned`. + +### Чтение (select) + +```c +typedef void (*db_sync_select_cb)(void* arg, uint64_t id, uint64_t timestamp, + const char* data, size_t data_len, uint64_t author, + const uint8_t* author_sig, size_t sig_len, + int delivered_peers, const char* delivery_chain); +int db_sync_select(struct DB_SYNC_INSTANCE* si, uint32_t offset, uint32_t limit, + db_sync_select_cb cb, void* arg); +``` +- `db_sync_select` — итератор по записям, упорядоченным `ORDER BY timestamp, author_signature`. `limit=0` — без ограничения. Для каждой записи вызывает `cb` с полями: id, timestamp, data (JSON), author (node_id), author_sig (64 байта), delivered_peers (счётчик доставок), delivery_chain (hex-идентификаторы пиров через запятую). Возвращает количество переданных в callback записей. + +### Callback на вставку + +```c +typedef void (*db_sync_insert_cb)(struct DB_SYNC_INSTANCE* si, + uint64_t record_timestamp, + const char* json_data, size_t len, + uint64_t author_node_id, void* arg); +void db_sync_set_insert_cb(struct DB_SYNC_INSTANCE* si, db_sync_insert_cb cb, void* arg); +``` +- `db_sync_set_insert_cb` — устанавливает callback, вызываемый после успешной вставки записи (локальной или от пира). `author_node_id = self` для локальных вставок, `= peer_node_id` для записей от пиров. + +### Внутренние структуры + +```c +struct DB_SYNC { + struct UTUN_INSTANCE* inst; // обратная ссылка на инстанс + sqlite3* db; // SQLite handle + uint64_t last_connected_tb; // время последнего подключения (timebase) + struct DB_SYNC_INSTANCE* instances; // динамический массив инстансов + int instance_count, instance_capacity; + void* peer_check_timer; // handle uasync-таймера + uint8_t enabled; // глобальный флаг из конфига +}; + +struct DB_SYNC_INSTANCE { + struct DB_SYNC* db_sync; // обратная ссылка + uint64_t hash; // routing key = SHA256(name||id_be)[0:8] + char table_name[64]; // "db_sync__" + uint64_t next_id; // монотонно возрастающий id записей + uint64_t last_timestamp_ms; // последний выданный timestamp (ms) + uint8_t enabled; // per-instance enable/disable + void* ttl_timer; // handle TTL-таймера + struct SI_PEER* peers; // динамический массив пиров + int peer_count, peer_capacity; + db_sync_insert_cb on_insert; // callback на вставку + void* on_insert_arg; +}; + +struct SI_PEER { + uint64_t node_id; // идентификатор пира + uint32_t synced_pos; // позиция, до которой синхронизированы + uint8_t sync_state; // 0=idle, 1=syncing, 2=synced +}; +``` + +### Конфигурация + +| Параметр | Файл конфига | По умолчанию | Описание | +|----------|-------------|--------------|----------| +| `db_sync_enabled` | `global` | `0` | Включить модуль синхронизации | +| `db_sync_ttl` | `global` | `86400` | TTL неотправленных собственных записей (секунды) | +| `db_path` | `global` | — | Путь к БД; файл создаётся как `/sync` | + +### Константы синхронизации + +| Константа | Значение | Описание | +|-----------|---------|----------| +| `ETCP_RT_ID_DB_SYNC` | `0x20` | ETCP service ID | +| `DB_REFINE_HASHES` | `16` | Макс. количество хешей в REFINE | +| `DB_SEND_DATA_MAX` | `32` | Макс. записей в одном SEND_DATA | +| `DB_SIG_SIZE` | `64` | Размер Ed25519 подписи | +| `DB_SYNC_PEER_CHECK_INTERVAL` | `5` | Интервал проверки пиров (секунды) | +| `DB_SYNC_TTL_INTERVAL` | `3600` | Интервал TTL-очистки (секунды) | + +### Зависимости + +- **etcp_api.h / etcp.h** — P2P-обмен сообщениями, коллбэки соединений +- **topo_group.h / topo_node_sqlite.h** — топология, discovery пиров, Ed25519 pubkeys +- **secure_channel.h** — SHA256 (`sc_sha256_*`), Ed25519 verify (`sc_ed25519_verify`) +- **lib/u_async.h** — асинхронный event loop, таймеры (`uasync_set_timeout`, `uasync_cancel_timeout`) +- **lib/sqlite3.h** — SQLite3 (WAL, synchronous=NORMAL, auto-checkpoint 10000) +- **lib/mem.h** — `u_malloc`, `u_calloc`, `u_realloc`, `u_free` +- **lib/debug_config.h** — `DEBUG_INFO`, `DEBUG_WARN`, `DEBUG_ERROR` (категория `DEBUG_CATEGORY_DEBUG`) +- **lib/sha256.h** — `SC_SHA256_CTX` (если не USE_OPENSSL) +- **lib/platform_compat.h** — `htobe64`, `get_time_tb`, `utun_gettimeofday` +- **openssl/evp.h** — низкоуровневый Ed25519 в `sc_ed25519_verify` + +### Внутренние функции + +| Функция | Назначение | +|---------|-----------| +| `db_sqlite_open/db_sqlite_close` | Открытие/закрытие SQLite с WAL-режимом | +| `db_sha256/db_hash64/db_chain_hash_compute` | SHA256 и вычисление chain_hash | +| `db_instance_find/db_instance_alloc` | Поиск/выделение инстанса по hash | +| `si_peer_find/si_peer_add` | Поиск/добавление пира в инстансе | +| `db_count` | `SELECT COUNT(*)` из таблицы инстанса | +| `db_chain_hash_at/db_chain_hash8_at` | Чтение chain_hash/chain_hash8 на позиции | +| `db_prev_chain_hash` | Поиск chain_hash записи, предшествующей данной | +| `db_cascade_from` | Пересчёт chain_hash начиная с позиции `from_pos` | +| `db_get_ed25519_pubkey` | Получение Ed25519 pubkey пира | +| `db_verify_author_sig` | Проверка Ed25519 подписи автора записи | +| `db_record_insert` | Полный цикл вставки: проверка подписи, проверка дубликата, вставка, каскад chain_hash | +| `db_sync_send/db_sync_send_hash` | Отправка сообщения пиру через ETCP | +| `si_delivery_update` | Обновление delivery_chain при доставке | +| `si_parse_record` | Разбор одной записи из wire-формата | +| `si_send_data_batch` | Формирование и отправка SEND_DATA пакета | +| `db_sync_recv_cb` | Центральный callback приёма ETCP-сообщений, маршрутизация по instance_hash | +| `db_handle_init_sync/_resp/_refine/_send_data/_push/_ack_push/_sync_done/_error` | Обработчики каждого типа сообщения | +| `db_sync_initiate_sync` | Отправка INIT_SYNC пиру | +| `db_verify_chain` | Полная верификация цепочки chain_hash (при старте инстанса) | +| `db_sync_peer_check_cb` | Таймер: периодический поиск активных пиров и запуск sync | +| `db_sync_instance_ttl_cb` | Таймер: TTL-очистка неотправленных собственных записей | diff --git a/src/dummynet_doc.md b/src/dummynet_doc.md new file mode 100644 index 00000000..6cb33ff1 --- /dev/null +++ b/src/dummynet_doc.md @@ -0,0 +1,86 @@ +# Dummynet — UDP Network Emulator + +## 1. Назначение +Эмулятор сетевых условий для тестирования: задержка (фиксированная + случайная), ограничение пропускной способности (token bucket) и случайные потери пакетов. Два режима работы: **FULL** (автономный UDP-прокси между портами) и **INLINE** (встраиваемый фильтр в `send_hook` ETCP_LINK без создания своего сокета). + +## 2. Как пользоваться + +### Режим FULL (автономный UDP-прокси) + +1. `dummynet_create(ua, bind_ip, listen_port)` — создаёт контекст, открывает UDP-сокет на `listen_port`. +2. `dummynet_set_direction(dn, DUMMYNET_FORWARD, ...)` и `DUMMYNET_BACKWARD` — настраивает параметры для каждого направления. +3. Пакеты от `listen_port-1` идут в направлении FORWARD, от `listen_port+1` — в BACKWARD. + +``` +[port-1] → [dummynet: delay→loss→shaper] → [dest_forward] +[port+1] → [dummynet: delay→loss→shaper] → [dest_backward] +``` + +### Режим INLINE (send_hook в ETCP_LINK) + +1. `dummynet_filter_create(ua)` — создаёт фильтр (нет своего сокета). +2. `dummynet_filter_set_loss(df, permille)` / `dummynet_filter_set_delay(df, ms, jitter_ms)` — настройка. +3. `dummynet_filter_attach(df, link)` — встраивается в `link->send_hook`, перехватывая все вызовы `socket_sendto`. +4. Дополнительно: `dummynet_filter_block_addr/unblock_addr` — блокировка отправки на конкретные адреса. + +### Механизмы эмуляции + +| Механизм | Реализация | +|----------|-----------| +| **Delay** (fixed + random) | При приёме пакета создаётся uasync-таймер (`dummynet_delay_callback`). После срабатывания пакет кладётся в ll_queue направления | +| **Bandwidth (token bucket)** | Shaper-таймер забирает из очереди по одному пакету с интервалом `len × 8 / bw_kbps` (timebase 0.1ms). Гарантирует не более `bandwidth_kbps` | +| **Loss (permille)** | При приёме: `rand() % 1000 < loss_permille` → дроп. В INLINE-режиме — в хуке отправки | + +### Статистика + +Структура `dummynet_stats`: `recv`, `sent`, `dropped` (переполнение очереди), `lost` (random loss), `queue_size` (текущий), `queue_max` (пиковый). +Дополнительные отладочные счётчики: `delay_timer_set/fire`, `shaper_timer_set/fire`. + +### Ключевые нюансы +- В FULL-режиме порты `listen_port-1`, `listen_port`, `listen_port+1` должны быть свободны. Направление определяется по порту источника. +- Шейпер использует burst allowance (`SHAPER_BURST_TB = 10 timebase units`), допуская небольшие всплески. +- При переполнении очереди (> `max_queue_pkts`) пакет дропается, увеличивается `dropped`. +- INLINE-фильтр в режиме блокировки адреса не дропает пакет, а возвращает `len` (пакет «успешно отправлен», но фактически никуда не ушёл). + +## 3. API + +### FULL-режим + +| Функция | Назначение | +|---------|------------| +| `dummynet_create(ua, bind_ip, listen_port)` | Создать контекст, открыть UDP-сокет, зарегистрировать в uasync | +| `dummynet_destroy(dn)` | Закрыть сокет, отменить таймеры, очистить очереди, освободить память | +| `dummynet_set_direction(dn, dir, delay_fixed_ms, delay_random_ms, bw_kbps, max_q, loss_permille, dest_ip, dest_port)` | Настроить параметры направления | +| `dummynet_get_stats(dn, dir)` | Получить статистику направления | +| `dummynet_reset_stats(dn, dir)` | Сбросить статистику (-1 = все направления) | +| `dummynet_get_socket(dn)` | Вернуть fd сокета | +| `dummynet_get_listen_port(dn)` | Вернуть listen_port | +| `dummynet_get_uasync(dn)` | Вернуть указатель на UASYNC | +| `dummynet_get_queue_size(dn, dir)` | Вернуть текущий размер очереди направления | +| `dummynet_get_debug_counters(dn, dir, ...)` | Вернуть отладочные счётчики таймеров | + +### INLINE-режим (dummynet_filter) + +| Функция | Назначение | +|---------|------------| +| `dummynet_filter_create(ua)` | Создать фильтр | +| `dummynet_filter_set_loss(df, permille)` | Установить вероятность потерь | +| `dummynet_filter_set_delay(df, delay_ms, jitter_ms)` | Установить задержку (NOTE: в текущей реализации delay не применяется в хуке) | +| `dummynet_filter_attach(df, link)` | Прикрепить хук к ETCP_LINK | +| `dummynet_filter_detach(df)` | Открепить хук | +| `dummynet_filter_destroy(df)` | Уничтожить фильтр | +| `dummynet_filter_get_stats(df)` | Получить статистику | +| `dummynet_filter_block_addr(df, ip, port)` | Заблокировать адрес (до 4 адресов) | +| `dummynet_filter_unblock_addr(df, ip, port)` | Разблокировать адрес | + +### Структуры + +| Структура | Назначение | +|-----------|------------| +| `dummynet_stats` | Статистика: `recv`, `sent`, `dropped`, `lost`, `queue_size`, `queue_max` | +| `dummynet_dir` | Внутренняя: параметры направления, очередь, таймеры шейпера, отладочные счётчики | +| `dummynet` | Контекст FULL-режима: ua, сокет, два `dummynet_dir`, пул пакетов | +| `dummynet_filter` | Контекст INLINE-режима: ua, loss_permille, delay, link, статистика, список блокировок | + +### Зависимости +`u_async.h`, `ll_queue.h`, `memory_pool.h`, `socket_compat.h`, `etcp_connections.h` (для ETCP_LINK), `platform_compat.h` diff --git a/src/eim_nat_doc.md b/src/eim_nat_doc.md new file mode 100644 index 00000000..0e1a667a --- /dev/null +++ b/src/eim_nat_doc.md @@ -0,0 +1,47 @@ +# EIM NAT (Endpoint-Independent Mapping NAT) + +## 1. Назначение +Трансляция сетевых адресов с endpoint-independent поведением: для каждого кортежа `(внутренний IP, внутренний порт, протокол)` выделяется уникальный внешний порт на шлюзе, не зависящий от внешнего адресата. Поддерживает TCP, UDP и ICMP Echo. Инкрементальный пересчёт контрольных сумм. + +## 2. Как пользоваться +```c +struct eim_nat_ctx nat; +eim_nat_init_ctx(&nat, &global_cfg); // таблица 64K записей +eim_nat_egress(&nat, pkt, len, node_id, conn); // src IP/port -> внешние +eim_nat_ingress(&nat, pkt, len, &entry); // dst IP/port -> внутренние +eim_nat_add_forward(&nat, proto, ip, port, ext_port); // статический проброс +eim_nat_destroy_ctx(&nat); +``` + +**Таблица:** 64K записей, индексируется по внешнему порту (внешний порт == индекс в таблице). + +**eim_nat_egress:** выделяет внешний порт (аллоцирует из циклического пула `next_port`), заменяет src IP на шлюзовой и src порт на внешний, корректирует IP- и транспортную контрольные суммы. Фрагментированные пакеты пропускаются без обработки. + +**eim_nat_ingress:** ищет запись по внешнему dst-порту в таблице, заменяет dst IP на внутренний и dst порт на внутренний, корректирует контрольные суммы. Возвращает `out_entry` — указатель на запись (содержит `src_node_id` и `src_conn` для маршрутизации ответа). + +**eim_nat_add_forward:** добавляет статическую запись (`EIM_NAT_ENTRY_STATIC`), которая не вытесняется динамическими. + +**Контрольные суммы:** пересчитываются инкрементально через `csum_update_n()` — старые значения вычитаются из ~csum, новые прибавляются. Это работает для TCP pseudo-header checksum и UDP. + +**Конфиг:** +```ini +[nat] +enabled = 1 +tun_ip = 10.0.0.1 +port_start = 10000 +port_end = 20000 +[nat_forward] +rule = tcp 192.168.1.2:80 :8080 +``` + +## 3. API + +| Функция/Структура | Описание | +|---|---| +| `struct eim_nat_entry` | Запись: internal_ip, internal_port, proto, state (FREE/ACTIVE/STATIC), src_node_id, src_conn, last_seen | +| `struct eim_nat_ctx` | Контекст: gateway_ip, port_start/end, next_port (циклический аллокатор), table[64K], initialized | +| `eim_nat_init_ctx(ctx, cfg)` | Инициализация таблицы, загрузка статических пробросов из конфига | +| `eim_nat_destroy_ctx(ctx)` | Освобождение таблицы, обнуление | +| `eim_nat_egress(ctx, ip_data, ip_len, src_node_id, src_conn)` | Трансляция исходящего пакета: src IP/port → шлюзовые. Создаёт динамическую запись при первом проходе | +| `eim_nat_ingress(ctx, ip_data, ip_len, out_entry)` | Обратная трансляция входящего пакета: dst IP/port → внутренние. Заполняет out_entry для маршрутизации | +| `eim_nat_add_forward(ctx, proto, ip_host, port_net, ext_port)` | Добавление статического проброса (runtime API) | diff --git a/src/etcp_api_doc.md b/src/etcp_api_doc.md new file mode 100644 index 00000000..62047ded --- /dev/null +++ b/src/etcp_api_doc.md @@ -0,0 +1,121 @@ +# ETCP API + +## 1. Назначение + +Модуль предоставляет публичный API для отправки и приёма пакетов через ETCP-соединения. Это входная точка для всех вышележащих подсистем (роутер, маршрутизация, NAT, TCP-прокси, транспорт сообщений). + +**Ключевая идея:** приёмный коллбэк `etcp_int_recv` диспетчеризует входящие пакеты по первому байту кодограммы (`cmd`). Любой модуль может зарегистрировать обработчик на свой `cmd` через `etcp_bind` и слать данные через `etcp_send`. + +Формат кодограммы (сырой пакет внутри ETCP): ` <данные N байт>`. + +Модуль **тонкий**: отправка — просто `queue_data_put` в очередь normalizer'а, приём — выбор обработчика по ID из массива `ETCP_BINDINGS`. + +## 2. Как пользоваться + +### Типовой сценарий: модуль хочет принимать пакеты своего типа + +```c +// Регистрируем коллбэк при инициализации модуля +etcp_bind(instance, ETCP_ID_DATA, my_data_recv_callback); + +// Коллбэк вызывается для каждого входящего пакета с cmd=ETCP_ID_DATA +void my_data_recv_callback(struct ETCP_CONN* conn, struct ll_entry* entry) { + // entry->dgram[0] = cmd (уже совпал с ETCP_ID_DATA) + // entry->dgram[1..len-1] = данные + process_data(conn, entry->dgram + 1, entry->len - 1); + queue_dgram_free(entry); + queue_entry_free(entry); +} +``` + +### Отправка пакета + +```c +struct ll_entry* e = queue_entry_new_from_pool(inst->data_pool); +memcpy(e->dgram, &my_cmd, 1); +memcpy(e->dgram + 1, payload, payload_len); +e->len = 1 + payload_len; +etcp_send(conn, e); // ownership entry передаётся +``` + +### Коллбэки состояния соединения + +```c +// Срабатывает после INIT-handshake, можно слать данные +etcp_conn_set_ready_cbk(conn, on_conn_ready, my_data); + +// Срабатывает при поднятии/падении соединения +etcp_conn_set_up_cbk(conn, on_conn_up, my_data); +etcp_conn_set_down_cbk(conn, on_conn_down, my_data); + +// На уровне instance — при создании нового входящего соединения +etcp_set_new_conn_cbk(instance, on_new_conn, my_data); +``` + +**Важно:** коллбэки `set_*` заменяют всю цепочку одним обработчиком; `add_*`/`remove_*` добавляют/убирают обработчик в связном списке из нескольких коллбэков. + +### Сервисные ID (для etcp_router) + +Используются `ETCP_RT_ID_*`, отличаются от сырых `ETCP_ID_*` (пакет оборачивается в транспортный ID `ETCP_ID_SVC_ROUTE=0x03`): +- `ETCP_RT_ID_DATA` (0x00) — маршрутизация данных +- `ETCP_RT_ID_NAT` (0x02) — NAT-трафик между узлами +- `ETCP_RT_ID_SVC_ROUTE` (0x03) — транспорт роутера +- `ETCP_RT_ID_TCP_PROXY` (0x04) — TCP-прокси +- `ETCP_RT_ID_UDP_PROXY` (0x05) — UDP-прокси +- `ETCP_RT_ID_ICMP_PROXY` (0x06) — ICMP-прокси +- `ETCP_RT_ID_MSG_TRANSPORT` (0x10) — локальный IPC-транспорт сообщений +- `ETCP_RT_ID_CONN_MGR` (0x11) — управление соединениями +- `ETCP_RT_ID_NTP_TIME` (0x12) — синхронизация времени + +### Фоновые соединения (etcp_connect) + +```c +etcp_connect(instance, node, on_connect, arg, + ETCP_CONNECT_EARLY | ETCP_CONNECT_BGP_READY); +``` + +Флаги: `ETCP_CONNECT_EARLY` — раннее подключение, `ETCP_CONNECT_LATE` — отложенное, `ETCP_CONNECT_BGP_READY` — после BGP-синхронизации. + +## 3. API + +### Структуры + +| Структура | Назначение | +|-----------|-----------| +| `ETCP_BINDINGS` | Per-instance массив `callbacks[256]` + коллбэк приёма метрик (`on_metrics_rcvd`) | + +### Типы коллбэков + +| Тип | Сигнатура | Назначение | +|-----|-----------|-----------| +| `etcp_recv_fn` | `void(conn, entry)` | Приём пакета — **обязан** освободить entry/dgram | +| `etcp_metrics_fn` | `void(user_ptr, conn, csv, csv_len, sig)` | Приём метрик (CSV + Ed25519-подпись) | +| `etcp_cbk_fn` | `void(conn, arg)` | События ready/up/down/new_conn | +| `etcp_connect_callback_t` | `void(arg, conn, type)` | Результат фонового подключения | + +### Функции + +| Функция | Назначение | +|---------|-----------| +| `etcp_bind(inst, id, fn)` | Подписаться на пакеты с cmd=`id` | +| `etcp_unbind(inst, id)` | Отписаться | +| `etcp_send(conn, entry)` | Отправить пакет (забирает ownership entry) | +| `etcp_int_recv(queue, arg)` | **Внутренняя:** диспетчер из очереди normalizer'а, вызывает bound-коллбэк по cmd | +| `etcp_conn_set_ready_cbk(conn, fn, arg)` | Установить одиночный коллбэк готовности (заменяет существующие) | +| `etcp_conn_set_up_cbk(conn, fn, arg)` | Установить коллбэк поднятия соединения | +| `etcp_conn_set_down_cbk(conn, fn, arg)` | Установить коллбэк падения соединения | +| `etcp_conn_add_ready_cbk/remove_ready_cbk` | Добавить/убрать из цепочки ready-коллбэков | +| `etcp_conn_add_up_cbk/remove_up_cbk` | Аналогично для up | +| `etcp_conn_add_down_cbk/remove_down_cbk` | Аналогично для down | +| `etcp_set_new_conn_cbk(inst, fn, arg)` | Установить коллбэк на входящее соединение | +| `etcp_add_new_conn_cbk/remove_new_conn_cbk` | Добавить/убрать из цепочки new_conn | +| `etcp_set_routing_exchange_state(conn, state)` | Установить состояние обмена маршрутами; при state≥3 вызывает `bgp_ready_cbk` | +| `etcp_connect(inst, node, cb, arg, flags)` | Фоновое подключение к узлу | + +### Командные ID (сырые, первый байт кодограммы) + +| Константа | Значение | Назначение | +|-----------|----------|-----------| +| `ETCP_ID_DATA` | 0x00 | Данные для передачи адресату | +| `ETCP_ID_ROUTE_ENTRY` | 0x01 | Элемент роутинг-таблицы (BGP) | +| `ETCP_ID_SVC_ROUTE` | 0x03 | Транспорт роутера (etcp_router) | diff --git a/src/etcp_bbr_doc.md b/src/etcp_bbr_doc.md new file mode 100644 index 00000000..75b0eab0 --- /dev/null +++ b/src/etcp_bbr_doc.md @@ -0,0 +1,95 @@ +# BBR Congestion Control (etcp_bbr) + +## 1. Назначение +Реализация алгоритма BBRv3 (Bottleneck Bandwidth and Round-trip propagation time) для контроля перегрузки на каждом ETCP-линке независимо. Вычисляет оптимальный cwnd и pacing rate на основе измерений bandwidth и RTT, а не потерь пакетов (в отличие от loss-based алгоритмов). Моделирует состояние канала через четыре фазы: STARTUP (экспоненциальный поиск bandwidth), DRAIN (слив лишнего inflight), PROBE_BW (циклическое зондирование), PROBE_RTT (периодический замер минимального RTT). + +## 2. Как пользоваться + +### Инициализация +```c +struct bbr bbr_state; +bbr_init(&bbr_state); // устанавливает STARTUP, сбрасывает все окна +``` +Сразу после init нужно установить `init_cwnd` через etcp/kernel, BBR начинает со STARTUP. + +### Вызов на каждом ACK +```c +struct bbr_rate_sample rs = { + .delivered = packets_delivered_this_ack, + .interval_us = us_since_last_ack, + .rtt_us = latest_rtt_us, + .acked_sacked = bytes_acked, + .lost = packets_lost_since_last_ack, + .is_app_limited = is_output_queue_below_target, + .tx_in_flight = current_inflight_packets, + .prior_delivered = cumulative_delivered_before, +}; + +uint32_t cwnd = current_cwnd, pacing_rate = current_pacing; +bbr_main(&bbr_state, &rs, &cwnd, &pacing_rate, MSS, inflight_bytes, is_cwnd_limited); +// применять cwnd, pacing_rate к линку +``` + +### Нотификация о потерях и старте передачи +```c +bbr_note_loss(&bbr_state); // вызывать при обнаружении потери +bbr_tx_start(&bbr_state); // вызывать при возобновлении отправки после idle +``` + +### Интеграция с ETCP_LINK +BBR создаётся в `etcp_link_new()` как `link->bbr`. `bbr_main()` вызывается в `etcp_handle_ack()` при обработке каждого ACK-пакета. Результаты (cwnd, pacing_rate) записываются в `link->inflight_lim_bytes` и `link->bbr_pacing_rate`. Pacing rate подаётся в traffic shaper линка. `link_id` задаётся в `link->local_link_id` для читаемых логов. + +### Тестирование +Поле `now_tb` позволяет подменять реальное время (`get_time_tb()`) на заданное — при `now_tb == 0` используется `get_time_tb()`, иначе управляемое значение. `on_cwnd_update` — callback для отслеживания изменений cwnd в тестах. + +### Фиксированная точка +- `BBR_SCALE = 8` — масштаб для gain-коэффициентов (2.77 = 2.77 * 256) +- `BW_SCALE = 24` — масштаб для bandwidth-значений (bytes/sec * mss) +- Все внутренние вычисления в фиксированной точке, преобразование в реальные единицы через `bbr_rate_bytes_per_sec()` + +## 3. API + +### Структуры + +**`struct bbr_rate_sample`** — сэмпл доставки для одного ACK +| Поле | Назначение | +|------|-----------| +| `delivered` | сколько пакетов доставлено этим ACK | +| `interval_us` | время с предыдущего ACK, мкс | +| `rtt_us` | последний измеренный RTT | +| `acked_sacked` | байт подтверждено (+SACK) | +| `prior_delivered` | кумулятивно доставлено до этого ACK (для детекта round) | +| `tx_in_flight` | inflight на момент отправки подтверждённых пакетов | +| `lost` | число потерянных пакетов с последнего ACK | +| `is_app_limited` | приложение не забивает канал (выходная очередь ниже порога) | + +**`struct bbr`** — состояние BBR на линк (подробности в etcp_bbr.h:55-108) + +### Функции + +**`void bbr_init(struct bbr* bbr)`** — инициализация: STARTUP, сброс окон, `min_rtt_us = ~0U`, `bw_lo = ~0U`, `inflight_hi/lo = ~0U`. + +**`void bbr_main(struct bbr* bbr, const struct bbr_rate_sample* rs, uint32_t* cwnd_out, uint32_t* pacing_rate_out, uint32_t mss, uint32_t inflight_bytes, int is_cwnd_limited)`** — главный вызов на каждый ACK. Обновляет delivered, детектит начало раунда, вычисляет sample_bw, обновляет модель (congestion signals, ack aggregation, full_bw, drain, cycle, min_rtt), применяет gains, вычисляет cwnd и pacing rate. + +**`void bbr_note_loss(struct bbr* bbr)`** — помечает потерю в текущем раунде/цикле. + +**`void bbr_tx_start(struct bbr* bbr)`** — отмечает возобновление передачи после idle, сбрасывает `ack_epoch`. + +### Режимы и циклы + +| Режим | Описание | +|-------|---------| +| `BBR_STARTUP` (0) | Экспоненциальный рост cwnd (gain 2.77 pace, 2.0 cwnd), пока не найден bottleneck | +| `BBR_DRAIN` (1) | Слив избыточного inflight (gain 1000/2885 ≈ 0.35 pace) | +| `BBR_PROBE_BW` (2) | Основной рабочий режим, циклическое зондирование | +| `BBR_PROBE_RTT` (3) | Периодический сброс cwnd до 4*MSS для замера min_rtt | + +Циклы внутри `BBR_PROBE_BW`: +- `BBR_BW_PROBE_UP` (1.25× pace) — зондирование большей полосы +- `BBR_BW_PROBE_DOWN` (0.75× pace) — слив после зонда +- `BBR_BW_PROBE_CRUISE` (1.0× pace) — крейсерский режим +- `BBR_BW_PROBE_REFILL` (1.0× pace) — восполнение перед новым зондом + +### Зависимости +- `lib/u_async.h` — `get_time_tb()` для реального времени +- `lib/debug_config.h` — `DEBUG_DEBUG(DEBUG_CATEGORY_BBR, ...)` для отладки diff --git a/src/etcp_connect_doc.md b/src/etcp_connect_doc.md new file mode 100644 index 00000000..3a1518a4 --- /dev/null +++ b/src/etcp_connect_doc.md @@ -0,0 +1,71 @@ +# ETCP Connect (etcp_connect) + +## 1. Назначение +Управление исходящими ETCP-соединениями. Предоставляет асинхронный API `etcp_connect()` для установки соединения с удалённым узлом: создаёт ETCP_CONN, настраивает криптографию, поднимает UDP-линки и опционально TCP/STCP-транспорт, отслеживает прогресс установки с таймерами. Доставляет коллбэки о фазах готовности (EARLY, LATE, BGP_READY). + +## 2. Как пользоваться + +```c +// Запуск подключения к узлу +etcp_connect(inst, node, my_connect_cb, my_arg, ETCP_CONNECT_EARLY | ETCP_CONNECT_LATE); +``` + +Коллбэк вызывается на разных фазах: +```c +void my_connect_cb(void* arg, struct ETCP_CONN* conn, int type) { + if (!conn) { + // type == 0 — таймаут/ошибка подключения + return; + } + if (type & ETCP_CONNECT_EARLY) + // первый линк готов (можно начинать обмен) + if (type & ETCP_CONNECT_LATE) + // settle-фаза завершена (все линки проверены, неудачные удалены) + if (type & ETCP_CONNECT_BGP_READY) + // BGP-синхронизация завершена (routing_exchange_active >= 3) +} +``` + +**Жизненный цикл:** +1. `etcp_connect()` — создаёт ETCP_CONN, crypto_ctx, UDP-линки и TCP/STCP +2. `connect_ready_cb()` — первый линк стал ready → EARLY-коллбэк → запуск settle-таймера +3. `connect_settle_timeout_cb()` — settle (min_rtt × 8, clamp 500..2000ms) → удаление неудачных линков → LATE-коллбэк +4. `connect_bgp_ready_cb()` — BGP завершён → BGP_READY-коллбэк +5. При таймауте (`connect_initial_timeout_cb`) — type=0 (conn=NULL), все ресурсы освобождаются + +**Нюансы:** +- Если соединение уже установлено — коллбэки вызываются немедленно +- Если соединение в процессе — коллбэк добавляется в список ожидающих (поддержка нескольких подписчиков) +- `etcp_connect_cancel_for_conn()` — только для deferred cleanup (фаза 2 закрытия), НЕ из mainloop +- При таймауте `etcp_connect()` может доставить type=0 с conn=NULL — нужно обрабатывать + +## 3. API + +### Структуры +**ETCP_CONNECT** — контекст одного исходящего соединения: +- `conn` — целевой ETCP_CONN +- `tcp_link` — STCP-транспорт (опционально) +- `cb_list` — цепочка коллбэков (несколько подписчиков) +- `initial_timer`, `settle_timer` — таймеры контроля прогресса +- `min_rtt` — минимальный RTT среди готовых линков (для расчёта settle-времени) +- `early_delivered` — флаг однократного вызова EARLY +- `tcp_ready` — TCP-линк поднят +- `done` — соединение завершено (ошибка/успех), дальнейшая обработка игнорируется + +### Константы коллбэков (etcp_api.h) +| Константа | Описание | +|---|---| +| `ETCP_CONNECT_EARLY` (1) | Первый линк готов | +| `ETCP_CONNECT_LATE` (2) | Settle-фаза завершена | +| `ETCP_CONNECT_BGP_READY` (4) | BGP-синхронизация завершена | + +### Функции +| Функция | Описание | +|---|---| +| `etcp_connect(inst, node, cb, arg, flags)` | Запустить асинхронное подключение к узлу. Возвращает 0 при успехе | +| `etcp_connect_cancel_for_conn(inst, conn)` | Отменить ожидание для conn. Только из etcp_connection_free_resources (фаза 2) | + +### Пути завершения +1. **Таймаут установки** (`connect_initial_timeout_cb`): stcp_link_close → etcp_connection_close → connect_cancel → type=0 +2. **Settle-таймаут** (`connect_settle_timeout_cb`): удаление неудачных линков/stcp → LATE → connect_cancel +3. **Штатное закрытие** (`utun_instance_destroy`): фаза 1 (detach) → фаза 2 (deferred: `etcp_connect_cancel_for_conn`) diff --git a/src/etcp_connections_doc.md b/src/etcp_connections_doc.md new file mode 100644 index 00000000..4f5b575a --- /dev/null +++ b/src/etcp_connections_doc.md @@ -0,0 +1,198 @@ +# 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. Как пользоваться + +### Инициализация сокетов и соединений + +Двухфазная инициализация: +```c +// Фаза 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 +``` + +### Отправка зашифрованного пакета + +```c +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 пробники) + +```c +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` (линк уже мёртв), клиент шлёт всегда + +## 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-пакет | + +### 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 | diff --git a/src/etcp_debug_doc.md b/src/etcp_debug_doc.md new file mode 100644 index 00000000..eb216a16 --- /dev/null +++ b/src/etcp_debug_doc.md @@ -0,0 +1,39 @@ +# ETCP Packet Section Dump (etcp_debug) + +## 1. Назначение +Вывод в лог человекочитаемой сводки по ETCP-пакету: направление (SEND/RECV), timestamp, и содержимое каждой секции — ACK (last_delivered, count, rx_dup), CH_TS (возвращаемый и принимаемый channel timestamp), PAYLOAD (размер и seq). Используется для отладки трафика на уровне отдельных пакетов. + +## 2. Как пользоваться + +```c +#include "etcp_debug.h" + +// В обработчике отправки/приёма пакета: +etcp_dump_pkt_sections(pkt, link, 1); // is_send=1 — отправка +etcp_dump_pkt_sections(pkt, link, 0); // is_send=0 — приём +``` + +Вывод идёт через `DEBUG_INFO(DEBUG_CATEGORY_DUMP, ...)`. Для включения: +``` +debug = dump=info +``` + +### Вспомогательная функция +```c +IP_STR ip = ip_to_string(0x0A000001); +// ip.a == "10.0.0.1" +``` + +## 3. API + +**`IP_STR ip_to_string(uint32_t ip)`** — конвертирует IP (network byte order) в строку `a.b.c.d`. Возвращает структуру с буфером 16 байт. + +**`void etcp_dump_pkt_sections(struct ETCP_DGRAM* pkt, struct ETCP_LINK* link, int is_send)`** — парсит `pkt->data` побайтово и выводит сводку секций: `ETCP_SECTION_ACK` (last_delivered, count, rx_dup_count), `ETCP_SECTION_TIMESTAMP` (ret_ts/recv_ts), `ETCP_SECTION_PAYLOAD` (size, seq). Неизвестные секции прерывают парсинг. Формат вывода: +``` +ETCP PKT: SEND ts=12345 ack=100/5 dup=0 ch_ts=200/150 payload=1200(seq=101) +``` + +### Зависимости +- `etcp_connections.h` — `struct ETCP_DGRAM`, `struct ETCP_LINK` +- `etcp.h` — константы `ETCP_SECTION_*` +- `lib/debug_config.h` — `DEBUG_INFO(DEBUG_CATEGORY_DUMP, ...)` diff --git a/src/etcp_doc.md b/src/etcp_doc.md new file mode 100644 index 00000000..920e77d6 --- /dev/null +++ b/src/etcp_doc.md @@ -0,0 +1,289 @@ +# etcp + +## 1. Назначение + +Ядро протокола ETCP — TCP-подобный надёжный транспорт поверх UDP с шифрованием (AES-CCM + X25519), multi-link (несколько каналов между двумя узлами), фрагментацией/сборкой пакетов, ретрансмиссией с экспоненциальным backoff, BBR congestion control и burst-измерением пропускной способности. + +Модуль управляет **одним ETCP-соединением** (struct `ETCP_CONN`) между локальным и удалённым узлом. Соединение может иметь несколько линков (struct `ETCP_LINK`), работающих через разные сокеты / сетевые пути. + +Этапы жизни соединения: +``` +создание (pending, state=0) → первый линк проинициализирован → ready (state=1) → close (state=2, detach) → ref_count=0 → полное освобождение ресурсов +``` + +## 2. Как пользоваться + +### 2.1. Типовой сценарий + +1. **Создание:** `etcp_connection_create(instance, name)` — создаёт `ETCP_CONN` в состоянии `pending` (state=0), размещает в очереди `instance->connections` с индексом peer_node_id=0. +2. **Добавление линков:** `etcp_link_new(etcp, socket, remote_addr, is_server)` — создаёт `ETCP_LINK`, добавляет в список `etcp->links`. +3. **Обмен ключами / INIT:** через `etcp_connections.c` происходит обмен INIT_REQUEST/INIT_RESPONSE, установка `peer_node_id`, инициализация `secure_channel` (X25519 + AES-CCM). +4. **Готовность:** `etcp_conn_ready()` → `etcp_conn_queue_set_ready()` — переиндексирует соединение в очереди `instance->connections` с реальным `peer_node_id`, переводит `state=1`, запускает callback'и `ready_cbks`, таймер метрик. +5. **Отправка данных:** `etcp_int_send(etcp, data, len)` — аллоцирует память из `data_pool`, создаёт `ETCP_FRAGMENT`, помещает в `input_queue`. +6. **Приём данных:** потребитель читает из `etcp->output_queue` — там лежат собранные `ETCP_FRAGMENT` с полезной нагрузкой. +7. **Закрытие:** `etcp_connection_close(etcp)` — 2-фазное: Phase 1 — detach от внешнего мира (линки, таймеры, роутинг), Phase 2 — отложенное освобождение ресурсов (очереди, пулы памяти, callback'и, struct) когда ref_count=0. + +### 2.2. Ключевые концепции + +#### Очереди данных (data flow) + +``` + ┌──────────────┐ + et cp_int_send ──► │ input_queue │ входная очередь (ETCP_FRAGMENT из io_pool) + └──────┬───────┘ + │ input_queue_cb: перенос во inflight с присвоением seq + ┌──────▼───────┐ + │ input_send_q │ очередь на отправку (INFLIGHT_PACKET из inflight_pool) + └──────┬───────┘ + │ etcp_request_pkt: формирует ETCP_DGRAM с секциями ACK+PAYLOAD + ┌──────▼───────┐ + │input_wait_ack│ ожидание ACK (INFLIGHT_PACKET, index по seq) + └──────┬───────┘ + │ etcp_ack_recv: ACK получен → освобождение inflight + [удалён] +``` + +``` +Приём: + ┌──────────┐ + etcp_conn_input ──► │ recv_q │ очередь сборки (ETCP_FRAGMENT, index по seq) + └────┬─────┘ + │ etcp_output_try_assembly: поиск непрерывной последовательности + ┌────▼──────┐ + │output_queue│ собранные пакеты для потребителя + └───────────┘ +``` + +- **`input_queue`** (ETCP_FRAGMENT) — пакеты от приложения к отправке. Callback `input_queue_cb` переносит их в `input_send_q`, создавая `INFLIGHT_PACKET` с seq. +- **`input_send_q`** (INFLIGHT_PACKET, hash-индекс по seq) — ожидающие отправки (новые или ретрансмиссии). Callback `input_send_q_cb` вызывает `etcp_conn_process_send_queue`. +- **`input_wait_ack`** (INFLIGHT_PACKET, hash-индекс по seq) — отправленные пакеты, ожидающие ACK. Callback `wait_ack_cb` запускает retrans-таймер. +- **`recv_q`** (ETCP_FRAGMENT, hash-индекс по seq) — принятые фрагменты, ждущие сборки. +- **`output_queue`** — собранные непрерывные пакеты для потребителя. +- **`ack_q`** (ACK_PACKET, hash-индекс по seq) — неотправленные ACK подтверждения, ожидающие piggyback. +- **`transit_queues`** — транзитные очереди для роутинга (хеш по `src_node_id:dst_node_id`). + +#### Ретрансмиссия с экспоненциальным backoff + +- Таймаут ретрансмиссии: `timeout = (rtt_avg_10 * K1 + jitter * K2) / 16`, где `K1=32`, `K2=32`. +- Минимальный timeout: 50 tb (5ms), максимальный: 10000 tb (1000ms). +- Пакеты в `input_wait_ack` упорядочены по времени отправки (FIFO). Проверка (`ack_timeout_check`) идёт с головы: как только найден пакет с неистекшим таймаутом — взводится таймер до его истечения, дальше не сканируем. +- Ретрансмит: перенос из `input_wait_ack` в `input_send_q`, инкремент `send_count`, добавление в `send_hist`. +- При ретрансмите старый линк (`last_link`) получает `total_retransmissions++` и вычитание из `inflight_bytes/packets`. + +#### RTT/Jitter/Bandwidth метрики + +- **RTT:** измеряется через секцию `ETCP_SECTION_TIMESTAMP` — результирующий `rtt_last` на соединение берётся как среднее по всем линкам (rtt_sum/cnt), `rtt_avg_10` — максимум RTT по линкам. +- **Jitter:** per-link экспоненциальное сглаживание разности RTT: `jitter += (|new_rtt - prev_rtt| * 65536 - jitter) / 32`. На соединении — среднее по линкам. +- **TT (transmission time):** время доставки отправленных пакетов, вычисляется из timestamp-секции. +- **RT (recv time):** время доставки принятых пакетов. +- **Bandwidth:** per-link, обновляется из BBR pacing rate и burst-измерений. + +#### Burst bandwidth measurement + +- Отправитель формирует пачку из `BURST_PACKET_COUNT` (12) пакетов, каждый содержит секцию `MEAS_TS` (type=0x07) с burst_id, flags, порядковым номером и µs-таймстемпом. +- Флаги: `IS_FIRST`, `IS_LAST`, `IS_FILLER` (если нет данных — пакет заполняется FILLER-секцией). +- Пропускаются первые `BURST_SKIP_COUNT` (3) пакета для исключения переходных процессов при замере inter-packet gap. +- Приёмник собирает времена прихода, вычисляет min/avg gap между пакетами, отправляет ответ `MEAS_RESP` (type=0x08) с gap_avg, gap_min, pkt_count. +- Bandwidth вычисляется как: `BW = pkt_size * 8000 / gap_min` (Kbps), BDP = `BW * 1000 / 8 * min_rtt_sec`. +- Минимальный интервал между burst: 500ms. Таймаут ожидания ответа: 2s. +- Ответ может быть piggyback'нут в обычный пакет (вне активного burst). + +#### Channel timestamp handling + +- Секция `ETCP_SECTION_TIMESTAMP` (type=0x06, 5 байт): добавляется когда есть новые данные о времени приёма от пира. +- Содержит: ret_ts (текущее время пира на момент получения нашего пакета) и dt (разница local_time - timestamp пакета). +- Позволяет вычислять RTT, jitter, tt, rt на всех линках, а также `recv_dt_avg_rx/tx` — экспоненциально сглаженные оценки односторонних задержек. + +#### 2-phase deferred cleanup + +- **Phase 1** (синхронно в `etcp_connection_close`): detach от внешнего мира — закрытие линков, отмена таймеров, остановка метрик, удаление из роутинга, удаление из очереди `instance->connections`, установка `state=2`. +- **Phase 2** (отложено через `uasync_call_soon`): освобождение очередей, memory_pool'ов, callback-цепочек, struct. Выполняется когда `ref_count == 0`. Если есть внешние ссылки — ждёт `etcp_conn_ref_free()`. +- `ref_count` нужен для безопасного доступа к conn из асинхронных контекстов (коллбэки, роутинг, BGP). + +#### Backpressure (пороговое ожидание) + +- `input_queue` имеет порог 0 (ждёт полного освобождения) — новые пакеты добавляются только когда очередь пуста. +- `etcp_conn_process_send_queue` пробует протолкнуть данные из `input_queue` → `input_send_q` когда `wait_ack_bytes <= optimal_inflight`. +- При ACK вызывается `input_queue_try_resume` — если `wait_ack_bytes <= optimal_inflight` и input_send_q пуста, возобновляет `input_queue_cb`. + +#### Normalizer (фрагментация/сборка) + +- `struct PKTNORM` — per-connection фрагментатор/дефрагментатор. Разбивает большие пакеты на части ≤ `frag_size` (MTU - overhead). +- При reinit/reset несобранные фрагменты возвращаются в normalizer для повторной обработки (`etcp_return_inflight_to_normalizer`). + +### 2.3. State machine + +| Поле | Значения | Описание | +|------|----------|----------| +| `state` | 0=pending, 1=ready, 2=deleted | Внешнее состояние conn | +| `initialized` | 0/1 | Хотя бы один линк прошёл обмен ключами | +| `reset_done` | 0/1 | 0=reinit разрешён, 1=соединение стабильно | +| `tx_state` | DATA_WAIT(1)/LINK_WAIT(2) | DATA_WAIT=можно отправлять, LINK_WAIT=все линки busy | +| `links_up` | 0/1 | Хотя бы один линк в статусе up | +| `got_initial_pkt` | 0/1 | Получен ли первый пакет (seq=1) после reset | +| `routing_exchange_active` | 0-4 | Статус BGP-обмена | + +## 3. API + +### 3.1. Основные структуры + +#### `struct ETCP_CONN` (`etcp.h:140`) + +Главная управляющая структура соединения. Содержит: +- **Состояние:** `state`, `ref_count`, `initialized`, `reset_done`, `links_up`, `tx_state`. +- **Очереди:** `input_queue`, `input_send_q`, `input_wait_ack`, `recv_q`, `output_queue`, `ack_q`, `transit_queues`. +- **Пулы памяти:** `inflight_pool` (для INFLIGHT_PACKET), `io_pool` (для ETCP_FRAGMENT). +- **Линки:** связный список `links`, `last_rr_link` (round-robin для load balancer). +- **Крипто:** `crypto_ctx` (secure_channel), `peer_ed25519_pubkey`. +- **Нормалайзер:** `normalizer` (PKTNORM) — фрагментация/сборка. +- **Метрики:** `rtt_last/avg_10/avg_100`, `jitter`, `tt_last`, окна (unacked_bytes, max_inflight, optimal_inflight). +- **Таймеры:** `retrans_timer`, `ack_resp_timer`. +- **Callback-цепочки:** `ready_cbks`, `up_cbks`, `down_cbks`, `bgp_ready_cbk`. +- **Счётчики:** ACK hit/miss, дубликаты, ретрансмиссии, `debug[8]` для live watch. +- **Метрики качества:** `metrics` (гистограммы RTT/потерь с 10-мин снимками, тотальные счётчики). +- **Идентификация:** `log_name` (формат `XXXX→YYYY [name]`), `name` (из конфига), `session_id`. + +#### `struct INFLIGHT_PACKET` (`etcp.h:108`) + +Пакет в состоянии inflight (отправлен или ждёт отправки). Поля: +- `seq` — sequence number (ID по протоколу). +- `state` — `WAIT_SEND` (в input_send_q) или `WAIT_ACK` (в input_wait_ack). +- `last_link` — последний линк, через который отправлен (для retrans/loss detection). +- `last_timestamp` — время последней отправки (для retrans timeout). +- `send_count` — количество попыток отправки. +- `retrans_req_count` — количество запросов ретрансмиссии. +- `send_hist[8]` — номера линков через которые передавался (кольцевой буфер, send_count — head). +- `delivered_at_send`, `inflight_at_send`, `is_app_limited` — снэпшоты для BBR rate estimation. + +#### `struct ETCP_FRAGMENT` (`etcp.h:123`) + +Фрагмент данных в очередях ввода/вывода. Поля: +- `seq` — sequence number. +- `timestamp` — timestamp пакета (от удалённой стороны при приёме). +- `ll` — `ll_entry` с dgram (указатель на data_pool) и len. + +#### `struct ACK_PACKET` (`etcp.h:129`) + +Неотправленное подтверждение приёма. Поля: +- `seq` — sequence number подтверждаемого пакета. +- `pkt_timestamp` — timestamp подтверждаемого пакета (часы удалённой стороны). +- `recv_timestamp` — локальное время приёма (для вычисления задержки ACK). + +#### `struct etcp_metrics` (`etcp.h:70`) + +Гистограммы и счётчики качества соединения: +- **Рабочие гистограммы:** `rtt_hist[12]`, `loss_hist[11]` — обновляются в реальном времени. +- **Финальные снимки:** `rtt_hist_final[12]`, `loss_hist_final[11]` — копируются раз в 10 мин. +- **Счётчики окна:** `work_samples/sent/rcvd/bytes_sent/bytes_rcvd/lost` — сбрасываются при снимке. +- **Тотальные:** `total_sent/rcvd/bytes_sent/bytes_rcvd/lost` — кумулятивные счётчики. +- **RTT-бакеты:** 0-1, 1-2, 2-3, 3-5, 5-8, 8-13, 13-21, 21-34, 34-55, 55-90, 90-150, 150+ ms (в 0.1ms). +- **Loss-бакеты:** 0%, 1%, ..., 10%+. +- Интервал снимка: 10 мин. Минимум пакетов для обновления: 200. + +### 3.2. Основные функции + +#### Жизненный цикл + +| Функция | Описание | +|---------|----------| +| `etcp_connection_create(instance, name)` | Создаёт ETCP_CONN, очереди, пулы, нормалайзер, помещает в `instance->connections` с key=0 (pending). | +| `etcp_connection_close(etcp)` | Phase 1: detach (линки, таймеры, роутинг). Phase 2: deferred free когда ref_count=0. | +| `etcp_conn_ref_take(conn)` | Увеличивает ref_count. Возвращает -1 если conn удалён. | +| `etcp_conn_ref_free(conn)` | Уменьшает ref_count. Если 0 и state==2 — планирует Phase 2. | +| `etcp_conn_reset(etcp)` | Сброс состояния после reinit: seq, метрики, очистка очередей, возврат данных в нормалайзер. | +| `etcp_conn_reinit(etcp)` | Полный reinit: сброс флагов, сброс NAT, сброс роутера, вызов `etcp_conn_reset`. | +| `etcp_links_reset(etcp)` | Сброс флага initialized у всех линков (после сбоя). | +| `etcp_conn_ready(conn)` | Вызывается когда первый линк проинициализирован. Устанавливает `initialized=1`, `reset_done=1`, запускает `conn_queue_set_ready`. | +| `etcp_conn_queue_set_ready(conn)` | Переиндексирует соединение в `instance->connections` с реальным `peer_node_id`, state=1, запускает ready/up callback'и, стартует таймер метрик. | +| `etcp_conn_set_peer_node_id(conn, id)` | Устанавливает peer_node_id. Если state=1 — переиндексирует в очереди. | +| `etcp_conn_on_inflight_lim_changed(etcp)` | Пересчитывает `optimal_inflight` как сумму `inflight_lim_bytes` всех линков. | +| `etcp_update_mtu(etcp)` | Пересчитывает MTU соединения как минимум MTU всех линков. Обновляет `frag_size` нормалайзера. | + +#### Отправка + +| Функция | Описание | +|---------|----------| +| `etcp_int_send(etcp, data, len)` | Отправка данных через ETCP: аллокация из data_pool, создание ETCP_FRAGMENT, помещение в input_queue. | +| `etcp_request_pkt(etcp)` | Формирует ETCP_DGRAM для отправки: выбирает линк через load balancer, берёт INFLIGHT_PACKET из input_send_q, добавляет ACK-секцию, опциональные секции (MEAS_TS, MEAS_RESP, TIMESTAMP, FILLER), payload. Перемещает inflight в wait_ack. | +| `etcp_encrypt_send(dgram)` | (etcp_connections.c) Шифрует ETCP_DGRAM (AES-CCM), добавляет nonce/tag/crc32/pubkey, отправляет через udp_send. | + +#### Приём + +| Функция | Описание | +|---------|----------| +| `etcp_conn_input(pkt)` | Обработка расшифрованного пакета: разбор секций (ACK, TIMESTAMP, PAYLOAD, MEAS_TS, MEAS_RESP, FILLER, METRICS), вызов `etcp_ack_recv`, добавление данных в `recv_q`/`ack_q`. | +| `etcp_ack_recv(etcp, seq, ts, dts)` | Обработка ACK: поиск пакета в `input_wait_ack`/`input_send_q`, вычитание inflight с линка, BBR rate estimation, освобождение памяти. | +| `etcp_output_try_assembly(etcp)` | Сборка выходной очереди: ищет непрерывную последовательность от `last_delivered_id+1` в `recv_q`, переносит в `output_queue`. | +| `etcp_stats(etcp)` | Вывод статистики в debug-лог: размеры очередей, RTT, счётчики, ID. | + +#### Callback'и / События + +| Функция | Описание | +|---------|----------| +| `etcp_on_link_down(etcp)` | Вызывается при падении линка. Проверяет все линки, если все down — вызывает `etcp_on_down`. | +| `etcp_on_up(etcp)` | Запуск цепочки `up_cbks`. | +| `etcp_on_down(etcp)` | Запуск цепочки `down_cbks`. | + +#### Метрики качества + +| Функция | Описание | +|---------|----------| +| `etcp_metrics_init(m)` | Обнуление структуры метрик. | +| `etcp_metrics_add_rtt(etcp, rtt_tb)` | Добавление RTT-замера в гистограмму. | +| `etcp_metrics_add_sent(etcp, len)` | Учёт отправленного пакета (working + total). | +| `etcp_metrics_add_rcvd(etcp, len)` | Учёт принятого пакета. | +| `etcp_metrics_add_loss(etcp, count)` | Учёт потерь. | +| `etcp_metrics_start_timer(etcp)` | Запуск 10-минутного таймера снимков. | +| `etcp_metrics_stop_timer(etcp)` | Остановка таймера метрик. | + +### 3.3. Константы + +| Константа | Значение | Описание | +|-----------|----------|----------| +| `MAX_INFLIGHT_BYTES` | 65536 | Начальное окно | +| `RETRANS_K1` | 32 | Множитель RTT для retrans timeout | +| `RETRANS_K2` | 32 | Множитель jitter для retrans timeout | +| `ACK_DELAY_TB` | 20 | Задержка отправки ACK (2ms) | +| `RTT_HISTORY_SIZE` | 10 | Размер истории RTT для jitter | +| `INFLIGHT_INITIAL_HASH_SIZE` | 1024 | Начальный размер хеш-таблиц | +| `MAX_INFLIGHT_SIZE` | 16384 | Максимум элементов в recv_q (защита от атак) | +| `ASM_BUF_MAX_SIZE` | 65536 | Максимальный размер буфера сборки | +| `BURST_PACKET_COUNT` | 12 | Пакетов в burst | +| `BURST_SKIP_COUNT` | 3 | Пропускаемых пакетов при замере gap | +| `MIN_BURST_INTERVAL_TB` | 5000 | Мин. интервал между burst (500ms) | +| `BURST_RESP_TIMEOUT_TB` | 20000 | Таймаут ответа на burst (2s) | +| `ETCP_METRICS_RTT_BUCKETS` | 12 | Число RTT-бакетов в гистограмме | +| `ETCP_METRICS_LOSS_BUCKETS` | 11 | Число loss-бакетов | +| `ETCP_METRICS_INTERVAL_TB` | 6000000 | Интервал снимков (10 мин) | +| `ETCP_METRICS_MIN_PACKETS` | 200 | Мин. пакетов для снимка | + +### 3.4. Секции пакета + +| Секция | Type | Размер | Описание | +|--------|------|--------|----------| +| `PAYLOAD` | 0x00 | 5 + data | Данные: type(1) + seq(4) + payload | +| `ACK` | 0x01 | 8 + N*8 | ACK: type(1) + count(1) + last_delivered_id(4) + rx_dup_count(2) + N×{seq(4) + recv_ts(2) + delay(2)} | +| `TIMESTAMP` | 0x06 | 5 | Channel timestamp: type(1) + ret_ts(2) + dt(2) | +| `MEAS_TS` | 0x07 | 9 | Burst measurement: type(1) + burst_id(2) + flags(1) + seq(1) + ts_us(2) + pkt_sz(2) | +| `MEAS_RESP` | 0x08 | 13 | Burst response: type(1) + burst_id(2) + valid(1) + gap_avg(4) + gap_min(4) + pkt_count(1) | +| `FILLER` | 0x09 | 3 + N | Filler padding: type(1) + len(2) + zeros(N) | +| `METRICS` | 0x0A | 2 + 64 + N | Metrics CSV: type(1) + csv_len(2) + ed25519_sig(64) + csv_data(N) | + +### 3.5. Зависимости + +| Модуль | Как используется | +|--------|-----------------| +| `etcp_connections.h` | ETCP_LINK, ETCP_DGRAM, ETCP_SOCKET, etcp_link_new/close, etcp_encrypt_send | +| `etcp_loadbalancer.h` | etcp_loadbalancer_select_link, etcp_loadbalancer_send | +| `etcp_router.h` | etcp_router_transit_queues_destroy, etcp_router_pause_retrans_for_node | +| `etcp_debug.h` | etcp_dump_pkt_sections | +| `etcp_connect.h` | etcp_connect_cancel_for_conn | +| `pkt_normalizer.h` | PKTNORM (pn_init, pn_deinit, pn_reset) | +| `secure_channel.h` | sc_context_t для шифрования | +| `routing.h` | routing_del_conn | +| `topo_group.h` | route_ping_cancel_for_conn | +| `../lib/ll_queue.h` | Все очереди | +| `../lib/u_async.h` | uasync_set_timeout, uasync_call_soon | +| `../lib/memory_pool.h` | memory_pool_alloc/free/init/destroy | +| `../lib/mem.h` | u_malloc, u_calloc, u_realloc, u_free, u_strdup | +| `../lib/debug_config.h` | DEBUG_ERROR/WARN/INFO/DEBUG/TRACE | diff --git a/src/etcp_dump_doc.md b/src/etcp_dump_doc.md new file mode 100644 index 00000000..a8827f49 --- /dev/null +++ b/src/etcp_dump_doc.md @@ -0,0 +1,59 @@ +# ETCP Connection State Dump (etcp_dump) + +## 1. Назначение +Диагностический дамп полного состояния ETCP-соединения в stdout. Охватывает: общие параметры, очереди, пулы памяти, normalizer, все линки с детализацией (BBR, shaper, burst, ошибки, таймеры, NAT). Используется для отладки проблем с соединениями — удобно вызывать из control-сервера или по сигналу. + +## 2. Как пользоваться + +```c +#include "etcp_dump.h" + +// Дамп одного соединения: +etcp_dump_conn_state(conn); + +// Дамп всех соединений инстанса: +etcp_dump_all_conns(instance); +``` + +Вывод идёт в `stdout` через `fprintf`, формат структурированный с отступами. Пример фрагмента: +``` +=== ETCP CONN STATE [myconn] === + GENERAL: ... peer=0x... initialized=1 links_up=2 tx_state=0 ... + IDS: next_tx=1050 last_rx=1048 last_del=1040 rx_ack_till=1045 + RTT: last=15 avg10=14 avg100=16 jitter=2 + STATS: bytes_sent=123456 retrans=3 reinit=0 reset=0 ack_pkts=200 + INFLIGHT: unacked=8192 optimal=65536 + ACK_DEBUG: hit_inf=500 hit_sndq=10 miss=0 link_wait=0 rx_dup=0 tx_dup=0 + TIMERS: retrans=ACTIVE ack_resp=free + --- QUEUES --- + input 5 pkts / 4096 bytes + input_send_q 2 pkts / 2048 bytes + ... + --- NORMALIZER --- + frag=1400 data_ptr=0/0 flush=free pending=free ... + --- POOLS --- + inflight alloc=100 reuse=50 + ... + --- LINK 0 --- + BASIC: id=0/0 is_server=0 link_state=3 link_status=1 ... + BBR: bytes=4096 pkts=2 lim=65536 mode=2 cycle=2 pacing=12500000 + RTT: last=12 jitter=3 min_rtt_us=5000 bw_lo=... bw_hi=... inflight_lo=... inflight_hi=... + ... + --- LINK 1 --- + ... +``` + +## 3. API + +**`void etcp_dump_conn_state(struct ETCP_CONN* conn)`** — полный дамп одного соединения. Выводит секции: GENERAL, IDS, RTT, STATS, INFLIGHT, ACK_DEBUG, DEBUG, TIMERS, QUEUES (6 очередей), NORMALIZER (pkt_normalizer + его очереди), POOLS (inflight_pool, io_pool, instance->data/ack/pkt_pool), LINKS (в цикле по связанному списку link->next, для каждого: BASIC, ADDR, KA, TIMERS, BBR, RTT, TT/RT, MTU, ERRORS, NAT, LAST_RECV, HANDSHAKE, BURST, SHAPER). + +**`void etcp_dump_all_conns(struct UTUN_INSTANCE* instance)`** — итерация по `instance->connections` (через `ll_entry`), вызывает `etcp_dump_conn_state` для каждого активного соединения. + +### Зависимости +- `etcp.h` — `struct ETCP_CONN`, поля состояния, очереди, normalizer +- `etcp_connections.h` — `struct ETCP_LINK` +- `pkt_normalizer.h` — `struct PKTNORM` +- `lib/ll_queue.h` — `queue_entry_count()`, `queue_total_bytes()` +- `lib/memory_pool.h` — `memory_pool_get_stats()` +- `lib/socket_compat.h` — `AF_INET`, `sockaddr_in`, `ntohs` +- `utun_instance.h` — `struct UTUN_INSTANCE` diff --git a/src/etcp_loadbalancer_doc.md b/src/etcp_loadbalancer_doc.md new file mode 100644 index 00000000..db033bb1 --- /dev/null +++ b/src/etcp_loadbalancer_doc.md @@ -0,0 +1,51 @@ +# ETCP Load Balancer (etcp_loadbalancer) + +## 1. Назначение +Балансировка исходящего трафика ETCP между несколькими линками (UDP-сокетами) внутри одного ETCP_CONN. Выбирает оптимальный линк по минимальному `inflight_bytes`, при равенстве — round-robin. Для каждого линка работает traffic shaper — ограничение полосы пропускания (bandwidth pacing) с таймерной задержкой и burst-режимом. + +## 2. Как пользоваться +Типовой сценарий: перед отправкой пакета вызывается `etcp_loadbalancer_send(dgram)`. Функция сама выбирает линк (если он ещё не задан), отправляет и обновляет shaper-нагрузку линка. + +```c +// Отправка пакета через loadbalancer +struct ETCP_DGRAM* dgram = memory_pool_alloc(inst->pkt_pool); +// ... заполнение dgram ... +etcp_loadbalancer_send(dgram); // сам выберет линк, зашифрует, отправит, освободит dgram +``` + +Если все линки заняты (shaper-таймер или inflight превышает лимит), пакет дропается. При освобождении линка shaper-таймер вызывает `loadbalancer_link_ready()`, который нотифицирует `ETCP_CONN->link_ready_for_send_fn`. + +```c +// Проверка статуса связи +if (etcp_loadbalancer_get_link_status(etcp)) { + // есть хотя бы один живой линк +} +``` + +**Нюансы:** +- Алгоритм выбора: min `inflight_bytes`, среди равных — round-robin (поле `last_rr_link` в ETCP_CONN) +- Shaper работает через `shaper_load_time_tb` / `shaper_sub_nanotime` — виртуальное время передачи, сравнивается с `now_tb + SHAPER_BURST_DELAY_TB` (1ms) +- Burst-пакеты (`link->burst_active`) обходят shaper +- При безлимитном bandwidth (`link->bandwidth == 0`) shaper пропускается +- `etcp_loadbalancer_send()` освобождает dgram через `memory_pool_free()` — после вызова dgram недействителен + +## 3. API + +### Структуры +Поля ETCP_LINK, используемые балансировщиком: +- `initialized` — линк готов к работе +- `link_status` — 1 = линк жив +- `inflight_bytes` — байт в полёте (минимизируемый критерий) +- `burst_active` — 1 = burst bypass всех ограничений +- `shaper_timer` — активный таймер задержки shaper'а +- `bandwidth` — Kbps, 0 = безлимит +- `shaper_load_time_tb`, `shaper_sub_nanotime` — аккумулированное время передачи (0.1ms timebase) + +### Функции +| Функция | Описание | +|---|---| +| `etcp_loadbalancer_select_link(etcp)` | Выбрать линк по min inflight_bytes с round-robin. NULL если все заняты | +| `etcp_loadbalancer_send(dgram)` | Выбрать линк → зашифровать/отправить → обновить shaper → освободить dgram | +| `loadbalancer_link_can_send(link)` | 1 если линк не заблокирован shaper'ом и burst не активен | +| `loadbalancer_link_ready(link)` | Уведомить ETCP_CONN о готовности линка (вызывает `link_ready_for_send_fn`) | +| `etcp_loadbalancer_get_link_status(etcp)` | 1 = есть живой линк, 0 = все недоступны | diff --git a/src/etcp_router_doc.md b/src/etcp_router_doc.md new file mode 100644 index 00000000..e63f5101 --- /dev/null +++ b/src/etcp_router_doc.md @@ -0,0 +1,131 @@ +# ETCP Router (etcp_router) + +## 1. Назначение +Сервисный слой маршрутизации поверх ETCP — упрощённый TCP с восстановлением порядка доставки, дедупликацией и контролем inflight. Мультиплексирует до 256 сервисов (`svc_id`) на одном ETCP-соединении. Поддерживает transit (многошаговую маршрутизацию через промежуточные узлы), ретрансмиты, minRTT-измерения и подписи Ed25519. + +## 2. Как пользоваться + +### Инициализация +```c +// В utun_instance_init(): +etcp_router_init(inst); + +// Зарегистрировать обработчик сервиса (svc_id 0..255) +etcp_router_bind(inst, svc_id, my_service_callback); +``` + +### Отправка +```c +// Простой способ: сформировать ll_entry с svc_id в первом байте +struct ll_entry* e = queue_entry_new(0); +e->dgram = u_malloc(1 + payload_len); +e->dgram[0] = svc_id; +memcpy(e->dgram + 1, payload, payload_len); +e->len = 1 + payload_len; +etcp_route_send(inst, dst_node_id, e, force); + +// Или через существующий ROUTER_CONN: +struct ETCP_ROUTER_CONN* rconn = etcp_router_conn_get(inst, remote_node_id, svc_id); +etcp_router_conn_send(rconn, data, len); +``` + +### Приём +```c +void my_service_callback(struct ETCP_CONN* conn, struct ll_entry* entry) { + if (!entry) { /* соединение закрыто */ return; } + uint8_t svc_id = entry->dgram[0]; + uint64_t from = *(uint64_t*)(entry->dgram + 1); // remote_node_id + // ... данные начиная с entry->dgram[9] ... + queue_dgram_free(entry); + queue_entry_free(entry); +} +``` + +### Backpressure +```c +// На send_q (inflight переполнен): +struct queue_waiter_handle h; +etcp_router_on_send_ready(inst, node_id, svc_id, &h, my_ready_cb, my_arg); +// Когда send_q освободится — вызовется my_ready_cb + +// На normalizer->input (сетевая очередь): +etcp_router_waiter_register(inst, node_id, &h, my_ready_cb, my_arg); +``` + +### Подписанные сообщения +```c +etcp_router_conn_send_signed(rconn, data, len); // добавляет Ed25519-подпись +``` + +## 3. API + +### Ключевые структуры + +**SVC_ROUTE_HDR** (25 байт) — заголовок пакета: +`cmd(1) + dst_node_id(8) + src_node_id(8) + seq(4) + svc_id(1) + flags(1) + timestamp(2)` + +Флаги: `ROUTER_FLAG_START` (0x80), `ROUTER_FLAG_RST` (0x40), `ROUTER_FLAG_SIGNED` (0x08), `ROUTER_FLAG_CLOSE` (0x02). Sess_id в bits 5-4. + +**ETCP_ROUTER_CONN** — состояние логического подключения (remote_node_id + svc_id): +- `tx_seq`, `rx_seq`, `tx_acked` — seq-нумерация для порядка и inflight +- `recv_q` — reorder-очередь с хеш-индексом по seq (восстановление порядка) +- `send_q` — очередь ожидания при полном inflight (backpressure) +- `inflight_q` — копии отправленных пакетов для ретрансмита (хеш по seq) +- `incoming_q` — FIFO между сетевым приёмом и recv_q (защита от гонок) +- `rtt`, `rtt_jitter`, `minrtt` — измерения задержки +- `inflight_limit` — текущий лимит пакетов в полёте (minrtt_probe снижает до 4) +- `no_ack_count` — последовательные ретрансмиты без ACK (при 17 — закрытие) +- `start_sent`, `peer_sync_done` — синхронизация после (пере)подключения +- `closed` — флаг закрытия (игнорирование таймеров) + +**ROUTER_INFLIGHT** — запись в inflight_q: `seq, last_sent_tb, send_count, payload*` + +**TRANSIT_QUEUE** — per (src,dst) пара на промежуточном узле: +- `q` — FIFO транзитных пакетов +- `waiter` — backpressure на send_input_q next_hop'а +- `conn` — ETCP_CONN следующего шага + +### Внутренняя архитектура +``` +Приём: сеть → incoming_q (FIFO) → router_incoming_q_cb → recv_q (хеш по seq) + ↓ + router_try_assembly → deliver (rx_seq++) + +Отправка: router_enqueue_send → send_q (FIFO, backpressure) + router_drain_send_q → router_send_one → etcp_send + inflight_q (копия) + ↓ + router_track_inflight_state + retrans_schedule +ACK: периодический (10ms) + idle (500ms) → router_send_ack(rx_seq) +``` + +### Константы +| Константа | Значение | Описание | +|---|---|---| +| `ROUTER_MAX_INFLIGHT` | 256 | Макс. пакетов в полёте | +| `ROUTER_ACK_INTERVAL_TB` | 100 | ACK интервал 10ms (0.1ms timebase) | +| `ROUTER_ACK_IDLE_TB` | 5000 | Idle таймаут 500ms | +| `ROUTER_RETRANS_TIMEOUT_TB` | 3000 | Таймаут ретрансмита 300ms | +| `ROUTER_NO_ACK_MAX_RETRANS` | 17 | Макс. ретрансмитов без ACK (≈5s) | +| `ROUTER_MAX_SEND_Q_PACKETS` | 64 | Порог backpressure send_q | + +### Функции +| Функция | Описание | +|---|---| +| `etcp_router_init(inst)` | Инициализация: bind ETCP_ID_SVC_ROUTE, создание router_conns | +| `etcp_router_destroy(inst)` | Деинициализация: unbind + close_all + free | +| `etcp_router_bind(inst, svc_id, cb)` | Зарегистрировать обработчик сервиса | +| `etcp_router_unbind(inst, svc_id)` | Удалить обработчик сервиса | +| `etcp_route_send(inst, dst, entry, force)` | Отправить пакет (loopback/transit/direct) | +| `etcp_router_conn_get(inst, remote, svc_id)` | Найти или создать ROUTER_CONN | +| `etcp_router_conn_send(rconn, data, len)` | Отправить данные с авто-seq и inflight-контролем | +| `etcp_router_conn_send_signed(rconn, data, len)` | Отправить с Ed25519-подписью | +| `etcp_router_conn_close(rconn)` | Закрыть seq-подключение (CLOSE + уведомление сервиса) | +| `etcp_router_conn_close_async(rconn, cb, arg)` | Асинхронное закрытие с коллбэком | +| `etcp_router_conn_close_all_for_node(inst, node_id)` | Закрыть все conn к узлу | +| `etcp_router_conn_restart(inst, remote, svc_id)` | Сброс состояния (перезапуск удалённой стороны) | +| `etcp_router_pause_retrans_for_node(inst, node_id)` | Сбросить ретрансмиты без закрытия (для conn reinit) | +| `router_set_max_inflight(rconn, new_max)` | Установить рабочий max_inflight | +| `etcp_router_input_q_count(inst, node_id)` | Размер normalizer->input очереди узла | +| `etcp_router_waiter_register/cancel(inst, node_id, h, cb, arg)` | Backpressure на normalizer->input | +| `etcp_router_on_send_ready/cancel_send_ready(inst, node, svc, h, cb, arg)` | Backpressure на send_q | +| `etcp_router_transit_queues_destroy(conn)` | Удалить все транзитные очереди ETCP_CONN | diff --git a/src/firewall_doc.md b/src/firewall_doc.md new file mode 100644 index 00000000..8841331f --- /dev/null +++ b/src/firewall_doc.md @@ -0,0 +1,28 @@ +# Firewall rule engine + +## 1. Назначение +Фильтр пакетов по IP/порту для входящего/исходящего трафика. Правила загружаются из конфига, сортируются по IP и порту, затем проверка идёт бинарным поиском. Используется для разрешения/запрета прохождения пакетов через туннель. + +## 2. Как пользоваться +```c +struct firewall_ctx fw; +fw_init(&fw); +fw_load_rules(&fw, &global_cfg); +if (fw_check(&fw, ip_host, port)) { /* пакет разрешён */ } +fw_free(&fw); +``` +Правила задаются в конфиге в секции `[firewall]`: +- `rule = ip port` — разрешить IP:port (port=0 — любой порт) +- `bypass_all = 1` — пропускать всё независимо от правил + +Сортировка — по IP, затем по порту. При проверке: бинарный поиск по IP, затем линейный скан соседних записей с тем же IP для сверки порта. Правило с `port=0` означает любой порт для этого IP. + +## 3. API + +| Функция/Структура | Описание | +|---|---| +| `struct firewall_ctx` | Набор правил (`rules`), их количество (`count`), флаг `bypass_all` | +| `fw_init(ctx)` | Обнуление контекста | +| `fw_load_rules(ctx, cfg)` | Копирование правил из конфига, сортировка qsort, установка bypass_all | +| `fw_check(ctx, ip, port)` | Возвращает 1 если пакет разрешён (bypass_all=1 или IP+port есть в правилах), 0 — запрещён | +| `fw_free(ctx)` | Освобождение памяти, обнуление полей | diff --git a/src/lwip_tcp/lwip_pbuf_doc.md b/src/lwip_tcp/lwip_pbuf_doc.md new file mode 100644 index 00000000..8bbe14af --- /dev/null +++ b/src/lwip_tcp/lwip_pbuf_doc.md @@ -0,0 +1,72 @@ +# lwip_pbuf + +## 1. Назначение +Упрощённая реализация pbuf (packet buffer) для встроенного lwIP TCP стека — только PBUF_RAM. + +В отличие от оригинального lwIP (где pbuf может быть RAM/ROM/Pool/Queue), здесь каждый pbuf — это единый блок `u_malloc`, содержащий: `struct pbuf` + зарезервированное место под заголовки (layer-зависимый offset) + полезные данные (payload). Поддерживаются цепочки pbuf через `next`, ref-counted управление памятью, сдвиг payload pointer через `pbuf_header`, цепочечное копирование данных. + +Используется модулями `lwip_tcp`, `tcp_proxy_server`, `tcp_proxy_client` для формирования и разбора TCP-сегментов. + +## 2. Как пользоваться + +Типовой сценарий: +```c +// Создать pbuf с запасом под заголовки +struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, data_len); // offset = 54 байта +// Записать заголовки (сдвиг payload назад в зарезервированную область) +pbuf_header(p, -54); // payload теперь в начале блока +memcpy(p->payload, &tcp_header, 54); +pbuf_header(p, 54); // вернуть payload на данные +// Заполнить данные +memcpy(p->payload, user_data, data_len); +// Прикрепить ещё сегментов +struct pbuf *tail = pbuf_alloc(PBUF_RAW, more_data_len); +memcpy(tail->payload, more_data, more_data_len); +pbuf_chain(p, tail); // p->tot_len = data_len + more_data_len +// Отправить… (потом освободить) +pbuf_free(p); // уменьшает ref, освобождает при ref==0 +``` + +Ключевые концепции: +- **PBUF_RAM-only**: один `u_malloc` на pbuf (структура + зарезервированный offset + данные). Нет пулов, нет ROM-буферов. +- **Ref-counted**: `pbuf_ref()` +1, `pbuf_free()` -1, освобождение при 0. `pbuf_free()` рекурсивно проходит цепочку. +- **pbuf_header(increment)**: сдвиг `payload`-указателя. Положительный increment — сужение данных (потребление заголовка). Отрицательный — расширение в зарезервированную область (создание заголовка). Проверяет границы через доступный offset (разницу между `payload` и `(uint8_t*)p + sizeof(struct pbuf)`). +- **pbuf_header_force()**: то же, но без проверки границ при положительном increment (форсированное переопределение границ). +- **Цепочка (chain)**: связный список через `p->next`. `tot_len` — суммарная длина всей цепочки, `len` — длина текущего сегмента. `pbuf_chain()` добавляет в конец, `pbuf_dechain()` отрезает первый от остальных и корректирует `tot_len`. +- **pbuf_copy_partial()/pbuf_take_at()**: работают с цепочкой прозрачно — пропускают сегменты по смещению, копируют чанками. + +## 3. API + +### Структуры + +| Структура | Описание | +|-----------|----------| +| `struct pbuf` | Элемент пакетного буфера: `next` (цепочка), `payload` (указатель на данные), `len` (длина сегмента), `tot_len` (суммарная длина цепочки), `ref` (счётчик ссылок), `flags`, `type_internal` | +| `PBUF_FLAG_TCP_FIN` | Флаг (0x01) — маркер FIN-сегмента TCP в поле `flags` | + +### Перечисления + +| Enum | Значения | Описание | +|------|----------|----------| +| `enum pbuf_layer` | `PBUF_TRANSPORT=54`, `PBUF_IP=34`, `PBUF_LINK=14`, `PBUF_RAW_TX=54`, `PBUF_RAW=0` | Зарезервированный offset под заголовки перед payload | + +### Функции + +| Функция | Описание | +|---------|----------| +| `pbuf_alloc(layer, length)` | Выделить pbuf с зарезервированным offset (layer) и данными длины length. Возвращает NULL при ошибке. | +| `pbuf_free(p)` | Рекурсивно пройти цепочку, уменьшить ref, освободить `u_free` при ref==0. | +| `pbuf_ref(p)` | Инкремент счётчика ссылок. | +| `pbuf_header(p, increment)` | Сдвинуть `payload` на increment байт. +increment = освободить место под заголовок, -increment = добавить заголовок (негативный сдвиг). Проверяет границы выделенной памяти. Возвращает 0 / -1. | +| `pbuf_header_force(p, increment)` | То же, но без проверки границ при положительном increment (форсирование). | +| `pbuf_chain(head, tail)` | Присоединить цепочку tail в конец head (head->tot_len не обновляется автоматически — caller делает это сам). | +| `pbuf_dechain(p)` | Отсоединить первый pbuf от цепочки: обнулить `next`, скорректировать `tot_len = len`. Возвращает остаток цепочки. | +| `pbuf_clen(p)` | Количество сегментов в цепочке. | +| `pbuf_copy_partial(p, buf, len, offset)` | Скопировать до len байт из цепочки p начиная со смещения offset в линейный буфер buf. Возвращает реально скопированное количество. | +| `pbuf_take(p, data, len)` | Записать len байт из data в цепочку p (начиная с offset 0). Обёртка над `pbuf_take_at`. | +| `pbuf_take_at(p, data, len, offset)` | Записать len байт из data в цепочку p начиная со смещения offset. Возвращает 0 при успехе, -1 если данных недостаточно. | +| `pbuf_get_contiguous(p, buf, len, offset)` | Скопировать len байт из цепочки в buf (как `pbuf_copy_partial`, но возвращает buf при успехе или NULL). | + +### Зависимости +- `../../lib/mem.h` — `u_malloc`/`u_free` (wrappers с leak tracking) +- `string.h` — `memcpy` diff --git a/src/lwip_tcp/lwip_tcp_doc.md b/src/lwip_tcp/lwip_tcp_doc.md new file mode 100644 index 00000000..ff20049e --- /dev/null +++ b/src/lwip_tcp/lwip_tcp_doc.md @@ -0,0 +1,461 @@ +# lwip_tcp + +## 1. Назначение + +Встроенный lwIP TCP стек, адаптированный для uTun. Полноценная реализация RFC 793 (TCP) с минимальным набором опций (только MSS), context-based архитектурой, single-threaded исполнением и интеграцией с event loop uasync. Не использует глобальных переменных lwIP — весь контекст изолирован в `struct lwip_tcp_ctx` (один экземпляр на TCP-прокси). + +Модуль состоит из 4 файлов: +- `lwip_tcp.c` — ядро TCP: жизненный цикл, таймеры, выделение PCB, управление состояниями +- `lwip_tcp_out.c` — отправка сегментов: `tcp_write`, `tcp_output`, `tcp_enqueue_flags`, ретрансмиссии, RST, ACK, zero-window probe +- `lwip_tcp_in.c` — приём сегментов: `lwip_tcp_input` → разбор заголовка, маршрутизация по PCB, `tcp_process`/`tcp_receive`, OOSEQ, ACK-обработка +- `lwip_pbuf.c` — упрощённый packet buffer (PBUF_RAM), используемый как хранилище данных сегментов +- `lwip_tcp_opts.h` — жёстко заданные константы конфигурации +- `lwip_tcp_priv.h` — внутренний API: wire-формат TCP заголовка, структура сегмента, макросы событий, функции для `_in` и `_out` + +Ключевые свойства: +- **Context-based:** весь TCP-стек инкапсулирован в `struct lwip_tcp_ctx`, никаких глобальных переменных +- **Single-threaded:** все операции в одном потоке, управляемом через `uasync` +- **uasync-driven:** таймер `tcp_tmr_cb` каждые `tmr_interval_ms` (по умолчанию 62.5ms = TCP_TMR_INTERVAL/4, ускоритель) вызывает `tcp_fasttmr` и каждый нечётный вызов — `tcp_slowtmr` +- **Output callback:** `ctx->output(pbuf, src_ip, dst_ip)` — вызывается когда TCP хочет отправить IP-пакет. Пользователь сам реализует инкапсуляцию в IP-заголовок и передачу +- **Memory pools:** пулы для `tcp_pcb`, `tcp_pcb_listen`, `tcp_seg` (выделение за O(1) из предварительно аллоцированной памяти) +- **Trace ring buffer:** 384 записи для отладки поведения TCP (поддерживает дамп) + +## 2. Как пользоваться + +### Типовой сценарий работы + +``` +lwip_tcp_init(ua, output_fn, arg) — инициализация контекста + └→ tcp_new(ctx) — создание TCP PCB + └→ tcp_bind(pcb, ip, port) — бинд локального адреса/порта + └→ tcp_listen(pcb) — перевод в LISTEN (сервер) + └→ tcp_connect(pcb, ip, port, cb) — исходящее соединение (клиент) + +lwip_tcp_input(ctx, pbuf, src_ip, dst_ip) — подача входящего IP-пакета + → разбор TCP-заголовка, диспетчеризация по активным PCB + +tcp_write(pcb, data, len, flags) — отправка данных + → данные попадают в pcb->unsent (очередь на отправку) + +tcp_output(pcb) — отправка unsent сегментов через output callback + → соблюдение окна перегрузки (cwnd) и окна приёмника (snd_wnd) + → алгоритм Nagle (можно отключить через tcp_nagle_disable) + +tcp_close(pcb) / tcp_abort(pcb) — закрытие соединения +lwip_tcp_destroy(ctx) — полная очистка контекста +``` + +### Таймеры + +Единый таймер `tcp_tmr_cb` (через `uasync_set_timeout`) вызывается каждые `tmr_interval_ms`: +- **Fast timer** (`tcp_fasttmr`): каждый вызов. Delayed ACK (250ms), close-pending retry, повторная подача refused_data +- **Slow timer** (`tcp_slowtmr`): каждый нечётный вызов (каждые 2×tmr_interval_ms = 125ms по умолчанию). Ретрансмиссия (RTO, backoff), persist timer (zero-window probe), таймауты FIN_WAIT_2/SYN_RCVD/LAST_ACK, очистка TIME_WAIT, OOSEQ timeout, poll-события + +`lwip_tcp_set_timer(ctx, interval_ms, rto_min_ms, rto_max_ms)` позволяет настроить интервал и лимиты RTO. + +### Ключевые концепции + +**4 списка PCB в контексте:** +- `bound_pcbs` — привязанные к порту, но без активного соединения (CLOSED после bind) +- `listen_pcbs` — слушающие (LISTEN), тип `tcp_pcb_listen` (облегчённая структура) +- `active_pcbs` — активные соединения (SYN_SENT..LAST_ACK) +- `tw_pcbs` — TIME_WAIT (ожидание 2×MSL) + +**10 TCP состояний (enum tcp_state):** +``` +CLOSED → LISTEN (bind + listen) +CLOSED → SYN_SENT → SYN_RCVD → ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED + → CLOSE_WAIT → LAST_ACK → CLOSED + → CLOSING → TIME_WAIT → CLOSED +``` + +**Send buffer и очереди сегментов:** +- `snd_buf` — максимальный объём данных, который можно принять от приложения через `tcp_write` (16×MSS = 23360 байт) +- `unsent` — очередь сегментов, готовых к отправке (не отправлены) +- `unacked` — отправленные, но ещё не подтверждённые сегменты +- `ooseq` — out-of-order сегменты, ожидающие сборки +- `snd_queuelen` — количество pbuf'ов в unsent (лимит: 64 = 4×SND_BUF/MSS) + +**RTT estimation (Van Jacobson):** +- `rttest` — время отправки сегмента для текущего замера RTT +- `sa`, `sv` — сглаженное среднее RTT и вариация (scaled, /8 и /4) +- `rto` — retransmission timeout (в единицах slow timer = 250ms) +- `rtime` — счётчик тиков с момента последней отправки + +**Алгоритм Nagle:** +- Включён по умолчанию (`TF_NODELAY` не установлен) +- Объединяет маленькие сегменты, если есть неподтверждённые данные +- Отключается: `tcp_nagle_disable(pcb)` → устанавливает `TF_NODELAY` + +**Congestion control (NewReno-like):** +- `cwnd` — congestion window, инициализируется 1 сегментом (slow start) +- `ssthresh` — порог slow start/congestion avoidance +- `dupacks` — счётчик дублирующихся ACK (fast retransmit при 3) +- `TF_INFR` — флаг «в процессе fast recovery» + +**TCP опции:** поддерживается только MSS (Maximum Segment Size). Window Scale, Timestamps, SACK отключены. + +### Приём данных из сети + +Внешний код получает IP-пакет (после IP-заголовка), формирует `struct pbuf*` с TCP-сегментом и вызывает: +```c +lwip_tcp_input(ctx, p, src_ip, dst_ip); +``` +Функция разбирает TCP-заголовок, находит PCB по (src_ip, src_port, dst_ip, dst_port) и передаёт управление `tcp_process()` / `tcp_receive()`. При получении данных вызывается callback `pcb->recv(arg, pcb, pbuf, err)`. + +### Отправка данных в сеть + +TCP вызывает `ctx->output(arg, pbuf, src_ip, dst_ip)` когда нужно отправить IP-пакет. Пользователь реализует: +1. Предварительное выделение места под IP-заголовок (pbuf_header) +2. Заполнение IP-заголовка +3. Расчёт IP checksum (`tcp_ip_checksum`) +4. Отправку через транспортный уровень (ETCP, raw socket и т.п.) + +### Memory pools + +Контекст создаёт 3 memory pool'а: +- `pcb_pool` — `sizeof(tcp_pcb)` элементов для активных соединений +- `pcb_listen_pool` — `sizeof(tcp_pcb_listen)` для слушающих PCB +- `seg_pool` — `sizeof(tcp_seg)` для сегментов + +При нехватке PCB в пуле (`tcp_alloc`) последовательно пытается освободить память: +1. Обработать pending close (`tcp_handle_closepend`) +2. Убить самый старый TIME_WAIT PCB (`tcp_kill_timewait`) +3. Убить LAST_ACK PCB (`tcp_kill_state`) +4. Убить CLOSING PCB (`tcp_kill_state`) +5. Убить наименее приоритетный активный PCB (`tcp_kill_prio`) + +### Callbacks (на PCB) + +| Callback | Тип | Когда вызывается | +|----------|-----|-----------------| +| `recv` | `tcp_recv_fn(arg, pcb, pbuf, err)` | Получены данные (pbuf ≠ NULL) или соединение закрыто (pbuf == NULL, err == LERR_OK) | +| `sent` | `tcp_sent_fn(arg, pcb, len)` | Данные подтверждены, освободилось место в snd_buf | +| `connected` | `tcp_connected_fn(arg, pcb, err)` | Завершено трёхстороннее рукопожатие (клиент) | +| `accept` | `tcp_accept_fn(arg, new_pcb, err)` | Входящее соединение (на listen_pcb) | +| `err` | `tcp_err_fn(arg, err)` | Асинхронная ошибка (RST, abort) | +| `poll` | `tcp_poll_fn(arg, pcb)` | Периодический опрос (интервал задаётся при `tcp_poll`) | + +### Диагностика + +- **Trace ring buffer**: `lwip_tcp_trace_record()` записывает кольцевые события (384 записи). `lwip_tcp_trace_dump()` выводит в консоль. Типы событий: 'S' (send), 'A' (ack), 'T' (timeout), 'R' (rexmit), 'F' (fast rexmit), 'K' (kill), 'X' (segment), 'D' (drop), 'L' (loss), 'P' (probe), 'M' (tmr), 'C' (close) +- **Stats**: счётчики ошибок выделения памяти (`pbuf_fails`, `seg_fails`, `pcb_fails`, `write_fails`, `enq_fails`, `rst_fails`, `ack_fails`, `probe_fails`, `split_fails`). `lwip_tcp_stats_dump()` / `lwip_tcp_stats_clear()` +- **Глобальный hook**: `g_tcp_trace` — функция обратного вызова для внешнего мониторинга (используется в тестах) + +## 3. API + +### Жизненный цикл контекста + +| Функция | Описание | +|---------|----------| +| `lwip_tcp_init(ua, output, output_arg)` | Создать TCP-контекст: пулы памяти (pcb, pcb_listen, seg), запустить таймер. Возвращает `lwip_tcp_ctx*` или NULL при ошибке | +| `lwip_tcp_destroy(ctx)` | Уничтожить контекст: остановить таймер, освободить все PCB (через abort/close), уничтожить пулы | +| `lwip_tcp_set_timer(ctx, interval_ms, rto_min_ms, rto_max_ms)` | Перенастроить интервал таймера (мс) и границы RTO. rto_max_ms=0 — без ограничения | + +### Входной трафик + +| Функция | Описание | +|---------|----------| +| `lwip_tcp_input(ctx, p, src_ip, dst_ip)` | Подать входящий IP-пакет (после IP-заголовка). pbuf освобождается внутри. src_ip/dst_ip — host byte order | + +### Управление PCB + +| Функция | Описание | +|---------|----------| +| `tcp_new(ctx)` | Создать новый TCP PCB с приоритетом NORMAL (64) | +| `tcp_alloc(ctx, prio)` | Создать PCB с заданным приоритетом (1..127). При нехватке памяти агрессивно освобождает TIME_WAIT/LAST_ACK/CLOSING/низкоприоритетные PCB | +| `tcp_bind(pcb, ip, port)` | Привязать локальный адрес/порт. port=0 — автоназначение из диапазона [0xC000..0xFFFF]. Переводит в bound_pcbs | +| `tcp_listen(pcb)` | Перевести CLOSED→LISTEN. Старый PCB освобождается, создаётся `tcp_pcb_listen` | +| `tcp_connect(pcb, ip, port, connected)` | Инициировать исходящее соединение. Отправляет SYN, переводит в SYN_SENT. Вызывает `connected` при успехе | +| `tcp_close(pcb)` | Активное закрытие: устанавливает TF_RXCLOSED, отправляет FIN (SYN_RCVD/ESTABLISHED→FIN_WAIT_1, CLOSE_WAIT→LAST_ACK). LISTEN: освобождает PCB и потомков | +| `tcp_shutdown(pcb, shut_rx, shut_tx)` | Частичное закрытие. shut_rx: перестать принимать, shut_tx: отправить FIN | +| `tcp_abort(pcb)` | Принудительный разрыв: отправляет RST, освобождает все очереди, вызывает errf | +| `tcp_abandon(pcb, reset)` | Подобен abort, но reset=0 не отправляет RST | +| `tcp_recved(pcb, len)` | Уведомить TCP что приложение обработало len байт, увеличить rcv_wnd. При достаточном росте окна отправляет ACK | + +### Отправка данных + +| Функция | Описание | +|---------|----------| +| `tcp_write(pcb, data, len, apiflags)` | Записать данные в send buffer. apiflags: `TCP_WRITE_FLAG_COPY` (копировать данные), `TCP_WRITE_FLAG_MORE` (будут ещё данные). При ошибке памяти выставляет TF_NAGLEMEMERR | +| `tcp_output(pcb)` | Отправить все готовые сегменты из unsent. Соблюдает cwnd и snd_wnd. При установленном TF_ACK_NOW сначала отправляет ACK. Копирует данные из input_pcb (если в процессе обработки входа) | +| `tcp_send_empty_ack(pcb)` | Отправить чистый ACK (без данных) | +| `tcp_send_fin(pcb)` | Добавить FIN к последнему unsent-сегменту или создать отдельный сегмент | +| `tcp_enqueue_flags(pcb, flags)` | Поставить в очередь сегмент с флагами (SYN/FIN) и MSS-опцией | + +### Ретрансмиссия + +| Функция | Описание | +|---------|----------| +| `tcp_rexmit_rto_prepare(pcb)` | Подготовка к RTO-ретрансмиссии: переместить все unacked→unsent. Выставляет TF_RTO | +| `tcp_rexmit_rto_commit(pcb)` | Завершить RTO-ретрансмиссию: увеличить nrtx, записать trace, вызвать tcp_output | +| `tcp_rexmit_rto(pcb)` | Полный RTO-цикл: prepare + commit | +| `tcp_rexmit(pcb)` | Ретрансмиссия самого старого unacked сегмента (вставить обратно в unsent по порядку seqno) | +| `tcp_rexmit_fast(pcb)` | Fast retransmit (при 3 dupacks): переотправить первый unacked, уменьшить ssthresh/сwnd, выставить TF_INFR | +| `tcp_rexmit_seg(pcb, seg)` | Ретрансмит конкретного сегмента (из ooseq/unacked) | +| `tcp_zero_window_probe(pcb)` | Отправить 1-байтовый probe при нулевом окне приёмника (persist timer) | +| `tcp_split_unsent_seg(pcb, split)` | Разделить первый unsent сегмент на две части (для zero-window probe) | + +### Внутренние (объявлены в priv.h) + +| Функция | Описание | +|---------|----------| +| `tcp_fasttmr(ctx)` | Fast timer: обработка delayed ACK, close-pending, refused_data | +| `tcp_slowtmr(ctx)` | Slow timer: ретрансмиссия (RTO/backoff), persist timer, таймауты FIN_WAIT_2/SYN_RCVD/LAST_ACK, OOSEQ cleanup, poll, TIME_WAIT cleanup | +| `tcp_txnow(ctx)` | Экстренная отправка для PCB с флагом TF_NAGLEMEMERR | +| `tcp_rst(pcb, seqno, ackno, local_ip, remote_ip, local_port, remote_port)` | Отправить RST-сегмент | +| `tcp_process_refused_data(pcb)` | Повторно подать refused_data в callback recv | +| `tcp_pcb_purge(pcb)` | Очистить все очереди сегментов (unsent, unacked, ooseq, refused_data) | +| `tcp_pcb_remove(pcblist, pcb)` | Удалить PCB из списка, очистить, перевести в CLOSED | +| `tcp_seg_copy(seg)` | Копия сегмента с ref на pbuf (stub — используйте `tcp_seg_copy_with_ctx`) | +| `tcp_seg_copy_with_ctx(ctx, seg)` | Копия сегмента с явным контекстом для доступа к seg_pool | +| `tcp_segs_free(seg)` / `tcp_seg_free(seg)` | Освобождение цепочки/одного сегмента (stub — используйте локальные версии) | +| `tcp_free_ooseq(pcb)` | Очистить OOSEQ-очередь | +| `tcp_free(pcb)` | Освободить PCB в соответствующий пул (pcb_pool или pcb_listen_pool) | +| `tcp_next_iss(pcb)` | Сгенерировать Initial Sequence Number | +| `tcp_checksum(data, len)` | Расчёт TCP checksum | +| `tcp_ip_checksum(iph)` | Расчёт IP header checksum | + +### Callback setters + +| Функция | Описание | +|---------|----------| +| `tcp_arg(pcb, arg)` | Установить пользовательский аргумент для callbacks | +| `tcp_recv(pcb, recv)` | Установить callback приёма данных | +| `tcp_sent(pcb, sent)` | Установить callback подтверждения отправки | +| `tcp_err(pcb, errf)` | Установить callback асинхронной ошибки | +| `tcp_accept(pcb, accept)` | Установить callback входящего соединения (только для LISTEN) | +| `tcp_poll(pcb, poll, interval)` | Установить периодический опрос (интервал в единицах slow timer ~500ms) | + +### Макросы-хелперы + +| Макрос | Описание | +|--------|---------| +| `tcp_mss(pcb)` | Текущий MSS соединения | +| `tcp_sndbuf(pcb)` | Доступное место в send buffer | +| `tcp_sndqueuelen(pcb)` | Количество pbuf в очереди отправки | +| `tcp_nagle_disable(pcb)` / `tcp_nagle_enable(pcb)` | Отключить/включить алгоритм Nagle | +| `tcp_nagle_disabled(pcb)` | Проверить, отключён ли Nagle | +| `tcp_set_flags(pcb, f)` / `tcp_clear_flags(pcb, f)` / `tcp_is_flag_set(pcb, f)` | Операции с флагами PCB | + +### Trace и диагностика + +| Функция | Описание | +|---------|----------| +| `lwip_tcp_trace_record(ctx, event, seq, len, wnd, rto, rtime, state)` | Записать событие в ring buffer | +| `lwip_tcp_trace_dump(ctx)` | Вывести последние 50 событий trace в stdout | +| `lwip_tcp_trace_clear(ctx)` | Очистить trace buffer | +| `lwip_tcp_stats_dump(ctx)` | Вывести счётчики ошибок в stdout | +| `lwip_tcp_stats_clear(ctx)` | Сбросить счётчики ошибок | +| `tcp_debug_state_str(s)` | Получить строковое имя состояния | + +## 4. Константы (lwip_tcp_opts.h) + +### Размеры и буферы + +| Константа | Значение | Описание | +|-----------|----------|----------| +| `TCP_MSS` | 1460 | Maximum Segment Size (MTU 1500 − IP(20) − TCP(20)) | +| `TCP_SND_BUF` | `16 * TCP_MSS` = 23360 | Send buffer (макс. данные от приложения) | +| `TCP_WND` | `8 * TCP_MSS` = 11680 | Receive window (объявляемый размер окна) | +| `TCP_SND_QUEUELEN` | `4 * TCP_SND_BUF / TCP_MSS` = 64 | Макс. количество pbuf в unsent | +| `TCP_SNDLOWAT` | `min(2*MSS, SND_BUF/2)` = 2920 | Нижняя граница snd_buf для callback sent | +| `INITIAL_MSS` | 536 | Начальный MSS (RFC 879) до согласования опций | + +### Таймеры и таймауты + +| Константа | Значение | Описание | +|-----------|----------|----------| +| `TCP_TMR_INTERVAL` | 250 ms | Базовый интервал таймера | +| `TCP_FAST_INTERVAL` | 250 ms | Fast timer interval | +| `TCP_SLOW_INTERVAL` | 500 ms (2×base) | Slow timer interval | +| `TCP_RTO_MIN_MS` | 3000 ms | Начальный RTO | +| `TCP_FIN_WAIT_TIMEOUT` | 20000 ms | Таймаут FIN_WAIT_2 (если установлен TF_RXCLOSED) | +| `TCP_SYN_RCVD_TIMEOUT` | 20000 ms | Таймаут SYN_RCVD | +| `TCP_MSL` | 60000 ms | Maximum Segment Lifetime (TIME_WAIT = 2×MSL = 120s) | +| `TCP_OOSEQ_TIMEOUT` | 6 × RTO | Таймаут очистки OOSEQ-очереди | + +### Ретрансмиссия + +| Константа | Значение | Описание | +|-----------|----------|----------| +| `TCP_MAXRTX` | 12 | Макс. количество ретрансмиссий (после — разрыв) | +| `TCP_SYNMAXRTX` | 6 | Макс. SYN-ретрансмиссий | +| `tcp_backoff[]` | `{1,2,3,4,5,6,7,7,7,7,7,7,7}` | Коэффициенты backoff для RTO (2^n, capped at 7) | +| `tcp_persist_backoff[]` | `{3,6,12,24,48,96,120}` | Backoff для persist timer (секунды, затем 2 минуты) | + +### Keepalive (отключён) + +| Константа | Значение | +|-----------|----------| +| `TCP_KEEPIDLE_DEFAULT` | 7200000 ms (2 часа) | +| `TCP_KEEPINTVL_DEFAULT` | 75000 ms | +| `TCP_KEEPCNT_DEFAULT` | 9 | + +### Порты + +| Константа | Значение | Описание | +|-----------|----------|----------| +| `TCP_LOCAL_PORT_RANGE_START` | 0xC000 (49152) | Начало диапазона эфемерных портов | +| `TCP_LOCAL_PORT_RANGE_END` | 0xFFFF (65535) | Конец диапазона | + +### Приоритеты + +| Константа | Значение | +|-----------|----------| +| `TCP_PRIO_MIN` | 1 | +| `TCP_PRIO_NORMAL` | 64 | +| `TCP_PRIO_MAX` | 127 | + +При нехватке PCB в пуле, `tcp_alloc` убивает наименее приоритетные активные соединения. Низкий приоритет = ближе к `TCP_PRIO_MAX`. + +### Отключённые фичи + +| Константа | Значение | Комментарий | +|-----------|----------|-------------| +| `TCP_WND_SCALE` | 0 | Window scale option (не нужен в пределах малых окон) | +| `TCP_TIMESTAMPS` | 0 | TCP timestamps option | +| `TCP_SACK_OUT` | 0 | Selective ACK | +| `TCP_QUEUE_OOSEQ` | 1 | OOSEQ включён | +| `TCP_OVERSIZE` | 0 | Без pre-allocation размера pbuf | +| `TCP_LISTEN_BACKLOG` | 0 | Без backlog (ждём пока приложение заберёт) | +| `TCP_KEEPALIVE` | 0 | Keepalive отключён | +| `TCP_CHECKSUM_ON_COPY` | 0 | Без checksum-on-copy оптимизации | +| `TCP_CALCULATE_EFF_SEND_MSS` | 0 | Без расчёта эффективного MSS | + +## 5. Структуры данных + +### `struct lwip_tcp_ctx` + +Основной контекст TCP-стека (один на инстанс TCP-прокси): + +| Поле | Тип | Назначение | +|------|-----|-----------| +| `ua` | `UASYNC*` | Event loop для таймера | +| `output` | `tcp_output_fn` | Callback отправки IP-пакета | +| `output_arg` | `void*` | Аргумент для output callback | +| `timer` | `void*` | Handle таймера uasync | +| `bound_pcbs` | `tcp_pcb*` | Список PCB в CLOSED после bind | +| `listen_pcbs` | `tcp_pcb*` | Список слушающих PCB (LISTEN) | +| `active_pcbs` | `tcp_pcb*` | Список активных соединений | +| `tw_pcbs` | `tcp_pcb*` | Список TIME_WAIT | +| `pcb_pool` | `memory_pool*` | Пул для `tcp_pcb` | +| `pcb_listen_pool` | `memory_pool*` | Пул для `tcp_pcb_listen` | +| `seg_pool` | `memory_pool*` | Пул для `tcp_seg` | +| `tmr_interval_ms` | `uint16_t` | Интервал таймера (по умолчанию 62.5ms) | +| `rto_min_ms` | `uint16_t` | Минимальный RTO (по умолчанию 3000) | +| `rto_max_ms` | `uint16_t` | Максимальный RTO (0=без ограничений) | +| `ticks` | `uint32_t` | Счётчик slow timer тиков | +| `slowtmr_ctr` | `uint8_t` | Флаг защиты от двойной обработки PCB | +| `tmr_phase` | `uint8_t` | Фаза (чёт-нечет) для чередования slow/fast | +| `iss_seed` | `uint16_t` | Сид для генерации ISN | +| `ip_id` | `uint16_t` | Счётчик IP ID (для IP-заголовка) | +| `port_seed` | `uint16_t` | Сид для автоназначения портов | +| `trace_id` | `char` | Идентификатор контекста для trace ('A'/'B') | +| `trace` | `tcp_trace_buf` | Кольцевой буфер отладки (384 записи) | +| `stats` | struct | Счётчики ошибок (pbuf, seg, pcb, write, enq, rst, ack, probe, split fails) | + +### `struct tcp_pcb` + +Protocol Control Block для активного соединения: + +| Группа | Поля | Назначение | +|--------|------|-----------| +| **Идентификация** | `local_ip/port`, `remote_ip/port`, `ttl`, `tos` | Сетевые адреса | +| **Состояние** | `state`, `prio`, `flags` | TCP состояние, приоритет, флаги (TF_*) | +| **Таймеры** | `polltmr/intervall`, `last_timer`, `tmr`, `rtime` | Poll-опрос, защита от двойного вызова, timestamp, RTO-счётчик | +| **Приём** | `rcv_nxt`, `rcv_wnd`, `rcv_ann_wnd`, `rcv_ann_right_edge` | Следующий ожидаемый seq, окно приёма, объявленное окно и правый край | +| **RTT** | `rttest`, `rtseq`, `sa`, `sv`, `rto`, `nrtx` | Замер RTT, сглаженное среднее/вариация, timeout, счётчик ретрансмиссий | +| **Duplicate ACK** | `dupacks`, `lastack` | Счётчик дублирующих ACK, последний ACK'нутый seq | +| **Congestion** | `cwnd`, `ssthresh`, `rto_end`, `bytes_acked` | Congestion window, порог slow start, конец RTO-окна, подтверждённые байты | +| **Отправка** | `snd_nxt`, `snd_wl1/2`, `snd_lbb`, `snd_wnd/max` | Следующий seqno, окно отправки, last byte buffered, окно приёмника | +| **Буфер** | `snd_buf`, `snd_queuelen` | Доступно для записи, длина очереди | +| **Сегменты** | `unsent`, `unacked`, `ooseq` | Очереди исходящих/подтверждённых/out-of-order сегментов | +| **Данные** | `refused_data` | Отложенные данные (когда приложение не готово) | +| **Callbacks** | `sent`, `recv`, `connected`, `pollfn`, `errf` | Функции обратного вызова | +| **Keepalive** | `keep_idle` | Интервал idle до первого keepalive probe (отключён) | +| **Persist** | `persist_cnt/backoff/probe` | Счётчик, backoff множитель, счётчик probe | +| **Размер** | `mss` | Согласованный MSS (начальный 536) | + +### `struct tcp_pcb_listen` + +Упрощённый PCB для LISTEN (не содержит полей данных/сегментов/таймеров): + +| Поле | Тип | Назначение | +|------|-----|-----------| +| `next` | `tcp_pcb*` | Указатель на следующий в списке | +| `ctx` | `lwip_tcp_ctx*` | Контекст | +| `callback_arg` | `void*` | Пользовательский аргумент | +| `state` | `tcp_state` | Всегда LISTEN | +| `prio` | `uint8_t` | Приоритет | +| `local_port` | `uint16_t` | Локальный порт | +| `local_ip` | `uint32_t` | Локальный IP (0 = ANY) | +| `accept` | `tcp_accept_fn` | Callback входящего соединения | + +### `struct tcp_seg` + +Узел очереди сегментов: + +| Поле | Тип | Назначение | +|------|-----|-----------| +| `next` | `tcp_seg*` | Следующий сегмент в списке | +| `p` | `pbuf*` | Пакетный буфер с TCP-заголовком и данными | +| `len` | `uint16_t` | Длина данных в сегменте | +| `flags` | `uint8_t` | Флаги опций (TF_SEG_OPTS_MSS) | +| `tcphdr` | `tcp_hdr*` | Указатель на TCP-заголовок внутри pbuf | + +Макрос `TCP_TCPLEN(seg)` = `seg->len + (FIN или SYN ? 1 : 0)` — занимаемое место в sequence space. + +### `struct tcp_hdr` + +Wire-формат TCP-заголовка (20 байт, packed): + +| Поле | Размер | Описание | +|------|--------|----------| +| `src` | `uint16_t` | Source port | +| `dest` | `uint16_t` | Destination port | +| `seqno` | `uint32_t` | Sequence number (network byte order) | +| `ackno` | `uint32_t` | Acknowledgement number | +| `_hdrlen_rsvd_flags` | `uint16_t` | Data offset (4 бита), reserved (6 бит), flags (6 бит) | +| `wnd` | `uint16_t` | Window size | +| `chksum` | `uint16_t` | Checksum | +| `urgp` | `uint16_t` | Urgent pointer | + +Макросы для доступа: `TCPH_HDRLEN(hdr)`, `TCPH_FLAGS(hdr)`, `TCPH_HDRLEN_SET(hdr, len)`, `TCPH_FLAGS_SET(hdr, flags)`, `TCPH_SET_FLAG/UNSET_FLAG(hdr, flags)`. + +Флаги: `TCP_FIN`(0x01), `TCP_SYN`(0x02), `TCP_RST`(0x04), `TCP_PSH`(0x08), `TCP_ACK`(0x10), `TCP_URG`(0x20), `TCP_ECE`(0x40), `TCP_CWR`(0x80). + +### `struct tcp_trace_entry` / `struct tcp_trace_buf` + +Кольцевой буфер отладки: 384 записи, каждая содержит ticks, event type, seq, len, wnd, rto, rtime, ctx_id, state. + +## 6. Диагностические события трассировки (trace event types) + +| Код | Значение | Где записывается | +|-----|----------|-----------------| +| `'S'` | Отправка сегмента (seq, len, wnd) | `tcp_output_segment()` в out.c | +| `'X'` | Отправка non-data сегмента (seq, len, cwnd) | `tcp_output_segment()` в out.c | +| `'A'` | Обработка ACK (ack, acked, cwnd) | `tcp_receive()` в in.c | +| `'D'` | Drop (seq, len) — дубликат или старый сегмент | `tcp_receive()` в in.c | +| `'r'` | Состояние RTO-счётчика (rtime, rto, state) | `tcp_receive()` в in.c | +| `'T'` | RTO timeout (rto, rtime) | `tcp_slowtmr()` в tcp.c | +| `'R'` | RTO retransmit commit (seq, len, rto) | `tcp_rexmit_rto_commit()` в out.c | +| `'F'` | Fast retransmit (seq, len, ssthresh) | `tcp_rexmit_fast()` в out.c | +| `'C'` | Close (state) | `tcp_close_shutdown()` в out.c/tcp.c | +| `'L'` | Loss simulation (drop len) | сетевой эмулятор | +| `'M'` | Timer tick | `tcp_tmr_cb()` в tcp.c | +| `'K'` | Kill PCB в slowtmr (state, nrtx, port) | `tcp_slowtmr()` в tcp.c | +| `'Y'` | Kill PCB в tcp_kill_* (seq, nrtx) | `tcp_kill_prio/state/timewait()` в tcp.c | +| `'Z'` | Abandon PCB (state, reset_flag, port) | `tcp_abandon()` в tcp.c, in.c | +| `'E'` | Error (err_code) | обработка ошибок в in.c | +| `'P'` | Zero-window probe (rto) | `tcp_zero_window_probe()` в out.c | + +## 7. Зависимости + +- `lib/mem.h` — `u_malloc`/`u_calloc`/`u_free`/`u_strdup` (для выделения `lwip_tcp_ctx`) +- `lib/debug_config.h` — `DEBUG_ERROR`, `DEBUG_WARN`, `DEBUG_INFO`, `DEBUG_DEBUG`, `DEBUG_TRACE`, категории `DEBUG_CATEGORY_ALL`/`DEBUG_CATEGORY_GENERAL` +- `lib/memory_pool.h` — `memory_pool_init/destroy/alloc/free/is_freed` (пулы PCB, сегментов) +- `lib/u_async.h` — `uasync_set_timeout`/`uasync_cancel_timeout`, `get_time_tb()` +- `lwip_pbuf.h` — `pbuf_alloc/free/ref/header/chain/dechain/clen/copy_partial/take/get_contiguous` +- `` — `memcpy`, `memset` +- `` / `` — `ntohl`/`ntohs`/`htonl`/`htons` (сетевой порядок байт) diff --git a/src/lwip_tcp/lwip_tcp_in_doc.md b/src/lwip_tcp/lwip_tcp_in_doc.md new file mode 100644 index 00000000..687f547e --- /dev/null +++ b/src/lwip_tcp/lwip_tcp_in_doc.md @@ -0,0 +1,104 @@ +# lwip_tcp_in + +## 1. Назначение + +Обработка входящих TCP-сегментов — адаптированный код из lwIP 2.1.x `tcp_in.c` для uTun. Все `#if`-блоки убраны, глобальный IP lwIP заменён явными параметрами `src_ip`/`dst_ip`. Контекст задаётся через `lwip_tcp_ctx`, однопоточное исполнение. + +Модуль реализует: +- Демультиплексирование входящего сегмента к PCB (по quad: src_ip, dst_ip, sport, dport) +- Полный TCP state machine на 11 состояний (SYN_SENT→SYN_RCVD→ESTABLISHED→...→TIME_WAIT) +- Поддержку simultaneous open (SYN_SENT + SYN+ACK), RST, SYN flood в ESTABLISHED +- Обработку ACK: window update, dupack detection/fast retransmit, congestion control (slow start / congestion avoidance), RTT estimation +- Реассемблинг данных: вставка не-по-порядку сегментов в сортированный OOSEQ-список, слияние при заполнении пробелов +- Congestion control: Reno вариант (slow start, congestion avoidance, fast retransmit, fast recovery через TF_INFR), cwnd при dupack >3 инкрементируется, при ack>lastack — выход из fast recovery (сброс cwnd в ssthresh) +- Защиту от вышедших за rcv_wnd данных (обрезание seg.len, обрезание FIN/SYN) +- Обработку delayed close: отложенное закрытие PCB после отправки всех данных + +## 2. Как пользоваться + +Внутренний модуль, главная точка входа — `lwip_tcp_input()` (вызывается из стека lwIP при получении IP-пакета с протоколом TCP). Передаётся контекст `lwip_tcp_ctx`, pbuf, src_ip, dst_ip. + +**Ключевые концепции:** + +- `lwip_tcp_input()` (стр.120): парсинг TCP-заголовка, демультиплексирование по active_pcbs→tw_pcbs→listen_pcbs, вызов `tcp_process()`, отправка RST если нет получателя. Продвигает найденный PCB в начало списка (LRU). + +- `tcp_process()` (стр.452): полный state machine — разбор комбинаций флагов (RST, SYN, ACK, FIN) в соответствии с состоянием PCB. Для каждого состояния своя логика: SYN_SENT ждёт SYN+ACK с корректным ackno, SYN_RCVD ждёт ACK (и обрабатывает retransmit SYN), ESTABLISHED/CLOSE_WAIT вызывают `tcp_receive()`, FIN_WAIT_1/2, CLOSING, LAST_ACK обрабатывают закрытие. + +- `tcp_receive()` (стр.703): основная логика ACK и данных: + - **Window update** (стр.710–720): обновление `snd_wnd`, трекинг `snd_wl1`/`snd_wl2` для защиты от старых ACK + - **Dupack detection** (стр.723–738): dupack — это ACK с ackno == lastack, tcplen==0, и snd_wnd не изменился. При dupack: инкремент счётчика, fast retransmit при >=3, инкремент cwnd при >3 (inflating) + - **ACK новых данных** (стр.739–806): обновление `lastack`, освобождение подтверждённых сегментов из unacked/unsent, congestion control (slow start при cwnd < ssthresh: cwnd += min(acked, 2*MSS) с учётом TF_RTO, congestion avoidance при cwnd >= ssthresh: cwnd += MSS когда bytes_acked >= cwnd), обновление snd_buf, выход из TF_RTO + - **RTT estimation** (стр.798–807): по методу Van Jacobson (sa/sv/rto), только если `rttest` установлен (был измеряемый сегмент) и ackno > rtseq + - **Reassembly** (стр.810–1001): если seqno in-window, но не in-order — создание/вставка в OOSEQ-список с автоматическим слиянием перекрывающихся сегментов и удалением полностью покрытых. Если seqno == rcv_nxt — приём данных, продвижение rcv_nxt, вытаскивание из ooseq примыкающих сегментов (стр.899–922) + - **PU-ACK** (стр.924/997/1000/1005): отправка ACK (delayed или immediately) в ответ на данные или out-of-window пакеты + +- `tcp_listen_input()` (стр.373): обработка входящего SYN для listen PCB — создание нового PCB, установка ISS, регистрация в active_pcbs, разбор опций (MSS), отправка SYN+ACK. + +- `tcp_timewait_input()` (стр.428): обработка пакетов для TIME_WAIT PCB — ответ RST на SYN в окне, продление таймера на FIN, ACK на данные. + +- `tcp_parseopt()` (стр.1030): разбор TCP-опций — только MSS (TCP_OPT_MSS), ограничение до `TCP_MSS` (1460). + +- **OOSEQ** (стр.647–672, 925–994): сортированный список out-of-order сегментов. `tcp_oos_insert_segment()` вставляет новый сегмент, сливая/обрезая соседние при перекрытии. FIN-флаг переносится на поглощающий сегмент. При вставке в середину списка предшествующий сегмент может обрезаться справа. + +- **Congestion control** (Reno): `cwnd` инициализируется как `min(4*MSS, max(2*MSS, 4380))`. Slow start: cwnd += min(acked, 2*MSS). Congestion avoidance: cwnd += MSS когда bytes_acked >= cwnd. Fast retransmit при 3 dupack. Fast recovery: TF_INFR включён, cwnd продолжает расти на MSS за dupack >3; выход при первом новом ACK (сброс cwnd = ssthresh). + +## 3. API + +### Внешние (вызываются извне модуля) + +| Функция | Назначение | +|---|---| +| `lwip_tcp_input(ctx, p, src_ip, dst_ip)` | Главная точка входа: демультиплексирование + state machine + reassembly | +| `tcp_trigger_input_pcb_close()` | Установка флага TF_CLOSED для отложенного закрытия извне | + +### Внутренние (статические, только в этом файле) + +| Функция | Назначение | +|---|---| +| `tcp_process(pcb)` | State machine: обработка RST/SYN, переходы SYN_SENT→ESTABLISHED, SYN_RCVD→ESTABLISHED, FIN-переходы | +| `tcp_receive(pcb)` | ACK-обработка (window update, dupack, congestion control, RTT), приём/реассемблинг данных | +| `tcp_listen_input(lpcb)` | SYN на listen PCB: создание нового PCB, SYN+ACK | +| `tcp_timewait_input(pcb)` | Обработка пакетов в TIME_WAIT | +| `tcp_parseopt(pcb)` | Разбор TCP-опций (MSS) | +| `tcp_oos_insert_segment(cseg, seg)` | Вставка сегмента в OOSEQ с обрезанием/слиянием | +| `tcp_free_acked_segments(pcb, seg_list)` | Освобождение полностью подтверждённых сегментов из списка, накопление `recv_acked` | +| `tcp_input_delayed_close(pcb)` | Отложенное закрытие PCB при TF_CLOSED | +| `tcp_get_next_optbyte()` | Последовательное чтение байтов TCP-опций (из tcphdr+опции или второго pbuf) | + +### Локальные хелперы + +| Функция | Назначение | +|---|---| +| `pbuf_cat_local(h, t)` | Конкатенация двух pbuf — используется при слиянии OOSEQ с recv_data | +| `tcp_seg_copy_local(pcb, seg)` | Копирование tcp_seg через memcpy + pbuf_ref (из seg_pool) | +| `tcp_seg_free_local(pcb, seg)` | Освобождение tcp_seg: pbuf_free + возврат в seg_pool | +| `tcp_segs_free_local(pcb, seg)` | Освобождение цепочки tcp_seg | +| `pbuf_realloc_local(p, new_len)` | Урезание pbuf-цепочки справа до нужной длины | + +### Локальные static-переменные (состояние обработки одного пакета) + +Используются для передачи данных между `lwip_tcp_input()` и вызываемыми функциями: +- `inseg` — текущий обрабатываемый сегмент +- `tcphdr`, `tcphdr_optlen`, `tcphdr_opt1len`, `tcphdr_opt2`, `tcp_optidx` — заголовок и опции +- `seqno`, `ackno` — номера последовательности (host byte order) +- `sport`, `dport` — порты (host byte order) +- `tcplen` — длина данных + SYN/FIN +- `flags` — флаги TCP-заголовка +- `recv_flags` — накопленные флаги обработки (TF_RESET, TF_CLOSED, TF_GOT_FIN) +- `recv_data` — собранные данные для передачи приложению через TCP_EVENT_RECV +- `recv_acked` — сумма подтверждённых байт для TCP_EVENT_SENT +- `tcp_input_pcb` — текущий PCB (для доступа из OOSEQ-функций) +- `tcp_src_ip`, `tcp_dst_ip` — IP-адреса пакета + +## 4. Зависимости + +- `lwip_tcp_priv.h` — заголовок TCP, макросы флагов/последовательностей, tcp_seg, события, таймеры +- `lwip_tcp.h` — PCB, контекст ctx, типы ошибок (LERR_*), состояния +- `lwip_tcp_opts.h` — TCP_MSS, TCP_WND, TCP_TMR_INTERVAL и прочие константы +- `lwip_pbuf.h` — pbuf API +- `lib/mem.h` — u_malloc/u_free +- `lib/debug_config.h` — DEBUG_ERROR/DEBUG_INFO +- `lib/memory_pool.h` — memory_pool_alloc/free для seg_pool +- Внешние функции из `lwip_tcp.c`: `tcp_update_rcv_ann_wnd()`, `tcp_abandon()` +- Внешние из `lwip_tcp_out.c`: `tcp_send_empty_ack()`, `tcp_enqueue_flags()`, `tcp_rst()`, `tcp_rexmit()`, `tcp_rexmit_rto()`, `tcp_rexmit_fast()`, `tcp_ack_now()`, `tcp_output()`, `tcp_alloc()`, `tcp_free()`, `tcp_pcb_purge()`, `tcp_pcb_remove()`, `tcp_process_refused_data()`, `tcp_abort()` +- `lwip_tcp_trace_record()` — запись в кольцевой буфер трассировки diff --git a/src/lwip_tcp/lwip_tcp_opts.h b/src/lwip_tcp/lwip_tcp_opts.h index edd70e4a..1bfdba6c 100644 --- a/src/lwip_tcp/lwip_tcp_opts.h +++ b/src/lwip_tcp/lwip_tcp_opts.h @@ -30,12 +30,10 @@ extern "C" { #define TCP_KEEPCNT_DEFAULT 9 #define TCP_WND_SCALE 0 // disabled -#define TCP_TIMESTAMPS 0 // disabled #define TCP_SACK_OUT 0 // disabled #define TCP_QUEUE_OOSEQ 1 #define TCP_OVERSIZE 0 #define TCP_LISTEN_BACKLOG 0 -#define TCP_KEEPALIVE 0 #define TCP_CHECKSUM_ON_COPY 0 #define TCP_CALCULATE_EFF_SEND_MSS 0 diff --git a/src/lwip_tcp/lwip_tcp_out_doc.md b/src/lwip_tcp/lwip_tcp_out_doc.md new file mode 100644 index 00000000..ed3a44e3 --- /dev/null +++ b/src/lwip_tcp/lwip_tcp_out_doc.md @@ -0,0 +1,167 @@ +# lwip_tcp_out + +## 1. Назначение +Исходящая сторона lwIP TCP стека: сегментация данных в MSS-чанки, планирование передачи, контроль перегрузки, ретрансмиссия и отправка управляющих сегментов (SYN, FIN, RST, ACK, zero-window probe). Адаптирован из lwIP для uTun — убраны timestamps, SACK, WND_SCALE, checksum-on-copy. + +## 2. Как пользоваться +Внутренний модуль — вызывается из `lwip_tcp.c` (таймеры, API), `lwip_tcp_in.c` (fast retransmit, SACK-обработка). + +### Ключевые концепции + +#### tcp_write() — сегментация и постановка в unsent +Пишет данные в выходной буфер PCB. Фазы: +1. **Дописывание в хвост last_unsent**: если последний unsent сегмент < MSS, добавляем цепочку pbuf к нему (copy/zero-copy, extend). +2. **Новые сегменты**: оставшиеся данные режутся на MSS-чанки, каждому создаётся `struct tcp_seg` через `tcp_create_segment`. +3. **Commit**: конкатенация к хвосту `pcb->unsent`, обновление `snd_lbb`, `snd_buf`, `snd_queuelen`. PSH-флаг ставится на последний сегмент, если не задан `TCP_WRITE_FLAG_MORE`. +4. Лимит `snd_queuelen` ≤ `TCP_SND_QUEUELEN` (4 * SND_BUF/MSS = 64). При превышении — LERR_MEM. +5. MSS адаптивен: если `snd_wnd_max` даёт меньшее окно, MSS снижается до `min(MSS, snd_wnd_max/2)`. + +#### tcp_output() — планирование и отправка +Ограничения: +- **cwnd**: `min(cwnd, snd_wnd)` — окно перегрузки +- **Нейгл**: `tcp_do_output_nagle()` — запрещает отправку маленьких сегментов если есть unacked данные и сегмент < MSS (отключается `TF_NODELAY` или при `TF_INFR`). +- **Zero-window persist**: если всё ограничено snd_wnd (а не cwnd) и unacked пуст — взводится `persist_backoff` для последующей отправки zero-window probe. +- **Re-entrancy guard**: `tcp_input_pcb` — если output вызван из input того же PCB, сразу return. +- Отправленные data-сегменты (TCP_TCPLEN > 0) перемещаются из `unsent` в `unacked` с сортировкой по seqno. +- Пустые сегменты (например, чистый ACK) освобождаются после отправки. +- Обновляется `snd_nxt`. + +#### tcp_enqueue_flags() — управляющие сегменты +Создаёт сегмент с заданными флагами (SYN, FIN, RST), ставит MSS-опцию для SYN. Инкрементирует `snd_lbb` для SYN/FIN (потребление номера последовательности). + +#### tcp_send_fin() — завершение соединения +Если есть unsent данные — вешает FIN на хвостовой сегмент. Иначе создаёт новый FIN-сегмент через `tcp_enqueue_flags`. + +#### tcp_split_unsent_seg() — дробление unsent +Отрезает хвост сегмента (например, для retransmit при потере). Копирует remainder в новый pbuf, создаёт новый `tcp_seg`, переносит флаги (PSH/FIN) на остаток. Вставляет после оригинального сегмента. + +#### Ретрансмиссия + +| Функция | Назначение | +|---------|-----------| +| `tcp_rexmit_rto_prepare()` | Переносит весь `unacked` обратно в `unsent`, ставит `TF_RTO`, сбрасывает `rttest` | +| `tcp_rexmit_rto_commit()` | Инкрементирует `nrtx`, дёргает `tcp_output()` | +| `tcp_rexmit_rto()` | prepare + commit — полный RTO retransmit | +| `tcp_rexmit()` | Перемещает 1-й unacked сегмент обратно в unsent (быстрая ретрансмиссия). Инкрементирует `nrtx`, сбрасывает `rttest` | +| `tcp_rexmit_fast()` | При 3 dupack: ретрансмитит 1-й unacked через `tcp_rexmit()`, затем `ssthresh = max(cwnd/2, 2*MSS)`, `cwnd = ssthresh + 3*MSS`, ставит `TF_INFR` | + +#### tcp_zero_window_probe() — зонд при нулевом окне +Отправляет 1 байт данных из первого unsent сегмента (или FIN если сегмент уже пустой FIN). Инкрементирует `persist_probe`, `snd_nxt` сдвигается на 1. + +#### Управляющие сегменты + +| Функция | Назначение | +|---------|-----------| +| `tcp_rst()` | RST+ACK сегмент с указанными seqno/ackno, IP/портами | +| `tcp_send_empty_ack()` | Пустой ACK с `rcv_ann_wnd` — подтверждение приёма | +| `tcp_keepalive()` | Заглушка (keepalive отключён) | + +#### Контрольные суммы +- `tcp_ip_checksum()` — IP-заголовок (20 байт, one's complement) +- `tcp_checksum()` — данные произвольной длины +- `tcp_pseudo_checksum()` — TCP псевдозаголовок (src_ip + dst_ip + proto + tcp_len + данные pbuf) + +#### RTT tracking +`rttest`/`rtseq` взводятся в `tcp_output_segment()` при отправке данных если `rttest == 0` — запоминается время (`ticks`) и seqno. Сбрасывается при ретрансмиссии. Используется в `lwip_tcp_in.c` для обновления SRTT/RTO. + +#### Выходной конвейер +Каждый сегмент, проходящий через `tcp_output_segment()`: +1. Заполняет ackno = `rcv_nxt`, wnd = `rcv_ann_wnd` +2. Записывает MSS-опцию если есть +3. Взводит `rttest`/`rtseq` если нужно +4. Сдвигает pbuf payload на TCP-заголовок +5. Вычисляет контрольную сумму TCP (псевдозаголовок) +6. Добавляет IP-заголовок (IPv4, vhl=0x45, ttl=64, вычисляет IP checksum) +7. Вызывает `ctx->output()` — коллбэк в tcp_proxy_client для отправки IP-пакета через ETCP +8. Снимает IP-заголовок обратно с pbuf + +Проверка `tcp_output_segment_busy()` блокирует отправку если pbuf.ref != 1 (ещё читается в input). + +#### Логирование и трассировка +- `lwip_tcp_trace_record()` — запись в ring-буфер событий: 'S'=send, 'X'=cwnd-limited, 'R'=RTO, 'F'=fast_rtx, 'P'=probe, 'C'=FIN +- `g_tcp_trace` — хук для внешнего наблюдателя (используется в тестах) +- `DEBUG_TRACE(DEBUG_CATEGORY_TRAFFIC, ...)` — детали отправки +- Статистика: `stats.write_fails`, `stats.enq_fails`, `stats.rst_fails`, `stats.ack_fails`, `stats.probe_fails`, `stats.split_fails`, `stats.seg_fails`, `stats.pbuf_fails` + +## 3. API + +### Публичные (объявлены в lwip_tcp.h / lwip_tcp_priv.h) + +```c +err_t tcp_write(struct tcp_pcb *pcb, const void *data, uint16_t len, uint8_t apiflags); +``` +Сегментация данных в unsent очередь. `apiflags`: `TCP_WRITE_FLAG_COPY` (копировать данные, иначе zero-copy), `TCP_WRITE_FLAG_MORE` (не ставить PSH на последний сегмент). Возвращает `LERR_OK` или `LERR_MEM`. + +```c +err_t tcp_output(struct tcp_pcb *pcb); +``` +Отправка unsent сегментов с учётом cwnd/snd_wnd и Нейгла. Перемещает отправленные в unacked. + +```c +err_t tcp_enqueue_flags(struct tcp_pcb *pcb, uint8_t flags); +``` +Постановка управляющего сегмента (SYN/FIN/RST) в unsent очередь. Для SYN добавляет MSS-опцию. + +```c +err_t tcp_send_fin(struct tcp_pcb *pcb); +``` +Добавляет FIN к последнему unsent сегменту или создаёт новый. + +```c +void tcp_rst(struct tcp_pcb *pcb, uint32_t seqno, uint32_t ackno, + uint32_t local_ip, uint32_t remote_ip, + uint16_t local_port, uint16_t remote_port); +``` +Отправка RST+ACK. + +```c +err_t tcp_send_empty_ack(struct tcp_pcb *pcb); +``` +Пустой ACK с текущим `rcv_ann_wnd`. + +```c +err_t tcp_rexmit_rto_prepare(struct tcp_pcb *pcb); +void tcp_rexmit_rto_commit(struct tcp_pcb *pcb); +void tcp_rexmit_rto(struct tcp_pcb *pcb); +``` +RTO ретрансмиссия: prepare (unacked→unsent), commit (nrtx++, tcp_output). `tcp_rexmit_rto()` — одно действие. + +```c +err_t tcp_rexmit(struct tcp_pcb *pcb); +``` +Перемещает первый unacked сегмент в unsent. + +```c +void tcp_rexmit_fast(struct tcp_pcb *pcb); +``` +Быстрая ретрансмиссия при 3 dupack: ssthresh/2, cwnd=ssthresh+3*MSS, tcp_rexmit. + +```c +err_t tcp_zero_window_probe(struct tcp_pcb *pcb); +``` +Отправка 1-байтового зонда из первого unsent сегмента. + +```c +err_t tcp_keepalive(struct tcp_pcb *pcb); +``` +Заглушка (keepalive отключён в opts). + +```c +err_t tcp_split_unsent_seg(struct tcp_pcb *pcb, uint16_t split); +``` +Дробление первого unsent сегмента на части по split байт. + +```c +uint16_t tcp_ip_checksum(const struct ip_hdr *iph); +uint16_t tcp_checksum(const void *data, uint16_t len); +``` +Вычисление контрольных сумм. + +## 4. Зависимости +- `lwip_tcp.h` — типы PCB, pbuf, err_t, коллбэки, enum state +- `lwip_tcp_priv.h` — `struct tcp_seg`, макросы TCPH_*, SEQ, Nagle +- `lwip_tcp_opts.h` — TCP_MSS, TCP_SND_QUEUELEN, TCP_WND etc. +- `lwip_pbuf.h` — `pbuf_alloc`, `pbuf_free`, `pbuf_header`, `pbuf_copy_partial`, `pbuf_take`, `pbuf_clen`, `pbuf_cat` +- `lib/mem.h` — u_malloc/u_free +- `lib/memory_pool.h` — `memory_pool_alloc`/`memory_pool_free` для seg_pool +- `lib/debug_config.h` — DEBUG_ERROR, DEBUG_TRACE, DEBUG_CATEGORY_ALL, DEBUG_CATEGORY_TRAFFIC diff --git a/src/msg_transport_doc.md b/src/msg_transport_doc.md new file mode 100644 index 00000000..0afb322e --- /dev/null +++ b/src/msg_transport_doc.md @@ -0,0 +1,69 @@ +# Msg Transport (msg_transport) + +## 1. Назначение +TCP-сервер для обмена сообщениями между локальными приложениями (например, chatgui) и удалёнными пирами через ETCP. Принимает TCP-соединения от локальных клиентов, привязывает (BIND) их к конкретному `peer_node_id` и пересылает данные туда-обратно через `etcp_router`. Поддерживает подписанные сообщения (Ed25519). + +## 2. Как пользоваться +1. Вызвать `msg_transport_init()` — создаёт слушающий TCP-сокет, регистрирует callback `on_etcp_recv` в `etcp_router_bind()`. +2. Локальное приложение (chatgui) подключается по TCP и шлёт фреймы протокола. +3. При остановке вызвать `msg_transport_shutdown()`. + +**Протокол фрейма:** +``` +[size:2][type:1][id:8][options:1][data...] +``` +- `size` — полный размер (включая поле size), native endian, до 8192 байт. +- `type` — команда (`MSG_CTL_*`, `MSG_DATA_*`, `MSG_RSP_*`). +- `id` — `peer_node_id` (uint64_t). +- `options` — битовые флаги (`MSG_OPT_SIGNED = 0x01`). + +**Нюансы:** +- Приватный ключ (`GET_PRIVKEY`) отдаётся только подключениям с loopback (127.0.0.1 / ::1). +- Каждый `peer_id` может быть забинден только одним клиентом одновременно. +- Сообщения от пира без привязанного клиента молча дропаются (счётчик `drops_no_client`). + +## 3. API + +### Структуры + +| Структура | Назначение | +|-----------|-----------| +| `struct msg_transport` | Состояние сервера: `listen_fd`, связный список `clients`, `svc_id`, счётчики. | +| `struct msg_client` | Состояние TCP-клиента: `fd`, буфер приёма, `bound_peer_id`, флаг `is_localhost`. | +| `struct msg_header` | Заголовок фрейма: `size`, `type`, `id`, `options` (12 байт, packed). | +| `struct msg_transport_counters` | Счётчики: accepted, closed, bind/unbind, msg_sent/recv, errors, drops. | + +### Функции + +| Функция | Назначение | +|---------|-----------| +| `msg_transport_init(out, instance, ua, bind_addr, max_clients, svc_id)` | Создать сервер, привязаться к порту, зарегистрировать `svc_id` в `etcp_router_bind`. | +| `msg_transport_shutdown(t)` | Закрыть всех клиентов, освободить сокет. | +| `msg_transport_get_counters(t, c)` | Скопировать счётчики в переданную структуру. | +| `msg_build_header(hdr, payload_size, msg_type, id, options)` | Заполнить заголовок фрейма (inline). | +| `msg_validate_header(hdr)` | Проверить корректность заголовка (inline). | + +### Типы сообщений + +| Тип | Направление | Описание | +|-----|-------------|----------| +| `MSG_CTL_BIND` (0x01) | GUI→utun | Привязать TCP-соединение к пиру `id`. Ответ: `RSP_BIND_OK`. | +| `MSG_CTL_UNBIND` (0x02) | GUI→utun | Отвязать соединение. | +| `MSG_CTL_GET_PUBKEY` (0x03) | GUI→utun | Запросить свой публичный ключ. Ответ: `RSP_PUBKEY` (32 байта). | +| `MSG_CTL_GET_PRIVKEY` (0x04) | GUI→utun | Запросить приватный ключ (только localhost). Ответ: `RSP_PRIVKEY` (32 байта). | +| `MSG_CTL_GET_PEER_INFO` (0x05) | GUI→utun | Запросить ключи пира `id`. Ответ: `RSP_PEER_INFO` (pubkey + ed25519_pubkey = 64 байта). | +| `MSG_DATA_SEND` (0x10) | GUI→utun | Отправить данные пиру `id` (через `etcp_router_conn_send` / `send_signed`). | +| `MSG_DATA_RECV` (0x11) | utun→GUI | Получены данные от пира `id`. | +| `MSG_RSP_ERROR` (0xFF) | utun→GUI | Ошибка (1 байт код + текст). | + +### Коды ошибок + +| Код | Константа | Описание | +|-----|-----------|----------| +| 0x01 | `MSG_ERR_INVALID_MSG` | Некорректный заголовок или тип. | +| 0x02 | `MSG_ERR_NOT_BOUND` | Нет привязки к пиру. | +| 0x03 | `MSG_ERR_NO_ROUTE` | Нет маршрута до пира. | +| 0x04 | `MSG_ERR_SEND_FAILED` | Ошибка отправки. | +| 0x06 | `MSG_ERR_ID_TAKEN` | `peer_id` уже занят другим клиентом. | +| 0x07 | `MSG_ERR_ACCESS_DENIED` | Доступ запрещён (например, `GET_PRIVKEY` не с loopback). | +| 0x08 | `MSG_ERR_PEER_NOT_FOUND` | Пир не найден в BGP-топологии. | diff --git a/src/nat_transport_doc.md b/src/nat_transport_doc.md new file mode 100644 index 00000000..b2c696da --- /dev/null +++ b/src/nat_transport_doc.md @@ -0,0 +1,44 @@ +# NAT Transport Layer (nat_transport) + +## 1. Назначение +Транспортный слой NAT поверх ETCP. Обеспечивает проброс IP-трафика между клиентом и провайдером NAT через ETCP-роутер (канал `ETCP_RT_ID_NAT`). Клиент захватывает пакеты из TUN-интерфейса и отправляет провайдеру; провайдер выпускает их в интернет через свой NAT-движок, а обратные пакеты возвращает клиенту. + +## 2. Как пользоваться + +### Конфигурация +- **Провайдер** (`nat_via_node_id = 0`): включает `eim_nat`. В конфиге задаётся `nat_tun_ifname`, `nat_tun_ip` и параметры NAT (пул портов, шлюз). +- **Клиент** (`nat_via_node_id = X`): eim_nat не инициализируется, all трафик с NAT TUN уходит указанному узлу-провайдеру. + +### Поток данных +1. **Клиент → Провайдер**: пакет из `nat_tun->output_queue` → callback `nat_transport_client_tun_out_cb` → упаковка в `NAT_SVC_HDR` (1 байт `ETCP_RT_ID_NAT` + 8 байт src_node_id + IP-пакет) → `etcp_route_send()`. +2. **Провайдер получает**: `nat_transport_etcp_recv_cb` (зарегистрирован через `etcp_router_bind`) → `eim_nat_egress()` (source NAT) → `tun_write()` в NAT TUN. +3. **Провайдер → Клиент (ответ)**: ответ из интернета попадает в `nat_tun->output_queue` → `nat_transport_provider_tun_out_cb` → `eim_nat_ingress()` (поиск исходного клиента) → упаковка в NAT_SVC_HDR → `etcp_route_send()` обратно клиенту. +4. **Клиент получает ответ**: `nat_transport_etcp_recv_cb` → `tun_write()` в NAT TUN клиента. + +### Ключевые нюансы +- Используется отдельный TUN-интерфейс (`nat_tun`), не основной `tun`. Для него нужен маршрут по умолчанию в системе. +- Callback'и работают через очередь `output_queue` TUN-интерфейса. По одному пакету за вызов. +- Все пакеты на ETCP-канале предваряются 9-байтным заголовком `NAT_SVC_HDR`. + +## 3. API + +### Структуры +| Структура | Назначение | +|-----------|------------| +| `nat_transport_ctx` | Контекст: `self_node_id`, `nat_via_node_id` (0=провайдер, иначе id провайдера), `nat_tun`, флаг `initialized` | + +### Функции +| Функция | Назначение | +|---------|------------| +| `nat_transport_init(inst)` | Инициализация: создаёт NAT TUN, настраивает eim_nat (если провайдер), биндит `ETCP_RT_ID_NAT` через etcp_router, вешает callback'и на TUN output_queue | +| `nat_transport_destroy(inst)` | Деинициализация: анбиндит ETCP-канал, закрывает TUN, разрушает eim_nat | + +### Внутренние коллбэки +| Коллбэк | Срабатывание | +|---------|-------------| +| `nat_transport_client_tun_out_cb` | Пакет из TUN output_queue на клиенте — упаковывает и шлёт провайдеру | +| `nat_transport_provider_tun_out_cb` | Пакет из TUN output_queue на провайдере — делает ingress NAT lookup, шлёт обратно клиенту | +| `nat_transport_etcp_recv_cb` | Получен пакет из канала `ETCP_RT_ID_NAT` — клиент пишет в TUN, провайдер делает egress NAT | + +### Зависимости +`eim_nat.h`, `etcp_router.h`, `etcp_api.h`, `tun_if.h`, `utun_instance.h`, `ll_queue.h` diff --git a/src/ntp_node_time_doc.md b/src/ntp_node_time_doc.md new file mode 100644 index 00000000..0bb2d820 --- /dev/null +++ b/src/ntp_node_time_doc.md @@ -0,0 +1,25 @@ +# NTP Node Time + +## 1. Назначение +Синхронизация времени между узлами uTun через ETCP-канал. Позволяет вычислять смещение часов относительно каждого соседа, обнаруживать дрейф и, если узел сам не синхронизирован с NTP, корректировать свои часы по времени синхронизированного соседа. Используется для точной координации событий между узлами (db_sync, timestamp-ы сообщений в чате). + +## 2. Как пользоваться +`ntp_node_time_init()` регистрирует callback на новые ETCP-соединения и callback на приём данных через `etcp_router` (service ID `ETCP_RT_ID_NTP_TIME = 0x12`). При установке нового соединения автоматически вызывается `ntp_node_on_conn_ready` — отправка `TIME_SYNC` соседу. При получении `TIME_SYNC` вычисляется per-peer offset, проверяется дрейф, и если мы не синхронизированы а сосед синхронизирован — корректируем свои часы и рассылаем коррекцию всем остальным соседям. + +`ntp_node_sync_peers()` — широковещательная рассылка `TIME_SYNC` всем активным соседям. Вызывается автоматически после успешной NTP-синхронизации из `ntp_time`. + +**Нюансы:** +- До 32 соседей (`NTP_NODE_MAX_PEERS`), при переполнении новый не добавляется +- Дрейф >2с — WARN, >10с — ERROR. Смещение не применяется автоматически к глобальному offset_us, кроме случая когда свой узел не синхронизирован +- Offset считается как `t4 - t_sender_us` (одностороннее смещение, без учёта RTT — предполагается что задержка ETCP мала) + +## 3. API + +**Структуры:** +- `struct NTP_NODE_PEER` — пара `node_id` + `offset_us` для одного соседа +- `struct NTP_NODE_TIME` — фиксированный массив `peers[32]` + счётчик `peer_count` + +**Функции:** +- `ntp_node_time_init(inst)` — регистрирует bind в `etcp_router` (RT_ID 0x12) и callback на новые соединения +- `ntp_node_time_destroy(inst)` — снимает регистрацию bind и callback, обнуляет peer_count +- `ntp_node_sync_peers(inst)` — отправляет `TIME_SYNC` всем активным/initialized соседям (вызывается после NTP-синхронизации) diff --git a/src/ntp_time_doc.md b/src/ntp_time_doc.md new file mode 100644 index 00000000..6a297d70 --- /dev/null +++ b/src/ntp_time_doc.md @@ -0,0 +1,27 @@ +# NTP Time + +## 1. Назначение +SNTP-клиент (RFC 5905) для синхронизации системного времени с внешними NTP-серверами. Модуль вычисляет смещение `offset_us = local_time - ntp_time` (положительное = локальные часы спешат) и позволяет везде в коде использовать `ntp_time_get_us()` вместо `gettimeofday()` для получения скорректированного времени. Без коррекции времени невозможна корректная работа node-to-node синхронизации (`ntp_node_time`) и оценка RTT/jitter в ETCP. + +## 2. Как пользоваться +`ntp_time_init()` читает список NTP-серверов из конфига (`global.ntp_servers`) и запускает периодический опрос через uasync-таймер. Опрос — по одному серверу за цикл с round-robin, при неудаче перебирает остальные. Интервал ресинка задаётся через `ntp_resync_interval` (по умолчанию 3600с). Первый запрос — сразу после init. При успешной синхронизации вызывается `ntp_node_sync_peers()` для рассылки времени соседним узлам. + +Для тестов: `ntp_time_set_test_addr()` задаёт прямой адрес NTP-сервера (IP:port), минуя DNS-резолвинг. + +**Нюансы:** +- Если `ntp_enabled=0` или серверов нет, модуль не опрашивает, `ntp_time_get_us()` возвращает локальное время +- Если синхронизации не было, то же самое — возвращается `gettimeofday()` без коррекции +- SNTP (simplified NTP) — использует только один запрос-ответ, без сложной фильтрации + +## 3. API + +**Структуры:** +- `struct NTP_TIME` — состояние синхронизации: флаг synced, offset_us, last_sync_tb, список серверов, текущий индекс и таймер + +**Функции:** +- `ntp_time_init(instance)` — читает конфиг, выделяет массив серверов, запускает первый sync-таймер +- `ntp_time_destroy(instance)` — отменяет таймер, освобождает память серверов +- `ntp_time_set_test_addr(ntp, ip, port)` — задать NTP-сервер напрямую (без DNS) для тестов +- `ntp_time_get_us(instance)` — скорректированное время в микросекундах (если synced), иначе `gettimeofday()` +- `ntp_time_get_seconds(instance)` — скорректированное время в секундах (time_t) +- `ntp_time_is_synced(instance)` — true если была успешная синхронизация diff --git a/src/packet_dump_doc.md b/src/packet_dump_doc.md new file mode 100644 index 00000000..d136340c --- /dev/null +++ b/src/packet_dump_doc.md @@ -0,0 +1,59 @@ +# Packet Dump (декодирование IP-пакетов) + +## 1. Назначение +Вспомогательный модуль для диагностики: парсит сырой IPv4-пакет и формирует человекочитаемую строку с IP-адресами, протоколом, портами, флагами и hex-дампом первых 32 байт payload. Используется только в отладочных логах (категории `TUN`, `ROUTING`) для трассировки проходящих через TUN пакетов. + +## 2. Как пользоваться + +```c +#include "packet_dump.h" + +// packet_data указывает на начало IPv4-заголовка +const uint8_t* packet_data = ...; +size_t len = ...; + +char* str = dump_ip_packet_to_buffer(packet_data, len); +DEBUG_DEBUG(DEBUG_CATEGORY_TUN, "[TUN->] %s", str); +``` + +**Важно:** функция возвращает указатель на статический буфер (1024 байта). Не thread-safe. Каждый вызов перезаписывает предыдущий результат. + +### Примеры вывода + +``` +IPv4 TCP 192.168.1.5:443 -> 10.0.0.2:54321 (1420 bytes) [ACK,PSH] seq=123456 ack=789012 win=64240 data: 1703012345... +IPv4 UDP 10.0.0.1:53 -> 192.168.1.3:45678 (76 bytes) len=56 data: 4500003c... +IPv4 ICMP 10.0.0.1 -> 8.8.8.8 (84 bytes) echo_request seq=1 id=1234 data: 08004d2c... +IPv4 ICMP 8.8.8.8 -> 10.0.0.1 (84 bytes) echo_reply seq=1 id=1234 data: 0000552c... +``` + +## 3. API + +### Функции + +**`dump_ip_packet_to_buffer(data, len)`** — парсит IPv4-заголовок и заголовок транспортного уровня, возвращает строку вида `IPv4 PROTO SRC:PORT -> DST:PORT (SIZE bytes) DETAILS data: HEX...`. + +Параметры: +- `data` — указатель на начало IPv4-пакета +- `len` — полная длина данных +- Возвращает `static char[1024]` + +Обрабатываемые протоколы: +- **TCP (6):** порты, seq/ack, флаги (FIN, SYN, RST, PSH, ACK, URG, ECE, CWR), window +- **UDP (17):** порты, длина +- **ICMP (1):** тип/code, echo_request/echo_reply с seq и id + +Для нераспознанных протоколов выводит `src_ip:0 -> dst_ip:0` и 32 байта payload (может выйти за границы пакета — диагностический код, безопасностью жертвует в пользу информативности). + +Hex-дамп: 32 байта транспортного payload (начиная с `data[ihl]`), 4 группы по 8 байт. + +**`tcp_flags_to_str(flags)`** (static) — преобразует байт TCP-флагов в строку вида `SYN,ACK` или `none`. + +### Где используется +- `tun_if.c` — логирование пакетов из/в TUN-интерфейс (категория `TUN`, уровень `DEBUG`) +- `tun_windows.c` — то же для Windows +- `routing.c` — логирование маршрутизируемых пакетов (категория `ROUTING`, уровень `DEBUG`) + +### Зависимости +- `platform_compat.h` — порядок байт (не используется в текущей реализации, IP-адреса читаются напрямую как LE) +- Стандартная библиотека: ``, `` diff --git a/src/pkt_normalizer_doc.md b/src/pkt_normalizer_doc.md new file mode 100644 index 00000000..0e7e0f73 --- /dev/null +++ b/src/pkt_normalizer_doc.md @@ -0,0 +1,95 @@ +# Packet Normalizer (фрагментация/дефрагментация) + +## 1. Назначение +Модуль адаптирует произвольные IP-пакеты к передаче через ETCP-канал с ограниченным MTU. Состоит из двух половин: + +- **Packer** (упаковщик) — разбивает входящие пакеты на фрагменты размером `frag_size` и помещает их в `etcp->input_queue`. Фрагменты собираются в общий буфер пачками для экономии накладных расходов, сброс по таймеру (call_soon) при пустой входной очереди. +- **Unpacker** (распаковщик) — собирает фрагменты из `etcp->output_queue` обратно в исходные пакеты по заголовку длины (2 байта LE), помещает результат в `pn->output` (который читает ETCP router/клиент). + +Каждое ETCP-соединение имеет свой экземпляр `PKTNORM` (создаётся в `etcp.c:247`). + +## 2. Как пользоваться + +### Создание и привязка +`pn_init()` вызывается автоматически при создании `ETCP_CONN`. Клиентский код получает нормализатор через `etcp->normalizer` и использует: + +- **Отправка:** положить пакет в `pn->input` (очередь `pn_input`). Packer сам разобьёт и отправит. +- **Приём:** читать собранные пакеты из `pn->output` (очередь `pn_output`). На эту очередь ETCP вешает свой callback `etcp_int_recv`. + +```c +// Создание (внутри etcp_connection_create): +etcp->normalizer = pn_init(etcp); +etcp->send_input_q = etcp->normalizer->input; // единая точка входа для отправки + +// Отправка пакета (пример из тестов): +struct ll_entry* entry = ll_alloc_lldgram(len); +memcpy(entry->dgram, data, len); +entry->len = len; +queue_data_put(pn->input, entry); + +// Приём (через callback на pn->output): +// queue_set_callback(pn->output, etcp_int_recv, etcp); +``` + +### Формат фрагментов +Каждый фрагмент, уходящий в ETCP, содержит конкатенацию пакетов, каждый предварён 2-байтовым LE-заголовком длины: +``` +[packet1_len:2 LE][packet1_data][packet2_len:2 LE][packet2_data]... +``` + +### Сброс состояния +При разрыве/переподключении вызывать `pn_reset()`. При ошибке сборки (некорректный заголовок) вызывается `etcp_conn_reinit()` — полный перезапуск соединения. + +## 3. API + +### Структуры + +**`struct PKTNORM`** — основная структура пары packer/unpacker. + +| Поле | Описание | +|------|----------| +| `input` | Входная очередь packer (`pn_input`) — сюда кладут пакеты для отправки | +| `output` | Выходная очередь unpacker (`pn_output`) — отсюда читают собранные пакеты | +| `frag_size` | Максимальный размер одного фрагмента ETCP (`mtu - ACK_REZERV - UDP_HDR - UDP_SC_HDR`) | +| `data` / `data_ptr` / `data_size` | Буфер-накопитель packer (из `data_pool`) | +| `recvpart` | Частично собранный пакет unpacker | +| `flush_timer` | Таймер сброса накопленного буфера (call_soon) | +| `input_waiter_handle` | Backpressure-ожидание на `etcp->input_queue` | +| `tx_wait_time` | Задержка сброса (по умолчанию 10, не используется — сброс через call_soon) | +| `alloc_errors` / `logic_errors` | Счётчики ошибок | +| `in_total_pkts` / `in_total_bytes` | Статистика входящих (в packer) | +| `out_total_pkts` / `out_total_bytes` | Статистика исходящих (из unpacker) | + +### Функции + +**`pn_init(etcp)`** — создаёт пару PKTNORM. Выделяет `input`/`output` очереди с порогом 0 (backpressure: ждать полного опустошения). Вешает callback-и: `packer_cb` на input, `pn_unpacker_cb` на `etcp->output_queue`, `etcp_int_recv` на output. Возвращает NULL при ошибке. + +**`pn_deinit(pn)`** — освобождает все ресурсы: очереди, буферы, таймеры, waiter-ы. Вызывает `routing_del_conn()` (deprecated, no-op). Снимает callback с `etcp->output_queue` для предотвращения use-after-free. + +**`pn_reset(pn)`** — сбрасывает состояние packer и unpacker (буферы, pending, recvpart, таймер). Используется при переподключении. + +**`pn_unpacker_reset_state(pn)`** — сбрасывает только unpacker (recvpart). + +**`pn_packer_send(pn, data, len)`** — копирует данные в `ll_alloc_lldgram` и кладёт в `pn->input`. Используется только в юнит-тестах. + +### Внутренние callback-и + +**`packer_cb`** — callback на `pn->input`. Ждёт освобождения `etcp->input_queue` через `queue_waiter_wait`, затем вызывает `etcp_input_ready_cb`. + +**`etcp_input_ready_cb`** — основной цикл packer. Извлекает пакет из `pn->input`, записывает 2-байтовый заголовок длины, копирует данные в буфер `pn->data`. При заполнении буфера (или если осталось <3 байт) вызывает `pn_send_to_etcp`. Если входная очередь пуста — ставит `flush_timer` через `uasync_call_soon` для сброса хвоста. + +**`pn_flush_cb`** — таймерный callback, вызывает `pn_send_to_etcp` при простое. + +**`pn_send_to_etcp(pn)`** — выделяет `ETCP_FRAGMENT` из `io_pool`, передаёт в него накопленный буфер `pn->data`, отправляет в `etcp->input_queue`. Буфер после этого обнуляется (передан во владение фрагмента). + +**`pn_unpacker_cb`** — callback на `etcp->output_queue`. В цикле извлекает фрагменты, парсит 2-байтовые заголовки длины, копирует данные в `pn->recvpart`. При полной сборке пакета помещает его в `pn->output`. При ошибке вызывает `etcp_conn_reinit`. + +**`pn_buf_renew(pn)`** — выделяет новый буфер из `data_pool` если `pn->data == NULL`. При нехватке места (<3 байт) вызывает `pn_send_to_etcp` для сброса текущего буфера. + +### Зависимости +- `etcp.h` / `etcp_api.h` — структуры `ETCP_CONN`, `ETCP_FRAGMENT`, `etcp_int_recv` +- `routing.h` — `routing_add_conn`/`routing_del_conn` (deprecated, no-op) +- `utun_instance.h` — `UTUN_INSTANCE` (data_pool, ua) +- `ll_queue.h` — очереди, waiter, threshold +- `u_async.h` — `uasync_call_soon` для flush-таймера +- `mem.h` / `memory_pool` — аллокация буферов и фрагментов diff --git a/src/proxy/icmp_proxy_doc.md b/src/proxy/icmp_proxy_doc.md new file mode 100644 index 00000000..e43f78c5 --- /dev/null +++ b/src/proxy/icmp_proxy_doc.md @@ -0,0 +1,77 @@ +# ICMP Proxy (icmp_proxy) + +## 1. Назначение +Проксирование ICMP Echo (ping) между клиентским и exit-узлом по протоколу ETCP. Клиент +принимает ICMP Echo Request с TUN-интерфейса, упаковывает запрос и отправляет через +`etcp_route_send` на exit. Exit-узел через нативный `SOCK_RAW`-сокет отправляет ICMP Echo +во внешнюю сеть, сопоставляет ответы по `echo_id`/`echo_seq` и возвращает клиенту. Клиент +восстанавливает IP/ICMP Echo Reply и доставляет в TUN. + +Один экземпляр контекста на процесс (глобальный `g_icmp_ctx`). Режим определяется флагом +`inst->tcp_proxy_server.enabled`. Для тестов предусмотрен loopback-режим без raw-сокета. + +## 2. Как пользоваться + +### Инициализация +```c +icmp_proxy_init(inst, ua); // exit создаёт SOCK_RAW, клиент — нет +``` +При инициализации регистрируется коллбэк `icmp_proxy_recv_cb` в `etcp_router` для сервиса +`ETCP_RT_ID_ICMP_PROXY`. На exit создаётся один `SOCK_RAW(IPPROTO_ICMP)` на весь модуль. + +### Клиент: отправка ping-запроса +```c +icmp_proxy_send_to_exit(inst, exit_node_id, dst_ip, orig_src_ip, echo_id, echo_seq, payload, len); +``` +Вызывается из обработчика TUN-трафика. Запрос упаковывается и отправляется через `etcp_route_send`. +Ответ приходит асинхронно в `icmp_proxy_recv_cb` (REPLY), где вызывается `icmp_proxy_deliver_reply` +для сборки IP/ICMP Echo Reply и записи в TUN. + +### Тестовый loopback +```c +icmp_proxy_set_test_loopback(inst, 1); // exit отвечает эмуляцией без raw-сокета +``` +В этом режиме exit не отправляет реальный ICMP Echo, а сразу возвращает REPLY клиенту — полезно +для тестирования прохождения пакетов через ETCP без необходимости в SOCK_RAW и внешней сети. + +### Очистка +```c +icmp_proxy_destroy(inst); // закрывает raw-сокет, удаляет pending-запросы, unbind +``` + +### Нюансы +- Запросы истекают через 5 секунд (`ICMP_TIMEOUT_TB`). Таймер `expire_timer` запускается + при добавлении первого запроса и перезапускается после каждой очистки. +- Неопознанные echo-ответы (не найденные в pending) логируются как `DEBUG_WARN` и игнорируются. +- Контрольная сумма ICMP пересчитывается на exit (при отправке) и на клиенте (при сборке ответа). +- Поле `orig_src_ip` в протоколе нужно для восстановления исходного dst_ip на клиенте. + +## 3. API + +### Структуры +| Структура | Назначение | +|-----------|-----------| +| `struct icmp_request` | Состояние активного echo-запроса на exit: client_node_id, dst_ip, orig_src_ip, echo_id, echo_seq, payload, время отправки. Связный список. | +| `struct icmp_proxy_ctx` | Глобальный контекст: is_exit, inst, ua, raw_sock, raw_read_id, список pending, таймаут запросов, флаг test_loopback, таймер очистки. | + +### Формат пакета +| Смещение | Размер | Поле | +|----------|--------|------| +| 0 | 1 | `svc_id` = `ETCP_RT_ID_ICMP_PROXY` | +| 1 | 1 | `subcmd` = REQUEST (0x01) / REPLY (0x02) | +| 2 | 8 | `sender_node_id` | +| 10 | 4 | `dst_ip` (IP цели для ping) | +| 14 | 4 | `orig_src_ip` (IP исходного отправителя для обратной доставки) | +| 18 | 2 | `echo_id` (icmp_id) | +| 20 | 2 | `echo_seq` (icmp_seq) | +| 22 | N | `payload` (данные ICMP echo) | + +### Функции +| Функция | Назначение | +|---------|-----------| +| `icmp_proxy_init(inst, ua)` | Выделяет контекст, определяет is_exit, на exit создаёт SOCK_RAW и регистрирует его в uasync. Регистрирует коллбэк в etcp_router. | +| `icmp_proxy_destroy(inst)` | Закрывает raw-сокет, отменяет таймер, освобождает pending, отвязывает коллбэк, освобождает память. | +| `icmp_proxy_recv_cb(conn, entry)` | Единый коллбэк etcp_router. По subcmd направляет REQUEST → `exit_handle_request`, REPLY → `client_handle_reply`. | +| `icmp_proxy_send_to_exit(inst, ...)` | Клиент: упаковывает ICMP Echo Request и отправляет через etcp_route_send на exit. | +| `icmp_proxy_deliver_reply(inst, ...)` | Клиент: собирает IP/ICMP Echo Reply-пакет и помещает в TUN input_queue. | +| `icmp_proxy_set_test_loopback(inst, enabled)` | Включает/выключает тестовый loopback-режим на exit (эмуляция echo-ответа без raw-сокета). | diff --git a/src/proxy/socks_proxy_doc.md b/src/proxy/socks_proxy_doc.md new file mode 100644 index 00000000..55d58ad1 --- /dev/null +++ b/src/proxy/socks_proxy_doc.md @@ -0,0 +1,132 @@ +# socks_proxy + +## 1. Назначение +Модуль реализует клиентскую сторону SOCKS5 и HTTP CONNECT прокси. Принимает TCP-соединения от локальных приложений (curl, браузер), выполняет handshake (SOCKS5 или HTTP CONNECT), определяет целевой IP:port (с разрешением доменных имён через DNS) и мультиплексирует трафик через ETCP к exit node. Взаимодействует с `tcp_proxy_server` на стороне exit node. + +Файлы: +- `src/proxy/socks_proxy.c` — реализация +- `src/proxy/socks_proxy.h` — интерфейс + +## 2. Как пользоваться + +### Инициализация +```c +struct socks_proxy_conn* conns = NULL; // связный список активных соединений +int conn_count = 0; // счётчик соединений +uint32_t next_stream_id = 0; // монотонный генератор stream_id +struct listen_ctx* listen = socks_proxy_init_listen( + ua, "127.0.0.1:1080", &conns, &conn_count, &next_stream_id, + inst, via_node_id, is_http); +``` +`is_http=0` — SOCKS5, `is_http=1` — HTTP CONNECT/прокси. + +### Приём ETCP-данных от exit node +Внешний dispatcher (`tcp_proxy_client`) вызывает `socks_proxy_handle_etcp()` при получении ETCP-пакетов с `svc_id=ETCP_RT_ID_TCP_PROXY`: +```c +int handled = socks_proxy_handle_etcp(&conns, &conn_count, stream_id, subcmd, data, data_len); +// handled=1 — найден и обработан, 0 — stream_id не найден +``` +Поддерживаемые subcmd: `DATA`, `CLOSE`, `ERROR`, `FIN`. + +### Поиск соединения +```c +struct socks_proxy_conn* c = socks_proxy_find_conn(conns, stream_id); +// обход связного списка, O(N) +``` + +### Завершение +```c +socks_proxy_conn_free_all(&conns, &conn_count); // все соединения +socks_proxy_close_listen(ua, listen, &sock_out); // слушатель +``` + +### Ключевые нюансы +- **Бэкпрессур (backpressure):** при неудаче `etcp_route_send()` данные сохраняются в `tx_buf/tx_len`, и регистрируется waiter через `etcp_router_on_send_ready()`. Когда ETCP готов к отправке, `tx_waiter_cb` повторяет попытку и возобновляет `read_queue`. +- **Несколько соединений:** управляются через связный список `struct socks_proxy_conn*` + счётчик. Каждое соединение — отдельный TCP-сокет (`tcp_conn`) и независимый ETCP stream. +- **stream_id** генерируется монотонно (`++next_stream_id`), передаётся в заголовке ETCP-пакетов для мультиплексирования. + +## 3. API + +### Структуры + +**`struct socks_proxy_conn`** — одно прокси-соединение: +- `tc` — TCP-соединение с клиентом (`tcp_conn`) +- `stream_id` — уникальный идентификатор потока в ETCP +- `dest_ip[4]`, `dest_port` — целевой адрес (куда хочет подключиться клиент) +- `state` — текущее состояние (SOCKS_STATE_* или HTTP_STATE_*) +- `is_http` — флаг: 0=SOCKS5, 1=HTTP +- `rem_closed`, `fin_remote`, `close_sent`, `close_pending` — флаги состояния закрытия +- `buf[1024]`, `buf_len` — буфер накопления данных для парсинга handshake +- `tx_buf`, `tx_len`, `tx_waiter` — бэкпрессур: отложенные данные и waiter handle +- `bytes_to_client`, `bytes_from_exit` — счётчики переданных байт (диагностика) +- `head`, `count` — указатели на голову списка и счётчик (для самоудаления) + +**`struct listen_ctx`** (непрозрачный) — контекст слушающего сокета: +- `listen_sock`, `socket_id` — сокет и uasync handle +- `conns`, `conn_count`, `next_stream_id` — общий список соединений + +### Основные функции + +| Функция | Описание | +|---------|----------| +| `socks_proxy_init_listen(ua, addr, conns, count, next_sid, inst, via_node, is_http)` | Создаёт слушающий TCP-сокет на `addr` (IP:PORT). Возвращает `listen_ctx*`. | +| `socks_proxy_close_listen(ua, ctx, sock_out)` | Закрывает слушающий сокет, освобождает ctx. | +| `socks_proxy_handle_etcp(head, count, stream_id, subcmd, data, len)` | Диспетчер ETCP-сообщений от exit node. Обрабатывает DATA (→write_queue клиенту), CLOSE/ERROR (→push_close), FIN (→push_fin). | +| `socks_proxy_find_conn(head, stream_id)` | Поиск соединения по stream_id в связном списке. | +| `socks_proxy_conn_free(c)` | Освобождает соединение: отправляет отложенный CLOSE/ERROR в ETCP, удаляет из списка, освобождает `tcp_conn`, buffer, waiter. | +| `socks_proxy_conn_free_all(head, count)` | Освобождает все соединения в списке. | + +### Внутренние функции + +| Функция | Описание | +|---------|----------| +| `send_msg(inst, dst, subcmd, sid, data, len, force)` | Формирует ETCP-пакет: `[TCP_PROXY_HDR_SIZE=6] svc_id(1)+subcmd(1)+stream_id(4)` + payload, отправляет через `etcp_route_send`. | +| `send_connect(c)` | Отправляет CONNECT (dest_ip[4]+dest_port[2]) на exit node. | +| `send_data(c, data, len)` | Отправляет DATA (релей пользовательского трафика). | +| `send_close(c)`, `send_error(c)`, `send_fin(c)` | Отправляют управляющие subcmd. | +| `write_to_client(c, data, len)` | Запись данных в `tc->write_queue` (отправка клиенту через TCP). | +| `process_socks_greeting(c)` | Парсинг SOCKS5 GREETING: ver=5, выбор метода 0x00 (no auth), ответ 05 00. | +| `process_socks_request(c)` | Парсинг SOCKS5 CONNECT REQUEST (atyp=1/3/4, IPv4/IPv6/domain). DNS-резолвинг для доменов. Ответ 05 00 00 01 + dummy addr. | +| `process_http_request(c)` | Парсинг HTTP: CONNECT host:port (туннель) или GET/POST с абсолютным URL (режим HTTP-прокси с переписыванием URL в относительный). | +| `on_accept_cb(sock, arg)` | Callback accept: создаёт `socks_proxy_conn`, `tcp_conn_create`, вешает read callback. | +| `on_read_cb(q, arg)` | Callback чтения из TCP: handshake-парсинг или релей данных через ETCP с бэкпрессуром. | +| `tx_waiter_cb(q, arg)` | Callback бэкпрессура: повторная отправка отложенных данных при готовности ETCP. | +| `on_fin_cb`, `on_error_cb`, `on_closed_cb` | Callback'и жизненного цикла `tcp_conn`: FIN→удалённый FIN, ошибка→send_error+free, закрыт→send_close+free. | + +## 4. Протокол + +### SOCKS5 flow (is_http=0) +``` +Клиент → uTun: [ver=5][nmethods][methods...] +uTun → Клиент: [05][00] (GREETING: no auth) +Клиент → uTun: [05][01][00][atyp][addr...][port] (CONNECT REQUEST) +uTun → Клиент: [05][00][00][01][0.0.0.0][0] (ответ ок) +uTun → exit (ETCP): TCP_PROXY CONNECT[dest_ip+port] +Клиент ↔ uTun ↔ exit: RELAY данных через ETCP DATA +``` + +### HTTP CONNECT flow (is_http=1) +``` +Клиент → uTun: CONNECT host:port HTTP/1.1\r\n\r\n +uTun → Клиент: HTTP/1.1 200 Connection Established\r\n\r\n +uTun → exit (ETCP): TCP_PROXY CONNECT[dest_ip+port] +Клиент ↔ uTun ↔ exit: RELAY данных через ETCP DATA +``` + +### HTTP-прокси (GET/POST, is_http=1) +``` +Клиент → uTun: GET http://host:port/path HTTP/1.1\r\nHost: ...\r\n\r\n +uTun → exit (ETCP): TCP_PROXY CONNECT[dest_ip+port] + TCP_PROXY DATA[GET /path HTTP/1.1\r\n...] +Клиент ↔ uTun ↔ exit: RELAY данных через ETCP DATA +``` +URL переписывается в относительный (`/path`), остальные заголовки передаются как есть. + +## 5. Зависимости +- `tcp_proxy_server.h` — константы subcmd (`TCP_PROXY_SUBCMD_*`) и размер заголовка (`TCP_PROXY_HDR_SIZE=6`) +- `etcp.h` / `etcp_api.h` — `etcp_route_send()`, `ETCP_RT_ID_TCP_PROXY` +- `etcp_router.h` — `etcp_router_on_send_ready()`, `etcp_router_cancel_send_ready()`, `etcp_router_waiter_cancel()` +- `tcp_io.h` / `tcp_conn` — TCP-соединения (`tcp_conn_create`, `tcp_conn_push_close`, `tcp_conn_push_fin`, write/read очереди) +- `u_async.h` — event loop, `uasync_add_socket_t` +- `ll_queue.h` — очереди (write_queue, read_queue), waiter механизм +- `memory_pool.h` — аллокация из пулов (`entry_pool`, `data_pool`) +- `utun_instance.h` — `struct UTUN_INSTANCE` diff --git a/src/proxy/tcp_proxy_client_doc.md b/src/proxy/tcp_proxy_client_doc.md new file mode 100644 index 00000000..dcba7551 --- /dev/null +++ b/src/proxy/tcp_proxy_client_doc.md @@ -0,0 +1,204 @@ +# tcp_proxy_client + +## 1. Назначение + +Клиентский оркестратор TCP прокси. Создаёт TUN-интерфейс, запускает встроенный lwIP TCP стек, перехватывает исходящие TCP-соединения со стороны клиента и туннелирует их через ETCP к удалённому exit-узлу (tcp_proxy_server). Поддерживает три режима перехвата трафика одновременно: + +1. **Прозрачное проксирование через TUN** — весь TCP-трафик с TUN-интерфейса попадает в lwIP, который управляет реальными TCP-сессиями. Исходящие соединения прозрачно проксируются на exit-узел. UDP (через `udp_proxy`) и ICMP Echo (через `icmp_proxy`) также пробрасываются. +2. **SOCKS5 proxy listener** — встроенный SOCKS5-сервер, принимающий локальные подключения и туннелирующий их через ETCP. +3. **HTTP CONNECT proxy listener** — встроенный HTTP CONNECT-прокси (работает через тот же механизм SOCKS, что и SOCKS5). + +Модуль полностью оркестрирует весь TCP-прокси-пайплайн на стороне клиента: захват трафика → lwIP TCP стек → ETCP-туннелирование → приём ответных данных → отдача в lwIP → запись IP-пакетов обратно в TUN. + +## 2. Как пользоваться + +### Типовой сценарий + +``` +tcp_proxy_client_create(inst, ua, tun_name, tun_ip, mtu, test_mode, + mappings, mapping_count, via_node_id, + socks_enabled, socks_addr, http_proxy_enabled, http_proxy_addr) +→ TUN init (tun_init_nat) — создание TUN-интерфейса с NAT +→ lwIP init (lwip_tcp_init) — запуск встроенного TCP стека +→ Настройка port mappings — прослушивание локальных портов в lwIP +→ SOCKS5/HTTP listen — запуск локальных прокси-слушателей +→ ETCP router bind — регистрация коллбэка tcp_proxy_client_router_recv_cb +→ UDP/ICMP proxy init +``` + +### Ключевые концепции + +**TUN interface** через `tun_init_nat()`. IP-пакеты из output_queue TUN направляются в `tcp_proxy_client_tun_input()` — главную точку входа inbound-трафика. + +**Встроенный lwIP TCP стек** (`lwip_tcp_init()`) с output-коллбэком `tcp_proxy_client_output_cb`, который пишет готовые IP-пакеты обратно в TUN (в сторону ОС клиента). + +**Inbound TUN трафик** (`tcp_proxy_client_tun_input()`): парсинг IP-пакета → по протоколу: +- TCP → вызов `lwip_tcp_input()` — пакет попадает в lwIP, где срабатывает `accept_cb` для новых соединений или `recv_cb` для существующих +- UDP → `udp_proxy_send_to_exit()` — проброс UDP в exit +- ICMP (type 8/Echo) → `icmp_proxy_send_to_exit()` — проброс ping в exit + +**Connection accept** (`tcp_proxy_client_accept_cb`): при новом входящем TCP-соединении в lwIP: +1. Выделяется `struct tcp_proxy_client_conn` +2. Определяется destination: поиск по port mapping config `mappings[]` (local_port → remote_ip:remote_port), иначе `local_ip:local_port` самого pcb +3. Назначается `stream_id` (инкремент `next_stream_id`) +4. Регистрируются lwIP коллбэки: `recv`, `sent`, `err`, `poll` +5. Создаётся очередь `to_lwip` для данных от exit +6. Отправляется ETCP CONNECT с dest_ip+dest_port через `etcp_route_send(force=1)` + +**Client-side lwIP** — чтение данных (`tcp_proxy_client_recv_cb`): +- При получении данных от lwIP (клиент что-то отправляет): копируем pbuf → `tcp_proxy_client_send_data()` → ETCP DATA +- При backpressure (очередь нормализатора переполнена): сохраняем в `tx_buf`, регистрируем waiter через `etcp_router_waiter_register()`. При освобождении очереди `tcp_proxy_client_tx_waiter_cb` досылает данные и вызывает `tcp_recved()` для window update +- При p==NULL (FIN от lwIP): выставляется `fin_local`, отправляется ETCP FIN. Если обе стороны закрылись — ETCP CLOSE + +**Exit-side data** — приём ответных данных (`tcp_proxy_client_handle_data()`): +- Данные от exit помещаются в очередь `to_lwip` +- `tcp_proxy_client_feed_from_transport()`: пока есть место в `tcp_sndbuf(pcb)`, читает из `to_lwip` и пишет через `tcp_write()` в lwIP. После записи вызывает `tcp_output()` для отправки +- Trigger на feed: при получении DATA, при `sent_cb` (lwIP освободил send buffer) + +**Full lifecycle:** +1. CONNECT — установка соединения с exit +2. DATA ↔ DATA — двунаправленный обмен данными +3. FIN (от клиента или exit) — half-close: `tcp_shutdown(pcb, 0, 1)` (закрытие только send-стороны) +4. CLOSE — полное закрытие после обоих FIN, очистка `tcp_proxy_client_conn_free()` +5. ERROR — аварийное закрытие (ошибка lwIP или exit), немедленная очистка + +**Per-port mapping конфигурация** (`tcp_proxy_client_mapping_config`): +```c +struct tcp_proxy_client_mapping_config { + uint16_t local_port; // локальный порт для прослушивания + char remote_ip[64]; // IP назначения на exit-узле + uint16_t remote_port; // порт назначения на exit-узле +}; +``` +При старте для каждого mapping создаётся listen pcb в lwIP. Также работает динамический listen для любых исходящих портов (`tcp_proxy_client_ensure_outbound_listen()`). + +**SOCKS5/HTTP proxy listeners** — используют `socks_proxy_init_listen()` (общий код с SOCKS, флаг `is_http` различает режимы). SOCKS/HTTP обрабатываются приоритетно в `router_recv_cb` — им не нужен TUN/lwIP. + +### Протокол обмена с exit-узлом + +Формат сообщения: `svc_id(1) + subcmd(1) + stream_id(4) + [data]` (заголовок `TCP_PROXY_HDR_SIZE = 6` байт). + +| Subcmd | Код | Направление | Описание | +|--------|-----|-------------|----------| +| CONNECT | 0x01 | client→exit | Установка TCP-соединения с destination | +| DATA | 0x03 | ↔ | Данные TCP-потока | +| CLOSE | 0x04 | ↔ | Полное закрытие потока | +| ERROR | 0x05 | ↔ | Ошибка, немедленная очистка | +| FIN | 0x06 | ↔ | Half-close (shutdown write) | + +Специальное сообщение: `svc_id(1) + peer_id(8)` (9 байт) — CLOSE_ALL: очистка всех соединений к указанному peer. + +### Механизмы надёжности + +- **Backpressure в нормализатор**: `etcp_router_waiter_register()` — если `etcp_route_send()` не может отправить (очередь переполнена), данные буферизуются в `tx_buf` и досылаются при освобождении +- **Повтор CLOSE/ERROR**: если отправка CLOSE/ERROR не удалась, выставляется `close_pending=1`. В `poll_cb` повторяется попытка +- **Очередь to_lwip**: буферизует данные от exit до готовности lwIP send buffer, обеспечивая window-based flow control + +## 3. API + +### Структуры + +#### `struct tcp_proxy_client_conn` — одно проксируемое TCP-соединение +| Поле | Тип | Назначение | +|------|-----|------------| +| `next` | `*` | Связный список всех соединений | +| `proxy` | `*tcp_proxy_client` | Обратный указатель на родительский прокси | +| `pcb` | `*tcp_pcb` | lwIP protocol control block для этого соединения | +| `stream_id` | `uint32_t` | Уникальный идентификатор потока в протоколе ETCP | +| `to_lwip` | `*ll_queue` | Очередь данных от exit → lwIP (flow control) | +| `fin_local` | `uint8_t` | Локальная сторона (lwIP) отправила FIN | +| `fin_remote` | `uint8_t` | Удалённая сторона (exit) отправила FIN | +| `rem_closed` | `uint8_t` | Exit прислал CLOSE — прекращаем обработку | +| `error` | `uint8_t` | Ошибка на любом конце — немедленная очистка | +| `close_sent` | `uint8_t` | CLOSE/ERROR отправлен на exit | +| `close_pending` | `uint8_t` | CLOSE/ERROR не доставлен, ждём повтора в poll | +| `tx_buf` | `*uint8_t` | Буфер при backpressure (lwIP→ETCP send fail) | +| `tx_len` | `uint16_t` | Длина данных в tx_buf | +| `tx_waiter` | `queue_waiter_handle` | Дескриптор waiter для backpressure | +| `dest_ip` | `uint8_t[4]` | IP назначения на exit-узле | +| `dest_port` | `uint16_t` | Порт назначения на exit-узле (network byte order) | + +#### `struct tcp_proxy_client` — состояние прокси-клиента +| Поле | Тип | Назначение | +|------|-----|------------| +| `inst` | `*UTUN_INSTANCE` | Родительский инстанс uTun | +| `ua` | `*UASYNC` | Асинхронный event loop | +| `tun` | `*tun_if` | TUN-интерфейс для захвата/инжекции трафика | +| `conns` | `*tcp_proxy_client_conn` | Голова связного списка lwIP-соединений | +| `conn_count` | `int` | Количество активных lwIP-соединений | +| `entry_pool` | `*memory_pool` | Пул для `struct ll_entry` (данные to_lwip) | +| `next_stream_id` | `uint32_t` | Счётчик stream_id (начинается с 1) | +| `via_node_id` | `uint64_t` | Node ID exit-узла для маршрутизации ETCP | +| `lwip` | `*lwip_tcp_ctx` | Контекст встроенного lwIP TCP стека | +| `mappings` | `*mapping_config` | Указатель на массив port mapping конфигурации | +| `mapping_count` | `int` | Количество mapping записей | +| `socks_enabled` | `int` | Флаг включения SOCKS5 прокси | +| `socks_conns` | `*socks_proxy_conn` | Список активных SOCKS5-соединений | +| `socks_conn_count` | `int` | Количество SOCKS5-соединений | +| `socks_listen` | `*listen_ctx` | Слушающий сокет SOCKS5 | +| `http_proxy_enabled` | `int` | Флаг включения HTTP CONNECT прокси | +| `http_conns` | `*socks_proxy_conn` | Список активных HTTP CONNECT соединений | +| `http_conn_count` | `int` | Количество HTTP CONNECT соединений | +| `http_listen` | `*listen_ctx` | Слушающий сокет HTTP CONNECT | + +### Функции + +#### `tcp_proxy_client_create()` +Создаёт и инициализирует весь прокси-клиент. Параметры: инстанс uTun, uasync, имя/IP/MTU TUN-интерфейса, test_mode, массив port mapping, via_node_id, флаги/адреса SOCKS и HTTP прокси. Возвращает NULL при ошибке. + +#### `tcp_proxy_client_destroy()` +Полная очистка: отвязка от ETCP router, остановка UDP/ICMP прокси, закрытие SOCKS/HTTP listeners, освобождение всех соединений (lwIP abort + очистка очередей to_lwip + tx_buf + waiter cancel), уничтожение lwIP стека, закрытие TUN, освобождение memory pool. + +#### `tcp_proxy_client_router_recv_cb()` +Главный ETCP-коллбэк (зарегистрирован через `etcp_router_bind` на `ETCP_RT_ID_TCP_PROXY=0x04`). Обрабатывает входящие сообщения от exit-узла: +- **DATA** → `tcp_proxy_client_handle_data()` — данные в очередь to_lwip и feed в lwIP +- **CLOSE** → `tcp_proxy_client_handle_close()` — пометка rem_closed, закрытие lwIP pcb, освобождение соединения +- **ERROR** → `tcp_proxy_client_handle_error()` — аварийное закрытие lwIP pcb, освобождение соединения +- **FIN** → `tcp_proxy_client_handle_fin()` — half-close через tcp_shutdown(pcb, 0, 1) +- **CLOSE_ALL** (9-байтовый пакет без conn) → очистка всех соединений к peer +- Сначала пробует SOCKS/HTTP обработчики (приоритет — могут работать без TUN) + +### Внутренние функции + +#### lwIP коллбэки +- **`tcp_proxy_client_output_cb()`** — output lwIP: запись IP-пакетов в TUN (в сторону ОС клиента) +- **`tcp_proxy_client_accept_cb()`** — новый входящий TCP: создание `tcp_proxy_client_conn`, определение destination из mapping конфига, отправка ETCP CONNECT +- **`tcp_proxy_client_recv_cb()`** — данные от клиента (lwIP→ETCP): копирование pbuf → ETCP DATA с backpressure +- **`tcp_proxy_client_sent_cb()`** — lwIP освободил send buffer: вызов feed_from_transport для передачи накопленных данных +- **`tcp_proxy_client_err_cb()`** — ошибка lwIP: pcb уже уничтожен стеком, отправка ETCP ERROR +- **`tcp_proxy_client_poll_cb()`** — периодический poll (2 интервала): очистка при error, повтор close_pending + +#### Отправка сообщений в ETCP +- **`tcp_proxy_client_send_msg()`** — базовая функция: сборка пакета `svc_id+subcmd+stream_id+data`, отправка через `etcp_route_send()` +- **`tcp_proxy_client_send_connect()`** — CONNECT с dest_ip+dest_port (6 байт данных), force=1 +- **`tcp_proxy_client_send_data()`** — DATA с payload, без force (backpressure через waiter) +- **`tcp_proxy_client_send_close()`** — CLOSE, force=1, при ошибке выставляет close_pending +- **`tcp_proxy_client_send_error()`** — ERROR, force=1, при ошибке выставляет close_pending +- **`tcp_proxy_client_send_fin()`** — FIN, force=1 + +#### Обработка трафика +- **`tcp_proxy_client_tun_input()`** — коллбэк очереди output_queue TUN. Парсит IP-пакеты: TCP → lwip_tcp_input (с динамическим listen на целевой порт), UDP/ICMP → соответствующие прокси +- **`tcp_proxy_client_handle_non_tcp()`** — перенаправление UDP в `udp_proxy_send_to_exit()` и ICMP Echo в `icmp_proxy_send_to_exit()` +- **`tcp_proxy_client_feed_from_transport()`** — чтение данных из очереди `to_lwip` и запись в lwIP через `tcp_write()` пока есть место в send buffer. После feed вызывает `tcp_output()` +- **`tcp_proxy_client_tx_waiter_cb()`** — коллбэк backpressure waiter: досылка `tx_buf` при освобождении normalizer очереди, вызов `tcp_recved()` для window update +- **`tcp_proxy_client_ensure_outbound_listen()`** — динамическое создание listen pcb в lwIP для заданного порта (если ещё не создан), нужно для прозрачного проксирования любых исходящих TCP-соединений + +#### Управление памятью и соединениями +- **`tcp_proxy_client_entry_from_data()`** — создание `ll_entry` из пула `entry_pool` с копированием данных +- **`tcp_proxy_client_find_conn()`** — линейный поиск соединения по `stream_id` +- **`tcp_proxy_client_conn_free()`** — полная очистка одного соединения: досылка pending CLOSE/ERROR, удаление из списка, очистка очереди to_lwip, освобождение tx_buf, cancel waiter, free памяти + +### Зависимости +- `tun_if.h` — TUN-интерфейс (инициализация, запись, закрытие) +- `lwip_tcp/lwip_tcp.h` — встроенный lwIP TCP стек (инициализация, input, управление pcb) +- `etcp.h` / `etcp_api.h` / `etcp_router.h` — ETCP-маршрутизация и отправка данных +- `tcp_proxy_server.h` — общие константы протокола (TCP_PROXY_SUBCMD_*, TCP_PROXY_HDR_SIZE) +- `socks_proxy.h` — SOCKS5/HTTP CONNECT прокси (init_listen, handle_etcp, conn_free_all) +- `udp_proxy.h` / `icmp_proxy.h` — проброс UDP и ICMP +- `config_parser.h` — `tcp_proxy_client_mapping_config` (local_port → remote_ip:remote_port) +- `utun_instance.h` — `struct UTUN_INSTANCE` с полем `tcp_proxy_client` +- `lib/u_async.h` — асинхронный event loop +- `lib/ll_queue.h` — lock-free очереди (to_lwip, output_queue TUN) +- `lib/memory_pool.h` — пул для `struct ll_entry` +- `lib/mem.h` — обёртки malloc/free с учётом утечек +- `lib/debug_config.h` — логирование diff --git a/src/proxy/tcp_proxy_server_doc.md b/src/proxy/tcp_proxy_server_doc.md new file mode 100644 index 00000000..bf521b76 --- /dev/null +++ b/src/proxy/tcp_proxy_server_doc.md @@ -0,0 +1,108 @@ +# tcp_proxy_server + +## 1. Назначение + +TCP прокси-сервер на exit node. Принимает CONNECT-запросы от клиента через ETCP, создаёт OS TCP сокет к реальному адресату, релеит данные в обе стороны: клиент↔ETCP↔сервер↔OS TCP↔destination. Поддерживает backpressure (пауза чтения из OS сокета при заполнении ETCP очереди), graceful CLOSE, экспоненциальный ретрай управляющих сообщений, диагностические таймеры. + +Работает в паре с `tcp_proxy_client` (на ingress node) и `lwip_tcp` (lwIP-стек для обработки TCP-сессий). + +При старте сервера автоматически инициализируются `udp_proxy` и `icmp_proxy`, если клиентский TCP прокси не активен (чтобы exit node обслуживал все виды трафика). + +## 2. Как пользоваться + +### Включение +В конфиге: `tcp_proxy_server_enabled = yes` — сервер регистрирует обработчик `ETCP_RT_ID_TCP_PROXY` через `etcp_router_bind()` и готов принимать команды от клиентов. + +### Типовой сценарий +1. **CONNECT (0x01):** клиент шлёт `dest_ip(4) + dest_port(2)`, сервер создаёт `tcp_proxy_server_conn`, открывает OS TCP сокет, делает `connect()` к destination. +2. **DATA (0x03):** клиент шлёт данные → сервер пишет в OS write_queue через pool-аллокацию. +3. **Чтение из destination:** данные с OS сокета попадают в `read_queue`, оттуда `read_queue_drain_cb` релеит их клиенту через ETCP. +4. **CLOSE (0x04):** клиент закрыл соединение → сервер паузит чтение, сливает read_queue, шлёт shutdown(SHUT_WR) в OS сокет. +5. **FIN (0x06):** клиент подтверждает получение FIN от destination → сервер push-ит FIN в OS сокет (завершение полного закрытия). +6. **ERROR (0x05):** ошибка на клиенте → сервер форсированно закрывает соединение. +7. **Очистка:** по `on_closed_cb` (OS сокет закрыт полностью) → `tcp_proxy_server_conn_free()` освобождает все ресурсы. + +### Ключевые нюансы +- **Backpressure:** если `etcp_route_send()` вернула ошибку — данные буферизуются в `tx_buf`, регистрируется `pause_waiter` (ожидание освобождения ETCP канала), запускается fallback `retry_timer` (500ms, force=1). При возобновлении — сначала делается повтор tx_buf, потом продолжается дрейн очереди. +- **FIN handling:** FIN от destination не отправляется клиенту пока есть pending writes в OS сокет (ждём `on_flushed_cb`) или pending reads (tx_buf / read_queue не пусты — флаг `dst_fin_deferred`). При опустошении очередей — FIN отправляется немедленно. +- **Graceful CLOSE:** при получении CLOSE от клиента чтение из OS сокета паузится через `tcp_conn_pause_read()`, read_queue сливается (данные отбрасываются), push-ится close в OS сокет. +- **Ретрай CLOSE/ERROR:** при неудачной отправке `send_msg()` — экспоненциальный backoff (50→100→200→…→5000 tb = 5ms→500ms) через `close_timer` + `close_retry_cb`. +- **Диагностический таймер (1s):** логирует TCP буферы ОС, размеры read/write очередей, free counts entry/data pool-ов. +- **CLOSE_ALL:** специальный 9-байтовый пакет без валидного conn удаляет все соединения для заданного peer. + +## 3. API + +### Структуры + +| Структура | Назначение | +|-----------|-----------| +| `tcp_proxy_server` | Корневой контекст: enabled, связный список conns, conn_count, ссылка на UTUN_INSTANCE | +| `tcp_proxy_server_conn` | Состояние одного прокси-соединения: stream_id, peer_node_id, dest_ip:port, tcp_conn, буферы/счётчики/таймеры для backpressure и закрытия | + +### Функции инициализации/завершения + +| Функция | Описание | +|---------|----------| +| `tcp_proxy_server_init(inst)` | Регистрирует ETCP биндинг на `ETCP_RT_ID_TCP_PROXY`, авто-инициализирует UDP/ICMP прокси если tcp_proxy_client не активен | +| `tcp_proxy_server_destroy(inst)` | Освобождает все соединения, уничтожает UDP/ICMP прокси | + +### Обработчики команд (вызываются из `tcp_proxy_server_recv_cb`) + +| Функция | Описание | +|---------|----------| +| `tcp_proxy_server_recv_cb(conn, entry)` | Главный ETCP-коллбэк: диспетчеризует subcmd → handle_connect/data/close/error/fin, обрабатывает CLOSE_ALL | +| `tcp_proxy_server_handle_connect(inst,entry,sid,src)` | Создаёт OS TCP сокет, `connect()` к destination, аллоцирует `tcp_proxy_server_conn`, регистрирует diag_timer и коллбэки tcp_conn | +| `tcp_proxy_server_handle_data(inst,conn,entry,sid)` | Копирует данные клиента в write_queue OS сокета через pool-аллокацию | +| `tcp_proxy_server_handle_close(inst, sid)` | Паузит чтение, сливает read_queue, отправляет shutdown в OS сокет, освобождает соединение если не connected | +| `tcp_proxy_server_handle_error(inst, sid)` | Делегирует в `handle_close` (обрабатывается идентично CLOSE) | +| `tcp_proxy_server_handle_fin(inst, sid)` | Push-ит FIN в write_queue OS сокета (клиент подтвердил получение FIN от destination) | + +### Управление соединениями + +| Функция | Описание | +|---------|----------| +| `tcp_proxy_server_find_conn(ctx, sid)` | Линейный поиск соединения по stream_id в связном списке | +| `tcp_proxy_server_conn_free(rc)` | Полное освобождение: защита от double-free, отмена таймеров, отправка pending close/error если надо, удаление из списка, `tcp_conn_destroy()`, u_free | + +### Внутренние callback-и (статические) + +| Функция | Описание | +|---------|----------| +| `send_msg(inst, dst, subcmd, sid, data, len, force)` | Формирует ETCP-пакет (svc_id + subcmd + sid + data) и отправляет через `etcp_route_send()` | +| `send_close(rc)` | Отправляет CLOSE клиенту, при неудаче запускает экспоненциальный ретрай через `close_timer` | +| `send_error(rc)` | Аналогично `send_close`, но шлёт ERROR | +| `send_fin(rc)` | Отправляет FIN клиенту (без ретрая) | +| `on_fin_cb(tc, arg)` | OS сокет получил FIN → машина состояний: если локальный FIN тоже → close; если write pending → ждать flush; если read pending → отложить FIN | +| `on_flushed_cb(tc, arg)` | Write очередь OS сокета опустела → отправить FIN клиенту или закрыть если оба FIN получены | +| `on_error_cb(tc, err, arg)` | Ошибка OS сокета → отправить ERROR клиенту, освободить соединение | +| `on_closed_cb(tc, arg)` | OS сокет полностью закрыт → освободить соединение | +| `read_queue_drain_cb(q, arg)` | Дрейн read_queue OS сокета → отправка данных клиенту через ETCP; backpressure: буферизация в tx_buf + pause_waiter + retry_timer | +| `pause_resume_cb(q, arg)` | ETCP канал освободился → возобновить дрейн read_queue | +| `retry_timer_cb(arg)` | Fallback-таймер 500ms: повтор tx_buf с force=1, при успехе возобновляет read_queue | +| `close_retry_cb(arg)` | Экспоненциальный backoff для повторной отправки CLOSE/ERROR (50→5000 tb) | +| `diag_timer_cb(arg)` | 1-секундный таймер: логирует TCP буферы ОС, размеры очередей, free-counts pool-ов | + +### Константы + +| Макрос | Значение | Описание | +|--------|----------|----------| +| `TCP_PROXY_HDR_SIZE` | 6 | svc_id(1) + subcmd(1) + stream_id(4) | +| `TCP_PROXY_CONNECT_HDR_SIZE` | 12 | HDR_SIZE + dest_ip(4) + dest_port(2) | +| `TCP_PROXY_SUBCMD_CONNECT` | 0x01 | Запрос на создание TCP соединения к destination | +| `TCP_PROXY_SUBCMD_DATA` | 0x03 | Данные для отправки в destination | +| `TCP_PROXY_SUBCMD_CLOSE` | 0x04 | Клиент закрывает прокси-соединение | +| `TCP_PROXY_SUBCMD_ERROR` | 0x05 | Ошибка на стороне клиента | +| `TCP_PROXY_SUBCMD_FIN` | 0x06 | Клиент подтвердил FIN от destination | + +### Зависимости + +| Модуль | Использование | +|--------|--------------| +| `tcp_io.h` | `tcp_conn` — абстракция OS TCP сокета с read/write очередями и memory pool-ами | +| `ll_queue.h` | Очереди read_queue/write_queue OS сокета, pause_waiter для backpressure | +| `etcp_router.h` | `etcp_router_bind()`, `etcp_route_send()`, `etcp_router_on_send_ready()`, `etcp_router_cancel_send_ready()` | +| `u_async.h` | Таймеры (`uasync_set_timeout`, `uasync_cancel_timeout`) | +| `udp_proxy.h` | Авто-инициализация при старте сервера | +| `icmp_proxy.h` | Авто-инициализация при старте сервера | +| `config_parser.h` | Флаг `tcp_proxy_server_enabled` | +| `mem.h` | `u_malloc`/`u_free`/`u_calloc` для tx_buf и структур | diff --git a/src/proxy/udp_proxy_doc.md b/src/proxy/udp_proxy_doc.md new file mode 100644 index 00000000..133e19a5 --- /dev/null +++ b/src/proxy/udp_proxy_doc.md @@ -0,0 +1,73 @@ +# UDP Proxy (udp_proxy) + +## 1. Назначение +Проксирование UDP-датаграмм между клиентским и exit-узлом по протоколу ETCP. Клиентский узел +принимает UDP-пакеты с TUN-интерфейса, инкапсулирует их и отправляет через `etcp_route_send` +на exit-узел. Exit-узел создаёт нативные UDP-сокеты для каждого потока (5-tuple) и ретранслирует +датаграммы во внешнюю сеть. Ответы проходят обратный путь — от exit к клиенту и затем в TUN. + +Один экземпляр контекста на процесс (глобальный `g_udp_ctx`), режим определяется флагом +`inst->tcp_proxy_server.enabled`: включён → `is_exit=1`, иначе `is_exit=0`. + +## 2. Как пользоваться + +### Инициализация +```c +udp_proxy_init(inst, ua); // exit или клиент — определяется автоматически +``` +При инициализации регистрируется коллбэк `udp_proxy_recv_cb` в `etcp_router` для сервиса +`ETCP_RT_ID_UDP_PROXY`. + +### Клиент: отправка датаграммы в exit +```c +udp_proxy_send_to_exit(inst, exit_node_id, src_ip, src_port, dst_ip, dst_port, payload, len); +``` +Вызывается из обработчика TUN-трафика. Пакет упаковывается в формат ETCP и отправляется +через `etcp_route_send`. Ответ приходит асинхронно в `udp_proxy_recv_cb`, который вызывает +`udp_proxy_deliver_reply` для записи IP/UDP-пакета обратно в TUN. + +### Exit: приём датаграмм +Exit автоматически принимает REQUEST-ы через `etcp_router`. Для каждого нового потока +(уникальный `client_node_id + src_ip:port + dst_ip:port`) создаётся нативный UDP-сокет, +который регистрируется в `uasync` на чтение. При получении ответа от внешнего хоста +ответ упаковывается и отправляется обратно клиенту. + +### Очистка +```c +udp_proxy_destroy(inst); // закрывает все flows, отменяет таймер, unbind из etcp_router +``` + +### Нюансы +- Потоки истекают через 60 секунд неактивности (`UDP_FLOW_TIMEOUT_TB`). Таймер `expire_timer` + запускается при создании первого потока, перезапускается после каждой очистки. +- UDP-сокеты на exit привязываются к `INADDR_ANY:0` (bind), трафик отправляется через `sendto`. +- Ответы в TUN на клиенте доставляются через `inst->tcp_proxy_client->tun->input_queue`. + +## 3. API + +### Структуры +| Структура | Назначение | +|-----------|-----------| +| `struct udp_flow` | Состояние одного UDP-потока на exit: 5-tuple, нативный сокет, uasync read_id, времена создания и последней активности. Связный список. | +| `struct udp_proxy_ctx` | Глобальный контекст: флаг is_exit, inst, ua, список flows, таймаут истечения, таймер очистки. | + +### Формат пакета +| Смещение | Размер | Поле | +|----------|--------|------| +| 0 | 1 | `svc_id` = `ETCP_RT_ID_UDP_PROXY` | +| 1 | 1 | `subcmd` = `UDP_PROXY_SUBCMD_DATA` (0x01) | +| 2 | 8 | `sender_node_id` | +| 10 | 4 | `src_ip` (клиент→exit: IP отправителя; exit→клиент: dst_ip) | +| 14 | 2 | `src_port` | +| 16 | 4 | `dst_ip` (клиент→exit: IP назначения; exit→клиент: src_ip) | +| 20 | 2 | `dst_port` | +| 22 | N | `payload` (UDP-датаграмма) | + +### Функции +| Функция | Назначение | +|---------|-----------| +| `udp_proxy_init(inst, ua)` | Выделяет контекст, определяет is_exit, регистрирует коллбэк в etcp_router. Возвращает 0 при успехе. | +| `udp_proxy_destroy(inst)` | Закрывает все потоки, сокеты, отменяет таймеры, отвязывает коллбэк, освобождает память. | +| `udp_proxy_recv_cb(conn, entry)` | Единый коллбэк etcp_router. По subcmd направляет в exit_handle_data (exit) или client_handle_reply (клиент). | +| `udp_proxy_send_to_exit(inst, ...)` | Клиент: упаковывает UDP-датаграмму и отправляет через etcp_route_send на exit. | +| `udp_proxy_deliver_reply(inst, ...)` | Клиент: собирает IP/UDP-пакет и помещает в TUN input_queue для доставки приложению. | diff --git a/src/route6_lib_doc.md b/src/route6_lib_doc.md new file mode 100644 index 00000000..d905f031 --- /dev/null +++ b/src/route6_lib_doc.md @@ -0,0 +1,50 @@ +# route6_lib — библиотека IPv6-маршрутизации на radix tree + +## 1. Назначение +Локальная таблица маршрутизации IPv6 для поиска longest-prefix match по заданному адресу. +Используется при пробросе трафика в TUN-интерфейс — по destination IP определяем узел-владелец +подсети и отправляем пакет ему через ETCP. + +Индексируется по `node_id` через `ll_queue`-хеш — это позволяет эффективно удалять все маршруты +узла при его withdraw из BGP-топологии. + +## 2. Как пользоваться +```c +struct ROUTE6_TABLE *rt = route6_table_create(ua); + +// При получении BGP-апдейта с новыми подсетями узла: +route6_insert(rt, nodeq); + +// При withdraw узла: +route6_delete(rt, nodeq); + +// При пробросе IPv6-пакета: +const struct ROUTE6_DATA *rd = route6_lookup(rt, dst_addr); +if (rd) forward_to_etcp(rd->v_node_info, pkt); +``` + +Ключевой нюанс: `make_key6` формирует 17-байтовый ключ в формате sockaddr-like +(первый байт = длина = 17, остальные 16 — адрес). Radix tree сравнивает ключи с отступом 1 байт +(RADIX_OFF), пропуская заголовок длины. + +## 3. API + +### Структуры +| Структура | Назначение | +|-----------|------------| +| `ROUTE6_DATA` | Запись маршрута: ключ+маска (17 байт sockaddr-like), `prefix_length`, `node_id` (для хеша), `v_node_info` (указатель на узел-владелец), `rn_nodes[2]` (scratch для radix) | +| `ROUTE6_TABLE` | Таблица: `rnh` (radix tree head), `queue` (ll_queue с хеш-индексом по `node_id`) | + +### Функции +| Функция | Назначение | +|---------|------------| +| `route6_table_create(ua)` | Создать таблицу: `rn_inithead` + `queue_new` с хеш-индексом по `node_id` | +| `route6_table_destroy(table)` | Удалить все маршруты (radix+queue), освободить таблицу | +| `route6_insert(table, node)` | Вставить все `v6_subnets` узла в radix tree и queue (с хеш-индексом) | +| `route6_delete(table, node)` | Удалить из radix tree все маршруты с заданным `node_id` (поиск через хеш) | +| `route6_lookup(table, addr)` | Longest-prefix match по 16-байтовому адресу, возвращает `ROUTE6_DATA*` или NULL | + +### Зависимости +- `lib/radix.h` — radix tree (longest-match lookup) +- `lib/ll_queue.h` — lock-free очередь с хеш-индексом для поиска по `node_id` +- `src/topo_node.h` — `TOPO_NODEQ`, `TOPO_SUBNET6` diff --git a/src/route_connectivity_doc.md b/src/route_connectivity_doc.md new file mode 100644 index 00000000..629e5d82 --- /dev/null +++ b/src/route_connectivity_doc.md @@ -0,0 +1,64 @@ +# route_connectivity — зондирование прямой связности с удалённым узлом + +## 1. Назначение +Проверяет прямую IP-достижимость всех адресов удалённого узла через `etcp_send_ping_to_socket()`. +Для каждого адреса перебирает локальные сокеты-кандидаты (до 8), с каждого делает серию из 3 пингов +(CONN_PROBE_COUNT=3, CONN_PROBE_TIMEOUT_MS=1000). Результаты (reachable/unreachable, min_rtt) +сохраняются в `TOPO_CONNECTIVITY` узла и используются `conn_mgr` для выбора оптимального пути. + +В отличие от `route_ping` (пинг через BGP-посредника), здесь пинги идут напрямую с локальных сокетов +на адреса удалённого узла — без промежуточных узлов. + +## 2. Как пользоваться +```c +// Запустить зондирование узла (вызывается conn_mgr при обнаружении нового узла): +route_connectivity_probe_node(instance, nodeq); + +// Отменить зондирование (при удалении узла / withdraw): +route_connectivity_cancel_node(instance, nodeq); + +// Отменить все зондирования (при destroy): +route_connectivity_cancel_all(instance); +``` + +Результаты доступны через `nodeq->connectivity`: +- `probe_status` — `PROBE_STATUS_NONE` / `PROBE_STATUS_IN_PROGRESS` / `PROBE_STATUS_DONE` +- `interface_status/nat_status/real_status` — `PROBE_RESULT_UNKNOWN` / `REACHABLE` / `UNREACHABLE` +- `interface_min_rtt/nat_min_rtt/real_min_rtt` — минимальный RTT по всем сокетам (0.1ms) +- `pending_count` — счётчик незавершённых серий (когда 0 → PROBE_STATUS_DONE) + +Ключевой нюанс: зондирование пропускает собственный узел (`nq->node->node_id == instance->node_id`). +Если зондирование уже в процессе, повторный вызов игнорируется. + +## 3. API + +### Внутренние структуры +| Структура | Назначение | +|-----------|------------| +| `conn_probe_ctx` | Контекст одной серии проб: `addr_type` (INTERFACE/NAT/REAL), `target_addr`, `candidate_sockets[8]`, `best_across_sockets` (min RTT по всем опробованным сокетам), `min_rtt` (в текущей серии), счётчики | + +### Подбор сокетов-кандидатов (`conn_match_candidate_sockets`) +Для каждого адреса узла подбирает локальные сокеты в порядке предпочтения: +1. **INTERFACE-адрес**: PRIVATE/LOCAL сокеты в той же /24 подсети +2. **NAT/REAL-адрес**: DIRECT > EIM > PUBLIC/UNKNOWN сокеты +3. Fallback: остальные подходящие сокеты, затем все IPv4 + +Для IPv6: предпочитает сокеты в той же /64 подсети. + +### Функции +| Функция | Назначение | +|---------|------------| +| `route_connectivity_probe_node(instance, nq)` | Перебрать все v4/v6 адреса узла, для каждого подобрать сокеты-кандидаты, запустить серии пингов по 3. Заполняет `nq->connectivity` | +| `route_connectivity_cancel_node(instance, nq)` | Сбросить `probe_status=NONE`, `pending_count=0`, занулить `nq` в ctx (чтобы callback не обновлял статистику) | +| `route_connectivity_cancel_all(instance)` | Сбросить состояние всех узлов в default-группе | + +### Логика завершения +- Серия из 3 пингов с одного сокета: если все 3 потеряны → переход к следующему сокету +- Если хоть 1 пинг успешен → серия считается успешной для данного адреса, обновляется `*_status=REACHABLE` и `*_min_rtt` +- Когда `pending_count` доходит до 0 → `probe_status = PROBE_STATUS_DONE` + +### Зависимости +- `src/etcp_connections.h` — `etcp_send_ping_to_socket()` (прямой пинг) +- `src/topo_node.h` — `TOPO_NODEQ`, `TOPO_CONNECTIVITY`, `TOPO_ADDR4`, `TOPO_ADDR6` +- `src/topo_group.h` — `topo_groups_get_default()` (для cancel_all) +- `lib/u_async.h` — get_time_tb() для отметок времени diff --git a/src/route_lib_doc.md b/src/route_lib_doc.md new file mode 100644 index 00000000..a53747ed --- /dev/null +++ b/src/route_lib_doc.md @@ -0,0 +1,68 @@ +# route_lib – библиотека таблицы IPv4-маршрутизации + +## 1. Назначение +Ведёт таблицу IPv4-маршрутов в памяти: динамический массив `ROUTE_ENTRY`, отсортированный по network-адресу с поддержкой вставки/удаления и LPM-поиска (Longest Prefix Match). Используется модулем `routing` для принятия решения, куда слать пакет — какому узлу через ETCP или локально в TUN. + +Таблица не содержит логики синхронизации — наполнение маршрутами идёт через BGP (`topo_node`). + +## 2. Как пользоваться +```c +struct ROUTE_TABLE* rt = route_table_create(); + +// Вставка подсетей узла (из BGP): +route_insert(rt, topo_nodeq); + +// Поиск маршрута (LPM): +struct ROUTE_ENTRY* e = route_lookup(rt, dest_ip); +if (e && e->v_node_info && e->v_node_info->hop_count > 0) { + // Отправить через ETCP узлу e->v_node_info->node->node_id +} else { + // Локальный маршрут — отдать в TUN +} + +// Удаление маршрутов узла: +route_delete(rt, topo_nodeq); + +route_table_destroy(rt); +``` + +**Ключевые нюансы:** +- Адреса в `ROUTE_ENTRY.network` хранятся в **big-endian** (сетевой порядок) +- Записи отсортированы по `network` ASC, при равенстве — по `prefix_length` DESC (более специфичные раньше) +- Алгоритм LPM: бинарный поиск с fallback-проверкой соседней записи +- `v_node_info == NULL` или `hop_count == 0` означает **локальный маршрут** +- Пересечение маршрутов запрещено — `route_insert` проверяет `check_route_overlap_in_table` +- Ёмкость массива динамически расширяется (×2) при переполнении + +## 3. API + +### Структуры + +| Структура | Поля | Описание | +|-----------|------|----------| +| `ROUTE_ENTRY` | `network` (uint32_t, BE), `prefix_length` (uint8_t), `v_node_info` (TOPO_NODEQ*) | Запись маршрута. `v_node_info==NULL` → локальный | +| `ROUTE_TABLE` | `entries`, `count`, `capacity`, `dynamic_subnets`, `local_subnets`, `stats` | Таблица маршрутизации. Динамический массив + статистика | + +### Функции + +| Функция | Описание | +|---------|----------| +| `route_table_create()` | Выделяет `ROUTE_TABLE`, `entries` на `INITIAL_CAPACITY=100`, счётчики в 0 | +| `route_table_destroy(table)` | Освобождает `entries`, `dynamic_subnets`, `local_subnets`, саму таблицу | +| `route_insert(table, node)` | Вставляет все v4-подсети узла (`TOPO_NODEQ`) с сортировкой. Проверяет overlap. Расширяет capacity при необходимости | +| `route_delete(table, node)` | Удаляет все записи, где `v_node_info == node`, со сдвигом массива | +| `route_lookup(table, dest_ip)` | LPM-поиск. Возвращает `ROUTE_ENTRY*` или NULL. Инкрементит `stats` (hits/misses) | +| `route_table_print(table)` | Печатает содержимое таблицы в лог | +| `parse_subnet(str, network, prefix_length)` | Парсит `"a.b.c.d/n"` → network (BE) + prefix_length. Возвращает 0/-1 | +| `is_local_subnet(ip)` | Проверяет, является ли IP локальным/мультикастовым/зарезервированным (0=true). RFC1918 + 0.0.0.0/8 + 127.0.0.0/8 + АPIPA + multicast + broadcast | +| `route_add_local_subnet(table, network, prefix_length)` | **Объявлена в .h, но не реализована** | + +### Внутренние функции + +| Функция | Описание | +|---------|----------| +| `prefix_to_mask(prefix)` | Преобразует длину префикса в маску: `/24` → `0xFFFFFF00` | +| `routes_overlap(n1, p1, n2, p2)` | Проверяет, совпадают ли две подсети (одинаковые network+prefix) | +| `check_route_overlap_in_table(net, pre, entries, count)` | Проверяет overlap со всеми существующими записями | +| `binary_search_insert_pos(entries, count, net, pre)` | Находит позицию для вставки с сохранением сортировки | +| `binary_search_lpm(table, dest_ip)` | Бинарный LPM-поиск с fallback на соседнюю запись | diff --git a/src/route_ping_doc.md b/src/route_ping_doc.md new file mode 100644 index 00000000..ed126cf5 --- /dev/null +++ b/src/route_ping_doc.md @@ -0,0 +1,62 @@ +# route_ping — удалённый пинг через BGP (NAT-детекция, проверка живости) + +## 1. Назначение +Механизм запроса удалённого пинга через BGP-канал. Узел A отправляет узлу B TOPO_PING_REQ +с IP:port цели, узел B выполняет серию пингов (`etcp_send_ping_to_socket`) и возвращает +TOPO_PING_RESP с результатами (count_ok, avg_rtt). + +Используется: +- `route_connectivity` — для зондирования связности (прямые пинги, без BGP) +- `topo_group` — для NAT-детекции через промежуточный узел (third-party ping) +- Общий механизм проверки живости адресов + +## 2. Как пользоваться +```c +// Отправить запрос пинга адреса target_ip:target_port через промежуточный узел to_conn +int ret = route_ping_send_req_addr(group, to_conn, + target_ip, target_port, + count=2, interval_ms=100, timeout_ms=500, + wait_timeout_ms=2000, callback, arg, pubkey); +``` + +Промежуточный узел (to_conn) получает запрос → выполняет серию `etcp_send_ping_to_socket()` +→ собирает статистику → отправляет ответ. + +Ключевой нюанс: `wait_timeout_ms` задаёт таймаут ожидания ответа на локальной стороне. +Если ответ не приходит вовремя, `route_ping_pending_timeout` вызывает callback с `success=0`. + +## 3. API + +### Протокольные структуры (packed, wire-format) +| Структура | Поля | +|-----------|------| +| `TOPO_PING_REQ` | `cmd=ETCP_ID_TOPO_ENTRY`, `subcmd=0x07`, `request_id`, `count`, `interval_ms`, `timeout_ms`, `target_ipv4`, `target_port`, `pubkey` (опционально) | +| `TOPO_PING_RESP` | `cmd`, `subcmd=0x08`, `request_id`, `count_sent`, `count_ok`, `avg_rtt` (0.1ms) | + +### Внутренние структуры +| Структура | Назначение | +|-----------|------------| +| `route_ping_series_ctx` | Контекст серии пингов (на отвечающей стороне): `reply_conn`, `request_id`, `target_addr`, `local_sock`, счётчики, статистика | +| `route_ping_pending` | Ожидающий запрос (на запрашивающей стороне): `request_id`, `callback`, `timeout_timer`, `via_conn` | + +### Функции +| Функция | Назначение | +|---------|------------| +| `route_ping_send_req_addr(group, to_conn, target_ip, target_port, count, interval, timeout, wait_timeout, cb, arg, pubkey)` | Отправить TOPO_PING_REQ через BGP, зарегистрировать callback | +| `route_ping_handle_req(group, from_conn, data, len)` | Обработать входящий PING_REQ: найти локальный IPv4-сокет, запустить серию `etcp_send_ping_to_socket()`, по завершении отправить PING_RESP | +| `route_ping_handle_resp(group, from_conn, data, len)` | Обработать PING_RESP: найти pending по `request_id`, отменить таймер, вызвать callback | +| `route_ping_destroy_pending(group)` | Очистить весь список pending (при уничтожении BGP) | +| `route_ping_cancel_for_conn(group, conn)` | Отменить pending запросы, связанные с конкретным `ETCP_CONN` (при разрыве) | + +### Callback +```c +typedef void (*route_ping_callback_t)(int success, uint16_t avg_rtt, + uint8_t count_sent, uint8_t count_ok, void* arg); +``` +Вызывается при получении ответа или по таймауту (`success=0`). + +### Зависимости +- `src/topo_group.h` — BGP-канал (список `ping_pending`, `next_ping_req_id`) +- `src/etcp_api.h` — `etcp_send()` для отправки через BGP +- `src/etcp_connections.h` — `etcp_send_ping_to_socket()` для собственно пинга +- `src/topo_node.h` — `topo_node_find_by_id()` diff --git a/src/routing_doc.md b/src/routing_doc.md new file mode 100644 index 00000000..b904a554 --- /dev/null +++ b/src/routing_doc.md @@ -0,0 +1,44 @@ +# routing – централизованный модуль маршрутизации + +## 1. Назначение +Связывает TUN-интерфейс и ETCP-транспорт в единый маршрутизатор. Принимает IP-пакеты из TUN, по IP-адресу назначения ищет маршрут в таблице (`route_lib`) и отправляет пакет через ETCP нужному узлу. В обратную сторону принимает пакеты из ETCP (callback `routing_pkt_from_etcp_cb`) и отдаёт в TUN локально. + +IPv6-пакеты на данный момент дропаются на этапе `extract_dst_ip` (возвращает 0). + +Модуль НЕ реализует таблицу маршрутизации сам — он пользуется `route_lib` (`instance->rt`) и BGP-обменом (`topo_node` / `topo_group`) для наполнения таблицы. + +## 2. Как пользоваться +**Порядок инициализации в `utun_instance.c`:** +1. `routing_create()` — создаёт `ROUTE_TABLE` в `instance->rt` +2. `routing_bind()` — биндит коллбек `routing_pkt_from_etcp_cb` через `etcp_router_bind(ETCP_RT_ID_DATA)` — принимать пакеты из ETCP +3. `routing_set_tun()` — вешает коллбек `routing_pkt_from_tun_cb` на `tun->output_queue` — забирать пакеты из TUN + +**Основной поток данных:** +- **TUN → сеть:** пакет попадает в `tun->output_queue` → `routing_pkt_from_tun_cb` → `route_pkt` → `etcp_route_send` +- **сеть → TUN:** пакет приходит через ETCP → `etcp_router` вызывает `routing_pkt_from_etcp_cb` → `route_pkt` → `queue_data_put(tun->input_queue)` + +**Ключевые нюансы:** +- `routing_add_conn` / `routing_del_conn` — **deprecated** (no-op), маршруты теперь управляются через BGP и таблицу `route_lib` +- Все не-IPv4 пакеты (IPv6, unknown) дропаются в `extract_dst_ip` +- Multicast (224.0.0.0/4) и limited broadcast (255.255.255.255) дропаются без логирования +- Пакеты без маршрута дропаются с инкрементом `instance->dropped_packets` + +## 3. API + +| Функция | Описание | +|---------|----------| +| `routing_create(instance)` | Создаёт `ROUTE_TABLE` в `instance->rt`. -1 при ошибке | +| `routing_bind(instance)` | Биндит обработчик входящих ETCP-пакетов через `etcp_router_bind` | +| `routing_destroy(instance)` | Отвязывает ETCP-обработчик, уничтожает `ROUTE_TABLE` | +| `routing_set_tun(instance)` | Вешает callback на `tun->output_queue` для перехвата пакетов из TUN | +| `routing_add_conn(etcp)` | **Deprecated**, ничего не делает | +| `routing_del_conn(etcp)` | **Deprecated**, ничего не делает | +| `route_pkt(instance, entry, src_node_id)` | Ядро маршрутизации: извлекает dst IP → `route_lookup` → `etcp_route_send` или `queue_data_put(tun->input_queue)` для локального | + +### Внутренние функции + +| Функция | Описание | +|---------|----------| +| `extract_dst_ip(data, len)` | Извлекает dst IPv4 из заголовка пакета (offset 16, 4 байта). Возвращает 0 для не-IPv4 | +| `routing_pkt_from_etcp_cb(conn, pkt)` | Callback: приём пакета из ETCP-роутера, вызывает `route_pkt` | +| `routing_pkt_from_tun_cb(q, arg)` | Callback: приём пакета из TUN output_queue, вызывает `route_pkt` | diff --git a/src/secure_channel_doc.md b/src/secure_channel_doc.md new file mode 100644 index 00000000..9e7b719a --- /dev/null +++ b/src/secure_channel_doc.md @@ -0,0 +1,352 @@ +# secure_channel + +## 1. Назначение + +Модуль реализует криптографический слой uTun: защищённый канал между двумя узлами на основе **X25519** (Elliptic Curve Diffie-Hellman) для обмена сессионным ключом и **AES-128-CCM** (Counter with CBC-MAC) для аутентифицированного шифрования трафика. Дополнительно предоставляет Ed25519 для цифровых подписей (аутентификация данных, происходящих от конкретного узла) и стриминговое шифрование (AES-128-CTR) для больших потоков данных, где нужна только конфиденциальность без аутентификации. + +Это **один из важнейших модулей** проекта — каждый ETCP-пакет, проходящий между узлами, шифруется и аутентифицируется через этот канал. + +**Ключевые криптографические операции:** +1. **X25519 ECDH** — получение общего секрета из своего приватного и чужого публичного ключа +2. **AES-128-CCM** — аутентифицированное шифрование (confidentiality + integrity): nonce(13) + ciphertext + tag(16) +3. **SHA256-based обфускация pubkey** — скрытие публичного ключа при передаче в INIT-пакетах (XOR с SHA256(salt || peer_pubkey)) +4. **Ed25519** — цифровые подписи данных (one-shot и стриминговая через SHA-512 аккумулятор) +5. **AES-128-CTR** — стриминговое шифрование без аутентификации (bulk data) +6. **CRC32** — дополнительный контроль целостности расшифрованных данных + +--- + +## 2. Как пользоваться + +### 2.1. Типовой сценарий: генерация → обмен → шифрование + +```c +// ── Шаг 1: Загрузка или генерация ключей ── +struct SC_MYKEYS my_keys; + +// Вариант A: сгенерировать новую пару +sc_generate_keypair(&my_keys); + +// Вариант B: загрузить из конфига (hex-строки) +sc_init_local_keys(&my_keys, pubkey_hex, privkey_hex); + +// ── Шаг 2: Инициализация контекста ── +sc_context_t ctx; +sc_init_ctx(&ctx, &my_keys); +// Теперь ctx.session_ready == 0 — ждём установки ключа пира + +// ── Шаг 3: Установка публичного ключа пира (после получения из сети) ── +uint8_t peer_pubkey[SC_PUBKEY_SIZE]; +// ... получить peer_pubkey из INIT-пакета / конфига ... +sc_set_peer_public_key(&ctx, peer_pubkey, SC_PEER_PUBKEY_BIN); // или SC_PEER_PUBKEY_HEX +// Внутри: X25519 ECDH → shared_secret → SHA256(shared_secret || "uTun-v3-session") → session_key +// Теперь ctx.session_ready == 1 + +// ── Шаг 4: Шифрование / дешифрование ── +uint8_t ciphertext[1500]; +size_t ct_len = sizeof(ciphertext); + +// Шифрование: nonce(13) || AES-CCM(plaintext+crc32) || tag(16) +sc_encrypt(&ctx, plaintext, plaintext_len, ciphertext, &ct_len); + +// Дешифрование: проверка тега (аутентификация) → проверка CRC32 → возврат plaintext +uint8_t decrypted[1500]; +size_t pt_len = sizeof(decrypted); +sc_decrypt(&ctx, ciphertext, ct_len, decrypted, &pt_len); +``` + +### 2.2. Обфускация pubkey (INIT-пакеты) + +При отправке INIT-пакета публичный ключ не передаётся в открытом виде. Вместо этого он обфусцируется через XOR с SHA256-хэшем от соли и публичного ключа пира: + +```c +uint8_t salt[SC_PUBKEY_ENC_SALT_SIZE]; // 8 случайных байт +random_bytes(salt, sizeof(salt)); + +uint8_t obfuscated[SC_PUBKEY_SIZE]; // 32 байта +sc_obfuscate_pubkey(salt, peer_pubkey, my_pubkey, obfuscated); + +// Передаётся в пакете: salt(8) || obfuscated_pubkey(32) = 40 байт (SC_PUBKEY_ENC_SIZE) +``` + +**Формат пакета с обфусцированным pubkey:** +- Заголовок и данные шифруются AES-CCM +- Блок `salt(8) + obfuscated_pubkey(32)` добавляется **после зашифрованных данных, без шифрования** + +**Деобфускация на приёмной стороне:** +```c +// Получатель знает salt из пакета и свой публичный ключ +sc_obfuscate_pubkey(salt, my_pubkey, received_obfuscated, decrypted_pubkey); +// XOR обратим: obfuscated XOR SHA256(salt||my_pubkey) = исходный pubkey отправителя +``` + +**Важный нюанс реализации:** в текущей версии `sc_obfuscate_pubkey()` вычисляет два SHA256-хэша (`SHA256(salt||peer_pubkey)` и `SHA256(peer_pubkey||salt)`), но XOR'ит публичный ключ только с первым. Второй хэш вычисляется, но не используется (возможно, задел на будущее). + +### 2.3. Стриминговое шифрование (AES-128-CTR) + +Для больших объёмов данных, где не нужна посылочная аутентификация: + +```c +struct sc_stream_state stream; +sc_stream_init(&ctx, &stream, stream_id); // stream_id — произвольный uint32_t + +sc_stream_xor(&stream, data_chunk1, len1); // XOR in-place (шифрует/дешифрует) +sc_stream_xor(&stream, data_chunk2, len2); + +sc_stream_cleanup(&stream); +``` + +Nonce (12 байт) для CTR выводится из `SHA256(session_key || stream_id_LE || "uTun3-stream")`. + +### 2.4. Ed25519 подписи + +**One-shot:** +```c +uint8_t sig[SC_SIGN_SIZE]; // 64 байта +sc_ed25519_sign(privkey, msg, msg_len, sig); +int ok = sc_ed25519_verify(pubkey, msg, msg_len, sig); // SC_OK при успехе +``` + +**Стриминговая (потоковая) подпись:** SHA-512 аккумулятор + one-shot Ed25519 в финале. Подходит для данных любого размера (память O(1)): +```c +struct sc_stream_sign_state state; + +// Подпись +sc_stream_sign_init(&ctx, &state); +sc_stream_sign_update(&state, chunk1, len1); +sc_stream_sign_update(&state, chunk2, len2); +sc_stream_sign_final(&state, sig, &sig_len); // автоматический cleanup + +// Верификация +sc_stream_sign_verify_init(&state, ed25519_pubkey); +sc_stream_sign_update(&state, chunk1, len1); +sc_stream_sign_update(&state, chunk2, len2); +int result = sc_stream_sign_verify(&state, sig, sig_len); // SC_OK или SC_ERR_AUTH_FAILED +``` + +**Деривация Ed25519-ключа из X25519:** `sc_derive_ed25519_pubkey(x25519_privkey, ed25519_pubkey)` — SHA-512 от X25519-приватного ключа даёт Ed25519-приватный ключ (32 байта), из которого извлекается Ed25519-публичный ключ. Это позволяет одному ключу обслуживать и ECDH, и подписи. + +### 2.5. Деривация node_id + +```c +uint64_t node_id = sc_derive_node_id(private_key); +// SHA256(private_key) → первые 8 байт, старший бит обнулён (0x7FFFFFFFFFFFFFFF) +``` + +### 2.6. SHA256-based транскодирование + +```c +sc_sha_transcode(key, key_len, data, data_len); +// data[i] ^= SHA256(key)[i % 32] — простой XOR с SHA256-хэшем ключа +``` + +--- + +## 3. API + +### 3.1. Константы + +| Константа | Значение | Описание | +|---|---|---| +| `SC_PRIVKEY_SIZE` | 32 | Размер приватного ключа X25519 (байт) | +| `SC_PUBKEY_SIZE` | 32 | Размер публичного ключа X25519 (байт) | +| `SC_HASH_SIZE` | 32 | Размер SHA256-хэша (байт) | +| `SC_NONCE_SIZE` | **13** | Размер nonce для AES-128-CCM. **Ровно 13 байт — требование CCM!** | +| `SC_SHARED_SECRET_SIZE` | 32 | Размер общего секрета X25519 (= SC_HASH_SIZE) | +| `SC_SESSION_KEY_SIZE` | 16 | Размер сессионного ключа AES-128 (байт) | +| `SC_TAG_SIZE` | 16 | Размер аутентификационного тега CCM (байт) | +| `SC_CRC32_SIZE` | 4 | Размер CRC32 в зашифрованном пакете (байт) | +| `SC_PUBKEY_ENC_SALT_SIZE` | 8 | Размер соли для обфускации pubkey (байт) | +| `SC_PUBKEY_ENC_SIZE` | 40 | Полный размер обфусцированного блока: salt(8) + obfuscated(32) | +| `SC_STREAM_NONCE_SIZE` | 12 | Размер nonce для AES-128-CTR (байт) | +| `SC_SIGN_SIZE` | 64 | Размер Ed25519 подписи (байт) | +| `SC_OK` | 0 | Успех | +| `SC_ERR_INVALID_ARG` | -1 | Неверные аргументы | +| `SC_ERR_CRYPTO` | -2 | Криптографическая ошибка (OpenSSL) | +| `SC_ERR_NOT_INITIALIZED` | -3 | Контекст/сессия не инициализирована | +| `SC_ERR_AUTH_FAILED` | -4 | Ошибка аутентификации (неверный тег/подпись) | +| `SC_ERR_CRC_FAILED` | -5 | Ошибка CRC32 (повреждение данных) | +| `SC_PEER_PUBKEY_BIN` | 0 | Флаг: ключ в бинарном формате | +| `SC_PEER_PUBKEY_HEX` | 1 | Флаг: ключ в hex-строке | + +### 3.2. Структуры + +**`struct SC_MYKEYS`** — локальная пара ключей узла: +- `uint8_t private_key[32]` — приватный ключ X25519 +- `uint8_t public_key[32]` — публичный ключ X25519 + +**`struct secure_channel` (sc_context_t)** — контекст защищённого канала между двумя конкретными узлами: +- `pk` — указатель на `SC_MYKEYS` (локальные ключи) +- `peer_public_key[32]` — публичный ключ пира (заполняется после ECDH) +- `session_key[16]` — производный сессионный ключ AES-128 +- `initialized` — флаг: `sc_init_ctx()` вызван +- `peer_key_set` — флаг: `sc_set_peer_public_key()` вызван успешно +- `session_ready` — флаг: можно шифровать/дешифровать +- `tx_counter` — счётчик отправленных пакетов (используется в nonce) +- `rx_counter` — счётчик принятых пакетов + +**`struct sc_stream_state`** — состояние стримингового шифра (AES-128-CTR): +- `ectx` — `EVP_CIPHER_CTX*` (opaque) +- `initialized` — флаг инициализации + +**`struct sc_stream_sign_state`** — состояние стриминговой подписи (Ed25519 + SHA-512): +- `md_ctx` — `EVP_MD_CTX*` (SHA-512 аккумулятор, opaque) +- `pkey` — `EVP_PKEY*` (Ed25519 ключ, opaque) +- `initialized` — флаг инициализации +- `is_sign` — 1 = режим подписи, 0 = режим верификации + +### 3.3. Функции + +#### Инициализация ключей и контекста + +| Функция | Описание | +|---|---| +| `sc_init_ctx(ctx, mykeys)` | Инициализирует контекст канала, привязывает локальные ключи. **Не делает ECDH** — только обнуляет состояние. | +| `sc_generate_keypair(keys)` | Генерирует новую пару X25519 через `EVP_PKEY_keygen`. Случайный приватный ключ. | +| `sc_init_local_keys(mykeys, pub_hex, priv_hex)` | Загружает ключи из hex-строк (обычно из конфига). Валидирует публичный ключ (не all-zero). | +| `sc_set_peer_public_key(ctx, peer_pubkey, mode)` | **Ключевая операция:** выполняет X25519 ECDH, выводит session_key, устанавливает `session_ready=1`. mode: `SC_PEER_PUBKEY_BIN` или `SC_PEER_PUBKEY_HEX`. | +| `sc_compute_public_key_from_private(privkey, pubkey_out)` | Вычисляет публичный ключ из приватного (X25519). | + +**Вывод session_key:** `SHA256(X25519_shared_secret || "uTun-v3-session") → truncate to 16 bytes` + +#### Шифрование / дешифрование + +| Функция | Описание | +|---|---| +| `sc_encrypt(ctx, plaintext, pt_len, ciphertext, &ct_len)` | AES-128-CCM шифрование. Формат вывода: `nonce(13) \|\| AES-CCM(plaintext + CRC32(plaintext)) \|\| tag(16)`. Инкрементирует `tx_counter`. | +| `sc_decrypt(ctx, ciphertext, ct_len, plaintext, &pt_len)` | AES-128-CCM дешифрование. Проверяет CCM-тег → проверяет CRC32. Инкрементирует `rx_counter` при успехе. | + +**Формат nonce:** `SHA256(random_seed(8) || tx_counter(8) || tv_sec(4) || tv_usec(4))` — truncated to 13 bytes. Использует OpenSSL SHA256 (не sha256.h). + +**Проверки при дешифровании:** +1. `ciphertext_len >= SC_NONCE_SIZE + SC_TAG_SIZE + SC_CRC32_SIZE` (минимум 13+16+4 = 33 байта) +2. CCM-тег: несовпадение → `SC_ERR_AUTH_FAILED` (пакет подделан или повреждён) +3. CRC32: несовпадение → `SC_ERR_CRC_FAILED` (повреждение данных после расшифровки) + +#### Обфускация pubkey + +| Функция | Описание | +|---|---| +| `sc_obfuscate_pubkey(salt, peer_pubkey, pubkey, output)` | `output[i] = pubkey[i] ^ SHA256(salt \|\| peer_pubkey)[i]` для i < 32. XOR обратим — повторный вызов с теми же salt/peer_pubkey восстанавливает исходный pubkey. | + +#### Стриминговое шифрование (AES-128-CTR) + +| Функция | Описание | +|---|---| +| `sc_stream_init(ctx, state, stream_id)` | Инициализирует CTR-контекст. Nonce из `SHA256(session_key \|\| stream_id_LE \|\| "uTun3-stream")`. | +| `sc_stream_xor(state, data, len)` | XOR данных с keystream in-place. Нулевая длина — no-op. | +| `sc_stream_cleanup(state)` | Освобождает `EVP_CIPHER_CTX`. | + +#### Ed25519 подписи (one-shot) + +| Функция | Описание | +|---|---| +| `sc_ed25519_sign(privkey[32], msg, msg_len, sig_out[64])` | Подпись сообщения. `privkey` — 32-байтный Ed25519-приватный ключ (НЕ X25519). | +| `sc_ed25519_verify(pubkey[32], msg, msg_len, sig[64])` | Проверка подписи. Возвращает `SC_OK` или `SC_ERR_AUTH_FAILED`. | + +#### Ed25519 подписи (стриминг) + +| Функция | Описание | +|---|---| +| `sc_stream_sign_init(ctx, state)` | Инициализирует подпись. Извлекает Ed25519-ключ из X25519 через `SHA512(x25519_privkey)`. | +| `sc_stream_sign_verify_init(state, ed25519_pubkey)` | Инициализирует верификацию с переданным Ed25519 pubkey. | +| `sc_stream_sign_update(state, data, len)` | SHA-512 update (инкрементальный). Нулевая длина — no-op. | +| `sc_stream_sign_final(state, sig_out, &sig_len)` | Завершает SHA-512 → one-shot Ed25519 подпись хэша → **автоматический cleanup**. `sig_len` всегда 64. | +| `sc_stream_sign_verify(state, sig, sig_len)` | Завершает SHA-512 → one-shot Ed25519 проверка → **автоматический cleanup**. Возвращает `SC_OK` или `SC_ERR_AUTH_FAILED`. | +| `sc_stream_sign_cleanup(state)` | Ручная очистка (если не вызван final/verify). | + +**Потоковая схема:** Ed25519 внутри делает SHA-512 от данных, поэтому для стриминга нужно было бы грузить все данные в память. Вместо этого: `SHA-512(данные)` делается инкрементально через `EVP_DigestUpdate`, затем 64-байтный хэш подписывается one-shot `EVP_DigestSign`. Результат: `Ed25519(SHA-512(данные))` — консистентно с обеих сторон. Память O(1). + +#### Деривация + +| Функция | Описание | +|---|---| +| `sc_derive_ed25519_pubkey(x25519_privkey, ed25519_pubkey_out)` | Из X25519-приватного ключа (32 байта) выводит Ed25519-публичный ключ (32 байта) через `SHA512(x25519_privkey)`. | +| `sc_derive_node_id(private_key)` | `SHA256(private_key)` → первые 8 байт как uint64_t, старший бит обнулён. Уникальный идентификатор узла. | + +#### Утилиты + +| Функция | Описание | +|---|---| +| `sc_sha_transcode(key, key_len, data, data_len)` | `data[i] ^= SHA256(key)[i % 32]`. Простой XOR с хэшем ключа. | + +### 3.4. Зависимости + +| Зависимость | Использование | +|---|---| +| **OpenSSL EVP** (`openssl/evp.h`) | X25519 keygen/derive (`EVP_PKEY`), AES-128-CCM, AES-128-CTR, Ed25519 sign/verify, SHA-512 | +| **OpenSSL SHA** (`openssl/sha.h`) | `SHA256_Init/Update/Final` для построения nonce в `sc_build_nonce()` | +| **`../lib/sha256.h`** (`sc_sha256_*`) | SHA256 для вывода session_key, stream nonce, obfuscate pubkey, sha_transcode, node_id | +| **`../lib/debug_config.h`** | `DEBUG_ERROR`, `DEBUG_WARN`, `DEBUG_INFO`, `DEBUG_DEBUG` (категория `DEBUG_CATEGORY_CRYPTO`) | +| **`../lib/platform_compat.h`** | `random_bytes()` (для соли и random seed), `utun_gettimeofday()` (для nonce) | +| **`crc32.h`** (`crc32_calc`) | Контроль целостности в шифрованных пакетах | + +### 3.5. Отсутствующие функции + +- **`sc_rekey()`** упоминается в AGENTS.md, но **не реализована** в текущей версии. Ротация сессионного ключа не поддерживается — после ECDH ключ фиксирован на всё время сессии. + +--- + +## 4. Формат зашифрованного пакета + +``` +┌──────────────┬─────────────────────────────────────┬──────────────┐ +│ nonce (13) │ AES-CCM(plaintext || CRC32(pt)) │ tag (16) │ +│ 13 bytes │ pt_len + 4 bytes │ 16 bytes │ +└──────────────┴─────────────────────────────────────┴──────────────┘ +``` + +- **nonce:** 13 байт, уникальный для каждого пакета (SHA256 от seed + counter + времени) +- **ciphertext:** AES-128-CCM in-place шифрование `plaintext || CRC32(plaintext)` +- **tag:** 16 байт, аутентификационный тег CCM (обеспечивает integrity + authenticity) + +**Порядок проверки при дешифровании:** CCM-тег → если ОК, извлечение plaintext → CRC32 проверка. + +### Формат INIT-пакета + +``` +┌───────────────────────────────────────────────────────┬──────────────────────────────────────┐ +│ AES-CCM(header(3) || data) + auth tag │ salt(8) || obfuscated_pubkey(32) │ +│ Зашифрованная часть (включает auth tag) │ Незашифрованная часть (SC_PUBKEY_ENC_SIZE=40) │ +└───────────────────────────────────────────────────────┴──────────────────────────────────────┘ +``` + +- Зашифрованная часть: ETCP-заголовок (timestamp uint16 + flag_up uint8) + данные, всё зашифровано AES-128-CCM с auth tag +- Незашифрованная часть: `salt(8) + pubkey XOR SHA256(salt || expected_peer_pubkey)` +- Получатель восстанавливает pubkey: `obfuscated XOR SHA256(salt || my_pubkey) = sender_pubkey` (XOR обратим) + +--- + +## 5. Состояния и flow + +``` +┌─────────────┐ sc_init_ctx() ┌──────────────┐ +│ Не создан │ ──────────────────────→│ initialized │ +└─────────────┘ │ session_ready=0│ + └──────┬───────┘ + │ sc_set_peer_public_key() + │ (X25519 ECDH → session_key) + ↓ + ┌──────────────┐ + │ session_ready=1│ + │ tx_counter=0 │ + │ rx_counter=0 │ + └──────┬───────┘ + │ sc_encrypt() / sc_decrypt() + ↓ + tx_counter++, rx_counter++ +``` + +- `session_ready` становится 1 только после успешного ECDH через `sc_set_peer_public_key()` +- Контекст привязан к конкретному пиру (peer_public_key фиксирован) +- Для смены пира нужен новый контекст +- Счётчики не синхронизируются между сторонами — каждая сторона считает свои tx/rx независимо + +--- + +## 6. Примечания по безопасности + +- **Nonce должен быть уникальным** для каждого шифрования с одним ключом. Здесь nonce строится из `SHA256(random_seed || counter || время)`. При случайном seed и монотонном counter'е коллизии крайне маловероятны. +- **CRC32 — не криптографическая защита.** Он дополнительно ловит битовые ошибки после расшифровки (belt-and-suspenders), но основная защита целостности — CCM-тег. +- **Обфускация pubkey — не шифрование.** Это XOR с детерминированным хэшем, обратима для любого, кто знает salt и второй pubkey. Защищает от пассивного наблюдения (не видно публичный ключ в открытом виде), но не от активной подмены. +- **OpenSSL EVP API** используется для всех криптоопераций (реализации FIPS-сертифицированы при использовании FIPS-провайдера). diff --git a/src/stcp_client_doc.md b/src/stcp_client_doc.md new file mode 100644 index 00000000..1a91fdb9 --- /dev/null +++ b/src/stcp_client_doc.md @@ -0,0 +1,55 @@ +# STCP Client + +## 1. Назначение +TCP-клиент протокола STCP (Streaming TCP). Устанавливает TCP-соединение с STCP-сервером, выполняет X25519-handshake с передачей обфусцированных публичных ключей, согласует сессионные ключи через ECDH, после чего переходит в режим обмена данными (DATA). Каждое сообщение шифруется потоковым XOR-шифром и проверяется CRC32. Является нижним уровнем STCP-стека — поверх него работает `stcp_link`. + +## 2. Как пользоваться + +### Типовой сценарий (клиент): +```c +stcp_ready_cb on_ready = (stcp_ready_cb)my_ready_handler; +stcp_close_cb on_close = (stcp_close_cb)my_close_handler; + +struct stcp_client *cli = stcp_client_connect( + ua, addr, port, &my_keys, peer_pubkey_bin, + on_ready, ready_ctx, on_close, close_ctx +); +// После handshake — on_ready. +// Соединение доступно через stcp_client_get_conn(cli). +// Tx: пакеты в conn->tx_queue → tx_cb шифрует и отправляет. +// Rx: пакеты попадают в conn->rx_queue. +// Завершение: stcp_client_destroy(cli). +``` + +### Ключевые нюансы +- **Handshake**: клиент посылает salt(8) + обфусцированный pubkey(XOR с SHA256(salt||server_pubkey), 32) + padding_size(2) + CRC32(4), зашифрованные stream_xor, плюс padding (8+ байт). Сервер присылает аналогичный ответ со статусом. После проверки CRC — переход в `STCP_STATE_DATA`. +- **Стрим-шифр**: используется `sc_stream_xor` с начальным nonce = `SHA256(session_key)`. Два стрима: `STCP_STREAM_CLIENT_SEND` (клиент→сервер) и `STCP_STREAM_SERVER_SEND` (сервер→клиент) — инициализируются в `client_derive_session`. +- **Фрейминг в DATA**: каждое сообщение — `len(2 LE)` + данные + `CRC32(4)`. CRC считается от данных, затем данные+CRC шифруются stream_xor. +- **Очереди**: `tx_queue` через callback `client_tx_queue_cb` шифрует каждое сообщение и отправляет. `rx_queue` получает расшифрованные сообщения для вышележащего слоя. +- **Асинхронность**: все операции через `UASYNC` — сокет в неблокирующем режиме. Поддержка частичной отправки (send_offset) и EAGAIN/WOULDBLOCK. +- **Обработка ошибок**: CRC mismatch, ошибки шифрования, сетевые ошибки — все ведут к `client_do_close` с кодом ошибки. + +## 3. API + +### Структура `struct stcp_client` +Обёртка над `struct stcp_conn`. Хранит `peer_pubkey`, callback `ready_cb`, ссылку на `UASYNC`. + +### `stcp_client_connect()` +Создаёт TCP-сокет, подключается (асинхронно через `EINPROGRESS`), регистрирует write-коллбэк `client_connect_write_cb`. При успешном соединении: проверяет `SO_ERROR`, выводит сессионные ключи через ECDH (`client_derive_session`), отправляет handshake (`client_send_handshake`). + +### `stcp_client_destroy()` +Закрывает соединение через `client_do_close`, освобождает `stcp_conn`, освобождает память. + +### `stcp_client_get_conn()` +Возвращает внутренний `struct stcp_conn *` для доступа к сокету и очередям. + +### Коллбэки +- **`stcp_ready_cb`**: вызывается при успешном handshake (переход в `STCP_STATE_DATA`). +- **`stcp_close_cb`**: вызывается при закрытии или ошибке соединения, получает код ошибки. + +### Внутренние функции +- **`client_derive_session()`** — вычисляет session_key через ECDH, инициализирует stream_send/stream_recv. +- **`client_send_handshake()`** — формирует и отправляет handshake-пакет (salt + обфусцированный pubkey + зашифрованный padding+CRC + padding). +- **`process_server_response()`** — деобфусцирует pubkey сервера, расшифровывает и проверяет CRC ответа сервера. +- **`stcp_conn_process_recv()`** — конечный автомат разбора входящих данных: в HS-состоянии ждёт полный ответ, в DATA — разбирает фреймы (len + data + CRC). +- **`encrypt_and_crc()` / `decrypt_and_check()`** — шифрование/расшифровка фрейма с CRC32 (stream_xor + проверка целостности). diff --git a/src/stcp_doc.md b/src/stcp_doc.md new file mode 100644 index 00000000..36a72b5a --- /dev/null +++ b/src/stcp_doc.md @@ -0,0 +1,52 @@ +# STCP (Secure TCP) — общая часть соединения + +## 1. Назначение + +STCP — потоковый TCP-протокол с X25519-ключеобменом и потоковым AES-CTR шифрованием (stream cipher). Обеспечивает безопасное TCP-соединение: клиент подключается, сервер принимает, выполняется ECDH-рукопожатие с обфускацией публичных ключей, после чего трафик шифруется AES-CTR с контрольными суммами CRC32. + +Модуль `stcp.c/stcp.h` содержит общие структуры, константы и базовый жизненный цикл соединения `struct stcp_conn`, используемый как клиентской (`stcp_client`), так и серверной (`stcp_server`) сторонами. + +## 2. Как пользоваться + +Типовая схема: вышестоящий код создаёт `struct stcp_conn` (сервер через `stcp_server_create`, клиент — через `stcp_client_connect`), устанавливает очереди приёма/передачи (`stcp_conn_set_tx_queue`, `stcp_conn_set_rx_queue`), коллбэк закрытия (`stcp_conn_set_on_close`). После рукопожатия (`STCP_STATE_DATA`) сообщения передаются через `rx_queue`/`tx_queue`: отправка — через ll_queue с коллбэком `tx_cb`, приём — данные раскладываются в `rx_queue`. + +Потоковое шифрование (`sc_stream_state`) не требует буферизации целых сообщений — XOR применяется побайтово к потоку, поэтому порядок отправки/приёма критичен. Для каждого направления создаётся отдельный stream (`stream_send`, `stream_recv`). + +Ключевые нюансы: +- Размер сообщения — `uint16_t` в префиксе, макс. 65535 байт. +- Каждое сообщение шифруется: 2 байта длины + данные + 4 байта CRC32. +- CRC32 проверяется после расшифровки для детектирования повреждений. +- При разрыве соединения или ошибке вызывается `on_close`. + +## 3. API + +### Состояния +- `STCP_STATE_INIT` — начальное (на клиенте) +- `STCP_STATE_HS_SERVER_WAIT` — сервер ждёт рукопожатия от клиента +- `STCP_STATE_DATA` — активный обмен данными (шифрованный) +- `STCP_STATE_CLOSED` / `STCP_STATE_ERROR` — завершение + +### stcp_conn — структура соединения +| Поле | Описание | +|------|----------| +| `sock` | TCP-сокет | +| `ua` | UASYNC event loop | +| `state` | Текущее состояние (enum stcp_state) | +| `is_server` | 1 = серверная сторона | +| `session_key[SC_SESSION_KEY_SIZE]` | Ключ X25519 ECDH (AES-128) | +| `stream_send` / `stream_recv` | Состояния потокового AES-CTR для отправки и приёма | +| `my_keys` | Локальные ключи X25519 | +| `peer_pubkey` | Публичный ключ пира (после рукопожатия) | +| `rx_queue` / `tx_queue` | ll_queue для приёма/отправки сообщений | +| `tx_cb` | Коллбэк очереди tx_queue | +| `recv_buf` | Буфер приёма TCP (динамический, до 128KB) | +| `send_buf` | Буфер отправки (для досыла при EAGAIN) | +| `hs_expected_len` / `hs_key_processed` | Состояние рукопожатия | +| `on_ready` | Вызывается после завершения рукопожатия | +| `on_close` | Вызывается при закрытии соединения | + +### Функции +- `stcp_conn_set_tx_queue(c, q)` — установить очередь отправки, привязывает `tx_cb` как коллбэк +- `stcp_conn_set_rx_queue(c, q)` — установить очередь приёма (в неё кладутся расшифрованные сообщения) +- `stcp_conn_set_on_close(c, cb, arg)` — установить коллбэк закрытия (err=0 — норма, иначе код ошибки) +- `stcp_conn_free(c)` — освободить сокет, буферы, очистить stream-ы. Если `allocated=1` — освободить и саму структуру diff --git a/src/stcp_link_doc.md b/src/stcp_link_doc.md new file mode 100644 index 00000000..8c0de8f4 --- /dev/null +++ b/src/stcp_link_doc.md @@ -0,0 +1,73 @@ +# STCP Link + +## 1. Назначение +Прослойка-мост между STCP-транспортом (TCP с потоковым шифрованием) и ETCP-уровнем (маршрутизация, балансировка, управление соединениями). Инкапсулирует создание STCP-сервера или клиента, настройку очередей приёма/передачи и диспетчеризацию входящих пакетов в `api_bindings` экземпляра `UTUN_INSTANCE`. По сути — STCP как транспортный линк для ETCP. + +## 2. Как пользоваться + +### Сервер (приём входящих соединений): +```c +struct stcp_link_config cfg = { + .ua = ua, .my_keys = &my_keys, .inst = utun_instance, + .peer_pubkey = NULL, .remote_addr = NULL, +}; +struct stcp_server *srv = stcp_server_listen(&cfg, port, on_link_cb, NULL); +// При входящем соединении вызывается on_link_cb со stcp_link. +// Остановка: stcp_link_server_destroy(srv). +``` + +### Клиент (исходящее соединение): +```c +struct stcp_link_config cfg = { + .ua = ua, .my_keys = &my_keys, .inst = utun_instance, + .peer_pubkey = server_pubkey, .peer_pubkey_mode = 0, + .remote_addr = (struct sockaddr_storage *)&addr, .remote_port = port, +}; +struct stcp_link *link = stcp_link_connect(&cfg); +// По готовности: stcp_link_is_ready(link) == 1, или через stcp_link_set_on_ready. +// Отправка: stcp_link_send(link, data, len). +// Закрытие: stcp_link_close(link). +``` + +### Ключевые нюансы +- **ETCP_CONN**: каждый `stcp_link` содержит встроенный `struct ETCP_CONN` (`etcp_conn`), который используется для диспетчеризации через `inst->api_bindings`. Поле `transport_link` служит обратным указателем на `stcp_link`. +- **Диспетчеризация RX**: входящие пакеты из `rx_queue` обрабатываются коллбэком `link_rx_cb`, который по первому байту (`id`) демультиплексирует пакет в соответствующий обработчик `api_bindings.callbacks[id]`. Если конкретный обработчик не найден — пакет уходит в `callbacks[0]`. +- **Очередь TX**: `tx_queue` настраивается с `queue_set_waiter_defer(1)` — отправка через пороговое ожидание (backpressure). Очередь связывается с `stcp_conn` через `stcp_conn_set_tx_queue`. +- **Разница сервер/клиент**: сервер при accept создаёт `stcp_link` в коллбэке `server_accept_cb`, клиент — при ready в `client_ready_cb`. Настройка очередей идентична. +- **Закрытие**: `stcp_link_close` корректно очищает `transit_queues` (очереди ETCP-трафика в пути), `rx_queue`, `tx_queue`, уничтожает `stcp_conn` (сервер) или `stcp_client` (клиент). + +## 3. API + +### Структура `struct stcp_link_config` +Конфигурация для создания линка. Содержит: +- `ua` — экземпляр UASYNC (один на поток) +- `my_keys` — ключи этой стороны для ECDH +- `inst` — UTUN_INSTANCE для диспетчеризации через api_bindings +- `peer_pubkey` / `peer_pubkey_mode` — публичный ключ пира (binary или hex) +- `remote_addr` / `remote_port` — адрес пира (только для клиента) + +### Структура `struct stcp_link` (opaque) +Содержит ссылки на `stcp_client`/`stcp_conn`, очереди RX/TX, встроенный `ETCP_CONN`, флаг `ready`, коллбэки on_ready/on_close. + +### `stcp_server_listen()` +Оборачивает `stcp_server_create()`, передавая `server_accept_cb` в качестве коллбэка при входящем соединении. При accept создаёт `stcp_link`, настраивает очереди и вызывает пользовательский `on_link`. + +### `stcp_link_connect()` +Создаёт `stcp_link`, подготавливает pubkey пира (конвертация hex→bin при необходимости), вызывает `stcp_client_connect()`. При успешном handshake (коллбэк `client_ready_cb`) настраивает очереди RX/TX и вызывает `on_ready_cb`. + +### `stcp_link_close()` +Полная очистка: освобождает `transit_queues` (с отменой waiter'ов), `rx_queue`, `tx_queue`, уничтожает `stcp_conn` или `stcp_client`. + +### `stcp_link_send()` +Отправляет сырые данные через STCP-линк: создаёт `ll_entry` с данными, кладёт в `conn->tx_queue`. Шифруется и отправляется коллбэком `tx_cb` из stcp_client/stcp_server. + +### `stcp_link_is_ready()` +Возвращает 1 если линк готов к передаче данных (handshake завершён). + +### `stcp_link_get_etcp_conn()` +Возвращает `struct ETCP_CONN *` для передачи в ETCP-уровень (маршрутизатор, балансировщик и т.д.). + +### Коллбэки +- **`stcp_link_set_on_ready()`** — вызывается когда линк переходит в состояние готовности. +- **`stcp_link_set_on_close()`** — вызывается при закрытии линка. +- **`stcp_server_on_link_cb`** — коллбэк сервера при новом входящем соединении. diff --git a/src/stcp_server_doc.md b/src/stcp_server_doc.md new file mode 100644 index 00000000..1af4706a --- /dev/null +++ b/src/stcp_server_doc.md @@ -0,0 +1,44 @@ +# STCP Server — приём и рукопожатие соединений + +## 1. Назначение + +STCP Server — серверная сторона протокола STCP. Создаёт слушающий TCP-сокет на указанном порту, принимает входящие соединения, выполняет серверную часть X25519-рукопожатия и поднимает зашифрованный канал AES-CTR для обмена сообщениями. + +Использует общую структуру `struct stcp_conn` из `stcp.h`. + +## 2. Как пользоваться + +```c +struct stcp_server *srv = stcp_server_create(ua, port, &my_keys, + on_connect_cb, conn_arg, // вызывается после успешного рукопожатия + on_close_cb, close_arg); // вызывается при закрытии/ошибке + +// ... use ... + +stcp_server_destroy(srv); +``` + +После завершения рукопожатия соединение переходит в `STCP_STATE_DATA`, вызывается `connect_cb`. Вышестоящий код может слать сообщения через `tx_queue` — они будут зашифрованы с CRC32 и отправлены. Входящие расшифрованные сообщения попадают в `rx_queue`. + +Ключевые нюансы: +- Сервер принимает клиентский pubkey+salt (обфусцированный), извлекает pubkey через `sc_obfuscate_pubkey`, выполняет ECDH. +- Отправляет ответ: статус OK + padding + CRC32, всё зашифровано AES-CTR. +- После рукопожатая трафик шифруется потоковым AES-CTR (XOR), для каждого направления — свой stream. +- Рукопожатие обрабатывается в два этапа: `process_client_handshake` (извлечение ключа, дешифровка) → `finish_client_handshake` (отправка ответа, переход в DATA). + +## 3. API + +### stcp_server — внутренняя структура +Слушающий сокет, ключи сервера, коллбэки `connect_cb` / `close_cb`. Не экспортируется, создаётся только через `stcp_server_create`. + +### Функции +- `stcp_server_create(ua, port, keys, connect_cb, arg, close_cb, close_arg)` — создать сервер: открыть TCP-сокет, bind к порту (IPV4, INADDR_ANY), listen(16), зарегистрировать accept-коллбэк. При приёме соединения создаёт `struct stcp_conn` в состоянии `STCP_STATE_HS_SERVER_WAIT`. +- `stcp_server_destroy(srv)` — закрыть слушающий сокет, освободить память. + +### Протокол рукопожатия (серверная сторона) +1. Сервер читает `SC_PUBKEY_ENC_SIZE + STCP_HS_ENC_CLIENT` байт (pubkey+salt + зашифрованные padding_size + CRC32). +2. Деобфусцирует клиентский pubkey (`sc_obfuscate_pubkey`, salt XOR с SHA256(salt||server_pubkey)). +3. Выполняет ECDH (`sc_init_ctx` + `sc_set_peer_public_key`) → получает `session_key`, инициализирует `stream_send`/`stream_recv`. +4. Расшифровывает padding_size, проверяет CRC32, ожидает padding-байты. +5. Формирует ответ: salt2(8) + обфусцированный серверный pubkey(32) + status=OK(1) + padding_size(2) + CRC32(4) + padding(8). Всё кроме salt2 зашифровано `sc_stream_xor`. +6. Переходит в `STCP_STATE_DATA`, вызывает `on_ready` (== `connect_cb`). diff --git a/src/topo_group_doc.md b/src/topo_group_doc.md new file mode 100644 index 00000000..24bf56c2 --- /dev/null +++ b/src/topo_group_doc.md @@ -0,0 +1,305 @@ +# topo_group + +## 1. Назначение + +BGP-подобный обмен топологией узлов между пирами uTun. Каждый узел хранит полную таблицу известных узлов и при подключении нового пира синхронизирует её с ним. При изменении информации об узле или обнаружении его недоступности — изменения распространяются всем пирам (broadcast NODEINFO/WITHDRAW). + +Модуль поддерживает **изолированные группы** — разные «пространства имён» узлов с разной семантикой: +- **TOPO_GROUP_TYPE_UTUN (1)** — VPN-сеть: обмен маршрутами (подсети) между узлами, NAT-детекция, проверка связности +- **TOPO_GROUP_TYPE_CHAT (2)** — чат-группа: только узлы, без подсетей, персистентность в SQLite (membership) + +Группа по умолчанию (UTUN) создаётся автоматически при `topo_groups_init()`. Дополнительные группы создаются через `topo_groups_create_group()` (для chatgui). Узлы разных групп изолированы: peer из utun-группы не видит узлы чат-группы и наоборот. + +Помимо обмена топологией, модуль выполняет: +- **NAT-детекцию** (NAT_INFO / NAT_CHECK_REQ): клиент запрашивает проверку своего NAT у сервера; сервер через третий узел пингует клиента и сообщает тип NAT (EIM/STRICT) +- **Поиск оптимального маршрута** до узла (`topo_group_find_conn_for_node`) — по min hop_count среди live-путей (ETCP соединений) +- **Зондирование связности** — для новых узлов запускает `route_connectivity_probe_node()` +- **Персистентность в SQLite** — синхронизация узлов и membership в БД (таблицы nodes, node_addresses, peers_*) + +## 2. Как пользоваться + +### 2.1. Инициализация (при старте utun) + +```c +struct TOPO_GROUPS* g = topo_groups_init(instance); // создаёт контейнер + utun-группу по умолчанию +``` +Здесь же: +- `etcp_bind(instance, ETCP_ID_TOPO_ENTRY, topo_group_receive_cbk)` — регистрирует приёмник пакетов топологии +- `etcp_add_new_conn_cbk(instance, topo_group_etcp_conn_cbk, NULL)` — подписывается на события on_up/on_down всех новых ETCP-соединений +- Открывается SQLite (если указан `db_path` в конфиге) + +### 2.2. Новое ETCP-соединение + +При ETCP on_up автоматически вызывается `topo_group_new_conn(group, conn)`: +1. Добавляет conn в `senders_list` (через `topo_group_add_to_senders`) +2. Запускает NAT check для всех линков всех соединений +3. Отправляет `TOPO_SUBCMD_REQUEST_TABLE` — запрос полной таблицы + +При приёме `TOPO_SUBCMD_REQUEST_TABLE` peer отправляет: +- NODEINFO c локальным узлом (local_node) +- Full table sync — все известные узлы, кроме тех, чей путь уже включает этого пира (защита от петель) +- `TOPO_SUBCMD_TABLE_COMPLETE` — метка завершения начальной синхронизации + +### 2.3. NODEINFO (обновление/создание узла) + +`topo_group_process_nodeinfo(group, from, data, len)`: +1. Проверка соответствия типа группы по флагу `TOPO_FLAG_SEND_SUBNETS` +2. Проверка `hop_count < MAX_HOPS(16)` +3. Проверка версии узла — если версия не новее (stale), только обновляет пути (hop_list) и выходит +4. Десериализация wire-формата → обновление/создание `TOPO_NODEQ` +5. Добавление пути (`topo_group_add_path`) через текущее соединение +6. Вставка в `routing` (`route_insert`) — только для UTUN-групп +7. Сохранение в SQLite (если есть) +8. Callback `node_updated_cb` (для chatgui member_sync) +9. Broadcast всем пирам, кроме hop_list (защита от петель) +10. Запуск зондирования связности для новых узлов с адресами + +### 2.4. WITHDRAW (удаление узла) + +`topo_group_process_withdraw(group, sender, data, len)`: +1. Поиск узла по `node_id` +2. `topo_group_remove_path_by_hop(nq, wd_source)` — удаление всех путей, содержащих wd_source в hop_list +3. Если путей не осталось (ret == 1): удаление из routing, уведомление control_server, broadcast withdraw дальше +4. Если группа CHAT — удаление member из SQLite + +### 2.5. Отключение соединения + +При ETCP on_down вызывается `topo_group_remove_conn(group, conn)`: +1. Отмена pending ping-запросов для этого conn +2. Для всех узлов с путями через этот conn — `topo_group_remove_path`. Если узел стал полностью недоступен (нет путей) — удаляется из routing и nodes, с broadcast withdraw +3. Удаление conn из `senders_list` + +### 2.6. Поиск пути до узла + +`topo_group_find_conn_for_node(group, node_id)` — перебирает `path`'ы узла, выбирает с минимальным `hop_count`. Предпочитает live-пути (links_up > 0). Если live-путей нет, fallback на любой путь с минимальным hop_count. + +Интенсивно используется `etcp_router`: +- При маршрутизации пакетов к удалённому узлу (`src/etcp_router.c:262`) +- При поиске next-hop для multihop соединений + +### 2.7. NAT-детекция + +1. При новом соединении вызывается `topo_group_start_link_nat_check` для каждого линка +2. Сервер ищет «третий узел» через `topo_group_find_third_node` — произвольный conn кроме проверяемого +3. Отправляет `PING_REQ` через третий узел на IP:port линка проверяемого клиента +4. По результату пинга: `NAT_TYPE_EIM` (если успех) или `NAT_TYPE_STRICT` +5. Результат сохраняется в `link->nat_type` и отправляется клиенту через `topo_group_send_nat_info` +6. Клиент обновляет свой `local_node` через `topo_group_handle_nat_info` и broadcast-ит изменения + +### 2.8. Широковещательная рассылка + +- **NODEINFO**: при поступлении новой/обновлённой информации об узле — отправляется всем соседям, кроме тех, чей node_id уже есть в hop_list (защита от петель) +- **WITHDRAW**: при удалении узла — отправляется всем соседям, кроме отправителя + +### 2.9. Изоляция групп + +При приёме NODEINFO проверяется флаг `TOPO_FLAG_SEND_SUBNETS`: +- UTUN-группа ожидает флаг = 1 (с подсетями) +- CHAT-группа ожидает флаг = 0 (без подсетей) +- При несовпадении отправляется `TOPO_SUBCMD_ERR_GROUP_MISMATCH` и пакет игнорируется + +### 2.10. Alien-узлы + +Узлы, помеченные `alien=1`, не участвуют в обмене маршрутами: при `topo_group_new_conn` для alien-пира обмен пропускается. + +## 3. API + +### 3.1. Структуры + +| Структура | Назначение | +|---|---| +| `TOPO_GROUPS` | Контейнер всех групп топологии экземпляра. Хранит `group_list` (ll_queue), memory_pool'ы для сериализации, sqlite3, callback `node_updated_cb` | +| `TOPO_GROUP` | Одна группа топологии. `group_id`, `group_type`, `nodes` (ll_queue узлов), `senders_list` (ll_queue conn'ов), `local_node` (TOPO_NODEQ) | +| `TOPO_GROUP_CONN_ITEM` | Элемент списка senders: conn + ll_entry — связка с senders_list | +| `TOPOMSG_NODEINFO_PKT` | Wire-формат пакета NODEINFO: cmd + subcmd + TOPOMSG_NODE + переменная часть | +| `TOPOMSG_WITHDRAW_PKT` | Wire-формат WITHDRAW: node_id (удаляемый) + wd_source (инициатор) | +| `TOPOMSG_NAT_INFO` | Wire-формат NAT_INFO: socket_id, IP, port (network byte order), nat_type | +| `TOPOMSG_NAT_CHECK_REQ` | Запрос проверки NAT от клиента к серверу: socket_id + interface IP:port | +| `TOPOMSG_TABLE_REQ` | Wire-формат запроса/завершения таблицы: cmd + subcmd | +| `TOPOMSG_ERR_GROUP_MISMATCH` | Wire-формат ошибки несоответствия типа группы: expected_type + received_flags | +| `nat_check_arg` | Контекст callback NAT-проверки: link + nat_ip + nat_port | + +### 3.2. Константы + +| Константа | Значение | Назначение | +|---|---|---| +| `ETCP_ID_TOPO_ENTRY` | 0x01 | ETCP ID для пакетов топологии | +| `TOPO_SUBCMD_NODEINFO` | 0x04 | Полная информация об узле + подсети | +| `TOPO_SUBCMD_REQUEST_TABLE` | 0x05 | Запрос полной таблицы | +| `TOPO_SUBCMD_WITHDRAW` | 0x06 | Узел стал недоступен | +| `TOPO_SUBCMD_NAT_INFO` | 0x09 | Информация о типе NAT клиента | +| `TOPO_SUBCMD_NAT_CHECK_REQ` | 0x0A | Запрос проверки NAT для сокета | +| `TOPO_SUBCMD_TABLE_COMPLETE` | 0x0B | Завершение начальной синхронизации | +| `TOPO_SUBCMD_ERR_GROUP_MISMATCH` | 0x0C | Ошибка несоответствия типа группы | +| `MAX_HOPS` | 16 | Максимальная длина hop-листа | +| `BGP_NODES_HASH_SIZE` | 256 | Размер хеш-таблицы nodes | +| `TOPO_GROUP_UTUN` | 0x8000000000000000ULL | group_id по умолчанию для utun-группы | + +### 3.3. Функции инициализации/завершения + +| Функция | Назначение | +|---|---| +| `topo_groups_init(instance)` | Создаёт TOPO_GROUPS, memory_pool'ы, открывает SQLite, создаёт utun-группу по умолчанию, регистрирует ETCP-коллбэки | +| `topo_groups_destroy(instance)` | Освобождает все группы, memory_pool'ы, закрывает SQLite; снимает ETCP-регистрацию | +| `topo_group_create(instance, group_id, group_type)` | Создаёт TOPO_GROUP: nodes (hash queue), senders_list, local_node; вызывается из topo_groups_init / topo_groups_create_group | +| `topo_group_destroy(group)` | Освобождает senders_list, local_node, nodes очереди; узлы должны быть удалены заранее | +| `topo_groups_create_group(g, group_id, group_type, channel_id)` | Создаёт новую группу и добавляет в group_list; для CHAT устанавливает channel_id | +| `topo_groups_find(g, group_id)` | Поиск группы по group_id через хеш-индекс group_list | +| `topo_groups_get_default(g)` | Возвращает utun-группу по умолчанию (group_id=TOPO_GROUP_UTUN) | + +### 3.4. Функции жизненного цикла соединения + +| Функция | Назначение | +|---|---| +| `topo_group_new_conn(group, conn)` | Добавляет conn в senders_list, запускает NAT check для всех линков, отправляет REQUEST_TABLE; alien-пиры пропускаются | +| `topo_group_remove_conn(group, conn)` | Отменяет NAT check и ping pending, удаляет все пути через conn, удаляет недоступные узлы, broadcast withdraw, убирает conn из senders_list | +| `topo_group_etcp_conn_cbk(conn, arg)` | Callback при создании нового ETCP-соединения: подписывается на on_up/on_down через etcp_conn_add_up_cbk/down_cbk | +| `topo_group_on_conn_up(conn, arg)` | При on_up: вызывает topo_group_new_conn | +| `topo_group_on_conn_down(conn, arg)` | При on_down: вызывает topo_group_remove_conn | + +### 3.5. Функции обработки пакетов + +| Функция | Назначение | +|---|---| +| `topo_group_receive_cbk(from_conn, entry)` | Точка входа приёма пакетов топологии: диспетчеризация по subcmd (NODEINFO/WITHDRAW/REQUEST_TABLE/PING_REQ/PING_RESP/NAT_INFO/NAT_CHECK_REQ/TABLE_COMPLETE/ERR_GROUP_MISMATCH) | +| `topo_group_process_nodeinfo(group, from, data, len)` | Обработка NODEINFO: проверка группы/версии/hops, десериализация, обновление/создание TOPO_NODEQ, пути, routing, SQLite, broadcast, зондирование связности | +| `topo_group_process_withdraw(group, sender, data, len)` | Обработка WITHDRAW: удаление путей с wd_source, если узел недоступен — удаление из routing, broadcast | +| `topo_group_handle_request_table(group, conn)` | Обработка REQUEST_TABLE: отправляет NODEINFO (local_node) + full table sync + TABLE_COMPLETE | +| `topo_group_handle_nat_info(group, from_conn, data, len)` | Обработка NAT_INFO: обновляет nat_type в local_node (TOPO_SOCKMETA4), ETCP_SOCKET, при изменениях broadcast NODEINFO | +| `topo_group_handle_nat_check_req(group, from_conn, data, len)` | Обработка NAT_CHECK_REQ от клиента: находит линк и запускает NAT-проверку | + +### 3.6. Функции поиска + +| Функция | Назначение | +|---|---| +| `topo_group_find_conn_for_node(group, node_id)` | Поиск оптимального ETCP_CONN до узла: min hop_count, предпочтение live-путям | +| `topo_group_find_third_node(group, exclude)` | Поиск произвольного conn'а (не exclude) для использования как посредника при NAT-детекции; исключает private/local conn'ы | + +### 3.7. Функции broadcast + +| Функция | Назначение | +|---|---| +| `topo_group_send_nodeinfo(group, node, conn)` | Сериализует TOPO_NODEQ и отправляет NODEINFO одному conn | +| `topo_group_send_withdraw(group, node_id)` | Broadcast WITHDRAW всем пирам (without exclude) | +| `topo_group_broadcast_withdraw(group, node_id, wd_source, exclude)` | Рассылает WITHDRAW всем conn в senders_list, кроме exclude | +| `topo_group_send_full_table(group, conn)` | Отправляет все известные узлы (кроме тех, чей путь включает target) указанному conn | +| `topo_group_send_table_request(group, conn)` | Отправляет запрос полной таблицы (TOPO_SUBCMD_REQUEST_TABLE) | +| `topo_group_send_table_complete(group, conn)` | Отправляет метку завершения синхронизации (TOPO_SUBCMD_TABLE_COMPLETE) | +| `topo_group_send_err_group_mismatch(group, conn, expected_type, received_flags)` | Отправляет ошибку несоответствия типа группы | + +### 3.8. Функции NAT + +| Функция | Назначение | +|---|---| +| `topo_group_send_nat_info(conn, socket_id, nat_ip, nat_port, nat_type)` | Отправляет клиенту информацию о его NAT (результат проверки) | +| `topo_group_send_nat_check_req(conn, socket_id)` | Отправляет серверу запрос на проверку NAT для указанного socket_id | +| `topo_group_start_link_nat_check(group, link)` | Запускает NAT-проверку для одного линка через третий узел; проверяет is_local_subnet, пропускает private/local линки | +| `topo_group_request_nat_check_all(group)` | Запускает NAT check для всех линков всех соединений | +| `topo_group_set_nat_check_local(group, allow)` | Разрешить/запретить NAT check для локальных подсетей (127.x, 10.x, 192.168.x, 172.16-31.x) | + +### 3.9. Функции путей + +| Функция | Назначение | +|---|---| +| `topo_group_add_path(nq, conn, hop_list, hop_count)` | Создаёт TOPO_NODEPATH для узла (conn + hop_list) и добавляет в paths | +| `topo_group_remove_path(nq, conn)` | Удаляет путь через указанный conn; возвращает 1 если путей не осталось (unreachable) | +| `topo_group_remove_path_by_hop(nq, wd_source)` | Удаляет все пути, содержащие wd_source в hop_list (используется в WITHDRAW) | +| `topo_group_add_to_senders(group, conn)` | Добавляет conn в senders_list (с проверкой дубликатов) | +| `topo_group_should_send_to(nq, target_id)` | Проверяет, нужно ли отправлять NODEINFO узла указанному пиру (target_id не в hop_list) | + +### 3.10. Callbacks/вспомогательные + +| Функция | Назначение | +|---|---| +| `topo_groups_set_node_updated_cb(groups, fn)` | Устанавливает callback при обновлении узла (используется chatgui для member_sync) | +| `topo_groups_set_sqlite_db(g, db)` | Устанавливает внешнюю БД SQLite | +| `nat_link_check_cb(success, avg_rtt, count_sent, count_ok, arg)` | Callback завершения NAT-проверки: устанавливает nat_type линка и отправляет результат клиенту | +| `nodeinfo_dump_log(data, len)` | Логирует краткую сводку NODEINFO (node_id, ver, счётчики, hops) | +| `nodeinfo_format(data, len)` | Форматирует полный NODEINFO с именем, подсетями в читаемую строку (аллокация через u_malloc) | +| `group_subcmd_name(subcmd)` | Возвращает строковое имя subcmd для логов | + +### 3.11. Проверка соответствия типа группы + +| Функция | Назначение | +|---|---| +| `topo_group_send_err_group_mismatch(group, conn, expected_type, received_flags)` | Отправляет ошибку GROUP_MISMATCH пиру, если его NODEINFO содержит неверный flags (разные типы групп) | + +## 4. Архитектурные связи + +### 4.1. Внешние зависимости + +| Модуль | Как используется | +|---|---| +| `topo_node.c/h` | Модель данных узла: TOPO_NODE, TOPO_NODEQ, ser/deserialize, find_by_id, update_my_nodeinfo, free_lists | +| `topo_node_sqlite.h` | Персистентность в SQLite: topo_node_sqlite_node_put, member_put, member_del, init | +| `route_lib.h` | Таблица маршрутов: route_insert, route_delete | +| `route_ping.h` | NAT-детекция и проверка связности: route_ping_send_req_addr, handle_req, handle_resp, cancel_for_conn | +| `route_connectivity.h` | Зондирование связности узла: route_connectivity_probe_node, cancel_node, cancel_all | +| `etcp_api.h` | Регистрация приёмника: etcp_bind, etcp_add_new_conn_cbk, etcp_unbind | +| `etcp_connections.h` | Управление соединениями: etcp_conn_add_up_cbk, etcp_conn_add_down_cbk, ETCP_CONN, ETCP_LINK | +| `etcp.h` | Отправка пакетов: etcp_send | +| `etcp_router.h` | Закрытие виртуальных каналов: etcp_router_conn_close_all_for_node | +| `control_server.h` | Уведомления мониторинга: control_server_notify_node_change, control_server_notify_node_removed | +| `conn_mgr.h` | Менеджер соединений: использует topo_group_find_conn_for_node для поиска путей | +| `utun_instance.h` | UTUN_INSTANCE: конфиг, node_id, ключи, rt, topo_groups | +| `secure_channel.h` | Ключи: sc_derive_ed25519_pubkey | +| `memory_pool.h` | Память для сериализации: memory_pool_init / destroy | +| `debug_config.h` | Логирование: DEBUG_INFO/WARN/ERROR/DEBUG с категорией BGP | +| `mem.h` | Аллокация: u_malloc, u_free, u_calloc | + +### 4.2. Кто вызывает topo_group + +| Вызывающий | Что вызывает | Для чего | +|---|---|---| +| `utun_instance.c` | `topo_groups_init`, `topo_groups_destroy` | Инициализация/завершение при старте/остановке utun | +| `etcp_connections.c` | `topo_group_send_nodeinfo` | При изменении конфигурации сокетов — broadcast обновлённого local_node | +| `etcp_router.c` | `topo_group_find_conn_for_node` | Поиск next-hop для маршрутов и multihop-соединений (~15 вызовов) | +| `conn_mgr.c` | `topo_group_find_conn_for_node` | Поиск пути для установки прямого/обратного/indirect соединения | +| Тесты | `topo_groups_get_default`, `topo_group_find_conn_for_node`, `topo_node_find_by_id`, `topo_group_set_nat_check_local` | 9 тестовых файлов | + +### 4.3. ETCP-коллбэки (верхний уровень) + +Точка входа для пакетов топологии — `etcp_bind(instance, ETCP_ID_TOPO_ENTRY, topo_group_receive_cbk)` в `topo_groups_init`. Единственный обработчик на весь ETCP ID 0x01. Диспетчеризация внутри по subcmd (byte data[1]). + +Коллбэки on_up/on_down новых соединений регистрируются через `etcp_add_new_conn_cbk(instance, topo_group_etcp_conn_cbk, NULL)`, которая при создании каждого нового ETCP_CONN подписывается на его события. + +## 5. Форматы пакетов (wire) + +### 5.1. NODEINFO +``` +cmd(1) | subcmd(1) | TOPOMSG_NODE (82+ байта) | [node_name] | [v4_sock_meta[]] | [v4_addrs[]] | [v6_sock_meta[]] | [v6_addrs[]] | [v4_subnets[]] | [v6_subnets[]] | [tranzit[]] | [hop_list[]] +``` + +### 5.2. WITHDRAW +``` +cmd(1) | subcmd(1) | node_id(8) | wd_source(8) = 18 байт +``` + +### 5.3. REQUEST_TABLE / TABLE_COMPLETE +``` +cmd(1) | subcmd(1) = 2 байта +``` + +### 5.4. NAT_INFO +``` +cmd(1) | subcmd(1) | socket_id(1) | nat_ip(4) | nat_port(2) | nat_type(1) = 10 байт +``` + +### 5.5. NAT_CHECK_REQ +``` +cmd(1) | subcmd(1) | socket_id(1) | interface_ip(4) | interface_port(2) = 9 байт +``` + +### 5.6. ERR_GROUP_MISMATCH +``` +cmd(1) | subcmd(1) | expected_type(1) | received_flags(1) = 4 байта +``` + +## 6. Примечания + +- **Хранилище памяти**: В отличие от STCP, модуль не имеет отдельной БД. Использует внешний SQLite3 pointer (`topo_groups->topo_sqlite_db`), который может быть открыт самим модулем (если указан `db_path`) или передан из chatgui через `topo_groups_set_sqlite_db`. +- **Версионирование NODEINFO**: Версия (ver, uint8_t 1-255) сравнивается через циклическую арифметику `(int8_t)(cur_ver - new_ver) >= 0`. Версия 0 не используется (нет данных). +- **Защита от петель**: NODEINFO рассылается только тем пирам, чей node_id отсутствует в hop_list. WITHDRAW broadcast всем кроме отправителя. +- **NAT check для локальных сетей**: По умолчанию запрещён (`allow_nat_check_local=0`), можно включить через `topo_group_set_nat_check_local(group, 1)` для тестов. +- **Aliens**: Узлы с `alien=1` хранятся в nodes, имеют пути, но не инициируют обмен маршрутами при подключении (topo_group_new_conn пропускает). +- **Обновление при NAT-детекции**: При получении NAT_INFO, если изменился nat_type сокета с не-EIM на EIM (или наоборот) — обновляется `local_node`, увеличивается версия и broadcast-ится всем пирам. diff --git a/src/topo_node_doc.md b/src/topo_node_doc.md new file mode 100644 index 00000000..f75cf75a --- /dev/null +++ b/src/topo_node_doc.md @@ -0,0 +1,170 @@ +# topo_node + +## 1. Назначение + +Модель данных узла топологии — структуры данных для представления узла в памяти, сериализации/десериализации в бинарный wire-формат для BGP-обмена, учёт ссылок (refcounting) и диагностический дамп. + +Модуль определяет два слоя представлений: +- **Память:** связанные списки (`TOPO_ADDR4`, `TOPO_SOCKMETA4`, `TOPO_SUBNET4` и др.) для удобной модификации. +- **Wire-формат:** packed-структуры (`TOPOMSG_NODE`, `TOPOMSG_ADDR4` и др.) для передачи по сети без выравнивания. + +Сам модуль не содержит сетевой логики BGP-обмена — этим занимается `topo_group`. Здесь только данные и их упаковка/распаковка. + +Узлы персистентно хранятся в SQLite через модуль `topo_node_sqlite`. Память для элементов списков (адреса, сокет-мета, подсети) выделяется из memory pool'ов из `topo_groups`. + +## 2. Как пользоваться + +### Типовые сценарии + +**Создание/обновление информации о себе:** +```c +topo_group_update_my_nodeinfo(instance, group); +``` +Строит `TOPO_NODEQ` (в `group->local_node`) из конфига: собирает список сокетов, адресов (interface/NAT/real), подсетей; при изменениях инкрементирует `ver` и помечает `dirty=1`. Для chat-групп (`TOPO_GROUP_TYPE_CHAT`) подсети не включаются. + +**Сериализация для отправки по BGP:** +```c +uint8_t buf[2048]; +int len = topo_node_serialize(group, nq, buf, sizeof(buf)); +``` +Запаковывает `TOPO_NODEQ` → бинарный буфер: заголовок `TOPOMSG_NODE`, затем имя узла, v4/v6 sock_meta, v4/v6 адреса, подсети (если `TOPO_FLAG_SEND_SUBNETS`), транзитные узлы и hop-лист. + +**Десериализация при получении NODEINFO:** +```c +struct TOPO_NODE *ni = NULL; +struct TOPO_NODESUBNETS *subnets = NULL; +struct TOPOMSG_TRANZIT *tranzit = NULL; +uint8_t tranzit_count = 0; +uint64_t *hop_list = NULL; +uint8_t hop_count = 0; +topo_node_deserialize(group, data, data_len, &ni, &subnets, &tranzit, &tranzit_count, &hop_list, &hop_count); +``` +Восстанавливает `TOPO_NODE`, подсети, транзит и hop-лист из бинарного буфера. Элементы списков аллоцируются из memory pool'ов. Выделенная память должна быть освобождена: +- `topo_node_unref(ni)` — освобождает `TOPO_NODE` и `node_name` +- `topo_node_free_lists(group, nq)` — освобождает списки обратно в пулы, а также `subnets`, `tranzit_data`, `hop_list` + +**Поиск узла по ID:** +```c +struct TOPO_NODEQ *nq = topo_node_find_by_id(group, node_id); +``` +Использует хеш-индекс очереди `group->nodes` (ключ — `node_id`, 8 байт). + +**Дамп всех узлов:** +```c +topo_node_dump_all(group); // в лог +topo_node_format_all(group, buf, size); // в строку +``` + +### Ключевые концепции + +- **TOPO_NODE** — базовые данные узла: pubkey (X25519), ed25519_pubkey, имя, версия, групповой ID. Разделяется между группами через счётчик ссылок. + +- **TOPO_NODEQ** — запись в очереди узлов группы. Содержит `TOPO_NODE*` (общий), плюс динамические пути, подсети, транзитные узлы, hop-лист, связность, conn_mgr. Все поля аллоцированы отдельно и освобождаются через `topo_node_free_lists()`. + +- **TOPO_NODEPATH** — путь до узла через конкретное ETCP-соединение. Хранится в `paths`-очереди типа `ll_queue`. После самой структуры идёт массив `uint64_t hop[hop_count]` (variable-length). + +- **Счётчик ссылок:** `topo_node_ref()`/`topo_node_unref()`. При падении счётчика до 0 освобождается `node_name` и сам `TOPO_NODE`. Сами списки адресов и sock_meta освобождаются отдельно через `free_lists()`. + +- **Адреса бывают трёх типов:** `TOPO_ADDR_INTERFACE` (LAN-адрес), `TOPO_ADDR_NAT` (после детекции NAT), `TOPO_ADDR_REAL` (подтверждённый прямой адрес). Для каждого сокета может быть до 3 адресов в списке. + +- **TOPO_CONNECTIVITY** — локальное состояние связности (не передаётся по BGP): статус зондирования (NONE/IN_PROGRESS/DONE), результаты для interface/nat/real адресов, минимальные RTT, время зондирования. + +- **TOPO_FLAG_SEND_SUBNETS** — флаг в `TOPO_NODE.flags`: если установлен, подсети сериализуются и включаются в wire-формат. Для chat-групп сбрасывается в 0. + +- **Wire-протокол:** заголовок `TOPOMSG_NODE` (75 байт packed) + динамическая часть: `node_name` (0-255 байт), `SOCKMETA4*N`, `ADDR4*N`, `SOCKMETA6*N`, `ADDR6*N`, опционально подсети, транзит, hop-лист. Размер динамической части вычисляется функцией `topo_node_dyn_size()`. + +## 3. API + +### Основные структуры + +| Структура | Назначение | +|-----------|-----------| +| `struct TOPO_NODE` | Идентичность узла: pubkeys (X25519+Ed25519), имя, версия, group_id, node_id, списки v4/v6 сокет-метаданных и адресов. Счётчик ссылок. | +| `struct TOPO_NODEQ` | Запись в очереди группы. Содержит `TOPO_NODE*`, подсети, транзитные данные, hop-лист, очередь `paths` (TOPO_NODEPATH), связность, conn_mgr. `best_socket` — указатель на лучший ETCP-сокет для связи. | +| `struct TOPO_NODEPATH` | Путь до узла через ETCP-соединение с hop_count. Variable-length: после структуры идёт `uint64_t hop[]`. | +| `struct TOPO_NODESUBNETS` | Списки v4 и v6 подсетей, анонсируемых узлом. Отдельный malloc, может быть NULL. | +| `struct TOPO_CONNECTIVITY` | Локальное состояние проверки связности: статусы зондирования, RTT для interface/nat/real адресов. | +| `struct TOPOMSG_NODE` | Packed-заголовок wire-формата (75 байт). Содержит счётчики элементов динамической части. | +| `struct TOPOMSG_ADDR4/6` | Packed wire-формат одного адреса (addr, port, type, socket_id, protocol). | +| `struct TOPOMSG_SOCKMETA4/6` | Packed wire-формат метаданных сокета (id, config_type, nat_type). | +| `struct TOPOMSG_SUBNET4/6` | Packed wire-формат подсети (addr, prefix_length). | +| `struct TOPOMSG_TRANZIT` | Packed wire-формат транзитного узла (node_id, rtt, link_q). | + +### Вспомогательные типы в памяти (связанные списки) + +| Тип | Назначение | +|-----|-----------| +| `struct TOPO_SOCKMETA4` | Элемент списка v4 сокет-метаданных (next*, id, config_type, nat_type). | +| `struct TOPO_ADDR4` | Элемент списка v4 адресов (next*, addr[4], port, type, socket_id, protocol). | +| `struct TOPO_SOCKMETA6` | Элемент списка v6 сокет-метаданных. | +| `struct TOPO_ADDR6` | Элемент списка v6 адресов. | +| `struct TOPO_SUBNET4` | Элемент списка v4 подсетей (next*, addr[4], prefix_length). | +| `struct TOPO_SUBNET6` | Элемент списка v6 подсетей. | + +Все элементы списков аллоцируются из memory pool'ов и освобождаются через `memory_pool_free()`. + +### Константы + +| Константа | Значение | Назначение | +|-----------|---------|-----------| +| `TOPO_ADDR_INTERFACE` | 0 | Интерфейсный (LAN) адрес сокета | +| `TOPO_ADDR_NAT` | 1 | NAT-адрес после детекции | +| `TOPO_ADDR_REAL` | 2 | Подтверждённый прямой адрес | +| `TOPO_PROTO_UDP` | 0x01 | UDP-транспорт | +| `TOPO_PROTO_TCP` | 0x02 | TCP-транспорт | +| `PROBE_STATUS_NONE` | 0 | Зондирование не запускалось | +| `PROBE_STATUS_IN_PROGRESS` | 1 | Зондирование идёт | +| `PROBE_STATUS_DONE` | 2 | Зондирование завершено | +| `PROBE_RESULT_UNKNOWN` | 0 | Результат неизвестен | +| `PROBE_RESULT_REACHABLE` | 1 | Адрес достижим | +| `PROBE_RESULT_UNREACHABLE` | 2 | Адрес недостижим | +| `CONN_TYPE_DIRECT` | 1 | Прямое соединение | +| `CONN_TYPE_REVERSE` | 2 | Обратное соединение | +| `CONN_TYPE_INDIRECT` | 3 | Соединение через посредника | +| `TOPO_FLAG_SEND_SUBNETS` | 0x01 | Флаг: отправлять подсети | +| `TOPO_GROUP_UTUN` | `0x8000000000000000ULL` | ID группы uTun по умолчанию | + +### Функции + +**Управление памятью и refcounting:** +- `topo_node_ref(ni)` — увеличить счётчик ссылок на `TOPO_NODE`. +- `topo_node_unref(ni)` — уменьшить счётчик; при 0 освободить `node_name` и `TOPO_NODE` (но не списки — их надо предварительно освободить через `topo_node_free_lists`). +- `topo_node_free_lists(group, nq)` — освободить все списки адресов/sock_meta обратно в memory pool'ы, освободить `subnets`, `tranzit_data`, `hop_list`, сделать `unref` на `node`, обнулить указатели. + +**Сериализация:** +- `topo_node_dyn_size(msg)` — вычислить размер динамической части wire-формата по полям-счётчикам заголовка `TOPOMSG_NODE`. Не учитывает сам заголовок. +- `topo_node_serialize(group, nq, out, out_max)` — сериализовать `TOPO_NODEQ` в бинарный буфер. Возвращает количество записанных байт или -1 при ошибке (переполнение или невалидные аргументы). Формат: заголовок `TOPOMSG_NODE`, затем name, v4 sock_meta, v4 addrs, v6 sock_meta, v6 addrs, подсети (опционально), tranzit, hop_list. + +**Десериализация:** +- `topo_node_deserialize(group, data, len, &out_ni, &out_subnets, &out_tranzit, &out_tranzit_count, &out_hop_list, &out_hop_count)` — восстановить `TOPO_NODE`, подсети, транзит и hop-лист из бинарного wire-формата. Аллоцирует `TOPO_NODE` через `u_calloc` (ref_count=1), элементы списков — из memory pool'ов, `subnets`, `tranzit_data`, `hop_list` — отдельными `u_malloc`. Возвращает 0 при успехе, -1 при несоответствии размера или ошибке аллокации. Если подсети есть в wire-формате но флаг `TOPO_FLAG_SEND_SUBNETS` не установлен, они пропускаются. + +**Поиск:** +- `topo_node_find_by_id(group, node_id)` — найти `TOPO_NODEQ` в очереди группы по `node_id` через хеш-индекс. Возвращает NULL если не найден. + +**Обновление информации о себе:** +- `topo_group_update_my_nodeinfo(instance, group)` — перестроить `group->local_node` (`TOPO_NODEQ`) из текущих данных инстанса (сокеты, NAT, адреса, подсети, ключи). Сравнивает количество элементов с предыдущим значением; если есть изменения — освобождает старый `local_node`, создаёт новый, инкрементирует `ver`, помечает `dirty=1`. Обновляет таблицу маршрутов (`route_delete`/`route_insert`). Для chat-групп подсети не включаются. Возвращает количество v4 подсетей. + +**Диагностика:** +- `topo_node_dump_all(group)` — дамп всех узлов группы в лог (DEBUG_CATEGORY_BGP). Для каждого узла выводит: ID, имя, версию, pubkeys, v4/v6 сокеты и адреса, подсети, транзитные узлы, пути (с hop-ами), статус связности, conn_mgr. Пропускает узлы с `node==NULL`. +- `topo_node_format_all(group, buf, buf_size)` — аналогичный дамп в строковый буфер. Возвращает количество записанных байт. При переполнении добавляет `[TRUNCATED]`. + +**Inline-аксессоры (из .h):** +- `topo_v4_sock_meta(ni)` / `topo_v4_addrs(ni)` / `topo_v6_sock_meta(ni)` / `topo_v6_addrs(ni)` — получить списки из `TOPO_NODE`. +- `topo_v4_subnets(r)` / `topo_v6_subnets(r)` — получить списки подсетей из `TOPO_NODESUBNETS*` (NULL-safe). +- `topo_list_count(head)` — посчитать количество элементов в любом из списков (next — первое поле). + +### Зависимости + +| Модуль | Использование | +|--------|--------------| +| `ll_queue.h` | `TOPO_NODEQ` — запись в очереди, хеш-индекс по `node_id`, `paths` — вложенная очередь | +| `secure_channel.h` | `SC_PUBKEY_SIZE` (32) для массивов pubkey/ed25519 | +| `mem.h` | `u_malloc`, `u_calloc`, `u_free`, `u_strdup` | +| `memory_pool.h` | `memory_pool_alloc`/`memory_pool_free` для элементов списков (пулы из `topo_groups`) | +| `debug_config.h` | `DEBUG_INFO`, `DEBUG_WARN`, `DEBUG_ERROR`, `log_dump` | +| `topo_group.h` | `struct TOPO_GROUP` (аргумент большинства функций) | +| `etcp.h` | `struct ETCP_CONN`, `struct ETCP_SOCKET` | +| `config_parser.h` | `struct CFG_ROUTE_ENTRY`, `struct CFG_SERVER` (при построении local_node) | +| `route_lib.h` | `route_insert()`, `route_delete()` (при обновлении local_node) | +| `etcp_debug.h` | `ip_to_str()` (для дампа адресов и подсетей) | +| `utun_instance.h` | `struct UTUN_INSTANCE` (аргумент `topo_group_update_my_nodeinfo`) | diff --git a/src/topo_node_sqlite_doc.md b/src/topo_node_sqlite_doc.md new file mode 100644 index 00000000..6dacbf70 --- /dev/null +++ b/src/topo_node_sqlite_doc.md @@ -0,0 +1,39 @@ +# topo_node_sqlite — SQLite-персистентность топологии + +## 1. Назначение +Обеспечивает сохранение и восстановление топологии P2P-сети (ноды, адреса, каналы, участники каналов) в SQLite-базе. База используется как основной источник данных для chatgui и восстановления состояния между запусками utun. + +В отличие от LMDB-слоя (`topo_node_lmdb`), который хранит данные в бинарном формате BGP-сообщений, SQLite-слой работает с нормализованными таблицами и используется для клиентских целей — чтение данных chatgui, получение списка участников канала, проверка online-статуса. + +## 2. Как пользоваться +1. Получить готовый `sqlite3* db` (открывается извне, обычно в `chat_sync.c`) +2. Вызвать `topo_node_sqlite_init(db)` для создания таблиц +3. Сохранять/обновлять ноды через `topo_node_sqlite_node_put()` +4. Сохранять каналы через `topo_node_sqlite_channel_put()` (автоматически создаёт таблицу `peers_`) +5. Добавлять участников в канал через `topo_node_sqlite_member_put()` +6. Читать участников канала через `topo_node_sqlite_channel_peers_all()` — возвращает бинарный буфер со всеми участниками (node_id, ключи, подписи, адреса) + +**Нюансы:** +- Имена таблиц `peers_` санитизируются: все символы кроме `[a-zA-Z0-9_]` заменяются на `_` +- `topo_node_sqlite_channel_peers_all()` формирует компактный бинарный буфер с числом записей в начале (uint16_t) +- Ключи в BLOB-полях всегда 32 байта (x25519/ed25519), подписи — 64 байта +- Функции `online` и `get_ed25519_pubkey` — простые SELECT-запросы без JOIN + +## 3. API + +| Функция | Описание | +|---------|----------| +| `topo_node_sqlite_init(db)` | Создаёт таблицы `nodes`, `node_addresses`, `channels` (если не существуют) | +| `topo_node_sqlite_node_put(db, nq)` | Сохраняет ноду и её адреса (IPv4/IPv6) в транзакции; старые адреса удаляются | +| `topo_node_sqlite_channel_put(...)` | Сохраняет канал в `channels` + создаёт таблицу `peers_` | +| `topo_node_sqlite_member_put(...)` | Добавляет/обновляет участника в `peers_` (INSERT OR REPLACE) | +| `topo_node_sqlite_member_get_join(db, ch_id, node_id, ...)` | Читает join_sig и join_ts участника канала | +| `topo_node_sqlite_member_del(db, ch_id, node_id)` | Удаляет участника из `peers_` | +| `topo_node_sqlite_node_update_verified(...)` | Обновляет ключи и имя ноды по условию `update_ts < новое_значение` (защита от stale-данных) | +| `topo_node_sqlite_channel_get(...)` | Читает метаданные канала (имя, владелец, ключи, подпись) | +| `topo_node_sqlite_channel_peers_all(...)` | Сериализует всех участников канала в бинарный буфер (count + [node_id\|x25519\|ed25519\|flags\|sigs\|name\|addrs]) | +| `topo_node_sqlite_node_set_online(db, id, online)` | Устанавливает флаг `online` для ноды | +| `topo_node_sqlite_node_get_online(db, id)` | Возвращает 1 если нода online | +| `topo_node_sqlite_get_ed25519_pubkey(db, id, out)` | Извлекает ed25519-публичный ключ ноды (32 байта) | + +**Структуры:** внешние — `sqlite3*`, `struct TOPO_NODEQ`, `struct TOPO_NODE`; собственных структур нет. diff --git a/src/tun_if_doc.md b/src/tun_if_doc.md new file mode 100644 index 00000000..f87225e1 --- /dev/null +++ b/src/tun_if_doc.md @@ -0,0 +1,54 @@ +# TUN Interface (tun_if) + +## 1. Назначение +Модуль создания и управления виртуальным сетевым интерфейсом (TUN) — точкой входа/выхода всего VPN-трафика. Через TUN-интерфейс ОС отправляет IP-пакеты в uTun и получает обработанные пакеты обратно. Реализует кроссплатформенную абстракцию над платформенными механизмами: Linux (`/dev/net/tun`), FreeBSD (`/dev/tun` + TUNSIFHEAD), Windows (WinTun API). + +Модель данных — две очереди: +- **output_queue** — пакеты, прочитанные из TUN (ОС → uTun → routing) +- **input_queue** — пакеты для записи в TUN (routing → uTun → ОС), с callback `tun_input_queue_callback` + +## 2. Как пользоваться +```c +// Инициализация из конфига (основной интерфейс VPN) +tun = tun_init(ua, config); +// или для NAT TUN с явными параметрами +nat_tun = tun_init_nat(ua, "tun_nat", "10.0.1.1", 1500, 0); + +// Чтение: очередь output_queue наполняется автоматически (через uasync/epoll), +// обработчик читает через queue_set_callback +struct ll_queue* out = tun_get_output_queue(tun); + +// Запись: пакет кладётся в input_queue, callback пишет в TUN +tun_write(tun, buf, len); // прямой вызов, не через очередь + +// Завершение +tun_close(tun); +``` + +**Root-права:** требуются на всех платформах для создания TUN-устройств и настройки IP. +**Тестовый режим** (`test_mode=1`): вместо реального TUN используется `socketpair(AF_UNIX, SOCK_DGRAM)`. Вторая сторона пары доступна через `tun_get_test_fd()`. + +**Платформенные различия:** +- **Linux:** `open("/dev/net/tun")` + `ioctl(TUNSETIFF)` с `IFF_TUN | IFF_NO_PI`, неблокирующий режим через `fcntl(O_NONBLOCK)` +- **FreeBSD:** `open("/dev/tun")` (cloning device), имя присваивается ядром, `ioctl(TUNSIFHEAD)` для multi-af (4-байтный заголовок AF), `ioctl(TUNSIFMODE)` для POINTOPOINT +- **Windows:** WinTun API через `wintun.dll`, кольцевой буфер 4 MiB, отдельный read-поток (`tun_read_thread_proc`), пакеты передаются в main thread через `uasync_post → tun_packet_handler` + +## 3. API + +### Структуры +- **`struct tun_if`** — хендл интерфейса: `fd` (Linux/BSD файловый дескриптор), `platform_handle`/`adapter_handle` (Windows WinTun), `ifname`, `ifindex`, очереди `input_queue`/`output_queue`, пул `pool` для ll_entry, счётчики статистики (`bytes_read/written`, `packets_read/written`, `read_errors`, `write_errors`) +- **`struct tun_packet_data`** — обёртка для передачи пакета из read-потока (Windows) в main thread: указатели на `tun_if` и `ll_entry` + +### Функции +- **`tun_init(ua, config)`** / **`tun_init_nat(ua, ifname, ip, mtu, test_mode)`** — создают TUN-интерфейс: платформенный init, настройка IP/MTU, поднятие интерфейса, создание очередей, регистрация в uasync (Linux/BSD) или запуск read-потока (Windows). `tun_init_nat` — вариант для отдельного NAT TUN с явными параметрами вместо конфига +- **`tun_close(tun)`** — остановка read-потока (Windows), удаление из uasync, слив и освобождение обеих очередей, уничтожение пула, платформенная очистка (`close(fd)` на Linux, `SIOCIFDESTROY` на FreeBSD, `WintunEndSession`+`WintunCloseAdapter` на Windows) +- **`tun_write(tun, buf, len)`** — прямая запись в TUN (в тестовом режиме — в output_queue, иначе `tun_platform_write`). Пакет имеет префиксный байт, отбрасываемый при записи +- **`tun_inject_packet(tun, buf, len)`** — инжекция пакета в output_queue (для тестов и Windows-нотификаций) +- **`tun_read_packet(tun, buf, len)`** — тестовый helper: чтение одного пакета из input_queue +- **`tun_packet_handler(arg)`** — callback для main thread (Windows): распаковывает `tun_packet_data`, кладёт `ll_entry` в output_queue +- **Платформенные функции** (`tun_linux.c` / `tun_freebsd.c` / `tun_windows.c`): + - `tun_platform_init()` — создание устройства, настройка IP/MTU, поднятие, получение `ifindex`, `O_NONBLOCK` + - `tun_platform_cleanup()` — закрытие fd/хендлов + - `tun_platform_read()` — чтение из TUN (FreeBSD: `readv` с отделением 4-байтного AF-заголовка) + - `tun_platform_write()` — запись в TUN (FreeBSD: `writev` с AF-заголовком; Windows: `WintunAllocateSendPacket` + `WintunSendPacket`) + - `tun_platform_get_poll_fd()` — fd для uasync (Windows возвращает -1, используется read-поток) diff --git a/src/tun_route_doc.md b/src/tun_route_doc.md new file mode 100644 index 00000000..0ae7d17d --- /dev/null +++ b/src/tun_route_doc.md @@ -0,0 +1,49 @@ +# TUN Route (tun_route) + +## 1. Назначение +Модуль управления маршрутами в системной таблице маршрутизации. Добавляет маршруты к заданным подсетям через TUN-интерфейс, чтобы исходящий трафик на эти подсети направлялся в виртуальный интерфейс uTun. Удаляет их при завершении работы. Кроссплатформенный: использует разные механизмы ОС с fallback-стратегией. + +## 2. Как пользоваться +```c +// Добавить один маршрут +tun_route_add(ifindex, "tun0", 0x0A000000, 8); // 10.0.0.0/8 + +// Добавить все маршруты из конфига +tun_route_add_all(ifindex, "tun0", cfg.routes); + +// Удалить все маршруты из конфига +tun_route_del_all(ifindex, "tun0", cfg.routes); + +// Удалить все маршруты через интерфейс (очистка) +tun_route_flush("tun0"); +``` + +**Network передаётся в host byte order** (внутри конвертируется в network byte order). `tun_route_add_all`/`tun_route_del_all` проходят по связному списку `CFG_ROUTE_ENTRY` и вызывают `tun_route_add`/`tun_route_del` для каждого элемента. + +**Root-права:** требуются для изменения системной таблицы маршрутизации. + +## 3. API + +### Функции +- **`tun_route_add(ifindex, ifname, network, prefix_len)`** — добавляет маршрут к подсети `network/prefix_len` через интерфейс `ifindex`. Пытается использовать нативный механизм платформы, при неудаче — fallback на shell-команду +- **`tun_route_del(ifindex, ifname, network, prefix_len)`** — удаляет аналогичный маршрут +- **`tun_route_add_all(ifindex, ifname, routes)`** — массовое добавление маршрутов из связного списка `CFG_ROUTE_ENTRY`. Возвращает количество успешно добавленных +- **`tun_route_del_all(ifindex, ifname, routes)`** — массовое удаление. Возвращает количество удалённых +- **`tun_route_flush(ifname)`** — удаление всех маршрутов, привязанных к интерфейсу `ifname`, без привязки к конфигу + +### Платформенные механизмы + +**Linux:** +1. Netlink socket (`AF_NETLINK`, `NETLINK_ROUTE`): сообщения `RTM_NEWROUTE`/`RTM_DELROUTE` с атрибутами `RTA_DST` (адрес сети) и `RTA_OIF` (индекс интерфейса). Ожидание ACK от ядра через `poll` с таймаутом 1с +2. Fallback: `ip route add/del / dev ` через `system()` +3. `tun_route_flush`: `ip route flush dev `, при неудаче — парсинг `ip route show` и удаление по одному + +**Windows:** +1. `netsh interface ip add/delete route` через `system()` (первичный метод) +2. Fallback: IP Helper API — `CreateIpForwardEntry`/`DeleteIpForwardEntry` с заполнением `MIB_IPFORWARDROW` +3. `tun_route_flush`: `GetIpForwardTable` → перебор записей по `dwForwardIfIndex` → `DeleteIpForwardEntry` + +**BSD/macOS:** +1. Routing socket (`PF_ROUTE`, `SOCK_RAW`): сообщения `RTM_ADD`/`RTM_DELETE`, адреса RTA_DST + RTA_NETMASK + RTA_IFP (sockaddr_dl) +2. Fallback: `route add/delete -net / -interface ` через `system()` +3. `tun_route_flush`: `route -n show -interface ` → парсинг awk → `route delete` diff --git a/src/utun_doc.md b/src/utun_doc.md new file mode 100644 index 00000000..f4a312aa --- /dev/null +++ b/src/utun_doc.md @@ -0,0 +1,50 @@ +# utun.c — Главная точка входа + +## 1. Назначение +Точка входа процесса uTun. Парсинг CLI, запуск в режиме демона, создание и жизненный цикл корневого инстанса `UTUN_INSTANCE`, главный цикл обработки событий `mainloop()`, обработка сигналов (graceful shutdown/reload). + +## 2. Как пользоваться +```bash +utun -f -c myconfig.conf -p /var/run/utun.pid -l /var/log/utun.log -d "etcp=debug" +``` +- `-f` — foreground (без демонизации) +- `-c` — путь к конфигу (по умолчанию `utun.conf`) +- `-p` — PID-файл (по умолчанию `/var/run/utun.pid`, Windows — нет) +- `-l` — лог-файл (по умолчанию `utun.log`) +- `-d` — отладочная конфигурация (формат: `категория=уровень,...`) +- `-h` — справка + +**Порядок запуска:** +1. Парсинг аргументов → инициализация системы отладки +2. `uasync_create()` → `utun_instance_create()` → `utun_instance_init()` +3. Демонизация (`fork`+`setsid`) если не `-f` +4. Запись PID-файла +5. `mainloop`: `uasync_poll(ua, 100)` в цикле пока `instance->running` +6. По `SIGTERM`/`SIGINT`: `g_shutdown=1` → выход из цикла → `utun_instance_destroy()` → `uasync_destroy()` → `u_report_unfreed_blocks()` +7. По `SIGHUP`: `g_reload=1` → `utun_instance_reload()` (селективный или полный) + +## 3. API + +### Функции +| Функция | Описание | +|---------|----------| +| `parse_args(argc, argv, args)` | Разбор CLI-аргументов через `getopt_long`, заполняет `cmd_args_t` | +| `print_usage(progname)` | Вывод справки по использованию | +| `write_pidfile(pidfile)` | Запись PID текущего процесса в файл | +| `remove_pidfile(pidfile)` | Удаление PID-файла при завершении | +| `daemonize()` | Демонизация: `fork()`→родитель выходит, `setsid()`, перенаправление fd 0/1/2 в `/dev/null` | +| `open_logfile(logfile)` | Открытие лог-файла с line-buffering | +| `signal_handler(sig)` | Обработчик: `SIGINT`/`SIGTERM`→shutdown, `SIGHUP`→reload, вызов `uasync_wakeup()` | +| `main(argc, argv)` | Главная функция: инициализация, демонизация, mainloop, cleanup | + +### Структуры +| Структура | Описание | +|-----------|----------| +| `cmd_args_t` | Аргументы командной строки: config_file, pid_file, log_file, debug_config, foreground, help | + +### Глобальные переменные +| Переменная | Описание | +|------------|----------| +| `g_shutdown` (sig_atomic_t) | Флаг завершения, выставляется из signal handler | +| `g_reload` (sig_atomic_t) | Флаг перезагрузки конфига по SIGHUP | +| `main_ua` | Указатель на главный UASYNC (для signal handler) | diff --git a/src/utun_instance_doc.md b/src/utun_instance_doc.md new file mode 100644 index 00000000..07726371 --- /dev/null +++ b/src/utun_instance_doc.md @@ -0,0 +1,116 @@ +# utun_instance — Корневой инстанс (центральный оркестратор) + +## 1. Назначение + +`UTUN_INSTANCE` — **центральная структура всего процесса uTun**. Владеет всеми подсистемами: TUN, таблица маршрутов, BGP-группы, ETCP-сокеты/соединения, control-сервер, message transport, firewall, NAT, TCP-прокси, memory pools, NTP, синхронизация БД, менеджер соединений. + +Это **единственная точка входа для инициализации и завершения** всех компонентов. Порядок инициализации и destroy строго определён — от низкоуровневых подсистем к высокоуровневым и обратно. + +## 2. Как пользоваться + +### Создание +```c +struct UASYNC* ua = uasync_create(); +struct UTUN_INSTANCE* inst = utun_instance_create(ua, "utun.conf"); // из файла +// или +struct UTUN_INSTANCE* inst = utun_instance_create_from_config(ua, config); // из готовой структуры +// или +struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua, config_text); // из строки (тесты) +``` + +### Инициализация (оркестрация) +```c +utun_instance_init(inst); // запускает всю цепочку инициализации +``` +`utun_instance_init()` **не вызывает** `utun_instance_create()` — create только аллоцирует память и загружает конфиг. `init` запускает все подсистемы в строгом порядке: +1. `db_sync_init` — распределённая БД с репликацией +2. `routing_set_tun` — привязка TUN к таблице маршрутов +3. `nat_transport_init` — NAT (если включён в конфиге) +4. `init_connections` — ETCP-сокеты, клиентские подключения, handshake +5. `control_server_init` — сервер мониторинга (etcpmon) +6. `msg_transport_init` — IPC-транспорт сообщений (чат, команды) +7. `ntp_time_init` + `ntp_node_time_init` — синхронизация времени + +### Основной цикл +```c +inst->running = 1; +while (inst->running) { + uasync_poll(inst->ua, 100); +} +``` + +### Остановка и destroy +```c +utun_instance_stop(inst); // running=0, wakeup +utun_instance_destroy(inst); // полный cleanup в обратном порядке +``` +`destroy` гарантирует порядок: NTP→msg_transport→control→ETCP sockets→db_sync→connections→pings→TUN→tcp_proxy→routing→NAT→etcp_router→conn_mgr→BGP→fw→stcp→networks→config→pools. **uasync не уничтожается** — вызывающий код должен сам вызвать `uasync_destroy()`. + +### Reload (SIGHUP) +```c +inst = utun_instance_reload(inst, ua, "utun.conf"); +``` +Сравнивает старый и новый конфиг: +- Если изменились ключи/node_id/tun_ifname → **полный перезапуск** (destroy + create + init) +- Иначе — **селективное обновление**: только изменённые/добавленные/удалённые сокеты, клиенты, линки. Неизменённые не трогаются (без close, без сброса таймеров). + +## 3. API + +### Структура `UTUN_INSTANCE` — ключевые поля + +| Поле | Тип | Назначение | +|------|-----|------------| +| `name` | `char[16]` | Имя инстанса из конфига | +| `config` | `struct utun_config*` | Полный разобранный конфиг | +| `node_id` | `uint64_t` | Идентификатор узла (из конфига или derived из privkey) | +| `my_keys` | `struct SC_MYKEYS` | Пара ключей X25519 (pub/priv) | +| `my_ed25519_pubkey` / `_privkey` | `uint8_t[32]` | Ed25519 ключи (для подписи) | +| `ua` | `struct UASYNC*` | Главный event loop (один на поток) | +| `running` | `int` | Флаг работы главного цикла | +| `tun` | `struct tun_if*` | TUN-интерфейс | +| `route_subnets` | `struct CFG_ROUTE_ENTRY*` | Маршруты для добавления/удаления системных роутов | +| `rt` | `struct ROUTE_TABLE*` | Таблица маршрутов | +| `topo_groups` | `struct TOPO_GROUPS*` | BGP-подобный обмен маршрутами | +| `connections` | `struct ll_queue*` | Очередь всех ETCP-соединений (индекс по peer_node_id) | +| `etcp_sockets` | `struct ETCP_SOCKET*` | Связный список UDP-сокетов | +| `stcp_server` | `struct stcp_server*` | STCP TCP-сервер | +| `data_pool` | `struct memory_pool*` | Пул для данных пакетов (payload) | +| `pkt_pool` | `struct memory_pool*` | Пул для `struct ETCP_DGRAM` | +| `ack_pool` | `struct memory_pool*` | Пул для `struct ACK_PACKET` | +| `control_srv` | `struct control_server*` | Сервер мониторинга (etcpmon backend) | +| `msg_t` | `struct msg_transport*` | IPC-транспорт сообщений | +| `fw` | `struct firewall_ctx` | Контекст файрвола | +| `nat` / `nat_tr` | `struct eim_nat_ctx` / `nat_transport_ctx` | EIM NAT + транспорт | +| `tcp_proxy_client` | `struct tcp_proxy_client*` | TCP-прокси клиент (опционально) | +| `tcp_proxy_server` | `struct tcp_proxy_server` | TCP-прокси сервер (exit node) | +| `router_bindings` / `router_conns` | | Привязки и соединения ETCP-роутера | +| `conn_mgr` | `struct CONN_MGR*` | Менеджер соединений (может быть NULL) | +| `db_sync` | `struct DB_SYNC*` | Распределённая синхронизация БД (может быть NULL) | +| `networks` | `struct ll_queue*` | Очередь сетей (NETWORK_ENTRY, индекс по 56-bit id) | +| `pending_connects` | `struct ETCP_CONNECT*` | Ожидающие фоновые подключения | +| `ntp` / `ntp_node` | `struct NTP_TIME` / `NTP_NODE_TIME` | Синхронизация времени | +| `stats_dir` | `char[512]` | Путь `/stats/` для файлов метрик | +| `next_socket_id` | `uint8_t` | Счётчик уникальных ID сокетов (0–255) | +| `socket_init_status` | `int` | Статус инициализации сокетов: 0=OK, 1=частично, -1=ошибка | +| `routed_packets` / `dropped_packets` | `uint64_t` | Счётчики маршрутизированных/отброшенных пакетов | + +### Основные функции + +| Функция | Описание | +|---------|----------| +| `utun_instance_create(ua, config_file)` | Загрузка конфига, аллокация инстанса, вызов `instance_init_common()`. Возвращает готовый к `init` инстанс или NULL | +| `utun_instance_create_from_config(ua, config)` | Создание из уже разобранного конфига (владение передаётся инстансу) | +| `utun_instance_create_from_str(ua, config_text)` | Создание из строки конфига (временный файл → parse → delete). Для тестов | +| `utun_instance_init(instance)` | **Основной оркестратор инициализации**. Запускает все подсистемы в правильном порядке, устанавливает `running=1` | +| `utun_instance_destroy(instance)` | **Полный cleanup** всех ресурсов в обратном порядке зависимостей | +| `utun_instance_stop(instance)` | Установка `running=0` + `uasync_wakeup()` для выхода из mainloop | +| `utun_instance_reload(instance, ua, config_file)` | Перезагрузка конфига по SIGHUP: полная при изменении ключей/TUN, иначе селективная | +| `utun_instance_set_tun_init_enabled(enabled)` | Глобальный флаг: включать инициализацию TUN или нет (для тестов без TUN) | +| `utun_instance_diagnose_leaks(instance, phase)` | Диагностика: подсчёт ETCP-сокетов/соединений/линков, проверка утечек пулов и TUN | + +### Внутренние (static) + +| Функция | Описание | +|---------|----------| +| `instance_init_common(instance, ua, config)` | Общая инициализация для всех create-функций: ключи, node_id, networks, memory pools, routing, TUN, сокеты, BGP, conn_mgr, firewall, etcp_router, routing_bind, tcp_proxy_server/client | +| `local_sockaddr_equal(a, b)` | Сравнение двух sockaddr_storage (IPv4/IPv6) — используется в reload | diff --git a/tools/bping/bping_doc.md b/tools/bping/bping_doc.md new file mode 100644 index 00000000..3e500ca0 --- /dev/null +++ b/tools/bping/bping_doc.md @@ -0,0 +1,50 @@ +# bping — Bandwidth Ping + +## 1. Назначение + +Утилита измерения пропускной способности и потерь канала через burst-ы ICMP Echo. +В отличие от обычного ping, отправляет пачками (burst) — до 10,000 пингов за раз на максимальной скорости +с последующим сбором ответов в течение 5 секунд. Позволяет оценить пропускную способность +и packet loss под нагрузкой. + +## 2. Как пользоваться + +``` +bping [-s размер] [-p посылок] [-b пачек] [-i интервал] host +``` + +| Опция | По умолчанию | Описание | +|-------|-------------|----------| +| `-s N` или `-s MIN:MAX` | 56 | Размер данных (payload) ICMP в байтах. Если диапазон — случайный размер в нём | +| `-p N` | 10 | Количество пингов в одной пачке (макс. 10000) | +| `-b N` | 0 (∞) | Число пачек. 0 — бесконечно (Ctrl+C для остановки) | +| `-i N` | 1.0 | Интервал между пачками в секундах (можно 0.001, 0.05 и т.д.) | + +Требует **root** или **cap_net_raw** (raw sockets). + +**Сборка:** `make` в `tools/bping/`, установка: `make install`. + +**Примеры:** +```bash +sudo bping 8.8.8.8 +bping -s 1472 -p 200 -i 0.05 1.1.1.1 +bping -s 100:600 -p 500 -i 0.01 192.168.1.1 +``` + +**Вывод:** per-packet RTT (размер, IP, ICMP seq, TTL, время) + per-burst сводка: +``` +--- 200 transmitted, 195 received, 2% loss --- +``` + +## 3. Ключевые детали + +- **SOCK_RAW + IPPROTO_ICMP** — raw сокет, требует привилегий +- **Без libpcap** — sendto/recvfrom напрямую, без сторонних библиотек +- ICMP Echo Request, идентификатор = `getpid() & 0xFFFF`, последовательные seq +- Данные заполняются `0x61` ('a'), как в стандартном ping +- Контрольная сумма ICMP — `in_cksum()`, своя реализация (RFC 1071) +- Приём: `select()` с таймаутом 1с в цикле до 5 секунд на пачку +- Ответы сопоставляются по ICMP ID + seq через `seq_list[]` (массив, не хеш-таблица) +- Межburst-интервал через `nanosleep()`, поддержка долей секунды +- Отдельный Makefile, не включён в autotools-сборку проекта +- SIGINT (Ctrl+C) — печать «⛔ bping остановлен» и выход diff --git a/tools/chatgui/chatgui_doc.md b/tools/chatgui/chatgui_doc.md new file mode 100644 index 00000000..32a15189 --- /dev/null +++ b/tools/chatgui/chatgui_doc.md @@ -0,0 +1,216 @@ +# chatgui + +## 1. Назначение + +Десктопный GUI-чат (Telegram-подобный) со встроенным P2P uTun-узлом. Приложение объединяет полнофункциональный интерфейс обмена сообщениями и децентрализованный транспортный слой в едином процессе. Рассчитан на работу без центральных серверов — вся маршрутизация, синхронизация и доставка сообщений идут напрямую между узлами по ETCP-протоколу. + +## 2. Архитектура + +### 2.1. Уровни приложения + +``` +┌──────────────────────────────────────┐ +│ GUI (Qt 6/5 Widgets, C++20) │ поток GUI +│ ├─ MainWindow (QSplitter) │ +│ ├─ ChannelList / MessageList │ +│ ├─ MessageDelegate (бабблы+реакции) │ +│ ├─ EmojiPanel (статика+анимация) │ +│ └─ LottieIcon / AnimTimer │ +├──────────────────────────────────────┤ +│ GuiBridge (Qt signals ↔ uasync) │ очередь событий +│ gui_bridge_post / uasync_post │ +├──────────────────────────────────────┤ +│ Транспорт (C, поток uasync) │ поток uasync +│ ├─ UtunNode (std::thread + UTUN_INSTANCE)│ +│ ├─ ChatCore (отправка, БД, invite) │ +│ ├─ ChatSync (P2P синхронизация) │ +│ ├─ MemberSync (мемберы через Merkle)│ +│ └─ MerkleSync (Merkle-дерево, 5 ур.)│ +├──────────────────────────────────────┤ +│ libutun (C, статическая библиотека) │ uasync +│ ├─ uasync (event loop, таймеры) │ +│ ├─ ETCP (надежная доставка, ACK) │ +│ ├─ SecureChannel (X25519+AES-CCM) │ +│ ├─ conn_mgr (управление подключ.) │ +│ ├─ topo_node/topo_group (BGP) │ +│ └─ ntp_time (синхронизация времени) │ +├──────────────────────────────────────┤ +│ SQLite3 (WAL mode) │ чтение+запись +│ ├─ DbManager (C++, read-only для GUI)│ +│ └─ chat_core (C, write из uasync) │ +└──────────────────────────────────────┘ +``` + +### 2.2. Потоковая модель + +Приложение работает в двух потоках: + +- **Поток GUI** — QApplication, все виджеты, отрисовка, QTimer для анимаций (AnimTimer 30fps). Только *чтение* из SQLite через DbManager. +- **Поток uasync** — UTUN_INSTANCE в выделенном std::thread. Вся сеть, криптография, ETCP, синхронизация, *запись* в SQLite. Один uasync-экземпляр на поток (правило u_async). + +**Мост между потоками (gui_bridge):** +- **uasync → GUI:** `gui_bridge_post(event_type, data, len)` — сериализованные события в Qt main event loop через `QMetaObject::invokeMethod` с `Qt::QueuedConnection`. 10 типов событий: MSG_RECEIVED, CONNECT_RESULT, NEW_PEER, CHANNEL_UPDATED, MEMBERS_CHANGED, MY_NODE_ID, AUTO_CONNECT_STATUS, CHANNEL_PEERS_ONLINE, DB_READY, STATUS_REFRESH. +- **GUI → uasync:** `gui_bridge_post_uasync_fn(fn, arg)` — выполнение функции в uasync-потоке через `uasync_post`. Используется для отправки сообщений, создания каналов, invite-подключений. + +Всё взаимодействие с БД (INSERT/UPDATE/DELETE) выполняется только из uasync-потока. GUI читает через DbManager (read-only), синхронизация не требуется — SQLite в WAL-режиме обеспечивает одновременное чтение. + +## 3. Ключевые компоненты + +### 3.1. GUI-слой + +**MainWindow** (`src/mainwindow.h`) — главное окно 900×600. Горизонтальный QSplitter из трёх панелей: +- **ChannelList** — список каналов (QListView + QStandardItemModel), контекстное меню (invite, join, create group, settings). +- **MessageList** — список сообщений канала (ChatView + QStandardItemModel), поле ввода InputBar, EmojiPanel. +- **AccountList** — список участников канала (MemberListModel), онлайн-статус, имена. + +Системный трей: QSystemTrayIcon + QMenu (показать/скрыть/выход). Закрытие окна сворачивает в трей. + +**ChatView** (`src/chatview.h`) — QListView с фоновым изображением и drag-to-select. При движении >5px от точки нажатия стартует выделение сообщений. Ctrl+C копирует: для одного — выделенный текст, для нескольких — `[time] author: text`. Сигнал `hoveredIndexChanged` используется для активации анимаций эмодзи в MessageList. + +**MessageList / MessageDelegate** — баббл-интерфейс сообщений: +- Аватары (32×32, цвет зависит от node_id), бабблы со скруглёнными углами (r=10px) и хвостиком. +- Цитаты: цветная полоса + мини-аватар + автор + текст. +- Реакции (эмодзи) в status bar под бабблом. +- Inline-анимированные эмодзи: при hover над сообщением — перебор кадров из LottieIcon, отрисовка поверх текста через QTextLayout. +- Растягивающийся InputBar (QTextEdit, Enter — отправка, Shift+Enter — перевод строки). + +**EmojiPanel** (`src/emojipanel.h`) — QTabWidget с категориями (смайлики, жесты, символы, анимации). Каждая категория — QGridLayout из EmojiButton (QPushButton без Q_OBJECT, hover-анимация через per-widget QTimer). Статические эмодзи — Unicode символы. Анимированные — TGS-файлы, рендерятся в EmojiButton::paintEvent. + +**LottieIcon** (`src/lottieicon.h`) — загрузка и рендер TGS-анимаций: +1. Gzip-декомпрессия через zlib (`inflateInit2` с `16+MAX_WBITS`) +2. Парсинг Lottie JSON через rlottie C API (`lottie_animation_from_data`) +3. Рендер кадра: `lottie_animation_render` → ARGB32 буфер → QImage + +5 встроенных TGS-анимаций (вшиты в бинарник через chatgui.qrc): sparkles, white_flag, greeting, stop_hand, robot. + +**AnimTimer** (`src/animtimer.h`) — синглтон-таймер 30fps с refcount-механизмом: +- `activate()` / `deactivate()` увеличивают/уменьшают счётчик ссылок. +- Срабатывает только когда есть активные анимации в видимых сообщениях. +- Сигнал `ticked` обновляет кадры всех зарегистрированных LottieIcon. + +**Invite-ссылки** (`src/invite_link.h`) — кодирование/декодирование приглашений в формат `utun://...` (base64, содержит channel_id, pubkey, список адресов). QR-коды через zxing-cpp. + +### 3.2. Транспортный слой + +**UtunNode** (`transport/utun_node.h`) — C++ обёртка над UTUN_INSTANCE: +- Запускает выделенный std::thread с `runLoop()` — создаёт UTUN_INSTANCE, вызывает `utun_instance_start()`, входит в uasync event loop. +- Приём сообщений: ETCP callback → `QByteArray` → сигнал `messageReceived(uint64_t src, QByteArray data)`. +- Отправка: `send(dstNodeId, data)` через ETCP поверх экземпляра uTun. +- Конфигурация: NodeConfig (INI-файл), генерация X25519/Ed25519 ключей при первом запуске. +- Включает отладку NTP-синхронизации для временных меток сообщений. + +**NodeConfig** (`transport/node_config.h`) — управление INI-конфигом uTun-узла: +- Генерация ключей (X25519 + Ed25519), сохранение/загрузка. +- Секции `[server]` и `[client]` для подключения к другим узлам. +- `[gui]` секция: путь к БД, настройки отладки. +- ConfigUpdater — утилита для программного изменения конфиг-файлов (append/replace). + +**ChatCore** (`transport/chat_core.h`) — центральный API чата в потоке uasync: +- `chat_core_submit_message` — отправка сообщения: сохранение в локальную БД (таблица `msg_`), рассылка онлайн-пирам через etcp_send. +- `chat_core_create_channel` — создание канала: генерация X25519/Ed25519 ключей канала, подпись Ed25519, запись в `channels`, членство владельца. +- `chat_core_connect_from_invite` — подключение к пиру из invite-ссылки. +- `chat_core_connect_auto` — авто-подключение к узлу из SQLite (topo_node), коллбэк с результатом. +- Работа с БД: цепочка хешей (chain hash = SHA256(prev_hash || timestamp || data_hash)), список каналов, список пиров. + +**ChatSync** (`transport/chat_sync.h`) — P2P синхронизация каналов: +- ETCP-сервисы: `0x30` (chat_sync — синхронизация каналов), `0x31` (member_sync — синхронизация участников). +- Протокол сообщений: INIT_SYNC → INIT_RESP → SEND_DATA ↔ PUSH+ACK_PUSH → SYNC_DONE. +- Batch-загрузка: до 32 сообщений за запрос, sparse-синхронизация (до 16 диапазонов). +- Таймаут пира: 5 секунд. +- Auto-connect: параллельное подключение до 10 узлов, останавливается при 3 успешных. Переключение группы при смене канала. + +**MemberSync** (`transport/member_sync.h`) — синхронизация участников канала на основе Merkle-деревьев: +- Данные: таблицы `peers_`, `nodes`, `node_addresses`. +- Хеш мембера = SHA256(node_id || x25519 || ed25519 || join_sig || join_ts || update_sig || update_ts || addrs). +- Автоматический пересчёт дерева при добавлении/удалении/обновлении мембера. +- Онлайн-статус: `member_sync_set_online(node_id, 0/1)`. + +**MerkleSync** (`transport/merkle_sync.h`) — универсальный протокол синхронизации на Merkle-деревьях: +- 5-уровневое префиксное дерево над 64-битными ключами (node_id). 32 бакета на уровень (по 5 бит). +- SHA256-хеши бакетов. Два пира обмениваются хешами, находят различающиеся бакеты, передают только их. +- Wire-протокол: MSG_HASHES → MSG_REQUEST → MSG_BATCH, рекурсивно до совпадения хешей. +- Таймаут 10s, 3 ретрая. Фоновая проверка консистентности (`merkle_sync_bg_check`). +- Не зависит от типа данных — потребитель предоставляет три коллбэка (update_bucket_hash, get_items, apply_items). member_sync адаптирует это под мемберов каналов. + +### 3.3. База данных + +**DbManager** (`db/db_manager.h`) — C++/Qt обёртка над SQLite3 (read-only для GUI-потока): +- **Схема:** `channels` (каналы с ключами X25519/Ed25519, подписи), `msg_` (per-channel таблицы сообщений с chain_hash), `peers_` (участники), `nodes` (узлы), `node_addresses`, `accounts`, `ui_state`, `merkle_tree_hash`. +- Чтение: `getChannels()`, `getMessages()`, `getChannelMembers()`, `loadNodeInfo()`, `getAccounts()`. +- Методы для GUI: список каналов, сообщения с пагинацией, участники, онлайн-статус. +- Sync-операции (курсоры): `syncListChannels()`, `syncCount()`, `syncChainHashAt()`, `syncCursorOpen/Next/Close()`. +- Проверка целостности: `integrityCheckQuick()` (PRAGMA quick_check), `integrityCheckFull()` (PRAGMA integrity_check), `integrityOptimize()` (PRAGMA optimize). + +## 4. Сборка + +**Система сборки:** CMake 3.16+ +**Стандарты:** C++20 (GUI), C99 (транспорт/libutun) +**Зависимости:** +- Qt 6 (Widgets + Network) или Qt 5.15+ +- rlottie (анимированные эмодзи) +- zlib (gzip-декомпрессия TGS) +- OpenSSL (X25519, Ed25519, SHA256, AES-CCM) +- zxing-cpp (QR-коды, встроен как submodule) + +**Структура сборки:** +- `libutun/` — статическая библиотека uTun: все исходники из `src/` (кроме utun.c) + lib/ (uasync, ll_queue, memory_pool, etc) + транспорт (chat_sync, member_sync, merkle_sync) +- `chatgui` — финальный бинарник: GUI-исходники + транспортные C++/C файлы + SQLite3 amalgamation +- Платформенный TUN: `tun_linux.c` / `tun_freebsd.c` / `tun_windows.c` + +```bash +mkdir -p build && cd build +cmake .. && make -j4 +./chatgui +``` + +## 5. Файловая структура + +``` +tools/chatgui/ +├── CMakeLists.txt # проект CMake +├── build.sh / build.bat # скрипты сборки +├── src/ # GUI-слой (Qt C++) +│ ├── main.cpp # точка входа, QApplication +│ ├── mainwindow.cpp # главное окно, трей +│ ├── channellist.cpp # список каналов +│ ├── chatview.cpp # QListView с фоном и drag-to-select +│ ├── messagelist.cpp # модель сообщений, инициализация анимаций +│ ├── messagedelegate.cpp # отрисовка бабблов, цитат, реакций, эмодзи +│ ├── inputbar.cpp # поле ввода + кнопка эмодзи +│ ├── emojipanel.cpp # QTabWidget с категориями эмодзи +│ ├── emojitabbar.cpp # кастомный QTabBar +│ ├── emoji.cpp # данные эмодзи, builtinEmojiSet() +│ ├── lottieicon.cpp # загрузка TGS, рендер через rlottie +│ ├── animtimer.cpp # синглтон-таймер 30fps с refcount +│ ├── accountlist.cpp # список участников канала +│ ├── memberlistmodel.cpp # модель для AccountList +│ ├── invite_link.cpp # кодирование/декодирование utun:// ссылок +│ ├── invitedialog.cpp # диалог приглашения +│ ├── joindialog.cpp # диалог входа по invite-ссылке +│ ├── settingsdialog.cpp # окно настроек +│ ├── networksettingspage.cpp # страница сетевых настроек +│ ├── databasesettingspage.cpp # настройки БД +│ ├── statuspage.cpp # страница статуса +│ ├── creategroupdialog.cpp # создание группы +│ └── qrcode_utils.cpp # генерация QR-кодов +├── transport/ # транспортный слой (C/C++) +│ ├── utun_node.cpp # C++ обёртка UTUN_INSTANCE в std::thread +│ ├── node_config.cpp # INI-конфиг узла (ключи, серверы, клиенты) +│ ├── config_updater.cpp # утилита редактирования INI-файлов +│ ├── gui_bridge_impl.cpp # мост uasync↔Qt (10 типов событий) +│ ├── chat_core.c # центральный API чата (отправка, БД, invite) +│ ├── chat_sync.c # P2P синхронизация каналов (ETCP 0x30) +│ ├── member_sync.c # синхронизация мемберов через Merkle +│ └── merkle_sync.c # универсальный Merkle-протокол синхронизации +├── db/ # база данных +│ ├── db_manager.h/cpp # C++/Qt обёртка SQLite3 (read-only для GUI) +│ └── sqlite3.c/h # SQLite3 amalgamation (из ../../lib/) +├── libutun/ # сборка статической библиотеки uTun +│ └── CMakeLists.txt # uasync + utun = все .c из lib/ и src/ +├── resources/ # ресурсы +│ ├── chatgui.qrc # Qt resource file (TGS вшиты в бинарник) +│ └── animations/ # TGS-анимации (5 шт.) +└── doc/ # документация + ├── desc.txt # описание схемы БД + └── db_schema.md # детальная схема таблиц SQLite +``` diff --git a/tools/etcpmon/etcpmon_doc.md b/tools/etcpmon/etcpmon_doc.md new file mode 100644 index 00000000..4e22cb89 --- /dev/null +++ b/tools/etcpmon/etcpmon_doc.md @@ -0,0 +1,120 @@ +# etcpmon + +## 1. Назначение + +Windows GUI монитор для ETCP соединений uTun в реальном времени (100ms refresh). Подключается по TCP к control_server внутри uTun и отображает метрики соединений, линков, TUN-интерфейса, роутера и дебаг-уровней. Позволяет менять debug levels на лету через GUI. + +## 2. Как пользоваться + +### Типовой сценарий + +1. В конфиге uTun (`utun.conf`): `control_ip=127.0.0.1`, `control_port=9090` +2. Запустить uTun → он поднимает TCP control server на указанном порту +3. Запустить `etcpmon.exe` (Windows, MinGW/MSYS2 сборка) +4. Ввести IP:порт сервера (по умолчанию `192.168.40.1:9091`), нажать Connect +5. Выбрать соединение из списка Connections — начнут поступать метрики + +### Что отображает + +**ETCP Connection:** +- RTT (last / avg10 / avg100, в 0.1ms), jitter, bytes sent, retransmissions, ACKs, unacked bytes, optimal inflight, links count +- RX/TX дубликаты, таймеры (retrans/ack_resp), WaitAck состояние (suspended/cb/timer) +- ID Sequence: next_tx_id, last_rx_id, last_delivered_id, rx_ack_till +- Счётчики ошибок: reinit, reset, pkt_format_errors + +**TUN / Routing:** +- Bytes/packets read/written, ошибки чтения/записи +- Routed/dropped пакеты, TUN очереди (InQ/OutQ packets/bytes) +- Таблица маршрутов: total, local, learned, BGP senders, BGP nodes + +**Links:** +- Статус (UP/DOWN), encrypt/decrypt/send/recv ошибки +- Total encrypted/decrypted bytes, bandwidth (Kbps), NAT changes +- RTT (last/avg10), transmit time, keepalive counters (sent/recv) +- Inflight bytes/packets/limit, таймеры (init/keepalive/shaper) + +**BBR per link:** +- mode (STARTUP/DRAIN/PROBE_BW/PROBE_RTT), cycle idx, full_bw/loss_in_round флаги +- pacing_rate (bytes/sec), min_rtt (us), bw_hi/bw_lo (bytes/sec), inflight_hi/lo, pacing_gain + +**Router Congestion (агрегированные):** +- total_conns, total_inflight, total_send_q, total_recv_q +- pkts sent/recv, send err, ACK sent/recv, dup_dropped, oob_dropped, stale_ack, sign_fail +- RTT max/avg, jitter max, minRTT min + +**Графики BBR (scrolling canvas, 860 сэмплов, 1px=1sample):** +- Inflight Bytes, Cwnd (inflight_lim), minRTT, bwHi, Pacing Rate, bwLo, inflight Hi, BBR Mode +- 8 каналов с индивидуальными галочками включения +- Вертикальная линия курсора с показом значений в блоках под графиком + +**Очереди (Queues & Errors):** +- InQ, InSend, WaitAck, AckQ, RecvQ, OutQ — bytes/packets +- Нормализатор: input/output pkts/bytes, alloc/logic errors, frag_size, data_ptr/size, cumulative totals +- ACK Debug: hit_inf, hit_sndq, miss, link_wait +- Системные ресурсы: active_timeouts, busy_memory_blocks + +**Debug Levels (правая колонка):** +- Глобальный уровень: NONE/ERROR/WARN/INFO/DEBUG/TRACE (радиокнопки) +- Per-category уровни с метками категорий +- Radio-кнопки отправляют CMD_SET_DEBUG_CONFIG на сервер при клике + +**Action:** +- Текстовое поле + кнопка Send — отправка произвольной команды (CMD_ACTION) +- Результат открывается в отдельном окне с моноширинным шрифтом Consolas + +### Сборка + +```cmd +cd tools\etcpmon +build.bat # через MSYS2 UCRT64 (рекомендуется) +``` +```bash +bash build.sh # Unix/MinGW +make # MinGW Makefile +``` + +Серверная часть (control_server) встроена в utun, собирается стандартным `./configure && make`. + +### Горячие клавиши + +- Tab в поле Server/Port — переключение между полями +- Enter в поле Server/Port — Connect/Disconnect +- Enter в поле Action — отправка команды + +## 3. API / Структура + +### Компоненты + +| Файл | Назначение | +|------|-----------| +| `etcpmon_main.c` | WinMain: инициализация GUI, создание окна, message loop | +| `etcpmon_client.h/c` | TCP клиент (Winsock2): connect, send/recv, парсинг протокола, history, pending requests с коллбэками | +| `etcpmon_gui.h/c` | WinAPI GUI: окно 1000×1400, все контролы (~200 edit-полей, списки, кнопки, radio, graph), тултипы | +| `etcpmon_graph.h/c` | Real-time график: 8 каналов, off-screen буфер, пересчёт min/max, курсор, обновление channel values | +| `etcpmon_protocol.h` | Бинарный протокол, общие структуры, shared между клиентом и сервером | + +### Протокол (бинарный, TCP) + +Формат: `[size:2][type:1][seq_id:1][payload...]` + +**Клиент → Сервер:** +- `0x01` CMD_LIST_CONN — запрос списка соединений (без payload) +- `0x02` CMD_SELECT_CONN — выбор соединения: `peer_node_id` (uint64_t) +- `0x03` CMD_GET_METRICS — запрос метрик для выбранного соединения +- `0x04` CMD_DISCONNECT — отключение +- `0x05` CMD_LIST_SOCKETS — запрос списка локальных сокетов +- `0x06` CMD_ACTION — текстовая команда (32 байта) +- `0x07` CMD_GET_DEBUG_CONFIG — запрос текущих debug levels +- `0x08` CMD_SET_DEBUG_CONFIG — установка debug levels: `[global_level:1][cat_count:1][levels:N]` +- `0x09` CMD_SUBSCRIBE_NODES — подписка на изменения узлов (topo) + +**Сервер → Клиент:** +- `0x81` RSP_CONN_LIST: `count(1) + N × {peer_node_id(8) + name(32)}` +- `0x82` RSP_METRICS: `etcp_metrics + tun_metrics + router_metrics + N × link_metrics` +- `0x83` RSP_SOCKET_LIST: `count(1) + N × socket_info` +- `0x84` RSP_DEBUG_CONFIG: `global_level(1) + cat_count(1) + names_csv(NUL-term) + levels[N]` +- `0x85` RSP_NODE_INFO — один узел (raw BGP_NODEINFO_PACKET) +- `0x86` RSP_NODE_INFO_END — конец списка узлов +- `0x88` RSP_NODE_REMOVED — узел удалён: `node_id(8)` +- `0x89` RSP_ACTION_RESULT — результат команды: `code(1) + text(NUL-term)` +- `0xFF` RSP_ERROR: `error_code(1) + message(NUL-term)` diff --git a/tools/proxy/proxy_doc.md b/tools/proxy/proxy_doc.md new file mode 100644 index 00000000..5a893b73 --- /dev/null +++ b/tools/proxy/proxy_doc.md @@ -0,0 +1,29 @@ +# proxy (тестовый инструмент) + +## 1. Назначение +UDP прокси с симуляцией сетевых условий — вносит потери пакетов и задержки для тестирования поведения ETCP в нестабильной сети. + +## 2. Как пользоваться +``` +udp_proxy --listen [IP:]PORT --target IP:PORT [опции] +``` + +| Опция | Значение | +|---|---| +| `--listen [IP:]PORT` | Адрес прослушивания (IP опционально, по умолчанию 0.0.0.0) | +| `--target IP:PORT` | Куда пробрасывать трафик | +| `--loss-forward N` | Потери в прямом направлении, % (0–100, по умолчанию 0) | +| `--loss-back N` | Потери в обратном направлении, % | +| `--delay-forward A:B` | Задержка в прямом направлении, диапазон ms | +| `--delay-back A:B` | Задержка в обратном направлении, диапазон ms | +| `--help` | Справка | + +Типовой сценарий: `udp_proxy --listen 0.0.0.0:12345 --target 8.8.8.8:53 --loss-forward 5 --delay-forward 10:50` — вставка между клиентом и сервером, наблюдение реакции ETCP на потери/задержки. + +## 3. Ключевые детали +- Использует `u_async` как событийный цикл (epoll/poll) +- Per-client flow tracking: каждый уникальный клиент (IP:port) получает выделенный backend-сокет, поиск по хешу через линейный связный список (`udp_flow_t`) +- Раздельные настройки потерь и задержек для forward (клиент→сервер) и backward (сервер→клиент) направлений +- Задержка — случайная в заданном диапазоне (ms), потери — случайные с заданной вероятностью (`rand() % 100 < loss`) +- Сборка: `make` в `tools/proxy/`, линкуется с `libuasync.a` и `libpthread` +- Скрипт `proxy.sh` — быстрый запуск с преднастроенными адресами для локального тестирования