upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/btfrv.cpp51
-rw-r--r--src/btfrv.h7
-rw-r--r--src/net.cpp13
-rw-r--r--src/net.h8
4 files changed, 72 insertions, 7 deletions
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,
258 return (RvSocket)s; 258 return (RvSocket)s;
259} 259}
260 260
261bool RvServiceWaitPaired(RvSocket s) 261bool RvServiceWaitPaired(RvSocket s, int nTimeoutSecs)
262{ 262{
263 // A registration socket is idle on purpose -- we announce ourselves and
264 // wait, possibly for a long time, for someone to dial. ConnectTo() clears
265 // the socket timeout for exactly that reason, and with no timeout at all
266 // this recv() has no way to end when the path dies without either side
267 // saying so: a NAT drops the idle mapping, a relay restarts, a route
268 // changes. No FIN arrives, nothing is readable, and the call never
269 // returns. The thread stays alive and asleep, so ThreadBtfAccept never
270 // reaches the `continue` that would register again, and the node goes deaf
271 // while every outward sign says it is healthy.
272 //
273 // Seen in production on the bootstrap seed: 27 minutes without a block,
274 // eight behind the network, relay reachable throughout, fixed instantly by
275 // a restart.
276 //
277 // TCP keepalive is the obvious answer and it does not work here -- I
278 // measured it. With probes every 5s and a 2-probe limit, a socket whose
279 // replies were being dropped was still ESTAB after four minutes, the probe
280 // counter stuck at zero. So the timeout is ours to enforce, at the one
281 // place that knows what waiting means.
282 //
283 // Expiring is not a failure. It costs one reconnect per interval and
284 // returns the loop to a known state.
285 if (nTimeoutSecs > 0)
286 {
287#ifdef _WIN32
288 DWORD tv = (DWORD)nTimeoutSecs * 1000;
289 setsockopt((SOCKET)s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
290#else
291 struct timeval tv; tv.tv_sec = nTimeoutSecs; tv.tv_usec = 0;
292 setsockopt((SOCKET)s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
293#endif
294 }
295
263 unsigned char paired = 0; 296 unsigned char paired = 0;
264 return ReadN((SOCKET)s, &paired, 1) && paired == 0x01; 297 bool fOk = ReadN((SOCKET)s, &paired, 1) && paired == 0x01;
298
299 // Hand back a socket that blocks again: from here it is a data tunnel, and
300 // a tunnel legitimately sits quiet between messages.
301 if (fOk && nTimeoutSecs > 0)
302 {
303#ifdef _WIN32
304 DWORD tvOff = 0;
305 setsockopt((SOCKET)s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tvOff, sizeof(tvOff));
306#else
307 struct timeval tvOff; tvOff.tv_sec = 0; tvOff.tv_usec = 0;
308 setsockopt((SOCKET)s, SOL_SOCKET, SO_RCVTIMEO, &tvOff, sizeof(tvOff));
309#endif
310 }
311 return fOk;
265} 312}
266 313
267RvSocket RvClientConnect(const char* relay_host, unsigned short port, 314RvSocket 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,
49// Block on a registered service socket until a client is paired to us. Returns 49// Block on a registered service socket until a client is paired to us. Returns
50// false if the relay drops us or errors, in which case the caller should close 50// false if the relay drops us or errors, in which case the caller should close
51// the socket and register again. 51// the socket and register again.
52bool RvServiceWaitPaired(RvSocket s); 52// Blocks until a client is paired with us. Give it a timeout, in seconds, and
53// it returns false once that passes with nobody arriving -- which the caller
54// should treat as "register again", not as an error. Zero means wait forever,
55// which is what this used to do unconditionally and must not be used for a
56// registration socket: see the note on the implementation.
57bool RvServiceWaitPaired(RvSocket s, int nTimeoutSecs = 0);
53 58
54// ---- client side (the node dialing a .btf address) ---- 59// ---- client side (the node dialing a .btf address) ----
55// Connect through the relay to the service registered under `target_pubkey`. 60// 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)
783 LogPrint("net", "rendezvous: registered at %s, waiting for a dial\n", 783 LogPrint("net", "rendezvous: registered at %s, waiting for a dial\n",
784 strMeeting.c_str()); 784 strMeeting.c_str());
785 785
786 // Now block for someone to arrive. 786 // Now wait for someone to arrive -- but not forever. This used to have
787 if (!btf::RvServiceWaitPaired(rv)) 787 // no bound, and a registration whose path died quietly left the thread
788 // parked in recv() with nothing to wake it: the loop never came back
789 // here, the node stopped being reachable, and nothing in the log said
790 // so. Re-registering every few minutes when nobody has dialled costs
791 // one reconnect and removes the whole failure mode.
792 if (!btf::RvServiceWaitPaired(rv, BTF_RENDEZVOUS_WAIT_SECS))
788 { 793 {
789 btf::RvClose(rv); 794 btf::RvClose(rv);
790 LogPrint("net", "rendezvous: %s dropped us before any dial, re-registering\n", 795 LogPrint("net", "rendezvous: no dial at %s within %ds (or it dropped us), "
791 strMeeting.c_str()); 796 "re-registering\n", strMeeting.c_str(), BTF_RENDEZVOUS_WAIT_SECS);
792 continue; 797 continue;
793 } 798 }
794 799
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;
43// connection is the intended traffic; anything faster is someone else's idea. 43// connection is the intended traffic; anything faster is someone else's idea.
44static const int64 PEX_MIN_INTERVAL = 60; 44static const int64 PEX_MIN_INTERVAL = 60;
45 45
46// How long to sit registered at a rendezvous relay before giving up on this
47// attempt and registering again. Bounded because a registration socket that
48// goes quiet is indistinguishable, from inside recv(), from one whose path has
49// died -- and the second kind never wakes up. Five minutes is short enough
50// that a node is unreachable only briefly and long enough that the reconnect
51// traffic is nothing: one per relay per five minutes.
52static const int BTF_RENDEZVOUS_WAIT_SECS = 300;
53
46// Descriptors to hand a peer: ours first, then peers that answered us. 54// Descriptors to hand a peer: ours first, then peers that answered us.
47void BtfPexCollect(std::vector<std::string>& vDescOut); 55void BtfPexCollect(std::vector<std::string>& vDescOut);
48// Verify descriptors a peer sent and remember the good ones. Returns how many. 56// Verify descriptors a peer sent and remember the good ones. Returns how many.