Enforce disjoint IPv4 route insertion atomically
This commit is contained in:
1 parent
34d1dcb7c8
commit
0bc0d6cc62
3 files changed
+133
-87
No files matched your search
@@ -21,22 +21,12 @@ static uint32_t prefix_to_mask(uint8_t prefix) {
|
||||
}
|
||||
|
||||
static bool routes_overlap(uint32_t net1, uint8_t pre1, uint32_t net2, uint8_t pre2) {
|
||||
if (pre1 == pre2 && net1 == net2) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool check_route_overlap_in_table(uint32_t network, uint8_t prefix,
|
||||
const struct ROUTE_ENTRY *entries, size_t count) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (routes_overlap(network, prefix, entries[i].network, entries[i].prefix_length)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
uint32_t mask = prefix_to_mask(pre1 < pre2 ? pre1 : pre2);
|
||||
return (net1 & mask) == (net2 & mask);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Поиск (LPM)
|
||||
// Поиск по непересекающимся диапазонам
|
||||
// ============================================================================
|
||||
|
||||
static int binary_search_insert_pos(struct ROUTE_ENTRY *entries, size_t count,
|
||||
@@ -56,51 +46,19 @@ static int binary_search_insert_pos(struct ROUTE_ENTRY *entries, size_t count,
|
||||
return left;
|
||||
}
|
||||
|
||||
static struct ROUTE_ENTRY* binary_search_lpm(struct ROUTE_TABLE *table, uint32_t dest_ip) {
|
||||
if (table->count == 0) return NULL;
|
||||
|
||||
/* В отсортированной таблице без пересечений подходящий диапазон может быть только один. */
|
||||
static struct ROUTE_ENTRY* binary_search_route(struct ROUTE_TABLE *table, uint32_t dest_ip) {
|
||||
int left = 0;
|
||||
int right = (int)table->count - 1;
|
||||
struct ROUTE_ENTRY *best = NULL;
|
||||
uint8_t best_prefix = 0;
|
||||
|
||||
while (left <= right) {
|
||||
int mid = left + (right - left) / 2;
|
||||
struct ROUTE_ENTRY *entry = &table->entries[mid];
|
||||
uint32_t mask = prefix_to_mask(entry->prefix_length);
|
||||
|
||||
if ((dest_ip & mask) == (entry->network & mask)) {
|
||||
if (entry->prefix_length > best_prefix) {
|
||||
best_prefix = entry->prefix_length;
|
||||
best = entry;
|
||||
}
|
||||
if (mid + 1 < (int)table->count) {
|
||||
struct ROUTE_ENTRY *next = &table->entries[mid + 1];
|
||||
uint32_t next_mask = prefix_to_mask(next->prefix_length);
|
||||
if ((dest_ip & next_mask) == (next->network & next_mask)) {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else if (dest_ip > entry->network) {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
right = mid - 1;
|
||||
}
|
||||
if ((dest_ip & mask) == entry->network) return entry;
|
||||
if (dest_ip > entry->network) left = mid + 1;
|
||||
else right = mid - 1;
|
||||
}
|
||||
|
||||
// fallback проверка предыдущей записи
|
||||
if (!best && left > 0) {
|
||||
struct ROUTE_ENTRY *entry = &table->entries[left - 1];
|
||||
uint32_t mask = prefix_to_mask(entry->prefix_length);
|
||||
if ((dest_ip & mask) == (entry->network & mask)) {
|
||||
best = entry;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -160,8 +118,25 @@ bool route_insert(struct ROUTE_TABLE *table, struct TOPO_GROUP_NODE *node) {
|
||||
|
||||
for (const struct TOPO_SUBNET4* s = sub; s; s = s->next) {
|
||||
uint32_t network; memcpy(&network, s->addr, 4); network = ntohl(network);
|
||||
if (check_route_overlap_in_table(network, s->prefix_length, table->entries, table->count)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "overlap detected for %s/%d", ip_to_string(network).a, s->prefix_length);
|
||||
if (s->prefix_length > 32) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "reject node=%016llx subnet=%s/%u: invalid IPv4 prefix",
|
||||
(unsigned long long)node->node_id, ip_to_string(network).a, s->prefix_length);
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < table->count; i++) {
|
||||
const struct ROUTE_ENTRY *e = &table->entries[i];
|
||||
if (!routes_overlap(network, s->prefix_length, e->network, e->prefix_length)) continue;
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "reject node=%016llx subnet=%s/%u: overlaps node=%016llx subnet=%s/%u",
|
||||
(unsigned long long)node->node_id, ip_to_string(network).a, s->prefix_length,
|
||||
(unsigned long long)e->v_node_info->node_id, ip_to_string(e->network).a, e->prefix_length);
|
||||
return false;
|
||||
}
|
||||
for (const struct TOPO_SUBNET4 *previous = sub; previous != s; previous = previous->next) {
|
||||
uint32_t other; memcpy(&other, previous->addr, 4); other = ntohl(other);
|
||||
if (!routes_overlap(network, s->prefix_length, other, previous->prefix_length)) continue;
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "reject node=%016llx subnet=%s/%u: overlaps own subnet=%s/%u",
|
||||
(unsigned long long)node->node_id, ip_to_string(network).a, s->prefix_length,
|
||||
ip_to_string(other).a, previous->prefix_length);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -176,6 +151,7 @@ bool route_insert(struct ROUTE_TABLE *table, struct TOPO_GROUP_NODE *node) {
|
||||
|
||||
for (const struct TOPO_SUBNET4* s = sub; s; s = s->next) {
|
||||
uint32_t network; memcpy(&network, s->addr, 4); network = ntohl(network);
|
||||
network &= prefix_to_mask(s->prefix_length);
|
||||
int pos = binary_search_insert_pos(table->entries, table->count, network, s->prefix_length);
|
||||
if (pos < (int)table->count) memmove(&table->entries[pos + 1], &table->entries[pos], (table->count - pos) * sizeof(struct ROUTE_ENTRY));
|
||||
struct ROUTE_ENTRY* e = &table->entries[pos];
|
||||
@@ -210,7 +186,7 @@ struct ROUTE_ENTRY* route_lookup(struct ROUTE_TABLE *table, uint32_t dest_ip) {
|
||||
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "dest_ip=%s", ip_to_string(dest_ip).a);
|
||||
|
||||
struct ROUTE_ENTRY *result = binary_search_lpm(table, dest_ip);
|
||||
struct ROUTE_ENTRY *result = binary_search_route(table, dest_ip);
|
||||
|
||||
if (result) {
|
||||
table->stats.routes_lookup_hits++;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* route_lib — локальная IPv4-таблица подсетей UTUN, отсортированный массив без пересечения маршрутов.
|
||||
* create → insert/add_local_subnet → lookup → delete/destroy; все операции в одном uasync-потоке.
|
||||
* create → insert → lookup → delete/destroy; все операции в одном uasync-потоке.
|
||||
* Пересечения и дубликаты запрещены, в том числе внутри одного набора подсетей; /0 занимает всю таблицу.
|
||||
* Вставка нормализует адреса по маске; при отказе таблица и её статистика не меняются.
|
||||
* network/dest_ip/parse_subnet — host order; только is_local_subnet принимает network order.
|
||||
* TOPO_GROUP_NODE заимствован: удалить его маршруты до освобождения узла.
|
||||
* lookup возвращает элемент массива, действительный до следующего изменения таблицы. */
|
||||
@@ -39,24 +41,24 @@ typedef enum {
|
||||
struct ROUTE_ENTRY {
|
||||
uint32_t network; // Сетевой адрес в host order
|
||||
uint8_t prefix_length; // Длина префикса подсети
|
||||
struct TOPO_GROUP_NODE* v_node_info; // узел владелец этих маршрутов. null если - локальный маршрут.
|
||||
struct TOPO_GROUP_NODE* v_node_info; // заимствованный владелец; локальный маршрут тоже содержит узел
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Таблица маршрутизации
|
||||
*
|
||||
* Структура представляет собой таблицу маршрутизации, содержащую записи маршрутов, подсети и статистику.
|
||||
* маршруты не должны пересекаться. т.е. адрес 192.168.1.1/30 нельзя добавить если есть 192.168.1.2/32 или 192.168.1.1/24
|
||||
* Подсети не пересекаются: например, 192.168.1.0/24 исключает 192.168.1.0/30 и 192.168.1.2/32.
|
||||
*/
|
||||
struct ROUTE_TABLE {
|
||||
struct ROUTE_ENTRY *entries; /**< Массив записей маршрутов */
|
||||
size_t count; /**< Текущее количество записей */
|
||||
size_t capacity; /**< Максимальная емкость таблицы */
|
||||
uint64_t my_node_id; /**< ID локального узла */
|
||||
uint32_t *dynamic_subnets; /**< Динамические подсети (массив пар: сеть, префикс) */
|
||||
size_t dynamic_subnet_count; /**< Количество динамических подсетей */
|
||||
uint32_t *local_subnets; /**< Локальные подсети (массив пар: сеть, префикс) */
|
||||
size_t local_subnet_count; /**< Количество локальных подсетей */
|
||||
uint32_t *dynamic_subnets; /**< Не используется текущей реализацией; destroy освобождает память */
|
||||
size_t dynamic_subnet_count; /**< Не используется текущей реализацией */
|
||||
uint32_t *local_subnets; /**< Не используется текущей реализацией; destroy освобождает память */
|
||||
size_t local_subnet_count; /**< Не используется текущей реализацией */
|
||||
struct {
|
||||
uint64_t local_routes; /**< Количество локальных маршрутов */
|
||||
uint64_t learned_routes; /**< Количество изученных маршрутов */
|
||||
@@ -80,10 +82,12 @@ struct ROUTE_TABLE *route_table_create(uint64_t my_node_id);
|
||||
void route_table_destroy(struct ROUTE_TABLE *table);
|
||||
|
||||
/**
|
||||
* @brief Вставляет в таблицу маршрутизации все подсети для указанного узла
|
||||
* @brief Атомарно вставляет все IPv4-подсети узла; /0..32, адреса нормализуются по маске
|
||||
* @param table Таблица без пересечений; перед заменой маршрутов узла вызвать route_delete
|
||||
* @param node узел, все маршруты которого нужно добавить
|
||||
*
|
||||
* @return true если подсети добавлены; false при отсутствии подсетей, пересечении или ошибке выделения памяти
|
||||
* @return true если весь набор добавлен; false при неверных аргументах/префиксе, отсутствии подсетей,
|
||||
* пересечении с таблицей/внутри набора или нехватке памяти. При false таблица не меняется.
|
||||
*/
|
||||
bool route_insert(struct ROUTE_TABLE *table, struct TOPO_GROUP_NODE *node);
|
||||
|
||||
@@ -112,7 +116,7 @@ struct ROUTE_ENTRY* route_lookup(struct ROUTE_TABLE *table, uint32_t dest_ip);
|
||||
void route_table_print(const struct ROUTE_TABLE *table);
|
||||
|
||||
/**
|
||||
* @brief Парсит строку подсети в сетевой адрес и длину префикса
|
||||
* @brief Разбирает IPv4/префикс; host-биты адреса сохраняются, префикс читается через atoi
|
||||
*
|
||||
* @param subnet_str Строка подсети (например, "192.168.1.0/24")
|
||||
* @param network Указатель для сохранения адреса в host order
|
||||
@@ -121,8 +125,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);
|
||||
|
||||
/**
|
||||
* @brief Проверяет что IP адрес НЕ является публичным (роутится в интернете)
|
||||
*
|
||||
|
||||
+89
-21
@@ -1,16 +1,4 @@
|
||||
/**
|
||||
* FULL REFACTORED TEST for new NODEINFO-based routing system
|
||||
*
|
||||
* Tests:
|
||||
* - TOPO_GROUP_NODE 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
|
||||
*/
|
||||
/* IPv4-таблица: владение, непересекающиеся подсети, атомарный отказ и поиск на границах. */
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
@@ -44,13 +32,8 @@ struct UTUN_INSTANCE {
|
||||
static struct TOPO_GROUP_NODE* create_test_node(uint64_t node_id, uint8_t ver, uint32_t subnet_net, uint8_t prefix) {
|
||||
struct TOPO_GROUP_NODE* nq = u_calloc(1, sizeof(struct TOPO_GROUP_NODE));
|
||||
if (!nq) return NULL;
|
||||
struct TOPO_NODE* ni = u_calloc(1, sizeof(struct TOPO_NODE));
|
||||
if (!ni) { u_free(nq); return NULL; }
|
||||
ni->group_ref_count = 1;
|
||||
ni->node_id = htobe64(node_id);
|
||||
ni->ver = ver;
|
||||
nq->node_id = ni->node_id;
|
||||
nq->paths = queue_new(NULL, 16, 0, 0, "test_paths");
|
||||
(void)ver;
|
||||
nq->node_id = node_id;
|
||||
struct TOPO_NODESUBNETS* r = u_calloc(1, sizeof(struct TOPO_NODESUBNETS));
|
||||
if (r) {
|
||||
struct TOPO_SUBNET4* sn = u_calloc(1, sizeof(struct TOPO_SUBNET4));
|
||||
@@ -152,11 +135,12 @@ static void test_performance_1000_nodes(void) {
|
||||
struct ROUTE_TABLE *t = route_table_create(0);
|
||||
clock_t start = clock();
|
||||
|
||||
struct TOPO_GROUP_NODE *nodes[1000];
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
uint32_t sub = 0x0a000000 + (i * 0x100); /* distinct 10.0.x.0/24 to avoid overlap */
|
||||
struct TOPO_GROUP_NODE* nq = create_test_node(0x1000000ULL + i, 1, sub, 24);
|
||||
nodes[i] = nq;
|
||||
route_insert(t, nq);
|
||||
/* note: nq not freed here (table holds ref to it), freed in test cleanup if needed */
|
||||
}
|
||||
|
||||
clock_t end = clock();
|
||||
@@ -166,6 +150,87 @@ static void test_performance_1000_nodes(void) {
|
||||
printf("(%.3fs for 1000 nodes) ", time);
|
||||
|
||||
route_table_destroy(t);
|
||||
for (int i = 0; i < 1000; i++) free_test_node(nodes[i]);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Проверяет оба порядка вложенных подсетей, дубликаты, host-биты и соседние диапазоны. */
|
||||
static void test_overlap_and_lookup(void) {
|
||||
TEST("overlap rejection and disjoint lookup");
|
||||
struct { uint32_t first, second; uint8_t p1, p2; bool accepted; } cases[] = {
|
||||
{0x0a000000, 0x0a010000, 8, 24, false},
|
||||
{0x0a010000, 0x0a000000, 24, 8, false},
|
||||
{0x0a010001, 0x0a0100ff, 24, 24, false},
|
||||
{0x0a010001, 0x0a010001, 32, 32, false},
|
||||
{0x0a010000, 0x0a010002, 31, 31, true},
|
||||
{0x0a010001, 0x0a010002, 32, 32, true},
|
||||
{0, 0x0a000001, 0, 32, false},
|
||||
{0x0a000001, 0, 32, 0, false}
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
|
||||
struct ROUTE_TABLE *t = route_table_create(1);
|
||||
struct TOPO_GROUP_NODE *a = create_test_node(1, 0, cases[i].first, cases[i].p1);
|
||||
struct TOPO_GROUP_NODE *b = create_test_node(2, 0, cases[i].second, cases[i].p2);
|
||||
ASSERT(route_insert(t, a), "first insertion");
|
||||
ASSERT_EQ(route_insert(t, b), cases[i].accepted, "overlap decision");
|
||||
ASSERT_EQ(t->count, cases[i].accepted ? 2 : 1, "atomic table count");
|
||||
ASSERT_EQ(t->stats.local_routes, 1, "local route count");
|
||||
ASSERT_EQ(t->stats.learned_routes, cases[i].accepted ? 1 : 0, "learned route count");
|
||||
struct ROUTE_ENTRY *found = route_lookup(t, cases[i].first);
|
||||
ASSERT(found && found->v_node_info == a, "first route lookup");
|
||||
if (cases[i].accepted) {
|
||||
found = route_lookup(t, cases[i].second);
|
||||
ASSERT(found && found->v_node_info == b, "second route lookup");
|
||||
}
|
||||
route_delete(t, a);
|
||||
route_delete(t, b);
|
||||
ASSERT_EQ(t->count, 0, "delete clears routes");
|
||||
route_table_destroy(t); free_test_node(a); free_test_node(b);
|
||||
}
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Ни поздний конфликт набора, ни неверный префикс не должны оставлять частичную вставку. */
|
||||
static void test_batch_atomicity(void) {
|
||||
TEST("batch rejection leaves table unchanged");
|
||||
struct ROUTE_TABLE *t = route_table_create(1);
|
||||
struct TOPO_GROUP_NODE *a = create_test_node(1, 0, 0x0a000000, 8);
|
||||
struct TOPO_GROUP_NODE *b = create_test_node(2, 0, 0xc0a80000, 24);
|
||||
ASSERT(route_insert(t, a), "base route");
|
||||
struct TOPO_SUBNET4 second = {0};
|
||||
uint32_t network = htonl(0x0a010000);
|
||||
memcpy(second.addr, &network, 4); second.prefix_length = 24;
|
||||
b->subnets->v4_subnets->next = &second;
|
||||
ASSERT(!route_insert(t, b), "late overlap with table");
|
||||
ASSERT_EQ(t->count, 1, "no partial insert before table conflict");
|
||||
network = htonl(0xc0a80080); memcpy(second.addr, &network, 4);
|
||||
ASSERT(!route_insert(t, b), "overlap inside batch");
|
||||
ASSERT_EQ(t->count, 1, "no partial insert before batch conflict");
|
||||
second.prefix_length = 33;
|
||||
ASSERT(!route_insert(t, b), "invalid prefix rejected");
|
||||
ASSERT_EQ(t->count, 1, "invalid prefix preserves table");
|
||||
ASSERT_EQ(t->stats.learned_routes, 0, "rejection preserves statistics");
|
||||
b->subnets->v4_subnets->next = NULL;
|
||||
route_table_destroy(t); free_test_node(a); free_test_node(b);
|
||||
PASS();
|
||||
}
|
||||
|
||||
static void test_lookup_boundaries(void) {
|
||||
TEST("normalized subnet boundaries and default route");
|
||||
struct ROUTE_TABLE *t = route_table_create(1);
|
||||
struct TOPO_GROUP_NODE *a = create_test_node(1, 0, 0x0a00007b, 24);
|
||||
ASSERT(route_insert(t, a), "noncanonical address accepted");
|
||||
ASSERT_EQ(t->entries[0].network, 0x0a000000, "network normalized");
|
||||
ASSERT(route_lookup(t, 0x0a000000), "first address");
|
||||
ASSERT(route_lookup(t, 0x0a0000ff), "last address");
|
||||
ASSERT(!route_lookup(t, 0x09ffffff), "before range");
|
||||
ASSERT(!route_lookup(t, 0x0a000100), "after range");
|
||||
route_delete(t, a);
|
||||
a->subnets->v4_subnets->prefix_length = 0;
|
||||
ASSERT(route_insert(t, a), "default route");
|
||||
ASSERT(route_lookup(t, 0), "default first address");
|
||||
ASSERT(route_lookup(t, UINT32_MAX), "default last address");
|
||||
route_table_destroy(t); free_test_node(a);
|
||||
PASS();
|
||||
}
|
||||
|
||||
@@ -187,6 +252,9 @@ int main(void) {
|
||||
test_table_create_destroy();
|
||||
test_nodeinfo_insert();
|
||||
test_versioning();
|
||||
test_overlap_and_lookup();
|
||||
test_batch_atomicity();
|
||||
test_lookup_boundaries();
|
||||
test_performance_1000_nodes();
|
||||
test_destroy_refcount();
|
||||
|
||||
|
||||
Reference in new issue
Block a user