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
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); |
|
}
|
|
|