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--relay/btfrv.cpp95
1 files changed, 94 insertions, 1 deletions
diff --git a/relay/btfrv.cpp b/relay/btfrv.cpp
index ffda08c..b1eee55 100644
--- a/relay/btfrv.cpp
+++ b/relay/btfrv.cpp
@@ -35,6 +35,7 @@ typedef int SOCKET;
35#include <thread> 35#include <thread>
36#include <mutex> 36#include <mutex>
37#include <atomic> 37#include <atomic>
38#include <chrono>
38 39
39namespace btf 40namespace btf
40{ 41{
@@ -46,6 +47,9 @@ static SOCKET g_listen = INVALID_SOCKET;
46static std::map<std::string, SOCKET> g_waiting; 47static std::map<std::string, SOCKET> g_waiting;
47static std::mutex g_mtx; 48static std::mutex g_mtx;
48 49
50// How often the reaper walks the waiting map.
51static const int REAP_INTERVAL_SECS = 60;
52
49bool RvInit() 53bool RvInit()
50{ 54{
51 static bool done = false; 55 static bool done = false;
@@ -106,6 +110,77 @@ static SOCKET ConnectTo(const char* host, unsigned short port)
106 return s; 110 return s;
107} 111}
108 112
113// ---- liveness of a waiting registration ----
114
115// True when the far end of a registration socket is provably gone.
116//
117// A registered service sends nothing while it waits, so the socket should never
118// be readable. Readable therefore means one of two things, and recv tells them
119// apart: 0 is a clean close, negative is an error, and either way the
120// registration is worthless. Anything else -- including a service that somehow
121// has data pending -- is left alone, because guessing wrong here unregisters a
122// node that is perfectly fine.
123//
124// select() rather than MSG_DONTWAIT so the same code compiles on Windows, and
125// so the socket's blocking mode is never touched.
126static bool RegistrationDead(SOCKET s)
127{
128 fd_set rd;
129 FD_ZERO(&rd);
130 FD_SET(s, &rd);
131 struct timeval tv;
132 tv.tv_sec = 0;
133 tv.tv_usec = 0;
134 int r = select((int)(s + 1), &rd, NULL, NULL, &tv);
135 if (r <= 0)
136 return false; // not readable: still waiting, healthy
137 char c;
138 int n = recv(s, &c, 1, MSG_PEEK);
139 return (n <= 0);
140}
141
142// Drop registrations whose far end has gone away.
143//
144// Deliberately no expiry by age. Nodes older than v1.2.7 register once and then
145// block forever waiting to be dialled -- they never re-register, so a clock
146// based rule would quietly make every one of them unreachable, and being
147// blocked in recv they would never find out. Only provable death counts.
148static void ReapDeadRegistrations()
149{
150 int nBefore = 0, nReaped = 0;
151 {
152 std::lock_guard<std::mutex> lk(g_mtx);
153 nBefore = (int)g_waiting.size();
154 std::map<std::string, SOCKET>::iterator it = g_waiting.begin();
155 while (it != g_waiting.end())
156 {
157 if (RegistrationDead(it->second))
158 {
159 CLOSESOCK(it->second);
160 g_waiting.erase(it++);
161 nReaped++;
162 }
163 else
164 ++it;
165 }
166 }
167 if (nReaped > 0)
168 printf("reaper: dropped %d dead registration(s), %d waiting\n",
169 nReaped, nBefore - nReaped);
170 fflush(stdout);
171}
172
173static void ReaperThread()
174{
175 while (!g_stop)
176 {
177 for (int i = 0; i < REAP_INTERVAL_SECS && !g_stop; i++)
178 std::this_thread::sleep_for(std::chrono::seconds(1));
179 if (g_stop) break;
180 ReapDeadRegistrations();
181 }
182}
183
109// ---- relay side ---- 184// ---- relay side ----
110 185
111// Pump bytes from `from` to `to`. Owns closing `from`; shuts down `to` on exit 186// Pump bytes from `from` to `to`. Owns closing `from`; shuts down `to` on exit
@@ -146,7 +221,23 @@ static void HandleIncoming(SOCKET s)
146 { 221 {
147 std::lock_guard<std::mutex> lk(g_mtx); 222 std::lock_guard<std::mutex> lk(g_mtx);
148 std::map<std::string, SOCKET>::iterator it = g_waiting.find(pubkey); 223 std::map<std::string, SOCKET>::iterator it = g_waiting.find(pubkey);
149 if (it != g_waiting.end()) { svc = it->second; g_waiting.erase(it); } 224 if (it != g_waiting.end())
225 {
226 // Check before handing it over. Pairing a client with a dead
227 // registration produces a tunnel that fails after both sides
228 // think they succeeded, which is the failure the dialling node
229 // reports as "tunnel to X failed" with nothing to explain it.
230 if (RegistrationDead(it->second))
231 {
232 CLOSESOCK(it->second);
233 g_waiting.erase(it);
234 }
235 else
236 {
237 svc = it->second;
238 g_waiting.erase(it);
239 }
240 }
150 } 241 }
151 if (svc == INVALID_SOCKET) 242 if (svc == INVALID_SOCKET)
152 { 243 {
@@ -187,6 +278,8 @@ bool RvRelayRun(unsigned short port)
187 if (bind(g_listen, (struct sockaddr*)&addr, sizeof(addr)) != 0) { CLOSESOCK(g_listen); return false; } 278 if (bind(g_listen, (struct sockaddr*)&addr, sizeof(addr)) != 0) { CLOSESOCK(g_listen); return false; }
188 if (listen(g_listen, 16) != 0) { CLOSESOCK(g_listen); return false; } 279 if (listen(g_listen, 16) != 0) { CLOSESOCK(g_listen); return false; }
189 280
281 std::thread(ReaperThread).detach();
282
190 while (!g_stop) 283 while (!g_stop)
191 { 284 {
192 struct sockaddr_in cli; 285 struct sockaddr_in cli;