diff options
| author | Bitflash <btf01@proton.me> | 2026-07-30 13:38:12 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-07-30 13:38:12 -0300 |
| commit | 9f7141b28e2da9db7d7cb099d1f13f28742ff0fa (patch) | |
| tree | a934b2a786639046298b23eef02a4d6468b1d629 /src/net.cpp | |
| parent | b539d776bd86df9224d97048f26f33d2750d1c92 (diff) | |
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.
Diffstat (limited to 'src/net.cpp')
| -rw-r--r-- | src/net.cpp | 13 |
1 files changed, 9 insertions, 4 deletions
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 | ||