diff --git a/src/chat/member_sync.c b/src/chat/member_sync.c index d47abdcc..223631bf 100644 --- a/src/chat/member_sync.c +++ b/src/chat/member_sync.c @@ -941,6 +941,15 @@ void member_sync_destroy(struct UTUN_INSTANCE* inst) { ge = ge->next; } } + /* освободить callback-списки (иначе утекут при chat_core_destroy) */ + if (CC(inst)) { + struct ms_props_cbk* pc = CC(inst)->props_cbks; + while (pc) { struct ms_props_cbk* n = pc->next; u_free(pc); pc = n; } + CC(inst)->props_cbks = NULL; + struct ms_apply_cbk* ac = CC(inst)->apply_cbks; + while (ac) { struct ms_apply_cbk* n = ac->next; u_free(ac); ac = n; } + CC(inst)->apply_cbks = NULL; + } merkle_sync_destroy(inst); } diff --git a/src/media_delivery/media_delivery.c b/src/media_delivery/media_delivery.c index 6c172053..1f54f699 100644 --- a/src/media_delivery/media_delivery.c +++ b/src/media_delivery/media_delivery.c @@ -1232,17 +1232,20 @@ static void md_on_conn_status(struct ETCP_CONN* conn, int status, void* arg) { /* ── main dispatch ── */ static void md_etcp_recv(struct ETCP_CONN* conn, struct ll_entry* entry) { - if (!conn || !entry || !entry->dgram || entry->len < 1) return; + if (!conn || !entry || !entry->dgram || entry->len < 1) { + if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } + return; + } struct UTUN_INSTANCE* inst = conn->instance; - if (!inst) return; + if (!inst) { queue_dgram_free(entry); queue_entry_free(entry); return; } struct media_delivery_ctx* md = &inst->md; - if (!md->initialized) { DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "%s: recv before init", MD_ID); return; } + if (!md->initialized) { DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "%s: recv before init", MD_ID); queue_dgram_free(entry); queue_entry_free(entry); return; } const uint8_t* data = entry->dgram; size_t len = entry->len; /* Единый формат router_deliver: [svc_id][src_node_id(8)][dst_node_id(8)][subcmd][...] */ - if (len < ROUTER_SVC_PAYLOAD_OFF + 1) return; + if (len < ROUTER_SVC_PAYLOAD_OFF + 1) { queue_dgram_free(entry); queue_entry_free(entry); return; } uint64_t from_node; memcpy(&from_node, data + ROUTER_SVC_SRC_OFF, 8); uint8_t subcmd = data[ROUTER_SVC_PAYLOAD_OFF]; @@ -1315,6 +1318,7 @@ static void md_etcp_recv(struct ETCP_CONN* conn, struct ll_entry* entry) { DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "%s: unknown subcmd 0x%02x from 0x%016llx", MD_ID, subcmd, (unsigned long long)from_node); } + queue_dgram_free(entry); queue_entry_free(entry); } /* ── table creation ── */ diff --git a/src/routing_layer/conn_mgr_core.c b/src/routing_layer/conn_mgr_core.c index 4468179f..6092727b 100644 --- a/src/routing_layer/conn_mgr_core.c +++ b/src/routing_layer/conn_mgr_core.c @@ -814,13 +814,17 @@ void cm_handle_disconnect(struct CONN_MGR* mgr, uint64_t node_id) { } void conn_mgr_router_recv_handler(struct ETCP_CONN* conn, struct ll_entry* entry) { - if (!entry || !entry->dgram || entry->len < ROUTER_SVC_PAYLOAD_OFF + 2) return; + if (!entry || !entry->dgram || entry->len < ROUTER_SVC_PAYLOAD_OFF + 2) { + if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } + return; + } if (!conn || !conn->instance) { DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "conn_mgr_router_recv: conn=%p", (void*)conn); + queue_dgram_free(entry); queue_entry_free(entry); return; } uint8_t* d = entry->dgram + ROUTER_SVC_PAYLOAD_OFF; size_t len = entry->len - ROUTER_SVC_PAYLOAD_OFF; - if (d[0] != ETCP_RT_ID_CONN_MGR) return; + if (d[0] != ETCP_RT_ID_CONN_MGR) { queue_dgram_free(entry); queue_entry_free(entry); return; } uint8_t sub = d[1]; /* conn_mgr — per-group: резолвим группу из заголовка доставки, а не instance->conn_mgr */ uint64_t group_id; memcpy(&group_id, entry->dgram + ROUTER_SVC_GROUP_OFF, 8); @@ -836,6 +840,7 @@ void conn_mgr_router_recv_handler(struct ETCP_CONN* conn, struct ll_entry* entry case CM_SUBCMD_INTERM_SELECTED: if (mgr) cm_handle_interm_selected(mgr, src, d, len); break; case CM_SUBCMD_DISCONNECT: if (len >= CM_DISCONNECT_SIZE && mgr) cm_handle_disconnect(mgr, ((struct CM_DISCONNECT*)d)->node_id); break; } + queue_dgram_free(entry); queue_entry_free(entry); } int conn_mgr_open(struct UTUN_INSTANCE* inst, diff --git a/src/routing_layer/topo_group.c b/src/routing_layer/topo_group.c index 9c83818c..e6821220 100644 --- a/src/routing_layer/topo_group.c +++ b/src/routing_layer/topo_group.c @@ -408,6 +408,11 @@ static void topo_group_destroy(struct TOPO_GROUP* group) { topo_nodeq_free_group_fields(group->instance->topo_groups, group->local_node); u_free(group->local_node); } + /* освободить подписчиков BGP-событий (member_sync и др.) */ + while (group->node_cbks) { + struct topo_node_cbk_entry* c = group->node_cbks; + group->node_cbks = c->next; u_free(c); + } DEBUG_INFO(DEBUG_CATEGORY_SYS, "[GRP_DESTROY] 5 done"); } @@ -420,13 +425,8 @@ void topo_groups_remove_group(struct TOPO_GROUPS* g, uint64_t group_id) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "remove_group: id=%016llx type=%d ch=%s", (unsigned long long)group_id, group->group_type, group->channel_id); - /* освободить подписчиков BGP-событий (member_sync и др.), т.к. группа умирает */ - struct topo_node_cbk_entry* c = group->node_cbks; - while (c) { struct topo_node_cbk_entry* n = c->next; u_free(c); c = n; } - group->node_cbks = NULL; - queue_remove_data(g->group_list, &group->ll); - topo_group_destroy(group); + topo_group_destroy(group); /* node_cbks освобождаются внутри */ queue_entry_free(&group->ll); } diff --git a/src/transport_layer/reality_relay.c b/src/transport_layer/reality_relay.c index b80b6ff6..0968bd42 100644 --- a/src/transport_layer/reality_relay.c +++ b/src/transport_layer/reality_relay.c @@ -48,8 +48,12 @@ struct reality_relay { int closed; size_t bytes_c2d, bytes_d2c; + + struct reality_relay *next; // цепочка активных релеев (для shutdown) }; +static struct reality_relay *g_relays = NULL; // активные релеи + static void relay_free(struct reality_relay *r); static void relay_flush_c2d(struct reality_relay *r); static void relay_flush_d2c(struct reality_relay *r); @@ -65,6 +69,12 @@ static void relay_free_cb(void *arg) { static void relay_free(struct reality_relay *r) { if (!r || r->closed) return; r->closed = 1; + { // вынуть из реестра активных релеев + struct reality_relay **pp = &g_relays; + while (*pp && *pp != r) pp = &(*pp)->next; + if (*pp) *pp = r->next; + r->next = NULL; + } if (r->dns_q) { adns_cancel(r->dns_q); r->dns_q = NULL; } if (r->client_sid) { uasync_remove_socket_t(r->ua, r->client_sock); r->client_sid = NULL; } if (r->dest_sid) { uasync_remove_socket_t(r->ua, r->dest_sock); r->dest_sid = NULL; } @@ -265,6 +275,7 @@ int reality_relay_start(struct UASYNC *ua, socket_t client_sock, r->client_sock = client_sock; r->dest_sock = SOCKET_INVALID; r->connecting = 1; + r->next = g_relays; g_relays = r; // берём владение client_sock сразу (снимаем старую регистрацию) — до парсинга, // чтобы любая ошибка ниже не оставила сокет зарегистрированным в uasync. @@ -315,3 +326,14 @@ int reality_relay_start(struct UASYNC *ua, socket_t client_sock, r->host, r->port, initial_len); return 0; } + +void reality_relay_shutdown(void) { + int count = 0; + while (g_relays) { + struct reality_relay *r = g_relays; + relay_free(r); // сам вынимает r из g_relays и откладывает u_free через call_soon + count++; + } + if (count > 0) + DEBUG_INFO(DEBUG_CATEGORY_REALITY, "reality_relay_shutdown: closed %d active relays", count); +} diff --git a/src/transport_layer/reality_relay.h b/src/transport_layer/reality_relay.h index 69ac333e..5b1ee0e8 100644 --- a/src/transport_layer/reality_relay.h +++ b/src/transport_layer/reality_relay.h @@ -26,6 +26,11 @@ int reality_relay_start(struct UASYNC *ua, socket_t client_sock, const char *dest, const uint8_t *initial_data, size_t initial_len); +// Принудительно завершить все активные релеи (вызывается при shutdown). +// Освобождение отложено через uasync_call_soon — после вызова нужно +// uasync_drain_immediate, чтобы реальный u_free прошёл. +void reality_relay_shutdown(void); + #ifdef __cplusplus } #endif diff --git a/src/utun_instance.c b/src/utun_instance.c index 4709d468..7962cccc 100644 --- a/src/utun_instance.c +++ b/src/utun_instance.c @@ -26,6 +26,7 @@ #include "radio/radio.h" #include "radio/radio_headless.h" #include "stcp_server.h" +#include "transport_layer/reality_relay.h" #include "control_server.h" #include "transport_layer/node_conn_direct.h" #include "transport_layer/socket_monitor.h" @@ -511,6 +512,7 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) { /* Phase J: TCP connections + sockets — MUST be before ETCP connections */ stcp_server_list_destroy_all(instance); + reality_relay_shutdown(); /* активные reality-релеи откладывают u_free через call_soon */ if (instance->tcp_connections) { struct ll_entry* entry = instance->tcp_connections->head; int tc = 0; @@ -577,6 +579,9 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) { /* Phase N: etcp_router + nat_det + firewall */ etcp_router_destroy(instance); + /* etcp_router_destroy откладывает queue_entry_free(rconn->ll) через uasync_call_soon + (router_close_finalize) — выкачать их, пока ua ещё жив. */ + uasync_drain_immediate(instance->ua); if (instance->nat_det) { nat_detection_destroy(instance->nat_det); instance->nat_det = NULL; } fw_free(&instance->fw); DEBUG_INFO(DEBUG_CATEGORY_SYS, "[DESTROY] N done — router/nat_det/fw"); @@ -634,6 +639,12 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) { instance->nodeinfo_cbks = r->next; u_free(r); } + // Cleanup activity callback chain + while (instance->activity_cbks) { + struct utun_activity_cbk_entry* r = instance->activity_cbks; + instance->activity_cbks = r->next; u_free(r); + } + // Shared SQLite DB закрываем в самом конце: routing/chat/media/db_sync // используют его во время всего teardown (см. topo_groups_destroy). if (instance->topo_sqlite_db) {