chat: BGP-driven address sync, member identity verify, join_sig fix

- Адреса узлов вынесены из wire-протокола member_sync/merkle/join/welcome
  в BGP/topo_group: node_addresses теперь пишет только topo_node_sqlite_addrs_put
  (источник истины ni->v4/v6_addrs), классификация config_type/nat_type в sockmeta
- Жёсткая анти-подделка: node_id=derive(x25519), ed25519 сверяется с доверенной
  таблицей nodes, NODEINFO node_id/pubkey mismatch отклоняется как forgery
- Единые member_sync_build_join_msg (112Б) и member_sync_build_update_msg:
  устранён баг _verify_join_sig (join_ts по смещению 80 вместо 104, длина 104→112),
  который рушил приём мемберов; все 7 мест подписи/проверки join_sig через хелпер
- Единое обновление своего мембера chat_core_update_my_member (вместо sync_my_addresses)
- Android standby: пауза таймеров (broadcast/db_sync/merkle_sync/chat_sync) через standby_wait
- name[16] → name[MAX_CONN_NAME_LEN] с truncation-warning в парсере
- conn_mgr: etcp_connect_cancel_by_node (use-after-free при отмене invite),
  промоушен conn в ncd после успешного invite
- Android GUI: foreground-gating опросов, сохранение портов интерфейсов, фикс udp-log flush
- chatgui: персистентный db_path; мелкие DEBUG-логи epoll/pool
This commit is contained in:
evgeny committed 2026-08-15 12:36:05 +03:00
1 parent c07bd385ca
commit 5c2501b4f3
34 files changed
+808 -882

No files matched your search

+172 -74
View File
@@ -2,6 +2,7 @@
#include "topo_node.h"
#include "topo_group.h"
#include "etcp_connections.h"
#include "../config_parser.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/memory_pool.h"
@@ -30,6 +31,137 @@ static void peers_table_name(const char* ch_id, char* buf, size_t sz) {
snprintf(buf, sz, "peers_%s", san);
}
/* ── Классификация адресов (локально, по IP) ── */
static int addr_type_from_ip(int family, const uint8_t* addr) {
if (!addr) return ADDR_TYPE_NETIF;
if (family == 4) {
uint8_t b0 = addr[0], b1 = addr[1];
if (b0 == 0 || b0 == 10 || b0 == 127) return ADDR_TYPE_NETIF;
if (b0 == 169 && b1 == 254) return ADDR_TYPE_NETIF;
if (b0 >= 224 && b0 <= 239) return ADDR_TYPE_NETIF;
if (b0 == 172 && b1 >= 16 && b1 <= 31) return ADDR_TYPE_NETIF;
if (b0 == 100 && b1 >= 64 && b1 <= 127) return ADDR_TYPE_NETIF; /* 100.64.0.0/10 (CGNAT) */
if (b0 == 192 && b1 == 168) return ADDR_TYPE_NETIF;
{ uint32_t ip; memcpy(&ip, addr, 4); if (ip == 0 || ip == 0xffffffff) return ADDR_TYPE_NETIF; }
return ADDR_TYPE_DIRECT;
} else {
if (addr[0] == 0xfe && (addr[1] & 0xc0) == 0x80) return ADDR_TYPE_NETIF;
if (addr[0] == 0xfc || addr[0] == 0xfd) return ADDR_TYPE_NETIF;
{ static const uint8_t z[16]; if (!memcmp(addr, z, 16)) return ADDR_TYPE_NETIF; }
{ static const uint8_t lb[16]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}; if (!memcmp(addr,lb,16)) return ADDR_TYPE_NETIF; }
return ADDR_TYPE_DIRECT;
}
}
/* Итоговая классификация: конфиг + автоопределение + IP (всё локально). */
static int addr_type_classify(int config_type, int nat_type, int family, const uint8_t* ip) {
if (config_type == CFG_SERVER_TYPE_PUBLIC) return ADDR_TYPE_DIRECT;
if (config_type == CFG_SERVER_TYPE_PRIVATE || config_type == CFG_SERVER_TYPE_LOCAL) return ADDR_TYPE_NETIF;
if (nat_type == NAT_TYPE_EIM) return ADDR_TYPE_NAT_EIM;
if (nat_type == NAT_TYPE_STRICT) return ADDR_TYPE_NAT_STRICT;
if (nat_type == NAT_TYPE_DIRECT) return ADDR_TYPE_DIRECT;
return addr_type_from_ip(family, ip);
}
static void smeta4_lookup(struct TOPO_SOCKMETA4* head, uint8_t sid, uint8_t* cfg, uint8_t* nat) {
*cfg = CFG_SERVER_TYPE_UNKNOWN; *nat = NAT_TYPE_UNKNOWN;
for (const struct TOPO_SOCKMETA4* m = head; m; m = m->next)
if (m->id == sid) { *cfg = m->config_type; *nat = m->nat_type; return; }
}
static void smeta6_lookup(struct TOPO_SOCKMETA6* head, uint8_t sid, uint8_t* cfg, uint8_t* nat) {
*cfg = CFG_SERVER_TYPE_UNKNOWN; *nat = NAT_TYPE_UNKNOWN;
for (const struct TOPO_SOCKMETA6* m = head; m; m = m->next)
if (m->id == sid) { *cfg = m->config_type; *nat = m->nat_type; return; }
}
/* ── Персистенция адресов узла (источник истины — BGP/topo_group) ── */
int topo_node_sqlite_addrs_put(sqlite3* db, uint64_t node_id, struct TOPO_NODE* ni) {
if (!db || !ni) return -1;
sqlite3_exec(db, "BEGIN", NULL, NULL, NULL);
sqlite3_stmt* ds = NULL;
if (sqlite3_prepare_v2(db, "DELETE FROM node_addresses WHERE node_id=?", -1, &ds, NULL) == SQLITE_OK) {
sqlite3_bind_int64(ds, 1, (sqlite3_int64)node_id);
sqlite3_step(ds);
sqlite3_finalize(ds);
}
sqlite3_stmt* is = NULL;
if (sqlite3_prepare_v2(db,
"INSERT INTO node_addresses(node_id,family,protocol,address,port,addr_type,config_type,nat_type,socket_id)"
" VALUES(?,?,?,?,?,?,?,?,?)", -1, &is, NULL) != SQLITE_OK) {
sqlite3_exec(db, "COMMIT", NULL, NULL, NULL);
return -1;
}
int written = 0;
for (const struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) {
uint8_t cfg, nat; smeta4_lookup(ni->v4_sock_meta, a->socket_id, &cfg, &nat);
int at = (a->type == TOPO_ADDR_INTERFACE) ? ADDR_TYPE_NETIF
: addr_type_classify(cfg, nat, 4, a->addr);
sqlite3_bind_int64(is, 1, (sqlite3_int64)node_id);
sqlite3_bind_int(is, 2, 4);
sqlite3_bind_int(is, 3, (int)a->protocol);
sqlite3_bind_blob(is, 4, a->addr, 4, SQLITE_STATIC);
sqlite3_bind_int(is, 5, (int)a->port);
sqlite3_bind_int(is, 6, at);
sqlite3_bind_int(is, 7, (int)cfg);
sqlite3_bind_int(is, 8, (int)nat);
sqlite3_bind_int(is, 9, (int)a->socket_id);
sqlite3_step(is); sqlite3_reset(is);
written++;
}
for (const struct TOPO_ADDR6* a = ni->v6_addrs; a; a = a->next) {
uint8_t cfg, nat; smeta6_lookup(ni->v6_sock_meta, a->socket_id, &cfg, &nat);
int at = (a->type == TOPO_ADDR_INTERFACE) ? ADDR_TYPE_NETIF
: addr_type_classify(cfg, nat, 6, a->addr);
sqlite3_bind_int64(is, 1, (sqlite3_int64)node_id);
sqlite3_bind_int(is, 2, 6);
sqlite3_bind_int(is, 3, (int)a->protocol);
sqlite3_bind_blob(is, 4, a->addr, 16, SQLITE_STATIC);
sqlite3_bind_int(is, 5, (int)a->port);
sqlite3_bind_int(is, 6, at);
sqlite3_bind_int(is, 7, (int)cfg);
sqlite3_bind_int(is, 8, (int)nat);
sqlite3_bind_int(is, 9, (int)a->socket_id);
sqlite3_step(is); sqlite3_reset(is);
written++;
}
sqlite3_finalize(is);
sqlite3_exec(db, "COMMIT", NULL, NULL, NULL);
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "addrs_put: node=%016llx v4=%d v6=%d total=%d",
(unsigned long long)node_id,
topo_list_count((struct _topo_head*)ni->v4_addrs),
topo_list_count((struct _topo_head*)ni->v6_addrs), written);
return 0;
}
int topo_node_sqlite_member_exists(sqlite3* db, uint64_t node_id) {
if (!db) return 0;
sqlite3_stmt* s = NULL;
if (sqlite3_prepare_v2(db,
"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'peers\\_%' ESCAPE '\\'",
-1, &s, NULL) != SQLITE_OK) return 0;
char sql[256]; int found = 0;
while (!found && sqlite3_step(s) == SQLITE_ROW) {
const char* tbl = (const char*)sqlite3_column_text(s, 0);
if (!tbl) continue;
snprintf(sql, sizeof(sql), "SELECT 1 FROM \"%s\" WHERE node_id=?", tbl);
sqlite3_stmt* m = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &m, NULL) == SQLITE_OK) {
sqlite3_bind_int64(m, 1, (sqlite3_int64)node_id);
if (sqlite3_step(m) == SQLITE_ROW) found = 1;
sqlite3_finalize(m);
}
}
sqlite3_finalize(s);
return found;
}
int topo_node_sqlite_init(sqlite3* db) {
if (!db) return -1;
@@ -55,6 +187,8 @@ int topo_node_sqlite_init(sqlite3* db) {
" port INTEGER NOT NULL CHECK(port > 0 AND port <= 65535),"
" rtt INTEGER,"
" addr_type INTEGER DEFAULT 0,"
" config_type INTEGER DEFAULT 0,"
" nat_type INTEGER DEFAULT 0,"
" socket_id INTEGER NOT NULL DEFAULT 0,"
" created_at INTEGER DEFAULT (unixepoch())"
");"
@@ -105,44 +239,14 @@ int topo_node_sqlite_init(sqlite3* db) {
}
sqlite3_exec(db, "ALTER TABLE nodes ADD COLUMN client_type INTEGER NOT NULL DEFAULT 0", NULL, NULL, NULL);
sqlite3_exec(db, "ALTER TABLE node_addresses ADD COLUMN config_type INTEGER DEFAULT 0", NULL, NULL, NULL);
sqlite3_exec(db, "ALTER TABLE node_addresses ADD COLUMN nat_type INTEGER DEFAULT 0", NULL, NULL, NULL);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "topo_node_sqlite tables initialized");
return 0;
}
int topo_node_sqlite_node_put(sqlite3* db, struct TOPO_GROUPS* groups, struct TOPO_GROUP_NODE* nq, time_t now_sec) {
if (!db || !nq) return -1;
struct TOPO_NODE* ni = topo_node_registry_find(groups, nq->node_id);
if (!ni) return -1;
sqlite3_exec(db, "BEGIN", NULL, NULL, NULL);
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db,
"INSERT INTO nodes(node_id, name, x25519_pubkey, ed25519_pubkey, client_type, last_seen_at, online)"
" VALUES(?,?,?,?,?,?,1)"
" ON CONFLICT(node_id) DO UPDATE SET"
" name=CASE WHEN excluded.name != '' THEN excluded.name ELSE nodes.name END, x25519_pubkey=excluded.x25519_pubkey,"
" ed25519_pubkey=excluded.ed25519_pubkey, client_type=CASE WHEN excluded.client_type!=0 THEN excluded.client_type ELSE nodes.client_type END,"
" last_seen_at=excluded.last_seen_at,\n"
" created_at=COALESCE(nodes.created_at, excluded.created_at)",
-1, &stmt, NULL) == SQLITE_OK) {
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)ni->node_id);
sqlite3_bind_text(stmt, 2, ni->node_name ? ni->node_name : "", -1, SQLITE_STATIC);
sqlite3_bind_blob(stmt, 3, ni->public_key, 32, SQLITE_STATIC);
sqlite3_bind_blob(stmt, 4, ni->ed25519_public_key, 32, SQLITE_STATIC);
sqlite3_bind_int(stmt, 5, (int)ni->client_type);
sqlite3_bind_int64(stmt, 6, (sqlite3_int64)now_sec);
sqlite3_step(stmt); sqlite3_finalize(stmt);
}
sqlite3_exec(db, "COMMIT", NULL, NULL, NULL);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "node_put: nid=%016llx COMMITTED (nodes only, addrs via member_sync)",
(unsigned long long)ni->node_id);
return 0;
}
int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id,
const char* name, uint64_t owner_node_id,
const uint8_t* x25519_pub, const uint8_t* x25519_priv,
@@ -463,45 +567,9 @@ int topo_node_sqlite_channel_peers_all(sqlite3* db, const char* channel_id,
uint8_t nl = puserinfo ? (uint8_t)strnlen(puserinfo, 255) : 0;
uint8_t atl = adm_tags_s ? (uint8_t)strnlen(adm_tags_s, 255) : 0;
sqlite3_stmt* ast = NULL;
snprintf(sql, sizeof(sql),
"SELECT family, socket_id, protocol, address, port FROM node_addresses"
" WHERE node_id=? LIMIT 16");
if (sqlite3_prepare_v2(db, sql, -1, &ast, NULL) != SQLITE_OK) continue;
sqlite3_bind_int64(ast, 1, (sqlite3_int64)node_id);
uint8_t addr_cnt = 0; size_t addr_start = 0;
while (sqlite3_step(ast) == SQLITE_ROW && addr_cnt < 255) {
int family = sqlite3_column_int(ast, 0);
int sock_id = sqlite3_column_int(ast, 1);
int proto = sqlite3_column_int(ast, 2);
int ip_len = (family == 4) ? 4 : 16;
const uint8_t* ip = (const uint8_t*)sqlite3_column_blob(ast, 3);
uint16_t port = (uint16_t)sqlite3_column_int(ast, 4);
if (!ip) continue;
if (off + 1 + 1 + 1 + ip_len + 2 > buf_sz) { sqlite3_finalize(ast); sqlite3_finalize(stmt); return -2; }
if (addr_cnt == 0) addr_start = off;
buf[off++] = (uint8_t)family;
buf[off++] = (uint8_t)sock_id;
buf[off++] = (uint8_t)proto;
memcpy(buf + off, ip, (size_t)ip_len); off += (size_t)ip_len;
buf[off++] = (uint8_t)((port >> 8) & 0xFF);
buf[off++] = (uint8_t)(port & 0xFF);
addr_cnt++;
}
sqlite3_finalize(ast);
uint8_t flags = (join_sig && join_ts) ? PEERS_FLAG_HAS_JOIN : 0;
size_t peer_needed = 8 + 32 + 32 + 1 + (flags & PEERS_FLAG_HAS_JOIN ? 64+8 : 0) + 64 + 8 + 1 + (size_t)nl + 1 + 1 + (size_t)atl + 64;
size_t addr_sz = off - addr_start;
if (addr_cnt > 0) {
if (off + peer_needed > buf_sz) { sqlite3_finalize(stmt); return -2; }
memmove(buf + addr_start + peer_needed, buf + addr_start, addr_sz);
off = addr_start;
} else {
if (off + peer_needed + 1 > buf_sz) { sqlite3_finalize(stmt); return -2; }
addr_sz = 0;
}
size_t peer_needed = 8 + 32 + 32 + 1 + (flags & PEERS_FLAG_HAS_JOIN ? 64+8 : 0) + 64 + 8 + 1 + (size_t)nl + 1 + (size_t)atl + 64;
if (off + peer_needed > buf_sz) { sqlite3_finalize(stmt); return -2; }
memcpy(buf + off, &node_id, 8); off += 8;
memcpy(buf + off, x25519, 32); off += 32;
memcpy(buf + off, ed_pub, 32); off += 32;
@@ -519,12 +587,10 @@ int topo_node_sqlite_channel_peers_all(sqlite3* db, const char* channel_id,
}
buf[off++] = nl;
if (nl) { memcpy(buf + off, puserinfo, nl); off += nl; }
buf[off++] = addr_cnt;
buf[off++] = atl;
if (atl) { memcpy(buf + off, adm_tags_s, atl); off += atl; }
if (adm_tags_sig_b && sqlite3_column_bytes(stmt, 9) >= 64) { memcpy(buf + off, adm_tags_sig_b, 64); off += 64; }
else { memset(buf + off, 0, 64); off += 64; }
off += addr_sz;
(*cnt_ptr)++;
}
sqlite3_finalize(stmt);
@@ -705,11 +771,12 @@ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* gr
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;
uint8_t sock_id_counter = 0;
{
sqlite3_stmt* s = NULL;
if (sqlite3_prepare_v2(db,
"SELECT address,port,addr_type,socket_id,protocol FROM node_addresses"
"SELECT address,port,addr_type,socket_id,protocol,config_type,nat_type 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);
@@ -729,16 +796,31 @@ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* gr
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);
m4->nat_type = (uint8_t)sqlite3_column_int(s, 6);
m4->next = NULL;
if (!m4_head) m4_head = m4_tail = m4;
else { m4_tail->next = m4; m4_tail = m4; }
}
}
}
sqlite3_finalize(s);
}
}
/* 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 FROM node_addresses"
"SELECT address,port,addr_type,socket_id,protocol,config_type,nat_type 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);
@@ -758,6 +840,20 @@ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* gr
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);
m6->nat_type = (uint8_t)sqlite3_column_int(s, 6);
m6->next = NULL;
if (!m6_head) m6_head = m6_tail = m6;
else { m6_tail->next = m6; m6_tail = m6; }
}
}
}
sqlite3_finalize(s);
}
@@ -772,6 +868,8 @@ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* gr
ni->node_name = name;
ni->v4_addrs = v4_head;
ni->v6_addrs = v6_head;
ni->v4_sock_meta = m4_head;
ni->v6_sock_meta = m6_head;
DEBUG_INFO(DEBUG_CATEGORY_BGP, "node_load: node 0x%016llx loaded from DB name=%s v4=%d v6=%d",
(unsigned long long)node_id, name ? name : "",