Files
utun2/route.txt
T
Evgeny 1889d23240 metrics: RTT/loss histograms + Ed25519 signing + peer exchange via INIT handshake + file logging to stats/<peer>.dat
- etcp_metrics struct: 12-bucket RTT histogram, 11-bucket loss histogram, 10-min snapshots (>=200 pkts)
- Total counters: packets/bytes sent/rcvd, losses (retrans + probe gap detection)
- CSV append to <stats_dir>/<peer_id>.dat every 10 min
- Ed25519 pubkey exchange in INIT handshake (V3), always present
- Signed metrics exchange via ETCP_SECTION_METRICS (0x0A)
- Received metrics saved as from_<peer_id>.dat with signature hex
- on_metrics_rcvd callback in ETCP_BINDINGS
- stats_dir derived from config file path, mkdir'd at instance init
- Simplified versioning: ETCP_INIT_REQ_SIZE / ETCP_INIT_RESP_SIZE via sizeof
2026-07-07 17:16:45 +03:00

240 lines
15 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Вот подробное описание работы модуля динамической маршрутизации.
---
## Архитектура: три слоя
```
┌─────────────────────────────────────────────────┐
│ ЯДРО ОС (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` |