Files
utun2/route.txt
T
evgeny 38c9f229bc stcp: ed25519 pubkey exchange in handshake, fix instance destroy segfault, fix chat_tcp_test
- u_async.c: process immediate_queue BEFORE epoll_wait in uasync_poll
- stcp_link.c: defensive NULL etcp_conn/etcp_link in stcp_link_close
- utun_instance.c: move stcp_server_list_destroy_all to Phase J (before ETCP),
  add deferred drain in both Phase J and L
- stcp.h/c: STCP_HS_ENC_CLIENT 6→38, STCP_HS_ENC_SERVER 7→39,
  exchange ed25519_pubkey in both handshake directions
- etcp_connections.c: copy peer_ed25519 from STCP to ETCP_CONN in
  etcp_link_enter_ready_tcp; guard link->etcp in tcp_link_close_cb
- chat_sync.c: fix lk->conn NULL dereference for TCP links
- test_stcp.c: update stcp_server_create/stcp_client_connect callsites
- tools/chat_tcp_test: change transport udp→tcp
2026-08-07 00:48:27 +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` |