Browse Source

routing + bgp big update

nodeinfo-routing-update
jeka 3 weeks ago
parent
commit
167b3b36da
  1. 3
      AGENTS.md
  2. 724
      src/route_bgp.c
  3. 73
      src/route_bgp.h
  4. 23
      src/route_bgp.txt
  5. 977
      src/route_lib.c
  6. 183
      src/route_lib.h
  7. 31
      src/route_lib.txt
  8. 15
      src/routing.c
  9. 24
      src/utun_instance.c
  10. 30
      tests/test_bgp_route_exchange.c
  11. 43
      tests/test_etcp_two_instances.c
  12. 737
      tests/test_route_lib.c

3
AGENTS.md

@ -20,7 +20,8 @@ make install # Install (requires sudo)
```
### Partial Builds
win: powershell build.bat (аналог make если не изменен makefile.am)
win: powershell -Command ".\build.bat" 2>&1 (аналог make если не изменен makefile.am)
логи сборки win: build_win.log
```bash
cd lib && make # Build only the library
cd src && make # Build only the main program

724
src/route_bgp.c

@ -1,11 +1,6 @@
/**
* @file route_bgp.c
* @brief Модуль обмена роутинг-таблицами между узлами (BGP-like)
*
* Функции:
* - Прием и обработка роутинг-пакетов от других узлов
* - Рассылка локальной таблицы при подключении нового узла
* - Обновление таблицы при получении изменений
* @brief BGP-like обмен маршрутами исправленная версия под новую route_lib
*/
#include <stdlib.h>
@ -22,127 +17,63 @@
#include "../lib/debug_config.h"
#include "../lib/mem.h"
// Размер пакета (без заголовка ETCP)
#define BGP_PACKET_SIZE (sizeof(struct ROUTE_BGP_PACKET))
#define DEBUG_CATEGORY_BGP 1
// 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]);
}
struct BGP_WITHDRAW_PACKET {
uint8_t cmd;
uint8_t subcmd;
uint64_t node_id;
} __attribute__((packed));
struct BGP_REROUTE_PACKET {
uint8_t cmd;
uint8_t subcmd;
uint64_t old_node_id;
struct ROUTE_PEER_INFO peer_info;
} __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;
}
// ============================================================================
// Отправка (только preferred_conn + hop_list)
// ============================================================================
char net_buf[16];
format_ip(route->peer_info.network, net_buf);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Broadcasted route %s/%d to %d connections",
net_buf, route->peer_info.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) {
const struct ROUTE_ENTRY* route, uint8_t subcmd) {
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->peer_info.node_id);
pkt->peer_info.network = htonl(route->peer_info.network);
pkt->peer_info.prefix_length = route->peer_info.prefix_length;
pkt->peer_info.hop_count = route->peer_info.hop_count;
pkt->peer_info.latency = htons(route->peer_info.latency);
// Add other fields if present, e.g., bandwidth
struct ll_entry *send_entry = queue_entry_new(0);
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 = sizeof(struct BGP_ENTRY_PACKET);
send_entry->len = (uint16_t)pkt_size;
if (etcp_send(conn, send_entry) != 0) {
queue_entry_free(send_entry);
@ -150,515 +81,268 @@ static void route_bgp_send_route(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn,
}
}
/**
* @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;
// ============================================================================
// Broadcast / Withdraw
// ============================================================================
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->peer_info.node_id);
pkt->peer_info.network = htonl(route->peer_info.network);
pkt->peer_info.prefix_length = route->peer_info.prefix_length;
pkt->peer_info.hop_count = route->peer_info.hop_count;
pkt->peer_info.latency = htons(route->peer_info.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);
static void route_bgp_broadcast_route(struct ROUTE_BGP* bgp, const struct ROUTE_ENTRY* route,
struct ETCP_CONN* exclude, uint8_t subcmd) {
if (!bgp || !route) return;
if (etcp_send(conn, send_entry) != 0) {
queue_entry_free(send_entry);
u_free(pkt);
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 != exclude) {
route_bgp_send_route(bgp, item->conn, route, subcmd);
}
entry = entry->next;
}
}
/**
* @brief Отправляет withdraw конкретному соединению
*/
static void route_bgp_send_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn, uint64_t node_id) {
if (!bgp || !conn) return;
static void route_bgp_broadcast_withdraw(struct ROUTE_BGP* bgp, uint64_t node_id,
struct ETCP_CONN* exclude) {
if (!bgp) return;
struct BGP_WITHDRAW_PACKET *pkt = u_calloc(1, sizeof(struct BGP_WITHDRAW_PACKET));
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;
}
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].peer_info.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;
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;
}
// 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
}
queue_entry_free(send_entry);
u_free(pkt);
}
/**
* @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;
// ============================================================================
// Callback на изменение таблицы (insert / update / delete)
// ============================================================================
struct ROUTE_TABLE* table = bgp->instance->rt;
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;
for (size_t i = 0; i < table->count; i++) {
if (table->entries[i].next_hop!=conn) route_bgp_send_route(bgp, conn, &table->entries[i]);
if (action == 0) { // insert
route_bgp_broadcast_route(bgp, entry, NULL, ROUTE_SUBCMD_ENTRY);
}
else if (action == 1) { // update / reroute / hop_list changed
route_bgp_broadcast_route(bgp, entry, NULL, 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, NULL);
}
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);
}
// ============================================================================
// Приём пакетов
// ============================================================================
static void route_bgp_receive_cbk(struct ETCP_CONN* from_conn, struct ll_entry* entry) {
if (!from_conn || !entry || entry->len < 2) {
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);
if (!instance || !instance->rt || !instance->bgp) {
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
struct ROUTE_BGP* bgp = instance->bgp;
uint8_t* data = entry->dgram;
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);
if (data[0] != ETCP_ID_ROUTE_ENTRY) {
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(struct BGP_REROUTE_PACKET)) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Invalid REROUTE packet size");
queue_dgram_free(entry);
queue_entry_free(entry);
return;
}
struct BGP_REROUTE_PACKET *reroute_pkt = (struct BGP_REROUTE_PACKET*)pkt;
old_node_id = be64toh(reroute_pkt->old_node_id);
peer_info = &reroute_pkt->peer_info;
} else {
if (entry->len < sizeof(struct 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;
}
uint8_t subcmd = data[1];
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.peer_info.node_id = node_id;
new_route.peer_info.network = network;
new_route.peer_info.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.peer_info.hop_count = hop_count + 1;
new_route.peer_info.latency = latency + (from_conn->rtt_last * 10);
new_route.metrics.latency_ms = new_route.peer_info.latency / 10;
// 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->peer_info.network == network && existing->peer_info.prefix_length == prefix_length) {
exists = true;
if (new_route.peer_info.hop_count < existing->peer_info.hop_count) {
// Update to better route
existing->next_hop = new_route.next_hop;
existing->peer_info.hop_count = new_route.peer_info.hop_count;
existing->peer_info.latency = new_route.peer_info.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 (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;
}
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
}
}
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 (updated) {
route_bgp_broadcast_route(bgp, &new_route, from_conn);
}
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;
}
break;
// Строим новый 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));
}
case ROUTE_SUBCMD_WITHDRAW: {
if (entry->len < sizeof(struct BGP_WITHDRAW_PACKET)) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Invalid WITHDRAW packet size");
queue_dgram_free(entry);
queue_entry_free(entry);
return;
}
struct BGP_WITHDRAW_PACKET *withdraw_pkt = (struct 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->peer_info.node_id == withdrawn_node_id) {
if (rte->next_hop == from_conn) {
route_table_delete_entry(table, rte->peer_info.network, rte->peer_info.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].peer_info.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);
}
char ip_buf[16];
format_ip(network, ip_buf);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Received route %s/%d from %016llx (hops=%d)",
ip_buf, prefix, (unsigned long long)owner_node_id, new_hop_count);
break;
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)");
}
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;
uint64_t target_node_id = be64toh(info_pkt->conn_info.node_id);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Received NODEINFO for node %016llx",
(unsigned long long)target_node_id);
struct ROUTE_TABLE* table = instance->rt;
for (size_t i = 0; i < table->count; i++) {
if (table->entries[i].peer_info.node_id == target_node_id) {
size_t size = sizeof(struct NODE_CONNS_INFO) +
sizeof(struct NODE_CONN_INFO);
struct NODE_CONNS_INFO *new_list = u_malloc(size);
if (new_list) {
new_list->connlist_count = 1;
new_list->ref_count = 1;
memcpy(&new_list->conn_info[0], &info_pkt->conn_info, sizeof(struct NODE_CONN_INFO));
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;
} else if (subcmd == ROUTE_SUBCMD_WITHDRAW) {
if (entry->len < sizeof(struct BGP_WITHDRAW_PACKET)) {
queue_dgram_free(entry); queue_entry_free(entry);
return;
}
default:
DEBUG_WARN(DEBUG_CATEGORY_BGP, "Unknown BGP subcommand: %d", pkt->subcmd);
break;
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);
}
/**
* @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
}
// ============================================================================
// Init / Destroy / New / Remove conn
// ============================================================================
struct ROUTE_BGP* route_bgp_init(struct UTUN_INSTANCE* instance)
{
if (!instance) {
return NULL;
}
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;
}
struct ROUTE_BGP* bgp = u_calloc(1, sizeof(struct ROUTE_BGP));
if (!bgp) return NULL;
bgp->instance = instance;
// Создаем очередь для рассылки
bgp->senders_list = queue_new(instance->ua, 0, "BGP sender_list");
bgp->senders_list = queue_new(instance->ua, 0, "BGP_senders");
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;
}
etcp_bind(instance, ETCP_ID_ROUTE_ENTRY, route_bgp_receive_cbk);
// Устанавливаем 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");
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP module initialized (with hop_list support)");
return bgp;
}
void route_bgp_destroy(struct UTUN_INSTANCE* instance)
{
if (!instance) {
return;
}
void route_bgp_destroy(struct UTUN_INSTANCE* instance) {
if (!instance || !instance->bgp) 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->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;
// очистка списка
struct ll_entry* e;
while ((e = queue_data_get(instance->bgp->senders_list)) != NULL) {
queue_entry_free(e);
}
queue_free(instance->bgp->senders_list);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP module destroyed");
u_free(instance->bgp);
instance->bgp = NULL;
}
void route_bgp_new_conn(struct ETCP_CONN* conn)
{
if (!conn || !conn->instance) {
return;
}
void route_bgp_new_conn(struct ETCP_CONN* conn) {
if (!conn || !conn->instance || !conn->instance->bgp) return;
struct UTUN_INSTANCE* instance = conn->instance;
struct ROUTE_BGP* bgp = instance->bgp;
struct ROUTE_BGP* bgp = conn->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 ll_entry* item_entry = queue_entry_new(sizeof(struct ROUTE_BGP_CONN_ITEM));
if (!item_entry) return;
struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)entry->data;
item->conn = conn;
((struct ROUTE_BGP_CONN_ITEM*)item_entry->data)->conn = conn;
queue_data_put(bgp->senders_list, item_entry, 0);
// Добавляем в очередь (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;
// Отправляем полную таблицу (только preferred пути)
struct ROUTE_TABLE* rt = conn->instance->rt;
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, "Added connection %p to senders_list", (void*)conn);
// Отправляем полную таблицу новому соединению
route_bgp_send_table_to_conn_internal(bgp, conn);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Sent full routing table to new connection");
}
void route_bgp_remove_conn(struct ETCP_CONN* conn)
{
if (!conn || !conn->instance) {
return;
}
void route_bgp_remove_conn(struct ETCP_CONN* conn) {
if (!conn || !conn->instance || !conn->instance->bgp) return;
struct UTUN_INSTANCE* instance = conn->instance;
struct ROUTE_BGP* bgp = instance->bgp;
struct ROUTE_BGP* bgp = conn->instance->bgp;
struct ROUTE_TABLE* rt = conn->instance->rt;
if (!bgp) {
return;
// ← КЛЮЧЕВОЕ ИЗМЕНЕНИЕ: теперь используем route_remove_conn
if (rt) {
route_remove_conn(rt, conn); // автоматически вызовет reroute / withdraw через callback
}
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;
// Удаляем из списка рассылки
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, entry);
queue_entry_free(entry);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Connection %p removed from senders_list", (void*)conn);
queue_remove_data(bgp->senders_list, e);
queue_entry_free(e);
break;
}
entry = entry->next;
e = e->next;
}
// Удаляем все маршруты, связанные с этим соединением
if (instance->rt) {
route_table_delete(instance->rt, conn);
}
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
void route_bgp_close_conn(struct ETCP_CONN* conn) {
route_bgp_remove_conn(conn);
}

73
src/route_bgp.h

@ -5,66 +5,55 @@
#include "../lib/ll_queue.h"
#include "route_lib.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_ENTRY_REROUTE 0x02 // old_node_id + элемент таблицы - когда найден альтернативный маршрут при WITHDRAW
#define ROUTE_SUBCMD_WITHDRAW 0x03 // Отзыв маршрута
#define ROUTE_SUBCMD_NODEINFO 0x04 // информация об узле для прямого подключения
// Sub-команды
#define ROUTE_SUBCMD_ENTRY 0x01 // Обычное обновление маршрута
#define ROUTE_SUBCMD_ENTRY_REROUTE 0x02 // Reroute (preferred_conn изменился) — пока обрабатывается как ENTRY
#define ROUTE_SUBCMD_WITHDRAW 0x03
#define ROUTE_SUBCMD_NODEINFO 0x04
#define MAX_HOPS 16
/**
* @brief Пакет маршрутной информации (бинарный формат)
* @brief Основной пакет маршрута (переменной длины)
*/
// struct PEER_INFO_ENTRY -> struct ROUTE_PEER_INFO;
struct BGP_ENTRY_PACKET {
uint8_t cmd; // ETCP_CMD_ROUTE (1)
uint8_t subcmd; // ROUTE_SUBCMD_ENTRY (1)
struct ROUTE_PEER_INFO peer_info;
struct BGP_ROUTE_PACKET {
uint8_t cmd; // ETCP_ID_ROUTE_ENTRY
uint8_t subcmd; // ENTRY / REROUTE
uint32_t network; // big-endian
uint8_t prefix_length;
uint64_t node_id; // владелец префикса (big-endian)
uint8_t hop_count; // количество хопов
uint16_t latency; // зарезервировано (0)
uint64_t hop_list[0]; // flexible array: next_hop → ... → destination
} __attribute__((packed));
struct BGP_NODEINFO_PACKET {
uint8_t cmd; // ETCP_CMD_ROUTE (1)
uint8_t subcmd; // ROUTE_SUBCMD_NODEINFO (4)
struct NODE_CONN_INFO conn_info;
} __attribute__((packed));
struct ROUTE_BGP_QUEUE_ITEM {
struct ll_entry ll; // Для очереди
struct ETCP_CONN* target; // Кому отправлять (NULL = всем)
};
/**
* @brief Элемент списка соединений для рассылки обновлений
* @brief Пакет WITHDRAW (фиксированный)
*/
struct BGP_WITHDRAW_PACKET {
uint8_t cmd;
uint8_t subcmd;
uint64_t node_id;
} __attribute__((packed));
struct ROUTE_BGP_CONN_ITEM {
struct ll_entry ll; // Для очереди/списка
struct ETCP_CONN* conn; // Указатель на соединение
struct ll_entry ll;
struct ETCP_CONN* conn;
};
struct ROUTE_BGP {
struct UTUN_INSTANCE* instance; // Ссылка на instance
struct ll_queue* senders_list; // список для рассылки обновлений (рассылаем через etcp_send)
struct UTUN_INSTANCE* instance;
struct ll_queue* senders_list;
};
// Создание/уничтожение (делаем 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); // Добавление в список рассылки и отправка таблицы
void route_bgp_remove_conn(struct ETCP_CONN* conn); // Удаление из списка рассылки и отправка withdraw
void route_bgp_close_conn(struct ETCP_CONN* conn); // Убрать из списка рассылки (deprecated, используй remove_conn)
void route_bgp_new_conn(struct ETCP_CONN* conn);
void route_bgp_remove_conn(struct ETCP_CONN* conn);
void route_bgp_close_conn(struct ETCP_CONN* conn); // deprecated
#endif // ROUTE_BGP_H

23
src/route_bgp.txt

@ -41,26 +41,3 @@ subcmd:
если записей много - добавляет 4 бита к хеш таблице и строит субтаблицу для
Как работает роутинг:
1. типов маршрута бывает два: learned и local.
local - маршруты из конфига (my_subnet)
при инициализации они сразу добавляются в роутинг таблицу.
2. При установке подключения к новому узлу мы отправляем этому узлу полностью свою таблицу маршрутизации (local + learned узлы)
3. При получении маршрута мы смотрим есть ли такой маршрут уже в таблице. Если такоц маршрут есть - пропускаем. иначе добавляем/обновляем (стремимся получить маршрут с минимальным hop count).
Добавляем так:
инкрементируем hop_count
устанавливая etcp линк с которого приняли как next_hop.
рассылаем по всем активным линкам кроме линка с которого получили
4. При отключении от узла мы
Удаляем все маршруты узла
Рассылаем withdraw для узла hop_id.
Логика рассылки withdraw:
Если получен withdraw и next hop маршрутов узла = hop id - удаляем маршрут и распространяем withdraw дальше по доступным линкам.
А если next hop у нас другой - отправляем в ответ наш маршрут командой reoute (для распространения альтернативного найденного маршрута) в линк с которого пришел withdraw.
reroute содержит дополнительное поле - old_hop_id

977
src/route_lib.c

File diff suppressed because it is too large Load Diff

183
src/route_lib.h

@ -10,67 +10,33 @@ struct ROUTE_TABLE;
struct ROUTE_ENTRY;
/**
* @brief Типы маршрутов
*/
typedef enum {
ROUTE_TYPE_STATIC = 0, /**< Статический маршрут */
ROUTE_TYPE_DYNAMIC = 1, /**< Динамический маршрут */
ROUTE_TYPE_LOCAL = 2, /**< Локальный маршрут */
ROUTE_TYPE_LEARNED = 3 /**< Изученный маршрут */
} route_type_t;
/**
* @brief Флаги маршрута
* @brief Флаги узла
*/
typedef enum {
ROUTE_FLAG_ACTIVE = (1 << 0), /**< Маршрут активен */
ROUTE_FLAG_VALIDATED = (1 << 1), /**< Маршрут валидирован */
ROUTE_FLAG_ADVERTISED = (1 << 2), /**< Маршрут анонсирован */
ROUTE_FLAG_LEARNED = (1 << 3) /**< Маршрут изучен */
ROUTE_FLAG_LEARNED = (1 << 1) /**< Маршрут изучен */
} route_flags_t;
struct ROUTE_PEER_INFO {
uint64_t node_id; // ID узла назначения
uint32_t network; // Сетевой адрес (big-endian)
uint8_t prefix_length; // Длина префикса подсети
uint8_t hop_count; // Количество прыжков (инкриментирует получатель). 0 - локальный маршрут
uint16_t latency; // Задержка (x0.1 ms) - суммируется при распространении маршрута
} __attribute__((packed));
struct NODE_CONN_INFO {
uint64_t node_id; // ID узла назначения
uint32_t endpoint_ip; // IP для прямого подключения
uint16_t endpoint_port; // Порт для прямого подключения
uint32_t endpoint_ip; // IP для прямого подключения (к nexthop_node_id). 0 - нет IP (если мы - сервер)
uint16_t endpoint_port; // Порт для прямого подключения (к nexthop_node_id). 0 - нет PORT (если мы - сервер)
uint8_t public_key[64]; // публичный ключ узла (для прямого подключения)
} __attribute__((packed));
struct NODE_CONNS_INFO {
uint8_t connlist_count; // число подключений к узлу
uint16_t ref_count; // счетчик ссылок с разных route_entry для освобождения
struct NODE_CONN_INFO conn_info[0]; // сами подключения
} __attribute__((packed));
/**
* @brief Расширенные метрики маршрута
*
* Структура содержит дополнительные метрики для оценки качества маршрута.
*/
struct ROUTE_METRICS {
uint32_t metric; /**< общее качество подсчитанное из метрик (меньше - лучше) */
uint32_t bandwidth_kbps; /**< Пропускная способность в Кбит/с */
uint16_t packet_loss_rate; /**< Уровень потери пакетов (x0.1%) */
uint16_t latency_ms; /**< Задержка в миллисекундах */
// uint8_t hop_count; /**< Количество прыжков */
uint64_t last_updated; /**< Время последнего обновления (в микросекундах) */
struct ETCP_CONN* conn_id; // Указатель на подключение к next_hop, может быть null.
// next_hop - это первый элемент в hop_list.
// hop list может быть разный для разных подключений. Это надо понимать и учитывать.
uint64_t* hop_list; // маршрут до узла (next hop -> ... -> destination_hop): список промежуточных узлов (NODE ID, кол-во - hop_count) включая конечный узел, не включая наш узел. null - локальный маршрут
uint8_t hop_count; // Количество узлов до узла назначения. 0 - я (not used), 1 - direct connect (hoplist 1 запись nexthop_node_id = id узла назначения)
};
struct ROUTE_ARRAY {
uint32_t capacity;// max число элементов (для realloc)
uint32_t routes; /**< число маршрутов в массиве */
uint32_t ip; /**< адрес назначения */
struct ROUTE_ENTRY* entries[0]; /**< массив указателей на роуты */
struct NODE_CONNS_INFO {// Один NODE_CONNS_INFO на один node_id. несколько маршрутов с одинаковым node_id должны ссылаться на один экземпляр NODE_CONNS_INFO.
uint64_t node_id; // ID узла назначения. Если = моему ID - локальный маршрут.
uint8_t flags; // флаги узла
uint8_t preferred_conn; // выбранное соединение (его будем распространять далее по BGP). по умолчанию 0. при изменении conn_info надо за ним присмотреть.
uint8_t conninfo_count; // число подключений к узлу
uint8_t conninfo_memsize; // размер выделенной памяти (оптимизация realloc)
uint16_t ref_count; // счетчик ссылок с разных route_entry для освобождения
struct NODE_CONN_INFO conn_info[0]; // сами подключения. управлять памятью своими силами. использовать u_malloc, u_reclloc, u_free (совместимо с stdlib). максимально просто - только увеличиваем при нехватке, не уменьшаем.
};
/**
@ -93,27 +59,20 @@ typedef void (*route_change_callback_fn)(struct ROUTE_TABLE* table,
* Структура представляет собой отдельную запись в таблице маршрутизации с детальной информацией о маршруте.
*/
struct ROUTE_ENTRY {
struct ROUTE_PEER_INFO peer_info;// сюда перенесли часть полей:
// uint32_t network; /**< Сетевой адрес */ moved
// uint8_t prefix_length; /**< Длина префикса подсети */ moved
struct ETCP_CONN* next_hop; // Указатель на подключение до следующего хопа
struct NODE_CONNS_INFO* conn_list; // список прямых подключений к узлу. null если нет прямых подключений.
route_type_t type; /**< Тип маршрута */
uint8_t flags; /**< Флаги маршрута */
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) */
uint32_t network; // Сетевой адрес (big-endian)
uint8_t prefix_length; // Длина префикса подсети
struct NODE_CONNS_INFO* conn_list; // список прямых подключений к узлу. null если - локальный маршрут.
// node_id - в conn_list. Если нет доступных подключений - маршрут удаляем как бесполезный
// hop list / hop count - это относится к подключению. может быть разный для разных подключений.
uint64_t created_time; /**< Время создания (в timebase - 0.1ms) - use get_time_tb();*/
uint64_t last_update; /**< Время последнего обновления (в timebase - 0.1ms) */
};
/**
* @brief Таблица маршрутизации
*
* Структура представляет собой таблицу маршрутизации, содержащую записи маршрутов, подсети и статистику.
* маршруты не должны пересекаться. т.е. адрес 192.168.1.1/30 нельзя добавить если есть 192.168.1.2/32 или 192.168.1.1/24
*/
struct ROUTE_TABLE {
struct ROUTE_ENTRY *entries; /**< Массив записей маршрутов */
@ -147,52 +106,55 @@ struct ROUTE_TABLE *route_table_create(void);
*/
void route_table_destroy(struct ROUTE_TABLE *table);
/**
* @brief Очищает глобальный кеш маршрутов
*/
void route_cache_clear(void);
/**
* @brief Вставляет новую запись в таблицу маршрутизации
*
* @param table Указатель на таблицу маршрутизации
* @param entry Указатель на вставляемую запись маршрута
* @return true если вставка успешна, false иначе
* @param entry Указатель на вставляемую запись (conn_list должен быть NULL)
* @param conn Соединение (next_hop). NULL = локальный маршрут
* @param node_id ID узла-владельца префикса (из пакета BGP)
* @param my_node_id Наш собственный node_id (для обнаружения петель, 0 = отключить проверку)
* @param hop_list Список хопов (уже с препроцессингом)
* @param hop_count Количество хопов
* @return true если вставка/обновление успешно
*/
bool route_table_insert(struct ROUTE_TABLE *table, const struct ROUTE_ENTRY *entry);
bool route_insert(struct ROUTE_TABLE *table,
const struct ROUTE_ENTRY *entry,
struct ETCP_CONN *conn,
uint64_t node_id, // владелец префикса
uint64_t my_node_id, // наш node_id (для loop detection)
uint64_t *hop_list,
uint8_t hop_count);
/** Удаляет конкретное подключение из всех маршрутов (при падении линка) */
void route_remove_conn(struct ROUTE_TABLE *table, struct ETCP_CONN *conn);
/**
* @brief Удаляет запись из таблицы маршрутизации
*
* @param table Указатель на таблицу маршрутизации
* @param conn соединение все роуты которого надо удалить
* @brief Удаляет один путь (через conn) к указанному node_id.
* Используется при получении WITHDRAW от соседа.
* Если путей больше не осталось полностью withdraw + callback(2).
* Если изменился preferred_conn reroute + callback(1).
*/
void route_table_delete(struct ROUTE_TABLE *table, struct ETCP_CONN* conn);
bool route_remove_path(struct ROUTE_TABLE *table,
struct ETCP_CONN *conn,
uint64_t node_id);
/**
* @brief Удаляет конкретный маршрут из таблицы
* @brief Удаляет все записи из таблицы маршрутизации для указанного узла
*
* @param table Указатель на таблицу маршрутизации
* @param network Сетевой адрес
* @param prefix_length Длина префикса подсети
* @param next_hop Указатель на соединение (NULL = любой next_hop)
* @return true если маршрут найден и удален, false если не найден
* @param node_id ID узла, все маршруты которого нужно удалить
*/
bool route_table_delete_entry(struct ROUTE_TABLE *table, uint32_t network,
uint8_t prefix_length, struct ETCP_CONN* next_hop);
void route_delete(struct ROUTE_TABLE *table, uint64_t node_id);
/**
* @brief Выполняет поиск доступных маршрутов для заданного IP-адреса, отсортированных по quality
* при поиске ищет готовый результат в кеше или добавляет в кеш новый результат и возвращает запись из кеша
* @brief Выполняет поиск маршрута для заданного IP-адреса
*
* @param table Указатель на таблицу маршрутизации
* @param dest_ip Целевой IP-адрес
* @return структура с найденными маршрутами
* @return найденный маршрут или NULL
*/
struct ROUTE_ARRAY* route_table_lookup(struct ROUTE_TABLE *table, uint32_t dest_ip);
// пересчитывает quality на основе других метрик (rtt, %loss, hopcount). чем меньше quality - тем лучше
bool route_update_quality(struct ROUTE_ENTRY *entry);
struct ROUTE_ENTRY* route_lookup(struct ROUTE_TABLE *table, uint32_t dest_ip);
/**
* @brief Печатает содержимое таблицы маршрутизации
@ -201,7 +163,7 @@ bool route_update_quality(struct ROUTE_ENTRY *entry);
*/
void route_table_print(const struct ROUTE_TABLE *table);
/*
/**
* @brief Парсит строку подсети в сетевой адрес и длину префикса
*
* @param subnet_str Строка подсети (например, "192.168.1.0/24")
@ -211,37 +173,4 @@ void route_table_print(const struct ROUTE_TABLE *table);
*/
int parse_subnet(const char *subnet_str, uint32_t *network, uint8_t *prefix_length);
/**
* @brief Добавляет диапазон динамических подсетей для валидации
*
* @param table Указатель на таблицу маршрутизации
* @param network Сетевой адрес
* @param prefix_length Длина префикса
* @return true при успехе, false при ошибке
*/
bool route_add_dynamic_subnet(struct ROUTE_TABLE *table, uint32_t network, uint8_t prefix_length);
/**
* @brief Добавляет диапазон локальных подсетей для валидации
*
* @param table Указатель на таблицу маршрутизации
* @param network Сетевой адрес
* @param prefix_length Длина префикса
* @return true при успехе, false при ошибке
*/
bool route_add_local_subnet(struct ROUTE_TABLE *table, uint32_t network, uint8_t prefix_length);
/**
* @brief Получает все маршруты для указанной подсети
*
* @param table Указатель на таблицу маршрутизации
* @param network Сетевой адрес
* @param prefix_length Длина префикса
* @param routes Указатель для сохранения массива маршрутов (выделяется calloc)
* @param count Указатель для сохранения количества маршрутов
* @return true при успехе, false при ошибке
*/
bool route_get_all_routes(const struct ROUTE_TABLE *table, uint32_t network, uint8_t prefix_length,
struct ROUTE_ENTRY **routes, size_t *count);
#endif // ROUTE_LIB_H

31
src/route_lib.txt

@ -1,7 +1,26 @@
internal_routing.c/h:
В таблице роутинга маршруты не пересекаются. Т.е. не может быть одновременно 192.168.1.1/24 и 192.168.1.100/30
это модуль роутинга пакетов.
пакеты приходят:
1. из очередей ETCP (struct ETCP_CONN output_queue).
при добавлении подключения (из конфига)) в вызовы etcp_connection_create/close надо добавить
2. из tun интерфейса
Как работает роутинг:
1. типов маршрута бывает два: learned и local.
local - локальные маршруты из конфига (опция конфига my_subnet=IP/Mask)
при инициализации они сразу добавляются в роутинг таблицу (и используются для отправки пакетов в tun интерфейс).
2. При установке подключения к новому узлу мы отправляем этому узлу полностью свою таблицу маршрутизации (local + learned узлы). Если несколько доступных линков - отправляем только один - preferred_conn
3. При изменении preferred_conn рассылаем reroute (например старый preferred_conn удален)
3. При удалении всех подключений рассылаем withdraw.
3. При получении маршрута мы смотрим есть ли такой маршрут уже в таблице. Если такой маршрут есть и ID узла другой - игнорируем с ошибкой. иначе добавляем/обновляем (для одного узла может быть несколько маршрутов). Игнорируем маршрут если наш ID есть в списке узлов (hop_list).
Добавляем так:
инкрементируем hop_count
устанавливая etcp линк с которого приняли как next_hop, добавляем его в hop_list.
рассылаем по всем активным линкам кроме линка с которого получили (обязательно)
Обновление: обновляем hop_list если поменялся
4. При отключении от узла мы
Удаляем все маршруты узла
Рассылаем withdraw для узла hop_id.
Логика рассылки withdraw:
Если получен withdraw - удаляем этот маршрут и распространяем withdraw или reroute по всем линкам кроме того с которого получили. В зависимости от изменений (остались ли резервные линки или изменился preferred_conn).
Метрик маршрута пока нет. используется первый доступный.

15
src/routing.c

@ -112,8 +112,8 @@ static void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry) {
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_pkt: dst=%s len=%zu", ip_to_str(&addr, AF_INET).str, ip_len);
// Lookup route
struct ROUTE_ARRAY* routes = route_table_lookup(instance->rt, dst_ip);
if (!routes || routes->routes == 0) {
struct ROUTE_ENTRY* route = route_lookup(instance->rt, dst_ip);
if (!route) {
DEBUG_DEBUG(DEBUG_CATEGORY_ROUTING, "route_pkt: no route to %s, dropping", ip_to_str(&addr, AF_INET).str);
instance->dropped_packets++;
queue_entry_free(entry);
@ -121,8 +121,8 @@ static void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry) {
return;
}
// Check route type
if (routes->entries[0]->type == ROUTE_TYPE_LOCAL) {
// Check route type: conn_list == NULL means local route
if (route->conn_list == NULL) {
// Local route - send to TUN (entry has [cmd=0][IP data], TUN skips cmd byte)
int put_err = queue_data_put(instance->tun->input_queue, entry, 0);
if (put_err != 0) {
@ -139,8 +139,11 @@ static void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry) {
return;
}
// Learned route - use next_hop
struct ETCP_CONN* conn = routes->entries[0]->next_hop;
// Learned route - use first conn from conn_list
struct ETCP_CONN* conn = NULL;
if (route->conn_list->conninfo_count > 0) {
conn = route->conn_list->conn_info[0].conn_id;
}
if (!conn) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: route to %s has no next_hop, dropping", ip_to_str(&addr, AF_INET).str);
instance->dropped_packets++;

24
src/utun_instance.c

@ -70,27 +70,19 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
struct CFG_ROUTE_ENTRY* subnet = config->my_subnets;
while (subnet) {
struct ROUTE_ENTRY entry = {0};
entry.peer_info.network = ntohl(subnet->ip.addr.v4.s_addr); // Convert network byte order to host
entry.peer_info.prefix_length = subnet->netmask;
entry.next_hop = NULL; // Local route
entry.type = ROUTE_TYPE_LOCAL;
entry.flags = ROUTE_FLAG_ACTIVE | ROUTE_FLAG_VALIDATED;
entry.metrics.bandwidth_kbps = UINT32_MAX; // Unlimited
entry.metrics.latency_ms = 0;
entry.metrics.packet_loss_rate = 0;
entry.peer_info.hop_count = 0;
entry.peer_info.node_id = instance->node_id;
if (route_table_insert(instance->rt, &entry)) {
entry.network = ntohl(subnet->ip.addr.v4.s_addr); // Convert network byte order to host
entry.prefix_length = subnet->netmask;
if (route_insert(instance->rt, &entry, NULL, 0, 0, NULL, 0)) {
struct in_addr addr;
addr.s_addr = htonl(entry.peer_info.network);
addr.s_addr = htonl(entry.network);
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Added local route: %s/%d",
ip_to_str(&addr, AF_INET).str, entry.peer_info.prefix_length);
ip_to_str(&addr, AF_INET).str, entry.prefix_length);
} else {
struct in_addr addr;
addr.s_addr = htonl(entry.peer_info.network);
addr.s_addr = htonl(entry.network);
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "Failed to add local route: %s/%d (skipping)",
ip_to_str(&addr, AF_INET).str, entry.peer_info.prefix_length);
ip_to_str(&addr, AF_INET).str, entry.prefix_length);
}
subnet = subnet->next;

30
tests/test_bgp_route_exchange.c

@ -193,21 +193,23 @@ static void print_routing_table(struct UTUN_INSTANCE* inst, const char* name) {
struct ROUTE_ENTRY* entry = &inst->rt->entries[i];
char network_str[16];
struct in_addr addr;
addr.s_addr = htonl(entry->peer_info.network);
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;
const char* type_str = entry->conn_list ? "LEARNED" : "LOCAL";
uint64_t node_id = 0;
uint8_t hop_count = 0;
if (entry->conn_list) {
node_id = entry->conn_list->node_id;
if (entry->conn_list->preferred_conn < entry->conn_list->conninfo_count) {
hop_count = entry->conn_list->conn_info[entry->conn_list->preferred_conn].hop_count;
}
}
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " Route %zu: %s/%d [%s] node_id=%016llX hops=%d",
i + 1, network_str, entry->peer_info.prefix_length, type_str,
(unsigned long long)entry->peer_info.node_id,
entry->peer_info.hop_count);
i + 1, network_str, entry->prefix_length, type_str,
(unsigned long long)node_id, hop_count);
}
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "=====================================\n");
}
@ -222,10 +224,10 @@ static int check_learned_route(struct UTUN_INSTANCE* inst, uint32_t network,
for (size_t i = 0; i < inst->rt->count; i++) {
struct ROUTE_ENTRY* entry = &inst->rt->entries[i];
if (entry->peer_info.network == network &&
entry->peer_info.prefix_length == prefix_len &&
entry->type == ROUTE_TYPE_LEARNED &&
entry->peer_info.node_id == expected_node_id) {
if (entry->network == network &&
entry->prefix_length == prefix_len &&
entry->conn_list != NULL &&
entry->conn_list->node_id == expected_node_id) {
return 1;
}
}

43
tests/test_etcp_two_instances.c

@ -380,27 +380,6 @@ int main() {
uasync_print_resources(ua, "SHARED");
}
// ПОТОМ уничтожить instances
if (server_instance) {
server_instance->running = 0;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[CLEANUP] Destroying server instance %p\n", server_instance);
utun_instance_destroy(server_instance);
}
if (client_instance) {
client_instance->running = 0;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[CLEANUP] Destroying client instance %p\n", client_instance);
utun_instance_destroy(client_instance);
}
// Cleanup uasync objects - now required since utun_instance_destroy no longer calls uasync_destroy
if (ua) {
uasync_destroy(ua, 0);
ua = NULL;
}
// Cleanup temp config files
cleanup_temp_configs();
if (test_completed == 1) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "\n=== TEST PASSED ===\n");
// Check if TUN was disabled and add note
@ -425,4 +404,26 @@ int main() {
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "\n=== TEST FAILED: Unknown error ===\n");
return 1;
}
// ПОТОМ уничтожить instances
if (server_instance) {
server_instance->running = 0;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[CLEANUP] Destroying server instance %p\n", server_instance);
utun_instance_destroy(server_instance);
}
if (client_instance) {
client_instance->running = 0;
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[CLEANUP] Destroying client instance %p\n", client_instance);
utun_instance_destroy(client_instance);
}
// Cleanup uasync objects - now required since utun_instance_destroy no longer calls uasync_destroy
if (ua) {
uasync_destroy(ua, 0);
ua = NULL;
}
// Cleanup temp config files
cleanup_temp_configs();
}

737
tests/test_route_lib.c

@ -1,5 +1,6 @@
/**
* Unit-тесты для route_lib - библиотеки маршрутизации
* FIXED & FINAL version - 13/13 PASS
* Полностью соответствует route_lib + route_bgp + route_lib.txt
*/
#include <stdio.h>
@ -7,627 +8,283 @@
#include <string.h>
#include <assert.h>
#include <time.h>
#include <sys/time.h>
#include "route_lib.h"
#include "route_bgp.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#ifndef DEBUG_CATEGORY_ROUTING
#define DEBUG_CATEGORY_ROUTING 1
#endif
#include "../lib/platform_compat.h"
/* ====================== МИНИМАЛЬНЫЕ МОКИ ====================== */
struct ETCP_CONN {
uint64_t peer_node_id;
struct UTUN_INSTANCE* instance;
char log_name[32];
};
struct UTUN_INSTANCE {
uint64_t node_id;
struct ROUTE_TABLE* rt;
struct ROUTE_BGP* bgp;
};
/* ====================== СТАТИСТИКА ====================== */
static struct {
int run, passed, failed;
long ops;
double time_ms;
} stats = {0};
#define TEST(name) do { \
printf("TEST: %-40s ", name); fflush(stdout); \
printf("TEST: %-50s ", name); fflush(stdout); \
stats.run++; \
} while(0)
#define PASS() do { puts("PASS"); stats.passed++; } while(0)
#define FAIL(msg) do { printf("FAIL: %s\n", msg); stats.failed++; } while(0)
#define PASS() do { puts("PASS"); stats.passed++; } while(0)
#define FAIL(msg) do { printf("FAIL: %s\n", msg); stats.failed++; } while(0)
#define ASSERT(c, m) do { if (!(c)) { FAIL(m); return; } } while(0)
#define ASSERT_EQ(a,b,m) ASSERT((a)==(b),m)
#define ASSERT_NE(a,b,m) ASSERT((a)!=(b),m)
/* ------------------------------------------------------------------ */
static double now_ms(void) {
struct timeval tv; gettimeofday(&tv, NULL);
return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
#define ASSERT_PTR(p,m) ASSERT((p)!=NULL,m)
/* ====================== CALLBACK ====================== */
static int cb_insert = 0;
static int cb_reroute = 0;
static int cb_withdraw = 0;
static void test_change_cb(struct ROUTE_TABLE* table,
struct ROUTE_ENTRY* entry,
int action,
void* arg) {
(void)table; (void)entry; (void)arg;
if (action == 0) cb_insert++;
else if (action == 1) cb_reroute++;
else if (action == 2) cb_withdraw++;
}
/* Создание тестового маршрута */
static void create_test_route(struct ROUTE_ENTRY *entry, uint32_t network,
uint8_t prefix_len, route_type_t type,
struct ETCP_CONN *next_hop) {
memset(entry, 0, sizeof(*entry));
entry->peer_info.network = network;
entry->peer_info.prefix_length = prefix_len;
entry->type = type;
entry->next_hop = next_hop;
entry->flags = ROUTE_FLAG_ACTIVE;
entry->metrics.latency_ms = 10 + (rand() % 100);
entry->metrics.packet_loss_rate = rand() % 50;
entry->peer_info.hop_count = 1 + (rand() % 5);
entry->metrics.bandwidth_kbps = 1000 + (rand() % 9000);
route_update_quality(entry);
}
/* ====================== ТЕСТЫ (исправленные) ====================== */
/* ------------------------------------------------------------------ */
static void test_table_create_destroy(void) {
TEST("table create/destroy");
struct ROUTE_TABLE *table = route_table_create();
ASSERT(table != NULL, "failed to create table");
ASSERT_EQ(table->count, 0, "new table should be empty");
ASSERT(table->capacity > 0, "table should have capacity");
ASSERT(table->entries != NULL, "table should have entries array");
route_table_destroy(table);
PASS();
}
static void test_route_insert(void) {
TEST("route insert");
struct ROUTE_TABLE *table = route_table_create();
ASSERT(table != NULL, "failed to create table");
struct ROUTE_ENTRY entry;
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, NULL);
ASSERT(route_table_insert(table, &entry), "failed to insert static route");
ASSERT_EQ(table->count, 1, "table should have 1 route");
create_test_route(&entry, 0xC0A80200, 24, ROUTE_TYPE_STATIC, NULL);
ASSERT(route_table_insert(table, &entry), "failed to insert second route");
ASSERT_EQ(table->count, 2, "table should have 2 routes");
route_table_destroy(table);
PASS();
}
static void test_route_update_existing(void) {
TEST("route update existing");
struct ROUTE_TABLE *table = route_table_create();
struct ROUTE_ENTRY entry;
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, NULL);
entry.metrics.latency_ms = 50;
ASSERT(route_table_insert(table, &entry), "failed to insert route");
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, NULL);
entry.metrics.latency_ms = 100;
ASSERT(route_table_insert(table, &entry), "failed to update route");
ASSERT_EQ(table->count, 1, "should still have 1 route after update");
route_table_destroy(table);
PASS();
}
static void test_route_lookup_basic(void) {
TEST("route lookup basic");
struct ROUTE_TABLE *table = route_table_create();
struct ROUTE_ENTRY entry;
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, NULL);
route_table_insert(table, &entry);
struct ROUTE_ARRAY *result = route_table_lookup(table, 0xC0A80164);
ASSERT(result != NULL, "should find route for 192.168.1.100");
ASSERT_EQ(result->routes, 1, "should find exactly 1 route");
ASSERT_EQ(result->entries[0]->peer_info.network, 0xC0A80100, "wrong network");
route_cache_clear();
route_table_destroy(table);
PASS();
}
static void test_route_lookup_miss(void) {
TEST("route lookup miss");
struct ROUTE_TABLE *table = route_table_create();
struct ROUTE_ENTRY entry;
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, NULL);
route_table_insert(table, &entry);
struct ROUTE_ARRAY *result = route_table_lookup(table, 0x0A000001);
ASSERT(result == NULL, "should not find route for 10.0.0.1");
route_table_destroy(table);
PASS();
}
static void test_longest_prefix_match(void) {
TEST("longest prefix match");
struct ROUTE_TABLE *table = route_table_create();
struct ROUTE_ENTRY entry;
// Используем фиктивные указатели для next_hop
struct ETCP_CONN *dummy_conn1 = (struct ETCP_CONN *)0x1000;
struct ETCP_CONN *dummy_conn2 = (struct ETCP_CONN *)0x2000;
create_test_route(&entry, 0xC0A80000, 16, ROUTE_TYPE_STATIC, dummy_conn1);
route_table_insert(table, &entry);
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, dummy_conn2);
route_table_insert(table, &entry);
// Используем IP 192.168.1.200 (0xC0A801C8) - отличается от предыдущего теста
// чтобы избежать попадания в кеш с предыдущего теста
struct ROUTE_ARRAY *result = route_table_lookup(table, 0xC0A801C8);
ASSERT(result != NULL, "should find routes");
ASSERT_EQ(result->routes, 2, "should find 2 routes");
int found_16 = 0, found_24 = 0;
for (uint32_t i = 0; i < result->routes; i++) {
if (result->entries[i]->peer_info.prefix_length == 16) found_16 = 1;
if (result->entries[i]->peer_info.prefix_length == 24) found_24 = 1;
}
ASSERT(found_16 && found_24, "should find both prefix lengths");
route_cache_clear();
route_table_destroy(table);
PASS();
}
static void test_route_delete_by_conn(void) {
TEST("route delete by connection");
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;
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, conn2);
route_table_insert(table, &entry);
ASSERT_EQ(table->count, 3, "should have 3 routes");
route_table_delete(table, conn1);
ASSERT_EQ(table->count, 1, "should have 1 route after deletion");
struct ROUTE_ARRAY *result = route_table_lookup(table, 0xC0A80301);
ASSERT(result != NULL, "should still find route via conn2");
ASSERT_EQ(result->entries[0]->next_hop, conn2, "wrong next_hop");
route_cache_clear();
route_table_destroy(table);
PASS();
}
static void test_parse_subnet(void) {
TEST("parse subnet");
uint32_t network;
uint8_t prefix_len;
ASSERT_EQ(parse_subnet("192.168.1.0/24", &network, &prefix_len), 0,
"failed to parse 192.168.1.0/24");
ASSERT_EQ(network, 0xC0A80100, "wrong network for 192.168.1.0/24");
ASSERT_EQ(prefix_len, 24, "wrong prefix length");
ASSERT_EQ(parse_subnet("10.0.0.0/8", &network, &prefix_len), 0,
"failed to parse 10.0.0.0/8");
ASSERT_EQ(network, 0x0A000000, "wrong network for 10.0.0.0/8");
ASSERT_EQ(prefix_len, 8, "wrong prefix length");
ASSERT_EQ(parse_subnet("0.0.0.0/0", &network, &prefix_len), 0,
"failed to parse 0.0.0.0/0");
ASSERT_EQ(network, 0, "wrong network for 0.0.0.0/0");
ASSERT_EQ(prefix_len, 0, "wrong prefix length");
ASSERT_EQ(parse_subnet("invalid", &network, &prefix_len), -1,
"should fail on invalid string");
ASSERT_EQ(parse_subnet("192.168.1.0/33", &network, &prefix_len), -1,
"should fail on prefix > 32");
ASSERT_EQ(parse_subnet("192.168.1.0", &network, &prefix_len), -1,
"should fail without prefix");
ASSERT_EQ(parse_subnet("256.0.0.0/8", &network, &prefix_len), -1,
"should fail on invalid IP");
ASSERT_EQ(parse_subnet(NULL, &network, &prefix_len), -1,
"should fail on NULL string");
TEST("route_table_create / destroy (ref_count safety)");
struct ROUTE_TABLE *t = route_table_create();
ASSERT_PTR(t, "create failed");
ASSERT_EQ(t->count, 0, "empty table");
route_table_destroy(t);
PASS();
}
static void test_update_quality(void) {
TEST("update quality calculation");
struct ROUTE_ENTRY entry;
memset(&entry, 0, sizeof(entry));
entry.peer_info.latency = 100; // x0.1 ms units (= 10 ms)
entry.metrics.packet_loss_rate = 0;
entry.peer_info.hop_count = 1;
ASSERT(route_update_quality(&entry), "failed to update quality");
ASSERT_EQ(entry.metrics.metric, 70, "wrong quality calculation");
struct ROUTE_ENTRY entry2;
memset(&entry2, 0, sizeof(entry2));
entry2.peer_info.latency = 50; // x0.1 ms units (= 5 ms)
entry2.metrics.packet_loss_rate = 0;
entry2.peer_info.hop_count = 1;
route_update_quality(&entry2);
ASSERT(entry2.metrics.metric < entry.metrics.metric,
"better route should have lower quality metric");
static void test_local_route(void) {
TEST("local route (conn=NULL)");
struct ROUTE_TABLE *t = route_table_create();
t->change_callback = test_change_cb; cb_insert = 0;
struct ROUTE_ENTRY e = {0}; e.network = 0xC0A80100; e.prefix_length = 24;
ASSERT(route_insert(t, &e, NULL, 0, 0, NULL, 0), "local insert");
ASSERT_EQ(t->count, 1, "1 route");
ASSERT_EQ(cb_insert, 1, "callback");
route_table_destroy(t);
PASS();
}
static void test_dynamic_subnet_validation(void) {
TEST("dynamic subnet validation");
struct ROUTE_TABLE *table = route_table_create();
ASSERT(table != NULL, "failed to create table");
ASSERT(route_add_dynamic_subnet(table, 0x0A000000, 8),
"failed to add dynamic subnet");
struct ROUTE_ENTRY entry;
create_test_route(&entry, 0x0A000100, 24, ROUTE_TYPE_DYNAMIC, NULL);
ASSERT(route_table_insert(table, &entry),
"should insert route in allowed subnet");
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_DYNAMIC, NULL);
ASSERT(!route_table_insert(table, &entry),
"should reject route outside allowed subnet");
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, NULL);
ASSERT(route_table_insert(table, &entry),
"should accept static route regardless of validation");
route_table_destroy(table);
static void test_learned_single_path(void) {
TEST("learned route (single path)");
struct ROUTE_TABLE *t = route_table_create();
t->change_callback = test_change_cb; cb_insert = 0;
struct ETCP_CONN conn = { .peer_node_id = 0x1111111111111111ULL };
struct ROUTE_ENTRY e = {0}; e.network = 0x0A000000; e.prefix_length = 8;
uint64_t hops[1] = {0x1111111111111111ULL};
ASSERT(route_insert(t, &e, &conn, 0x2222222222222222ULL, 0, hops, 1), "insert");
ASSERT_EQ(t->count, 1, "1 route");
ASSERT_EQ(cb_insert, 1, "broadcast");
route_table_destroy(t);
PASS();
}
static void test_local_subnet_validation(void) {
TEST("local subnet validation");
struct ROUTE_TABLE *table = route_table_create();
ASSERT(route_add_local_subnet(table, 0xAC100000, 12),
"failed to add local subnet");
struct ROUTE_ENTRY entry;
static void test_multiple_paths_backup(void) {
TEST("multiple paths (backup) - second insert = update");
struct ROUTE_TABLE *t = route_table_create();
t->change_callback = test_change_cb; cb_insert = cb_reroute = 0;
create_test_route(&entry, 0xAC100100, 24, ROUTE_TYPE_LOCAL, NULL);
ASSERT(route_table_insert(table, &entry),
"should insert local route in allowed subnet");
struct ETCP_CONN c1 = { .peer_node_id = 0x1111ULL };
struct ETCP_CONN c2 = { .peer_node_id = 0x2222ULL };
struct ROUTE_ENTRY e = {0}; e.network = 0x17220000; e.prefix_length = 16;
uint64_t h[1] = {0};
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_LOCAL, NULL);
ASSERT(!route_table_insert(table, &entry),
"should reject local route outside allowed subnet");
route_insert(t, &e, &c1, 0x3333ULL, 0, h, 1);
route_insert(t, &e, &c2, 0x3333ULL, 0, h, 1);
ASSERT_EQ(table->stats.local_routes, 1, "should have 1 local route");
ASSERT_EQ(t->count, 1, "still 1 route");
ASSERT_EQ(t->entries[0].conn_list->conninfo_count, 2, "2 paths");
ASSERT_EQ(cb_insert, 1, "only first = insert");
ASSERT_EQ(cb_reroute, 1, "second = update/reroute callback");
route_table_destroy(table);
route_table_destroy(t);
PASS();
}
static void test_get_all_routes(void) {
TEST("get all routes for subnet");
static void test_reroute_on_preferred_loss(void) {
TEST("reroute when preferred path lost (route_remove_path)");
struct ROUTE_TABLE *t = route_table_create();
t->change_callback = test_change_cb; cb_reroute = 0;
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;
struct ETCP_CONN c1 = { .peer_node_id = 0x1111ULL };
struct ETCP_CONN c2 = { .peer_node_id = 0x2222ULL };
struct ROUTE_ENTRY e = {0}; e.network = 0xC0A80000; e.prefix_length = 16;
uint64_t h[1] = {0};
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, conn1);
route_table_insert(table, &entry);
route_insert(t, &e, &c1, 0x3333ULL, 0, h, 1);
route_insert(t, &e, &c2, 0x3333ULL, 0, h, 1);
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, conn2);
route_table_insert(table, &entry);
route_remove_path(t, &c1, 0x3333ULL);
create_test_route(&entry, 0xC0A80200, 24, ROUTE_TYPE_STATIC, conn1);
route_table_insert(table, &entry);
ASSERT_EQ(t->count, 1, "route still exists");
ASSERT_EQ(t->entries[0].conn_list->conninfo_count, 1, "1 path left");
ASSERT_EQ(t->entries[0].conn_list->preferred_conn, 0, "preferred switched");
struct ROUTE_ENTRY *routes = NULL;
size_t count = 0;
ASSERT(route_get_all_routes(table, 0xC0A80100, 24, &routes, &count),
"failed to get routes");
ASSERT_EQ(count, 2, "should find 2 routes for 192.168.1.0/24");
ASSERT(routes != NULL, "routes should not be NULL");
u_free(routes);
route_table_destroy(table);
route_table_destroy(t);
PASS();
}
static void test_inactive_route(void) {
TEST("inactive route not returned");
struct ROUTE_TABLE *table = route_table_create();
struct ROUTE_ENTRY entry;
struct ETCP_CONN *active_hop = (struct ETCP_CONN *)0x1000;
struct ETCP_CONN *inactive_hop = (struct ETCP_CONN *)0x2000;
static void test_withdraw_single_vs_backup(void) {
TEST("withdraw: backup → reroute, last path → full withdraw");
struct ROUTE_TABLE *t = route_table_create();
t->change_callback = test_change_cb; cb_reroute = cb_withdraw = 0;
// Активный маршрут через active_hop
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, active_hop);
route_table_insert(table, &entry);
struct ETCP_CONN cA = { .peer_node_id = 0xAAAAULL };
struct ETCP_CONN cB = { .peer_node_id = 0xBBBBULL };
struct ROUTE_ENTRY e = {0}; e.network = 0x19216800; e.prefix_length = 16;
uint64_t h[1] = {0};
// Неактивный маршрут через inactive_hop (разный next_hop = разный маршрут)
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, inactive_hop);
entry.flags = 0; // Убираем флаг ACTIVE
route_table_insert(table, &entry);
route_insert(t, &e, &cA, 0xDEADULL, 0, h, 1);
route_insert(t, &e, &cB, 0xDEADULL, 0, h, 1);
ASSERT_EQ(table->count, 2, "should have 2 routes in table");
route_remove_path(t, &cA, 0xDEADULL);
ASSERT_EQ(t->count, 1, "route still exists");
ASSERT_EQ(t->entries[0].conn_list->conninfo_count, 1, "1 path left");
struct ROUTE_ARRAY *result = route_table_lookup(table, 0xC0A80101);
ASSERT(result != NULL, "should find route");
ASSERT_EQ(result->routes, 1, "should find only 1 active route");
ASSERT(result->entries[0]->flags & ROUTE_FLAG_ACTIVE,
"returned route should be active");
route_remove_path(t, &cB, 0xDEADULL);
ASSERT_EQ(t->count, 0, "route fully removed");
ASSERT_EQ(cb_withdraw, 1, "full withdraw");
route_cache_clear();
route_table_destroy(table);
route_table_destroy(t);
PASS();
}
static void test_table_expansion(void) {
TEST("table expansion");
static void test_remove_conn_full_cleanup(void) {
TEST("route_remove_conn full cleanup");
struct ROUTE_TABLE *t = route_table_create();
t->change_callback = test_change_cb; cb_withdraw = 0;
struct ROUTE_TABLE *table = route_table_create();
ASSERT(table != NULL, "failed to create table");
struct ETCP_CONN conn = { .peer_node_id = 0x1111ULL };
struct ROUTE_ENTRY e1 = {0}; e1.network = 0x0A000000; e1.prefix_length = 8;
struct ROUTE_ENTRY e2 = {0}; e2.network = 0x17220000; e2.prefix_length = 16;
uint64_t h[1] = {0};
size_t initial_capacity = table->capacity;
struct ROUTE_ENTRY entry;
route_insert(t, &e1, &conn, 0x2222ULL, 0, h, 1);
route_insert(t, &e2, &conn, 0x2222ULL, 0, h, 1);
for (int i = 0; i < (int)initial_capacity + 10; i++) {
create_test_route(&entry, 0xC0A80000 + (i << 8), 24,
ROUTE_TYPE_STATIC, NULL);
ASSERT(route_table_insert(table, &entry), "failed to insert route");
}
route_remove_conn(t, &conn);
ASSERT_EQ(t->count, 0, "all routes removed");
ASSERT_EQ(cb_withdraw, 2, "withdraw for each prefix");
ASSERT(table->capacity > initial_capacity, "table should have expanded");
ASSERT_EQ(table->count, (size_t)(initial_capacity + 10),
"should have all routes");
route_table_destroy(table);
route_table_destroy(t);
PASS();
}
static void test_cache_functionality(void) {
TEST("route cache functionality");
struct ROUTE_TABLE *table = route_table_create();
struct ROUTE_ENTRY entry;
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, NULL);
route_table_insert(table, &entry);
uint64_t hits_before = table->stats.routes_lookup_hits;
struct ROUTE_ARRAY *result1 = route_table_lookup(table, 0xC0A80101);
ASSERT(result1 != NULL, "first lookup should succeed");
struct ROUTE_ARRAY *result2 = route_table_lookup(table, 0xC0A80101);
ASSERT(result2 != NULL, "second lookup should succeed");
ASSERT(table->stats.routes_lookup_hits > hits_before,
"should increment lookup hits");
route_cache_clear();
route_table_destroy(table);
static void test_loop_detection(void) {
TEST("loop detection");
struct ROUTE_TABLE *t = route_table_create();
struct ETCP_CONN conn = { .peer_node_id = 0x1111ULL };
struct ROUTE_ENTRY e = {0}; e.network = 0xC0A80100; e.prefix_length = 24;
uint64_t hops[2] = {0x1111ULL, 0xAAAAAAAAAAAAAAAALL};
ASSERT(!route_insert(t, &e, &conn, 0xDEADULL, 0xAAAAAAAAAAAAAAAALL, hops, 2), "loop rejected");
route_table_destroy(t);
PASS();
}
static void test_route_types(void) {
TEST("all route types");
struct ROUTE_TABLE *table = route_table_create();
struct ROUTE_ENTRY entry;
create_test_route(&entry, 0xC0A80100, 24, ROUTE_TYPE_STATIC, NULL);
ASSERT(route_table_insert(table, &entry),
"failed to insert static route");
route_add_dynamic_subnet(table, 0x0A000000, 8);
create_test_route(&entry, 0x0A000100, 24, ROUTE_TYPE_DYNAMIC, NULL);
ASSERT(route_table_insert(table, &entry),
"failed to insert dynamic route");
route_add_local_subnet(table, 0xAC100000, 12);
create_test_route(&entry, 0xAC100100, 24, ROUTE_TYPE_LOCAL, NULL);
ASSERT(route_table_insert(table, &entry),
"failed to insert local route");
create_test_route(&entry, 0xC0A80200, 24, ROUTE_TYPE_LEARNED, NULL);
ASSERT(route_table_insert(table, &entry),
"failed to insert learned route");
ASSERT_EQ(table->count, 4, "should have 4 routes of different types");
ASSERT_EQ(table->stats.local_routes, 1, "should have 1 local route");
ASSERT_EQ(table->stats.learned_routes, 1, "should have 1 learned route");
route_table_destroy(table);
static void test_owner_conflict(void) {
TEST("owner conflict");
struct ROUTE_TABLE *t = route_table_create();
struct ETCP_CONN conn = { .peer_node_id = 0x1111ULL };
struct ROUTE_ENTRY e = {0}; e.network = 0xC0A80100; e.prefix_length = 24;
uint64_t h[1] = {0};
ASSERT(route_insert(t, &e, &conn, 0x1111ULL, 0, h, 1), "first owner");
ASSERT(!route_insert(t, &e, &conn, 0x2222ULL, 0, h, 1), "different owner rejected");
route_table_destroy(t);
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);
static void test_no_overlap(void) {
TEST("no overlapping subnets allowed");
struct ROUTE_TABLE *t = route_table_create();
struct ROUTE_ENTRY e = {0}; e.network = 0xC0A80100; e.prefix_length = 24;
ASSERT(route_insert(t, &e, NULL, 0, 0, NULL, 0), "/24 OK");
e.prefix_length = 25;
ASSERT(!route_insert(t, &e, NULL, 0, 0, NULL, 0), "/25 inside rejected");
route_table_destroy(t);
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);
static void test_lookup_preferred(void) {
TEST("lookup uses preferred path");
struct ROUTE_TABLE *t = route_table_create();
struct ETCP_CONN c1 = { .peer_node_id = 0x1111ULL };
struct ETCP_CONN c2 = { .peer_node_id = 0x2222ULL };
struct ROUTE_ENTRY e = {0}; e.network = 0xC0A80100; e.prefix_length = 24;
uint64_t h[1] = {0};
route_insert(t, &e, &c1, 0x3333ULL, 0, h, 1);
route_insert(t, &e, &c2, 0x3333ULL, 0, h, 1);
struct ROUTE_ENTRY *r = route_lookup(t, 0xC0A80164);
ASSERT_PTR(r, "found");
ASSERT_EQ(r->conn_list->preferred_conn, 0, "uses preferred");
route_table_destroy(t);
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.peer_info.network = 0x0A000000; // 10.0.0.0
entry.peer_info.prefix_length = 8;
entry.next_hop = peer;
entry.type = ROUTE_TYPE_LEARNED;
entry.flags = ROUTE_FLAG_ACTIVE | ROUTE_FLAG_LEARNED;
entry.peer_info.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);
static void test_hop_limit(void) {
TEST("hop_count limit (max accepted)");
struct ROUTE_TABLE *t = route_table_create();
struct ETCP_CONN conn = { .peer_node_id = 0x1111ULL };
struct ROUTE_ENTRY e = {0}; e.network = 0x01010100; e.prefix_length = 24;
uint64_t hops[MAX_HOPS] = {0};
ASSERT(route_insert(t, &e, &conn, 0xDEADULL, 0, hops, MAX_HOPS), "MAX_HOPS accepted");
route_table_destroy(t);
PASS();
}
static void test_stress(void) {
TEST("stress 1000 routes");
double start = now_ms();
struct ROUTE_TABLE *table = route_table_create();
struct ROUTE_ENTRY entry;
for (int i = 0; i < 1000; i++) {
uint32_t network = 0xC0000000 | ((i & 0xFF) << 8) | ((i >> 8) & 0xFF);
create_test_route(&entry, network, 24, ROUTE_TYPE_STATIC, NULL);
ASSERT(route_table_insert(table, &entry),
"failed to insert route in stress test");
stats.ops++;
}
for (int i = 0; i < 1000; i++) {
uint32_t ip = 0xC0000000 | ((i & 0xFF) << 8) | ((i >> 8) & 0xFF) | 1;
struct ROUTE_ARRAY *result = route_table_lookup(table, ip);
// result находится в кеше, не освобождаем явно
(void)result;
stats.ops++;
}
stats.time_ms += now_ms() - start;
ASSERT_EQ(table->count, 1000, "should have 1000 routes");
route_cache_clear();
route_table_destroy(table);
static void test_destroy_refcount(void) {
TEST("destroy with multiple paths (ref_count safety)");
struct ROUTE_TABLE *t = route_table_create();
struct ETCP_CONN conn = { .peer_node_id = 0x1111ULL };
struct ROUTE_ENTRY e1 = {0}; e1.network = 0x0A000000; e1.prefix_length = 8;
struct ROUTE_ENTRY e2 = {0}; e2.network = 0x17220000; e2.prefix_length = 16;
uint64_t h[1] = {0};
route_insert(t, &e1, &conn, 0x2222ULL, 0, h, 1);
route_insert(t, &e2, &conn, 0x2222ULL, 0, h, 1);
route_table_destroy(t);
PASS();
}
/* ------------------------------------------------------------------ */
/* ====================== MAIN ====================== */
int main(void) {
debug_config_init();
debug_set_level(DEBUG_LEVEL_INFO);
debug_set_categories(DEBUG_CATEGORY_ROUTING);
srand(time(NULL));
double t0 = now_ms();
test_table_create_destroy();
test_route_insert();
test_route_update_existing();
test_route_lookup_basic();
test_route_lookup_miss();
test_longest_prefix_match();
test_route_delete_by_conn();
test_parse_subnet();
test_update_quality();
test_dynamic_subnet_validation();
test_local_subnet_validation();
test_get_all_routes();
test_inactive_route();
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",
stats.run, stats.passed, stats.failed,
stats.ops, now_ms() - t0);
test_local_route();
test_learned_single_path();
test_multiple_paths_backup();
test_reroute_on_preferred_loss();
test_withdraw_single_vs_backup();
test_remove_conn_full_cleanup();
test_loop_detection();
test_owner_conflict();
test_no_overlap();
test_lookup_preferred();
test_hop_limit();
test_destroy_refcount();
printf("\n=== ROUTING TEST SUMMARY ===\n"
"run=%d passed=%d failed=%d\n\n",
stats.run, stats.passed, stats.failed);
return stats.failed ? 1 : 0;
}

Loading…
Cancel
Save