OverviewFilesCommitsIssuesDocsReleasesPeers

hyperswarm-peer / test / test_handshake_dedup.cpp

200 lines · cpp ref:
// Gate: a REPEATED inbound handshake gets the SAME reply, not a second connection.
//
// hyperdht's server keys every inbound PEER_HANDSHAKE by its Noise message bytes
// (server.js _onpeerhandshake, k = hex(noise)) and answers a repeat — a retried request,
// or the same handshake arriving by a second path — with the cached reply of the first
// (`_connects.get(k)`). Ours allocated a new handshake per copy: a new Noise reply, a new
// udx stream id, a second connection that then "replaced" the first, leaving the client
// bound to a stream we had destroyed. keetd's M3d runs saw it on the public DHT (each JS
// dial arriving twice; header_recv=0 on every stream).
//
//   1  the same handshake twice, direct, same tid: ONE handshake, the listener told ONCE,
//      and the two reply packets byte-identical
//   2  the same handshake again, now relayed (FROM_RELAY) by another node: still one
//      handshake, still told once, and a reply goes to that relay
//   3  a DIFFERENT handshake: a new one is made (dedup must not swallow everything)
#include <cstdio>
#include <cstring>
#include <vector>

#include <arpa/inet.h>
#include <netinet/in.h>
#include <poll.h>
#include <sys/socket.h>
#include <unistd.h>

#include <sodium.h>

#include "dht_messages.h"
#include "dht_rpc.h"
#include "hyperdht.h"
#include "noise_ik.h"

extern "C" {
#include <udx.h>
#include <uv.h>
}

static int passed = 0, failed = 0;
static void check (bool ok, const char *what) { (ok ? passed : failed)++; printf("  %s %s\n", ok ? "PASS" : "FAIL", what); }

// a plain UDP socket standing in for the client (or a relay): where replies land
struct Catcher {
	int fd;
	uint16_t port;
	Catcher () {
		fd = socket(AF_INET, SOCK_DGRAM, 0);
		sockaddr_in a{};
		a.sin_family = AF_INET;
		a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
		bind(fd, (sockaddr *)&a, sizeof a);
		socklen_t l = sizeof a;
		getsockname(fd, (sockaddr *)&a, &l);
		port = ntohs(a.sin_port);
	}
	~Catcher () { close(fd); }
	// pump the loop until a packet arrives (or ~1s)
	std::vector<uint8_t> next (uv_loop_t *loop) {
		for (int i = 0; i < 100; i++) {
			uv_run(loop, UV_RUN_NOWAIT);
			pollfd p{ fd, POLLIN, 0 };
			if (poll(&p, 1, 10) > 0) {
				uint8_t b[2048];
				const ssize_t n = recv(fd, b, sizeof b, 0);
				if (n > 0) return std::vector<uint8_t>(b, b + n);
			}
		}
		return {};
	}
};

static std::vector<uint8_t> encode_hs (const HandshakeMessage &h) {
	EncodeState s = { 0, 0, nullptr };
	dht_msg::handshake_preencode(s, h);
	std::vector<uint8_t> b(s.end);
	s.buffer = b.data();
	s.start = 0;
	dht_msg::handshake_encode(s, h);
	return b;
}

int main () {
	if (sodium_init() < 0) return 2;
	uv_loop_t loop;
	uv_loop_init(&loop);
	udx_t udx;
	udx_init(&loop, &udx, nullptr);
	udx_socket_t sock;
	udx_socket_init(&udx, &sock, nullptr);
	sockaddr_in addr;
	uv_ip4_addr("127.0.0.1", 0, &addr);
	udx_socket_bind(&sock, (sockaddr *)&addr, 0);

	DhtRpc rpc;
	rpc.init(&sock, &loop);
	Ed25519KeyPair server_kp;
	crypto_sign_keypair(server_kp.public_key, server_kp.secret_key);
	HyperDht dht;
	dht.init(&rpc, server_kp);
	dht.listen();
	dht.tick(1000);

	int told = 0;
	dht.on_handshake_request = [&] (uint32_t, const uint8_t *, const dht_msg::NoisePayload &) { told++; };

	uint8_t ns[32]; // NS_PEER_HANDSHAKE, as test_handshake_pool derives it
	{
		const char *name = "hyperswarm/dht";
		uint8_t base[32];
		crypto_generichash(base, 32, (const uint8_t *)name, strlen(name), nullptr, 0);
		const uint8_t id = 0;
		crypto_generichash_state st;
		crypto_generichash_init(&st, nullptr, 0, 32);
		crypto_generichash_update(&st, base, 32);
		crypto_generichash_update(&st, &id, 1);
		crypto_generichash_final(&st, ns, 32);
	}

	auto make_noise = [&] (uint32_t udx_id) {
		Ed25519KeyPair kp;
		crypto_sign_keypair(kp.public_key, kp.secret_key);
		NoiseIK ik(true, kp, ns, server_kp.public_key);
		dht_msg::NoisePayload p;
		memset(&p, 0, sizeof p);
		p.version = 1;
		p.firewall = 1;
		p.has_udx = true;
		p.udx.id = udx_id;
		p.has_secret_stream = true;
		EncodeState ps = { 0, 0, nullptr };
		dht_msg::noise_payload_preencode(ps, p);
		std::vector<uint8_t> pb(ps.end);
		ps.buffer = pb.data();
		ps.start = 0;
		dht_msg::noise_payload_encode(ps, p);
		return ik.send(pb.data(), pb.size());
	};

	auto deliver = [&] (const std::vector<uint8_t> &value, uint16_t from_port, uint16_t tid) {
		DhtRequest req;
		memset(&req, 0, sizeof req);
		req.command = HYPERDHT_CMD_PEER_HANDSHAKE;
		req.tid = tid;
		req.from.ip[0] = 127; req.from.ip[3] = 1;
		req.from.port = from_port;
		req.value = value.data();
		req.value_len = value.size();
		if (rpc.on_request) rpc.on_request(req);
	};

	printf("\n--- 1: the same handshake twice, direct ---\n");
	Catcher client;
	const auto noise = make_noise(4242);
	HandshakeMessage h;
	memset(&h, 0, sizeof h);
	h.mode = HS_MODE_FROM_CLIENT;
	h.noise = const_cast<uint8_t *>(noise.data());
	h.noise_len = noise.size();
	const auto direct = encode_hs(h);
	deliver(direct, client.port, 77);
	const auto r1 = client.next(&loop);
	deliver(direct, client.port, 77);
	const auto r2 = client.next(&loop);
	check(!r1.empty() && !r2.empty(), "both copies were answered");
	check(r1 == r2, "the two replies are byte-identical (the cached reply)");
	check(dht.handshake_count() == 1, "one handshake held, not two");
	check(told == 1, "the listener was told once");

	printf("\n--- 2: the same handshake, relayed by another node ---\n");
	Catcher relay;
	HandshakeMessage hr;
	memset(&hr, 0, sizeof hr);
	hr.mode = HS_MODE_FROM_RELAY;
	hr.noise = const_cast<uint8_t *>(noise.data());
	hr.noise_len = noise.size();
	hr.has_peer_address = true;
	hr.peer_address.ip[0] = 127; hr.peer_address.ip[3] = 1;
	hr.peer_address.port = client.port;
	deliver(encode_hs(hr), relay.port, 78);
	check(!relay.next(&loop).empty(), "the relay got a reply");
	check(dht.handshake_count() == 1, "still one handshake");
	check(told == 1, "the listener was still told only once");

	printf("\n--- 3: a different handshake ---\n");
	Catcher other;
	const auto noise2 = make_noise(4343);
	HandshakeMessage h2;
	memset(&h2, 0, sizeof h2);
	h2.mode = HS_MODE_FROM_CLIENT;
	h2.noise = const_cast<uint8_t *>(noise2.data());
	h2.noise_len = noise2.size();
	deliver(encode_hs(h2), other.port, 79);
	check(!other.next(&loop).empty(), "answered");
	check(dht.handshake_count() == 2 && told == 2, "a new handshake, the listener told");

	udx_socket_close(&sock);
	uv_run(&loop, UV_RUN_NOWAIT);
	printf("\n%s test_handshake_dedup: %d/%d\n", failed ? "FAIL" : "PASS", passed, passed + failed);
	return failed ? 1 : 0;
}