From 51d617e70f90a972185917ea0096c1ecef2bfa8b Mon Sep 17 00:00:00 2001 From: Bitflash-sh Date: Fri, 24 Jul 2026 11:18:11 -0300 Subject: Bitflash: fair-launch release Satoshi's bitcoin-0.1.0 reborn with RandomX CPU proof of work, anonymous .btf addressing over Nostr with CGNAT rendezvous, automatic peer discovery, a headless Linux node, and Windows GUI wallet. Fair launch, no premine, 21M cap. --- src/btfrv.h | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/btfrv.h (limited to 'src/btfrv.h') diff --git a/src/btfrv.h b/src/btfrv.h new file mode 100644 index 0000000..c776201 --- /dev/null +++ b/src/btfrv.h @@ -0,0 +1,59 @@ +// Copyright (c) 2026 Bitflash developers +// Distributed under the MIT/X11 software license. +// +// Bitflash rendezvous transport -- the "meeting node" that lets two nodes behind +// CGNAT connect without a public IP or port forwarding. Both sides connect OUT +// to the relay (which HAS a public IP, e.g. the VPS); the relay pairs them by +// `.btf` pubkey and forwards raw bytes between them. The endpoints then run the +// echan end-to-end channel on top, so the relay sees only ciphertext and cannot +// MITM (the client boxes to the service's STATIC key from its self-certified +// descriptor -- a substituting relay just breaks the channel, never reads it). +// +// This is the server that runs on the VPS (headless, Layer 6 ports it to Linux) +// plus the client/service helpers the desktop node uses. + +#ifndef BITFLASH_BTFRV_H +#define BITFLASH_BTFRV_H + +#include + +namespace btf +{ + +// A connected, paired rendezvous socket (opaque). Read/write raw bytes; the +// caller layers echan on top. Close with RvClose. +typedef intptr_t RvSocket; +static const RvSocket RV_INVALID = (RvSocket)-1; + +// Initialize sockets (Winsock on Windows). Idempotent. +bool RvInit(); + +// ---- relay side (runs on the VPS) ---- +// Run the rendezvous relay forever on `port`, forwarding between paired peers. +// Blocks; run it in a thread. Returns false if it can't bind/listen. +bool RvRelayRun(unsigned short port); + +// Stop a running relay (signals RvRelayRun to return). +void RvRelayStop(); + +// ---- service side (the node being reached by its .btf address) ---- +// Register at the relay under `my_pubkey` (32 bytes) and block until a client is +// paired to us. Returns the paired socket, or RV_INVALID on error. +RvSocket RvServiceRegister(const char* relay_host, unsigned short port, + const unsigned char my_pubkey[32]); + +// ---- client side (the node dialing a .btf address) ---- +// Connect through the relay to the service registered under `target_pubkey`. +// Returns the paired socket, or RV_INVALID on error. +RvSocket RvClientConnect(const char* relay_host, unsigned short port, + const unsigned char target_pubkey[32]); + +// Blocking read/write of exactly n bytes over a rendezvous socket. Return false +// on error/EOF. +bool RvReadN(RvSocket s, void* buf, int n); +bool RvWriteN(RvSocket s, const void* buf, int n); +void RvClose(RvSocket s); + +} // namespace btf + +#endif -- cgit v1.2.3 From 997e511e120272e8f2ddd29716b486e597410f02 Mon Sep 17 00:00:00 2001 From: Bitflash Date: Wed, 29 Jul 2026 13:28:50 -0300 Subject: rendezvous: register and wait for a dial as separate steps (#30) A node could not become reachable. RvServiceRegister connected, sent its 'S'+pubkey header, and then blocked reading the relay's 0x01 byte -- which the relay only writes when a client pairs with the service. So the call did not return when the node was registered; it returned when somebody had already dialled it. ThreadBtfAccept called BtfSetActiveRelay after that. The result is a deadlock: - to be dialled, a node must publish a descriptor naming its meeting node - to publish, BtfActiveRelay() must be set - it is only set once somebody has dialled Before #26 this was masked. BtfActiveRelay() fell back to vBtfMeetingRelays[0], so a node always published something, and ThreadBtfAccept picks its relay at random -- so it worked whenever the two happened to match, roughly one time in the number of relays. That is the flakiness this project has been chasing: not a bad relay, an advertisement that usually named the wrong one. #26 removed the fallback, which was right on its own terms and turned an unreliable heuristic into a reliable deadlock. This is the other half of that change and they belong together. RvServiceRegister now returns as soon as the relay has the service listed. RvServiceWaitPaired does the blocking read. ThreadBtfAccept registers, advertises, then waits. RvClientConnect is untouched -- a dialer blocking until it is paired is correct. Registration failure also had no log line at all, which is why this stayed invisible: a node that never registered looked identical to one waiting quietly for a peer. There are now lines for registering, failing over, and being dropped. Verified: a fresh node reports `registered at 90.156.222.107:8434, waiting for a dial` and then self-resolves with `meeting_node=90.156.222.107:8434` -- the relay it is actually on. Two nodes on one machine reach each other. --- src/btfrv.cpp | 13 +++++++++++-- src/btfrv.h | 13 +++++++++++-- src/net.cpp | 25 ++++++++++++++++++++++--- 3 files changed, 44 insertions(+), 7 deletions(-) (limited to 'src/btfrv.h') diff --git a/src/btfrv.cpp b/src/btfrv.cpp index 0d02ca8..63e092e 100644 --- a/src/btfrv.cpp +++ b/src/btfrv.cpp @@ -250,11 +250,20 @@ RvSocket RvServiceRegister(const char* relay_host, unsigned short port, hdr[0] = (unsigned char)'S'; memcpy(hdr + 1, my_pubkey, 32); if (!WriteN(s, hdr, 33)) { CLOSESOCK(s); return RV_INVALID; } - unsigned char paired = 0; - if (!ReadN(s, &paired, 1) || paired != 0x01) { CLOSESOCK(s); return RV_INVALID; } + // Return here: registered, not yet paired. The relay lists the service as + // soon as it reads this header. The 0x01 that used to be consumed below + // only arrives when somebody dials us, so blocking on it made "registered" + // and "already in use" indistinguishable to the caller -- and left the node + // unable to advertise a meeting node until after it had been reached there. return (RvSocket)s; } +bool RvServiceWaitPaired(RvSocket s) +{ + unsigned char paired = 0; + return ReadN((SOCKET)s, &paired, 1) && paired == 0x01; +} + RvSocket RvClientConnect(const char* relay_host, unsigned short port, const unsigned char target_pubkey[32]) { diff --git a/src/btfrv.h b/src/btfrv.h index c776201..9d6756e 100644 --- a/src/btfrv.h +++ b/src/btfrv.h @@ -37,11 +37,20 @@ bool RvRelayRun(unsigned short port); void RvRelayStop(); // ---- service side (the node being reached by its .btf address) ---- -// Register at the relay under `my_pubkey` (32 bytes) and block until a client is -// paired to us. Returns the paired socket, or RV_INVALID on error. +// Register at the relay under `my_pubkey` (32 bytes). Returns as soon as the +// relay has us listed, WITHOUT waiting for anyone to dial -- the caller needs +// that moment to start advertising this relay as its meeting node. Waiting for +// the dial was folded into this call before, which deadlocked discovery: a node +// could not be advertised until it had been dialled, and could not be dialled +// until it was advertised. Returns RV_INVALID on error. RvSocket RvServiceRegister(const char* relay_host, unsigned short port, const unsigned char my_pubkey[32]); +// Block on a registered service socket until a client is paired to us. Returns +// false if the relay drops us or errors, in which case the caller should close +// the socket and register again. +bool RvServiceWaitPaired(RvSocket s); + // ---- client side (the node dialing a .btf address) ---- // Connect through the relay to the service registered under `target_pubkey`. // Returns the paired socket, or RV_INVALID on error. diff --git a/src/net.cpp b/src/net.cpp index 7b2ef83..b5d12d0 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -554,7 +554,10 @@ void ThreadBtfAccept(void* parg) string strHost = strMeeting.substr(0, colon); int nPort = atoi(strMeeting.substr(colon + 1).c_str()); - // Blocks until a client is paired to us (or the relay drops us) + // Returns as soon as the relay has us listed -- it does not wait for a + // dial. That distinction is the whole point: we have to be advertised + // before anyone can dial us, so registering and waiting cannot be the + // same call. btf::RvSocket rv = btf::RvServiceRegister(strHost.c_str(), (unsigned short)nPort, pk); if (fShutdown) { @@ -564,13 +567,29 @@ void ThreadBtfAccept(void* parg) } if (rv == btf::RV_INVALID) { + LogPrint("net", "rendezvous: could not register at %s, trying another\n", + strMeeting.c_str()); iRelay++; // this relay is down/attacked -> try the next one Sleep(3000); continue; } - // Registered OK: advertise THIS relay in our descriptor so clients dial - // us here, and keep using it (iRelay unchanged) until it fails. + + // Registered. Advertise THIS relay now, while we are listed and before + // anybody dials -- a descriptor naming it is what makes a dial possible + // at all. Keep using it (iRelay unchanged) until it fails. BtfSetActiveRelay(strMeeting); + LogPrint("net", "rendezvous: registered at %s, waiting for a dial\n", + strMeeting.c_str()); + + // Now block for someone to arrive. + if (!btf::RvServiceWaitPaired(rv)) + { + btf::RvClose(rv); + LogPrint("net", "rendezvous: %s dropped us before any dial, re-registering\n", + strMeeting.c_str()); + continue; + } + btf_socket_t hSocket = btf::BtfServiceWrap(rv, sk); if (hSocket == INVALID_SOCKET) continue; -- cgit v1.2.3 From 9f7141b28e2da9db7d7cb099d1f13f28742ff0fa Mon Sep 17 00:00:00 2001 From: Bitflash Date: Thu, 30 Jul 2026 13:38:12 -0300 Subject: rendezvous: bound the wait for a dial, so a dead path is noticed (#51) A node registers at a relay and waits to be dialled. The wait had no bound, and ConnectTo() clears the socket timeout on purpose -- a registration is idle by design and may sit for a long time. What that cannot survive is the path dying with neither end saying so, which is the ordinary case and not the exotic one: a NAT drops the idle mapping, a relay restarts, a route changes. No FIN arrives, nothing becomes readable, and recv() never returns. The thread stays alive and asleep, so ThreadBtfAccept never reaches the `continue` that would register again. The node is unreachable and every outward sign says it is fine. Caught on the bootstrap seed: 27 minutes without a block, eight behind the network, relay reachable the whole time, and a restart fixing it instantly. Nothing in the log marked the moment it went deaf. I tried TCP keepalive first and it does not work here. With probes every 5s and a 2-probe limit, a socket whose replies were being dropped was still ESTAB after four minutes with the probe counter stuck at zero. It is measured, not assumed, and that approach is not in this change. So the bound is ours to enforce, at the one place that knows what waiting means. RvServiceWaitPaired takes a timeout; expiring is not an error but "nobody came, go round again", which the loop above already handles. Cost is one reconnect per relay per five minutes. Verified by killing a registration's path silently -- dropping only the relay's replies, so no RST or FIN is ever seen. With a 60s bound for the test: rendezvous: no dial at 92.246.128.180:8434 within 60s (or it dropped us), re-registering Registrations went 1 -> 2 and the node carried on. Before this, the same treatment left it blocked past four minutes with no log line at all. The socket goes back to blocking once a dial lands, because from that point it is a data tunnel and a tunnel is allowed to sit quiet between messages. --- src/btfrv.cpp | 51 +++++++++++++++++++++++++++++++++++++++++++++++++-- src/btfrv.h | 7 ++++++- src/net.cpp | 13 +++++++++---- src/net.h | 8 ++++++++ 4 files changed, 72 insertions(+), 7 deletions(-) (limited to 'src/btfrv.h') diff --git a/src/btfrv.cpp b/src/btfrv.cpp index 63e092e..7efd38b 100644 --- a/src/btfrv.cpp +++ b/src/btfrv.cpp @@ -258,10 +258,57 @@ RvSocket RvServiceRegister(const char* relay_host, unsigned short port, return (RvSocket)s; } -bool RvServiceWaitPaired(RvSocket s) +bool RvServiceWaitPaired(RvSocket s, int nTimeoutSecs) { + // A registration socket is idle on purpose -- we announce ourselves and + // wait, possibly for a long time, for someone to dial. ConnectTo() clears + // the socket timeout for exactly that reason, and with no timeout at all + // this recv() has no way to end when the path dies without either side + // saying so: a NAT drops the idle mapping, a relay restarts, a route + // changes. No FIN arrives, nothing is readable, and the call never + // returns. The thread stays alive and asleep, so ThreadBtfAccept never + // reaches the `continue` that would register again, and the node goes deaf + // while every outward sign says it is healthy. + // + // Seen in production on the bootstrap seed: 27 minutes without a block, + // eight behind the network, relay reachable throughout, fixed instantly by + // a restart. + // + // TCP keepalive is the obvious answer and it does not work here -- I + // measured it. With probes every 5s and a 2-probe limit, a socket whose + // replies were being dropped was still ESTAB after four minutes, the probe + // counter stuck at zero. So the timeout is ours to enforce, at the one + // place that knows what waiting means. + // + // Expiring is not a failure. It costs one reconnect per interval and + // returns the loop to a known state. + if (nTimeoutSecs > 0) + { +#ifdef _WIN32 + DWORD tv = (DWORD)nTimeoutSecs * 1000; + setsockopt((SOCKET)s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)); +#else + struct timeval tv; tv.tv_sec = nTimeoutSecs; tv.tv_usec = 0; + setsockopt((SOCKET)s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); +#endif + } + unsigned char paired = 0; - return ReadN((SOCKET)s, &paired, 1) && paired == 0x01; + bool fOk = ReadN((SOCKET)s, &paired, 1) && paired == 0x01; + + // Hand back a socket that blocks again: from here it is a data tunnel, and + // a tunnel legitimately sits quiet between messages. + if (fOk && nTimeoutSecs > 0) + { +#ifdef _WIN32 + DWORD tvOff = 0; + setsockopt((SOCKET)s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tvOff, sizeof(tvOff)); +#else + struct timeval tvOff; tvOff.tv_sec = 0; tvOff.tv_usec = 0; + setsockopt((SOCKET)s, SOL_SOCKET, SO_RCVTIMEO, &tvOff, sizeof(tvOff)); +#endif + } + return fOk; } RvSocket RvClientConnect(const char* relay_host, unsigned short port, diff --git a/src/btfrv.h b/src/btfrv.h index 9d6756e..9cd7476 100644 --- a/src/btfrv.h +++ b/src/btfrv.h @@ -49,7 +49,12 @@ RvSocket RvServiceRegister(const char* relay_host, unsigned short port, // Block on a registered service socket until a client is paired to us. Returns // false if the relay drops us or errors, in which case the caller should close // the socket and register again. -bool RvServiceWaitPaired(RvSocket s); +// Blocks until a client is paired with us. Give it a timeout, in seconds, and +// it returns false once that passes with nobody arriving -- which the caller +// should treat as "register again", not as an error. Zero means wait forever, +// which is what this used to do unconditionally and must not be used for a +// registration socket: see the note on the implementation. +bool RvServiceWaitPaired(RvSocket s, int nTimeoutSecs = 0); // ---- client side (the node dialing a .btf address) ---- // Connect through the relay to the service registered under `target_pubkey`. diff --git a/src/net.cpp b/src/net.cpp index 395092d..2581578 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -783,12 +783,17 @@ void ThreadBtfAccept(void* parg) LogPrint("net", "rendezvous: registered at %s, waiting for a dial\n", strMeeting.c_str()); - // Now block for someone to arrive. - if (!btf::RvServiceWaitPaired(rv)) + // Now wait for someone to arrive -- but not forever. This used to have + // no bound, and a registration whose path died quietly left the thread + // parked in recv() with nothing to wake it: the loop never came back + // here, the node stopped being reachable, and nothing in the log said + // so. Re-registering every few minutes when nobody has dialled costs + // one reconnect and removes the whole failure mode. + if (!btf::RvServiceWaitPaired(rv, BTF_RENDEZVOUS_WAIT_SECS)) { btf::RvClose(rv); - LogPrint("net", "rendezvous: %s dropped us before any dial, re-registering\n", - strMeeting.c_str()); + LogPrint("net", "rendezvous: no dial at %s within %ds (or it dropped us), " + "re-registering\n", strMeeting.c_str(), BTF_RENDEZVOUS_WAIT_SECS); continue; } diff --git a/src/net.h b/src/net.h index 364fbfb..4379d04 100644 --- a/src/net.h +++ b/src/net.h @@ -43,6 +43,14 @@ static const unsigned int MAX_PEX_DESCRIPTOR_BYTES = 1024; // connection is the intended traffic; anything faster is someone else's idea. static const int64 PEX_MIN_INTERVAL = 60; +// How long to sit registered at a rendezvous relay before giving up on this +// attempt and registering again. Bounded because a registration socket that +// goes quiet is indistinguishable, from inside recv(), from one whose path has +// died -- and the second kind never wakes up. Five minutes is short enough +// that a node is unreachable only briefly and long enough that the reconnect +// traffic is nothing: one per relay per five minutes. +static const int BTF_RENDEZVOUS_WAIT_SECS = 300; + // Descriptors to hand a peer: ours first, then peers that answered us. void BtfPexCollect(std::vector& vDescOut); // Verify descriptors a peer sent and remember the good ones. Returns how many. -- cgit v1.2.3 From 7c70eb4498e88725241493033edfe89554ff1b18 Mon Sep 17 00:00:00 2001 From: bitflash-maintainer Date: Wed, 2 Sep 2026 09:03:01 +0000 Subject: tunnel: report which stage failed instead of guessing the cause --- src/btfrv.cpp | 16 ++++++++++++++-- src/btfrv.h | 19 +++++++++++++++++-- src/btftunnel.cpp | 37 +++++++++++++++++++++++++++++++++---- src/btftunnel.h | 25 +++++++++++++++++++++++-- src/main.cpp | 11 ++++++----- 5 files changed, 93 insertions(+), 15 deletions(-) (limited to 'src/btfrv.h') diff --git a/src/btfrv.cpp b/src/btfrv.cpp index 133eb89..864fe3d 100644 --- a/src/btfrv.cpp +++ b/src/btfrv.cpp @@ -334,17 +334,29 @@ bool RvServiceWaitPaired(RvSocket s, int nTimeoutSecs) } RvSocket RvClientConnect(const char* relay_host, unsigned short port, - const unsigned char target_pubkey[32]) + const unsigned char target_pubkey[32], + RvClientFail* pFail) { + if (pFail) *pFail = RVC_FAIL_DIAL; if (!RvInit()) return RV_INVALID; SOCKET s = ConnectTo(relay_host, port); if (s == INVALID_SOCKET) return RV_INVALID; + if (pFail) *pFail = RVC_FAIL_PAIR; unsigned char hdr[33]; hdr[0] = (unsigned char)'C'; memcpy(hdr + 1, target_pubkey, 32); if (!WriteN(s, hdr, 33)) { CLOSESOCK(s); return RV_INVALID; } unsigned char paired = 0; - if (!ReadN(s, &paired, 1) || paired != 0x01) { CLOSESOCK(s); return RV_INVALID; } + if (!ReadN(s, &paired, 1)) { CLOSESOCK(s); return RV_INVALID; } + if (paired != 0x01) + { + // The relay answered and declined: HandleIncoming writes 0x00 and + // closes when no service is parked under this pubkey right now. + if (pFail) *pFail = RVC_FAIL_REFUSED; + CLOSESOCK(s); + return RV_INVALID; + } + if (pFail) *pFail = RVC_FAIL_NONE; return (RvSocket)s; } diff --git a/src/btfrv.h b/src/btfrv.h index 9cd7476..1e85cb0 100644 --- a/src/btfrv.h +++ b/src/btfrv.h @@ -57,10 +57,25 @@ RvSocket RvServiceRegister(const char* relay_host, unsigned short port, bool RvServiceWaitPaired(RvSocket s, int nTimeoutSecs = 0); // ---- client side (the node dialing a .btf address) ---- +// Where a failed RvClientConnect stopped. RV_INVALID alone collapses three +// different situations -- relay unreachable, relay reachable but nobody +// registered, connection died mid-pairing -- and they point at different +// components. The caller can only report the right one if this side says +// which it saw. +enum RvClientFail +{ + RVC_FAIL_NONE = 0, + RVC_FAIL_DIAL, // TCP connect to the relay itself failed + RVC_FAIL_REFUSED, // relay answered 0x00: no service registered under this pubkey + RVC_FAIL_PAIR, // relay connection died before pairing completed +}; + // Connect through the relay to the service registered under `target_pubkey`. -// Returns the paired socket, or RV_INVALID on error. +// Returns the paired socket, or RV_INVALID on error -- with `pFail`, if +// given, naming the stage that failed. RvSocket RvClientConnect(const char* relay_host, unsigned short port, - const unsigned char target_pubkey[32]); + const unsigned char target_pubkey[32], + RvClientFail* pFail = 0); // Blocking read/write of exactly n bytes over a rendezvous socket. Return false // on error/EOF. diff --git a/src/btftunnel.cpp b/src/btftunnel.cpp index 63d178b..408476e 100644 --- a/src/btftunnel.cpp +++ b/src/btftunnel.cpp @@ -136,18 +136,47 @@ static btf_socket_t StartTunnel(RvSocket rv, const unsigned char shared[32]) return appEnd; } +const char* TunnelFailText(TunnelFail f) +{ + switch (f) + { + case TUNNEL_FAIL_NONE: return "no failure"; + case TUNNEL_FAIL_INIT: return "local socket/crypto init failed"; + case TUNNEL_FAIL_DIAL: return "meeting node unreachable (tcp dial failed)"; + case TUNNEL_FAIL_REFUSED: return "relay reachable, but no service registered there for this address"; + case TUNNEL_FAIL_PAIR: return "relay connection died before pairing completed"; + case TUNNEL_FAIL_HANDSHAKE: return "end-to-end handshake failed"; + case TUNNEL_FAIL_LOCAL: return "local tunnel setup failed"; + } + return "unknown failure"; +} + btf_socket_t BtfClientTunnel(const char* meeting_host, unsigned short port, const unsigned char target_pubkey[32], - const unsigned char service_enc_pub[32]) + const unsigned char service_enc_pub[32], + TunnelFail* pFail) { + if (pFail) *pFail = TUNNEL_FAIL_INIT; if (!TunnelInit()) return INVALID_SOCKET; - RvSocket rv = RvClientConnect(meeting_host, port, target_pubkey); - if (rv == RV_INVALID) return INVALID_SOCKET; + RvClientFail rvFail = RVC_FAIL_NONE; + RvSocket rv = RvClientConnect(meeting_host, port, target_pubkey, &rvFail); + if (rv == RV_INVALID) + { + if (pFail) + *pFail = rvFail == RVC_FAIL_DIAL ? TUNNEL_FAIL_DIAL + : rvFail == RVC_FAIL_REFUSED ? TUNNEL_FAIL_REFUSED + : TUNNEL_FAIL_PAIR; + return INVALID_SOCKET; + } unsigned char eph_pub[32], shared[32]; + if (pFail) *pFail = TUNNEL_FAIL_HANDSHAKE; if (!ChanClientHandshake(service_enc_pub, eph_pub, shared)) { RvClose(rv); return INVALID_SOCKET; } if (!RvWriteN(rv, eph_pub, 32)) { RvClose(rv); return INVALID_SOCKET; } - return StartTunnel(rv, shared); + if (pFail) *pFail = TUNNEL_FAIL_LOCAL; + btf_socket_t app = StartTunnel(rv, shared); + if (app != INVALID_SOCKET && pFail) *pFail = TUNNEL_FAIL_NONE; + return app; } btf_socket_t BtfServiceWrap(RvSocket rv, const unsigned char my_enc_sk[32]) diff --git a/src/btftunnel.h b/src/btftunnel.h index 24a7509..3cc87cd 100644 --- a/src/btftunnel.h +++ b/src/btftunnel.h @@ -36,12 +36,33 @@ namespace btf // Initialize (sockets + libsodium). Idempotent. bool TunnelInit(); +// Which stage of BtfClientTunnel failed. INVALID_SOCKET alone says only "no +// tunnel"; a caller that then guesses at causes in its log line sends whoever +// reads it to the wrong component (issue #17: "relay unreachable or pool not +// listening" blamed two healthy parts). The stage is what this side actually +// knows -- report that. +enum TunnelFail +{ + TUNNEL_FAIL_NONE = 0, + TUNNEL_FAIL_INIT, // local socket/crypto init failed + TUNNEL_FAIL_DIAL, // TCP connect to the meeting node failed + TUNNEL_FAIL_REFUSED, // relay reachable, but no service registered under the pubkey + TUNNEL_FAIL_PAIR, // relay connection died before pairing completed + TUNNEL_FAIL_HANDSHAKE, // end-to-end channel handshake failed + TUNNEL_FAIL_LOCAL, // local pump plumbing (socket pair / threads) failed +}; + +// Short description of a TunnelFail for log lines. +const char* TunnelFailText(TunnelFail f); + // Client: dial the service registered under `target_pubkey` at the meeting node // (host:port), box to its static x25519 `service_enc_pub`, and return a local -// plaintext SOCKET tunneled end-to-end to the service. INVALID_SOCKET on error. +// plaintext SOCKET tunneled end-to-end to the service. INVALID_SOCKET on error +// -- with `pFail`, if given, naming the stage that failed. btf_socket_t BtfClientTunnel(const char* meeting_host, unsigned short port, const unsigned char target_pubkey[32], - const unsigned char service_enc_pub[32]); + const unsigned char service_enc_pub[32], + TunnelFail* pFail = 0); // Service: wrap an already-paired rendezvous socket (RvServiceRegister followed // by RvServiceWaitPaired -- the pairing byte MUST already be consumed) with diff --git a/src/main.cpp b/src/main.cpp index 7b3e555..c0a8825 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3758,13 +3758,14 @@ static bool PoolParticipantMiner() // Step 2b: open encrypted tunnel to pool // ------------------------------------------------------------------ SetParticipantMiningStatus("opening tunnel"); + btf::TunnelFail tunnelFail = btf::TUNNEL_FAIL_NONE; s = btf::BtfClientTunnel(host.c_str(), (unsigned short)port, - target_pubkey, service_enc_pub); + target_pubkey, service_enc_pub, &tunnelFail); if (s == INVALID_SOCKET) { - LogPrint("worker", "[worker] tunnel connect FAILED via %s" - " (relay unreachable or pool not listening)\n", - meetingHostPort.c_str()); - SetParticipantMiningStatus("tunnel connect failed"); + LogPrint("worker", "[worker] tunnel connect FAILED via %s: %s\n", + meetingHostPort.c_str(), btf::TunnelFailText(tunnelFail)); + SetParticipantMiningStatus(std::string("tunnel connect failed: ") + + btf::TunnelFailText(tunnelFail)); return false; } LogPrint("worker", "[worker] tunnel open via %s\n", meetingHostPort.c_str()); -- cgit v1.2.3