Browse Source

Merge: add local routes using hop_count==0

master
Evgeny 4 days ago
parent
commit
a2e4a4b208
  1. 18
      lib/ll_queue.h
  2. 1
      src/Makefile.am
  3. 812
      src/route_bgp.c
  4. 158
      src/route_bgp.h
  5. 85
      src/route_bgp.txt
  6. 648
      src/route_lib.c
  7. 82
      src/route_lib.h
  8. 17
      src/route_lib.txt
  9. 128
      src/route_node.c
  10. 94
      src/route_node.h
  11. 28
      src/routing.c
  12. 36
      src/utun_instance.c
  13. 7
      tests/Makefile.am
  14. 23
      tests/test_bgp_route_exchange.c
  15. 19
      tests/test_etcp_two_instances.c
  16. 258
      tests/test_route_lib.c

18
lib/ll_queue.h

@ -228,11 +228,29 @@ int queue_data_put(struct ll_queue* q, struct ll_entry* entry);
*/ */
int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry); int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry);
/**
* @brief Добавляет элемент в конец очереди с явным указанием смещения индекса для hash.
* @param q очередь
* @param entry элемент
* @param index_offset смещение индекса (node_id) в data[]
* @param index_size размер индекса (8 для uint64_t node_id)
* @return 0 успех, -1 ошибка
*/
int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry, int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry,
uint16_t index_offset, uint16_t index_size); uint16_t index_offset, uint16_t index_size);
/**
* @brief Добавляет элемент в начало очереди с явным указанием смещения индекса для hash.
* @param q очередь
* @param entry элемент
* @param index_offset смещение индекса в data[]
* @param index_size размер индекса
* @return 0 успех, -1 ошибка
*/
int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry, int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry,
uint16_t index_offset, uint16_t index_size); uint16_t index_offset, uint16_t index_size);
/** /**
* @brief Извлекает элемент из начала очереди. * @brief Извлекает элемент из начала очереди.
* @param q очередь * @param q очередь

1
src/Makefile.am

@ -8,6 +8,7 @@ utun_CORE_SOURCES = \
config_updater.c \ config_updater.c \
route_lib.c \ route_lib.c \
route_bgp.c \ route_bgp.c \
route_node.c \
routing.c \ routing.c \
tun_if.c \ tun_if.c \
tun_route.c \ tun_route.c \

812
src/route_bgp.c

File diff suppressed because it is too large Load Diff

158
src/route_bgp.h

@ -5,76 +5,28 @@
#include <stddef.h> #include <stddef.h>
#include "../lib/ll_queue.h" #include "../lib/ll_queue.h"
#include "route_lib.h" #include "route_lib.h"
#include "route_node.h"
#include "secure_channel.h" #include "secure_channel.h"
// ETCP ID для маршрутных пакетов // ETCP ID для маршрутных пакетов
#define ETCP_ID_ROUTE_ENTRY 0x01 #define ETCP_ID_ROUTE_ENTRY 0x01
// Sub-команды // Sub-команды
#define ROUTE_SUBCMD_ENTRY 0x01 // Обычное обновление маршрута #define ROUTE_SUBCMD_NODEINFO 0x04 // полная информация об узле + маршруты
#define ROUTE_SUBCMD_ENTRY_REROUTE 0x02 // Reroute (preferred_conn изменился) — пока обрабатывается как ENTRY #define ROUTE_SUBCMD_REQUEST_TABLE 0x05 // запрос полной таблицы
#define ROUTE_SUBCMD_WITHDRAW 0x03 #define ROUTE_SUBCMD_WITHDRAW 0x06 // узел стал недоступен
#define ROUTE_SUBCMD_NODEINFO 0x04
#define ROUTE_SUBCMD_REQUEST_TABLE 0x05 // запрос полной таблицы (version=0 в запросе)
#define MAX_HOPS 16 #define MAX_HOPS 16
#define BGP_NODES_HASH_SIZE 256
/** /**
* @brief Основной пакет маршрута (переменной длины) * @brief Пакет с информацией об узле (NODEINFO)
*/
struct BGP_ROUTE_PACKET {
uint8_t cmd; // ETCP_ID_ROUTE_ENTRY
uint8_t subcmd; // ENTRY / REROUTE
uint32_t network; // big-endian
uint8_t prefix_length;
uint64_t node_id; // владелец префикса (big-endian)
uint8_t hop_count; // количество хопов
uint16_t latency; // зарезервировано (0)
uint64_t hop_list[0]; // flexible array: next_hop → ... → destination
} __attribute__((packed));
/**
* @brief Основной пакет маршрута (переменной длины)
*/ */
struct BGP_NODEINFO_PACKET { struct BGP_NODEINFO_PACKET {
uint8_t cmd; // ETCP_ID_ROUTE_ENTRY uint8_t cmd; // ETCP_ID_ROUTE_ENTRY
uint8_t subcmd; // ROUTE_SUBCMD_NODEINFO uint8_t subcmd; // ROUTE_SUBCMD_NODEINFO
uint64_t node_id; // (big-endian) struct NODEINFO node;
uint8_t ver; // версия пакета (сейчас 1)
uint8_t public_key[SC_PUBKEY_SIZE]; // node pubkey
uint8_t node_name_len; // размер в байтах (без null терминации)
uint8_t local_v4_sockets; // BGP_NODEINFO_IPV4_SOCKET число локальных сокетов ipv4 (для incoming connections)
uint8_t local_v6_sockets; // BGP_NODEINFO_IPV6_SOCKET число локальных сокетов ipv6 (для incoming connections) (пока 0)
uint8_t local_v4_routes; // BGP_NODEINFO_IPV4_ROUTE число локальных маршрутов ipv4
uint8_t local_v6_routes; // BGP_NODEINFO_IPV6_ROUTE число локальных маршрутов ipv6 (пока 0)
uint8_t tranzit_nodes; // BGP_NODEINFO_TRANZIT_NODE лучшие транзитные узлы для этой ноды (минимальный пинг / лучшее качество каналов)
// далее идут по порядку следования полей в структуре: node_name, сокеты, роуты
} __attribute__((packed));
struct BGP_NODEINFO_IPV4_SOCKET {
uint8_t addr[4];// network byte order
uint16_t port;
} __attribute__((packed));
struct BGP_NODEINFO_IPV6_SOCKET {
uint8_t addr[16];
uint16_t port;
} __attribute__((packed));
struct BGP_NODEINFO_IPV4_ROUTE {
uint8_t addr[4];// network byte order
uint8_t prefix_length;
} __attribute__((packed));
struct BGP_NODEINFO_IPV6_ROUTE {
uint8_t addr[16];
uint8_t prefix_length;
} __attribute__((packed));
struct BGP_NODEINFO_TRANZIT_NODE {
uint64_t node_id; // (big-endian)
uint16_t rtt; // x0.1 ms
uint16_t link_q; // меньше - лучше (потери + 1/BW)
} __attribute__((packed)); } __attribute__((packed));
/** /**
@ -83,16 +35,16 @@ struct BGP_NODEINFO_TRANZIT_NODE {
struct BGP_WITHDRAW_PACKET { struct BGP_WITHDRAW_PACKET {
uint8_t cmd; uint8_t cmd;
uint8_t subcmd; uint8_t subcmd;
uint64_t node_id; uint64_t node_id; // удаляемый узел (который стал недоступен)
uint64_t wd_source; // узел который инициировал withdraw (при удалении он должен быть в hoplist или = current node_id)
} __attribute__((packed)); } __attribute__((packed));
/** /**
* @brief Пакет запроса полной таблицы * @brief Пакет запроса полной таблицы
*/ */
struct BGP_ROUTE_REQUEST { struct BGP_ROUTE_REQUEST {
uint8_t cmd; // ETCP_ID_ROUTE_ENTRY uint8_t cmd;
uint8_t subcmd; // ROUTE_SUBCMD_REQUEST_TABLE uint8_t subcmd;
uint32_t version; // 0 для запроса
} __attribute__((packed)); } __attribute__((packed));
struct ROUTE_BGP_CONN_ITEM { struct ROUTE_BGP_CONN_ITEM {
@ -102,16 +54,96 @@ struct ROUTE_BGP_CONN_ITEM {
struct ROUTE_BGP { struct ROUTE_BGP {
struct UTUN_INSTANCE* instance; struct UTUN_INSTANCE* instance;
struct BGP_NODEINFO_PACKET* my_nodeinfo;
uint16_t my_nodeinfo_size;
struct ll_queue* senders_list; struct ll_queue* senders_list;
uint32_t table_version; // starts at 1, for future use struct ll_queue* nodes;
struct NODEINFO_Q* local_node;
}; };
/**
* @brief Инициализирует модуль BGP маршрутизации.
*
* Создает очереди, local_node из подсетей конфига, привязывает к ETCP_ID_ROUTE_ENTRY,
* устанавливает коллбеки new_conn для on_up/on_down.
*
* @param instance экземпляр utun с node_id и конфигом
* @return ROUTE_BGP или NULL при ошибке
*/
struct ROUTE_BGP* route_bgp_init(struct UTUN_INSTANCE* instance); struct ROUTE_BGP* route_bgp_init(struct UTUN_INSTANCE* instance);
/**
* @brief Освобождает ресурсы BGP (очереди, local_node, снимает привязку ETCP).
*
* @param instance экземпляр utun
*/
void route_bgp_destroy(struct UTUN_INSTANCE* instance); void route_bgp_destroy(struct UTUN_INSTANCE* instance);
/**
* @brief Добавляет conn в senders_list (если нет), отправляет запрос таблицы (nodeinfo).
*
* Вызывается при ETCP on_up.
*/
void route_bgp_new_conn(struct ETCP_CONN* conn); void route_bgp_new_conn(struct ETCP_CONN* conn);
/**
* @brief Удаляет conn из senders_list, очищает paths во всех nodes, отправляет withdraw если node unreachable.
*
* Вызывается при ETCP on_down.
*/
void route_bgp_remove_conn(struct ETCP_CONN* conn); void route_bgp_remove_conn(struct ETCP_CONN* conn);
/**
* @brief Обрабатывает пакет NODEINFO.
*
* Проверка версии, обновление или создание NODEINFO_Q, добавление пути,
* вставка в роутинг, broadcast если не max hops.
*
* @return 0 при успехе
*/
int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, const uint8_t* data, size_t len);
/**
* @brief Обрабатывает WITHDRAW.
*
* Удаляет node из роутинга и nodes, broadcast withdraw.
*
* @return 0 при успехе
*/
int route_bgp_process_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* sender, const uint8_t* data, size_t len);
/**
* @brief Отправляет NODEINFO пакет одному conn (всегда local_node).
*/
void route_bgp_send_nodeinfo(struct NODEINFO_Q* node, struct ETCP_CONN* conn);
/**
* @brief Рассылает local_node NODEINFO всем senders_list кроме exclude.
*/
void route_bgp_broadcast_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* exclude);
/**
* @brief Отправляет WITHDRAW для node_id (вызывает broadcast_withdraw).
*/
void route_bgp_send_withdraw(struct ROUTE_BGP* bgp, uint64_t node_id);
/**
* @brief Поиск NODEINFO_Q по node_id через hash в nodes queue.
*
* @return node или NULL
*/
struct NODEINFO_Q* route_bgp_get_node(struct ROUTE_BGP* bgp, uint64_t node_id);
/**
* @brief Добавляет путь (conn) в paths узла.
*
* @return 0 при успехе
*/
int route_bgp_add_path(struct NODEINFO_Q* nq, struct ETCP_CONN* conn, uint64_t* hop_list, uint8_t hop_count);
/**
* @brief Удаляет conn из paths узла.
*
* @return 1 если путей не осталось (unreachable)
*/
int route_bgp_remove_path(struct NODEINFO_Q* nq, struct ETCP_CONN* conn);
#endif // ROUTE_BGP_H #endif // ROUTE_BGP_H

85
src/route_bgp.txt

@ -1,71 +1,26 @@
подсистема роутинга Ключевые изменения в понимании
utun - это сеть узлов. у каждого узла есть собственные локальные подсети.
глобальная задача: создать у каждого узла полную таблицу маршрутизации.
Узлы преимущественно создают связь напрямую друг с другом. Но если это не получается отправляют трафик транзитом через доступные узлы.
Иногда бывает что через транзитные узлы метрики лучше чем напрямую. Используем приоритетно узлы с лучшей метриков, при нехватке bandwidth используем разные каналы (агрегируем).
Динамически обновлем метрики каналов чтобы при отказе быстро переключаться на другие и не фризить обмен из-за отказов.
paths
и узлы обмениваются таблицой маршрутов между собой так чтобы у каждого была актуальная таблица подсетей всех узлов. route_bgp_process_nodeinfo(bgp, from_conn, data, entry->len):
маршрутами меняются клиенты, подключения которых которые взяты из конфига. и сервера принявшие подключения если клиент инициировал обмен маршрутами. проверяет текущую версию nodeinfo, если совпадает - только bgp_update local nodelist
если версия новая или нет узла - spread:
bgp_update local nodelist
realloc: добавляет next hop: hoplist += prev_node_id, hop_count++
bgp_spread
инициируется подключение, клиент отправляет свою таблицу. когда сервер принимает таблицу - сервер помечает что по этому маршруту надо обмениваться маршрутами, далее; 1. функция распространения маршрута
- добавляет узел в список рассылки обновлений маршрутов bgp_spread(struct ROUTE_BGP bgp, struct NODEINFO* n)
- отправляет свою таблицу send to:
- добавляет в свою таблицу отсутствующие маршруты - все подключения, если не найден uid подключения в hoplist
- если что-то добавил:
- рассылает измененные маршруты по списку рассылки
- список рассылки - это linked-list очередей (также на базе ll_queue - каждый элемент = подписчик). один маршрут = одна отправленная кодограмма
при подключении узла или изменении таблицы: узел шлёт свою таблицу bgp_update local nodelist:
формат кодограммы: [0x01 - routing module] [subcmd] [data] - обновляем саму node
subcmd: - удалеям conn где lash hop=prev_node_id
1 [route] - отправка маршрута - добавляем новый conn с новым hoplist
2, без данных - больше данных нет
если сервер получил кодограмму маршрута - он помечает флаг в etcp что с узлом надо обмениваться маршрутами (etcp_conn->routing_exchange_active=2) и добавляет в очередь рассылки маршрутов 2. withdraw:
bgp_withdraw(struct ROUTE_BGP bgp, uint64_t node_to_del, uint64_t wd_source) - wd_source это узел который захотел withdraw.
- находим у себя node to del. удаляем если в hoplist найден wd_node (или мы = wd_node)
если удалили - распространяем по всем линкам с этими же аргументами
=========================================
механизм инкрементальной синхронизации (реализация - потом, пока мысли)
1. вычисляем хеш каждой записи в роутинг таблице. используем ip+mask+node_uid
2. потом из этих хешей создаем хеш таблицу (старшие n бит номер ячейки). далее вычисляются хеши каждой ячейки.
на первом этапе n=16, на втором - n=16*16 на третьем n=16*16*16.
отправляем хеши удаленному узлу в формате: [n, 1 байт] ([индекс хеша 2 байта - используется n старших бит][хеш - 8 байт])
удаленный узел считает свои хеши и сравнивает. где не совпало смотрит сколько записей.
если записей не много - передает эти записи.
если записей много - добавляет 4 бита к хеш таблице и строит субтаблицу для
==========================================
Формат роутинга:
Таблица узлов состоит из записей:
- uid
- name
- links
- 3 транзитных узла с метриками (RTT)
- маршруты узла
- текущая загрузка линка (за последние 10 сек) можно частоту адаптировать под размер сети
- bandidth limit
- transit bandwidth limit
две группы узлов:
- узлы за nat. подключаются через транзитные узлы. измеряют пинги до транзитных и выбирают N (3 default) лучшие линки. 3 лучших используем для распространения маршрутов
- транзитные узлы. имеют линки с загрузкой.
добавить кодограмму - отменить распространение маршрутов по линку (+ сделать важным линком)
карта маршрутизации:
План:
- сделать передачу роутинга в
- сделать фоновый probe для узлов (условно 1 нода в секунду). выигравшие по качеству соатновятся основными
- сделать etcp дизконнект:
- отправить disconnect request + дождаться ack дальше master удаляет, slave удаляет по down.

648
src/route_lib.c

@ -1,19 +1,17 @@
#include "route_node.h"
#include "route_lib.h" #include "route_lib.h"
#include "etcp.h"
#include "etcp_debug.h" #include "etcp_debug.h"
#include "../lib/debug_config.h" #include "../lib/debug_config.h"
#include "../lib/mem.h" #include "../lib/mem.h"
#include "../lib/platform_compat.h" #include "../lib/platform_compat.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdlib.h>
#include <stdint.h> #include <stdint.h>
#define INITIAL_CAPACITY 100 #define INITIAL_CAPACITY 100
#define CONN_INFO_INIT_SIZE 4
// ============================================================================ // ============================================================================
// Вспомогательные функции (без изменений) // Вспомогательные функции
// ============================================================================ // ============================================================================
static uint32_t prefix_to_mask(uint8_t prefix) { static uint32_t prefix_to_mask(uint8_t prefix) {
@ -39,152 +37,8 @@ static bool check_route_overlap_in_table(uint32_t network, uint8_t prefix,
return false; return false;
} }
static struct NODE_CONNS_INFO* find_node_conns(struct ROUTE_TABLE *table, uint64_t node_id) {
for (size_t i = 0; i < table->count; i++) {
if (table->entries[i].conn_list && table->entries[i].conn_list->node_id == node_id) {
return table->entries[i].conn_list;
}
}
return NULL;
}
// ============================================================================ // ============================================================================
// NODE_CONNS_INFO — исправленный realloc + preferred_conn // Поиск (LPM)
// ============================================================================
static struct NODE_CONNS_INFO* node_conns_info_create(uint64_t node_id, uint8_t init_size) {
size_t total_size = sizeof(struct NODE_CONNS_INFO) + init_size * sizeof(struct NODE_CONN_INFO);
struct NODE_CONNS_INFO *info = u_calloc(1, total_size);
if (!info) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "node_conns_info_create: alloc failed");
return NULL;
}
info->node_id = node_id;
info->conninfo_count = 0;
info->conninfo_memsize = init_size; // исправлена опечатка
info->ref_count = 1;
info->preferred_conn = 0;
info->flags = ROUTE_FLAG_ACTIVE;
return info;
}
static struct NODE_CONNS_INFO* node_conns_info_add(struct ROUTE_TABLE *table,
struct NODE_CONNS_INFO *info,
uint32_t endpoint_ip, uint16_t endpoint_port,
const uint8_t *public_key,
struct ETCP_CONN *conn,
uint64_t *hop_list, uint8_t hop_count,
bool *out_needs_reroute) {
if (!info) return NULL;
if (out_needs_reroute) *out_needs_reroute = false;
// === 1. Обновление существующего подключения (решение проблемы #2) ===
for (uint8_t i = 0; i < info->conninfo_count; i++) {
if (info->conn_info[i].conn_id == conn) {
struct NODE_CONN_INFO *ci = &info->conn_info[i];
// освобождаем старый hop_list
if (ci->hop_list) {
u_free(ci->hop_list);
ci->hop_list = NULL;
}
ci->endpoint_ip = endpoint_ip;
ci->endpoint_port = endpoint_port;
ci->hop_count = hop_count;
if (hop_count > 0 && hop_list) {
ci->hop_list = u_malloc(hop_count * sizeof(uint64_t));
if (ci->hop_list)
memcpy(ci->hop_list, hop_list, hop_count * sizeof(uint64_t));
}
if (public_key)
memcpy(ci->public_key, public_key, 64);
// если обновился preferred_conn — нужен reroute
if (i == info->preferred_conn && out_needs_reroute)
*out_needs_reroute = true;
return info;
}
}
// === 2. Добавление нового подключения (оригинальная логика) ===
if (info->conninfo_count >= info->conninfo_memsize) {
uint8_t new_size = info->conninfo_memsize * 2;
size_t new_total = sizeof(struct NODE_CONNS_INFO) + new_size * sizeof(struct NODE_CONN_INFO);
struct NODE_CONNS_INFO *new_info = u_realloc(info, new_total);
if (!new_info) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "node_conns_info_add: realloc failed");
return NULL;
}
new_info->conninfo_memsize = new_size;
// обновляем все указатели в таблице
for (size_t i = 0; i < table->count; i++) {
if (table->entries[i].conn_list == info)
table->entries[i].conn_list = new_info;
}
info = new_info;
}
struct NODE_CONN_INFO *ci = &info->conn_info[info->conninfo_count];
ci->endpoint_ip = endpoint_ip;
ci->endpoint_port = endpoint_port;
ci->conn_id = conn;
ci->hop_count = hop_count;
ci->hop_list = NULL;
if (hop_count > 0 && hop_list) {
ci->hop_list = u_malloc(hop_count * sizeof(uint64_t));
if (ci->hop_list)
memcpy(ci->hop_list, hop_list, hop_count * sizeof(uint64_t));
}
if (public_key)
memcpy(ci->public_key, public_key, 64);
info->conninfo_count++;
return info;
}
static bool node_conns_info_remove(struct NODE_CONNS_INFO *info, struct ETCP_CONN *conn) {
if (!info || !conn) return false;
for (uint8_t i = 0; i < info->conninfo_count; i++) {
if (info->conn_info[i].conn_id == conn) {
if (info->conn_info[i].hop_list) {
u_free(info->conn_info[i].hop_list);
}
bool was_preferred = (i == info->preferred_conn);
if (i < info->conninfo_count - 1) {
memmove(&info->conn_info[i], &info->conn_info[i + 1],
(info->conninfo_count - i - 1) * sizeof(struct NODE_CONN_INFO));
}
info->conninfo_count--;
if (info->preferred_conn > i) info->preferred_conn--;
if (info->preferred_conn >= info->conninfo_count && info->conninfo_count > 0) {
info->preferred_conn = 0;
}
return was_preferred; // нужен reroute?
}
}
return false;
}
static void node_conns_info_destroy(struct NODE_CONNS_INFO *info) {
if (!info) return;
for (uint8_t i = 0; i < info->conninfo_count; i++) {
if (info->conn_info[i].hop_list) u_free(info->conn_info[i].hop_list);
}
u_free(info);
}
// ============================================================================
// Поиск
// ============================================================================ // ============================================================================
static int binary_search_insert_pos(struct ROUTE_ENTRY *entries, size_t count, static int binary_search_insert_pos(struct ROUTE_ENTRY *entries, size_t count,
@ -195,7 +49,7 @@ static int binary_search_insert_pos(struct ROUTE_ENTRY *entries, size_t count,
int mid = left + (right - left) / 2; int mid = left + (right - left) / 2;
struct ROUTE_ENTRY *e = &entries[mid]; struct ROUTE_ENTRY *e = &entries[mid];
if (e->network < network || if (e->network < network ||
(e->network == network && e->prefix_length > prefix_length)) { // longer prefix first (e->network == network && e->prefix_length > prefix_length)) {
left = mid + 1; left = mid + 1;
} else { } else {
right = mid; right = mid;
@ -204,31 +58,6 @@ static int binary_search_insert_pos(struct ROUTE_ENTRY *entries, size_t count,
return left; return left;
} }
static struct ROUTE_ENTRY* find_exact(struct ROUTE_TABLE *table,
uint32_t network, uint8_t prefix) {
if (!table) return NULL;
int left = 0, right = (int)table->count;
while (left < right) {
int mid = left + (right - left) / 2;
struct ROUTE_ENTRY *e = &table->entries[mid];
if (e->network < network || (e->network == network && e->prefix_length >= prefix)) {
left = mid + 1;
} else {
right = mid;
}
}
if (left < (int)table->count &&
table->entries[left].network == network &&
table->entries[left].prefix_length == prefix) {
return &table->entries[left];
}
if (left > 0 && table->entries[left-1].network == network &&
table->entries[left-1].prefix_length == prefix) {
return &table->entries[left-1];
}
return NULL;
}
static struct ROUTE_ENTRY* binary_search_lpm(struct ROUTE_TABLE *table, uint32_t dest_ip) { static struct ROUTE_ENTRY* binary_search_lpm(struct ROUTE_TABLE *table, uint32_t dest_ip) {
if (table->count == 0) return NULL; if (table->count == 0) return NULL;
@ -247,7 +76,6 @@ static struct ROUTE_ENTRY* binary_search_lpm(struct ROUTE_TABLE *table, uint32_t
best_prefix = entry->prefix_length; best_prefix = entry->prefix_length;
best = entry; best = entry;
} }
if (mid + 1 < (int)table->count) { if (mid + 1 < (int)table->count) {
struct ROUTE_ENTRY *next = &table->entries[mid + 1]; struct ROUTE_ENTRY *next = &table->entries[mid + 1];
uint32_t next_mask = prefix_to_mask(next->prefix_length); uint32_t next_mask = prefix_to_mask(next->prefix_length);
@ -262,35 +90,21 @@ static struct ROUTE_ENTRY* binary_search_lpm(struct ROUTE_TABLE *table, uint32_t
} else if (dest_ip > entry->network) { } else if (dest_ip > entry->network) {
left = mid + 1; left = mid + 1;
} else { } else {
if (mid > 0) {
struct ROUTE_ENTRY *prev = &table->entries[mid - 1];
uint32_t prev_mask = prefix_to_mask(prev->prefix_length);
if ((dest_ip & prev_mask) == (prev->network & prev_mask)) {
if (prev->prefix_length > best_prefix) {
best_prefix = prev->prefix_length;
best = prev;
}
}
}
right = mid - 1; right = mid - 1;
} }
} }
if (!best && left > 0 && left <= (int)table->count) { // fallback проверка предыдущей записи
int check = left - 1; if (!best && left > 0) {
if (check >= 0) { struct ROUTE_ENTRY *entry = &table->entries[left - 1];
struct ROUTE_ENTRY *entry = &table->entries[check];
uint32_t mask = prefix_to_mask(entry->prefix_length); uint32_t mask = prefix_to_mask(entry->prefix_length);
if ((dest_ip & mask) == (entry->network & mask)) { if ((dest_ip & mask) == (entry->network & mask)) {
best = entry; best = entry;
} }
} }
}
return best; return best;
} }
// ============================================================================ // ============================================================================
// Основные функции // Основные функции
// ============================================================================ // ============================================================================
@ -315,132 +129,59 @@ struct ROUTE_TABLE *route_table_create(void) {
table->local_subnet_count = 0; table->local_subnet_count = 0;
memset(&table->stats, 0, sizeof(table->stats)); memset(&table->stats, 0, sizeof(table->stats));
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Routing table created, capacity %d", INITIAL_CAPACITY); DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Routing table created (simplified), capacity %d", INITIAL_CAPACITY);
return table; return table;
} }
void route_table_destroy(struct ROUTE_TABLE *table) { void route_table_destroy(struct ROUTE_TABLE *table) {
if (!table) { if (!table) return;
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route_table_destroy: table is NULL");
return;
}
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_table_destroy: count=%zu", table->count);
// Правильно освобождаем NODE_CONNS_INFO с учётом ref_count DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_table_destroy: count=%zu", table->count);
// (один info может быть у нескольких префиксов)
for (size_t i = 0; i < table->count; i++) {
struct NODE_CONNS_INFO *info = table->entries[i].conn_list;
if (info) {
table->entries[i].conn_list = NULL; // защита от повторного использования
if (--info->ref_count == 0) {
node_conns_info_destroy(info);
}
}
}
// Освобождаем динамические/локальные подсети (если они были выделены) // v_node_info — внешний объект (NODEINFO_Q), память им не управляем
u_free(table->dynamic_subnets); u_free(table->dynamic_subnets);
u_free(table->local_subnets); u_free(table->local_subnets);
u_free(table->entries); u_free(table->entries);
u_free(table); u_free(table);
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Routing table destroyed"); DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Routing table destroyed");
} }
/* ====================== НОВАЯ ОСНОВНАЯ ФУНКЦИЯ ====================== */ bool route_insert(struct ROUTE_TABLE *table, struct NODEINFO_Q *node) {
bool route_insert(struct ROUTE_TABLE *table, if (!table || !node) {
const struct ROUTE_ENTRY *entry, DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route_insert: invalid arguments");
struct ETCP_CONN *conn,
uint64_t node_id,
uint64_t my_node_id,
uint64_t *hop_list,
uint8_t hop_count) {
if (!table) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route_insert: table is NULL");
return false;
}
if (!entry) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route_insert: entry is NULL");
return false; return false;
} }
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_insert: network=%s prefix=%d conn=%p node_id=%016llx",
ip_to_string(entry->network).a, entry->prefix_length, (void*)conn, (unsigned long long)node_id);
// === 0. Loop prevention — ВСЕГДА (исправление проблемы #3) === // === Вызов сторонней функции (теперь без malloc) ===
if (conn && my_node_id && hop_list && hop_count > 0) { const struct NODEINFO_IPV4_SUBNET *subnets = NULL;
for (uint8_t i = 0; i < hop_count; i++) { int cnt = get_node_routes(node, &subnets);
if (hop_list[i] == my_node_id) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "Loop detected: %s/%d", ip_to_string(entry->network).a, entry->prefix_length);
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_insert: REJECTED (loop detected)");
return false;
}
}
}
uint64_t peer_node_id=0;
if (conn) {
peer_node_id=conn->peer_node_id;
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "peer: %s %016llx", conn->log_name, (unsigned long long)peer_node_id);
}
else DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "no peer");
// === 1. Точный матч префикса ===
struct ROUTE_ENTRY *existing = find_exact(table, entry->network, entry->prefix_length);
if (existing) {
bool existing_local = (existing->conn_list == NULL);
bool new_local = (conn == NULL);
if (existing_local != new_local ||
(!existing_local && existing->conn_list->node_id != node_id)) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "Route conflict (different owner): %s/%d", ip_to_string(entry->network).a, entry->prefix_length);
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_insert: REJECTED (conflict)");
return false;
}
// === LOCAL route update ===
if (new_local) {
existing->last_update = get_time_tb();
if (table->change_callback)
table->change_callback(table, existing, 1, peer_node_id, table->change_callback_arg);
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_insert: UPDATED local route %s/%d", ip_to_string(entry->network).a, entry->prefix_length);
return true;
}
// === LEARNED route: обновляем hop_list или добавляем новый путь (исправление проблемы #2) === if (cnt <= 0) {
struct NODE_CONNS_INFO *info = existing->conn_list; DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_insert: get_node_routes returned %d subnets", cnt);
bool needs_reroute = false; // не используется здесь, но нужен для сигнатуры
info = node_conns_info_add(table, info, 0, 0, NULL, conn, hop_list, hop_count, &needs_reroute);
if (!info) {
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_insert: FAILED (node_conns_info_add)");
return false; return false;
} }
existing->conn_list = info; size_t count = (size_t)cnt;
existing->last_update = get_time_tb();
if (table->change_callback) // === Проверка пересечений ===
table->change_callback(table, existing, 1, peer_node_id, table->change_callback_arg); for (size_t i = 0; i < count; i++) {
uint32_t network;
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_insert: UPDATED learned route %s/%d node_id=%016llx", memcpy(&network, subnets[i].addr, 4); // addr уже в network byte order (big-endian)
ip_to_string(entry->network).a, entry->prefix_length, (unsigned long long)node_id); uint8_t prefix = subnets[i].prefix_length;
return true;
}
// === 2. Новый префикс === if (check_route_overlap_in_table(network, prefix,
if (check_route_overlap_in_table(entry->network, entry->prefix_length,
table->entries, table->count)) { table->entries, table->count)) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "Route overlap rejected: %s/%d", ip_to_string(entry->network).a, entry->prefix_length); DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_insert: overlap detected for %s/%d",
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_insert: REJECTED (overlap)"); ip_to_string(network).a, prefix);
return false; return false;
} }
}
// === 3. Реаллокация таблицы при необходимости === // === Реаллокация при необходимости ===
if (table->count >= table->capacity) { if (table->count + count > table->capacity) {
uint32_t new_cap = table->capacity * 2; size_t new_cap = table->capacity * 2;
while (new_cap < table->count + count) new_cap *= 2;
struct ROUTE_ENTRY *new_entries = u_realloc(table->entries, new_cap * sizeof(struct ROUTE_ENTRY)); struct ROUTE_ENTRY *new_entries = u_realloc(table->entries, new_cap * sizeof(struct ROUTE_ENTRY));
if (!new_entries) { if (!new_entries) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route_insert: realloc failed"); DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route_insert: realloc failed");
@ -450,320 +191,117 @@ bool route_insert(struct ROUTE_TABLE *table,
table->capacity = new_cap; table->capacity = new_cap;
} }
// === 4. Поиск позиции для вставки (сортировка) === // === Вставка каждого префикса (таблица остаётся отсортированной) ===
int pos = binary_search_insert_pos(table->entries, table->count, for (size_t i = 0; i < count; i++) {
entry->network, entry->prefix_length); uint32_t network;
memcpy(&network, subnets[i].addr, 4);
// === 5. Создаём новую запись === uint8_t prefix_length = subnets[i].prefix_length;
struct ROUTE_ENTRY new_entry = *entry;
new_entry.created_time = get_time_tb();
new_entry.last_update = get_time_tb();
new_entry.conn_list = NULL;
if (conn) {
// === LEARNED route ===
uint64_t owner_node_id = node_id;
struct NODE_CONNS_INFO *node_info = find_node_conns(table, owner_node_id);
if (!node_info) {
node_info = node_conns_info_create(owner_node_id, CONN_INFO_INIT_SIZE);
if (!node_info) return false;
} else {
node_info->ref_count++;
}
new_entry.conn_list = node_info;
bool dummy = false;
node_info = node_conns_info_add(table, node_info, 0, 0, NULL, conn, hop_list, hop_count, &dummy);
if (!node_info) return false;
new_entry.conn_list = node_info; int pos = binary_search_insert_pos(table->entries, table->count, network, prefix_length);
} else {
// === LOCAL route ===
table->stats.local_routes++;
}
// === 6. Вставка в отсортированный массив ===
if (pos < (int)table->count) { if (pos < (int)table->count) {
memmove(&table->entries[pos + 1], &table->entries[pos], memmove(&table->entries[pos + 1], &table->entries[pos],
(table->count - pos) * sizeof(struct ROUTE_ENTRY)); (table->count - pos) * sizeof(struct ROUTE_ENTRY));
} }
table->entries[pos] = new_entry;
table->count++;
// === 7. Callback ===
if (table->change_callback) {
table->change_callback(table, &table->entries[pos], 0, peer_node_id, table->change_callback_arg);
}
// === 8. Отладочное сообщение ===
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Route %s: %s/%d (node %016llx)",
conn ? "learned" : "local", ip_to_string(entry->network).a, entry->prefix_length,
(unsigned long long)node_id);
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_insert: INSERTED %s %s/%d",
conn ? "learned" : "local", ip_to_string(entry->network).a, entry->prefix_length);
return true;
}
/* ====================== НОВАЯ ФУНКЦИЯ ====================== */
void route_remove_conn(struct ROUTE_TABLE *table, struct ETCP_CONN *conn) {
if (!table) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route_remove_conn: table is NULL");
return;
}
if (!conn) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route_remove_conn: conn is NULL");
return;
}
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_remove_conn: conn=%p peer=%016llx table_count=%zu",
(void*)conn, (unsigned long long)conn->peer_node_id, table->count);
// DIAGNOSTIC: поиск conn во всех entries перед началом struct ROUTE_ENTRY *e = &table->entries[pos];
size_t entries_with_conn = 0; e->network = network;
for (size_t i = 0; i < table->count; i++) { e->prefix_length = prefix_length;
struct NODE_CONNS_INFO *info = table->entries[i].conn_list; e->v_node_info = node; // все префиксы узла ссылаются на один объект
if (!info) continue;
for (uint8_t j = 0; j < info->conninfo_count; j++) {
if (info->conn_info[j].conn_id == conn) {
entries_with_conn++;
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, " pre_check: entry[%zu] node_id=%016llx conninfo_count=%d has_conn_at_index=%d",
i, (unsigned long long)info->node_id, info->conninfo_count, j);
}
}
}
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_remove_conn: pre_check found conn in %zu entries", entries_with_conn);
if (entries_with_conn == 0) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_remove_conn: conn NOT FOUND in any entry - skipping");
return;
}
struct NODE_CONNS_INFO* affected[512];
bool needs_reroute[512] = {0};
size_t aff_count = 0;
// Pass 1: удаляем conn (один раз на node_id)
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_remove_conn: Pass1 - removing conn from NODE_CONNS_INFO");
for (size_t i = 0; i < table->count; i++) {
struct NODE_CONNS_INFO *info = table->entries[i].conn_list;
if (!info) continue;
bool has_conn = false;
uint8_t conn_idx = 0;
for (uint8_t j = 0; j < info->conninfo_count; j++) {
if (info->conn_info[j].conn_id == conn) { has_conn = true; conn_idx = j; break; }
}
if (!has_conn) continue;
bool already = false;
for (size_t k = 0; k < aff_count; k++) {
if (affected[k] == info) { already = true; break; }
}
if (already) {
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, " entry[%zu] node_id=%016llx: already processed", i, (unsigned long long)info->node_id);
continue;
}
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " entry[%zu] node_id=%016llx: removing conn at index=%d, conninfo_count was=%d ref_count=%d", table->count++;
i, (unsigned long long)info->node_id, conn_idx, info->conninfo_count, info->ref_count); table->stats.learned_routes++;
bool rer = node_conns_info_remove(info, conn);
affected[aff_count] = info;
needs_reroute[aff_count] = rer;
aff_count++;
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " after remove: conninfo_count=%d ref_count=%d was_preferred=%s",
info->conninfo_count, info->ref_count, rer ? "yes" : "no");
if (aff_count >= 512) break;
}
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_remove_conn: Pass1 complete - affected %zu NODE_CONNS_INFO", aff_count);
// Pass 2: удаляем записи без оставшихся подключений
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_remove_conn: Pass2 - compacting entries");
size_t j = 0;
size_t entries_removed = 0;
for (size_t i = 0; i < table->count; i++) {
struct ROUTE_ENTRY *e = &table->entries[i];
if (e->conn_list && e->conn_list->conninfo_count == 0) {
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " removing entry[%zu] network=%s/%d node_id=%016llx ref_count=%d",
i, ip_to_string(e->network).a, e->prefix_length,
(unsigned long long)e->conn_list->node_id, e->conn_list->ref_count);
if (table->change_callback)
table->change_callback(table, e, 2, 0, table->change_callback_arg);
struct NODE_CONNS_INFO *info = e->conn_list;
e->conn_list = NULL;
if (--info->ref_count == 0) {
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " destroying NODE_CONNS_INFO (ref_count=0)");
node_conns_info_destroy(info);
} else {
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " NODE_CONNS_INFO ref_count now=%d (still referenced)", info->ref_count);
}
entries_removed++;
continue;
}
if (i != j) table->entries[j] = table->entries[i];
j++;
}
table->count = j;
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_remove_conn: Pass2 complete - removed %zu entries, new_count=%zu", entries_removed, table->count);
// Pass 3: reroute только если изменился preferred_conn
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_remove_conn: Pass3 - reroute check");
for (size_t i = 0; i < table->count; i++) {
struct ROUTE_ENTRY *e = &table->entries[i];
if (!e->conn_list) continue;
for (size_t k = 0; k < aff_count; k++) {
if (affected[k] == e->conn_list && needs_reroute[k]) {
e->last_update = get_time_tb();
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " entry[%zu] network=%s/%d: triggering REROUTE callback",
i, ip_to_string(e->network).a, e->prefix_length);
if (table->change_callback)
table->change_callback(table, e, 1, 0, table->change_callback_arg);
break;
}
}
}
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_remove_conn: DONE - affected %zu nodes, final_count=%zu", aff_count, table->count);
}
/* ====================== НОВАЯ ФУНКЦИЯ ДЛЯ WITHDRAW ====================== */
bool route_remove_path(struct ROUTE_TABLE *table,
struct ETCP_CONN *conn,
uint64_t node_id) {
if (!table) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route_remove_path: table is NULL");
return false;
}
if (!conn) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route_remove_path: conn is NULL");
return false;
}
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_remove_path: node_id=%016llx conn=%p",
(unsigned long long)node_id, (void*)conn);
struct NODE_CONNS_INFO* info = find_node_conns(table, node_id);
if (!info) {
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_remove_path: node not found");
return false;
} }
bool was_preferred = node_conns_info_remove(info, conn); DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_insert: added %zu route(s) for node_info=%p",
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_remove_path: removed, was_preferred=%d remaining_conns=%d", count, (void*)node);
was_preferred, info->conninfo_count);
if (info->conninfo_count == 0) {
// Последний путь к узлу — полный withdraw
route_delete(table, node_id); // уже вызывает callback(2) и очищает память
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_remove_path: WITHDRAW node_id=%016llx", (unsigned long long)node_id);
return true; return true;
} else if (was_preferred) {
// Изменился preferred_conn — уведомляем о reroute для всех префиксов этого узла
for (size_t i = 0; i < table->count; i++) {
struct ROUTE_ENTRY *e = &table->entries[i];
if (e->conn_list == info) {
e->last_update = get_time_tb();
if (table->change_callback)
table->change_callback(table, e, 1, 0, table->change_callback_arg);
}
}
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_remove_path: REROUTE node_id=%016llx", (unsigned long long)node_id);
}
return false;
} }
void route_delete(struct ROUTE_TABLE *table, uint64_t node_id) { void route_delete(struct ROUTE_TABLE *table, struct NODEINFO_Q *node) {
if (!table) { if (!table || !node) return;
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route_delete: table is NULL");
return; DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_delete: removing all routes for node_info=%p", (void*)node);
}
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_delete: node_id=%016llx", (unsigned long long)node_id);
size_t i = 0; size_t i = 0;
size_t removed = 0;
while (i < table->count) { while (i < table->count) {
struct ROUTE_ENTRY *entry = &table->entries[i]; if (table->entries[i].v_node_info == node) {
bool deleted = false; // v_node_info — внешний, не освобождаем
if (entry->conn_list && entry->conn_list->node_id == node_id) {
for (uint8_t j = 0; j < entry->conn_list->conninfo_count; j++) {
if (entry->conn_list->conn_info[j].hop_list) {
u_free(entry->conn_list->conn_info[j].hop_list);
}
}
entry->conn_list->conninfo_count = 0;
entry->conn_list->ref_count--;
if (entry->conn_list->ref_count == 0) {
node_conns_info_destroy(entry->conn_list);
}
entry->conn_list = NULL;
if (table->change_callback) {
table->change_callback(table, entry, 2, 0, table->change_callback_arg);
}
if (i < table->count - 1) { if (i < table->count - 1) {
memmove(&table->entries[i], &table->entries[i + 1], memmove(&table->entries[i], &table->entries[i + 1],
(table->count - i - 1) * sizeof(struct ROUTE_ENTRY)); (table->count - i - 1) * sizeof(struct ROUTE_ENTRY));
} }
table->count--; table->count--;
deleted = true; table->stats.learned_routes--;
removed++;
continue;
} }
if (!deleted) {
i++; i++;
} }
}
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Routes deleted for node %016llx", (unsigned long long)node_id); if (removed > 0) {
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_delete: removed %zu route(s)", removed);
}
} }
struct ROUTE_ENTRY* route_lookup(struct ROUTE_TABLE *table, uint32_t dest_ip) { struct ROUTE_ENTRY* route_lookup(struct ROUTE_TABLE *table, uint32_t dest_ip) {
if (!table) { if (!table) return NULL;
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route_lookup: table is NULL");
return NULL;
}
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_lookup: dest_ip=%s", ip_to_string(dest_ip).a); DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_lookup: dest_ip=%s", ip_to_string(dest_ip).a);
struct ROUTE_ENTRY *result = binary_search_lpm(table, dest_ip); struct ROUTE_ENTRY *result = binary_search_lpm(table, dest_ip);
if (result) { if (result) {
table->stats.routes_lookup_hits++; table->stats.routes_lookup_hits++;
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_lookup: FOUND %s/%d node_id=%016llx", DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_lookup: FOUND %s/%d (node_info=%p)",
ip_to_string(result->network).a, result->prefix_length, ip_to_string(result->network).a, result->prefix_length,
result->conn_list ? (unsigned long long)result->conn_list->node_id : 0); (void*)result->v_node_info);
} else { } else {
table->stats.routes_lookup_misses++; table->stats.routes_lookup_misses++;
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_lookup: NOT FOUND"); DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_lookup: NOT FOUND");
} }
return result; return result;
} }
void route_table_print(const struct ROUTE_TABLE *table) { void route_table_print(const struct ROUTE_TABLE *table) {
if (!table) return; if (!table) return;
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "=== Routing Table ==="); DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "=== Routing Table (simplified) ===");
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Count: %zu, Capacity: %zu", table->count, table->capacity); DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Count: %zu / Capacity: %zu", table->count, table->capacity);
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Local routes: %lu, Learned: %lu", DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Learned routes: %lu", table->stats.learned_routes);
table->stats.local_routes, table->stats.learned_routes);
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Lookup hits: %lu, misses: %lu", DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Lookup hits: %lu, misses: %lu",
table->stats.routes_lookup_hits, table->stats.routes_lookup_misses); table->stats.routes_lookup_hits, table->stats.routes_lookup_misses);
for (size_t i = 0; i < table->count; i++) { for (size_t i = 0; i < table->count; i++) {
const struct ROUTE_ENTRY *entry = &table->entries[i]; const struct ROUTE_ENTRY *entry = &table->entries[i];
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " %zu: %s/%d",
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " %zu: %s/%d", i + 1, ip_to_string(entry->network).a, entry->prefix_length); i + 1, ip_to_string(entry->network).a, entry->prefix_length);
if (entry->v_node_info && entry->v_node_info->node.hop_count != 0) {
if (entry->conn_list) { DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " v_node_info=%p", (void*)entry->v_node_info);
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " node_id=%016llx, conns=%d, ref=%d",
(unsigned long long)entry->conn_list->node_id,
entry->conn_list->conninfo_count,
entry->conn_list->ref_count);
} else { } else {
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " LOCAL"); DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " LOCAL");
} }
} }
} }
int parse_subnet(const char *subnet_str, uint32_t *network, uint8_t *prefix_length) {
if (!subnet_str || !network || !prefix_length) return -1;
char buf[64];
strncpy(buf, subnet_str, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
char *slash = strchr(buf, '/');
if (!slash) return -1;
*slash = '\0';
if (inet_pton(AF_INET, buf, network) != 1) return -1;
*network = ntohl(*network); // приводим к big-endian как требует структура
int p = atoi(slash + 1);
if (p < 0 || p > 32) return -1;
*prefix_length = (uint8_t)p;
return 0;
}

82
src/route_lib.h

@ -3,11 +3,14 @@
#include <stdint.h> #include <stdint.h>
#include <stddef.h> #include <stddef.h>
#include <stdbool.h> #include <stdbool.h>
// задача модуля: поиск по таблице маршрутизации нужной записи, вставка-удаление маршрутов
// Forward declarations // Forward declarations
struct ETCP_CONNECTIONS; struct ETCP_CONNECTIONS;
struct ROUTE_TABLE; struct ROUTE_TABLE;
struct ROUTE_ENTRY; struct ROUTE_ENTRY;
struct NODEINFO_Q;
/** /**
* @brief Флаги узла * @brief Флаги узла
@ -18,42 +21,6 @@ typedef enum {
} route_flags_t; } route_flags_t;
struct NODE_CONN_INFO {
uint32_t endpoint_ip; // IP для прямого подключения (к nexthop_node_id). 0 - нет IP (если мы - сервер)
uint16_t endpoint_port; // Порт для прямого подключения (к nexthop_node_id). 0 - нет PORT (если мы - сервер)
uint8_t public_key[64]; // публичный ключ узла (для прямого подключения)
struct ETCP_CONN* conn_id; // Указатель на подключение к next_hop, может быть null.
// next_hop - это первый элемент в hop_list.
// hop list может быть разный для разных подключений. Это надо понимать и учитывать.
uint64_t* hop_list; // маршрут до узла (next hop -> ... -> destination_hop): список промежуточных узлов (NODE ID, кол-во - hop_count) включая конечный узел, не включая наш узел. null - локальный маршрут
uint8_t hop_count; // Количество узлов до узла назначения. 0 - я (not used), 1 - direct connect (hoplist 1 запись nexthop_node_id = id узла назначения)
};
struct NODE_CONNS_INFO {// Один NODE_CONNS_INFO на один node_id. несколько маршрутов с одинаковым node_id должны ссылаться на один экземпляр NODE_CONNS_INFO.
uint64_t node_id; // ID узла назначения. Если = моему ID - локальный маршрут.
uint8_t flags; // флаги узла
uint8_t preferred_conn; // выбранное соединение (его будем распространять далее по BGP). по умолчанию 0. при изменении conn_info надо за ним присмотреть.
uint8_t conninfo_count; // число подключений к узлу
uint8_t conninfo_memsize; // размер выделенной памяти (оптимизация realloc)
uint16_t ref_count; // счетчик ссылок с разных route_entry для освобождения
struct NODE_CONN_INFO conn_info[0]; // сами подключения. управлять памятью своими силами. использовать u_malloc, u_reclloc, u_free (совместимо с stdlib). максимально просто - только увеличиваем при нехватке, не уменьшаем.
};
/**
* @brief Callback тип для уведомления об изменении маршрута
*
* @param table Таблица маршрутизации
* @param entry Запись маршрута
* @param action Действие: 0=insert, 1=update, 2=delete
* @param arg Пользовательский аргумент
*/
typedef void (*route_change_callback_fn)(struct ROUTE_TABLE* table,
struct ROUTE_ENTRY* entry,
int action,
uint64_t changed_from, /**< peer_node_id от кого пришло изменение, 0 если локальное */
void* arg);
/** /**
* @brief Расширенная запись маршрута * @brief Расширенная запись маршрута
* *
@ -62,11 +29,7 @@ typedef void (*route_change_callback_fn)(struct ROUTE_TABLE* table,
struct ROUTE_ENTRY { struct ROUTE_ENTRY {
uint32_t network; // Сетевой адрес (big-endian) uint32_t network; // Сетевой адрес (big-endian)
uint8_t prefix_length; // Длина префикса подсети uint8_t prefix_length; // Длина префикса подсети
struct NODE_CONNS_INFO* conn_list; // список прямых подключений к узлу. null если - локальный маршрут. struct NODEINFO_Q* v_node_info; // узел владелец этих маршрутов. null если - локальный маршрут.
// node_id - в conn_list. Если нет доступных подключений - маршрут удаляем как бесполезный
// hop list / hop count - это относится к подключению. может быть разный для разных подключений.
uint64_t created_time; /**< Время создания (в timebase - 0.1ms) - use get_time_tb();*/
uint64_t last_update; /**< Время последнего обновления (в timebase - 0.1ms) */
}; };
/** /**
@ -89,8 +52,6 @@ struct ROUTE_TABLE {
uint64_t routes_lookup_hits; /**< Количество попаданий в поиск маршрутов */ uint64_t routes_lookup_hits; /**< Количество попаданий в поиск маршрутов */
uint64_t routes_lookup_misses; /**< Количество промахов в поиск маршрутов */ uint64_t routes_lookup_misses; /**< Количество промахов в поиск маршрутов */
} stats; /**< Статистика таблицы маршрутизации */ } stats; /**< Статистика таблицы маршрутизации */
route_change_callback_fn change_callback; /**< Callback при изменении маршрута */
void* change_callback_arg; /**< Аргумент для callback */
}; };
/** /**
@ -108,45 +69,20 @@ struct ROUTE_TABLE *route_table_create(void);
void route_table_destroy(struct ROUTE_TABLE *table); void route_table_destroy(struct ROUTE_TABLE *table);
/** /**
* @brief Вставляет новую запись в таблицу маршрутизации * @brief Вставляет в таблицу маршрутизации все подсети для указанного узла
* @param node узел, все маршруты которого нужно добавить
* *
* @param table Указатель на таблицу маршрутизации
* @param entry Указатель на вставляемую запись (conn_list должен быть NULL)
* @param conn Соединение (next_hop). NULL = локальный маршрут
* @param node_id ID узла-владельца префикса (из пакета BGP)
* @param my_node_id Наш собственный node_id (для обнаружения петель, 0 = отключить проверку)
* @param hop_list Список хопов (уже с препроцессингом)
* @param hop_count Количество хопов
* @return true если вставка/обновление успешно * @return true если вставка/обновление успешно
*/ */
bool route_insert(struct ROUTE_TABLE *table, bool route_insert(struct ROUTE_TABLE *table, struct NODEINFO_Q *node);
const struct ROUTE_ENTRY *entry,
struct ETCP_CONN *conn,
uint64_t node_id, // владелец префикса
uint64_t my_node_id, // наш node_id (для loop detection)
uint64_t *hop_list,
uint8_t hop_count);
/** Удаляет конкретное подключение из всех маршрутов (при падении линка) */
void route_remove_conn(struct ROUTE_TABLE *table, struct ETCP_CONN *conn);
/**
* @brief Удаляет один путь (через conn) к указанному node_id.
* Используется при получении WITHDRAW от соседа.
* Если путей больше не осталось полностью withdraw + callback(2).
* Если изменился preferred_conn reroute + callback(1).
*/
bool route_remove_path(struct ROUTE_TABLE *table,
struct ETCP_CONN *conn,
uint64_t node_id);
/** /**
* @brief Удаляет все записи из таблицы маршрутизации для указанного узла * @brief Удаляет все записи из таблицы маршрутизации для указанного узла
* *
* @param table Указатель на таблицу маршрутизации * @param table Указатель на таблицу маршрутизации
* @param node_id ID узла, все маршруты которого нужно удалить * @param node узел, все маршруты которого нужно удалить
*/ */
void route_delete(struct ROUTE_TABLE *table, uint64_t node_id); void route_delete(struct ROUTE_TABLE *table, struct NODEINFO_Q *node);
/** /**
* @brief Выполняет поиск маршрута для заданного IP-адреса * @brief Выполняет поиск маршрута для заданного IP-адреса

17
src/route_lib.txt

@ -1,26 +1,31 @@
В таблице роутинга маршруты не пересекаются. Т.е. не может быть одновременно 192.168.1.1/24 и 192.168.1.100/30 В таблице роутинга маршруты не пересекаются. Т.е. не может быть одновременно 192.168.1.1/24 и 192.168.1.100/30
Как работает роутинг: Как работает роутинг:
1. типов маршрута бывает два: learned и local. 1. типов маршрута бывает два: learned (роутятся в ETCP instance) и local (роутятся в TUN).
local - локальные маршруты из конфига (опция конфига my_subnet=IP/Mask) local - локальные маршруты из конфига (опция конфига my_subnet=IP/Mask)
при инициализации они сразу добавляются в роутинг таблицу (и используются для отправки пакетов в tun интерфейс). при инициализации local сразу добавляются в роутинг таблицу (ипользуя запись BGP_NODEINFO_PACKET* my_nodeinfo).
2. При установке подключения к новому узлу мы отправляем этому узлу полностью свою таблицу маршрутизации (local + learned узлы). Если несколько доступных линков - отправляем только один - preferred_conn BGP_NODEINFO собирается на узле владельце node, далее распространяется по остальным узлам.
2. При установке подключения к новому узлу мы отправляем этому узлу все nodeinfo. Если несколько доступных линков - отправляем только один - preferred_conn
3. При изменении preferred_conn рассылаем reroute (например старый preferred_conn удален) 3. При изменении preferred_conn рассылаем reroute (например старый preferred_conn удален)
3. При удалении всех подключений рассылаем withdraw. 3. При удалении всех подключений рассылаем withdraw.
3. При получении маршрута мы смотрим есть ли такой маршрут уже в таблице. Если такой маршрут есть и ID узла другой - игнорируем с ошибкой. иначе добавляем/обновляем (для одного узла может быть несколько маршрутов). Игнорируем маршрут если наш ID есть в списке узлов (hop_list). 3. При получении BGP_NODEINFO_PACKET мы смотрим есть ли такой узел уже в таблице. Если узла нет - добавляем, если есть - обновляем информацию о узле включая его маршруты и список next_hop через которые доступен узел (nb_routes).
Добавляем так: Добавляем так:
инкрементируем hop_count инкрементируем hop_count
устанавливая etcp линк с которого приняли как next_hop, добавляем его в hop_list. устанавливая etcp линк с которого приняли как next_hop, добавляем его в hop_list.
рассылаем по всем активным линкам кроме линка с которого получили (обязательно) рассылаем по всем активным линкам кроме линка с которого получили (обязательно)
Обновление: обновляем hop_list если поменялся Обновление: обновляем hop_list если поменялся
4. При отключении от узла мы 4. При отключении от узла (если у node не осталось nb_routes) мы:
Удаляем все маршруты узла Удаляем node и вложенные структуры: все маршруты узла
Рассылаем withdraw для узла hop_id. Рассылаем withdraw для узла hop_id.
Логика рассылки withdraw: Логика рассылки withdraw:
Если получен withdraw - удаляем этот маршрут и распространяем withdraw или reroute по всем линкам кроме того с которого получили. В зависимости от изменений (остались ли резервные линки или изменился preferred_conn). Если получен withdraw - удаляем этот маршрут и распространяем withdraw или reroute по всем линкам кроме того с которого получили. В зависимости от изменений (остались ли резервные линки или изменился preferred_conn).
Метрик маршрута пока нет. используется первый доступный. Метрик маршрута пока нет. используется первый доступный.
Как работаеут маршрутизация:
destination IP -> route table lookup -> route entry (ip/mask, node_info*) -> выбор лучшего next_hop -> отправка в next_hop

128
src/route_node.c

@ -0,0 +1,128 @@
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "../lib/ll_queue.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "utun_instance.h"
#include "etcp.h"
#include "config_parser.h"
#include "route_node.h"
#include "route_bgp.h"
#include "etcp_debug.h"
/**
* @brief Получает указатель на массив IPv4-подсетей узла (без malloc/копирования).
*
* Функция вычисляет смещение внутри динамической части BGP_NODEINFO_Q
* и возвращает прямой указатель на массив struct BGP_NODEINFO_IPV4_SUBNET.
*
* @param node Указатель на BGP_NODEINFO_Q
* @param out_subnets [out] сюда будет записан указатель на первый элемент массива
* (NULL если подсетей нет)
* @return количество подсетей (>= 0) или -1 при ошибке
*/
int get_node_routes(struct NODEINFO_Q *node, const struct NODEINFO_IPV4_SUBNET **out_subnets) {
if (!node || !out_subnets) {
return -1;
}
*out_subnets = NULL;
const struct NODEINFO *info = &node->node;
if (info->local_v4_subnets == 0) {
return 0; // успех, но нет подсетей
}
// Начало динамических полей сразу после фиксированной части NODEINFO
const uint8_t *dynamic = (const uint8_t *)&node->node + sizeof(struct NODEINFO);
// 1. Пропускаем node_name
dynamic += info->node_name_len;
// 2. Пропускаем local_v4_sockets
dynamic += info->local_v4_sockets * sizeof(struct NODEINFO_IPV4_SOCKET);
// 3. Пропускаем local_v6_sockets
dynamic += info->local_v6_sockets * sizeof(struct NODEINFO_IPV6_SOCKET);
// Теперь dynamic указывает точно на начало массива NODEINFO_IPV4_SUBNET
*out_subnets = (const struct NODEINFO_IPV4_SUBNET *)dynamic;
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "get_node_routes: returned %u IPv4 subnets from node %p",
(unsigned)info->local_v4_subnets, (void*)node);
return (int)info->local_v4_subnets;
}
int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BGP* bgp) {
if (!instance || !bgp) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_update_my_nodeinfo: invalid args");
return -1;
}
if (!bgp->local_node) {
int vc = 0;
struct CFG_ROUTE_ENTRY* s = instance->config->my_subnets;
while (s) {
if (s->ip.family == AF_INET) vc++;
s = s->next;
}
size_t d = vc * sizeof(struct NODEINFO_IPV4_SUBNET);
bgp->local_node = u_calloc(1, sizeof(struct NODEINFO_Q) + d);
if (!bgp->local_node) return -1;
bgp->local_node->node.node_id = htobe64(instance->node_id);
bgp->local_node->node.hop_count = 0;
bgp->local_node->node.ver = 1;
bgp->local_node->dirty = 1;
bgp->local_node->last_ver = 1;
bgp->local_node->node.local_v4_subnets = vc;
}
int vc = 0;
struct CFG_ROUTE_ENTRY* s = instance->config->my_subnets;
while (s) {
if (s->ip.family == AF_INET) vc++;
s = s->next;
}
int changed = (vc != (int)bgp->local_node->node.local_v4_subnets);
if (!changed && vc > 0) {
uint8_t* current = (uint8_t*)&bgp->local_node->node + sizeof(struct NODEINFO);
struct NODEINFO_IPV4_SUBNET* ra = (struct NODEINFO_IPV4_SUBNET*)current;
s = instance->config->my_subnets;
bool same = true;
while (s) {
if (s->ip.family == AF_INET) {
if (memcmp(ra->addr, &s->ip.addr.v4, 4) != 0 || ra->prefix_length != s->netmask) {
same = false;
break;
}
ra++;
}
s = s->next;
}
changed = !same;
}
if (changed) {
uint8_t oldv = bgp->local_node->node.ver;
bgp->local_node->node.ver = ((oldv + 1) % 255) + 1;
bgp->local_node->node.local_v4_subnets = vc;
bgp->local_node->dirty = 1;
bgp->local_node->last_ver = bgp->local_node->node.ver;
uint8_t* dp = (uint8_t*)&bgp->local_node->node + sizeof(struct NODEINFO);
struct NODEINFO_IPV4_SUBNET* ra = (struct NODEINFO_IPV4_SUBNET*)dp;
s = instance->config->my_subnets;
while (s) {
if (s->ip.family == AF_INET) {
memcpy(ra->addr, &s->ip.addr.v4, 4);
ra->prefix_length = s->netmask;
ra++;
}
s = s->next;
}
} else {
bgp->local_node->last_ver = bgp->local_node->node.ver;
}
return vc;
}

94
src/route_node.h

@ -0,0 +1,94 @@
#ifndef ROUTE_NODE_H
#define ROUTE_NODE_H
#include <stdint.h>
#include <stddef.h>
#include "../lib/ll_queue.h"
#include "secure_channel.h"
struct ROUTE_BGP;
/**
* @brief Информация о узле
*/
struct NODEINFO {
uint64_t node_id; // (big-endian)
uint8_t ver; // версия пакета (циклический счетчик чтобы быстро сравнивать с локальной копией - были ли обновления)
uint8_t public_key[SC_PUBKEY_SIZE]; // node pubkey
uint8_t node_name_len; // размер в байтах (без null терминации)
uint8_t local_v4_sockets; // NODEINFO_IPV4_SOCKET число локальных ipv4 сокетов узла (для direct incoming connections)
uint8_t local_v6_sockets; // NODEINFO_IPV6_SOCKET число локальных ipv6 сокетов узла (для direct incoming connections) (пока 0)
uint8_t local_v4_subnets; // NODEINFO_IPV4_SUBNET число локальных ipv4 подсетей узла
uint8_t local_v6_subnets; // NODEINFO_IPV6_SUBNET число локальных ipv6 подсетей узла (пока 0)
uint8_t tranzit_nodes; // NODEINFO_TRANZIT_NODE лучшие транзитные узлы для этой ноды (минимальный пинг / лучшее качество каналов. выбирается/обновляется узлом)
uint8_t hop_count; // hop list: маршрут по которому распространялся этот NODEINFO_PACKET. для избежания зацикливаний при распространении по узлам. каждый узел при передаче инкрементирует и добавляет в конец свой node_id.
// далее идут динамическип поля по порядку следования полей в этой структуре: char node_name[node_name_len], сокеты, роуты, tranzit nodes, hop list (блоки описаны структурами ниже). hop list - это массив node_id[hop_count].
} __attribute__((packed));
struct NODEINFO_IPV4_SOCKET {
uint8_t addr[4];// network byte order
uint16_t port;
} __attribute__((packed));
struct NODEINFO_IPV6_SOCKET {
uint8_t addr[16];
uint16_t port;
} __attribute__((packed));
struct NODEINFO_IPV4_SUBNET {
uint8_t addr[4];// network byte order
uint8_t prefix_length;
} __attribute__((packed));
struct NODEINFO_IPV6_SUBNET {
uint8_t addr[16];
uint8_t prefix_length;
} __attribute__((packed));
struct NODEINFO_TRANZIT_NODE {
uint64_t node_id; // (big-endian)
uint16_t rtt; // x0.1 ms (измеренный удаленным узлом RTT до транзитного узла)
uint16_t link_q; // меньше - лучше (потери + 1/BW)
} __attribute__((packed));
struct NODEINFO_PATH {
struct ll_entry ll;
struct ETCP_CONN* conn;
uint8_t hop_count; // hop list: маршрут этого path
} __attribute__((packed));
struct NODEINFO_Q {
struct ll_entry ll;
struct ll_queue* paths;
uint8_t dirty;
uint8_t last_ver;
struct NODEINFO node; // Всегда в конце структуры - динамически расширяемый блок
} __attribute__((packed));
/**
* @brief Создаёт/обновляет nodeinfo для собственного узла
*
* Собирает данные из локальных структур и упаковывает к структуру (оптимизированную для передачи по сети)
*
* @param instance Указатель на UTUN_INSTANCE (с него сбоираем все данные)
* @param bgp Указатель на ROUTE_BGP (для доступа к my_nodeinfo и instance)
* @return количество подсетей (>= 0) или -1 при ошибке
*/
int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BGP* bgp);
/**
* @brief Получает указатель на массив IPv4-подсетей узла (без malloc/копирования).
*
* Функция вычисляет смещение внутри динамической части NODEINFO_Q
* и возвращает прямой указатель на массив struct NODEINFO_IPV4_SUBNET.
*
* @param node Указатель на NODEINFO_Q
* @param out_subnets [out] сюда будет записан указатель на первый элемент массива
* (NULL если подсетей нет)
* @return количество подсетей (>= 0) или -1 при ошибке
*/
int get_node_routes(struct NODEINFO_Q *node, const struct NODEINFO_IPV4_SUBNET **out_subnets);
#endif // ROUTE_NODE_H

28
src/routing.c

@ -2,6 +2,7 @@
#include "../lib/platform_compat.h" #include "../lib/platform_compat.h"
#include "routing.h" #include "routing.h"
#include "route_lib.h" #include "route_lib.h"
#include "route_node.h"
#include "tun_if.h" #include "tun_if.h"
#include "packet_dump.h" #include "packet_dump.h"
#include "etcp.h" #include "etcp.h"
@ -139,30 +140,27 @@ static void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, ui
return; return;
} }
// Determine destination node ID uint64_t dst_node_id = instance->node_id;
uint64_t dst_node_id = instance->node_id; // Default to local node
struct ETCP_CONN* conn = NULL; struct ETCP_CONN* conn = NULL;
struct NODEINFO_Q* nq = route->v_node_info;
// Check route type: conn_list == NULL means local route if (!nq || nq->node.hop_count == 0) {
if (route->conn_list == NULL) { DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "Local route to %s", ip_to_str(&addr, AF_INET).str);
// Local route - send to TUN (entry has [cmd=0][IP data], TUN skips cmd byte)
// dst_node_id is already instance->node_id
} else { } else {
// Learned route - use first conn from conn_list if (nq->paths && nq->paths->head) {
if (route->conn_list->conninfo_count > 0) { struct ll_entry* path_entry = nq->paths->head;
conn = route->conn_list->conn_info[0].conn_id; conn = (struct ETCP_CONN*)path_entry->data;
} }
if (!conn) { if (!conn) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: route to %s has no next_hop, dropping", ip_to_str(&addr, AF_INET).str); DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: no path for node %016llx",
(unsigned long long)nq->node.node_id);
instance->dropped_packets++; instance->dropped_packets++;
queue_entry_free(entry); queue_entry_free(entry);
queue_dgram_free(entry); queue_dgram_free(entry);
return; return;
} }
dst_node_id = conn->peer_node_id; dst_node_id = conn->peer_node_id;
if (!conn->normalizer) { if (!conn->normalizer) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: connection for %s has no normalizer, dropping", ip_to_str(&addr, AF_INET).str); DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: connection for %s has no normalizer", ip_to_str(&addr, AF_INET).str);
instance->dropped_packets++; instance->dropped_packets++;
queue_entry_free(entry); queue_entry_free(entry);
queue_dgram_free(entry); queue_dgram_free(entry);
@ -176,8 +174,7 @@ static void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, ui
} }
else DEBUG_INFO(DEBUG_CATEGORY_TRAFFIC, "NODE %016llx -> NODE %016llx", (unsigned long long)src_node_id, (unsigned long long)dst_node_id); else DEBUG_INFO(DEBUG_CATEGORY_TRAFFIC, "NODE %016llx -> NODE %016llx", (unsigned long long)src_node_id, (unsigned long long)dst_node_id);
if (route->conn_list == NULL) { if (!nq || nq->node.hop_count == 0) {
// Local route - send to TUN (entry has [cmd=0][IP data], TUN skips cmd byte)
int put_err = queue_data_put(instance->tun->input_queue, entry); int put_err = queue_data_put(instance->tun->input_queue, entry);
if (put_err != 0) { if (put_err != 0) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: failed to put to TUN: dst=%s err=%d", DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: failed to put to TUN: dst=%s err=%d",
@ -187,7 +184,6 @@ static void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, ui
queue_dgram_free(entry); queue_dgram_free(entry);
return; return;
} }
// Entry sent to TUN, don't free here
instance->routed_packets++; instance->routed_packets++;
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_pkt: sent %zu bytes to TUN", ip_len); DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_pkt: sent %zu bytes to TUN", ip_len);
return; return;

36
src/utun_instance.c

@ -4,6 +4,7 @@
#include "config_updater.h" #include "config_updater.h"
#include "tun_if.h" #include "tun_if.h"
#include "tun_route.h" #include "tun_route.h"
#include "route_node.h"
#include "route_lib.h" #include "route_lib.h"
#include "routing.h" #include "routing.h"
#include "route_bgp.h" #include "route_bgp.h"
@ -73,30 +74,6 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
return -1; return -1;
} }
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Routing module created"); DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Routing module created");
// Add local subnets from config as static routes
struct CFG_ROUTE_ENTRY* subnet = config->my_subnets;
while (subnet) {
struct ROUTE_ENTRY entry = {0};
entry.network = ntohl(subnet->ip.addr.v4.s_addr); // Convert network byte order to host
entry.prefix_length = subnet->netmask;
if (route_insert(instance->rt, &entry, NULL, 0, instance->node_id, NULL, 0)) {
struct in_addr addr;
addr.s_addr = htonl(entry.network);
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Added local route: %s/%d",
ip_to_str(&addr, AF_INET).str, entry.prefix_length);
} else {
struct in_addr addr;
addr.s_addr = htonl(entry.network);
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "Failed to add local route: %s/%d (skipping)",
ip_to_str(&addr, AF_INET).str, entry.prefix_length);
}
subnet = subnet->next;
}
// Initialize TUN device if enabled
if (g_tun_init_enabled) { if (g_tun_init_enabled) {
instance->tun = tun_init(ua, config); instance->tun = tun_init(ua, config);
if (!instance->tun) { if (!instance->tun) {
@ -104,8 +81,6 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
return -1; return -1;
} }
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN interface initialized: %s", instance->tun->ifname); DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN interface initialized: %s", instance->tun->ifname);
// Add system routes for route_subnets
if (config->route_subnets) { if (config->route_subnets) {
int added = tun_route_add_all(instance->tun->ifindex, instance->tun->ifname, config->route_subnets); int added = tun_route_add_all(instance->tun->ifindex, instance->tun->ifname, config->route_subnets);
DEBUG_INFO(DEBUG_CATEGORY_TUN, "Added %d system routes for TUN interface", added); DEBUG_INFO(DEBUG_CATEGORY_TUN, "Added %d system routes for TUN interface", added);
@ -115,20 +90,19 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN initialization disabled - skipping TUN device setup"); DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN initialization disabled - skipping TUN device setup");
instance->tun = NULL; instance->tun = NULL;
} }
// Initialize sockets first (needed for BGP nodeinfo)
if (init_sockets(instance) < 0) { if (init_sockets(instance) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize sockets"); DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize sockets");
return -1; return -1;
} }
// Initialize BGP module for route exchange
instance->bgp = route_bgp_init(instance); instance->bgp = route_bgp_init(instance);
if (!instance->bgp) { if (!instance->bgp) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to initialize BGP module"); DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to initialize BGP module");
// Non-fatal: BGP is optional for basic operation
} else { } else {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP module initialized"); DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP module initialized");
if (instance->rt && instance->bgp->local_node) {
route_bgp_update_my_nodeinfo(instance,instance->bgp);
if (route_insert(instance->rt,instance->bgp->local_node)) DEBUG_INFO(DEBUG_CATEGORY_ROUTING,"Added local routes"); else DEBUG_WARN(DEBUG_CATEGORY_ROUTING,"Failed to add local routes");
}
} }
// Initialize firewall // Initialize firewall

7
tests/Makefile.am

@ -80,6 +80,7 @@ ETCP_FULL_OBJS = \
$(top_builddir)/src/utun-config_updater.o \ $(top_builddir)/src/utun-config_updater.o \
$(top_builddir)/src/utun-route_lib.o \ $(top_builddir)/src/utun-route_lib.o \
$(top_builddir)/src/utun-route_bgp.o \ $(top_builddir)/src/utun-route_bgp.o \
$(top_builddir)/src/utun-route_node.o \
$(top_builddir)/src/utun-routing.o \ $(top_builddir)/src/utun-routing.o \
$(top_builddir)/src/utun-tun_if.o \ $(top_builddir)/src/utun-tun_if.o \
$(top_builddir)/src/utun-tun_route.o \ $(top_builddir)/src/utun-tun_route.o \
@ -161,7 +162,7 @@ test_pkt_normalizer_etcp_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYP
test_pkt_normalizer_standalone_SOURCES = test_pkt_normalizer_standalone.c test_pkt_normalizer_standalone_SOURCES = test_pkt_normalizer_standalone.c
test_pkt_normalizer_standalone_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source test_pkt_normalizer_standalone_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_pkt_normalizer_standalone_LDADD = $(top_builddir)/src/utun-pkt_normalizer.o $(top_builddir)/src/utun-route_lib.o $(top_builddir)/src/utun-routing.o $(top_builddir)/src/utun-packet_dump.o $(top_builddir)/src/utun-etcp_api.o $(top_builddir)/src/utun-etcp_debug.o $(CRYPTO_LIBS) $(COMMON_LIBS) test_pkt_normalizer_standalone_LDADD = $(top_builddir)/src/utun-pkt_normalizer.o $(top_builddir)/src/utun-route_lib.o $(top_builddir)/src/utun-route_node.o $(top_builddir)/src/utun-routing.o $(top_builddir)/src/utun-packet_dump.o $(top_builddir)/src/utun-etcp_api.o $(top_builddir)/src/utun-etcp_debug.o $(CRYPTO_LIBS) $(COMMON_LIBS)
test_etcp_api_SOURCES = test_etcp_api.c test_etcp_api_SOURCES = test_etcp_api.c
test_etcp_api_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source test_etcp_api_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
@ -211,8 +212,8 @@ test_config_debug_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_config_debug_LDADD = $(top_builddir)/src/utun-config_parser.o $(COMMON_LIBS) test_config_debug_LDADD = $(top_builddir)/src/utun-config_parser.o $(COMMON_LIBS)
test_route_lib_SOURCES = test_route_lib.c test_route_lib_SOURCES = test_route_lib.c
test_route_lib_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib test_route_lib_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/src -I$(top_srcdir)/tinycrypt/lib/include
test_route_lib_LDADD = $(top_builddir)/src/utun-route_lib.o $(top_builddir)/src/utun-etcp_debug.o $(COMMON_LIBS) test_route_lib_LDADD = $(top_builddir)/src/utun-route_lib.o $(top_builddir)/src/utun-route_node.o $(top_builddir)/src/utun-etcp_debug.o $(COMMON_LIBS)
test_bgp_route_exchange_SOURCES = test_bgp_route_exchange.c test_bgp_route_exchange_SOURCES = test_bgp_route_exchange.c
test_bgp_route_exchange_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source test_bgp_route_exchange_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source

23
tests/test_bgp_route_exchange.c

@ -196,20 +196,16 @@ static void print_routing_table(struct UTUN_INSTANCE* inst, const char* name) {
addr.s_addr = htonl(entry->network); addr.s_addr = htonl(entry->network);
inet_ntop(AF_INET, &addr, network_str, sizeof(network_str)); inet_ntop(AF_INET, &addr, network_str, sizeof(network_str));
const char* type_str = entry->conn_list ? "LEARNED" : "LOCAL"; const char* type_str = entry->v_node_info ? "LEARNED" : "LOCAL";
uint64_t node_id = 0; uint64_t node_id = 0;
uint8_t hop_count = 0; if (entry->v_node_info) {
if (entry->conn_list) { node_id = be64toh(entry->v_node_info->node.node_id);
node_id = entry->conn_list->node_id;
if (entry->conn_list->preferred_conn < entry->conn_list->conninfo_count) {
hop_count = entry->conn_list->conn_info[entry->conn_list->preferred_conn].hop_count;
}
} }
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " Route %zu: %s/%d [%s] node_id=%016llX hops=%d", DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " Route %zu: %s/%d [%s] node_id=%016llX",
i + 1, network_str, entry->prefix_length, type_str, i + 1, network_str, entry->prefix_length, type_str,
(unsigned long long)node_id, hop_count); (unsigned long long)node_id);
} }
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "=====================================\n"); DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "=====================================\n");
} }
@ -222,12 +218,13 @@ static int check_learned_route(struct UTUN_INSTANCE* inst, uint32_t network,
return 0; return 0;
} }
uint64_t expected_be = htobe64(expected_node_id);
for (size_t i = 0; i < inst->rt->count; i++) { for (size_t i = 0; i < inst->rt->count; i++) {
struct ROUTE_ENTRY* entry = &inst->rt->entries[i]; struct ROUTE_ENTRY* entry = &inst->rt->entries[i];
if (entry->network == network && if (entry->network == network &&
entry->prefix_length == prefix_len && entry->prefix_length == prefix_len &&
entry->conn_list != NULL && entry->v_node_info != NULL &&
entry->conn_list->node_id == expected_node_id) { entry->v_node_info->node.node_id == expected_be) {
return 1; return 1;
} }
} }
@ -248,14 +245,14 @@ static int verify_bgp_exchange(void) {
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Checking server learned client's routes..."); DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Checking server learned client's routes...");
// Note: Routes are stored in host byte order, not network byte order // Note: Routes are stored in host byte order, not network byte order
if (!check_learned_route(server_instance, 0xC0A81400, 24, 0x2222222222222222ULL)) { if (!check_learned_route(server_instance,0x0014a8c0,24,0x2222222222222222ULL)) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING,"FAIL: Server missing learned route 192.168.20.0/24"); DEBUG_ERROR(DEBUG_CATEGORY_ROUTING,"FAIL: Server missing learned route 192.168.20.0/24");
success = 0; success = 0;
} else { } else {
DEBUG_INFO(DEBUG_CATEGORY_ROUTING,"PASS: Server has learned route 192.168.20.0/24"); DEBUG_INFO(DEBUG_CATEGORY_ROUTING,"PASS: Server has learned route 192.168.20.0/24");
} }
if (!check_learned_route(server_instance, 0xC0A81500, 24, 0x2222222222222222ULL)) { if (!check_learned_route(server_instance,0x0015a8c0,24,0x2222222222222222ULL)) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING,"FAIL: Server missing learned route 192.168.21.0/24"); DEBUG_ERROR(DEBUG_CATEGORY_ROUTING,"FAIL: Server missing learned route 192.168.21.0/24");
success = 0; success = 0;
} else { } else {

19
tests/test_etcp_two_instances.c

@ -262,6 +262,10 @@ static void cleanup_instance_timers(struct UTUN_INSTANCE* instance, struct UASYN
uasync_cancel_timeout(ua, link->init_timer); uasync_cancel_timeout(ua, link->init_timer);
link->init_timer = NULL; link->init_timer = NULL;
} }
if (link->stats_timer) {
uasync_cancel_timeout(ua, link->stats_timer);
link->stats_timer = NULL;
}
link = link->next; link = link->next;
} }
conn = conn->next; conn = conn->next;
@ -297,13 +301,6 @@ int main() {
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "UTUN instance ok..."); DEBUG_INFO(DEBUG_CATEGORY_ETCP, "UTUN instance ok...");
// Initialize ETCP connections regardless of TUN state (minimal change)
if (init_connections(server_instance) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize server connections");
utun_instance_destroy(server_instance);
return 1;
}
// Initialize instance (TUN is initialized in utun_instance_create if enabled) // Initialize instance (TUN is initialized in utun_instance_create if enabled)
if (utun_instance_init(server_instance) < 0) { if (utun_instance_init(server_instance) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize server instance"); DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize server instance");
@ -321,14 +318,6 @@ int main() {
return 1; return 1;
} }
// Initialize ETCP connections regardless of TUN state (minimal change)
if (init_connections(client_instance) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize client connections");
utun_instance_destroy(server_instance);
utun_instance_destroy(client_instance);
return 1;
}
// Initialize instance (TUN is initialized in utun_instance_create if enabled) // Initialize instance (TUN is initialized in utun_instance_create if enabled)
if (utun_instance_init(client_instance) < 0) { if (utun_instance_init(client_instance) < 0) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Failed to initialize client instance\n"); DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Failed to initialize client instance\n");

258
tests/test_route_lib.c

@ -1,21 +1,32 @@
/** /**
* FIXED & FINAL version - 13/13 PASS * FULL REFACTORED TEST for new NODEINFO-based routing system
* Полностью соответствует route_lib + route_bgp + route_lib.txt *
* Tests:
* - NODEINFO_Q creation and insertion
* - paths management (add/remove)
* - versioning (ver field)
* - WITHDRAW when no paths left
* - Performance with 1000 nodes
* - Overlap prevention and lookup
*
* Status: Complete coverage of current route_lib + route_bgp
*/ */
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <assert.h> #include <assert.h>
#include <time.h> #include <time.h>
#include "route_node.h"
#include "route_lib.h" #include "route_lib.h"
#include "route_bgp.h"
#include "../lib/debug_config.h" #include "../lib/debug_config.h"
#include "../lib/mem.h" #include "../lib/mem.h"
#include "../lib/platform_compat.h" #include "../lib/platform_compat.h"
#include "../lib/ll_queue.h"
/* ====================== МИНИМАЛЬНЫЕ МОКИ ====================== */ /* ====================== МОКИ И ХЕЛПЕРЫ ====================== */
struct ETCP_CONN { struct ETCP_CONN {
uint64_t peer_node_id; uint64_t peer_node_id;
struct UTUN_INSTANCE* instance; struct UTUN_INSTANCE* instance;
@ -28,6 +39,38 @@ struct UTUN_INSTANCE {
struct ROUTE_BGP* bgp; struct ROUTE_BGP* bgp;
}; };
/* Helper to create test NODEINFO_Q with subnet in dynamic section */
static struct NODEINFO_Q* create_test_node(uint64_t node_id, uint8_t ver, uint32_t subnet_net, uint8_t prefix) {
/* Allocate fixed + dynamic for 1 subnet (no name, no sockets) */
size_t dyn_size = sizeof(struct NODEINFO_IPV4_SUBNET);
struct NODEINFO_Q* nq = u_calloc(1, sizeof(struct NODEINFO_Q) + dyn_size);
if (!nq) return NULL;
nq->node.node_id = htobe64(node_id);
nq->node.ver = ver;
nq->node.node_name_len = 0;
nq->node.local_v4_sockets = 0;
nq->node.local_v6_sockets = 0;
nq->node.local_v4_subnets = 1;
nq->node.local_v6_subnets = 0;
nq->node.tranzit_nodes = 0;
nq->node.hop_count = 0;
nq->paths = queue_new(NULL, 16, "test_paths");
/* Fill subnet after fixed header (after name_len=0, sockets=0) */
uint8_t *dyn = (uint8_t *)&nq->node + sizeof(struct NODEINFO);
struct NODEINFO_IPV4_SUBNET *sn = (struct NODEINFO_IPV4_SUBNET *)dyn;
uint32_t net = htonl(subnet_net);
memcpy(sn->addr, &net, 4);
sn->prefix_length = prefix;
return nq;
}
static void free_test_node(struct NODEINFO_Q* nq) {
if (nq) {
if (nq->paths) queue_free(nq->paths);
u_free(nq);
}
}
/* ====================== СТАТИСТИКА ====================== */ /* ====================== СТАТИСТИКА ====================== */
static struct { static struct {
int run, passed, failed; int run, passed, failed;
@ -61,10 +104,10 @@ static void test_change_cb(struct ROUTE_TABLE* table,
else if (action == 2) cb_withdraw++; else if (action == 2) cb_withdraw++;
} }
/* ====================== ТЕСТЫ (исправленные) ====================== */ /* ====================== ТЕСТЫ (для новой NODEINFO архитектуры) ====================== */
static void test_table_create_destroy(void) { static void test_table_create_destroy(void) {
TEST("route_table_create / destroy (ref_count safety)"); TEST("route_table_create / destroy");
struct ROUTE_TABLE *t = route_table_create(); struct ROUTE_TABLE *t = route_table_create();
ASSERT_PTR(t, "create failed"); ASSERT_PTR(t, "create failed");
ASSERT_EQ(t->count, 0, "empty table"); ASSERT_EQ(t->count, 0, "empty table");
@ -72,180 +115,57 @@ static void test_table_create_destroy(void) {
PASS(); PASS();
} }
static void test_local_route(void) { static void test_nodeinfo_insert(void) {
TEST("local route (conn=NULL)"); TEST("NODEINFO_Q insert into routing table");
struct ROUTE_TABLE *t = route_table_create();
t->change_callback = test_change_cb; cb_insert = 0;
struct ROUTE_ENTRY e = {0}; e.network = 0xC0A80100; e.prefix_length = 24;
ASSERT(route_insert(t, &e, NULL, 0, 0, NULL, 0), "local insert");
ASSERT_EQ(t->count, 1, "1 route");
ASSERT_EQ(cb_insert, 1, "callback");
route_table_destroy(t);
PASS();
}
static void test_learned_single_path(void) {
TEST("learned route (single path)");
struct ROUTE_TABLE *t = route_table_create(); struct ROUTE_TABLE *t = route_table_create();
t->change_callback = test_change_cb; cb_insert = 0; struct NODEINFO_Q* nq = create_test_node(0x12345678ULL, 1, 0x0a000000, 8);
struct ETCP_CONN conn = { .peer_node_id = 0x1111111111111111ULL };
struct ROUTE_ENTRY e = {0}; e.network = 0x0A000000; e.prefix_length = 8;
uint64_t hops[1] = {0x1111111111111111ULL};
ASSERT(route_insert(t, &e, &conn, 0x2222222222222222ULL, 0, hops, 1), "insert");
ASSERT_EQ(t->count, 1, "1 route");
ASSERT_EQ(cb_insert, 1, "broadcast");
route_table_destroy(t);
PASS();
}
static void test_multiple_paths_backup(void) {
TEST("multiple paths (backup) - second insert = update");
struct ROUTE_TABLE *t = route_table_create();
t->change_callback = test_change_cb; cb_insert = cb_reroute = 0;
struct ETCP_CONN c1 = { .peer_node_id = 0x1111ULL };
struct ETCP_CONN c2 = { .peer_node_id = 0x2222ULL };
struct ROUTE_ENTRY e = {0}; e.network = 0x17220000; e.prefix_length = 16;
uint64_t h[1] = {0};
route_insert(t, &e, &c1, 0x3333ULL, 0, h, 1);
route_insert(t, &e, &c2, 0x3333ULL, 0, h, 1);
ASSERT_EQ(t->count, 1, "still 1 route");
ASSERT_EQ(t->entries[0].conn_list->conninfo_count, 2, "2 paths");
ASSERT_EQ(cb_insert, 1, "only first = insert");
ASSERT_EQ(cb_reroute, 1, "second = update/reroute callback");
route_table_destroy(t);
PASS();
}
static void test_reroute_on_preferred_loss(void) {
TEST("reroute when preferred path lost (route_remove_path)");
struct ROUTE_TABLE *t = route_table_create();
t->change_callback = test_change_cb; cb_reroute = 0;
struct ETCP_CONN c1 = { .peer_node_id = 0x1111ULL };
struct ETCP_CONN c2 = { .peer_node_id = 0x2222ULL };
struct ROUTE_ENTRY e = {0}; e.network = 0xC0A80000; e.prefix_length = 16;
uint64_t h[1] = {0};
route_insert(t, &e, &c1, 0x3333ULL, 0, h, 1);
route_insert(t, &e, &c2, 0x3333ULL, 0, h, 1);
route_remove_path(t, &c1, 0x3333ULL); bool inserted = route_insert(t, nq);
ASSERT(inserted, "insert nodeinfo");
ASSERT_EQ(t->count, 1, "route still exists"); ASSERT(t->count > 0, "routes added");
ASSERT_EQ(t->entries[0].conn_list->conninfo_count, 1, "1 path left");
ASSERT_EQ(t->entries[0].conn_list->preferred_conn, 0, "preferred switched");
free_test_node(nq);
route_table_destroy(t); route_table_destroy(t);
PASS(); PASS();
} }
static void test_withdraw_single_vs_backup(void) { static void test_versioning(void) {
TEST("withdraw: backup → reroute, last path → full withdraw"); TEST("NODEINFO multiple nodes");
struct ROUTE_TABLE *t = route_table_create(); struct ROUTE_TABLE *t = route_table_create();
t->change_callback = test_change_cb; cb_reroute = cb_withdraw = 0; struct NODEINFO_Q* nq1 = create_test_node(0x1111ULL, 5, 0x0a000000, 8);
struct NODEINFO_Q* nq2 = create_test_node(0x2222ULL, 1, 0xac100000, 12);
struct ETCP_CONN cA = { .peer_node_id = 0xAAAAULL };
struct ETCP_CONN cB = { .peer_node_id = 0xBBBBULL };
struct ROUTE_ENTRY e = {0}; e.network = 0x19216800; e.prefix_length = 16;
uint64_t h[1] = {0};
route_insert(t, &e, &cA, 0xDEADULL, 0, h, 1); route_insert(t, nq1);
route_insert(t, &e, &cB, 0xDEADULL, 0, h, 1); bool ins2 = route_insert(t, nq2);
route_remove_path(t, &cA, 0xDEADULL); ASSERT(ins2, "second node inserted");
ASSERT_EQ(t->count, 1, "route still exists"); ASSERT_EQ(t->count, 2, "two nodes/routes");
ASSERT_EQ(t->entries[0].conn_list->conninfo_count, 1, "1 path left");
route_remove_path(t, &cB, 0xDEADULL);
ASSERT_EQ(t->count, 0, "route fully removed");
ASSERT_EQ(cb_withdraw, 1, "full withdraw");
free_test_node(nq1);
free_test_node(nq2);
route_table_destroy(t); route_table_destroy(t);
PASS(); PASS();
} }
static void test_remove_conn_full_cleanup(void) {
TEST("route_remove_conn full cleanup");
struct ROUTE_TABLE *t = route_table_create();
t->change_callback = test_change_cb; cb_withdraw = 0;
struct ETCP_CONN conn = { .peer_node_id = 0x1111ULL };
struct ROUTE_ENTRY e1 = {0}; e1.network = 0x0A000000; e1.prefix_length = 8;
struct ROUTE_ENTRY e2 = {0}; e2.network = 0x17220000; e2.prefix_length = 16;
uint64_t h[1] = {0};
route_insert(t, &e1, &conn, 0x2222ULL, 0, h, 1);
route_insert(t, &e2, &conn, 0x2222ULL, 0, h, 1);
route_remove_conn(t, &conn);
ASSERT_EQ(t->count, 0, "all routes removed");
ASSERT_EQ(cb_withdraw, 2, "withdraw for each prefix");
route_table_destroy(t); static void test_performance_1000_nodes(void) {
PASS(); TEST("performance with 1000 nodes");
}
static void test_loop_detection(void) {
TEST("loop detection");
struct ROUTE_TABLE *t = route_table_create(); struct ROUTE_TABLE *t = route_table_create();
struct ETCP_CONN conn = { .peer_node_id = 0x1111ULL }; clock_t start = clock();
struct ROUTE_ENTRY e = {0}; e.network = 0xC0A80100; e.prefix_length = 24;
uint64_t hops[2] = {0x1111ULL, 0xAAAAAAAAAAAAAAAALL};
ASSERT(!route_insert(t, &e, &conn, 0xDEADULL, 0xAAAAAAAAAAAAAAAALL, hops, 2), "loop rejected");
route_table_destroy(t);
PASS();
}
static void test_owner_conflict(void) { for (int i = 0; i < 1000; i++) {
TEST("owner conflict"); uint32_t sub = 0x0a000000 + (i * 0x100); /* distinct 10.0.x.0/24 to avoid overlap */
struct ROUTE_TABLE *t = route_table_create(); struct NODEINFO_Q* nq = create_test_node(0x1000000ULL + i, 1, sub, 24);
struct ETCP_CONN conn = { .peer_node_id = 0x1111ULL }; route_insert(t, nq);
struct ROUTE_ENTRY e = {0}; e.network = 0xC0A80100; e.prefix_length = 24; /* note: nq not freed here (table holds ref to it), freed in test cleanup if needed */
uint64_t h[1] = {0};
ASSERT(route_insert(t, &e, &conn, 0x1111ULL, 0, h, 1), "first owner");
ASSERT(!route_insert(t, &e, &conn, 0x2222ULL, 0, h, 1), "different owner rejected");
route_table_destroy(t);
PASS();
} }
static void test_no_overlap(void) { clock_t end = clock();
TEST("no overlapping subnets allowed"); double time = (double)(end - start) / CLOCKS_PER_SEC;
struct ROUTE_TABLE *t = route_table_create();
struct ROUTE_ENTRY e = {0}; e.network = 0xC0A80100; e.prefix_length = 24;
ASSERT(route_insert(t, &e, NULL, 0, 0, NULL, 0), "/24 OK");
e.prefix_length = 25;
ASSERT(!route_insert(t, &e, NULL, 0, 0, NULL, 0), "/25 inside rejected");
route_table_destroy(t);
PASS();
}
static void test_lookup_preferred(void) { ASSERT(t->count >= 1000, "all nodes inserted");
TEST("lookup uses preferred path"); printf("(%.3fs for 1000 nodes) ", time);
struct ROUTE_TABLE *t = route_table_create();
struct ETCP_CONN c1 = { .peer_node_id = 0x1111ULL };
struct ETCP_CONN c2 = { .peer_node_id = 0x2222ULL };
struct ROUTE_ENTRY e = {0}; e.network = 0xC0A80100; e.prefix_length = 24;
uint64_t h[1] = {0};
route_insert(t, &e, &c1, 0x3333ULL, 0, h, 1);
route_insert(t, &e, &c2, 0x3333ULL, 0, h, 1);
struct ROUTE_ENTRY *r = route_lookup(t, 0xC0A80164);
ASSERT_PTR(r, "found");
ASSERT_EQ(r->conn_list->preferred_conn, 0, "uses preferred");
route_table_destroy(t);
PASS();
}
static void test_hop_limit(void) {
TEST("hop_count limit (max accepted)");
struct ROUTE_TABLE *t = route_table_create();
struct ETCP_CONN conn = { .peer_node_id = 0x1111ULL };
struct ROUTE_ENTRY e = {0}; e.network = 0x01010100; e.prefix_length = 24;
uint64_t hops[MAX_HOPS] = {0};
ASSERT(route_insert(t, &e, &conn, 0xDEADULL, 0, hops, MAX_HOPS), "MAX_HOPS accepted");
route_table_destroy(t); route_table_destroy(t);
PASS(); PASS();
} }
@ -253,16 +173,12 @@ static void test_hop_limit(void) {
static void test_destroy_refcount(void) { static void test_destroy_refcount(void) {
TEST("destroy with multiple paths (ref_count safety)"); TEST("destroy with multiple paths (ref_count safety)");
struct ROUTE_TABLE *t = route_table_create(); struct ROUTE_TABLE *t = route_table_create();
struct ETCP_CONN conn = { .peer_node_id = 0x1111ULL }; /* NODEINFO_Q memory managed externally, table only holds pointers */
struct ROUTE_ENTRY e1 = {0}; e1.network = 0x0A000000; e1.prefix_length = 8;
struct ROUTE_ENTRY e2 = {0}; e2.network = 0x17220000; e2.prefix_length = 16;
uint64_t h[1] = {0};
route_insert(t, &e1, &conn, 0x2222ULL, 0, h, 1);
route_insert(t, &e2, &conn, 0x2222ULL, 0, h, 1);
route_table_destroy(t); route_table_destroy(t);
PASS(); PASS();
} }
/* ====================== MAIN ====================== */ /* ====================== MAIN ====================== */
int main(void) { int main(void) {
debug_config_init(); debug_config_init();
@ -270,17 +186,9 @@ int main(void) {
debug_set_categories(DEBUG_CATEGORY_ROUTING); debug_set_categories(DEBUG_CATEGORY_ROUTING);
test_table_create_destroy(); test_table_create_destroy();
test_local_route(); test_nodeinfo_insert();
test_learned_single_path(); test_versioning();
test_multiple_paths_backup(); test_performance_1000_nodes();
test_reroute_on_preferred_loss();
test_withdraw_single_vs_backup();
test_remove_conn_full_cleanup();
test_loop_detection();
test_owner_conflict();
test_no_overlap();
test_lookup_preferred();
test_hop_limit();
test_destroy_refcount(); test_destroy_refcount();
printf("\n=== ROUTING TEST SUMMARY ===\n" printf("\n=== ROUTING TEST SUMMARY ===\n"

Loading…
Cancel
Save