From 656da48e357cbb9b93ab6f55f50bfa29636f4dc6 Mon Sep 17 00:00:00 2001 From: evgeny Date: Fri, 4 Sep 2026 13:21:24 +0300 Subject: [PATCH] =?UTF-8?q?topo=5Fgroup:=20=D0=B0=D1=81=D0=B8=D0=BC=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D1=80=D0=B8=D1=87=D0=BD=D1=8B=D0=B9=20=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D1=80=D1=82=20BGP=20(JOIN=5FGROUP)=20+=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=20group=5Fid/flags=20=D0=B8=D0=B7=20TOPO=5FN?= =?UTF-8?q?ODE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UTUN: BGP инициирует только сторона-клиент ([client]) и явные etcp_connect; сервер добавляет пира по JOIN_GROUP (фикс обратного маршрута: сервер не создавал узел клиента из-за дедупа senders_list) - topo_group_new_conn: conn_add_path до дедупа; handle_request_table без add_to_senders - TOPO_NODE: убраны group_id/flags (shared-реестр), передаются в topo_node_serialize - routing/control_server: явный TOPO_GROUP_UTUN - CHAT: гейт по peers_* (get_member_channels) + пассивное add/remove в member_sync - tests: тесты адаптированы (route_ping ждёт registry+v4_addrs) - chatgui-android: voice_recorder_init в utun_bridge_voice_start (idempotent) --- src/chat/member_sync.c | 19 +++ src/config_parser.c | 11 ++ src/config_parser.h | 3 + src/control_server.c | 2 +- src/media_delivery/media_download.c | 5 + src/routing_layer/routing.c | 2 +- src/routing_layer/topo_group.c | 137 +++++++++++++++--- src/routing_layer/topo_group.h | 10 ++ src/routing_layer/topo_group_invite.c | 2 +- src/routing_layer/topo_node.c | 16 +- src/routing_layer/topo_node.h | 6 +- src/routing_layer/topo_node_doc.md | 4 +- src/routing_layer/topo_node_sqlite.c | 41 ++++++ src/routing_layer/topo_node_sqlite.h | 4 + tests/test_auto_socket_dynamic.c | 2 +- tests/test_etcp_connect.c | 2 +- tests/test_etcp_router_reconnect.c | 2 +- tests/test_route_ping.c | 7 +- .../jni_bridge/android_jni_bridge.c | 1 + .../libutun_lite/voice_recorder.c | 6 +- 20 files changed, 236 insertions(+), 46 deletions(-) diff --git a/src/chat/member_sync.c b/src/chat/member_sync.c index b98b191e..9f9292fd 100644 --- a/src/chat/member_sync.c +++ b/src/chat/member_sync.c @@ -497,6 +497,16 @@ int member_sync_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id, if (rc == 0) changed = 0; /* запись была no-op (данные идентичны) */ } + /* ── пассивное добавление: если узел уже подключён напрямую — добавить в CHAT-группу ── */ + if (changed && inst->topo_groups) { + uint64_t gid = strtoull(ch_id, NULL, 10); + struct TOPO_GROUP* g = topo_groups_find(inst->topo_groups, gid); + if (g && g->group_type == TOPO_GROUP_TYPE_CHAT) { + struct ETCP_CONN* c = instance_find_conn(inst, m->node_id); + if (c) topo_group_new_conn(g, c); + } + } + DEBUG_DEBUG(DEBUG_CATEGORY_MEMBER_SYNC, "%s: apply_record ch=%s nid=0x%016llx → changed=%d stale=%d", MS_ID, ch_id, (unsigned long long)m->node_id, changed, stale); return (changed ? MS_APPLY_CHANGED : 0) | (stale ? MS_APPLY_STALE : 0); @@ -608,6 +618,15 @@ int member_sync_verify_and_purge(struct UTUN_INSTANCE* inst, const char* ch_id) MS_ID, (unsigned long long)nid, ch_id); topo_node_sqlite_member_del(db, ch_id, nid); merkle_sync_recompute_path(inst, ch_id, nid); + /* пассивное удаление: если узел подключён напрямую — убрать из CHAT-группы */ + if (inst->topo_groups) { + uint64_t gid = strtoull(ch_id, NULL, 10); + struct TOPO_GROUP* g = topo_groups_find(inst->topo_groups, gid); + if (g && g->group_type == TOPO_GROUP_TYPE_CHAT) { + struct ETCP_CONN* c = instance_find_conn(inst, nid); + if (c) topo_group_remove_conn(g, c); + } + } purged++; } } diff --git a/src/config_parser.c b/src/config_parser.c index 71b025b4..5038c4f6 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -1569,4 +1569,15 @@ int update_config_keys(const char *filename, const char *priv_key, const char *p fclose(fp); return 0; +} + +int config_peer_in_clients(const struct utun_config* config, const uint8_t* pubkey) { + if (!config || !pubkey) return 0; + uint8_t pk_bin[SC_PUBKEY_SIZE]; + for (const struct CFG_CLIENT* cli = config->clients; cli; cli = cli->next) { + if (!cli->peer_public_key_hex[0]) continue; + if (hex_to_binary(cli->peer_public_key_hex, pk_bin, SC_PUBKEY_SIZE) != 0) continue; + if (memcmp(pk_bin, pubkey, SC_PUBKEY_SIZE) == 0) return 1; + } + return 0; } \ No newline at end of file diff --git a/src/config_parser.h b/src/config_parser.h index a2f33cda..0f99b73d 100644 --- a/src/config_parser.h +++ b/src/config_parser.h @@ -228,6 +228,9 @@ void print_config(const struct utun_config *config); void print_chatserver_invite_link(const struct utun_config *config, const struct CFG_SERVER *servers); int update_config_keys(const char *filename, const char *priv_key, const char *pub_key); +/* Узел (по pubkey, 32 байта) присутствует в списке [client] конфига? 1=да, 0=нет. */ +int config_peer_in_clients(const struct utun_config* config, const uint8_t* pubkey); + #ifdef __cplusplus } #endif diff --git a/src/control_server.c b/src/control_server.c index d214ed65..7b94bc25 100644 --- a/src/control_server.c +++ b/src/control_server.c @@ -1390,7 +1390,7 @@ static void send_single_node_info(struct control_server* server, struct control_ struct TOPO_NODE* ni = topo_node_registry_find(server->instance->topo_groups, nq->node_id); if (!ni) return; uint8_t buf[8192]; - int ser_len = topo_node_serialize(ni, nq, buf + 2, sizeof(buf) - 2, 0); + int ser_len = topo_node_serialize(ni, nq, TOPO_GROUP_UTUN, TOPO_FLAG_SEND_SUBNETS, buf + 2, sizeof(buf) - 2, 0); if (ser_len < 0) { DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to serialize node info"); return; } buf[0] = CTRL_BGP_CMD; buf[1] = CTRL_BGP_SUBCMD; size_t data_size = 2 + (size_t)ser_len; diff --git a/src/media_delivery/media_download.c b/src/media_delivery/media_download.c index 3ef28c5a..d14c1202 100644 --- a/src/media_delivery/media_download.c +++ b/src/media_delivery/media_download.c @@ -61,6 +61,11 @@ static void md_mkdir_parent(const char* filepath) { static int md_dl_send(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t dst, const uint8_t* data, size_t len) { if (!inst || !inst->topo_groups || !inst->connections) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "%s: md_dl_send: no routing (inst=%p topo=%p conn=%p)", MDL_ID, (void*)inst, (void*)(inst ? inst->topo_groups : NULL), (void*)(inst ? inst->connections : NULL)); return -1; } + { struct TOPO_GROUP* grp = topo_groups_find(inst->topo_groups, group_id); + struct ETCP_CONN* gc = grp ? topo_group_find_conn_for_node(grp, dst) : NULL; + DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "%s: md_dl_send group=0x%016llx dst=0x%016llx grp=%p conn_in_group=%p", + MDL_ID, (unsigned long long)group_id, (unsigned long long)dst, (void*)grp, (void*)gc); + } struct ll_entry* e = queue_entry_new(0); if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "%s: queue_entry_new failed for send to 0x%016llx", MDL_ID, (unsigned long long)dst); return -1; } e->dgram = u_malloc(len + 1); diff --git a/src/routing_layer/routing.c b/src/routing_layer/routing.c index 6676f898..e23248ea 100644 --- a/src/routing_layer/routing.c +++ b/src/routing_layer/routing.c @@ -145,7 +145,7 @@ void route_pkt(struct UTUN_INSTANCE* instance, struct ll_entry* entry, uint64_t } else { DEBUG_TRACE(DEBUG_CATEGORY_ROUTING, "route_pkt: sending %zu bytes to node %016llx dst=%s", ip_len, (unsigned long long)nq->node_id, ip_to_str(&addr, AF_INET).str); - int send_err = etcp_route_send(instance, rni->group_id, nq->node_id, entry, 1); + int send_err = etcp_route_send(instance, TOPO_GROUP_UTUN, nq->node_id, entry, 1); if (send_err != 0) { DEBUG_WARN(DEBUG_CATEGORY_ROUTING, "route_pkt: etcp_route_send failed: dst=%s err=%d", ip_to_str(&addr, AF_INET).str, send_err); diff --git a/src/routing_layer/topo_group.c b/src/routing_layer/topo_group.c index a6ee7696..a06b6046 100644 --- a/src/routing_layer/topo_group.c +++ b/src/routing_layer/topo_group.c @@ -48,6 +48,20 @@ static void topo_group_send_table_request(struct TOPO_GROUP* group, struct ETCP_ if (etcp_send(conn, e) != 0) { u_free(req); queue_entry_free(e); } } +static void topo_group_send_join_group(struct TOPO_GROUP* group, struct ETCP_CONN* conn) { + if (!group || !conn) return; + DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Sending join group request to %s grp=%016llx", conn->log_name, (unsigned long long)group->group_id); + struct TOPOMSG_JOIN_GROUP* req = u_calloc(1, sizeof(struct TOPOMSG_JOIN_GROUP)); + if (!req) return; + req->cmd = ETCP_ID_TOPO_ENTRY; + req->subcmd = TOPO_SUBCMD_JOIN_GROUP; + req->group_id = group->group_id; + struct ll_entry* e = queue_entry_new(0); + if (!e) { u_free(req); return; } + e->dgram = (uint8_t*)req; e->len = sizeof(struct TOPOMSG_JOIN_GROUP); + if (etcp_send(conn, e) != 0) { u_free(req); queue_entry_free(e); } +} + static void topo_group_send_table_complete(struct TOPO_GROUP* group, struct ETCP_CONN* conn) { if (!group || !conn) return; struct TOPOMSG_TABLE_REQ* req = u_calloc(1, sizeof(struct TOPOMSG_TABLE_REQ)); @@ -65,6 +79,7 @@ static void topo_group_add_to_senders(struct TOPO_GROUP* group, struct ETCP_CONN static bool topo_group_should_send_to(const struct TOPO_GROUP_NODE* nq, uint64_t target_id); static void topo_group_send_full_table(struct TOPO_GROUP* group, struct ETCP_CONN* conn); static void topo_group_handle_request_table(struct TOPO_GROUP* group, struct ETCP_CONN* conn); +static void topo_group_handle_join_group(struct TOPO_GROUP* group, struct ETCP_CONN* conn); static void nodeinfo_dump_log(const uint8_t* data, size_t len) { if (!data || len < sizeof(struct TOPOMSG_NODEINFO_PKT)) return; @@ -114,6 +129,7 @@ static const char* group_subcmd_name(uint8_t subcmd) { case TOPO_SUBCMD_WITHDRAW: return "WITHDRAW"; case TOPO_SUBCMD_TABLE_COMPLETE: return "TABLE_COMPLETE"; case TOPO_SUBCMD_ERR_GROUP_MISMATCH: return "ERR_GROUP_MISMATCH"; + case TOPO_SUBCMD_JOIN_GROUP: return "JOIN_GROUP"; default: return "?"; } } @@ -164,6 +180,8 @@ static void topo_group_receive_cbk(struct ETCP_CONN* from_conn, struct ll_entry* pkt_group_id = ((struct TOPOMSG_TABLE_REQ*)data)->group_id; } else if (subcmd == TOPO_SUBCMD_ERR_GROUP_MISMATCH && entry->len >= sizeof(struct TOPOMSG_ERR_GROUP_MISMATCH)) { pkt_group_id = ((struct TOPOMSG_ERR_GROUP_MISMATCH*)data)->group_id; + } else if (subcmd == TOPO_SUBCMD_JOIN_GROUP && entry->len >= sizeof(struct TOPOMSG_JOIN_GROUP)) { + pkt_group_id = ((struct TOPOMSG_JOIN_GROUP*)data)->group_id; } struct TOPO_GROUP* group = topo_groups_find(instance->topo_groups, pkt_group_id); @@ -177,6 +195,7 @@ static void topo_group_receive_cbk(struct ETCP_CONN* from_conn, struct ll_entry* if (subcmd == TOPO_SUBCMD_NODEINFO) { nodeinfo_dump_log(data, entry->len); topo_group_process_nodeinfo(group, from_conn, data, entry->len); } else if (subcmd == TOPO_SUBCMD_WITHDRAW) topo_group_process_withdraw(group, from_conn, data, entry->len); else if (subcmd == TOPO_SUBCMD_REQUEST_TABLE) topo_group_handle_request_table(group, from_conn); + else if (subcmd == TOPO_SUBCMD_JOIN_GROUP) topo_group_handle_join_group(group, from_conn); else if (subcmd == TOPO_SUBCMD_TABLE_COMPLETE) { etcp_set_routing_exchange_state(from_conn, 3); DEBUG_INFO(DEBUG_CATEGORY_BGP, "BGP sync complete with %s: %d nodes grp=%016llx", from_conn->log_name, queue_entry_count(group->nodes), (unsigned long long)group->group_id); } else if (subcmd == TOPO_SUBCMD_ERR_GROUP_MISMATCH) { if (entry->len >= sizeof(struct TOPOMSG_ERR_GROUP_MISMATCH)) { @@ -194,14 +213,77 @@ static void topo_group_receive_cbk(struct ETCP_CONN* from_conn, struct ll_entry* // Init / Destroy / New / Remove conn // ============================================================================ +/* Создать TOPO_GROUP_NODE (если нет) + прямой путь через conn (hop=1). Без BGP. + Используется из topo_group_new_conn (add + init BGP) и из приёма TABLE_REQ (входящий запросил BGP). */ +static void topo_group_conn_add_path(struct TOPO_GROUP* group, struct ETCP_CONN* conn, uint64_t node_id) { + if (!group || !conn || !node_id || node_id == group->instance->node_id) return; + + /* узел в реестре — иначе загрузить из БД (nodes + node_addresses), чтобы форвардинг мог сериализовать */ + { struct TOPO_GROUPS* groups = group->instance->topo_groups; + struct TOPO_NODE* ni = topo_node_registry_find(groups, node_id); + if (!ni) { + sqlite3* db = group->instance->topo_sqlite_db; + ni = db ? topo_node_sqlite_node_load(db, groups, node_id) : NULL; + if (ni) ni = topo_node_registry_store(groups, ni); + } + } + + struct TOPO_GROUP_NODE* nq = topo_node_find_by_id(group, node_id); + if (nq && nq->paths) { + struct ll_entry* pe = nq->paths->head; + while (pe) { if (((struct TOPO_NODEPATH*)pe)->conn == conn) return; pe = pe->next; } + } + if (!nq) { + struct ll_entry* qe = queue_entry_new(sizeof(struct TOPO_GROUP_NODE)); + if (!qe) return; + nq = (struct TOPO_GROUP_NODE*)qe; + memset((uint8_t*)nq + sizeof(struct ll_entry), 0, sizeof(*nq) - sizeof(struct ll_entry)); + nq->node_id = node_id; + queue_data_put_with_index(group->nodes, &nq->ll); + } + uint64_t hop[1] = { node_id }; + topo_group_add_path(nq, conn, hop, 1, 0); + nq->conn_presence |= NCONN_DIRECT; + nq->conn_up |= NCONN_DIRECT; + + DEBUG_INFO(DEBUG_CATEGORY_BGP, "conn_add_path: node=0x%016llx grp=%016llx ch=%s conn=%s", + (unsigned long long)node_id, (unsigned long long)group->group_id, group->channel_id, conn->log_name); +} + static void topo_group_conn_status(struct ETCP_CONN* conn, int status, void* arg) { struct TOPO_GROUPS* groups = (struct TOPO_GROUPS*)arg; if (!conn || !groups) return; - struct ll_entry* fe = groups->group_list->head; - while (fe) { struct TOPO_GROUP* g = (struct TOPO_GROUP*)fe; fe = fe->next; - if (status == ETCP_CONN_STATUS_UP) topo_group_new_conn(g, conn); - if (status == ETCP_CONN_STATUS_DOWN) topo_group_remove_conn(g, conn); - if (status == ETCP_CONN_STATUS_DELETE) topo_group_remove_conn(g, conn); + + if (status == ETCP_CONN_STATUS_UP && conn->peer_node_id) { + /* non-CHAT (UTUN): узлы из конфига (clients) + явные подключения (etcp_connect). */ + int explicit_conn = conn->bgp_ready_cbk != NULL; + struct ll_entry* fe = groups->group_list->head; + while (fe) { + struct TOPO_GROUP* g = (struct TOPO_GROUP*)fe; fe = fe->next; + if (g->group_type != TOPO_GROUP_TYPE_CHAT) { + int is_client = conn->instance->config && config_peer_in_clients(conn->instance->config, conn->crypto_ctx.peer_public_key); + if (is_client || explicit_conn) { + topo_group_new_conn(g, conn); /* добавить + инициировать BGP */ + topo_group_send_join_group(g, conn); /* запросить членство у пира */ + } + } + } + /* CHAT: только члены каналов (peers_* в БД). */ + sqlite3* db = conn->instance ? conn->instance->topo_sqlite_db : NULL; + uint64_t* chs = NULL; int chn = 0; + if (db && topo_node_sqlite_get_member_channels(db, conn->peer_node_id, &chs, &chn) == 0 && chn > 0) { + for (int i = 0; i < chn; i++) { + struct TOPO_GROUP* cg = topo_groups_find(groups, chs[i]); + if (cg && cg->group_type == TOPO_GROUP_TYPE_CHAT) + topo_group_new_conn(cg, conn); + } + } + u_free(chs); + } else if (status == ETCP_CONN_STATUS_DOWN || status == ETCP_CONN_STATUS_DELETE) { + struct ll_entry* fe = groups->group_list->head; + while (fe) { struct TOPO_GROUP* g = (struct TOPO_GROUP*)fe; fe = fe->next; + topo_group_remove_conn(g, conn); + } } } @@ -467,9 +549,21 @@ void topo_group_new_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn) { if (!conn->instance) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "conn->instance is NULL"); return; } if (!conn->instance->rt) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "conn->instance->rt is NULL"); return; } + topo_recovery_cancel_for_node(group, conn->peer_node_id); + + /* создать узел + прямой путь (сразу, не дожидаясь NODEINFO; дедуп внутри по conn). + Делаем ДО проверки senders_list: conn может уже быть в senders_list (через handle_request_table), + но узел ещё не создан (входящий JOIN_GROUP). */ + topo_group_conn_add_path(group, conn, conn->peer_node_id); + { struct TOPO_GROUP_NODE* peer_nq = topo_node_find_by_id(group, conn->peer_node_id); + if (peer_nq) { + peer_nq->connectivity.last_ping_time = get_time_tb(); + if (conn->instance->topo_sqlite_db) topo_node_sqlite_nodeinfo_updated(conn->instance->topo_sqlite_db, conn->peer_node_id); + } + } + /* дедуп: тот же conn стреляет ETCP_CONN_STATUS_UP дважды (UDP-линк, затем TCP-линк). - * Если conn уже в senders_list — повторно не обрабатываем, иначе active_conn_count - * задваивается и переподключение никогда не стартует. */ + * Если conn уже в senders_list — не слать повторно TABLE_REQ и не задваивать active_conn_count. */ { struct ll_entry* se = group->senders_list ? group->senders_list->head : NULL; while (se) { if (((struct TOPO_GROUP_CONN_ITEM*)se->data)->conn == conn) { @@ -480,15 +574,6 @@ void topo_group_new_conn(struct TOPO_GROUP* group, struct ETCP_CONN* conn) { } } - topo_recovery_cancel_for_node(group, conn->peer_node_id); - - struct TOPO_GROUP_NODE* peer_nq = topo_node_find_by_id(group, conn->peer_node_id); - if (peer_nq) { - peer_nq->connectivity.last_ping_time = get_time_tb(); - peer_nq->conn_presence |= NCONN_DIRECT; - peer_nq->conn_up |= NCONN_DIRECT; - if (conn->instance->topo_sqlite_db) topo_node_sqlite_nodeinfo_updated(conn->instance->topo_sqlite_db, conn->peer_node_id); - } topo_group_add_to_senders(group, conn); DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "topo_group_new_conn: peer=%016llx group=%016llx type=%d ch=%s", (unsigned long long)conn->peer_node_id, (unsigned long long)group->group_id, group->group_type, group->channel_id); @@ -740,8 +825,6 @@ int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from if (nodeinfo1) { { struct TOPO_NODE* stored = topo_node_registry_store(group->instance->topo_groups, new_ni); if (stored != new_ni) new_ni = stored; } - new_ni->group_id = group->group_id; - new_ni->flags = pkt->node.flags; nodeinfo1->node_id = new_ni ? new_ni->node_id : 0; nodeinfo1->subnets = new_subnets; } else { @@ -751,8 +834,6 @@ int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from memset((uint8_t*)nodeinfo1 + sizeof(struct ll_entry), 0, sizeof(*nodeinfo1) - sizeof(struct ll_entry)); { struct TOPO_NODE* stored = topo_node_registry_store(group->instance->topo_groups, new_ni); if (stored != new_ni) new_ni = stored; } - new_ni->group_id = group->group_id; - new_ni->flags = pkt->node.flags; nodeinfo1->node_id = new_ni ? new_ni->node_id : 0; nodeinfo1->subnets = new_subnets; nodeinfo1->connectivity.probe_status = PROBE_STATUS_NONE; @@ -888,8 +969,9 @@ void topo_group_send_nodeinfo(struct TOPO_GROUP* group, struct TOPO_GROUP_NODE* u_free(p); return; } - DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "send_nodeinfo: node %016llx ver=%d grp=%016llx to conn=%s", (unsigned long long)node->node_id, sni->ver, (unsigned long long)sni->group_id, conn->log_name); - int ser_len = topo_node_serialize(sni, node, p + 2, max_sz - 2, cumulative_rtt); + DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "send_nodeinfo: node %016llx ver=%d grp=%016llx to conn=%s", (unsigned long long)node->node_id, sni->ver, (unsigned long long)group->group_id, conn->log_name); + uint8_t sflags = (group->group_type == TOPO_GROUP_TYPE_CHAT) ? 0 : TOPO_FLAG_SEND_SUBNETS; + int ser_len = topo_node_serialize(sni, node, group->group_id, sflags, p + 2, max_sz - 2, cumulative_rtt); if (ser_len < 0) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "send_nodeinfo: serialize failed for node %016llx", (unsigned long long)node->node_id); u_free(p); return; } struct ll_entry* e = queue_entry_new(0); @@ -941,10 +1023,19 @@ static void topo_group_handle_request_table(struct TOPO_GROUP* group, struct ETC if (!group || !conn) return; topo_group_send_nodeinfo(group, group->local_node, conn, 0); topo_group_send_full_table(group, conn); - topo_group_add_to_senders(group, conn); + /* senders_list заполняется только через topo_group_new_conn (add + BGP); + здесь не добавляем, иначе new_conn (на JOIN_GROUP) упирается в дедуп и не шлёт TABLE_REQ обратно. */ topo_group_send_table_complete(group, conn); } +static void topo_group_handle_join_group(struct TOPO_GROUP* group, struct ETCP_CONN* conn) { + if (!group || !conn) return; + DEBUG_INFO(DEBUG_CATEGORY_BGP, "handle_join_group: from %s grp=%016llx type=%d", conn->log_name, (unsigned long long)group->group_id, group->group_type); + /* входящий запросил членство: для non-CHAT (UTUN) — добавить запросившего узла + инициировать BGP обратно */ + if (group->group_type != TOPO_GROUP_TYPE_CHAT) + topo_group_new_conn(group, conn); +} + /* ── BGP node event callbacks ── */ void topo_group_add_node_cbk(struct TOPO_GROUP* group, topo_node_event_fn fn, void* arg) { diff --git a/src/routing_layer/topo_group.h b/src/routing_layer/topo_group.h index fa934ec7..3b74e50e 100644 --- a/src/routing_layer/topo_group.h +++ b/src/routing_layer/topo_group.h @@ -96,6 +96,7 @@ void topo_fire_nodeinfo_cbk(struct UTUN_INSTANCE* instance, struct TOPO_GROUP* g #define TOPO_SUBCMD_WITHDRAW 0x06 // узел стал недоступен #define TOPO_SUBCMD_TABLE_COMPLETE 0x0B // завершение начальной синхронизации таблицы #define TOPO_SUBCMD_ERR_GROUP_MISMATCH 0x0C // ошибка несоответствия типа группы +#define TOPO_SUBCMD_JOIN_GROUP 0x0D // запрос членства в группе #define MAX_HOPS 16 #define BGP_NODES_HASH_SIZE 256 @@ -129,6 +130,15 @@ struct TOPOMSG_TABLE_REQ { uint64_t group_id; // идентификатор группы } __attribute__((packed)); +/** + * @brief Пакет запроса членства в группе (отправитель просит добавить его в группу) + */ +struct TOPOMSG_JOIN_GROUP { + uint8_t cmd; + uint8_t subcmd; + uint64_t group_id; // идентификатор группы +} __attribute__((packed)); + /** * @brief Пакет ошибки несоответствия типа группы */ diff --git a/src/routing_layer/topo_group_invite.c b/src/routing_layer/topo_group_invite.c index d5d9b2a8..20cb9c77 100644 --- a/src/routing_layer/topo_group_invite.c +++ b/src/routing_layer/topo_group_invite.c @@ -436,7 +436,7 @@ int topo_group_invite_join(struct UTUN_INSTANCE* inst, uint64_t group_id, /* 2. Копировать ni в group_ni */ struct TOPO_GROUPS* groups = inst->topo_groups; struct TOPO_NODE* gni = u_calloc(1, sizeof(*gni)); if (!gni) return -1; - gni->node_id = nid; gni->ver = 1; gni->group_id = group_id; memcpy(gni->public_key, ni->public_key, SC_PUBKEY_SIZE); + gni->node_id = nid; gni->ver = 1; memcpy(gni->public_key, ni->public_key, SC_PUBKEY_SIZE); for (const struct TOPO_ADDR4* src = ni->v4_addrs; src; src = src->next) { struct TOPO_ADDR4* a4 = memory_pool_alloc(groups->v4_addr_pool); if (!a4) continue; memset(a4, 0, sizeof(*a4)); memcpy(a4->addr, src->addr, 4); a4->port = src->port; diff --git a/src/routing_layer/topo_node.c b/src/routing_layer/topo_node.c index 4af685c6..b396807f 100644 --- a/src/routing_layer/topo_node.c +++ b/src/routing_layer/topo_node.c @@ -259,13 +259,15 @@ int topo_node_dyn_size(const struct TOPOMSG_NODE* msg) { + msg->hop_count * 8; } -int topo_node_serialize(struct TOPO_NODE* ni, struct TOPO_GROUP_NODE* nq, uint8_t* out, size_t out_max, uint16_t cumulative_rtt) { +int topo_node_serialize(struct TOPO_NODE* ni, struct TOPO_GROUP_NODE* nq, + uint64_t group_id, uint8_t flags, + uint8_t* out, size_t out_max, uint16_t cumulative_rtt) { if (!ni || !nq || !out) return -1; struct TOPOMSG_NODE msg; memset(&msg, 0, sizeof(msg)); - msg.flags = ni->flags; - msg.group_id = ni->group_id; + msg.flags = flags; + msg.group_id = group_id; msg.node_id = ni->node_id; msg.ver = ni->ver; memcpy(msg.public_key, ni->public_key, SC_PUBKEY_SIZE); @@ -339,8 +341,6 @@ int topo_node_deserialize(struct TOPO_GROUP* group, const uint8_t* data, size_t struct TOPO_NODE* ni = u_calloc(1, sizeof(struct TOPO_NODE)); if (!ni) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "alloc TOPO_NODE failed"); return -1; } ni->group_ref_count = 0; - ni->flags = msg->flags; - ni->group_id = msg->group_id; ni->node_id = msg->node_id; ni->ver = msg->ver; memcpy(ni->public_key, msg->public_key, SC_PUBKEY_SIZE); @@ -551,7 +551,7 @@ void topo_node_dump_all(struct TOPO_GROUP* group) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "--- Node #%d: id=%016llx name=\"%s\" ver=%u grp=%llu%s ---", idx, (unsigned long long)ni->node_id, name_s, ni->ver, - (unsigned long long)ni->group_id, is_local ? " [LOCAL]" : ""); + (unsigned long long)group->group_id, is_local ? " [LOCAL]" : ""); log_dump(DEBUG_LEVEL_INFO, DEBUG_CATEGORY_BGP, " pubkey ", ni->public_key, SC_PUBKEY_SIZE); log_dump(DEBUG_LEVEL_INFO, DEBUG_CATEGORY_BGP, " ed25519", ni->ed25519_public_key, SC_PUBKEY_SIZE); @@ -642,7 +642,7 @@ int topo_node_format_all(struct TOPO_GROUP* group, char* buf, size_t buf_size) { FMT_ADD("--- Node #%d: id=%016llx name=\"%s\" ver=%u grp=%llu%s ---\n", idx, (unsigned long long)ni->node_id, name_s, ni->ver, - (unsigned long long)ni->group_id, is_local ? " [LOCAL]" : ""); + (unsigned long long)group->group_id, is_local ? " [LOCAL]" : ""); FMT_ADD(" pubkey: "); for (int k = 0; k < SC_PUBKEY_SIZE; k++) FMT_ADD("%02x", ni->public_key[k]); FMT_ADD("\n"); FMT_ADD(" ed25519: "); for (int k = 0; k < SC_PUBKEY_SIZE; k++) FMT_ADD("%02x", ni->ed25519_public_key[k]); FMT_ADD("\n"); @@ -797,8 +797,6 @@ int topo_group_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct TOPO_GR struct TOPO_NODE* ni = u_calloc(1, sizeof(struct TOPO_NODE)); if (!ni) { u_free(lq); group->local_node = NULL; return -1; } ni->group_ref_count = 0; - ni->flags = (group->group_type == TOPO_GROUP_TYPE_CHAT) ? 0 : TOPO_FLAG_SEND_SUBNETS; - ni->group_id = group->group_id; ni->node_id = instance->node_id; ni->ver = (old_ver % 255) + 1; ni->client_type = instance->client_type; diff --git a/src/routing_layer/topo_node.h b/src/routing_layer/topo_node.h index 65a0a3a0..3a6b57b2 100644 --- a/src/routing_layer/topo_node.h +++ b/src/routing_layer/topo_node.h @@ -139,7 +139,7 @@ static inline int topo_list_count(struct _topo_head* head) { int n = 0; while (h struct TOPO_NODE { struct ll_entry ll; uint32_t group_ref_count; - uint8_t flags; uint64_t group_id, node_id; uint8_t ver; + uint64_t node_id; uint8_t ver; uint8_t public_key[SC_PUBKEY_SIZE], ed25519_public_key[SC_PUBKEY_SIZE]; uint8_t x25519_self_sig[64]; uint8_t client_type; // CLIENT_TYPE_SERVER/DESKTOP/MOBILE @@ -191,7 +191,9 @@ struct TOPO_GROUP_NODE* topo_node_find_by_id(struct TOPO_GROUP* group, uint64_t void topo_nodeq_free_group_fields(struct TOPO_GROUPS* groups, struct TOPO_GROUP_NODE* nq); int topo_node_dyn_size(const struct TOPOMSG_NODE* msg); -int topo_node_serialize(struct TOPO_NODE* ni, struct TOPO_GROUP_NODE* nq, uint8_t* out, size_t out_max, uint16_t cumulative_rtt); +int topo_node_serialize(struct TOPO_NODE* ni, struct TOPO_GROUP_NODE* nq, + uint64_t group_id, uint8_t flags, + uint8_t* out, size_t out_max, uint16_t cumulative_rtt); int topo_node_deserialize(struct TOPO_GROUP* group, const uint8_t* data, size_t len, struct TOPO_NODE** out_ni, struct TOPO_NODESUBNETS** out_subnets, uint64_t** out_hop_list, uint8_t* out_hop_count, uint16_t* out_cumulative_rtt); diff --git a/src/routing_layer/topo_node_doc.md b/src/routing_layer/topo_node_doc.md index c347909e..b57c8a37 100644 --- a/src/routing_layer/topo_node_doc.md +++ b/src/routing_layer/topo_node_doc.md @@ -37,7 +37,7 @@ topo_group_update_my_nodeinfo(instance, group); ```c struct TOPO_NODE* ni = topo_node_registry_find(groups, nq->node_id); uint8_t buf[4096]; -int len = topo_node_serialize(ni, nq, buf, sizeof(buf), cumulative_rtt); +int len = topo_node_serialize(ni, nq, group_id, flags, buf, sizeof(buf), cumulative_rtt); ``` Запаковывает `TOPO_NODE` + per-group данные (`TOPO_GROUP_NODE`) в буфер: заголовок `TOPOMSG_NODE`, затем имя узла, v4/v6 sock_meta, v4/v6 адреса, подсети (если @@ -143,7 +143,7 @@ topo_node_format_all(group, buf, size); // в строку **Сериализация:** - `topo_node_dyn_size(msg)` — размер динамической части по счётчикам заголовка. -- `topo_node_serialize(ni, nq, out, out_max, cumulative_rtt)` — сериализовать `TOPO_NODE` + per-group в буфер. +- `topo_node_serialize(ni, nq, group_id, flags, out, out_max, cumulative_rtt)` — сериализовать `TOPO_NODE` + per-group в буфер. - `topo_node_deserialize(group, data, len, &out_ni, &out_subnets, &out_hop_list, &out_hop_count, &out_cumulative_rtt)` — восстановить из wire-формата. **Пути:** diff --git a/src/routing_layer/topo_node_sqlite.c b/src/routing_layer/topo_node_sqlite.c index 370ba08f..e0de6657 100644 --- a/src/routing_layer/topo_node_sqlite.c +++ b/src/routing_layer/topo_node_sqlite.c @@ -9,6 +9,7 @@ #include "../lib/json_flat.h" #include #include +#include #define PEERS_JOIN_SIG_SIZE 64 #define PEERS_JOIN_TS_SIZE 8 @@ -168,6 +169,46 @@ int topo_node_sqlite_member_exists(sqlite3* db, uint64_t node_id) { return found; } +int topo_node_sqlite_get_member_channels(sqlite3* db, uint64_t node_id, + uint64_t** out_ch_ids, int* out_count) { + if (!db || !out_ch_ids || !out_count) return -1; + *out_ch_ids = NULL; *out_count = 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 -1; + + uint64_t* ids = NULL; int count = 0, cap = 0; + char sql[256]; + while (sqlite3_step(s) == SQLITE_ROW) { + const char* tbl = (const char*)sqlite3_column_text(s, 0); + if (!tbl || strncmp(tbl, "peers_", 6) != 0) 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); + int is_member = (sqlite3_step(m) == SQLITE_ROW); + sqlite3_finalize(m); + if (is_member) { + uint64_t ch = strtoull(tbl + 6, NULL, 10); + if (ch != 0) { + if (count == cap) { + cap = cap ? cap * 2 : 8; + uint64_t* tmp = u_realloc(ids, (size_t)cap * sizeof(uint64_t)); + if (!tmp) { u_free(ids); sqlite3_finalize(s); return -1; } + ids = tmp; + } + ids[count++] = ch; + } + } + } + } + sqlite3_finalize(s); + *out_ch_ids = ids; *out_count = count; + return 0; +} + int topo_node_sqlite_init(sqlite3* db) { if (!db) return -1; diff --git a/src/routing_layer/topo_node_sqlite.h b/src/routing_layer/topo_node_sqlite.h index c8a331c7..1c278a52 100644 --- a/src/routing_layer/topo_node_sqlite.h +++ b/src/routing_layer/topo_node_sqlite.h @@ -23,6 +23,10 @@ int topo_node_sqlite_addrs_put(sqlite3* db, uint64_t node_id, struct TOPO_NODE* /* Есть ли у узла запись мембера хотя бы в одной peers_ (реальный мембер с ключами). */ int topo_node_sqlite_member_exists(sqlite3* db, uint64_t node_id); +/* Список channel_id (uint64) всех peers_, где узел является членом. 0=ок. */ +int topo_node_sqlite_get_member_channels(sqlite3* db, uint64_t node_id, + uint64_t** out_ch_ids, int* out_count); + 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, diff --git a/tests/test_auto_socket_dynamic.c b/tests/test_auto_socket_dynamic.c index 1a5e36b3..a5b73da9 100644 --- a/tests/test_auto_socket_dynamic.c +++ b/tests/test_auto_socket_dynamic.c @@ -160,7 +160,7 @@ static struct TOPO_GROUP_NODE* mk_srv_node(void) { struct TOPO_GROUP_NODE* nq = u_calloc(1, sizeof(struct TOPO_GROUP_NODE)); struct TOPO_NODE* ni = u_calloc(1, sizeof(struct TOPO_NODE)); if (!nq || !ni) { u_free(nq); u_free(ni); return NULL; } - ni->group_ref_count = 1; ni->node_id = ctx.srv_node_id; ni->group_id = TOPO_GROUP_UTUN; + ni->group_ref_count = 1; ni->node_id = ctx.srv_node_id; memcpy(ni->public_key, ctx.srv_pubkey, SC_PUBKEY_SIZE); nq->ll.size = sizeof(struct TOPO_GROUP_NODE) - sizeof(struct ll_entry); nq->node_id = ctx.srv_node_id; diff --git a/tests/test_etcp_connect.c b/tests/test_etcp_connect.c index fd6b5cbe..a9b34752 100644 --- a/tests/test_etcp_connect.c +++ b/tests/test_etcp_connect.c @@ -72,7 +72,7 @@ static struct TOPO_GROUP_NODE* mknode(struct UTUN_INSTANCE* inst, uint64_t nid, struct TOPO_GROUP_NODE* nq = u_calloc(1, sizeof(struct TOPO_GROUP_NODE)); struct TOPO_NODE* ni = u_calloc(1, sizeof(struct TOPO_NODE)); if (!nq || !ni) { u_free(nq); u_free(ni); return NULL; } - ni->group_ref_count = 1; ni->node_id = nid; ni->ver = 0; ni->group_id = TOPO_GROUP_UTUN; + ni->group_ref_count = 1; ni->node_id = nid; ni->ver = 0; memcpy(ni->public_key, pubkey, SC_PUBKEY_SIZE); nq->ll.size = sizeof(struct TOPO_GROUP_NODE) - sizeof(struct ll_entry); nq->node_id = nid; diff --git a/tests/test_etcp_router_reconnect.c b/tests/test_etcp_router_reconnect.c index 074d76bd..d1e9b184 100644 --- a/tests/test_etcp_router_reconnect.c +++ b/tests/test_etcp_router_reconnect.c @@ -83,7 +83,7 @@ static struct TOPO_GROUP_NODE* mknode(struct UTUN_INSTANCE* inst, uint64_t nid, struct TOPO_GROUP_NODE* nq = u_calloc(1, sizeof(struct TOPO_GROUP_NODE)); struct TOPO_NODE* ni = u_calloc(1, sizeof(struct TOPO_NODE)); if (!nq || !ni) { u_free(nq); u_free(ni); return NULL; } - ni->group_ref_count = 1; ni->node_id = nid; ni->ver = 0; ni->group_id = TOPO_GROUP_UTUN; + ni->group_ref_count = 1; ni->node_id = nid; ni->ver = 0; memcpy(ni->public_key, pubkey, SC_PUBKEY_SIZE); nq->ll.size = sizeof(struct TOPO_GROUP_NODE) - sizeof(struct ll_entry); nq->node_id = nid; diff --git a/tests/test_route_ping.c b/tests/test_route_ping.c index 0b1e3706..6bbb6961 100644 --- a/tests/test_route_ping.c +++ b/tests/test_route_ping.c @@ -241,18 +241,19 @@ int main(void) { goto cleanup; } - // 2. Wait for BGP exchange so B learns about C + // 2. Wait for BGP exchange so B learns C (registry has C's nodeinfo + addresses) DEBUG_INFO(DEBUG_CATEGORY_BGP, "Waiting for BGP exchange (B learns C)..."); int bgp_wait_cycles = 0; while (!test_timed_out && bgp_wait_cycles < 500) { - if (topo_groups_get_default(inst_b->topo_groups) && topo_node_find_by_id(topo_groups_get_default(inst_b->topo_groups), g_node_id_c) != NULL) { + struct TOPO_NODE* ni_c = topo_node_registry_find(inst_b->topo_groups, g_node_id_c); + if (ni_c && ni_c->v4_addrs) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "B learned about C"); break; } uasync_poll(ua, 10); bgp_wait_cycles++; } - if (!topo_groups_get_default(inst_b->topo_groups) || topo_node_find_by_id(topo_groups_get_default(inst_b->topo_groups), g_node_id_c) == NULL) { + if (!topo_node_registry_find(inst_b->topo_groups, g_node_id_c)) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "B did not learn about C in time"); goto cleanup; } diff --git a/tools/chatgui-android/jni_bridge/android_jni_bridge.c b/tools/chatgui-android/jni_bridge/android_jni_bridge.c index 04b7c2a4..bcddb5ff 100644 --- a/tools/chatgui-android/jni_bridge/android_jni_bridge.c +++ b/tools/chatgui-android/jni_bridge/android_jni_bridge.c @@ -1245,6 +1245,7 @@ int utun_bridge_is_responsive(void) { * ────────────────────────────────────────────────────────────────── */ int utun_bridge_voice_start(const char* channel_id) { + voice_recorder_init(g_db_path); /* idempotent: гарантирует g_rec после stop/restart */ return voice_recorder_start(channel_id, 48000, 1); } diff --git a/tools/chatgui-android/libutun_lite/voice_recorder.c b/tools/chatgui-android/libutun_lite/voice_recorder.c index b6e1370e..0e58aab5 100644 --- a/tools/chatgui-android/libutun_lite/voice_recorder.c +++ b/tools/chatgui-android/libutun_lite/voice_recorder.c @@ -115,7 +115,11 @@ void voice_recorder_deinit(void) { int voice_recorder_start(const char* channel_id, int sample_rate, int channels) { pthread_mutex_lock(&g_init_mtx); - if (!g_rec) { pthread_mutex_unlock(&g_init_mtx); return -1; } + if (!g_rec) { + DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "voice_recorder_start: not initialized (g_rec=NULL)"); + pthread_mutex_unlock(&g_init_mtx); + return -1; + } pthread_mutex_lock(&g_rec->mtx); if (g_rec->active) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "voice_recorder_start: already active");