upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/btfrv.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/btfrv.cpp')
-rw-r--r--src/btfrv.cpp106
1 files changed, 86 insertions, 20 deletions
diff --git a/src/btfrv.cpp b/src/btfrv.cpp
index d4f079f..7d705ef 100644
--- a/src/btfrv.cpp
+++ b/src/btfrv.cpp
@@ -40,6 +40,8 @@ typedef int SOCKET;
40#include <thread> 40#include <thread>
41#include <mutex> 41#include <mutex>
42#include <atomic> 42#include <atomic>
43#include <chrono>
44#include <system_error>
43 45
44namespace btf 46namespace btf
45{ 47{
@@ -102,31 +104,42 @@ static SOCKET ConnectTo(const char* host, unsigned short port)
102#else 104#else
103 struct addrinfo hints, *res = NULL; 105 struct addrinfo hints, *res = NULL;
104 memset(&hints, 0, sizeof(hints)); 106 memset(&hints, 0, sizeof(hints));
105 hints.ai_family = AF_INET; 107 hints.ai_family = AF_UNSPEC; // IPv4 or IPv6 (some relays are v6-only)
106 hints.ai_socktype = SOCK_STREAM; 108 hints.ai_socktype = SOCK_STREAM;
107 char portstr[16]; sprintf(portstr, "%u", (unsigned)port); 109 char portstr[16]; sprintf(portstr, "%u", (unsigned)port);
108 if (getaddrinfo(host, portstr, &hints, &res) != 0 || !res) return INVALID_SOCKET; 110 if (getaddrinfo(host, portstr, &hints, &res) != 0 || !res) return INVALID_SOCKET;
109 SOCKET s = BtfSocketTag(socket(res->ai_family, res->ai_socktype, res->ai_protocol), SOCK_RV_DIAL);
110 if (s == INVALID_SOCKET) { freeaddrinfo(res); return INVALID_SOCKET; }
111 111
112 // Apply a short timeout only for the connect() and initial handshake so an 112 // Try each resolved address until one connects.
113 // unresponsive relay doesn't block the thread. Once the tunnel is live the 113 SOCKET s = INVALID_SOCKET;
114 // timeout is cleared -- a forwarding pipe must block indefinitely on recv(). 114 for (struct addrinfo* rp = res; rp != NULL; rp = rp->ai_next)
115 {
116 s = BtfSocketTag(socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol), SOCK_RV_DIAL);
117 if (s == INVALID_SOCKET)
118 continue;
119
120 // Apply a short timeout only for the connect() and initial handshake so an
121 // unresponsive relay doesn't block the thread. Once the tunnel is live the
122 // timeout is cleared -- a forwarding pipe must block indefinitely on recv().
115#ifdef _WIN32 123#ifdef _WIN32
116 DWORD tvShort = 10000; 124 DWORD tvShort = 10000;
117 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tvShort, sizeof(tvShort)); 125 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tvShort, sizeof(tvShort));
118 setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tvShort, sizeof(tvShort)); 126 setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tvShort, sizeof(tvShort));
119#else 127#else
120 struct timeval tvShort; tvShort.tv_sec = 10; tvShort.tv_usec = 0; 128 struct timeval tvShort; tvShort.tv_sec = 10; tvShort.tv_usec = 0;
121 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tvShort, sizeof(tvShort)); 129 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char*)&tvShort, sizeof(tvShort));
122 setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tvShort, sizeof(tvShort)); 130 setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (char*)&tvShort, sizeof(tvShort));
123#endif 131#endif
124 132
125 if (connect(s, res->ai_addr, (int)res->ai_addrlen) != 0) 133 if (connect(s, rp->ai_addr, (int)rp->ai_addrlen) != 0)
126 { 134 {
127 CLOSESOCK(s); freeaddrinfo(res); return INVALID_SOCKET; 135 CLOSESOCK(s);
136 s = INVALID_SOCKET;
137 continue;
138 }
139 break;
128 } 140 }
129 freeaddrinfo(res); 141 freeaddrinfo(res);
142 if (s == INVALID_SOCKET) return INVALID_SOCKET;
130#endif 143#endif
131 144
132 // Clear the timeout -- the socket is now a live tunnel and must not 145 // Clear the timeout -- the socket is now a live tunnel and must not
@@ -198,8 +211,28 @@ static void HandleIncoming(SOCKET s)
198 bool a = WriteN(svc, &ok, 1); 211 bool a = WriteN(svc, &ok, 1);
199 bool b = WriteN(s, &ok, 1); 212 bool b = WriteN(s, &ok, 1);
200 if (!a || !b) { CLOSESOCK(svc); CLOSESOCK(s); return; } 213 if (!a || !b) { CLOSESOCK(svc); CLOSESOCK(s); return; }
201 std::thread(Forward, s, svc).detach(); 214 // One thread pumps s -> svc; this thread pumps svc -> s. Spawning a
202 std::thread(Forward, svc, s).detach(); 215 // thread per direction left no clean way to fail the second spawn:
216 // std::thread throws std::system_error when pthread_create is refused
217 // (NPROC limit, container pids cgroup, memory pressure), the first
218 // pump was already detached, and closing its sockets from here would
219 // race it -- so nothing caught the throw and it reached
220 // std::terminate, killing the relay over one pair it could not serve.
221 // Running one direction on this thread leaves exactly one spawn to
222 // guard, and a failure here has nothing running yet to race.
223 try
224 {
225 std::thread(Forward, s, svc).detach();
226 }
227 catch (const std::system_error& e)
228 {
229 fprintf(stderr, "rendezvous relay: cannot spawn forward thread (%s), pair dropped\n",
230 e.what());
231 CLOSESOCK(svc);
232 CLOSESOCK(s);
233 return;
234 }
235 Forward(svc, s);
203 } 236 }
204 else 237 else
205 { 238 {
@@ -225,6 +258,7 @@ bool RvRelayRun(unsigned short port)
225 if (bind(g_listen, (struct sockaddr*)&addr, sizeof(addr)) != 0) { CLOSESOCK(g_listen); return false; } 258 if (bind(g_listen, (struct sockaddr*)&addr, sizeof(addr)) != 0) { CLOSESOCK(g_listen); return false; }
226 if (listen(g_listen, 16) != 0) { CLOSESOCK(g_listen); return false; } 259 if (listen(g_listen, 16) != 0) { CLOSESOCK(g_listen); return false; }
227 260
261 int nSpawnFails = 0;
228 while (!g_stop) 262 while (!g_stop)
229 { 263 {
230 struct sockaddr_in cli; 264 struct sockaddr_in cli;
@@ -235,7 +269,27 @@ bool RvRelayRun(unsigned short port)
235 if (g_stop) break; 269 if (g_stop) break;
236 continue; 270 continue;
237 } 271 }
238 std::thread(HandleIncoming, s).detach(); 272 try
273 {
274 std::thread(HandleIncoming, s).detach();
275 nSpawnFails = 0;
276 }
277 catch (const std::system_error& e)
278 {
279 // A refused pthread_create is backpressure, not a reason to die:
280 // the condition behind it (NPROC, pids cgroup, memory) does not
281 // clear in milliseconds, and the connection count feeding it is
282 // attacker-influenced. Drop this one peer, stop accepting for a
283 // moment -- longer each time, capped -- and keep serving the
284 // connections already established.
285 fprintf(stderr, "rendezvous relay: cannot spawn connection thread (%s), connection dropped\n",
286 e.what());
287 CLOSESOCK(s);
288 nSpawnFails++;
289 int nWaitMs = nSpawnFails < 5 ? nSpawnFails * 1000 : 5000;
290 for (int i = 0; i < nWaitMs && !g_stop; i += 100)
291 std::this_thread::sleep_for(std::chrono::milliseconds(100));
292 }
239 } 293 }
240 CLOSESOCK(g_listen); 294 CLOSESOCK(g_listen);
241 g_listen = INVALID_SOCKET; 295 g_listen = INVALID_SOCKET;
@@ -323,17 +377,29 @@ bool RvServiceWaitPaired(RvSocket s, int nTimeoutSecs)
323} 377}
324 378
325RvSocket RvClientConnect(const char* relay_host, unsigned short port, 379RvSocket RvClientConnect(const char* relay_host, unsigned short port,
326 const unsigned char target_pubkey[32]) 380 const unsigned char target_pubkey[32],
381 RvClientFail* pFail)
327{ 382{
383 if (pFail) *pFail = RVC_FAIL_DIAL;
328 if (!RvInit()) return RV_INVALID; 384 if (!RvInit()) return RV_INVALID;
329 SOCKET s = ConnectTo(relay_host, port); 385 SOCKET s = ConnectTo(relay_host, port);
330 if (s == INVALID_SOCKET) return RV_INVALID; 386 if (s == INVALID_SOCKET) return RV_INVALID;
387 if (pFail) *pFail = RVC_FAIL_PAIR;
331 unsigned char hdr[33]; 388 unsigned char hdr[33];
332 hdr[0] = (unsigned char)'C'; 389 hdr[0] = (unsigned char)'C';
333 memcpy(hdr + 1, target_pubkey, 32); 390 memcpy(hdr + 1, target_pubkey, 32);
334 if (!WriteN(s, hdr, 33)) { CLOSESOCK(s); return RV_INVALID; } 391 if (!WriteN(s, hdr, 33)) { CLOSESOCK(s); return RV_INVALID; }
335 unsigned char paired = 0; 392 unsigned char paired = 0;
336 if (!ReadN(s, &paired, 1) || paired != 0x01) { CLOSESOCK(s); return RV_INVALID; } 393 if (!ReadN(s, &paired, 1)) { CLOSESOCK(s); return RV_INVALID; }
394 if (paired != 0x01)
395 {
396 // The relay answered and declined: HandleIncoming writes 0x00 and
397 // closes when no service is parked under this pubkey right now.
398 if (pFail) *pFail = RVC_FAIL_REFUSED;
399 CLOSESOCK(s);
400 return RV_INVALID;
401 }
402 if (pFail) *pFail = RVC_FAIL_NONE;
337 return (RvSocket)s; 403 return (RvSocket)s;
338} 404}
339 405