From a353797edb5dca212b43df6d71f6b26e41c4730e Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 24 Oct 2026 20:22:45 +0200 Subject: [PATCH] Specify NCD ownership callbacks matching and fixed little-endian control wire --- src/transport_layer/node_conn_direct.c | 5 ++- src/transport_layer/node_conn_direct.h | 53 ++++++++++++++++++-------- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/transport_layer/node_conn_direct.c b/src/transport_layer/node_conn_direct.c index 36476add..e75a6f34 100644 --- a/src/transport_layer/node_conn_direct.c +++ b/src/transport_layer/node_conn_direct.c @@ -675,7 +675,8 @@ static void ncd_recv_control_handler(struct ETCP_CONN* conn, struct ll_entry* e) struct ncd_control_msg message; memcpy(&message, e->dgram, sizeof(message)); const struct ncd_control_msg* msg = &message; - uint64_t sender_id = msg->node_id; + uint64_t sender_id = 0; + for (unsigned i = 0; i < 8; i++) sender_id |= (uint64_t)e->dgram[2 + i] << (8 * i); struct ncd_entry* entry = ncd_registry_find(conn->instance, sender_id); if (!sender_id || sender_id != conn->peer_node_id || (entry && entry->conn != conn)) { DEBUG_WARN(DEBUG_CATEGORY_NCD, "[ncd] control from wrong transport node=%016llx", (unsigned long long)sender_id); @@ -698,6 +699,7 @@ static void ncd_recv_control_handler(struct ETCP_CONN* conn, struct ll_entry* e) if (!qe) DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] KEEP_ALIVE allocation failed"); else { memcpy(qe->dgram, &resp, sizeof(resp)); qe->len = sizeof(resp); + for (unsigned i = 0; i < 8; i++) qe->dgram[2 + i] = (uint8_t)(conn->instance->node_id >> (8 * i)); if (etcp_send(conn, qe) != 0) { DEBUG_WARN(DEBUG_CATEGORY_NCD, "[ncd] KEEP_ALIVE send failed node=%016llx", (unsigned long long)sender_id); queue_dgram_free(qe); queue_entry_free(qe); @@ -933,6 +935,7 @@ static void ncd_handle_close(struct NODE_CONN_DIRECT* h, int force) { if (!e) DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] CLOSE allocation failed"); else { memcpy(e->dgram, &msg, sizeof(msg)); e->len = sizeof(msg); + for (unsigned i = 0; i < 8; i++) e->dgram[2 + i] = (uint8_t)(entry->inst->node_id >> (8 * i)); if (etcp_send(conn,e) != 0) { DEBUG_WARN(DEBUG_CATEGORY_NCD, "[ncd] CLOSE send failed"); queue_dgram_free(e); queue_entry_free(e); } diff --git a/src/transport_layer/node_conn_direct.h b/src/transport_layer/node_conn_direct.h index 82f9ab2b..c75ce274 100644 --- a/src/transport_layer/node_conn_direct.h +++ b/src/transport_layer/node_conn_direct.h @@ -4,7 +4,7 @@ * Несколько handles делят один ETCP_CONN; close снимает только своё владение. * NCD создаёт и обновляет UDP/STCP-линки, но не проверяет членство и готовность topo-группы. * Для групповой сессии используйте topo_group_peer_open, для звонка/файла и выбора пути — conn_mgr_open. - * Все операции — в потоке uasync. + * Все операции — в потоке uasync. Диагностика — категория etcp, подробности на debug. */ /* Владение транспортом: @@ -12,21 +12,28 @@ * Временный handle закрывают либо передают новому владельцу через transfer. * Потребность сервиса в доставке на всё время звонка/файла выражает CONN_MGR_HANDLE (conn_mgr.h). * Последний handle работающего транспорта начинает CLOSE/KEEP_ALIVE (5 с либо срок пробуждения пира); - * DOWN-транспорт удаляется по таймауту ETCP. + * Закрытие последнего handle до первого UP или после TIMEOUT удаляет транспорт сразу; + * ранее работавший DOWN-транспорт остаётся до deadline ETCP. * Переходы UP/DOWN/TIMEOUT/CLOSED доставляются синхронно. Закрытие любого handle * из callback безопасно. Начальное UP при open готового conn откладывается через uasync_call_soon. + * Вложенный переход отменяет оставшуюся рассылку прежнего события. После close указатель handle недействителен. + * CLOSED отменяет таймеры и отсоединяет conn; handle всё равно закрывает владелец, в том числе после destroy ядра. */ #ifndef NODE_CONN_DIRECT_H #define NODE_CONN_DIRECT_H #include +#ifdef __cplusplus +extern "C" { +#endif + struct UTUN_INSTANCE; struct ETCP_CONN; struct TOPO_NODE; struct ETCP_SOCKET; -// Привязать CLOSE/KEEP_ALIVE при запуске ядра, в том числе без исходящих handles. +// Привязать CLOSE/KEEP_ALIVE при запуске ядра, в том числе без исходящих handles; 0 — успех, -1 — ошибка. int node_conn_direct_init(struct UTUN_INSTANCE* inst); // Число локальных владельцев конкретного транспорта; внешние ETCP refs сюда не входят. @@ -42,7 +49,8 @@ struct NODE_CONN_DIRECT; /* Рабочий ETCP, совместимая исходящая пара либо возможность reverse на наш опубликованный адрес. * Без линков/handles/таймеров/проб. Исходящие пары — тот же обход, что open; reverse — sock_match/публикуемые адреса. - * Учитывает IPv6 и исходящий TCP fallback; возможность попытки не доказывает достижимость. */ + * Учитывает IPv6 и исходящий TCP fallback; 1 — кандидат есть, 0 — нет/ошибка. SQLite не загружает. + * Возможность попытки не доказывает достижимость и не подтверждает готовность группы. */ int node_conn_direct_compatible(struct UTUN_INSTANCE* inst, uint64_t node_id); // ─── Callback events (единый callback на handle) ─── @@ -58,10 +66,14 @@ typedef void (*ncd_callback)(struct NODE_CONN_DIRECT* h, enum ncd_event event, v /* * Открыть handle к узлу по node_id. * Node info ищется через node_registry, fallback — SQLite. - * cb вызывается при изменении статуса / таймауте. + * cb/arg — callback и его контекст (cb=NULL допустим). Новый handle получает события только после возврата open. * Если conn уже готов — cb(NCD_EVENT_UP) через uasync_call_soon (не синхронно). * До первой отправки дождаться UP. Результат: NCD_NEW/NCD_REUSED или NCD_ERR. * При успехе вызывающий обязан закрыть *out_handle, в том числе после CLOSED. + * out_handle обязателен; при ошибке *out_handle=NULL, старые handles и FIN_WAIT сохраняются. + * Принятый snapshot и уже добавленные линки могут сохраниться при частичной ошибке обновления. + * specific_sock=NULL выбирает все разрешённые пары; иначе нужен обычный сокет этого instance. + * Явный сокет задаёт локальную политику, но сохраняются проверки family, протокола, target и REALITY. */ int node_conn_direct_open(struct UTUN_INSTANCE* inst, uint64_t node_id, ncd_callback cb, void* cb_arg, @@ -70,7 +82,10 @@ int node_conn_direct_open(struct UTUN_INSTANCE* inst, uint64_t node_id, /* * То же, но с уже загруженным TOPO_NODE (ni не владеем — можно передать временный на стеке). - * ni должен содержать public_key, v4_addrs/v6_addrs для создания линков. + * ni заимствован до возврата; node_id должен совпадать с ни́м и производным public_key. + * timestamp>0 требует верной подписи: более свежая запись принимается целиком, старая уступает известной, + * одинаковый timestamp с другой подписью отклоняется. timestamp=0 — bootstrap, он не заменяет подписанную запись. + * Адреса могут отсутствовать: handle ожидает также входящего подключения. Обход адресов использует отдельную копию. */ int node_conn_direct_open_node(struct UTUN_INSTANCE* inst, uint64_t node_id, ncd_callback cb, void* cb_arg, @@ -82,7 +97,8 @@ int node_conn_direct_open_node(struct UTUN_INSTANCE* inst, uint64_t node_id, void node_conn_direct_close(struct NODE_CONN_DIRECT* h); /* Применить принятую запись из общего реестра к уже существующему соединению. - * Не создаёт соединение и не приобретает handle. Число новых линков или -1 при ошибке. */ + * Не создаёт соединение и не приобретает handle. Число новых линков, 0 при отсутствии записи/транспорта, -1 при ошибке. + * Добавляет недостающие пары и обновляет TCP/REALITY; работающие линки по исчезнувшим адресам сохраняются. */ int node_conn_direct_update_node(struct UTUN_INSTANCE* inst, uint64_t node_id); /* Как close, но последний handle закрывает транспорт сразу, без fin_wait. @@ -90,7 +106,8 @@ int node_conn_direct_update_node(struct UTUN_INSTANCE* inst, uint64_t node_id); void node_conn_direct_force_close(struct NODE_CONN_DIRECT* h); /* Сменить или сбросить (cb=NULL) callback на уже открытом handle. - * Не влияет на refcounting и состояние conn. */ + * Не влияет на refcounting и состояние conn, не повторяет уже доставленное событие. + * Ещё не доставленный начальный UP использует новый callback/arg. */ void node_conn_direct_set_callback(struct NODE_CONN_DIRECT* h, ncd_callback cb, void* cb_arg); /* Передать владение handle новому владельцу (смена callback + arg). @@ -104,7 +121,7 @@ uint64_t node_conn_direct_node_id(const struct NODE_CONN_DIRECT* h); // Заимствованный транспорт, в том числе до UP; NULL после CLOSED. Не закрывать напрямую. struct ETCP_CONN* node_conn_direct_get_conn(struct NODE_CONN_DIRECT* h); -// ─── Протокол CLOSE / KEEP_ALIVE (ETCP_ID_NCD_CONTROL = 0x12) ─── +// Прямой ETCP control: ровно 10 байт, cmd=0x12, subcmd, node_id little-endian; sender сверяется с peer транспорта. #define NCD_SUBCMD_CLOSE 0x01 #define NCD_SUBCMD_KEEP_ALIVE 0x02 @@ -113,7 +130,7 @@ struct ETCP_CONN* node_conn_direct_get_conn(struct NODE_CONN_DIRECT* h); struct ncd_control_msg { uint8_t cmd; /* ETCP_ID_NCD_CONTROL */ uint8_t subcmd; /* NCD_SUBCMD_CLOSE / KEEP_ALIVE */ - uint64_t node_id; /* node_id отправителя */ + uint64_t node_id; /* wire: 8 байт little-endian; не native uint64_t на big-endian платформе */ }; #pragma pack(pop) @@ -122,18 +139,24 @@ struct ncd_control_msg { // ─── Управление линками на конкретном сокете ─── /* * ncd_add_socket_links — итерирует все активные NCD-соединения (ncd_registry), - * для каждого загружает TOPO_NODE с адресами пира и создаёт линки (etcp_link_new) - * на переданном сокете (только совместимые family: v4/v6). Аналог ncd_create_links - * но вызывается извне (auto_socket) при добавлении нового интерфейса. - * Возвращает количество созданных линков (0 если нечего добавить или ошибка). + * кроме FIN_WAIT; загружает TOPO_NODE и добавляет все разрешённые пары на новом сокете. + * Использует правила open(NULL): family/UDP/TCP, локальная политика, NAT-профиль, REALITY, IPv6 scope. + * sock — обычный зарегистрированный сокет inst, заимствован до возврата; callbacks не должны удалять его. + * Возвращает число новых линков, 0 если нечего добавить, -1 при ошибке (частичные добавления сохраняются). */ int ncd_add_socket_links(struct UTUN_INSTANCE* inst, struct ETCP_SOCKET* sock); /* * ncd_remove_socket_links — итерирует все активные NCD-соединения, * для каждого закрывает линки (etcp_link_close) привязанные к переданному сокету. - * Вызывается при удалении сокета (интерфейс пропал). Возвращает количество закрытых линков. + * Вызывается перед удалением сокета; sock заимствован до возврата. Сам сокет не удаляет. + * Возвращает число линков, закрытых этим вызовом, или -1 при отказе памяти. NULL inst/sock — 0. + * Вложенные callbacks могут закрывать другие handles/линки. Транспорт вне NCD удаляет сам etcp_socket_remove. */ int ncd_remove_socket_links(struct UTUN_INSTANCE* inst, struct ETCP_SOCKET* sock); +#ifdef __cplusplus +} +#endif + #endif