Compare commits

...

26 Commits

Author SHA1 Message Date
Evgeny 189a93c71d fix cpu consume at idle 3 days ago
Evgeny fd815150f6 Control: harden socket against malformed data and buffer overflows 3 days ago
Evgeny c8bac5d322 1 5 days ago
Evgeny d733bcbf4d 1 5 days ago
Evgeny feb8024506 1 5 days ago
Evgeny c76646a39a 1 5 days ago
Evgeny 07e4f6f086 withdraw send fix 6 days ago
Evgeny 5050f9a8aa remove node after link down 6 days ago
Evgeny e47593cd77 1 6 days ago
Evgeny 5184181695 1 6 days ago
Evgeny b9f8930d1c 1 6 days ago
Evgeny 2d9fbd9911 1 6 days ago
Evgeny ceb94bee7a BGP: add table requesters to senders_list for broadcast propagation to further nodes 6 days ago
Evgeny 491f44e05e 1 6 days ago
Evgeny 2f97196ffd Fix: traces and null checks in etcp_send to debug/prevent core dump; better routing logs with dst 6 days ago
Evgeny 9a9a4ccf3c 1 6 days ago
Evgeny 2260e068f0 1 6 days ago
Evgeny d3c4b5f6e7 1 6 days ago
Evgeny 7ee3193dde 1 6 days ago
Evgeny 9628686d63 1 6 days ago
Evgeny a2e4a4b208 Merge: add local routes using hop_count==0 6 days ago
Evgeny 6938bfcb6a Routing: add local routes to table via hop_count==0 detection 6 days ago
Evgeny 9f13cf5459 route redesign + tests 6 days ago
Evgeny be6058c642 backup: before plan 1 fix for route_lib test (non-overlapping subnets) 6 days ago
Evgeny 27618217cc fix: update tests for new NODEINFO routing API 1 week ago
Evgeny a93e34f85f feat: transition to NODEINFO based routing system 1 week ago
  1. 18
      lib/ll_queue.h
  2. 5
      lib/u_async.c
  3. 111
      src/config_parser.c
  4. 10
      src/config_parser.h
  5. 83
      src/control_server.c
  6. 2
      src/etcp.c
  7. 21
      src/etcp_api.c
  8. 568
      src/route_bgp.c
  9. 91
      src/route_bgp.h
  10. 31
      src/route_bgp.txt
  11. 33
      src/route_lib.c
  12. 9
      src/route_lib.h
  13. 133
      src/route_node.c
  14. 93
      src/route_node.h
  15. 27
      src/routing.c
  16. 36
      src/utun_instance.c
  17. 7
      tests/Makefile.am
  18. 23
      tests/test_bgp_route_exchange.c
  19. 19
      tests/test_etcp_two_instances.c
  20. 259
      tests/test_route_lib.c
  21. 16
      tools/etcpmon/etcpmon_gui.c
  22. 4
      tools/etcpmon/etcpmon_gui.h
  23. 2
      tools/etcpmon/etcpmon_protocol.h
  24. 23
      utun.conf.sample

18
lib/ll_queue.h

@ -228,11 +228,29 @@ int queue_data_put(struct ll_queue* q, struct ll_entry* entry);
*/
int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry);
/**
* @brief Добавляет элемент в конец очереди с явным указанием смещения индекса для hash.
* @param q очередь
* @param entry элемент
* @param index_offset смещение индекса (node_id) в data[]
* @param index_size размер индекса (8 для uint64_t node_id)
* @return 0 успех, -1 ошибка
*/
int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry,
uint16_t index_offset, uint16_t index_size);
/**
* @brief Добавляет элемент в начало очереди с явным указанием смещения индекса для hash.
* @param q очередь
* @param entry элемент
* @param index_offset смещение индекса в data[]
* @param index_size размер индекса
* @return 0 успех, -1 ошибка
*/
int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry,
uint16_t index_offset, uint16_t index_size);
/**
* @brief Извлекает элемент из начала очереди.
* @param q очередь

5
lib/u_async.c

@ -867,13 +867,10 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
req_timeout.tv_usec = (timeout_tb % 10000) * 100;
}
// Use minimum of requested and next timer if both finite
struct timeval poll_timeout;
if (timeout_tb < 0) {
// Infinite requested - use next timer if any
poll_timeout = next_timeout;
} else {
// Finite requested - min of requested and next
if (next_timeout.tv_sec < req_timeout.tv_sec ||
(next_timeout.tv_sec == req_timeout.tv_sec && next_timeout.tv_usec < req_timeout.tv_usec)) {
poll_timeout = next_timeout;
@ -881,6 +878,8 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
poll_timeout = req_timeout;
}
}
if (poll_timeout.tv_sec == 0 && poll_timeout.tv_usec == 0 && timeout_tb > 0) poll_timeout = req_timeout;
int timeout_ms;
if (timeout_tb < 0 && (next_timeout.tv_sec > 0 || next_timeout.tv_usec > 0)) {

111
src/config_parser.c

@ -30,7 +30,8 @@ typedef enum {
SECTION_CLIENT,
SECTION_ROUTING,
SECTION_DEBUG,
SECTION_FIREWALL
SECTION_FIREWALL,
SECTION_CONTROL
} section_type_t;
static char* trim(char *str) {
@ -258,6 +259,51 @@ static int parse_firewall_rule(const char *rule_str, struct global_config *globa
return 0;
}
static int parse_control_allow(const char *rule_str, struct global_config *global) {
if (!rule_str || !global) return -1;
char rule_copy[64];
strncpy(rule_copy, rule_str, sizeof(rule_copy) - 1);
rule_copy[sizeof(rule_copy) - 1] = '\0';
trim(rule_copy);
uint8_t cidr = 32;
char *slash = strchr(rule_copy, '/');
if (slash) {
*slash = '\0';
cidr = (uint8_t)atoi(slash + 1);
if (cidr > 32) cidr = 32;
}
struct in_addr addr;
if (inet_pton(AF_INET, rule_copy, &addr) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "parse_control_allow: invalid IP: %s", rule_str);
return -1;
}
uint32_t ip = ntohl(addr.s_addr);
uint32_t mask = (cidr == 32) ? 0xFFFFFFFF : (~0U << (32 - cidr));
if ((global->control_allow_count % INITIAL_ARRAY_CAPACITY) == 0) {
int new_cap = global->control_allow_count + INITIAL_ARRAY_CAPACITY;
struct CFG_CONTROL_ALLOW *new_allows = u_realloc(global->control_allows,
new_cap * sizeof(struct CFG_CONTROL_ALLOW));
if (!new_allows) {
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to realloc control allows");
return -1;
}
global->control_allows = new_allows;
}
struct CFG_CONTROL_ALLOW *rule = &global->control_allows[global->control_allow_count];
rule->network = ip & mask;
rule->netmask = mask;
global->control_allow_count++;
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Added control_allow: %s/%u", rule_str, (unsigned)cidr);
return 0;
}
static struct CFG_SERVER* find_server_by_name(struct CFG_SERVER *servers, const char *name) {
struct CFG_SERVER *srv = servers;
while (srv) {
@ -303,29 +349,6 @@ static int parse_global(const char *key, const char *value, struct global_config
global->keepalive_interval = atoi(value);
return 0;
}
if (strcmp(key, "control_ip") == 0) {
// Store control_ip for later processing with control_port
strncpy(global->control_ip, value, sizeof(global->control_ip) - 1);
global->control_ip[sizeof(global->control_ip) - 1] = '\0';
return 0;
}
if (strcmp(key, "control_port") == 0) {
// Use control_ip from config, fallback to localhost if not set
char control_ip[MAX_ADDR_LEN];
if (global->control_ip[0] != '\0') {
strncpy(control_ip, global->control_ip, sizeof(control_ip) - 1);
} else {
strcpy(control_ip, "127.0.0.1");
}
char port_str[16];
snprintf(port_str, sizeof(port_str), "%s", value);
parse_sockaddr(control_ip, port_str, &global->control_sock);
return 0;
}
if (strcmp(key, "net_debug") == 0) {
global->net_debug = atoi(value);
}
if (strcmp(key, "debug_level") == 0) {
return assign_string(global->debug_level, sizeof(global->debug_level), value);
}
@ -356,6 +379,30 @@ static int parse_global(const char *key, const char *value, struct global_config
return 0;
}
static int parse_control(const char *key, const char *value, struct global_config *global) {
if (strcmp(key, "ip") == 0 || strcmp(key, "control_ip") == 0) {
strncpy(global->control_ip, value, sizeof(global->control_ip) - 1);
global->control_ip[sizeof(global->control_ip) - 1] = '\0';
return 0;
}
if (strcmp(key, "port") == 0 || strcmp(key, "control_port") == 0) {
char control_ip[MAX_ADDR_LEN];
if (global->control_ip[0] != '\0') {
strncpy(control_ip, global->control_ip, sizeof(control_ip) - 1);
} else {
strcpy(control_ip, "127.0.0.1");
}
char port_str[16];
snprintf(port_str, sizeof(port_str), "%s", value);
parse_sockaddr(control_ip, port_str, &global->control_sock);
return 0;
}
if (strcmp(key, "allow") == 0 || strcmp(key, "control_allow") == 0) {
return parse_control_allow(value, global);
}
return 0;
}
static int parse_server(const char *key, const char *value, struct CFG_SERVER *srv) {
if (strcmp(key, "addr") == 0) {
return parse_address_and_port(value, &srv->ip);
@ -455,6 +502,7 @@ static section_type_t parse_section_header(const char *line, char *name, size_t
if (strcasecmp(section, "routing") == 0) return SECTION_ROUTING;
if (strcasecmp(section, "debug") == 0) return SECTION_DEBUG;
if (strcasecmp(section, "firewall") == 0) return SECTION_FIREWALL;
if (strcasecmp(section, "control") == 0) return SECTION_CONTROL;
char *colon = strchr(section, ':');
if (!colon) return SECTION_UNKNOWN;
@ -485,6 +533,8 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
cfg->global.firewall_rules = NULL;
cfg->global.firewall_rule_count = 0;
cfg->global.firewall_bypass_all = 0;
cfg->global.control_allows = NULL;
cfg->global.control_allow_count = 0;
section_type_t cur_section = SECTION_UNKNOWN;
struct CFG_SERVER *cur_server = NULL;
@ -583,6 +633,11 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
}
}
break;
case SECTION_CONTROL:
if (parse_control(key, value, &cfg->global) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid control key '%s'", filename, line_num, key);
}
break;
default:
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key);
break;
@ -657,6 +712,8 @@ void free_config(struct utun_config *config) {
// Free firewall rules
u_free(config->global.firewall_rules);
// Free control allow rules (array allocated with realloc)
u_free(config->global.control_allows);
u_free(config);
}
@ -678,12 +735,12 @@ void print_config(const struct utun_config *cfg) {
const struct global_config *g = &cfg->global;
char ip_buffer[64];
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Global: priv_key=%s, pub_key=%s, node_id=%llx, tun_if=%s, tun_ip=%s, mtu=%d, keepalive=%d, test_mode=%d, net_debug=%d",
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Global: priv_key=%s, pub_key=%s, node_id=%llx, tun_if=%s, tun_ip=%s, mtu=%d, keepalive=%d, test_mode=%d",
g->my_private_key_hex, g->my_public_key_hex,
(unsigned long long)g->my_node_id,
g->tun_ifname[0] ? g->tun_ifname : "auto",
ip_to_string(&g->tun_ip, ip_buffer, sizeof(ip_buffer)),
g->mtu, g->keepalive_timeout, g->tun_test_mode, g->net_debug);
g->mtu, g->keepalive_timeout, g->tun_test_mode);
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Servers:");
struct CFG_SERVER *s = cfg->servers;
@ -741,6 +798,8 @@ void print_config(const struct utun_config *cfg) {
} else {
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " no rules");
}
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Control allows: %d rules (default deny all if 0)", g->control_allow_count);
}
int update_config_keys(const char *filename, const char *priv_key, const char *pub_key) {

10
src/config_parser.h

@ -66,6 +66,11 @@ struct CFG_FIREWALL_RULE {
uint8_t bypass; // 1 for allow=all (bypass all checks)
};
struct CFG_CONTROL_ALLOW {
uint32_t network; // IPv4 network in host byte order
uint32_t netmask; // netmask in host byte order (e.g. 0xffffff00 for /24)
};
struct global_config {
char name[16]; // Instance name
char my_private_key_hex[MAX_KEY_LEN];
@ -76,7 +81,6 @@ struct global_config {
int mtu;
struct sockaddr_storage control_sock;
char control_ip[MAX_ADDR_LEN]; // Control server IP address
int net_debug;
// Debug and logging configuration
char log_file[256]; // Path to log file (empty = stdout)
@ -101,6 +105,10 @@ struct global_config {
struct CFG_FIREWALL_RULE *firewall_rules;
int firewall_rule_count;
int firewall_bypass_all;
// Control server configuration ([control] section)
struct CFG_CONTROL_ALLOW *control_allows;
int control_allow_count;
};
struct utun_config {

83
src/control_server.c

@ -10,6 +10,7 @@
#include "etcp_connections.h"
#include "tun_if.h"
#include "route_lib.h"
#include "route_bgp.h"
#include "pkt_normalizer.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
@ -269,6 +270,24 @@ void control_server_shutdown(struct control_server* server) {
* Client Connection Handling
* ============================================================================ */
static int is_control_ip_allowed(const struct control_server* server, uint32_t client_ip) {
if (!server || !server->instance || !server->instance->config) {
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control IP check: no config available");
return 0;
}
const struct global_config *g = &server->instance->config->global;
if (g->control_allow_count == 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control connection denied (no allow rules) - add control_allow=IP/mask to [control] in config");
return 0;
}
for (int i = 0; i < g->control_allow_count; i++) {
const struct CFG_CONTROL_ALLOW *r = &g->control_allows[i];
if ((client_ip & r->netmask) == r->network) return 1;
}
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control connection denied from IP (not in allow list) - add control_allow=IP/mask to [control]");
return 0;
}
static void accept_callback(socket_t fd, void* arg) {
struct control_server* server = (struct control_server*)arg;
@ -317,6 +336,28 @@ static void accept_callback(socket_t fd, void* arg) {
fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);
}
#endif
/* Check allowed IP (default deny all) */
uint32_t client_ip = 0;
if (client_addr.ss_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)&client_addr;
client_ip = ntohl(sin->sin_addr.s_addr);
} else {
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "IPv6 not supported for control allow list");
#ifdef _WIN32
closesocket(client_fd);
#else
close(client_fd);
#endif
return;
}
if (!is_control_ip_allowed(server, client_ip)) {
#ifdef _WIN32
closesocket(client_fd);
#else
close(client_fd);
#endif
return;
}
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Accept...");
/* Check max clients */
@ -487,8 +528,11 @@ static void client_read_callback(socket_t fd, void* arg) {
}
client->recv_len += received;
/* Process messages */
if (client->recv_len > ETCPMON_MAX_MSG_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Control recv buffer overflow");
if (server) close_client(server, client);
return;
}
if (server) {
handle_client_data(server, client);
}
@ -560,6 +604,17 @@ static void handle_client_data(struct control_server* server, struct control_cli
while (client->recv_len >= sizeof(struct etcpmon_msg_header)) {
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)client->recv_buffer;
if (hdr->size == 0 || hdr->size > ETCPMON_MAX_MSG_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Invalid message size from client: %u", hdr->size);
close_client(server, client);
return;
}
if (hdr->type < ETCPMON_CMD_LIST_CONN || hdr->type > ETCPMON_CMD_DISCONNECT) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Invalid command type from client: 0x%02X", hdr->type);
close_client(server, client);
return;
}
/* Validate header */
if (etcpmon_validate_header(hdr) != 0) {
if (server->log_file) {
@ -605,6 +660,10 @@ static void handle_client_data(struct control_server* server, struct control_cli
}
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Client selected connection: %016llX",
(unsigned long long)cmd->peer_node_id);
} else {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Bad SELECT_CONN payload size %u", payload_size);
close_client(server, client);
return;
}
break;
@ -627,14 +686,9 @@ static void handle_client_data(struct control_server* server, struct control_cli
return;
default:
if (server->log_file) {
fprintf(server->log_file, "%llu: [ERROR] Unknown command from client: 0x%02X\n",
(unsigned long long)get_timestamp_ms(), hdr->type);
fflush(server->log_file);
}
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Unknown command from client: 0x%02X", hdr->type);
send_error(client, ETCPMON_ERR_INVALID_CMD, "Unknown command", req_seq);
break;
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Unknown command from client: 0x%02X", hdr->type);
close_client(server, client);
return;
}
/* Remove processed message from buffer */
@ -857,6 +911,15 @@ static void send_metrics(struct control_server* server, struct control_client* c
rsp->tun.rt_local = 0;
rsp->tun.rt_learned = 0;
}
/* BGP routing */
if (instance->bgp) {
rsp->tun.rt_bgp_senders = (uint32_t)queue_entry_count(instance->bgp->senders_list);
rsp->tun.rt_bgp_nodes = (uint32_t)queue_entry_count(instance->bgp->nodes);
} else {
rsp->tun.rt_bgp_senders = 0;
rsp->tun.rt_bgp_nodes = 0;
}
} else {
memset(&rsp->tun, 0, sizeof(rsp->tun));
}

2
src/etcp.c

@ -91,7 +91,7 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance, char* n
etcp->input_send_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, "input_send_q"); // Hash for send_q
etcp->input_wait_ack = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, "input_wait_ack"); // Hash for wait_ack
etcp->recv_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, "recv_q"); // Hash for send_q
etcp->ack_q = queue_new(instance->ua, 0, "ack_q");
etcp->ack_q = queue_new(instance->ua, INFLIGHT_INITIAL_HASH_SIZE, "ack_q");
etcp->inflight_pool = memory_pool_init(sizeof(struct INFLIGHT_PACKET));
etcp->io_pool = memory_pool_init(sizeof(struct ETCP_FRAGMENT));
etcp->optimal_inflight=10000;

21
src/etcp_api.c

@ -101,40 +101,31 @@ int etcp_unbind(struct UTUN_INSTANCE* inst, uint8_t id) {
}
int etcp_send(struct ETCP_CONN* conn, struct ll_entry* entry) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "etcp_send enter conn=%p entry=%p", conn, entry);
if (!conn) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: NULL connection");
return -1;
}
if (!entry) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: NULL entry");
return -1;
}
if (!conn->normalizer) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: Connection has no normalizer");
return -1;
}
struct PKTNORM* pn = conn->normalizer;
if (!pn->input) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: Normalizer has no input queue");
if (!pn || !pn->input) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: pn=%p or input null", pn);
return -1;
}
// Помещаем entry в очередь input normalizer
// queue_data_put забирает ownership entry
// DEBUG_TRACE(DEBUG_CATEGORY_NORMALIZER, "Before put to input");
int result = queue_data_put(pn->input, entry);
DEBUG_TRACE(DEBUG_CATEGORY_NORMALIZER, "After put to input");
DEBUG_TRACE(DEBUG_CATEGORY_NORMALIZER, "etcp_send after put result=%d", result);
if (result != 0) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_send: queue_data_put failed (queue full?)");
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_send: queue_data_put failed");
return -1;
}
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_send: Packet queued for sending to [%s]", conn->log_name);
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_send: queued to [%s]", conn->log_name);
return 0;
}

568
src/route_bgp.c

@ -2,95 +2,111 @@
* @file route_bgp.c
* @brief BGP-like обмен маршрутами исправленная версия под новую route_lib
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "../lib/platform_compat.h"
#include "route_node.h"
#include "route_lib.h"
#include "route_bgp.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "utun_instance.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"
#include "route_node.h"
#include "route_lib.h"
#include "route_bgp.h"
// ============================================================================
// Вспомогательные функции
// ============================================================================
// ============================================================================
// Отправка (только preferred_conn + hop_list)
// Вспомогательные функции
// ============================================================================
/* Old route packet sending function removed - using NODEINFO only now */
static void route_bgp_send_table_request(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn) {
if (!bgp || !conn) return;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Sending table request to %s", conn->log_name);
route_bgp_send_nodeinfo(bgp, conn);
struct BGP_ROUTE_REQUEST* req = u_calloc(1, sizeof(struct BGP_ROUTE_REQUEST));
if (!req) return;
req->cmd = ETCP_ID_ROUTE_ENTRY;
req->subcmd = ROUTE_SUBCMD_REQUEST_TABLE;
struct ll_entry* e = queue_entry_new(0);
if (!e) {
u_free(req);
return;
}
/* Old full table sending removed - now using NODEINFO */
e->dgram = (uint8_t*)req;
e->len = sizeof(struct BGP_ROUTE_REQUEST);
etcp_send(conn, e);
}
static void route_bgp_add_to_senders(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn);
static bool route_bgp_should_send_to(const struct NODEINFO_Q* nq, uint64_t target_id);
static void route_bgp_send_full_table(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn);
static void route_bgp_handle_request_table(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn);
static char* nodeinfo_format(const uint8_t* data, size_t len) {
if (!data || len < sizeof(struct BGP_NODEINFO_PACKET)) return NULL;
struct BGP_NODEINFO_PACKET* pkt = (struct BGP_NODEINFO_PACKET*)data;
struct NODEINFO* ni = &pkt->node;
uint64_t node_id = ni->node_id;
const uint8_t* dyn = data + sizeof(struct BGP_NODEINFO_PACKET);
size_t off = 0;
char name_buf[64] = {0};
size_t nl = ni->node_name_len;
if (nl > 0 && nl < 63 && off + nl < len - sizeof(struct BGP_NODEINFO_PACKET)) {
memcpy(name_buf, dyn + off, nl); off += nl;
}
off += ni->local_v4_sockets * sizeof(struct NODEINFO_IPV4_SOCKET);
off += ni->local_v6_sockets * sizeof(struct NODEINFO_IPV6_SOCKET);
char subs_buf[512] = {0};
if (ni->local_v4_subnets > 0) {
const struct NODEINFO_IPV4_SUBNET* subs = (const struct NODEINFO_IPV4_SUBNET*)(dyn + off);
size_t sl = 0;
for (uint8_t i = 0; i < ni->local_v4_subnets && sl < 400; i++) {
uint32_t addr = (subs[i].addr[0]<<24)|(subs[i].addr[1]<<16)|(subs[i].addr[2]<<8)|subs[i].addr[3];
char tmp[64]; snprintf(tmp,sizeof(tmp),"%d.%d.%d.%d/%u ",(addr>>24)&255,(addr>>16)&255,(addr>>8)&255,addr&255,subs[i].prefix_length);
strcat(subs_buf,tmp); sl += strlen(tmp);
}
}
int need = snprintf(NULL,0,"NODEINFO nid=%016llx ver=%u name=\"%s\" v4subs=\"%s\" v4s=%u v6s=%u v4subcnt=%u hop=%u", (unsigned long long)node_id,(unsigned)ni->ver,name_buf,subs_buf,(unsigned)ni->local_v4_sockets,(unsigned)ni->local_v6_sockets,(unsigned)ni->local_v4_subnets,(unsigned)ni->hop_count);
char* buf = u_malloc(need+1);
if (!buf) return NULL;
snprintf(buf,need+1,"NODEINFO nid=%016llx ver=%u name=\"%s\" v4subs=\"%s\" v4s=%u v6s=%u v4subcnt=%u hop=%u", (unsigned long long)node_id,(unsigned)ni->ver,name_buf,subs_buf,(unsigned)ni->local_v4_sockets,(unsigned)ni->local_v6_sockets,(unsigned)ni->local_v4_subnets,(unsigned)ni->hop_count);
return buf;
}
/* Old full table sending removed - now using NODEINFO broadcast now */
// ============================================================================
// Broadcast / Withdraw
// Broadcast / Withdraw: node_id - удаляемый узел, wd_source - от кого получена команда удалить
// ============================================================================
/* Old broadcast route function removed - using NODEINFO broadcast now */
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);
static void route_bgp_broadcast_withdraw(struct ROUTE_BGP* bgp, uint64_t node_id, uint64_t wd_source, struct ETCP_CONN* exclude) {
if (!bgp) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "bgp is NULL"); return; }
struct BGP_WITHDRAW_PACKET* pkt = u_calloc(1, sizeof(struct BGP_WITHDRAW_PACKET));
if (!pkt) return;
pkt->cmd = ETCP_ID_ROUTE_ENTRY;
pkt->subcmd = ROUTE_SUBCMD_WITHDRAW;
pkt->node_id = htobe64(node_id);
struct ll_entry* send_entry = queue_entry_new(0);
if (!send_entry) { u_free(pkt); return; }
send_entry->dgram = (uint8_t*)pkt;
send_entry->len = sizeof(struct BGP_WITHDRAW_PACKET);
struct ll_entry* e = bgp->senders_list->head;
pkt->node_id = node_id;
pkt->wd_source = wd_source;
struct ll_entry* e = bgp->senders_list ? bgp->senders_list->head : NULL;
while (e) {
struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)e->data;
if (item->conn != exclude) {
if (item && item->conn && 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;
copy->dgram = u_malloc(sizeof(*pkt));
if (copy->dgram) memcpy(copy->dgram, pkt, sizeof(*pkt));
copy->len = sizeof(*pkt);
etcp_send(item->conn, copy);
}
}
e = e->next;
}
queue_entry_free(send_entry);
u_free(pkt);
}
// ============================================================================
// Callback на изменение таблицы (insert / update / delete)
// ============================================================================
/* Old route change callback removed - now using NODEINFO updates */
// ============================================================================
// Приём пакетов
@ -128,12 +144,12 @@ static void route_bgp_receive_cbk(struct ETCP_CONN* from_conn, struct ll_entry*
from_conn->log_name, subcmd, entry->len);
if (subcmd == ROUTE_SUBCMD_NODEINFO) {
char* s = nodeinfo_format(data, entry->len); if (s) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s from %s", s, from_conn->log_name); u_free(s); }
route_bgp_process_nodeinfo(bgp, from_conn, data, entry->len);
} else if (subcmd == ROUTE_SUBCMD_WITHDRAW) {
route_bgp_process_withdraw(bgp, data, entry->len);
route_bgp_process_withdraw(bgp, from_conn, data, entry->len);
} else if (subcmd == ROUTE_SUBCMD_REQUEST_TABLE) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Received table request from %s", from_conn->log_name);
route_bgp_send_nodeinfo(bgp, from_conn);
route_bgp_handle_request_table(bgp, from_conn);
}
queue_dgram_free(entry);
@ -181,7 +197,8 @@ struct ROUTE_BGP* route_bgp_init(struct UTUN_INSTANCE* instance) {
return NULL;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_bgp_init: node_id=%016llx", (unsigned long long)instance->node_id);
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) {
@ -207,10 +224,27 @@ struct ROUTE_BGP* route_bgp_init(struct UTUN_INSTANCE* instance) {
return NULL;
}
// Build initial my_nodeinfo
if (route_bgp_build_my_nodeinfo(instance, bgp) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_init: build_my_nodeinfo failed");
int vc = 0;
struct CFG_ROUTE_ENTRY* s = instance->config->my_subnets;
while (s) {
if (s->ip.family == AF_INET) vc++;
s = s->next;
}
size_t d = vc * sizeof(struct NODEINFO_IPV4_SUBNET);
bgp->local_node = u_calloc(1, sizeof(struct NODEINFO_Q) + d);
if (!bgp->local_node) {
queue_free(bgp->nodes);
queue_free(bgp->senders_list);
u_free(bgp);
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_init: local_node alloc failed");
return NULL;
}
bgp->local_node->node.node_id = instance->node_id;
bgp->local_node->node.hop_count = 0;
bgp->local_node->node.ver = 1;
bgp->local_node->dirty = 0;
route_bgp_update_my_nodeinfo(instance, bgp);
etcp_bind(instance, ETCP_ID_ROUTE_ENTRY, route_bgp_receive_cbk);
@ -231,25 +265,26 @@ void route_bgp_destroy(struct UTUN_INSTANCE* instance) {
return;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_bgp_destroy: node_id=%016llx", (unsigned long long)instance->node_id);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_bgp_destroy: node_id=%016llx",
(unsigned long long)instance->node_id);
etcp_unbind(instance, ETCP_ID_ROUTE_ENTRY);
// очистка списка senders
struct ll_entry* e;
while ((e = queue_data_get(instance->bgp->senders_list)) != NULL) {
queue_entry_free(e);
}
queue_free(instance->bgp->senders_list);
// очистка nodes
if (instance->bgp->nodes) {
queue_free(instance->bgp->nodes);
if (instance->bgp->local_node) {
if (instance->bgp->local_node->paths) {
queue_free(instance->bgp->local_node->paths);
}
u_free(instance->bgp->local_node);
}
// Free my_nodeinfo
if (instance->bgp->my_nodeinfo) {
u_free(instance->bgp->my_nodeinfo);
if (instance->bgp->nodes) {
queue_free(instance->bgp->nodes);
}
u_free(instance->bgp);
@ -275,32 +310,9 @@ void route_bgp_new_conn(struct ETCP_CONN* conn) {
}
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);
route_bgp_add_to_senders(bgp, conn);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "New connection added to senders_list");
}
// === 3. Отправляем запрос на получение таблицы (обе стороны при on_up) ===
route_bgp_send_table_request(bgp, conn);
}
@ -314,8 +326,9 @@ void route_bgp_remove_conn(struct ETCP_CONN* conn) {
return;
}
// SAFETY: проверяем что conn ещё есть в senders_list
struct ROUTE_BGP* bgp = conn->instance->bgp;
// SAFETY: проверяем что conn ещё есть в senders_list
bool found_in_list = false;
struct ll_entry* e = bgp->senders_list->head;
while (e) {
@ -332,17 +345,37 @@ void route_bgp_remove_conn(struct ETCP_CONN* conn) {
return;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_bgp_remove_conn: peer=%016llx", (unsigned long long)conn->peer_node_id);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "route_bgp_remove_conn: peer=%016llx",
(unsigned long long)conn->peer_node_id);
struct ROUTE_TABLE* rt = conn->instance->rt;
// Remove this connection from all nodes' path lists
// and send WITHDRAW if a node becomes unreachable
bool need_withdraw = false;
struct ll_entry* node_entry = bgp->nodes ? bgp->nodes->head : NULL;
while (node_entry) {
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)node_entry->data;
route_bgp_remove_path(nq, conn);
node_entry = node_entry->next;
struct ll_entry* next = node_entry->next;
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)node_entry;
if (route_bgp_remove_path(nq, conn) == 1) {
need_withdraw = true;
if (rt) {
route_delete(rt, nq);
}
nq->dirty = 1;
if (nq->paths) {
queue_free(nq->paths);
nq->paths = NULL;
}
uint64_t key = nq->node.node_id;
struct ll_entry* entry = node_entry;
if (entry) {
queue_remove_data(bgp->nodes, entry);
queue_entry_free(entry);
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Removed node %016llx after link down", (unsigned long long)key);
}
node_entry = next;
}
// Удаляем из списка рассылки
@ -357,81 +390,348 @@ void route_bgp_remove_conn(struct ETCP_CONN* conn) {
e = e->next;
}
if (need_withdraw) {
route_bgp_broadcast_withdraw(bgp, conn->peer_node_id, conn->instance->node_id, NULL);
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Connection removed, paths updated");
}
/* ================================================
* NEW NODEINFO BASED IMPLEMENTATION
* ================================================ */
struct NODEINFO_Q* route_bgp_get_node(struct ROUTE_BGP* bgp, uint64_t node_id) {
if (!bgp || !bgp->nodes) return NULL;
uint64_t key = htobe64(node_id);
struct ll_entry* e = queue_find_data_by_index(bgp->nodes, &key, 8);
return e ? (struct NODEINFO_Q*)e->data : NULL;
if (!bgp || !bgp->nodes) {
return NULL;
}
int route_bgp_add_path(struct NODEINFO_Q* nq, struct ETCP_CONN* conn) {
if (!nq || !conn) return -1;
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "Added path for node %016llx via conn %s",
(unsigned long long)nq->node.node_id, conn->log_name);
uint64_t key = node_id;
struct ll_entry* e = queue_find_data_by_index(bgp->nodes, &key, 8);
return e ? (struct NODEINFO_Q*)e : NULL;
}
int route_bgp_add_path(struct NODEINFO_Q* nq, struct ETCP_CONN* conn, uint64_t* hop_list, uint8_t hop_count) {
if (!nq || !conn || hop_count > MAX_HOPS || !hop_list) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "add_path: invalid args");
return -1;
}
if (!nq->paths) {
nq->paths = queue_new(conn->instance->ua, 0, "node_paths");
if (!nq->paths) return -1;
}
size_t hop_size = hop_count * 8;
size_t path_size = sizeof(struct NODEINFO_PATH) - sizeof(struct ll_entry) + hop_size;
struct ll_entry* pe = queue_entry_new(path_size);
if (!pe) return -1;
struct NODEINFO_PATH* path = (struct NODEINFO_PATH*)pe;
path->conn = conn;
path->hop_count = hop_count;
uint64_t* stored = (uint64_t*)((uint8_t*)path + sizeof(struct NODEINFO_PATH));
memcpy(stored, hop_list, hop_size);
queue_data_put(nq->paths, pe);
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "Added path for node %016llx via conn %s with %d hops", (unsigned long long)nq->node.node_id, conn->log_name, hop_count);
return 0;
}
int route_bgp_remove_path(struct NODEINFO_Q* nq, struct ETCP_CONN* conn) {
if (!nq || !conn) return -1;
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "Removed path for node %016llx via conn %s",
(unsigned long long)nq->node.node_id, conn->log_name);
int route_bgp_remove_path_by_hop(struct NODEINFO_Q* nq, uint64_t wd_source) {
if (!nq || !nq->paths) return 0;
int removed = 0;
struct ll_entry* e = nq->paths->head;
while (e) {
struct NODEINFO_PATH* path = (struct NODEINFO_PATH*)e;
// parse hoplist in dyn after hop_count (after fixed PATH)
uint64_t* hop = (uint64_t*)((uint8_t*)path + sizeof(struct NODEINFO_PATH));
bool has_wd = false;
for (uint8_t i = 0; i < path->hop_count; i++) {
if (hop[i] == wd_source) {
has_wd = true;
break;
}
}
if (has_wd) {
struct ll_entry* next = e->next;
queue_remove_data(nq->paths, e);
queue_entry_free(e);
removed++;
e = next;
continue;
}
e = e->next;
}
if (removed > 0 && nq->paths && queue_entry_count(nq->paths) == 0) {
queue_free(nq->paths);
nq->paths = NULL;
return 1; // unreachable
}
return removed;
}
int route_bgp_remove_path(struct NODEINFO_Q* nq, struct ETCP_CONN* conn)
{
if (!nq || !conn || !nq->paths) {
return -1;
}
// Remove conn from paths queue
struct ll_entry* e = nq->paths->head;
while (e) {
struct NODEINFO_PATH* path = (struct NODEINFO_PATH*)e;
if (path->conn == conn) {
queue_remove_data(nq->paths, e);
queue_entry_free(e);
break;
}
e = e->next;
}
int remaining = nq->paths ? queue_entry_count(nq->paths) : 0;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Removed path for node %016llx via %s, remaining paths: %d",
(unsigned long long)nq->node.node_id, conn->log_name, remaining);
// If no paths left - node is unreachable
if (remaining == 0) {
if (nq->paths) {
queue_free(nq->paths);
nq->paths = NULL;
}
return 1; // signal that node became unreachable
}
return 0;
}
int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from,
const uint8_t* data, size_t len) {
int nodeinfo_dyn_size(struct NODEINFO* node) {
return node->node_name_len +
node->local_v4_sockets * sizeof(struct NODEINFO_IPV4_SOCKET) +
node->local_v6_sockets * sizeof(struct NODEINFO_IPV6_SOCKET) +
node->local_v4_subnets * sizeof(struct NODEINFO_IPV4_SUBNET) +
node->local_v6_subnets * sizeof(struct NODEINFO_IPV6_SUBNET) +
node->tranzit_nodes * 8 +
node->hop_count * 8;
}
int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, const uint8_t* data, size_t len) {
if (!bgp || !from || len < sizeof(struct BGP_NODEINFO_PACKET)) return -1;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Processing NODEINFO from %s", from->log_name);
struct BGP_NODEINFO_PACKET* pkt = (struct BGP_NODEINFO_PACKET*)data;
struct NODEINFO* ni = &pkt->node;
int dyn_size=nodeinfo_dyn_size(ni);
if (len!=dyn_size + sizeof(struct BGP_NODEINFO_PACKET)) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "Incorrect packet size (%s)",from->log_name);
return -1;
}
uint64_t node_id = ni->node_id;
if (ni->hop_count >= MAX_HOPS) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "NODEINFO from %s dropped: too many hops (%d)",
from->log_name, ni->hop_count);
return -1;
}
struct NODEINFO_Q* nodeinfo1 = route_bgp_get_node(bgp, node_id);
uint8_t new_ver = ni->ver;
if (nodeinfo1 && (int8_t)(nodeinfo1->last_ver-new_ver)>=0) {
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "NODEINFO from %s ignored (old ver %d <= %d)",
from->log_name, new_ver, nodeinfo1->last_ver);
return 0;
}
int new_data_size=sizeof(struct NODEINFO_Q) - sizeof(struct ll_entry) + dyn_size + 8;
struct ll_queue* paths=NULL;
int need_alloc=0;
if (nodeinfo1) {// remove old node
paths=nodeinfo1->paths;
if (nodeinfo1->ll.size < new_data_size) {
need_alloc=1;
}
} else need_alloc=1;
if (need_alloc) {
if (nodeinfo1) {
queue_remove_data(bgp->nodes, &nodeinfo1->ll);
queue_entry_free(&nodeinfo1->ll);
}
nodeinfo1 = (struct NODEINFO_Q*)queue_entry_new(new_data_size);
paths = queue_new(bgp->instance->ua, 0, "node_paths");
memcpy(&nodeinfo1->node, ni, sizeof(struct NODEINFO) + dyn_size);
queue_data_put_with_index(bgp->nodes, &nodeinfo1->ll, offsetof(struct NODEINFO_Q, node.node_id)-sizeof(struct ll_entry), 8);
}
else memcpy(&nodeinfo1->node, ni, sizeof(struct NODEINFO) + dyn_size);
nodeinfo1->paths = paths;
nodeinfo1->last_ver = new_ver;
// add last hop
uint64_t* hop_list = (uint64_t*)((uint8_t*)&nodeinfo1->node + sizeof(struct NODEINFO) + dyn_size);
uint64_t sender = from->peer_node_id;
memcpy(hop_list, &sender, 8);
nodeinfo1->node.hop_count++;
// update node path
route_bgp_remove_path_by_hop(nodeinfo1, from->peer_node_id);
route_bgp_add_path(nodeinfo1, from, hop_list, nodeinfo1->node.hop_count);
if (bgp->instance->rt) {
route_insert(bgp->instance->rt, nodeinfo1);
}
int hop_count=nodeinfo1->node.hop_count;
// рассылаем по узлам которых нет в hop_list этой ноды (loop prevention)
struct ll_entry* e = bgp->senders_list ? bgp->senders_list->head : NULL;
while (e) {
struct ROUTE_BGP_CONN_ITEM* item = (struct ROUTE_BGP_CONN_ITEM*)e->data;
if (item->conn) {
uint64_t id=item->conn->peer_node_id;
int found=0;
for (int i=0; i<hop_count; i++) if (hop_list[i]==id) found=1;
if (found==0) {
route_bgp_send_nodeinfo(nodeinfo1, item->conn);
}
else DEBUG_INFO(DEBUG_CATEGORY_BGP, "Skip send NODEINFO to node %016llx",id);
}
e = e->next;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Processed NODEINFO from %s (node=%016llx,ver=%d,paths=%d)",
from->log_name, (unsigned long long)node_id, new_ver,
nodeinfo1->paths ? queue_entry_count(nodeinfo1->paths) : 0);
return 0;
}
int route_bgp_process_withdraw(struct ROUTE_BGP* bgp, const uint8_t* data, size_t len) {
int route_bgp_process_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* sender, const uint8_t* data, size_t len) {
if (!bgp || len < sizeof(struct BGP_WITHDRAW_PACKET)) return -1;
struct BGP_WITHDRAW_PACKET* wp = (struct BGP_WITHDRAW_PACKET*)data;
uint64_t node_id = be64toh(wp->node_id);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Processing WITHDRAW for node %016llx", (unsigned long long)node_id);
uint64_t node_id = wp->node_id;
uint64_t wd_source = wp->wd_source;
struct NODEINFO_Q* nq = route_bgp_get_node(bgp, node_id);
if (!nq) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "node not found");
return 0;
}
int ret=route_bgp_remove_path_by_hop(nq, wd_source);
if (ret>0 || !nq->paths || (nq->paths && queue_entry_count(nq->paths) == 0)) {
if (bgp->instance && bgp->instance->rt) {
route_delete(bgp->instance->rt, nq);
}
nq->dirty = 1;
if (nq->paths) {
queue_free(nq->paths);
nq->paths = NULL;
}
uint64_t key = node_id;
struct ll_entry* entry = queue_find_data_by_index(bgp->nodes, &key, 8);
if (entry) {
queue_remove_data(bgp->nodes, entry);
queue_entry_free(entry);
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Removed node %016llx after WITHDRAW", (unsigned long long)node_id);
route_bgp_broadcast_withdraw(bgp, node_id, wd_source, sender);
}
return 0;
}
void route_bgp_send_nodeinfo(struct NODEINFO_Q* node, struct ETCP_CONN* conn) {
if (!node || !conn) {
return;
}
void route_bgp_send_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn) {
if (!bgp || !conn || !bgp->my_nodeinfo) return;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Sending NODEINFO to %s", conn->log_name);
struct ll_entry* entry = queue_entry_new(0);
if (!entry) return;
int dyn = nodeinfo_dyn_size(&node->node);
entry->dgram = u_malloc(bgp->my_nodeinfo_size);
if (!entry->dgram) {
queue_entry_free(entry);
size_t ps = sizeof(struct BGP_NODEINFO_PACKET) + dyn;
uint8_t* p = u_malloc(ps);
if (!p) {
return;
}
memcpy(entry->dgram, bgp->my_nodeinfo, bgp->my_nodeinfo_size);
entry->len = bgp->my_nodeinfo_size;
etcp_send(conn, entry);
p[0] = ETCP_ID_ROUTE_ENTRY;
p[1] = ROUTE_SUBCMD_NODEINFO;
memcpy(p + 2, &node->node, sizeof(struct NODEINFO));
uint8_t* ds = (uint8_t*)&node->node + sizeof(struct NODEINFO);
memcpy(p + sizeof(struct BGP_NODEINFO_PACKET), ds, dyn);
struct ll_entry* e = queue_entry_new(0);
if (!e) {
u_free(p);
return;
}
void route_bgp_broadcast_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* exclude) {
if (!bgp) return;
DEBUG_TRACE(DEBUG_CATEGORY_BGP, "Broadcasting NODEINFO");
struct ll_entry* e = bgp->senders_list ? bgp->senders_list->head : NULL;
e->dgram = p;
e->len = ps;
etcp_send(conn, e);
}
static void route_bgp_add_to_senders(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn) {
if (!bgp || !conn || !bgp->senders_list) return;
bool already = 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 && item->conn != exclude) {
route_bgp_send_nodeinfo(bgp, item->conn);
if (((struct ROUTE_BGP_CONN_ITEM*)e->data)->conn == conn) { already = true; break; }
e = e->next;
}
if (!already) {
struct ll_entry* item_entry = queue_entry_new(sizeof(struct ROUTE_BGP_CONN_ITEM));
if (item_entry) {
((struct ROUTE_BGP_CONN_ITEM*)item_entry->data)->conn = conn;
queue_data_put(bgp->senders_list, item_entry);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Added to senders_list");
}
}
}
static bool route_bgp_should_send_to(const struct NODEINFO_Q* nq, uint64_t target_id) {
if (!nq || !nq->paths) return false;
struct ll_entry* e = nq->paths->head;
while (e) {
struct NODEINFO_PATH* path = (struct NODEINFO_PATH*)e;
uint64_t* hop = (uint64_t*)((uint8_t*)path + sizeof(struct NODEINFO_PATH));
bool has_id = false;
for (uint8_t i = 0; i < path->hop_count; i++) {
if (hop[i] == target_id) { has_id = true; break; }
}
if (!has_id) return true;
e = e->next;
}
return false;
}
void route_bgp_send_withdraw(struct ROUTE_BGP* bgp, uint64_t node_id) {
if (!bgp) return;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Sending WITHDRAW for node %016llx", (unsigned long long)node_id);
static void route_bgp_send_full_table(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn) {
if (!bgp || !conn) return;
uint64_t target = conn->peer_node_id;
struct ll_entry* e = bgp->nodes ? bgp->nodes->head : NULL;
while (e) {
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)e;
if (nq->node.hop_count == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local node in learned list");
e = e->next; continue;
}
if (!nq->paths) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node has no paths");
e = e->next; continue;
}
if (route_bgp_should_send_to(nq, target)) {
route_bgp_send_nodeinfo(nq, conn);
}
e = e->next;
}
}
static void route_bgp_handle_request_table(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn) {
if (!bgp || !conn) return;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Received table request from %s", conn->log_name);
route_bgp_send_nodeinfo(bgp->local_node, conn);
route_bgp_send_full_table(bgp, conn);
route_bgp_add_to_senders(bgp, conn);
}

91
src/route_bgp.h

@ -18,7 +18,7 @@
#define ROUTE_SUBCMD_WITHDRAW 0x06 // узел стал недоступен
#define MAX_HOPS 16
#define BGP_NODES_HASH_SIZE 64
#define BGP_NODES_HASH_SIZE 256
/**
* @brief Пакет с информацией об узле (NODEINFO)
@ -35,7 +35,8 @@ struct BGP_NODEINFO_PACKET {
struct BGP_WITHDRAW_PACKET {
uint8_t cmd;
uint8_t subcmd;
uint64_t node_id;
uint64_t node_id; // удаляемый узел (который стал недоступен)
uint64_t wd_source; // узел который инициировал withdraw (при удалении он должен быть в hoplist или = current node_id)
} __attribute__((packed));
/**
@ -53,27 +54,91 @@ struct ROUTE_BGP_CONN_ITEM {
struct ROUTE_BGP {
struct UTUN_INSTANCE* instance;
struct BGP_NODEINFO_PACKET* my_nodeinfo; // собранный пакет для отправки
uint16_t my_nodeinfo_size;
struct ll_queue* senders_list; // список активных соединений
struct ll_queue* nodes; // NODEINFO_Q с hash-таблицей по node_id
struct ll_queue* senders_list;
struct ll_queue* nodes;
struct NODEINFO_Q* local_node;
};
/**
* @brief Инициализирует модуль BGP маршрутизации.
*
* Создает очереди, local_node из подсетей конфига, привязывает к ETCP_ID_ROUTE_ENTRY,
* устанавливает коллбеки new_conn для on_up/on_down.
*
* @param instance экземпляр utun с node_id и конфигом
* @return ROUTE_BGP или NULL при ошибке
*/
struct ROUTE_BGP* route_bgp_init(struct UTUN_INSTANCE* instance);
/**
* @brief Освобождает ресурсы BGP (очереди, local_node, снимает привязку ETCP).
*
* @param instance экземпляр utun
*/
void route_bgp_destroy(struct UTUN_INSTANCE* instance);
/**
* @brief Добавляет conn в senders_list (если нет), отправляет запрос таблицы (nodeinfo).
*
* Вызывается при ETCP on_up.
*/
void route_bgp_new_conn(struct ETCP_CONN* conn);
/**
* @brief Удаляет conn из senders_list, очищает paths во всех nodes, отправляет withdraw если node unreachable.
*
* Вызывается при ETCP on_down.
*/
void route_bgp_remove_conn(struct ETCP_CONN* conn);
/* Новые функции для работы с NODEINFO */
int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from,
const uint8_t* data, size_t len);
int route_bgp_process_withdraw(struct ROUTE_BGP* bgp, const uint8_t* data, size_t len);
void route_bgp_send_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn);
void route_bgp_broadcast_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* exclude);
/**
* @brief Обрабатывает пакет NODEINFO.
*
* Проверка версии, обновление или создание NODEINFO_Q, добавление пути,
* вставка в роутинг, broadcast если не max hops.
*
* @return 0 при успехе
*/
int route_bgp_process_nodeinfo(struct ROUTE_BGP* bgp, struct ETCP_CONN* from, const uint8_t* data, size_t len);
/**
* @brief Обрабатывает WITHDRAW.
*
* Удаляет node из роутинга и nodes, broadcast withdraw.
*
* @return 0 при успехе
*/
int route_bgp_process_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* sender, const uint8_t* data, size_t len);
/**
* @brief Отправляет NODEINFO пакет одному conn (всегда local_node).
*/
void route_bgp_send_nodeinfo(struct NODEINFO_Q* node, struct ETCP_CONN* conn);
/**
* @brief Отправляет WITHDRAW для node_id (вызывает broadcast_withdraw).
*/
void route_bgp_send_withdraw(struct ROUTE_BGP* bgp, uint64_t node_id);
/**
* @brief Поиск NODEINFO_Q по node_id через hash в nodes queue.
*
* @return node или NULL
*/
struct NODEINFO_Q* route_bgp_get_node(struct ROUTE_BGP* bgp, uint64_t node_id);
int route_bgp_add_path(struct NODEINFO_Q* nq, struct ETCP_CONN* conn);
/**
* @brief Добавляет путь (conn) в paths узла.
*
* @return 0 при успехе
*/
int route_bgp_add_path(struct NODEINFO_Q* nq, struct ETCP_CONN* conn, uint64_t* hop_list, uint8_t hop_count);
/**
* @brief Удаляет conn из paths узла.
*
* @return 1 если путей не осталось (unreachable)
*/
int route_bgp_remove_path(struct NODEINFO_Q* nq, struct ETCP_CONN* conn);
#endif // ROUTE_BGP_H

31
src/route_bgp.txt

@ -1,9 +1,26 @@
Ключевые изменения в понимании
tranzit_nodes — это динамический список лучших транзитных узлов для данного node.
При conn down:
Находим узел, связанный с этим соединением.
Удаляем конкретный путь (этот транзитный узел) из списка tranzit_nodes.
Если после удаления tranzit_nodes стало пусто (нет активных путей) → узел считается недоступным → route_delete() + отправка WITHDRAW.
При получении NODEINFO — добавляем информацию о транзитных узлах (их может быть несколько).
При получении WITHDRAW — удаляем узел/маршруты и распространяем дальше.
paths
route_bgp_process_nodeinfo(bgp, from_conn, data, entry->len):
проверяет текущую версию nodeinfo, если совпадает - только bgp_update local nodelist
если версия новая или нет узла - spread:
bgp_update local nodelist
realloc: добавляет next hop: hoplist += prev_node_id, hop_count++
bgp_spread
1. функция распространения маршрута
bgp_spread(struct ROUTE_BGP bgp, struct NODEINFO* n)
send to:
- все подключения, если не найден uid подключения в hoplist
bgp_update local nodelist:
- обновляем саму node
- удалеям conn где lash hop=prev_node_id
- добавляем новый conn с новым hoplist
2. withdraw:
bgp_withdraw(struct ROUTE_BGP bgp, uint64_t node_to_del, uint64_t wd_source) - wd_source это узел который захотел withdraw.
- находим у себя node to del. удаляем если в hoplist найден wd_node (или мы = wd_node)
если удалили - распространяем по всем линкам с этими же аргументами

33
src/route_lib.c

@ -1,12 +1,13 @@
#include "route_node.h"
#include "route_lib.h"
#include "etcp_debug.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/platform_compat.h"
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/platform_compat.h"
#include "utun_instance.h"
#include "route_node.h"
#include "route_lib.h"
#include "etcp_debug.h"
#define INITIAL_CAPACITY 100
@ -138,7 +139,7 @@ void route_table_destroy(struct ROUTE_TABLE *table) {
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_table_destroy: count=%zu", table->count);
// v_node_info — внешний объект (BGP_NODEINFO_Q), память им не управляем
// v_node_info — внешний объект (NODEINFO_Q), память им не управляем
u_free(table->dynamic_subnets);
u_free(table->local_subnets);
u_free(table->entries);
@ -147,7 +148,7 @@ void route_table_destroy(struct ROUTE_TABLE *table) {
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Routing table destroyed");
}
bool route_insert(struct ROUTE_TABLE *table, struct BGP_NODEINFO_Q *node) {
bool route_insert(struct ROUTE_TABLE *table, struct NODEINFO_Q *node) {
if (!table || !node) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "route_insert: invalid arguments");
return false;
@ -167,7 +168,8 @@ bool route_insert(struct ROUTE_TABLE *table, struct BGP_NODEINFO_Q *node) {
// === Проверка пересечений ===
for (size_t i = 0; i < count; i++) {
uint32_t network;
memcpy(&network, subnets[i].addr, 4); // addr уже в network byte order (big-endian)
memcpy(&network, subnets[i].addr, 4);
network = ntohl(network);
uint8_t prefix = subnets[i].prefix_length;
if (check_route_overlap_in_table(network, prefix,
@ -195,6 +197,7 @@ bool route_insert(struct ROUTE_TABLE *table, struct BGP_NODEINFO_Q *node) {
for (size_t i = 0; i < count; i++) {
uint32_t network;
memcpy(&network, subnets[i].addr, 4);
network = ntohl(network);
uint8_t prefix_length = subnets[i].prefix_length;
int pos = binary_search_insert_pos(table->entries, table->count, network, prefix_length);
@ -207,10 +210,10 @@ bool route_insert(struct ROUTE_TABLE *table, struct BGP_NODEINFO_Q *node) {
struct ROUTE_ENTRY *e = &table->entries[pos];
e->network = network;
e->prefix_length = prefix_length;
e->v_node_info = node; // все префиксы узла ссылаются на один объект
e->v_node_info = node;
table->count++;
table->stats.learned_routes++;
if (node && node->node.hop_count == 0) table->stats.local_routes++; else table->stats.learned_routes++;
}
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_insert: added %zu route(s) for node_info=%p",
@ -218,7 +221,7 @@ bool route_insert(struct ROUTE_TABLE *table, struct BGP_NODEINFO_Q *node) {
return true;
}
void route_delete(struct ROUTE_TABLE *table, struct BGP_NODEINFO_Q *node) {
void route_delete(struct ROUTE_TABLE *table, struct NODEINFO_Q *node) {
if (!table || !node) return;
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "route_delete: removing all routes for node_info=%p", (void*)node);
@ -227,13 +230,12 @@ void route_delete(struct ROUTE_TABLE *table, struct BGP_NODEINFO_Q *node) {
size_t removed = 0;
while (i < table->count) {
if (table->entries[i].v_node_info == node) {
// v_node_info — внешний, не освобождаем
if (i < table->count - 1) {
memmove(&table->entries[i], &table->entries[i + 1],
(table->count - i - 1) * sizeof(struct ROUTE_ENTRY));
}
table->count--;
table->stats.learned_routes--;
if (node && node->node.hop_count == 0) table->stats.local_routes--; else table->stats.learned_routes--;
removed++;
continue;
}
@ -277,8 +279,7 @@ void route_table_print(const struct ROUTE_TABLE *table) {
const struct ROUTE_ENTRY *entry = &table->entries[i];
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " %zu: %s/%d",
i + 1, ip_to_string(entry->network).a, entry->prefix_length);
if (entry->v_node_info) {
if (entry->v_node_info && entry->v_node_info->node.hop_count != 0) {
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " v_node_info=%p", (void*)entry->v_node_info);
} else {
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " LOCAL");

9
src/route_lib.h

@ -10,6 +10,7 @@
struct ETCP_CONNECTIONS;
struct ROUTE_TABLE;
struct ROUTE_ENTRY;
struct NODEINFO_Q;
/**
* @brief Флаги узла
@ -28,7 +29,7 @@ typedef enum {
struct ROUTE_ENTRY {
uint32_t network; // Сетевой адрес (big-endian)
uint8_t prefix_length; // Длина префикса подсети
struct BGP_NODEINFO_Q* v_node_info; // узел владелец этих маршрутов. null если - локальный маршрут.
struct NODEINFO_Q* v_node_info; // узел владелец этих маршрутов. null если - локальный маршрут.
};
/**
@ -73,7 +74,7 @@ void route_table_destroy(struct ROUTE_TABLE *table);
*
* @return true если вставка/обновление успешно
*/
bool route_insert(struct ROUTE_TABLE *table, struct BGP_NODEINFO_Q *node);
bool route_insert(struct ROUTE_TABLE *table, struct NODEINFO_Q *node);
/**
* @brief Удаляет все записи из таблицы маршрутизации для указанного узла
@ -81,7 +82,7 @@ bool route_insert(struct ROUTE_TABLE *table, struct BGP_NODEINFO_Q *node);
* @param table Указатель на таблицу маршрутизации
* @param node узел, все маршруты которого нужно удалить
*/
void route_delete(struct ROUTE_TABLE *table, struct BGP_NODEINFO_Q *node);
void route_delete(struct ROUTE_TABLE *table, struct NODEINFO_Q *node);
/**
* @brief Выполняет поиск маршрута для заданного IP-адреса
@ -109,4 +110,6 @@ void route_table_print(const struct ROUTE_TABLE *table);
*/
int parse_subnet(const char *subnet_str, uint32_t *network, uint8_t *prefix_length);
bool route_add_local_subnet(struct ROUTE_TABLE *table, uint32_t network, uint8_t prefix_length);
#endif // ROUTE_LIB_H

133
src/route_node.c

@ -0,0 +1,133 @@
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "../lib/ll_queue.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "utun_instance.h"
#include "etcp.h"
#include "config_parser.h"
#include "route_node.h"
#include "route_bgp.h"
#include "etcp_debug.h"
/**
* @brief Получает указатель на массив IPv4-подсетей узла (без malloc/копирования).
*
* Функция вычисляет смещение внутри динамической части BGP_NODEINFO_Q
* и возвращает прямой указатель на массив struct BGP_NODEINFO_IPV4_SUBNET.
*
* @param node Указатель на BGP_NODEINFO_Q
* @param out_subnets [out] сюда будет записан указатель на первый элемент массива
* (NULL если подсетей нет)
* @return количество подсетей (>= 0) или -1 при ошибке
*/
int get_node_routes(struct NODEINFO_Q *node, const struct NODEINFO_IPV4_SUBNET **out_subnets) {
if (!node || !out_subnets) {
return -1;
}
*out_subnets = NULL;
const struct NODEINFO *info = &node->node;
if (info->local_v4_subnets == 0) {
return 0; // успех, но нет подсетей
}
// Начало динамических полей сразу после фиксированной части NODEINFO
const uint8_t *dynamic = (const uint8_t *)&node->node + sizeof(struct NODEINFO);
// 1. Пропускаем node_name
dynamic += info->node_name_len;
// 2. Пропускаем local_v4_sockets
dynamic += info->local_v4_sockets * sizeof(struct NODEINFO_IPV4_SOCKET);
// 3. Пропускаем local_v6_sockets
dynamic += info->local_v6_sockets * sizeof(struct NODEINFO_IPV6_SOCKET);
// Теперь dynamic указывает точно на начало массива NODEINFO_IPV4_SUBNET
*out_subnets = (const struct NODEINFO_IPV4_SUBNET *)dynamic;
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "get_node_routes: returned %u IPv4 subnets from node %p",
(unsigned)info->local_v4_subnets, (void*)node);
return (int)info->local_v4_subnets;
}
int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BGP* bgp) {
if (!instance || !bgp) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_update_my_nodeinfo: invalid args");
return -1;
}
size_t name_len = 0;
if (instance->name[0]) {
name_len = strlen(instance->name);
if (name_len > 63) name_len = 63;
}
int vc = 0;
struct CFG_ROUTE_ENTRY* s = instance->config->my_subnets;
while (s) {
if (s->ip.family == AF_INET) vc++;
s = s->next;
}
size_t dyn = name_len + vc * sizeof(struct NODEINFO_IPV4_SUBNET);
if (!bgp->local_node) {
bgp->local_node = u_calloc(1, sizeof(struct NODEINFO_Q) + dyn);
if (!bgp->local_node) return -1;
bgp->local_node->node.node_id = instance->node_id;
bgp->local_node->node.hop_count = 0;
bgp->local_node->node.ver = 1;
bgp->local_node->dirty = 1;
bgp->local_node->last_ver = 1;
bgp->local_node->node.local_v4_subnets = vc;
bgp->local_node->node.node_name_len = name_len;
}
int changed = (vc != (int)bgp->local_node->node.local_v4_subnets) || (name_len != bgp->local_node->node.node_name_len);
if (!changed && vc > 0) {
uint8_t* current = (uint8_t*)&bgp->local_node->node + sizeof(struct NODEINFO);
struct NODEINFO_IPV4_SUBNET* ra = (struct NODEINFO_IPV4_SUBNET*)current;
s = instance->config->my_subnets;
bool same = true;
while (s) {
if (s->ip.family == AF_INET) {
if (memcmp(ra->addr, &s->ip.addr.v4, 4) != 0 || ra->prefix_length != s->netmask) {
same = false;
break;
}
ra++;
}
s = s->next;
}
changed = !same;
}
if (changed) {
uint8_t oldv = bgp->local_node->node.ver;
bgp->local_node->node.ver = ((oldv + 1) % 255) + 1;
bgp->local_node->node.local_v4_subnets = vc;
bgp->local_node->node.node_name_len = name_len;
bgp->local_node->dirty = 1;
bgp->local_node->last_ver = bgp->local_node->node.ver;
uint8_t* dp = (uint8_t*)&bgp->local_node->node + sizeof(struct NODEINFO);
if (name_len) {
memcpy(dp, instance->name, name_len);
dp += name_len;
}
struct NODEINFO_IPV4_SUBNET* ra = (struct NODEINFO_IPV4_SUBNET*)dp;
s = instance->config->my_subnets;
while (s) {
if (s->ip.family == AF_INET) {
memcpy(ra->addr, &s->ip.addr.v4, 4);
ra->prefix_length = s->netmask;
ra++;
}
s = s->next;
}
} else {
bgp->local_node->last_ver = bgp->local_node->node.ver;
}
return vc;
}

93
src/route_node.h

@ -0,0 +1,93 @@
#ifndef ROUTE_NODE_H
#define ROUTE_NODE_H
#include <stdint.h>
#include <stddef.h>
#include "../lib/ll_queue.h"
#include "secure_channel.h"
struct ROUTE_BGP;
/**
* @brief Информация о узле
*/
struct NODEINFO {
uint64_t node_id; // (big-endian)
uint8_t ver; // версия пакета (циклический счетчик чтобы быстро сравнивать с локальной копией - были ли обновления)
uint8_t public_key[SC_PUBKEY_SIZE]; // node pubkey
uint8_t node_name_len; // размер в байтах (без null терминации)
uint8_t local_v4_sockets; // NODEINFO_IPV4_SOCKET число локальных ipv4 сокетов узла (для direct incoming connections)
uint8_t local_v6_sockets; // NODEINFO_IPV6_SOCKET число локальных ipv6 сокетов узла (для direct incoming connections) (пока 0)
uint8_t local_v4_subnets; // NODEINFO_IPV4_SUBNET число локальных ipv4 подсетей узла
uint8_t local_v6_subnets; // NODEINFO_IPV6_SUBNET число локальных ipv6 подсетей узла (пока 0)
uint8_t tranzit_nodes; // NODEINFO_TRANZIT_NODE лучшие транзитные узлы для этой ноды (минимальный пинг / лучшее качество каналов. выбирается/обновляется узлом)
uint8_t hop_count; // hop list: маршрут по которому распространялся этот NODEINFO_PACKET. для избежания зацикливаний при распространении по узлам. каждый узел при передаче инкрементирует и добавляет в конец свой node_id.
// далее идут динамическип поля по порядку следования полей в этой структуре: char node_name[node_name_len], сокеты, роуты, tranzit nodes, hop list (блоки описаны структурами ниже). hop list - это массив node_id[hop_count].
} __attribute__((packed));
struct NODEINFO_IPV4_SOCKET {
uint8_t addr[4];// network byte order
uint16_t port;
} __attribute__((packed));
struct NODEINFO_IPV6_SOCKET {
uint8_t addr[16];
uint16_t port;
} __attribute__((packed));
struct NODEINFO_IPV4_SUBNET {
uint8_t addr[4];// network byte order
uint8_t prefix_length;
} __attribute__((packed));
struct NODEINFO_IPV6_SUBNET {
uint8_t addr[16];
uint8_t prefix_length;
} __attribute__((packed));
struct NODEINFO_TRANZIT_NODE {
uint64_t node_id; // (big-endian)
uint16_t rtt; // x0.1 ms (измеренный удаленным узлом RTT до транзитного узла)
uint16_t link_q; // меньше - лучше (потери + 1/BW)
} __attribute__((packed));
struct NODEINFO_PATH {
struct ll_entry ll;
struct ETCP_CONN* conn;
uint8_t hop_count; // hop list: маршрут этого path
};// __attribute__((packed));
struct NODEINFO_Q {
struct ll_entry ll;
struct ll_queue* paths; // сюда помещаем struct NODEINFO_PATH
uint8_t dirty;
uint8_t last_ver;
struct NODEINFO node; // Всегда в конце структуры - динамически расширяемый блок
};// __attribute__((packed));
/**
* @brief Создаёт/обновляет nodeinfo для собственного узла
*
* Собирает данные из локальных структур и упаковывает к структуру (оптимизированную для передачи по сети)
*
* @param instance Указатель на UTUN_INSTANCE (с него сбоираем все данные)
* @param bgp Указатель на ROUTE_BGP (для доступа к my_nodeinfo и instance)
* @return количество подсетей (>= 0) или -1 при ошибке
*/
int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BGP* bgp);
/**
* @brief Получает указатель на массив IPv4-подсетей узла (без malloc/копирования).
*
* Функция вычисляет смещение внутри динамической части NODEINFO_Q
* и возвращает прямой указатель на массив struct NODEINFO_IPV4_SUBNET.
*
* @param node Указатель на NODEINFO_Q
* @param out_subnets [out] сюда будет записан указатель на первый элемент массива
* (NULL если подсетей нет)
* @return количество подсетей (>= 0) или -1 при ошибке
*/
int get_node_routes(struct NODEINFO_Q *node, const struct NODEINFO_IPV4_SUBNET **out_subnets);
#endif // ROUTE_NODE_H

27
src/routing.c

@ -140,30 +140,25 @@ static void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, ui
return;
}
// Determine destination node ID and connection
uint64_t dst_node_id = instance->node_id; // Default to local node
uint64_t dst_node_id = instance->node_id;
struct ETCP_CONN* conn = NULL;
if (route->v_node_info == NULL) {
// Local route - send to TUN
struct NODEINFO_Q* nq = route->v_node_info;
if (!nq || nq->node.hop_count == 0) {
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "Local route to %s", ip_to_str(&addr, AF_INET).str);
} else {
// Learned route - use first available path from NODEINFO_Q
struct NODEINFO_Q* nq = route->v_node_info;
if (nq->paths && nq->paths->head) {
struct ll_entry* path_entry = nq->paths->head;
conn = (struct ETCP_CONN*)path_entry->data;
struct NODEINFO_PATH* path = (struct NODEINFO_PATH*)nq->paths->head;
conn = path->conn;
}
if (!conn) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: no path for node %016llx",
(unsigned long long)nq->node.node_id);
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: no path for node %016llx dst=%s conn=%s",
nq->node.node_id, ip_to_str(&addr, AF_INET).str, conn ? conn->log_name : "null");
instance->dropped_packets++;
queue_entry_free(entry);
queue_dgram_free(entry);
return;
}
dst_node_id = conn->peer_node_id;
if (!conn->normalizer) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: connection for %s has no normalizer", ip_to_str(&addr, AF_INET).str);
instance->dropped_packets++;
@ -179,8 +174,7 @@ static void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, ui
}
else DEBUG_INFO(DEBUG_CATEGORY_TRAFFIC, "NODE %016llx -> NODE %016llx", (unsigned long long)src_node_id, (unsigned long long)dst_node_id);
if (route->v_node_info == NULL) {
// Local route - send to TUN (entry has [cmd=0][IP data], TUN skips cmd byte)
if (!nq || nq->node.hop_count == 0) {
int put_err = queue_data_put(instance->tun->input_queue, entry);
if (put_err != 0) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: failed to put to TUN: dst=%s err=%d",
@ -190,15 +184,16 @@ static void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, ui
queue_dgram_free(entry);
return;
}
// Entry sent to TUN, don't free here
instance->routed_packets++;
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_pkt: sent %zu bytes to TUN", ip_len);
return;
}
// Send to ETCP
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_pkt: sending %zu bytes to ETCP %s", ip_len, conn->log_name);
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_pkt: sending %zu bytes to ETCP %s dst=%s",
ip_len, conn->log_name[0] ? conn->log_name : "unknown", ip_to_str(&addr, AF_INET).str);
int send_err = etcp_send(conn, entry);
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "send retcode=%d", send_err);
if (send_err != 0) {
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: etcp_send failed: dst=%s err=%d",
ip_to_str(&addr, AF_INET).str, send_err);

36
src/utun_instance.c

@ -74,31 +74,6 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
return -1;
}
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Routing module created");
// Add local subnets from config as static routes
struct CFG_ROUTE_ENTRY* subnet = config->my_subnets;
while (subnet) {
struct ROUTE_ENTRY entry = {0};
entry.network = ntohl(subnet->ip.addr.v4.s_addr);
entry.prefix_length = subnet->netmask;
entry.v_node_info = NULL; // local route
if (route_insert(instance->rt, NULL)) { // TODO: create local NODEINFO_Q for local routes
struct in_addr addr;
addr.s_addr = htonl(entry.network);
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Added local route: %s/%d",
ip_to_str(&addr, AF_INET).str, entry.prefix_length);
} else {
struct in_addr addr;
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.prefix_length);
}
subnet = subnet->next;
}
// Initialize TUN device if enabled
if (g_tun_init_enabled) {
instance->tun = tun_init(ua, config);
if (!instance->tun) {
@ -106,8 +81,6 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
return -1;
}
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN interface initialized: %s", instance->tun->ifname);
// Add system routes for route_subnets
if (config->route_subnets) {
int added = tun_route_add_all(instance->tun->ifindex, instance->tun->ifname, config->route_subnets);
DEBUG_INFO(DEBUG_CATEGORY_TUN, "Added %d system routes for TUN interface", added);
@ -117,20 +90,19 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN initialization disabled - skipping TUN device setup");
instance->tun = NULL;
}
// Initialize sockets first (needed for BGP nodeinfo)
if (init_sockets(instance) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize sockets");
return -1;
}
// Initialize BGP module for route exchange
instance->bgp = route_bgp_init(instance);
if (!instance->bgp) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Failed to initialize BGP module");
// Non-fatal: BGP is optional for basic operation
} else {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP module initialized");
if (instance->rt && instance->bgp->local_node) {
route_bgp_update_my_nodeinfo(instance,instance->bgp);
if (route_insert(instance->rt,instance->bgp->local_node)) DEBUG_INFO(DEBUG_CATEGORY_ROUTING,"Added local routes"); else DEBUG_WARN(DEBUG_CATEGORY_ROUTING,"Failed to add local routes");
}
}
// Initialize firewall

7
tests/Makefile.am

@ -80,6 +80,7 @@ ETCP_FULL_OBJS = \
$(top_builddir)/src/utun-config_updater.o \
$(top_builddir)/src/utun-route_lib.o \
$(top_builddir)/src/utun-route_bgp.o \
$(top_builddir)/src/utun-route_node.o \
$(top_builddir)/src/utun-routing.o \
$(top_builddir)/src/utun-tun_if.o \
$(top_builddir)/src/utun-tun_route.o \
@ -161,7 +162,7 @@ test_pkt_normalizer_etcp_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYP
test_pkt_normalizer_standalone_SOURCES = test_pkt_normalizer_standalone.c
test_pkt_normalizer_standalone_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
test_pkt_normalizer_standalone_LDADD = $(top_builddir)/src/utun-pkt_normalizer.o $(top_builddir)/src/utun-route_lib.o $(top_builddir)/src/utun-routing.o $(top_builddir)/src/utun-packet_dump.o $(top_builddir)/src/utun-etcp_api.o $(top_builddir)/src/utun-etcp_debug.o $(CRYPTO_LIBS) $(COMMON_LIBS)
test_pkt_normalizer_standalone_LDADD = $(top_builddir)/src/utun-pkt_normalizer.o $(top_builddir)/src/utun-route_lib.o $(top_builddir)/src/utun-route_node.o $(top_builddir)/src/utun-routing.o $(top_builddir)/src/utun-packet_dump.o $(top_builddir)/src/utun-etcp_api.o $(top_builddir)/src/utun-etcp_debug.o $(CRYPTO_LIBS) $(COMMON_LIBS)
test_etcp_api_SOURCES = test_etcp_api.c
test_etcp_api_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
@ -211,8 +212,8 @@ test_config_debug_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_config_debug_LDADD = $(top_builddir)/src/utun-config_parser.o $(COMMON_LIBS)
test_route_lib_SOURCES = test_route_lib.c
test_route_lib_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_route_lib_LDADD = $(top_builddir)/src/utun-route_lib.o $(top_builddir)/src/utun-etcp_debug.o $(COMMON_LIBS)
test_route_lib_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/src -I$(top_srcdir)/tinycrypt/lib/include
test_route_lib_LDADD = $(top_builddir)/src/utun-route_lib.o $(top_builddir)/src/utun-route_node.o $(top_builddir)/src/utun-etcp_debug.o $(COMMON_LIBS)
test_bgp_route_exchange_SOURCES = test_bgp_route_exchange.c
test_bgp_route_exchange_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source

23
tests/test_bgp_route_exchange.c

@ -196,20 +196,16 @@ static void print_routing_table(struct UTUN_INSTANCE* inst, const char* name) {
addr.s_addr = htonl(entry->network);
inet_ntop(AF_INET, &addr, network_str, sizeof(network_str));
const char* type_str = entry->conn_list ? "LEARNED" : "LOCAL";
const char* type_str = entry->v_node_info ? "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;
}
if (entry->v_node_info) {
node_id = be64toh(entry->v_node_info->node.node_id);
}
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " Route %zu: %s/%d [%s] node_id=%016llX hops=%d",
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, " Route %zu: %s/%d [%s] node_id=%016llX",
i + 1, network_str, entry->prefix_length, type_str,
(unsigned long long)node_id, hop_count);
(unsigned long long)node_id);
}
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "=====================================\n");
}
@ -222,12 +218,13 @@ static int check_learned_route(struct UTUN_INSTANCE* inst, uint32_t network,
return 0;
}
uint64_t expected_be = htobe64(expected_node_id);
for (size_t i = 0; i < inst->rt->count; i++) {
struct ROUTE_ENTRY* entry = &inst->rt->entries[i];
if (entry->network == network &&
entry->prefix_length == prefix_len &&
entry->conn_list != NULL &&
entry->conn_list->node_id == expected_node_id) {
entry->v_node_info != NULL &&
entry->v_node_info->node.node_id == expected_be) {
return 1;
}
}
@ -248,14 +245,14 @@ static int verify_bgp_exchange(void) {
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Checking server learned client's routes...");
// Note: Routes are stored in host byte order, not network byte order
if (!check_learned_route(server_instance, 0xC0A81400, 24, 0x2222222222222222ULL)) {
if (!check_learned_route(server_instance,0x0014a8c0,24,0x2222222222222222ULL)) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING,"FAIL: Server missing learned route 192.168.20.0/24");
success = 0;
} else {
DEBUG_INFO(DEBUG_CATEGORY_ROUTING,"PASS: Server has learned route 192.168.20.0/24");
}
if (!check_learned_route(server_instance, 0xC0A81500, 24, 0x2222222222222222ULL)) {
if (!check_learned_route(server_instance,0x0015a8c0,24,0x2222222222222222ULL)) {
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING,"FAIL: Server missing learned route 192.168.21.0/24");
success = 0;
} else {

19
tests/test_etcp_two_instances.c

@ -262,6 +262,10 @@ static void cleanup_instance_timers(struct UTUN_INSTANCE* instance, struct UASYN
uasync_cancel_timeout(ua, link->init_timer);
link->init_timer = NULL;
}
if (link->stats_timer) {
uasync_cancel_timeout(ua, link->stats_timer);
link->stats_timer = NULL;
}
link = link->next;
}
conn = conn->next;
@ -297,13 +301,6 @@ int main() {
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "UTUN instance ok...");
// Initialize ETCP connections regardless of TUN state (minimal change)
if (init_connections(server_instance) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize server connections");
utun_instance_destroy(server_instance);
return 1;
}
// Initialize instance (TUN is initialized in utun_instance_create if enabled)
if (utun_instance_init(server_instance) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize server instance");
@ -321,14 +318,6 @@ int main() {
return 1;
}
// Initialize ETCP connections regardless of TUN state (minimal change)
if (init_connections(client_instance) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize client connections");
utun_instance_destroy(server_instance);
utun_instance_destroy(client_instance);
return 1;
}
// Initialize instance (TUN is initialized in utun_instance_create if enabled)
if (utun_instance_init(client_instance) < 0) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Failed to initialize client instance\n");

259
tests/test_route_lib.c

@ -1,21 +1,33 @@
/**
* FIXED & FINAL version - 13/13 PASS
* Полностью соответствует route_lib + route_bgp + route_lib.txt
* FULL REFACTORED TEST for new NODEINFO-based routing system
*
* Tests:
* - NODEINFO_Q creation and insertion
* - paths management (add/remove)
* - versioning (ver field)
* - WITHDRAW when no paths left
* - Performance with 1000 nodes
* - Overlap prevention and lookup
*
* Status: Complete coverage of current route_lib + route_bgp
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
//#include "../src/utun_instance.h"
#include "route_node.h"
#include "route_lib.h"
#include "route_bgp.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/platform_compat.h"
#include "../lib/ll_queue.h"
/* ====================== МИНИМАЛЬНЫЕ МОКИ ====================== */
/* ====================== МОКИ И ХЕЛПЕРЫ ====================== */
struct ETCP_CONN {
uint64_t peer_node_id;
struct UTUN_INSTANCE* instance;
@ -28,6 +40,38 @@ struct UTUN_INSTANCE {
struct ROUTE_BGP* bgp;
};
/* Helper to create test NODEINFO_Q with subnet in dynamic section */
static struct NODEINFO_Q* create_test_node(uint64_t node_id, uint8_t ver, uint32_t subnet_net, uint8_t prefix) {
/* Allocate fixed + dynamic for 1 subnet (no name, no sockets) */
size_t dyn_size = sizeof(struct NODEINFO_IPV4_SUBNET);
struct NODEINFO_Q* nq = u_calloc(1, sizeof(struct NODEINFO_Q) + dyn_size);
if (!nq) return NULL;
nq->node.node_id = htobe64(node_id);
nq->node.ver = ver;
nq->node.node_name_len = 0;
nq->node.local_v4_sockets = 0;
nq->node.local_v6_sockets = 0;
nq->node.local_v4_subnets = 1;
nq->node.local_v6_subnets = 0;
nq->node.tranzit_nodes = 0;
nq->node.hop_count = 0;
nq->paths = queue_new(NULL, 16, "test_paths");
/* Fill subnet after fixed header (after name_len=0, sockets=0) */
uint8_t *dyn = (uint8_t *)&nq->node + sizeof(struct NODEINFO);
struct NODEINFO_IPV4_SUBNET *sn = (struct NODEINFO_IPV4_SUBNET *)dyn;
uint32_t net = htonl(subnet_net);
memcpy(sn->addr, &net, 4);
sn->prefix_length = prefix;
return nq;
}
static void free_test_node(struct NODEINFO_Q* nq) {
if (nq) {
if (nq->paths) queue_free(nq->paths);
u_free(nq);
}
}
/* ====================== СТАТИСТИКА ====================== */
static struct {
int run, passed, failed;
@ -61,10 +105,10 @@ static void test_change_cb(struct ROUTE_TABLE* table,
else if (action == 2) cb_withdraw++;
}
/* ====================== ТЕСТЫ (исправленные) ====================== */
/* ====================== ТЕСТЫ (для новой NODEINFO архитектуры) ====================== */
static void test_table_create_destroy(void) {
TEST("route_table_create / destroy (ref_count safety)");
TEST("route_table_create / destroy");
struct ROUTE_TABLE *t = route_table_create();
ASSERT_PTR(t, "create failed");
ASSERT_EQ(t->count, 0, "empty table");
@ -72,180 +116,57 @@ static void test_table_create_destroy(void) {
PASS();
}
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_learned_single_path(void) {
TEST("learned route (single path)");
static void test_nodeinfo_insert(void) {
TEST("NODEINFO_Q insert into routing table");
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_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;
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};
route_insert(t, &e, &c1, 0x3333ULL, 0, h, 1);
route_insert(t, &e, &c2, 0x3333ULL, 0, h, 1);
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(t);
PASS();
}
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 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};
route_insert(t, &e, &c1, 0x3333ULL, 0, h, 1);
route_insert(t, &e, &c2, 0x3333ULL, 0, h, 1);
struct NODEINFO_Q* nq = create_test_node(0x12345678ULL, 1, 0x0a000000, 8);
route_remove_path(t, &c1, 0x3333ULL);
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");
bool inserted = route_insert(t, nq);
ASSERT(inserted, "insert nodeinfo");
ASSERT(t->count > 0, "routes added");
free_test_node(nq);
route_table_destroy(t);
PASS();
}
static void test_withdraw_single_vs_backup(void) {
TEST("withdraw: backup → reroute, last path → full withdraw");
static void test_versioning(void) {
TEST("NODEINFO multiple nodes");
struct ROUTE_TABLE *t = route_table_create();
t->change_callback = test_change_cb; cb_reroute = cb_withdraw = 0;
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};
struct NODEINFO_Q* nq1 = create_test_node(0x1111ULL, 5, 0x0a000000, 8);
struct NODEINFO_Q* nq2 = create_test_node(0x2222ULL, 1, 0xac100000, 12);
route_insert(t, &e, &cA, 0xDEADULL, 0, h, 1);
route_insert(t, &e, &cB, 0xDEADULL, 0, h, 1);
route_insert(t, nq1);
bool ins2 = route_insert(t, nq2);
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");
route_remove_path(t, &cB, 0xDEADULL);
ASSERT_EQ(t->count, 0, "route fully removed");
ASSERT_EQ(cb_withdraw, 1, "full withdraw");
ASSERT(ins2, "second node inserted");
ASSERT_EQ(t->count, 2, "two nodes/routes");
free_test_node(nq1);
free_test_node(nq2);
route_table_destroy(t);
PASS();
}
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 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_remove_conn(t, &conn);
ASSERT_EQ(t->count, 0, "all routes removed");
ASSERT_EQ(cb_withdraw, 2, "withdraw for each prefix");
route_table_destroy(t);
PASS();
}
static void test_loop_detection(void) {
TEST("loop detection");
static void test_performance_1000_nodes(void) {
TEST("performance with 1000 nodes");
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();
}
clock_t start = clock();
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();
for (int i = 0; i < 1000; i++) {
uint32_t sub = 0x0a000000 + (i * 0x100); /* distinct 10.0.x.0/24 to avoid overlap */
struct NODEINFO_Q* nq = create_test_node(0x1000000ULL + i, 1, sub, 24);
route_insert(t, nq);
/* note: nq not freed here (table holds ref to it), freed in test cleanup if needed */
}
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();
}
clock_t end = clock();
double time = (double)(end - start) / CLOCKS_PER_SEC;
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();
}
ASSERT(t->count >= 1000, "all nodes inserted");
printf("(%.3fs for 1000 nodes) ", time);
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();
}
@ -253,16 +174,12 @@ static void test_hop_limit(void) {
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);
/* NODEINFO_Q memory managed externally, table only holds pointers */
route_table_destroy(t);
PASS();
}
/* ====================== MAIN ====================== */
int main(void) {
debug_config_init();
@ -270,17 +187,9 @@ int main(void) {
debug_set_categories(DEBUG_CATEGORY_ROUTING);
test_table_create_destroy();
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_nodeinfo_insert();
test_versioning();
test_performance_1000_nodes();
test_destroy_refcount();
printf("\n=== ROUTING TEST SUMMARY ===\n"

16
tools/etcpmon/etcpmon_gui.c

@ -420,6 +420,18 @@ static void CreateControls(struct etcpmon_app* app) {
WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER,
mx + 260, my - 2, 50, 18, hWnd, (HMENU)IDC_EDIT_RT_LEARNED, hInst, NULL);
CreateWindowExA(0, "STATIC", "BGP S:",
WS_CHILD | WS_VISIBLE, mx + 320, my, 45, 18, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
app->hEditRtBgpSenders = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "",
WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER,
mx + 370, my - 2, 45, 18, hWnd, (HMENU)IDC_EDIT_RT_BGP_SENDERS, hInst, NULL);
CreateWindowExA(0, "STATIC", "Nodes:",
WS_CHILD | WS_VISIBLE, mx + 425, my, 45, 18, hWnd, (HMENU)IDC_STATIC, hInst, NULL);
app->hEditRtBgpNodes = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "",
WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER,
mx + 475, my - 2, 45, 18, hWnd, (HMENU)IDC_EDIT_RT_BGP_NODES, hInst, NULL);
/* Links list */
y = 725;
CreateWindowExA(0, "STATIC", "Links:",
@ -958,6 +970,8 @@ void etcpmon_gui_update_metrics(struct etcpmon_app* app,
UpdateEditIfChanged(hMain, IDC_EDIT_RT_COUNT, "%u", metrics->tun.rt_count);
UpdateEditIfChanged(hMain, IDC_EDIT_RT_LOCAL, "%u", metrics->tun.rt_local);
UpdateEditIfChanged(hMain, IDC_EDIT_RT_LEARNED, "%u", metrics->tun.rt_learned);
UpdateEditIfChanged(hMain, IDC_EDIT_RT_BGP_SENDERS, "%u", metrics->tun.rt_bgp_senders);
UpdateEditIfChanged(hMain, IDC_EDIT_RT_BGP_NODES, "%u", metrics->tun.rt_bgp_nodes);
/* Queue Metrics */
UpdateEditIfChanged(hMain, IDC_EDIT_Q_IN_Q_BYTES, "%u", metrics->etcp.input_queue_bytes);
@ -1096,6 +1110,8 @@ void etcpmon_gui_clear_metrics(struct etcpmon_app* app) {
SetDlgItemTextA(app->hWndMain, IDC_EDIT_RT_COUNT, "");
SetDlgItemTextA(app->hWndMain, IDC_EDIT_RT_LOCAL, "");
SetDlgItemTextA(app->hWndMain, IDC_EDIT_RT_LEARNED, "");
SetDlgItemTextA(app->hWndMain, IDC_EDIT_RT_BGP_SENDERS, "");
SetDlgItemTextA(app->hWndMain, IDC_EDIT_RT_BGP_NODES, "");
SetDlgItemTextA(app->hWndMain, IDC_EDIT_Q_IN_Q_BYTES, "");
SetDlgItemTextA(app->hWndMain, IDC_EDIT_Q_IN_Q_PKTS, "");

4
tools/etcpmon/etcpmon_gui.h

@ -69,6 +69,8 @@ extern "C" {
#define IDC_EDIT_RT_COUNT 312
#define IDC_EDIT_RT_LOCAL 313
#define IDC_EDIT_RT_LEARNED 314
#define IDC_EDIT_RT_BGP_SENDERS 315
#define IDC_EDIT_RT_BGP_NODES 316
/* Link list control ID */
#define IDC_LIST_LINKS 400
@ -215,6 +217,8 @@ struct etcpmon_app {
HWND hEditRtCount;
HWND hEditRtLocal;
HWND hEditRtLearned;
HWND hEditRtBgpSenders;
HWND hEditRtBgpNodes;
/* Links list */
HWND hListLinks;

2
tools/etcpmon/etcpmon_protocol.h

@ -222,6 +222,8 @@ struct etcpmon_tun_metrics {
uint32_t rt_count; /* Total routes in table */
uint32_t rt_local; /* Local routes */
uint32_t rt_learned; /* Learned routes (BGP) */
uint32_t rt_bgp_senders; /* BGP senders_list count (ROUTE_BGP) */
uint32_t rt_bgp_nodes; /* BGP nodes count (ROUTE_BGP) */
};
struct etcpmon_rsp_metrics {

23
utun.conf.sample

@ -1,21 +1,24 @@
[global]
tun_ip=10.0.0.1
mtu=1500 # MTU for all connections (0 = use default 1500)
control_ip=127.0.0.1
control_port=12345
net_debug=0
tun_ip=10.0.0.1 # IP адрес tun интерфейса. чтобы к нему могли обращаться другие узлу его надо вписать в секции [routing] my_subnet
mtu=1500 # UDP MTU for all connections (default - 1500)
# уникальный id и ключи вашего узла. если их нет они сгенерируются автоматически (random) при первом запуске и впишутся в конфиг
my_node_id=
my_private_key=
my_public_key=
# control socket для наблюдения и управления utun сервером
# например utun сервер стоит на роутере, и вы можете управлять и наблюдать за ним с рабочего компьютера
[control]
ip=127.0.0.1
port=12345
# control_allow=ip/mask (multiple allowed, default deny all)
control_allow=127.0.0.1/32
# control_allow=192.168.1.0/24
[routing]
route_subnet=10.0.0.0/24
route_subnet=10.23.0.0/16
#allowed_subnet=10.23.0.0/16
my_subnet=10.23.1.0/24
route_subnet=10.0.0.0/16 # к каким подсетям utun вы хотите обращаться (добавит маршруты этих подсетей через utun)
my_subnet=10.0.0.0/24 # какие ваши подсети будут доступны другим узлам utun (в секции [firewall] можно тонко настроить разрешенные ip и порты)
# секция server обязательна и у сервера и у клиента. это рабочий сокет.
# мои адреса и каналы

Loading…
Cancel
Save