Apply nonce-verified LAN endpoints to offline NCD transports
This commit is contained in:
1 parent
88c2d7a4e3
commit
469f95c596
3 files changed
+217
-4
No files matched your search
+80
-1
@@ -4,6 +4,13 @@
|
||||
#include "chat_setting.h"
|
||||
#include "../utun_instance.h"
|
||||
#include "../local_transport/protocol.h"
|
||||
#include "../routing_layer/topo_group.h"
|
||||
#include "../routing_layer/topo_group_connect.h"
|
||||
#include "../routing_layer/topo_node_sqlite.h"
|
||||
#include "../routing_layer/sock_match.h"
|
||||
#include "../transport_layer/etcp.h"
|
||||
#include "../transport_layer/etcp_connections.h"
|
||||
#include "../transport_layer/node_conn_direct.h"
|
||||
#include "../../lib/debug_config.h"
|
||||
#include "../../lib/mem.h"
|
||||
#include <string.h>
|
||||
@@ -76,9 +83,81 @@ static void activity(struct UTUN_INSTANCE* inst, int active, void* arg) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_DISCOVERY, "failed to apply discovery activity node=%016llx", (unsigned long long)inst->node_id);
|
||||
}
|
||||
|
||||
/* Только принимающие рабочие endpoints этого IP; скрытые и SOCKS/probe-сокеты не публикуются. */
|
||||
static int route_sockets(void* arg, const struct sockaddr_storage* address, uint32_t index,
|
||||
struct local_route_socket* out, size_t capacity) {
|
||||
struct UTUN_INSTANCE* inst = arg;
|
||||
size_t count = 0;
|
||||
for (struct ETCP_SOCKET* sock = inst->etcp_sockets; sock; sock = sock->next) {
|
||||
if (sock->type == CFG_SERVER_TYPE_PRIVATE || !sock_matches_lan(sock, address, index)) continue;
|
||||
uint16_t port = ss_get_port(&sock->local_addr);
|
||||
if (!port || !sock->generation) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DISCOVERY, "LAN socket omitted name=%s id=%u port=%u generation=%016llx",
|
||||
sock->name, sock->sock_id, port, (unsigned long long)sock->generation); continue;
|
||||
}
|
||||
if (count == capacity) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_DISCOVERY, "too many LAN sockets interface=%u capacity=%zu", index, capacity); return -1;
|
||||
}
|
||||
out[count++] = (struct local_route_socket){ .socket_id = sock->sock_id, .generation = sock->generation,
|
||||
.port = port, .protocol = sock->is_tcp ? LOCAL_ROUTE_TCP | (sock->reality_enabled ? LOCAL_ROUTE_REALITY : 0) : LOCAL_ROUTE_UDP };
|
||||
}
|
||||
return (int)count;
|
||||
}
|
||||
|
||||
/* Ключи не покидают ядро; wire magic/version/type отделяют эту подпись от других протоколов. */
|
||||
static int route_sign(void* arg, const uint8_t* data, size_t len, uint8_t signature[64]) {
|
||||
struct UTUN_INSTANCE* inst = arg;
|
||||
if (inst->node_id != sc_derive_node_id_from_pubkey(inst->my_keys.public_key) ||
|
||||
sc_ed25519_sign(inst->my_ed25519_privkey, data, len, signature) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_DISCOVERY, "LAN endpoint signing failed node=%016llx", (unsigned long long)inst->node_id); return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Отсутствие ключа не мешает открытому discovery, но не даёт адрес для NCD. */
|
||||
static int route_verify(void* arg, uint64_t peer, const uint8_t* data, size_t len, const uint8_t signature[64]) {
|
||||
struct UTUN_INSTANCE* inst = arg;
|
||||
uint8_t key[32];
|
||||
struct TOPO_NODE* node = topo_node_registry_find(inst->topo_groups, peer);
|
||||
if (node && node->timestamp) memcpy(key, node->ed25519_public_key, sizeof(key));
|
||||
else if (!inst->topo_sqlite_db || topo_node_sqlite_get_ed25519_pubkey(inst->topo_sqlite_db, peer, key)) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DISCOVERY, "LAN route ignored peer=%016llx reason=unknown signing key", (unsigned long long)peer); return -1;
|
||||
}
|
||||
if (sc_ed25519_verify(key, data, len, signature) != SC_OK) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DISCOVERY, "LAN route signature rejected peer=%016llx", (unsigned long long)peer); return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Общие адреса узла нельзя заменять, пока доступен прямой транспорт или любой BGP-путь. */
|
||||
static int route_offline(void* arg, uint64_t peer) {
|
||||
struct UTUN_INSTANCE* inst = arg;
|
||||
struct ETCP_CONN* conn = inst->connections ? instance_find_conn(inst, peer) : NULL;
|
||||
if (conn && conn->links_up && !conn->close_requested && conn->state != 2) return 0;
|
||||
for (struct ll_entry* e = inst->topo_groups && inst->topo_groups->group_list ? inst->topo_groups->group_list->head : NULL;
|
||||
e; e = e->next) {
|
||||
if (topo_node_best_path(topo_node_find_by_id((struct TOPO_GROUP*)e, peer))) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Обновление помогает уже удерживаемому NCD; право создавать соединение остаётся у сервисов. */
|
||||
static void routes_changed(void* arg, uint64_t peer) {
|
||||
struct UTUN_INSTANCE* inst = arg;
|
||||
if (inst->connections && node_conn_direct_update_local(inst, peer) < 0)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_DISCOVERY, "LAN address application failed peer=%016llx", (unsigned long long)peer);
|
||||
for (struct ll_entry* e = inst->topo_groups && inst->topo_groups->group_list ? inst->topo_groups->group_list->head : NULL;
|
||||
e; e = e->next) {
|
||||
struct TOPO_GROUP* group = (struct TOPO_GROUP*)e;
|
||||
if (group->group_type == TOPO_GROUP_TYPE_CHAT && !group->stopping) topo_group_connect_changed(group);
|
||||
}
|
||||
}
|
||||
|
||||
int chat_local_start(struct UTUN_INSTANCE* inst) {
|
||||
if (inst->local_transport || inst->client_type == CLIENT_TYPE_SERVER) return 0;
|
||||
struct local_transport_callbacks callbacks = { peers_changed, received, delivered, activity_changed };
|
||||
struct local_transport_callbacks callbacks = { .peers_changed = peers_changed, .received = received, .delivery = delivered,
|
||||
.activity_changed = activity_changed, .route_sockets = route_sockets, .route_sign = route_sign,
|
||||
.route_verify = route_verify, .route_offline = route_offline, .routes_changed = routes_changed };
|
||||
inst->local_transport = local_transport_create(inst->ua, inst->node_id, inst->name, &callbacks, inst);
|
||||
if (!inst->local_transport) return -1;
|
||||
if (chat_local_apply_settings(inst) < 0) { chat_local_stop(inst); return -1; }
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/* Адаптер локального транспорта: только доставка готовых invite-ссылок, без запуска JOIN. */
|
||||
/* Адаптер LAN discovery: готовые invite-ссылки и подписанные адреса известных узлов, без запуска JOIN.
|
||||
* Адреса подтверждаются nonce-ответом только при отсутствии прямого UP и BGP-путей во всех группах.
|
||||
* Ключи берутся из registry/SQLite и не передаются в открытых объявлениях; NODEINFO не изменяется. */
|
||||
#ifndef CHAT_LOCAL_H
|
||||
#define CHAT_LOCAL_H
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "topo_group.h"
|
||||
#include "ntp_time.h"
|
||||
#include "config_parser.h"
|
||||
#include "../local_transport/local_transport.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/u_async.h"
|
||||
@@ -53,6 +54,7 @@ struct ncd_entry {
|
||||
|
||||
void* connect_timer; /* однократный таймер первого подъёма */
|
||||
void* fin_wait_timer; /* таймер ожидания ответа на CLOSE */
|
||||
void* lan_timer; /* очистка неподтверждённых LAN-проб */
|
||||
uint8_t up : 1; /* текущий статус: 1=есть живой линк */
|
||||
uint8_t timed_out : 1; /* connect_timer уже сработал */
|
||||
|
||||
@@ -135,7 +137,11 @@ static struct TOPO_NODE* ncd_lookup_node(struct UTUN_INSTANCE* inst, uint64_t no
|
||||
}
|
||||
}
|
||||
if (inst->topo_sqlite_db && inst->topo_groups) {
|
||||
struct TOPO_NODE* ni = topo_node_sqlite_node_load(inst->topo_sqlite_db, inst->topo_groups, node_id);
|
||||
struct local_route route;
|
||||
int lan = local_transport_route(inst->local_transport, node_id, 0, &route);
|
||||
struct TOPO_NODE* ni = lan ? topo_node_sqlite_snapshot_load(inst->topo_sqlite_db, inst->topo_groups, node_id) :
|
||||
topo_node_sqlite_node_load(inst->topo_sqlite_db, inst->topo_groups, node_id);
|
||||
if (lan && !ni) ni = topo_node_sqlite_identity_load(inst->topo_sqlite_db, node_id);
|
||||
if (ni) {
|
||||
struct TOPO_NODE* stored = topo_node_registry_store(inst->topo_groups, ni);
|
||||
if (!stored) {
|
||||
@@ -250,6 +256,100 @@ static int ncd_add_tcp_link(struct ETCP_CONN* conn, struct ETCP_SOCKET* sock,
|
||||
}
|
||||
|
||||
/* Проверка совместимости и создание используют один обход пар, включая TCP fallback и IPv6. */
|
||||
/* Сверить неподтверждённые линки с живым discovery-кешем; закрытие может вызвать callbacks NCD. */
|
||||
static int ncd_prune_lan(struct ncd_entry* entry, int force) {
|
||||
struct ETCP_CONN* conn = entry->conn;
|
||||
for (;;) {
|
||||
struct ETCP_LINK* stale = NULL;
|
||||
int pending = 0;
|
||||
for (struct ETCP_LINK* link = conn->links; link; link = link->next) {
|
||||
if (!link->lan_generation || link->closing) continue;
|
||||
if (link->link_status) { link->lan_generation = 0; continue; }
|
||||
int found = 0;
|
||||
struct local_route route;
|
||||
for (size_t i = 0; !force && local_transport_route(entry->inst->local_transport, entry->node_id, i, &route); i++) {
|
||||
if (route.socket.generation == link->lan_generation && route.interface_index == link->lan_interface &&
|
||||
!!(route.socket.protocol & LOCAL_ROUTE_TCP) == !!link->is_tcp &&
|
||||
ncd_same_address(&route.address, &link->remote_addr) && sock_matches_lan(link->conn, &route.local_address,
|
||||
route.interface_index)) { found = 1; break; }
|
||||
}
|
||||
if (!found) { stale = link; break; }
|
||||
pending++;
|
||||
}
|
||||
if (!stale) return pending;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] retire LAN probe node=%016llx link=%u generation=%016llx interface=%u",
|
||||
(unsigned long long)entry->node_id, stale->local_link_id,
|
||||
(unsigned long long)stale->lan_generation, stale->lan_interface);
|
||||
etcp_link_close(stale);
|
||||
if (entry->closed || conn->close_requested) return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* TTL относится к пробе: после успешного ETCP-handshake линк живёт по обычным правилам транспорта. */
|
||||
static void ncd_lan_timer(void* arg) {
|
||||
struct ncd_entry* entry = arg;
|
||||
entry->lan_timer = NULL;
|
||||
struct ETCP_CONN* conn = entry->conn;
|
||||
if (etcp_conn_ref_take(conn) < 0) return;
|
||||
entry->active_calls++;
|
||||
if (ncd_prune_lan(entry, 0) && !entry->closed && !conn->close_requested) {
|
||||
entry->lan_timer = uasync_set_timeout(entry->ua, 10000, entry, ncd_lan_timer, "ncd_lan_probe");
|
||||
if (!entry->lan_timer) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] LAN cleanup timer failed node=%016llx", (unsigned long long)entry->node_id);
|
||||
ncd_prune_lan(entry, 1);
|
||||
}
|
||||
}
|
||||
entry->active_calls--; ncd_collect(entry); etcp_conn_ref_free(conn);
|
||||
}
|
||||
|
||||
/* LAN-кандидаты имеют свою подпись/TTL и не подменяют содержимое или timestamp NODEINFO. */
|
||||
static int ncd_create_lan_links(struct ncd_entry* entry, const struct TOPO_NODE* ni,
|
||||
struct ETCP_SOCKET* specific_sock, struct UTUN_INSTANCE* check_inst) {
|
||||
struct UTUN_INSTANCE* inst = check_inst ? check_inst : entry->inst;
|
||||
if (!inst->local_transport) return 0;
|
||||
struct ETCP_CONN* conn = check_inst ? NULL : entry->conn;
|
||||
uint64_t id = check_inst ? ni->node_id : entry->node_id;
|
||||
if (conn && (conn->fin_wait || conn->close_requested)) return 0;
|
||||
if (conn) ncd_prune_lan(entry, 0);
|
||||
if (conn && (entry->closed || conn->close_requested)) return -1;
|
||||
int count = 0;
|
||||
struct local_route route;
|
||||
for (size_t i = 0; local_transport_route(inst->local_transport, id, i, &route); i++) {
|
||||
const struct TOPO_REALITY_SOCK* reality = NULL;
|
||||
if (route.socket.protocol & LOCAL_ROUTE_REALITY) {
|
||||
reality = topo_node_find_reality_sock(ni, route.socket.socket_id);
|
||||
if (!reality) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_NCD, "[ncd] LAN REALITY metadata unavailable node=%016llx socket=%u",
|
||||
(unsigned long long)id, route.socket.socket_id); continue;
|
||||
}
|
||||
}
|
||||
for (struct ETCP_SOCKET* sock = inst->etcp_sockets; sock; sock = sock->next) {
|
||||
if ((specific_sock && sock != specific_sock) || !!sock->is_tcp != !!(route.socket.protocol & LOCAL_ROUTE_TCP) ||
|
||||
!sock_matches_lan(sock, &route.local_address, route.interface_index)) continue;
|
||||
if (check_inst) return 1;
|
||||
if (!entry->lan_timer) {
|
||||
entry->lan_timer = uasync_set_timeout(entry->ua, 10000, entry, ncd_lan_timer, "ncd_lan_probe");
|
||||
if (!entry->lan_timer) { DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] LAN cleanup timer allocation failed"); return -1; }
|
||||
}
|
||||
int added = sock->is_tcp ? ncd_add_tcp_link(conn, sock, &route.address, reality) :
|
||||
ncd_add_udp_link(conn, sock, &route.address);
|
||||
if (added < 0 || entry->closed || conn->close_requested) return -1;
|
||||
for (struct ETCP_LINK* link = conn->links; link; link = link->next) {
|
||||
if (link->conn != sock || !!link->is_tcp != !!sock->is_tcp || !ncd_same_address(&link->remote_addr, &route.address)) continue;
|
||||
if ((added || link->lan_generation) && !link->link_status) {
|
||||
link->lan_generation = route.socket.generation; link->lan_interface = route.interface_index;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (added) DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] LAN probe node=%016llx socket=%u generation=%016llx interface=%u address=%s",
|
||||
(unsigned long long)id, route.socket.socket_id, (unsigned long long)route.socket.generation,
|
||||
route.interface_index, sockaddr_storage_to_str(&route.address).str);
|
||||
count += added;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static int ncd_create_links(struct ncd_entry* entry, struct TOPO_NODE* ni,
|
||||
struct ETCP_SOCKET* specific_sock, int force_socket, struct UTUN_INSTANCE* check_inst) {
|
||||
if (!ni) { DEBUG_WARN(DEBUG_CATEGORY_NCD, "[ncd] link matching without node snapshot"); return 0; }
|
||||
@@ -385,6 +485,10 @@ static int ncd_create_links(struct ncd_entry* entry, struct TOPO_NODE* ni,
|
||||
}
|
||||
}
|
||||
|
||||
int lan = ncd_create_lan_links(entry, ni, specific_sock, check_inst);
|
||||
if (lan < 0) return -1;
|
||||
if (check_inst && lan) return 1;
|
||||
link_count += lan;
|
||||
if (link_count > 0) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NCD, "[ncd] create_links: node=0x%016llx v4=%d/skp=%d v6=%d/skp=%d → %d new links (UDP+TCP)",
|
||||
(unsigned long long)nid, v4_addrs, v4_skip, v6_addrs, v6_skip, link_count);
|
||||
@@ -397,7 +501,16 @@ int node_conn_direct_compatible(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||
struct ETCP_CONN* conn = instance_find_conn(inst, node_id);
|
||||
if (conn && conn->state != 2 && !conn->close_requested && conn->links_up) return 1;
|
||||
struct TOPO_NODE* node = topo_node_registry_find(inst->topo_groups, node_id);
|
||||
if (!node) { DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] compatibility unknown snapshot node=%016llx", (unsigned long long)node_id); return 0; }
|
||||
if (!node) {
|
||||
struct local_route route;
|
||||
if (inst->topo_sqlite_db && local_transport_route(inst->local_transport, node_id, 0, &route)) {
|
||||
struct TOPO_NODE* identity = topo_node_sqlite_identity_load(inst->topo_sqlite_db, node_id);
|
||||
int possible = identity ? ncd_create_lan_links(NULL, identity, NULL, inst) : 0;
|
||||
topo_node_destroy(inst->topo_groups, identity);
|
||||
return possible > 0;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_NCD, "[ncd] compatibility unknown snapshot node=%016llx", (unsigned long long)node_id); return 0;
|
||||
}
|
||||
if (ncd_create_links(NULL, node, NULL, 0, inst) > 0) return 1;
|
||||
/* Входящее подключение тоже допустимо: REVERSE_REQ просит пира открыть наш опубликованный адрес.
|
||||
* TCP имеет собственный исходящий socket fallback; для UDP нужен известный UDP-сокет пира.
|
||||
@@ -461,6 +574,24 @@ int node_conn_direct_update_node(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||
return added;
|
||||
}
|
||||
|
||||
/* Адрес получен отдельно от BGP; нет транспорта — сохраняем только discovery-кеш. */
|
||||
int node_conn_direct_update_local(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||
if (!inst || !node_id) { DEBUG_ERROR(DEBUG_CATEGORY_NCD, "[ncd] invalid LAN update"); return -1; }
|
||||
struct ncd_entry* entry = ncd_registry_find(inst, node_id);
|
||||
if (!entry || entry->conn->fin_wait) return 0;
|
||||
struct ETCP_CONN* conn = entry->conn;
|
||||
if (etcp_conn_ref_take(conn) < 0) return -1;
|
||||
entry->active_calls++;
|
||||
struct TOPO_NODE* known = ncd_lookup_node(inst, node_id);
|
||||
struct TOPO_NODE* copy = known ? ncd_copy_node(inst, known) : NULL;
|
||||
if (known) topo_node_registry_unref(inst->topo_groups, node_id);
|
||||
int result = copy ? ncd_create_lan_links(entry, copy, NULL, NULL) : 0;
|
||||
if (!copy) ncd_prune_lan(entry, 0);
|
||||
topo_node_destroy(inst->topo_groups, copy);
|
||||
entry->active_calls--; ncd_collect(entry); etcp_conn_ref_free(conn);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ═══════════ единая диспетчеризация событий ═══════════ */
|
||||
/*
|
||||
* Центральный диспетчер: принимает событие (UP/DOWN/TIMEOUT), проверяет что оно
|
||||
@@ -555,6 +686,7 @@ static void ncd_finish(struct ncd_entry* entry, int close_conn) {
|
||||
}
|
||||
if (entry->connect_timer) { uasync_cancel_timeout(entry->ua, entry->connect_timer); entry->connect_timer = NULL; }
|
||||
if (entry->fin_wait_timer) { uasync_cancel_timeout(entry->ua, entry->fin_wait_timer); entry->fin_wait_timer = NULL; }
|
||||
if (entry->lan_timer) { uasync_cancel_timeout(entry->ua, entry->lan_timer); entry->lan_timer = NULL; }
|
||||
struct ETCP_CONN* conn = entry->conn; entry->conn = NULL;
|
||||
if (conn) {
|
||||
if (conn->fin_wait_clear_arg == entry) {
|
||||
|
||||
Reference in new issue
Block a user