diff --git a/lib/memory_pool.c b/lib/memory_pool.c index eea9588d..daf12341 100644 --- a/lib/memory_pool.c +++ b/lib/memory_pool.c @@ -84,12 +84,12 @@ void* memory_pool_alloc_impl(struct memory_pool* pool, const char* location) { pool->reuse_count++; memset(obj, 0, pool->object_size); pool_init_tags(pool, obj, location); - DEBUG_TRACE(DEBUG_CATEGORY_SYS, "pool_alloc reused: %s, remaining=%zu", location, pool->free_count); +// DEBUG_TRACE(DEBUG_CATEGORY_SYS, "pool_alloc reused: %s, remaining=%zu", location, pool->free_count); return obj; } pool->allocations++; - DEBUG_TRACE(DEBUG_CATEGORY_SYS, "pool_alloc: %s, total_allocs=%zu", location, pool->allocations); +// DEBUG_TRACE(DEBUG_CATEGORY_SYS, "pool_alloc: %s, total_allocs=%zu", location, pool->allocations); void* obj = u_calloc_impl(1, POOL_ALLOC_SIZE, location); if (obj) pool_init_tags(pool, obj, location); return obj; @@ -108,11 +108,11 @@ void memory_pool_free_impl(struct memory_pool* pool, void* obj, const char* loca pool->free_head = obj; pool->free_count++; g_total_free++; - DEBUG_TRACE(DEBUG_CATEGORY_SYS, "pool_free returned: %s, free_count=%zu", location, pool->free_count); +// DEBUG_TRACE(DEBUG_CATEGORY_SYS, "pool_free returned: %s, free_count=%zu", location, pool->free_count); return; } - DEBUG_TRACE(DEBUG_CATEGORY_SYS, "pool_free direct: %s", location); +// DEBUG_TRACE(DEBUG_CATEGORY_SYS, "pool_free direct: %s", location); u_free_impl(obj, location); } diff --git a/lib/u_async.c b/lib/u_async.c index 3fa98136..e7399e58 100644 --- a/lib/u_async.c +++ b/lib/u_async.c @@ -931,6 +931,7 @@ static void process_epoll_events(struct UASYNC* ua, struct epoll_event* events, for (int i = 0; i < n_events; i++) { // Wakeup fd detected by data.fd < 0 (lower 32 bits of data.u64 = -1) if (events[i].data.fd < 0) { + DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "wakeup→pipe"); if (events[i].events & EPOLLIN) { drain_wakeup_pipe(ua); } process_posted_tasks(ua); continue; @@ -942,6 +943,10 @@ static void process_epoll_events(struct UASYNC* ua, struct epoll_event* events, struct socket_node* node = socket_array_get(ua->sockets, fd); if (!node || !node->active) continue; if (node->gen != ev_gen) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "EPOLL STALE fd=%d ev_gen=%u node_gen=%u — skipped", fd, ev_gen, node->gen); continue; } + int r = (events[i].events & EPOLLIN) ? 1 : 0; + int w = (events[i].events & EPOLLOUT) ? 1 : 0; + int e = (events[i].events & (EPOLLERR | EPOLLHUP)) ? 1 : 0; + DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "epoll→fd=%d r=%d w=%d e=%d", fd, r, w, e); int local_fd = node->fd; socket_t local_sock = node->sock; int local_type = node->type; @@ -1059,9 +1064,10 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) { ua->last_poll_exit_us = get_time_us(); if (ret < 0) { if (saved_errno == EINTR) { + DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "epoll_wait→EINTR"); return; } - perror("epoll_wait"); + DEBUG_ERROR(DEBUG_CATEGORY_SYS, "epoll_wait→error errno=%d", saved_errno); return; } diff --git a/src/broadcast.c b/src/broadcast.c index e9d534dc..5b7ab2ce 100644 --- a/src/broadcast.c +++ b/src/broadcast.c @@ -4,6 +4,9 @@ #include "../lib/debug_config.h" #include "../lib/mem.h" #include "../lib/u_async.h" +#ifdef UTUN_HAVE_STANDBY +#include "standby.h" +#endif #include "etcp_api.h" #include "etcp_connections.h" #include "etcp.h" @@ -25,6 +28,7 @@ struct broadcast_ctx { struct ll_queue* messages; struct broadcast_cbk_entry* cbks; void* cleanup_timer; + void* cleanup_wait; /* standby_wait handle (Android) */ uint32_t send_seq; }; @@ -57,6 +61,9 @@ void broadcast_destroy(struct TOPO_GROUP* group) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "broadcast_destroy grp=%016llx", (unsigned long long)group->group_id); if (ctx->cleanup_timer) { uasync_cancel_timeout(group->instance->ua, ctx->cleanup_timer); ctx->cleanup_timer = NULL; } +#ifdef UTUN_HAVE_STANDBY + if (ctx->cleanup_wait) { standby_wait_cancel(ctx->cleanup_wait); ctx->cleanup_wait = NULL; } +#endif while (ctx->cbks) { struct broadcast_cbk_entry* c = ctx->cbks; ctx->cbks = c->next; u_free(c); } @@ -151,6 +158,13 @@ static void broadcast_fire_cbks(struct broadcast_ctx* ctx, const uint8_t* uuid, static void broadcast_cleanup_timer_cb(void* arg) { struct broadcast_ctx* ctx = (struct broadcast_ctx*)arg; if (!ctx || !ctx->group) return; +#ifdef UTUN_HAVE_STANDBY + if (standby_get_sleep_tb() > 0) { + ctx->cleanup_wait = standby_wait(ctx, broadcast_cleanup_timer_cb); + return; + } + ctx->cleanup_wait = NULL; +#endif uint64_t now_tb = get_time_tb(); int removed = 0; diff --git a/src/chat/chat_channel.c b/src/chat/chat_channel.c index 9422c5b5..14b45655 100644 --- a/src/chat/chat_channel.c +++ b/src/chat/chat_channel.c @@ -32,12 +32,10 @@ static int channel_sign_join(const uint8_t* ch_x25519, const uint8_t* ch_ed25519 uint64_t join_ts, const uint8_t* ed25519_privkey, uint8_t sig_out[64]) { - uint8_t msg[256]; size_t mlen = 0; - memcpy(msg + mlen, ch_x25519, 32); mlen += 32; - memcpy(msg + mlen, ch_ed25519, 32); mlen += 32; - memcpy(msg + mlen, &node_id, 8); mlen += 8; - memcpy(msg + mlen, node_pubkey, 32); mlen += 32; - memcpy(msg + mlen, &join_ts, 8); mlen += 8; + uint8_t msg[128]; + int mlen = member_sync_build_join_msg(ch_x25519, ch_ed25519, node_id, node_pubkey, join_ts, + msg, (int)sizeof(msg)); + if (mlen < 0) { memset(sig_out, 0, 64); return -1; } EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, ed25519_privkey, 32); @@ -45,80 +43,13 @@ static int channel_sign_join(const uint8_t* ch_x25519, const uint8_t* ch_ed25519 EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); if (!mdctx) { EVP_PKEY_free(pkey); memset(sig_out, 0, 64); return -1; } int ok = EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey) == 1 - && EVP_DigestSign(mdctx, sig_out, &(size_t){64}, msg, mlen) == 1; + && EVP_DigestSign(mdctx, sig_out, &(size_t){64}, msg, (size_t)mlen) == 1; EVP_MD_CTX_free(mdctx); EVP_PKEY_free(pkey); if (!ok) memset(sig_out, 0, 64); return ok ? 0 : -1; } -/* ─── сбор адресов текущего узла (для member_sync_put) ─── */ - -static int collect_my_addrs(uint8_t* buf, int max_buf_sz) { - int pos = 0, count = 0; - - struct ETCP_SOCKET* s = g_cc.inst->etcp_sockets; - while (s) { - if (s->is_tcp) { s = s->next; continue; } - struct sockaddr_storage* sa = s->interface_addr.ss_family ? &s->interface_addr : NULL; - if (!sa) sa = s->local_addr.ss_family ? &s->local_addr : NULL; - if (sa && sa->ss_family == AF_INET) { - struct sockaddr_in* sin = (struct sockaddr_in*)sa; - if (pos + 9 > max_buf_sz) break; - buf[pos++] = 4; buf[pos++] = s->sock_id; buf[pos++] = 1; // proto=1 (UDP) - memcpy(buf + pos, &sin->sin_addr, 4); pos += 4; - uint16_t port = ntohs(sin->sin_port); - buf[pos++] = (uint8_t)(port >> 8); buf[pos++] = (uint8_t)(port & 0xFF); - count++; - uint8_t* ip = (uint8_t*)&sin->sin_addr; - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] collect_my_addrs UDP sock=%d %d.%d.%d.%d:%d", - CC_ID, s->sock_id, ip[0], ip[1], ip[2], ip[3], port); - } else if (sa && sa->ss_family == AF_INET6) { - struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sa; - if (pos + 21 > max_buf_sz) break; - buf[pos++] = 6; buf[pos++] = s->sock_id; buf[pos++] = 1; // proto=1 (UDP) - memcpy(buf + pos, &sin6->sin6_addr, 16); pos += 16; - uint16_t port = ntohs(sin6->sin6_port); - buf[pos++] = (uint8_t)(port >> 8); buf[pos++] = (uint8_t)(port & 0xFF); - count++; - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] collect_my_addrs UDP v6 sock=%d port=%d", - CC_ID, s->sock_id, port); - } - s = s->next; - } - - { struct ETCP_SOCKET* ts = g_cc.inst->etcp_sockets; - while (ts) { - if (!ts->is_tcp) { ts = ts->next; continue; } - struct sockaddr_storage* addr = ts->interface_addr.ss_family ? &ts->interface_addr : &ts->local_addr; - if (addr && addr->ss_family == AF_INET) { - struct sockaddr_in* sin = (struct sockaddr_in*)addr; - if (pos + 9 > max_buf_sz) break; - buf[pos++] = 4; buf[pos++] = ts->sock_id; buf[pos++] = 2; // proto=2 (TCP) - memcpy(buf + pos, &sin->sin_addr, 4); pos += 4; - uint16_t port = ntohs(sin->sin_port); - buf[pos++] = (uint8_t)(port >> 8); buf[pos++] = (uint8_t)(port & 0xFF); - count++; - uint8_t* ip = (uint8_t*)&sin->sin_addr; - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] collect_my_addrs TCP sock=%d %d.%d.%d.%d:%d", - CC_ID, ts->sock_id, ip[0], ip[1], ip[2], ip[3], port); - } else if (addr && addr->ss_family == AF_INET6) { - struct sockaddr_in6* sin6 = (struct sockaddr_in6*)addr; - if (pos + 21 > max_buf_sz) break; - buf[pos++] = 6; buf[pos++] = ts->sock_id; buf[pos++] = 2; // proto=2 (TCP) - memcpy(buf + pos, &sin6->sin6_addr, 16); pos += 16; - uint16_t port = ntohs(sin6->sin6_port); - buf[pos++] = (uint8_t)(port >> 8); buf[pos++] = (uint8_t)(port & 0xFF); - count++; - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] collect_my_addrs TCP v6 sock=%d port=%d", - CC_ID, ts->sock_id, port); - } - ts = ts->next; } - } - - return count; -} - /* ─── подготовка инфраструктуры канала (db_sync instance) ─── */ void chat_core_ensure_channel_ready(const char* ch_id) { @@ -186,20 +117,19 @@ void chat_core_create_channel(struct chat_channel_create* req) { myid, g_cc.inst->my_keys.public_key, join_ts, g_cc.inst->my_ed25519_privkey, join_sig); - uint8_t my_addrs[256]; int my_addr_cnt = collect_my_addrs(my_addrs, (int)sizeof(my_addrs)); - char juser3[256]; snprintf(juser3, sizeof(juser3), "{\"name\":\"%s\"}", g_cc.inst->name[0] ? g_cc.inst->name : ""); int mrc = member_sync_put(g_cc.inst, req->channel_id, myid, g_cc.inst->my_keys.public_key, g_cc.inst->my_ed25519_pubkey, - join_sig, join_ts, NULL, 0, juser3, my_addrs, my_addr_cnt, NULL, NULL, 0); + join_sig, join_ts, NULL, 0, juser3, NULL, NULL, 0); if (mrc < 0) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: member_sync_put(self) FAILED ch=%s rc=%d", CC_ID, req->channel_id, mrc); } else if (mrc & MS_APPLY_CHANGED) { member_sync_broadcast_one(g_cc.inst, req->channel_id, myid); } - chat_core_sync_my_addresses(); + /* адреса + update_ts + подпись + синк — единой функцией */ + chat_core_update_my_member(); } chat_event_post(CHAT_EVT_CHANNEL_UPDATED, data, 1 + ch_id_len); diff --git a/src/chat/chat_core.c b/src/chat/chat_core.c index fd17c59c..e5678c83 100644 --- a/src/chat/chat_core.c +++ b/src/chat/chat_core.c @@ -667,7 +667,7 @@ int chat_member_tags_commit(struct chat_member_tags* t) { if (stg_val && strcmp(stg_val, "yes") == 0) stg = 1; int rc = member_sync_put(g_cc.inst, t->ch_id, t->node_id, x25, ed, jsig, jts, usig, uts, - uinfo ? uinfo : "", NULL, 0, json, sig, stg); + uinfo ? uinfo : "", json, sig, stg); sqlite3_finalize(st); if (rc < 0) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: tags_commit — member_sync_put failed rc=%d", CC_ID, rc); return -1; } if (rc & MS_APPLY_CHANGED) member_sync_broadcast_one(g_cc.inst, t->ch_id, t->node_id); diff --git a/src/chat/chat_core.h b/src/chat/chat_core.h index 77f83f36..6e052b80 100644 --- a/src/chat/chat_core.h +++ b/src/chat/chat_core.h @@ -74,6 +74,8 @@ void chat_core_set_my_node_id(uint64_t node_id); void chat_core_update_my_name(const char* name); void chat_core_update_my_name_trampoline(void* arg); void chat_core_sync_my_addresses(void); +/* Единое обновление своего мембера: адреса + bump update_ts + подпись (с адресами) + синк */ +void chat_core_update_my_member(void); /* Сохранение key-value в ui_state (вызывается из uasync-потока) */ void chat_core_save_ui_state(const char* key, const char* value); diff --git a/src/chat/chat_core_priv.h b/src/chat/chat_core_priv.h index 4acd683d..af6d9f88 100644 --- a/src/chat/chat_core_priv.h +++ b/src/chat/chat_core_priv.h @@ -73,31 +73,6 @@ static inline size_t b64_decode(const char* src, size_t src_len, uint8_t* dst, s return out; } -/* ── addr_type classification ── */ - -#include "../routing_layer/topo_node.h" - -static inline 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; - } -} - /* ── Глобальное состояние (определено в chat_core.c) ── */ struct chat_core_ctx { diff --git a/src/chat/chat_profile.c b/src/chat/chat_profile.c index a52f7792..7797b2bd 100644 --- a/src/chat/chat_profile.c +++ b/src/chat/chat_profile.c @@ -32,63 +32,7 @@ void chat_core_update_my_name(const char* name) { name, g_cc.inst->my_keys.public_key, g_cc.inst->my_ed25519_pubkey, (uint64_t)ntp_time_get_seconds(g_cc.inst), 0); snprintf(g_cc.inst->name, sizeof(g_cc.inst->name), "%s", name); - - uint64_t myid = g_cc.my_node_id; - sqlite3_stmt* cs = NULL; - sqlite3_prepare_v2(g_cc.db, "SELECT channel_id FROM channels", -1, &cs, NULL); - if (cs) { - while (sqlite3_step(cs) == SQLITE_ROW) { - const char* ch = (const char*)sqlite3_column_text(cs, 0); - if (!ch) continue; - char tbl[80]; peers_table_name(ch, tbl, sizeof(tbl)); - char buf[256]; snprintf(buf, sizeof(buf), "SELECT update_ts FROM \"%s\" WHERE node_id=?", tbl); - sqlite3_stmt* ps = NULL; - if (sqlite3_prepare_v2(g_cc.db, buf, -1, &ps, NULL) != SQLITE_OK) continue; - sqlite3_bind_int64(ps, 1, (sqlite3_int64)myid); - if (sqlite3_step(ps) != SQLITE_ROW) { sqlite3_finalize(ps); continue; } - uint64_t local_uts = (uint64_t)sqlite3_column_int64(ps, 0); - sqlite3_finalize(ps); - - /* join неизменен при смене имени — читаем существующий join_sig/join_ts */ - uint8_t join_sig[64]; uint64_t join_ts = 0; - if (topo_node_sqlite_member_get_join(g_cc.db, ch, myid, join_sig, &join_ts) != 0) continue; - - char juser[256]; snprintf(juser, sizeof(juser), "{\"name\":\"%s\"}", name); - uint64_t update_ts = (uint64_t)ntp_time_get_seconds(g_cc.inst); - if (update_ts <= local_uts) update_ts = local_uts + 1; /* гарантируем рост ver */ - - /* update_sig = sign(join_sig || update_ts || userinfo) ключом мембера */ - uint8_t umsg[256 + 64 + 8]; size_t ulen = 0; - memcpy(umsg + ulen, join_sig, 64); ulen += 64; - memcpy(umsg + ulen, &update_ts, 8); ulen += 8; - size_t jl = strlen(juser); - memcpy(umsg + ulen, juser, jl + 1); ulen += jl + 1; - uint8_t update_sig[64]; memset(update_sig, 0, 64); - EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, - g_cc.inst->my_ed25519_privkey, 32); - if (pkey) { - EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); - if (mdctx) { - if (EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey) == 1) - EVP_DigestSign(mdctx, update_sig, &(size_t){64}, umsg, ulen); - EVP_MD_CTX_free(mdctx); - } - EVP_PKEY_free(pkey); - } - - int r2 = member_sync_put(g_cc.inst, ch, myid, g_cc.inst->my_keys.public_key, - g_cc.inst->my_ed25519_pubkey, join_sig, join_ts, - update_sig, update_ts, juser, NULL, 0, NULL, NULL, 0); - if (r2 < 0) DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: member_sync_put(my_name) FAILED ch=%s rc=%d", CC_ID, ch, r2); - else if (r2 & MS_APPLY_CHANGED) member_sync_broadcast_one(g_cc.inst, ch, myid); - - uint8_t mevt[1 + 64 + 1 + CHAT_MEMBER_DISPLAY_SIZE]; uint8_t cl = (uint8_t)strlen(ch); - mevt[0] = cl; memcpy(mevt + 1, ch, cl); mevt[1 + cl] = 1; - if (chat_core_get_single_member(ch, myid, mevt + 1 + cl + 1) == 0) - chat_event_post(CHAT_EVT_MEMBER_UPDATED, mevt, 1 + cl + 1 + CHAT_MEMBER_DISPLAY_SIZE); - } - sqlite3_finalize(cs); - } + chat_core_update_my_member(); } void chat_core_update_my_name_trampoline(void* arg) { @@ -96,136 +40,71 @@ void chat_core_update_my_name_trampoline(void* arg) { u_free(arg); } -void chat_core_sync_my_addresses(void) { - if (!g_cc.initialized || !g_cc.db || !g_cc.inst) return; +/* ── Обновление своего мембера (userinfo + update_ts + подпись + синк). + Адреса больше не здесь — их синхронизирует BGP/topo_group. ── */ - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] sync_my_addresses START my_node=0x%016llx", CC_ID, (unsigned long long)g_cc.my_node_id); +void chat_core_update_my_member(void) { + if (!g_cc.initialized || !g_cc.db || !g_cc.inst) return; - sqlite3_stmt* del = NULL; - sqlite3_prepare_v2(g_cc.db, "DELETE FROM node_addresses WHERE node_id=?", -1, &del, NULL); - if (del) { - sqlite3_bind_int64(del, 1, (sqlite3_int64)g_cc.my_node_id); - sqlite3_step(del); - int deleted = sqlite3_changes(g_cc.db); - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] DELETE for my_node=0x%016llx: %d rows deleted", CC_ID, (unsigned long long)g_cc.my_node_id, deleted); - sqlite3_finalize(del); - } + uint64_t myid = g_cc.my_node_id; + const char* my_name = g_cc.inst->name[0] ? g_cc.inst->name : ""; + char juser[256]; snprintf(juser, sizeof(juser), "{\"name\":\"%s\"}", my_name); - sqlite3_stmt* ins = NULL; - sqlite3_prepare_v2(g_cc.db, - "INSERT OR REPLACE INTO node_addresses(node_id,family,protocol,address,port,addr_type,socket_id)" - " VALUES(?,?,1,?,?,?,?)", -1, &ins, NULL); - if (!ins) { - DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] FAIL prepare INSERT stmt my_node=0x%016llx", CC_ID, (unsigned long long)g_cc.my_node_id); - return; - } + sqlite3_stmt* cs = NULL; + sqlite3_prepare_v2(g_cc.db, "SELECT channel_id FROM channels", -1, &cs, NULL); + if (!cs) return; + while (sqlite3_step(cs) == SQLITE_ROW) { + const char* ch = (const char*)sqlite3_column_text(cs, 0); + if (!ch) continue; + + char tbl[80]; peers_table_name(ch, tbl, sizeof(tbl)); + uint8_t join_sig[64]; uint64_t join_ts = 0; + if (topo_node_sqlite_member_get_join(g_cc.db, ch, myid, join_sig, &join_ts) != 0) continue; + + uint64_t local_uts = 0; + { char q[256]; snprintf(q, sizeof(q), "SELECT update_ts FROM \"%s\" WHERE node_id=?", tbl); + sqlite3_stmt* ps = NULL; + if (sqlite3_prepare_v2(g_cc.db, q, -1, &ps, NULL) == SQLITE_OK) { + sqlite3_bind_int64(ps, 1, (sqlite3_int64)myid); + if (sqlite3_step(ps) == SQLITE_ROW) local_uts = (uint64_t)sqlite3_column_int64(ps, 0); + sqlite3_finalize(ps); + } + } - struct ETCP_SOCKET* sock = g_cc.inst->etcp_sockets; - int sock_count = 0, written = 0, tcp_written = 0, skipped = 0; - while (sock) { - sock_count++; - if (sock->is_tcp) { sock = sock->next; continue; } - if (sock->type == CFG_SERVER_TYPE_PRIVATE || sock->type == CFG_SERVER_TYPE_LOCAL) { skipped++; sock = sock->next; continue; } - int sock_family = sock->local_addr.ss_family ? sock->local_addr.ss_family : 0; - struct sockaddr_storage* sa = (sock->interface_addr.ss_family == sock_family) ? &sock->interface_addr : NULL; - if (!sa) sa = sock->local_addr.ss_family ? &sock->local_addr : NULL; - if (!sa) { DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] socket %d — no address for family=%d", CC_ID, sock_count, sock_family); sock = sock->next; continue; } - - if (sa->ss_family == AF_INET) { - struct sockaddr_in* sin = (struct sockaddr_in*)sa; - sqlite3_bind_int64(ins, 1, (sqlite3_int64)g_cc.my_node_id); - sqlite3_bind_int(ins, 2, 4); - sqlite3_bind_blob(ins, 3, &sin->sin_addr, 4, SQLITE_STATIC); - sqlite3_bind_int(ins, 4, (int)ntohs(sin->sin_port)); - sqlite3_bind_int(ins, 5, addr_type_from_ip(4, (const uint8_t*)&sin->sin_addr)); - sqlite3_bind_int(ins, 6, (int)sock->sock_id); - sqlite3_step(ins); sqlite3_reset(ins); - uint8_t* ip = (uint8_t*)&sin->sin_addr; - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] INSERT my_node=0x%016llx sock=%d %d.%d.%d.%d:%d", - CC_ID, (unsigned long long)g_cc.my_node_id, sock->sock_id, ip[0], ip[1], ip[2], ip[3], (int)ntohs(sin->sin_port)); - written++; - } else if (sa->ss_family == AF_INET6) { - struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sa; - sqlite3_bind_int64(ins, 1, (sqlite3_int64)g_cc.my_node_id); - sqlite3_bind_int(ins, 2, 6); - sqlite3_bind_blob(ins, 3, &sin6->sin6_addr, 16, SQLITE_STATIC); - sqlite3_bind_int(ins, 4, (int)ntohs(sin6->sin6_port)); - sqlite3_bind_int(ins, 5, addr_type_from_ip(6, sin6->sin6_addr.s6_addr)); - sqlite3_bind_int(ins, 6, (int)sock->sock_id); - sqlite3_step(ins); sqlite3_reset(ins); - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] INSERT v6 my_node=0x%016llx sock=%d port=%d", - CC_ID, (unsigned long long)g_cc.my_node_id, sock->sock_id, (int)ntohs(sin6->sin6_port)); - written++; + uint64_t now = (uint64_t)ntp_time_get_seconds(g_cc.inst); + uint64_t update_ts = (now > local_uts) ? now : (local_uts + 1); /* строго больше локального */ + + uint8_t umsg[8192]; + int ulen = member_sync_build_update_msg(join_sig, update_ts, juser, umsg, (int)sizeof(umsg)); + if (ulen < 0) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: build_update_msg failed ch=%s", CC_ID, ch); continue; } + uint8_t update_sig[64]; memset(update_sig, 0, 64); + EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, g_cc.inst->my_ed25519_privkey, 32); + if (pkey) { + EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); + if (mdctx) { + if (EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey) == 1) + EVP_DigestSign(mdctx, update_sig, &(size_t){64}, umsg, (size_t)ulen); + EVP_MD_CTX_free(mdctx); + } + EVP_PKEY_free(pkey); } - sock = sock->next; - } - sqlite3_finalize(ins); - - { sqlite3_stmt* tcp_ins = NULL; - sqlite3_prepare_v2(g_cc.db, - "INSERT OR REPLACE INTO node_addresses(node_id,family,protocol,address,port,addr_type,socket_id)" - " VALUES(?,?,2,?,?,?,?)", -1, &tcp_ins, NULL); - if (tcp_ins) { - struct ETCP_SOCKET* ts = g_cc.inst->etcp_sockets; - int tcp_count = 0; - while (ts) { if (!ts->is_tcp) { ts = ts->next; continue; } - if (ts->type == CFG_SERVER_TYPE_PRIVATE || ts->type == CFG_SERVER_TYPE_LOCAL) { ts = ts->next; continue; } - tcp_count++; - struct sockaddr_storage* addr = ts->interface_addr.ss_family ? &ts->interface_addr : &ts->local_addr; - if (!addr || !addr->ss_family) { ts = ts->next; continue; } - if (addr->ss_family == AF_INET) { - struct sockaddr_in* sin = (struct sockaddr_in*)addr; - if (sin->sin_addr.s_addr == 0) { ts = ts->next; continue; } - sqlite3_bind_int64(tcp_ins, 1, (sqlite3_int64)g_cc.my_node_id); - sqlite3_bind_int(tcp_ins, 2, 4); - sqlite3_bind_blob(tcp_ins, 3, &sin->sin_addr, 4, SQLITE_STATIC); - sqlite3_bind_int(tcp_ins, 4, (int)ntohs(sin->sin_port)); - sqlite3_bind_int(tcp_ins, 5, addr_type_from_ip(4, (const uint8_t*)&sin->sin_addr)); - sqlite3_bind_int(tcp_ins, 6, (int)ts->sock_id); - sqlite3_step(tcp_ins); sqlite3_reset(tcp_ins); - uint8_t* ip = (uint8_t*)&sin->sin_addr; - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] INSERT TCP my_node=0x%016llx sock=%d %d.%d.%d.%d:%d", - CC_ID, (unsigned long long)g_cc.my_node_id, ts->sock_id, ip[0], ip[1], ip[2], ip[3], (int)ntohs(sin->sin_port)); - tcp_written++; - } else if (addr->ss_family == AF_INET6) { - struct sockaddr_in6* sin6 = (struct sockaddr_in6*)addr; - if (memcmp(&sin6->sin6_addr, &in6addr_any, 16) == 0) { ts = ts->next; continue; } - sqlite3_bind_int64(tcp_ins, 1, (sqlite3_int64)g_cc.my_node_id); - sqlite3_bind_int(tcp_ins, 2, 6); - sqlite3_bind_blob(tcp_ins, 3, &sin6->sin6_addr, 16, SQLITE_STATIC); - sqlite3_bind_int(tcp_ins, 4, (int)ntohs(sin6->sin6_port)); - sqlite3_bind_int(tcp_ins, 5, addr_type_from_ip(6, sin6->sin6_addr.s6_addr)); - sqlite3_bind_int(tcp_ins, 6, (int)ts->sock_id); - sqlite3_step(tcp_ins); sqlite3_reset(tcp_ins); - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] INSERT TCP v6 my_node=0x%016llx sock=%d port=%d", - CC_ID, (unsigned long long)g_cc.my_node_id, ts->sock_id, (int)ntohs(sin6->sin6_port)); - tcp_written++; - } - ts = ts->next; } - sqlite3_finalize(tcp_ins); - if (tcp_written > 0) - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] TCP DONE: %d addresses written, my_node=0x%016llx", - CC_ID, tcp_written, (unsigned long long)g_cc.my_node_id); - } - } - if (sock_count == 0 && tcp_written == 0) - DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] no sockets found — NO addresses written!", CC_ID); - else if (written == 0 && tcp_written == 0) - DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] %d sockets found but 0 addresses written", CC_ID, sock_count); - else - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] DONE: %d UDP + %d TCP = %d total (skipped %d PRIVATE/LOCAL), my_node=0x%016llx", - CC_ID, written, tcp_written, written + tcp_written, skipped, (unsigned long long)g_cc.my_node_id); - - sqlite3_stmt* chk = NULL; - sqlite3_prepare_v2(g_cc.db, "SELECT COUNT(*) FROM node_addresses WHERE node_id=?", -1, &chk, NULL); - if (chk) { - sqlite3_bind_int64(chk, 1, (sqlite3_int64)g_cc.my_node_id); - int cnt = sqlite3_step(chk) == SQLITE_ROW ? sqlite3_column_int(chk, 0) : -1; - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] VERIFY: total rows for my_node=0x%016llx = %d", - CC_ID, (unsigned long long)g_cc.my_node_id, cnt); - sqlite3_finalize(chk); + int r2 = member_sync_put(g_cc.inst, ch, myid, g_cc.inst->my_keys.public_key, + g_cc.inst->my_ed25519_pubkey, join_sig, join_ts, + update_sig, update_ts, juser, NULL, NULL, 0); + if (r2 < 0) DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: member_sync_put(self) FAILED ch=%s rc=%d", CC_ID, ch, r2); + else if (r2 & MS_APPLY_CHANGED) member_sync_broadcast_one(g_cc.inst, ch, myid); + + DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: update my member ch=%s ts=%llu", CC_ID, ch, + (unsigned long long)update_ts); } + sqlite3_finalize(cs); +} + +void chat_core_sync_my_addresses(void) { + /* Адреса теперь синхронизируются BGP/topo_group (topo_node_update_my_addresses). + Здесь оставлено только обновление userinfo/подписи своего мембера. */ + chat_core_update_my_member(); } void chat_core_save_ui_state(const char* key, const char* value) { diff --git a/src/chat/chat_sync.c b/src/chat/chat_sync.c index 087f2356..d8fd59df 100644 --- a/src/chat/chat_sync.c +++ b/src/chat/chat_sync.c @@ -22,6 +22,9 @@ #include "../../lib/debug_config.h" #include "../../lib/mem.h" #include "../../lib/platform_compat.h" +#ifdef UTUN_HAVE_STANDBY +#include "standby.h" +#endif #include #include @@ -46,6 +49,7 @@ struct chat_sync { struct channel_cache* channels; int channel_count; void* refresh_timer; + void* refresh_wait; /* standby_wait handle (Android) */ void* info_req_timer; void* join_timer; void* sync_timer; @@ -600,6 +604,13 @@ static void cs_refresh_channels(struct chat_sync* cs) { static void refresh_timer_cb(void* arg) { (void)arg; if (!g_cs || !g_cs->initialized) return; +#ifdef UTUN_HAVE_STANDBY + if (standby_get_sleep_tb() > 0) { + g_cs->refresh_wait = standby_wait(g_cs, refresh_timer_cb); + return; + } + g_cs->refresh_wait = NULL; +#endif cs_refresh_channels(g_cs); g_cs->refresh_timer = uasync_set_timeout(g_cs->inst->ua, 30u * 10000u, g_cs, refresh_timer_cb, "cs_refresh"); @@ -649,6 +660,9 @@ void chat_sync_destroy(struct UTUN_INSTANCE* inst) { etcp_unbind(inst, ETCP_RT_ID_CHAT_SYNC); etcp_remove_conn_status_cbk(inst, cs_on_conn_status, NULL); if (cs->refresh_timer) { uasync_cancel_timeout(inst->ua, cs->refresh_timer); cs->refresh_timer = NULL; } +#ifdef UTUN_HAVE_STANDBY + if (cs->refresh_wait) { standby_wait_cancel(cs->refresh_wait); cs->refresh_wait = NULL; } +#endif cs_cancel_proto_timers(cs); for (int i = 0; i < cs->channel_count; i++) @@ -979,14 +993,12 @@ static void cs_handle_channel_info_req(struct chat_sync* cs, uint64_t peer, { sqlite3* vdb = cs->inst->topo_sqlite_db; if (topo_node_sqlite_member_get_join(vdb, ch_id, myid, my_join_sig, &my_join_ts) != 0) { - uint8_t join_msg[256]; size_t mlen = 0; - memcpy(join_msg + mlen, x25519, 32); mlen += 32; - memcpy(join_msg + mlen, ed_pub, 32); mlen += 32; - memcpy(join_msg + mlen, &myid, 8); mlen += 8; - memcpy(join_msg + mlen, cs->inst->my_keys.public_key, 32); mlen += 32; + uint8_t join_msg[128]; my_join_ts = (uint64_t)ntp_time_get_seconds(cs->inst); - memcpy(join_msg + mlen, &my_join_ts, 8); mlen += 8; - cs_ed25519_sign(cs->inst->my_ed25519_privkey, join_msg, mlen, my_join_sig); + int jmlen = member_sync_build_join_msg(x25519, ed_pub, myid, + cs->inst->my_keys.public_key, my_join_ts, + join_msg, (int)sizeof(join_msg)); + if (jmlen >= 0) cs_ed25519_sign(cs->inst->my_ed25519_privkey, join_msg, (size_t)jmlen, my_join_sig); DEBUG_DEBUG(DEBUG_CATEGORY_MEMBER_SYNC, "%s: CHANNEL_INFO_RESP created NEW join_sig: myid=%016llx ts=%llu", CS_ID, (unsigned long long)myid, (unsigned long long)my_join_ts); } @@ -996,12 +1008,11 @@ static void cs_handle_channel_info_req(struct chat_sync* cs, uint64_t peer, { char juser[256]; snprintf(juser, sizeof(juser), "{\"name\":\"%s\"}", cs->inst->name[0] ? cs->inst->name : ""); - uint8_t umsg[256]; size_t ulen = 0; - memcpy(umsg + ulen, my_join_sig, 64); ulen += 64; my_update_ts = (uint64_t)ntp_time_get_seconds(cs->inst); - memcpy(umsg + ulen, &my_update_ts, 8); ulen += 8; - size_t jl = strlen(juser); memcpy(umsg + ulen, juser, jl); umsg[ulen + jl] = '\0'; ulen += jl + 1; - cs_ed25519_sign(cs->inst->my_ed25519_privkey, umsg, ulen, my_update_sig); + uint8_t umsg[256]; + int ulen = member_sync_build_update_msg(my_join_sig, my_update_ts, juser, + umsg, (int)sizeof(umsg)); + if (ulen >= 0) cs_ed25519_sign(cs->inst->my_ed25519_privkey, umsg, (size_t)ulen, my_update_sig); } uint8_t buf[1024]; size_t boff = 0; @@ -1085,48 +1096,17 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer, } const uint8_t* inv_x25519 = inv_conn->crypto_ctx.peer_public_key; const uint8_t* inv_ed = inv_conn->peer_ed25519_pubkey; - uint8_t ivmsg[256]; size_t ilen = 0; - if (inviter_join_sig) memcpy(ivmsg + ilen, inviter_join_sig, 64); else memset(ivmsg + ilen, 0, 64); - ilen += 64; - memcpy(ivmsg + ilen, &inviter_update_ts, 8); ilen += 8; - { const char* nm = inv_userinfo[0] ? inv_userinfo : ""; - size_t nl2 = strlen(nm); memcpy(ivmsg + ilen, nm, nl2 + 1); ilen += nl2 + 1; } - if (cs_ed25519_verify(inv_ed, ivmsg, ilen, inviter_update_sig) != 0) { + static const uint8_t zero_sig[64] = {0}; + const uint8_t* jsig = inviter_join_sig ? inviter_join_sig : zero_sig; + uint8_t ivmsg[256]; + int ilen = member_sync_build_update_msg(jsig, inviter_update_ts, + inv_userinfo[0] ? inv_userinfo : "", + ivmsg, (int)sizeof(ivmsg)); + if (ilen < 0 || cs_ed25519_verify(inv_ed, ivmsg, (size_t)ilen, inviter_update_sig) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: CHANNEL_INFO_RESP invalid inviter_update_sig peer=%016llx ts=%llu", CS_ID, (unsigned long long)peer, (unsigned long long)inviter_update_ts); } sqlite3* vdb = cs->inst->topo_sqlite_db; - /* save inviter's address from ETCP connection */ - { DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] INFO_RESP save inviter peer=%016llx", CS_ID, (unsigned long long)peer); - struct ETCP_LINK* lk = inv_conn->links; - int lk_count = 0, lk_written = 0; - while (lk) { lk_count++; - if (lk->initialized && lk->remote_addr.ss_family == AF_INET) { - struct sockaddr_in* sin = (struct sockaddr_in*)&lk->remote_addr; - uint8_t ip[4]; memcpy(ip, &sin->sin_addr, 4); uint16_t port = ntohs(sin->sin_port); - int sock_id = lk->conn ? (int)lk->conn->sock_id : 0; - char sql[256]; snprintf(sql, sizeof(sql), - "INSERT OR REPLACE INTO node_addresses(node_id,family,protocol,address,port,addr_type,socket_id) VALUES(?,4,1,?,?,?,?)"); - sqlite3_stmt* as = NULL; - if (sqlite3_prepare_v2(vdb, sql, -1, &as, NULL) == SQLITE_OK) { - sqlite3_bind_int64(as, 1, (sqlite3_int64)peer); - sqlite3_bind_blob(as, 2, ip, 4, SQLITE_STATIC); - sqlite3_bind_int(as, 3, port); - sqlite3_bind_int(as, 4, addr_type_from_ip(4, ip)); - sqlite3_bind_int(as, 5, sock_id); - sqlite3_step(as); sqlite3_finalize(as); - lk_written++; - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] INFO_RESP INSERT peer=%016llx sock=%d %d.%d.%d.%d:%d", - CS_ID, (unsigned long long)peer, sock_id, ip[0], ip[1], ip[2], ip[3], port); - } - } else if (lk->initialized) { - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] INFO_RESP SKIP non-IPv4 link for peer=%016llx", CS_ID, (unsigned long long)peer); - } - lk = lk->next; - } - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] INFO_RESP DONE peer=%016llx: %d links, %d written", - CS_ID, (unsigned long long)peer, lk_count, lk_written); - } int rc = topo_node_sqlite_member_put(vdb, ch_id, peer, inviter_join_sig, inviter_join_ts, inviter_update_sig, inviter_update_ts, inv_x25519, inv_ed, inv_userinfo, inviter_join_sig, NULL, NULL, 0); if (rc != 0) DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: member_put(inviter) FAILED ch=%s peer=%016llx rc=%d", CS_ID, ch_id, (unsigned long long)peer, rc); char inv_name[128]; json_flat_get(inv_userinfo, "name", inv_name, sizeof(inv_name)); @@ -1139,14 +1119,11 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer, memcpy(my_x25519, cs->inst->my_keys.public_key, 32); uint8_t join_sig[64]; uint64_t join_ts; { - uint8_t msg[256]; size_t mlen = 0; - memcpy(msg + mlen, x25519, 32); mlen += 32; - memcpy(msg + mlen, ed_pub, 32); mlen += 32; - memcpy(msg + mlen, &myid, 8); mlen += 8; - memcpy(msg + mlen, my_x25519, 32); mlen += 32; + uint8_t msg[128]; join_ts = (uint64_t)ntp_time_get_seconds(cs->inst); - memcpy(msg + mlen, &join_ts, 8); mlen += 8; - cs_ed25519_sign(cs->inst->my_ed25519_privkey, msg, mlen, join_sig); + int mlen = member_sync_build_join_msg(x25519, ed_pub, myid, my_x25519, join_ts, + msg, (int)sizeof(msg)); + if (mlen >= 0) cs_ed25519_sign(cs->inst->my_ed25519_privkey, msg, (size_t)mlen, join_sig); } /* send JOIN_CHANNEL */ @@ -1161,47 +1138,6 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer, cs->inst->name[0] ? cs->inst->name : ""); uint8_t nml = (uint8_t)strlen(juser2); jbuf[joff++] = nml; memcpy(jbuf + joff, juser2, nml); joff += nml; - /* add local addresses */ - uint8_t addr_cnt = 0; size_t ac_pos = joff; - jbuf[joff++] = 0; - struct ETCP_SOCKET* sock = cs->inst->etcp_sockets; - while (sock && addr_cnt < 255) { - struct sockaddr_storage* sa = NULL; - if (sock->nat_addr.ss_family) { - sa = &sock->nat_addr; - int is_zero = 0; - if (sa->ss_family == AF_INET) { if (((struct sockaddr_in*)sa)->sin_addr.s_addr == 0) is_zero = 1; } - else { static const uint8_t z[16]; if (!memcmp(&((struct sockaddr_in6*)sa)->sin6_addr, z, 16)) is_zero = 1; } - if (is_zero) sa = NULL; - } - if (!sa && sock->interface_addr.ss_family) sa = &sock->interface_addr; - if (!sa) { sock = sock->next; continue; } - if (sa->ss_family == AF_INET) { - struct sockaddr_in* sin = (struct sockaddr_in*)sa; - if (joff + 9 > sizeof(jbuf)) break; - jbuf[joff++] = 4; - jbuf[joff++] = sock->sock_id; - jbuf[joff++] = 1; - memcpy(jbuf + joff, &sin->sin_addr, 4); joff += 4; - uint16_t p = ntohs(sin->sin_port); - jbuf[joff++] = (uint8_t)((p >> 8) & 0xFF); - jbuf[joff++] = (uint8_t)(p & 0xFF); - addr_cnt++; - } else if (sa->ss_family == AF_INET6) { - struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sa; - if (joff + 21 > sizeof(jbuf)) break; - jbuf[joff++] = 6; - jbuf[joff++] = sock->sock_id; - jbuf[joff++] = 1; - memcpy(jbuf + joff, &sin6->sin6_addr, 16); joff += 16; - uint16_t p = ntohs(sin6->sin6_port); - jbuf[joff++] = (uint8_t)((p >> 8) & 0xFF); - jbuf[joff++] = (uint8_t)(p & 0xFF); - addr_cnt++; - } - sock = sock->next; - } - jbuf[ac_pos] = addr_cnt; cs_send(cs, ch_id, peer, jbuf, joff); cs->join_timer = uasync_set_timeout(cs->inst->ua, CS_JOIN_TIMEOUT_MS * 10, cs, cs_join_timeout_cb, "cs_join"); @@ -1211,7 +1147,7 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer, static void cs_handle_channel_join(struct chat_sync* cs, uint64_t peer, const char* ch_id, const uint8_t* pl, size_t len) { - if (len < 8 + 32 + 32 + 64 + 8 + 1 + 1) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: CHANNEL_JOIN too short len=%zu peer=%016llx", CS_ID, len, (unsigned long long)peer); return; } + if (len < 8 + 32 + 32 + 64 + 8 + 1) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: CHANNEL_JOIN too short len=%zu peer=%016llx", CS_ID, len, (unsigned long long)peer); return; } const uint8_t* p = pl; uint64_t node_id; memcpy(&node_id, p, 8); p += 8; DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN ch=%s node=%016llx from=%016llx", CS_ID, ch_id, (unsigned long long)node_id, (unsigned long long)peer); @@ -1222,7 +1158,6 @@ static void cs_handle_channel_join(struct chat_sync* cs, uint64_t peer, uint8_t userinfo_len = *p++; char joiner_userinfo[128] = ""; if (p + userinfo_len <= pl + len) { memcpy(joiner_userinfo, p, userinfo_len); joiner_userinfo[userinfo_len] = '\0'; p += userinfo_len; } - uint8_t addr_cnt = *p++; /* verify join_sig */ uint8_t ch_x25519[32], ch_ed25519[32]; @@ -1231,13 +1166,10 @@ static void cs_handle_channel_join(struct chat_sync* cs, uint64_t peer, DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN channel not in DB ch=%s", CS_ID, ch_id); return; } - uint8_t vmsg[256]; size_t vlen = 0; - memcpy(vmsg + vlen, ch_x25519, 32); vlen += 32; - memcpy(vmsg + vlen, ch_ed25519, 32); vlen += 32; - memcpy(vmsg + vlen, &node_id, 8); vlen += 8; - memcpy(vmsg + vlen, x25519, 32); vlen += 32; - memcpy(vmsg + vlen, &join_ts, 8); vlen += 8; - if (cs_ed25519_verify(ed_pub, vmsg, vlen, join_sig) != 0) { + uint8_t vmsg[128]; + int vlen = member_sync_build_join_msg(ch_x25519, ch_ed25519, node_id, x25519, join_ts, + vmsg, (int)sizeof(vmsg)); + if (vlen < 0 || cs_ed25519_verify(ed_pub, vmsg, (size_t)vlen, join_sig) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN invalid sig node=0x%016llx ch=%s — signed(node=%016llx x25519=%016llx ed=%016llx ts=%llu)", CS_ID, (unsigned long long)node_id, ch_id, (unsigned long long)node_id, *(const uint64_t*)x25519, *(const uint64_t*)ed_pub, (unsigned long long)join_ts); @@ -1250,40 +1182,6 @@ static void cs_handle_channel_join(struct chat_sync* cs, uint64_t peer, { char j_name[128]; json_flat_get(joiner_userinfo, "name", j_name, sizeof(j_name)); topo_node_sqlite_node_update_verified(db, node_id, j_name, x25519, ed_pub, join_ts, ntp_time_get_seconds(cs->inst)); } - - /* save/update node addresses */ - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] JOIN save addrs node=0x%016llx addr_cnt=%d", CS_ID, (unsigned long long)node_id, addr_cnt); - for (uint8_t i = 0; i < addr_cnt && p + 2 <= pl + len; i++) { - uint8_t fm = *p++; - uint8_t sid = *p++; - uint8_t proto = *p++; - int ip_len = (fm == 4) ? 4 : 16; - if (p + ip_len + 2 > pl + len) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN addr truncated node=0x%016llx fm=%u i=%u", CS_ID, (unsigned long long)node_id, fm, i); break; } - const uint8_t* ip = p; p += ip_len; - uint16_t port = ((uint16_t)p[0] << 8) | p[1]; p += 2; - - char sql[256]; snprintf(sql, sizeof(sql), - "INSERT OR REPLACE INTO node_addresses(node_id,family,protocol,address,port,addr_type,socket_id)" - " VALUES(?,?,?,?,?,?,?)"); - sqlite3_stmt* stmt = NULL; - if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) { - sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id); - sqlite3_bind_int(stmt, 2, fm); - sqlite3_bind_int(stmt, 3, (int)proto); - sqlite3_bind_blob(stmt, 4, ip, ip_len, SQLITE_STATIC); - sqlite3_bind_int(stmt, 5, (int)port); - sqlite3_bind_int(stmt, 6, addr_type_from_ip(fm, ip)); - sqlite3_bind_int(stmt, 7, (int)sid); - sqlite3_step(stmt); sqlite3_finalize(stmt); - } - if (fm == 4) - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] JOIN INSERT node=0x%016llx sock=%d %d.%d.%d.%d:%d", - CS_ID, (unsigned long long)node_id, sid, ip[0], ip[1], ip[2], ip[3], port); - else - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] JOIN INSERT v6 node=0x%016llx sock=%d port=%d", - CS_ID, (unsigned long long)node_id, sid, port); - } - /* build WELCOME with all current peers */ uint8_t wbuf[8192]; { @@ -1309,12 +1207,6 @@ static void cs_handle_channel_join(struct chat_sync* cs, uint64_t peer, memcpy(ubuf + uoff, &join_ts, 8); uoff += 8; ubuf[uoff++] = userinfo_len; memcpy(ubuf + uoff, joiner_userinfo, userinfo_len); uoff += userinfo_len; - ubuf[uoff++] = addr_cnt; - size_t addr_data_sz = (size_t)(p - (pl + 8 + 32 + 32 + 64 + 8 + 1 + userinfo_len + 1)); - if (uoff + addr_data_sz <= sizeof(ubuf)) { - memcpy(ubuf + uoff, pl + 8 + 32 + 32 + 64 + 8 + 1 + userinfo_len + 1, addr_data_sz); - uoff += addr_data_sz; - } cs_propagate(cs, ch_id, peer, ubuf, uoff); } @@ -1352,8 +1244,8 @@ static void cs_handle_channel_join(struct chat_sync* cs, uint64_t peer, if (cs->invite_send_timer) { uasync_cancel_timeout(cs->inst->ua, cs->invite_send_timer); cs->invite_send_timer = NULL; } } - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN accepted node=0x%016llx ch=%s addrs=%d", - CS_ID, (unsigned long long)node_id, ch_id, addr_cnt); + DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: JOIN accepted node=0x%016llx ch=%s", + CS_ID, (unsigned long long)node_id, ch_id); } /* ─── WELCOME (0x0C): inviter → joiner with full peer list ─── */ @@ -1369,7 +1261,7 @@ static void cs_handle_welcome(struct chat_sync* cs, uint64_t peer, uint16_t pc; memcpy(&pc, p, 2); p += 2; DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: WELCOME ch=%s from=%016llx peers=%u", CS_ID, ch_id, (unsigned long long)peer, pc); for (uint16_t i = 0; i < pc; i++) { - if ((size_t)(p - pl) + 8 + 32 + 32 + 1 + 64 + 8 + 1 > len) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: WELCOME peer[%u] truncated at %td len=%zu", CS_ID, i, p - pl, len); break; } /* min: id+x25519+ed+flags+update_sig(64+8)+nl+ac */ + if ((size_t)(p - pl) + 8 + 32 + 32 + 1 + 64 + 8 + 1 + 1 + 64 > len) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: WELCOME peer[%u] truncated at %td len=%zu", CS_ID, i, p - pl, len); break; } /* min: id+x25519+ed+flags+update_sig(64+8)+nl+atl+adm_tags_sig(64) */ uint64_t node_id; memcpy(&node_id, p, 8); p += 8; const uint8_t* x25519 = p; p += 32; const uint8_t* ed_pub = p; p += 32; @@ -1386,7 +1278,6 @@ static void cs_handle_welcome(struct chat_sync* cs, uint64_t peer, uint8_t nl = *p++; char peer_userinfo[256] = ""; if (nl && p + nl <= pl + len) { memcpy(peer_userinfo, p, nl); peer_userinfo[nl] = '\0'; p += nl; } - uint8_t ac = *p++; uint8_t atl = *p++; if (atl > 0 && p + atl <= pl + len) p += atl; p += 64; /* adm_tags_sig */ @@ -1400,25 +1291,12 @@ static void cs_handle_welcome(struct chat_sync* cs, uint64_t peer, DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: WELCOME channel not in DB ch=%s", CS_ID, ch_id); continue; } - uint8_t vmsg[256]; size_t vlen = 0; - memcpy(vmsg + vlen, ch_x2, 32); vlen += 32; - memcpy(vmsg + vlen, ch_ed2, 32); vlen += 32; - memcpy(vmsg + vlen, &node_id, 8); vlen += 8; - memcpy(vmsg + vlen, x25519, 32); vlen += 32; - memcpy(vmsg + vlen, &join_ts, 8); vlen += 8; - EVP_PKEY* pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, ed_pub, 32); - if (pkey) { - EVP_MD_CTX* ver = EVP_MD_CTX_new(); - if (ver) { - if (EVP_DigestVerifyInit(ver, NULL, NULL, NULL, pkey) != 1 - || EVP_DigestVerify(ver, join_sig, 64, vmsg, vlen) != 1) { - DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: WELCOME invalid join_sig node=0x%016llx — skipping", CS_ID, (unsigned long long)node_id); - EVP_MD_CTX_free(ver); EVP_PKEY_free(pkey); - continue; - } - EVP_MD_CTX_free(ver); - } - EVP_PKEY_free(pkey); + uint8_t vmsg[128]; + int vlen = member_sync_build_join_msg(ch_x2, ch_ed2, node_id, x25519, join_ts, + vmsg, (int)sizeof(vmsg)); + if (vlen < 0 || cs_ed25519_verify(ed_pub, vmsg, (size_t)vlen, join_sig) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: WELCOME invalid join_sig node=0x%016llx — skipping", CS_ID, (unsigned long long)node_id); + continue; } } @@ -1427,40 +1305,6 @@ static void cs_handle_welcome(struct chat_sync* cs, uint64_t peer, { char pn[256]; json_flat_get(peer_userinfo, "name", pn, sizeof(pn)); topo_node_sqlite_node_update_verified(db, node_id, pn, x25519, ed_pub, join_ts, ntp_time_get_seconds(cs->inst)); } - - - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] WELCOME save addrs node=0x%016llx ac=%d", CS_ID, (unsigned long long)node_id, ac); - for (uint8_t j = 0; j < ac; j++) { - if (p + 2 > pl + len) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: WELCOME addr[%u] header truncated node=0x%016llx", CS_ID, j, (unsigned long long)node_id); break; } - uint8_t fm = *p++; - uint8_t sid = *p++; - uint8_t proto = *p++; - int ip_len = (fm == 4) ? 4 : 16; - if (p + ip_len + 2 > pl + len) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: WELCOME addr[%u] ip truncated node=0x%016llx fm=%u", CS_ID, j, (unsigned long long)node_id, fm); break; } - const uint8_t* ip = p; p += ip_len; - uint16_t port = ((uint16_t)p[0] << 8) | p[1]; p += 2; - - char sql[256]; snprintf(sql, sizeof(sql), - "INSERT OR REPLACE INTO node_addresses(node_id,family,protocol,address,port,addr_type,socket_id)" - " VALUES(?,?,?,?,?,?,?)"); - sqlite3_stmt* stmt2 = NULL; - if (sqlite3_prepare_v2(db, sql, -1, &stmt2, NULL) == SQLITE_OK) { - sqlite3_bind_int64(stmt2, 1, (sqlite3_int64)node_id); - sqlite3_bind_int(stmt2, 2, fm); - sqlite3_bind_int(stmt2, 3, (int)proto); - sqlite3_bind_blob(stmt2, 4, ip, ip_len, SQLITE_STATIC); - sqlite3_bind_int(stmt2, 5, (int)port); - sqlite3_bind_int(stmt2, 6, addr_type_from_ip(fm, ip)); - sqlite3_bind_int(stmt2, 7, (int)sid); - sqlite3_step(stmt2); sqlite3_finalize(stmt2); - } - if (fm == 4) - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] WELCOME INSERT node=0x%016llx sock=%d %d.%d.%d.%d:%d", - CS_ID, (unsigned long long)node_id, sid, ip[0], ip[1], ip[2], ip[3], port); - else - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] WELCOME INSERT v6 node=0x%016llx sock=%d port=%d", - CS_ID, (unsigned long long)node_id, sid, port); - } member_sync_cancel(g_cs->inst, node_id, ch_id); } @@ -1520,7 +1364,7 @@ static void cs_handle_welcome(struct chat_sync* cs, uint64_t peer, static void cs_handle_peer_upsert(struct chat_sync* cs, uint64_t peer, const char* ch_id, const uint8_t* pl, size_t len) { - if (len < 8 + 32 + 32 + 64 + 8 + 1 + 1 + 1) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: PEER_UPSERT too short len=%zu", CS_ID, len); return; } + if (len < 8 + 32 + 32 + 64 + 8 + 1) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: PEER_UPSERT too short len=%zu", CS_ID, len); return; } const uint8_t* p = pl; uint64_t node_id; memcpy(&node_id, p, 8); p += 8; const uint8_t* x25519 = p; p += 32; @@ -1530,7 +1374,6 @@ static void cs_handle_peer_upsert(struct chat_sync* cs, uint64_t peer, uint8_t userinfo_len = *p++; char peer_userinfo[128] = ""; if (p + userinfo_len <= pl + len) { memcpy(peer_userinfo, p, userinfo_len); peer_userinfo[userinfo_len] = '\0'; p += userinfo_len; } - uint8_t ac = *p++; /* verify join_sig */ uint8_t ch_x3[32], ch_ed3[32]; @@ -1539,13 +1382,10 @@ static void cs_handle_peer_upsert(struct chat_sync* cs, uint64_t peer, DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: PEER_UPSERT channel not in DB ch=%s", CS_ID, ch_id); return; } - uint8_t vmsg[256]; size_t vlen = 0; - memcpy(vmsg + vlen, ch_x3, 32); vlen += 32; - memcpy(vmsg + vlen, ch_ed3, 32); vlen += 32; - memcpy(vmsg + vlen, &node_id, 8); vlen += 8; - memcpy(vmsg + vlen, x25519, 32); vlen += 32; - memcpy(vmsg + vlen, &join_ts, 8); vlen += 8; - if (cs_ed25519_verify(ed_pub, vmsg, vlen, join_sig) != 0) { + uint8_t vmsg[128]; + int vlen = member_sync_build_join_msg(ch_x3, ch_ed3, node_id, x25519, join_ts, + vmsg, (int)sizeof(vmsg)); + if (vlen < 0 || cs_ed25519_verify(ed_pub, vmsg, (size_t)vlen, join_sig) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: PEER_UPSERT invalid sig node=0x%016llx", CS_ID, (unsigned long long)node_id); return; @@ -1558,40 +1398,6 @@ static void cs_handle_peer_upsert(struct chat_sync* cs, uint64_t peer, { char pn2[128]; json_flat_get(peer_userinfo, "name", pn2, sizeof(pn2)); topo_node_sqlite_node_update_verified(db, node_id, pn2, x25519, ed_pub, join_ts, ntp_time_get_seconds(cs->inst)); } - - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] PEER_UPSERT save addrs node=0x%016llx ac=%d", CS_ID, (unsigned long long)node_id, ac); - for (uint8_t i = 0; i < ac; i++) { - if (p + 2 > pl + len) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: PEER_UPSERT addr[%u] header truncated node=0x%016llx", CS_ID, i, (unsigned long long)node_id); break; } - uint8_t fm = *p++; - uint8_t sid = *p++; - uint8_t proto = *p++; - int ip_len = (fm == 4) ? 4 : 16; - if (p + ip_len + 2 > pl + len) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: PEER_UPSERT addr[%u] ip truncated node=0x%016llx fm=%u", CS_ID, i, (unsigned long long)node_id, fm); break; } - const uint8_t* ip = p; p += ip_len; - uint16_t port = ((uint16_t)p[0] << 8) | p[1]; p += 2; - - char sql[256]; snprintf(sql, sizeof(sql), - "INSERT OR REPLACE INTO node_addresses(node_id,family,protocol,address,port,addr_type,socket_id)" - " VALUES(?,?,?,?,?,?,?)"); - sqlite3_stmt* stmt = NULL; - if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) { - sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id); - sqlite3_bind_int(stmt, 2, fm); - sqlite3_bind_int(stmt, 3, (int)proto); - sqlite3_bind_blob(stmt, 4, ip, ip_len, SQLITE_STATIC); - sqlite3_bind_int(stmt, 5, (int)port); - sqlite3_bind_int(stmt, 6, addr_type_from_ip(fm, ip)); - sqlite3_bind_int(stmt, 7, (int)sid); - sqlite3_step(stmt); sqlite3_finalize(stmt); - } - if (fm == 4) - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] PEER_UPSERT INSERT node=0x%016llx sock=%d %d.%d.%d.%d:%d", - CS_ID, (unsigned long long)node_id, sid, ip[0], ip[1], ip[2], ip[3], port); - else - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] PEER_UPSERT INSERT v6 node=0x%016llx sock=%d port=%d", - CS_ID, (unsigned long long)node_id, sid, port); - } - cs_refresh_channels(cs); { uint8_t mevt[1 + 64 + 1 + CHAT_MEMBER_DISPLAY_SIZE]; uint8_t ml = (uint8_t)strlen(ch_id); @@ -1735,11 +1541,9 @@ static int cs_send_channel_invite(struct chat_sync* cs, struct UTUN_INSTANCE* in uint8_t upd_sig[64] = {0}; uint64_t upd_ts = (uint64_t)ntp_time_get_seconds(inst); { char juser[256]; snprintf(juser, sizeof(juser), "{\"name\":\"%s\"}", inst->name[0] ? inst->name : ""); - uint8_t umsg[256]; size_t ulen = 0; - memcpy(umsg + ulen, join_sig, 64); ulen += 64; - memcpy(umsg + ulen, &upd_ts, 8); ulen += 8; - size_t jl = strlen(juser); memcpy(umsg + ulen, juser, jl); umsg[ulen + jl] = '\0'; ulen += jl + 1; - cs_ed25519_sign(inst->my_ed25519_privkey, umsg, ulen, upd_sig); + uint8_t umsg[256]; + int ulen = member_sync_build_update_msg(join_sig, upd_ts, juser, umsg, (int)sizeof(umsg)); + if (ulen >= 0) cs_ed25519_sign(inst->my_ed25519_privkey, umsg, (size_t)ulen, upd_sig); } uint8_t buf[1024]; size_t boff = 0; buf[boff++] = CS_MSG_CHANNEL_INVITE; diff --git a/src/chat/db_sync.c b/src/chat/db_sync.c index 2c4c2f1a..f942335f 100644 --- a/src/chat/db_sync.c +++ b/src/chat/db_sync.c @@ -11,6 +11,9 @@ #include "../../lib/debug_config.h" #include "../../lib/mem.h" #include "../../lib/u_async.h" +#ifdef UTUN_HAVE_STANDBY +#include "standby.h" +#endif #include #include "../../lib/sqlite3.h" #include "../../lib/platform_compat.h" @@ -81,6 +84,7 @@ struct DB_SYNC { struct DB_SYNC_INSTANCE* instances; int instance_count, instance_capacity; void* peer_check_timer; + void* peer_check_wait; /* standby_wait handle (Android) */ uint8_t enabled; struct db_sync_done_cbk_entry* done_cbks; }; @@ -484,6 +488,14 @@ static void db_sync_flush_recalc(struct DB_SYNC_INSTANCE* si) static int db_get_ed25519_pubkey(struct DB_SYNC* db, uint64_t node_id, uint8_t out[32]) { if (node_id == db->inst->node_id) { memcpy(out, db->inst->my_ed25519_pubkey, 32); return 0; } + /* из in-memory registry (BGP/topo, self-sig верифицирован) — первичный источник */ + if (db->inst->topo_groups) { + struct TOPO_NODE* ni = topo_node_registry_find(db->inst->topo_groups, node_id); + if (ni) { + uint64_t chk; memcpy(&chk, ni->ed25519_public_key, 8); + if (chk != 0) { memcpy(out, ni->ed25519_public_key, 32); return 0; } + } + } if (db->inst->topo_sqlite_db) { if (topo_node_sqlite_get_ed25519_pubkey(db->inst->topo_sqlite_db, node_id, out) == 0) return 0; } @@ -1488,6 +1500,13 @@ static void db_sync_peer_check_cb(void* arg) struct DB_SYNC* db = (struct DB_SYNC*)arg; if (!db || !db->enabled) return; db->peer_check_timer = NULL; +#ifdef UTUN_HAVE_STANDBY + if (standby_get_sleep_tb() > 0) { + db->peer_check_wait = standby_wait(db, db_sync_peer_check_cb); + return; + } + db->peer_check_wait = NULL; +#endif DEBUG_TRACE(DEBUG_CATEGORY_CHAT_SYNC, "instances=%d", db->instance_count); struct TOPO_GROUP* g = topo_groups_get_default(db->inst->topo_groups); @@ -1666,6 +1685,9 @@ void db_sync_destroy(struct UTUN_INSTANCE* inst) etcp_unbind(inst, ETCP_RT_ID_DB_SYNC); if (db->peer_check_timer) { uasync_cancel_timeout(inst->ua, db->peer_check_timer); db->peer_check_timer = NULL; } +#ifdef UTUN_HAVE_STANDBY + if (db->peer_check_wait) { standby_wait_cancel(db->peer_check_wait); db->peer_check_wait = NULL; } +#endif for (int i = 0; i < db->instance_count; i++) { struct DB_SYNC_INSTANCE* si = &db->instances[i]; diff --git a/src/chat/member_sync.c b/src/chat/member_sync.c index db84076c..2107793d 100644 --- a/src/chat/member_sync.c +++ b/src/chat/member_sync.c @@ -15,8 +15,6 @@ #define MS_ID "member_sync" -struct addr_item { uint8_t family; uint8_t addr[16]; uint16_t port; }; - /* ── DB access ── */ static sqlite3* _db(struct UTUN_INSTANCE* inst) { @@ -37,20 +35,10 @@ static void _peers_table(const char* ch_id, char* buf, size_t sz) { /* ── Member hash (identical to old _compute_member_hash) ── */ -static int _addr_cmp(const void* a, const void* b) { - const struct addr_item* ia = (const struct addr_item*)a; - const struct addr_item* ib = (const struct addr_item*)b; - if (ia->family != ib->family) return (int)ia->family - (int)ib->family; - int r = memcmp(ia->addr, ib->addr, (size_t)(ia->family == 4 ? 4 : 16)); - if (r) return r; - return (int)ia->port - (int)ib->port; -} - static void _compute_member_hash(uint64_t node_id, const uint8_t* x25519, const uint8_t* ed25519, const uint8_t* join_sig, uint64_t join_ts, const uint8_t* update_sig, uint64_t update_ts, - const uint8_t* addrs_data, int addr_count, const char* adm_tags, const uint8_t* adm_tags_sig, uint8_t hash_out[MT_HASH_SIZE]) { EVP_MD_CTX* ctx = EVP_MD_CTX_new(); @@ -62,27 +50,6 @@ static void _compute_member_hash(uint64_t node_id, const uint8_t* x25519, EVP_DigestUpdate(ctx, &join_ts, 8); if (update_sig) EVP_DigestUpdate(ctx, update_sig, 64); else { static const uint8_t z[64]; EVP_DigestUpdate(ctx, z, 64); } EVP_DigestUpdate(ctx, &update_ts, 8); - uint8_t ac = (uint8_t)addr_count; - EVP_DigestUpdate(ctx, &ac, 1); - - if (addr_count > 0 && addrs_data) { - struct addr_item items[256]; int n = 0; - const uint8_t* p = addrs_data; - for (int i = 0; i < addr_count && n < 256; i++) { - uint8_t fam = *p++; items[n].family = fam; - int ip_len = fam == 4 ? 4 : 16; - memcpy(items[n].addr, p, (size_t)ip_len); p += ip_len; - items[n].port = ((uint16_t)p[0] << 8) | p[1]; p += 2; - n++; - } - qsort(items, (size_t)n, sizeof(struct addr_item), _addr_cmp); - for (int i = 0; i < n; i++) { - EVP_DigestUpdate(ctx, &items[i].family, 1); - EVP_DigestUpdate(ctx, items[i].addr, (size_t)(items[i].family == 4 ? 4 : 16)); - uint8_t port_be[2] = { (uint8_t)(items[i].port >> 8), (uint8_t)(items[i].port & 0xFF) }; - EVP_DigestUpdate(ctx, port_be, 2); - } - } uint8_t atl = adm_tags ? (uint8_t)strnlen(adm_tags, 255) : 0; EVP_DigestUpdate(ctx, &atl, 1); @@ -93,6 +60,49 @@ static void _compute_member_hash(uint64_t node_id, const uint8_t* x25519, EVP_MD_CTX_free(ctx); } +int member_sync_build_update_msg(const uint8_t* join_sig, uint64_t update_ts, + const char* userinfo, + uint8_t* out, int out_sz) { + if (!join_sig || !out || out_sz < 0) return -1; + int off = 0; + + if (off + 64 > out_sz) return -1; + memcpy(out + off, join_sig, 64); off += 64; + + if (off + 8 > out_sz) return -1; + memcpy(out + off, &update_ts, 8); off += 8; + + size_t ul = userinfo ? strlen(userinfo) : 0; + if (off + (int)ul + 1 > out_sz) return -1; + memcpy(out + off, userinfo ? userinfo : "", ul + 1); off += (int)ul + 1; + + return off; +} + +int member_sync_build_join_msg(const uint8_t* ch_x25519, const uint8_t* ch_ed25519, + uint64_t node_id, const uint8_t* node_x25519, uint64_t join_ts, + uint8_t* out, int out_sz) { + if (!ch_x25519 || !ch_ed25519 || !node_x25519 || !out || out_sz < 0) return -1; + int off = 0; + + if (off + 32 > out_sz) return -1; + memcpy(out + off, ch_x25519, 32); off += 32; + + if (off + 32 > out_sz) return -1; + memcpy(out + off, ch_ed25519, 32); off += 32; + + if (off + 8 > out_sz) return -1; + memcpy(out + off, &node_id, 8); off += 8; + + if (off + 32 > out_sz) return -1; + memcpy(out + off, node_x25519, 32); off += 32; + + if (off + 8 > out_sz) return -1; + memcpy(out + off, &join_ts, 8); off += 8; + + return off; +} + /* ── merkle_sync_data_ops implementation ── */ static int _member_update_bucket_hash(void* ctx, const char* ns, uint8_t level, @@ -129,34 +139,8 @@ static int _member_update_bucket_hash(void* ctx, const char* ns, uint8_t level, const uint8_t* atsig = (const uint8_t*)sqlite3_column_blob(stmt, 8); if (!x25 || !ed) continue; - uint8_t addrs[2048]; int addr_off = 0; int addr_count = 0; - sqlite3_stmt* as = NULL; - sqlite3_prepare_v2(db, - "SELECT family, socket_id, protocol, address, port FROM node_addresses WHERE node_id=?" - " ORDER BY family, address, port", -1, &as, NULL); - if (as) { - sqlite3_bind_int64(as, 1, (sqlite3_int64)nid); - while (sqlite3_step(as) == SQLITE_ROW && addr_off < (int)sizeof(addrs) - 9) { - int fam = sqlite3_column_int(as, 0); - int sid = sqlite3_column_int(as, 1); - int proto = sqlite3_column_int(as, 2); - int ip_sz = fam == 4 ? 4 : 16; - if (addr_off + 3 + ip_sz + 2 > (int)sizeof(addrs)) break; - addrs[addr_off++] = (uint8_t)fam; - addrs[addr_off++] = (uint8_t)sid; - addrs[addr_off++] = (uint8_t)proto; - memcpy(addrs + addr_off, sqlite3_column_blob(as, 3), (size_t)ip_sz); - addr_off += ip_sz; - uint16_t p = (uint16_t)sqlite3_column_int(as, 4); - addrs[addr_off++] = (uint8_t)(p >> 8); - addrs[addr_off++] = (uint8_t)(p & 0xFF); - addr_count++; - } - sqlite3_finalize(as); - } - uint8_t mh[MT_HASH_SIZE]; - _compute_member_hash(nid, x25, ed, sig, jts, usig, uts, addrs, addr_count, atags, atsig, mh); + _compute_member_hash(nid, x25, ed, sig, jts, usig, uts, atags, atsig, mh); EVP_DigestUpdate(sha_ctx, mh, MT_HASH_SIZE); count++; } @@ -205,34 +189,8 @@ static int _member_get_items(void* ctx, const char* ns, uint8_t level, uint8_t nl = nm ? (uint8_t)strnlen(nm, 255) : 0; uint8_t atl = atags ? (uint8_t)strnlen(atags, 255) : 0; - sqlite3_stmt* as = NULL; - sqlite3_prepare_v2(db, - "SELECT family, socket_id, protocol, address, port FROM node_addresses WHERE node_id=?" - " ORDER BY family, address, port", -1, &as, NULL); - uint8_t addrs[2048]; int addr_off = 0; int addr_count = 0; - if (as) { - sqlite3_bind_int64(as, 1, (sqlite3_int64)nid); - while (sqlite3_step(as) == SQLITE_ROW && addr_off < (int)sizeof(addrs) - 9) { - int fam = sqlite3_column_int(as, 0); - int sid = sqlite3_column_int(as, 1); - int proto = sqlite3_column_int(as, 2); - int ip_sz = fam == 4 ? 4 : 16; - if (addr_off + 3 + ip_sz + 2 > (int)sizeof(addrs)) break; - addrs[addr_off++] = (uint8_t)fam; - addrs[addr_off++] = (uint8_t)sid; - addrs[addr_off++] = (uint8_t)proto; - memcpy(addrs + addr_off, sqlite3_column_blob(as, 3), (size_t)ip_sz); - addr_off += ip_sz; - uint16_t p = (uint16_t)sqlite3_column_int(as, 4); - addrs[addr_off++] = (uint8_t)(p >> 8); - addrs[addr_off++] = (uint8_t)(p & 0xFF); - addr_count++; - } - sqlite3_finalize(as); - } - uint8_t flags = (sig && jts) ? PEERS_FLAG_HAS_JOIN : 0; - size_t need = 8 + 32 + 32 + 1 + (flags ? 72ULL : 0ULL) + 64 + 8 + 1 + (size_t)nl + 1 + (size_t)atl + 64 + 1 + (size_t)addr_off; + size_t need = 8 + 32 + 32 + 1 + (flags ? 72ULL : 0ULL) + 64 + 8 + 1 + (size_t)nl + 1 + (size_t)atl + 64; if (off + need > *len) { sqlite3_finalize(stmt); return -2; } memcpy(buf + off, &nid, 8); off += 8; memcpy(buf + off, x25, 32); off += 32; @@ -255,8 +213,6 @@ static int _member_get_items(void* ctx, const char* ns, uint8_t level, if (atl) { memcpy(buf + off, atags, atl); off += atl; } if (atsig && sqlite3_column_bytes(stmt, 9) >= 64) { memcpy(buf + off, atsig, 64); off += 64; } else { memset(buf + off, 0, 64); off += 64; } - buf[off++] = (uint8_t)addr_count; - memcpy(buf + off, addrs, (size_t)addr_off); off += (size_t)addr_off; (*cnt)++; } sqlite3_finalize(stmt); @@ -299,69 +255,37 @@ static int _sig_is_zero64(const uint8_t* sig) { return 1; } -/* Записывает адреса узла в node_addresses (DELETE + INSERT). Только локальная запись. */ -static void _member_write_addrs(sqlite3* db, uint64_t member_id, - const uint8_t* addrs_data, int addr_count) { - if (!addrs_data || addr_count <= 0) return; - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] write_addrs node=0x%016llx addr_count=%d", - MS_ID, (unsigned long long)member_id, addr_count); - sqlite3_exec(db, "BEGIN", NULL, NULL, NULL); - sqlite3_stmt* ds = NULL; - sqlite3_prepare_v2(db, "DELETE FROM node_addresses WHERE node_id=?", -1, &ds, NULL); - if (ds) { sqlite3_bind_int64(ds, 1, (sqlite3_int64)member_id); sqlite3_step(ds); - int deleted = sqlite3_changes(db); - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] write_addrs DELETE node=0x%016llx: %d rows deleted", - MS_ID, (unsigned long long)member_id, deleted); - sqlite3_finalize(ds); } - - sqlite3_stmt* as = NULL; - sqlite3_prepare_v2(db, - "INSERT INTO node_addresses(node_id,family,protocol,address,port,addr_type,socket_id)" - " VALUES(?,?,?,?,?,?,?)", -1, &as, NULL); - if (as) { - const uint8_t* p = addrs_data; - int written = 0; - for (int i = 0; i < addr_count; i++) { - uint8_t fam = *p++; - uint8_t sid = *p++; - uint8_t proto = *p++; - int ip_sz = fam == 4 ? 4 : 16; - sqlite3_bind_int64(as, 1, (sqlite3_int64)member_id); - sqlite3_bind_int(as, 2, fam); - sqlite3_bind_int(as, 3, (int)proto); - sqlite3_bind_blob(as, 4, p, ip_sz, SQLITE_STATIC); - p += ip_sz; - uint16_t port = ((uint16_t)p[0] << 8) | p[1]; p += 2; - sqlite3_bind_int(as, 5, port); - sqlite3_bind_int(as, 6, addr_type_from_ip(fam, p - ip_sz - 2)); - sqlite3_bind_int(as, 7, (int)sid); - sqlite3_step(as); sqlite3_reset(as); - if (fam == 4) { - const uint8_t* ip = p - 6; - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] write_addrs INSERT node=0x%016llx proto=%d sock=%d %d.%d.%d.%d:%d", - MS_ID, (unsigned long long)member_id, proto, sid, ip[0], ip[1], ip[2], ip[3], port); - } else { - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] write_addrs INSERT v6 node=0x%016llx sock=%d port=%d", - MS_ID, (unsigned long long)member_id, sid, port); - } - written++; - } - sqlite3_finalize(as); - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] write_addrs DONE node=0x%016llx: %d addrs written", - MS_ID, (unsigned long long)member_id, written); +/* Верифицирует join_sig: sign(ch_x25519||ch_ed25519||node_id||x25519||join_ts) ключом ed25519. */ +static int _verify_join_sig(sqlite3* db, const char* ch_id, const struct ms_member_rec* m) { + if (!m->join_sig || _sig_is_zero64(m->join_sig)) return 0; + uint8_t ch_x[32], ch_ed[32]; + if (topo_node_sqlite_channel_get(db, ch_id, NULL, 0, NULL, ch_x, ch_ed, NULL) != 0) { + DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: verify_join_sig — no channel key ns=%s", MS_ID, ch_id); + return 0; } - sqlite3_exec(db, "COMMIT", NULL, NULL, NULL); + uint8_t vmsg[128]; + int vlen = member_sync_build_join_msg(ch_x, ch_ed, m->node_id, m->x25519, m->join_ts, + vmsg, (int)sizeof(vmsg)); + if (vlen < 0) return 0; + + EVP_PKEY* pk = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, m->ed25519, 32); + if (!pk) return 0; + EVP_MD_CTX* vctx = EVP_MD_CTX_new(); + int ok = vctx && (EVP_DigestVerifyInit(vctx, NULL, NULL, NULL, pk) == 1) + && (EVP_DigestVerify(vctx, m->join_sig, 64, vmsg, (size_t)vlen) == 1); + if (vctx) EVP_MD_CTX_free(vctx); + EVP_PKEY_free(pk); + if (!ok) DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: apply_record invalid join_sig node=0x%016llx ns=%s", MS_ID, (unsigned long long)m->node_id, ch_id); + return ok ? 1 : 0; } int member_sync_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t from_peer, const struct ms_member_rec* m) { - (void)from_peer; if (!inst || !ch_id || !m || !m->x25519 || !m->ed25519) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: apply_record — invalid args", MS_ID); return -1; } sqlite3* db = _db(inst); if (!db) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: apply_record — db is NULL", MS_ID); return -1; } - uint64_t self = inst->node_id; int stale = 0, changed = 0; DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: apply_record ch=%s nid=%016llx uts=%llu tags=%s", @@ -394,29 +318,54 @@ int member_sync_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id, } } - /* ── Блок A (мембер): verify update_sig + сравнить ver (update_ts) ── */ - int block_a_ok = 1; /* без update_sig (join-only) — валиден как identity */ - if (m->update_sig) { + /* ── Блок A (мембер): verify update_sig (или join_sig) + сравнить ver ── */ + int is_local = (from_peer == inst->node_id); + int identity_ok = 1; + if (!is_local) { + /* жёсткая привязка идентичности: node_id = derive(x25519), ed25519 — доверенный из nodes */ + if (sc_derive_node_id_from_pubkey(m->x25519) != m->node_id) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: apply_record node_id/x25519 mismatch (forgery) nid=0x%016llx ns=%s", MS_ID, (unsigned long long)m->node_id, ch_id); + identity_ok = 0; + } else { + uint8_t trusted_ed[32]; + if (topo_node_sqlite_get_ed25519_pubkey(db, m->node_id, trusted_ed) == 0 + && memcmp(m->ed25519, trusted_ed, 32) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: apply_record ed25519 mismatch trusted (forgery) nid=0x%016llx ns=%s", MS_ID, (unsigned long long)m->node_id, ch_id); + identity_ok = 0; + } + } + } + + int block_a_ok = 0; + if (is_local) { + /* локальная запись (member_sync_put) — данные доверенные, без верификации */ + block_a_ok = 1; + } else if (!identity_ok) { + block_a_ok = 0; + } else if (m->update_sig && !_sig_is_zero64(m->update_sig)) { const uint8_t* jsig = m->join_sig ? m->join_sig : (has_local ? local_join_sig : NULL); - if (!jsig) { block_a_ok = 0; } - else { - uint8_t vmsg[512]; size_t vlen = 0; - memcpy(vmsg + vlen, jsig, 64); vlen += 64; - memcpy(vmsg + vlen, &m->update_ts, 8); vlen += 8; - size_t nl2 = m->userinfo ? strlen(m->userinfo) : 0; - memcpy(vmsg + vlen, m->userinfo ? m->userinfo : "", nl2 + 1); vlen += nl2 + 1; - EVP_PKEY* pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, m->ed25519, 32); - if (pkey) { - EVP_MD_CTX* ver = EVP_MD_CTX_new(); - if (ver) { - int ok = (EVP_DigestVerifyInit(ver, NULL, NULL, NULL, pkey) == 1) - && (EVP_DigestVerify(ver, m->update_sig, 64, vmsg, vlen) == 1); - if (!ok) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: apply_record invalid update_sig node=0x%016llx ns=%s", MS_ID, (unsigned long long)m->node_id, ch_id); block_a_ok = 0; } - EVP_MD_CTX_free(ver); + if (jsig) { + uint8_t vmsg[8192]; + int vlen = member_sync_build_update_msg(jsig, m->update_ts, + m->userinfo ? m->userinfo : "", vmsg, (int)sizeof(vmsg)); + if (vlen >= 0) { + EVP_PKEY* pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, m->ed25519, 32); + if (pkey) { + EVP_MD_CTX* ver = EVP_MD_CTX_new(); + if (ver) { + int ok = (EVP_DigestVerifyInit(ver, NULL, NULL, NULL, pkey) == 1) + && (EVP_DigestVerify(ver, m->update_sig, 64, vmsg, (size_t)vlen) == 1); + if (ok) block_a_ok = 1; + else DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: apply_record invalid update_sig node=0x%016llx ns=%s", MS_ID, (unsigned long long)m->node_id, ch_id); + EVP_MD_CTX_free(ver); + } + EVP_PKEY_free(pkey); } - EVP_PKEY_free(pkey); } } + } else if (m->join_sig && !_sig_is_zero64(m->join_sig)) { + /* join-only запись — верифицируем join_sig напрямую */ + block_a_ok = _verify_join_sig(db, ch_id, m); } int block_a_changed = 0, block_a_stale = 0; @@ -424,9 +373,12 @@ int member_sync_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id, if (!has_local) { block_a_changed = 1; } else { - if (m->join_sig && m->join_ts > local_join_ts) block_a_changed = 1; /* re-join / key rotation */ - else if (m->update_ts > local_update_ts) block_a_changed = 1; - else if (m->update_ts < local_update_ts) block_a_stale = 1; + if (m->join_sig && m->join_ts > local_join_ts) block_a_changed = 1; /* re-join / key rotation */ + else if (m->join_sig && m->join_ts < local_join_ts) block_a_stale = 1; /* старая эпоха */ + else { + if (m->update_ts > local_update_ts) block_a_changed = 1; + else if (m->update_ts < local_update_ts) block_a_stale = 1; + } } } @@ -479,10 +431,20 @@ int member_sync_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id, DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: apply_record block_put FAILED ch=%s nid=0x%016llx", MS_ID, ch_id, (unsigned long long)m->node_id); return -1; } - /* адреса — только для чужих узлов, при обновлении блока A */ - if (m->node_id != self) - _member_write_addrs(db, m->node_id, m->addrs_data, m->addr_count); changed = 1; + /* узел в nodes пишет только merkle-путь (verified identity) */ + char node_name[128] = ""; + if (m->userinfo) json_flat_get(m->userinfo, "name", node_name, sizeof(node_name)); + topo_node_sqlite_node_update_verified(db, m->node_id, node_name, m->x25519, m->ed25519, + m->update_ts ? m->update_ts : m->join_ts, + ntp_time_get_seconds(inst)); + /* если BGP уже знает адреса узла (пришли раньше мембера) — персистим их в node_addresses */ + struct TOPO_NODE* ni = inst->topo_groups + ? topo_node_registry_find(inst->topo_groups, m->node_id) : NULL; + if (ni && (ni->v4_addrs || ni->v6_addrs)) { + topo_node_sqlite_addrs_put(db, m->node_id, ni); + topo_node_sqlite_nodeinfo_updated(db, m->node_id); + } } if (block_b_changed) { if (topo_node_sqlite_member_owner_put(db, ch_id, m->node_id, m->adm_tags, m->adm_tags_sig, adm_storage) != 0) { @@ -518,7 +480,7 @@ static int _member_apply_items(void* ctx, const char* ns, uint64_t from_peer, int changed_count = 0; - for (uint16_t i = 0; i < count && mrem >= 148; i++) { + for (uint16_t i = 0; i < count && mrem >= 73; i++) { uint64_t nid; memcpy(&nid, mp, 8); mp += 8; mrem -= 8; const uint8_t* x25 = mp; mp += 32; mrem -= 32; const uint8_t* ed = mp; mp += 32; mrem -= 32; @@ -542,16 +504,6 @@ static int _member_apply_items(void* ctx, const char* ns, uint64_t from_peer, const uint8_t* atsig = NULL; if (atl && mrem >= atl) { memcpy(atags, mp, atl); atags[atl] = '\0'; mp += atl; mrem -= atl; } if (mrem >= 64) { atsig = mp; mp += 64; mrem -= 64; } - uint8_t ac = *mp++; mrem--; - const uint8_t* addrs = mp; - int consumed = 0; - for (int a = 0; a < (int)ac && mrem >= (size_t)(2 + consumed); a++) { - uint8_t fam = mp[consumed]; consumed++; - uint8_t sid = mp[consumed]; consumed++; - uint8_t proto = mp[consumed]; consumed++; - int sz = fam == 4 ? 4 : 16; - consumed += sz + 2; - } struct ms_member_rec m; memset(&m, 0, sizeof(m)); @@ -562,12 +514,10 @@ static int _member_apply_items(void* ctx, const char* ns, uint64_t from_peer, m.userinfo = nm; m.adm_tags = atags[0] ? atags : NULL; m.adm_tags_sig = atsig; - m.addrs_data = nid != inst->node_id ? addrs : NULL; - m.addr_count = nid != inst->node_id ? (int)ac : 0; int r = member_sync_apply_record(inst, ns, from_peer, &m); - DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: [ADDR_SYNC] member_sync recv ns=%s nid=0x%016llx ac=%d r=%d", MS_ID, ns, (unsigned long long)nid, ac, r); - if (r < 0) { mp += consumed; mrem -= (size_t)consumed; continue; } + DEBUG_DEBUG(DEBUG_CATEGORY_MEMBER_SYNC, "%s: member_sync recv ns=%s nid=0x%016llx r=%d", MS_ID, ns, (unsigned long long)nid, r); + if (r < 0) continue; if (r & MS_APPLY_CHANGED) { changed_count++; /* fire node_props_changed callbacks */ @@ -581,7 +531,6 @@ static int _member_apply_items(void* ctx, const char* ns, uint64_t from_peer, /* у нас версия новее — отправить наш полный рекорд автору */ member_sync_send_to(inst, ns, nid, from_peer); } - mp += consumed; mrem -= (size_t)consumed; } if (changed_count > 0) merkle_sync_broadcast(inst, ns, from_peer, data, len); @@ -692,35 +641,9 @@ static int _serialize_member(struct UTUN_INSTANCE* inst, const char* ch_id, uint uint8_t nl = nm ? (uint8_t)strnlen(nm, 255) : 0; uint8_t atl = atags ? (uint8_t)strnlen(atags, 255) : 0; - sqlite3_stmt* as = NULL; - sqlite3_prepare_v2(db, - "SELECT family, socket_id, protocol, address, port FROM node_addresses WHERE node_id=?" - " ORDER BY family, address, port", -1, &as, NULL); - uint8_t addrs[2048]; int addr_off = 0; int addr_count = 0; - if (as) { - sqlite3_bind_int64(as, 1, (sqlite3_int64)nid); - while (sqlite3_step(as) == SQLITE_ROW && addr_off < (int)sizeof(addrs) - 9) { - int fam = sqlite3_column_int(as, 0); - int sid = sqlite3_column_int(as, 1); - int proto = sqlite3_column_int(as, 2); - int ip_sz = fam == 4 ? 4 : 16; - if (addr_off + 3 + ip_sz + 2 > (int)sizeof(addrs)) break; - addrs[addr_off++] = (uint8_t)fam; - addrs[addr_off++] = (uint8_t)sid; - addrs[addr_off++] = (uint8_t)proto; - memcpy(addrs + addr_off, sqlite3_column_blob(as, 3), (size_t)ip_sz); - addr_off += ip_sz; - uint16_t p = (uint16_t)sqlite3_column_int(as, 4); - addrs[addr_off++] = (uint8_t)(p >> 8); - addrs[addr_off++] = (uint8_t)(p & 0xFF); - addr_count++; - } - sqlite3_finalize(as); - } - uint8_t flags = (sig && jts) ? PEERS_FLAG_HAS_JOIN : 0; size_t need = 2 + 8 + 32 + 32 + 1 + (flags & PEERS_FLAG_HAS_JOIN ? 72 : 0) - + 72 + 1 + (size_t)nl + 1 + (size_t)atl + 64 + 1 + (size_t)addr_off; + + 72 + 1 + (size_t)nl + 1 + (size_t)atl + 64; if (need > buf_sz) { sqlite3_finalize(stmt); return -1; } size_t off = 0; uint16_t wcnt = 1; memcpy(buf, &wcnt, 2); off += 2; @@ -736,8 +659,6 @@ static int _serialize_member(struct UTUN_INSTANCE* inst, const char* ch_id, uint if (atsig && sqlite3_column_bytes(stmt, 9) >= 64) { memcpy(buf + off, atsig, 64); } else { memset(buf + off, 0, 64); } off += 64; - buf[off++] = (uint8_t)addr_count; - memcpy(buf + off, addrs, (size_t)addr_off); off += (size_t)addr_off; sqlite3_finalize(stmt); *out_len = off; @@ -765,10 +686,9 @@ int member_sync_put(struct UTUN_INSTANCE* inst, const char* ch_id, const uint8_t* join_sig, uint64_t join_ts, const uint8_t* update_sig, uint64_t update_ts, const char* userinfo, - const uint8_t* addrs_data, int addr_count, const char* adm_tags, const uint8_t* adm_tags_sig, int storage) { if (!inst || !ch_id) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: put — inst=%p ch_id=%s", MS_ID, (void*)inst, ch_id ? ch_id : "(null)"); return -1; } - DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: put ch=%s nid=%016llx userinfo=%s ac=%d adm_tags=%s storage=%d", MS_ID, ch_id, (unsigned long long)member_id, userinfo ? userinfo : "", addr_count, adm_tags ? adm_tags : "", storage); + DEBUG_TRACE(DEBUG_CATEGORY_MEMBER_SYNC, "%s: put ch=%s nid=%016llx userinfo=%s adm_tags=%s storage=%d", MS_ID, ch_id, (unsigned long long)member_id, userinfo ? userinfo : "", adm_tags ? adm_tags : "", storage); struct ms_member_rec m; memset(&m, 0, sizeof(m)); @@ -778,7 +698,6 @@ int member_sync_put(struct UTUN_INSTANCE* inst, const char* ch_id, m.update_sig = update_sig; m.update_ts = update_ts; m.userinfo = userinfo; m.adm_tags = adm_tags; m.adm_tags_sig = adm_tags_sig; m.storage = storage; - m.addrs_data = addrs_data; m.addr_count = addr_count; return member_sync_apply_record(inst, ch_id, inst->node_id, &m); } diff --git a/src/chat/member_sync.h b/src/chat/member_sync.h index 0f307837..1ac7966a 100644 --- a/src/chat/member_sync.h +++ b/src/chat/member_sync.h @@ -58,7 +58,7 @@ struct UTUN_INSTANCE; #define MS_APPLY_CHANGED 0x01 /* блок стал новее — обновлён, нужен recompute + relay остальным */ #define MS_APPLY_STALE 0x02 /* у нас версия новее — отправить наш полный рекорд автору */ -/* Распарсенный рекорд мембера (два подписанных блока + адреса). */ +/* Распарсенный рекорд мембера (два подписанных блока). */ struct ms_member_rec { uint64_t node_id; const uint8_t* x25519; /* 32 байта, обязательно */ @@ -71,8 +71,6 @@ struct ms_member_rec { const char* adm_tags; /* JSON владельца с "ver", может быть NULL */ const uint8_t* adm_tags_sig; /* 64 байта, может быть NULL */ int storage; /* 0/1, производная от adm_tags.storage */ - const uint8_t* addrs_data; /* wire-адреса, может быть NULL */ - int addr_count; }; /* @@ -108,8 +106,6 @@ void member_sync_cancel(struct UTUN_INSTANCE* inst, uint64_t peer, const char* c * сообщение: ch_x25519(32) || ch_ed25519(32) || node_id(8LE) || x25519(32) || join_ts(8LE) * join_ts — NTP-синхронизированное время подписи (unix epoch seconds) * userinfo — метаданные узла (имя) в канале - * addrs_data — адреса в wire-формате: [family:1][ip:4|16][port:2]... - * addr_count — количество адресов (0 если нет) * * Пишет только в таблицу peers_ (INSERT OR REPLACE). * nodes обновляется отдельно через topo_node_sqlite_node_update_verified(). @@ -123,17 +119,35 @@ int member_sync_put(struct UTUN_INSTANCE* inst, const char* ch_id, const uint8_t* join_sig, uint64_t join_ts, const uint8_t* update_sig, uint64_t update_ts, const char* userinfo, - const uint8_t* addrs_data, int addr_count, const char* adm_tags, const uint8_t* adm_tags_sig, int storage); +/* + * Строит сообщение для update_sig блока A (мембер): + * join_sig(64) || update_ts(8) || userinfo(с NUL) + * Используется и для подписи, и для верификации — байты обязаны совпадать. + * Возвращает длину сообщения или -1. + */ +int member_sync_build_update_msg(const uint8_t* join_sig, uint64_t update_ts, + const char* userinfo, + uint8_t* out, int out_sz); + +/* + * Строит сообщение для join_sig (каноническая раскладка 112 байт): + * ch_x25519(32) || ch_ed25519(32) || node_id(8) || node_x25519(32) || join_ts(8) + * Единственный источник истины для подписи и верификации join_sig во всех модулях. + * Возвращает длину сообщения (112) или -1. + */ +int member_sync_build_join_msg(const uint8_t* ch_x25519, const uint8_t* ch_ed25519, + uint64_t node_id, const uint8_t* node_x25519, uint64_t join_ts, + uint8_t* out, int out_sz); + /* * Применить рекорд мембера с по-блочным сравнением версий: - * - блок A (мембер): ver = update_ts, подписан ключом мембера (update_sig); + * - блок A (мембер): ver = update_ts (внутри эпохи join_ts), подписан ключом мембера; * - блок B (владелец): ver = adm_tags.ver, подписан канальным ключом (adm_tags_sig). * Каждый блок обновляется независимо, только если его ver увеличился. * Если ver пришедшего блока меньше локального — блок игнорируется и выставляется * MS_APPLY_STALE (нужно отправить нашу свежую запись автору from_peer). - * Адреса пишутся только при обновлении блока A и только для чужих узлов. * Возвращает битовую маску MS_APPLY_*, <0 — ошибка. */ int member_sync_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id, diff --git a/src/chat/merkle_sync.c b/src/chat/merkle_sync.c index 10af384c..3297fe54 100644 --- a/src/chat/merkle_sync.c +++ b/src/chat/merkle_sync.c @@ -7,6 +7,9 @@ #include "../../lib/mem.h" #include "../../lib/u_async.h" #include "../../lib/ll_queue.h" +#ifdef UTUN_HAVE_STANDBY +#include "standby.h" +#endif #include #include @@ -51,6 +54,7 @@ struct merkle_sync { struct ms_session* sessions; uint8_t initialized; void* wal_timer; + void* wal_wait; /* standby_wait handle (Android) */ }; /* ── DB access ── */ @@ -471,6 +475,7 @@ static void _handle_hashes(struct merkle_sync* ms, uint64_t peer, const char* ns const uint8_t* pl, size_t plen, int parent_idx) { if (plen < 3) return; uint8_t level = pl[0]; uint8_t pb = pl[1]; + if (level > MT_MAX_LEVEL || pb > 8 || (size_t)(3 + pb) > plen) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: handle_hashes bad level=%u prefix_bytes=%u plen=%zu", MS_ID, level, pb, plen); return; } uint64_t prefix = _prefix_read(pl + 2, pb); uint8_t is_data = pl[2 + pb]; const uint8_t* payload = pl + 3 + pb; @@ -669,6 +674,7 @@ static void _recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) { size_t dlen = entry->len; uint8_t ch_len = d[1]; + if (ch_len >= 64) { DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: recv ch_len too large (%u) from=%016llx", MS_ID, ch_len, (unsigned long long)peer); u_free(entry->dgram); queue_entry_free(entry); return; } if (dlen < (size_t)(2 + ch_len + 1)) { u_free(entry->dgram); queue_entry_free(entry); return; } char ns[64]; memcpy(ns, d + 2, ch_len); ns[ch_len] = '\0'; uint8_t type = d[2 + ch_len]; @@ -780,6 +786,13 @@ int merkle_sync_bg_check(struct UTUN_INSTANCE* inst, const char* ns) { static void _wal_timer_cb(void* arg) { struct merkle_sync* ms = (struct merkle_sync*)arg; if (!ms || !ms->initialized || !ms->inst) return; +#ifdef UTUN_HAVE_STANDBY + if (standby_get_sleep_tb() > 0) { + ms->wal_wait = standby_wait(ms, _wal_timer_cb); + return; + } + ms->wal_wait = NULL; +#endif sqlite3* db = _db(ms->inst); if (!db) { ms->wal_timer = NULL; return; } int pn = 0, ckpt = 0; @@ -823,6 +836,9 @@ void merkle_sync_destroy(struct UTUN_INSTANCE* inst) { etcp_unbind(inst, ms->svc_id); if (ms->wal_timer) { uasync_cancel_timeout(inst->ua, ms->wal_timer); ms->wal_timer = NULL; } +#ifdef UTUN_HAVE_STANDBY + if (ms->wal_wait) { standby_wait_cancel(ms->wal_wait); ms->wal_wait = NULL; } +#endif struct ms_session* s = ms->sessions; while (s) { struct ms_session* next = s->next; diff --git a/src/chat/merkle_tz.md b/src/chat/merkle_tz.md new file mode 100644 index 00000000..6f2054ea --- /dev/null +++ b/src/chat/merkle_tz.md @@ -0,0 +1,132 @@ +# ТЗ: событийная синхронизация чата (db_sync + merkle_sync) вместо периодического опроса + +## 1. Цель +После завершения первичной репликации синхронизация должна перейти в **событийный режим**: +любое изменение базы немедленно рассылается пирам (push), без периодического таймера-опроса. +Периодические таймеры остаются только на время первичного догона и детекции таймаутов. +Рассылка ведётся поштучно через **отдельную очередь на каждый пир** с автопередачей +в etcp-очередь и контролем congestion (backpressure). + +## 2. Текущее состояние (контекст) + +### db_sync (`src/chat/db_sync.c`) +- Событийный push **уже есть**: `db_record_insert_cascade` (стр. 623) при каждой записи шлёт + `DB_MSG_PUSH` пирам с `sync_state >= 1` (SYNCING и SYNCED). Вызывается из + `db_sync_insert_signed` (стр. 1780, локальная запись) и recv-обработчиков (стр. 1024, 1156). +- `db_sync_peer_check_cb` (стр. 1462) — периодический 5с (`DB_SYNC_PEER_CHECK_INTERVAL=5`): + ищет несинхронных пиров (`sync_state==0`) → `db_sync_initiate_sync`; детект таймаута + (`sync_state==1` завис → 0). **Перевзводится безусловно** (стр. 1470, 1545, 1627). + Отмена в destroy (стр. 1644). +- `struct SI_PEER.sync_state`: 0=нужен sync, 1=идёт, 2=синхронизирован. `struct DB_SYNC.peer_check_timer`. +- Триггеры начала sync: `db_sync_on_conn_up` (стр. 1268), recv-обработчики (стр. 747, 817, 1212). + +### merkle_sync (`src/chat/merkle_sync.c`) +- `_bg_timer_cb` (стр. 702) — периодический 5с (`MS_BG_INTERVAL_MS=5000`): round-robin + `merkle_sync_bg_check` по каналам + WAL-checkpoint (`tick % 12`). **Перевзводится безусловно** + (стр. 732). Отмена в destroy (стр. 767). +- Переход «корневой хеш совпал → synced» **реализован**: `_handle_hashes` (стр. 442-445): + `differs==0 && level==0` → `_session_done(MT_OK)` → `SESS_SYNCED`. +- Рассылка: `merkle_sync_broadcast` (стр. 658) — **всем** сессиям (без проверки `SESS_SYNCED`), + кроме `from_peer`; `merkle_sync_push_update` (стр. 618) — только `SESS_SYNCED` (лёгкие апдейты, online). +- Отправка: `_send_msg` (стр. 241) → `ms_find_conn_for_node` (стр. 231) → `etcp_send` → + `queue_data_put(conn->send_input_q)` (при переполнении **дропает**, backpressure нет). +- `struct ms_session` (стр. 31): `ns`, `peer`, `sess_state`, `done_cb`, `pending`. + `struct merkle_sync` (стр. 42): `sessions`, `bg_timer`. + +### member_sync (`src/chat/member_sync.c`) +- `member_sync_put` (стр. 595) / `member_sync_del` (стр. 677): пишут в БД + + `merkle_sync_recompute_path`. **Сами не рассылают.** +- `member_sync_set_online` (стр. 703): только пишет в БД; push делается вручную в + `chat_sync.c` (стр. 402, 439). +- `member_sync_broadcast_one` (стр. 514): читает мембера + `merkle_sync_broadcast`. + Вызывается вручную из `chat_core.c:673`, `chat_profile.c:77`; **НЕ** вызывается из + `chat_channel.c:193` (self-add). +- `_member_apply_items` (apply_items, стр. ~425): `member_sync_put` поштучно + batch + `merkle_sync_broadcast` (relay, стр. 440). + +### Очереди / backpressure (`lib/ll_queue.h`) +- Есть: `queue_new`, `queue_set_callback`, `queue_set_threshold`, `queue_waiter_wait`, + `queue_resume_callback`, `queue_data_put/get`, `queue_entry_count`. +- `conn->send_input_q` = вход normalizer'а (`pkt_normalizer.c:53`), порог `(0,0)` + (ждать полного освобождения), `queue_set_waiter_defer(pn->input, 1)`. + +## 3. Требования + +### 3.1 db_sync — «work-driven» таймер +1. Хелпер `db_sync_has_pending_work(db)`: есть ли у любого активного инстанса пир с `sync_state != 2`. +2. Хелпер `db_sync_resume_peer_check(db)`: взвести `peer_check_timer`, если `NULL` и `db->enabled`. +3. `db_sync_peer_check_cb`: в начале `db->peer_check_timer = NULL`; в конце — взводить только + если есть работа, иначе не взводить и логировать `INFO` «all peers synced — peer_check stopped». + - Случай `!g` (нет default-группы, стр. 1468) — сохранить текущее поведение (перевзвод) + или обработать отдельно (решить при реализации). +4. Возобновление по событиям: в `db_sync_initiate_sync` (единая точка старта sync) вызвать + `db_sync_resume_peer_check(si->db_sync)`. Это покрывает `db_sync_on_conn_up` и recv-обработчики. +5. Важно: таймер должен крутиться пока есть `sync_state==1` (иначе не будет детекта таймаута). + +### 3.2 merkle_sync — событийный bg_check + отдельный WAL-таймер +1. Убрать периодический `_bg_timer_cb` (5с) с round-robin проверкой. +2. `merkle_sync_bg_check` (стр. 672) сделать **событийным**: пересчёт дерева выполняется сразу + при записи/`apply_update` (через `merkle_sync_recompute_path`, который уже вызывается из + `member_sync_put/del`). Периодической сверки нет. +3. WAL-checkpoint вынести в **отдельный редкий таймер** (~60с, локально, без сети). + Хранить отдельный handle; отменять в destroy. (Интервал уточнить: 60с.) + +### 3.3 Централизованное событие «изменение базы» (member_sync) +1. Единый путь «запись → пересчёт дерева → рассылка»: + - `member_sync_put` / `member_sync_del`: после `merkle_sync_recompute_path` автоматически + ставить изменение в очередь рассылки (п. 3.4), **кроме источника** (`from_peer`/self). + - `member_sync_set_online`: автоматически `merkle_sync_push_update`. +2. Убрать ручные рассылки из `chat_core.c:673`, `chat_profile.c:77`, `chat_sync.c:402/439`, + `member_sync_broadcast_one` (или оставить как узкий хелпер, но не требовать от caller'ов). +3. Receive-путь `_member_apply_items`: избежать двойной рассылки — либо `member_sync_put` + рассылает сам (тогда убрать batch-relay стр. 440), либо оставить relay как единый batch и + НЕ рассылать из `member_sync_put` при приёме. **Выбрать один механизм** + (рекомендуется: put рассылает поштучно, relay убрать). + +### 3.4 По-пировая очередь рассылки с congestion control (merkle_sync) +1. **Очередь на пира**: добавить `struct ll_queue* out_q` в `struct ms_session` + (сессия = peer + ns; это и есть «по-пировая» на канал). Создать при создании сессии, + освободить при удалении/деините. (Если нужен строгий per-peer независимо от канала — + отдельная структура `ms_peer_out{peer, out_q, waiter}`; по умолчанию per-session.) +2. **Энкью**: `merkle_sync_broadcast` / централизованный путь вместо немедленного + `_send_broadcast_data` кладёт **поштучно** каждый item в `out_q` каждой подходящей сессии + (кроме `from_peer`). Item содержит ns + данные мембера. +3. **Автопередача**: `queue_set_callback(out_q, drain_cb, session)`. `drain_cb`: + `queue_data_get(out_q)` → собрать wire-сообщение → `etcp_send(conn, entry)` → + `queue_resume_callback(out_q)`. +4. **Congestion control (backpressure)**: не дропать при переполнении etcp-очереди. Перед + `etcp_send` проверять занятость `conn->send_input_q`; при превышении порога — + `queue_waiter_wait(conn->send_input_q, session->waiter, resume_drain_cb, session)` и + приостановить drain; возобновить по колбэку порога. Порог/лимит (пакеты/байты) уточнить, + ориентир — не дропать, но и не копить безлимитно. + +## 4. Файлы/функции (ориентир) +- `src/chat/db_sync.c` (+`.h`): `db_sync_has_pending_work`, `db_sync_resume_peer_check`, + правка `db_sync_peer_check_cb`, вызов resume в `db_sync_initiate_sync`, + поле `peer_check_timer` уже есть. +- `src/chat/merkle_sync.c` (+`.h`): поле `out_q`/waiter в `struct ms_session`, `drain_cb`, + правка `merkle_sync_broadcast`/`push_update`, замена `_bg_timer_cb` на событийный + + отдельный WAL-таймер. +- `src/chat/member_sync.c` (+`.h`): автопередача из `member_sync_put/del/set_online`, + убрать ручные `member_sync_broadcast_one` у caller'ов (`chat_core.c`, `chat_profile.c`, + `chat_sync.c`, `chat_channel.c`). +- Учесть `#ifdef UTUN_HAVE_STANDBY` для standby-гейтинга не требуется после этого (таймеры сами + останавливаются), но keepalive/прочие не трогаем. + +## 5. Критерии приёмки +1. После полного догона (все пиры `sync_state==2` / все сессии `SESS_SYNCED`) в логе нет + `db_sync_peer`/`ms_bg` каждые 5с. +2. Локальная запись (сообщение/мембер/online) рассылается всем подходящим пирам без участия + caller'а; self-add при входе в канал тоже рассылается. +3. При переполнении etcp-очереди рассылка не дропает, а приостанавливается и возобновляется + (backpressure). +4. После события «изменение базы» дерево merkle пересчитано, и изменение разошлось; + «корневой хеш совпал → synced» работает как раньше. +5. Юнит/интеграционные проверки (при наличии) проходят; спам DEBUG/TRACE `sys` в standby + отсутствует (при разумном `debug`-уровне). + +## 6. Риски / открытые вопросы +- Гранулярность очереди: per-session (peer+ns) vs строго per-peer — уточнить. +- Значения порога backpressure и интервала WAL-таймера. +- Взаимодействие рассылки «даже во время sync» с протоколом merkle-сессии (broadcast идёт + параллельно обмену хешами) — убедиться, что не ломает `pending`-машину сессии. diff --git a/src/config_parser.c b/src/config_parser.c index 83e7394b..64cd2a45 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -350,7 +350,14 @@ static struct CFG_SERVER* find_server_by_name(struct CFG_SERVER *servers, const static int parse_global(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) { if (strcmp(key, "my_node_name") == 0) { - return assign_string(global->name, sizeof(global->name), value); + size_t nlen = strlen(value); + if (nlen >= sizeof(global->name)) { + DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: my_node_name too long (%zu chars), truncating to %zu", + filename, line_num, nlen, sizeof(global->name) - 1); + } + strncpy(global->name, value, sizeof(global->name) - 1); + global->name[sizeof(global->name) - 1] = '\0'; + return 0; } if (strcmp(key, "my_private_key") == 0) { return assign_string(global->my_private_key_hex, MAX_KEY_LEN, value); diff --git a/src/config_parser.h b/src/config_parser.h index 25bdf17d..7d874a38 100644 --- a/src/config_parser.h +++ b/src/config_parser.h @@ -105,7 +105,7 @@ struct tcp_proxy_client_mapping_config { }; struct global_config { - char name[16]; // Instance name + char name[MAX_CONN_NAME_LEN]; // Instance name char my_private_key_hex[MAX_KEY_LEN]; char my_public_key_hex[MAX_KEY_LEN]; uint64_t my_node_id; diff --git a/src/config_updater.c b/src/config_updater.c index f9a8bf00..e040c1a9 100644 --- a/src/config_updater.c +++ b/src/config_updater.c @@ -306,7 +306,7 @@ int config_ensure_keys_and_node_id(const char *filename) { if (!need_priv_key && !need_pub_key && !need_node_id) { free_config(config); u_free(file_buf); return 0; } - char cfg_name[16]; snprintf(cfg_name, sizeof(cfg_name), "%s", global->name[0] ? global->name : "utun"); + char cfg_name[MAX_CONN_NAME_LEN]; snprintf(cfg_name, sizeof(cfg_name), "%s", global->name[0] ? global->name : "utun"); free_config(config); diff --git a/src/routing_layer/conn_mgr_core.c b/src/routing_layer/conn_mgr_core.c index 72986340..027fa5ed 100644 --- a/src/routing_layer/conn_mgr_core.c +++ b/src/routing_layer/conn_mgr_core.c @@ -919,8 +919,6 @@ void cm_handle_invite_info_resp(struct CONN_MGR* mgr, struct ETCP_CONN* conn, co if (inv->temp_nq) { struct TOPO_NODE* ni2 = topo_node_registry_find(inv->mgr->instance->topo_groups, inv->temp_nq->node_id); if (ni2) memcpy(ni2->ed25519_public_key, resp->ed25519_pubkey, SC_PUBKEY_SIZE); - if (inv->mgr->instance->topo_sqlite_db) - topo_node_sqlite_node_put(inv->mgr->instance->topo_sqlite_db, inv->mgr->instance->topo_groups, inv->temp_nq, (time_t)(get_time_tb() / 10000)); } /* ── парсим channel_info (новое) ── */ @@ -1010,7 +1008,24 @@ void cm_handle_invite_info_resp(struct CONN_MGR* mgr, struct ETCP_CONN* conn, co (unsigned long long)inv->node_id, (int)nnl, nnl ? (const char*)resp->node_name : "?", ch_id); if (inv->cb) inv->cb(inv->handle, inv->node_id, inv->mgr->group->group_id, CONN_EVENT_JOIN, inv->cb_arg); if (inv->invite_conn) { - node_conn_direct_open_node(inv->mgr->instance, inv->node_id, cm_ncd_callback, NULL, NULL, NULL, NULL); + /* переиспользуем существующий conn через ncd: он добавляет недостающие линки + * (ncd_create_links с дедупом по etcp_link_find_by_addr) */ + struct TOPO_NODE* ni = topo_node_registry_find(inv->mgr->instance->topo_groups, inv->node_id); + struct CONN_MGR_ENTRY* entry = cm_ensure_entry(inv->mgr, inv->node_id); + if (ni && entry) { + entry->state = CONN_MGR_STATE_CONNECTED; + entry->conn_type = CONN_TYPE_DIRECT; + if (inv->handle && !inv->handle->entry) { + inv->handle->entry = entry; + inv->handle->next = entry->handles; + entry->handles = inv->handle; + } + node_conn_direct_open_node(inv->mgr->instance, inv->node_id, + cm_ncd_callback, entry, &entry->ncd_handle, ni, NULL); + } else { + DEBUG_ERROR(DEBUG_CATEGORY_SYS, "[INV_SUCCESS] cannot promote conn: ni=%p entry=%p node=0x%016llx", + (void*)ni, (void*)entry, (unsigned long long)inv->node_id); + } inv->invite_conn = NULL; } DEBUG_INFO(DEBUG_CATEGORY_SYS, "[INV_SUCCESS] conn transferred to ncd, calling cancel"); @@ -1046,6 +1061,9 @@ static void cm_invite_cancel(struct cm_invite_pending* inv) { uasync_cancel_timeout(inv->mgr->instance->ua, inv->overall_timer); inv->overall_timer = NULL; DEBUG_INFO(DEBUG_CATEGORY_SYS, "[INV_CANCEL] overall_timer cancelled"); } + /* гасим висящий etcp_connect (если ещё идёт) — иначе его settle-таймер дёрнет + * cm_invite_etcp_cb с освобождённым inv (use-after-free) */ + etcp_connect_cancel_by_node(inv->mgr->instance, inv->node_id); if (inv->temp_nq) { queue_remove_data(inv->mgr->group->nodes, &inv->temp_nq->ll); topo_nodeq_free_group_fields(inv->mgr->instance->topo_groups, inv->temp_nq); diff --git a/src/routing_layer/topo_group.c b/src/routing_layer/topo_group.c index 43f9cac1..e1b35c88 100644 --- a/src/routing_layer/topo_group.c +++ b/src/routing_layer/topo_group.c @@ -703,6 +703,14 @@ int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from (unsigned long long)node_id, v4c, v6c, from->log_name); } /* verify Ed25519 self-signature over canonical message */ + if (node_id != sc_derive_node_id_from_pubkey(new_ni->public_key)) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "NODEINFO node_id mismatch public_key (forgery) node=%016llx from=%s", + (unsigned long long)node_id, from->log_name); + topo_node_destroy(group->instance->topo_groups, new_ni); + u_free(new_subnets); u_free(new_hop_list); + if (nodeinfo1) { queue_remove_data(group->nodes, &nodeinfo1->ll); queue_free(paths); queue_entry_free(&nodeinfo1->ll); } + return -1; + } { uint64_t ekchk; memcpy(&ekchk, new_ni->ed25519_public_key, 8); if (ekchk == 0) { @@ -769,7 +777,9 @@ int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from if (node_id != group->instance->node_id) { sqlite3* sdb = group->instance->topo_sqlite_db; if (sdb) { - topo_node_sqlite_node_put(sdb, group->instance->topo_groups, nodeinfo1, ntp_time_get_seconds(group->instance)); + /* узлы в nodes пишет только merkle-путь; BGP лишь дополняет IP для мемберов */ + if (topo_node_sqlite_member_exists(sdb, node_id)) + topo_node_sqlite_addrs_put(sdb, node_id, new_ni); if (group->group_type == TOPO_GROUP_TYPE_CHAT && group->channel_id[0]) { topo_node_sqlite_member_put(sdb, group->channel_id, node_id, NULL, 0, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, 0); } diff --git a/src/routing_layer/topo_node.c b/src/routing_layer/topo_node.c index 33d87d1b..1dbafbd4 100644 --- a/src/routing_layer/topo_node.c +++ b/src/routing_layer/topo_node.c @@ -164,9 +164,11 @@ void topo_node_registry_unref(struct TOPO_GROUPS* groups, uint64_t node_id) { // ===== serialization ===== int topo_node_build_sig_msg(struct TOPO_NODE* ni, uint8_t* buf, size_t buf_size) { - if (!ni || !buf || buf_size < 64) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "build_sig_msg: invalid args"); return -1; } + if (!ni || !buf || buf_size < 64 + 9) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "build_sig_msg: invalid args"); return -1; } size_t off = 0; + memcpy(buf + off, &ni->node_id, 8); off += 8; + buf[off++] = ni->ver; memcpy(buf + off, ni->public_key, SC_PUBKEY_SIZE); off += SC_PUBKEY_SIZE; size_t name_len = ni->node_name ? strlen(ni->node_name) : 0; @@ -959,6 +961,9 @@ void topo_node_update_my_addresses(struct UTUN_INSTANCE* instance) { topo_node_sign_self(instance, ni); + if (instance->topo_sqlite_db && topo_node_sqlite_member_exists(instance->topo_sqlite_db, instance->node_id)) + topo_node_sqlite_addrs_put(instance->topo_sqlite_db, instance->node_id, ni); + struct ll_entry* ge = instance->topo_groups->group_list->head; while (ge) { struct TOPO_GROUP* g = (struct TOPO_GROUP*)ge; diff --git a/src/routing_layer/topo_node_sqlite.c b/src/routing_layer/topo_node_sqlite.c index b95295e4..f7fa4301 100644 --- a/src/routing_layer/topo_node_sqlite.c +++ b/src/routing_layer/topo_node_sqlite.c @@ -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 : "", diff --git a/src/routing_layer/topo_node_sqlite.h b/src/routing_layer/topo_node_sqlite.h index e407ac97..a3e85cc7 100644 --- a/src/routing_layer/topo_node_sqlite.h +++ b/src/routing_layer/topo_node_sqlite.h @@ -16,7 +16,12 @@ int topo_node_sqlite_init(sqlite3* db); -int topo_node_sqlite_node_put(sqlite3* db, struct TOPO_GROUPS* groups, struct TOPO_GROUP_NODE* nq, time_t now_sec); +/* Персистентная запись адресов узла в node_addresses (источник истины — BGP/topo_group). + * DELETE + INSERT из ni->v4_addrs/v6_addrs, классификация config_type/nat_type из SOCKMETA. */ +int topo_node_sqlite_addrs_put(sqlite3* db, uint64_t node_id, struct TOPO_NODE* ni); + +/* Есть ли у узла запись мембера хотя бы в одной peers_ (реальный мембер с ключами). */ +int topo_node_sqlite_member_exists(sqlite3* db, uint64_t node_id); int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id, const char* name, uint64_t owner_node_id, diff --git a/src/transport_layer/etcp_connect.c b/src/transport_layer/etcp_connect.c index 81716419..231f75fe 100644 --- a/src/transport_layer/etcp_connect.c +++ b/src/transport_layer/etcp_connect.c @@ -414,3 +414,26 @@ void etcp_connect_cancel_for_conn(struct UTUN_INSTANCE* inst, struct ETCP_CONN* } } } + +/* Полная отмена pending-коннекта по node_id: снимает connect_init_cb/bgp_ready_cbk + * с conn (conn остаётся жив, например, переиспользуется ncd), отменяет таймеры, + * вынимает ctx из pending_connects и освобождает. Безопасно вызывать, если ctx нет. */ +void etcp_connect_cancel_by_node(struct UTUN_INSTANCE* inst, uint64_t node_id) { + if (!inst) return; + struct ETCP_CONNECT** pp = &inst->pending_connects; + while (*pp) { + struct ETCP_CONNECT* ctx = *pp; + if (ctx->node_id == node_id) { + *pp = ctx->next; + if (ctx->conn) { + etcp_conn_remove_cbk(ctx->conn, connect_init_cb, ctx); + ctx->conn->bgp_ready_cbk = NULL; + } + if (ctx->initial_timer) { uasync_cancel_timeout(inst->ua, ctx->initial_timer); ctx->initial_timer = NULL; } + if (ctx->settle_timer) { uasync_cancel_timeout(inst->ua, ctx->settle_timer); ctx->settle_timer = NULL; } + u_free(ctx); + } else { + pp = &(*pp)->next; + } + } +} diff --git a/src/transport_layer/etcp_connect.h b/src/transport_layer/etcp_connect.h index d779052b..c6fe47ba 100644 --- a/src/transport_layer/etcp_connect.h +++ b/src/transport_layer/etcp_connect.h @@ -190,4 +190,8 @@ int etcp_connect(struct UTUN_INSTANCE* instance, struct TOPO_GROUP_NODE* node, */ void etcp_connect_cancel_for_conn(struct UTUN_INSTANCE* inst, struct ETCP_CONN* conn); +/* Полная отмена pending-коннекта по node_id: снимает коллбэки с conn (conn остаётся + * жив — переиспользуется, напр. ncd), отменяет таймеры, освобождает ctx. */ +void etcp_connect_cancel_by_node(struct UTUN_INSTANCE* inst, uint64_t node_id); + #endif diff --git a/src/utun_instance.h b/src/utun_instance.h index 7446aaca..09ff664d 100644 --- a/src/utun_instance.h +++ b/src/utun_instance.h @@ -92,7 +92,7 @@ struct utun_activity_cbk_entry { // uTun instance configuration struct UTUN_INSTANCE { // Identification - char name[16]; // Instance name from config + char name[MAX_CONN_NAME_LEN]; // Instance name from config // Configuration (moved from utun_state) struct utun_config *config; diff --git a/tests/test_etcp_connect.c b/tests/test_etcp_connect.c index be830dc3..fd6b5cbe 100644 --- a/tests/test_etcp_connect.c +++ b/tests/test_etcp_connect.c @@ -79,7 +79,7 @@ static struct TOPO_GROUP_NODE* mknode(struct UTUN_INSTANCE* inst, uint64_t nid, struct TOPO_SOCKMETA4* sm = memory_pool_alloc(inst->topo_groups->v4_sock_meta_pool); if (sm) { sm->id = 0; sm->config_type = CFG_SERVER_TYPE_PUBLIC; sm->nat_type = NAT_TYPE_UNKNOWN; sm->next = ni->v4_sock_meta; ni->v4_sock_meta = sm; } struct TOPO_ADDR4* addr = memory_pool_alloc(inst->topo_groups->v4_addr_pool); - if (addr) { addr->addr[0] = a; addr->addr[1] = b; addr->addr[2] = c; addr->addr[3] = d; addr->port = port; addr->type = TOPO_ADDR_INTERFACE; addr->socket_id = 0; addr->next = ni->v4_addrs; ni->v4_addrs = addr; } + if (addr) { addr->addr[0] = a; addr->addr[1] = b; addr->addr[2] = c; addr->addr[3] = d; addr->port = port; addr->type = TOPO_ADDR_INTERFACE; addr->socket_id = 0; addr->protocol = TOPO_PROTO_UDP; addr->next = ni->v4_addrs; ni->v4_addrs = addr; } topo_node_registry_store(inst->topo_groups, ni); return nq; } diff --git a/tests/test_media_delivery_full.c b/tests/test_media_delivery_full.c index 7c2cc75c..7e338aca 100644 --- a/tests/test_media_delivery_full.c +++ b/tests/test_media_delivery_full.c @@ -153,7 +153,7 @@ static void setup_chat_group(void) { member_sync_put(g_inst[i], CH_ID, g_nid[m], g_inst[m]->my_keys.public_key, g_inst[m]->my_ed25519_pubkey, NULL, 0, NULL, 0, "{\"name\":\"node\"}", - NULL, 0, adm[m], NULL, 0); + adm[m], NULL, 0); } } int ok = 1; diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/MainActivity.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/MainActivity.kt index 0b807aa6..483eb6f1 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/MainActivity.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/MainActivity.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.core.content.ContextCompat import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.lifecycle.compose.LifecycleStartEffect import com.utun.chat.data.Channel import com.utun.chat.data.InviteData import com.utun.chat.data.LogManager @@ -36,6 +37,13 @@ class MainActivity : ComponentActivity() { setContent { val vm: ChatViewModel = viewModel(factory = ChatViewModel.Factory()) + /* Pause GUI polling (health-check, recording/playback timers) when backgrounded, + refresh statuses on resume. */ + LifecycleStartEffect(Unit) { + vm.setForeground(true) + onStopOrDispose { vm.setForeground(false) } + } + var screen by remember { mutableStateOf(Screen.ChannelList) } var qrResult by remember { mutableStateOf(null) } var showJoinDialog by remember { mutableStateOf(false) } diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt index d2f5b40c..02fff92c 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt @@ -209,7 +209,7 @@ class ConfigProvider(private val context: Context) { /* ── Server entries ── */ - suspend fun detectInterfaces(): List { + suspend fun detectInterfaces(saved: List = emptyList()): List { val servers = mutableListOf() val seen = mutableSetOf() try { @@ -224,14 +224,18 @@ class ConfigProvider(private val context: Context) { addr is Inet6Address && isUsableV6(addr) && !isTemporaryV6(ni.name, addr.hostAddress ?: "") -> addr.hostAddress?.let { it.split("%")[0] } else -> null } ?: continue - val key = "${ni.name}/${if (ip.contains(":")) "v6" else "v4"}" + val isV6 = ip.contains(":") + val key = "${ni.name}/${if (isV6) "v6" else "v4"}" if (!seen.add(key)) continue + val savedPort = saved.firstOrNull { + it.ifName == ni.name && it.bindIp.contains(":") == isV6 + }?.port servers.add(ServerEntry( id = UUID.randomUUID().toString(), ifName = ni.name, bindIp = ip, protocol = "tcp", - port = ThreadLocalRandom.current().nextInt(2000, 10000) + port = savedPort ?: ThreadLocalRandom.current().nextInt(2000, 10000) )) } } @@ -395,7 +399,9 @@ class ConfigProvider(private val context: Context) { /** Build full INI config text for NativeLib.start() */ fun buildConfigText(): String { if (getConnectionMode() == "auto") { - val detected = runBlocking { detectInterfaces() } + val cfgFile = File(configFilePath()) + val saved = if (cfgFile.exists()) loadServersFromConfig() else emptyList() + val detected = runBlocking { detectInterfaces(saved) } LogManager.addLog("INFO", "CFG", "buildConfigText: auto mode, detected ${detected.size} interfaces" + detected.joinToString(", ") { "${it.ifName}=${it.protocol}" }) saveServersToConfig(detected) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt index 14c476a0..cfda8776 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/viewmodel/ChatViewModel.kt @@ -86,6 +86,20 @@ class ChatViewModel : ViewModel() { @Volatile private var realPlaybackElapsedMs = 0 + private val _foreground = MutableStateFlow(false) + + fun setForeground(fg: Boolean) { + _foreground.value = fg + if (fg) { + _isStuck.value = false + if (dbReady && repo != null) { + refreshChannels() + refreshLocalSockets() + _currentChannel.value?.let { refreshMessages(it.id); refreshMembers(it.id) } + } + } + } + init { viewModelScope.launch { AppEventHandler.events.collect { (type, data) -> handleEvent(type, data) } @@ -104,37 +118,43 @@ class ChatViewModel : ViewModel() { } } viewModelScope.launch { - while (isActive) { - delay(3000) - if (!dbReady || !NativeLib.isReady()) { _isStuck.value = true; continue } - NativeLib.ping() - delay(100) - _isStuck.value = !NativeLib.isResponsive() + _foreground.collectLatest { fg -> + while (fg && isActive) { + delay(3000) + if (!dbReady || !NativeLib.isReady()) { _isStuck.value = true; continue } + NativeLib.ping() + delay(100) + _isStuck.value = !NativeLib.isResponsive() + } } } viewModelScope.launch { - while (isActive) { - if (_isRecording.value) { - _recordingDurationMs.value = (System.currentTimeMillis() - recordingStartTimeMs).toInt() + _foreground.collectLatest { fg -> + while (fg && isActive) { + if (_isRecording.value) { + _recordingDurationMs.value = (System.currentTimeMillis() - recordingStartTimeMs).toInt() + } + delay(80) } - delay(80) } } viewModelScope.launch { - while (isActive) { - delay(50) - val s = _playbackState.value - if (s.playing && !s.paused) { - val real = realPlaybackElapsedMs - val targetMs = if (real >= s.durMs && s.durMs > 0) s.durMs else real - val newMs = (s.elapsedMs + (targetMs - s.elapsedMs) * 0.3f).toInt().coerceIn(0, s.durMs) - val newProg = if (s.durMs > 0) newMs.toFloat() / s.durMs else 0f - - if (real >= s.durMs && s.durMs > 0 && newMs >= s.durMs - 30) { - _playbackState.value = PlaybackState() - realPlaybackElapsedMs = 0 - } else { - _playbackState.value = s.copy(elapsedMs = newMs, progress = newProg) + _foreground.collectLatest { fg -> + while (fg && isActive) { + delay(50) + val s = _playbackState.value + if (s.playing && !s.paused) { + val real = realPlaybackElapsedMs + val targetMs = if (real >= s.durMs && s.durMs > 0) s.durMs else real + val newMs = (s.elapsedMs + (targetMs - s.elapsedMs) * 0.3f).toInt().coerceIn(0, s.durMs) + val newProg = if (s.durMs > 0) newMs.toFloat() / s.durMs else 0f + + if (real >= s.durMs && s.durMs > 0 && newMs >= s.durMs - 30) { + _playbackState.value = PlaybackState() + realPlaybackElapsedMs = 0 + } else { + _playbackState.value = s.copy(elapsedMs = newMs, progress = newProg) + } } } } diff --git a/tools/chatgui-android/jni_bridge/android_udp_log.c b/tools/chatgui-android/jni_bridge/android_udp_log.c index b8eabe6c..21d7f9bf 100644 --- a/tools/chatgui-android/jni_bridge/android_udp_log.c +++ b/tools/chatgui-android/jni_bridge/android_udp_log.c @@ -53,7 +53,6 @@ static void udp_open_socket(void) { } ULOG("socket opened fd=%d -> %s:%d", g_udp_fd, inet_ntoa(g_udp_addr.sin_addr), (int)ntohs(g_udp_addr.sin_port)); - udp_reschedule_timer(); } static void udp_do_send(const char* data, int len) { @@ -80,7 +79,8 @@ void udp_trampoline(void* arg) { g_udp_buf_len = 0; } g_udp_timer = NULL; - if (g_udp_fd >= 0) udp_reschedule_timer(); + /* перевзводим только если остались неотправленные данные */ + if (g_udp_buf_len > 0) udp_reschedule_timer(); } void udp_log_set_target(const char* ip, int port) { @@ -116,6 +116,8 @@ void udp_log_write(const char* data, int len) { if (g_udp_buf_len >= UDP_FLUSH_BYTES) { udp_do_send(g_udp_buf, g_udp_buf_len); g_udp_buf_len = 0; + } else { + udp_reschedule_timer(); /* взвести flush для частичных данных */ } } diff --git a/tools/chatgui/src/main.cpp b/tools/chatgui/src/main.cpp index ddfa90f6..baf76cfd 100644 --- a/tools/chatgui/src/main.cpp +++ b/tools/chatgui/src/main.cpp @@ -130,6 +130,10 @@ int main(int argc, char *argv[]) { if (!QDir::isAbsolutePath(dbPath)) dbPath = QCoreApplication::applicationDirPath() + "/" + dbPath; QDir(dbPath).mkpath("."); + if (cfg.dbPath() != dbPath) { + cfg.setDbPath(dbPath); + cfg.save(); + } DbManager db; MainWindow w(nullptr, &db, cfgPath, dbPath, cfg.debugFile(), diff --git a/tools/chatgui/transport/node_config.cpp b/tools/chatgui/transport/node_config.cpp index 646bd35c..19aec409 100644 --- a/tools/chatgui/transport/node_config.cpp +++ b/tools/chatgui/transport/node_config.cpp @@ -328,6 +328,7 @@ bool NodeConfig::saveFull() { if (!m_privKey.isEmpty()) out << "my_private_key=" << m_privKey << "\n"; if (!m_pubKey.isEmpty()) out << "my_public_key=" << m_pubKey << "\n"; out << "auto_sockets=" << (m_autoSockets ? "yes" : "no") << "\n"; + out << "db_path=" << (m_dbPath.isEmpty() ? "chat_data" : m_dbPath) << "\n"; out << "\n"; for (auto& srv : m_servers) { @@ -404,6 +405,7 @@ bool NodeConfig::save() { config_update_in_lines(lines, "global", "my_private_key", m_privKey, true); config_update_in_lines(lines, "global", "my_public_key", m_pubKey, true); config_update_in_lines(lines, "global", "auto_sockets", m_autoSockets ? "yes" : "no", true); + config_update_in_lines(lines, "global", "db_path", m_dbPath.isEmpty() ? "chat_data" : m_dbPath, true); if (!m_controlAddr.isEmpty()) { int sep = m_controlAddr.lastIndexOf(':'); diff --git a/tools/chatgui/transport/node_config.h b/tools/chatgui/transport/node_config.h index 7ca5256c..81597277 100644 --- a/tools/chatgui/transport/node_config.h +++ b/tools/chatgui/transport/node_config.h @@ -47,6 +47,7 @@ public: /* GUI settings (from [gui] section) */ QString dbPath() const { return m_dbPath; } + void setDbPath(const QString& p) { m_dbPath = p; } QString debugFile() const { return m_debugFile; } QString debugLevel() const { return m_debugLevel; } QString debugCategories() const { return m_debugCategories; }