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
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` |
|
|
|