You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

239 lines
15 KiB

Вот подробное описание работы модуля динамической маршрутизации.
---
## Архитектура: три слоя
```
┌─────────────────────────────────────────────────┐
│ ЯДРО ОС (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` |