diff --git a/src/routing_layer/conn_mgr_doc.md b/src/routing_layer/conn_mgr_doc.md index 8dcadf6b..a8bc3536 100644 --- a/src/routing_layer/conn_mgr_doc.md +++ b/src/routing_layer/conn_mgr_doc.md @@ -18,7 +18,8 @@ Доступность определяется по опубликованным адресам и общей классификации `sock_match`: публичный IPv4, EIM/прямой NAT, пригодный глобальный IPv6. -Приватные адреса EIM-интерфейса не считаются внешними. +Приватные адреса EIM-интерфейса не считаются внешними. Для интерфейса с политикой +PUBLIC объявленный LAN-listener также разрешает сначала попробовать DIRECT. DIRECT использует NCD, который создаёт UDP/TCP-линки по известной записи узла, включая IPv4/IPv6, LAN и Reality-параметры. LOCAL — те же локальные handshake-пробы: @@ -45,11 +46,9 @@ REVERSE просит получателя открыть/дополнить NCD 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_route_conn()` возвращает заимствованный next hop подтверждённой доставки. +Его `allow_probe` разрешает использовать ещё не подтверждённый выбор только для +собственного управляющего обмена CM. ETCP-router может отдельно использовать BGP fallback. Один timer на entry обслуживает ближайшее запланированное действие, повторы и общий предел ожидания. READY и изменение маршрутов перепроверяют пути через @@ -72,7 +71,8 @@ TIMEOUT и срок очередного цикла проб независим Если потеряны все пути, сервис получает DOWN. Затем новый цикл запускает DIRECT и LOCAL сразу, REVERSE через 0/500 мс; при отсутствии пригодного резерва INDIRECT запускается сразу независимо от NAT. Рабочий посредник сохраняется во время проб. -Каждый цикл ограничен 15 секундами. Между неудачными циклами пауза растёт: +Дедлайн цикла — через 15 секунд от запланированного INDIRECT: 15–16 секунд +от начального старта, при recovery — 15 секунд. Между неудачными циклами пауза растёт: 1 → 2 → 4 → 8 → 15 секунд, далее остаётся 15 секунд. Подтверждённый прямой READY останавливает поиск и сбрасывает паузу. @@ -114,7 +114,34 @@ TIMEOUT и срок очередного цикла проб независим Wire-структуры описаны в `conn_mgr_priv.h`. DIRECT_REQ содержит только request_id. INTERM_SELECTED и INTERM_ACK содержат request_id и до трёх `{node_id, rtt}`. -Совместимость с прежним адресным DIRECT_REQ не поддерживается. +Структуры фиксированной длины, все числа native endian: DIRECT_REQ 6 байт, +EXCHANGE_REQ 37, EXCHANGE_RESP 68, SELECTED/ACK 37. Массивы на проводе всегда +содержат три слота; count указывает число действительных. RTT в единицах 0.1 мс. +request_id ненулевой; ноль пропускается при переполнении счётчика. +DIRECT_RESP и DISCONNECT удалены; неизвестные команды отклоняются с WARN. +Удалённый узел не может командой закрыть локальную потребность сервиса. +Добавление участников и получение описания канала — chat_join/member_sync и topo_group_invite. + +## Фоновые проверки и освобождение + +BG ping проверяет по одному узлу каждые 100 мс; полный цикл начинается не чаще +одного раза в 60 секунд. Узлы с живым прямым линком пропускаются. Выбирается +первый подходящий UDP endpoint IPv4/IPv6. Candidate ping работает каждые 2 секунды: +обновляет до трёх кандидатов, пингует активные UDP/STCP-линки и запускает пробы +route_connectivity для неактивных. Без активного линка устаревший замер удаляет +кандидата через 30 секунд; проверка свежести RTT для согласования использует 20 секунд. +NAT-детектор откладывает эти пробы, standby переносит их в активную фазу. + +Кандидаты упорядочены по RTT, затем node_id. При заполнении кеша кандидат с +READY-сессией вытесняет недоступного независимо от его старого RTT. Проба +недоступного узла не вытесняет READY-кандидата из полностью рабочего списка. +Перед согласованием каждое плечо и RTT всё равно проверяются заново. + +close/cleanup сразу снимают объект с рабочего списка и отменяют callbacks. +Память закрытых handles/entries остаётся у менеджера до начала фонового callback +или destroy: вложенные callbacks могут безопасно закрывать себя и следующий handle. +Сборка не выделяет память и не зависит от успешного call_soon при закрытии. +Destroy отсоединяет ещё открытые handles; они остаются у вызывающего до close. ## API и владение @@ -132,6 +159,10 @@ INTERM_SELECTED и INTERM_ACK содержат request_id и до трёх `{nod NCD-владение CM. Группа и другие NCD-владельцы сохраняют транспорт. - `conn_mgr_get_conn()` возвращает заимствованный прямой транспорт либо NULL при INDIRECT/отсоединённом handle. Данные следует отправлять через router. + Сервис передаёт собственный svc_id и выбирает шифрование; общего conn_mgr_send нет. +- Загрузка узла из SQLite временно удерживает запись реестра. Ошибки регистрации, + группового узла, CM-entry или handle освобождают созданные объекты. Приобретение + handle уже готового пути возвращает ошибку, если отложенный UP не удалось запланировать. Диагностика CM — категория `general`: INFO показывает расписание, выбор и смену пути с временем от старта, новые циклы и паузы; DEBUG — события NCD, RTT и обработку ответов; WARN/ERROR — @@ -144,3 +175,6 @@ INTERM_SELECTED и INTERM_ACK содержат request_id и до трёх `{nod обрыв после начального срока, повторные циклы с реальными паузами 1/2/4с, смену адресов и локальных сокетов, потерю маршрута посредника, устаревший ACK, начальный TIMEOUT с последующим UP и закрытие последнего handle из TIMEOUT callback. +`test_conn_mgr_handles` дополнительно проверяет READY-кандидата при заполненном +недоступными узлами кеше, отказ начального UP и close без аллокаций; `test_topo_node_wire` +проверяет четыре отказа bootstrap и ошибки обязательных CM-таймеров. diff --git a/src/routing_layer/conn_mgr_priv.h b/src/routing_layer/conn_mgr_priv.h index 75c64dfe..2fd28b07 100644 --- a/src/routing_layer/conn_mgr_priv.h +++ b/src/routing_layer/conn_mgr_priv.h @@ -38,7 +38,7 @@ struct stcp_client; #define CONN_MGR_EXCHANGE_RETRANSMIT_MS 1000 #define CONN_MGR_RETRY_MIN_MS 1000 #define CONN_MGR_RETRY_MAX_MS 15000 -#define CACHE_MS 20000 /* время жизни RTT-замера ~20с (через cm_is_rtt_fresh, мс) */ +#define CACHE_MS 20000 /* свежесть RTT-пробы 20с; поля *_tb и RTT используют 0.1мс */ #define CONN_MGR_MAX_INTERMEDIARIES 3 /* ═══════ состояния ═══════ */ @@ -47,7 +47,6 @@ enum CONN_MGR_STATE { CONN_MGR_STATE_DISCONNECTED = 0, CONN_MGR_STATE_CONNECTING = 1, CONN_MGR_STATE_CONNECTED = 2, - CONN_MGR_STATE_FAILED = 3, }; enum CM_TRY_STATE { @@ -67,8 +66,12 @@ enum { CM_SUBCMD_INTERM_ACK = 0x07, }; +/* RTT — 0.1мс; 0/FFFF не участвуют в согласовании. */ struct CONN_MGR_CANDIDATE { uint64_t node_id; uint16_t rtt; } __attribute__((packed)); +/* Полные фиксированные структуры, включая неиспользованные слоты массивов. + * Native endian, cmd=ETCP_RT_ID_CONN_MGR; request_id ненулевой. + * DIRECT_REQ=6B, EXCHANGE_REQ=37B, EXCHANGE_RESP=68B, SELECTED/ACK=37B. */ #pragma pack(push, 1) struct CM_DIRECT_REQ { uint8_t cmd, subcmd; uint32_t request_id; }; 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]; }; @@ -84,7 +87,7 @@ struct CM_INTERM_SEL { uint8_t cmd, subcmd; uint32_t request_id; uint8_t coun /* ═══════ внутренние структуры ═══════ */ struct CONN_MGR_HANDLE { - uint64_t node_id; uint64_t group_id; conn_mgr_cb_t cb; void* cb_arg; + uint64_t node_id; uint64_t group_id; conn_mgr_cb_t cb; void* cb_arg; /* исходная цель/группа, заимствованный callback */ struct CONN_MGR_ENTRY* entry; struct CONN_MGR_HANDLE* next; struct CONN_MGR* mgr; /* владелец памяти до destroy; NULL у отсоединённого после destroy handle */ struct CONN_MGR_HANDLE* owner_next; /* список всех handles mgr либо список закрытых до безопасного GC */ @@ -96,19 +99,19 @@ struct CONN_MGR_HANDLE { struct CONN_MGR_ENTRY { struct ll_entry ll; /* ll_queue entry — первый элемент */ uint64_t node_id; /* хеш-ключ 8B, offset от data = 0 */ - uint8_t state, conn_type; - uint64_t last_traffic_tb; - struct CONN_MGR_HANDLE* handles; struct NODE_CONN_DIRECT* ncd_handle; - uint64_t intermediaries[3]; uint8_t intermediariy_count; - uint8_t local_scan_state, main_connect_state, db_loaded; + uint8_t state, conn_type; /* CONN_MGR_STATE / CONN_TYPE; рабочая доставка независимо от проб */ + uint64_t last_traffic_tb; /* последняя принятая CM-команда при CONNECTED; не idle-дедлайн */ + struct CONN_MGR_HANDLE* handles; struct NODE_CONN_DIRECT* ncd_handle; /* владельцы запроса; отдельный общий прямой транспорт CM */ + uint64_t intermediaries[3]; uint8_t intermediariy_count; /* подтверждённый резерв, актуальность проверяется перед выбором */ + uint8_t local_scan_state, main_connect_state, db_loaded; /* CM_TRY / CM_TRY / временный узел из SQLite */ uint8_t reverse_started, indirect_started, indirect_confirmed, direct_up, timeout_notified, ever_up; - uint8_t attempt_active; - uint16_t retry_delay_ms; - void* timer; void* refresh_token; + uint8_t attempt_active; /* исходящий цикл; не признак CONNECTED */ + uint16_t retry_delay_ms; /* пауза между циклами, 1000..15000мс */ + void* timer; void* refresh_token; /* ближайший шаг/срок; отложенный пересчёт после маршрутов */ uint64_t start_tb, reverse_start_tb, indirect_start_tb, deadline_tb; uint64_t initial_deadline_tb, retry_tb, node_timestamp, peer_exchange_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, peer_reverse_request_id; + uint32_t reverse_request_id, exchange_request_id, peer_exchange_request_id, peer_reverse_request_id; /* независимые исходящие/входящие id */ struct CM_INTERM_SEL selection; /* Исходящий выбор до подтверждения пира. */ struct CONN_MGR* mgr; struct CONN_MGR_ENTRY* retired_next; /* уже снятая с entries запись до безопасного GC */ @@ -121,7 +124,7 @@ struct CONN_MGR { struct CONN_MGR_ENTRY* retired_entries; /* сборка в фоновых callbacks/destroy не требует выделений памяти */ void *bg_ping_timer, *candidate_ping_timer; void *bg_ping_wait, *candidate_ping_wait; /* standby_wait handle (Android) */ - size_t bg_ping_cursor; uint64_t bg_ping_cycle_start_tb; + size_t bg_ping_cursor; uint64_t bg_ping_cycle_start_tb; /* один узел за тик; полный цикл не чаще 60с */ 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; uint8_t initialized;