From 0132a984dd1087139336330b7d7ea0fa2bd13a49 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 27 Oct 2026 14:01:25 +0100 Subject: [PATCH] Describe bounded control flow and corrected monitor behavior --- src/control_server_doc.md | 30 +++++++++++--- tools/etcpmon/etcpmon_doc.md | 78 +++++++++++++++++++++++++++++------- 2 files changed, 89 insertions(+), 19 deletions(-) diff --git a/src/control_server_doc.md b/src/control_server_doc.md index 3e4e9533d..ef51cba77 100644 --- a/src/control_server_doc.md +++ b/src/control_server_doc.md @@ -5,16 +5,28 @@ TCP-сервер для мониторинга ETCP-соединений. При ## 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()` — сервер сам разошлёт уведомления подписанным клиентам. +2. Выполнять цикл uasync: read-колбэк сам принимает и разбирает команды. Периодический вызов + `control_server_process_updates()` не требуется; эта функция лишь повторно разбирает уже накопленные данные. +3. При изменении/удалении узла UTUN-группы вызывать `control_server_notify_node_change()` / + `control_server_notify_node_removed()` — сервер отметит ID и отправит актуальное состояние при освобождении TX. 4. При остановке вызвать `control_server_shutdown()`. **Нюансы:** - Доступ разрешён только IPv4-клиентам по `control_allow` в конфиге; без правил доступ запрещён. IPv6-клиенты отклоняются. -- Клиенты отключаются по idle-таймауту (30 секунд без команд). Каждая обработанная команда перезапускает таймер. +- В покое клиент отключается после 30 секунд без команд. При активной отправке тот же таймер + отсчитывает 30 секунд без положительного `send()`; поступление команд его не продлевает. - Лог пишется в `control_server.log`: дампы содержат первые 256 байт каждого чтения RX и некоторых ответов TX. - `control_server_process_updates()` разбирает команды; метрики отправляются только по `CMD_GET_METRICS`. - Очередь отправки владеет исходными буферами до полной отправки или отключения; частичная отправка хранит отдельное смещение. +- В TX помещается один кадр до 32 КиБ. Следующий producer ждёт пустой очереди через deferred + `queue_waiter_wait()`. Чтение сокета и разбор уже принятого RX-буфера приостановлены до освобождения TX. +- Подписка сохраняет ID текущих узлов (`8 × N` байт), сериализует их по одному при освобождении TX. + Узлы повторно ищутся по ID; удалённые до своей очереди пропускаются. После начального списка + отправляется `NODE_INFO_END` с seq запроса, затем накопленные live-изменения. +- Live-изменения объединяются по ID (последнее обновление/удаление); предел — 256 различных ID. + При переполнении подписчик отключается с WARN. CHAT-узлы в эту подписку не входят. +- При stop/ошибке отменяются timer и waiter, включая уже запланированный callback, затем освобождаются + список ID и TX-буфер. `control_client_send()` сам клиента не освобождает: ошибку обрабатывает владелец callback. - Если список сокетов превышает предел сообщения (32 768 байт; сейчас помещается 230 записей), возвращается `RSP_ERROR` с кодом `SERVER_BUSY`. Список не усекается; сокеты `probe_only` не учитываются. - `SET_DEBUG_CONFIG` требует ровно `2 + category_count` байт, не более 64 категорий. @@ -35,7 +47,7 @@ TCP-сервер для мониторинга ETCP-соединений. При |---------|-----------| | `control_server_init(server, ua, instance, bind_addr, max_clients)` | Создать слушающий сокет, зарегистрировать в uasync. Возвращает 0 / -1. | | `control_server_shutdown(server)` | Закрыть всех клиентов, освободить слушающий сокет, закрыть лог-файл. | -| `control_server_process_updates(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)` | Разослать подписанным клиентам уведомление об удалении узла. | @@ -48,8 +60,16 @@ TCP-сервер для мониторинга 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` | Для `"nodes"` — `RSP_ACTION_RESULT`; для `"nat"` — без ответа | `"nat"` — запрос NAT-проверки; `"nodes"` — дамп BGP-узлов текстом. | +| `CMD_ACTION` | `RSP_ACTION_RESULT` или `RSP_ERROR`, с seq запроса | Полные имена `"nodes"`/`"nat"`; `nat` подтверждает запуск проверки, неизвестное имя даёт ошибку. | | `CMD_GET_DEBUG_CONFIG` | `RSP_DEBUG_CONFIG` | Текущие уровни отладки (глобальный + по категориям). | | `CMD_SET_DEBUG_CONFIG` | — | Установить уровни отладки. | | `CMD_SUBSCRIBE_NODES` | `RSP_NODE_INFO` (список) + `RSP_NODE_INFO_END` | Подписка на изменения узлов + текущий список. | | `CMD_DISCONNECT` | — | Отключение клиента. | + +Общий формат задаёт `tools/etcpmon/etcpmon_protocol.h`: 4-байтовый заголовок с `seq_id`, packed-структуры, +целые в порядке байтов хоста, без согласования версии/шифрования. Обычный ответ копирует `seq_id` +запроса; снимки `NODE_INFO` имеют `seq=0`, маркер `NODE_INFO_END` — seq запроса подписки. +`NODE_INFO` содержит префикс `01 04` и результат `topo_node_serialize()` для UTUN с подсетями. + +Action требует ровно 32 байта payload с NUL внутри поля. Повреждённая команда закрывает соединение; +неизвестное корректное имя или недоступная служба возвращают `RSP_ERROR` без разрыва TCP. diff --git a/tools/etcpmon/etcpmon_doc.md b/tools/etcpmon/etcpmon_doc.md index 25e3e71ce..e82aef42e 100644 --- a/tools/etcpmon/etcpmon_doc.md +++ b/tools/etcpmon/etcpmon_doc.md @@ -2,7 +2,10 @@ ## 1. Назначение -Windows GUI монитор для ETCP соединений uTun в реальном времени (100ms refresh). Подключается по TCP к control_server внутри uTun и отображает метрики соединений, линков, TUN-интерфейса, роутера и дебаг-уровней. Позволяет менять debug levels на лету через GUI. +Windows GUI монитор для ETCP соединений uTun. Подключается по обычному IPv4 TCP к control_server внутри uTun +и отображает метрики соединений, линков, TUN, роутера и уровней отладки. Все сетевые вызовы и callbacks +выполняются в GUI-потоке: WM_TIMER установлен на 10 мс, автоматический запрос метрик — раз в 100 мс +при отсутствии предыдущего запроса в pending. Частота полученных сэмплов зависит от ответов сервера. Connections и локальные сокеты опрашиваются раз в секунду, даже при пустом списке соединений. Выбор сохраняется по ID узла при изменении порядка и имён. При удалении выбранного соединения @@ -14,16 +17,28 @@ Connections и локальные сокеты опрашиваются раз ### Типовой сценарий -1. В конфиге uTun (`utun.conf`): `control_ip=127.0.0.1`, `control_port=9090` +1. В конфиге uTun настроить `[control]` (без `allow` сервер отклоняет все подключения): + + ```ini + [control] + ip=127.0.0.1 + port=9090 + allow=127.0.0.1/32 + ``` 2. Запустить uTun → он поднимает TCP control server на указанном порту 3. Запустить `etcpmon.exe` (Windows, MinGW/MSYS2 сборка) -4. Ввести IP:порт сервера (по умолчанию `192.168.40.1:9091`), нажать Connect -5. Выбрать соединение из списка Connections — начнут поступать метрики +4. Ввести числовой IPv4 и порт сервера, нажать Connect. При создании окна уже начинается попытка + подключения к `192.168.40.1:9091`; для смены адреса сначала нажать Disconnect. +5. Первое доступное соединение выбирается автоматически; другое можно выбрать в Connections. + +Канал управления не шифруется и не проверяет ключи ETCP; `allow` фильтрует только IPv4 источника. +Для удалённого управления требуется доверенный защищённый канал. DNS-имена и IPv6 клиент не принимает. ### Что отображает **ETCP Connection:** -- RTT (last / avg10 / avg100, в 0.1ms), jitter, bytes sent, retransmissions, ACKs, unacked bytes, optimal inflight, links count +- RTT last / avg10 и jitter (wire: 0.1 мс, GUI: мс), bytes sent, retransmissions, ACKs, unacked bytes, optimal inflight, links count +- Поле RTT Avg100 в окне есть, но соответствующего значения в протоколе нет: оно не заполняется - 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 @@ -36,7 +51,7 @@ Connections и локальные сокеты опрашиваются раз **Links:** - Статус (UP/DOWN), encrypt/decrypt/send/recv ошибки - Total encrypted/decrypted bytes, bandwidth (Kbps), NAT changes -- RTT (last/avg10), transmit time, keepalive counters (sent/recv) +- RTT last, поле с подписью avg10 (фактически BBR minRTT, 0 без BBR), transmit time, keepalive counters (sent/recv) - Inflight bytes/packets/limit, таймеры (init/keepalive/shaper) **BBR per link:** @@ -44,13 +59,13 @@ Connections и локальные сокеты опрашиваются раз - 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 +- 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 каналов с индивидуальными галочками включения +- История первого линка, до 860 сэмплов; если галочек нет, отображаются первые четыре канала - Вертикальная линия курсора с показом значений в блоках под графиком **Очереди (Queues & Errors):** @@ -65,8 +80,11 @@ Connections и локальные сокеты опрашиваются раз - Radio-кнопки отправляют CMD_SET_DEBUG_CONFIG на сервер при клике **Action:** -- Текстовое поле + кнопка Send — отправка произвольной команды (CMD_ACTION) -- Результат открывается в отдельном окне с моноширинным шрифтом Consolas +- Текстовое поле + кнопка Send отправляют CMD_ACTION (до 31 байта текста + NUL) +- Сервер распознаёт префиксы `nodes` (текстовый дамп узлов) и `nat` (перезапуск NAT/STUN-проверок) +- `nodes` возвращает текст в отдельное окно Consolas, `nat` — подтверждение запуска проверки +- Имена сравниваются целиком. Неизвестная команда или недоступная служба возвращают `RSP_ERROR` + с исходным seq; GUI показывает ошибку и сохраняет TCP-соединение ### Сборка @@ -80,12 +98,20 @@ make # MinGW Makefile make check # WinAPI/Winsock регрессии списка, выбора и переподключения ``` +На Linux с MinGW и Wine из каталога `tools/etcpmon`: + +```bash +CC=x86_64-w64-mingw32-gcc bash build.sh +make CC=x86_64-w64-mingw32-gcc TEST_RUNNER=wine check +wine etcpmon.exe +``` + Серверная часть (control_server) встроена в utun, собирается стандартным `./configure && make`. ### Горячие клавиши - Tab в поле Server/Port — переключение между полями -- Enter в поле Server/Port — Connect/Disconnect +- Enter в поле Server/Port — Connect, если отключён; Escape — Disconnect - Enter в поле Action — отправка команды ## 3. API / Структура @@ -96,7 +122,7 @@ make check # WinAPI/Winsock регрессии списка, выбор |------|-----------| | `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_gui.h/c` | WinAPI GUI: заданный размер окна 1250×1400, контролы, таймер, callbacks, тултипы | | `etcpmon_graph.h/c` | Real-time график: 8 каналов, off-screen буфер, пересчёт min/max, курсор, обновление channel values | | `etcpmon_protocol.h` | Бинарный протокол, общие структуры, shared между клиентом и сервером | @@ -104,6 +130,13 @@ make check # WinAPI/Winsock регрессии списка, выбор Формат: `[size:2][type:1][seq_id:1][payload...]` +`size` включает 4-байтовый заголовок, диапазон 4..32768. Все структуры packed(1), целые передаются +в порядке байтов хоста. Константа VERSION=3 не передаётся по сети: клиенту и серверу нужны одинаковые +форматы структур и порядок байтов. TCP-чтение может содержать часть кадра или несколько кадров. +`seq=0` не создаёт ожидания в клиенте; `seq=1..255` сопоставляет ответ с pending по одному номеру. +SELECT_CONN, SET_DEBUG_CONFIG и DISCONNECT не подтверждаются. GUI отправляет списки с seq=0, +а GET_DEBUG_CONFIG, автоопрос метрик и Action — с ненулевым seq и таймаутом. + **Клиент → Сервер:** - `0x01` CMD_LIST_CONN — запрос списка соединений (без payload) - `0x02` CMD_SELECT_CONN — выбор соединения: `peer_node_id` (uint64_t) @@ -120,8 +153,25 @@ make check # WinAPI/Winsock регрессии списка, выбор - `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 — конец списка узлов +- `0x85` RSP_NODE_INFO — префикс `01 04` + `topo_node_serialize()` для UTUN с подсетями, seq=0 +- `0x86` RSP_NODE_INFO_END — конец списка узлов с seq запроса подписки - `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)` + +Подписку на узлы поддерживает сервер, но GUI etcpmon её не запрашивает и эти уведомления не отображает. + +### Очереди, таймауты и единицы + +- Все команды клиента копируются в общий TX FIFO (до 64 КиБ/256 кадров). Short send и WSAEWOULDBLOCK + сохраняют смещение для следующего прохода. Переполнение не принимает новую команду и возвращает ошибку. +- Pending регистрируется вместе с кадром, срок ответа 5 секунд начинается после его полной отправки. + Тип и payload ответа проверяются до снятия pending/callback; `RSP_ERROR` завершает любой запрос. +- Connect и отсутствие прогресса TX также ограничены 5 секундами. Ответы на списки с seq=0 отдельно + не отслеживаются; EOF/фатальная ошибка/таймаут закрывают соединение с диагностикой. +- Сервер держит один готовый кадр до 32 КиБ. Чтение и разбор запросов ждут его отправки через + отменяемый deferred waiter. Для начального списка узлов хранятся ID, сериализация выполняется порционно. +- Live-изменения UTUN объединяются по ID (до 256); переполнение отключает подписчика с WARN. + Серверный таймаут 30 секунд во время TX продлевается только положительным send, иначе — командами. +- История хранит байты, байты/с и minRTT в мс. При выводе используются B/KiB/MiB и B/s/KiB/s/MiB/s, + включая строки BBR. Сравнение идёт с текстом самого контрола, поэтому очистка/reconnect не скрывают прежние значения.