Browse Source

stcp: add on_close callback to stcp_server_create/stcp_client_connect; member_sync: add join_ts/update_sig/update_ts fields, verify update_sig in apply; topo_node_sqlite: add join_ts/update_sig/update_ts to member_put, add member_get_join, add node_update_verified; chat_sync: include join_ts in join_msg, add update_sig/update_ts to CHANNEL_INFO_RESP; WELCOME verify join_sig; tests: use generated node_ids, reduce max ticks

topo_upd
Evgeny 3 months ago
parent
commit
3d04c37f56
  1. 14
      src/config_updater.c
  2. 3
      src/etcp.c
  3. 19
      src/etcp_connect.c
  4. 73
      src/etcp_connect.h
  5. 4
      src/etcp_connections.c
  6. 10
      src/secure_channel.c
  7. 2
      src/secure_channel.h
  8. 4
      src/stcp_client.c
  9. 4
      src/stcp_client.h
  10. 4
      src/stcp_link.c
  11. 9
      src/stcp_server.c
  12. 6
      src/stcp_server.h
  13. 2
      src/topo_group.c
  14. 115
      src/topo_node_sqlite.c
  15. 18
      src/topo_node_sqlite.h
  16. 13
      src/utun_instance.c
  17. 14
      tests/bbr_integration/test_bbr_integration.c
  18. 35
      tests/test_bgp_route_exchange.c
  19. 143
      tests/test_bgp_triangle.c
  20. 21
      tests/test_conn_mgr.c
  21. 21
      tests/test_etcp_connect.c
  22. 10
      tests/test_etcp_router.c
  23. 21
      tests/test_etcp_router_reconnect.c
  24. 10
      tests/test_icmp_proxy.c
  25. 4
      tests/test_ipv6_sockets.c
  26. 26
      tests/test_lwip_tcp.c
  27. 33
      tests/test_nat_detection.c
  28. 35
      tests/test_nat_transport.c
  29. 19
      tests/test_route_ping.c
  30. 4
      tests/test_socks_http_proxy.c
  31. 37
      tests/test_stcp.c
  32. 2
      tests/test_uasync_socket_race.c
  33. 10
      tests/test_udp_proxy.c
  34. 12
      tools/chatgui/transport/chat_core.c
  35. 134
      tools/chatgui/transport/chat_sync.c
  36. 145
      tools/chatgui/transport/member_sync.c
  37. 21
      tools/chatgui/transport/member_sync.h
  38. 4
      tools/chatgui/transport/merkle_sync.h

14
src/config_updater.c

@ -5,7 +5,6 @@
#include "secure_channel.h"
#include "debug_config.h"
#include "../lib/platform_compat.h"
#include "../lib/sha256.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -111,10 +110,10 @@ static int insert_or_replace_option(char **buf, size_t *buf_len, size_t *buf_cap
char *opt_pos = find_option(*buf, *buf_len, option);
if (opt_pos) {
// Determine if old line has trailing \n
// Determine end of old line (1 byte after \n or end of buffer)
char *nl = strchr(opt_pos, '\n');
int has_trailing_nl = (nl != NULL);
char *line_end = nl ? nl : *buf + *buf_len;
char *line_end = nl ? nl + 1 : *buf + *buf_len;
// Prepare new line
char new_line[MAX_LINE_LEN];
@ -127,7 +126,7 @@ static int insert_or_replace_option(char **buf, size_t *buf_len, size_t *buf_cap
if (new_len <= 0 || new_len >= (int)sizeof(new_line)) return -1;
// Calculate length difference
size_t old_line_len = has_trailing_nl ? (line_end - opt_pos + 1) : (line_end - opt_pos);
size_t old_line_len = line_end - opt_pos;
long len_diff = new_len - old_line_len;
// Ensure buffer capacity
@ -143,7 +142,7 @@ static int insert_or_replace_option(char **buf, size_t *buf_len, size_t *buf_cap
if (!opt_pos) return -1;
nl = strchr(opt_pos, '\n');
has_trailing_nl = (nl != NULL);
line_end = nl ? nl : *buf + *buf_len;
line_end = nl ? nl + 1 : *buf + *buf_len;
}
// Move content and insert new line
@ -294,9 +293,8 @@ int config_ensure_keys_and_node_id(const char *filename) {
global->my_public_key_hex[0] ? global->my_public_key_hex : "NULL", new_pub_key);
}
// ── Step 3: derive node_id from privkey via SHA-256 ──
{ uint8_t sha_hash[32]; SC_SHA256_CTX ctx; sc_sha256_init(&ctx); sc_sha256_update(&ctx, priv_bin, SC_PRIVKEY_SIZE); sc_sha256_final(&ctx, sha_hash);
memcpy(&new_node_id, sha_hash, 8); new_node_id &= 0x7FFFFFFFFFFFFFFFULL; }
// ── Step 3: derive node_id from privkey ──
new_node_id = sc_derive_node_id(priv_bin);
if (!is_valid_node_id(global->my_node_id) || global->my_node_id != new_node_id) {
need_node_id = 1;
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "Node_id mismatch (or missing), fixing: config=%016llx derived=%016llx file=%s",

3
src/etcp.c

@ -1,6 +1,7 @@
// etcp.c - ETCP Protocol Implementation (refactored and expanded based on etcp_protocol.txt)
#include "etcp.h"
#include "etcp_connect.h"
#include "etcp_debug.h"
#include "etcp_loadbalancer.h"
#include "etcp_router.h"
@ -328,6 +329,8 @@ static void etcp_connection_free_resources(struct ETCP_CONN* etcp) {
if (etcp->inflight_pool) { memory_pool_destroy(etcp->inflight_pool); etcp->inflight_pool = NULL; }
if (etcp->io_pool) { memory_pool_destroy(etcp->io_pool); etcp->io_pool = NULL; }
etcp_connect_cancel_for_conn(etcp->instance, etcp);
u_free(etcp->name);
u_free(etcp);
}

19
src/etcp_connect.c

@ -1,5 +1,6 @@
#include "etcp_api.h"
#include "etcp.h"
#include "etcp_connect.h"
#include "utun_instance.h"
#include "topo_node.h"
#include "stcp_link.h"
@ -284,3 +285,21 @@ int etcp_connect(struct UTUN_INSTANCE* inst, struct TOPO_NODEQ* node,
DEBUG_INFO(DEBUG_CATEGORY_ETCP_CONNECT, "[etcp_connect] started for node 0x%016llx", (unsigned long long)node_id);
return 0;
}
void etcp_connect_cancel_for_conn(struct UTUN_INSTANCE* inst, struct ETCP_CONN* conn) {
if (!inst || !conn) return;
struct ETCP_CONNECT** pp = &inst->pending_connects;
while (*pp) {
struct ETCP_CONNECT* ctx = *pp;
if (ctx->conn == conn) {
*pp = ctx->next;
ctx->done = 1;
ctx->conn = NULL;
if (ctx->initial_timer) { uasync_cancel_timeout(inst->ua, ctx->initial_timer); ctx->initial_timer = NULL; }
if (ctx->settle_timer) { uasync_cancel_timeout(inst->ua, ctx->settle_timer); ctx->settle_timer = NULL; }
u_free(ctx);
} else {
pp = &(*pp)->next;
}
}
}

73
src/etcp_connect.h

@ -0,0 +1,73 @@
#ifndef ETCP_CONNECT_H
#define ETCP_CONNECT_H
struct UTUN_INSTANCE;
struct ETCP_CONN;
/*
* ============================================================================
* Штатное закрытие исходящих соединений (etcp_connect)
* ============================================================================
*
* Исходящее соединение создаётся через etcp_connect():
* etcp_connect(inst, node, cb, arg, flags)
* ├── etcp_connection_create(inst, NULL) → conn (добавляется в inst->connections)
* ├── ctx = u_calloc(...); ctx->conn = conn
* ├── для TCP: ctx->tcp_link = stcp_link_connect(...)
* │ conn->transport_link = ctx->tcp_link
* └── ctx->next = inst->pending_connects; inst->pending_connects = ctx
*
* Пути завершения:
*
* 1. Таймаут установки (connect_initial_timeout_cb):
* stcp_link_close(tcp_link) → etcp_connection_close(conn) → connect_cancel(ctx)
*
* 2. Settle таймаут (connect_settle_timeout_cb):
* stcp_link_close(tcp_link) (если !tcp_ready) → connect_cancel(ctx)
*
* 3. Штатное закрытие через utun_instance_destroy():
* utun_instance_destroy(inst)
* ├── etcp_connection_close(conn) для всех conn
* │ Фаза 1 (detach): закрыть линки, удалить из inst->connections, state=2
* │ Фаза 2 (deferred): uasync_call_soon(ua, conn, etcp_connection_free_deferred)
* │
* ├── while (immediate_queue_head) uasync_poll(ua, 0) // ждём завершения
* │ └── etcp_connection_free_resources(conn)
* │ ├── drain_and_free_queue(*)
* │ ├── etcp_connect_cancel_for_conn(inst, conn) ← эта функция
* │ └── u_free(etcp)
* │
* └── memory_pool_destroy(pkt/ack/data) // теперь безопасно
*
* Закрытие tcp_link НЕ входит в ответственность cancel-функций.
* tcp_link закрывается ДО вызова connect_cancel / cancel_for_conn
* (в путях 1 и 2 явно; в пути 3 tcp_link закрыт ранее или утекает — pre-existing).
*
* ============================================================================
*/
/*
* ============================================================================
* etcp_connect_cancel_for_conn()
* ============================================================================
*
* Аналог приватного connect_cancel(), но ищет ETCP_CONNECT ctx по совпадению
* ctx->conn == conn (а не по указателю на ctx).
*
* Делает:
* 1. Удаляет ctx из inst->pending_connects
* 2. ctx->done = 1; ctx->conn = NULL
* 3. uasync_cancel_timeout(initial_timer / settle_timer)
* 4. u_free(ctx)
*
* НЕ закрывает ctx->tcp_link (чужая ответственность).
*
* Вызывается ТОЛЬКО из etcp_connection_free_resources (фаза 2 deferred cleanup).
* НЕ вызывать из mainloop / uasync_poll callbacks — conn уже может быть в state=2.
*
* ============================================================================
*/
void etcp_connect_cancel_for_conn(struct UTUN_INSTANCE* inst, struct ETCP_CONN* conn);
#endif

4
src/etcp_connections.c

@ -329,7 +329,7 @@ static void keepalive_timer_cb(void* arg) {
// Adaptive keepalive period (only if adaptive enabled)
if (link->pkt_sent_since_keepalive)
link->ka_period_ms = KA_PERIOD_MIN_MS;
link->ka_period_ms = (uint16_t)link->keepalive_interval;
else if (link->etcp->instance && link->etcp->instance->config &&
link->etcp->instance->config->global.keepalive_adaptive) {
uint32_t next = (uint32_t)link->ka_period_ms * 105 / 100 + 1;
@ -791,7 +791,7 @@ struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn
if (link->keepalive_interval < 10) link->keepalive_interval = 10;
link->keepalive_sent_count = 0;
link->keepalive_recv_count = 0;
link->ka_period_ms = KA_PERIOD_MIN_MS;
link->ka_period_ms = (uint16_t)link->keepalive_interval;
link->inflight_lim_bytes = link->mtu * 4; // BBR init_cwnd (~4 packets)
link->bandwidth = 10000; // начальная оценка 10 Mbps для шейпера
link->burst_id = 0;

10
src/secure_channel.c

@ -407,6 +407,16 @@ sc_status_t sc_derive_ed25519_pubkey(const uint8_t *x25519_privkey, uint8_t *ed2
return SC_OK;
}
uint64_t sc_derive_node_id(const uint8_t *private_key) {
if (!private_key) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "sc_derive_node_id: NULL private_key"); return 0; }
uint8_t sha_hash[SC_HASH_SIZE]; SC_SHA256_CTX ctx;
sc_sha256_init(&ctx); sc_sha256_update(&ctx, private_key, SC_PRIVKEY_SIZE); sc_sha256_final(&ctx, sha_hash);
uint64_t node_id;
memcpy(&node_id, sha_hash, 8);
node_id &= 0x7FFFFFFFFFFFFFFFULL;
return node_id;
}
sc_status_t sc_stream_sign_init(sc_context_t *ctx, struct sc_stream_sign_state *state) {
if (!ctx || !state) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "invalid args"); return SC_ERR_INVALID_ARG; }
if (!ctx->initialized || !ctx->pk) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "ctx not initialized"); return SC_ERR_NOT_INITIALIZED; }

2
src/secure_channel.h

@ -111,6 +111,8 @@ void sc_stream_sign_cleanup(struct sc_stream_sign_state *state);
sc_status_t sc_derive_ed25519_pubkey(const uint8_t *x25519_privkey, uint8_t *ed25519_pubkey_out);
uint64_t sc_derive_node_id(const uint8_t *private_key);
#ifdef __cplusplus
}

4
src/stcp_client.c

@ -245,7 +245,8 @@ static void client_connect_write_cb(socket_t sock, void *arg) {
struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uint16_t port,
struct SC_MYKEYS *keys, const uint8_t *peer_pubkey,
stcp_ready_cb ready_cb, void *arg) {
stcp_ready_cb ready_cb, void *arg,
stcp_close_cb close_cb, void *close_arg) {
if (!ua || !addr || !keys || !peer_pubkey || !ready_cb) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "invalid args"); return NULL; }
struct stcp_client *cli = u_calloc(1, sizeof(struct stcp_client));
if (!cli) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "calloc failed"); return NULL; }
@ -254,6 +255,7 @@ struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uin
struct stcp_conn *c = &cli->conn;
c->ua = ua; c->state = STCP_STATE_INIT; c->is_server = 0; c->my_keys = *keys;
c->on_ready = ready_cb; c->ready_arg = arg;
c->on_close = close_cb; c->close_arg = close_arg;
c->tx_cb = client_tx_queue_cb;
c->sock = socket(AF_INET, SOCK_STREAM, 0);

4
src/stcp_client.h

@ -10,10 +10,12 @@ extern "C" {
#include "stcp.h"
typedef void (*stcp_ready_cb)(struct stcp_conn *conn, void *arg);
typedef void (*stcp_close_cb)(struct stcp_conn *conn, int err, void *arg);
struct stcp_client *stcp_client_connect(struct UASYNC *ua, const char *addr, uint16_t port,
struct SC_MYKEYS *keys, const uint8_t *peer_pubkey,
stcp_ready_cb ready_cb, void *arg);
stcp_ready_cb ready_cb, void *arg,
stcp_close_cb close_cb, void *close_arg);
void stcp_client_destroy(struct stcp_client *cli);
struct stcp_conn *stcp_client_get_conn(struct stcp_client *cli);

4
src/stcp_link.c

@ -117,7 +117,7 @@ struct stcp_server *stcp_server_listen(struct stcp_link_config *cfg, uint16_t po
ss->cfg = *cfg;
ss->on_link = on_link;
ss->on_link_arg = arg;
ss->srv = stcp_server_create(cfg->ua, port, cfg->my_keys, server_accept_cb, ss);
ss->srv = stcp_server_create(cfg->ua, port, cfg->my_keys, server_accept_cb, ss, NULL, NULL);
if (!ss->srv) { u_free(ss); return NULL; }
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "port=%u", port);
return ss;
@ -156,7 +156,7 @@ struct stcp_link *stcp_link_connect(struct stcp_link_config *cfg) {
}
link->cli = stcp_client_connect(cfg->ua, addr_str, port, cfg->my_keys, link->peer_pubkey,
client_ready_cb, link);
client_ready_cb, link, NULL, NULL);
if (!link->cli) { u_free(link); return NULL; }
link->etcp_conn.instance = cfg->inst;

9
src/stcp_server.c

@ -25,6 +25,8 @@ struct stcp_server {
struct SC_MYKEYS my_keys;
stcp_connect_cb connect_cb;
void *cb_arg;
stcp_close_cb close_cb;
void *close_arg;
};
static void server_accept_cb(socket_t sock, void *arg);
@ -333,13 +335,16 @@ static void server_accept_cb(socket_t listen_sock, void *arg) {
c->my_keys = srv->my_keys;
c->on_ready = srv->connect_cb;
c->ready_arg = srv->cb_arg;
c->on_close = srv->close_cb;
c->close_arg = srv->close_arg;
c->socket_id = uasync_add_socket_t(srv->ua, cli_sock, server_conn_read_cb, server_conn_write_cb, NULL, c);
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "stcp_server: accepted connection fd=%d", (int)cli_sock);
}
struct stcp_server *stcp_server_create(struct UASYNC *ua, uint16_t port,
struct SC_MYKEYS *keys,
stcp_connect_cb connect_cb, void *arg) {
stcp_connect_cb connect_cb, void *arg,
stcp_close_cb close_cb, void *close_arg) {
if (!ua || !keys || !connect_cb) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "invalid args"); return NULL; }
struct stcp_server *srv = u_calloc(1, sizeof(struct stcp_server));
if (!srv) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "calloc failed"); return NULL; }
@ -347,6 +352,8 @@ struct stcp_server *stcp_server_create(struct UASYNC *ua, uint16_t port,
srv->my_keys = *keys;
srv->connect_cb = connect_cb;
srv->cb_arg = arg;
srv->close_cb = close_cb;
srv->close_arg = close_arg;
srv->listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (srv->listen_sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socket failed"); u_free(srv); return NULL; }

6
src/stcp_server.h

@ -10,10 +10,12 @@ extern "C" {
#include "stcp.h"
typedef void (*stcp_connect_cb)(struct stcp_conn *conn, void *arg);
typedef void (*stcp_close_cb)(struct stcp_conn *conn, int err, void *arg);
struct stcp_server *stcp_server_create(struct UASYNC *ua, uint16_t port,
struct SC_MYKEYS *keys,
stcp_connect_cb connect_cb, void *arg);
struct SC_MYKEYS *keys,
stcp_connect_cb connect_cb, void *arg,
stcp_close_cb close_cb, void *close_arg);
void stcp_server_destroy(struct stcp_server *srv);

2
src/topo_group.c

@ -643,7 +643,7 @@ int topo_group_process_nodeinfo(struct TOPO_GROUP* group, struct ETCP_CONN* from
if (sdb) {
topo_node_sqlite_node_put(sdb, nodeinfo1);
if (group->group_type == TOPO_GROUP_TYPE_CHAT && group->channel_id[0]) {
topo_node_sqlite_member_put(sdb, group->channel_id, node_id, NULL, NULL);
topo_node_sqlite_member_put(sdb, group->channel_id, node_id, NULL, 0, NULL, 0, NULL, NULL, NULL, NULL);
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "SQLite member_put: ch=%s node=%016llx", group->channel_id, (unsigned long long)node_id);
}
}

115
src/topo_node_sqlite.c

@ -5,6 +5,7 @@
#include <stdio.h>
#define PEERS_JOIN_SIG_SIZE 64
#define PEERS_JOIN_TS_SIZE 8
static void sanitize_ch_id(const char* ch_id, char* out, size_t out_sz) {
size_t i = 0;
@ -35,6 +36,7 @@ int topo_node_sqlite_init(sqlite3* db) {
" ed25519_pubkey BLOB,"
" last_seen_at INTEGER,"
" online INTEGER DEFAULT 0,"
" update_ts INTEGER DEFAULT 0,"
" created_at INTEGER DEFAULT (unixepoch())"
");"
@ -147,9 +149,15 @@ int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id,
char sql[512];
snprintf(sql, sizeof(sql),
"CREATE TABLE IF NOT EXISTS \"%s\" ("
" node_id INTEGER NOT NULL,"
" join_sig BLOB NOT NULL,"
" creator_sig BLOB,"
" node_id INTEGER NOT NULL,"
" x25519_pubkey BLOB NOT NULL,"
" ed25519_pubkey BLOB NOT NULL,"
" join_sig BLOB NOT NULL,"
" join_ts INTEGER NOT NULL,"
" update_sig BLOB,"
" update_ts INTEGER DEFAULT 0,"
" name TEXT NOT NULL DEFAULT '',"
" creator_sig BLOB,"
" comment TEXT,"
" joined_at INTEGER DEFAULT (unixepoch()),"
" PRIMARY KEY (node_id))", peers_tbl);
@ -178,23 +186,71 @@ int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id,
}
int topo_node_sqlite_member_put(sqlite3* db, const char* channel_id, uint64_t node_id,
const uint8_t* join_sig, const uint8_t* creator_sig) {
if (!db || !channel_id) return -1;
const uint8_t* join_sig, uint64_t join_ts,
const uint8_t* update_sig, uint64_t update_ts,
const uint8_t* x25519_pubkey, const uint8_t* ed25519_pubkey, const char* name,
const uint8_t* creator_sig) {
if (!db || !channel_id || !x25519_pubkey || !ed25519_pubkey) return -1;
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
char sql[256];
snprintf(sql, sizeof(sql),
"INSERT OR REPLACE INTO \"%s\"(node_id, join_sig, creator_sig) VALUES(?,?,?)", peers_tbl);
"INSERT OR REPLACE INTO \"%s\"(node_id, x25519_pubkey, ed25519_pubkey,"
" join_sig, join_ts, update_sig, update_ts, name, creator_sig)"
" VALUES(?,?,?,?,?,?,?,?,?)", peers_tbl);
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id);
if (join_sig) sqlite3_bind_blob(stmt, 2, join_sig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC);
else { static const unsigned char zsig[PEERS_JOIN_SIG_SIZE] = {0}; sqlite3_bind_blob(stmt, 2, zsig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC); }
if (creator_sig) sqlite3_bind_blob(stmt, 3, creator_sig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC);
else sqlite3_bind_null(stmt, 3);
sqlite3_bind_blob(stmt, 2, x25519_pubkey, 32, SQLITE_STATIC);
sqlite3_bind_blob(stmt, 3, ed25519_pubkey, 32, SQLITE_STATIC);
if (join_sig) sqlite3_bind_blob(stmt, 4, join_sig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC);
else { static const unsigned char zsig[PEERS_JOIN_SIG_SIZE] = {0}; sqlite3_bind_blob(stmt, 4, zsig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC); }
sqlite3_bind_int64(stmt, 5, (sqlite3_int64)join_ts);
if (update_sig) sqlite3_bind_blob(stmt, 6, update_sig, PEERS_UPDATE_SIG_SIZE, SQLITE_STATIC);
else sqlite3_bind_null(stmt, 6);
sqlite3_bind_int64(stmt, 7, (sqlite3_int64)update_ts);
sqlite3_bind_text(stmt, 8, name ? name : "", -1, SQLITE_STATIC);
if (creator_sig) sqlite3_bind_blob(stmt, 9, creator_sig, PEERS_JOIN_SIG_SIZE, SQLITE_STATIC);
else sqlite3_bind_null(stmt, 9);
int rc = sqlite3_step(stmt); sqlite3_finalize(stmt);
return rc == SQLITE_DONE ? 0 : -1;
}
int topo_node_sqlite_node_update_verified(sqlite3* db, uint64_t node_id,
const char* name, const uint8_t* x25519, const uint8_t* ed25519, uint64_t update_ts) {
if (!db || !name || !x25519 || !ed25519) return -1;
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db,
"UPDATE nodes SET name=?, x25519_pubkey=?, ed25519_pubkey=?, update_ts=?"
" WHERE node_id=? AND (update_ts IS NULL OR update_ts < ?)", -1, &stmt, NULL) != SQLITE_OK) return -1;
sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC);
sqlite3_bind_blob(stmt, 2, x25519, 32, SQLITE_STATIC);
sqlite3_bind_blob(stmt, 3, ed25519, 32, SQLITE_STATIC);
sqlite3_bind_int64(stmt, 4, (sqlite3_int64)update_ts);
sqlite3_bind_int64(stmt, 5, (sqlite3_int64)node_id);
sqlite3_bind_int64(stmt, 6, (sqlite3_int64)update_ts);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
return 0;
}
int topo_node_sqlite_member_get_join(sqlite3* db, const char* channel_id, uint64_t node_id,
uint8_t* join_sig_out, uint64_t* join_ts_out) {
if (!db || !channel_id || !join_sig_out || !join_ts_out) return -1;
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
char sql[256];
snprintf(sql, sizeof(sql), "SELECT join_sig, join_ts FROM \"%s\" WHERE node_id=?", peers_tbl);
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)node_id);
int rc = -1;
if (sqlite3_step(stmt) == SQLITE_ROW) {
const uint8_t* sig = (const uint8_t*)sqlite3_column_blob(stmt, 0);
if (sig) { memcpy(join_sig_out, sig, PEERS_JOIN_SIG_SIZE); *join_ts_out = (uint64_t)sqlite3_column_int64(stmt, 1); rc = 0; }
}
sqlite3_finalize(stmt);
return rc;
}
int topo_node_sqlite_member_del(sqlite3* db, const char* channel_id, uint64_t node_id) {
if (!db || !channel_id) return -1;
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
@ -236,11 +292,11 @@ int topo_node_sqlite_channel_peers_all(sqlite3* db, const char* channel_id,
char peers_tbl[80]; peers_table_name(channel_id, peers_tbl, sizeof(peers_tbl));
sqlite3_stmt* stmt = NULL;
char sql[256];
char sql[512];
snprintf(sql, sizeof(sql),
"SELECT p.node_id, p.join_sig, p.creator_sig,"
" n.x25519_pubkey, n.ed25519_pubkey"
" FROM \"%s\" p LEFT JOIN nodes n ON p.node_id = n.node_id", peers_tbl);
"SELECT node_id, x25519_pubkey, ed25519_pubkey,"
" join_sig, join_ts, update_sig, update_ts, name"
" FROM \"%s\" ORDER BY node_id ASC", peers_tbl);
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return -1;
size_t off = 0;
@ -250,10 +306,15 @@ int topo_node_sqlite_channel_peers_all(sqlite3* db, const char* channel_id,
while (sqlite3_step(stmt) == SQLITE_ROW) {
uint64_t node_id = (uint64_t)sqlite3_column_int64(stmt, 0);
const uint8_t* join_sig = (const uint8_t*)sqlite3_column_blob(stmt, 1);
const uint8_t* x25519 = (const uint8_t*)sqlite3_column_blob(stmt, 3);
const uint8_t* ed_pub = (const uint8_t*)sqlite3_column_blob(stmt, 4);
if (!join_sig || !x25519 || !ed_pub) continue;
const uint8_t* x25519 = (const uint8_t*)sqlite3_column_blob(stmt, 1);
const uint8_t* ed_pub = (const uint8_t*)sqlite3_column_blob(stmt, 2);
const uint8_t* join_sig = (const uint8_t*)sqlite3_column_blob(stmt, 3);
uint64_t join_ts = (uint64_t)sqlite3_column_int64(stmt, 4);
const uint8_t* update_sig = (const uint8_t*)sqlite3_column_blob(stmt, 5);
uint64_t update_ts = (uint64_t)sqlite3_column_int64(stmt, 6);
const char* pname = (const char*)sqlite3_column_text(stmt, 7);
if (!x25519 || !ed_pub) continue;
uint8_t nl = pname ? (uint8_t)strnlen(pname, 255) : 0;
sqlite3_stmt* ast = NULL;
snprintf(sql, sizeof(sql),
@ -280,7 +341,7 @@ int topo_node_sqlite_channel_peers_all(sqlite3* db, const char* channel_id,
sqlite3_finalize(ast);
if (addr_cnt == 0) continue;
size_t peer_needed = 8 + 32 + 32 + 64 + 1;
size_t peer_needed = 8 + 32 + 32 + 1 + 64 + 8 + 64 + 8 + 1 + (size_t)nl + 1;
size_t addr_sz = off - addr_start;
if (off + peer_needed > buf_sz) { sqlite3_finalize(stmt); return -2; }
memmove(buf + addr_start + peer_needed, buf + addr_start, addr_sz);
@ -288,7 +349,21 @@ int topo_node_sqlite_channel_peers_all(sqlite3* db, const char* channel_id,
memcpy(buf + off, &node_id, 8); off += 8;
memcpy(buf + off, x25519, 32); off += 32;
memcpy(buf + off, ed_pub, 32); off += 32;
memcpy(buf + off, join_sig, 64); off += 64;
uint8_t flags = (join_sig && join_ts) ? PEERS_FLAG_HAS_JOIN : 0;
buf[off++] = flags;
if (flags & PEERS_FLAG_HAS_JOIN) {
memcpy(buf + off, join_sig, 64); off += 64;
memcpy(buf + off, &join_ts, 8); off += 8;
}
if (update_sig && update_ts) {
memcpy(buf + off, update_sig, 64); off += 64;
memcpy(buf + off, &update_ts, 8); off += 8;
} else {
memset(buf + off, 0, 64); off += 64;
uint64_t z = 0; memcpy(buf + off, &z, 8); off += 8;
}
buf[off++] = nl;
if (nl) { memcpy(buf + off, pname, nl); off += nl; }
buf[off++] = addr_cnt;
off += addr_sz;
(*cnt_ptr)++;

18
src/topo_node_sqlite.h

@ -7,7 +7,12 @@
struct TOPO_NODEQ;
#define PEERS_JOIN_SIG_SIZE 64
#define PEERS_JOIN_SIG_SIZE 64
#define PEERS_JOIN_TS_SIZE 8
#define PEERS_UPDATE_SIG_SIZE 64
/* flags byte in wire format */
#define PEERS_FLAG_HAS_JOIN 0x01
int topo_node_sqlite_init(sqlite3* db);
@ -20,10 +25,19 @@ int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id,
const uint8_t* signature);
int topo_node_sqlite_member_put(sqlite3* db, const char* channel_id, uint64_t node_id,
const uint8_t* join_sig, const uint8_t* creator_sig);
const uint8_t* join_sig, uint64_t join_ts,
const uint8_t* update_sig, uint64_t update_ts,
const uint8_t* x25519_pubkey, const uint8_t* ed25519_pubkey, const char* name,
const uint8_t* creator_sig);
int topo_node_sqlite_member_get_join(sqlite3* db, const char* channel_id, uint64_t node_id,
uint8_t* join_sig_out, uint64_t* join_ts_out);
int topo_node_sqlite_member_del(sqlite3* db, const char* channel_id, uint64_t node_id);
int topo_node_sqlite_node_update_verified(sqlite3* db, uint64_t node_id,
const char* name, const uint8_t* x25519, const uint8_t* ed25519, uint64_t update_ts);
int topo_node_sqlite_channel_get(sqlite3* db, const char* channel_id,
char* name_out, int name_sz, int* is_dm, uint64_t* owner_node_id,
uint8_t* x25519_pub, uint8_t* ed25519_pub, uint8_t* signature);

13
src/utun_instance.c

@ -92,13 +92,7 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
// Derive node_id from privkey if not set in config
if (!instance->node_id) {
uint8_t sha_hash[32];
SC_SHA256_CTX ctx;
sc_sha256_init(&ctx);
sc_sha256_update(&ctx, instance->my_keys.private_key, SC_PRIVKEY_SIZE);
sc_sha256_final(&ctx, sha_hash);
memcpy(&instance->node_id, sha_hash, 8);
instance->node_id &= 0x7FFFFFFFFFFFFFFFULL;
instance->node_id = sc_derive_node_id(instance->my_keys.private_key);
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "node_id derived from privkey: %016llx", (unsigned long long)instance->node_id);
}
@ -428,6 +422,11 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
}
queue_free(instance->connections); instance->connections = NULL;
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "[INSTANCE_DESTROY] ETCP connections cleanup complete");
// Wait for all deferred callbacks (etcp_connection_free_deferred)
// to finish before destroying pools
while (instance->ua && instance->ua->immediate_queue_head)
uasync_poll(instance->ua, 0);
struct PING_CONTEXT* p = instance->pending_pings;
while (p) {

14
tests/bbr_integration/test_bbr_integration.c

@ -3,7 +3,7 @@
* @brief BBR integration test — max-speed traffic under emulated network conditions
*
* Network: 50ms delay, 5% loss, 1 Mbit shaper, ~100KB queue
* Duration: 10 seconds, metrics every 100ms
* Duration: 3.5 seconds, metrics every 100ms
*
* Architecture (single uasync, single thread):
* Sender(client:20000) → Dummynet(:20001) → Receiver(server:20002)
@ -43,7 +43,7 @@
#define CLI_PORT 21000
#define PAYLOAD_SIZE 1200
#define TEST_DURATION_MS 5000
#define TEST_DURATION_MS 3500
#define METRICS_TB 1000 /* 100ms in 0.1ms units */
#define SEND_TIMER_TB 1 /* 0.1ms re-schedule */
#define BURST_MAX 64
@ -117,6 +117,8 @@ static struct UTUN_INSTANCE* create_instance(struct UASYNC* u, uint64_t node_id,
inst->data_pool = memory_pool_init(PACKET_DATA_SIZE, "data_pool");
inst->pkt_pool = memory_pool_init(sizeof(struct ETCP_DGRAM) + PACKET_DATA_SIZE, "pkt_pool");
if (!inst->ack_pool || !inst->data_pool || !inst->pkt_pool) { u_free(inst); return NULL; }
inst->connections = queue_new(u, 256, 0, 8, "connections");
if (!inst->connections) { u_free(inst); return NULL; }
struct utun_config* cfg = u_calloc(1, sizeof(*cfg));
if (!cfg) { u_free(inst); return NULL; }
strncpy(cfg->global.my_public_key_hex, pub_hex, MAX_KEY_LEN - 1);
@ -382,7 +384,7 @@ int main(void) {
printf("Waiting for connection...\n");
struct ETCP_CONN* conn = NULL;
uint64_t t0 = now_us();
while ((now_us() - t0) < 10000000ULL) {
while ((now_us() - t0) < 5000000ULL) {
uasync_poll(ctx.ua, 1);
if (ctx.sender->connections && ctx.sender->connections->head)
conn = ((struct conn_queue_entry*)ctx.sender->connections->head->data)->conn;
@ -393,7 +395,7 @@ int main(void) {
}
printf("Connection established, stabilizing...\n");
t0 = now_us();
while ((now_us() - t0) < 500000ULL) uasync_poll(ctx.ua, 1);
while ((now_us() - t0) < 100000ULL) uasync_poll(ctx.ua, 1);
if (conn && conn->normalizer && conn->normalizer->input) {
queue_set_threshold(conn->normalizer->input, 0, 0);
@ -401,7 +403,7 @@ int main(void) {
}
/* Start test */
printf("\n=== Starting traffic (%d seconds) ===\n\n", TEST_DURATION_MS / 1000);
printf("\n=== Starting traffic (%.1f seconds) ===\n\n", TEST_DURATION_MS / 1000.0);
ctx.log_file = fopen("test_bbr_integration.log", "w");
if (ctx.log_file)
fprintf(ctx.log_file, "# BBR Integration Test — %ums delay %u%% loss %ukbps\n\n",
@ -444,7 +446,7 @@ int main(void) {
printf("\nDraining...\n");
ctx.test_done = 1;
t0 = now_us();
while ((now_us() - t0) < 2000000ULL) uasync_poll(ctx.ua, 10);
while ((now_us() - t0) < 500000ULL) uasync_poll(ctx.ua, 10);
print_metrics(&ctx);
/* Final summary */

35
tests/test_bgp_route_exchange.c

@ -51,9 +51,9 @@
#define PHASE_TIMEOUT_TB 100000 // 10s per phase
#define POLL_INTERVAL_MS 5
#define NODE_ID_A 0x1111111111111111ULL
#define NODE_ID_B 0x2222222222222222ULL
#define NODE_ID_C 0x3333333333333333ULL
static uint64_t g_node_id_a = 0;
static uint64_t g_node_id_b = 0;
static uint64_t g_node_id_c = 0;
static struct UTUN_INSTANCE* inst_a = NULL;
static struct UTUN_INSTANCE* inst_b = NULL;
@ -104,9 +104,9 @@ static int create_temp_configs(void) {
// Config C: server only, no keys (config_ensure_keys_and_node_id adds them)
if (write_file(config_c,
"[global]\n"
"my_node_id=0x3333333333333333\n"
"tun_ip=10.100.0.3/24\n"
"tun_ifname=tun102\n"
"keepalive_interval=50\n"
"keepalive_adaptive=0\n"
"\n"
"[routing]\n"
@ -126,9 +126,9 @@ static int create_temp_configs(void) {
// Config B: server for A + client to C, needs C's pubkey
if (write_file(config_b,
"[global]\n"
"my_node_id=0x2222222222222222\n"
"tun_ip=10.100.0.2/24\n"
"tun_ifname=tun101\n"
"keepalive_interval=50\n"
"keepalive_adaptive=0\n"
"\n"
"[routing]\n"
@ -158,9 +158,9 @@ static int create_temp_configs(void) {
// Config A: two servers + client to B with 2 links, needs B's pubkey
if (write_file(config_a,
"[global]\n"
"my_node_id=0x1111111111111111\n"
"tun_ip=10.100.0.1/24\n"
"tun_ifname=tun100\n"
"keepalive_interval=50\n"
"keepalive_adaptive=0\n"
"\n"
"[routing]\n"
@ -211,11 +211,10 @@ static int count_initialized_links(struct UTUN_INSTANCE* inst) {
static int check_learned_route(struct UTUN_INSTANCE* inst, uint32_t network, uint8_t prefix_len, uint64_t node_id) {
if (!inst || !inst->rt) return 0;
uint64_t expected_be = htobe64(node_id);
for (size_t i = 0; i < inst->rt->count; i++) {
struct ROUTE_ENTRY* e = &inst->rt->entries[i];
if (e->network == network && e->prefix_length == prefix_len &&
e->v_node_info && e->v_node_info->node->node_id == expected_be) return 1;
e->v_node_info && e->v_node_info->node->node_id == node_id) return 1;
}
return 0;
}
@ -277,16 +276,16 @@ static int cond_all_links_init(void) {
}
static int cond_bgp_a_to_c(void) {
return check_learned_route(inst_a, ntohl(inet_addr("192.168.30.0")), 24, NODE_ID_C);
return check_learned_route(inst_a, ntohl(inet_addr("192.168.30.0")), 24, g_node_id_c);
}
static int cond_bgp_c_to_a(void) {
return check_learned_route(inst_c, ntohl(inet_addr("192.168.10.0")), 24, NODE_ID_A);
return check_learned_route(inst_c, ntohl(inet_addr("192.168.10.0")), 24, g_node_id_a);
}
static int cond_bgp_a_to_c_gone(void) {
return !check_learned_route(inst_a, ntohl(inet_addr("192.168.30.0")), 24, NODE_ID_C);
return !check_learned_route(inst_a, ntohl(inet_addr("192.168.30.0")), 24, g_node_id_c);
}
static int cond_bgp_c_to_a_gone(void) {
return !check_learned_route(inst_c, ntohl(inet_addr("192.168.10.0")), 24, NODE_ID_A);
return !check_learned_route(inst_c, ntohl(inet_addr("192.168.10.0")), 24, g_node_id_a);
}
// -------------------- Main --------------------
@ -311,6 +310,8 @@ int main(void) {
ASSERT(utun_instance_init(inst_b) == 0, "inst_b init");
ASSERT(utun_instance_init(inst_c) == 0, "inst_c init");
g_node_id_a = inst_a->node_id; g_node_id_b = inst_b->node_id; g_node_id_c = inst_c->node_id;
// Find links for dummynet filters (client links: idx 0,1 on A, idx 0 on B)
struct ETCP_LINK* ab_link1 = find_client_link(inst_a, 0);
struct ETCP_LINK* ab_link2 = find_client_link(inst_a, 1);
@ -335,10 +336,12 @@ int main(void) {
PHASE("2: Kill one A↔B link");
dummynet_filter_attach(df_ab1, ab_link1);
dummynet_filter_set_loss(df_ab1, 1000);
// Wait for keepalive timeout + margin, then verify routes survive (link2 still up)
for (int i = 0; i < 600 && test_phase == 0; i++) uasync_poll(ua, POLL_INTERVAL_MS); // 3s
ASSERT(check_learned_route(inst_a, ntohl(inet_addr("192.168.30.0")), 24, NODE_ID_C), "routes lost after 1 link down");
ASSERT(check_learned_route(inst_c, ntohl(inet_addr("192.168.10.0")), 24, NODE_ID_A), "routes lost C side");
// Wait for link-down detection by keepalive, then verify routes survive (link2 still up)
for (int i = 0; i < 200 && test_phase == 0 && ab_link1->link_status; i++) uasync_poll(ua, POLL_INTERVAL_MS);
// Small margin after link-down before checks
for (int i = 0; i < 40 && test_phase == 0; i++) uasync_poll(ua, POLL_INTERVAL_MS);
ASSERT(check_learned_route(inst_a, ntohl(inet_addr("192.168.30.0")), 24, g_node_id_c), "routes lost after 1 link down");
ASSERT(check_learned_route(inst_c, ntohl(inet_addr("192.168.10.0")), 24, g_node_id_a), "routes lost C side");
// --- Phase 3: Restore the link ---
PHASE("3: Restore one A↔B link");

143
tests/test_bgp_triangle.c

@ -56,11 +56,11 @@
#define PHASE_TIMEOUT_TB 100000
#define POLL_INTERVAL_MS 5
#define NODE_ID_A 0x1111111111111111ULL
#define NODE_ID_B 0x2222222222222222ULL
#define NODE_ID_C 0x3333333333333333ULL
#define NODE_ID_D 0x4444444444444444ULL
#define NODE_ID_E 0x5555555555555555ULL
static uint64_t g_node_id_a = 0;
static uint64_t g_node_id_b = 0;
static uint64_t g_node_id_c = 0;
static uint64_t g_node_id_d = 0;
static uint64_t g_node_id_e = 0;
static struct UTUN_INSTANCE* inst_a = NULL;
static struct UTUN_INSTANCE* inst_b = NULL;
@ -91,7 +91,6 @@ struct ncfg {
static void build_node_config(char* buf, size_t size, const struct ncfg* c) {
int off = snprintf(buf, size,
"[global]\n"
"my_node_id=0x%llx\n"
"my_private_key=%s\n"
"my_public_key=%s\n"
"tun_ip=%s\n"
@ -101,7 +100,7 @@ static void build_node_config(char* buf, size_t size, const struct ncfg* c) {
"[routing]\n"
"my_subnet=%s\n"
"\n",
(unsigned long long)c->node_id, c->priv_hex, c->pub_hex, c->tun_ip, c->my_subnet);
c->priv_hex, c->pub_hex, c->tun_ip, c->my_subnet);
for (int i = 0; i < c->srv_cnt; i++)
off += snprintf(buf + off, size - off,
"[server: %s]\naddr=127.0.0.1:%d\ntype=public\n\n", c->srvs[i].name, c->srvs[i].port);
@ -152,11 +151,10 @@ static int node_version(struct UTUN_INSTANCE* inst, uint64_t node_id) {
static int route_count_for_node(struct UTUN_INSTANCE* inst, uint64_t node_id) {
if (!inst || !inst->rt) return 0;
uint64_t expected_be = htobe64(node_id);
int cnt = 0;
for (size_t i = 0; i < inst->rt->count; i++)
if (inst->rt->entries[i].v_node_info &&
inst->rt->entries[i].v_node_info->node->node_id == expected_be) cnt++;
inst->rt->entries[i].v_node_info->node->node_id == node_id) cnt++;
return cnt;
}
@ -173,16 +171,14 @@ static int count_initialized_links(struct UTUN_INSTANCE* inst) {
return n;
}
static struct ETCP_LINK* find_client_link(struct UTUN_INSTANCE* inst, int idx) {
static struct ETCP_LINK* find_client_link(struct UTUN_INSTANCE* inst, const char* conn_name) {
if (!inst || !inst->connections) return NULL;
struct ll_entry* entry = inst->connections->head;
while (entry) {
struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data;
if (!ce->conn->name || strcmp(ce->conn->name, conn_name) != 0) { entry = entry->next; continue; }
struct ETCP_LINK* l = ce->conn->links;
while (l) {
if (l->is_server == 0) { if (idx == 0) return l; idx--; }
l = l->next;
}
while (l) { if (l->is_server == 0) return l; l = l->next; }
entry = entry->next;
}
return NULL;
@ -229,26 +225,26 @@ static int cond_links_init(void) {
count_initialized_links(inst_e) >= 1;
}
static int cond_d_in_a(void) { return peer_in_nodes(inst_a, NODE_ID_D); }
static int cond_d_gone_a(void) { return !peer_in_nodes(inst_a, NODE_ID_D); }
static int cond_e_in_a(void) { return peer_in_nodes(inst_a, NODE_ID_E); }
static int cond_e_gone_a(void) { return !peer_in_nodes(inst_a, NODE_ID_E); }
static int cond_c_in_a(void) { return peer_in_nodes(inst_a, NODE_ID_C); }
static int cond_c_gone_a(void) { return !peer_in_nodes(inst_a, NODE_ID_C); }
static int cond_d_in_a(void) { return peer_in_nodes(inst_a, g_node_id_d); }
static int cond_d_gone_a(void) { return !peer_in_nodes(inst_a, g_node_id_d); }
static int cond_e_in_a(void) { return peer_in_nodes(inst_a, g_node_id_e); }
static int cond_e_gone_a(void) { return !peer_in_nodes(inst_a, g_node_id_e); }
static int cond_c_in_a(void) { return peer_in_nodes(inst_a, g_node_id_c); }
static int cond_c_gone_a(void) { return !peer_in_nodes(inst_a, g_node_id_c); }
static int cond_c_two_paths(void) {
return node_path_count(inst_a, NODE_ID_C) >= 2 &&
node_path_has_peer(inst_a, NODE_ID_C, NODE_ID_C) &&
node_path_has_peer(inst_a, NODE_ID_C, NODE_ID_B);
return node_path_count(inst_a, g_node_id_c) >= 2 &&
node_path_has_peer(inst_a, g_node_id_c, g_node_id_c) &&
node_path_has_peer(inst_a, g_node_id_c, g_node_id_b);
}
static int cond_c_one_path_B(void) {
return node_path_count(inst_a, NODE_ID_C) == 1 &&
node_path_has_peer(inst_a, NODE_ID_C, NODE_ID_B);
return node_path_count(inst_a, g_node_id_c) == 1 &&
node_path_has_peer(inst_a, g_node_id_c, g_node_id_b);
}
static int cond_b_gone_a(void) { return !peer_in_nodes(inst_a, NODE_ID_B); }
static int cond_b_gone_a(void) { return !peer_in_nodes(inst_a, g_node_id_b); }
static int cond_c_one_path(void) { return node_path_count(inst_a, NODE_ID_C) == 1; }
static int cond_c_one_path(void) { return node_path_count(inst_a, g_node_id_c) == 1; }
/* ================================================================
* Main
@ -282,7 +278,7 @@ int main(void) {
char cfg_a[2048], cfg_b[2048], cfg_c[2048], cfg_d[2048], cfg_e[2048];
build_node_config(cfg_a, sizeof(cfg_a), &(struct ncfg){
.node_id = NODE_ID_A, .tun_ip = "10.100.0.1/24", .my_subnet = "192.168.10.0/24",
.node_id = g_node_id_a, .tun_ip = "10.100.0.1/24", .my_subnet = "192.168.10.0/24",
.priv_hex = priv_hex[0], .pub_hex = pub_hex[0],
.srv_cnt = 2, .srvs = {{"a_srv_b", p_ab_a}, {"a_srv_c", p_ac_a}},
.cli_cnt = 2, .clis = {
@ -292,7 +288,7 @@ int main(void) {
});
build_node_config(cfg_b, sizeof(cfg_b), &(struct ncfg){
.node_id = NODE_ID_B, .tun_ip = "10.100.0.2/24", .my_subnet = "192.168.20.0/24",
.node_id = g_node_id_b, .tun_ip = "10.100.0.2/24", .my_subnet = "192.168.20.0/24",
.priv_hex = priv_hex[1], .pub_hex = pub_hex[1],
.srv_cnt = 3, .srvs = {{"b_srv", p_ab_b}, {"b_cli_c", p_bc_b}, {"b_cli_e", p_be_b}},
.cli_cnt = 2, .clis = {
@ -302,7 +298,7 @@ int main(void) {
});
build_node_config(cfg_c, sizeof(cfg_c), &(struct ncfg){
.node_id = NODE_ID_C, .tun_ip = "10.100.0.3/24", .my_subnet = "192.168.30.0/24",
.node_id = g_node_id_c, .tun_ip = "10.100.0.3/24", .my_subnet = "192.168.30.0/24",
.priv_hex = priv_hex[2], .pub_hex = pub_hex[2],
.srv_cnt = 3, .srvs = {{"c_srv_a", p_ac_c}, {"c_srv_b", p_bc_c}, {"c_cli_d", p_cd_c}},
.cli_cnt = 1, .clis = {
@ -311,14 +307,14 @@ int main(void) {
});
build_node_config(cfg_d, sizeof(cfg_d), &(struct ncfg){
.node_id = NODE_ID_D, .tun_ip = "10.100.0.4/24", .my_subnet = "192.168.40.0/24",
.node_id = g_node_id_d, .tun_ip = "10.100.0.4/24", .my_subnet = "192.168.40.0/24",
.priv_hex = priv_hex[3], .pub_hex = pub_hex[3],
.srv_cnt = 1, .srvs = {{"d_srv", p_cd_d}},
.cli_cnt = 0, .clis = {},
});
build_node_config(cfg_e, sizeof(cfg_e), &(struct ncfg){
.node_id = NODE_ID_E, .tun_ip = "10.100.0.5/24", .my_subnet = "192.168.50.0/24",
.node_id = g_node_id_e, .tun_ip = "10.100.0.5/24", .my_subnet = "192.168.50.0/24",
.priv_hex = priv_hex[4], .pub_hex = pub_hex[4],
.srv_cnt = 1, .srvs = {{"e_srv", p_be_e}},
.cli_cnt = 0, .clis = {},
@ -340,9 +336,12 @@ int main(void) {
ASSERT(utun_instance_init(inst_d) == 0, "init d");
ASSERT(utun_instance_init(inst_e) == 0, "init e");
g_node_id_a = inst_a->node_id; g_node_id_b = inst_b->node_id; g_node_id_c = inst_c->node_id;
g_node_id_d = inst_d->node_id; g_node_id_e = inst_e->node_id;
/* ---------- Dummynet filters (2 managed links) ---------- */
struct ETCP_LINK* ab_link = find_client_link(inst_a, 0);
struct ETCP_LINK* ac_link = find_client_link(inst_a, 1);
struct ETCP_LINK* ab_link = find_client_link(inst_a, "to_b");
struct ETCP_LINK* ac_link = find_client_link(inst_a, "to_c");
ASSERT(ab_link && ac_link, "A links not found");
df_ab = dummynet_filter_create(ua); ASSERT(df_ab, "df_ab");
@ -358,25 +357,25 @@ int main(void) {
ASSERT(wait_for("D in A nodes", cond_d_in_a, PHASE_TIMEOUT_TB), "D not in A");
ASSERT(wait_for("E in A nodes", cond_e_in_a, PHASE_TIMEOUT_TB), "E not in A");
ASSERT(peer_in_nodes(inst_a, NODE_ID_B), "B missing");
ASSERT(peer_in_nodes(inst_a, NODE_ID_C), "C missing");
ASSERT(peer_in_nodes(inst_a, NODE_ID_D), "D missing");
ASSERT(peer_in_nodes(inst_a, NODE_ID_E), "E missing");
ASSERT(peer_in_nodes(inst_a, g_node_id_b), "B missing");
ASSERT(peer_in_nodes(inst_a, g_node_id_c), "C missing");
ASSERT(peer_in_nodes(inst_a, g_node_id_d), "D missing");
ASSERT(peer_in_nodes(inst_a, g_node_id_e), "E missing");
{
int pb = node_path_count(inst_a, NODE_ID_B);
int pc = node_path_count(inst_a, NODE_ID_C);
int pd = node_path_count(inst_a, NODE_ID_D);
int pe = node_path_count(inst_a, NODE_ID_E);
int pb = node_path_count(inst_a, g_node_id_b);
int pc = node_path_count(inst_a, g_node_id_c);
int pd = node_path_count(inst_a, g_node_id_d);
int pe = node_path_count(inst_a, g_node_id_e);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 1: B_paths=%d C_paths=%d D_paths=%d E_paths=%d", pb, pc, pd, pe);
ASSERT(pb >= 1, "B: expected >=1");
ASSERT(pc >= 1, "C: expected >=1");
ASSERT(pd >= 1, "D: expected >=1");
ASSERT(pe >= 1, "E: expected >=1");
}
ASSERT(route_count_for_node(inst_a, NODE_ID_B) > 0, "B routes missing");
ASSERT(route_count_for_node(inst_a, NODE_ID_C) > 0, "C routes missing");
ASSERT(route_count_for_node(inst_a, NODE_ID_D) > 0, "D routes missing");
ASSERT(route_count_for_node(inst_a, NODE_ID_E) > 0, "E routes missing");
ASSERT(route_count_for_node(inst_a, g_node_id_b) > 0, "B routes missing");
ASSERT(route_count_for_node(inst_a, g_node_id_c) > 0, "C routes missing");
ASSERT(route_count_for_node(inst_a, g_node_id_d) > 0, "D routes missing");
ASSERT(route_count_for_node(inst_a, g_node_id_e) > 0, "E routes missing");
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 1 PASSED");
/* ================================================================
@ -387,9 +386,9 @@ int main(void) {
dummynet_filter_set_loss(df_ac, 1000);
ASSERT(wait_for("C: 1 path via B after A-C kill", cond_c_one_path_B, PHASE_TIMEOUT_TB), "C paths not 1");
{
int pc = node_path_count(inst_a, NODE_ID_C);
int pd = node_path_count(inst_a, NODE_ID_D);
int pe = node_path_count(inst_a, NODE_ID_E);
int pc = node_path_count(inst_a, g_node_id_c);
int pd = node_path_count(inst_a, g_node_id_d);
int pe = node_path_count(inst_a, g_node_id_e);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 2: C_paths=%d D_paths=%d E_paths=%d", pc, pd, pe);
ASSERT(cond_c_in_a(), "C missing after A-C kill");
ASSERT(cond_d_in_a(), "D missing after A-C kill");
@ -407,7 +406,7 @@ int main(void) {
dummynet_filter_set_loss(df_ac, 0);
ASSERT(wait_for("C: >=2 paths after A-C restore", cond_c_two_paths, PHASE_TIMEOUT_TB), "C paths not >=2");
{
int cp = node_path_count(inst_a, NODE_ID_C);
int cp = node_path_count(inst_a, g_node_id_c);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 3 PASSED (C paths=%d)", cp);
}
@ -420,15 +419,15 @@ int main(void) {
/* After A-B kill: direct paths removed, B/C/E survive via C relay */
ASSERT(wait_for("A-B paths removed", cond_c_one_path, PHASE_TIMEOUT_TB), "A-B paths not removed");
{
int pb = node_path_count(inst_a, NODE_ID_B);
int pe = node_path_count(inst_a, NODE_ID_E);
int pb = node_path_count(inst_a, g_node_id_b);
int pe = node_path_count(inst_a, g_node_id_e);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 4: B_paths=%d E_paths=%d", pb, pe);
ASSERT(peer_in_nodes(inst_a, NODE_ID_B), "B missing — should survive via C");
ASSERT(peer_in_nodes(inst_a, NODE_ID_E), "E missing — should survive via C");
ASSERT(peer_in_nodes(inst_a, g_node_id_b), "B missing — should survive via C");
ASSERT(peer_in_nodes(inst_a, g_node_id_e), "E missing — should survive via C");
ASSERT(cond_c_in_a(), "C missing");
ASSERT(cond_d_in_a(), "D missing");
ASSERT(node_path_count(inst_a, NODE_ID_C) == 1, "C: expected 1 path via A-C");
ASSERT(node_path_has_peer(inst_a, NODE_ID_C, NODE_ID_C), "C: missing direct path");
ASSERT(node_path_count(inst_a, g_node_id_c) == 1, "C: expected 1 path via A-C");
ASSERT(node_path_has_peer(inst_a, g_node_id_c, g_node_id_c), "C: missing direct path");
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 4 PASSED");
@ -439,10 +438,10 @@ int main(void) {
dummynet_filter_set_loss(df_ac, 1000);
ASSERT(wait_for("C gone from A", cond_c_gone_a, PHASE_TIMEOUT_TB), "C still in A");
ASSERT(wait_for("D gone from A", cond_d_gone_a, PHASE_TIMEOUT_TB), "D still in A");
ASSERT(!peer_in_nodes(inst_a, NODE_ID_B), "B should be gone");
ASSERT(!peer_in_nodes(inst_a, NODE_ID_C), "C should be gone");
ASSERT(!peer_in_nodes(inst_a, NODE_ID_D), "D should be gone");
ASSERT(!peer_in_nodes(inst_a, NODE_ID_E), "E should be gone");
ASSERT(!peer_in_nodes(inst_a, g_node_id_b), "B should be gone");
ASSERT(!peer_in_nodes(inst_a, g_node_id_c), "C should be gone");
ASSERT(!peer_in_nodes(inst_a, g_node_id_d), "D should be gone");
ASSERT(!peer_in_nodes(inst_a, g_node_id_e), "E should be gone");
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 5 PASSED");
/* ================================================================
@ -454,18 +453,18 @@ int main(void) {
ASSERT(wait_for("D in A after restore", cond_d_in_a, 2 * PHASE_TIMEOUT_TB), "D not back");
ASSERT(wait_for("E in A after restore", cond_e_in_a, PHASE_TIMEOUT_TB), "E not back");
ASSERT(wait_for("C: >=2 paths after restore", cond_c_two_paths, PHASE_TIMEOUT_TB), "C paths not >=2");
ASSERT(peer_in_nodes(inst_a, NODE_ID_B), "B missing after restore");
ASSERT(peer_in_nodes(inst_a, NODE_ID_C), "C missing after restore");
ASSERT(peer_in_nodes(inst_a, NODE_ID_D), "D missing after restore");
ASSERT(peer_in_nodes(inst_a, NODE_ID_E), "E missing after restore");
ASSERT(node_path_count(inst_a, NODE_ID_C) >= 2, "C: expected >=2 paths");
ASSERT(node_path_count(inst_a, NODE_ID_D) >= 1, "D: expected >=1");
ASSERT(node_path_count(inst_a, NODE_ID_B) >= 1, "B: expected >=1");
ASSERT(node_path_count(inst_a, NODE_ID_E) >= 1, "E: expected >=1");
ASSERT(route_count_for_node(inst_a, NODE_ID_B) > 0, "B routes");
ASSERT(route_count_for_node(inst_a, NODE_ID_C) > 0, "C routes");
ASSERT(route_count_for_node(inst_a, NODE_ID_D) > 0, "D routes");
ASSERT(route_count_for_node(inst_a, NODE_ID_E) > 0, "E routes");
ASSERT(peer_in_nodes(inst_a, g_node_id_b), "B missing after restore");
ASSERT(peer_in_nodes(inst_a, g_node_id_c), "C missing after restore");
ASSERT(peer_in_nodes(inst_a, g_node_id_d), "D missing after restore");
ASSERT(peer_in_nodes(inst_a, g_node_id_e), "E missing after restore");
ASSERT(node_path_count(inst_a, g_node_id_c) >= 2, "C: expected >=2 paths");
ASSERT(node_path_count(inst_a, g_node_id_d) >= 1, "D: expected >=1");
ASSERT(node_path_count(inst_a, g_node_id_b) >= 1, "B: expected >=1");
ASSERT(node_path_count(inst_a, g_node_id_e) >= 1, "E: expected >=1");
ASSERT(route_count_for_node(inst_a, g_node_id_b) > 0, "B routes");
ASSERT(route_count_for_node(inst_a, g_node_id_c) > 0, "C routes");
ASSERT(route_count_for_node(inst_a, g_node_id_d) > 0, "D routes");
ASSERT(route_count_for_node(inst_a, g_node_id_e) > 0, "E routes");
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Phase 6 PASSED");
DEBUG_INFO(DEBUG_CATEGORY_BGP, "=== ALL PHASES PASSED ===");

21
tests/test_conn_mgr.c

@ -34,8 +34,8 @@
#define TIMEOUT_TB 300000
#define POLL_MS 5
#define NID_A 0xAAAA000000000001ULL
#define NID_B 0xBBBB000000000002ULL
static uint64_t g_nid_a = 0;
static uint64_t g_nid_b = 0;
static struct UTUN_INSTANCE* g_a = NULL, *g_b = NULL;
static struct UASYNC* ua = NULL;
@ -76,16 +76,16 @@ static void ccb(int r, uint64_t id, void* arg) {
static void test1(void* arg) {
(void)arg; if (result) return;
if (!topo_node_find_by_id(topo_groups_get_default(g_a->topo_groups), NID_B)) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test1, "t1"); return; }
if (!topo_node_find_by_id(topo_groups_get_default(g_a->topo_groups), g_nid_b)) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test1, "t1"); return; }
fprintf(stderr, "Test 1: direct — conn_mgr_connect_node(B)\n"); fflush(stderr);
conn_mgr_connect_node(g_a->conn_mgr, NID_B, 0, ccb, NULL);
conn_mgr_connect_node(g_a->conn_mgr, g_nid_b, 0, ccb, NULL);
uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test2, "t2a");
}
static void test2(void* arg) {
(void)arg; if (result) return;
if (!cdone) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test2, "t2b"); return; }
if (cresult != CONN_MGR_OK) { fail("connect failed"); return; }
uint8_t st, ty; conn_mgr_get_status(g_a->conn_mgr, NID_B, &st, &ty);
uint8_t st, ty; conn_mgr_get_status(g_a->conn_mgr, g_nid_b, &st, &ty);
fprintf(stderr, "Test 2: status state=%d type=%d\n", st, ty); fflush(stderr);
if (st == CONN_MGR_STATE_CONNECTED && ty == CONN_TYPE_DIRECT) fprintf(stderr, " OK: DIRECT\n");
else { fail("status mismatch"); return; }
@ -108,12 +108,15 @@ static void setup(void) {
test_mkdtemp(tdir);
int base = 47000 + (getpid() % 15000); pa = base; pb = base + 1;
snprintf(ca, sizeof(ca), "%s/a.conf", tdir); snprintf(cb, sizeof(cb), "%s/b.conf", tdir);
wf(ca, "[global]\nmy_node_id=0x%llx\ntun_ip=10.99.0.1/24\ntun_ifname=tun99\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", NID_A, pa);
wf(cb, "[global]\nmy_node_id=0x%llx\ntun_ip=10.99.0.2/24\ntun_ifname=tun98\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", NID_B, pb);
wf(ca, "[global]\ntun_ip=10.99.0.1/24\ntun_ifname=tun99\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", pa);
wf(cb, "[global]\ntun_ip=10.99.0.2/24\ntun_ifname=tun98\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", pb);
config_ensure_keys_and_node_id(ca); config_ensure_keys_and_node_id(cb);
{ struct utun_config* cfa = parse_config(ca); struct utun_config* cfb = parse_config(cb);
g_nid_a = cfa->global.my_node_id; g_nid_b = cfb->global.my_node_id;
free_config(cfa); free_config(cfb); }
char *p0 = gv(ca,"pub"), *r0 = gv(ca,"priv"), *p1 = gv(cb,"pub"), *r1 = gv(cb,"priv");
wf(ca, "[global]\nmy_node_id=0x%llx\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.1/24\ntun_ifname=tun99\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[client: to_b]\nkeepalive=1\npeer_public_key=%s\nlink=s1:127.0.0.1:%d\n[allowed_keys]\nallow_all=1\n", NID_A, r0, p0, pa, p1, pb);
wf(cb, "[global]\nmy_node_id=0x%llx\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.2/24\ntun_ifname=tun98\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", NID_B, r1, p1, pb);
wf(ca, "[global]\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.1/24\ntun_ifname=tun99\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[client: to_b]\nkeepalive=1\npeer_public_key=%s\nlink=s1:127.0.0.1:%d\n[allowed_keys]\nallow_all=1\n", r0, p0, pa, p1, pb);
wf(cb, "[global]\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.2/24\ntun_ifname=tun98\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", r1, p1, pb);
u_free(p0); u_free(r0); u_free(p1); u_free(r1);
}
static void cleanup(void) { test_unlink(ca); test_unlink(cb); test_rmdir(tdir); }

21
tests/test_etcp_connect.c

@ -24,8 +24,8 @@
#define TIMEOUT_TB 300000
#define POLL_MS 5
#define NID_A 0xAAAA000000000001ULL
#define NID_B 0xBBBB000000000002ULL
static uint64_t nid_a = 0;
static uint64_t nid_b = 0;
static struct UTUN_INSTANCE* g_a = NULL, *g_b = NULL;
static struct UASYNC* ua = NULL;
@ -87,7 +87,7 @@ static struct TOPO_NODEQ* mknode(uint64_t nid, const uint8_t pubkey[32],
static void test1(void* arg) {
(void)arg; if (result) return;
if (lks(g_a) < 1 || lks(g_b) < 1) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test1, "t1"); return; }
struct TOPO_NODEQ* nb = topo_node_find_by_id(topo_groups_get_default(g_a->topo_groups), NID_B);
struct TOPO_NODEQ* nb = topo_node_find_by_id(topo_groups_get_default(g_a->topo_groups), nid_b);
if (!nb) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test1, "t1b"); return; }
fprintf(stderr, "Test 1: already connected → EARLY|LATE\n"); fflush(stderr);
cb_type = 0; cb_conn_ok = 0; cb_count = 0;
@ -162,7 +162,7 @@ static void test6(void* arg) {
/* ======================== Test 7: EARLY-only flag ======================== */
static void test7(void* arg) {
(void)arg; if (result) return;
struct TOPO_NODEQ* nb = topo_node_find_by_id(topo_groups_get_default(g_a->topo_groups), NID_B);
struct TOPO_NODEQ* nb = topo_node_find_by_id(topo_groups_get_default(g_a->topo_groups), nid_b);
if (!nb || lks(g_a) < 1) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test7, "t7"); return; }
fprintf(stderr, "Test 7: EARLY-only flag\n"); fflush(stderr);
cb_type = 0; cb_conn_ok = 0; cb_count = 0;
@ -182,7 +182,7 @@ static void test8(void* arg) {
/* ======================== Test 9: LATE-only flag ======================== */
static void test9(void* arg) {
(void)arg; if (result) return;
struct TOPO_NODEQ* nb = topo_node_find_by_id(topo_groups_get_default(g_a->topo_groups), NID_B);
struct TOPO_NODEQ* nb = topo_node_find_by_id(topo_groups_get_default(g_a->topo_groups), nid_b);
if (!nb || lks(g_a) < 1) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test9, "t9"); return; }
fprintf(stderr, "Test 9: LATE-only flag\n"); fflush(stderr);
cb_type = 0; cb_conn_ok = 0; cb_count = 0;
@ -206,12 +206,15 @@ static void setup(void) {
test_mkdtemp(tdir);
int base = 48000 + (getpid() % 10000); pa = base; pb = base + 1;
snprintf(ca, sizeof(ca), "%s/a.conf", tdir); snprintf(cb, sizeof(cb), "%s/b.conf", tdir);
wf(ca, "[global]\nmy_node_id=0x%llx\ntun_ip=10.99.0.1/24\ntun_ifname=tun99\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", NID_A, pa);
wf(cb, "[global]\nmy_node_id=0x%llx\ntun_ip=10.99.0.2/24\ntun_ifname=tun98\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", NID_B, pb);
wf(ca, "[global]\ntun_ip=10.99.0.1/24\ntun_ifname=tun99\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", pa);
wf(cb, "[global]\ntun_ip=10.99.0.2/24\ntun_ifname=tun98\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", pb);
config_ensure_keys_and_node_id(ca); config_ensure_keys_and_node_id(cb);
{ struct utun_config* cfa = parse_config(ca); struct utun_config* cfb = parse_config(cb);
nid_a = cfa->global.my_node_id; nid_b = cfb->global.my_node_id;
free_config(cfa); free_config(cfb); }
char *p0 = gv(ca,"pub"), *r0 = gv(ca,"priv"), *p1 = gv(cb,"pub"), *r1 = gv(cb,"priv");
wf(ca, "[global]\nmy_node_id=0x%llx\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.1/24\ntun_ifname=tun99\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[client: to_b]\nkeepalive=1\npeer_public_key=%s\nlink=s1:127.0.0.1:%d\n[allowed_keys]\nallow_all=1\n", NID_A, r0, p0, pa, p1, pb);
wf(cb, "[global]\nmy_node_id=0x%llx\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.2/24\ntun_ifname=tun98\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", NID_B, r1, p1, pb);
wf(ca, "[global]\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.1/24\ntun_ifname=tun99\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[client: to_b]\nkeepalive=1\npeer_public_key=%s\nlink=s1:127.0.0.1:%d\n[allowed_keys]\nallow_all=1\n", r0, p0, pa, p1, pb);
wf(cb, "[global]\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.2/24\ntun_ifname=tun98\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", r1, p1, pb);
u_free(p0); u_free(r0); u_free(p1); u_free(r1);
}
static void cleanup(void) { test_unlink(ca); test_unlink(cb); test_rmdir(tdir); }

10
tests/test_etcp_router.c

@ -58,8 +58,8 @@ static struct UASYNC* ua = NULL;
static struct dummynet* dn = NULL;
static int g_dn_port = 0, g_srv_port = 0, g_cli_port = 0;
static const uint64_t server_node_id = 0x1111111111111111ULL;
static const uint64_t client_node_id = 0x2222222222222222ULL;
static uint64_t server_node_id = 0;
static uint64_t client_node_id = 0;
// Bitmap: 0=not received, 1=received
static uint8_t rcvd_bitmap[BITMAP_SIZE];
@ -164,7 +164,6 @@ static void write_configs(void) {
if (!f) { fprintf(stderr, "fopen server fail\n"); exit(1); }
fprintf(f,
"[global]\n"
"my_node_id=0x1111111111111111\n"
"my_private_key=%s\n"
"my_public_key=%s\n"
"tun_ip=10.99.0.1/24\n"
@ -185,7 +184,6 @@ static void write_configs(void) {
if (!f) { fprintf(stderr, "fopen client fail\n"); exit(1); }
fprintf(f,
"[global]\n"
"my_node_id=0x2222222222222222\n"
"my_private_key=%s\n"
"my_public_key=%s\n"
"tun_ip=10.99.0.2/24\n"
@ -496,7 +494,7 @@ static void monitor(void* arg) {
g_total_drop++;
g_cycle_drop_count[g_cycle]++;
}
g_mon_id = uasync_set_timeout(ua, 500, NULL, monitor, "mon"); // 50ms = ROUTER_SEND_RESUME_TB
g_mon_id = uasync_set_timeout(ua, 10, NULL, monitor, "mon");
return;
}
@ -628,6 +626,8 @@ int main(void) {
cli = utun_instance_create(ua, client_conf);
if (!cli || utun_instance_init(cli) < 0) { printf("[FAIL] client create\n"); goto done; }
server_node_id = srv->node_id; client_node_id = cli->node_id;
// Create dummynet between client and server
if (setup_dummynet() != 0) { printf("[FAIL] dummynet setup\n"); goto done; }

21
tests/test_etcp_router_reconnect.c

@ -50,9 +50,9 @@ static char temp_dir[] = "/tmp/utun_test_XXXXXX";
static char a_conf[512], b_conf[512], c_conf[512];
static int a_port, b_port_a, b_port_c, c_port;
static const uint64_t node_a = 0xAAAAAAAA00000001ULL;
static const uint64_t node_b = 0xBBBBBBBB00000001ULL;
static const uint64_t node_c = 0xCCCCCCCC00000001ULL;
static uint64_t node_a = 0;
static uint64_t node_b = 0;
static uint64_t node_c = 0;
static struct SC_MYKEYS keys_a, keys_b, keys_c;
static char privhex_a[65], privhex_b[65], privhex_c[65], pubhex_a[65], pubhex_b[65], pubhex_c[65];
@ -104,21 +104,21 @@ static int create_temp_configs(void) {
snprintf(a_conf,sizeof(a_conf),"%s/a.conf",temp_dir);
FILE* f=fopen(a_conf,"w"); if(!f){perror("a_conf");return -1;}
fprintf(f,"[global]\nmy_node_id=0x%016llX\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.1/24\ntun_ifname=tun99\n\n"
"[server:hub_a]\naddr=127.0.0.1:%d\ntype=public\n\n[allowed_keys]\nallow_all=1\n",(unsigned long long)node_a,privhex_a,pubhex_a,a_port);
fprintf(f,"[global]\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.1/24\ntun_ifname=tun99\n\n"
"[server:hub_a]\naddr=127.0.0.1:%d\ntype=public\n\n[allowed_keys]\nallow_all=1\n",privhex_a,pubhex_a,a_port);
fclose(f);
snprintf(b_conf,sizeof(b_conf),"%s/b.conf",temp_dir);
f=fopen(b_conf,"w"); if(!f){perror("b_conf");return -1;}
fprintf(f,"[global]\nmy_node_id=0x%016llX\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.2/24\ntun_ifname=tun98\n\n"
fprintf(f,"[global]\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.2/24\ntun_ifname=tun98\n\n"
"[server:s_a]\naddr=127.0.0.1:%d\ntype=public\n\n[server:s_c]\naddr=127.0.0.1:%d\ntype=public\n",
(unsigned long long)node_b,privhex_b,pubhex_b,b_port_a,b_port_c);
privhex_b,pubhex_b,b_port_a,b_port_c);
fclose(f);
snprintf(c_conf,sizeof(c_conf),"%s/c.conf",temp_dir);
f=fopen(c_conf,"w"); if(!f){perror("c_conf");return -1;}
fprintf(f,"[global]\nmy_node_id=0x%016llX\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.3/24\ntun_ifname=tun97\n\n"
"[server:hub_c]\naddr=127.0.0.1:%d\ntype=public\n\n[allowed_keys]\nallow_all=1\n",(unsigned long long)node_c,privhex_c,pubhex_c,c_port);
fprintf(f,"[global]\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.3/24\ntun_ifname=tun97\n\n"
"[server:hub_c]\naddr=127.0.0.1:%d\ntype=public\n\n[allowed_keys]\nallow_all=1\n",privhex_c,pubhex_c,c_port);
fclose(f);
return 0;
}
@ -243,6 +243,9 @@ int main(void) {
g_a=utun_instance_create(ua,a_conf); if(!g_a||utun_instance_init(g_a)<0){fprintf(stderr,"FAIL: a\n");goto fail;}
g_c=utun_instance_create(ua,c_conf); if(!g_c||utun_instance_init(g_c)<0){fprintf(stderr,"FAIL: c\n");goto fail;}
g_b=utun_instance_create(ua,b_conf); if(!g_b||utun_instance_init(g_b)<0){fprintf(stderr,"FAIL: b\n");goto fail;}
node_a=g_a->node_id; node_b=g_b->node_id; node_c=g_c->node_id;
etcp_router_bind(g_c, TEST_SVC_ID, recv_handler);
struct TOPO_NODEQ* nq_a = mknode(node_a, keys_a.public_key, 127,0,0,1, (uint16_t)a_port);

10
tests/test_icmp_proxy.c

@ -40,7 +40,6 @@ static const char* cfg_node_client(void) {
static char buf[1024];
snprintf(buf, sizeof(buf),
"[global]\n"
"my_node_id=0xEEEE000000000001\n"
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"tun_ip=10.99.0.1/24\n"
@ -52,7 +51,7 @@ static const char* cfg_node_client(void) {
"enabled=yes\n"
"tun_name=tun_tcp\n"
"tun_ip=10.99.0.1\n"
"via_node=0xEEEE000000000002\n");
"via_node=0x2b2d74c71e38c7fe\n");
return buf;
}
@ -60,7 +59,6 @@ static const char* cfg_node_exit(void) {
static char buf[1024];
snprintf(buf, sizeof(buf),
"[global]\n"
"my_node_id=0xEEEE000000000002\n"
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"tun_ip=10.99.0.2/24\n"
@ -75,8 +73,8 @@ static struct UTUN_INSTANCE* cli = NULL;
static struct UTUN_INSTANCE* exit_node = NULL;
static struct UASYNC* ua = NULL;
static int g_ok = 0, g_done = 0, g_test_phase = 0;
static uint64_t exit_node_id = 0xEEEE000000000002ULL;
static uint64_t client_node_id = 0xEEEE000000000001ULL;
static uint64_t exit_node_id = 0;
static uint64_t client_node_id = 0;
static uint16_t test_echo_id = 0x1234, test_echo_seq = 0x0001;
static uint8_t send_buf[ICMP_PAYLOAD_SIZE];
static int reply_rcvd = 0;
@ -160,6 +158,8 @@ int main(void) {
if (utun_instance_init(cli) < 0) { printf("[FAIL] client init\n"); goto done; }
if (utun_instance_init(exit_node) < 0) { printf("[FAIL] exit init\n"); goto done; }
exit_node_id = exit_node->node_id; client_node_id = cli->node_id;
icmp_proxy_set_test_loopback(exit_node, 1);
g_to_id = uasync_set_timeout(ua, 100, NULL, monitor, "mon");

4
tests/test_ipv6_sockets.c

@ -57,7 +57,6 @@ static char client_config_path[256];
static const char* server_config =
"[global]\n"
"my_node_name=server_v6\n"
"my_node_id=0xAAAA000000000001\n"
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"tun_ip=10.99.0.1/24\n"
@ -77,7 +76,6 @@ static const char* server_config =
static const char* client_config =
"[global]\n"
"my_node_name=client_v6\n"
"my_node_id=0xBBBB000000000002\n"
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"tun_ip=10.99.0.2/24\n"
@ -246,7 +244,7 @@ static void monitor(void* arg) {
return;
}
case 2: {
int ok = verify_remote_v6_nodeinfo("client_remote", client_instance, 0xAAAA000000000001ULL);
int ok = verify_remote_v6_nodeinfo("client_remote", client_instance, server_instance->node_id);
if (!ok) { test_phase = 4; return; }
printf("=== ALL CHECKS PASSED ===\n");

26
tests/test_lwip_tcp.c

@ -195,6 +195,20 @@ static char stc(struct conn_state *s)
}
}
static int check_all_done(struct tcp_bridge *br)
{
int done = 0;
for (int i = 0; i < br->sc; i++) {
struct conn_state *c = br->st[i];
if (!c) { done++; continue; }
if (c->err) { done++; }
else if (!c->pcb) { done++; }
else if (c->pcb->state >= FIN_WAIT_1) { done++; }
else if (c->sdone && c->rdone) { done++; }
}
return done >= br->sc;
}
static void progress_timer(void *arg)
{
struct tcp_bridge *br = arg;
@ -393,9 +407,10 @@ static int run_test1(struct tcp_bridge *br)
tcp_connect(cl->pcb, IP_A, port, (tcp_connected_fn)t1_connected);
// poll until done
while (!br->ad && !sv->err && !cl->err) {
while (!br->ad) {
uasync_poll(br->ua, POLL_TB);
bridge_deliver(br);
if (check_all_done(br)) br->ad = 1;
}
uasync_poll(br->ua, POLL_TB);
bridge_deliver(br);
@ -573,6 +588,7 @@ static int run_test2(struct tcp_bridge *br)
while (!br->ad) {
uasync_poll(br->ua, POLL_TB);
bridge_deliver(br);
if (check_all_done(br)) br->ad = 1;
}
uasync_poll(br->ua, POLL_TB);
bridge_deliver(br);
@ -646,8 +662,8 @@ int main(int argc, char *argv[])
br->b = lwip_tcp_init(ua, bo_b, br);
if (!br->a || !br->b) { fprintf(stderr, "[FAIL] init\n"); return 1; }
br->a->trace_id = 'A'; br->b->trace_id = 'B';
lwip_tcp_set_timer(br->a, 1, 10, 40);
lwip_tcp_set_timer(br->b, 1, 10, 40);
lwip_tcp_set_timer(br->a, 10, 50, 200);
lwip_tcp_set_timer(br->b, 10, 50, 200);
int r1 = run_test1(br);
@ -657,8 +673,8 @@ int main(int argc, char *argv[])
br->b = lwip_tcp_init(ua, bo_b, br);
if (!br->a || !br->b) { fprintf(stderr, "[FAIL] reinit\n"); return 1; }
br->a->trace_id = 'A'; br->b->trace_id = 'B';
lwip_tcp_set_timer(br->a, 1, 10, 40);
lwip_tcp_set_timer(br->b, 1, 10, 40);
lwip_tcp_set_timer(br->a, 10, 50, 200);
lwip_tcp_set_timer(br->b, 10, 50, 200);
int r2 = run_test2(br);

33
tests/test_nat_detection.c

@ -30,9 +30,9 @@
#include "../lib/debug_config.h"
#define TEST_TIMEOUT_MS 5000
#define NODE_ID_S 0x1111111111111111ULL
#define NODE_ID_C1 0x2222222222222222ULL
#define NODE_ID_C2 0x3333333333333333ULL
static uint64_t g_node_id_s = 0;
static uint64_t g_node_id_c1 = 0;
static uint64_t g_node_id_c2 = 0;
static struct UTUN_INSTANCE* inst_s = NULL;
static struct UTUN_INSTANCE* inst_c1 = NULL;
@ -81,7 +81,6 @@ static int create_temp_configs(void) {
const char* tpl_s =
"[global]\n"
"my_node_id=0x1111111111111111\n"
"tun_ip=10.100.0.1/24\n"
"tun_ifname=tun100\n"
"\n"
@ -98,7 +97,6 @@ static int create_temp_configs(void) {
const char* tpl_c2 =
"[global]\n"
"my_node_id=0x3333333333333333\n"
"tun_ip=10.100.0.3/24\n"
"tun_ifname=tun103\n"
"\n"
@ -119,7 +117,6 @@ static int create_temp_configs(void) {
const char* tpl_c1 =
"[global]\n"
"my_node_id=0x2222222222222222\n"
"tun_ip=10.100.0.2/24\n"
"tun_ifname=tun102\n"
"\n"
@ -221,6 +218,8 @@ int main(void) {
goto cleanup;
}
g_node_id_s = inst_s->node_id; g_node_id_c1 = inst_c1->node_id; g_node_id_c2 = inst_c2->node_id;
test_timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS * 10, NULL, test_timeout_cb, "test_nat");
// 1. Wait for links C1->S and C2->S to initialize
@ -242,16 +241,16 @@ int main(void) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Waiting for BGP exchange (S learns C1 and C2)...");
int bgp_wait_cycles = 0;
while (!test_timed_out && bgp_wait_cycles < 500) {
if (topo_groups_get_default(inst_s->topo_groups) && topo_node_find_by_id(topo_groups_get_default(inst_s->topo_groups), NODE_ID_C1) != NULL &&
topo_node_find_by_id(topo_groups_get_default(inst_s->topo_groups), NODE_ID_C2) != NULL) {
if (topo_groups_get_default(inst_s->topo_groups) && topo_node_find_by_id(topo_groups_get_default(inst_s->topo_groups), g_node_id_c1) != NULL &&
topo_node_find_by_id(topo_groups_get_default(inst_s->topo_groups), g_node_id_c2) != NULL) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "S learned about C1 and C2");
break;
}
uasync_poll(ua, 10);
bgp_wait_cycles++;
}
if (!topo_groups_get_default(inst_s->topo_groups) || topo_node_find_by_id(topo_groups_get_default(inst_s->topo_groups), NODE_ID_C1) == NULL ||
topo_node_find_by_id(topo_groups_get_default(inst_s->topo_groups), NODE_ID_C2) == NULL) {
if (!topo_groups_get_default(inst_s->topo_groups) || topo_node_find_by_id(topo_groups_get_default(inst_s->topo_groups), g_node_id_c1) == NULL ||
topo_node_find_by_id(topo_groups_get_default(inst_s->topo_groups), g_node_id_c2) == NULL) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "S did not learn about C1/C2 in time");
goto cleanup;
}
@ -268,7 +267,7 @@ int main(void) {
}
// 3. Wait for NAT detection to complete for C1 - check link on server
struct ETCP_LINK* link_sc1 = find_link_to_node(inst_s, NODE_ID_C1);
struct ETCP_LINK* link_sc1 = find_link_to_node(inst_s, g_node_id_c1);
if (!link_sc1) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "No link to C1 on server");
goto cleanup;
@ -311,7 +310,7 @@ int main(void) {
}
// 5. Verify all NAT fields on server
struct TOPO_NODEQ* node_c1 = topo_node_find_by_id(topo_groups_get_default(inst_s->topo_groups), NODE_ID_C1);
struct TOPO_NODEQ* node_c1 = topo_node_find_by_id(topo_groups_get_default(inst_s->topo_groups), g_node_id_c1);
if (!node_c1) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "TOPO_NODEQ for C1 disappeared");
goto cleanup;
@ -337,7 +336,7 @@ int main(void) {
{ struct ll_entry* e = inst_s->connections->head;
while (e) {
struct conn_queue_entry* ce = (struct conn_queue_entry*)e->data;
if (ce->conn->peer_node_id == NODE_ID_C1) { conn_sc1 = ce->conn; break; }
if (ce->conn->peer_node_id == g_node_id_c1) { conn_sc1 = ce->conn; break; }
e = e->next;
} }
if (!link_sc1 || link_sc1->nat_type != NAT_TYPE_EIM) {
@ -395,7 +394,7 @@ int main(void) {
bgp_wait_cycles = 0;
int c2_verified_nat = 0;
while (!test_timed_out && bgp_wait_cycles < 500) {
struct TOPO_NODEQ* node_c1_on_c2 = topo_groups_get_default(inst_c2->topo_groups) ? topo_node_find_by_id(topo_groups_get_default(inst_c2->topo_groups), NODE_ID_C1) : NULL;
struct TOPO_NODEQ* node_c1_on_c2 = topo_groups_get_default(inst_c2->topo_groups) ? topo_node_find_by_id(topo_groups_get_default(inst_c2->topo_groups), g_node_id_c1) : NULL;
if (node_c1_on_c2 && node_c1_on_c2->node) {
for (const struct TOPO_SOCKMETA4* m = node_c1_on_c2->node->v4_sock_meta; m; m = m->next) {
if (m->nat_type == NAT_VERIFIED_EIM) { c2_verified_nat = 1; break; }
@ -417,7 +416,7 @@ int main(void) {
struct ll_entry* entry = inst_s->connections->head;
while (entry) {
struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data;
if (ce->conn->peer_node_id == NODE_ID_C2) { conn_sc2 = ce->conn; break; }
if (ce->conn->peer_node_id == g_node_id_c2) { conn_sc2 = ce->conn; break; }
entry = entry->next;
}
if (!conn_sc2) {
@ -460,14 +459,14 @@ int main(void) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Waiting for C2 to learn C1 nodeinfo...");
bgp_wait_cycles = 0;
while (!test_timed_out && bgp_wait_cycles < 500) {
if (topo_groups_get_default(inst_c2->topo_groups) && topo_node_find_by_id(topo_groups_get_default(inst_c2->topo_groups), NODE_ID_C1) != NULL) {
if (topo_groups_get_default(inst_c2->topo_groups) && topo_node_find_by_id(topo_groups_get_default(inst_c2->topo_groups), g_node_id_c1) != NULL) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "C2 learned C1 nodeinfo");
break;
}
uasync_poll(ua, 10);
bgp_wait_cycles++;
}
if (!topo_groups_get_default(inst_c2->topo_groups) || topo_node_find_by_id(topo_groups_get_default(inst_c2->topo_groups), NODE_ID_C1) == NULL) {
if (!topo_groups_get_default(inst_c2->topo_groups) || topo_node_find_by_id(topo_groups_get_default(inst_c2->topo_groups), g_node_id_c1) == NULL) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: C2 did not learn C1 nodeinfo in time");
goto cleanup;
}

35
tests/test_nat_transport.c

@ -33,8 +33,8 @@
#include "../lib/ll_queue.h"
#define TEST_TIMEOUT_MS 15000
#define NODE_ID_PROVIDER 0xAAAA000000000001ULL
#define NODE_ID_CLIENT 0xBBBB000000000001ULL
static uint64_t g_provider_node_id = 0;
static uint64_t g_client_node_id = 0;
#define NAT_SVC_HDR_SIZE 9 // svc_id(1) + src_node_id(8)
static struct UTUN_INSTANCE* inst_provider = NULL;
@ -72,6 +72,14 @@ static int write_config(const char* path, const char* content) {
return 0;
}
static uint64_t get_node_id_from_config(const char* path) {
struct utun_config* cfg = parse_config(path);
if (!cfg) return 0;
uint64_t nid = cfg->global.my_node_id;
free_config(cfg);
return nid;
}
static char* get_pubkey_from_config(const char* path) {
struct utun_config* cfg = parse_config(path);
if (!cfg) return NULL;
@ -90,7 +98,6 @@ static int create_temp_configs(void) {
const char* tpl_provider =
"[global]\n"
"my_node_id=0xAAAA000000000001\n"
"tun_ip=10.100.0.1/24\n"
"tun_ifname=tun_provider\n"
"tun_test_mode=1\n"
@ -110,13 +117,13 @@ static int create_temp_configs(void) {
"port_end=20099\n";
if (write_config(config_provider, tpl_provider) != 0) return -1;
if (config_ensure_keys_and_node_id(config_provider) != 0) return -1;
uint64_t prov_nid = get_node_id_from_config(config_provider);
char* pub_prov = get_pubkey_from_config(config_provider);
if (!pub_prov) return -1;
char tpl_client_full[4096];
snprintf(tpl_client_full, sizeof(tpl_client_full),
"[global]\n"
"my_node_id=0xBBBB000000000001\n"
"tun_ip=10.200.0.1/24\n"
"tun_ifname=tun_client\n"
"tun_test_mode=1\n"
@ -134,8 +141,8 @@ static int create_temp_configs(void) {
"enabled=1\n"
"tun_ifname=tun_nat_client\n"
"tun_ip=100.64.1.1/24\n"
"nat_via=0xAAAA000000000001\n",
pub_prov);
"nat_via=0x%016llx\n",
pub_prov, (unsigned long long)prov_nid);
free(pub_prov);
if (write_config(config_client, tpl_client_full) != 0) return -1;
if (config_ensure_keys_and_node_id(config_client) != 0) return -1;
@ -265,7 +272,7 @@ static int test_init_destroy(void) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Client NAT transport not initialized");
ok = 0;
}
if (inst_client->nat_tr.nat_via_node_id != NODE_ID_PROVIDER) {
if (inst_client->nat_tr.nat_via_node_id != g_provider_node_id) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Client nat_via_node_id mismatch: 0x%016llx",
(unsigned long long)inst_client->nat_tr.nat_via_node_id);
ok = 0;
@ -300,7 +307,7 @@ static int test_provider_egress(void) {
entry->dgram = dgram;
entry->len = total;
int ret = etcp_route_send(inst_client, NODE_ID_PROVIDER, entry, 0);
int ret = etcp_route_send(inst_client, g_provider_node_id, entry, 0);
if (ret != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "etcp_route_send failed");
queue_dgram_free(entry);
@ -337,9 +344,9 @@ static int test_provider_egress(void) {
e->internal_ip, be16toh(e->internal_port), e->proto,
inst_provider->nat.port_start, (unsigned long long)e->src_node_id);
if (e->src_node_id != NODE_ID_CLIENT) {
if (e->src_node_id != g_client_node_id) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "src_node_id mismatch: %016llx vs %016llx",
(unsigned long long)e->src_node_id, NODE_ID_CLIENT);
(unsigned long long)e->src_node_id, g_client_node_id);
return 0;
}
if (e->internal_ip != 0x0A0000FE) {
@ -469,7 +476,7 @@ static int test_full_roundtrip(void) {
struct ll_entry* entry = queue_entry_new(0);
entry->dgram = dgram;
entry->len = total;
int ret = etcp_route_send(inst_client, NODE_ID_PROVIDER, entry, 0);
int ret = etcp_route_send(inst_client, g_provider_node_id, entry, 0);
if (ret != 0) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "etcp_route_send failed"); queue_dgram_free(entry); queue_entry_free(entry); return 0; }
// Poll for provider to process egress
@ -549,6 +556,8 @@ int main(void) {
goto cleanup;
}
g_provider_node_id = inst_provider->node_id; g_client_node_id = inst_client->node_id;
test_timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS, NULL, test_timeout_cb, "test_nat_transport");
// Wait for ETCP link between client and provider
@ -566,8 +575,8 @@ int main(void) {
DEBUG_INFO(DEBUG_CATEGORY_NAT, "Waiting for BGP...");
int bgp_cycles = 0;
while (!test_timed_out && bgp_cycles < 500) {
if (topo_groups_get_default(inst_provider->topo_groups) && topo_node_find_by_id(topo_groups_get_default(inst_provider->topo_groups), NODE_ID_CLIENT) &&
topo_groups_get_default(inst_client->topo_groups) && topo_node_find_by_id(topo_groups_get_default(inst_client->topo_groups), NODE_ID_PROVIDER)) {
if (topo_groups_get_default(inst_provider->topo_groups) && topo_node_find_by_id(topo_groups_get_default(inst_provider->topo_groups), g_client_node_id) &&
topo_groups_get_default(inst_client->topo_groups) && topo_node_find_by_id(topo_groups_get_default(inst_client->topo_groups), g_provider_node_id)) {
DEBUG_INFO(DEBUG_CATEGORY_NAT, "BGP exchanged");
break;
}

19
tests/test_route_ping.c

@ -29,9 +29,9 @@
#include "../lib/debug_config.h"
#define TEST_TIMEOUT_MS 10000
#define NODE_ID_A 0x1111111111111111ULL
#define NODE_ID_B 0x2222222222222222ULL
#define NODE_ID_C 0x3333333333333333ULL
static uint64_t g_node_id_a = 0;
static uint64_t g_node_id_b = 0;
static uint64_t g_node_id_c = 0;
static struct UTUN_INSTANCE* inst_a = NULL;
static struct UTUN_INSTANCE* inst_b = NULL;
@ -81,7 +81,6 @@ static int create_temp_configs(void) {
// 1. Config C (no keys)
const char* tpl_c =
"[global]\n"
"my_node_id=0x3333333333333333\n"
"tun_ip=10.100.0.3/24\n"
"tun_ifname=tun102\n"
"\n"
@ -100,7 +99,6 @@ static int create_temp_configs(void) {
char tpl_b[4096];
snprintf(tpl_b, sizeof(tpl_b),
"[global]\n"
"my_node_id=0x2222222222222222\n"
"tun_ip=10.100.0.2/24\n"
"tun_ifname=tun101\n"
"\n"
@ -126,7 +124,6 @@ static int create_temp_configs(void) {
char tpl_a[4096];
snprintf(tpl_a, sizeof(tpl_a),
"[global]\n"
"my_node_id=0x1111111111111111\n"
"tun_ip=10.100.0.1/24\n"
"tun_ifname=tun100\n"
"\n"
@ -226,6 +223,8 @@ int main(void) {
goto cleanup;
}
g_node_id_a = inst_a->node_id; g_node_id_b = inst_b->node_id; g_node_id_c = inst_c->node_id;
test_timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS * 10, NULL, test_timeout_cb, "test_route_ping");
// 1. Wait for links A->B and B->C to initialize
@ -246,20 +245,20 @@ int main(void) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Waiting for BGP exchange (B learns C)...");
int bgp_wait_cycles = 0;
while (!test_timed_out && bgp_wait_cycles < 500) {
if (topo_groups_get_default(inst_b->topo_groups) && topo_node_find_by_id(topo_groups_get_default(inst_b->topo_groups), NODE_ID_C) != NULL) {
if (topo_groups_get_default(inst_b->topo_groups) && topo_node_find_by_id(topo_groups_get_default(inst_b->topo_groups), g_node_id_c) != NULL) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "B learned about C");
break;
}
uasync_poll(ua, 10);
bgp_wait_cycles++;
}
if (!topo_groups_get_default(inst_b->topo_groups) || topo_node_find_by_id(topo_groups_get_default(inst_b->topo_groups), NODE_ID_C) == NULL) {
if (!topo_groups_get_default(inst_b->topo_groups) || topo_node_find_by_id(topo_groups_get_default(inst_b->topo_groups), g_node_id_c) == NULL) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "B did not learn about C in time");
goto cleanup;
}
// 3. Get target info for C and send route ping request from A to B
struct ETCP_CONN* conn_ab = find_conn_to_peer(inst_a, NODE_ID_B);
struct ETCP_CONN* conn_ab = find_conn_to_peer(inst_a, g_node_id_b);
if (!conn_ab) {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "A has no connection to B");
goto cleanup;
@ -269,7 +268,7 @@ int main(void) {
/* addr[] в network byte order, port в host byte order */
uint32_t target_ip = 0;
uint16_t target_port = 0;
struct TOPO_NODEQ* nq = topo_groups_get_default(inst_b->topo_groups) ? topo_node_find_by_id(topo_groups_get_default(inst_b->topo_groups), NODE_ID_C) : NULL;
struct TOPO_NODEQ* nq = topo_groups_get_default(inst_b->topo_groups) ? topo_node_find_by_id(topo_groups_get_default(inst_b->topo_groups), g_node_id_c) : NULL;
if (nq && nq->node) {
const struct TOPO_ADDR4* a = nq->node->v4_addrs;
if (a) {

4
tests/test_socks_http_proxy.c

@ -184,7 +184,6 @@ static char* make_cfg_server(void) {
static char buf[1024];
snprintf(buf, sizeof(buf),
"[global]\ntun_enabled=no\n"
"my_node_id=0x2222000000000001\n"
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"[server: s1]\naddr=127.0.0.1:%d\ntype=public\n"
@ -197,7 +196,6 @@ static char* make_cfg_client(void) {
static char buf[1024];
snprintf(buf, sizeof(buf),
"[global]\ntun_enabled=no\n"
"my_node_id=0x1111000000000001\n"
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"[server: s1]\naddr=127.0.0.1:%d\ntype=public\n"
@ -209,7 +207,7 @@ static char* make_cfg_client(void) {
"socks_addr=127.0.0.1:%d\n"
"http_proxy_enabled=yes\n"
"http_proxy_addr=127.0.0.1:%d\n"
"via_node=0x2222000000000001\n",
"via_node=0x2b2d74c71e38c7fe\n",
g_cli_port, g_srv_port, g_socks_port, g_http_proxy_port);
return buf;
}

37
tests/test_stcp.c

@ -63,7 +63,6 @@ static void setup_peer(struct test_peer *p, struct stcp_conn *conn) {
queue_set_callback(p->rx, peer_rx_cb, p);
stcp_conn_set_rx_queue(conn, p->rx);
stcp_conn_set_tx_queue(conn, p->tx);
stcp_conn_set_on_close(conn, peer_close_cb, p);
p->ready = 1;
}
@ -101,8 +100,8 @@ static int test1_sizes(void) {
struct test_peer srv = {0}, cli = {0};
uint16_t port = BASE_PORT + 1;
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, server_connect_cb, &srv); TASSERT(ss);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, client_ready_cb, &cli); TASSERT(sc);
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, server_connect_cb, &srv, peer_close_cb, &srv); TASSERT(ss);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, client_ready_cb, &cli, peer_close_cb, &cli); TASSERT(sc);
size_t sizes[] = {0, 1, 16, 17, 255, 256, 1000, 65535};
int n_sizes = 8;
@ -111,7 +110,7 @@ static int test1_sizes(void) {
for (int i = 0; i < 65536; i++) payload[i] = (uint8_t)(i * 7 + 13);
int sent = 0, ticks = 0;
while (srv.msg_count < n_sizes && ticks < 5000) {
while (srv.msg_count < n_sizes && ticks < 200) {
uasync_poll(ua, 10);
if (srv.ready && cli.ready && !sent) {
for (int i = 0; i < n_sizes; i++) TASSERT(peer_send(&cli, payload, sizes[i]) == 0);
@ -141,11 +140,11 @@ static int test2_many(void) {
struct test_peer srv = {0}, cli = {0};
uint16_t port = BASE_PORT + 2;
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, server_connect_cb, &srv); TASSERT(ss);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, client_ready_cb, &cli); TASSERT(sc);
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, server_connect_cb, &srv, peer_close_cb, &srv); TASSERT(ss);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, client_ready_cb, &cli, peer_close_cb, &cli); TASSERT(sc);
int sent = 0, ticks = 0;
while (srv.msg_count < 200 && ticks < 5000) {
while (srv.msg_count < 200 && ticks < 200) {
uasync_poll(ua, 10);
if (srv.ready && cli.ready && !sent) {
for (int i = 0; i < 200; i++) {
@ -179,14 +178,14 @@ static int test3_wrong_key(void) {
struct test_peer srv = {0}, cli = {0};
uint16_t port = BASE_PORT + 3;
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, server_connect_cb, &srv); TASSERT(ss);
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, server_connect_cb, &srv, peer_close_cb, &srv); TASSERT(ss);
struct SC_MYKEYS rogue;
TASSERT(sc_generate_keypair(&rogue) == SC_OK);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, rogue.public_key, client_ready_cb, &cli); TASSERT(sc);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, rogue.public_key, client_ready_cb, &cli, peer_close_cb, &cli); TASSERT(sc);
int ticks = 0;
while (ticks < 3000) {
while (ticks < 200) {
uasync_poll(ua, 10);
if (cli.closed || srv.closed) break;
ticks++;
@ -207,11 +206,11 @@ static int test4_close(void) {
struct test_peer srv = {0}, cli = {0};
uint16_t port = BASE_PORT + 4;
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, server_connect_cb, &srv); TASSERT(ss);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, client_ready_cb, &cli); TASSERT(sc);
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, server_connect_cb, &srv, peer_close_cb, &srv); TASSERT(ss);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, client_ready_cb, &cli, peer_close_cb, &cli); TASSERT(sc);
int closed = 0, ticks = 0;
while (!srv.closed && ticks < 3000) {
while (!srv.closed && ticks < 200) {
uasync_poll(ua, 10);
if (srv.ready && cli.ready && !closed) {
uint8_t m = 0xAB; peer_send(&cli, &m, 1);
@ -254,16 +253,16 @@ static int test5_multi(void) {
memset(srvp, 0, sizeof(srvp)); memset(clip, 0, sizeof(clip));
g_multi_peers = srvp; g_multi_idx = 0; g_multi_max = NCLI;
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, multi_connect_cb, NULL); TASSERT(ss);
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, multi_connect_cb, NULL, NULL, NULL); TASSERT(ss);
struct stcp_client *clients[NCLI] = {0};
for (int i = 0; i < NCLI; i++) {
clients[i] = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, client_ready_cb, &clip[i]);
clients[i] = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, client_ready_cb, &clip[i], peer_close_cb, &clip[i]);
TASSERT(clients[i]);
}
int sent = 0, ticks = 0;
while (ticks < 5000) {
while (ticks < 200) {
uasync_poll(ua, 10);
if (!sent) {
int all_ready = 1;
@ -306,8 +305,8 @@ static int test6_interleaved(void) {
struct test_peer srv = {0}, cli = {0};
uint16_t port = BASE_PORT + 6;
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, server_connect_cb, &srv); TASSERT(ss);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, client_ready_cb, &cli); TASSERT(sc);
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, server_connect_cb, &srv, peer_close_cb, &srv); TASSERT(ss);
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key, client_ready_cb, &cli, peer_close_cb, &cli); TASSERT(sc);
int round = 0, ticks = 0;
while (srv.msg_count < 50 || cli.msg_count < 50) {
@ -317,7 +316,7 @@ static int test6_interleaved(void) {
uint8_t sb = (uint8_t)(round + 200);
if (peer_send(&cli, &cb, 1) == 0 && peer_send(&srv, &sb, 1) == 0) round++;
}
if (++ticks > 5000) break;
if (++ticks > 200) break;
}
TASSERT(srv.msg_count >= 50);
TASSERT(cli.msg_count >= 50);

2
tests/test_uasync_socket_race.c

@ -150,7 +150,7 @@ int main(void) {
g_mon_id = uasync_set_timeout(g_ua, 100, NULL, monitor, "mon");
void* to_id = uasync_set_timeout(g_ua, TIMEOUT_MS * 10, NULL, test_timeout, "to");
while (!g_done) uasync_poll(g_ua, 50);
while (!g_done) uasync_poll(g_ua, 0);
if (to_id) uasync_cancel_timeout(g_ua, to_id);

10
tests/test_udp_proxy.c

@ -39,7 +39,6 @@ static const char* cfg_node_client(void) {
static char buf[1024];
snprintf(buf, sizeof(buf),
"[global]\n"
"my_node_id=0xDDDD000000000001\n"
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"tun_ip=10.99.0.1/24\n"
@ -51,7 +50,7 @@ static const char* cfg_node_client(void) {
"enabled=yes\n"
"tun_name=tun_tcp\n"
"tun_ip=10.99.0.1\n"
"via_node=0xDDDD000000000002\n");
"via_node=0x2b2d74c71e38c7fe\n");
return buf;
}
@ -59,7 +58,6 @@ static const char* cfg_node_exit(void) {
static char buf[1024];
snprintf(buf, sizeof(buf),
"[global]\n"
"my_node_id=0xDDDD000000000002\n"
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"tun_ip=10.99.0.2/24\n"
@ -79,8 +77,8 @@ static int g_echo_count = 0;
static uint8_t g_echo_buf[8192];
static ssize_t g_echo_len = 0;
static int g_ok = 0, g_done = 0, g_test_phase = 0;
static uint64_t exit_node_id = 0xDDDD000000000002ULL;
static uint64_t client_node_id = 0xDDDD000000000001ULL;
static uint64_t exit_node_id = 0;
static uint64_t client_node_id = 0;
static uint8_t send_buf[PAYLOAD_SIZE], recv_buf[PAYLOAD_SIZE];
static int reply_rcvd = 0;
static void* g_to_id = NULL;
@ -167,6 +165,8 @@ int main(void) {
if (utun_instance_init(cli) < 0) { printf("[FAIL] client init\n"); goto done; }
if (utun_instance_init(exit_node) < 0) { printf("[FAIL] exit init\n"); goto done; }
exit_node_id = exit_node->node_id; client_node_id = cli->node_id;
// UDP echo server (same uasync, no fork)
g_echo_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (g_echo_sock == SOCKET_INVALID) { printf("[FAIL] echo socket\n"); goto done; }

12
tools/chatgui/transport/chat_core.c

@ -19,6 +19,7 @@
#include "../../../src/etcp.h"
#include "../../../src/etcp_connections.h"
#include "../../../src/secure_channel.h"
#include "../../../src/ntp_time.h"
#include "../../../lib/u_async.h"
#include "../../../lib/ll_queue.h"
#include "../../../lib/mem.h"
@ -302,6 +303,8 @@ void chat_core_update_my_name(const char* name) {
memcpy(join_msg + mlen, g_cc.inst->my_keys.public_key, 32); mlen += 32;
{ const char* nm = name[0] ? name : "";
size_t nl = strlen(nm); memcpy(join_msg + mlen, nm, nl); mlen += nl; join_msg[mlen++] = '\0'; }
uint64_t join_ts = (uint64_t)ntp_time_get_seconds(g_cc.inst);
memcpy(join_msg + mlen, &join_ts, 8); mlen += 8;
uint8_t new_sig[64]; memset(new_sig, 0, 64);
EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL,
g_cc.inst->my_ed25519_privkey, 32);
@ -314,9 +317,10 @@ void chat_core_update_my_name(const char* name) {
}
EVP_PKEY_free(pkey);
}
topo_node_sqlite_member_put(g_cc.db, ch, myid, new_sig, NULL);
topo_node_sqlite_member_put(g_cc.db, ch, myid, new_sig, join_ts, NULL, 0,
g_cc.inst->my_keys.public_key, g_cc.inst->my_ed25519_pubkey, name, NULL);
member_sync_put(g_cc.inst, ch, myid, g_cc.inst->my_keys.public_key,
g_cc.inst->my_ed25519_pubkey, new_sig, NULL, 0);
g_cc.inst->my_ed25519_pubkey, new_sig, join_ts, NULL, 0, name, NULL, 0);
uint8_t evt[65]; uint8_t cl = (uint8_t)strlen(ch);
evt[0] = cl; memcpy(evt + 1, ch, cl);
gui_bridge_post(GUI_EVT_MEMBERS_CHANGED, evt, 1 + cl);
@ -1071,6 +1075,8 @@ void chat_core_create_channel(struct chat_channel_create* req) {
memcpy(join_msg + mlen, g_cc.inst->my_keys.public_key, 32); mlen += 32;
{ const char* nm = g_cc.inst->name[0] ? g_cc.inst->name : "";
size_t nl = strlen(nm); memcpy(join_msg + mlen, nm, nl); mlen += nl; join_msg[mlen++] = '\0'; }
uint64_t join_ts = (uint64_t)ntp_time_get_seconds(g_cc.inst);
memcpy(join_msg + mlen, &join_ts, 8); mlen += 8;
uint8_t join_sig[64];
EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL,
g_cc.inst->my_ed25519_privkey, 32);
@ -1089,7 +1095,7 @@ void chat_core_create_channel(struct chat_channel_create* req) {
}
member_sync_put(g_cc.inst, req->channel_id, myid,
g_cc.inst->my_keys.public_key, g_cc.inst->my_ed25519_pubkey,
join_sig, NULL, 0);
join_sig, join_ts, NULL, 0, g_cc.inst->name, NULL, 0);
}
/* notify GUI — members already in DB, channel will show with self as participant */

134
tools/chatgui/transport/chat_sync.c

@ -10,6 +10,7 @@
#include "../../../src/conn_mgr.h"
#include "../../../src/topo_group.h"
#include "../../../src/secure_channel.h"
#include "../../../src/ntp_time.h"
#include "../../../lib/u_async.h"
#include "../../../lib/ll_queue.h"
#include "../../../lib/debug_config.h"
@ -791,17 +792,34 @@ static void cs_handle_channel_info_req(struct chat_sync* cs, uint64_t peer,
if (memcmp(cs->inst->my_keys.public_key, x25519, 32) != 0)
DEBUG_WARN(DEBUG_CATEGORY_CONNECTIVITY, "%s: CHANNEL_INFO_RESP pubkey MISMATCH: my=%016llx ch=%016llx — channel was created with DIFFERENT keys!",
CS_ID, *(const uint64_t*)cs->inst->my_keys.public_key, *(const uint64_t*)x25519);
uint8_t my_join_sig[64] = {0};
uint8_t join_msg[256]; size_t mlen = 0;
mlen += snprintf((char*)join_msg + mlen, sizeof(join_msg) - mlen, "%s", ch_id) + 1;
memcpy(join_msg + mlen, &myid, 8); mlen += 8;
memcpy(join_msg + mlen, cs->inst->my_keys.public_key, 32); mlen += 32;
{ const char* nm = cs->inst->name[0] ? cs->inst->name : "";
size_t nl = strlen(nm); memcpy(join_msg + mlen, nm, nl); mlen += nl; join_msg[mlen++] = '\0'; }
cs_ed25519_sign(cs->inst->my_ed25519_privkey, join_msg, mlen, my_join_sig);
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "%s: CHANNEL_INFO_RESP signed my_join_sig: myid=%016llx my_pub=%016llx ch_pub=%016llx name=%s",
CS_ID, (unsigned long long)myid, *(const uint64_t*)cs->inst->my_keys.public_key,
*(const uint64_t*)x25519, cs->inst->name);
/* load or create join_sig */
uint8_t my_join_sig[64] = {0}; uint64_t my_join_ts = 0;
{
sqlite3* vdb = cs->inst->topo_groups->topo_sqlite_db;
if (topo_node_sqlite_member_get_join(vdb, ch_id, myid, my_join_sig, &my_join_ts) != 0) {
uint8_t join_msg[256]; size_t mlen = 0;
mlen += snprintf((char*)join_msg + mlen, sizeof(join_msg) - mlen, "%s", ch_id) + 1;
memcpy(join_msg + mlen, &myid, 8); mlen += 8;
memcpy(join_msg + mlen, cs->inst->my_keys.public_key, 32); mlen += 32;
my_join_ts = (uint64_t)ntp_time_get_seconds(cs->inst);
memcpy(join_msg + mlen, &my_join_ts, 8); mlen += 8;
cs_ed25519_sign(cs->inst->my_ed25519_privkey, join_msg, mlen, my_join_sig);
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "%s: CHANNEL_INFO_RESP created NEW join_sig: myid=%016llx ts=%llu",
CS_ID, (unsigned long long)myid, (unsigned long long)my_join_ts);
}
}
/* generate update_sig */
uint8_t my_update_sig[64] = {0}; uint64_t my_update_ts;
{
uint8_t umsg[256]; size_t ulen = 0;
ulen += snprintf((char*)umsg + ulen, sizeof(umsg) - ulen, "%s", ch_id) + 1;
memcpy(umsg + ulen, &myid, 8); ulen += 8;
memcpy(umsg + ulen, cs->inst->my_keys.public_key, 32); ulen += 32;
my_update_ts = (uint64_t)ntp_time_get_seconds(cs->inst);
memcpy(umsg + ulen, &my_update_ts, 8); ulen += 8;
memcpy(umsg + ulen, my_join_sig, 64); ulen += 64;
cs_ed25519_sign(cs->inst->my_ed25519_privkey, umsg, ulen, my_update_sig);
}
uint8_t buf[1024]; size_t boff = 0;
buf[boff++] = CS_MSG_CHANNEL_INFO_RESP;
@ -812,7 +830,14 @@ static void cs_handle_channel_info_req(struct chat_sync* cs, uint64_t peer,
memcpy(buf + boff, x25519, 32); boff += 32;
memcpy(buf + boff, ed_pub, 32); boff += 32;
memcpy(buf + boff, ch_sig, 64); boff += 64;
memcpy(buf + boff, my_join_sig, 64); boff += 64;
uint8_t inv_flags = (my_join_sig[0] || my_join_ts) ? PEERS_FLAG_HAS_JOIN : 0;
buf[boff++] = inv_flags;
if (inv_flags & PEERS_FLAG_HAS_JOIN) {
memcpy(buf + boff, my_join_sig, 64); boff += 64;
memcpy(buf + boff, &my_join_ts, 8); boff += 8;
}
memcpy(buf + boff, my_update_sig, 64); boff += 64;
memcpy(buf + boff, &my_update_ts, 8); boff += 8;
{ const char* iname = cs->inst->name[0] ? cs->inst->name : "";
uint8_t il = (uint8_t)strlen(iname);
buf[boff++] = il; memcpy(buf + boff, iname, il); boff += il; }
@ -826,7 +851,7 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer,
if (cs->info_req_timer) { uasync_cancel_timeout(cs->inst->ua, cs->info_req_timer); cs->info_req_timer = NULL; }
if (len < 1) { DEBUG_WARN(DEBUG_CATEGORY_CONNECTIVITY, "%s: CHANNEL_INFO_RESP too short len=%zu peer=%016llx", CS_ID, len, (unsigned long long)peer); return; }
uint8_t nl = pl[0];
if (1 + nl + 8 + 1 + 32 + 32 + 64 + 64 + 1 > len) { DEBUG_WARN(DEBUG_CATEGORY_CONNECTIVITY, "%s: CHANNEL_INFO_RESP truncated len=%zu min=%d", CS_ID, len, 1 + nl + 8 + 1 + 32 + 32 + 64 + 64 + 1); return; }
if (1 + nl + 8 + 1 + 32 + 32 + 64 + 1 + 64 + 8 + 1 > len) { DEBUG_WARN(DEBUG_CATEGORY_CONNECTIVITY, "%s: CHANNEL_INFO_RESP truncated len=%zu", CS_ID, len); return; }
const uint8_t* p = pl + 1;
char name[128]; memcpy(name, p, nl); name[nl] = '\0'; p += nl;
uint64_t owner; memcpy(&owner, p, 8); p += 8;
@ -834,7 +859,12 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer,
const uint8_t* x25519 = p; p += 32;
const uint8_t* ed_pub = p; p += 32;
const uint8_t* ch_sig = p; p += 64;
const uint8_t* inviter_join_sig = p; p += 64;
uint8_t inv_flags = *p++;
const uint8_t* inviter_join_sig = NULL; uint64_t inviter_join_ts = 0;
if (inv_flags & PEERS_FLAG_HAS_JOIN) {
if (p + 72 > pl + len) return; inviter_join_sig = p; p += 64; memcpy(&inviter_join_ts, p, 8); p += 8; }
const uint8_t* inviter_update_sig = p; p += 64; uint64_t inviter_update_ts;
memcpy(&inviter_update_ts, p, 8); p += 8;
uint8_t inv_name_len = *p++;
char inv_name[128] = "";
if (p + inv_name_len <= pl + len) { memcpy(inv_name, p, inv_name_len); inv_name[inv_name_len] = '\0'; }
@ -872,12 +902,14 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer,
ilen += snprintf((char*)ivmsg + ilen, sizeof(ivmsg) - ilen, "%s", ch_id) + 1;
memcpy(ivmsg + ilen, &peer, 8); ilen += 8;
memcpy(ivmsg + ilen, inv_x25519, 32); ilen += 32;
memcpy(ivmsg + ilen, &inviter_update_ts, 8); ilen += 8;
{ const char* nm = inv_name[0] ? inv_name : "";
size_t nl2 = strlen(nm); memcpy(ivmsg + ilen, nm, nl2); ilen += nl2; ivmsg[ilen++] = '\0';
if (cs_ed25519_verify(inv_ed, ivmsg, ilen, inviter_join_sig) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: CHANNEL_INFO_RESP invalid inviter_join_sig peer=%016llx x25519=%016llx inv_ed=%016llx inv_name=%s",
CS_ID, (unsigned long long)peer, *(const uint64_t*)inv_x25519, *(const uint64_t*)inv_ed, nm);
}
size_t nl2 = strlen(nm); (void)nl2; }
if (inviter_join_sig) memcpy(ivmsg + ilen, inviter_join_sig, 64); else memset(ivmsg + ilen, 0, 64);
ilen += 64;
if (cs_ed25519_verify(inv_ed, ivmsg, ilen, inviter_update_sig) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: CHANNEL_INFO_RESP invalid inviter_update_sig peer=%016llx ts=%llu",
CS_ID, (unsigned long long)peer, (unsigned long long)inviter_update_ts);
}
/* save inviter node_info to local DB */
sqlite3* vdb = cs->inst->topo_groups->topo_sqlite_db;
@ -911,17 +943,15 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer,
lk = lk->next;
}
}
topo_node_sqlite_member_put(vdb, ch_id, peer, inviter_join_sig, inviter_join_ts, inviter_update_sig, inviter_update_ts, inv_x25519, inv_ed, inv_name, inviter_join_sig);
topo_node_sqlite_node_update_verified(vdb, peer, inv_name, inv_x25519, inv_ed, inviter_join_ts);
}
/* save inviter as node and member */
topo_node_sqlite_member_put(cs->inst->topo_groups->topo_sqlite_db, ch_id, peer,
inviter_join_sig, inviter_join_sig);
/* generate our own join_sig */
uint64_t myid = cs->inst->node_id;
uint8_t my_x25519[32];
memcpy(my_x25519, cs->inst->my_keys.public_key, 32);
uint8_t join_sig[64];
uint8_t join_sig[64]; uint64_t join_ts;
{
uint8_t msg[256]; size_t mlen = 0;
mlen += snprintf((char*)msg + mlen, sizeof(msg) - mlen, "%s", ch_id) + 1;
@ -929,6 +959,8 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer,
memcpy(msg + mlen, my_x25519, 32); mlen += 32;
{ const char* nm = cs->inst->name[0] ? cs->inst->name : "";
size_t nl = strlen(nm); memcpy(msg + mlen, nm, nl); mlen += nl; msg[mlen++] = '\0'; }
join_ts = (uint64_t)ntp_time_get_seconds(cs->inst);
memcpy(msg + mlen, &join_ts, 8); mlen += 8;
cs_ed25519_sign(cs->inst->my_ed25519_privkey, msg, mlen, join_sig);
}
@ -939,6 +971,7 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer,
memcpy(jbuf + joff, my_x25519, 32); joff += 32;
memcpy(jbuf + joff, cs->inst->my_ed25519_pubkey, 32); joff += 32;
memcpy(jbuf + joff, join_sig, 64); joff += 64;
memcpy(jbuf + joff, &join_ts, 8); joff += 8;
{ const char* nm = cs->inst->name[0] ? cs->inst->name : "";
uint8_t nml = (uint8_t)strlen(nm);
jbuf[joff++] = nml; memcpy(jbuf + joff, nm, nml); joff += nml; }
@ -984,12 +1017,13 @@ static void cs_handle_channel_info_resp(struct chat_sync* cs, uint64_t peer,
static void cs_handle_channel_join(struct chat_sync* cs, uint64_t peer,
const char* ch_id, const uint8_t* pl, size_t len) {
if (len < 8 + 32 + 32 + 64 + 1 + 1) { DEBUG_WARN(DEBUG_CATEGORY_CONNECTIVITY, "%s: CHANNEL_JOIN too short len=%zu peer=%016llx", CS_ID, len, (unsigned long long)peer); return; }
if (len < 8 + 32 + 32 + 64 + 8 + 1 + 1) { DEBUG_WARN(DEBUG_CATEGORY_CONNECTIVITY, "%s: CHANNEL_JOIN too short len=%zu peer=%016llx", CS_ID, len, (unsigned long long)peer); return; }
const uint8_t* p = pl;
uint64_t node_id; memcpy(&node_id, p, 8); p += 8;
const uint8_t* x25519 = p; p += 32;
const uint8_t* ed_pub = p; p += 32;
const uint8_t* join_sig = p; p += 64;
uint64_t join_ts; memcpy(&join_ts, p, 8); p += 8;
uint8_t name_len = *p++;
char joiner_name[128] = "";
if (p + name_len <= pl + len) { memcpy(joiner_name, p, name_len); joiner_name[name_len] = '\0'; p += name_len; }
@ -1002,15 +1036,17 @@ static void cs_handle_channel_join(struct chat_sync* cs, uint64_t peer,
memcpy(vmsg + vlen, x25519, 32); vlen += 32;
{ const char* nm = joiner_name[0] ? joiner_name : "";
size_t nl = strlen(nm); memcpy(vmsg + vlen, nm, nl); vlen += nl; vmsg[vlen++] = '\0'; }
memcpy(vmsg + vlen, &join_ts, 8); vlen += 8;
if (cs_ed25519_verify(ed_pub, vmsg, vlen, join_sig) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: JOIN invalid sig node=0x%016llx ch=%s — signed(node=%016llx x25519=%016llx ed=%016llx name=%s)", CS_ID,
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: JOIN invalid sig node=0x%016llx ch=%s — signed(node=%016llx x25519=%016llx ed=%016llx name=%s ts=%llu)", CS_ID,
(unsigned long long)node_id, ch_id,
(unsigned long long)node_id, *(const uint64_t*)x25519, *(const uint64_t*)ed_pub, joiner_name);
(unsigned long long)node_id, *(const uint64_t*)x25519, *(const uint64_t*)ed_pub, joiner_name, (unsigned long long)join_ts);
return;
}
sqlite3* db = cs->inst->topo_groups->topo_sqlite_db;
topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, NULL);
topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, joiner_name, NULL);
topo_node_sqlite_node_update_verified(db, node_id, joiner_name, x25519, ed_pub, join_ts);
/* save joiner node_info to local DB */
if (db && joiner_name[0]) {
sqlite3_stmt* ns = NULL;
@ -1065,12 +1101,13 @@ static void cs_handle_channel_join(struct chat_sync* cs, uint64_t peer,
memcpy(ubuf + uoff, x25519, 32); uoff += 32;
memcpy(ubuf + uoff, ed_pub, 32); uoff += 32;
memcpy(ubuf + uoff, join_sig, 64); uoff += 64;
memcpy(ubuf + uoff, &join_ts, 8); uoff += 8;
ubuf[uoff++] = name_len;
memcpy(ubuf + uoff, joiner_name, name_len); uoff += name_len;
ubuf[uoff++] = addr_cnt;
size_t addr_data_sz = (size_t)(p - (pl + 8 + 32 + 32 + 64 + 1 + name_len + 1));
size_t addr_data_sz = (size_t)(p - (pl + 8 + 32 + 32 + 64 + 8 + 1 + name_len + 1));
if (uoff + addr_data_sz <= sizeof(ubuf)) {
memcpy(ubuf + uoff, pl + 8 + 32 + 32 + 64 + 1 + name_len + 1, addr_data_sz);
memcpy(ubuf + uoff, pl + 8 + 32 + 32 + 64 + 8 + 1 + name_len + 1, addr_data_sz);
uoff += addr_data_sz;
}
cs_propagate(cs, ch_id, peer, ubuf, uoff);
@ -1107,14 +1144,40 @@ static void cs_handle_welcome(struct chat_sync* cs, uint64_t peer,
const uint8_t* p = pl;
uint16_t pc; memcpy(&pc, p, 2); p += 2;
for (uint16_t i = 0; i < pc; i++) {
if ((size_t)(p - pl) + 8 + 32 + 32 + 64 + 1 > len) break;
if ((size_t)(p - pl) + 8 + 32 + 32 + 64 + 8 + 1 + 1 > len) break;
uint64_t node_id; memcpy(&node_id, p, 8); p += 8;
const uint8_t* x25519 = p; p += 32;
const uint8_t* ed_pub = p; p += 32;
const uint8_t* join_sig = p; p += 64;
uint64_t join_ts; memcpy(&join_ts, p, 8); p += 8;
uint8_t nl = *p++;
char peer_name[256] = "";
if (nl && p + nl <= pl + len) { memcpy(peer_name, p, nl); peer_name[nl] = '\0'; p += nl; }
uint8_t ac = *p++;
topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, NULL);
/* verify join_sig — all fields present in WELCOME wire */
uint8_t vmsg[256]; size_t vlen = 0;
vlen += snprintf((char*)vmsg + vlen, sizeof(vmsg) - vlen, "%s", ch_id) + 1;
memcpy(vmsg + vlen, &node_id, 8); vlen += 8;
memcpy(vmsg + vlen, x25519, 32); vlen += 32;
{ const char* nm = peer_name[0] ? peer_name : "";
size_t nls = strlen(nm); memcpy(vmsg + vlen, nm, nls); vlen += nls; vmsg[vlen++] = '\0'; }
memcpy(vmsg + vlen, &join_ts, 8); vlen += 8;
{ EVP_PKEY* pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, ed_pub, 32);
if (pkey) {
EVP_MD_CTX* ver = EVP_MD_CTX_new();
if (ver) {
if (EVP_DigestVerifyInit(ver, NULL, NULL, NULL, pkey) != 1
|| EVP_DigestVerify(ver, join_sig, 64, vmsg, vlen) != 1)
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: WELCOME invalid join_sig node=0x%016llx", CS_ID, (unsigned long long)node_id);
EVP_MD_CTX_free(ver);
}
EVP_PKEY_free(pkey);
}
}
topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, peer_name, NULL);
topo_node_sqlite_node_update_verified(db, node_id, peer_name, x25519, ed_pub, join_ts);
for (uint8_t j = 0; j < ac; j++) {
if (p + 1 > pl + len) break;
@ -1157,12 +1220,13 @@ static void cs_handle_welcome(struct chat_sync* cs, uint64_t peer,
static void cs_handle_peer_upsert(struct chat_sync* cs, uint64_t peer,
const char* ch_id, const uint8_t* pl, size_t len) {
if (len < 8 + 32 + 32 + 64 + 1 + 1 + 1) { DEBUG_WARN(DEBUG_CATEGORY_CONNECTIVITY, "%s: PEER_UPSERT too short len=%zu", CS_ID, len); return; }
if (len < 8 + 32 + 32 + 64 + 8 + 1 + 1 + 1) { DEBUG_WARN(DEBUG_CATEGORY_CONNECTIVITY, "%s: PEER_UPSERT too short len=%zu", CS_ID, len); return; }
const uint8_t* p = pl;
uint64_t node_id; memcpy(&node_id, p, 8); p += 8;
const uint8_t* x25519 = p; p += 32;
const uint8_t* ed_pub = p; p += 32;
const uint8_t* join_sig = p; p += 64;
uint64_t join_ts; memcpy(&join_ts, p, 8); p += 8;
uint8_t name_len = *p++;
char peer_name[128] = "";
if (p + name_len <= pl + len) { memcpy(peer_name, p, name_len); peer_name[name_len] = '\0'; p += name_len; }
@ -1175,6 +1239,7 @@ static void cs_handle_peer_upsert(struct chat_sync* cs, uint64_t peer,
memcpy(vmsg + vlen, x25519, 32); vlen += 32;
{ const char* nm = peer_name[0] ? peer_name : "";
size_t nl = strlen(nm); memcpy(vmsg + vlen, nm, nl); vlen += nl; vmsg[vlen++] = '\0'; }
memcpy(vmsg + vlen, &join_ts, 8); vlen += 8;
if (cs_ed25519_verify(ed_pub, vmsg, vlen, join_sig) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: PEER_UPSERT invalid sig node=0x%016llx", CS_ID,
(unsigned long long)node_id);
@ -1183,7 +1248,8 @@ static void cs_handle_peer_upsert(struct chat_sync* cs, uint64_t peer,
sqlite3* db = cs->inst->topo_groups->topo_sqlite_db;
topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, NULL);
topo_node_sqlite_member_put(db, ch_id, node_id, join_sig, join_ts, NULL, 0, x25519, ed_pub, peer_name, NULL);
topo_node_sqlite_node_update_verified(db, node_id, peer_name, x25519, ed_pub, join_ts);
if (db && peer_name[0]) {
sqlite3_stmt* ns = NULL;
sqlite3_prepare_v2(db, "INSERT OR REPLACE INTO nodes(node_id,name,x25519_pubkey,ed25519_pubkey) VALUES(?,?,?,?)", -1, &ns, NULL);

145
tools/chatgui/transport/member_sync.c

@ -9,6 +9,7 @@
#include <string.h>
#include <stdlib.h>
#include <sqlite3.h>
#include <openssl/evp.h>
#define MS_ID "member_sync"
@ -44,8 +45,10 @@ static int _addr_cmp(const void* a, const void* b) {
}
static void _compute_member_hash(uint64_t node_id, const uint8_t* x25519,
const uint8_t* ed25519, const uint8_t* join_sig,
const uint8_t* addrs_data, int addr_count, int online,
const uint8_t* ed25519,
const uint8_t* join_sig, uint64_t join_ts,
const uint8_t* update_sig, uint64_t update_ts,
const uint8_t* addrs_data, int addr_count,
uint8_t hash_out[MT_HASH_SIZE]) {
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
EVP_DigestInit_ex(ctx, EVP_sha256(), NULL);
@ -53,6 +56,9 @@ static void _compute_member_hash(uint64_t node_id, const uint8_t* x25519,
EVP_DigestUpdate(ctx, x25519, 32);
EVP_DigestUpdate(ctx, ed25519, 32);
EVP_DigestUpdate(ctx, join_sig, 64);
EVP_DigestUpdate(ctx, &join_ts, 8);
if (update_sig) EVP_DigestUpdate(ctx, update_sig, 64); else { static const uint8_t z[64]; EVP_DigestUpdate(ctx, z, 64); }
EVP_DigestUpdate(ctx, &update_ts, 8);
uint8_t ac = (uint8_t)addr_count;
EVP_DigestUpdate(ctx, &ac, 1);
@ -75,8 +81,6 @@ static void _compute_member_hash(uint64_t node_id, const uint8_t* x25519,
}
}
uint8_t ol = (uint8_t)(online ? 1 : 0);
EVP_DigestUpdate(ctx, &ol, 1);
EVP_DigestFinal_ex(ctx, hash_out, NULL);
EVP_MD_CTX_free(ctx);
}
@ -93,9 +97,10 @@ static int _member_update_bucket_hash(void* ctx, const char* ns, uint8_t level,
uint64_t mask = (mask_shift >= 0) ? (~0ULL << mask_shift) : UINT64_MAX;
char sql[512]; snprintf(sql, sizeof(sql),
"SELECT p.node_id, n.x25519_pubkey, n.ed25519_pubkey, p.join_sig, n.online"
" FROM \"%s\" p JOIN nodes n ON p.node_id=n.node_id"
" WHERE (p.node_id & %lld) == %lld ORDER BY p.node_id ASC",
"SELECT node_id, x25519_pubkey, ed25519_pubkey,"
" join_sig, join_ts, update_sig, update_ts"
" FROM \"%s\""
" WHERE (node_id & %lld) == %lld ORDER BY node_id ASC",
peers_tbl, (long long)mask, (long long)prefix64);
sqlite3_stmt* stmt = NULL;
@ -107,10 +112,12 @@ static int _member_update_bucket_hash(void* ctx, const char* ns, uint8_t level,
const uint8_t* x25 = (const uint8_t*)sqlite3_column_blob(stmt, 1);
const uint8_t* ed = (const uint8_t*)sqlite3_column_blob(stmt, 2);
const uint8_t* sig = (const uint8_t*)sqlite3_column_blob(stmt, 3);
int online = sqlite3_column_int(stmt, 4);
if (!x25 || !ed || !sig) continue;
uint64_t jts = (uint64_t)sqlite3_column_int64(stmt, 4);
const uint8_t* usig = (const uint8_t*)sqlite3_column_blob(stmt, 5);
uint64_t uts = (uint64_t)sqlite3_column_int64(stmt, 6);
if (!x25 || !ed) continue;
uint8_t mh[MT_HASH_SIZE];
_compute_member_hash(nid, x25, ed, sig, NULL, 0, online, mh);
_compute_member_hash(nid, x25, ed, sig, jts, usig, uts, NULL, 0, mh);
EVP_DigestUpdate(sha_ctx, mh, MT_HASH_SIZE);
count++;
}
@ -129,9 +136,10 @@ static int _member_get_items(void* ctx, const char* ns, uint8_t level,
uint64_t mask = (mask_shift >= 0) ? (~0ULL << mask_shift) : UINT64_MAX;
char sql[512]; snprintf(sql, sizeof(sql),
"SELECT p.node_id, n.x25519_pubkey, n.ed25519_pubkey, p.join_sig, n.online"
" FROM \"%s\" p JOIN nodes n ON p.node_id=n.node_id"
" WHERE (p.node_id & %lld) == %lld ORDER BY p.node_id ASC",
"SELECT node_id, x25519_pubkey, ed25519_pubkey,"
" join_sig, join_ts, update_sig, update_ts, name"
" FROM \"%s\""
" WHERE (node_id & %lld) == %lld ORDER BY node_id ASC",
peers_tbl, (long long)mask, (long long)prefix);
sqlite3_stmt* stmt = NULL;
@ -146,8 +154,12 @@ static int _member_get_items(void* ctx, const char* ns, uint8_t level,
const uint8_t* x25 = (const uint8_t*)sqlite3_column_blob(stmt, 1);
const uint8_t* ed = (const uint8_t*)sqlite3_column_blob(stmt, 2);
const uint8_t* sig = (const uint8_t*)sqlite3_column_blob(stmt, 3);
int online = sqlite3_column_int(stmt, 4);
if (!x25 || !ed || !sig) continue;
uint64_t jts = (uint64_t)sqlite3_column_int64(stmt, 4);
const uint8_t* usig = (const uint8_t*)sqlite3_column_blob(stmt, 5);
uint64_t uts = (uint64_t)sqlite3_column_int64(stmt, 6);
const char* nm = (const char*)sqlite3_column_text(stmt, 7);
if (!x25 || !ed) continue;
uint8_t nl = nm ? (uint8_t)strnlen(nm, 255) : 0;
sqlite3_stmt* as = NULL;
sqlite3_prepare_v2(db,
@ -170,13 +182,26 @@ static int _member_get_items(void* ctx, const char* ns, uint8_t level,
sqlite3_finalize(as);
}
size_t need = 8 + 32 + 32 + 64 + 1 + 1 + (size_t)addr_off;
uint8_t flags = (sig && jts) ? PEERS_FLAG_HAS_JOIN : 0;
size_t need = 8 + 32 + 32 + 1 + (flags ? 72ULL : 0ULL) + 64 + 8 + 1 + (size_t)nl + 1 + (size_t)addr_off;
if (off + need > *len) { sqlite3_finalize(stmt); return -2; }
memcpy(buf + off, &nid, 8); off += 8;
memcpy(buf + off, x25, 32); off += 32;
memcpy(buf + off, ed, 32); off += 32;
memcpy(buf + off, sig, 64); off += 64;
buf[off++] = (uint8_t)(online ? 1 : 0);
buf[off++] = flags;
if (flags & PEERS_FLAG_HAS_JOIN) {
memcpy(buf + off, sig, 64); off += 64;
memcpy(buf + off, &jts, 8); off += 8;
}
if (usig && uts) {
memcpy(buf + off, usig, 64); off += 64;
memcpy(buf + off, &uts, 8); off += 8;
} else {
memset(buf + off, 0, 64); off += 64;
uint64_t z = 0; memcpy(buf + off, &z, 8); off += 8;
}
buf[off++] = nl;
if (nl) { memcpy(buf + off, nm, nl); off += nl; }
buf[off++] = (uint8_t)addr_count;
memcpy(buf + off, addrs, (size_t)addr_off); off += (size_t)addr_off;
(*cnt)++;
@ -194,12 +219,24 @@ static int _member_apply_items(void* ctx, const char* ns,
uint16_t count; memcpy(&count, data, 2);
const uint8_t* mp = data + 2; size_t mrem = len - 2;
for (uint16_t i = 0; i < count && mrem >= 137; i++) {
for (uint16_t i = 0; i < count && mrem >= 148; i++) {
uint64_t nid; memcpy(&nid, mp, 8); mp += 8; mrem -= 8;
const uint8_t* x25 = mp; mp += 32; mrem -= 32;
const uint8_t* ed = mp; mp += 32; mrem -= 32;
const uint8_t* sig = mp; mp += 64; mrem -= 64;
uint8_t online = *mp++; mrem--;
uint8_t flags = *mp++; mrem--;
const uint8_t* jsig = NULL; uint64_t jts = 0;
if (flags & PEERS_FLAG_HAS_JOIN) {
if (mrem < 72) break; jsig = mp; mp += 64; mrem -= 64; memcpy(&jts, mp, 8); mp += 8; mrem -= 8;
} else {
/* lookup join from local DB */
uint8_t buf_jsig[64]; uint64_t buf_jts;
if (topo_node_sqlite_member_get_join(_db(inst), ns, nid, buf_jsig, &buf_jts) == 0) { jsig = buf_jsig; jts = buf_jts; }
}
if (mrem < 72) break;
const uint8_t* usig = mp; mp += 64; mrem -= 64; uint64_t uts; memcpy(&uts, mp, 8); mp += 8; mrem -= 8;
uint8_t nl = *mp++; mrem--;
char nm[256] = "";
if (nl && mrem >= nl) { memcpy(nm, mp, nl); nm[nl] = '\0'; mp += nl; mrem -= nl; }
uint8_t ac = *mp++; mrem--;
const uint8_t* addrs = mp;
int consumed = 0;
@ -208,8 +245,30 @@ static int _member_apply_items(void* ctx, const char* ns,
int sz = fam == 4 ? 4 : 16;
consumed += sz + 2;
}
member_sync_put(inst, ns, nid, x25, ed, sig, addrs, (int)ac);
if (online) member_sync_set_online(inst, nid, 1);
/* verify update_sig if available */
if (usig && uts && jsig && ed) {
uint8_t vmsg[256]; size_t vlen = 0;
vlen += snprintf((char*)vmsg + vlen, sizeof(vmsg) - vlen, "%s", ns) + 1;
memcpy(vmsg + vlen, &nid, 8); vlen += 8;
memcpy(vmsg + vlen, x25, 32); vlen += 32;
memcpy(vmsg + vlen, &uts, 8); vlen += 8;
memcpy(vmsg + vlen, jsig, 64); vlen += 64;
{ EVP_PKEY* pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, ed, 32);
if (pkey) {
EVP_MD_CTX* ver = EVP_MD_CTX_new();
if (ver) {
int ok = (EVP_DigestVerifyInit(ver, NULL, NULL, NULL, pkey) == 1)
&& (EVP_DigestVerify(ver, usig, 64, vmsg, vlen) == 1);
if (!ok)
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: member_sync invalid update_sig node=0x%016llx ns=%s", MS_ID,
(unsigned long long)nid, ns);
EVP_MD_CTX_free(ver);
}
EVP_PKEY_free(pkey);
}
}
}
member_sync_put(inst, ns, nid, x25, ed, jsig, jts, usig, uts, nm, addrs, (int)ac);
mp += consumed; mrem -= (size_t)consumed;
}
return 0;
@ -278,27 +337,17 @@ void member_sync_cancel(struct UTUN_INSTANCE* inst, uint64_t peer, const char* c
int member_sync_put(struct UTUN_INSTANCE* inst, const char* ch_id,
uint64_t member_id, const uint8_t* x25519,
const uint8_t* ed25519, const uint8_t* join_sig,
const uint8_t* ed25519,
const uint8_t* join_sig, uint64_t join_ts,
const uint8_t* update_sig, uint64_t update_ts,
const char* name,
const uint8_t* addrs_data, int addr_count) {
if (!inst || !ch_id) return -1;
sqlite3* db = _db(inst); if (!db) return -1;
sqlite3_stmt* ns = NULL;
sqlite3_prepare_v2(db,
"INSERT OR REPLACE INTO nodes(node_id, name, x25519_pubkey, ed25519_pubkey, online)"
" VALUES(?,COALESCE((SELECT name FROM nodes WHERE node_id=?),''),?,?,"
" COALESCE((SELECT online FROM nodes WHERE node_id=?),0))",
-1, &ns, NULL);
if (ns) {
sqlite3_bind_int64(ns, 1, (sqlite3_int64)member_id);
sqlite3_bind_int64(ns, 2, (sqlite3_int64)member_id);
sqlite3_bind_blob(ns, 3, x25519, 32, SQLITE_STATIC);
sqlite3_bind_blob(ns, 4, ed25519, 32, SQLITE_STATIC);
sqlite3_bind_int64(ns, 5, (sqlite3_int64)member_id);
sqlite3_step(ns); sqlite3_finalize(ns);
}
topo_node_sqlite_member_put(db, ch_id, member_id, join_sig, NULL);
topo_node_sqlite_member_put(db, ch_id, member_id,
join_sig, join_ts, update_sig, update_ts,
x25519, ed25519, name, NULL);
if (addrs_data && addr_count > 0) {
sqlite3_exec(db, "BEGIN", NULL, NULL, NULL);
@ -356,22 +405,6 @@ void member_sync_set_online(struct UTUN_INSTANCE* inst, uint64_t node_id, int on
if (!inst) return;
sqlite3* db = _db(inst); if (!db) return;
topo_node_sqlite_node_set_online(db, node_id, online);
sqlite3_stmt* stmt = NULL;
if (sqlite3_prepare_v2(db, "SELECT channel_id FROM channels", -1, &stmt, NULL) != SQLITE_OK) return;
while (sqlite3_step(stmt) == SQLITE_ROW) {
const char* ch_id = (const char*)sqlite3_column_text(stmt, 0);
if (!ch_id) continue;
char peers_tbl[128]; _peers_table(ch_id, peers_tbl, sizeof(peers_tbl));
char buf[256]; snprintf(buf, sizeof(buf), "SELECT 1 FROM \"%s\" WHERE node_id=?", peers_tbl);
sqlite3_stmt* cs = NULL;
if (sqlite3_prepare_v2(db, buf, -1, &cs, NULL) == SQLITE_OK) {
sqlite3_bind_int64(cs, 1, (sqlite3_int64)node_id);
if (sqlite3_step(cs) == SQLITE_ROW)
merkle_sync_recompute_path(inst, ch_id, node_id);
sqlite3_finalize(cs);
}
}
sqlite3_finalize(stmt);
}
const uint8_t* member_sync_get_hash(struct UTUN_INSTANCE* inst, const char* ch_id,

21
tools/chatgui/transport/member_sync.h

@ -3,7 +3,7 @@
*
* Тонкая прослойка над merkle_sync, адаптированная под мемберов каналов.
* Данные хранятся в таблицах: nodes, node_addresses, peers_<channel_id>.
* Хеш мембера = SHA256(node_id || x25519 || ed25519 || join_sig || addrs || online).
* Хеш мембера = SHA256(node_id || x25519 || ed25519 || join_sig || join_ts || addrs).
*
* ── Использование ──
*
@ -20,13 +20,12 @@
* }
*
* // добавление/обновление мембера (из chat_core_create_channel, cs_handle_welcome)
* member_sync_put(inst, ch_id, node_id, x25519, ed25519, join_sig, addrs, ac);
* member_sync_put(inst, ch_id, node_id, x25519, ed25519, join_sig, join_ts, addrs, ac);
* // дерево автоматически пересчитано
*
* // онлайн-статус (из cs_on_conn_up / cs_on_conn_down)
* member_sync_set_online(inst, node_id, 1); // online
* member_sync_set_online(inst, node_id, 0); // offline
* // пересчитывает дерево для всех каналов, где состоит node_id
*
* // удаление мембера
* member_sync_del(inst, ch_id, node_id);
@ -85,19 +84,24 @@ void member_sync_cancel(struct UTUN_INSTANCE* inst, uint64_t peer, const char* c
* x25519 — X25519 публичный ключ (32 байта)
* ed25519 — Ed25519 публичный ключ (32 байта)
* join_sig — Ed25519 подпись join-сообщения (64 байта)
* сообщение: ch_id || '\0' || node_id(8LE) || x25519(32) || name || '\0'
* сообщение: ch_id || '\0' || node_id(8LE) || x25519(32) || name || '\0' || join_ts(8LE)
* join_ts — NTP-синхронизированное время подписи (unix epoch seconds)
* name — имя узла (из подписанного сообщения)
* addrs_data — адреса в wire-формате: [family:1][ip:4|16][port:2]...
* addr_count — количество адресов (0 если нет)
*
* Пишет в таблицы: nodes (INSERT OR REPLACE), node_addresses,
* peers_<ch_id> (INSERT OR REPLACE).
* Затем вызывает merkle_sync_recompute_path() для пересчёта дерева.
* Пишет только в таблицу peers_<ch_id> (INSERT OR REPLACE).
* nodes обновляется отдельно через topo_node_sqlite_node_update_verified().
* Вызывает merkle_sync_recompute_path() для пересчёта дерева.
*
* Возвращает 0 при успехе, -1 при ошибке.
*/
int member_sync_put(struct UTUN_INSTANCE* inst, const char* ch_id,
uint64_t member_id, const uint8_t* x25519,
const uint8_t* ed25519, const uint8_t* join_sig,
const uint8_t* ed25519,
const uint8_t* join_sig, uint64_t join_ts,
const uint8_t* update_sig, uint64_t update_ts,
const char* name,
const uint8_t* addrs_data, int addr_count);
/* Удалить мембера из канала и пересчитать дерево. */
@ -108,7 +112,6 @@ int member_sync_count(struct UTUN_INSTANCE* inst, const char* ch_id);
/*
* Установить онлайн-статус узла (nodes.online = 0/1).
* Пересчитывает дерево для всех каналов, где состоит node_id.
*/
void member_sync_set_online(struct UTUN_INSTANCE* inst, uint64_t node_id, int online);

4
tools/chatgui/transport/merkle_sync.h

@ -120,7 +120,7 @@ struct merkle_sync_data_ops {
* *len — [in] размер буфера, [out] записанный размер
*
* Wire-формат для мемберов:
* [count:2][node_id:8][x25519:32][ed25519:32][join_sig:64][online:1][addr_cnt:1][addrs:var]...
* [count:2][node_id:8][x25519:32][ed25519:32][flags:1]([join_sig:64][join_ts:8])[update_sig:64][update_ts:8][name_len:1][name:var][addr_cnt:1][addrs:var]...
*
* Возвращает 0 при успехе, <0 при ошибке, -2 если буфер мал.
*/
@ -207,7 +207,7 @@ void merkle_sync_cancel(struct UTUN_INSTANCE* inst, uint64_t peer, const char* n
* Пересчитать Merkle-дерево для ключа key — все уровни от 1 до 5.
* Вызывается после изменения данных: merkle_sync сам не знает,
* когда данные изменились — потребитель должен вызвать явно.
* member_sync делает это внутри put/del/set_online.
* member_sync делает это внутри put/del.
*/
void merkle_sync_recompute_path(struct UTUN_INSTANCE* inst, const char* ns, uint64_t key);

Loading…
Cancel
Save