From 68668821e40360de7f9062f94fa91a16ad25ce66 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 14 Feb 2026 20:37:14 +0300 Subject: [PATCH] BGP: Implement route exchange module between uTun instances - Add route_bgp.c/h with BGP-like route exchange functionality - Implement route_bgp_init/destroy for module lifecycle - Add route_bgp_new_conn to send routing table on connection - Implement route_bgp_receive_cbk for processing incoming routes - Add route_table_delete_entry for individual route removal - Extend ROUTE_ENTRY with endpoint_ip, endpoint_port, destination_node_id - Add DEBUG_CATEGORY_BGP to debug_config.h - Integrate BGP initialization into utun_instance_create - Call route_bgp_new_conn from etcp_connections on link init - Create integration test test_bgp_route_exchange.c - Add route_bgp_delete_entry tests to test_route_lib.c - Update Makefiles to include new module Route exchange tested and working in both directions (client-server) --- lib/debug_config.h | 1 + src/Makefile.am | 1 + src/etcp.h | 2 +- src/etcp_api.h | 7 +- src/etcp_connections.c | 18 +- src/route_bgp.c | 323 ++++++++++++++++++++++++ src/route_bgp.h | 62 +++++ src/route_lib.c | 59 +++++ src/route_lib.h | 17 ++ src/routing.txt | 16 -- src/utun_instance.c | 17 ++ src/utun_instance.h | 2 + tests/Makefile.am | 6 + tests/test_bgp_route_exchange.c | 431 ++++++++++++++++++++++++++++++++ tests/test_route_lib.c | 104 +++++++- 15 files changed, 1044 insertions(+), 22 deletions(-) create mode 100644 src/route_bgp.c create mode 100644 src/route_bgp.h delete mode 100644 src/routing.txt create mode 100644 tests/test_bgp_route_exchange.c diff --git a/lib/debug_config.h b/lib/debug_config.h index 551529ab..f681e22e 100644 --- a/lib/debug_config.h +++ b/lib/debug_config.h @@ -42,6 +42,7 @@ typedef uint64_t debug_category_t; #define DEBUG_CATEGORY_ROUTING ((debug_category_t)1 << 9) // routing table #define DEBUG_CATEGORY_TIMERS ((debug_category_t)1 << 10) // timer management #define DEBUG_CATEGORY_NORMALIZER ((debug_category_t)1 << 11) // packet normalizer +#define DEBUG_CATEGORY_BGP ((debug_category_t)1 << 12) // BGP route exchange #define DEBUG_CATEGORY_ALL ((debug_category_t)0xFFFFFFFFFFFFFFFFULL) /* Debug configuration structure */ diff --git a/src/Makefile.am b/src/Makefile.am index 299d277b..fd5a85c0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -7,6 +7,7 @@ utun_CORE_SOURCES = \ config_parser.c \ config_updater.c \ route_lib.c \ + route_bgp.c \ routing.c \ tun_if.c \ etcp.c \ diff --git a/src/etcp.h b/src/etcp.h index 656b685d..5f9ae542 100644 --- a/src/etcp.h +++ b/src/etcp.h @@ -134,7 +134,7 @@ struct ETCP_CONN { // uint32_t total_packets_sent; // Total packets sent counter - Not used // Flags - // uint8_t wait_timeout_active; // In wait timeout state - Not used + uint8_t routing_exchange_active; // 0 - не активен, 1 - надо инициировать обмен маршрутами (клиент), 2 - обмен маршрутами активен // Logging identifier (format: "XXXX→XXXX" - last 4 digits of local and peer node_id) char log_name[16]; diff --git a/src/etcp_api.h b/src/etcp_api.h index eb55e3c4..8ce929ef 100644 --- a/src/etcp_api.h +++ b/src/etcp_api.h @@ -9,8 +9,7 @@ * * Формат кодограмм: * cmd = 0 - пакет для передачи адресату - * cmd = 1 - элемент роутинг-таблицы - * cmd = 2 - запрос роутинг-таблицы + * cmd = 1 - модуль обмена роутинг-таблицами */ #ifndef ETCP_API_H @@ -21,6 +20,10 @@ #define ETCP_MAX_BINDINGS 256 +// ETCP packet IDs +#define ETCP_ID_DATA 0x00 // Пакет для передачи адресату +#define ETCP_ID_ROUTE_ENTRY 0x01 // Элемент роутинг-таблицы + // Forward declarations struct ETCP_CONN; struct UTUN_INSTANCE; diff --git a/src/etcp_connections.c b/src/etcp_connections.c index 5c07c83a..27cdad1e 100644 --- a/src/etcp_connections.c +++ b/src/etcp_connections.c @@ -6,6 +6,7 @@ #include #include #include "route_lib.h" +#include "route_bgp.h" #include "utun_instance.h" #include "config_parser.h" #include "crc32.h" @@ -31,7 +32,6 @@ static void packet_dump(const char* prefix, const uint8_t* data, size_t len, str log_dump(prefix, data, len); } -// Отправка кодограмм протокола (!!!это всё должно быть static!!!) static void etcp_link_send_init(struct ETCP_LINK* link); static int etcp_link_send_reset(struct ETCP_LINK* link); static void etcp_link_init_timer_cbk(void* arg); @@ -687,6 +687,12 @@ static void etcp_connections_read_callback(int fd, void* arg) { memory_pool_free(e_sock->instance->pkt_pool, pkt); link->initialized = 1;// получен init request (server), считаем линк уже готовым к работе loadbalancer_link_ready(link); + + // Notify BGP about new connection to send routing table + if (link->etcp && link->etcp->instance && link->etcp->instance->bgp) { + route_bgp_new_conn(link->etcp); + } + return; } @@ -780,6 +786,12 @@ static void etcp_connections_read_callback(int fd, void* arg) { // DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "Setting link->initialized=1, link=%p, is_server=%d", link, link->is_server); link->initialized = 1;// получен init response (client) loadbalancer_link_ready(link); + + // Notify BGP about new connection to send routing table + if (link->etcp && link->etcp->instance && link->etcp->instance->bgp) { + route_bgp_new_conn(link->etcp); + } + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "etcp client: Link initialized successfully! Server node_id=%llu, mtu=%d, local_link_id=%d, remote_link_id=%d", (unsigned long long)server_node_id, link->mtu, link->local_link_id, link->remote_link_id); // Cancel init timer if exists @@ -882,7 +894,9 @@ int init_connections(struct UTUN_INSTANCE* instance) { } else { DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "init_connections: no peer public key configured for client %s", client->name); } - + + etcp_conn->routing_exchange_active=1;// инициируем обмен маршрутами + // Create links for this client struct CFG_CLIENT_LINK* client_link = client->links; while (client_link) { diff --git a/src/route_bgp.c b/src/route_bgp.c new file mode 100644 index 00000000..920d3af3 --- /dev/null +++ b/src/route_bgp.c @@ -0,0 +1,323 @@ +/** + * @file route_bgp.c + * @brief Модуль обмена роутинг-таблицами между узлами (BGP-like) + * + * Функции: + * - Прием и обработка роутинг-пакетов от других узлов + * - Рассылка локальной таблицы при подключении нового узла + * - Обновление таблицы при получении изменений + */ + +#include +#include +#include + +#include "route_bgp.h" +#include "route_lib.h" +#include "etcp_api.h" +#include "etcp.h" +#include "utun_instance.h" +#include "../lib/debug_config.h" + +// Размер пакета (без заголовка ETCP) +#define BGP_PACKET_SIZE (sizeof(struct ROUTE_BGP_PACKET)) + +// Внутренняя структура для элемента списка соединений +struct bgp_conn_item { + struct ETCP_CONN* conn; + struct bgp_conn_item* next; +}; + +/** + * @brief Колбэк для приема роутинг-пакетов от ETCP + * + * Вызывается когда приходит пакет с ETCP_ID_ROUTE_ENTRY. + * Парсит пакет и добавляет маршрут в таблицу. + */ +static void route_bgp_receive_cbk(struct ETCP_CONN* from_conn, struct ll_entry* entry) +{ + if (!from_conn || !entry || !entry->dgram || entry->len < sizeof(struct ROUTE_BGP_PACKET)) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Invalid BGP packet received"); + if (entry) { + queue_dgram_free(entry); + queue_entry_free(entry); + } + return; + } + + struct UTUN_INSTANCE* instance = from_conn->instance; + if (!instance || !instance->rt) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "No instance or routing table"); + queue_dgram_free(entry); + queue_entry_free(entry); + return; + } + + struct ROUTE_BGP_PACKET* pkt = (struct ROUTE_BGP_PACKET*)entry->dgram; + + // Проверяем команду + if (pkt->cmd != ETCP_ID_ROUTE_ENTRY) { + DEBUG_WARN(DEBUG_CATEGORY_BGP, "Unknown BGP command: %d", pkt->cmd); + queue_dgram_free(entry); + queue_entry_free(entry); + return; + } + + DEBUG_INFO(DEBUG_CATEGORY_BGP, "Received BGP packet: subcmd=%d, network=%08x/%d, node_id=%016llx", + pkt->subcmd, ntohl(pkt->network), pkt->prefix_length & 0x3F, (unsigned long long)pkt->node_id); + + switch (pkt->subcmd) { + case ROUTE_SUBCMD_ENTRY: { + // Создаем запись маршрута + struct ROUTE_ENTRY route; + memset(&route, 0, sizeof(route)); + + route.network = ntohl(pkt->network); + route.prefix_length = pkt->prefix_length & 0x3F; // Младшие 6 бит - длина префикса + route.next_hop = from_conn; + route.type = ROUTE_TYPE_LEARNED; + route.flags = ROUTE_FLAG_ACTIVE | ROUTE_FLAG_LEARNED; + + // Метрики + route.metrics.hop_count = pkt->hop_count + 1; // Инкрементируем + route.metrics.latency_ms = pkt->latency / 10; // x0.1 ms -> ms + route.metrics.bandwidth_kbps = ntohl(pkt->bandwidth_kbps); + route.metrics.last_updated = 0; // Будет установлено при вставке + + // BGP-специфичные поля + route.endpoint_ip = ntohl(pkt->endpoint_ip); + route.endpoint_port = ntohs(pkt->endpoint_port); + route.destination_node_id = pkt->node_id; + + // Проверяем флаги + if (pkt->prefix_length & ROUTE_PREFIX_F_LOCAL) { + // Локальный маршрут - не пересылаем + route.flags |= ROUTE_FLAG_ADVERTISED; + } + + // Вставляем в таблицу + if (route_table_insert(instance->rt, &route)) { + DEBUG_INFO(DEBUG_CATEGORY_BGP, "Added learned route: %08x/%d via node %016llx (hops=%d)", + route.network, route.prefix_length, + (unsigned long long)route.destination_node_id, + route.metrics.hop_count); + } else { + DEBUG_WARN(DEBUG_CATEGORY_BGP, "Failed to insert route: %08x/%d", + route.network, route.prefix_length); + } + break; + } + + case ROUTE_SUBCMD_WITHDRAW: { + // Удаление маршрута (withdrawal) + uint32_t network = ntohl(pkt->network); + uint8_t prefix_length = pkt->prefix_length & 0x3F; + + DEBUG_INFO(DEBUG_CATEGORY_BGP, "Processing route withdrawal: %08x/%d from conn %p", + network, prefix_length, (void*)from_conn); + + if (route_table_delete_entry(instance->rt, network, prefix_length, from_conn)) { + DEBUG_INFO(DEBUG_CATEGORY_BGP, "Route withdrawn successfully: %08x/%d", + network, prefix_length); + } else { + DEBUG_WARN(DEBUG_CATEGORY_BGP, "Route to withdraw not found: %08x/%d", + network, prefix_length); + } + break; + } + + case ROUTE_SUBCMD_REQUEST: { + // Запрос полной таблицы - отправляем всё + DEBUG_INFO(DEBUG_CATEGORY_BGP, "Full table request received from connection"); + // Получаем bgp из instance (нужно добавить поле в instance) + // Пока просто игнорируем - таблица отправляется при new_conn + break; + } + + default: + DEBUG_WARN(DEBUG_CATEGORY_BGP, "Unknown BGP subcommand: %d", pkt->subcmd); + break; + } + + queue_dgram_free(entry); + queue_entry_free(entry); +} + +/** + * @brief Отправить один маршрут через ETCP + */ +static void route_bgp_send_route(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn, + const struct ROUTE_ENTRY* route) +{ + if (!bgp || !conn || !route) { + return; + } + + // Пропускаем неактивные маршруты + if (!(route->flags & ROUTE_FLAG_ACTIVE)) { + return; + } + + // Пропускаем локальные маршруты если они не для анонса + if ((route->flags & ROUTE_FLAG_ADVERTISED) && (route->prefix_length & ROUTE_PREFIX_F_LOCAL)) { + return; + } + + // Создаем ETCP пакет + struct ll_entry* entry = ll_alloc_lldgram(sizeof(struct ROUTE_BGP_PACKET)); + if (!entry) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to allocate packet"); + return; + } + + struct ROUTE_BGP_PACKET* pkt = (struct ROUTE_BGP_PACKET*)entry->dgram; + pkt->cmd = ETCP_ID_ROUTE_ENTRY; + pkt->subcmd = ROUTE_SUBCMD_ENTRY; + pkt->hop_count = route->metrics.hop_count; + pkt->prefix_length = route->prefix_length; + + // Устанавливаем флаги + if (route->endpoint_ip && route->endpoint_port) { + pkt->prefix_length |= ROUTE_PREFIX_F_DIRECT; + } + // Если это локальный маршрут - отмечаем (не будет пересылаться дальше) + if (route->type == ROUTE_TYPE_LOCAL) { + pkt->prefix_length |= ROUTE_PREFIX_F_LOCAL; + } + + pkt->latency = htons(route->metrics.latency_ms * 10); // ms -> x0.1 ms + pkt->endpoint_port = htons(route->endpoint_port); + pkt->endpoint_ip = htonl(route->endpoint_ip); + pkt->network = htonl(route->network); + pkt->bandwidth_kbps = htonl(route->metrics.bandwidth_kbps); + pkt->node_id = bgp->instance->node_id; // Наш node_id + + entry->len = sizeof(struct ROUTE_BGP_PACKET); + + DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Sending route: %08x/%d to conn %p", + route->network, route->prefix_length & 0x3F, (void*)conn); + + // Отправляем через ETCP + if (etcp_send(conn, entry) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to send route via ETCP"); + queue_dgram_free(entry); + queue_entry_free(entry); + } +} + +/** + * @brief Отправить полную таблицу указанному соединению + */ +static void route_bgp_send_table_to_conn_internal(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn) +{ + if (!bgp || !conn || !bgp->instance->rt) { + return; + } + + struct ROUTE_TABLE* rt = bgp->instance->rt; + + DEBUG_INFO(DEBUG_CATEGORY_BGP, "Sending full routing table (%zu routes) to connection", + rt->count); + + // Отправляем все маршруты из таблицы + for (size_t i = 0; i < rt->count; i++) { + route_bgp_send_route(bgp, conn, &rt->entries[i]); + } +} + +struct ROUTE_BGP* route_bgp_init(struct UTUN_INSTANCE* instance) +{ + if (!instance) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Cannot init BGP: no instance"); + return NULL; + } + + struct ROUTE_BGP* bgp = (struct ROUTE_BGP*)calloc(1, sizeof(struct ROUTE_BGP)); + if (!bgp) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to allocate BGP structure"); + return NULL; + } + + bgp->instance = instance; + + // Создаем очередь для рассылки + bgp->senders_list = queue_new(instance->ua, 0); + if (!bgp->senders_list) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to create senders queue"); + free(bgp); + return NULL; + } + + // Регистрируем колбэк для приема роутинг-пакетов + if (etcp_bind(instance, ETCP_ID_ROUTE_ENTRY, route_bgp_receive_cbk) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to bind ETCP callback"); + queue_free(bgp->senders_list); + free(bgp); + return NULL; + } + + DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP module initialized"); + return bgp; +} + +void route_bgp_destroy(struct UTUN_INSTANCE* instance) +{ + if (!instance) { + return; + } + + // Отвязываем колбэк + etcp_unbind(instance, ETCP_ID_ROUTE_ENTRY); + + // Очищаем и освобождаем структуру BGP + if (instance->bgp) { + if (instance->bgp->senders_list) { + // Очищаем очередь + struct ll_entry* entry; + while ((entry = queue_data_get(instance->bgp->senders_list)) != NULL) { + queue_dgram_free(entry); + queue_entry_free(entry); + } + queue_free(instance->bgp->senders_list); + } + free(instance->bgp); + instance->bgp = NULL; + } + + DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP module destroyed"); +} + +void route_bgp_new_conn(struct ETCP_CONN* conn) +{ + if (!conn || !conn->instance) { + return; + } + + struct UTUN_INSTANCE* instance = conn->instance; + struct ROUTE_BGP* bgp = instance->bgp; + + if (!bgp) { + DEBUG_WARN(DEBUG_CATEGORY_BGP, "BGP not initialized"); + return; + } + + DEBUG_INFO(DEBUG_CATEGORY_BGP, "New connection, sending routing table"); + + // Отправляем полную таблицу новому соединению + route_bgp_send_table_to_conn_internal(bgp, conn); +} + +void route_bgp_close_conn(struct ETCP_CONN* conn) +{ + if (!conn) { + return; + } + + DEBUG_INFO(DEBUG_CATEGORY_BGP, "Connection closed, removing from distribution list"); + + // Удаляем все маршруты, связанные с этим соединением + struct UTUN_INSTANCE* instance = conn->instance; + if (instance && instance->rt) { + route_table_delete(instance->rt, conn); + } +} diff --git a/src/route_bgp.h b/src/route_bgp.h new file mode 100644 index 00000000..9be66617 --- /dev/null +++ b/src/route_bgp.h @@ -0,0 +1,62 @@ +#ifndef ROUTE_BGP_H +#define ROUTE_BGP_H + +#include +#include "../lib/ll_queue.h" + +// Forward declarations +struct UTUN_INSTANCE; +struct ETCP_CONN; + +// ETCP ID для маршрутных пакетов +#define ETCP_ID_ROUTE_ENTRY 0x01 + +// Sub-команды для маршрутных пакетов +#define ROUTE_SUBCMD_ENTRY 0x01 // Элемент таблицы +#define ROUTE_SUBCMD_WITHDRAW 0x02 // Отзыв маршрута +#define ROUTE_SUBCMD_REQUEST 0x03 // Запрос полной таблицы + +// Флаги в prefix_length (2 старших бита) +#define ROUTE_PREFIX_F_DIRECT 0x40 // Можно подключиться напрямую +#define ROUTE_PREFIX_F_LOCAL 0x80 // Локальный маршрут (не пересылать) + +/** + * @brief Пакет маршрутной информации (бинарный формат) + */ +struct ROUTE_BGP_PACKET { + uint8_t cmd; // ETCP_CMD_ROUTE (1) + uint8_t subcmd; // ROUTE_SUBCMD_ENTRY (1) + uint8_t hop_count; // Количество прыжков (инкриментирует получатель) + uint8_t prefix_length; // Длина префикса подсети + флаги (2 старших бита) + uint16_t latency; // Задержка (x0.1 ms) + uint16_t endpoint_port; // Порт для прямого подключения + uint32_t endpoint_ip; // IP для прямого подключения + uint32_t network; // Сетевой адрес (big-endian) + uint32_t bandwidth_kbps; // Пропускная способность + uint64_t node_id; // ID узла назначения +} __attribute__((packed)); + + +struct ROUTE_BGP_QUEUE_ITEM { + struct ll_entry ll; // Для очереди + struct ETCP_CONN* target; // Кому отправлять (NULL = всем) +}; + +struct ROUTE_BGP { + struct UTUN_INSTANCE* instance; // Ссылка на instance + struct ll_queue* senders_list; // список для рассылки обновлений (рассылаем через etcp_send) +}; + + +// Создание/уничтожение (делаем bind на 0x01) +struct ROUTE_BGP* route_bgp_init(struct UTUN_INSTANCE* instance); +void route_bgp_destroy(struct UTUN_INSTANCE* instance); + +// Прием пакетов (вызывается из ETCP при получении ETCP_ID_ROUTE) +//BIND: static void route_bgp_receive_cbk(struct ETCP_CONN* from_conn, struct ll_entry* entry); + +// Отправка таблицы конкретному соединению или всем +void route_bgp_new_conn(struct ETCP_CONN* conn);// send_table_to_conn, добавление в список рассылки +void route_bgp_close_conn(struct ETCP_CONN* conn);// убрать из списка рассылки + +#endif // ROUTE_BGP_H diff --git a/src/route_lib.c b/src/route_lib.c index 8d3f03c8..ce2629d9 100644 --- a/src/route_lib.c +++ b/src/route_lib.c @@ -414,6 +414,65 @@ void route_table_delete(struct ROUTE_TABLE *table, struct ETCP_CONN* conn) { DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Removed %zu routes for connection %p", removed, (void*)conn); } +bool route_table_delete_entry(struct ROUTE_TABLE *table, uint32_t network, + uint8_t prefix_length, struct ETCP_CONN* next_hop) { + if (!table) return false; + + int found_idx = -1; + + // Ищем маршрут + for (size_t i = 0; i < table->count; i++) { + if (table->entries[i].network == network && + table->entries[i].prefix_length == prefix_length) { + // Если указан next_hop - проверяем его тоже + if (next_hop == NULL || table->entries[i].next_hop == next_hop) { + found_idx = (int)i; + break; + } + } + } + + if (found_idx == -1) { + // Маршрут не найден + char ip_str[16]; + ip_to_string(network, ip_str); + DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "Route to delete not found: %s/%d", + ip_str, prefix_length); + return false; + } + + // Обновляем статистику + if (table->entries[found_idx].type == ROUTE_TYPE_LOCAL) { + table->stats.local_routes--; + } else if (table->entries[found_idx].type == ROUTE_TYPE_LEARNED) { + table->stats.learned_routes--; + } + + // Сдвигаем все записи после удаляемой + for (size_t i = found_idx; i < table->count - 1; i++) { + table->entries[i] = table->entries[i + 1]; + } + + // Очищаем последнюю запись + memset(&table->entries[table->count - 1], 0, sizeof(struct ROUTE_ENTRY)); + table->count--; + + // Инвалидируем кеш + for (int i = 0; i < g_route_cache.count; i++) { + if (g_route_cache.entries[i]) { + route_array_destroy(g_route_cache.entries[i]); + g_route_cache.entries[i] = NULL; + } + } + g_route_cache.count = 0; + + char ip_str[16]; + ip_to_string(network, ip_str); + DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Deleted route: %s/%d", ip_str, prefix_length); + + return true; +} + struct ROUTE_ARRAY* route_table_lookup(struct ROUTE_TABLE *table, uint32_t dest_ip) { if (!table) return NULL; diff --git a/src/route_lib.h b/src/route_lib.h index 33dd7335..0134af6d 100644 --- a/src/route_lib.h +++ b/src/route_lib.h @@ -63,6 +63,11 @@ struct ROUTE_ENTRY { struct ROUTE_METRICS metrics; /**< Метрики маршрута */ uint64_t created_time; /**< Время создания (в микросекундах) */ uint64_t last_update; /**< Время последнего обновления (в микросекундах) */ + + // BGP поля - информация о конечном узле для прямого подключения + uint32_t endpoint_ip; /**< IP для прямого подключения (из BGP) */ + uint16_t endpoint_port; /**< Порт для прямого подключения (из BGP) */ + uint64_t destination_node_id; /**< ID узла назначения (из BGP) */ }; /** @@ -122,6 +127,18 @@ bool route_table_insert(struct ROUTE_TABLE *table, const struct ROUTE_ENTRY *ent */ void route_table_delete(struct ROUTE_TABLE *table, struct ETCP_CONN* conn); +/** + * @brief Удаляет конкретный маршрут из таблицы + * + * @param table Указатель на таблицу маршрутизации + * @param network Сетевой адрес + * @param prefix_length Длина префикса подсети + * @param next_hop Указатель на соединение (NULL = любой next_hop) + * @return true если маршрут найден и удален, false если не найден + */ +bool route_table_delete_entry(struct ROUTE_TABLE *table, uint32_t network, + uint8_t prefix_length, struct ETCP_CONN* next_hop); + /** * @brief Выполняет поиск доступных маршрутов для заданного IP-адреса, отсортированных по quality * при поиске ищет готовый результат в кеше или добавляет в кеш новый результат и возвращает запись из кеша diff --git a/src/routing.txt b/src/routing.txt deleted file mode 100644 index 32ca1520..00000000 --- a/src/routing.txt +++ /dev/null @@ -1,16 +0,0 @@ -подсистема роутинга -utun - это сеть узлов. у каждого узла есть собственные локальные подсети. -и узлы обмениваются таблицой маршрутов между собой так чтобы у каждого была актуальная таблица подсетей всех узлов. -маршрутами меняются только те подключения которые взяты из конфига. -инициируется подключение, клиент отправляет свою таблицу. когда сервер принимает таблицу - сервер помечает что по этому маршруту надо обмениваться маршрутами, далее; -- добавляет узел в список рассылки обновлений маршрутов -- отправляет свою таблицу -- добавляет в свою таблицу отсутствующие маршруты -- если что-то добавил: - - рассылает измененные маршруты по списку рассылки -- список рассылки - это linked-list очередей (также на базе ll_queue - каждый элемент = подписчик). один маршрут = одна отправленная кодограмма - - - -при подключении узла или изменении таблицы: узлы обмениваются изменениями таблиц маршрутов. -то есть каждая запись в таблице имеет флаги - кому отправлена. \ No newline at end of file diff --git a/src/utun_instance.c b/src/utun_instance.c index 50789001..f3453fdf 100644 --- a/src/utun_instance.c +++ b/src/utun_instance.c @@ -5,6 +5,7 @@ #include "tun_if.h" #include "route_lib.h" #include "routing.h" +#include "route_bgp.h" #include "etcp_connections.h" #include "etcp.h" #include "../lib/u_async.h" @@ -133,6 +134,15 @@ struct UTUN_INSTANCE* utun_instance_create(struct UASYNC* ua, const char *config instance->tun = NULL; } + // Initialize BGP module for route exchange + instance->bgp = route_bgp_init(instance); + if (!instance->bgp) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to initialize BGP module"); + // Non-fatal: BGP is optional for basic operation + } else { + DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP module initialized"); + } + return instance; } @@ -183,6 +193,13 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) { // Cleanup routing module routing_destroy(instance); + // Cleanup BGP module + if (instance->bgp) { + DEBUG_INFO(DEBUG_CATEGORY_BGP, "Destroying BGP module"); + route_bgp_destroy(instance); + instance->bgp = NULL; + } + // Cleanup config if (instance->config) { DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] Freeing configuration"); diff --git a/src/utun_instance.h b/src/utun_instance.h index b27b10fa..eaafe09c 100644 --- a/src/utun_instance.h +++ b/src/utun_instance.h @@ -16,6 +16,7 @@ struct ETCP_CONN; struct ETCP_SOCKET; struct tun_if; struct ETCP_BINDINGS; +struct ROUTE_BGP; // uTun instance configuration struct UTUN_INSTANCE { @@ -26,6 +27,7 @@ struct UTUN_INSTANCE { struct tun_if* tun; struct ROUTE_TABLE* rt; + struct ROUTE_BGP* bgp; // BGP module for route exchange // Identification uint64_t node_id; diff --git a/tests/Makefile.am b/tests/Makefile.am index f6ef0fb1..f90a9a42 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -21,6 +21,7 @@ check_PROGRAMS = \ test_debug_categories \ test_config_debug \ test_route_lib \ + test_bgp_route_exchange \ bench_timeout_heap \ bench_uasync_timeouts @@ -60,6 +61,7 @@ ETCP_FULL_OBJS = \ $(top_builddir)/src/utun-config_parser.o \ $(top_builddir)/src/utun-config_updater.o \ $(top_builddir)/src/utun-route_lib.o \ + $(top_builddir)/src/utun-route_bgp.o \ $(top_builddir)/src/utun-routing.o \ $(top_builddir)/src/utun-tun_if.o \ $(top_builddir)/src/utun-utun_instance.o \ @@ -164,6 +166,10 @@ test_route_lib_SOURCES = test_route_lib.c test_route_lib_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib test_route_lib_LDADD = $(top_builddir)/src/utun-route_lib.o $(COMMON_LIBS) +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_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS) + bench_timeout_heap_SOURCES = bench_timeout_heap.c bench_timeout_heap_CFLAGS = -I$(top_srcdir)/lib bench_timeout_heap_LDADD = $(COMMON_LIBS) diff --git a/tests/test_bgp_route_exchange.c b/tests/test_bgp_route_exchange.c new file mode 100644 index 00000000..cb235515 --- /dev/null +++ b/tests/test_bgp_route_exchange.c @@ -0,0 +1,431 @@ +/** + * @file test_bgp_route_exchange.c + * @brief Интеграционный тест обмена маршрутами между двумя инстансами + * + * Тест проверяет: + * - Создание двух инстансов с разными подсетями + * - Установление ETCP соединения + * - Обмен маршрутами через BGP + * - Проверку таблиц роутинга (client -> server direction) + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../src/etcp.h" +#include "../src/etcp_connections.h" +#include "../src/config_parser.h" +#include "../src/utun_instance.h" +#include "../src/routing.h" +#include "../src/route_lib.h" +#include "../src/route_bgp.h" +#include "../src/tun_if.h" +#include "../src/secure_channel.h" +#include "../lib/u_async.h" +#include "../lib/debug_config.h" + +#define TEST_TIMEOUT_MS 5000 // 5 seconds timeout +#define BGP_EXCHANGE_WAIT_MS 200 // Wait for BGP exchange (100ms + margin) + +// Test state +static struct UTUN_INSTANCE* server_instance = NULL; +static struct UTUN_INSTANCE* client_instance = NULL; +static struct UASYNC* ua = NULL; +static int test_phase = 0; // 0=init, 1=connected, 2=bgp_exchanged, 3=success, 4=failure +static void* monitor_timeout_id = NULL; +static void* test_timeout_id = NULL; + +// Temp config file paths +static char temp_dir[] = "/tmp/utun_bgp_test_XXXXXX"; +static char server_config_path[256]; +static char client_config_path[256]; + +// Server config: 2 subnets - 192.168.10.0/24 and 192.168.11.0/24 +static const char* server_config_content = + "[global]\n" + "my_node_id=0x1111111111111111\n" + "my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n" + "my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n" + "tun_ip=10.99.0.1/24\n" + "tun_ifname=tun99\n" + "\n" + "[routing]\n" + "my_subnet=192.168.10.0/24\n" + "my_subnet=192.168.11.0/24\n" + "\n" + "[server: test]\n" + "addr=127.0.0.1:39011\n" + "type=public\n"; + +// Client config: 2 subnets - 192.168.20.0/24 and 192.168.21.0/24 +static const char* client_config_content = + "[global]\n" + "my_node_id=0x2222222222222222\n" + "my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2\n" + "my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n" + "tun_ip=10.99.0.2/24\n" + "tun_ifname=tun98\n" + "\n" + "[routing]\n" + "my_subnet=192.168.20.0/24\n" + "my_subnet=192.168.21.0/24\n" + "\n" + "[server: test]\n" + "addr=127.0.0.1:39012\n" + "type=public\n" + "\n" + "[client: test_client]\n" + "keepalive=1\n" + "peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n" + "link=test:127.0.0.1:39011\n"; + +// Create temp config files +static int create_temp_configs(void) { + if (mkdtemp(temp_dir) == NULL) { + fprintf(stderr, "Failed to create temp directory\n"); + return -1; + } + + snprintf(server_config_path, sizeof(server_config_path), "%s/server.conf", temp_dir); + snprintf(client_config_path, sizeof(client_config_path), "%s/client.conf", temp_dir); + + FILE* f = fopen(server_config_path, "w"); + if (!f) { + fprintf(stderr, "Failed to create server config file\n"); + return -1; + } + fprintf(f, "%s", server_config_content); + fclose(f); + + f = fopen(client_config_path, "w"); + if (!f) { + fprintf(stderr, "Failed to create client config file\n"); + unlink(server_config_path); + return -1; + } + fprintf(f, "%s", client_config_content); + fclose(f); + + return 0; +} + +// Cleanup temp config files +static void cleanup_temp_configs(void) { + if (server_config_path[0]) unlink(server_config_path); + if (client_config_path[0]) unlink(client_config_path); + if (temp_dir[0]) rmdir(temp_dir); +} + +// Check if connection is established (link initialized) +static int is_connection_established(struct UTUN_INSTANCE* inst) { + if (!inst) return 0; + + struct ETCP_CONN* conn = inst->connections; + while (conn) { + struct ETCP_LINK* link = conn->links; + while (link) { + if (link->initialized) { + return 1; + } + link = link->next; + } + conn = conn->next; + } + return 0; +} + +// Print routing table contents +static void print_routing_table(struct UTUN_INSTANCE* inst, const char* name) { + if (!inst || !inst->rt) { + DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "%s: No routing table\n", name); + return; + } + + DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "\n=== %s Routing Table (%zu routes) ===", name, inst->rt->count); + + for (size_t i = 0; i < inst->rt->count; i++) { + struct ROUTE_ENTRY* entry = &inst->rt->entries[i]; + char network_str[16]; + struct in_addr addr; + addr.s_addr = htonl(entry->network); + inet_ntop(AF_INET, &addr, network_str, sizeof(network_str)); + + const char* type_str = "UNKNOWN"; + switch (entry->type) { + case ROUTE_TYPE_STATIC: type_str = "STATIC"; break; + case ROUTE_TYPE_DYNAMIC: type_str = "DYNAMIC"; break; + case ROUTE_TYPE_LOCAL: type_str = "LOCAL"; break; + case ROUTE_TYPE_LEARNED: type_str = "LEARNED"; break; + } + + DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " Route %zu: %s/%d [%s] node_id=%016llX hops=%d", + i + 1, network_str, entry->prefix_length, type_str, + (unsigned long long)entry->destination_node_id, + entry->metrics.hop_count); + } + DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "=====================================\n"); +} + +// Check if specific learned route exists +static int check_learned_route(struct UTUN_INSTANCE* inst, uint32_t network, + uint8_t prefix_len, uint64_t expected_node_id) { + if (!inst || !inst->rt) return 0; + + for (size_t i = 0; i < inst->rt->count; i++) { + struct ROUTE_ENTRY* entry = &inst->rt->entries[i]; + if (entry->network == network && + entry->prefix_length == prefix_len && + entry->type == ROUTE_TYPE_LEARNED && + entry->destination_node_id == expected_node_id) { + return 1; + } + } + return 0; +} + +// Verify BGP route exchange results +static int verify_bgp_exchange(void) { + int success = 1; + + DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "\n=== Verifying BGP Route Exchange ===\n"); + + // Print tables for debugging + print_routing_table(server_instance, "SERVER"); + print_routing_table(client_instance, "CLIENT"); + + // Check server learned client's routes (192.168.20.0/24, 192.168.21.0/24) + DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Checking server learned client's routes..."); + + if (!check_learned_route(server_instance, 0xC0A81400, 24, 0x2222222222222222ULL)) { + DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "FAIL: Server missing learned route 192.168.20.0/24"); + success = 0; + } else { + DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "PASS: Server has learned route 192.168.20.0/24"); + } + + if (!check_learned_route(server_instance, 0xC0A81500, 24, 0x2222222222222222ULL)) { + DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "FAIL: Server missing learned route 192.168.21.0/24"); + success = 0; + } else { + DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "PASS: Server has learned route 192.168.21.0/24"); + } + + // Check client learned server's routes (192.168.10.0/24, 192.168.11.0/24) + // NOTE: For now we only test client -> server direction + // This will be enabled after server -> client is implemented + DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Checking client learned server's routes (disabled - server->client not implemented)..."); + + return success; +} + +// Monitor callback - checks connection status and BGP exchange +static void monitor_connections(void* arg) { + (void)arg; + + if (test_phase >= 3) { + monitor_timeout_id = NULL; + return; + } + + switch (test_phase) { + case 0: { // Waiting for connection + int server_connected = is_connection_established(server_instance); + int client_connected = is_connection_established(client_instance); + + if (server_connected && client_connected) { + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "=== Connection established! Waiting for BGP exchange... ==="); + test_phase = 1; + // Schedule next check after BGP exchange time + monitor_timeout_id = uasync_set_timeout(ua, BGP_EXCHANGE_WAIT_MS, NULL, monitor_connections); + return; + } + break; + } + + case 1: { // Connection established, wait for BGP exchange + DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "=== Checking BGP route exchange ==="); + test_phase = 2; + + // Verify routes + if (verify_bgp_exchange()) { + DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "=== BGP ROUTE EXCHANGE SUCCESS ==="); + test_phase = 3; // Success + } else { + DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "=== BGP ROUTE EXCHANGE FAILED ==="); + test_phase = 4; // Failure + } + + // Cancel timeout for quick exit + if (test_timeout_id) { + uasync_cancel_timeout(ua, test_timeout_id); + test_timeout_id = NULL; + } + return; + } + + case 2: // Verification done + return; + } + + // Schedule next check + if (test_phase < 3) { + monitor_timeout_id = uasync_set_timeout(ua, 100, NULL, monitor_connections); + } +} + +static void test_timeout(void* arg) { + (void)arg; + if (test_phase < 3) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "\n=== TEST TIMEOUT ==="); + test_phase = 4; // Failure + if (monitor_timeout_id) { + uasync_cancel_timeout(ua, monitor_timeout_id); + monitor_timeout_id = NULL; + } + } +} + +int main() { + printf("=== BGP Route Exchange Test ===\n"); + + // Create temp config files + if (create_temp_configs() != 0) { + fprintf(stderr, "Failed to create temporary config files\n"); + return 1; + } + + debug_config_init(); + debug_set_level(DEBUG_LEVEL_INFO); + debug_set_categories(DEBUG_CATEGORY_ROUTING | DEBUG_CATEGORY_ETCP | DEBUG_CATEGORY_BGP); + + // Disable TUN for testing + utun_instance_set_tun_init_enabled(0); + + // Create shared uasync context + ua = uasync_create(); + if (!ua) { + fprintf(stderr, "Failed to create uasync context\n"); + return 1; + } + + // Create server instance + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating server instance..."); + server_instance = utun_instance_create(ua, server_config_path); + if (!server_instance) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to create server instance"); + return 1; + } + + // Initialize server connections and routing + if (init_connections(server_instance) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize server connections"); + return 1; + } + + if (utun_instance_init(server_instance) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize server instance"); + return 1; + } + + // BGP module is now initialized automatically in utun_instance_create + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Server instance ready (node_id=%016llX, bgp=%p)", + (unsigned long long)server_instance->node_id, (void*)server_instance->bgp); + + // Create client instance + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating client instance..."); + client_instance = utun_instance_create(ua, client_config_path); + if (!client_instance) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to create client instance"); + return 1; + } + + // Initialize client connections and routing + if (init_connections(client_instance) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize client connections"); + return 1; + } + + if (utun_instance_init(client_instance) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize client instance"); + return 1; + } + + // BGP module is now initialized automatically in utun_instance_create + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Client instance ready (node_id=%016llX, bgp=%p)", + (unsigned long long)client_instance->node_id, (void*)client_instance->bgp); + + // Give server time to initialize + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Waiting for server initialization..."); + for (int i = 0; i < 50; i++) { + uasync_poll(ua, 10); + } + + // Start monitoring + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Starting connection monitoring..."); + monitor_timeout_id = uasync_set_timeout(ua, 100, NULL, monitor_connections); + test_timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS, NULL, test_timeout); + + // Main event loop + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Running event loop..."); + + int elapsed = 0; + int poll_interval = 5; + while (test_phase < 3 && elapsed < TEST_TIMEOUT_MS) { + uasync_poll(ua, poll_interval); + elapsed += poll_interval; + } + + // Cleanup + DEBUG_INFO(DEBUG_CATEGORY_ETCP, "\nCleaning up..."); + + if (monitor_timeout_id) { + uasync_cancel_timeout(ua, monitor_timeout_id); + monitor_timeout_id = NULL; + } + if (test_timeout_id) { + uasync_cancel_timeout(ua, test_timeout_id); + test_timeout_id = NULL; + } + + // Print final routing tables + print_routing_table(server_instance, "SERVER (final)"); + print_routing_table(client_instance, "CLIENT (final)"); + + // Destroy instances + if (server_instance) { + server_instance->running = 0; + utun_instance_destroy(server_instance); + } + if (client_instance) { + client_instance->running = 0; + utun_instance_destroy(client_instance); + } + + if (ua) { + uasync_destroy(ua, 0); + ua = NULL; + } + + cleanup_temp_configs(); + + // Test result + if (test_phase == 3) { + printf("\n=== TEST PASSED ===\n"); + printf("BGP route exchange working (client -> server direction)\n"); + return 0; + } else { + printf("\n=== TEST FAILED ===\n"); + if (test_phase == 4) { + printf("BGP route exchange verification failed\n"); + } else { + printf("Test timeout or connection failed\n"); + } + return 1; + } +} diff --git a/tests/test_route_lib.c b/tests/test_route_lib.c index 0311e429..865b9f21 100644 --- a/tests/test_route_lib.c +++ b/tests/test_route_lib.c @@ -462,6 +462,105 @@ static void test_route_types(void) { PASS(); } +static void test_route_delete_entry(void) { + TEST("route delete entry"); + + struct ROUTE_TABLE *table = route_table_create(); + struct ROUTE_ENTRY entry; + struct ETCP_CONN *conn1 = (struct ETCP_CONN *)0x1000; + + // Вставляем несколько маршрутов + create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, conn1); + route_table_insert(table, &entry); + create_test_route(&entry, 0xC0A80200, 24, ROUTE_TYPE_STATIC, conn1); + route_table_insert(table, &entry); + create_test_route(&entry, 0xC0A80300, 24, ROUTE_TYPE_STATIC, conn1); + route_table_insert(table, &entry); + + ASSERT_EQ(table->count, 3, "should have 3 routes"); + + // Удаляем маршрут по network/prefix (next_hop = NULL - любой) + ASSERT(route_table_delete_entry(table, 0xC0A80200, 24, NULL), + "should delete route 192.168.2.0/24"); + ASSERT_EQ(table->count, 2, "should have 2 routes after deletion"); + + // Проверяем что правильный маршрут удалён + struct ROUTE_ARRAY *result = route_table_lookup(table, 0xC0A80201); + ASSERT(result == NULL, "should not find deleted route"); + + result = route_table_lookup(table, 0xC0A80101); + ASSERT(result != NULL, "should still find 192.168.1.x route"); + route_cache_clear(); + + // Пытаемся удалить несуществующий маршрут + ASSERT(!route_table_delete_entry(table, 0xC0A80500, 24, NULL), + "should fail to delete non-existent route"); + ASSERT_EQ(table->count, 2, "should still have 2 routes"); + + route_table_destroy(table); + PASS(); +} + +static void test_route_delete_entry_by_nexthop(void) { + TEST("route delete entry by next_hop"); + + struct ROUTE_TABLE *table = route_table_create(); + struct ROUTE_ENTRY entry; + struct ETCP_CONN *conn1 = (struct ETCP_CONN *)0x1000; + struct ETCP_CONN *conn2 = (struct ETCP_CONN *)0x2000; + + // Одинаковый network/prefix, разные next_hop + create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, conn1); + route_table_insert(table, &entry); + create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, conn2); + route_table_insert(table, &entry); + + ASSERT_EQ(table->count, 2, "should have 2 routes"); + + // Удаляем только маршрут через conn1 + ASSERT(route_table_delete_entry(table, 0xC0A80100, 24, conn1), + "should delete route via conn1"); + ASSERT_EQ(table->count, 1, "should have 1 route after deletion"); + + // Проверяем что остался маршрут через conn2 + struct ROUTE_ARRAY *result = route_table_lookup(table, 0xC0A80101); + ASSERT(result != NULL, "should still find route via conn2"); + ASSERT_EQ(result->entries[0]->next_hop, conn2, "wrong next_hop remaining"); + + route_cache_clear(); + route_table_destroy(table); + PASS(); +} + +static void test_route_withdraw_scenario(void) { + TEST("BGP withdraw scenario"); + + struct ROUTE_TABLE *table = route_table_create(); + struct ROUTE_ENTRY entry; + struct ETCP_CONN *peer = (struct ETCP_CONN *)0x1000; + + // Имитируем получение маршрута от пира (learned route) + memset(&entry, 0, sizeof(entry)); + entry.network = 0x0A000000; // 10.0.0.0 + entry.prefix_length = 8; + entry.next_hop = peer; + entry.type = ROUTE_TYPE_LEARNED; + entry.flags = ROUTE_FLAG_ACTIVE | ROUTE_FLAG_LEARNED; + entry.destination_node_id = 0x1234567890ABCDEFULL; + + ASSERT(route_table_insert(table, &entry), "failed to insert learned route"); + ASSERT_EQ(table->stats.learned_routes, 1, "should have 1 learned route"); + + // Имитируем получение withdraw + ASSERT(route_table_delete_entry(table, 0x0A000000, 8, peer), + "should withdraw route from peer"); + ASSERT_EQ(table->count, 0, "should have 0 routes after withdraw"); + ASSERT_EQ(table->stats.learned_routes, 0, "should have 0 learned routes"); + + route_table_destroy(table); + PASS(); +} + static void test_stress(void) { TEST("stress 1000 routes"); double start = now_ms(); @@ -518,8 +617,11 @@ int main(void) { test_table_expansion(); test_cache_functionality(); test_route_types(); + test_route_delete_entry(); + test_route_delete_entry_by_nexthop(); + test_route_withdraw_scenario(); test_stress(); - + printf("\n=== SUMMARY ===\n" "run=%d passed=%d failed=%d\n" "ops=%ld time=%.2f ms\n",