Вот подробное описание работы модуля динамической маршрутизации.

---

## Архитектура: три слоя

```
┌─────────────────────────────────────────────────┐
│  ЯДРО ОС (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` |
