267 lines
17 KiB
C
267 lines
17 KiB
C
#include <assert.h>
|
|
#include <string.h>
|
|
#include <openssl/evp.h>
|
|
#include "utun_instance.h"
|
|
#include "routing_layer/topo_group.h"
|
|
#include "routing_layer/topo_node_sqlite.h"
|
|
#include "transport_layer/secure_channel.h"
|
|
#include "transport_layer/etcp.h"
|
|
#include "config_parser.h"
|
|
#include "../lib/mem.h"
|
|
#include "../lib/memory_pool.h"
|
|
#include "../lib/u_async.h"
|
|
#include "../lib/debug_config.h"
|
|
|
|
static void sign_record(struct UTUN_INSTANCE* inst, struct TOPO_NODE* ni) {
|
|
uint8_t msg[TOPO_SIG_MSG_MAX_SIZE];
|
|
int len = topo_node_build_sig_msg(ni, msg, sizeof(msg));
|
|
assert(len > 0);
|
|
assert(sc_ed25519_sign(inst->my_ed25519_privkey, msg, (size_t)len, ni->x25519_self_sig) == SC_OK);
|
|
}
|
|
|
|
static struct TOPO_NODE* record(struct UTUN_INSTANCE* inst, uint64_t timestamp, const char* name, int address) {
|
|
struct TOPO_NODE* ni = u_calloc(1, sizeof(*ni));
|
|
assert(ni);
|
|
ni->node_id = inst->node_id;
|
|
ni->timestamp = timestamp;
|
|
ni->node_name = u_strdup(name);
|
|
memcpy(ni->public_key, inst->my_keys.public_key, 32);
|
|
memcpy(ni->ed25519_public_key, inst->my_ed25519_pubkey, 32);
|
|
if (address) {
|
|
ni->v4_addrs = memory_pool_alloc(inst->topo_groups->v4_addr_pool);
|
|
assert(ni->v4_addrs);
|
|
*ni->v4_addrs = (struct TOPO_ADDR4){ .addr = {127,0,0,1}, .port = 9000, .protocol = TOPO_PROTO_UDP };
|
|
}
|
|
sign_record(inst, ni);
|
|
return ni;
|
|
}
|
|
|
|
/* Старая БД запрещала port=0; перестройка CHECK сохраняет адреса и восстанавливает индексы. */
|
|
static void test_zero_port_schema(void) {
|
|
sqlite3 *db = NULL; assert(sqlite3_open(":memory:", &db) == SQLITE_OK);
|
|
assert(sqlite3_exec(db, "CREATE TABLE node_addresses("
|
|
"id INTEGER PRIMARY KEY,node_id INTEGER,family INTEGER,protocol INTEGER,address BLOB,"
|
|
"port INTEGER CHECK(port > 0 AND port <= 65535),rtt INTEGER,addr_type INTEGER,config_type INTEGER,"
|
|
"nat_mapping INTEGER,nat_inbound INTEGER,nat_source INTEGER,address_kind INTEGER,socket_id INTEGER,"
|
|
"options BLOB,created_at INTEGER);"
|
|
"CREATE UNIQUE INDEX idx_na_unique ON node_addresses(node_id,family,socket_id,protocol,address,port,address_kind);"
|
|
"INSERT INTO node_addresses VALUES(7,1,4,2,X'CB00712C',443,15,4,2,0,0,1,1,3,X'0102',123)",
|
|
NULL, NULL, NULL) == SQLITE_OK);
|
|
assert(!topo_node_sqlite_init(db));
|
|
sqlite3_stmt *statement = NULL;
|
|
assert(sqlite3_prepare_v2(db, "SELECT port,rtt,nat_source,length(options),created_at FROM node_addresses WHERE id=7",
|
|
-1, &statement, NULL) == SQLITE_OK);
|
|
assert(sqlite3_step(statement) == SQLITE_ROW && sqlite3_column_int(statement, 0) == 443);
|
|
assert(sqlite3_column_int(statement, 1) == 15 && sqlite3_column_int(statement, 2) == ADDRESS_SOURCE_ETCP);
|
|
assert(sqlite3_column_int(statement, 3) == 2 && sqlite3_column_int(statement, 4) == 123);
|
|
sqlite3_finalize(statement);
|
|
assert(sqlite3_exec(db, "UPDATE node_addresses SET port=0 WHERE id=7", NULL, NULL, NULL) == SQLITE_OK);
|
|
assert(sqlite3_exec(db, "INSERT INTO node_addresses SELECT 8,node_id,family,protocol,address,port,rtt,"
|
|
"addr_type,config_type,nat_mapping,nat_inbound,nat_source,address_kind,socket_id,options,created_at"
|
|
" FROM node_addresses WHERE id=7", NULL, NULL, NULL) == SQLITE_CONSTRAINT);
|
|
assert(!topo_node_sqlite_init(db));
|
|
sqlite3_close(db);
|
|
}
|
|
|
|
/* Новый wire и SQL сохраняют адреса, протоколы, классификацию и REALITY; смена формата оставляет bootstrap. */
|
|
static void test_compact_addresses(struct UTUN_INSTANCE *inst) {
|
|
test_zero_port_schema();
|
|
sqlite3 *saved_db = inst->topo_sqlite_db;
|
|
assert(sqlite3_open(":memory:", &inst->topo_sqlite_db) == SQLITE_OK);
|
|
sqlite3 *db = inst->topo_sqlite_db;
|
|
assert(!topo_node_sqlite_init(db));
|
|
assert(!topo_node_sqlite_node_update_verified(db, inst->node_id, "address fixture", inst->my_keys.public_key,
|
|
inst->my_ed25519_pubkey, 100, 100));
|
|
struct TOPO_NODE *node = record(inst, INT64_MAX - 100, "address fixture", 1);
|
|
memcpy(node->v4_addrs->addr, (uint8_t[]){194,63,142,140}, 4);
|
|
node->v4_addrs->port = 1333; node->v4_addrs->socket_id = 5;
|
|
node->v4_sock_meta = memory_pool_alloc(inst->topo_groups->v4_sock_meta_pool);
|
|
node->v6_sock_meta = memory_pool_alloc(inst->topo_groups->v6_sock_meta_pool);
|
|
node->v6_addrs = memory_pool_alloc(inst->topo_groups->v6_addr_pool);
|
|
node->reality_socks = u_calloc(1, sizeof(*node->reality_socks));
|
|
assert(node->v4_sock_meta && node->v6_sock_meta && node->v6_addrs && node->reality_socks);
|
|
*node->v4_sock_meta = (struct TOPO_SOCKMETA4){.id = 5, .config_type = CFG_SERVER_TYPE_PUBLIC,
|
|
.address_profile = {ADDRESS_MAP_UNCHANGED, ADDRESS_IN_OPEN, ADDRESS_SOURCE_ETCP}};
|
|
*node->v6_sock_meta = (struct TOPO_SOCKMETA6){.id = 6, .config_type = CFG_SERVER_TYPE_PUBLIC,
|
|
.address_profile = {ADDRESS_MAP_UNCHANGED, ADDRESS_IN_CONTACT, ADDRESS_SOURCE_ETCP}};
|
|
*node->v6_addrs = (struct TOPO_ADDR6){.addr = {0x2a,0x04,0x52,0,0,0,0,0,0,0,0,0,0,0,0,0xf},
|
|
.port = 443, .socket_id = 6, .protocol = TOPO_PROTO_TCP | TOPO_PROTO_REALITY};
|
|
node->reality_socks->socket_id = 6; strcpy(node->reality_socks->server_name, "example.org");
|
|
sign_record(inst, node);
|
|
assert(sizeof(struct TOPOMSG_SOCKMETA4) == 5 && sizeof(struct TOPOMSG_SOCKMETA6) == 5);
|
|
assert(!topo_node_sqlite_snapshot_put(db, node) && !topo_node_sqlite_addrs_put(db, inst->node_id, node));
|
|
struct TOPO_NODE *loaded = topo_node_sqlite_snapshot_load(db, inst->topo_groups, inst->node_id);
|
|
assert(loaded && !topo_node_verify(loaded));
|
|
assert(!memcmp(loaded->v4_addrs->addr, node->v4_addrs->addr, 4) && loaded->v4_addrs->port == 1333);
|
|
assert(loaded->v4_sock_meta->address_profile.inbound == ADDRESS_IN_OPEN);
|
|
assert(!memcmp(loaded->v6_addrs->addr, node->v6_addrs->addr, 16) && loaded->v6_addrs->port == 443);
|
|
assert(loaded->v6_addrs->protocol == (TOPO_PROTO_TCP | TOPO_PROTO_REALITY));
|
|
assert(loaded->v6_sock_meta->address_profile.inbound == ADDRESS_IN_CONTACT);
|
|
assert(!strcmp(loaded->reality_socks->server_name, "example.org"));
|
|
topo_node_destroy(inst->topo_groups, loaded);
|
|
sqlite3_stmt *statement = NULL;
|
|
assert(sqlite3_prepare_v2(db, "SELECT family,protocol,port,socket_id,addr_type,length(options)"
|
|
" FROM node_addresses ORDER BY family", -1, &statement, NULL) == SQLITE_OK);
|
|
assert(sqlite3_step(statement) == SQLITE_ROW && sqlite3_column_int(statement, 0) == 4);
|
|
assert(sqlite3_column_int(statement, 1) == TOPO_PROTO_UDP && sqlite3_column_int(statement, 2) == 1333);
|
|
assert(sqlite3_column_int(statement, 3) == 5 && sqlite3_column_int(statement, 4) == ADDR_TYPE_DIRECT);
|
|
assert(sqlite3_step(statement) == SQLITE_ROW && sqlite3_column_int(statement, 0) == 6);
|
|
assert(sqlite3_column_int(statement, 1) == (TOPO_PROTO_TCP | TOPO_PROTO_REALITY));
|
|
assert(sqlite3_column_int(statement, 2) == 443 && sqlite3_column_int(statement, 3) == 6);
|
|
assert(sqlite3_column_int(statement, 4) == ADDR_TYPE_CONTACT && sqlite3_column_int(statement, 5) == TOPO_ADDR_REALITY_OPTS_SIZE);
|
|
assert(sqlite3_step(statement) == SQLITE_DONE); sqlite3_finalize(statement);
|
|
|
|
/* Публичный IP интерфейса не становится NAT UNKNOWN из-за отсутствия измерения входящих. */
|
|
struct address_profile known = node->v4_sock_meta->address_profile;
|
|
node->v4_sock_meta->address_profile = (struct address_profile){0};
|
|
assert(!topo_node_sqlite_addrs_put(db, inst->node_id, node));
|
|
assert(sqlite3_prepare_v2(db, "SELECT addr_type FROM node_addresses WHERE family=4", -1, &statement, NULL) == SQLITE_OK);
|
|
assert(sqlite3_step(statement) == SQLITE_ROW && sqlite3_column_int(statement, 0) == ADDR_TYPE_NETIF);
|
|
sqlite3_finalize(statement);
|
|
node->v4_sock_meta->address_profile = known;
|
|
assert(!topo_node_sqlite_addrs_put(db, inst->node_id, node));
|
|
|
|
/* Две старые колонки отмечают прежний формат. Миграция не теряет IP/port, REALITY и максимум timestamp. */
|
|
assert(sqlite3_exec(db, "ALTER TABLE node_addresses ADD COLUMN nat_evidence INTEGER DEFAULT 1;"
|
|
"ALTER TABLE node_addresses ADD COLUMN nat_checked INTEGER DEFAULT 1", NULL, NULL, NULL) == SQLITE_OK);
|
|
assert(!topo_node_sqlite_init(db));
|
|
assert(topo_node_sqlite_snapshot_timestamp(db, inst->node_id) == node->timestamp);
|
|
assert(!topo_node_sqlite_snapshot_load(db, inst->topo_groups, inst->node_id));
|
|
loaded = topo_node_sqlite_node_load(db, inst->topo_groups, inst->node_id);
|
|
assert(loaded && !loaded->timestamp && loaded->v4_addrs && loaded->v6_addrs && loaded->reality_socks);
|
|
assert(!loaded->v4_sock_meta->address_profile.source && !loaded->v6_sock_meta->address_profile.source);
|
|
assert(loaded->v4_addrs->port == 1333 && loaded->v6_addrs->port == 443);
|
|
assert(!strcmp(loaded->reality_socks->server_name, "example.org"));
|
|
topo_node_destroy(inst->topo_groups, loaded);
|
|
node->timestamp = 0;
|
|
assert(!topo_node_sign_self(inst, node) && node->timestamp == INT64_MAX - 99);
|
|
assert(!topo_node_sqlite_init(db));
|
|
loaded = topo_node_sqlite_snapshot_load(db, inst->topo_groups, inst->node_id);
|
|
assert(loaded && loaded->timestamp == node->timestamp && !topo_node_verify(loaded));
|
|
topo_node_destroy(inst->topo_groups, loaded); topo_node_destroy(inst->topo_groups, node);
|
|
sqlite3_close(db); inst->topo_sqlite_db = saved_db;
|
|
}
|
|
|
|
static void test_bgp(struct UTUN_INSTANCE* signer) {
|
|
struct UTUN_INSTANCE receiver = { .ua = signer->ua, .topo_groups = signer->topo_groups };
|
|
struct TOPO_GROUP group = { .instance = &receiver, .group_id = 10, .group_type = TOPO_GROUP_TYPE_CHAT };
|
|
struct ETCP_CONN from = { .instance = &receiver, .peer_node_id = 7 };
|
|
group.nodes = queue_new(receiver.ua, 16, 0, 8, "snapshot_bgp");
|
|
struct TOPO_NODE* ni = record(signer, INT64_MAX - 7, "BGP", 1);
|
|
from.peer_node_id = ni->node_id; from.links_up = 1;
|
|
struct TOPO_GROUP_NODE nq = {0};
|
|
uint8_t packet[4096] = {0};
|
|
int len = topo_node_serialize(ni, &nq, group.group_id, 0, packet + sizeof(struct TOPOMSG_HEADER), sizeof(packet) - sizeof(struct TOPOMSG_HEADER), 0);
|
|
assert(len > 0);
|
|
assert(topo_group_process_nodeinfo(&group, &from, packet, (size_t)len + sizeof(struct TOPOMSG_HEADER)) == 0);
|
|
struct TOPO_GROUP_NODE* peer = topo_node_find_by_id(&group, ni->node_id);
|
|
assert(peer && peer->last_timestamp == ni->timestamp && queue_entry_count(peer->paths) == 1);
|
|
assert(topo_group_process_nodeinfo(&group, &from, packet, (size_t)len + sizeof(struct TOPOMSG_HEADER)) == 0);
|
|
assert(queue_entry_count(peer->paths) == 1);
|
|
struct TOPOMSG_NODEINFO_PKT* wire = (struct TOPOMSG_NODEINFO_PKT*)packet;
|
|
wire->node.timestamp++; /* подделка не удаляет узел и ранее принятый маршрут */
|
|
assert(topo_group_process_nodeinfo(&group, &from, packet, (size_t)len + sizeof(struct TOPOMSG_HEADER)) < 0);
|
|
assert(topo_node_find_by_id(&group, ni->node_id) == peer && queue_entry_count(peer->paths) == 1);
|
|
wire->node.timestamp--;
|
|
ni->timestamp--;
|
|
sign_record(signer, ni);
|
|
len = topo_node_serialize(ni, &nq, group.group_id, 0, packet + sizeof(struct TOPOMSG_HEADER), sizeof(packet) - sizeof(struct TOPOMSG_HEADER), 0);
|
|
assert(topo_group_process_nodeinfo(&group, &from, packet, (size_t)len + sizeof(struct TOPOMSG_HEADER)) == 0);
|
|
assert(peer->last_timestamp == INT64_MAX - 7 && queue_entry_count(peer->paths) == 1);
|
|
topo_group_remove_path(peer, &from);
|
|
topo_nodeq_remove_node(&group, peer);
|
|
topo_node_destroy(signer->topo_groups, ni);
|
|
queue_free(group.nodes);
|
|
}
|
|
|
|
int main(void) {
|
|
debug_config_init();
|
|
debug_set_level(DEBUG_LEVEL_DEBUG);
|
|
struct UTUN_INSTANCE inst = {0};
|
|
struct TOPO_GROUPS groups = { .instance = &inst };
|
|
inst.topo_groups = &groups;
|
|
inst.ua = uasync_create();
|
|
assert(inst.ua);
|
|
groups.node_registry = queue_new(inst.ua, 16, 0, 8, "snapshot_test");
|
|
groups.v4_addr_pool = memory_pool_init(sizeof(struct TOPO_ADDR4), "snapshot_v4");
|
|
groups.v4_sock_meta_pool = memory_pool_init(sizeof(struct TOPO_SOCKMETA4), "snapshot_meta4");
|
|
groups.v6_sock_meta_pool = memory_pool_init(sizeof(struct TOPO_SOCKMETA6), "snapshot_meta6");
|
|
groups.v6_addr_pool = memory_pool_init(sizeof(struct TOPO_ADDR6), "snapshot_v6");
|
|
assert(groups.v4_addr_pool && groups.v4_sock_meta_pool && groups.v6_sock_meta_pool && groups.v6_addr_pool);
|
|
assert(sc_generate_keypair(&inst.my_keys) == SC_OK);
|
|
memcpy(inst.my_ed25519_privkey, inst.my_keys.private_key, 32);
|
|
EVP_PKEY* key = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, inst.my_ed25519_privkey, 32);
|
|
size_t key_len = 32;
|
|
assert(key && EVP_PKEY_get_raw_public_key(key, inst.my_ed25519_pubkey, &key_len) == 1);
|
|
EVP_PKEY_free(key);
|
|
inst.node_id = sc_derive_node_id_from_pubkey(inst.my_keys.public_key);
|
|
assert(sqlite3_open(":memory:", &inst.topo_sqlite_db) == SQLITE_OK);
|
|
assert(topo_node_sqlite_init(inst.topo_sqlite_db) == 0);
|
|
test_compact_addresses(&inst);
|
|
|
|
struct TOPO_NODE* first = record(&inst, 100, "old", 1);
|
|
assert(topo_node_verify(first) == 0);
|
|
first->timestamp++;
|
|
assert(topo_node_verify(first) < 0); /* timestamp защищён подписью */
|
|
first->timestamp--;
|
|
assert(topo_node_registry_store(&groups, first) == first);
|
|
struct TOPO_NODE* stale = record(&inst, 99, "stale", 0);
|
|
assert(topo_node_registry_store(&groups, stale) == first);
|
|
topo_node_registry_unref(&groups, inst.node_id);
|
|
assert(first->timestamp == 100 && first->v4_addrs && !strcmp(first->node_name, "old"));
|
|
|
|
struct TOPO_NODE* conflict = record(&inst, 100, "conflict", 0);
|
|
assert(topo_node_registry_store(&groups, conflict) == first);
|
|
topo_node_registry_unref(&groups, inst.node_id);
|
|
assert(first->v4_addrs && !strcmp(first->node_name, "old"));
|
|
|
|
assert(topo_node_sqlite_snapshot_put(inst.topo_sqlite_db, first) == 0);
|
|
struct TOPO_NODE* loaded = topo_node_sqlite_snapshot_load(inst.topo_sqlite_db, &groups, inst.node_id);
|
|
assert(loaded && loaded->timestamp == 100 && loaded->v4_addrs && topo_node_verify(loaded) == 0);
|
|
topo_node_destroy(&groups, loaded);
|
|
|
|
struct TOPO_NODE* fresh = record(&inst, 101, "new", 0);
|
|
assert(topo_node_registry_store(&groups, fresh) == first);
|
|
topo_node_registry_unref(&groups, inst.node_id);
|
|
assert(first->timestamp == 101 && !first->v4_addrs && !strcmp(first->node_name, "new"));
|
|
assert(topo_node_verify(first) == 0); /* удаление адресов не оставляет частей старой записи */
|
|
assert(topo_node_sqlite_snapshot_put(inst.topo_sqlite_db, first) == 0);
|
|
loaded = record(&inst, 100, "old", 1);
|
|
assert(topo_node_sqlite_snapshot_put(inst.topo_sqlite_db, loaded) == 0);
|
|
topo_node_destroy(&groups, loaded);
|
|
loaded = topo_node_sqlite_snapshot_load(inst.topo_sqlite_db, &groups, inst.node_id);
|
|
assert(loaded && loaded->timestamp == 101 && !loaded->v4_addrs && topo_node_verify(loaded) == 0);
|
|
topo_node_destroy(&groups, loaded);
|
|
|
|
/* Сохранённая версия из будущего: локальные часы/перезапуск не откатывают timestamp. */
|
|
topo_node_registry_unref(&groups, inst.node_id);
|
|
first = topo_node_registry_store(&groups, record(&inst, 99, "stale after restart", 1));
|
|
assert(first && first->timestamp == 101 && !first->v4_addrs && topo_node_verify(first) == 0);
|
|
first->timestamp = INT64_MAX - 10;
|
|
sign_record(&inst, first);
|
|
assert(topo_node_sqlite_snapshot_put(inst.topo_sqlite_db, first) == 0);
|
|
first->timestamp = 0;
|
|
assert(topo_node_sign_self(&inst, first) == 0);
|
|
assert(first->timestamp == INT64_MAX - 9 && topo_node_verify(first) == 0);
|
|
assert(topo_node_sign_self(&inst, first) == 0);
|
|
assert(first->timestamp == INT64_MAX - 8 && topo_node_verify(first) == 0);
|
|
test_bgp(&inst);
|
|
uint64_t before = first->timestamp;
|
|
assert(sqlite3_exec(inst.topo_sqlite_db,
|
|
"CREATE TRIGGER fail_snapshot BEFORE UPDATE ON node_snapshots BEGIN SELECT RAISE(ABORT,'test write failure'); END",
|
|
NULL, NULL, NULL) == SQLITE_OK);
|
|
assert(topo_node_sign_self(&inst, first) < 0);
|
|
assert(first->timestamp == before && topo_node_verify(first) == 0);
|
|
|
|
topo_node_registry_unref(&groups, inst.node_id);
|
|
assert(!groups.node_registry->head);
|
|
queue_free(groups.node_registry);
|
|
memory_pool_destroy(groups.v4_addr_pool);
|
|
memory_pool_destroy(groups.v4_sock_meta_pool);
|
|
memory_pool_destroy(groups.v6_sock_meta_pool);
|
|
memory_pool_destroy(groups.v6_addr_pool);
|
|
sqlite3_close(inst.topo_sqlite_db);
|
|
uasync_destroy(inst.ua, 0);
|
|
return 0;
|
|
}
|