Browse Source

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)
nodeinfo-routing-update
Evgeny 8 months ago
parent
commit
68668821e4
  1. 1
      lib/debug_config.h
  2. 1
      src/Makefile.am
  3. 2
      src/etcp.h
  4. 7
      src/etcp_api.h
  5. 18
      src/etcp_connections.c
  6. 323
      src/route_bgp.c
  7. 62
      src/route_bgp.h
  8. 59
      src/route_lib.c
  9. 17
      src/route_lib.h
  10. 16
      src/routing.txt
  11. 17
      src/utun_instance.c
  12. 2
      src/utun_instance.h
  13. 6
      tests/Makefile.am
  14. 431
      tests/test_bgp_route_exchange.c
  15. 104
      tests/test_route_lib.c

1
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 */

1
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 \

2
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];

7
src/etcp_api.h

@ -9,8 +9,7 @@
*
* Формат кодограмм: <cmd 1 byte> <data ... n bytes>
* 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;

18
src/etcp_connections.c

@ -6,6 +6,7 @@
#include <errno.h>
#include <string.h>
#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) {

323
src/route_bgp.c

@ -0,0 +1,323 @@
/**
* @file route_bgp.c
* @brief Модуль обмена роутинг-таблицами между узлами (BGP-like)
*
* Функции:
* - Прием и обработка роутинг-пакетов от других узлов
* - Рассылка локальной таблицы при подключении нового узла
* - Обновление таблицы при получении изменений
*/
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#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);
}
}

62
src/route_bgp.h

@ -0,0 +1,62 @@
#ifndef ROUTE_BGP_H
#define ROUTE_BGP_H
#include <stdint.h>
#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

59
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;

17
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
* при поиске ищет готовый результат в кеше или добавляет в кеш новый результат и возвращает запись из кеша

16
src/routing.txt

@ -1,16 +0,0 @@
подсистема роутинга
utun - это сеть узлов. у каждого узла есть собственные локальные подсети.
и узлы обмениваются таблицой маршрутов между собой так чтобы у каждого была актуальная таблица подсетей всех узлов.
маршрутами меняются только те подключения которые взяты из конфига.
инициируется подключение, клиент отправляет свою таблицу. когда сервер принимает таблицу - сервер помечает что по этому маршруту надо обмениваться маршрутами, далее;
- добавляет узел в список рассылки обновлений маршрутов
- отправляет свою таблицу
- добавляет в свою таблицу отсутствующие маршруты
- если что-то добавил:
- рассылает измененные маршруты по списку рассылки
- список рассылки - это linked-list очередей (также на базе ll_queue - каждый элемент = подписчик). один маршрут = одна отправленная кодограмма
при подключении узла или изменении таблицы: узлы обмениваются изменениями таблиц маршрутов.
то есть каждая запись в таблице имеет флаги - кому отправлена.

17
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");

2
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;

6
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)

431
tests/test_bgp_route_exchange.c

@ -0,0 +1,431 @@
/**
* @file test_bgp_route_exchange.c
* @brief Интеграционный тест обмена маршрутами между двумя инстансами
*
* Тест проверяет:
* - Создание двух инстансов с разными подсетями
* - Установление ETCP соединения
* - Обмен маршрутами через BGP
* - Проверку таблиц роутинга (client -> server direction)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#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;
}
}

104
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",

Loading…
Cancel
Save