conn_mgr: race connection probes and report call path with correct RTT
This commit is contained in:
1 parent
1c51df039f
commit
111432aa64
26 files changed
+1112
-995
No files matched your search
@@ -8,9 +8,9 @@
|
||||
* - не является NCD handle и не привязан временем жизни к одному ETCP_CONN.
|
||||
*
|
||||
* NCD обеспечивает общий прямой мультиадресный транспорт для BGP/topo_group.
|
||||
* NCD handle попытки CM после установления связи должен перейти группе либо быть закрыт;
|
||||
* CM удерживает свой NCD handle для позднего UP и мониторинга прямого пути;
|
||||
* группа приобретает независимое NCD-владение при согласовании своей сессии.
|
||||
* CM handle остаётся у медиа-сервиса. Закрытие CM handle отменяет его запрос, а не владение группы.
|
||||
* Это контракт разделения ролей; механизм передачи транспорта группе относится к NCD/topo_group.
|
||||
* Групповые сессии открывают через topo_group_peer_open(). Все операции — в потоке uasync. */
|
||||
#ifndef CONN_MGR_H
|
||||
#define CONN_MGR_H
|
||||
@@ -58,7 +58,8 @@ void conn_mgr_router_recv_handler(struct ETCP_CONN* conn, struct ll_entry* entry
|
||||
/* ═══════ единая точка входа ═══════ */
|
||||
|
||||
/**
|
||||
* Единственная публичная функция подключения — 3-фазное (DIRECT→REVERSE→INDIRECT).
|
||||
* Параллельные пробы: DIRECT сразу, REVERSE через 0/500мс, INDIRECT через 0/1000мс от общего старта.
|
||||
* Посредник используется сразу после проверки; поздний прямой путь заменяет его без повторного UP.
|
||||
* Возврат 0 — запрос принят, -1 — ошибка запуска. cb обязателен.
|
||||
* Ошибка до запуска может возвращаться без callback. В callback h может быть NULL.
|
||||
* Успешно выданный handle закрывает вызывающий, в том числе после TIMEOUT.
|
||||
@@ -77,7 +78,7 @@ void conn_mgr_close(struct CONN_MGR_HANDLE* h);
|
||||
|
||||
/* ═══════ доступ к соединению ═══════ */
|
||||
|
||||
/** Заимствованный ETCP_CONN от NCD, если он есть; NULL для INDIRECT или отсоединённого handle. */
|
||||
/** Заимствованный READY прямой ETCP_CONN; NULL для INDIRECT, ожидания или отсоединённого handle. */
|
||||
struct ETCP_CONN* conn_mgr_get_conn(struct CONN_MGR_HANDLE* h);
|
||||
|
||||
/** Передать e->dgram через маршрутизатор CM. Освобождает entry при ненулевом h;
|
||||
@@ -89,6 +90,15 @@ int conn_mgr_send(struct CONN_MGR_HANDLE* h, struct ll_entry* e);
|
||||
/** Обновить RTT кандидата-посредника (≤3, сортировка по возрастанию). */
|
||||
void conn_mgr_update_best_candidates(struct CONN_MGR* mgr, uint64_t node_id, uint16_t rtt);
|
||||
|
||||
/** Запланировать перепроверку путей после изменения маршрутов или READY групповых сессий. */
|
||||
void conn_mgr_routes_changed(struct CONN_MGR* mgr);
|
||||
|
||||
/** Заимствованный next hop: READY прямой путь/подтверждённый посредник; allow_probe — только для управляющего обмена CM. */
|
||||
struct ETCP_CONN* conn_mgr_route_conn(struct CONN_MGR* mgr, uint64_t node_id, int allow_probe);
|
||||
|
||||
/** Тип живого транспорта к node_id: исходящий DIRECT, только входящие REVERSE, другой next hop INDIRECT. */
|
||||
uint8_t conn_mgr_connection_type(const struct ETCP_CONN* conn, uint64_t node_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
+329
-542
File diff suppressed because it is too large.
Load diff
@@ -1,215 +1,114 @@
|
||||
# conn_mgr — Менеджер установки соединений
|
||||
# Connection Manager
|
||||
|
||||
## 1. Назначение
|
||||
Трёхфазный асинхронный менеджер подключения к удалённым нодам. Отвечает за установку ETCP-соединения с выбором оптимального способа:
|
||||
`conn_mgr` подбирает путь для медиа, файлов и звонков. Один менеджер принадлежит
|
||||
одной группе. Несколько сервисов могут удерживать свои handles к одному узлу:
|
||||
транспорт и пробы общие, callbacks и отмена принадлежат каждому владельцу.
|
||||
Все операции выполняются в uasync-потоке.
|
||||
|
||||
1. **DIRECT** — прямое INIT-рукопожатие со всеми известными адресами цели (IPv4/IPv6, TCP/UDP, с NAT-фильтрацией через `sock_match`)
|
||||
2. **REVERSE** — если у нас прямой IP, а цель за NAT: отправляем наши адреса через BGP (`DIRECT_REQ`), цель подключается к нам
|
||||
3. **INDIRECT** — обмениваемся через BGP списками кандидатов-посредников, выбираем общих по минимальной сумме RTT, трафик идёт через `etcp_router`
|
||||
## Расписание подключения
|
||||
|
||||
Также выполняет фоновые задачи: периодический ping всех BGP-нод, поддержание списка лучших посредников, idle-таймаут для неактивных соединений, локальное сканирование сети (LAN broadcast ping).
|
||||
Все сроки отсчитываются от одного `start_tb`, а не от таймаута предыдущей пробы.
|
||||
|
||||
Один `CONN_MGR` обслуживает одну `TOPO_GROUP` (создаётся в `conn_mgr_init(group)`). Внутри — кеш `entries` (по одной записи на целевой узел) и список `handle`'ов (по одному на каждый вызов `conn_mgr_open`).
|
||||
| Доступность извне | DIRECT | LOCAL | REVERSE | INDIRECT |
|
||||
|---|---|---|---|---|
|
||||
| Мы доступны, пир нет | Сразу | В составе DIRECT | Сразу | 1000 мс |
|
||||
| Оба доступны | Сразу | В составе DIRECT | 500 мс | 1000 мс |
|
||||
| Мы недоступны, пир доступен | Сразу | В составе DIRECT | 500 мс | 1000 мс |
|
||||
| Оба недоступны | Сразу | Сразу | 500 мс | Сразу |
|
||||
|
||||
## 2. Как пользоваться
|
||||
Доступность определяется по опубликованным адресам и общей классификации
|
||||
`sock_match`: публичный IPv4, EIM/прямой NAT, пригодный глобальный IPv6.
|
||||
Приватные адреса EIM-интерфейса не считаются внешними.
|
||||
|
||||
### Инициализация
|
||||
```c
|
||||
struct CONN_MGR* mgr = conn_mgr_init(group);
|
||||
// Регистрируется в etcp_router через conn_mgr_router_recv_handler (ETCP_RT_ID_CONN_MGR)
|
||||
// Запускает фоновые таймеры: bg_ping, candidate_ping
|
||||
```
|
||||
DIRECT использует NCD, который создаёт UDP/TCP-линки по известной записи узла,
|
||||
включая IPv4/IPv6, LAN и Reality-параметры. LOCAL — те же локальные handshake-пробы:
|
||||
отдельные ping-контексты и повторные линки не создаются.
|
||||
REVERSE просит получателя открыть/дополнить NCD к отправителю по уже известному
|
||||
подписанному снимку. Запрос обрабатывается и при CONNECTING, и при работе через
|
||||
посредника. Повторы запроса идут раз в секунду в течение трёх секунд.
|
||||
|
||||
### Подключение (handle-based)
|
||||
```c
|
||||
static void my_cb(struct CONN_MGR_HANDLE* h, uint64_t node_id, uint64_t group_id,
|
||||
enum conn_mgr_event event, void* arg) {
|
||||
if (event == CONN_EVENT_UP) { /* связь есть */ }
|
||||
else if (event == CONN_EVENT_DOWN) { /* временный обрыв, сам восстановится */ }
|
||||
else if (event == CONN_EVENT_TIMEOUT) { /* не удалось, закройте handle */ }
|
||||
}
|
||||
Запуск нового способа не закрывает NCD и не удаляет предыдущие линки. NCD TIMEOUT
|
||||
по умолчанию через две секунды завершает только ожидание первой прямой попытки;
|
||||
удерживаемый handle продолжает получать поздний UP.
|
||||
|
||||
struct CONN_MGR_HANDLE* h = NULL;
|
||||
conn_mgr_open(inst, group_id, node_id, my_cb, arg, &h);
|
||||
```
|
||||
## Рабочий путь
|
||||
|
||||
### Отправка данных
|
||||
```c
|
||||
struct ll_entry* e = queue_entry_new(0);
|
||||
e->dgram = data; e->len = len;
|
||||
conn_mgr_send(h, e); // забирает владение e, освобождает внутри
|
||||
struct ETCP_CONN* conn = conn_mgr_get_conn(h); // NULL для INDIRECT
|
||||
```
|
||||
Состояние доставки (`state`, `conn_type`) отделено от состояния проб.
|
||||
Посредник используется сразу после успешного согласования, пока прямой путь
|
||||
ещё устанавливается. Прямой транспорт становится рабочим после READY сессии
|
||||
нужной группы. Тогда он имеет приоритет независимо от порядка ответов INDIRECT.
|
||||
|
||||
### Закрытие
|
||||
```c
|
||||
conn_mgr_close(h); // убирает ОДИН handle; последний рвёт соединение
|
||||
```
|
||||
Переходы между посредником и прямым путём не сбрасывают состояние `ETCP_ROUTER_CONN`,
|
||||
его очереди, seq и неподтверждённые пакеты. Список подтверждённых посредников
|
||||
сохраняется как резерв. Для выбора посредника проверяются READY первого плеча
|
||||
и наличие объявления маршрута к получателю именно от этого посредника.
|
||||
|
||||
### Кандидаты-посредники
|
||||
`cm_candidate_ping_timer_cb` (раз в ~2с) пополняет `best_candidates` (топ-3 по RTT) из узлов группы с активным соединением и пингует/probe'ит их. Список используется фазой INDIRECT. Внешний код (`route_connectivity`) лишь заполняет `nq->connectivity`, откуда берутся свежие RTT.
|
||||
DIRECT/REVERSE определяются по текущим живым линкам: наличие исходящего означает
|
||||
DIRECT, только входящие — REVERSE. Метка первого UP не используется как постоянный тип.
|
||||
Окно звонка показывает фактический путь router, включая BGP fallback до готовности CM:
|
||||
DIRECT/REVERSE/INDIRECT/NONE. Событие `CHAT_EVT_CALL_CONNECTION` содержит call_id,
|
||||
тип и next hop; публикуется при изменении. Проверка идёт раз в секунду общим таймером
|
||||
звонка, сразу при начале/принятии и при событиях CM. Завершение сбрасывает статус
|
||||
и отменяет таймер. Отображение одинаково в Qt и Android.
|
||||
|
||||
### Нюансы
|
||||
- Несколько `conn_mgr_open` на один узел → несколько handle на одном entry; все получают события
|
||||
- Повторный `open` для уже подключённой ноды вернёт handle сразу, UP доставляется отложенно (`uasync_call_soon`)
|
||||
- Для `open` в состоянии CONNECTING новый handle добавляется в список ожидания
|
||||
- Для INDIRECT-соединений `conn_mgr_get_conn` возвращает `NULL`, `conn_mgr_send` идёт через `etcp_router` (первый посредник с живым conn)
|
||||
- Пока есть активный ETCP-линк к ноде — соединение считается DIRECT
|
||||
Один timer на entry обслуживает ближайшее запланированное действие, повторы и
|
||||
общий предел ожидания. READY и изменение маршрутов перепроверяют пути через
|
||||
`conn_mgr_routes_changed()`, без периодического опроса работающих соединений.
|
||||
Начальное ожидание ограничено 15 секундами от срока запуска INDIRECT, даже если
|
||||
нет кандидатов/RTT/ответа. Получение ответа не отменяет этот предел.
|
||||
Входящие записи без сервисных handles удаляются по этому сроку; групповые владельцы
|
||||
сохраняют общий транспорт.
|
||||
|
||||
## 3. API
|
||||
После готовности прямого пути новые дополнительные пробы не запускаются.
|
||||
Уже начатое согласование INDIRECT может завершиться и сохранить резерв.
|
||||
После начального бюджета поздние NCD/READY всё ещё могут улучшить путь. Бесконечные
|
||||
повторные поиски посредников и гарантированное восстановление не предусмотрены.
|
||||
|
||||
### События (enum conn_mgr_event)
|
||||
## INDIRECT
|
||||
|
||||
| Событие | Значение | Смысл |
|
||||
|---------|----------|-------|
|
||||
| `CONN_EVENT_UP` | 0 | Связь есть, можно отправлять данные |
|
||||
| `CONN_EVENT_DOWN` | 1 | Временный обрыв, handle жив, само восстановится |
|
||||
| `CONN_EVENT_TIMEOUT` | 2 | Подключение не удалось, handle жив, закройте сами |
|
||||
1. Инициатор отправляет EXCHANGE_REQ со своими кандидатами.
|
||||
2. Получатель отвечает EXCHANGE_RESP: свои кандидаты и RTT до кандидатов инициатора.
|
||||
3. Инициатор объединяет кандидатов без дублей, исключает себя/получателя,
|
||||
неизвестные RTT и узлы без прямой READY-сессии. Выбирает до трёх по сумме RTT
|
||||
обоих плеч; при равных RTT — по node_id.
|
||||
4. INTERM_SELECTED отправляется через выбранный next hop. Пока нет подтверждения,
|
||||
сервис ещё не получает UP.
|
||||
5. Получатель проверяет своё плечо и возвращает INTERM_ACK через выбранного
|
||||
посредника. Инициатор принимает только узлы из собственного выбора.
|
||||
|
||||
### Коллбэк
|
||||
```c
|
||||
typedef void (*conn_mgr_cb_t)(struct CONN_MGR_HANDLE* h,
|
||||
uint64_t node_id, uint64_t group_id,
|
||||
enum conn_mgr_event event, void* arg);
|
||||
```
|
||||
`h` может быть `NULL` (ошибки до создания handle). `node_id`/`group_id` всегда валидны.
|
||||
Пустой список и RTT=0/UINT16_MAX не дают успеха. Реальный нулевой текущий RTT
|
||||
после уже выполненного замера округляется до 1, чтобы отличать его от отсутствия
|
||||
замера. При отсутствии пригодных кандидатов EXCHANGE повторяется раз в секунду
|
||||
до общего дедлайна; новые кандидаты могут появиться благодаря фоновым проверкам.
|
||||
Запрос работает и при одностороннем `conn_mgr_open()`.
|
||||
Ответы привязаны одновременно к отправителю и request_id. Повторы идемпотентны;
|
||||
поздний ответ не может вытеснить рабочий прямой путь.
|
||||
|
||||
### Корневые структуры
|
||||
Wire-структуры описаны в `conn_mgr_priv.h`. DIRECT_REQ содержит только request_id.
|
||||
INTERM_SELECTED и INTERM_ACK содержат request_id и до трёх `{node_id, rtt}`.
|
||||
Совместимость с прежним адресным DIRECT_REQ не поддерживается.
|
||||
|
||||
| Структура | Описание |
|
||||
|-----------|----------|
|
||||
| `CONN_MGR` | Корневая структура менеджера. `instance`, `group`, очередь `entries`, топ-3 `best_candidates`, таймеры (`bg_ping_timer`, `candidate_ping_timer`), `bg_ping_cursor`, списки `reverse_pending`/`exchange_pending`, `next_request_id` |
|
||||
| `CONN_MGR_ENTRY` | Запись о подключении к одной ноде: `state`, `conn_type`, `last_traffic_tb`, список `handles`, `ncd_handle`, `intermediaries[3]`/`rr_idx`, `local_scan_state`/`main_connect_state`, `db_loaded`, `main.phase` |
|
||||
| `CONN_MGR_HANDLE` | Handle для одного вызова `conn_mgr_open`: `node_id`, `group_id`, `cb`, `cb_arg`, ссылка на `entry`, `next`, `deliver_up_token` (отложенный UP) |
|
||||
| `CONN_MGR_CANDIDATE` | Пара `{node_id, rtt}` — запись о кандидате-посреднике (packed) |
|
||||
| `cm_reverse_pending` | Запись об ожидании REVERSE-подключения (связный список) |
|
||||
| `cm_exchange_pending` | Запись об ожидании INDIRECT-обмена: кешированный `EXCHANGE_RESP`, флаги `received`/`probed`, таймер probe-повторов |
|
||||
## API и владение
|
||||
|
||||
### Жизненный цикл
|
||||
- `conn_mgr_open()` создаёт отдельный handle. Если попытка уже идёт, добавляет
|
||||
ожидающего; если путь готов, доставляет UP отложенно.
|
||||
Первый сервисный handle входящего пути запускает своё расписание проб,
|
||||
сохраняя уже работающий путь через посредника.
|
||||
- UP означает появление пригодного пути. Улучшение пути не повторяет UP.
|
||||
- DOWN означает потерю рабочих путей; при живом резерве DOWN не доставляется.
|
||||
- TIMEOUT означает, что начальное подключение не получило ни одного рабочего пути.
|
||||
Ошибка отдельной пробы при живом посреднике не становится TIMEOUT для сервиса.
|
||||
- DOWN/TIMEOUT не освобождают handle. Владелец обязан вызвать `conn_mgr_close()`.
|
||||
- Закрытие последнего handle отменяет CM timer и callbacks и закрывает только
|
||||
NCD-владение CM. Группа и другие NCD-владельцы сохраняют транспорт.
|
||||
- `conn_mgr_get_conn()` возвращает заимствованный прямой транспорт либо NULL
|
||||
при INDIRECT/отсоединённом handle. Данные следует отправлять через router.
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `conn_mgr_init(group)` | Выделяет CONN_MGR, создаёт очередь entries, запускает bg_ping и candidate_ping таймеры |
|
||||
| `conn_mgr_destroy(mgr)` | Отменяет таймеры, освобождает все entries, exchange_pending, reverse_pending |
|
||||
| `conn_mgr_router_recv_handler(conn, entry)` | Обработчик входящих пакетов `ETCP_RT_ID_CONN_MGR` из etcp_router; диспетчеризация по subcmd |
|
||||
Диагностика CM — категория `general`: INFO показывает расписание, выбор и смену
|
||||
пути с временем от старта; DEBUG — события NCD, RTT и обработку ответов; WARN/ERROR —
|
||||
ошибки, недоступные пути и некорректные пакеты. Создание адресных линков NCD —
|
||||
категория `connection`.
|
||||
|
||||
### Подключение / отключение
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `conn_mgr_open(inst, group_id, node_id, cb, cb_arg, &h)` | Единая точка входа. Резолвит группу, запускает 3-фазное подключение. Если уже подключена — сразу отложенный UP. Если в процессе — добавляет handle в список |
|
||||
| `conn_mgr_close(h)` | Убирает один handle. Последний handle при DIRECT/REVERSE шлёт DISCONNECT и чистит entry |
|
||||
| `conn_mgr_send(h, entry)` | Отправляет данные через `etcp_router`; для INDIRECT — через первого посредника с живым conn; обновляет `last_traffic_tb` |
|
||||
| `conn_mgr_get_conn(h)` | ETCP_CONN для DIRECT/REVERSE, NULL для INDIRECT |
|
||||
|
||||
### Кандидаты-посредники
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `conn_mgr_update_best_candidates(mgr, node_id, rtt)` | Вставляет/обновляет кандидата в отсортированном топ-3 по RTT. Худшие кандидаты вытесняются |
|
||||
|
||||
## 4. Алгоритм подключения
|
||||
|
||||
> Нумерация фаз в коде ведётся через `entry->main.phase`: DIRECT = 0, REVERSE = 2, INDIRECT = 3 (значение 1 не используется).
|
||||
|
||||
### `conn_mgr_open` / `cm_open` (core)
|
||||
1. Отклоняет `node_id == 0`.
|
||||
2. Ищет целевой `TOPO_GROUP_NODE`; если нет — подгружает из SQLite (`db_loaded = 1`).
|
||||
3. Уже `CONNECTED` → новый handle + отложенный UP.
|
||||
4. Уже `CONNECTING` → добавляет handle в список ожидания.
|
||||
5. Есть живой ETCP-линк → помечает `CONNECTED` (DIRECT), открывает NCD, отложенный UP.
|
||||
6. Иначе: новый entry → `CONNECTING`. При локальных адресах у обеих сторон — запускает локальный скан параллельно, затем фазу DIRECT.
|
||||
|
||||
### Фаза 1 — DIRECT (`cm_start_phase_direct`)
|
||||
- `node_conn_direct_open_node` открывает NCD-соединение (пустой `ni`, коллбэк `cm_ncd_callback`).
|
||||
- `cm_direct_add_links` вручную добавляет линки: перебор адресов в приоритете `NAT > INTERFACE`, v4+v6, TCP+UDP, сопоставление сокетов через `sock_match`.
|
||||
- Хоть один линк → ждём INIT handshake. Ноль линков → закрыть NCD, перейти к REVERSE/INDIRECT (для `db_loaded` — cleanup + TIMEOUT).
|
||||
- NCD держит таймаут 2с. Результаты через `cm_ncd_callback`:
|
||||
- `NCD_EVENT_UP` → `conn_type = (main.phase==2) ? REVERSE : DIRECT`, `CONNECTED`, регистрация conn в группе (`topo_group_new_conn`) и доставка UP.
|
||||
- `NCD_EVENT_TIMEOUT` → выбор следующей фазы.
|
||||
- `NCD_EVENT_DOWN` → доставка DOWN (handle жив).
|
||||
|
||||
### Локальный скан (`cm_start_local_scan`)
|
||||
Параллельно DIRECT. Если у обеих сторон приватные/локальные адреса — LAN ping (`etcp_send_ping_to_socket`, до 3 попыток по 100мс). Успех только обновляет RTT (`topo_node_ping_update_rtt`) и помечает узел достижимым локально (`local_scan_state`); соединение при этом устанавливает DIRECT-фаза обычным путём.
|
||||
|
||||
### Фаза 2 — REVERSE (`cm_start_phase_reverse`)
|
||||
Запускается при `NCD_EVENT_TIMEOUT`, если у нас прямой IP, а у цели нет (`cm_has_direct_ip(local) && !cm_has_direct_ip(target)`):
|
||||
1. Собираем наши публичные/EIM/direct адреса.
|
||||
2. Шлём `DIRECT_REQ` (наши адреса) цели через BGP-маршрут `etcp_route_send`, таймер 15с. При таймауте NCD-соединение фазы DIRECT **не закрывается** — оно остаётся в `instance->connections`, чтобы обратный INIT цели переиспользовал его. (В пути «ноль линков» из `cm_direct_add_links` NCD закрывается — линков для INIT-matching всё равно нет.)
|
||||
3. Цель (`cm_handle_direct_req`) открывает NCD к нам и добавляет линки к нашим адресам с NAT-фильтрацией (`sock_match`) — линк создаётся от любого совместимого сокета (цель может быть за NAT).
|
||||
4. Цель дозванивается → её INIT матчится с нашим conn по `peer_node_id` → conn поднимается → `cm_ncd_callback(UP)` с `main.phase==2` → `conn_type=REVERSE`, `CONNECTED`, UP.
|
||||
5. Таймаут → `cm_reverse_timeout_cb` (закрывает NCD) → INDIRECT.
|
||||
|
||||
### Фаза 3 — INDIRECT (indirect.c)
|
||||
1. `cm_start_phase_indirect`: пустой `best_candidates` → TIMEOUT. Иначе шлёт `INTERM_EXCHANGE_REQ` со своим топ-4 кандидатов, таймер 15с.
|
||||
2. Цель (`cm_handle_interm_exchange_req`) пробегает RTT до кандидатов инициатора (probe при протухших), шлёт `EXCHANGE_RESP` со своими кандидатами + замерами RTT.
|
||||
3. Цель (`cm_handle_interm_exchange_resp`) кеширует ответ, проверяет свежесть RTT до своих кандидатов; протухшие — probe + ретрай 200мс; свежие → `cm_compute_intermediaries`.
|
||||
4. `cm_compute_intermediaries` суммирует RTT по общим узлам, сортирует, берёт топ-3, шлёт `INTERM_SELECTED`, помечает `conn_type=INDIRECT`, `CONNECTED`, UP.
|
||||
5. Цель (`cm_handle_interm_selected`) находит свой entry по `src`, сохраняет посредников, помечает `CONNECTED`, `cm_update_nodeinfo` + UP (если ещё не было).
|
||||
|
||||
Трафик идёт через `etcp_router` (первый посредник с живым conn; `rr_idx` зарезервирован, не используется).
|
||||
|
||||
## 5. Протокол (ETCP_RT_ID_CONN_MGR)
|
||||
|
||||
### Подкоманды
|
||||
|
||||
| Константа | Значение | Описание |
|
||||
|-----------|----------|----------|
|
||||
| `CM_SUBCMD_DIRECT_REQ` | 0x01 | Фаза 2: "я за NAT, подключись ко мне" — наши адреса |
|
||||
| `CM_SUBCMD_DIRECT_RESP` | 0x02 | Ответ на DIRECT_REQ (зарезервирован) |
|
||||
| `CM_SUBCMD_INTERM_EXCHANGE_REQ` | 0x03 | Фаза 3 инициатор: наш топ-4 кандидатов |
|
||||
| `CM_SUBCMD_INTERM_EXCHANGE_RESP` | 0x04 | Фаза 3 ответ: свои + чужие кандидаты с RTT обеих сторон |
|
||||
| `CM_SUBCMD_INTERM_SELECTED` | 0x05 | Фаза 3 финал: выбранные посредники |
|
||||
| `CM_SUBCMD_DISCONNECT` | 0x06 | Уведомление о разрыве |
|
||||
|
||||
### Пакеты протокола (все packed)
|
||||
|
||||
| Структура | Поля |
|
||||
|-----------|------|
|
||||
| `CM_DIRECT_REQ` | `cmd, subcmd, request_id, addr_count` + массив адресов `{type, ip[4], port, sock_id}` |
|
||||
| `CM_DIRECT_RESP` | `cmd, subcmd, request_id, accepted` |
|
||||
| `CM_EXCHANGE_REQ` | `cmd, subcmd, request_id, candidate_count, candidates[4]` |
|
||||
| `CM_EXCHANGE_RESP` | `cmd, subcmd, request_id, my_count, your_count, my_candidates[4], your_candidates[4]` |
|
||||
| `CM_INTERM_SEL` | `cmd, subcmd, request_id, count, selected[3]` |
|
||||
| `CM_DISCONNECT` | `cmd, subcmd, node_id` |
|
||||
|
||||
## 6. Фоновые процессы (monitor.c)
|
||||
|
||||
| Таймер | Интервал | Описание |
|
||||
|--------|----------|----------|
|
||||
| `bg_ping` | ~100мс (один узел за тик, цикл ≥10с) | Пинг всех BGP-нод без активного соединения, поддерживает свежие RTT для best_candidates |
|
||||
| `candidate_ping` | ~2с | Удаляет протухших кандидатов (>30с), обновляет `best_candidates`, probe'ит неактивных |
|
||||
|
||||
## 7. Ключевые константы
|
||||
|
||||
| Константа | Значение | Смысл |
|
||||
|-----------|----------|-------|
|
||||
| `CONN_MGR_MAX_CANDIDATES` | 3 | Макс. число лучших посредников в списке |
|
||||
| `CONN_MGR_MAX_INTERMEDIARIES` | 3 | Макс. число выбранных посредников для одного соединения |
|
||||
| `CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS` | 2000 | Таймаут фазы 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_LOCAL_SCAN_ATTEMPTS` | 3 | Попыток локального сканирования |
|
||||
| `CONN_MGR_LOCAL_SCAN_TIMEOUT_MS` | 100 | Таймаут одной попытки локального скана |
|
||||
| `CACHE_MS` | 20000 | Срок свежести RTT-замера (~20с) |
|
||||
|
||||
## 8. Типы соединений (итоговый результат)
|
||||
|
||||
| Тип | Значение | Описание |
|
||||
|-----|----------|----------|
|
||||
| `CONN_TYPE_NONE` | 0 | Нет соединения |
|
||||
| `CONN_TYPE_DIRECT` | 1 | Прямое соединение |
|
||||
| `CONN_TYPE_REVERSE` | 2 | Цель подключилась к нам |
|
||||
| `CONN_TYPE_INDIRECT` | 3 | Через посредника |
|
||||
|
||||
## 9. Тесты
|
||||
|
||||
| Тест | Покрытие |
|
||||
|------|----------|
|
||||
| `tests/test_conn_mgr.c` | DIRECT (существующее соединение + узел из БД), TIMEOUT |
|
||||
| `tests/test_conn_mgr_already_connected.c` | DIRECT к уже подключённому узлу |
|
||||
| `tests/test_conn_mgr_phases.c` | Все три фазы на loopback-топологии C(relay)↔A/B: DIRECT, REVERSE (A public + B nat), INDIRECT (A/B nat через C). Адреса цели «портятся» на неверный порт, чтобы DIRECT честно провалился; таймаут probe уменьшен до 10мс через `route_connectivity_set_probe_timeout_ms()` |
|
||||
Проверки: `test_conn_mgr`, `test_conn_mgr_handles`, `test_conn_mgr_already_connected`,
|
||||
`test_conn_mgr_phases`. Последний проверяет расписание, LAN, односторонний INDIRECT,
|
||||
поздний DIRECT после NCD TIMEOUT, резерв, реальные данные и обработку кандидатов.
|
||||
@@ -1,206 +1,212 @@
|
||||
/* conn_mgr_indirect.c — INDIRECT фаза + exchange-обработчики */
|
||||
/* conn_mgr_indirect.c — согласование и проверка пути через посредников. */
|
||||
#include "conn_mgr_priv.h"
|
||||
#include "etcp.h"
|
||||
#include <string.h>
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "etcp_router.h"
|
||||
#include "utun_instance.h"
|
||||
#include "topo_group.h"
|
||||
#include "route_connectivity.h"
|
||||
|
||||
/* INDIRECT фаза: отправляет EXCHANGE_REQ с нашими лучшими кандидатами (best_candidates)
|
||||
* цели через BGP-маршрут. Ожидаем EXCHANGE_RESP — по RTT-замерам с обеих сторон
|
||||
* выберем общего посредника с минимальной суммой RTT (cm_compute_intermediaries).
|
||||
* Периодически ретрансмитим EXCHANGE_REQ (1с) — надёжность на случай потери пакета. */
|
||||
void cm_start_phase_indirect(struct CONN_MGR_ENTRY* entry) {
|
||||
struct CONN_MGR* mgr = entry->mgr;
|
||||
if (!mgr->best_candidate_count) return;
|
||||
entry->main.request_id = ++mgr->next_request_id;
|
||||
struct CM_EXCHANGE_REQ req; memset(&req,0,sizeof(req));
|
||||
req.cmd=ETCP_RT_ID_CONN_MGR; req.subcmd=CM_SUBCMD_INTERM_EXCHANGE_REQ; req.request_id=entry->main.request_id;
|
||||
req.candidate_count = mgr->best_candidate_count > 4 ? 4 : mgr->best_candidate_count;
|
||||
for (uint8_t i=0;i<req.candidate_count;i++) req.candidates[i]=mgr->best_candidates[i];
|
||||
struct ll_entry* qe = queue_entry_new(0); if (!qe) return;
|
||||
qe->dgram=u_malloc(1+sizeof(req)); qe->dgram[0]=ETCP_RT_ID_CONN_MGR; memcpy(qe->dgram+1,&req,sizeof(req)); qe->len=(uint16_t)(1+sizeof(req));
|
||||
/* копия для ретрансмита (etcp_route_send забирает владение qe) */
|
||||
struct cm_exchange_pending* ep = u_calloc(1,sizeof(*ep));
|
||||
if (ep) {
|
||||
ep->request_id=entry->main.request_id; ep->entry=entry;
|
||||
ep->pkt=u_malloc(1+sizeof(req));
|
||||
if (ep->pkt) { memcpy(ep->pkt, qe->dgram, 1+sizeof(req)); ep->pkt_len=1+sizeof(req); }
|
||||
ep->next=mgr->exchange_pending; mgr->exchange_pending=ep;
|
||||
}
|
||||
etcp_route_send(mgr->instance, mgr->group->group_id, entry->node_id, qe, 1, 0);
|
||||
entry->indirect_active = 1;
|
||||
entry->indirect_deadline_tb = get_time_tb() + CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS*10;
|
||||
if (entry->main.timer) { uasync_cancel_timeout(mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; }
|
||||
entry->main.timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_EXCHANGE_RETRANSMIT_MS*10, entry, cm_exchange_tick_cb, "conn_mgr_interm_exch");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 3 sent EXCHANGE_REQ to 0x%016llx with %u candidates",
|
||||
(unsigned long long)entry->node_id, req.candidate_count);
|
||||
/* Оба плеча посредника должны быть прямыми READY-сессиями. RTT=0/FFFF — нет замера. */
|
||||
static int cm_valid_candidate(struct CONN_MGR* mgr, uint64_t target, uint64_t nid, uint16_t remote_rtt) {
|
||||
return nid && nid != mgr->instance->node_id && nid != target && remote_rtt && remote_rtt != UINT16_MAX &&
|
||||
cm_candidate_rtt(mgr, nid) != UINT16_MAX;
|
||||
}
|
||||
|
||||
/* Периодический тик INDIRECT: ретрансмит EXCHANGE_REQ, по дедлайну — таймаут обмена. */
|
||||
void cm_exchange_tick_cb(void* arg) {
|
||||
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg;
|
||||
entry->main.timer = NULL;
|
||||
if (!entry->indirect_active) return;
|
||||
if (get_time_tb() >= entry->indirect_deadline_tb) {
|
||||
entry->indirect_active = 0;
|
||||
{ struct cm_exchange_pending** pp=&entry->mgr->exchange_pending;
|
||||
while(*pp){if((*pp)->entry==entry){struct cm_exchange_pending* ep=*pp; *pp=ep->next; if(ep->pkt)u_free(ep->pkt); u_free(ep); break;} pp=&(*pp)->next;} }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: exchange timeout for 0x%016llx", (unsigned long long)entry->node_id);
|
||||
if (!entry->reverse_active) cm_deliver_event(entry, CONN_EVENT_TIMEOUT);
|
||||
void cm_start_phase_indirect(struct CONN_MGR_ENTRY* entry) {
|
||||
struct CONN_MGR* mgr = entry->mgr;
|
||||
if (!entry->indirect_started) {
|
||||
entry->indirect_started = 1; entry->exchange_request_id = ++mgr->next_request_id;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: INDIRECT start peer=%016llx elapsed=%llums candidates=%u",
|
||||
(unsigned long long)entry->node_id, (unsigned long long)((get_time_tb() - entry->start_tb) / 10),
|
||||
mgr->best_candidate_count);
|
||||
}
|
||||
entry->indirect_next_tb = get_time_tb() + CONN_MGR_EXCHANGE_RETRANSMIT_MS * 10;
|
||||
if (entry->selection.count) {
|
||||
cm_send_control(mgr, entry->node_id, &entry->selection, sizeof(entry->selection));
|
||||
return;
|
||||
}
|
||||
for (struct cm_exchange_pending* x = entry->mgr->exchange_pending; x; x = x->next) {
|
||||
if (x->entry == entry && x->pkt) {
|
||||
struct ll_entry* qe = queue_entry_new(0);
|
||||
if (qe) {
|
||||
qe->dgram = u_malloc(x->pkt_len);
|
||||
if (qe->dgram) { memcpy(qe->dgram, x->pkt, x->pkt_len); qe->len = (uint16_t)x->pkt_len;
|
||||
etcp_route_send(entry->mgr->instance, entry->mgr->group->group_id, entry->node_id, qe, 1, 0); }
|
||||
else queue_entry_free(qe);
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "conn_mgr: exchange retransmit EXCHANGE_REQ 0x%016llx", (unsigned long long)entry->node_id);
|
||||
struct CM_EXCHANGE_REQ req = {0};
|
||||
req.cmd = ETCP_RT_ID_CONN_MGR; req.subcmd = CM_SUBCMD_INTERM_EXCHANGE_REQ; req.request_id = entry->exchange_request_id;
|
||||
for (uint8_t i = 0; i < mgr->best_candidate_count; i++) {
|
||||
uint64_t nid = mgr->best_candidates[i].node_id;
|
||||
uint16_t rtt = cm_candidate_rtt(mgr, nid);
|
||||
if (nid == entry->node_id || rtt == UINT16_MAX) continue;
|
||||
req.candidates[req.candidate_count++] = (struct CONN_MGR_CANDIDATE){nid, rtt};
|
||||
}
|
||||
// Даже пустой запрос позволяет получить кандидатов другой стороны. Повторы подхватят новые замеры.
|
||||
cm_send_control(mgr, entry->node_id, &req, sizeof(req));
|
||||
}
|
||||
|
||||
void cm_finalize_indirect(struct CONN_MGR_ENTRY* entry) {
|
||||
if (!entry->intermediariy_count) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: empty intermediary selection peer=%016llx", (unsigned long long)entry->node_id);
|
||||
return;
|
||||
}
|
||||
entry->indirect_confirmed = 1;
|
||||
uint8_t old_type = entry->conn_type;
|
||||
cm_refresh_path(entry); // Готовый прямой путь имеет приоритет; повторного UP при улучшении нет.
|
||||
if (entry->state != CONN_MGR_STATE_DISCONNECTED && entry->conn_type == old_type) cm_update_nodeinfo(entry);
|
||||
}
|
||||
|
||||
/* Объединение кандидатов обеих сторон без дублей, выбор по сумме двух действительных RTT. */
|
||||
void cm_compute_intermediaries(struct CONN_MGR_ENTRY* entry, struct CM_EXCHANGE_RESP* resp) {
|
||||
struct scored_candidate { uint64_t nid; uint32_t rtt; };
|
||||
struct scored_candidate all[2 * CONN_MGR_MAX_CANDIDATES]; uint8_t count = 0;
|
||||
for (int side = 0; side < 2; side++) {
|
||||
uint8_t n = side ? resp->your_count : resp->my_count;
|
||||
struct CONN_MGR_CANDIDATE* candidates = side ? resp->your_candidates : resp->my_candidates;
|
||||
for (uint8_t i = 0; i < n; i++) {
|
||||
uint64_t nid = candidates[i].node_id;
|
||||
if (!cm_valid_candidate(entry->mgr, entry->node_id, nid, candidates[i].rtt)) continue;
|
||||
int duplicate = 0;
|
||||
for (uint8_t j = 0; j < count; j++) if (all[j].nid == nid) duplicate = 1;
|
||||
if (duplicate) continue;
|
||||
all[count].nid = nid; all[count++].rtt = (uint32_t)cm_candidate_rtt(entry->mgr, nid) + candidates[i].rtt;
|
||||
}
|
||||
}
|
||||
for (uint8_t i = 0; i < count; i++) for (uint8_t j = i + 1; j < count; j++) {
|
||||
if (all[j].rtt < all[i].rtt || (all[j].rtt == all[i].rtt && all[j].nid < all[i].nid)) {
|
||||
struct scored_candidate tmp = all[i]; all[i] = all[j]; all[j] = tmp;
|
||||
}
|
||||
}
|
||||
if (!count) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "conn_mgr: no common measured READY intermediary peer=%016llx; retry pending",
|
||||
(unsigned long long)entry->node_id);
|
||||
return;
|
||||
}
|
||||
struct CM_INTERM_SEL* sel = &entry->selection; memset(sel, 0, sizeof(*sel));
|
||||
sel->cmd = ETCP_RT_ID_CONN_MGR; sel->subcmd = CM_SUBCMD_INTERM_SELECTED; sel->request_id = entry->exchange_request_id;
|
||||
sel->count = count > CONN_MGR_MAX_INTERMEDIARIES ? CONN_MGR_MAX_INTERMEDIARIES : count;
|
||||
for (uint8_t i = 0; i < sel->count; i++) {
|
||||
sel->selected[i].node_id = all[i].nid; sel->selected[i].rtt = cm_candidate_rtt(entry->mgr, all[i].nid);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "conn_mgr: intermediary peer=%016llx via=%016llx sum_rtt=%u",
|
||||
(unsigned long long)entry->node_id, (unsigned long long)all[i].nid, all[i].rtt);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: INDIRECT selected peer=%016llx count=%u; verifying delivery",
|
||||
(unsigned long long)entry->node_id, sel->count);
|
||||
cm_send_control(entry->mgr, entry->node_id, sel, sizeof(*sel));
|
||||
}
|
||||
|
||||
void cm_handle_interm_exchange_resp(struct CONN_MGR* mgr, uint64_t src, const uint8_t* data, size_t len) {
|
||||
if (len != sizeof(struct CM_EXCHANGE_RESP)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: malformed EXCHANGE_RESP peer=%016llx len=%zu", (unsigned long long)src, len);
|
||||
return;
|
||||
}
|
||||
struct CM_EXCHANGE_RESP resp; memcpy(&resp, data, sizeof(resp));
|
||||
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, src);
|
||||
if (!entry || !entry->indirect_started || entry->indirect_confirmed || entry->selection.count ||
|
||||
entry->exchange_request_id != resp.request_id || get_time_tb() >= entry->deadline_tb) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "conn_mgr: stale EXCHANGE_RESP peer=%016llx request=%u", (unsigned long long)src, resp.request_id);
|
||||
return;
|
||||
}
|
||||
if (resp.my_count > CONN_MGR_MAX_CANDIDATES || resp.your_count > CONN_MGR_MAX_CANDIDATES) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: invalid candidate counts peer=%016llx", (unsigned long long)src); return;
|
||||
}
|
||||
cm_compute_intermediaries(entry, &resp);
|
||||
}
|
||||
|
||||
void cm_handle_interm_selected(struct CONN_MGR* mgr, uint64_t src, const uint8_t* data, size_t len) {
|
||||
if (len != sizeof(struct CM_INTERM_SEL)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: malformed INTERM_SELECTED peer=%016llx len=%zu", (unsigned long long)src, len);
|
||||
return;
|
||||
}
|
||||
struct CM_INTERM_SEL sel; memcpy(&sel, data, sizeof(sel));
|
||||
if (!sel.count || sel.count > CONN_MGR_MAX_INTERMEDIARIES) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: invalid intermediary count=%u peer=%016llx", sel.count, (unsigned long long)src); return;
|
||||
}
|
||||
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, src);
|
||||
if (!entry) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: selection without exchange peer=%016llx", (unsigned long long)src); return; }
|
||||
if (entry->peer_exchange_request_id != sel.request_id) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: stale selection peer=%016llx request=%u", (unsigned long long)src, sel.request_id);
|
||||
return;
|
||||
}
|
||||
if (get_time_tb() >= entry->deadline_tb && !entry->indirect_confirmed) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: selection after deadline peer=%016llx", (unsigned long long)src);
|
||||
return;
|
||||
}
|
||||
struct CM_INTERM_SEL ack = {0}; ack.cmd = ETCP_RT_ID_CONN_MGR; ack.subcmd = CM_SUBCMD_INTERM_ACK; ack.request_id = sel.request_id;
|
||||
for (uint8_t i = 0; i < sel.count; i++) {
|
||||
uint64_t nid = sel.selected[i].node_id;
|
||||
if (!cm_valid_candidate(mgr, src, nid, sel.selected[i].rtt)) continue;
|
||||
int duplicate = 0;
|
||||
for (uint8_t j = 0; j < ack.count; j++) if (ack.selected[j].node_id == nid) duplicate = 1;
|
||||
if (!duplicate) ack.selected[ack.count++] = (struct CONN_MGR_CANDIDATE){nid, cm_candidate_rtt(mgr, nid)};
|
||||
}
|
||||
if (!ack.count) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: selected intermediaries unavailable peer=%016llx", (unsigned long long)src);
|
||||
cm_send_control(mgr, src, &ack, sizeof(ack)); return;
|
||||
}
|
||||
for (uint8_t i = 0; i < ack.count; i++) entry->intermediaries[i] = ack.selected[i].node_id;
|
||||
entry->intermediariy_count = ack.count;
|
||||
// Публикуем путь до ACK, чтобы ответ прошёл через выбранного посредника.
|
||||
cm_finalize_indirect(entry);
|
||||
cm_send_control(mgr, src, &ack, sizeof(ack));
|
||||
}
|
||||
|
||||
void cm_handle_interm_ack(struct CONN_MGR* mgr, uint64_t src, const uint8_t* data, size_t len) {
|
||||
if (len != sizeof(struct CM_INTERM_SEL)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: malformed INTERM_ACK peer=%016llx len=%zu", (unsigned long long)src, len); return;
|
||||
}
|
||||
struct CM_INTERM_SEL ack; memcpy(&ack, data, sizeof(ack));
|
||||
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, src);
|
||||
if (!entry || entry->exchange_request_id != ack.request_id || !entry->selection.count || entry->indirect_confirmed ||
|
||||
get_time_tb() >= entry->deadline_tb) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "conn_mgr: stale INTERM_ACK peer=%016llx request=%u", (unsigned long long)src, ack.request_id);
|
||||
return;
|
||||
}
|
||||
if (ack.count > CONN_MGR_MAX_INTERMEDIARIES) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: invalid ACK count=%u peer=%016llx", ack.count, (unsigned long long)src); return;
|
||||
}
|
||||
entry->intermediariy_count = 0;
|
||||
for (uint8_t i = 0; i < ack.count; i++) {
|
||||
uint64_t nid = ack.selected[i].node_id;
|
||||
if (!cm_valid_candidate(mgr, src, nid, ack.selected[i].rtt)) continue;
|
||||
for (uint8_t j = 0; j < entry->selection.count; j++) {
|
||||
if (entry->selection.selected[j].node_id != nid) continue;
|
||||
int duplicate = 0;
|
||||
for (uint8_t k = 0; k < entry->intermediariy_count; k++) if (entry->intermediaries[k] == nid) duplicate = 1;
|
||||
if (!duplicate) entry->intermediaries[entry->intermediariy_count++] = nid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
entry->main.timer = uasync_set_timeout(entry->mgr->instance->ua, CONN_MGR_EXCHANGE_RETRANSMIT_MS*10,
|
||||
entry, cm_exchange_tick_cb, "conn_mgr_interm_exch");
|
||||
}
|
||||
|
||||
/* Финализация INDIRECT: пометить CONNECTED и доставить UP. Вызывается либо сразу
|
||||
* (reverse не активен), либо из cm_reverse_tick_cb после reverse-таймаута. */
|
||||
void cm_finalize_indirect(struct CONN_MGR_ENTRY* entry) {
|
||||
entry->conn_type = CONN_TYPE_INDIRECT;
|
||||
entry->state = CONN_MGR_STATE_CONNECTED;
|
||||
entry->indirect_active = 0; entry->indirect_ready = 0;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: INDIRECT CONNECTED 0x%016llx via %u intermediaries",
|
||||
(unsigned long long)entry->node_id, entry->intermediariy_count);
|
||||
cm_update_nodeinfo(entry);
|
||||
cm_deliver_event(entry, CONN_EVENT_UP);
|
||||
}
|
||||
|
||||
/* Вычисляет лучших общих посредников по данным из EXCHANGE_RESP:
|
||||
* your_candidates — кандидаты инициатора + НАШ RTT до них
|
||||
* my_candidates — НАШИ кандидаты + RTT цели до них
|
||||
* Суммирует RTT для каждого общего узла, сортирует, выбирает топ-3.
|
||||
* Отправляет INTERM_SELECTED инициатору и доставляет CONN_EVENT_UP. */
|
||||
void cm_compute_intermediaries(struct CONN_MGR_ENTRY* entry, struct CM_EXCHANGE_RESP* resp) {
|
||||
struct { uint64_t nid; uint64_t rtt; } all[8]; uint8_t cnt=0;
|
||||
uint8_t your_n = resp->your_count > 4 ? 4 : resp->your_count;
|
||||
uint8_t my_n = resp->my_count > 4 ? 4 : resp->my_count;
|
||||
uint64_t self = entry->mgr->instance->node_id, target = entry->node_id;
|
||||
for (uint8_t i=0;i<your_n&&cnt<8;i++) {
|
||||
uint64_t nid=resp->your_candidates[i].node_id;
|
||||
if (nid==self||nid==target) continue; /* сам/цель не может быть посредником */
|
||||
uint16_t our=0; for(uint8_t j=0;j<CONN_MGR_MAX_CANDIDATES;j++) if(entry->mgr->best_candidates[j].node_id==nid){our=entry->mgr->best_candidates[j].rtt;break;}
|
||||
all[cnt]=(typeof(all[0])){nid,(uint64_t)our+resp->your_candidates[i].rtt}; cnt++;
|
||||
if (!entry->intermediariy_count) {
|
||||
memset(&entry->selection, 0, sizeof(entry->selection));
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: intermediary verification failed peer=%016llx; repeating exchange", (unsigned long long)src);
|
||||
return;
|
||||
}
|
||||
for (uint8_t i=0;i<my_n&&cnt<8;i++) {
|
||||
uint64_t nid=resp->my_candidates[i].node_id;
|
||||
if (nid==self||nid==target) continue; /* сам/цель не может быть посредником */
|
||||
struct TOPO_GROUP_NODE* nq=topo_node_find_by_id(entry->mgr->group,nid);
|
||||
uint16_t our=nq?topo_get_chain_rtt(nq):0; if(our==0xFFFF)our=0;
|
||||
all[cnt]=(typeof(all[0])){nid,(uint64_t)our+resp->my_candidates[i].rtt}; cnt++;
|
||||
}
|
||||
for (uint8_t i=0;i<cnt;i++) for(uint8_t j=i+1;j<cnt;j++) if(all[j].rtt<all[i].rtt){typeof(all[0])t=all[i];all[i]=all[j];all[j]=t;}
|
||||
uint8_t sel=cnt>CONN_MGR_MAX_INTERMEDIARIES?CONN_MGR_MAX_INTERMEDIARIES:cnt;
|
||||
for(uint8_t i=0;i<sel;i++)entry->intermediaries[i]=all[i].nid;
|
||||
entry->intermediariy_count=sel; entry->rr_idx=0;
|
||||
struct CM_INTERM_SEL pkt; memset(&pkt,0,sizeof(pkt));
|
||||
pkt.cmd=ETCP_RT_ID_CONN_MGR; pkt.subcmd=CM_SUBCMD_INTERM_SELECTED; pkt.count=sel;
|
||||
for(uint8_t i=0;i<sel;i++){pkt.selected[i].node_id=all[i].nid;pkt.selected[i].rtt=0;
|
||||
struct TOPO_GROUP_NODE* nq=topo_node_find_by_id(entry->mgr->group,all[i].nid); if(nq)pkt.selected[i].rtt=topo_get_chain_rtt(nq);}
|
||||
struct ll_entry* qe=queue_entry_new(0);
|
||||
if(qe){qe->dgram=u_malloc(1+sizeof(pkt));qe->dgram[0]=ETCP_RT_ID_CONN_MGR;memcpy(qe->dgram+1,&pkt,sizeof(pkt));qe->len=(uint16_t)(1+sizeof(pkt));etcp_route_send(entry->mgr->instance,entry->mgr->group->group_id,entry->node_id,qe,1,0);}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL,"conn_mgr: indirect OK for 0x%016llx via %u intermediaries",(unsigned long long)entry->node_id,sel);
|
||||
/* reverse приоритетнее: если он ещё идёт — откладываем финализацию (indirect_ready),
|
||||
* иначе финализируем INDIRECT сразу. */
|
||||
if (entry->reverse_active) { entry->indirect_ready = 1; DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: indirect deferred 0x%016llx (reverse active)", (unsigned long long)entry->node_id); return; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: INDIRECT confirmed peer=%016llx count=%u elapsed=%llums",
|
||||
(unsigned long long)src, entry->intermediariy_count, (unsigned long long)((get_time_tb() - entry->start_tb) / 10));
|
||||
cm_finalize_indirect(entry);
|
||||
}
|
||||
|
||||
void cm_exchange_probe_retry_cb(void* arg) {
|
||||
struct cm_exchange_pending* ep=(struct cm_exchange_pending*)arg; ep->timer=NULL;
|
||||
uint8_t need=0; uint64_t now=get_time_tb();
|
||||
for(uint8_t i=0;i<ep->cached.my_count&&i<4;i++){
|
||||
struct TOPO_GROUP_NODE* nq=topo_node_find_by_id(ep->entry->mgr->group,ep->cached.my_candidates[i].node_id);
|
||||
if(!nq) continue; /* self / не в группе — не посредник, пропускаем */
|
||||
if(!cm_is_rtt_fresh(nq,now)){need++;break;}
|
||||
}
|
||||
if(!need){cm_compute_intermediaries(ep->entry,&ep->cached);struct cm_exchange_pending**pp=&ep->entry->mgr->exchange_pending;while(*pp){if(*pp==ep){*pp=ep->next;break;}pp=&(*pp)->next;}if(ep->pkt)u_free(ep->pkt);u_free(ep);}
|
||||
else ep->timer=uasync_set_timeout(ep->entry->mgr->instance->ua,2000,ep,cm_exchange_probe_retry_cb,"cm_exch_probe");
|
||||
}
|
||||
|
||||
/* Инициатор получил EXCHANGE_RESP: кеширует ответ, проверяет свежесть RTT
|
||||
* до СВОИХ кандидатов (тех, что в my_candidates ответа). Если протухли —
|
||||
* probe + retry-таймер 200мс. Если свежие — cm_compute_intermediaries. */
|
||||
void cm_handle_interm_exchange_resp(struct CONN_MGR* mgr, const uint8_t* data, size_t len) {
|
||||
const struct CM_EXCHANGE_RESP* resp=(const struct CM_EXCHANGE_RESP*)data;
|
||||
if(len<offsetof(struct CM_EXCHANGE_RESP,your_candidates))return;
|
||||
if(resp->my_count>4||resp->your_count>4)return;
|
||||
if(len<offsetof(struct CM_EXCHANGE_RESP,your_candidates)+(size_t)resp->your_count*sizeof(struct CONN_MGR_CANDIDATE))return;
|
||||
struct cm_exchange_pending* ep=mgr->exchange_pending;
|
||||
while(ep){if(ep->request_id==resp->request_id)break;ep=ep->next;}
|
||||
if(!ep){DEBUG_INFO(DEBUG_CATEGORY_GENERAL,"conn_mgr: EXCHANGE_RESP req=%u no pending",resp->request_id);return;}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL,"conn_mgr: EXCHANGE_RESP req=%u my=%u your=%u",
|
||||
resp->request_id, resp->my_count, resp->your_count);
|
||||
if(ep->entry->main.timer){uasync_cancel_timeout(mgr->instance->ua,ep->entry->main.timer);ep->entry->main.timer=NULL;}
|
||||
memcpy(&ep->cached,resp,len<sizeof(ep->cached)?len:sizeof(ep->cached)); ep->received=1; ep->probed=0;
|
||||
uint8_t need=0; uint64_t now=get_time_tb();
|
||||
for(uint8_t i=0;i<ep->cached.my_count&&i<4;i++){
|
||||
struct TOPO_GROUP_NODE* nq=topo_node_find_by_id(mgr->group,ep->cached.my_candidates[i].node_id);
|
||||
if(!nq) continue; /* self / не в группе — не посредник, пропускаем */
|
||||
if(!cm_is_rtt_fresh(nq,now)){need++;route_connectivity_probe_node(mgr->instance,mgr->group,nq);}
|
||||
}
|
||||
if(!need){ep->probed=1;cm_compute_intermediaries(ep->entry,&ep->cached);struct cm_exchange_pending**pp=&mgr->exchange_pending;while(*pp){if(*pp==ep){*pp=ep->next;break;}pp=&(*pp)->next;}if(ep->pkt)u_free(ep->pkt);u_free(ep);return;}
|
||||
ep->timer=uasync_set_timeout(mgr->instance->ua,2000,ep,cm_exchange_probe_retry_cb,"cm_exch_probe");
|
||||
}
|
||||
|
||||
/* Финальный выбор посредников от инициатора (приходит второй стороне INDIRECT-
|
||||
* обмена). Сохраняет выбранные intermediaries в entry, помечает CONNECTED. */
|
||||
void cm_handle_interm_selected(struct CONN_MGR* mgr, uint64_t src, const uint8_t* data, size_t len) {
|
||||
const struct CM_INTERM_SEL* sel=(const struct CM_INTERM_SEL*)data;
|
||||
if(len<offsetof(struct CM_INTERM_SEL,selected))return;
|
||||
uint8_t c=sel->count>CONN_MGR_MAX_INTERMEDIARIES?CONN_MGR_MAX_INTERMEDIARIES:sel->count;
|
||||
if(len<offsetof(struct CM_INTERM_SEL,selected)+(size_t)c*sizeof(struct CONN_MGR_CANDIDATE))return;
|
||||
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, src);
|
||||
if(!entry)return;
|
||||
if(entry->state==CONN_MGR_STATE_CONNECTED)return; /* симметричный случай — UP уже доставлен инициаторским путём */
|
||||
if(!entry->indirect_active && !entry->reverse_active)return; /* не INDIRECT/REVERSE фаза */
|
||||
for(uint8_t i=0;i<c;i++)entry->intermediaries[i]=sel->selected[i].node_id;
|
||||
entry->intermediariy_count=c; entry->rr_idx=0;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL,"conn_mgr: got INTERM_SELECTED from 0x%016llx count=%u",(unsigned long long)src,c);
|
||||
if (entry->reverse_active) { entry->indirect_ready = 1; DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: indirect deferred 0x%016llx (reverse active)", (unsigned long long)entry->node_id); return; }
|
||||
cm_finalize_indirect(entry);
|
||||
}
|
||||
|
||||
/* Цель получила EXCHANGE_REQ: измеряет RTT до кандидатов инициатора (если
|
||||
* замеры протухли — запускает probe), отправляет EXCHANGE_RESP со СВОИМИ
|
||||
* кандидатами + замерами RTT до кандидатов инициатора. */
|
||||
void cm_handle_interm_exchange_req(uint64_t src, struct CONN_MGR* mgr, struct CM_EXCHANGE_REQ* req) {
|
||||
if (!mgr) return;
|
||||
if (req->candidate_count > 4) return;
|
||||
uint64_t now=get_time_tb();
|
||||
for(uint8_t i=0;i<req->candidate_count&&i<4;i++){
|
||||
struct TOPO_GROUP_NODE* nq=topo_node_find_by_id(mgr->group,req->candidates[i].node_id);
|
||||
if(nq&&cm_is_rtt_fresh(nq,now))continue; route_connectivity_probe_node(mgr->instance,mgr->group,nq);
|
||||
if (req->candidate_count > CONN_MGR_MAX_CANDIDATES || !topo_node_registry_find(mgr->instance->topo_groups, src)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: invalid EXCHANGE_REQ peer=%016llx count=%u", (unsigned long long)src, req->candidate_count);
|
||||
return;
|
||||
}
|
||||
struct CM_EXCHANGE_RESP resp; memset(&resp,0,sizeof(resp));
|
||||
resp.cmd=ETCP_RT_ID_CONN_MGR; resp.subcmd=CM_SUBCMD_INTERM_EXCHANGE_RESP; resp.request_id=req->request_id;
|
||||
resp.my_count=mgr->best_candidate_count>4?4:mgr->best_candidate_count;
|
||||
for(uint8_t i=0;i<resp.my_count;i++)resp.my_candidates[i]=mgr->best_candidates[i];
|
||||
resp.your_count=req->candidate_count>4?4:req->candidate_count;
|
||||
for(uint8_t i=0;i<resp.your_count;i++){
|
||||
resp.your_candidates[i].node_id=req->candidates[i].node_id;
|
||||
struct TOPO_GROUP_NODE* nq2=topo_node_find_by_id(mgr->group,req->candidates[i].node_id);
|
||||
resp.your_candidates[i].rtt=nq2?topo_get_chain_rtt(nq2):0;
|
||||
struct CONN_MGR_ENTRY* entry = cm_ensure_entry(mgr, src);
|
||||
if (!entry) return;
|
||||
entry->peer_exchange_request_id = req->request_id;
|
||||
if (entry->state == CONN_MGR_STATE_DISCONNECTED) {
|
||||
entry->state = CONN_MGR_STATE_CONNECTING; entry->start_tb = get_time_tb();
|
||||
entry->deadline_tb = entry->start_tb + CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS * 10;
|
||||
cm_arm_timer(entry);
|
||||
}
|
||||
size_t sz=offsetof(struct CM_EXCHANGE_RESP,your_candidates)+(size_t)resp.your_count*sizeof(struct CONN_MGR_CANDIDATE);
|
||||
struct ll_entry* qe=queue_entry_new(0);
|
||||
if(qe){qe->dgram=u_malloc(1+sz);qe->dgram[0]=ETCP_RT_ID_CONN_MGR;memcpy(qe->dgram+1,&resp,sz);qe->len=(uint16_t)(1+sz);etcp_route_send(mgr->instance,mgr->group->group_id,src,qe,1,0);}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL,"conn_mgr: sent EXCHANGE_RESP to 0x%016llx my=%u your=%u",(unsigned long long)src,resp.my_count,resp.your_count);
|
||||
struct CM_EXCHANGE_RESP resp = {0};
|
||||
resp.cmd = ETCP_RT_ID_CONN_MGR; resp.subcmd = CM_SUBCMD_INTERM_EXCHANGE_RESP; resp.request_id = req->request_id;
|
||||
for (uint8_t i = 0; i < mgr->best_candidate_count; i++) {
|
||||
uint64_t nid = mgr->best_candidates[i].node_id; uint16_t rtt = cm_candidate_rtt(mgr, nid);
|
||||
if (nid == src || rtt == UINT16_MAX) continue;
|
||||
resp.my_candidates[resp.my_count++] = (struct CONN_MGR_CANDIDATE){nid, rtt};
|
||||
}
|
||||
for (uint8_t i = 0; i < req->candidate_count; i++) {
|
||||
uint64_t nid = req->candidates[i].node_id; uint16_t rtt = cm_candidate_rtt(mgr, nid);
|
||||
if (nid == src || nid == mgr->instance->node_id || rtt == UINT16_MAX) continue;
|
||||
resp.your_candidates[resp.your_count++] = (struct CONN_MGR_CANDIDATE){nid, rtt};
|
||||
}
|
||||
cm_send_control(mgr, src, &resp, sizeof(resp));
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "conn_mgr: EXCHANGE_RESP peer=%016llx request=%u my=%u your=%u",
|
||||
(unsigned long long)src, req->request_id, resp.my_count, resp.your_count);
|
||||
}
|
||||
@@ -26,9 +26,8 @@ struct stcp_client;
|
||||
#define CONN_MGR_CANDIDATE_PING_TB 20000
|
||||
#define CONN_MGR_BG_PING_INTERVAL_TB 1000
|
||||
#define CONN_MGR_BG_PING_CYCLE_MIN_TB 600000
|
||||
#define CONN_MGR_LOCAL_SCAN_ATTEMPTS 3
|
||||
#define CONN_MGR_LOCAL_SCAN_TIMEOUT_MS 100
|
||||
#define CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS 2000
|
||||
#define CONN_MGR_REVERSE_DELAY_MS 500
|
||||
#define CONN_MGR_INDIRECT_DELAY_MS 1000
|
||||
#define CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS 3000
|
||||
#define CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS 15000
|
||||
#define CONN_MGR_REVERSE_RETRANSMIT_MS 1000
|
||||
@@ -61,15 +60,16 @@ enum {
|
||||
CM_SUBCMD_INTERM_EXCHANGE_RESP= 0x04,
|
||||
CM_SUBCMD_INTERM_SELECTED = 0x05,
|
||||
CM_SUBCMD_DISCONNECT = 0x06,
|
||||
CM_SUBCMD_INTERM_ACK = 0x07,
|
||||
};
|
||||
|
||||
struct CONN_MGR_CANDIDATE { uint64_t node_id; uint16_t rtt; } __attribute__((packed));
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct CM_DIRECT_REQ { uint8_t cmd, subcmd; uint32_t request_id; uint8_t addr_count; };
|
||||
struct CM_DIRECT_REQ { uint8_t cmd, subcmd; uint32_t request_id; };
|
||||
struct CM_DIRECT_RESP { uint8_t cmd, subcmd; uint32_t request_id; uint8_t accepted; };
|
||||
struct CM_EXCHANGE_REQ { uint8_t cmd, subcmd; uint32_t request_id; uint8_t candidate_count; struct CONN_MGR_CANDIDATE candidates[4]; };
|
||||
struct CM_EXCHANGE_RESP { uint8_t cmd, subcmd; uint32_t request_id; uint8_t my_count, your_count; struct CONN_MGR_CANDIDATE my_candidates[4], your_candidates[4]; };
|
||||
struct CM_EXCHANGE_REQ { uint8_t cmd, subcmd; uint32_t request_id; uint8_t candidate_count; struct CONN_MGR_CANDIDATE candidates[CONN_MGR_MAX_CANDIDATES]; };
|
||||
struct CM_EXCHANGE_RESP { uint8_t cmd, subcmd; uint32_t request_id; uint8_t my_count, your_count; struct CONN_MGR_CANDIDATE my_candidates[CONN_MGR_MAX_CANDIDATES], your_candidates[CONN_MGR_MAX_CANDIDATES]; };
|
||||
struct CM_INTERM_SEL { uint8_t cmd, subcmd; uint32_t request_id; uint8_t count; struct CONN_MGR_CANDIDATE selected[3]; };
|
||||
struct CM_DISCONNECT { uint8_t cmd, subcmd; uint64_t node_id; };
|
||||
#pragma pack(pop)
|
||||
@@ -83,15 +83,6 @@ struct CM_DISCONNECT { uint8_t cmd, subcmd; uint64_t node_id; };
|
||||
|
||||
/* ═══════ внутренние структуры ═══════ */
|
||||
|
||||
struct cm_reverse_pending { struct cm_reverse_pending* next; uint32_t request_id; struct CONN_MGR_ENTRY* entry; uint8_t* pkt; size_t pkt_len; };
|
||||
|
||||
struct cm_exchange_pending {
|
||||
struct cm_exchange_pending* next; uint32_t request_id;
|
||||
struct CONN_MGR_ENTRY* entry; void* timer;
|
||||
struct CM_EXCHANGE_RESP cached; uint8_t received, probed;
|
||||
uint8_t* pkt; size_t pkt_len;
|
||||
};
|
||||
|
||||
struct CONN_MGR_HANDLE {
|
||||
uint64_t node_id; uint64_t group_id; conn_mgr_cb_t cb; void* cb_arg;
|
||||
struct CONN_MGR_ENTRY* entry; struct CONN_MGR_HANDLE* next;
|
||||
@@ -106,13 +97,14 @@ struct CONN_MGR_ENTRY {
|
||||
uint8_t state, conn_type;
|
||||
uint64_t last_traffic_tb;
|
||||
struct CONN_MGR_HANDLE* handles; struct NODE_CONN_DIRECT* ncd_handle;
|
||||
uint8_t ncd_down_notified;
|
||||
uint64_t intermediaries[3]; uint8_t intermediariy_count, rr_idx;
|
||||
uint64_t intermediaries[3]; uint8_t intermediariy_count;
|
||||
uint8_t local_scan_state, main_connect_state, db_loaded;
|
||||
uint8_t reverse_active, indirect_active, indirect_ready;
|
||||
void* reverse_timer;
|
||||
uint64_t reverse_deadline_tb, indirect_deadline_tb;
|
||||
struct { void* timer; uint32_t request_id; void* conn_ctx; } main;
|
||||
uint8_t reverse_started, indirect_started, indirect_confirmed, direct_up, timeout_notified, ever_up;
|
||||
void* timer; void* refresh_token;
|
||||
uint64_t start_tb, reverse_start_tb, indirect_start_tb, deadline_tb;
|
||||
uint64_t reverse_next_tb, reverse_deadline_tb, indirect_next_tb;
|
||||
uint32_t reverse_request_id, exchange_request_id, peer_exchange_request_id;
|
||||
struct CM_INTERM_SEL selection; /* Исходящий выбор до подтверждения пира. */
|
||||
struct CONN_MGR* mgr;
|
||||
};
|
||||
|
||||
@@ -124,10 +116,7 @@ struct CONN_MGR {
|
||||
size_t bg_ping_cursor; uint64_t bg_ping_cycle_start_tb;
|
||||
struct CONN_MGR_CANDIDATE best_candidates[CONN_MGR_MAX_CANDIDATES]; uint8_t best_candidate_count;
|
||||
struct stcp_client* cand_tcp_pings[CONN_MGR_MAX_CANDIDATES]; /* влётные candidate TCP-пинги (для отмены при destroy) */
|
||||
uint32_t next_request_id, direct_timeout_ms; uint8_t initialized;
|
||||
struct cm_reverse_pending* reverse_pending;
|
||||
struct cm_exchange_pending* exchange_pending;
|
||||
uint8_t _unused[0];
|
||||
uint32_t next_request_id; uint8_t initialized;
|
||||
};
|
||||
|
||||
/* ═══════ декларации внутренних функций ═══════ */
|
||||
@@ -151,28 +140,26 @@ void cm_start_phase_indirect(struct CONN_MGR_ENTRY* entry);
|
||||
void cm_start_local_scan(struct CONN_MGR_ENTRY* entry);
|
||||
void cm_handle_direct_req(uint64_t src, struct CONN_MGR* mgr, const uint8_t* data, size_t len);
|
||||
void cm_handle_disconnect(struct CONN_MGR* mgr, uint64_t node_id);
|
||||
void cm_reverse_tick_cb(void* arg);
|
||||
void cm_finalize_indirect(struct CONN_MGR_ENTRY* entry);
|
||||
void cm_add_v4_link(struct ETCP_CONN* conn, const uint8_t* addr, uint16_t port, struct ETCP_SOCKET* s);
|
||||
void cm_add_v6_link(struct ETCP_CONN* conn, const uint8_t addr[16], uint16_t port, struct ETCP_SOCKET* s);
|
||||
void cm_refresh_path(struct CONN_MGR_ENTRY* entry);
|
||||
void cm_arm_timer(struct CONN_MGR_ENTRY* entry);
|
||||
int cm_send_control(struct CONN_MGR* mgr, uint64_t node_id, const void* packet, size_t len);
|
||||
uint16_t cm_candidate_rtt(struct CONN_MGR* mgr, uint64_t node_id);
|
||||
struct ETCP_CONN* cm_direct_conn(struct CONN_MGR* mgr, uint64_t node_id);
|
||||
int cm_has_direct_ip(struct CONN_MGR* mgr, struct TOPO_GROUP_NODE* nq);
|
||||
int cm_has_local_addr(struct CONN_MGR* mgr, struct TOPO_GROUP_NODE* nq);
|
||||
uint8_t cm_classify_v6_addr(const uint8_t addr[16]);
|
||||
uint8_t cm_sock_v6_classify(const struct ETCP_SOCKET* s);
|
||||
|
||||
#define CM_V6_LL 0
|
||||
#define CM_V6_LOC 1
|
||||
#define CM_V6_DIR 2
|
||||
#define CM_V6_OTH 3
|
||||
#define CM_V6_ANY 4
|
||||
|
||||
/* indirect */
|
||||
void cm_exchange_tick_cb(void* arg);
|
||||
void cm_compute_intermediaries(struct CONN_MGR_ENTRY* entry, struct CM_EXCHANGE_RESP* resp);
|
||||
void cm_exchange_probe_retry_cb(void* arg);
|
||||
void cm_handle_interm_exchange_req(uint64_t src, struct CONN_MGR* mgr, struct CM_EXCHANGE_REQ* req);
|
||||
void cm_handle_interm_exchange_resp(struct CONN_MGR* mgr, const uint8_t* data, size_t len);
|
||||
void cm_handle_interm_exchange_resp(struct CONN_MGR* mgr, uint64_t src, const uint8_t* data, size_t len);
|
||||
void cm_handle_interm_selected(struct CONN_MGR* mgr, uint64_t src, const uint8_t* data, size_t len);
|
||||
void cm_handle_interm_ack(struct CONN_MGR* mgr, uint64_t src, const uint8_t* data, size_t len);
|
||||
|
||||
/* monitor */
|
||||
void cm_bg_ping_timer_cb(void* arg);
|
||||
|
||||
@@ -22,6 +22,7 @@ in ---> {Q} -> [src, etcp] --> ... --> [dst,etcp] -> {asm_q} -> {buf q} ---> out
|
||||
#include "etcp.h"
|
||||
#include "utun_instance.h"
|
||||
#include "topo_group.h"
|
||||
#include "conn_mgr.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
@@ -294,16 +295,11 @@ static struct ETCP_ROUTER_CONN* router_conn_find(struct UTUN_INSTANCE* inst,
|
||||
// ====================================================================
|
||||
|
||||
// Найти ETCP_CONN для отправки (учитывая indirect-посредников). NULL = нет маршрута.
|
||||
static struct ETCP_CONN* router_route_conn(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t remote_node_id) {
|
||||
static struct ETCP_CONN* router_route_conn(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t remote_node_id, uint8_t svc_id) {
|
||||
struct TOPO_GROUP* group = topo_groups_find(inst->topo_groups, group_id);
|
||||
if (group) {
|
||||
struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(group, remote_node_id);
|
||||
if (nq && nq->conn_mgr_type == CONN_TYPE_INDIRECT && nq->conn_mgr_intermediariy_count > 0) {
|
||||
for (uint8_t i = 0; i < nq->conn_mgr_intermediariy_count; i++) {
|
||||
struct ETCP_CONN* c = topo_group_find_conn_for_node(group, nq->conn_mgr_intermediaries[i]);
|
||||
if (c) return c;
|
||||
}
|
||||
}
|
||||
struct ETCP_CONN* preferred = conn_mgr_route_conn(group->conn_mgr, remote_node_id, svc_id == ETCP_RT_ID_CONN_MGR);
|
||||
if (preferred) return preferred;
|
||||
return topo_group_find_conn_for_node(group, remote_node_id);
|
||||
}
|
||||
/* group_id=0 (или группа не найдена) — глобальная маршрутизация: прямое соединение по node_id */
|
||||
@@ -312,7 +308,7 @@ static struct ETCP_CONN* router_route_conn(struct UTUN_INSTANCE* inst, uint64_t
|
||||
|
||||
// Тонкая обёртка над router_route_conn для rconn (next_hop для отправки).
|
||||
static struct ETCP_CONN* router_send_conn(struct ETCP_ROUTER_CONN* rconn) {
|
||||
return router_route_conn(rconn->inst, rconn->group_id, rconn->remote_node_id);
|
||||
return router_route_conn(rconn->inst, rconn->group_id, rconn->remote_node_id, rconn->svc_id);
|
||||
}
|
||||
|
||||
// Есть ли физический маршрут до узла (прямой/indirect/глобальный): 1 — есть, 0 — нет.
|
||||
@@ -320,7 +316,17 @@ static struct ETCP_CONN* router_send_conn(struct ETCP_ROUTER_CONN* rconn) {
|
||||
int etcp_router_has_route(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst_node_id) {
|
||||
if (!inst) return 0;
|
||||
if (dst_node_id == inst->node_id) return 1;
|
||||
return router_route_conn(inst, group_id, dst_node_id) != NULL;
|
||||
return router_route_conn(inst, group_id, dst_node_id, 0) != NULL;
|
||||
}
|
||||
|
||||
struct ETCP_ROUTER_PATH etcp_router_get_path(struct UTUN_INSTANCE* inst, uint64_t group_id,
|
||||
uint64_t dst_node_id, uint8_t svc_id) {
|
||||
struct ETCP_ROUTER_PATH path = {0};
|
||||
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "path snapshot without instance"); return path; }
|
||||
struct ETCP_CONN* conn = router_route_conn(inst, group_id, dst_node_id, svc_id);
|
||||
path.type = conn_mgr_connection_type(conn, dst_node_id);
|
||||
if (path.type != CONN_TYPE_NONE) path.next_hop_node_id = conn->peer_node_id;
|
||||
return path;
|
||||
}
|
||||
|
||||
// Собрать и закодировать (подпись/шифрование) пакет в начале отправки.
|
||||
|
||||
@@ -244,6 +244,12 @@ struct ETCP_ROUTER_CONN* etcp_router_conn_get(struct UTUN_INSTANCE* inst,
|
||||
// 1 = найден транспорт (для self всегда 1); это не гарантия UP/готовности логического канала.
|
||||
int etcp_router_has_route(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst_node_id);
|
||||
|
||||
// Снимок текущего пути отправки сервиса, только uasync-поток; без создания канала/изменения очередей.
|
||||
// type — CONN_TYPE_*; отсутствие живого пути: NONE и next_hop_node_id=0.
|
||||
struct ETCP_ROUTER_PATH { uint8_t type; uint64_t next_hop_node_id; };
|
||||
struct ETCP_ROUTER_PATH etcp_router_get_path(struct UTUN_INSTANCE* inst, uint64_t group_id,
|
||||
uint64_t dst_node_id, uint8_t svc_id);
|
||||
|
||||
// Отправить данные с авто-seq и контролем inflight
|
||||
// data: payload без svc_id, mode: ROUTE_CRYPTO_SIGN / ROUTE_CRYPTO_ENCRYPT (0 = обычный)
|
||||
// Копирует data; 0=принято в очередь, <0=ошибка. Доставка подтверждается отдельно протоколом.
|
||||
|
||||
@@ -217,6 +217,7 @@ static void topo_group_log_exchange(struct TOPO_GROUP* group, struct TOPO_GROUP_
|
||||
(unsigned long long)group->group_id, (unsigned long long)peer->conn->peer_node_id,
|
||||
(unsigned long long)peer->local_epoch, peer->table_sent, peer->table_received,
|
||||
topo_group_peer_ready(group, peer->conn->peer_node_id));
|
||||
conn_mgr_routes_changed(group->conn_mgr);
|
||||
}
|
||||
|
||||
static struct TOPOMSG_HEADER topo_header(struct TOPO_GROUP_CONN_ITEM* peer, uint8_t cmd) {
|
||||
@@ -1153,6 +1154,7 @@ static void topo_group_routes_changed(struct TOPO_GROUP* group, struct TOPO_GROU
|
||||
for (struct topo_node_cbk_entry* c = group->node_cbks; c; c = c->next) c->fn(group, id, event, c->arg);
|
||||
topo_group_publish_route(group, id);
|
||||
topo_recovery_changed(group);
|
||||
conn_mgr_routes_changed(group->conn_mgr);
|
||||
}
|
||||
|
||||
/* TABLE_COMPLETE подтверждает только NODEINFO текущего обмена. Сбой обмена
|
||||
|
||||
Reference in new issue
Block a user