|
|
|
|
@ -1,17 +1,25 @@
|
|
|
|
|
/**
|
|
|
|
|
* @file test_bgp_route_exchange.c |
|
|
|
|
* @brief Интеграционный тест обмена маршрутами между двумя инстансами |
|
|
|
|
*
|
|
|
|
|
* Тест проверяет: |
|
|
|
|
* - Создание двух инстансов с разными подсетями |
|
|
|
|
* - Установление ETCP соединения |
|
|
|
|
* - Обмен маршрутами через BGP |
|
|
|
|
* - Проверку таблиц роутинга (client -> server direction) |
|
|
|
|
* @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" |
|
|
|
|
@ -20,7 +28,6 @@
|
|
|
|
|
#ifdef _WIN32 |
|
|
|
|
#include <windows.h> |
|
|
|
|
#include <direct.h> |
|
|
|
|
#define _mkdir(path) _mkdir(path) |
|
|
|
|
#else |
|
|
|
|
#include <unistd.h> |
|
|
|
|
#endif |
|
|
|
|
@ -28,436 +35,352 @@
|
|
|
|
|
#include "../src/etcp.h" |
|
|
|
|
#include "../src/etcp_connections.h" |
|
|
|
|
#include "../src/config_parser.h" |
|
|
|
|
#include "../src/config_updater.h" |
|
|
|
|
#include "../src/utun_instance.h" |
|
|
|
|
#include "../src/routing.h" |
|
|
|
|
#include "../src/route_lib.h" |
|
|
|
|
#include "../src/route_bgp.h" |
|
|
|
|
#include "../src/dummynet.h" |
|
|
|
|
#include "../src/tun_if.h" |
|
|
|
|
#include "../src/secure_channel.h" |
|
|
|
|
#include "../lib/u_async.h" |
|
|
|
|
#include "../lib/debug_config.h" |
|
|
|
|
#include "../lib/mem.h" |
|
|
|
|
|
|
|
|
|
#define TEST_TIMEOUT_MS 5000 // 5 seconds timeout
|
|
|
|
|
#define BGP_EXCHANGE_WAIT_MS 200 // Wait for BGP exchange (100ms + margin)
|
|
|
|
|
#define TEST_TIMEOUT_TB 200000 // 20s in 0.1ms units
|
|
|
|
|
#define PHASE_TIMEOUT_TB 100000 // 10s per phase
|
|
|
|
|
#define POLL_INTERVAL_MS 5 |
|
|
|
|
|
|
|
|
|
// Test state
|
|
|
|
|
static struct UTUN_INSTANCE* server_instance = NULL; |
|
|
|
|
static struct UTUN_INSTANCE* client_instance = NULL; |
|
|
|
|
#define NODE_ID_A 0x1111111111111111ULL |
|
|
|
|
#define NODE_ID_B 0x2222222222222222ULL |
|
|
|
|
#define NODE_ID_C 0x3333333333333333ULL |
|
|
|
|
|
|
|
|
|
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=init, 1=connected, 2=bgp_exchanged, 3=success, 4=failure
|
|
|
|
|
static void* monitor_timeout_id = NULL; |
|
|
|
|
static void* test_timeout_id = NULL; |
|
|
|
|
static int test_phase = 0; // 0=running, 1=success, 2=failure
|
|
|
|
|
static void* timeout_id = NULL; |
|
|
|
|
|
|
|
|
|
// Temp config file paths
|
|
|
|
|
#ifdef _WIN32 |
|
|
|
|
static char temp_dir[MAX_PATH]; |
|
|
|
|
#else |
|
|
|
|
static char temp_dir[] = "/tmp/utun_bgp_test_XXXXXX"; |
|
|
|
|
#endif |
|
|
|
|
static char server_config_path[256]; |
|
|
|
|
static char client_config_path[256]; |
|
|
|
|
// 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; |
|
|
|
|
|
|
|
|
|
// Cross-platform mkdtemp replacement
|
|
|
|
|
static int create_temp_directory(void) { |
|
|
|
|
#ifdef _WIN32 |
|
|
|
|
char tmp_path[MAX_PATH]; |
|
|
|
|
GetTempPathA(MAX_PATH, tmp_path); |
|
|
|
|
|
|
|
|
|
// Generate unique directory name
|
|
|
|
|
srand((unsigned int)GetTickCount()); |
|
|
|
|
int attempts = 0; |
|
|
|
|
while (attempts < 100) { |
|
|
|
|
snprintf(temp_dir, sizeof(temp_dir), "%s\\utun_bgp_test_%08x",
|
|
|
|
|
tmp_path, (unsigned int)rand()); |
|
|
|
|
if (_mkdir(temp_dir) == 0) { |
|
|
|
|
return 0; // Success
|
|
|
|
|
} |
|
|
|
|
attempts++; |
|
|
|
|
} |
|
|
|
|
return -1; // Failed after 100 attempts
|
|
|
|
|
#else |
|
|
|
|
return (test_mkdtemp(temp_dir) != 0) ? -1 : 0; |
|
|
|
|
#endif |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Server config: 2 subnets - 192.168.10.0/24 and 192.168.11.0/24
|
|
|
|
|
static const char* server_config_content = |
|
|
|
|
"[global]\n" |
|
|
|
|
"my_node_id=0x1111111111111111\n" |
|
|
|
|
"my_private_key=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n" |
|
|
|
|
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n" |
|
|
|
|
"tun_ip=10.99.0.1/24\n" |
|
|
|
|
"tun_ifname=tun99\n" |
|
|
|
|
"\n" |
|
|
|
|
"[routing]\n" |
|
|
|
|
"my_subnet=192.168.10.0/24\n" |
|
|
|
|
"my_subnet=192.168.11.0/24\n" |
|
|
|
|
"\n" |
|
|
|
|
"[server: test]\n" |
|
|
|
|
"addr=127.0.0.1:39011\n" |
|
|
|
|
"type=public\n" |
|
|
|
|
"\n" |
|
|
|
|
"[allowed_keys]\n" |
|
|
|
|
"allow_all=1\n"; |
|
|
|
|
|
|
|
|
|
// Client config: 2 subnets - 192.168.20.0/24 and 192.168.21.0/24
|
|
|
|
|
static const char* client_config_content = |
|
|
|
|
"[global]\n" |
|
|
|
|
"my_node_id=0x2222222222222222\n" |
|
|
|
|
"my_private_key=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2\n" |
|
|
|
|
"my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\n" |
|
|
|
|
"tun_ip=10.99.0.2/24\n" |
|
|
|
|
"tun_ifname=tun98\n" |
|
|
|
|
"\n" |
|
|
|
|
"[routing]\n" |
|
|
|
|
"my_subnet=192.168.20.0/24\n" |
|
|
|
|
"my_subnet=192.168.21.0/24\n" |
|
|
|
|
"\n" |
|
|
|
|
"[server: test]\n" |
|
|
|
|
"addr=127.0.0.1:39012\n" |
|
|
|
|
"type=public\n" |
|
|
|
|
"\n" |
|
|
|
|
"[client: test_client]\n" |
|
|
|
|
"keepalive=1\n" |
|
|
|
|
"peer_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\n" |
|
|
|
|
"link=test:127.0.0.1:39011\n"; |
|
|
|
|
|
|
|
|
|
// Create temp config files
|
|
|
|
|
static int create_temp_configs(void) { |
|
|
|
|
if (create_temp_directory() != 0) { |
|
|
|
|
fprintf(stderr, "Failed to create temp directory\n"); |
|
|
|
|
return -1; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
snprintf(server_config_path, sizeof(server_config_path), "%s/server.conf", temp_dir); |
|
|
|
|
snprintf(client_config_path, sizeof(client_config_path), "%s/client.conf", temp_dir); |
|
|
|
|
|
|
|
|
|
FILE* f = fopen(server_config_path, "w"); |
|
|
|
|
if (!f) { |
|
|
|
|
fprintf(stderr, "Failed to create server config file\n"); |
|
|
|
|
return -1; |
|
|
|
|
} |
|
|
|
|
fprintf(f, "%s", server_config_content); |
|
|
|
|
fclose(f); |
|
|
|
|
|
|
|
|
|
f = fopen(client_config_path, "w"); |
|
|
|
|
if (!f) { |
|
|
|
|
fprintf(stderr, "Failed to create client config file\n"); |
|
|
|
|
test_unlink(server_config_path); |
|
|
|
|
return -1; |
|
|
|
|
} |
|
|
|
|
fprintf(f, "%s", client_config_content); |
|
|
|
|
fclose(f); |
|
|
|
|
|
|
|
|
|
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" |
|
|
|
|
"my_node_id=0x3333333333333333\n" |
|
|
|
|
"tun_ip=10.100.0.3/24\n" |
|
|
|
|
"tun_ifname=tun102\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" |
|
|
|
|
"my_node_id=0x2222222222222222\n" |
|
|
|
|
"tun_ip=10.100.0.2/24\n" |
|
|
|
|
"tun_ifname=tun101\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" |
|
|
|
|
"my_node_id=0x1111111111111111\n" |
|
|
|
|
"tun_ip=10.100.0.1/24\n" |
|
|
|
|
"tun_ifname=tun100\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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Cleanup temp config files
|
|
|
|
|
static void cleanup_temp_configs(void) { |
|
|
|
|
#ifdef _WIN32 |
|
|
|
|
if (server_config_path[0]) _unlink(server_config_path); |
|
|
|
|
if (client_config_path[0]) _unlink(client_config_path); |
|
|
|
|
if (temp_dir[0]) _rmdir(temp_dir); |
|
|
|
|
#else |
|
|
|
|
if (server_config_path[0]) test_unlink(server_config_path); |
|
|
|
|
if (client_config_path[0]) test_unlink(client_config_path); |
|
|
|
|
if (temp_dir[0]) test_rmdir(temp_dir); |
|
|
|
|
#endif |
|
|
|
|
test_unlink(config_a); test_unlink(config_b); test_unlink(config_c); |
|
|
|
|
test_rmdir(temp_dir); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Check if connection is established (link initialized)
|
|
|
|
|
static int is_connection_established(struct UTUN_INSTANCE* inst) { |
|
|
|
|
static int has_initialized_link(struct UTUN_INSTANCE* inst) { |
|
|
|
|
if (!inst) return 0; |
|
|
|
|
|
|
|
|
|
struct ETCP_CONN* conn = inst->connections; |
|
|
|
|
while (conn) { |
|
|
|
|
struct ETCP_LINK* link = conn->links; |
|
|
|
|
while (link) { |
|
|
|
|
if (link->initialized) { |
|
|
|
|
return 1; |
|
|
|
|
} |
|
|
|
|
link = link->next; |
|
|
|
|
} |
|
|
|
|
conn = conn->next; |
|
|
|
|
} |
|
|
|
|
while (conn) { struct ETCP_LINK* l = conn->links; while (l) { if (l->initialized) return 1; l = l->next; } conn = conn->next; } |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Print routing table contents
|
|
|
|
|
static void print_routing_table(struct UTUN_INSTANCE* inst, const char* name) { |
|
|
|
|
if (!inst || !inst->rt) { |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "%s: No routing table\n", name); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "\n=== %s Routing Table (%zu routes) ===", name, inst->rt->count); |
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < inst->rt->count; i++) { |
|
|
|
|
struct ROUTE_ENTRY* entry = &inst->rt->entries[i]; |
|
|
|
|
char network_str[16]; |
|
|
|
|
struct in_addr addr; |
|
|
|
|
addr.s_addr = htonl(entry->network); |
|
|
|
|
inet_ntop(AF_INET, &addr, network_str, sizeof(network_str)); |
|
|
|
|
|
|
|
|
|
const char* type_str = entry->v_node_info ? "LEARNED" : "LOCAL"; |
|
|
|
|
|
|
|
|
|
uint64_t node_id = 0; |
|
|
|
|
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",
|
|
|
|
|
i + 1, network_str, entry->prefix_length, type_str, |
|
|
|
|
(unsigned long long)node_id); |
|
|
|
|
} |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "=====================================\n"); |
|
|
|
|
static int count_initialized_links(struct UTUN_INSTANCE* inst) { |
|
|
|
|
int n = 0; |
|
|
|
|
if (!inst) return 0; |
|
|
|
|
struct ETCP_CONN* conn = inst->connections; |
|
|
|
|
while (conn) { struct ETCP_LINK* l = conn->links; while (l) { if (l->initialized) n++; l = l->next; } conn = conn->next; } |
|
|
|
|
return n; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Check if specific learned route exists
|
|
|
|
|
static int check_learned_route(struct UTUN_INSTANCE* inst, uint32_t network,
|
|
|
|
|
uint8_t prefix_len, uint64_t expected_node_id) { |
|
|
|
|
if (!inst || !inst->rt) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "check_learned_route: no instance or rt"); |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
uint64_t expected_be = htobe64(expected_node_id); |
|
|
|
|
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; |
|
|
|
|
uint64_t expected_be = htobe64(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->v_node_info != NULL && |
|
|
|
|
entry->v_node_info->node.node_id == expected_be) { |
|
|
|
|
return 1; |
|
|
|
|
} |
|
|
|
|
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 == expected_be) return 1; |
|
|
|
|
} |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Verify BGP route exchange results
|
|
|
|
|
static int verify_bgp_exchange(void) { |
|
|
|
|
int success = 1; |
|
|
|
|
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "\n=== Verifying BGP Route Exchange ===\n"); |
|
|
|
|
|
|
|
|
|
// Print tables for debugging
|
|
|
|
|
print_routing_table(server_instance, "SERVER"); |
|
|
|
|
print_routing_table(client_instance, "CLIENT"); |
|
|
|
|
|
|
|
|
|
// Check server learned client's routes (192.168.20.0/24, 192.168.21.0/24)
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Checking server learned client's routes..."); |
|
|
|
|
|
|
|
|
|
if (!check_learned_route(server_instance,0xc0a81400,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)) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING,"FAIL: Server missing learned route 192.168.21.0/24"); |
|
|
|
|
success = 0; |
|
|
|
|
} else { |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ROUTING,"PASS: Server has learned route 192.168.21.0/24"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Check client learned server's routes (192.168.10.0/24, 192.168.11.0/24)
|
|
|
|
|
// NOTE: For now we only test client -> server direction
|
|
|
|
|
// This will be enabled after server -> client is implemented
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "Checking client learned server's routes (disabled - server->client not implemented)..."); |
|
|
|
|
|
|
|
|
|
return success; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Monitor callback - checks connection status and BGP exchange
|
|
|
|
|
static void monitor_connections(void* arg) { |
|
|
|
|
(void)arg; |
|
|
|
|
|
|
|
|
|
if (test_phase >= 3) { |
|
|
|
|
monitor_timeout_id = NULL; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
switch (test_phase) { |
|
|
|
|
case 0: { // Waiting for connection
|
|
|
|
|
int server_connected = is_connection_established(server_instance); |
|
|
|
|
int client_connected = is_connection_established(client_instance); |
|
|
|
|
|
|
|
|
|
if (server_connected && client_connected) { |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "=== Connection established! Waiting for BGP exchange... ==="); |
|
|
|
|
test_phase = 1; |
|
|
|
|
// Schedule next check after BGP exchange time
|
|
|
|
|
monitor_timeout_id = uasync_set_timeout(ua, BGP_EXCHANGE_WAIT_MS, NULL, monitor_connections, "test_monitor"); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
case 1: { // Connection established, wait for BGP exchange
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "=== Checking BGP route exchange ==="); |
|
|
|
|
test_phase = 2; |
|
|
|
|
|
|
|
|
|
// Verify routes
|
|
|
|
|
if (verify_bgp_exchange()) { |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ROUTING, "=== BGP ROUTE EXCHANGE SUCCESS ==="); |
|
|
|
|
test_phase = 3; // Success
|
|
|
|
|
} else { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "=== BGP ROUTE EXCHANGE FAILED ==="); |
|
|
|
|
test_phase = 4; // Failure
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Cancel timeout for quick exit
|
|
|
|
|
if (test_timeout_id) { |
|
|
|
|
uasync_cancel_timeout(ua, test_timeout_id); |
|
|
|
|
test_timeout_id = NULL; |
|
|
|
|
static struct ETCP_LINK* find_client_link(struct UTUN_INSTANCE* inst, int idx) { |
|
|
|
|
if (!inst) return NULL; |
|
|
|
|
struct ETCP_CONN* conn = inst->connections; |
|
|
|
|
while (conn) { |
|
|
|
|
struct ETCP_LINK* l = conn->links; |
|
|
|
|
while (l) { |
|
|
|
|
if (l->is_server == 0) { |
|
|
|
|
if (idx == 0) return l; |
|
|
|
|
idx--; |
|
|
|
|
} |
|
|
|
|
return; |
|
|
|
|
l = l->next; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
case 2: // Verification done
|
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Schedule next check
|
|
|
|
|
if (test_phase < 3) { |
|
|
|
|
monitor_timeout_id = uasync_set_timeout(ua, 100, NULL, monitor_connections, "test_monitor"); |
|
|
|
|
conn = conn->next; |
|
|
|
|
} |
|
|
|
|
return NULL; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void test_timeout(void* arg) { |
|
|
|
|
static void test_timeout_cb(void* arg) { |
|
|
|
|
(void)arg; |
|
|
|
|
if (test_phase < 3) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "\n=== TEST TIMEOUT ==="); |
|
|
|
|
test_phase = 4; // Failure
|
|
|
|
|
if (monitor_timeout_id) { |
|
|
|
|
uasync_cancel_timeout(ua, monitor_timeout_id); |
|
|
|
|
monitor_timeout_id = NULL; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
int main() { |
|
|
|
|
printf("=== BGP Route Exchange Test ===\n"); |
|
|
|
|
|
|
|
|
|
// Create temp config files
|
|
|
|
|
if (create_temp_configs() != 0) { |
|
|
|
|
fprintf(stderr, "Failed to create temporary config files\n"); |
|
|
|
|
return 1; |
|
|
|
|
#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; } } 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, 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, 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, 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, NODE_ID_A); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// -------------------- Main --------------------
|
|
|
|
|
int main(void) { |
|
|
|
|
debug_config_init(); |
|
|
|
|
debug_set_level(DEBUG_LEVEL_INFO); |
|
|
|
|
debug_set_categories(DEBUG_CATEGORY_ROUTING | DEBUG_CATEGORY_ETCP | DEBUG_CATEGORY_BGP); |
|
|
|
|
|
|
|
|
|
// Disable TUN for testing
|
|
|
|
|
debug_set_categories(DEBUG_CATEGORY_BGP | DEBUG_CATEGORY_ETCP | DEBUG_CATEGORY_CONNECTION); |
|
|
|
|
utun_instance_set_tun_init_enabled(0); |
|
|
|
|
|
|
|
|
|
// Create shared uasync context
|
|
|
|
|
|
|
|
|
|
if (create_temp_configs() != 0) return 1; |
|
|
|
|
|
|
|
|
|
ua = uasync_create(); |
|
|
|
|
if (!ua) { |
|
|
|
|
fprintf(stderr, "Failed to create uasync context\n"); |
|
|
|
|
return 1; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Create server instance
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating server instance..."); |
|
|
|
|
server_instance = utun_instance_create(ua, server_config_path); |
|
|
|
|
if (!server_instance) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to create server instance"); |
|
|
|
|
return 1; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Initialize server instance (includes connections, routing, and BGP)
|
|
|
|
|
if (utun_instance_init(server_instance) < 0) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize server instance"); |
|
|
|
|
return 1; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// BGP module is now initialized automatically in utun_instance_create
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Server instance ready (node_id=%016llX, bgp=%p)",
|
|
|
|
|
(unsigned long long)server_instance->node_id, (void*)server_instance->bgp); |
|
|
|
|
|
|
|
|
|
// Create client instance
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating client instance..."); |
|
|
|
|
client_instance = utun_instance_create(ua, client_config_path); |
|
|
|
|
if (!client_instance) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to create client instance"); |
|
|
|
|
return 1; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Initialize client instance (includes connections, routing, and BGP)
|
|
|
|
|
if (utun_instance_init(client_instance) < 0) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize client instance"); |
|
|
|
|
return 1; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// BGP module is now initialized automatically in utun_instance_create
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Client instance ready (node_id=%016llX, bgp=%p)",
|
|
|
|
|
(unsigned long long)client_instance->node_id, (void*)client_instance->bgp); |
|
|
|
|
|
|
|
|
|
// Give server time to initialize
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Waiting for server initialization..."); |
|
|
|
|
for (int i = 0; i < 50; i++) { |
|
|
|
|
uasync_poll(ua, 10); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Start monitoring
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Starting connection monitoring..."); |
|
|
|
|
monitor_timeout_id = uasync_set_timeout(ua, 100, NULL, monitor_connections, "test_monitor"); |
|
|
|
|
test_timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS, NULL, test_timeout, "test_timeout"); |
|
|
|
|
|
|
|
|
|
// Main event loop - use uasync timers only, no artificial timing
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Running event loop..."); |
|
|
|
|
|
|
|
|
|
while (test_phase < 3) { |
|
|
|
|
uasync_poll(ua, -1); // Wait indefinitely for events/timeouts
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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"); |
|
|
|
|
|
|
|
|
|
// 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 keepalive timeout + margin, then verify routes survive (link2 still up)
|
|
|
|
|
for (int i = 0; i < 600 && test_phase == 0; i++) uasync_poll(ua, POLL_INTERVAL_MS); // 3s
|
|
|
|
|
ASSERT(check_learned_route(inst_a, ntohl(inet_addr("192.168.30.0")), 24, NODE_ID_C), "routes lost after 1 link down"); |
|
|
|
|
ASSERT(check_learned_route(inst_c, ntohl(inet_addr("192.168.10.0")), 24, 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
|
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "\nCleaning up..."); |
|
|
|
|
|
|
|
|
|
if (monitor_timeout_id) { |
|
|
|
|
uasync_cancel_timeout(ua, monitor_timeout_id); |
|
|
|
|
monitor_timeout_id = NULL; |
|
|
|
|
} |
|
|
|
|
if (test_timeout_id) { |
|
|
|
|
uasync_cancel_timeout(ua, test_timeout_id); |
|
|
|
|
test_timeout_id = NULL; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Print final routing tables
|
|
|
|
|
print_routing_table(server_instance, "SERVER (final)"); |
|
|
|
|
print_routing_table(client_instance, "CLIENT (final)"); |
|
|
|
|
|
|
|
|
|
// Destroy instances
|
|
|
|
|
if (server_instance) { |
|
|
|
|
server_instance->running = 0; |
|
|
|
|
utun_instance_destroy(server_instance); |
|
|
|
|
} |
|
|
|
|
if (client_instance) { |
|
|
|
|
client_instance->running = 0; |
|
|
|
|
utun_instance_destroy(client_instance); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (ua) { |
|
|
|
|
uasync_destroy(ua, 0); |
|
|
|
|
ua = NULL; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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(); |
|
|
|
|
|
|
|
|
|
// Test result
|
|
|
|
|
if (test_phase == 3) { |
|
|
|
|
printf("\n=== TEST PASSED ===\n"); |
|
|
|
|
printf("BGP route exchange working (client -> server direction)\n"); |
|
|
|
|
return 0; |
|
|
|
|
} else { |
|
|
|
|
printf("\n=== TEST FAILED ===\n"); |
|
|
|
|
if (test_phase == 4) { |
|
|
|
|
printf("BGP route exchange verification failed\n"); |
|
|
|
|
} else { |
|
|
|
|
printf("Test timeout or connection failed\n"); |
|
|
|
|
} |
|
|
|
|
return 1; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
printf("=== %s ===\n", test_phase == 1 ? "TEST PASSED" : "TEST FAILED"); |
|
|
|
|
return test_phase == 1 ? 0 : 1; |
|
|
|
|
} |
|
|
|
|
|