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