Describe bounded control flow and corrected monitor behavior

This commit is contained in:
evgeny committed 2026-10-27 14:01:25 +01:00
1 parent cb50e268a4
commit 0132a984dd
2 files changed
+89 -19

No files matched your search

+25 -5
View File
@@ -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.
+64 -14
View File
@@ -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 не скрывают прежние значения.