Version signed node snapshots with persistent timestamps

This commit is contained in:
evgeny committed 2026-09-27 19:11:11 +03:00
1 parent 7374d1648c
commit ec51b2d61f
10 files changed
+440 -95

No files matched your search

+125 -17
View File
@@ -221,9 +221,36 @@ static int topo_node_is_full_identity(const struct TOPO_NODE* ni) {
struct TOPO_NODE* topo_node_registry_store(struct TOPO_GROUPS* groups, struct TOPO_NODE* ni) {
if (!groups || !ni) return NULL;
struct TOPO_NODE* existing = topo_node_registry_find(groups, ni->node_id);
if (existing == ni) { topo_node_ref(existing); return existing; }
if (ni->timestamp && topo_node_verify(ni) < 0) return NULL;
if (existing) {
topo_node_ref(existing);
if (existing->timestamp || ni->timestamp) {
if (ni->timestamp <= existing->timestamp) {
if (ni->timestamp == existing->timestamp && memcmp(ni->x25519_self_sig, existing->x25519_self_sig, 64))
DEBUG_WARN(DEBUG_CATEGORY_BGP, "node timestamp conflict: node=%016llx timestamp=%llu",
(unsigned long long)ni->node_id, (unsigned long long)ni->timestamp);
else DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "node unchanged/stale: node=%016llx current=%llu incoming=%llu",
(unsigned long long)ni->node_id, (unsigned long long)existing->timestamp,
(unsigned long long)ni->timestamp);
topo_node_free_raw(groups, ni);
return existing;
}
/* Сохраняем адрес объекта для заёмщиков, заменяем всё подписанное содержимое сразу. */
topo_node_identity_free(groups, existing);
u_free(existing->node_name);
struct ll_entry ll = existing->ll;
uint32_t refs = existing->group_ref_count;
*existing = *ni;
existing->ll = ll;
existing->group_ref_count = refs;
u_free(ni);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "node snapshot updated: node=%016llx timestamp=%llu",
(unsigned long long)existing->node_id, (unsigned long long)existing->timestamp);
return existing;
}
/* Идентичность (ver/имя/pubkeys/подпись) — авторитетна у полного NODEINFO,
* иначе реестр держит устаревший ver/имя и форвард nodeinfo ломает подпись. */
if (topo_node_is_full_identity(ni)) {
@@ -255,6 +282,14 @@ struct TOPO_NODE* topo_node_registry_store(struct TOPO_GROUPS* groups, struct TO
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "registry_acquire: queue_entry_new failed for node=%016llx", (unsigned long long)ni->node_id);
return NULL;
}
if (groups->instance && groups->instance->topo_sqlite_db) {
struct TOPO_NODE* saved = topo_node_sqlite_snapshot_load(groups->instance->topo_sqlite_db, groups, ni->node_id);
if (saved && saved->timestamp >= ni->timestamp) {
if (saved->timestamp == ni->timestamp && memcmp(saved->x25519_self_sig, ni->x25519_self_sig, 64))
DEBUG_WARN(DEBUG_CATEGORY_BGP, "persisted node timestamp conflict node=%016llx", (unsigned long long)ni->node_id);
topo_node_free_raw(groups, ni); ni = saved;
} else topo_node_free_raw(groups, saved);
}
topo_node_ref(ni);
memcpy(qe->data, &ni->node_id, 8);
memcpy(qe->data + 8, &ni, sizeof(ni));
@@ -290,6 +325,8 @@ int topo_node_build_sig_msg(struct TOPO_NODE* ni, uint8_t* buf, size_t buf_size)
memcpy(buf + off, &ni->node_id, 8); off += 8;
buf[off++] = ni->ver;
if (off + 8 + SC_PUBKEY_SIZE > buf_size) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "signature buffer too small"); return -1; }
for (int i = 7; i >= 0; i--) buf[off++] = (uint8_t)(ni->timestamp >> (i * 8));
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;
@@ -338,12 +375,48 @@ int topo_node_build_sig_msg(struct TOPO_NODE* ni, uint8_t* buf, size_t buf_size)
}
/* Подписывает свой узел Ed25519: build_sig_msg + sc_ed25519_sign → ni->x25519_self_sig. */
void topo_node_sign_self(struct UTUN_INSTANCE* instance, struct TOPO_NODE* ni) {
if (!instance || !ni) return;
int topo_node_sign_self(struct UTUN_INSTANCE* instance, struct TOPO_NODE* ni) {
if (!instance || !ni) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid signing arguments"); return -1; }
uint64_t old_timestamp = ni->timestamp;
uint8_t old_sig[64]; memcpy(old_sig, ni->x25519_self_sig, sizeof(old_sig));
uint64_t previous = ni->timestamp;
if (instance->topo_sqlite_db) {
uint64_t saved = topo_node_sqlite_snapshot_timestamp(instance->topo_sqlite_db, ni->node_id);
if (saved > previous) previous = saved;
}
int64_t now = ntp_time_get_us(instance);
if (previous >= INT64_MAX) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node timestamp exhausted"); return -1; }
ni->timestamp = now > 0 && (uint64_t)now > previous ? (uint64_t)now : previous + 1;
uint8_t sig_msg[TOPO_SIG_MSG_MAX_SIZE];
int sig_len = topo_node_build_sig_msg(ni, sig_msg, sizeof(sig_msg));
if (sig_len > 0 && sc_ed25519_sign(instance->my_ed25519_privkey, sig_msg, (size_t)sig_len, ni->x25519_self_sig) != SC_OK)
if (sig_len <= 0 || sc_ed25519_sign(instance->my_ed25519_privkey, sig_msg, (size_t)sig_len, ni->x25519_self_sig) != SC_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Ed25519 self-sign FAILED for node=%016llx", (unsigned long long)ni->node_id);
ni->timestamp = old_timestamp;
memcpy(ni->x25519_self_sig, old_sig, sizeof(old_sig));
return -1;
}
if (instance->topo_sqlite_db && topo_node_sqlite_snapshot_put(instance->topo_sqlite_db, ni) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot persist local node snapshot node=%016llx", (unsigned long long)ni->node_id);
ni->timestamp = old_timestamp;
memcpy(ni->x25519_self_sig, old_sig, sizeof(old_sig));
return -1;
}
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "node signed: node=%016llx timestamp=%llu",
(unsigned long long)ni->node_id, (unsigned long long)ni->timestamp);
return 0;
}
int topo_node_verify(const struct TOPO_NODE* ni) {
uint8_t msg[TOPO_SIG_MSG_MAX_SIZE];
if (!ni || !ni->timestamp || ni->timestamp > INT64_MAX || ni->node_id != sc_derive_node_id_from_pubkey(ni->public_key)) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "invalid signed node identity or timestamp"); return -1;
}
int len = topo_node_build_sig_msg((struct TOPO_NODE*)ni, msg, sizeof(msg));
if (len <= 0 || sc_ed25519_verify(ni->ed25519_public_key, msg, (size_t)len, ni->x25519_self_sig) != SC_OK) {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "node signature rejected: node=%016llx timestamp=%llu",
(unsigned long long)ni->node_id, (unsigned long long)ni->timestamp); return -1;
}
return 0;
}
/* Вычисляет суммарный размер динамической части NODEINFO по счётчикам заголовка. */
@@ -372,10 +445,19 @@ int topo_node_serialize(struct TOPO_NODE* ni, struct TOPO_GROUP_NODE* nq,
msg.group_id = group_id;
msg.node_id = ni->node_id;
msg.ver = ni->ver;
msg.timestamp = ni->timestamp;
memcpy(msg.public_key, ni->public_key, SC_PUBKEY_SIZE);
memcpy(msg.ed25519_public_key, ni->ed25519_public_key, SC_PUBKEY_SIZE);
memcpy(msg.x25519_self_sig, ni->x25519_self_sig, 64);
msg.node_name_len = ni->node_name ? (uint8_t)strlen(ni->node_name) : 0;
if ((ni->node_name && strlen(ni->node_name) > 63) ||
topo_list_count((struct _topo_head*)ni->v4_sock_meta) > UINT8_MAX ||
topo_list_count((struct _topo_head*)ni->v4_addrs) > UINT8_MAX ||
topo_list_count((struct _topo_head*)ni->v6_sock_meta) > UINT8_MAX ||
topo_list_count((struct _topo_head*)ni->v6_addrs) > UINT8_MAX ||
topo_list_count((struct _topo_head*)ni->reality_socks) > UINT8_MAX) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node exceeds wire format limits"); return -1;
}
msg.local_v4_sockets = topo_list_count((struct _topo_head*)ni->v4_sock_meta);
msg.local_v4_addrs = topo_list_count((struct _topo_head*)ni->v4_addrs);
msg.local_v6_sockets = topo_list_count((struct _topo_head*)ni->v6_sock_meta);
@@ -457,6 +539,7 @@ int topo_node_deserialize(struct TOPO_GROUP* group, const uint8_t* data, size_t
ni->group_ref_count = 0;
ni->node_id = msg->node_id;
ni->ver = msg->ver;
ni->timestamp = msg->timestamp;
memcpy(ni->public_key, msg->public_key, SC_PUBKEY_SIZE);
memcpy(ni->ed25519_public_key, msg->ed25519_public_key, SC_PUBKEY_SIZE);
memcpy(ni->x25519_self_sig, msg->x25519_self_sig, 64);
@@ -862,6 +945,7 @@ int topo_group_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct TOPO_GR
old_ver = oni->ver;
changed = (vc != (group->local_node->subnets ? topo_list_count((struct _topo_head*)group->local_node->subnets->v4_subnets) : 0))
|| (name_len != (oni->node_name ? strlen(oni->node_name) : 0))
|| (name_len && strncmp(instance->name, oni->node_name ? oni->node_name : "", name_len))
|| (memcmp(oni->public_key, instance->my_keys.public_key, SC_PUBKEY_SIZE) != 0)
|| (vc6 != (group->local_node->subnets ? topo_list_count((struct _topo_head*)group->local_node->subnets->v6_subnets) : 0))
|| (oni->client_type != instance->client_type)
@@ -875,6 +959,8 @@ int topo_group_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct TOPO_GR
group->local_node->last_ver, (unsigned long long)group->group_id);
if (changed) {
struct TOPO_NODE* ni = topo_node_registry_find(instance->topo_groups, instance->node_id);
if (ni) topo_node_ref(ni); /* новая группа удерживает запись до освобождения старой */
if (group->local_node) {
if (instance->rt) route_delete(instance->rt, group->local_node);
topo_nodeq_free_group_fields(instance->topo_groups, group->local_node);
@@ -883,12 +969,16 @@ int topo_group_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct TOPO_GR
}
group->local_node = u_calloc(1, sizeof(struct TOPO_GROUP_NODE));
if (!group->local_node) return -1;
if (!group->local_node) {
if (ni) topo_node_registry_unref(instance->topo_groups, instance->node_id);
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "local group node allocation failed"); return -1;
}
struct TOPO_GROUP_NODE* lq = group->local_node;
struct TOPO_NODE* ni = u_calloc(1, sizeof(struct TOPO_NODE));
int new_identity = ni == NULL;
if (!ni) ni = u_calloc(1, sizeof(struct TOPO_NODE));
if (!ni) { u_free(lq); group->local_node = NULL; return -1; }
ni->group_ref_count = 0;
struct TOPO_NODE old_identity = *ni;
ni->node_id = instance->node_id;
ni->ver = (old_ver % 255) + 1;
ni->client_type = instance->client_type;
@@ -896,15 +986,20 @@ int topo_group_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct TOPO_GR
lq->radio = group->radio_active;
memcpy(ni->public_key, instance->my_keys.public_key, SC_PUBKEY_SIZE);
memcpy(ni->ed25519_public_key, group->ed25519_public_key, SC_PUBKEY_SIZE);
if (name_len) { ni->node_name = u_malloc(name_len + 1); if (ni->node_name) { memcpy(ni->node_name, instance->name, name_len); ni->node_name[name_len] = 0; } }
{ uint64_t saved_node_id = ni->node_id; uint32_t saved_ver = ni->ver;
ni = topo_node_registry_store(instance->topo_groups, ni);
lq->node_id = saved_node_id;
lq->last_ver = saved_ver; }
/* адреса и sock_meta владеет topo_node_update_my_addresses (реестр + БД + подпись + рассылка) */
topo_node_sign_self(instance, ni);
ni->node_name = u_strdup(instance->name);
if (!ni->node_name || topo_node_sign_self(instance, ni) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot publish local identity");
u_free(ni->node_name); *ni = old_identity;
if (new_identity) topo_node_free_raw(instance->topo_groups, ni);
else topo_node_registry_unref(instance->topo_groups, instance->node_id);
u_free(lq); group->local_node = NULL; return -1;
}
u_free(old_identity.node_name);
if (new_identity) ni = topo_node_registry_store(instance->topo_groups, ni);
if (!ni) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot register local identity"); return -1; }
lq->node_id = ni->node_id;
lq->last_ver = ni->ver;
lq->last_timestamp = ni->timestamp;
if (group->group_type != TOPO_GROUP_TYPE_CHAT && (vc || vc6)) {
struct TOPO_NODESUBNETS* r = u_calloc(1, sizeof(struct TOPO_NODESUBNETS));
@@ -1061,13 +1156,26 @@ int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance) {
sockmeta6_list_equal(ni->v6_sock_meta, m6_head) && addr6_list_equal(ni->v6_addrs, v6_head) &&
reality_sock_list_equal(ni->reality_socks, r_head));
if (changed) {
struct TOPO_NODE candidate = *ni;
candidate.v4_sock_meta = m4_head; candidate.v4_addrs = v4_head;
candidate.v6_sock_meta = m6_head; candidate.v6_addrs = v6_head;
candidate.reality_socks = r_head;
candidate.ver = (ni->ver % 255) + 1;
if (topo_node_sign_self(instance, &candidate) < 0) {
free_v4_sock_list(instance->topo_groups->v4_sock_meta_pool, m4_head);
free_v4_addr_list(instance->topo_groups->v4_addr_pool, v4_head);
free_v6_sock_list(instance->topo_groups->v6_sock_meta_pool, m6_head);
free_v6_addr_list(instance->topo_groups->v6_addr_pool, v6_head);
free_reality_sock_list(r_head);
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "address snapshot publication failed"); return -1;
}
free_v4_sock_list(instance->topo_groups->v4_sock_meta_pool, ni->v4_sock_meta); ni->v4_sock_meta = m4_head;
free_v4_addr_list(instance->topo_groups->v4_addr_pool, ni->v4_addrs); ni->v4_addrs = v4_head;
free_v6_sock_list(instance->topo_groups->v6_sock_meta_pool, ni->v6_sock_meta); ni->v6_sock_meta = m6_head;
free_v6_addr_list(instance->topo_groups->v6_addr_pool, ni->v6_addrs); ni->v6_addrs = v6_head;
free_reality_sock_list(ni->reality_socks); ni->reality_socks = r_head;
ni->ver = (ni->ver % 255) + 1;
topo_node_sign_self(instance, ni);
ni->ver = candidate.ver; ni->timestamp = candidate.timestamp;
memcpy(ni->x25519_self_sig, candidate.x25519_self_sig, sizeof(ni->x25519_self_sig));
DEBUG_INFO(DEBUG_CATEGORY_BGP, "my addresses updated, new ver=%d", ni->ver);
} else {
free_v4_sock_list(instance->topo_groups->v4_sock_meta_pool, m4_head);