You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

500 lines
19 KiB

/**
* @file route_bgp.c
* @brief Модуль обмена роутинг-таблицами между узлами (BGP-like)
*
* Функции:
* - Прием и обработка роутинг-пакетов от других узлов
* - Рассылка локальной таблицы при подключении нового узла
* - Обновление таблицы при получении изменений
*/
#include <stdlib.h>
#include <string.h>
#include "../lib/platform_compat.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))
// Forward declarations
static void route_bgp_send_route(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn,
const struct ROUTE_ENTRY* route);
static void route_bgp_on_route_change(struct ROUTE_TABLE* table,
struct ROUTE_ENTRY* entry,
int action, void* arg);
/**
* @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 Отправить withdraw (отзыв маршрута) через ETCP
*/
static void route_bgp_send_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn,
const struct ROUTE_ENTRY* route)
{
if (!bgp || !conn || !route) {
return;
}
// Создаем ETCP пакет
struct ll_entry* entry = ll_alloc_lldgram(sizeof(struct ROUTE_BGP_PACKET));
if (!entry) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to allocate withdraw packet");
return;
}
struct ROUTE_BGP_PACKET* pkt = (struct ROUTE_BGP_PACKET*)entry->dgram;
pkt->cmd = ETCP_ID_ROUTE_ENTRY;
pkt->subcmd = ROUTE_SUBCMD_WITHDRAW;
pkt->hop_count = 0;
pkt->prefix_length = route->prefix_length & 0x3F;
pkt->latency = 0;
pkt->endpoint_port = 0;
pkt->endpoint_ip = 0;
pkt->network = htonl(route->network);
pkt->bandwidth_kbps = 0;
pkt->node_id = route->destination_node_id;
entry->len = sizeof(struct ROUTE_BGP_PACKET);
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Sending withdraw: %08x/%d to conn %p",
route->network, route->prefix_length, (void*)conn);
// Отправляем через ETCP
if (etcp_send(conn, entry) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to send withdraw via ETCP");
queue_dgram_free(entry);
queue_entry_free(entry);
}
}
/**
* @brief Отправить withdraw для всех маршрутов через указанное соединение
*/
static void route_bgp_send_withdraw_for_conn(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 withdraw for routes via conn %p to all peers", (void*)conn);
// Находим все маршруты через это соединение и отправляем withdraw
for (size_t i = 0; i < rt->count; i++) {
struct ROUTE_ENTRY* entry = &rt->entries[i];
if (entry->next_hop == conn && entry->type == ROUTE_TYPE_LEARNED) {
// Отправляем withdraw всем соединениям в senders_list
struct ll_entry* item_entry = bgp->senders_list->head;
while (item_entry) {
struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)item_entry->data;
if (item->conn != conn) { // Не отправляем тому, кто прислал
route_bgp_send_withdraw(bgp, item->conn, entry);
}
item_entry = item_entry->next;
}
}
}
}
/**
* @brief Колбэк при изменении таблицы маршрутизации
*
* Рассылает обновления всем соединениям в senders_list
*/
static void route_bgp_on_route_change(struct ROUTE_TABLE* table,
struct ROUTE_ENTRY* entry,
int action, void* arg)
{
struct ROUTE_BGP* bgp = (struct ROUTE_BGP*)arg;
if (!bgp || !entry) {
return;
}
// Не рассылаем локальные маршруты помеченные ROUTE_PREFIX_F_LOCAL
if (entry->type == ROUTE_TYPE_LOCAL && (entry->prefix_length & ROUTE_PREFIX_F_LOCAL)) {
return;
}
// Не рассылаем маршруты через которые мы их получили (чтобы избежать петель)
// Это делается в цикле ниже
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Route change: action=%d, network=%08x/%d, type=%d",
action, entry->network, entry->prefix_length, entry->type);
// Перебираем все соединения в senders_list
struct ll_entry* item_entry = bgp->senders_list->head;
while (item_entry) {
struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)item_entry->data;
// Не отправляем тому, от кого пришел маршрут
if (entry->type == ROUTE_TYPE_LEARNED && entry->next_hop == item->conn) {
item_entry = item_entry->next;
continue;
}
if (action == 2) { // delete - отправить withdraw
route_bgp_send_withdraw(bgp, item->conn, entry);
} else { // insert (0) или update (1) - отправить маршрут
route_bgp_send_route(bgp, item->conn, entry);
}
item_entry = item_entry->next;
}
}
/**
* @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;
}
// Устанавливаем callback на изменение таблицы маршрутизации
if (instance->rt) {
instance->rt->change_callback = route_bgp_on_route_change;
instance->rt->change_callback_arg = bgp;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Route change callback registered");
}
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) {
// Убираем callback из таблицы маршрутизации
if (instance->rt && instance->rt->change_callback == route_bgp_on_route_change) {
instance->rt->change_callback = NULL;
instance->rt->change_callback_arg = NULL;
}
if (instance->bgp->senders_list) {
// Очищаем очередь - освобождаем элементы conn_item
struct ll_entry* entry;
while ((entry = queue_data_get(instance->bgp->senders_list)) != NULL) {
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;
}
// Создаем элемент для добавления в senders_list
struct ll_entry* entry = queue_entry_new(sizeof(struct ROUTE_BGP_CONN_ITEM));
if (!entry) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to allocate connection item");
return;
}
struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)entry->data;
item->conn = conn;
// Добавляем в очередь (id = 0, не используем хеш)
if (queue_data_put(bgp->senders_list, entry, 0) != 0) {
queue_entry_free(entry);
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to add connection to senders_list");
return;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Added connection %p to senders_list", (void*)conn);
// Отправляем полную таблицу новому соединению
route_bgp_send_table_to_conn_internal(bgp, conn);
}
void route_bgp_remove_conn(struct ETCP_CONN* conn)
{
if (!conn || !conn->instance) {
return;
}
struct UTUN_INSTANCE* instance = conn->instance;
struct ROUTE_BGP* bgp = instance->bgp;
if (!bgp) {
return;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Removing connection %p from senders_list", (void*)conn);
// Отправляем withdraw всем остальным соединениям для маршрутов через это соединение
route_bgp_send_withdraw_for_conn(bgp, conn);
// Находим и удаляем соединение из senders_list
struct ll_entry* entry = bgp->senders_list->head;
while (entry) {
struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)entry->data;
if (item->conn == conn) {
// Удаляем из очереди
queue_remove_data(bgp->senders_list, entry);
queue_entry_free(entry);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Connection %p removed from senders_list", (void*)conn);
break;
}
entry = entry->next;
}
// Удаляем все маршруты, связанные с этим соединением
if (instance->rt) {
route_table_delete(instance->rt, conn);
}
}
void route_bgp_close_conn(struct ETCP_CONN* conn)
{
// Устаревшая функция, используйте route_bgp_remove_conn
route_bgp_remove_conn(conn);
}