upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorNsable-sh <nsable-sh@noreply.git.bitflash.network>2026-08-11 16:03:12 -0300
committerNsable-sh <nsable-sh@noreply.git.bitflash.network>2026-08-11 16:03:12 -0300
commit6092e1e7da199d6c15b9548d5d4cebcf8babfdcf (patch)
treec5870b95d13abe80d1350f0a028f6f658b61b3ae /src
parent066379ebcb1dac720221fe1a1841fa6f59df21d9 (diff)
net: add Tor mode for outbound discovery
Diffstat (limited to 'src')
-rw-r--r--src/main_gui.cpp25
-rw-r--r--src/proxy.cpp50
-rw-r--r--src/proxy.h3
-rw-r--r--src/readme.txt3
-rw-r--r--src/selftest.cpp25
5 files changed, 100 insertions, 6 deletions
diff --git a/src/main_gui.cpp b/src/main_gui.cpp
index 90a6050..5194023 100644
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -138,6 +138,7 @@ static void PrintUsage()
138 printf("Network:\n"); 138 printf("Network:\n");
139 printf(" /port=N (P2P listen port, default 8433)\n"); 139 printf(" /port=N (P2P listen port, default 8433)\n");
140 printf(" /socks=HOST:PORT (SOCKS5 proxy for outbound Nostr and .btf relay dials)\n"); 140 printf(" /socks=HOST:PORT (SOCKS5 proxy for outbound Nostr and .btf relay dials)\n");
141 printf(" /tor[=HOST:PORT] (Tor mode; default SOCKS5 proxy is 127.0.0.1:9050)\n");
141 printf(" /btfseed=ADDRESS:ENCHEX (extra bootstrap peer, repeatable)\n"); 142 printf(" /btfseed=ADDRESS:ENCHEX (extra bootstrap peer, repeatable)\n");
142 printf("\n"); 143 printf("\n");
143 printf("Wallet:\n"); 144 printf("Wallet:\n");
@@ -285,16 +286,30 @@ static void ParseStartupArguments(int argc, char* argv[])
285 if (!announceRelay.empty()) 286 if (!announceRelay.empty())
286 strBtfAnnounceRelay = announceRelay; 287 strBtfAnnounceRelay = announceRelay;
287 288
288 string socksProxy = argval2(argc, argv, "/socks", "-socks"); 289 bool fTorMode = arg(argc, argv, "/tor") || arg(argc, argv, "-tor");
289 if (!socksProxy.empty()) 290 if (fTorMode)
290 { 291 {
292 string torProxy = argval2(argc, argv, "/tor", "-tor");
291 string err; 293 string err;
292 if (!BtfSetSocks5Proxy(socksProxy, err)) 294 if (!BtfEnableTorProxy(torProxy, err))
293 fprintf(stderr, "Ignoring /socks=%s: %s\n", socksProxy.c_str(), err.c_str()); 295 fprintf(stderr, "Ignoring /tor=%s: %s\n", torProxy.c_str(), err.c_str());
294 else 296 else
295 fprintf(stderr, "SOCKS5 proxy enabled for outbound discovery/relay dials: %s\n", 297 fprintf(stderr, "Tor mode enabled through SOCKS5 proxy %s\n",
296 BtfSocks5ProxyName().c_str()); 298 BtfSocks5ProxyName().c_str());
297 } 299 }
300 else
301 {
302 string socksProxy = argval2(argc, argv, "/socks", "-socks");
303 if (!socksProxy.empty())
304 {
305 string err;
306 if (!BtfSetSocks5Proxy(socksProxy, err))
307 fprintf(stderr, "Ignoring /socks=%s: %s\n", socksProxy.c_str(), err.c_str());
308 else
309 fprintf(stderr, "SOCKS5 proxy enabled for outbound discovery/relay dials: %s\n",
310 BtfSocks5ProxyName().c_str());
311 }
312 }
298 313
299 // /btfseed=ADDRESS:ENCHEX -- extra bootstrap peers, repeatable. Useful for 314 // /btfseed=ADDRESS:ENCHEX -- extra bootstrap peers, repeatable. Useful for
300 // testing the seed path and for private networks that ship no compiled list. 315 // testing the seed path and for private networks that ship no compiled list.
diff --git a/src/proxy.cpp b/src/proxy.cpp
index d7dc324..7d30911 100644
--- a/src/proxy.cpp
+++ b/src/proxy.cpp
@@ -9,8 +9,10 @@
9 9
10static CCriticalSection cs_socks5; 10static CCriticalSection cs_socks5;
11static bool g_fSocks5Proxy = false; 11static bool g_fSocks5Proxy = false;
12static bool g_fTorProxy = false;
12static std::string g_socks5Host; 13static std::string g_socks5Host;
13static unsigned short g_socks5Port = 0; 14static unsigned short g_socks5Port = 0;
15static const char* DEFAULT_TOR_SOCKS5_PROXY = "127.0.0.1:9050";
14 16
15static void SetSocketTimeout(SOCKET s, int nTimeoutSecs) 17static void SetSocketTimeout(SOCKET s, int nTimeoutSecs)
16{ 18{
@@ -67,6 +69,24 @@ static bool ValidProxyIpv6HostChar(char c)
67 return isxdigit((unsigned char)c) || c == ':' || c == '.'; 69 return isxdigit((unsigned char)c) || c == ':' || c == '.';
68} 70}
69 71
72static std::string LowerProxyHost(std::string host)
73{
74 if (!host.empty() && host[host.size() - 1] == '.')
75 host.erase(host.size() - 1);
76 for (size_t i = 0; i < host.size(); i++)
77 host[i] = (char)tolower((unsigned char)host[i]);
78 return host;
79}
80
81bool BtfIsTorOnionHost(const std::string& host)
82{
83 std::string lower = LowerProxyHost(host);
84 static const char* suffix = ".onion";
85 size_t suffixLen = strlen(suffix);
86 return lower.size() > suffixLen &&
87 lower.compare(lower.size() - suffixLen, suffixLen, suffix) == 0;
88}
89
70static bool ParsePort(const std::string& port, unsigned short& portOut, 90static bool ParsePort(const std::string& port, unsigned short& portOut,
71 std::string& errOut) 91 std::string& errOut)
72{ 92{
@@ -176,7 +196,8 @@ bool BtfParseSocks5Proxy(const std::string& spec, std::string& hostOut,
176 return true; 196 return true;
177} 197}
178 198
179bool BtfSetSocks5Proxy(const std::string& spec, std::string& errOut) 199static bool SetSocks5Proxy(const std::string& spec, bool fTorMode,
200 std::string& errOut)
180{ 201{
181 std::string host; 202 std::string host;
182 unsigned short port = 0; 203 unsigned short port = 0;
@@ -187,15 +208,27 @@ bool BtfSetSocks5Proxy(const std::string& spec, std::string& errOut)
187 g_socks5Host = host; 208 g_socks5Host = host;
188 g_socks5Port = port; 209 g_socks5Port = port;
189 g_fSocks5Proxy = true; 210 g_fSocks5Proxy = true;
211 g_fTorProxy = fTorMode;
190 } 212 }
191 return true; 213 return true;
192} 214}
193 215
216bool BtfSetSocks5Proxy(const std::string& spec, std::string& errOut)
217{
218 return SetSocks5Proxy(spec, false, errOut);
219}
220
221bool BtfEnableTorProxy(const std::string& spec, std::string& errOut)
222{
223 return SetSocks5Proxy(spec.empty() ? DEFAULT_TOR_SOCKS5_PROXY : spec, true, errOut);
224}
225
194void BtfClearSocks5Proxy() 226void BtfClearSocks5Proxy()
195{ 227{
196 CRITICAL_BLOCK(cs_socks5) 228 CRITICAL_BLOCK(cs_socks5)
197 { 229 {
198 g_fSocks5Proxy = false; 230 g_fSocks5Proxy = false;
231 g_fTorProxy = false;
199 g_socks5Host.clear(); 232 g_socks5Host.clear();
200 g_socks5Port = 0; 233 g_socks5Port = 0;
201 } 234 }
@@ -208,6 +241,13 @@ bool BtfSocks5ProxyEnabled()
208 return false; 241 return false;
209} 242}
210 243
244bool BtfTorProxyEnabled()
245{
246 CRITICAL_BLOCK(cs_socks5)
247 return g_fSocks5Proxy && g_fTorProxy;
248 return false;
249}
250
211std::string BtfSocks5ProxyName() 251std::string BtfSocks5ProxyName()
212{ 252{
213 CRITICAL_BLOCK(cs_socks5) 253 CRITICAL_BLOCK(cs_socks5)
@@ -315,7 +355,15 @@ SOCKET BtfConnectSocket(const std::string& destHost, unsigned short destPort,
315 } 355 }
316 356
317 if (!fProxy) 357 if (!fProxy)
358 {
359 if (BtfIsTorOnionHost(destHost))
360 {
361 error("Refusing direct .onion connection to %s:%u; use /tor or /socks\n",
362 destHost.c_str(), (unsigned)destPort);
363 return INVALID_SOCKET;
364 }
318 return ConnectDirect(destHost, destPort, nSockSite, nTimeoutSecs); 365 return ConnectDirect(destHost, destPort, nSockSite, nTimeoutSecs);
366 }
319 367
320 SOCKET s = ConnectDirect(proxyHost, proxyPort, nSockSite, nTimeoutSecs); 368 SOCKET s = ConnectDirect(proxyHost, proxyPort, nSockSite, nTimeoutSecs);
321 if (s == INVALID_SOCKET) 369 if (s == INVALID_SOCKET)
diff --git a/src/proxy.h b/src/proxy.h
index b5e69c8..6429cf2 100644
--- a/src/proxy.h
+++ b/src/proxy.h
@@ -21,8 +21,11 @@
21bool BtfParseSocks5Proxy(const std::string& spec, std::string& hostOut, 21bool BtfParseSocks5Proxy(const std::string& spec, std::string& hostOut,
22 unsigned short& portOut, std::string& errOut); 22 unsigned short& portOut, std::string& errOut);
23bool BtfSetSocks5Proxy(const std::string& spec, std::string& errOut); 23bool BtfSetSocks5Proxy(const std::string& spec, std::string& errOut);
24bool BtfEnableTorProxy(const std::string& spec, std::string& errOut);
24void BtfClearSocks5Proxy(); 25void BtfClearSocks5Proxy();
25bool BtfSocks5ProxyEnabled(); 26bool BtfSocks5ProxyEnabled();
27bool BtfTorProxyEnabled();
28bool BtfIsTorOnionHost(const std::string& host);
26std::string BtfSocks5ProxyName(); 29std::string BtfSocks5ProxyName();
27 30
28SOCKET BtfConnectSocket(const std::string& destHost, unsigned short destPort, 31SOCKET BtfConnectSocket(const std::string& destHost, unsigned short destPort,
diff --git a/src/readme.txt b/src/readme.txt
index 1fa77df..794fe20 100644
--- a/src/readme.txt
+++ b/src/readme.txt
@@ -46,6 +46,9 @@ Command-line options (in addition to the original ones)
46 route outbound Nostr and .btf rendezvous dials through a 46 route outbound Nostr and .btf rendezvous dials through a
47 SOCKS5 proxy, e.g. /socks=127.0.0.1:9050 for local Tor 47 SOCKS5 proxy, e.g. /socks=127.0.0.1:9050 for local Tor
48 or /socks=[::1]:9050 for an IPv6 loopback proxy 48 or /socks=[::1]:9050 for an IPv6 loopback proxy
49 /tor[=HOST:PORT]
50 Tor mode for outbound Nostr and .btf rendezvous dials;
51 defaults to the local Tor SOCKS5 proxy at 127.0.0.1:9050
49 /solomine mine without requiring a connected peer (local testing) 52 /solomine mine without requiring a connected peer (local testing)
50 /operator operator mode (run pool server) 53 /operator operator mode (run pool server)
51 /participant=ADDR 54 /participant=ADDR
diff --git a/src/selftest.cpp b/src/selftest.cpp
index aa4e528..cca5335 100644
--- a/src/selftest.cpp
+++ b/src/selftest.cpp
@@ -1474,6 +1474,11 @@ static int RunSocks5ProxySelfTest()
1474 "rejects overflowing port") ? 0 : 1; 1474 "rejects overflowing port") ? 0 : 1;
1475 nFail += Check(!BtfParseSocks5Proxy("user:pass@localhost:9050", host, port, err), 1475 nFail += Check(!BtfParseSocks5Proxy("user:pass@localhost:9050", host, port, err),
1476 "rejects unsupported authenticated proxy syntax") ? 0 : 1; 1476 "rejects unsupported authenticated proxy syntax") ? 0 : 1;
1477 nFail += Check(BtfIsTorOnionHost("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd.onion") &&
1478 BtfIsTorOnionHost("Relay.Example.ONION.") &&
1479 !BtfIsTorOnionHost("relay.example") &&
1480 !BtfIsTorOnionHost(".onion"),
1481 "detects .onion hosts without accepting lookalikes") ? 0 : 1;
1477 1482
1478 BtfClearSocks5Proxy(); 1483 BtfClearSocks5Proxy();
1479 nFail += Check(!BtfSocks5ProxyEnabled(), "starts disabled after clear") ? 0 : 1; 1484 nFail += Check(!BtfSocks5ProxyEnabled(), "starts disabled after clear") ? 0 : 1;
@@ -1490,6 +1495,26 @@ static int RunSocks5ProxySelfTest()
1490 "enables bracketed IPv6 proxy endpoint") ? 0 : 1; 1495 "enables bracketed IPv6 proxy endpoint") ? 0 : 1;
1491 BtfClearSocks5Proxy(); 1496 BtfClearSocks5Proxy();
1492 1497
1498 nFail += Check(BtfEnableTorProxy("", err) &&
1499 BtfSocks5ProxyEnabled() &&
1500 BtfTorProxyEnabled() &&
1501 BtfSocks5ProxyName() == "127.0.0.1:9050",
1502 "enables Tor mode on the default local SOCKS5 endpoint") ? 0 : 1;
1503 BtfClearSocks5Proxy();
1504
1505 nFail += Check(BtfEnableTorProxy("[::1]:9050", err) &&
1506 BtfSocks5ProxyEnabled() &&
1507 BtfTorProxyEnabled() &&
1508 BtfSocks5ProxyName() == "[::1]:9050",
1509 "enables Tor mode on a custom IPv6 endpoint") ? 0 : 1;
1510 BtfClearSocks5Proxy();
1511
1512 nFail += Check(BtfSetSocks5Proxy("127.0.0.1:9051", err) &&
1513 BtfSocks5ProxyEnabled() &&
1514 !BtfTorProxyEnabled(),
1515 "plain SOCKS5 mode is distinct from Tor mode") ? 0 : 1;
1516 BtfClearSocks5Proxy();
1517
1493 std::string handshakeErr; 1518 std::string handshakeErr;
1494 bool fHandshakeOk = RunSocks5HandshakeProbe(handshakeErr); 1519 bool fHandshakeOk = RunSocks5HandshakeProbe(handshakeErr);
1495 nFail += Check(fHandshakeOk, handshakeErr.empty() ? 1520 nFail += Check(fHandshakeOk, handshakeErr.empty() ?