diff --git a/src/routing_layer/topo_node_sqlite.c b/src/routing_layer/topo_node_sqlite.c index 2a726472..05efca82 100644 --- a/src/routing_layer/topo_node_sqlite.c +++ b/src/routing_layer/topo_node_sqlite.c @@ -1140,157 +1140,157 @@ struct TOPO_NODE* topo_node_sqlite_snapshot_load(sqlite3* db, struct TOPO_GROUPS } struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* groups, uint64_t node_id) { - if (!db || !groups) return NULL; + if (!db || !groups || !groups->instance || groups->instance->topo_groups != groups) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node load: invalid database/groups node=%016llx", (unsigned long long)node_id); return NULL; + } struct TOPO_NODE* snapshot = topo_node_sqlite_snapshot_load(db, groups, node_id); if (snapshot) return snapshot; + struct TOPO_NODE* ni = u_calloc(1, sizeof(*ni)); + if (!ni) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node load: allocation failed node=%016llx", (unsigned long long)node_id); return NULL; } + sqlite3_stmt* s = NULL; uint8_t pubkey[32] = {0}, ed_pubkey[32] = {0}; char* name = NULL; uint8_t client_type = 0; int found = 0; - { - sqlite3_stmt* s = NULL; - if (sqlite3_prepare_v2(db, "SELECT name,x25519_pubkey,ed25519_pubkey,client_type FROM nodes WHERE node_id=?", - -1, &s, NULL) == SQLITE_OK) { - sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id); - if (sqlite3_step(s) == SQLITE_ROW) { - const char* n = (const char*)sqlite3_column_text(s, 0); - if (n) name = u_strdup(n); - const void* pk = sqlite3_column_blob(s, 1); - if (pk && sqlite3_column_bytes(s, 1) >= 32) memcpy(pubkey, pk, 32); - const void* ep = sqlite3_column_blob(s, 2); - if (ep && sqlite3_column_bytes(s, 2) >= 32) memcpy(ed_pubkey, ep, 32); - client_type = (uint8_t)sqlite3_column_int(s, 3); - found = 1; - } - sqlite3_finalize(s); - } - } - if (!found) { u_free(name); return NULL; } - struct TOPO_ADDR4* v4_head = NULL; struct TOPO_ADDR4* v4_tail = NULL; struct TOPO_SOCKMETA4* m4_head = NULL; struct TOPO_SOCKMETA4* m4_tail = NULL; + struct TOPO_ADDR6* v6_head = NULL; struct TOPO_ADDR6* v6_tail = NULL; + struct TOPO_SOCKMETA6* m6_head = NULL; struct TOPO_SOCKMETA6* m6_tail = NULL; struct TOPO_REALITY_SOCK* r_head = NULL; struct TOPO_REALITY_SOCK* r_tail = NULL; { - sqlite3_stmt* s = NULL; + if (sqlite3_prepare_v2(db, "SELECT name,x25519_pubkey,ed25519_pubkey,client_type FROM nodes WHERE node_id=?", + -1, &s, NULL) != SQLITE_OK) goto sql_failed; + if (sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id) != SQLITE_OK) goto sql_failed; + int rc = sqlite3_step(s); + if (rc == SQLITE_ROW) { + const char* n = (const char*)sqlite3_column_text(s, 0); + if (n) { name = u_strdup(n); if (!name) goto allocation_failed; } + const void* pk = sqlite3_column_blob(s, 1); + if (pk && sqlite3_column_bytes(s, 1) >= 32) memcpy(pubkey, pk, 32); + const void* ep = sqlite3_column_blob(s, 2); + if (ep && sqlite3_column_bytes(s, 2) >= 32) memcpy(ed_pubkey, ep, 32); + client_type = (uint8_t)sqlite3_column_int(s, 3); + found = 1; + } else if (rc != SQLITE_DONE) goto sql_failed; + sqlite3_finalize(s); s = NULL; + } + if (!found) { DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "node load: missing node=%016llx", (unsigned long long)node_id); goto failed; } + + { if (sqlite3_prepare_v2(db, "SELECT address,port,addr_type,socket_id,protocol,config_type,nat_mapping,options,address_kind FROM node_addresses" " WHERE node_id=? AND family=4 ORDER BY addr_type", - -1, &s, NULL) == SQLITE_OK) { - sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id); - while (sqlite3_step(s) == SQLITE_ROW) { - const void* addr = sqlite3_column_blob(s, 0); - if (!addr || sqlite3_column_bytes(s, 0) < 4) continue; - int port = sqlite3_column_int(s, 1); - int sock_id_raw = sqlite3_column_int(s, 3); - uint8_t sock_id = (uint8_t)sock_id_raw; - uint8_t type = (uint8_t)sqlite3_column_int(s, 8); - struct TOPO_ADDR4* a4 = memory_pool_alloc(groups->v4_addr_pool); - if (!a4) continue; - memset(a4, 0, sizeof(*a4)); - int proto = sqlite3_column_int(s, 4); - memcpy(a4->addr, addr, 4); a4->port = (uint16_t)port; - a4->type = type; a4->socket_id = sock_id; a4->protocol = (uint8_t)proto; - if (!v4_head) v4_head = v4_tail = a4; - else { v4_tail->next = a4; v4_tail = a4; } + -1, &s, NULL) != SQLITE_OK) goto sql_failed; + if (sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id) != SQLITE_OK) goto sql_failed; + int rc; + while ((rc = sqlite3_step(s)) == SQLITE_ROW) { + const void* addr = sqlite3_column_blob(s, 0); + if (!addr || sqlite3_column_bytes(s, 0) < 4) continue; + int port = sqlite3_column_int(s, 1); + int sock_id_raw = sqlite3_column_int(s, 3); + uint8_t sock_id = (uint8_t)sock_id_raw; + uint8_t type = (uint8_t)sqlite3_column_int(s, 8); + struct TOPO_ADDR4* a4 = memory_pool_alloc(groups->v4_addr_pool); + if (!a4) goto allocation_failed; + memset(a4, 0, sizeof(*a4)); + int proto = sqlite3_column_int(s, 4); + memcpy(a4->addr, addr, 4); a4->port = (uint16_t)port; + a4->type = type; a4->socket_id = sock_id; a4->protocol = (uint8_t)proto; + if (!v4_head) v4_head = v4_tail = a4; + else { v4_tail->next = a4; v4_tail = a4; } - struct TOPO_SOCKMETA4* ex = m4_head; - while (ex && ex->id != sock_id) ex = ex->next; - if (!ex) { - struct TOPO_SOCKMETA4* m4 = memory_pool_alloc(groups->v4_sock_meta_pool); - if (m4) { - m4->id = sock_id; - m4->config_type = (uint8_t)sqlite3_column_int(s, 5); - /* Без подписанного snapshot профиль из SQL-проекции не считается доказательством. */ - memset(&m4->address_profile, 0, sizeof(m4->address_profile)); - m4->next = NULL; - if (!m4_head) m4_head = m4_tail = m4; - else { m4_tail->next = m4; m4_tail = m4; } - } - } - if (sqlite3_column_type(s, 7) == SQLITE_BLOB - && sqlite3_column_bytes(s, 7) == TOPO_ADDR_REALITY_OPTS_SIZE - && (proto & TOPO_PROTO_REALITY)) { - const struct TOPO_ADDR_REALITY_OPTS* o = (const struct TOPO_ADDR_REALITY_OPTS*)sqlite3_column_blob(s, 7); - struct TOPO_REALITY_SOCK* r = u_calloc(1, sizeof(*r)); - if (r) { - r->socket_id = sock_id; - memcpy(r->short_id, o->short_id, 8); - memcpy(r->server_pubkey, o->server_pubkey, 32); - memcpy(r->version, o->version, 3); - memcpy(r->server_name, o->server_name, TOPO_REALITY_SNAME_MAX); - r->server_name[TOPO_REALITY_SNAME_MAX - 1] = 0; - if (!r_head) r_head = r_tail = r; - else { r_tail->next = r; r_tail = r; } - } - } + struct TOPO_SOCKMETA4* ex = m4_head; + while (ex && ex->id != sock_id) ex = ex->next; + if (!ex) { + struct TOPO_SOCKMETA4* m4 = memory_pool_alloc(groups->v4_sock_meta_pool); + if (!m4) goto allocation_failed; + m4->id = sock_id; + m4->config_type = (uint8_t)sqlite3_column_int(s, 5); + /* Без подписанного snapshot профиль из SQL-проекции не считается доказательством. */ + memset(&m4->address_profile, 0, sizeof(m4->address_profile)); + m4->next = NULL; + if (!m4_head) m4_head = m4_tail = m4; + else { m4_tail->next = m4; m4_tail = m4; } + } + if (sqlite3_column_type(s, 7) == SQLITE_BLOB + && sqlite3_column_bytes(s, 7) == TOPO_ADDR_REALITY_OPTS_SIZE + && (proto & TOPO_PROTO_REALITY)) { + const struct TOPO_ADDR_REALITY_OPTS* o = (const struct TOPO_ADDR_REALITY_OPTS*)sqlite3_column_blob(s, 7); + struct TOPO_REALITY_SOCK* r = u_calloc(1, sizeof(*r)); + if (!r) goto allocation_failed; + r->socket_id = sock_id; + memcpy(r->short_id, o->short_id, 8); + memcpy(r->server_pubkey, o->server_pubkey, 32); + memcpy(r->version, o->version, 3); + memcpy(r->server_name, o->server_name, TOPO_REALITY_SNAME_MAX); + r->server_name[TOPO_REALITY_SNAME_MAX - 1] = 0; + if (!r_head) r_head = r_tail = r; + else { r_tail->next = r; r_tail = r; } } - sqlite3_finalize(s); } + if (rc != SQLITE_DONE) goto sql_failed; + sqlite3_finalize(s); s = NULL; } /* IPv6 addresses */ - struct TOPO_ADDR6* v6_head = NULL; struct TOPO_ADDR6* v6_tail = NULL; - struct TOPO_SOCKMETA6* m6_head = NULL; struct TOPO_SOCKMETA6* m6_tail = NULL; { - sqlite3_stmt* s = NULL; if (sqlite3_prepare_v2(db, "SELECT address,port,addr_type,socket_id,protocol,config_type,nat_mapping,options,address_kind FROM node_addresses" " WHERE node_id=? AND family=6 ORDER BY addr_type", - -1, &s, NULL) == SQLITE_OK) { - sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id); - while (sqlite3_step(s) == SQLITE_ROW) { - const void* addr = sqlite3_column_blob(s, 0); - if (!addr || sqlite3_column_bytes(s, 0) < 16) continue; - int port = sqlite3_column_int(s, 1); - int sock_id_raw = sqlite3_column_int(s, 3); - uint8_t sock_id = (uint8_t)sock_id_raw; - uint8_t type = (uint8_t)sqlite3_column_int(s, 8); - struct TOPO_ADDR6* a6 = memory_pool_alloc(groups->v6_addr_pool); - if (!a6) continue; - memset(a6, 0, sizeof(*a6)); - int proto6 = sqlite3_column_int(s, 4); - memcpy(a6->addr, addr, 16); a6->port = (uint16_t)port; - a6->type = type; a6->socket_id = sock_id; a6->protocol = (uint8_t)proto6; - if (!v6_head) v6_head = v6_tail = a6; - else { v6_tail->next = a6; v6_tail = a6; } + -1, &s, NULL) != SQLITE_OK) goto sql_failed; + if (sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id) != SQLITE_OK) goto sql_failed; + int rc; + while ((rc = sqlite3_step(s)) == SQLITE_ROW) { + const void* addr = sqlite3_column_blob(s, 0); + if (!addr || sqlite3_column_bytes(s, 0) < 16) continue; + int port = sqlite3_column_int(s, 1); + int sock_id_raw = sqlite3_column_int(s, 3); + uint8_t sock_id = (uint8_t)sock_id_raw; + uint8_t type = (uint8_t)sqlite3_column_int(s, 8); + struct TOPO_ADDR6* a6 = memory_pool_alloc(groups->v6_addr_pool); + if (!a6) goto allocation_failed; + memset(a6, 0, sizeof(*a6)); + int proto6 = sqlite3_column_int(s, 4); + memcpy(a6->addr, addr, 16); a6->port = (uint16_t)port; + a6->type = type; a6->socket_id = sock_id; a6->protocol = (uint8_t)proto6; + if (!v6_head) v6_head = v6_tail = a6; + else { v6_tail->next = a6; v6_tail = a6; } - struct TOPO_SOCKMETA6* ex = m6_head; - while (ex && ex->id != sock_id) ex = ex->next; - if (!ex) { - struct TOPO_SOCKMETA6* m6 = memory_pool_alloc(groups->v6_sock_meta_pool); - if (m6) { - m6->id = sock_id; - m6->config_type = (uint8_t)sqlite3_column_int(s, 5); - memset(&m6->address_profile, 0, sizeof(m6->address_profile)); - m6->next = NULL; - if (!m6_head) m6_head = m6_tail = m6; - else { m6_tail->next = m6; m6_tail = m6; } - } - } - if (sqlite3_column_type(s, 7) == SQLITE_BLOB - && sqlite3_column_bytes(s, 7) == TOPO_ADDR_REALITY_OPTS_SIZE - && (proto6 & TOPO_PROTO_REALITY)) { - const struct TOPO_ADDR_REALITY_OPTS* o = (const struct TOPO_ADDR_REALITY_OPTS*)sqlite3_column_blob(s, 7); - struct TOPO_REALITY_SOCK* r = u_calloc(1, sizeof(*r)); - if (r) { - r->socket_id = sock_id; - memcpy(r->short_id, o->short_id, 8); - memcpy(r->server_pubkey, o->server_pubkey, 32); - memcpy(r->version, o->version, 3); - memcpy(r->server_name, o->server_name, TOPO_REALITY_SNAME_MAX); - r->server_name[TOPO_REALITY_SNAME_MAX - 1] = 0; - if (!r_head) r_head = r_tail = r; - else { r_tail->next = r; r_tail = r; } - } - } + struct TOPO_SOCKMETA6* ex = m6_head; + while (ex && ex->id != sock_id) ex = ex->next; + if (!ex) { + struct TOPO_SOCKMETA6* m6 = memory_pool_alloc(groups->v6_sock_meta_pool); + if (!m6) goto allocation_failed; + m6->id = sock_id; + m6->config_type = (uint8_t)sqlite3_column_int(s, 5); + memset(&m6->address_profile, 0, sizeof(m6->address_profile)); + m6->next = NULL; + if (!m6_head) m6_head = m6_tail = m6; + else { m6_tail->next = m6; m6_tail = m6; } + } + if (sqlite3_column_type(s, 7) == SQLITE_BLOB + && sqlite3_column_bytes(s, 7) == TOPO_ADDR_REALITY_OPTS_SIZE + && (proto6 & TOPO_PROTO_REALITY)) { + const struct TOPO_ADDR_REALITY_OPTS* o = (const struct TOPO_ADDR_REALITY_OPTS*)sqlite3_column_blob(s, 7); + struct TOPO_REALITY_SOCK* r = u_calloc(1, sizeof(*r)); + if (!r) goto allocation_failed; + r->socket_id = sock_id; + memcpy(r->short_id, o->short_id, 8); + memcpy(r->server_pubkey, o->server_pubkey, 32); + memcpy(r->version, o->version, 3); + memcpy(r->server_name, o->server_name, TOPO_REALITY_SNAME_MAX); + r->server_name[TOPO_REALITY_SNAME_MAX - 1] = 0; + if (!r_head) r_head = r_tail = r; + else { r_tail->next = r; r_tail = r; } } - sqlite3_finalize(s); } + if (rc != SQLITE_DONE) goto sql_failed; + sqlite3_finalize(s); s = NULL; + } + if (!v4_head && !v6_head) { + DEBUG_WARN(DEBUG_CATEGORY_BGP, "node load: no bootstrap addresses node=%016llx", (unsigned long long)node_id); goto failed; } - if (!v4_head && !v6_head) { u_free(name); DEBUG_WARN(DEBUG_CATEGORY_BGP, "node_load: node 0x%016llx has no addresses (may be stub/seed — will retry when addresses arrive)", (unsigned long long)node_id); return NULL; } - struct TOPO_NODE* ni = u_calloc(1, sizeof(struct TOPO_NODE)); - if (!ni) { u_free(name); return NULL; } ni->node_id = node_id; ni->ver = 1; ni->group_ref_count = 0; ni->client_type = client_type; memcpy(ni->public_key, pubkey, 32); memcpy(ni->ed25519_public_key, ed_pubkey, 32); @@ -1306,6 +1306,18 @@ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* gr topo_list_count((struct _topo_head*)v4_head), topo_list_count((struct _topo_head*)v6_head)); return ni; +sql_failed: + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node load SQL failed node=%016llx rc=%d: %s", + (unsigned long long)node_id, sqlite3_extended_errcode(db), sqlite3_errmsg(db)); + goto failed; +allocation_failed: + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node load: bootstrap allocation failed node=%016llx", (unsigned long long)node_id); +failed: + sqlite3_finalize(s); + ni->node_name = name; ni->v4_addrs = v4_head; ni->v4_sock_meta = m4_head; + ni->v6_addrs = v6_head; ni->v6_sock_meta = m6_head; ni->reality_socks = r_head; + topo_node_destroy(groups, ni); + return NULL; } int topo_node_sqlite_set_connected(sqlite3* db, const char* channel_id, uint64_t node_id, int connected) { diff --git a/tests/test_topo_node_wire.c b/tests/test_topo_node_wire.c index fe6636d9..ba0707a0 100644 --- a/tests/test_topo_node_wire.c +++ b/tests/test_topo_node_wire.c @@ -4,6 +4,7 @@ #include #include "utun_instance.h" #include "routing_layer/topo_group.h" +#include "routing_layer/topo_node_sqlite.h" #include "transport_layer/etcp.h" #include "../lib/debug_config.h" #include "../lib/mem.h" @@ -24,7 +25,7 @@ static unsigned fail_at, attempts, inside_pool; static int track_allocations; static int allocation_fails(const char *where) { - if (!track_allocations || !strstr(where, "topo_node.c:")) return 0; + if (!track_allocations || (!strstr(where, "topo_node.c:") && !strstr(where, "topo_node_sqlite.c:"))) return 0; attempts++; if (attempts != fail_at) return 0; DEBUG_WARN(DEBUG_CATEGORY_BGP, "[fault] NODEINFO allocation %u rejected at %s", attempts, where); @@ -262,6 +263,39 @@ static void test_allocation_failures(struct wire_fixture *f) { fail_at = 0; DEBUG_INFO(DEBUG_CATEGORY_BGP, "[PASS] NODEINFO all %u allocation failures leave empty outputs and no live allocations", total); } + +/* SQL bootstrap обязан освободить уже прочитанные адреса/Reality при отказе любого следующего выделения. */ +static void test_bootstrap_allocation_failures(struct wire_fixture *f) { + fixture_packet(f, 0, 0); + struct TOPO_NODE *node = NULL; + assert(!topo_node_deserialize(&f->group, f->wire, f->length, &node, NULL, NULL, NULL, NULL)); + node->v4_addrs->protocol = node->v6_addrs->protocol = TOPO_PROTO_TCP | TOPO_PROTO_REALITY; + sqlite3 *db = NULL; + assert(sqlite3_open(":memory:", &db) == SQLITE_OK && !topo_node_sqlite_init(db)); + assert(!topo_node_sqlite_node_update_verified(db, node->node_id, "bootstrap", node->public_key, node->ed25519_public_key, 1, 1)); + assert(!topo_node_sqlite_addrs_put(db, node->node_id, node)); + attempts = 0; fail_at = 0; track_allocations = 1; + struct TOPO_NODE *loaded = topo_node_sqlite_node_load(db, &f->groups, node->node_id); + track_allocations = 0; + assert(loaded && loaded->v4_addrs && loaded->v6_addrs && loaded->reality_socks && attempts > 8); + unsigned total = attempts; + topo_node_destroy(&f->groups, loaded); + for (unsigned nth = 1; nth <= total; nth++) { + size_t before = live_allocations(); + attempts = 0; fail_at = nth; track_allocations = 1; + loaded = topo_node_sqlite_node_load(db, &f->groups, node->node_id); + track_allocations = 0; + assert(!loaded && attempts == nth && live_allocations() == before); + assert(sqlite3_next_stmt(db, NULL) == NULL); + } + fail_at = 0; + assert(sqlite3_exec(db, "DROP TABLE node_addresses", NULL, NULL, NULL) == SQLITE_OK); + size_t before = live_allocations(); + assert(!topo_node_sqlite_node_load(db, &f->groups, node->node_id) && live_allocations() == before); + assert(sqlite3_next_stmt(db, NULL) == NULL); + topo_node_destroy(&f->groups, node); sqlite3_close(db); + DEBUG_INFO(DEBUG_CATEGORY_BGP, "[PASS] SQL bootstrap: %u allocation failures and SQL failure leave no live objects/statements", total); +} #endif int main(void) { @@ -275,6 +309,7 @@ int main(void) { test_empty_and_arguments(&fixture); #ifdef __linux__ test_allocation_failures(&fixture); + test_bootstrap_allocation_failures(&fixture); #endif fixture_destroy(&fixture); assert(u_get_allocated_count() == before);