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.
 
 
 
 
 
 

470 lines
18 KiB

/**
* @file route_bgp.c
* @brief BGP-like обмен маршрутами — исправленная версия под новую route_lib
*/
#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 "etcp_debug.h"
#include "utun_instance.h"
#include "config_parser.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
// ============================================================================
// Вспомогательные функции
// ============================================================================
// ============================================================================
// Отправка (только preferred_conn + hop_list)
// ============================================================================
static void route_bgp_send_route(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn,
const struct ROUTE_ENTRY* route, uint8_t subcmd) {
if (!bgp || !conn || !route) return;
size_t pkt_size;
struct BGP_ROUTE_PACKET* pkt;
if (!route->conn_list) {
pkt_size = sizeof(struct BGP_ROUTE_PACKET);
pkt = u_calloc(1, pkt_size);
if (!pkt) return;
pkt->node_id = htobe64(bgp->instance->node_id);
pkt->hop_count = 0;
} else {
uint8_t pref = route->conn_list->preferred_conn;
if (pref >= route->conn_list->conninfo_count) pref = 0;
struct NODE_CONN_INFO* ci = &route->conn_list->conn_info[pref];
pkt_size = sizeof(struct BGP_ROUTE_PACKET) + ci->hop_count * sizeof(uint64_t);
pkt = u_calloc(1, pkt_size);
if (!pkt) return;
pkt->node_id = htobe64(route->conn_list->node_id);
pkt->hop_count = ci->hop_count;
if (ci->hop_count > 0 && ci->hop_list)
memcpy(pkt->hop_list, ci->hop_list, ci->hop_count * sizeof(uint64_t));
}
pkt->cmd = ETCP_ID_ROUTE_ENTRY;
pkt->subcmd = subcmd; // ← теперь используем REROUTE
pkt->network = htonl(route->network);
pkt->prefix_length = route->prefix_length;
pkt->latency = 0;
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 = (uint16_t)pkt_size;
if (etcp_send(conn, send_entry) != 0) {
queue_entry_free(send_entry);
u_free(pkt);
} else {
const char* subcmd_name = (subcmd == ROUTE_SUBCMD_ENTRY) ? "ENTRY" :
(subcmd == ROUTE_SUBCMD_WITHDRAW) ? "WITHDRAW" : "REROUTE";
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Sending route %s/%d, peer %s (%s)",
ip_to_string(route->network).a, route->prefix_length, conn->log_name, subcmd_name);
}
}
// ============================================================================
// Broadcast / Withdraw
// ============================================================================
static void route_bgp_broadcast_route(struct ROUTE_BGP* bgp, const struct ROUTE_ENTRY* route,
struct ETCP_CONN* exclude, uint8_t subcmd) {
if (!bgp) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_broadcast_route: bgp is NULL");
return;
}
if (!route) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_broadcast_route: route is NULL");
return;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_bgp_broadcast_route: network=%s/%d exclude=%p subcmd=%d",
ip_to_string(route->network).a, route->prefix_length, (void*)exclude, subcmd);
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, subcmd);
sent_count++;
}
entry = entry->next;
}
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "route_bgp_broadcast_route: sent to %d peers", sent_count);
}
static void route_bgp_broadcast_withdraw(struct ROUTE_BGP* bgp, uint64_t node_id,
struct ETCP_CONN* exclude) {
if (!bgp) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_broadcast_withdraw: bgp is NULL");
return;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_bgp_broadcast_withdraw: node_id=%016llx exclude=%p",
(unsigned long long)node_id, (void*)exclude);
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);
struct ll_entry* e = bgp->senders_list->head;
while (e) {
struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)e->data;
if (item->conn != exclude) {
struct ll_entry* copy = queue_entry_new(0);
if (copy) {
copy->dgram = u_malloc(send_entry->len);
memcpy(copy->dgram, send_entry->dgram, send_entry->len);
copy->len = send_entry->len;
etcp_send(item->conn, copy);
}
}
e = e->next;
}
queue_entry_free(send_entry);
u_free(pkt);
}
// ============================================================================
// Callback на изменение таблицы (insert / update / delete)
// ============================================================================
static void route_bgp_on_route_change(struct ROUTE_TABLE* table,
struct ROUTE_ENTRY* entry,
int action,
uint64_t changed_from,
void* arg) {
struct ROUTE_BGP* bgp = (struct ROUTE_BGP*)arg;
if (!bgp || !entry) return;
// changed_from - это peer_node_id от кого пришёл маршрут (0 для локальных)
// находим conn по peer_node_id для использования как exclude
struct ETCP_CONN* exclude_conn = NULL;
if (changed_from != 0) {
struct ll_entry* e = bgp->senders_list->head;
while (e) {
struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)e->data;
if (item->conn->peer_node_id == changed_from) {
exclude_conn = item->conn;
break;
}
e = e->next;
}
}
if (action == 0) { // insert
route_bgp_broadcast_route(bgp, entry, exclude_conn, ROUTE_SUBCMD_ENTRY);
}
else if (action == 1) { // update / reroute / hop_list changed
route_bgp_broadcast_route(bgp, entry, exclude_conn, ROUTE_SUBCMD_ENTRY_REROUTE);
}
else if (action == 2) { // withdraw
uint64_t node_id = entry->conn_list ? entry->conn_list->node_id : 0;
route_bgp_broadcast_withdraw(bgp, node_id, exclude_conn);
}
}
// ============================================================================
// Приём пакетов
// ============================================================================
static void route_bgp_receive_cbk(struct ETCP_CONN* from_conn, struct ll_entry* entry) {
if (!from_conn) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_receive_cbk: from_conn is NULL");
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
return;
}
if (!entry || entry->len < 2) {
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
return;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_bgp_receive_cbk: from=%016llx len=%zu",
(unsigned long long)from_conn->peer_node_id, entry->len);
struct UTUN_INSTANCE* instance = from_conn->instance;
if (!instance || !instance->rt || !instance->bgp) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_receive_cbk: invalid instance/rt/bgp");
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
struct ROUTE_BGP* bgp = instance->bgp;
uint8_t* data = entry->dgram;
if (data[0] != ETCP_ID_ROUTE_ENTRY) {
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
uint8_t subcmd = data[1];
if (subcmd == ROUTE_SUBCMD_ENTRY || subcmd == ROUTE_SUBCMD_ENTRY_REROUTE) {
if (entry->len < sizeof(struct BGP_ROUTE_PACKET)) {
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
struct BGP_ROUTE_PACKET* pkt = (struct BGP_ROUTE_PACKET*)data;
uint32_t network = ntohl(pkt->network);
uint8_t prefix = pkt->prefix_length;
uint64_t owner_node_id = be64toh(pkt->node_id);
uint8_t recv_hop_count = pkt->hop_count;
if (recv_hop_count > MAX_HOPS - 1) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "Too many hops (%d)", recv_hop_count);
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
// Строим новый hop_list: next_hop = отправитель + полученный список
uint8_t new_hop_count = recv_hop_count + 1;
uint64_t new_hop_list[MAX_HOPS];
new_hop_list[0] = from_conn->peer_node_id;
if (recv_hop_count > 0) {
memcpy(&new_hop_list[1], pkt->hop_list, recv_hop_count * sizeof(uint64_t));
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Received route %s/%d from %016llx (hops=%d)", ip_to_string(network).a, prefix, (unsigned long long)owner_node_id, new_hop_count);
struct ROUTE_ENTRY new_route = {0};
new_route.network = network;
new_route.prefix_length = prefix;
bool inserted = route_insert(instance->rt, &new_route,
from_conn,
owner_node_id,
instance->node_id,
new_hop_list,
new_hop_count);
if (!inserted) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "Route insert rejected (loop/overlap/owner conflict)");
} else {
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "route_bgp_receive_cbk: INSERTED");
}
} else if (subcmd == ROUTE_SUBCMD_WITHDRAW) {
if (entry->len < sizeof(struct BGP_WITHDRAW_PACKET)) {
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
struct BGP_WITHDRAW_PACKET* wp = (struct BGP_WITHDRAW_PACKET*)data;
uint64_t node_id = be64toh(wp->node_id);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Received WITHDRAW for node %016llx", (unsigned long long)node_id);
// === ИСПРАВЛЕНИЕ: поддержка резервных путей ===
route_remove_path(instance->rt, from_conn, node_id);
// callback внутри route_remove_path уже сделает broadcast (withdraw или reroute)
}
queue_dgram_free(entry);
queue_entry_free(entry);
}
// ============================================================================
// Init / Destroy / New / Remove conn
// ============================================================================
// Callback для готовности ETCP соединения - отправляет маршруты
static void route_bgp_on_conn_ready(struct ETCP_CONN* conn, void* arg) {
(void)arg;
if (conn && conn->instance && conn->instance->bgp) {
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "Connection %s ready ", conn->log_name);
route_bgp_new_conn(conn);
}
}
// Callback для новых ETCP соединений - устанавливает ready callback
static void route_bgp_etcp_conn_cbk(struct ETCP_CONN* conn, void* arg) {
(void)arg;
if (conn && conn->instance && conn->instance->bgp) {
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "Set ETCP ready callback for connection %s", conn->log_name);
etcp_conn_set_ready_cbk(conn, route_bgp_on_conn_ready, conn->instance->bgp);
}
}
struct ROUTE_BGP* route_bgp_init(struct UTUN_INSTANCE* instance) {
if (!instance) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_init: instance is NULL");
return NULL;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_bgp_init: node_id=%016llx", (unsigned long long)instance->node_id);
struct ROUTE_BGP* bgp = u_calloc(1, sizeof(struct ROUTE_BGP));
if (!bgp) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_init: alloc failed");
return NULL;
}
bgp->instance = instance;
bgp->senders_list = queue_new(instance->ua, 0, "BGP_senders");
if (!bgp->senders_list) {
u_free(bgp);
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_init: queue_new failed");
return NULL;
}
etcp_bind(instance, ETCP_ID_ROUTE_ENTRY, route_bgp_receive_cbk);
if (instance->rt) {
instance->rt->change_callback = route_bgp_on_route_change;
instance->rt->change_callback_arg = bgp;
}
// Устанавливаем callback для новых ETCP соединений
etcp_set_new_conn_cbk(instance, route_bgp_etcp_conn_cbk, NULL);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP module initialized (with hop_list support)");
return bgp;
}
void route_bgp_destroy(struct UTUN_INSTANCE* instance) {
if (!instance) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_destroy: instance is NULL");
return;
}
if (!instance->bgp) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_destroy: bgp is NULL");
return;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_bgp_destroy: node_id=%016llx", (unsigned long long)instance->node_id);
etcp_unbind(instance, ETCP_ID_ROUTE_ENTRY);
if (instance->rt && instance->rt->change_callback == route_bgp_on_route_change) {
instance->rt->change_callback = NULL;
instance->rt->change_callback_arg = NULL;
}
// очистка списка
struct ll_entry* e;
while ((e = queue_data_get(instance->bgp->senders_list)) != NULL) {
queue_entry_free(e);
}
queue_free(instance->bgp->senders_list);
u_free(instance->bgp);
instance->bgp = NULL;
}
void route_bgp_new_conn(struct ETCP_CONN* conn) {
if (!conn) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_new_conn: conn is NULL");
return;
}
if (!conn->instance) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_new_conn: conn->instance is NULL");
return;
}
if (!conn->instance->bgp) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_new_conn: conn->instance->bgp is NULL");
return;
}
if (!conn->instance->rt) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_new_conn: conn->instance->rt is NULL");
return;
}
struct ROUTE_BGP* bgp = conn->instance->bgp;
struct ROUTE_TABLE* rt = conn->instance->rt;
// === 1. Проверяем, уже есть ли это соединение в списке ===
bool already_exists = false;
struct ll_entry* e = bgp->senders_list->head;
while (e) {
struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)e->data;
if (item->conn == conn) {
already_exists = true;
break;
}
e = e->next;
}
// === 2. Если нет — добавляем (только один раз) ===
if (!already_exists) {
struct ll_entry* item_entry = queue_entry_new(sizeof(struct ROUTE_BGP_CONN_ITEM));
if (!item_entry) return;
((struct ROUTE_BGP_CONN_ITEM*)item_entry->data)->conn = conn;
queue_data_put(bgp->senders_list, item_entry, 0);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "New connection added to senders_list");
}
// === 3. В любом случае отправляем полную таблицу (это и нужно при переподключении) ===
for (size_t i = 0; i < rt->count; i++) {
route_bgp_send_route(bgp, conn, &rt->entries[i], ROUTE_SUBCMD_ENTRY);
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Sent full routing table to %s (reconnect OK)",
conn->log_name);
}
void route_bgp_remove_conn(struct ETCP_CONN* conn) {
if (!conn) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_remove_conn: conn is NULL");
return;
}
if (!conn->instance || !conn->instance->bgp) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_remove_conn: invalid instance/bgp");
return;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_bgp_remove_conn: peer=%016llx", (unsigned long long)conn->peer_node_id);
struct ROUTE_BGP* bgp = conn->instance->bgp;
struct ROUTE_TABLE* rt = conn->instance->rt;
// ← КЛЮЧЕВОЕ ИЗМЕНЕНИЕ: теперь используем route_remove_conn
if (rt) {
route_remove_conn(rt, conn); // автоматически вызовет reroute / withdraw через callback
}
// Удаляем из списка рассылки
struct ll_entry* e = bgp->senders_list->head;
while (e) {
struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)e->data;
if (item->conn == conn) {
queue_remove_data(bgp->senders_list, e);
queue_entry_free(e);
break;
}
e = e->next;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Connection removed, routes updated via route_remove_conn");
}
void route_bgp_close_conn(struct ETCP_CONN* conn) {
route_bgp_remove_conn(conn);
}