e2e: AES-128-CCM encryption via BGP router (ROUTER_FLAG_ENCRYPTED), ECDH key per peer-pair from NODEINFO pubkeys, cache 8 entries

- etcp_router.h/c: new flag ROUTER_FLAG_ENCRYPTED (0x04), etcp_route_send_encrypted(), inflight/retrans/send_q support
- utun_instance.h: e2e_ctx_cache[8] — per-peer sc_context_t cache with derived session key
- topo_node.c: topo_node_sign_self() — single point for Ed25519 self-signing of NODEINFO
- topo_node.c: fix stale x25519_self_sig after address update (topo_node_sign_self call)
- nat_detection.c: fix stale x25519_self_sig after in-place socket meta modification
- test_etcp_router_unit: 5 E2E tests (basic round-trip, no NODEINFO drop, tampered, cache reuse, ENCRYPTED+SIGNED combined)
- builds clean, 70/70 tests pass
This commit is contained in:
evgeny committed 2026-08-09 08:19:20 +03:00
1 parent ca176e1f24
commit b2e4aadbf0
44 files changed
+2586 -145

No files matched your search

+37 -12
View File
@@ -5,6 +5,7 @@
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/memory_pool.h"
#include "../lib/json_flat.h"
#include <string.h>
#include <stdio.h>
@@ -40,6 +41,7 @@ int topo_node_sqlite_init(sqlite3* db) {
" ed25519_pubkey BLOB,"
" last_seen_at INTEGER,"
" online INTEGER DEFAULT 0,"
" client_type INTEGER NOT NULL DEFAULT 0,"
" update_ts INTEGER DEFAULT 0,"
" created_at INTEGER DEFAULT (unixepoch())"
");"
@@ -102,6 +104,8 @@ int topo_node_sqlite_init(sqlite3* db) {
}
}
sqlite3_exec(db, "ALTER TABLE nodes ADD COLUMN client_type INTEGER NOT NULL DEFAULT 0", NULL, NULL, NULL);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "topo_node_sqlite tables initialized");
return 0;
}
@@ -116,18 +120,20 @@ int topo_node_sqlite_node_put(sqlite3* db, struct TOPO_GROUPS* groups, struct TO
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db,
"INSERT INTO nodes(node_id, name, x25519_pubkey, ed25519_pubkey, last_seen_at, online)"
" VALUES(?,?,?,?,?,1)"
"INSERT INTO nodes(node_id, name, x25519_pubkey, ed25519_pubkey, client_type, last_seen_at, online)"
" VALUES(?,?,?,?,?,?,1)"
" ON CONFLICT(node_id) DO UPDATE SET"
" name=CASE WHEN excluded.name != '' THEN excluded.name ELSE nodes.name END, x25519_pubkey=excluded.x25519_pubkey,"
" ed25519_pubkey=excluded.ed25519_pubkey, last_seen_at=excluded.last_seen_at,\n"
" ed25519_pubkey=excluded.ed25519_pubkey, client_type=CASE WHEN excluded.client_type!=0 THEN excluded.client_type ELSE nodes.client_type END,"
" last_seen_at=excluded.last_seen_at,\n"
" created_at=COALESCE(nodes.created_at, excluded.created_at)",
-1, &stmt, NULL) == SQLITE_OK) {
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)ni->node_id);
sqlite3_bind_text(stmt, 2, ni->node_name ? ni->node_name : "", -1, SQLITE_STATIC);
sqlite3_bind_blob(stmt, 3, ni->public_key, 32, SQLITE_STATIC);
sqlite3_bind_blob(stmt, 4, ni->ed25519_public_key, 32, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 5, (sqlite3_int64)now_sec);
sqlite3_bind_int(stmt, 5, (int)ni->client_type);
sqlite3_bind_int64(stmt, 6, (sqlite3_int64)now_sec);
sqlite3_step(stmt); sqlite3_finalize(stmt);
}
@@ -160,12 +166,12 @@ int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id,
" update_ts INTEGER DEFAULT 0," /* NTP timestamp подписи update_sig */
" userinfo TEXT NOT NULL DEFAULT ''," /* JSON с информацией о пользователе: {"name":"<display_name>"} */
" creator_sig BLOB," /* Ed25519 подпись создателя (PEERS_JOIN_SIG_SIZE=64), пока заглушка */
" adm_tags TEXT," /* теги администратора, key=value через пробел/запятую.
сейчас: "supernode=yes" → node_type=4 */
" adm_tags_sig BLOB," /* Ed25519 подпись над adm_tags (64 байта) — верификация пока не реализована */
" adm_tags TEXT," /* JSON-теги администратора: {"key":"val",...,"ver":"N"}.
подпись: sc_ed25519_sign(ch_ed_priv, json||node_id_8LE) */
" adm_tags_sig BLOB," /* Ed25519 подпись над adm_tags (64 байта) канальным ключом */
" node_type INTEGER NOT NULL DEFAULT 0," /* вычисляемый тип узла (topo_node_sqlite_nodeinfo_updated):
0=неизвестно 1=прямой(addr_type NETIF/DIRECT/NAT_STRICT)
2=EIM NAT 3=(не исп.) 4=суперузел(DIRECT+"supernode=yes") */
0=неизвестно 1=прямой(addr_type NETIF/DIRECT/NAT_STRICT)
2=EIM NAT 3=(не исп.) 4=суперузел(DIRECT+adm_tags.supernode=yes) */
" node_RTT INTEGER," /* RTT до узла в мс, часть индекса (node_type, node_RTT) */
" connected INTEGER NOT NULL DEFAULT 0," /* 1 = было активное подключение к узлу */
" storage INTEGER NOT NULL DEFAULT 0," /* 1 = приоритетный хранитель аттачей */
@@ -347,6 +353,21 @@ int topo_node_sqlite_channel_get(sqlite3* db, const char* channel_id,
return 0;
}
int topo_node_sqlite_channel_get_priv(sqlite3* db, const char* channel_id,
uint8_t* ed25519_priv_out) {
if (!db || !channel_id || !ed25519_priv_out) return -1;
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db,
"SELECT ed25519_privkey FROM channels WHERE channel_id=?", -1, &stmt, NULL) != SQLITE_OK) return -1;
sqlite3_bind_text(stmt, 1, channel_id, -1, SQLITE_STATIC);
if (sqlite3_step(stmt) != SQLITE_ROW) { sqlite3_finalize(stmt); return -1; }
const uint8_t* blob = (const uint8_t*)sqlite3_column_blob(stmt, 0);
if (!blob || sqlite3_column_bytes(stmt, 0) < 32) { sqlite3_finalize(stmt); return -1; }
memcpy(ed25519_priv_out, blob, 32);
sqlite3_finalize(stmt);
return 0;
}
int topo_node_sqlite_channel_peers_all(sqlite3* db, const char* channel_id,
uint8_t* buf, size_t buf_sz, size_t* out_len) {
if (!db || !channel_id || !buf || !out_len) return -1;
@@ -528,8 +549,9 @@ int topo_node_sqlite_nodeinfo_updated(sqlite3* db, uint64_t node_id) {
if (sqlite3_step(a) == SQLITE_ROW) {
const char* tags = (const char*)sqlite3_column_text(a, 0);
if (tags) {
if (strstr(tags, "supernode=yes")) node_type = 4;
if (strstr(tags, "storage=yes")) is_storage = 1;
char buf[64];
if (json_flat_get(tags, "supernode", buf, sizeof(buf)) == 0 && strcmp(buf, "yes") == 0) node_type = 4;
if (json_flat_get(tags, "storage", buf, sizeof(buf)) == 0 && strcmp(buf, "yes") == 0) is_storage = 1;
}
}
sqlite3_finalize(a);
@@ -598,10 +620,11 @@ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* gr
uint8_t pubkey[32] = {0}, ed_pubkey[32] = {0};
char* name = NULL;
uint8_t client_type = 0;
int found = 0;
{
sqlite3_stmt* s = NULL;
if (sqlite3_prepare_v2(db, "SELECT name,x25519_pubkey,ed25519_pubkey FROM nodes WHERE node_id=?",
if (sqlite3_prepare_v2(db, "SELECT name,x25519_pubkey,ed25519_pubkey,client_type FROM nodes WHERE node_id=?",
-1, &s, NULL) == SQLITE_OK) {
sqlite3_bind_int64(s, 1, (sqlite3_int64)node_id);
if (sqlite3_step(s) == SQLITE_ROW) {
@@ -611,6 +634,7 @@ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* gr
if (pk && sqlite3_column_bytes(s, 1) >= 32) memcpy(pubkey, pk, 32);
const void* ep = sqlite3_column_blob(s, 2);
if (ep && sqlite3_column_bytes(s, 2) >= 32) memcpy(ed_pubkey, ep, 32);
client_type = (uint8_t)sqlite3_column_int(s, 3);
found = 1;
}
sqlite3_finalize(s);
@@ -681,6 +705,7 @@ struct TOPO_NODE* topo_node_sqlite_node_load(sqlite3* db, struct TOPO_GROUPS* gr
struct TOPO_NODE* ni = u_calloc(1, sizeof(struct TOPO_NODE));
if (!ni) { u_free(name); return NULL; }
ni->node_id = node_id; ni->ver = 1; ni->group_ref_count = 0;
ni->client_type = client_type;
memcpy(ni->public_key, pubkey, 32); memcpy(ni->ed25519_public_key, ed_pubkey, 32);
ni->node_name = name;
ni->v4_addrs = v4_head;