diff --git a/doc/node_snapshot.md b/doc/node_snapshot.md new file mode 100644 index 00000000..d468a58a --- /dev/null +++ b/doc/node_snapshot.md @@ -0,0 +1,25 @@ +# Подписанная запись узла + +`TOPO_NODE.timestamp` — версия всей подписанной записи, а не отдельного адреса. +Узел-владелец назначает Unix microseconds, строго больше предыдущей сохранённой +версии. Имя, тип, активность, адреса, метаданные сокетов, REALITY и подпись +заменяются вместе. Timestamp входит в подпись. Групповые пути и подсети +остаются в `TOPO_GROUP_NODE` и не являются частью общей записи узла. + +Получатель проверяет подпись до изменения реестра или маршрутов. Более свежая +запись заменяет старую целиком, включая пустые списки адресов. Старые записи +игнорируются; разные подписанные записи с одинаковым timestamp — конфликт. +Повторное BGP-объявление может обновить путь, но не содержимое общей записи. +8-битный `ver` сохраняется как диагностический счётчик; свежесть определяется +только timestamp. + +Таблица `node_snapshots` хранит полную сериализованную запись без группового +окружения. `node_addresses` остаётся проекцией для поиска и отображения, а не +источником для восстановления подписанной записи. При наличии сохранённого +snapshot загрузка использует его. Локальная публикация сохраняется до рассылки; +ошибка сохранения не разрешает публикацию новой версии. + +Timestamp 0 обозначает bootstrap-сведения без подписи (конфиг, приглашение, +начальные адреса). Они могут помочь установить первое соединение, но не +заменяют принятую подписанную запись. По BGP принимаются подписанные записи +с ненулевым timestamp. Формат NODEINFO изменён без обратной совместимости. diff --git a/src/routing_layer/topo_group.c b/src/routing_layer/topo_group.c index bc35af43..028d3977 100644 --- a/src/routing_layer/topo_group.c +++ b/src/routing_layer/topo_group.c @@ -932,21 +932,6 @@ int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from struct TOPO_GROUP_NODE* nodeinfo1 = topo_node_find_by_id(group, node_id); uint8_t new_ver = ni->ver; - if (nodeinfo1 && (int8_t)(nodeinfo1->last_ver - new_ver) >= 0) { - DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "NODEINFO skip (stale ver): node=%016llx cur_ver=%d new_ver=%d from=%s", (unsigned long long)node_id, nodeinfo1->last_ver, new_ver, from->log_name); - int new_hops = ni->hop_count + 1; - if (new_hops <= MAX_HOPS) { - uint64_t hop_list[MAX_HOPS]; - const uint8_t* hop_src = (const uint8_t*)ni + sizeof(struct TOPOMSG_NODE) + dyn - ni->hop_count * 8; - memcpy(hop_list, hop_src, ni->hop_count * 8); - hop_list[ni->hop_count] = from->peer_node_id; - topo_group_remove_path(nodeinfo1, from); - { uint8_t _hc; uint16_t _rtt; topo_node_best_hop_list(nodeinfo1, &_hc, &_rtt); - topo_group_add_path(nodeinfo1, from, hop_list, new_hops, _rtt); } - } - return 0; - } - DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "NODEINFO: node=%016llx ver=%d v4s=%d v4a=%d v6s=%d v6a=%d hops=%d from=%s", (unsigned long long)node_id, new_ver, ni->local_v4_sockets, ni->local_v4_addrs, ni->local_v6_sockets, ni->local_v6_addrs, ni->hop_count, from->log_name); @@ -954,7 +939,7 @@ int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from struct ll_queue* paths = NULL; int is_new_node = 0; if (nodeinfo1) { - paths = nodeinfo1->paths; nodeinfo1->paths = NULL; + paths = nodeinfo1->paths; } else { is_new_node = 1; } const uint8_t* ser_data = data + 2; @@ -965,7 +950,6 @@ int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from uint16_t incoming_cumulative_rtt = 0; if (topo_node_deserialize(group, ser_data, ser_len, &new_ni, &new_subnets, &new_hop_list, &new_hop_count, &incoming_cumulative_rtt) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "NODEINFO deserialize failed from %s nid=%016llx", from->log_name, (unsigned long long)node_id); - if (nodeinfo1) { topo_nodeq_remove_node(group, nodeinfo1); queue_free(paths); } return -1; } { int v4c = topo_list_count((struct _topo_head*)new_ni->v4_addrs); @@ -973,63 +957,67 @@ int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "NODEINFO deser: nid=%016llx v4a=%d v6a=%d from=%s", (unsigned long long)node_id, v4c, v6c, from->log_name); } - /* verify Ed25519 self-signature over canonical message */ - if (node_id != sc_derive_node_id_from_pubkey(new_ni->public_key)) { - DEBUG_ERROR(DEBUG_CATEGORY_BGP, "NODEINFO node_id mismatch public_key (forgery) node=%016llx from=%s", - (unsigned long long)node_id, from->log_name); + /* Проверяем целую запись до изменения маршрутов и общего реестра. */ + struct TOPO_NODE* current = topo_node_registry_find(group->instance->topo_groups, node_id); + int conflict = current && current->timestamp == new_ni->timestamp && + memcmp(current->x25519_self_sig, new_ni->x25519_self_sig, 64); + if (topo_node_verify(new_ni) < 0 || conflict) { + DEBUG_WARN(DEBUG_CATEGORY_BGP, "NODEINFO rejected node=%016llx timestamp=%llu conflict=%d", + (unsigned long long)node_id, (unsigned long long)new_ni->timestamp, conflict); topo_node_destroy(group->instance->topo_groups, new_ni); - u_free(new_subnets); u_free(new_hop_list); - if (nodeinfo1) { topo_nodeq_remove_node(group, nodeinfo1); queue_free(paths); } + struct TOPO_GROUP_NODE rejected = { .subnets = new_subnets }; + topo_nodeq_free_group_fields(group->instance->topo_groups, &rejected); + u_free(new_hop_list); return -1; } - { - uint64_t ekchk; memcpy(&ekchk, new_ni->ed25519_public_key, 8); - if (ekchk == 0) { - DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "NODEINFO x25519_self_sig SKIP (ed25519 key zero) node=%016llx from=%s", - (unsigned long long)node_id, from->log_name); - } else { - uint8_t sig_msg[TOPO_SIG_MSG_MAX_SIZE]; - int sig_len = topo_node_build_sig_msg(new_ni, sig_msg, sizeof(sig_msg)); - if (sig_len <= 0 || sc_ed25519_verify(new_ni->ed25519_public_key, sig_msg, (size_t)sig_len, new_ni->x25519_self_sig) != SC_OK) { - DEBUG_ERROR(DEBUG_CATEGORY_BGP, "NODEINFO x25519_self_sig VERIFY FAIL node=%016llx ed_pubkey=%016llx... sig_len=%d from=%s — rejecting as forgery", - (unsigned long long)node_id, ekchk, sig_len, from->log_name); - topo_node_destroy(group->instance->topo_groups, new_ni); - u_free(new_subnets); u_free(new_hop_list); - if (nodeinfo1) { topo_nodeq_remove_node(group, nodeinfo1); queue_free(paths); } - return -1; - } - DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "NODEINFO x25519_self_sig OK node=%016llx ed_pubkey=%016llx... sig_len=%d from=%s", - (unsigned long long)node_id, ekchk, sig_len, from->log_name); - } + if (nodeinfo1 && new_ni->timestamp <= nodeinfo1->last_timestamp) { + DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "NODEINFO unchanged/stale node=%016llx current=%llu incoming=%llu", + (unsigned long long)node_id, (unsigned long long)nodeinfo1->last_timestamp, + (unsigned long long)new_ni->timestamp); + uint64_t hops[MAX_HOPS]; + if (new_hop_count) memcpy(hops, new_hop_list, new_hop_count * sizeof(*hops)); + hops[new_hop_count] = from->peer_node_id; + topo_group_remove_path(nodeinfo1, from); + topo_group_add_path(nodeinfo1, from, hops, new_hop_count + 1, incoming_cumulative_rtt); + topo_node_destroy(group->instance->topo_groups, new_ni); + struct TOPO_GROUP_NODE rejected = { .subnets = new_subnets }; + topo_nodeq_free_group_fields(group->instance->topo_groups, &rejected); + u_free(new_hop_list); + return 0; } - - if (nodeinfo1) { - topo_nodeq_free_group_fields(group->instance->topo_groups, nodeinfo1); - { struct TOPO_NODE* stored = topo_node_registry_store(group->instance->topo_groups, new_ni); - if (stored != new_ni) new_ni = stored; } - nodeinfo1->node_id = new_ni ? new_ni->node_id : 0; - nodeinfo1->subnets = new_subnets; - } else { + uint64_t incoming_timestamp = new_ni->timestamp; + if (!nodeinfo1) { struct ll_entry* qe = queue_entry_new(sizeof(struct TOPO_GROUP_NODE)); - if (!qe) { topo_node_destroy(group->instance->topo_groups, new_ni); u_free(new_hop_list); u_free(new_subnets); return -1; } + if (!qe) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "group node allocation failed"); + topo_node_destroy(group->instance->topo_groups, new_ni); + struct TOPO_GROUP_NODE rejected = { .subnets = new_subnets }; + topo_nodeq_free_group_fields(group->instance->topo_groups, &rejected); + u_free(new_hop_list); return -1; + } nodeinfo1 = (struct TOPO_GROUP_NODE*)qe; 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; } - nodeinfo1->node_id = new_ni ? new_ni->node_id : 0; - nodeinfo1->subnets = new_subnets; - nodeinfo1->connectivity.probe_status = PROBE_STATUS_NONE; - nodeinfo1->connectivity.interface_status = PROBE_RESULT_UNKNOWN; - nodeinfo1->connectivity.nat_status = PROBE_RESULT_UNKNOWN; - nodeinfo1->connectivity.real_status = PROBE_RESULT_UNKNOWN; - nodeinfo1->connectivity.ping_req_time = 0; - queue_data_put_with_index(group->nodes, &nodeinfo1->ll); } + struct TOPO_NODE* stored = topo_node_registry_store(group->instance->topo_groups, new_ni); + if (!stored) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "node registry update failed"); + topo_node_destroy(group->instance->topo_groups, new_ni); + struct TOPO_GROUP_NODE rejected = { .subnets = new_subnets }; + topo_nodeq_free_group_fields(group->instance->topo_groups, &rejected); + if (is_new_node) queue_entry_free(&nodeinfo1->ll); + u_free(new_hop_list); return -1; + } + new_ni = stored; + if (!is_new_node) topo_nodeq_free_group_fields(group->instance->topo_groups, nodeinfo1); + nodeinfo1->node_id = node_id; + nodeinfo1->subnets = new_subnets; + if (is_new_node) queue_data_put_with_index(group->nodes, &nodeinfo1->ll); if (is_new_node) DEBUG_INFO(DEBUG_CATEGORY_BGP, "node added to group: node=%016llx grp=%016llx type=%u ch=%s (bgp)", (unsigned long long)node_id, (unsigned long long)group->group_id, group->group_type, group->channel_id); nodeinfo1->paths = paths; nodeinfo1->last_ver = new_ver; + nodeinfo1->last_timestamp = incoming_timestamp; nodeinfo1->radio = (ni->flags & TOPO_FLAG_RADIO) ? 1 : 0; uint64_t hop_list[MAX_HOPS]; @@ -1049,6 +1037,8 @@ int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from if (node_id != group->instance->node_id) { sqlite3* sdb = group->instance->topo_sqlite_db; if (sdb) { + if (topo_node_sqlite_snapshot_put(sdb, new_ni) < 0) + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "cannot persist received NODEINFO node=%016llx", (unsigned long long)node_id); /* узлы в nodes пишет только merkle-путь; BGP лишь дополняет IP для мемберов */ if (topo_node_sqlite_member_exists(sdb, node_id)) topo_node_sqlite_addrs_put(sdb, node_id, new_ni); diff --git a/src/routing_layer/topo_node.c b/src/routing_layer/topo_node.c index 15054d00..bd92a9b8 100644 --- a/src/routing_layer/topo_node.c +++ b/src/routing_layer/topo_node.c @@ -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); diff --git a/src/routing_layer/topo_node.h b/src/routing_layer/topo_node.h index 9cf4ae9e..0eb9dbcc 100644 --- a/src/routing_layer/topo_node.h +++ b/src/routing_layer/topo_node.h @@ -129,6 +129,7 @@ struct TOPOMSG_REALITY_SOCK { struct TOPOMSG_NODE { uint8_t flags; uint64_t group_id, node_id; uint8_t ver; + uint64_t timestamp; // версия всей подписанной записи, Unix microseconds uint8_t public_key[SC_PUBKEY_SIZE], ed25519_public_key[SC_PUBKEY_SIZE]; uint8_t x25519_self_sig[64]; uint8_t node_name_len, local_v4_sockets, local_v4_addrs, local_v6_sockets, local_v6_addrs; @@ -175,6 +176,7 @@ struct TOPO_NODE { struct ll_entry ll; uint32_t group_ref_count; uint64_t node_id; uint8_t ver; + uint64_t timestamp; // 0 = неподписанные bootstrap-сведения 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 @@ -206,6 +208,7 @@ struct TOPO_GROUP_NODE { struct TOPO_NODESUBNETS* subnets; // подсети узла (v4/v6), для route_insert() struct ll_queue* paths; // TOPO_NODEPATH{conn,hop_count,rtt} — маршруты uint8_t last_ver; // последняя версия NODEINFO (защита от stale, BGP) + uint64_t last_timestamp; // последняя принятая версия записи в группе uint8_t conn_mgr_type; // CONN_TYPE_DIRECT/REVERSE/INDIRECT/NONE uint64_t conn_mgr_intermediaries[CONN_MGR_MAX_INTERMEDIARIES]; // посредники для INDIRECT uint8_t conn_mgr_intermediariy_count; // количество посредников @@ -253,7 +256,8 @@ uint16_t topo_get_chain_rtt(struct TOPO_GROUP_NODE* nq); int topo_node_build_sig_msg(struct TOPO_NODE* ni, uint8_t* buf, size_t buf_size); /** Sign self NODEINFO with Ed25519: build_sig_msg + sc_ed25519_sign → ni->x25519_self_sig */ -void topo_node_sign_self(struct UTUN_INSTANCE* instance, struct TOPO_NODE* ni); +int topo_node_sign_self(struct UTUN_INSTANCE* instance, struct TOPO_NODE* ni); +int topo_node_verify(const struct TOPO_NODE* ni); static inline const struct TOPO_SOCKMETA4* topo_v4_sock_meta(const struct TOPO_NODE* ni) { return ni->v4_sock_meta; } static inline const struct TOPO_ADDR4* topo_v4_addrs(const struct TOPO_NODE* ni) { return ni->v4_addrs; } diff --git a/src/routing_layer/topo_node_sqlite.c b/src/routing_layer/topo_node_sqlite.c index 415b48d6..60bccf0e 100644 --- a/src/routing_layer/topo_node_sqlite.c +++ b/src/routing_layer/topo_node_sqlite.c @@ -253,6 +253,8 @@ int topo_node_sqlite_init(sqlite3* db) { if (!db) return -1; const char* sql = + "CREATE TABLE IF NOT EXISTS node_snapshots (" + "node_id INTEGER PRIMARY KEY, timestamp INTEGER NOT NULL, record BLOB NOT NULL);" "CREATE TABLE IF NOT EXISTS nodes (" " node_id INTEGER PRIMARY KEY," " name TEXT," @@ -903,8 +905,68 @@ void topo_node_sqlite_update_rtt(sqlite3* db, uint64_t node_id, uint16_t rtt) { (unsigned long long)node_id, (unsigned)rtt); } +uint64_t topo_node_sqlite_snapshot_timestamp(sqlite3* db, uint64_t node_id) { + sqlite3_stmt* stmt = NULL; + uint64_t timestamp = 0; + if (sqlite3_prepare_v2(db, "SELECT timestamp FROM node_snapshots WHERE node_id=?", -1, &stmt, NULL) != SQLITE_OK) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "snapshot timestamp: %s", sqlite3_errmsg(db)); return 0; + } + sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id); + int rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) timestamp = (uint64_t)sqlite3_column_int64(stmt, 0); + else if (rc != SQLITE_DONE) DEBUG_ERROR(DEBUG_CATEGORY_BGP, "snapshot timestamp read: %s", sqlite3_errmsg(db)); + sqlite3_finalize(stmt); + return timestamp; +} + +int topo_node_sqlite_snapshot_put(sqlite3* db, struct TOPO_NODE* ni) { + if (!db || topo_node_verify(ni) < 0) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid node snapshot"); return -1; } + uint8_t record[4096]; + struct TOPO_GROUP_NODE nq = {0}; + int len = topo_node_serialize(ni, &nq, 0, 0, record, sizeof(record), 0); + if (len < 0) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "snapshot serialization failed"); return -1; } + sqlite3_stmt* stmt = NULL; + const char* sql = "INSERT INTO node_snapshots(node_id,timestamp,record) VALUES(?,?,?) " + "ON CONFLICT(node_id) DO UPDATE SET timestamp=excluded.timestamp,record=excluded.record " + "WHERE excluded.timestamp > node_snapshots.timestamp"; + int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + sqlite3_bind_int64(stmt, 1, (sqlite3_int64)ni->node_id); + sqlite3_bind_int64(stmt, 2, (sqlite3_int64)ni->timestamp); + sqlite3_bind_blob(stmt, 3, record, len, SQLITE_STATIC); + rc = sqlite3_step(stmt); + } + if (rc != SQLITE_DONE) DEBUG_ERROR(DEBUG_CATEGORY_BGP, "snapshot write: %s", sqlite3_errmsg(db)); + sqlite3_finalize(stmt); + return rc == SQLITE_DONE ? 0 : -1; +} + +struct TOPO_NODE* topo_node_sqlite_snapshot_load(sqlite3* db, struct TOPO_GROUPS* groups, uint64_t node_id) { + sqlite3_stmt* stmt = NULL; + struct TOPO_NODE* ni = NULL; + if (sqlite3_prepare_v2(db, "SELECT record FROM node_snapshots WHERE node_id=?", -1, &stmt, NULL) != SQLITE_OK) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "snapshot load: %s", sqlite3_errmsg(db)); return NULL; + } + sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id); + int rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + struct TOPO_GROUP group = { .instance = groups->instance }; + const uint8_t* data = sqlite3_column_blob(stmt, 0); + int len = sqlite3_column_bytes(stmt, 0); + if (topo_node_deserialize(&group, data, (size_t)len, &ni, NULL, NULL, NULL, NULL) < 0 || + !ni || ni->node_id != node_id || topo_node_verify(ni) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "invalid persisted snapshot node=%016llx", (unsigned long long)node_id); + topo_node_destroy(groups, ni); ni = NULL; + } + } else if (rc != SQLITE_DONE) DEBUG_ERROR(DEBUG_CATEGORY_BGP, "snapshot read: %s", sqlite3_errmsg(db)); + sqlite3_finalize(stmt); + return ni; +} + struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* groups, uint64_t node_id) { if (!db || !groups) return NULL; + struct TOPO_NODE* snapshot = topo_node_sqlite_snapshot_load(db, groups, node_id); + if (snapshot) return snapshot; uint8_t pubkey[32] = {0}, ed_pubkey[32] = {0}; char* name = NULL; diff --git a/src/routing_layer/topo_node_sqlite.h b/src/routing_layer/topo_node_sqlite.h index ddea9f8d..e01a9517 100644 --- a/src/routing_layer/topo_node_sqlite.h +++ b/src/routing_layer/topo_node_sqlite.h @@ -15,6 +15,9 @@ #define PEERS_FLAG_HAS_JOIN 0x01 int topo_node_sqlite_init(sqlite3* db); +int topo_node_sqlite_snapshot_put(sqlite3* db, struct TOPO_NODE* ni); +uint64_t topo_node_sqlite_snapshot_timestamp(sqlite3* db, uint64_t node_id); +struct TOPO_NODE* topo_node_sqlite_snapshot_load(sqlite3* db, struct TOPO_GROUPS* groups, uint64_t node_id); /* Персистентная запись адресов узла в node_addresses (источник истины — BGP/topo_group). * DELETE + INSERT из ni->v4_addrs/v6_addrs, классификация config_type/nat_type из SOCKMETA. */ diff --git a/tests/Makefile.am b/tests/Makefile.am index b0dbb14b..d5917a13 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -64,6 +64,7 @@ check_PROGRAMS = \ test_invite_group_create \ test_etcp_connect \ test_node_conn_direct \ + test_node_snapshot \ test_db_sync \ test_merkle_sync \ test_merkle_protocol \ @@ -387,6 +388,10 @@ test_node_conn_direct_SOURCES = test_node_conn_direct.c test_node_conn_direct_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib test_node_conn_direct_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS) +test_node_snapshot_SOURCES = test_node_snapshot.c +test_node_snapshot_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib +test_node_snapshot_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS) + test_db_sync_SOURCES = test_db_sync.c test_db_sync_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib test_db_sync_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS) diff --git a/tests/test_etcp_stcp.c b/tests/test_etcp_stcp.c index cafb0286..b2c2a343 100644 --- a/tests/test_etcp_stcp.c +++ b/tests/test_etcp_stcp.c @@ -106,14 +106,13 @@ int main(void) { struct TOPO_GROUP_NODE *srv_node = topo_groups_get_default(srv_inst->topo_groups) ? topo_groups_get_default(srv_inst->topo_groups)->local_node : NULL; TASSERT(srv_node); { struct TOPO_NODE* srv_ni = topo_node_registry_find(srv_inst->topo_groups, srv_node->node_id); TASSERT(srv_ni); - struct TOPO_NODE* cli_ni = u_calloc(1, sizeof(struct TOPO_NODE)); - TASSERT(cli_ni); memcpy(cli_ni, srv_ni, sizeof(struct TOPO_NODE)); cli_ni->group_ref_count = 0; - cli_ni->v4_sock_meta = NULL; cli_ni->v4_addrs = NULL; cli_ni->v6_sock_meta = NULL; cli_ni->v6_addrs = NULL; - struct TOPO_ADDR4* a = memory_pool_alloc(cli_inst->topo_groups->v4_addr_pool); - TASSERT(a); a->addr[0]=127; a->addr[1]=0; a->addr[2]=0; a->addr[3]=1; a->port = port; - a->type = TOPO_ADDR_INTERFACE; a->socket_id = 0; a->protocol = TOPO_PROTO_TCP; - a->next = cli_ni->v4_addrs; cli_ni->v4_addrs = a; - topo_node_registry_store(cli_inst->topo_groups, cli_ni); } + uint8_t wire[4096]; + int len = topo_node_serialize(srv_ni, srv_node, 0, 0, wire, sizeof(wire), 0); + TASSERT(len > 0); + struct TOPO_NODE* cli_ni = NULL; + struct TOPO_GROUP* cli_group = topo_groups_get_default(cli_inst->topo_groups); + TASSERT(topo_node_deserialize(cli_group, wire, (size_t)len, &cli_ni, NULL, NULL, NULL, NULL) == 0); + TASSERT(topo_node_registry_store(cli_inst->topo_groups, cli_ni)); } cli_inst->etcp_connect_timeout_tb = 100000; TASSERT(etcp_connect(cli_inst, srv_node, connect_cb, NULL, ETCP_CONNECT_EARLY | ETCP_CONNECT_LATE) == 0); diff --git a/tests/test_node_snapshot.c b/tests/test_node_snapshot.c new file mode 100644 index 00000000..c7760258 --- /dev/null +++ b/tests/test_node_snapshot.c @@ -0,0 +1,150 @@ +#include +#include +#include +#include "utun_instance.h" +#include "routing_layer/topo_group.h" +#include "routing_layer/topo_node_sqlite.h" +#include "transport_layer/secure_channel.h" +#include "transport_layer/etcp.h" +#include "../lib/mem.h" +#include "../lib/memory_pool.h" +#include "../lib/u_async.h" +#include "../lib/debug_config.h" + +static void sign_record(struct UTUN_INSTANCE* inst, struct TOPO_NODE* ni) { + uint8_t msg[TOPO_SIG_MSG_MAX_SIZE]; + int len = topo_node_build_sig_msg(ni, msg, sizeof(msg)); + assert(len > 0); + assert(sc_ed25519_sign(inst->my_ed25519_privkey, msg, (size_t)len, ni->x25519_self_sig) == SC_OK); +} + +static struct TOPO_NODE* record(struct UTUN_INSTANCE* inst, uint64_t timestamp, const char* name, int address) { + struct TOPO_NODE* ni = u_calloc(1, sizeof(*ni)); + assert(ni); + ni->node_id = inst->node_id; + ni->timestamp = timestamp; + ni->node_name = u_strdup(name); + memcpy(ni->public_key, inst->my_keys.public_key, 32); + memcpy(ni->ed25519_public_key, inst->my_ed25519_pubkey, 32); + if (address) { + ni->v4_addrs = memory_pool_alloc(inst->topo_groups->v4_addr_pool); + assert(ni->v4_addrs); + *ni->v4_addrs = (struct TOPO_ADDR4){ .addr = {127,0,0,1}, .port = 9000, .protocol = TOPO_PROTO_UDP }; + } + sign_record(inst, ni); + return ni; +} + +static void test_bgp(struct UTUN_INSTANCE* signer) { + struct UTUN_INSTANCE receiver = { .ua = signer->ua, .topo_groups = signer->topo_groups }; + struct TOPO_GROUP group = { .instance = &receiver, .group_id = 10, .group_type = TOPO_GROUP_TYPE_CHAT }; + struct ETCP_CONN from = { .instance = &receiver, .peer_node_id = 7 }; + group.nodes = queue_new(receiver.ua, 16, 0, 8, "snapshot_bgp"); + struct TOPO_NODE* ni = record(signer, INT64_MAX - 7, "BGP", 1); + struct TOPO_GROUP_NODE nq = {0}; + uint8_t packet[4096] = {0}; + int len = topo_node_serialize(ni, &nq, group.group_id, 0, packet + 2, sizeof(packet) - 2, 0); + assert(len > 0); + assert(topo_group_process_nodeinfo(&group, &from, packet, (size_t)len + 2) == 0); + struct TOPO_GROUP_NODE* peer = topo_node_find_by_id(&group, ni->node_id); + assert(peer && peer->last_timestamp == ni->timestamp && queue_entry_count(peer->paths) == 1); + assert(topo_group_process_nodeinfo(&group, &from, packet, (size_t)len + 2) == 0); + assert(queue_entry_count(peer->paths) == 1); + struct TOPOMSG_NODEINFO_PKT* wire = (struct TOPOMSG_NODEINFO_PKT*)packet; + wire->node.timestamp++; /* подделка не удаляет узел и ранее принятый маршрут */ + assert(topo_group_process_nodeinfo(&group, &from, packet, (size_t)len + 2) < 0); + assert(topo_node_find_by_id(&group, ni->node_id) == peer && queue_entry_count(peer->paths) == 1); + wire->node.timestamp--; + ni->timestamp--; + sign_record(signer, ni); + len = topo_node_serialize(ni, &nq, group.group_id, 0, packet + 2, sizeof(packet) - 2, 0); + assert(topo_group_process_nodeinfo(&group, &from, packet, (size_t)len + 2) == 0); + assert(peer->last_timestamp == INT64_MAX - 7 && queue_entry_count(peer->paths) == 1); + topo_group_remove_path(peer, &from); + topo_nodeq_remove_node(&group, peer); + topo_node_destroy(signer->topo_groups, ni); + queue_free(group.nodes); +} + +int main(void) { + debug_config_init(); + debug_set_level(DEBUG_LEVEL_DEBUG); + struct UTUN_INSTANCE inst = {0}; + struct TOPO_GROUPS groups = { .instance = &inst }; + inst.topo_groups = &groups; + inst.ua = uasync_create(); + assert(inst.ua); + groups.node_registry = queue_new(inst.ua, 16, 0, 8, "snapshot_test"); + groups.v4_addr_pool = memory_pool_init(sizeof(struct TOPO_ADDR4), "snapshot_v4"); + assert(sc_generate_keypair(&inst.my_keys) == SC_OK); + memcpy(inst.my_ed25519_privkey, inst.my_keys.private_key, 32); + EVP_PKEY* key = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, inst.my_ed25519_privkey, 32); + size_t key_len = 32; + assert(key && EVP_PKEY_get_raw_public_key(key, inst.my_ed25519_pubkey, &key_len) == 1); + EVP_PKEY_free(key); + inst.node_id = sc_derive_node_id_from_pubkey(inst.my_keys.public_key); + assert(sqlite3_open(":memory:", &inst.topo_sqlite_db) == SQLITE_OK); + assert(topo_node_sqlite_init(inst.topo_sqlite_db) == 0); + + struct TOPO_NODE* first = record(&inst, 100, "old", 1); + assert(topo_node_verify(first) == 0); + first->timestamp++; + assert(topo_node_verify(first) < 0); /* timestamp защищён подписью */ + first->timestamp--; + assert(topo_node_registry_store(&groups, first) == first); + struct TOPO_NODE* stale = record(&inst, 99, "stale", 0); + assert(topo_node_registry_store(&groups, stale) == first); + topo_node_registry_unref(&groups, inst.node_id); + assert(first->timestamp == 100 && first->v4_addrs && !strcmp(first->node_name, "old")); + + struct TOPO_NODE* conflict = record(&inst, 100, "conflict", 0); + assert(topo_node_registry_store(&groups, conflict) == first); + topo_node_registry_unref(&groups, inst.node_id); + assert(first->v4_addrs && !strcmp(first->node_name, "old")); + + assert(topo_node_sqlite_snapshot_put(inst.topo_sqlite_db, first) == 0); + struct TOPO_NODE* loaded = topo_node_sqlite_snapshot_load(inst.topo_sqlite_db, &groups, inst.node_id); + assert(loaded && loaded->timestamp == 100 && loaded->v4_addrs && topo_node_verify(loaded) == 0); + topo_node_destroy(&groups, loaded); + + struct TOPO_NODE* fresh = record(&inst, 101, "new", 0); + assert(topo_node_registry_store(&groups, fresh) == first); + topo_node_registry_unref(&groups, inst.node_id); + assert(first->timestamp == 101 && !first->v4_addrs && !strcmp(first->node_name, "new")); + assert(topo_node_verify(first) == 0); /* удаление адресов не оставляет частей старой записи */ + assert(topo_node_sqlite_snapshot_put(inst.topo_sqlite_db, first) == 0); + loaded = record(&inst, 100, "old", 1); + assert(topo_node_sqlite_snapshot_put(inst.topo_sqlite_db, loaded) == 0); + topo_node_destroy(&groups, loaded); + loaded = topo_node_sqlite_snapshot_load(inst.topo_sqlite_db, &groups, inst.node_id); + assert(loaded && loaded->timestamp == 101 && !loaded->v4_addrs && topo_node_verify(loaded) == 0); + topo_node_destroy(&groups, loaded); + + /* Сохранённая версия из будущего: локальные часы/перезапуск не откатывают timestamp. */ + topo_node_registry_unref(&groups, inst.node_id); + first = topo_node_registry_store(&groups, record(&inst, 99, "stale after restart", 1)); + assert(first && first->timestamp == 101 && !first->v4_addrs && topo_node_verify(first) == 0); + first->timestamp = INT64_MAX - 10; + sign_record(&inst, first); + assert(topo_node_sqlite_snapshot_put(inst.topo_sqlite_db, first) == 0); + first->timestamp = 0; + assert(topo_node_sign_self(&inst, first) == 0); + assert(first->timestamp == INT64_MAX - 9 && topo_node_verify(first) == 0); + assert(topo_node_sign_self(&inst, first) == 0); + assert(first->timestamp == INT64_MAX - 8 && topo_node_verify(first) == 0); + test_bgp(&inst); + uint64_t before = first->timestamp; + assert(sqlite3_exec(inst.topo_sqlite_db, + "CREATE TRIGGER fail_snapshot BEFORE UPDATE ON node_snapshots BEGIN SELECT RAISE(ABORT,'test write failure'); END", + NULL, NULL, NULL) == SQLITE_OK); + assert(topo_node_sign_self(&inst, first) < 0); + assert(first->timestamp == before && topo_node_verify(first) == 0); + + topo_node_registry_unref(&groups, inst.node_id); + assert(!groups.node_registry->head); + queue_free(groups.node_registry); + memory_pool_destroy(groups.v4_addr_pool); + sqlite3_close(inst.topo_sqlite_db); + uasync_destroy(inst.ua, 0); + return 0; +} diff --git a/tests/test_stcp_traffic.c b/tests/test_stcp_traffic.c index 922aeaad..c19b3121 100644 --- a/tests/test_stcp_traffic.c +++ b/tests/test_stcp_traffic.c @@ -161,14 +161,13 @@ int main(void) { struct TOPO_GROUP_NODE *srv_node = topo_groups_get_default(srv_inst->topo_groups) ? topo_groups_get_default(srv_inst->topo_groups)->local_node : NULL; TASSERT(srv_node); { struct TOPO_NODE* srv_ni = topo_node_registry_find(srv_inst->topo_groups, srv_node->node_id); TASSERT(srv_ni); - struct TOPO_NODE* cli_ni = u_calloc(1, sizeof(struct TOPO_NODE)); - TASSERT(cli_ni); memcpy(cli_ni, srv_ni, sizeof(struct TOPO_NODE)); cli_ni->group_ref_count = 0; - cli_ni->v4_sock_meta = NULL; cli_ni->v4_addrs = NULL; cli_ni->v6_sock_meta = NULL; cli_ni->v6_addrs = NULL; - struct TOPO_ADDR4* a = memory_pool_alloc(cli_inst->topo_groups->v4_addr_pool); - TASSERT(a); a->addr[0]=127; a->addr[1]=0; a->addr[2]=0; a->addr[3]=1; a->port = port; - a->type = TOPO_ADDR_INTERFACE; a->socket_id = 0; a->protocol = TOPO_PROTO_TCP; - a->next = cli_ni->v4_addrs; cli_ni->v4_addrs = a; - topo_node_registry_store(cli_inst->topo_groups, cli_ni); } + uint8_t wire[4096]; + int len = topo_node_serialize(srv_ni, srv_node, 0, 0, wire, sizeof(wire), 0); + TASSERT(len > 0); + struct TOPO_NODE* cli_ni = NULL; + struct TOPO_GROUP* cli_group = topo_groups_get_default(cli_inst->topo_groups); + TASSERT(topo_node_deserialize(cli_group, wire, (size_t)len, &cli_ni, NULL, NULL, NULL, NULL) == 0); + TASSERT(topo_node_registry_store(cli_inst->topo_groups, cli_ni)); } cli_inst->etcp_connect_timeout_tb = 100000; TASSERT(etcp_connect(cli_inst, srv_node, connect_cb, NULL, ETCP_CONNECT_EARLY | ETCP_CONNECT_LATE) == 0);