upleb.uk

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

summaryrefslogtreecommitdiff
path: root/relay/btfrv.cpp
diff options
context:
space:
mode:
authorbitflash-coin <bookofshamers@outlook.com>2026-08-04 06:34:32 -0700
committerbitflash-coin <bookofshamers@outlook.com>2026-08-04 06:34:32 -0700
commit5ce650382352c1503f40abf08129ada69402af55 (patch)
tree6a18198901f1e2c4933f1ac53c9365eec512bf63 /relay/btfrv.cpp
parentf34113291da7e5e3dc7d2468d4f9c212c05c584b (diff)
public commit
Diffstat (limited to 'relay/btfrv.cpp')
-rw-r--r--relay/btfrv.cpp392
1 files changed, 392 insertions, 0 deletions
diff --git a/relay/btfrv.cpp b/relay/btfrv.cpp
new file mode 100644
index 0000000..833c47c
--- /dev/null
+++ b/relay/btfrv.cpp
@@ -0,0 +1,392 @@
1// Copyright (c) 2026 Bitflash developers
2// Distributed under the MIT/X11 software license.
3//
4// Bitflash rendezvous transport. See btfrv.h.
5// Windows/Winsock implementation (Layer 6 will #ifdef this for Linux/BSD sockets).
6
7#include "btfrv.h"
8
9// Portable sockets: Winsock on Windows, BSD sockets on Linux/POSIX (for the
10// headless relay `bitflashd` that runs on the VPS).
11#ifdef _WIN32
12#include <winsock2.h>
13#include <ws2tcpip.h>
14#define CLOSESOCK closesocket
15#else
16#include <sys/socket.h>
17#include <netinet/in.h>
18#include <arpa/inet.h>
19#include <netdb.h>
20#include <unistd.h>
21#include <csignal>
22typedef int SOCKET;
23#define INVALID_SOCKET (-1)
24#define SOCKET_ERROR (-1)
25#define CLOSESOCK ::close
26#ifndef SD_BOTH
27#define SD_BOTH SHUT_RDWR
28#endif
29#endif
30
31#include <cstring>
32#include <cstdio>
33#include <string>
34#include <deque>
35#include <map>
36#include <thread>
37#include <mutex>
38#include <atomic>
39#include <chrono>
40
41namespace btf
42{
43
44static std::atomic<bool> g_stop(false);
45static SOCKET g_listen = INVALID_SOCKET;
46
47// Services that have registered and are waiting for a client, keyed by pubkey.
48// More than one registration per node, on purpose.
49//
50// This used to be one socket per pubkey, and a dial *consumed* it: the first
51// caller took the registration and everyone else was told "not found" until the
52// node noticed and registered again. A node re-registers only after its pairing
53// has been handed off, so between the two there is a window where it is
54// unreachable to the whole network. Measured from a node's own counters: half
55// of all outbound dials failed that way -- 15 of 30 in eleven minutes -- while
56// descriptors resolved 53 times out of 54 and the node's own registrations
57// paired 11 times out of 12. The rendezvous was not losing anybody; it was
58// letting one caller in at a time.
59//
60// A queue per key lets a node keep spare registrations parked, so a dial that
61// arrives while another is being paired finds the next one instead of a closed
62// door. Old nodes register exactly as before and simply keep one entry: the
63// wire protocol does not change, so a relay running this serves both.
64static const size_t MAX_WAITING_PER_KEY = 4;
65static std::map<std::string, std::deque<SOCKET> > g_waiting;
66static std::mutex g_mtx;
67
68// How often the reaper walks the waiting map.
69static const int REAP_INTERVAL_SECS = 60;
70
71bool RvInit()
72{
73 static bool done = false;
74 if (done) return true;
75#ifdef _WIN32
76 WSADATA wsa;
77 if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) return false;
78#else
79 signal(SIGPIPE, SIG_IGN); // don't die when writing to a closed peer
80#endif
81 done = true;
82 return true;
83}
84
85// ---- low-level helpers ----
86
87static bool ReadN(SOCKET s, void* buf, int n)
88{
89 char* p = (char*)buf;
90 int off = 0;
91 while (off < n)
92 {
93 int r = recv(s, p + off, n - off, 0);
94 if (r <= 0) return false;
95 off += r;
96 }
97 return true;
98}
99
100static bool WriteN(SOCKET s, const void* buf, int n)
101{
102 const char* p = (const char*)buf;
103 int off = 0;
104 while (off < n)
105 {
106 int r = send(s, p + off, n - off, 0);
107 if (r <= 0) return false;
108 off += r;
109 }
110 return true;
111}
112
113static SOCKET ConnectTo(const char* host, unsigned short port)
114{
115 struct addrinfo hints, *res = NULL;
116 memset(&hints, 0, sizeof(hints));
117 hints.ai_family = AF_INET;
118 hints.ai_socktype = SOCK_STREAM;
119 char portstr[16]; sprintf(portstr, "%u", (unsigned)port);
120 if (getaddrinfo(host, portstr, &hints, &res) != 0 || !res) return INVALID_SOCKET;
121 SOCKET s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
122 if (s == INVALID_SOCKET) { freeaddrinfo(res); return INVALID_SOCKET; }
123 if (connect(s, res->ai_addr, (int)res->ai_addrlen) != 0)
124 {
125 CLOSESOCK(s); freeaddrinfo(res); return INVALID_SOCKET;
126 }
127 freeaddrinfo(res);
128 return s;
129}
130
131// ---- liveness of a waiting registration ----
132
133// True when the far end of a registration socket is provably gone.
134//
135// A registered service sends nothing while it waits, so the socket should never
136// be readable. Readable therefore means one of two things, and recv tells them
137// apart: 0 is a clean close, negative is an error, and either way the
138// registration is worthless. Anything else -- including a service that somehow
139// has data pending -- is left alone, because guessing wrong here unregisters a
140// node that is perfectly fine.
141//
142// select() rather than MSG_DONTWAIT so the same code compiles on Windows, and
143// so the socket's blocking mode is never touched.
144static bool RegistrationDead(SOCKET s)
145{
146 fd_set rd;
147 FD_ZERO(&rd);
148 FD_SET(s, &rd);
149 struct timeval tv;
150 tv.tv_sec = 0;
151 tv.tv_usec = 0;
152 int r = select((int)(s + 1), &rd, NULL, NULL, &tv);
153 if (r <= 0)
154 return false; // not readable: still waiting, healthy
155 char c;
156 int n = recv(s, &c, 1, MSG_PEEK);
157 return (n <= 0);
158}
159
160// Drop registrations whose far end has gone away.
161//
162// Deliberately no expiry by age. Nodes older than v1.2.7 register once and then
163// block forever waiting to be dialled -- they never re-register, so a clock
164// based rule would quietly make every one of them unreachable, and being
165// blocked in recv they would never find out. Only provable death counts.
166static void ReapDeadRegistrations()
167{
168 int nBefore = 0, nReaped = 0;
169 {
170 std::lock_guard<std::mutex> lk(g_mtx);
171 std::map<std::string, std::deque<SOCKET> >::iterator it = g_waiting.begin();
172 while (it != g_waiting.end())
173 {
174 std::deque<SOCKET>& q = it->second;
175 nBefore += (int)q.size();
176 for (std::deque<SOCKET>::iterator jt = q.begin(); jt != q.end();)
177 {
178 if (RegistrationDead(*jt))
179 {
180 CLOSESOCK(*jt);
181 jt = q.erase(jt);
182 nReaped++;
183 }
184 else
185 ++jt;
186 }
187 if (q.empty())
188 g_waiting.erase(it++);
189 else
190 ++it;
191 }
192 }
193 if (nReaped > 0)
194 printf("reaper: dropped %d dead registration(s), %d waiting\n",
195 nReaped, nBefore - nReaped);
196 fflush(stdout);
197}
198
199static void ReaperThread()
200{
201 while (!g_stop)
202 {
203 for (int i = 0; i < REAP_INTERVAL_SECS && !g_stop; i++)
204 std::this_thread::sleep_for(std::chrono::seconds(1));
205 if (g_stop) break;
206 ReapDeadRegistrations();
207 }
208}
209
210// ---- relay side ----
211
212// Pump bytes from `from` to `to`. Owns closing `from`; shuts down `to` on exit
213// so the peer-direction thread unblocks and closes its own socket exactly once.
214static void Forward(SOCKET from, SOCKET to)
215{
216 char buf[32 * 1024];
217 for (;;)
218 {
219 int r = recv(from, buf, sizeof(buf), 0);
220 if (r <= 0) break;
221 if (!WriteN(to, buf, r)) break;
222 }
223 shutdown(to, SD_BOTH);
224 CLOSESOCK(from);
225}
226
227static void HandleIncoming(SOCKET s)
228{
229 unsigned char hdr[33];
230 if (!ReadN(s, hdr, 33)) { CLOSESOCK(s); return; }
231 char role = (char)hdr[0];
232 std::string pubkey((char*)hdr + 1, 32);
233
234 if (role == 'S')
235 {
236 // Register and wait; a future client will pair us.
237 std::lock_guard<std::mutex> lk(g_mtx);
238 std::deque<SOCKET>& q = g_waiting[pubkey];
239
240 // Drop registrations this node has already lost the far end of, rather
241 // than dropping the previous one on principle. The old code closed the
242 // prior registration on every new one, which is why a node could never
243 // hold a spare.
244 for (std::deque<SOCKET>::iterator it = q.begin(); it != q.end();)
245 {
246 if (RegistrationDead(*it)) { CLOSESOCK(*it); it = q.erase(it); }
247 else ++it;
248 }
249
250 // A ceiling, because a stranger can open these. Oldest goes first: it
251 // is the one most likely to be stale.
252 while (q.size() >= MAX_WAITING_PER_KEY)
253 {
254 CLOSESOCK(q.front());
255 q.pop_front();
256 }
257 q.push_back(s);
258 // leave the socket open in the queue; do not close here.
259 }
260 else if (role == 'C')
261 {
262 SOCKET svc = INVALID_SOCKET;
263 {
264 std::lock_guard<std::mutex> lk(g_mtx);
265 std::map<std::string, std::deque<SOCKET> >::iterator it = g_waiting.find(pubkey);
266 if (it != g_waiting.end())
267 {
268 std::deque<SOCKET>& q = it->second;
269 // Take the oldest one that is still alive, discarding the dead
270 // as we go. Checking before handing it over matters: pairing a
271 // client with a dead registration produces a tunnel that fails
272 // after both sides think they succeeded, which is the failure
273 // the dialling node reports as "tunnel to X failed" with
274 // nothing to explain it.
275 while (!q.empty())
276 {
277 SOCKET candidate = q.front();
278 q.pop_front();
279 if (RegistrationDead(candidate))
280 {
281 CLOSESOCK(candidate);
282 continue;
283 }
284 svc = candidate;
285 break;
286 }
287 if (q.empty())
288 g_waiting.erase(it);
289 }
290 }
291 if (svc == INVALID_SOCKET)
292 {
293 unsigned char no = 0x00;
294 WriteN(s, &no, 1);
295 CLOSESOCK(s);
296 return;
297 }
298 // Pair: tell both sides, then forward raw bytes bidirectionally.
299 unsigned char ok = 0x01;
300 bool a = WriteN(svc, &ok, 1);
301 bool b = WriteN(s, &ok, 1);
302 if (!a || !b) { CLOSESOCK(svc); CLOSESOCK(s); return; }
303 std::thread(Forward, s, svc).detach();
304 std::thread(Forward, svc, s).detach();
305 }
306 else
307 {
308 CLOSESOCK(s);
309 }
310}
311
312bool RvRelayRun(unsigned short port)
313{
314 if (!RvInit()) return false;
315 g_stop = false;
316
317 g_listen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
318 if (g_listen == INVALID_SOCKET) return false;
319 int yes = 1;
320 setsockopt(g_listen, SOL_SOCKET, SO_REUSEADDR, (char*)&yes, sizeof(yes));
321
322 struct sockaddr_in addr;
323 memset(&addr, 0, sizeof(addr));
324 addr.sin_family = AF_INET;
325 addr.sin_addr.s_addr = INADDR_ANY;
326 addr.sin_port = htons(port);
327 if (bind(g_listen, (struct sockaddr*)&addr, sizeof(addr)) != 0) { CLOSESOCK(g_listen); return false; }
328 if (listen(g_listen, 16) != 0) { CLOSESOCK(g_listen); return false; }
329
330 std::thread(ReaperThread).detach();
331
332 while (!g_stop)
333 {
334 struct sockaddr_in cli;
335 socklen_t len = sizeof(cli);
336 SOCKET s = accept(g_listen, (struct sockaddr*)&cli, &len);
337 if (s == INVALID_SOCKET)
338 {
339 if (g_stop) break;
340 continue;
341 }
342 std::thread(HandleIncoming, s).detach();
343 }
344 CLOSESOCK(g_listen);
345 g_listen = INVALID_SOCKET;
346 return true;
347}
348
349void RvRelayStop()
350{
351 g_stop = true;
352 if (g_listen != INVALID_SOCKET)
353 CLOSESOCK(g_listen); // unblock accept()
354}
355
356// ---- service / client helpers ----
357
358RvSocket RvServiceRegister(const char* relay_host, unsigned short port,
359 const unsigned char my_pubkey[32])
360{
361 if (!RvInit()) return RV_INVALID;
362 SOCKET s = ConnectTo(relay_host, port);
363 if (s == INVALID_SOCKET) return RV_INVALID;
364 unsigned char hdr[33];
365 hdr[0] = (unsigned char)'S';
366 memcpy(hdr + 1, my_pubkey, 32);
367 if (!WriteN(s, hdr, 33)) { CLOSESOCK(s); return RV_INVALID; }
368 unsigned char paired = 0;
369 if (!ReadN(s, &paired, 1) || paired != 0x01) { CLOSESOCK(s); return RV_INVALID; }
370 return (RvSocket)s;
371}
372
373RvSocket RvClientConnect(const char* relay_host, unsigned short port,
374 const unsigned char target_pubkey[32])
375{
376 if (!RvInit()) return RV_INVALID;
377 SOCKET s = ConnectTo(relay_host, port);
378 if (s == INVALID_SOCKET) return RV_INVALID;
379 unsigned char hdr[33];
380 hdr[0] = (unsigned char)'C';
381 memcpy(hdr + 1, target_pubkey, 32);
382 if (!WriteN(s, hdr, 33)) { CLOSESOCK(s); return RV_INVALID; }
383 unsigned char paired = 0;
384 if (!ReadN(s, &paired, 1) || paired != 0x01) { CLOSESOCK(s); return RV_INVALID; }
385 return (RvSocket)s;
386}
387
388bool RvReadN(RvSocket s, void* buf, int n) { return ReadN((SOCKET)s, buf, n); }
389bool RvWriteN(RvSocket s, const void* buf, int n) { return WriteN((SOCKET)s, buf, n); }
390void RvClose(RvSocket s) { if (s != RV_INVALID) CLOSESOCK((SOCKET)s); }
391
392} // namespace btf