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.
164 lines
7.6 KiB
164 lines
7.6 KiB
/* Управляемая сеть: настоящий BGP receiver/sender, подписи и очереди, без UDP. |
|
* Доставка по каждому ребру сохраняет FIFO, порядок между рёбрами меняется. */ |
|
#include <assert.h> |
|
#include <stdio.h> |
|
#include <string.h> |
|
#include "utun_instance.h" |
|
#include "config_updater.h" |
|
#include "topo_group.h" |
|
#include "etcp.h" |
|
#include "etcp_api.h" |
|
#include "node_conn_direct.h" |
|
#include "../lib/debug_config.h" |
|
#include "test_utils.h" |
|
|
|
#define N 4 |
|
#define GROUP 42 |
|
static struct UASYNC* ua; |
|
static struct UTUN_INSTANCE* nodes[N]; |
|
static struct TOPO_GROUP* groups[N]; |
|
static struct ETCP_CONN* conns[N][N]; |
|
static struct NODE_CONN_DIRECT* owners[N][N]; |
|
static unsigned graph, delivered; |
|
static uint32_t random_state = 1; |
|
|
|
static unsigned next_random(void) { |
|
random_state = random_state * 1664525U + 1013904223U; |
|
return random_state; |
|
} |
|
|
|
static unsigned edge(int a, int b) { |
|
if (a > b) { int t = a; a = b; b = t; } |
|
unsigned bit = 1; |
|
for (int i = 0; i < N; i++) for (int j = i + 1; j < N; j++, bit <<= 1) |
|
if (i == a && j == b) return bit; |
|
return 0; |
|
} |
|
|
|
static void release(struct ll_entry* e) { queue_dgram_free(e); queue_entry_free(e); } |
|
|
|
/* Единственный ручной потребитель send_input_q; штатный normalizer отключён. */ |
|
static int step(void) { |
|
uasync_poll(ua, 0); |
|
int work = 0, offset = next_random() % (N * N); |
|
for (int k = 0; k < N * N; k++) { |
|
int a = ((k + offset) % (N * N)) / N, b = (k + offset) % N; |
|
if (a == b) continue; |
|
struct ll_queue* q = conns[a][b]->send_input_q; |
|
assert(queue_entry_count(q) <= 1); |
|
struct ll_entry* e = queue_data_get(q); |
|
if (!e) continue; |
|
work++; |
|
if ((graph & edge(a, b)) && e->len && e->dgram[0] == ETCP_ID_TOPO_ENTRY) { |
|
delivered++; |
|
nodes[b]->api_bindings.callbacks[ETCP_ID_TOPO_ENTRY](conns[b][a], e); |
|
} else release(e); |
|
queue_resume_callback(q); |
|
} |
|
return work; |
|
} |
|
|
|
static void settle(void) { |
|
unsigned before = delivered; |
|
int idle = 0; |
|
for (int i = 0; i < 4000 && idle < 16; i++) idle = step() ? 0 : idle + 1; |
|
assert(idle == 16); |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "model settled graph=%02x packets=%u", graph, delivered - before); |
|
} |
|
|
|
/* Смена графа без обработки очередей между событиями разрыва и восстановления. */ |
|
static void change(unsigned mask) { |
|
unsigned old = graph; graph = mask; |
|
for (int a = 0; a < N; a++) for (int b = a + 1; b < N; b++) { |
|
if (!(old & edge(a, b)) || (mask & edge(a, b))) continue; |
|
conns[a][b]->links_up = conns[b][a]->links_up = 0; |
|
topo_group_remove_conn(groups[a], conns[a][b], TOPO_REMOVE_REMOTE_LEAVE); |
|
topo_group_remove_conn(groups[b], conns[b][a], TOPO_REMOVE_REMOTE_LEAVE); |
|
struct ll_entry* e; |
|
while ((e = queue_data_get(conns[a][b]->send_input_q))) release(e); |
|
while ((e = queue_data_get(conns[b][a]->send_input_q))) release(e); |
|
} |
|
for (int a = 0; a < N; a++) for (int b = a + 1; b < N; b++) { |
|
if ((old & edge(a, b)) || !(mask & edge(a, b))) continue; |
|
conns[a][b]->links_up = conns[b][a]->links_up = 1; |
|
assert(topo_group_new_conn(groups[a], conns[a][b]) == 0); |
|
assert(topo_group_new_conn(groups[b], conns[b][a]) == 0); |
|
} |
|
} |
|
|
|
/* Независимый эталон — кратчайшие расстояния в графе реальных соединений. */ |
|
static void verify(void) { |
|
int distance[N][N]; |
|
for (int a = 0; a < N; a++) for (int b = 0; b < N; b++) |
|
distance[a][b] = a == b ? 0 : (graph & edge(a, b)) ? 1 : 100; |
|
for (int k = 0; k < N; k++) for (int a = 0; a < N; a++) for (int b = 0; b < N; b++) |
|
if (distance[a][k] + distance[k][b] < distance[a][b]) distance[a][b] = distance[a][k] + distance[k][b]; |
|
for (int a = 0; a < N; a++) for (int b = 0; b < N; b++) { |
|
if (a == b) continue; |
|
struct ETCP_CONN* conn = topo_group_find_conn_for_node(groups[a], nodes[b]->node_id); |
|
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "verify graph=%02x src=%d dst=%d expected=%d conn=%p", graph, a, b, distance[a][b], conn); |
|
assert((conn != NULL) == (distance[a][b] < 100)); |
|
if (!conn) continue; |
|
struct TOPO_GROUP_NODE* node = topo_node_find_by_id(groups[a], nodes[b]->node_id); |
|
uint8_t count; uint64_t* hops = topo_node_best_hop_list(node, &count, NULL); |
|
assert(count == distance[a][b]); |
|
int previous = a; |
|
for (int h = count - 1; h >= 0; h--) { |
|
int current = 0; |
|
while (current < N && nodes[current]->node_id != hops[h]) current++; |
|
assert(current < N && (graph & edge(previous, current))); |
|
previous = current; |
|
} |
|
assert(previous == b); |
|
for (int h = 0; h < count; h++) for (int j = h + 1; j < count; j++) assert(hops[h] != hops[j]); |
|
if (graph & edge(a, b)) assert(topo_group_peer_ready(groups[a], nodes[b]->node_id)); |
|
} |
|
} |
|
|
|
static void create(void) { |
|
ua = uasync_create(); assert(ua); |
|
for (int i = 0; i < N; i++) { |
|
struct SC_MYKEYS keys; assert(sc_generate_keypair(&keys) == SC_OK); |
|
char pub[65], priv[65], config[512]; |
|
bytes_to_hex(keys.public_key, 32, pub, sizeof(pub)); bytes_to_hex(keys.private_key, 32, priv, sizeof(priv)); |
|
snprintf(config, sizeof(config), "[global]\nmy_public_key=%s\nmy_private_key=%s\n", pub, priv); |
|
nodes[i] = utun_instance_create_from_str(ua, config); assert(nodes[i]); |
|
assert(utun_core_start(nodes[i]) == 0); |
|
groups[i] = topo_groups_create_group(nodes[i]->topo_groups, GROUP, TOPO_GROUP_TYPE_UTUN, NULL); assert(groups[i]); |
|
} |
|
for (int a = 0; a < N; a++) for (int b = 0; b < N; b++) { |
|
if (a == b) continue; |
|
struct ETCP_CONN* c = conns[a][b] = etcp_connection_create(nodes[a], "virtual_bgp"); assert(c); |
|
c->peer_node_id = nodes[b]->node_id; |
|
assert(sc_init_ctx(&c->crypto_ctx, &nodes[a]->my_keys) == SC_OK); |
|
assert(sc_set_peer_public_key(&c->crypto_ctx, nodes[b]->my_keys.public_key, SC_PEER_PUBKEY_BIN) == SC_OK); |
|
queue_set_callback(c->send_input_q, NULL, NULL); |
|
etcp_conn_ready(c); |
|
assert(node_conn_direct_open(nodes[a], c->peer_node_id, NULL, NULL, &owners[a][b], NULL) >= 0); |
|
} |
|
} |
|
|
|
int main(void) { |
|
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); debug_set_category_level(DEBUG_CATEGORY_BGP, DEBUG_LEVEL_DEBUG); |
|
utun_instance_set_tun_init_enabled(0); |
|
create(); |
|
unsigned triangle = edge(0, 1) | edge(1, 2) | edge(0, 2) | edge(2, 3); |
|
change(triangle); settle(); verify(); |
|
change(triangle & ~edge(0, 2)); settle(); verify(); |
|
change(triangle); settle(); verify(); |
|
change(triangle & ~edge(1, 2) & ~edge(0, 2)); settle(); verify(); |
|
change(triangle); settle(); verify(); |
|
/* Все графы, включая изолированные компоненты; затем churn без ожидания сходимости. */ |
|
for (unsigned mask = 0; mask < 64; mask++) { change(mask); settle(); verify(); } |
|
for (int i = 0; i < 100; i++) { |
|
change(next_random() >> 26); |
|
for (unsigned n = next_random() % 5; n; n--) step(); |
|
} |
|
settle(); verify(); |
|
change(0); settle(); verify(); |
|
for (int a = 0; a < N; a++) for (int b = 0; b < N; b++) if (a != b) node_conn_direct_close(owners[a][b]); |
|
for (int a = 0; a < N; a++) utun_instance_destroy(nodes[a]); |
|
uasync_poll(ua, 0); uasync_destroy(ua, 0); |
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP paths: triangle, partitions, all four-node graphs and churn passed packets=%u", delivered); |
|
return 0; |
|
}
|
|
|