bgp: add test_bgp_triangle + keepalive_adaptive config option
- keepalive_adaptive=0 disables adaptive keepalive period growth, keeping it fixed at 200ms for faster timeout detection in tests - test_bgp_triangle: 5-node triangle topology test (6 phases): validates alternative path recording (fix#1), cascading node removal on full isolation, and full recovery - config generation via utun_instance_create_from_str() with pre-generated keys — no temp files needed
This commit is contained in:
1 parent
1c09ad2e5a
commit
7c953c1929
6 files changed
+504
-4
No files matched your search
@@ -44,6 +44,7 @@ check_PROGRAMS = \
|
||||
test_icmp_proxy \
|
||||
test_tcp_proxy_client \
|
||||
test_bgp_route_exchange \
|
||||
test_bgp_triangle \
|
||||
test_conn_mgr \
|
||||
test_bbr_integration \
|
||||
test_intensive_memory_pool \
|
||||
@@ -310,6 +311,10 @@ test_bgp_route_exchange_SOURCES = test_bgp_route_exchange.c
|
||||
test_bgp_route_exchange_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_bgp_route_exchange_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_bgp_triangle_SOURCES = test_bgp_triangle.c
|
||||
test_bgp_triangle_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_bgp_triangle_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_conn_mgr_SOURCES = test_conn_mgr.c
|
||||
test_conn_mgr_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
|
||||
test_conn_mgr_LDADD = $(top_builddir)/src/utun-dummynet.o $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
@@ -107,6 +107,7 @@ static int create_temp_configs(void) {
|
||||
"my_node_id=0x3333333333333333\n"
|
||||
"tun_ip=10.100.0.3/24\n"
|
||||
"tun_ifname=tun102\n"
|
||||
"keepalive_adaptive=0\n"
|
||||
"\n"
|
||||
"[routing]\n"
|
||||
"my_subnet=192.168.30.0/24\n"
|
||||
@@ -128,6 +129,7 @@ static int create_temp_configs(void) {
|
||||
"my_node_id=0x2222222222222222\n"
|
||||
"tun_ip=10.100.0.2/24\n"
|
||||
"tun_ifname=tun101\n"
|
||||
"keepalive_adaptive=0\n"
|
||||
"\n"
|
||||
"[routing]\n"
|
||||
"my_subnet=192.168.20.0/24\n"
|
||||
@@ -159,6 +161,7 @@ static int create_temp_configs(void) {
|
||||
"my_node_id=0x1111111111111111\n"
|
||||
"tun_ip=10.100.0.1/24\n"
|
||||
"tun_ifname=tun100\n"
|
||||
"keepalive_adaptive=0\n"
|
||||
"\n"
|
||||
"[routing]\n"
|
||||
"my_subnet=192.168.10.0/24\n"
|
||||
|
||||
@@ -0,0 +1,485 @@
|
||||
/**
|
||||
* @file test_bgp_triangle.c
|
||||
* @brief Тест треугольной BGP-маршрутизации с цепочкой D и листом E (5 узлов)
|
||||
*
|
||||
* Топология:
|
||||
* E
|
||||
* │
|
||||
* A ── B ── C ── D
|
||||
* │ │
|
||||
* └─────┘
|
||||
*
|
||||
* A↔B, A↔C, B↔C — треугольник (проверка fix#1: альтернативные пути при старой версии)
|
||||
* C↔D — транзитивная цепочка
|
||||
* B↔E — лист (проверка каскадного удаления)
|
||||
*
|
||||
* Фазы:
|
||||
* 1. Все линки up — BGP-обмен, проверка полного состояния
|
||||
* 2. Kill A–C — C/D/E только через B
|
||||
* 3. Restore A–C — C шлёт NODEINFO напрямую (та же версия). Fix#1: путь [C] добавлен
|
||||
* 4. Kill A–B — C/D выживают через A–C, B/E живут через C
|
||||
* 5. Kill A–C — A изолирован, все узлы удалены
|
||||
* 6. Restore A–C + A–B — полное восстановление
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <time.h>
|
||||
#include "../lib/platform_compat.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#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/route_node.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_TB 200000
|
||||
#define PHASE_TIMEOUT_TB 100000
|
||||
#define POLL_INTERVAL_MS 5
|
||||
|
||||
#define NODE_ID_A 0x1111111111111111ULL
|
||||
#define NODE_ID_B 0x2222222222222222ULL
|
||||
#define NODE_ID_C 0x3333333333333333ULL
|
||||
#define NODE_ID_D 0x4444444444444444ULL
|
||||
#define NODE_ID_E 0x5555555555555555ULL
|
||||
|
||||
static struct UTUN_INSTANCE* inst_a = NULL;
|
||||
static struct UTUN_INSTANCE* inst_b = NULL;
|
||||
static struct UTUN_INSTANCE* inst_c = NULL;
|
||||
static struct UTUN_INSTANCE* inst_d = NULL;
|
||||
static struct UTUN_INSTANCE* inst_e = NULL;
|
||||
static struct UASYNC* ua = NULL;
|
||||
static int test_phase = 0;
|
||||
static void* timeout_id = NULL;
|
||||
|
||||
static struct dummynet_filter* df_ab = NULL;
|
||||
static struct dummynet_filter* df_ac = NULL;
|
||||
|
||||
/* ================================================================
|
||||
* Config generator
|
||||
* ================================================================ */
|
||||
|
||||
struct lnk { const char* local_srv; int remote_port; };
|
||||
struct cli { const char* name; const char* peer_pubkey_hex; int lnk_cnt; struct lnk lnks[2]; };
|
||||
struct srv { const char* name; int port; };
|
||||
struct ncfg {
|
||||
uint64_t node_id; const char* tun_ip; const char* my_subnet;
|
||||
const char* priv_hex; const char* pub_hex;
|
||||
int srv_cnt; struct srv srvs[4];
|
||||
int cli_cnt; struct cli clis[3];
|
||||
};
|
||||
|
||||
static void build_node_config(char* buf, size_t size, const struct ncfg* c) {
|
||||
int off = snprintf(buf, size,
|
||||
"[global]\n"
|
||||
"my_node_id=0x%llx\n"
|
||||
"my_private_key=%s\n"
|
||||
"my_public_key=%s\n"
|
||||
"tun_ip=%s\n"
|
||||
"tun_ifname=tun99\n"
|
||||
"keepalive_adaptive=0\n"
|
||||
"\n"
|
||||
"[routing]\n"
|
||||
"my_subnet=%s\n"
|
||||
"\n",
|
||||
(unsigned long long)c->node_id, c->priv_hex, c->pub_hex, c->tun_ip, c->my_subnet);
|
||||
for (int i = 0; i < c->srv_cnt; i++)
|
||||
off += snprintf(buf + off, size - off,
|
||||
"[server: %s]\naddr=127.0.0.1:%d\ntype=public\n\n", c->srvs[i].name, c->srvs[i].port);
|
||||
for (int i = 0; i < c->cli_cnt; i++) {
|
||||
off += snprintf(buf + off, size - off,
|
||||
"[client: %s]\nkeepalive=1\npeer_public_key=%s\n", c->clis[i].name, c->clis[i].peer_pubkey_hex);
|
||||
for (int j = 0; j < c->clis[i].lnk_cnt; j++)
|
||||
off += snprintf(buf + off, size - off,
|
||||
"link=%s:127.0.0.1:%d\n", c->clis[i].lnks[j].local_srv, c->clis[i].lnks[j].remote_port);
|
||||
off += snprintf(buf + off, size - off, "\n");
|
||||
}
|
||||
off += snprintf(buf + off, size - off, "[allowed_keys]\nallow_all=1\n");
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Verification helpers — проверка внутренних структур
|
||||
* ================================================================ */
|
||||
|
||||
static int peer_in_nodes(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||
if (!inst || !inst->bgp || !inst->bgp->nodes) return 0;
|
||||
return nodeinfo_find_by_id(inst->bgp, node_id) != NULL;
|
||||
}
|
||||
|
||||
static int node_path_count(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(inst->bgp, node_id);
|
||||
if (!nq || !nq->paths) return 0;
|
||||
return queue_entry_count(nq->paths);
|
||||
}
|
||||
|
||||
static int node_path_has_peer(struct UTUN_INSTANCE* inst, uint64_t node_id, uint64_t peer_id) {
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(inst->bgp, node_id);
|
||||
if (!nq || !nq->paths) return 0;
|
||||
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));
|
||||
for (uint8_t i = 0; i < path->hop_count; i++)
|
||||
if (hop[i] == peer_id) return 1;
|
||||
e = e->next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int node_version(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(inst->bgp, node_id);
|
||||
return nq ? nq->last_ver : -1;
|
||||
}
|
||||
|
||||
static int route_count_for_node(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||
if (!inst || !inst->rt) return 0;
|
||||
uint64_t expected_be = htobe64(node_id);
|
||||
int cnt = 0;
|
||||
for (size_t i = 0; i < inst->rt->count; i++)
|
||||
if (inst->rt->entries[i].v_node_info &&
|
||||
inst->rt->entries[i].v_node_info->node.node_id == expected_be) cnt++;
|
||||
return cnt;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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--; }
|
||||
l = l->next;
|
||||
}
|
||||
conn = conn->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void test_timeout_cb(void* arg) {
|
||||
(void)arg;
|
||||
test_phase = 2;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "test_bgp_triangle: 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); goto cleanup; } } while(0)
|
||||
|
||||
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_links_init(void) {
|
||||
return count_initialized_links(inst_a) >= 2 &&
|
||||
count_initialized_links(inst_b) >= 2 &&
|
||||
count_initialized_links(inst_c) >= 1 &&
|
||||
count_initialized_links(inst_e) >= 1;
|
||||
}
|
||||
|
||||
static int cond_d_in_a(void) { return peer_in_nodes(inst_a, NODE_ID_D); }
|
||||
static int cond_d_gone_a(void) { return !peer_in_nodes(inst_a, NODE_ID_D); }
|
||||
static int cond_e_in_a(void) { return peer_in_nodes(inst_a, NODE_ID_E); }
|
||||
static int cond_e_gone_a(void) { return !peer_in_nodes(inst_a, NODE_ID_E); }
|
||||
static int cond_c_in_a(void) { return peer_in_nodes(inst_a, NODE_ID_C); }
|
||||
static int cond_c_gone_a(void) { return !peer_in_nodes(inst_a, NODE_ID_C); }
|
||||
static int cond_c_two_paths(void) {
|
||||
return node_path_count(inst_a, NODE_ID_C) >= 2 &&
|
||||
node_path_has_peer(inst_a, NODE_ID_C, NODE_ID_C) &&
|
||||
node_path_has_peer(inst_a, NODE_ID_C, NODE_ID_B);
|
||||
}
|
||||
|
||||
static int cond_c_one_path_B(void) {
|
||||
return node_path_count(inst_a, NODE_ID_C) == 1 &&
|
||||
node_path_has_peer(inst_a, NODE_ID_C, NODE_ID_B);
|
||||
}
|
||||
|
||||
static int cond_b_gone_a(void) { return !peer_in_nodes(inst_a, NODE_ID_B); }
|
||||
|
||||
static int cond_c_one_path(void) { return node_path_count(inst_a, NODE_ID_C) == 1; }
|
||||
|
||||
/* ================================================================
|
||||
* Main
|
||||
* ================================================================ */
|
||||
|
||||
int main(void) {
|
||||
debug_config_init();
|
||||
debug_set_level(DEBUG_LEVEL_INFO);
|
||||
debug_set_categories(DEBUG_CATEGORY_BGP | DEBUG_CATEGORY_ETCP | DEBUG_CATEGORY_CONNECTION);
|
||||
utun_instance_set_tun_init_enabled(0);
|
||||
|
||||
/* ---------- Pre-generate 5 keypairs ---------- */
|
||||
struct SC_MYKEYS keys[5];
|
||||
char pub_hex[5][SC_PUBKEY_SIZE * 2 + 1];
|
||||
char priv_hex[5][SC_PRIVKEY_SIZE * 2 + 1];
|
||||
for (int i = 0; i < 5; i++) {
|
||||
sc_generate_keypair(&keys[i]);
|
||||
bytes_to_hex(keys[i].public_key, SC_PUBKEY_SIZE, pub_hex[i], sizeof(pub_hex[i]));
|
||||
bytes_to_hex(keys[i].private_key, SC_PRIVKEY_SIZE, priv_hex[i], sizeof(priv_hex[i]));
|
||||
}
|
||||
|
||||
/* ---------- Ports ---------- */
|
||||
int base = 41000 + (getpid() % 15000);
|
||||
int p_ab_a = base++, p_ab_b = base++; // A↔B
|
||||
int p_ac_a = base++, p_ac_c = base++; // A↔C
|
||||
int p_bc_b = base++, p_bc_c = base++; // B↔C (C's second server for B)
|
||||
int p_be_b = base++, p_be_e = base++; // B↔E
|
||||
int p_cd_c = base++, p_cd_d = base++; // C↔D
|
||||
|
||||
/* ---------- Build config strings ---------- */
|
||||
char cfg_a[2048], cfg_b[2048], cfg_c[2048], cfg_d[2048], cfg_e[2048];
|
||||
|
||||
build_node_config(cfg_a, sizeof(cfg_a), &(struct ncfg){
|
||||
.node_id = NODE_ID_A, .tun_ip = "10.100.0.1/24", .my_subnet = "192.168.10.0/24",
|
||||
.priv_hex = priv_hex[0], .pub_hex = pub_hex[0],
|
||||
.srv_cnt = 2, .srvs = {{"a_srv_b", p_ab_a}, {"a_srv_c", p_ac_a}},
|
||||
.cli_cnt = 2, .clis = {
|
||||
{"to_b", pub_hex[1], 1, {{"a_srv_b", p_ab_b}}},
|
||||
{"to_c", pub_hex[2], 1, {{"a_srv_c", p_ac_c}}},
|
||||
}
|
||||
});
|
||||
|
||||
build_node_config(cfg_b, sizeof(cfg_b), &(struct ncfg){
|
||||
.node_id = NODE_ID_B, .tun_ip = "10.100.0.2/24", .my_subnet = "192.168.20.0/24",
|
||||
.priv_hex = priv_hex[1], .pub_hex = pub_hex[1],
|
||||
.srv_cnt = 3, .srvs = {{"b_srv", p_ab_b}, {"b_cli_c", p_bc_b}, {"b_cli_e", p_be_b}},
|
||||
.cli_cnt = 2, .clis = {
|
||||
{"to_c", pub_hex[2], 1, {{"b_cli_c", p_bc_c}}},
|
||||
{"to_e", pub_hex[4], 1, {{"b_cli_e", p_be_e}}},
|
||||
}
|
||||
});
|
||||
|
||||
build_node_config(cfg_c, sizeof(cfg_c), &(struct ncfg){
|
||||
.node_id = NODE_ID_C, .tun_ip = "10.100.0.3/24", .my_subnet = "192.168.30.0/24",
|
||||
.priv_hex = priv_hex[2], .pub_hex = pub_hex[2],
|
||||
.srv_cnt = 3, .srvs = {{"c_srv_a", p_ac_c}, {"c_srv_b", p_bc_c}, {"c_cli_d", p_cd_c}},
|
||||
.cli_cnt = 1, .clis = {
|
||||
{"to_d", pub_hex[3], 1, {{"c_cli_d", p_cd_d}}},
|
||||
}
|
||||
});
|
||||
|
||||
build_node_config(cfg_d, sizeof(cfg_d), &(struct ncfg){
|
||||
.node_id = NODE_ID_D, .tun_ip = "10.100.0.4/24", .my_subnet = "192.168.40.0/24",
|
||||
.priv_hex = priv_hex[3], .pub_hex = pub_hex[3],
|
||||
.srv_cnt = 1, .srvs = {{"d_srv", p_cd_d}},
|
||||
.cli_cnt = 0, .clis = {},
|
||||
});
|
||||
|
||||
build_node_config(cfg_e, sizeof(cfg_e), &(struct ncfg){
|
||||
.node_id = NODE_ID_E, .tun_ip = "10.100.0.5/24", .my_subnet = "192.168.50.0/24",
|
||||
.priv_hex = priv_hex[4], .pub_hex = pub_hex[4],
|
||||
.srv_cnt = 1, .srvs = {{"e_srv", p_be_e}},
|
||||
.cli_cnt = 0, .clis = {},
|
||||
});
|
||||
|
||||
/* ---------- Create instances ---------- */
|
||||
ua = uasync_create();
|
||||
ASSERT(ua, "uasync_create");
|
||||
|
||||
inst_a = utun_instance_create_from_str(ua, cfg_a); ASSERT(inst_a, "inst_a");
|
||||
inst_b = utun_instance_create_from_str(ua, cfg_b); ASSERT(inst_b, "inst_b");
|
||||
inst_c = utun_instance_create_from_str(ua, cfg_c); ASSERT(inst_c, "inst_c");
|
||||
inst_d = utun_instance_create_from_str(ua, cfg_d); ASSERT(inst_d, "inst_d");
|
||||
inst_e = utun_instance_create_from_str(ua, cfg_e); ASSERT(inst_e, "inst_e");
|
||||
|
||||
ASSERT(utun_instance_init(inst_a) == 0, "init a");
|
||||
ASSERT(utun_instance_init(inst_b) == 0, "init b");
|
||||
ASSERT(utun_instance_init(inst_c) == 0, "init c");
|
||||
ASSERT(utun_instance_init(inst_d) == 0, "init d");
|
||||
ASSERT(utun_instance_init(inst_e) == 0, "init e");
|
||||
|
||||
/* ---------- Dummynet filters (2 managed links) ---------- */
|
||||
struct ETCP_LINK* ab_link = find_client_link(inst_a, 0);
|
||||
struct ETCP_LINK* ac_link = find_client_link(inst_a, 1);
|
||||
ASSERT(ab_link && ac_link, "A links not found");
|
||||
|
||||
df_ab = dummynet_filter_create(ua); ASSERT(df_ab, "df_ab");
|
||||
df_ac = dummynet_filter_create(ua); ASSERT(df_ac, "df_ac");
|
||||
|
||||
timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_TB, NULL, test_timeout_cb, "test_timeout");
|
||||
|
||||
/* ================================================================
|
||||
* Phase 1: Initial BGP exchange — all 5 nodes visible
|
||||
* ================================================================ */
|
||||
PHASE("1: Initial BGP — all nodes visible");
|
||||
ASSERT(wait_for("links init", cond_links_init, PHASE_TIMEOUT_TB), "links init timeout");
|
||||
ASSERT(wait_for("D in A nodes", cond_d_in_a, PHASE_TIMEOUT_TB), "D not in A");
|
||||
ASSERT(wait_for("E in A nodes", cond_e_in_a, PHASE_TIMEOUT_TB), "E not in A");
|
||||
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_B), "B missing");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_C), "C missing");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_D), "D missing");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_E), "E missing");
|
||||
{
|
||||
int pb = node_path_count(inst_a, NODE_ID_B);
|
||||
int pc = node_path_count(inst_a, NODE_ID_C);
|
||||
int pd = node_path_count(inst_a, NODE_ID_D);
|
||||
int pe = node_path_count(inst_a, NODE_ID_E);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 1: B_paths=%d C_paths=%d D_paths=%d E_paths=%d", pb, pc, pd, pe);
|
||||
ASSERT(pb >= 1, "B: expected >=1");
|
||||
ASSERT(pc >= 1, "C: expected >=1");
|
||||
ASSERT(pd >= 1, "D: expected >=1");
|
||||
ASSERT(pe >= 1, "E: expected >=1");
|
||||
}
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_B) > 0, "B routes missing");
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_C) > 0, "C routes missing");
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_D) > 0, "D routes missing");
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_E) > 0, "E routes missing");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 1 PASSED");
|
||||
|
||||
/* ================================================================
|
||||
* Phase 2: Kill A–C — C/D/E only through B
|
||||
* ================================================================ */
|
||||
PHASE("2: Kill A–C — C/D/E via B only");
|
||||
dummynet_filter_attach(df_ac, ac_link);
|
||||
dummynet_filter_set_loss(df_ac, 1000);
|
||||
ASSERT(wait_for("C: 1 path via B after A-C kill", cond_c_one_path_B, PHASE_TIMEOUT_TB), "C paths not 1");
|
||||
{
|
||||
int pc = node_path_count(inst_a, NODE_ID_C);
|
||||
int pd = node_path_count(inst_a, NODE_ID_D);
|
||||
int pe = node_path_count(inst_a, NODE_ID_E);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 2: C_paths=%d D_paths=%d E_paths=%d", pc, pd, pe);
|
||||
ASSERT(cond_c_in_a(), "C missing after A-C kill");
|
||||
ASSERT(cond_d_in_a(), "D missing after A-C kill");
|
||||
ASSERT(cond_e_in_a(), "E missing after A-C kill");
|
||||
ASSERT(pd == 1, "D: expected 1 path");
|
||||
ASSERT(pe >= 1, "E: expected >=1");
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 2 PASSED");
|
||||
|
||||
/* ================================================================
|
||||
* Phase 3: Restore A–C — C sends NODEINFO directly (same ver).
|
||||
* Fix#1: path [C] added despite old version.
|
||||
* ================================================================ */
|
||||
PHASE("3: Restore A–C");
|
||||
dummynet_filter_set_loss(df_ac, 0);
|
||||
ASSERT(wait_for("C: >=2 paths after A-C restore", cond_c_two_paths, PHASE_TIMEOUT_TB), "C paths not >=2");
|
||||
{
|
||||
int cp = node_path_count(inst_a, NODE_ID_C);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 3 PASSED (C paths=%d)", cp);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Phase 4: Kill A–B — B/E survive via C relay, C/D via A-C
|
||||
* ================================================================ */
|
||||
PHASE("4: Kill A–B — B/E survive via C");
|
||||
dummynet_filter_attach(df_ab, ab_link);
|
||||
dummynet_filter_set_loss(df_ab, 1000);
|
||||
/* After A-B kill: direct paths removed, B/C/E survive via C relay */
|
||||
ASSERT(wait_for("A-B paths removed", cond_c_one_path, PHASE_TIMEOUT_TB), "A-B paths not removed");
|
||||
{
|
||||
int pb = node_path_count(inst_a, NODE_ID_B);
|
||||
int pe = node_path_count(inst_a, NODE_ID_E);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 4: B_paths=%d E_paths=%d", pb, pe);
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_B), "B missing — should survive via C");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_E), "E missing — should survive via C");
|
||||
ASSERT(cond_c_in_a(), "C missing");
|
||||
ASSERT(cond_d_in_a(), "D missing");
|
||||
ASSERT(node_path_count(inst_a, NODE_ID_C) == 1, "C: expected 1 path via A-C");
|
||||
ASSERT(node_path_has_peer(inst_a, NODE_ID_C, NODE_ID_C), "C: missing direct path");
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 4 PASSED");
|
||||
|
||||
/* ================================================================
|
||||
* Phase 5: Kill A–C — A fully isolated (A-B already dead)
|
||||
* ================================================================ */
|
||||
PHASE("5: Kill A–C — A isolated");
|
||||
dummynet_filter_set_loss(df_ac, 1000);
|
||||
ASSERT(wait_for("C gone from A", cond_c_gone_a, PHASE_TIMEOUT_TB), "C still in A");
|
||||
ASSERT(wait_for("D gone from A", cond_d_gone_a, PHASE_TIMEOUT_TB), "D still in A");
|
||||
ASSERT(!peer_in_nodes(inst_a, NODE_ID_B), "B should be gone");
|
||||
ASSERT(!peer_in_nodes(inst_a, NODE_ID_C), "C should be gone");
|
||||
ASSERT(!peer_in_nodes(inst_a, NODE_ID_D), "D should be gone");
|
||||
ASSERT(!peer_in_nodes(inst_a, NODE_ID_E), "E should be gone");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 5 PASSED");
|
||||
|
||||
/* ================================================================
|
||||
* Phase 6: Restore A–C + A–B — full recovery
|
||||
* ================================================================ */
|
||||
PHASE("6: Full restore — both links back");
|
||||
dummynet_filter_set_loss(df_ac, 0);
|
||||
dummynet_filter_set_loss(df_ab, 0);
|
||||
ASSERT(wait_for("D in A after restore", cond_d_in_a, 2 * PHASE_TIMEOUT_TB), "D not back");
|
||||
ASSERT(wait_for("E in A after restore", cond_e_in_a, PHASE_TIMEOUT_TB), "E not back");
|
||||
ASSERT(wait_for("C: >=2 paths after restore", cond_c_two_paths, PHASE_TIMEOUT_TB), "C paths not >=2");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_B), "B missing after restore");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_C), "C missing after restore");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_D), "D missing after restore");
|
||||
ASSERT(peer_in_nodes(inst_a, NODE_ID_E), "E missing after restore");
|
||||
ASSERT(node_path_count(inst_a, NODE_ID_C) >= 2, "C: expected >=2 paths");
|
||||
ASSERT(node_path_count(inst_a, NODE_ID_D) >= 1, "D: expected >=1");
|
||||
ASSERT(node_path_count(inst_a, NODE_ID_B) >= 1, "B: expected >=1");
|
||||
ASSERT(node_path_count(inst_a, NODE_ID_E) >= 1, "E: expected >=1");
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_B) > 0, "B routes");
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_C) > 0, "C routes");
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_D) > 0, "D routes");
|
||||
ASSERT(route_count_for_node(inst_a, NODE_ID_E) > 0, "E routes");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 6 PASSED");
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_BGP, "=== ALL PHASES PASSED ===");
|
||||
test_phase = 1;
|
||||
|
||||
cleanup:
|
||||
if (df_ab) { dummynet_filter_detach(df_ab); dummynet_filter_destroy(df_ab); }
|
||||
if (df_ac) { dummynet_filter_detach(df_ac); dummynet_filter_destroy(df_ac); }
|
||||
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 (inst_d) { inst_d->running = 0; utun_instance_destroy(inst_d); }
|
||||
if (inst_e) { inst_e->running = 0; utun_instance_destroy(inst_e); }
|
||||
if (ua) { uasync_destroy(ua, 0); ua = NULL; }
|
||||
|
||||
printf("=== %s ===\n", test_phase == 1 ? "TEST PASSED" : "TEST FAILED");
|
||||
return test_phase == 1 ? 0 : 1;
|
||||
}
|
||||
Reference in new issue
Block a user