Вот подробное описание работы модуля динамической маршрутизации. --- ## Архитектура: три слоя ``` ┌─────────────────────────────────────────────────┐ │ ЯДРО ОС (netlink / ip route) │ ← tun_route.c — только при старте/стопе │ НЕ динамически, только для configured subnets │ ├─────────────────────────────────────────────────┤ │ ПЕРЕСЫЛКА ПАКЕТОВ │ ← routing.c │ route_pkt() → route_lookup() → etcp_route_send │ или queue_data_put в TUN ├─────────────────────────────────────────────────┤ │ ТАБЛИЦА МАРШРУТОВ (ROUTE_TABLE) │ ← route_lib.c │ route_insert / route_delete / route_lookup │ LPM через бинарный поиск ├─────────────────────────────────────────────────┤ │ BGP-ОБМЕН (ROUTE_BGP) │ ← route_bgp.c │ NODEINFO (анонс) / WITHDRAW (отзыв) / │ broadcast / loop prevention │ ping (NAT-детекция) │ └─────────────────────────────────────────────────┘ ``` ## Ключевые структуры данных ### `ROUTE_TABLE` (`route_lib.h:46`) ```c struct ROUTE_TABLE { struct ROUTE_ENTRY *entries; // отсортированный плоский массив size_t count, capacity; }; ``` Маршруты хранятся в **отсортированном плоском массиве** (не trie, не radix). Сортировка: `network ASC`, затем `prefix_length DESC`. Это позволяет LPM через бинарный поиск. ### `ROUTE_ENTRY` (`route_lib.h:34`) ```c struct ROUTE_ENTRY { uint32_t network; // host byte order uint8_t prefix_length; struct NODEINFO_Q* v_node_info; // NULL = локальный маршрут }; ``` Каждая запись ссылается на узел-владелец (`NODEINFO_Q`). Все маршруты одного узла удаляются за один проход по этому указателю. ### `NODEINFO` (`route_node.h:73`) Динамическая структура (packed) с полями фиксированной части + динамический хвост: имя, метаданные сокетов, IPv4/IPv6 адреса, подсети, транзитные узлы, hop_list. Передаётся по BGP как NODEINFO-пакет. ### `NODEINFO_Q` (`route_node.h:151`) Локальная обёртка вокруг `NODEINFO`: ```c struct NODEINFO_Q { struct ll_entry ll; // linkage в очереди bgp->nodes (хэш-индекс по node_id) struct ll_queue* paths; // список NODEINFO_PATH — по каким соединениям доступен узел uint8_t last_ver; // последняя известная версия NODEINFO struct NODE_CONNECTIVITY connectivity; // локальное состояние проб (ping/RTT) struct NODEINFO node; // всегда в конце, динамический размер }; ``` ### `ROUTE_BGP` (`route_bgp.h:90`) ```c struct ROUTE_BGP { struct ll_queue* senders_list; // ROUTE_BGP_CONN_ITEM — все активные пиры для broadcast struct ll_queue* nodes; // хэш-индексированная очередь NODEINFO_Q struct NODEINFO_Q* local_node; // свой собственный узел // ... ping_pending, LMDB и т.д. }; ``` --- ## Добавление маршрутов Есть **три сценария** попадания маршрутов в таблицу: ### 1. Получение NODEINFO от пира (основной путь) **Точка входа:** `route_bgp_receive_cbk()` (`route_bgp.c:162`) → `route_bgp_process_nodeinfo()` (`route_bgp.c:750`). Алгоритм `route_bgp_process_nodeinfo`: 1. **Валидация:** проверка размера, `hop_count < MAX_HOPS(16)`, не свой ли node_id 2. **Проверка версии:** `nodeinfo1->last_ver >= new_ver` — значит версия та же или новее → только обновляем paths (через какой conn доступен), маршруты не трогаем 3. **Новая версия или новый узел:** - Выделяется/перевыделяется `NODEINFO_Q` (если изменился динамический размер) - Копируется NODEINFO + динамические данные - Источник (`from->peer_node_id`) добавляется в конец hop_list (инкремент `hop_count`) - Старый путь удаляется (`route_bgp_remove_path_by_hop`), новый добавляется (`route_bgp_add_path`) - **`route_insert(bgp->instance->rt, nodeinfo1)`** ← вот здесь маршруты попадают в таблицу - Сохраняется в LMDB 4. **Broadcast:** рассылается всем пирам кроме тех, кто уже в hop_list (loop prevention) 5. **Connectivity probe:** для новых или изменившихся узлов запускается зондирование связности #### `route_insert()` (`route_lib.c:148`) — что происходит внутри: ```c bool route_insert(struct ROUTE_TABLE *table, struct NODEINFO_Q *node) ``` 1. Вызывает `get_node_routes(node, &subnets)` — получает указатель на IPv4-подсети внутри динамических данных NODEINFO (без копирования) 2. Конвертирует адреса из `network byte order → host byte order` через `ntohl()` 3. Проверяет **пересечения** с существующими маршрутами (точное совпадение network+prefix) 4. При необходимости расширяет массив entries (`u_realloc`, ×2) 5. Для каждой подсети: - Находит позицию вставки бинарным поиском (`binary_search_insert_pos`): по `network ASC`, затем `prefix_length DESC` - Сдвигает элементы вправо через `memmove()` - Записывает `entry->network`, `entry->prefix_length`, `entry->v_node_info = node` 6. Инкрементирует `stats.local_routes` (если `hop_count==0`) или `stats.learned_routes` **Важно:** при обновлении существующего узла (realloc) старые маршруты **сначала удаляются** (`route_delete`), потом вставляются новые. Это на случай если изменился состав подсетей. ### 2. Обновление своего local_node (`route_node.c`) `route_bgp_update_my_nodeinfo()` вызывается при: - Инициализации BGP - Изменении конфигурации - NAT-детекции (изменился `nat_type` сокета) Алгоритм: 1. Считает сокеты, адреса, подсети из конфига 2. Сравнивает с текущим `local_node` — если изменилось: - **`route_delete(rt, local_node)`** — удаляет старые локальные маршруты - Освобождает старый `local_node`, выделяет новый - Заполняет NODEINFO (node_id, hop_count=0, ключи, подсети) - Инкрементирует `ver` - **`route_insert(rt, local_node)`** — вставляет новые 3. Если изменился состав адресов — помечает `dirty=1`, рассылает обновлённый NODEINFO ### 3. Получение полной таблицы (синхронизация при connect) При установке соединения: 1. `route_bgp_new_conn()` (`route_bgp.c:372`) добавляет conn в `senders_list` и отправляет `ROUTE_SUBCMD_REQUEST_TABLE` 2. Пир отвечает `route_bgp_handle_request_table()` (`route_bgp.c:1015`): - Шлёт свой `local_node` - Шлёт все известные узлы из `bgp->nodes` (с loop prevention через hop_list) - Шлёт `ROUTE_SUBCMD_TABLE_COMPLETE` 3. Каждый полученный NODEINFO обрабатывается через `route_bgp_process_nodeinfo` → `route_insert` --- ## Удаление маршрутов Есть **два сценария**: ### 1. Получен WITHDRAW от пира **Точка входа:** `route_bgp_process_withdraw()` (`route_bgp.c:890`). Алгоритм: 1. Извлекает `node_id` (кого удаляем) и `wd_source` (кто инициировал) 2. Ищет `NODEINFO_Q` по `node_id` 3. Вызывает `route_bgp_remove_path_by_hop(nq, wd_source)` — удаляет все пути, в hop_list которых есть `wd_source` 4. Если **путей не осталось** (unreachable): - **`route_delete(bgp->instance->rt, nq)`** — удаляет все маршруты узла из таблицы - Отменяет connectivity probes - Удаляет paths, узел из `bgp->nodes` - Закрывает все соединения etcp_router для этого узла - **Broadcast WITHDRAW** дальше всем пирам (кроме отправителя) #### `route_delete()` (`route_lib.c:221`) — что происходит внутри: ```c void route_delete(struct ROUTE_TABLE *table, struct NODEINFO_Q *node) ``` Проходит по всему массиву entries, для каждой записи где `entry->v_node_info == node`: - Сдвигает оставшиеся элементы влево через `memmove()` - Декрементирует `count` и соответствующий счётчик статистики - **Не освобождает** `NODEINFO_Q` (этим занимается вызывающий код в `route_bgp.c`) ### 2. Соединение ETCP упало (conn DOWN) **Точка входа:** `route_bgp_remove_conn()` (`route_bgp.c:417`). Алгоритм: 1. Отменяет pending NAT checks для линков этого соединения 2. Итерирует **все узлы** в `bgp->nodes`: - Для каждого вызывает `route_bgp_remove_path(nq, conn)` — удаляет путь через этот conn - Если путь был последним (unreachable): - **`route_delete(rt, nq)`** - Удаляет узел из `bgp->nodes`, отменяет пробы - Закрывает etcp_router соединения - **Broadcast WITHDRAW** с `wd_source = peer_node_id` (или свой node_id если удаляемый узел и есть сам пир) 3. Удаляет conn из `senders_list` --- ## Поиск маршрута и пересылка пакетов ### `route_lookup()` (`route_lib.c:248`) — LPM Бинарный поиск по отсортированному массиву: - Сравнивает `(dest_ip & mask) == (network & mask)` - Запоминает лучший (наибольшая `prefix_length`) - При совпадении продолжает поиск вправо (там могут быть более специфичные /32 внутри /24) - Возвращает `ROUTE_ENTRY*` или NULL ### `route_pkt()` (`routing.c:53`) — единая точка пересылки 1. Извлекает IP-пакет (пропуская 1-байтовый cmd) 2. Дропает мультикаст (224-239) и броадкаст (255.255.255.255) 3. `route_lookup(instance->rt, dst_ip)` — ищет маршрут 4. **Нет маршрута** → дроп, `dropped_packets++` 5. **Маршрут удалённый** (`v_node_info != NULL && hop_count > 0`) → `etcp_route_send()` (через service router) 6. **Маршрут локальный** (нет v_node_info или hop_count==0) → `queue_data_put(instance->tun->input_queue)` (доставка в TUN) Два входа в `route_pkt`: - **Из TUN** (исходящий): `routing_pkt_from_tun_cb()` — callback на `tun->output_queue` - **Из ETCP** (входящий): `routing_pkt_from_etcp_cb()` — callback через `etcp_router_bind(ETCP_RT_ID_DATA=0)` --- ## Loop prevention Distance-vector стиль: каждый NODEINFO-пакет содержит **hop_list** — массив node_id, через которые он прошёл. Перед broadcast проверяется, что целевой пир **не находится** в hop_list. Аналогично при WITHDRAW — `wd_source` ищется в hop_list каждого path. --- ## NAT-детекция (влияет на обновление local_node) 1. При новом соединении сканируются все линки, для каждого вызывается `route_bgp_start_link_nat_check()` 2. Находится «третий узел» (другой подключённый пир, не private) 3. Через третий узел отправляется ping-запрос на целевой IP/порт (`route_ping_send_req_addr`) 4. Третий узел выполняет UDP-пинг, возвращает результат 5. По результату устанавливается `NAT_TYPE_EIM` (reachable) или `NAT_TYPE_STRICT` 6. Отправляется `NAT_INFO` обратно владельцу сокета 7. Владелец обновляет `local_node` → `route_bgp_update_my_nodeinfo()` → удаление/вставка локальных маршрутов → broadcast обновлённого NODEINFO --- ## Итого: кто когда вызывает route_insert/route_delete | Событие | Операция | Функция | |---|---|---| | Получен новый/обновлённый NODEINFO | `route_insert` | `route_bgp_process_nodeinfo` `:849` | | Обновление своего local_node | `route_delete` старого + `route_insert` нового | `route_bgp_update_my_nodeinfo` (`route_node.c`) | | Получен WITHDRAW (узел недоступен) | `route_delete` | `route_bgp_process_withdraw` `:904` | | Соединение упало (conn DOWN) | `route_delete` для каждого узла без путей | `route_bgp_remove_conn` `:468` | | Переаллокация NODEINFO_Q при обновлении | `route_delete` старого | `route_bgp_process_nodeinfo` `:812` |