// E2E Interop Server: C++ DHT server that listens for incoming connections.
//
// Usage: ./test_interop_server_bin <bootstrap_host:port,...>
//
// Flow:
// 1. Init libuv + libudx + udx_socket
// 2. Init DhtRpc, bootstrap to provided nodes
// 3. Init HyperDht + DhtConnector
// 4. Listen (announce on BLAKE2b(own_pk))
// 5. Print public key to stdout (JS test reads it)
// 6. On incoming handshake → complete → holepunch/LAN → connected
// 7. Exit 0 success / 1 failure
//
// This tests the path: JS client → JS relay → C++ server
// which is NOT tested by test_interop.js (that tests JS server → C++ client).
#include "compact_encoding.h"
#include "dht_connector.h"
#include "dht_messages.h"
#include "dht_rpc.h"
#include "holepuncher.h"
#include "hyperdht.h"
#include "noise_ik.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sodium.h>
extern "C" {
#include <udx.h>
#include <uv.h>
}
// ---- Global state ----
static uv_loop_t g_loop;
static udx_t g_udx;
static udx_socket_t g_socket;
static uv_timer_t g_tick_timer;
static uv_timer_t g_timeout_timer;
static DhtRpc *g_rpc = nullptr;
static HyperDht *g_dht = nullptr;
static DhtConnector *g_connector = nullptr;
static bool g_bootstrapped = false;
static bool g_connected = false;
static bool g_handshake_received = false;
static bool g_handshake_complete = false;
static int g_exit_code = 1;
// ---- Callbacks ----
static void on_socket_recv(udx_socket_t *socket, ssize_t read_len,
const uv_buf_t *buf, const struct sockaddr *from) {
if (read_len <= 0) return;
const uint8_t *data = (const uint8_t *)buf->base;
size_t len = (size_t)read_len;
// DHT-RPC messages.
if (g_rpc && len >= 4) {
uint8_t type_byte = data[0];
if (type_byte == DHT_REQUEST_ID || type_byte == DHT_RESPONSE_ID) {
g_rpc->on_message(data, len, (const struct sockaddr_in *)from);
return;
}
}
// Holepunch probes.
if (g_connector && len == 1 && data[0] == 0x00) {
const struct sockaddr_in *from_in = (const struct sockaddr_in *)from;
IPv4Address addr;
memcpy(addr.ip, &from_in->sin_addr.s_addr, 4);
addr.port = ntohs(from_in->sin_port);
g_connector->on_probe_received(addr);
}
}
static void on_tick(uv_timer_t *handle) {
if (!g_rpc) return;
uint64_t now = uv_now(&g_loop);
g_rpc->tick(now);
if (g_dht) g_dht->tick(now);
if (g_connector) g_connector->tick(now);
}
static void on_timeout(uv_timer_t *handle) {
fprintf(stderr, "TIMEOUT — no incoming connection established\n");
fprintf(stderr, " bootstrapped=%d handshake_received=%d handshake_complete=%d connected=%d\n",
g_bootstrapped, g_handshake_received, g_handshake_complete, g_connected);
g_exit_code = 1;
uv_stop(&g_loop);
}
static void cleanup_and_exit() {
uv_timer_stop(&g_tick_timer);
uv_timer_stop(&g_timeout_timer);
uv_stop(&g_loop);
}
// ---- Main ----
int main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <bootstrap_host:port,...>\n", argv[0]);
return 1;
}
if (sodium_init() < 0) {
fprintf(stderr, "sodium_init failed\n");
return 1;
}
// Parse comma-separated bootstrap nodes.
char bootstrap_buf[512];
strncpy(bootstrap_buf, argv[1], sizeof(bootstrap_buf) - 1);
bootstrap_buf[sizeof(bootstrap_buf) - 1] = '\0';
const char *boot_ptrs[8];
int boot_count = 0;
char *tok = strtok(bootstrap_buf, ",");
while (tok && boot_count < 8) {
boot_ptrs[boot_count++] = tok;
tok = strtok(nullptr, ",");
}
printf("bootstrap: %s (%d nodes)\n", argv[1], boot_count);
// Init libuv.
uv_loop_init(&g_loop);
udx_init(&g_loop, &g_udx, nullptr);
// Init socket.
udx_socket_init(&g_udx, &g_socket, nullptr);
g_socket.data = nullptr;
struct sockaddr_in bind_addr;
uv_ip4_addr("0.0.0.0", 0, &bind_addr);
udx_socket_bind(&g_socket, (struct sockaddr *)&bind_addr, 0);
struct sockaddr_in local_addr;
int addr_len = sizeof(local_addr);
udx_socket_getsockname(&g_socket, (struct sockaddr *)&local_addr, &addr_len);
int port = ntohs(local_addr.sin_port);
printf("bound to port %d\n", port);
// Start recv.
udx_socket_recv_start(&g_socket, on_socket_recv);
// Timer.
uv_timer_init(&g_loop, &g_tick_timer);
uv_timer_start(&g_tick_timer, on_tick, 0, 1);
// Timeout (15s).
uv_timer_init(&g_loop, &g_timeout_timer);
uv_timer_start(&g_timeout_timer, on_timeout, 15000, 0);
// Generate our Ed25519 keypair.
Ed25519KeyPair kp;
crypto_sign_keypair(kp.public_key, kp.secret_key);
// Print public key to stdout (JS test reads this).
printf("PUBLIC_KEY:");
for (int i = 0; i < 32; i++) printf("%02x", kp.public_key[i]);
printf("\n");
fflush(stdout);
// Init DHT-RPC.
g_rpc = new DhtRpc();
g_rpc->init(&g_socket, &g_loop);
g_rpc->set_bootstrap(boot_ptrs, boot_count);
IPv4Address local;
local.ip[0] = 127; local.ip[1] = 0; local.ip[2] = 0; local.ip[3] = 1;
local.port = port;
g_rpc->set_local_address(local);
// Collect real interface addresses so handshake payload includes LAN IPs.
uv_interface_address_t *ifaces = nullptr;
int iface_count = 0;
uv_interface_addresses(&ifaces, &iface_count);
IPv4Address lan_addrs[8];
int lan_count = 0;
if (ifaces) {
for (int i = 0; i < iface_count && lan_count < 8; i++) {
if (ifaces[i].is_internal) continue;
if (ifaces[i].address.address4.sin_family != AF_INET) continue;
memcpy(lan_addrs[lan_count].ip, &ifaces[i].address.address4.sin_addr.s_addr, 4);
lan_addrs[lan_count].port = port;
lan_count++;
}
uv_free_interface_addresses(ifaces, iface_count);
}
if (lan_count == 0) { lan_addrs[0] = local; lan_count = 1; }
g_rpc->set_interface_addresses(lan_addrs, lan_count);
printf("interface addresses: %d (first: %d.%d.%d.%d:%d)\n", lan_count,
lan_addrs[0].ip[0], lan_addrs[0].ip[1], lan_addrs[0].ip[2], lan_addrs[0].ip[3], lan_addrs[0].port);
// Init HyperDht.
g_dht = new HyperDht();
g_dht->init(g_rpc, kp);
// Init DhtConnector.
g_connector = new DhtConnector();
g_connector->init(g_dht, &g_socket, &g_loop);
g_connector->set_local_addresses(lan_addrs, lan_count);
// On connected (incoming connection completed).
g_connector->on_connected = [](uint32_t conn_id, const DhtConnection &conn) {
printf("CONNECTED! incoming connection completed (conn_id=%u)\n", conn_id);
printf(" remote pk: ");
for (int i = 0; i < 8; i++) printf("%02x", conn.remote_public_key[i]);
printf("...\n");
printf(" punched address: %d.%d.%d.%d:%d\n",
conn.punched_address.ip[0], conn.punched_address.ip[1],
conn.punched_address.ip[2], conn.punched_address.ip[3],
conn.punched_address.port);
g_connected = true;
g_exit_code = 0;
cleanup_and_exit();
};
g_connector->on_failed = [](uint32_t conn_id, const uint8_t *remote_pk, const char *error) {
printf("connection failed: conn_id=%u error=%s\n", conn_id, error);
};
// Debug: track handshake request (incoming FROM_RELAY).
auto orig_hs_request = g_dht->on_handshake_request;
g_dht->on_handshake_request = [orig_hs_request](uint32_t hs_id, const uint8_t *remote_pk,
const dht_msg::NoisePayload &payload) {
g_handshake_received = true;
printf("HANDSHAKE REQUEST received! hs_id=%u\n", hs_id);
printf(" remote pk: ");
for (int i = 0; i < 8; i++) printf("%02x", remote_pk[i]);
printf("...\n");
printf(" payload: version=%u firewall=%u has_udx=%d\n",
payload.version, payload.firewall, payload.has_udx ? 1 : 0);
if (orig_hs_request) orig_hs_request(hs_id, remote_pk, payload);
};
// Debug: track handshake complete.
auto orig_hs_complete = g_dht->on_handshake_complete;
g_dht->on_handshake_complete = [orig_hs_complete](uint32_t hs_id, const DhtHandshake &hs) {
g_handshake_complete = true;
printf("HANDSHAKE COMPLETE! hs_id=%u is_initiator=%d\n", hs_id, hs.is_initiator ? 1 : 0);
printf(" has_server_address=%d server=%d.%d.%d.%d:%d\n",
hs.has_server_address,
hs.server_address.ip[0], hs.server_address.ip[1],
hs.server_address.ip[2], hs.server_address.ip[3], hs.server_address.port);
printf(" has_client_address=%d client=%d.%d.%d.%d:%d\n",
hs.has_client_address,
hs.client_address.ip[0], hs.client_address.ip[1],
hs.client_address.ip[2], hs.client_address.ip[3], hs.client_address.port);
if (orig_hs_complete) orig_hs_complete(hs_id, hs);
};
// Bootstrap callback — start listening after bootstrap.
g_rpc->on_bootstrapped = [&]() {
g_bootstrapped = true;
printf("bootstrap complete (table has %d nodes)\n", g_rpc->table().size());
printf("firewall: %u\n", g_rpc->local_firewall());
// Listen: announce on BLAKE2b(own_pk) so find_peer can discover us.
printf("listening on DHT...\n");
g_connector->listen();
printf("READY\n");
fflush(stdout);
};
// Bootstrap.
printf("bootstrapping...\n");
g_rpc->bootstrap();
// Run event loop.
uv_run(&g_loop, UV_RUN_DEFAULT);
// Cleanup.
delete g_connector;
delete g_dht;
delete g_rpc;
uv_close((uv_handle_t *)&g_tick_timer, nullptr);
uv_close((uv_handle_t *)&g_timeout_timer, nullptr);
udx_socket_close(&g_socket);
uv_run(&g_loop, UV_RUN_DEFAULT);
uv_loop_close(&g_loop);
printf("exit code: %d\n", g_exit_code);
return g_exit_code;
}