You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

393 lines
15 KiB

/**
* @file test_bgp_route_exchange.c
* @brief Тест цепочной BGP-маршрутизации A↔B↔C с эмуляцией потерь через dummynet_filter
*
* Топология: A (2 линка) ↔ B (2 линка к A, 1 к C) ↔ C (1 линк)
* Потери имитируются через dummynet_filter, встроенный в send-путь линка.
*
* Фазы:
* 0. Инициализация соединений
* 1. Ожидание BGP-обмена, проверка маршрутов A↔C
* 2. Потеря одного линка A↔B (loss=100%) → маршруты НЕ пропадают
* 3. Восстановление линка (loss=0%)
* 4. Потеря линка B↔C → маршруты удалены на A и C
* 5. Восстановление B↔C → маршруты вернулись
* 6. Потеря обоих линков A↔B → маршруты удалены
* 7. Восстановление одного линка A↔B → маршруты вернулись
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include <sys/stat.h>
#include "../lib/platform_compat.h"
#include "test_utils.h"
#ifdef _WIN32
#include <windows.h>
#include <direct.h>
#else
#include <unistd.h>
#endif
#include "etcp.h"
#include "etcp_connections.h"
#include "../src/config_parser.h"
#include "../src/config_updater.h"
#include "../src/utun_instance.h"
#include "routing.h"
#include "route_lib.h"
#include "topo_group.h"
#include "dummynet.h"
#include "../src/tun_if.h"
#include "secure_channel.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#define TEST_TIMEOUT_TB 200000 // 20s in 0.1ms units
#define PHASE_TIMEOUT_TB 100000 // 10s per phase
#define POLL_INTERVAL_MS 5
static uint64_t g_node_id_a = 0;
static uint64_t g_node_id_b = 0;
static uint64_t g_node_id_c = 0;
static struct UTUN_INSTANCE* inst_a = NULL;
static struct UTUN_INSTANCE* inst_b = NULL;
static struct UTUN_INSTANCE* inst_c = NULL;
static struct UASYNC* ua = NULL;
static int test_phase = 0; // 0=running, 1=success, 2=failure
static void* timeout_id = NULL;
// dummynet filters per link
static struct dummynet_filter* df_ab1 = NULL;
static struct dummynet_filter* df_ab2 = NULL;
static struct dummynet_filter* df_bc = NULL;
static char temp_dir[] = "/tmp/utun_bgp_mesh_XXXXXX";
static char config_a[256], config_b[256], config_c[256];
static int port_b_srv = 0, port_b_cli = 0, port_c_srv = 0;
static int port_a_srv1 = 0, port_a_srv2 = 0;
static int write_file(const char* path, const char* fmt, ...) {
va_list ap;
FILE* f = fopen(path, "w");
if (!f) return -1;
va_start(ap, fmt);
vfprintf(f, fmt, ap);
va_end(ap);
fclose(f);
return 0;
}
static char* get_pubkey(const char* path) {
struct utun_config* cfg = parse_config(path);
if (!cfg) return NULL;
char* pub = strdup(cfg->global.my_public_key_hex);
free_config(cfg);
return pub;
}
static int create_temp_configs(void) {
if (test_mkdtemp(temp_dir) != 0) { fprintf(stderr, "mkdtemp failed\n"); return -1; }
int base = 41000 + (getpid() % 15000);
port_b_srv = base; port_b_cli = base + 1; port_c_srv = base + 2;
port_a_srv1 = base + 10; port_a_srv2 = base + 11;
snprintf(config_a, sizeof(config_a), "%s/a.conf", temp_dir);
snprintf(config_b, sizeof(config_b), "%s/b.conf", temp_dir);
snprintf(config_c, sizeof(config_c), "%s/c.conf", temp_dir);
// Config C: server only, no keys (config_ensure_keys_and_node_id adds them)
if (write_file(config_c,
"[global]\n"
"tun_ip=10.100.0.3/24\n"
"tun_ifname=tun102\n"
"keepalive_interval=50\n"
"keepalive_adaptive=0\n"
"\n"
"[routing]\n"
"my_subnet=192.168.30.0/24\n"
"\n"
"[server: c_srv]\n"
"addr=127.0.0.1:%d\n"
"type=public\n"
"\n"
"[allowed_keys]\n"
"allow_all=1\n",
port_c_srv) != 0) return -1;
if (config_ensure_keys_and_node_id(config_c) != 0) return -1;
char* pub_c = get_pubkey(config_c);
if (!pub_c) return -1;
// Config B: server for A + client to C, needs C's pubkey
if (write_file(config_b,
"[global]\n"
"tun_ip=10.100.0.2/24\n"
"tun_ifname=tun101\n"
"keepalive_interval=50\n"
"keepalive_adaptive=0\n"
"\n"
"[routing]\n"
"my_subnet=192.168.20.0/24\n"
"\n"
"[server: b_srv]\n"
"addr=127.0.0.1:%d\n"
"type=public\n"
"\n"
"[server: b_cli_srv]\n"
"addr=127.0.0.1:%d\n"
"type=public\n"
"\n"
"[client: to_c]\n"
"keepalive=1\n"
"peer_public_key=%s\n"
"link=b_cli_srv:127.0.0.1:%d\n"
"\n"
"[allowed_keys]\n"
"allow_all=1\n",
port_b_srv, port_b_cli, pub_c, port_c_srv) != 0) { free(pub_c); return -1; }
free(pub_c);
if (config_ensure_keys_and_node_id(config_b) != 0) return -1;
char* pub_b = get_pubkey(config_b);
if (!pub_b) return -1;
// Config A: two servers + client to B with 2 links, needs B's pubkey
if (write_file(config_a,
"[global]\n"
"tun_ip=10.100.0.1/24\n"
"tun_ifname=tun100\n"
"keepalive_interval=50\n"
"keepalive_adaptive=0\n"
"\n"
"[routing]\n"
"my_subnet=192.168.10.0/24\n"
"\n"
"[server: a_srv1]\n"
"addr=127.0.0.1:%d\n"
"type=public\n"
"\n"
"[server: a_srv2]\n"
"addr=127.0.0.1:%d\n"
"type=public\n"
"\n"
"[client: to_b]\n"
"keepalive=1\n"
"peer_public_key=%s\n"
"link=a_srv1:127.0.0.1:%d\n"
"link=a_srv2:127.0.0.1:%d\n"
"\n"
"[allowed_keys]\n"
"allow_all=1\n",
port_a_srv1, port_a_srv2, pub_b, port_b_srv, port_b_srv) != 0) { free(pub_b); return -1; }
free(pub_b);
if (config_ensure_keys_and_node_id(config_a) != 0) return -1;
return 0;
}
static void cleanup_temp_configs(void) {
test_unlink(config_a); test_unlink(config_b); test_unlink(config_c);
test_rmdir(temp_dir);
}
static int has_initialized_link(struct UTUN_INSTANCE* inst) {
if (!inst || !inst->connections) return 0;
struct ll_entry* entry = inst->connections->head;
while (entry) { struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data; struct ETCP_LINK* l = ce->conn->links; while (l) { if (l->initialized) return 1; l = l->next; } entry = entry->next; }
return 0;
}
static int count_initialized_links(struct UTUN_INSTANCE* inst) {
int n = 0;
if (!inst || !inst->connections) return 0;
struct ll_entry* entry = inst->connections->head;
while (entry) { struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data; struct ETCP_LINK* l = ce->conn->links; while (l) { if (l->initialized) n++; l = l->next; } entry = entry->next; }
return n;
}
static int check_learned_route(struct UTUN_INSTANCE* inst, uint32_t network, uint8_t prefix_len, uint64_t node_id) {
if (!inst || !inst->rt) return 0;
for (size_t i = 0; i < inst->rt->count; i++) {
struct ROUTE_ENTRY* e = &inst->rt->entries[i];
if (e->network == network && e->prefix_length == prefix_len &&
e->v_node_info && e->v_node_info->node->node_id == node_id) return 1;
}
return 0;
}
static struct ETCP_LINK* find_client_link(struct UTUN_INSTANCE* inst, int idx) {
if (!inst || !inst->connections) return NULL;
struct ll_entry* entry = inst->connections->head;
while (entry) {
struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data;
struct ETCP_LINK* l = ce->conn->links;
while (l) {
if (l->is_server == 0) {
if (idx == 0) return l;
idx--;
}
l = l->next;
}
entry = entry->next;
}
return NULL;
}
static void test_timeout_cb(void* arg) {
(void)arg;
test_phase = 2;
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "test_bgp_route_exchange: overall timeout");
}
static void fail(const char* msg) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: %s", msg);
test_phase = 2;
}
#define PHASE(name) do { \
if (test_phase != 0) break; \
DEBUG_INFO(DEBUG_CATEGORY_BGP, "=== PHASE: %s ===", name); \
} while(0)
#define ASSERT(cond, msg) do { if (!(cond)) { fail(msg); return -1; } } while(0)
// Poll until condition is met or timeout (clock-based)
static int wait_for(const char* desc, int (*cond)(void), int timeout_tb) {
uint64_t start = get_time_tb();
while (!cond() && (get_time_tb() - start) < (uint64_t)timeout_tb && test_phase == 0)
uasync_poll(ua, POLL_INTERVAL_MS);
if (!cond() && test_phase == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "timeout waiting for: %s", desc);
test_phase = 2;
return 0;
}
return test_phase == 0;
}
// -------------------- Condition callbacks for wait_for --------------------
static int cond_all_links_init(void) {
return count_initialized_links(inst_a) >= 2 &&
count_initialized_links(inst_b) >= 1 &&
count_initialized_links(inst_c) >= 1;
}
static int cond_bgp_a_to_c(void) {
return check_learned_route(inst_a, ntohl(inet_addr("192.168.30.0")), 24, g_node_id_c);
}
static int cond_bgp_c_to_a(void) {
return check_learned_route(inst_c, ntohl(inet_addr("192.168.10.0")), 24, g_node_id_a);
}
static int cond_bgp_a_to_c_gone(void) {
return !check_learned_route(inst_a, ntohl(inet_addr("192.168.30.0")), 24, g_node_id_c);
}
static int cond_bgp_c_to_a_gone(void) {
return !check_learned_route(inst_c, ntohl(inet_addr("192.168.10.0")), 24, g_node_id_a);
}
// -------------------- Main --------------------
int main(void) {
debug_config_init();
debug_set_level(DEBUG_LEVEL_ERROR);
debug_set_categories(DEBUG_CATEGORY_BGP);
utun_instance_set_tun_init_enabled(0);
if (create_temp_configs() != 0) return 1;
ua = uasync_create();
ASSERT(ua, "uasync_create");
// Create instances
inst_a = utun_instance_create(ua, config_a); ASSERT(inst_a, "inst_a create");
inst_b = utun_instance_create(ua, config_b); ASSERT(inst_b, "inst_b create");
inst_c = utun_instance_create(ua, config_c); ASSERT(inst_c, "inst_c create");
// Init connections
ASSERT(utun_instance_init(inst_a) == 0, "inst_a init");
ASSERT(utun_instance_init(inst_b) == 0, "inst_b init");
ASSERT(utun_instance_init(inst_c) == 0, "inst_c init");
g_node_id_a = inst_a->node_id; g_node_id_b = inst_b->node_id; g_node_id_c = inst_c->node_id;
// Find links for dummynet filters (client links: idx 0,1 on A, idx 0 on B)
struct ETCP_LINK* ab_link1 = find_client_link(inst_a, 0);
struct ETCP_LINK* ab_link2 = find_client_link(inst_a, 1);
struct ETCP_LINK* bc_link = find_client_link(inst_b, 0);
ASSERT(ab_link1 && ab_link2, "A↔B links not found");
ASSERT(bc_link, "B↔C link not found");
// Create dummynet filters
df_ab1 = dummynet_filter_create(ua); ASSERT(df_ab1, "df_ab1");
df_ab2 = dummynet_filter_create(ua); ASSERT(df_ab2, "df_ab2");
df_bc = dummynet_filter_create(ua); ASSERT(df_bc, "df_bc");
timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_TB, NULL, test_timeout_cb, "test_timeout");
// --- Phase 1: Wait for all links and BGP exchange ---
PHASE("1: Initial BGP exchange");
ASSERT(wait_for("all links initialized", cond_all_links_init, PHASE_TIMEOUT_TB), "links init");
ASSERT(wait_for("BGP A→C", cond_bgp_a_to_c, PHASE_TIMEOUT_TB), "bgp A→C");
ASSERT(wait_for("BGP C→A", cond_bgp_c_to_a, PHASE_TIMEOUT_TB), "bgp C→A");
// --- Phase 2: Kill one A↔B link, verify routes stay ---
PHASE("2: Kill one A↔B link");
dummynet_filter_attach(df_ab1, ab_link1);
dummynet_filter_set_loss(df_ab1, 1000);
// Wait for link-down detection by keepalive, then verify routes survive (link2 still up)
for (int i = 0; i < 200 && test_phase == 0 && ab_link1->link_status; i++) uasync_poll(ua, POLL_INTERVAL_MS);
// Small margin after link-down before checks
for (int i = 0; i < 40 && test_phase == 0; i++) uasync_poll(ua, POLL_INTERVAL_MS);
ASSERT(check_learned_route(inst_a, ntohl(inet_addr("192.168.30.0")), 24, g_node_id_c), "routes lost after 1 link down");
ASSERT(check_learned_route(inst_c, ntohl(inet_addr("192.168.10.0")), 24, g_node_id_a), "routes lost C side");
// --- Phase 3: Restore the link ---
PHASE("3: Restore one A↔B link");
dummynet_filter_set_loss(df_ab1, 0);
ASSERT(wait_for("BGP recovery after ab1 restored", cond_bgp_a_to_c, PHASE_TIMEOUT_TB), "bgp A→C recovery");
// --- Phase 4: Kill B↔C link ---
PHASE("4: Kill B↔C link");
dummynet_filter_attach(df_bc, bc_link);
dummynet_filter_set_loss(df_bc, 1000);
ASSERT(wait_for("A route to C removed", cond_bgp_a_to_c_gone, PHASE_TIMEOUT_TB), "bgp A→C gone");
ASSERT(wait_for("C route to A removed", cond_bgp_c_to_a_gone, PHASE_TIMEOUT_TB), "bgp C→A gone");
// --- Phase 5: Restore B↔C ---
PHASE("5: Restore B↔C link");
dummynet_filter_set_loss(df_bc, 0);
ASSERT(wait_for("BGP A→C restored", cond_bgp_a_to_c, PHASE_TIMEOUT_TB), "bgp A→C restored");
ASSERT(wait_for("BGP C→A restored", cond_bgp_c_to_a, PHASE_TIMEOUT_TB), "bgp C→A restored");
// --- Phase 6: Kill both A↔B links ---
PHASE("6: Kill both A↔B links");
dummynet_filter_set_loss(df_ab1, 1000);
dummynet_filter_attach(df_ab2, ab_link2);
dummynet_filter_set_loss(df_ab2, 1000);
ASSERT(wait_for("A route to C gone (both links)", cond_bgp_a_to_c_gone, PHASE_TIMEOUT_TB), "bgp A→C gone 2");
ASSERT(wait_for("C route to A gone (both links)", cond_bgp_c_to_a_gone, PHASE_TIMEOUT_TB), "bgp C→A gone 2");
// --- Phase 7: Restore one A↔B link (ab1 only, ab2 stays dead) ---
PHASE("7: Restore one A↔B link");
dummynet_filter_set_loss(df_ab1, 0);
ASSERT(wait_for("BGP A→C restored via one link", cond_bgp_a_to_c, PHASE_TIMEOUT_TB), "bgp A→C restored 2");
ASSERT(wait_for("BGP C→A restored via one link", cond_bgp_c_to_a, PHASE_TIMEOUT_TB), "bgp C→A restored 2");
DEBUG_INFO(DEBUG_CATEGORY_BGP, "=== ALL PHASES PASSED ===");
test_phase = 1;
// Cleanup
dummynet_filter_detach(df_ab1); dummynet_filter_detach(df_ab2); dummynet_filter_detach(df_bc);
dummynet_filter_destroy(df_ab1); dummynet_filter_destroy(df_ab2); dummynet_filter_destroy(df_bc);
if (timeout_id) uasync_cancel_timeout(ua, timeout_id);
if (inst_a) { inst_a->running = 0; utun_instance_destroy(inst_a); }
if (inst_b) { inst_b->running = 0; utun_instance_destroy(inst_b); }
if (inst_c) { inst_c->running = 0; utun_instance_destroy(inst_c); }
if (ua) { uasync_destroy(ua, 0); ua = NULL; }
cleanup_temp_configs();
printf("=== %s ===\n", test_phase == 1 ? "TEST PASSED" : "TEST FAILED");
return test_phase == 1 ? 0 : 1;
}