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.
 
 
 
 
 
 

665 lines
24 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 "etcp_connections.h"
#include "utun_instance.h"
#include "config_parser.h"
#include "../lib/debug_config.h"
#include "../lib/mem.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_send_reroute(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn,
uint64_t old_node_id, const struct ROUTE_ENTRY* route);
static void route_bgp_broadcast_route(struct ROUTE_BGP* bgp, const struct ROUTE_ENTRY* route,
struct ETCP_CONN* exclude);
static void route_bgp_broadcast_withdraw(struct ROUTE_BGP* bgp, uint64_t node_id,
struct ETCP_CONN* exclude);
static void route_bgp_send_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn, uint64_t node_id);
static void route_bgp_send_table_to_conn_internal(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn);
static void route_bgp_on_route_change(struct ROUTE_TABLE* table,
struct ROUTE_ENTRY* entry,
int action, void* arg);
static void route_bgp_send_withdraw_for_conn(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn);
static const char* route_type_to_str(route_type_t type) {
switch (type) {
case ROUTE_TYPE_LOCAL: return "LOCAL";
case ROUTE_TYPE_LEARNED: return "LEARNED";
default: return "UNKNOWN";
}
}
static void format_ip(uint32_t ip, char* buffer) {
if (!buffer) return;
uint8_t* b = (uint8_t*)&ip;
snprintf(buffer, 16, "%u.%u.%u.%u", b[3], b[2], b[1], b[0]);
}
typedef struct {
uint8_t cmd;
uint8_t subcmd;
uint64_t node_id;
} BGP_WITHDRAW_PACKET __attribute__((packed));
typedef struct {
uint8_t cmd;
uint8_t subcmd;
uint64_t old_node_id;
struct ROUTE_PEER_INFO peer_info;
} BGP_REROUTE_PACKET __attribute__((packed));
/**
* @brief Рассылает маршрут всем соединениям (кроме exclude)
*/
static void route_bgp_broadcast_route(struct ROUTE_BGP* bgp, const struct ROUTE_ENTRY* route,
struct ETCP_CONN* exclude) {
if (!bgp || !route) return;
struct ll_entry* entry = bgp->senders_list->head;
int sent_count = 0;
while (entry) {
struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)entry->data;
if (item->conn != exclude) {
route_bgp_send_route(bgp, item->conn, route);
sent_count++;
}
entry = entry->next;
}
char net_buf[16];
format_ip(route->network, net_buf);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Broadcasted route %s/%d to %d connections",
net_buf, route->prefix_length, sent_count);
}
/**
* @brief Рассылает withdraw всем соединениям (кроме exclude)
*/
static void route_bgp_broadcast_withdraw(struct ROUTE_BGP* bgp, uint64_t node_id,
struct ETCP_CONN* exclude) {
if (!bgp) return;
struct ll_entry* entry = bgp->senders_list->head;
int sent_count = 0;
while (entry) {
struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)entry->data;
if (item->conn != exclude) {
route_bgp_send_withdraw(bgp, item->conn, node_id);
sent_count++;
}
entry = entry->next;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Broadcasted withdraw for node %016llx to %d connections",
(unsigned long long)node_id, sent_count);
}
/**
* @brief Отправляет маршрут конкретному соединению
*/
static void route_bgp_send_route(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn,
const struct ROUTE_ENTRY* route) {
if (!bgp || !conn || !route) return;
struct BGP_ENTRY_PACKET *pkt = u_calloc(1, sizeof(struct BGP_ENTRY_PACKET));
if (!pkt) return;
pkt->cmd = ETCP_ID_ROUTE_ENTRY;
pkt->subcmd = ROUTE_SUBCMD_ENTRY;
pkt->peer_info.node_id = htobe64(route->node_id);
pkt->peer_info.network = htonl(route->network);
pkt->peer_info.prefix_length = route->prefix_length;
pkt->peer_info.hop_count = route->hop_count;
pkt->peer_info.latency = htons(route->latency);
// Add other fields if present, e.g., bandwidth
struct ll_entry *send_entry = queue_entry_new(0);
if (!send_entry) {
u_free(pkt);
return;
}
send_entry->dgram = (uint8_t*)pkt;
send_entry->len = sizeof(struct BGP_ENTRY_PACKET);
if (etcp_send(conn, send_entry) != 0) {
queue_entry_free(send_entry);
u_free(pkt);
}
}
/**
* @brief Отправляет reroute конкретному соединению
*/
static void route_bgp_send_reroute(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn,
uint64_t old_node_id, const struct ROUTE_ENTRY* route) {
if (!bgp || !conn || !route) return;
struct BGP_REROUTE_PACKET *pkt = u_calloc(1, sizeof(struct BGP_REROUTE_PACKET));
if (!pkt) return;
pkt->cmd = ETCP_ID_ROUTE_ENTRY;
pkt->subcmd = ROUTE_SUBCMD_ENTRY_REROUTE;
pkt->old_node_id = htobe64(old_node_id);
pkt->peer_info.node_id = htobe64(route->node_id);
pkt->peer_info.network = htonl(route->network);
pkt->peer_info.prefix_length = route->prefix_length;
pkt->peer_info.hop_count = route->hop_count;
pkt->peer_info.latency = htons(route->latency);
struct ll_entry *send_entry = queue_entry_new(0);
if (!send_entry) {
u_free(pkt);
return;
}
send_entry->dgram = (uint8_t*)pkt;
send_entry->len = sizeof(struct BGP_REROUTE_PACKET);
if (etcp_send(conn, send_entry) != 0) {
queue_entry_free(send_entry);
u_free(pkt);
}
}
/**
* @brief Отправляет withdraw конкретному соединению
*/
static void route_bgp_send_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn, uint64_t node_id) {
if (!bgp || !conn) return;
struct BGP_WITHDRAW_PACKET *pkt = u_calloc(1, sizeof(struct BGP_WITHDRAW_PACKET));
if (!pkt) return;
pkt->cmd = ETCP_ID_ROUTE_ENTRY;
pkt->subcmd = ROUTE_SUBCMD_WITHDRAW;
pkt->node_id = htobe64(node_id);
struct ll_entry *send_entry = queue_entry_new(0);
if (!send_entry) {
u_free(pkt);
return;
}
send_entry->dgram = (uint8_t*)pkt;
send_entry->len = sizeof(struct BGP_WITHDRAW_PACKET);
if (etcp_send(conn, send_entry) != 0) {
queue_entry_free(send_entry);
u_free(pkt);
}
}
/**
* @brief Отправляет withdraw для всех affected node_id при удалении conn
*/
static void route_bgp_send_withdraw_for_conn(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn) {
if (!bgp || !conn || !conn->instance || !conn->instance->rt) return;
struct ROUTE_TABLE* table = conn->instance->rt;
// Collect unique node_ids where next_hop == conn
uint64_t unique_nodes[1024]; // assume max
size_t unique_count = 0;
bool found;
for (size_t i = 0; i < table->count; i++) {
if (table->entries[i].next_hop == conn) {
uint64_t nid = table->entries[i].node_id;
found = false;
for (size_t j = 0; j < unique_count; j++) {
if (unique_nodes[j] == nid) {
found = true;
break;
}
}
if (!found && unique_count < 1024) {
unique_nodes[unique_count++] = nid;
}
}
}
// Broadcast withdraw for each unique node_id
for (size_t j = 0; j < unique_count; j++) {
route_bgp_broadcast_withdraw(bgp, unique_nodes[j], conn); // exclude conn, but since removing, anyway
}
}
/**
* @brief Отправляет полную таблицу конкретному соединению
*/
static void route_bgp_send_table_to_conn_internal(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn) {
if (!bgp || !conn || !bgp->instance || !bgp->instance->rt) return;
struct ROUTE_TABLE* table = bgp->instance->rt;
for (size_t i = 0; i < table->count; i++) {
route_bgp_send_route(bgp, conn, &table->entries[i]);
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Sent full routing table (%zu entries) to connection %p",
table->count, (void*)conn);
}
/**
* @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 < 2) {
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 BGP_ENTRY_PACKET* pkt = (struct BGP_ENTRY_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;
}
char ip_buf[16];
uint32_t network;
uint8_t prefix_length;
uint64_t node_id;
switch (pkt->subcmd) {
case ROUTE_SUBCMD_ENTRY:
case ROUTE_SUBCMD_ENTRY_REROUTE: {
uint64_t old_node_id = 0;
struct ROUTE_PEER_INFO *peer_info;
if (pkt->subcmd == ROUTE_SUBCMD_ENTRY_REROUTE) {
if (entry->len < sizeof(BGP_REROUTE_PACKET)) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Invalid REROUTE packet size");
queue_dgram_free(entry);
queue_entry_free(entry);
return;
}
BGP_REROUTE_PACKET *reroute_pkt = (BGP_REROUTE_PACKET*)pkt;
old_node_id = be64toh(reroute_pkt->old_node_id);
peer_info = &reroute_pkt->peer_info;
} else {
if (entry->len < sizeof(BGP_ENTRY_PACKET)) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Invalid ENTRY packet size");
queue_dgram_free(entry);
queue_entry_free(entry);
return;
}
peer_info = &pkt->peer_info;
}
node_id = be64toh(peer_info->node_id);
network = ntohl(peer_info->network);
prefix_length = peer_info->prefix_length;
uint8_t hop_count = peer_info->hop_count;
uint16_t latency = ntohs(peer_info->latency);
format_ip(network, ip_buf);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Received BGP %s: network=%s/%d, node_id=%016llx, hop=%d, lat=%u",
(pkt->subcmd == ROUTE_SUBCMD_ENTRY) ? "ENTRY" : "REROUTE",
ip_buf, prefix_length, (unsigned long long)node_id, hop_count, latency);
struct ROUTE_ENTRY new_route;
memset(&new_route, 0, sizeof(new_route));
new_route.node_id = node_id;
new_route.network = network;
new_route.prefix_length = prefix_length;
new_route.next_hop = from_conn;
new_route.type = ROUTE_TYPE_LEARNED;
new_route.flags = ROUTE_FLAG_ACTIVE | ROUTE_FLAG_LEARNED;
new_route.hop_count = hop_count + 1;
new_route.latency = latency + (from_conn->rtt_last * 10);
new_route.metrics.latency_ms = new_route.latency / 10;
new_route.metrics.hop_count = new_route.hop_count;
// Set defaults for other metrics
new_route.metrics.bandwidth_kbps = 100000; // example default
new_route.metrics.packet_loss_rate = 0;
route_update_quality(&new_route);
struct ROUTE_TABLE* table = instance->rt;
bool exists = false;
bool updated = false;
for (size_t i = 0; i < table->count; i++) {
struct ROUTE_ENTRY* existing = &table->entries[i];
if (existing->network == network && existing->prefix_length == prefix_length) {
exists = true;
if (new_route.hop_count < existing->hop_count) {
// Update to better route
existing->next_hop = new_route.next_hop;
existing->hop_count = new_route.hop_count;
existing->latency = new_route.latency;
existing->metrics = new_route.metrics;
existing->last_update = get_time_tb();
if (table->change_callback) {
table->change_callback(table, existing, 1, table->change_callback_arg);
}
updated = true;
}
break;
}
}
if (!exists) {
new_route.created_time = get_time_tb();
new_route.last_update = new_route.created_time;
new_route.metrics.last_updated = new_route.created_time;
if (route_table_insert(table, &new_route)) {
updated = true; // treat insert as update for broadcast
}
}
if (updated) {
route_bgp_broadcast_route(bgp, &new_route, from_conn);
}
break;
}
case ROUTE_SUBCMD_WITHDRAW: {
if (entry->len < sizeof(BGP_WITHDRAW_PACKET)) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Invalid WITHDRAW packet size");
queue_dgram_free(entry);
queue_entry_free(entry);
return;
}
BGP_WITHDRAW_PACKET *withdraw_pkt = (BGP_WITHDRAW_PACKET*)pkt;
uint64_t withdrawn_node_id = be64toh(withdraw_pkt->node_id);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Received WITHDRAW for node %016llx",
(unsigned long long)withdrawn_node_id);
struct ROUTE_TABLE* table = instance->rt;
bool has_alternative = false;
size_t i = 0;
while (i < table->count) {
struct ROUTE_ENTRY* rte = &table->entries[i];
if (rte->node_id == withdrawn_node_id) {
if (rte->next_hop == from_conn) {
route_table_delete_entry(table, rte->network, rte->prefix_length, from_conn);
// Do not increment i, array shifted
} else {
has_alternative = true;
i++;
}
} else {
i++;
}
}
if (has_alternative) {
// Send reroute for each remaining route to from_conn
for (size_t j = 0; j < table->count; j++) {
if (table->entries[j].node_id == withdrawn_node_id) {
route_bgp_send_reroute(bgp, from_conn, withdrawn_node_id, &table->entries[j]);
}
}
} else {
// Propagate withdraw
route_bgp_broadcast_withdraw(bgp, withdrawn_node_id, from_conn);
}
break;
}
case ROUTE_SUBCMD_NODEINFO: {
if (entry->len < sizeof(struct BGP_NODEINFO_PACKET)) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Invalid NODEINFO packet size");
queue_dgram_free(entry);
queue_entry_free(entry);
return;
}
struct BGP_NODEINFO_PACKET *info_pkt = (struct BGP_NODEINFO_PACKET*)pkt;
struct NODE_CONNS_INFO *node_info = &info_pkt->node_info;
if (node_info->connlist_count == 0) break;
uint64_t target_node_id = be64toh(node_info->NODE_CONN_INFO[0].node_id); // assume first
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Received NODEINFO for node %016llx, count=%d",
(unsigned long long)target_node_id, node_info->connlist_count);
struct ROUTE_TABLE* table = instance->rt;
for (size_t i = 0; i < table->count; i++) {
if (table->entries[i].node_id == target_node_id) {
size_t size = sizeof(struct NODE_CONNS_INFO) +
node_info->connlist_count * sizeof(struct NODE_CONN_INFO);
struct NODE_CONNS_INFO *new_list = u_malloc(size);
if (new_list) {
memcpy(new_list, node_info, size);
new_list->ref_count = 1;
if (table->entries[i].conn_list) {
if (--table->entries[i].conn_list->ref_count == 0) {
u_free(table->entries[i].conn_list);
}
}
table->entries[i].conn_list = new_list;
if (table->change_callback) {
table->change_callback(table, &table->entries[i], 1, table->change_callback_arg);
}
}
break;
}
}
break;
}
default:
DEBUG_WARN(DEBUG_CATEGORY_BGP, "Unknown BGP subcommand: %d", pkt->subcmd);
break;
}
queue_dgram_free(entry);
queue_entry_free(entry);
}
/**
* @brief Callback на изменение маршрута (broadcast при insert/update)
*/
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;
if (action == 0 || action == 1) { // insert or update
route_bgp_broadcast_route(bgp, entry, NULL);
}
// for delete, handled in remove_conn/withdraw
}
struct ROUTE_BGP* route_bgp_init(struct UTUN_INSTANCE* instance)
{
if (!instance) {
return NULL;
}
struct ROUTE_BGP* bgp = (struct ROUTE_BGP*)u_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, "BGP sender_list");
if (!bgp->senders_list) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to create senders queue");
u_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);
u_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);
}
u_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);
}