upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBitflash <admin@bitflash.network>2026-08-13 16:07:04 -0300
committerBitflash <admin@bitflash.network>2026-08-13 16:07:04 -0300
commitea08342aba66400f588c6a03685a6f39927cb062 (patch)
tree3d7fa8c459fb3b493b878f68b85cb53b7c16659e
parent8e21e8ca0b8d50c29376bc2f0a89575baa477213 (diff)
parent5309170e567f4a8655b3b622e7b26b6d8b382942 (diff)
Merge pull request 'net: add onion-only btf dial mode' (#35) from managed-tor-two-node-smoke into main
-rw-r--r--docs/tor.md28
-rw-r--r--scripts/smoke-managed-tor-onion.ps1174
-rw-r--r--src/main_gui.cpp4
-rw-r--r--src/net.cpp25
-rw-r--r--src/net.h1
-rw-r--r--src/nostr.cpp11
-rw-r--r--src/proxy.cpp51
7 files changed, 283 insertions, 11 deletions
diff --git a/docs/tor.md b/docs/tor.md
index a408704..0016f8b 100644
--- a/docs/tor.md
+++ b/docs/tor.md
@@ -201,3 +201,31 @@ but an already-discovered onion-capable peer can be reached directly over Tor.
201 201
202Tor owns the private key under `HiddenServiceDir`; back it up if you want the 202Tor owns the private key under `HiddenServiceDir`; back it up if you want the
203onion name to stay stable. 203onion name to stay stable.
204
205## Release smoke test
206
207After building a Windows binary with managed Tor support, run the two-node smoke
208test before cutting a release:
209
210```powershell
211powershell -ExecutionPolicy Bypass -File scripts\smoke-managed-tor-onion.ps1 `
212 -Bitflash .\src\bitflash.exe `
213 -Tor C:\path\to\tor.exe
214```
215
216The script creates two temporary datadirs, starts node A with `-managedtor`,
217waits for it to publish its `.btf` identity and generated `.onion` endpoint,
218then starts node B with its own `-managedtor`, `-oniononly`, and
219`-connectbtf=<node-a>`. It passes only when node B logs that it connected to
220node A `via direct onion`; rendezvous fallback cannot satisfy the test.
221
222By default the script waits 120 seconds after node A's descriptor self-resolves
223before it starts node B. Tor can create the local `hostname` before the hidden
224service descriptor is visible enough for client circuits, and early dials often
225fail with SOCKS5 reply `0x01`. Override this with `-OnionWarmupSec N` when
226testing a slow or already-warmed Tor environment.
227
228This test still uses Nostr for descriptor discovery, because that is how nodes
229learn each other's signed `.btf` descriptors. It does not require a Bitflash
230rendezvous relay to carry the P2P connection: once the descriptor includes the
231signed onion endpoint, the actual node connection goes directly through Tor.
diff --git a/scripts/smoke-managed-tor-onion.ps1 b/scripts/smoke-managed-tor-onion.ps1
new file mode 100644
index 0000000..70658c7
--- /dev/null
+++ b/scripts/smoke-managed-tor-onion.ps1
@@ -0,0 +1,174 @@
1param(
2 [string]$Bitflash,
3 [string]$Tor,
4 [string]$WorkDir,
5 [int]$TimeoutSec = 600,
6 [int]$OnionWarmupSec = 120
7)
8
9$ErrorActionPreference = "Stop"
10
11function Resolve-RepoRoot {
12 $scriptDir = Split-Path -Parent $PSCommandPath
13 return (Resolve-Path (Join-Path $scriptDir "..")).Path
14}
15
16function Get-FreeTcpPort {
17 $listener = [Net.Sockets.TcpListener]::new([Net.IPAddress]::Loopback, 0)
18 $listener.Start()
19 try {
20 return ([Net.IPEndPoint]$listener.LocalEndpoint).Port
21 }
22 finally {
23 $listener.Stop()
24 }
25}
26
27function Wait-RegexInFile {
28 param(
29 [string]$Path,
30 [string]$Pattern,
31 [datetime]$Deadline,
32 [string]$Label
33 )
34
35 while ((Get-Date) -lt $Deadline) {
36 if (Test-Path $Path) {
37 $text = Get-Content -Raw -ErrorAction SilentlyContinue $Path
38 if ($null -ne $text) {
39 $m = [regex]::Match($text, $Pattern)
40 if ($m.Success) {
41 return $m
42 }
43 }
44 }
45 Start-Sleep -Milliseconds 500
46 }
47 throw "timed out waiting for $Label"
48}
49
50function Stop-TestProcesses {
51 param(
52 [System.Diagnostics.Process[]]$Processes,
53 [string[]]$DataDirKeys
54 )
55
56 foreach ($p in $Processes) {
57 try {
58 $p.Refresh()
59 if (!$p.HasExited) {
60 Stop-Process -Id $p.Id -Force
61 }
62 }
63 catch {}
64 }
65
66 foreach ($key in $DataDirKeys) {
67 if ([string]::IsNullOrWhiteSpace($key)) {
68 continue
69 }
70 try {
71 Get-CimInstance Win32_Process -Filter "name = 'tor.exe'" |
72 Where-Object { $_.CommandLine -like "*$key*" } |
73 ForEach-Object { Stop-Process -Id $_.ProcessId -Force }
74 }
75 catch {}
76 }
77}
78
79$repo = Resolve-RepoRoot
80if ([string]::IsNullOrWhiteSpace($Bitflash)) {
81 $Bitflash = Join-Path $repo "src\bitflash.exe"
82}
83if ([string]::IsNullOrWhiteSpace($Tor)) {
84 $candidates = @(
85 (Join-Path $repo "src\tor\tor.exe"),
86 (Join-Path $repo "Bitflash-$(Select-String -Path (Join-Path $repo 'Makefile') -Pattern '^VERSION[ ]*:=' | ForEach-Object { ($_.Line -split ':=')[1].Trim() })-windows\tor\tor.exe")
87 )
88 foreach ($candidate in $candidates) {
89 if (Test-Path $candidate) {
90 $Tor = $candidate
91 break
92 }
93 }
94}
95if ([string]::IsNullOrWhiteSpace($WorkDir)) {
96 $WorkDir = Join-Path $env:TEMP ("bitflash-managed-tor-two-node-" + (Get-Date -Format "yyyyMMddHHmmss"))
97}
98
99if (!(Test-Path $Bitflash)) {
100 throw "Bitflash binary not found: $Bitflash"
101}
102if (!(Test-Path $Tor)) {
103 throw "Tor binary not found. Pass -Tor or build the Windows-with-Tor package."
104}
105
106New-Item -ItemType Directory -Force -Path $WorkDir | Out-Null
107$dataA = Join-Path $WorkDir "node-a"
108$dataB = Join-Path $WorkDir "node-b"
109New-Item -ItemType Directory -Force -Path $dataA | Out-Null
110New-Item -ItemType Directory -Force -Path $dataB | Out-Null
111
112$portA = Get-FreeTcpPort
113$portB = Get-FreeTcpPort
114$deadline = (Get-Date).AddSeconds($TimeoutSec)
115$oldPath = $env:PATH
116$env:PATH = "C:\msys64\ucrt64\bin;C:\msys64\usr\bin;" + $env:PATH
117
118$procA = $null
119$procB = $null
120$dataKeys = @((Split-Path $WorkDir -Leaf))
121
122try {
123 $argsA = @(
124 "-datadir=$dataA",
125 "-port=$portA",
126 "-managedtor=$Tor",
127 "-nogui",
128 "-debug",
129 "-checkblocks=0"
130 )
131 $procA = Start-Process -FilePath $Bitflash -ArgumentList $argsA -WorkingDirectory (Split-Path $Bitflash -Parent) -PassThru -WindowStyle Hidden
132 $logA = Join-Path $dataA "debug.log"
133
134 $btfMatch = Wait-RegexInFile -Path $logA -Pattern "Nostr: node \.btf address = ([a-z2-7]+\.btf)" -Deadline $deadline -Label "node A .btf address"
135 $btfA = $btfMatch.Groups[1].Value
136
137 $onionMatch = Wait-RegexInFile -Path $logA -Pattern "Managed Tor hidden service ready: ([a-z2-7]{56}\.onion:\d+)" -Deadline $deadline -Label "node A managed onion"
138 $onionA = $onionMatch.Groups[1].Value
139
140 Wait-RegexInFile -Path $logA -Pattern "Nostr: \.btf self-resolve OK via .*" -Deadline $deadline -Label "node A descriptor self-resolve" | Out-Null
141 if ($OnionWarmupSec -gt 0) {
142 Write-Host "Waiting $OnionWarmupSec second(s) for Tor hidden-service descriptor propagation"
143 Start-Sleep -Seconds $OnionWarmupSec
144 }
145
146 $argsB = @(
147 "-datadir=$dataB",
148 "-port=$portB",
149 "-managedtor=$Tor",
150 "-oniononly",
151 "-connectbtf=$btfA",
152 "-nogui",
153 "-debug",
154 "-checkblocks=0"
155 )
156 $procB = Start-Process -FilePath $Bitflash -ArgumentList $argsB -WorkingDirectory (Split-Path $Bitflash -Parent) -PassThru -WindowStyle Hidden
157 $logB = Join-Path $dataB "debug.log"
158
159 Wait-RegexInFile -Path $logB -Pattern ("connected " + [regex]::Escape($btfA) + " via direct onion ") -Deadline $deadline -Label "node B direct onion connection to node A" | Out-Null
160
161 Write-Host "Managed Tor two-node onion smoke test passed"
162 Write-Host " node A .btf: $btfA"
163 Write-Host " node A onion: $onionA"
164 Write-Host " node A port: $portA"
165 Write-Host " node B port: $portB"
166 Write-Host " work dir: $WorkDir"
167}
168finally {
169 $env:PATH = $oldPath
170 $procs = @()
171 if ($null -ne $procA) { $procs += $procA }
172 if ($null -ne $procB) { $procs += $procB }
173 Stop-TestProcesses -Processes $procs -DataDirKeys $dataKeys
174}
diff --git a/src/main_gui.cpp b/src/main_gui.cpp
index deed471..f0fe903 100644
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -143,6 +143,7 @@ static void PrintUsage()
143 printf(" /socks=HOST:PORT (SOCKS5 proxy for Nostr, .btf relay, and onion dials)\n"); 143 printf(" /socks=HOST:PORT (SOCKS5 proxy for Nostr, .btf relay, and onion dials)\n");
144 printf(" /tor[=HOST:PORT] (Tor mode; default SOCKS5 proxy is 127.0.0.1:9050)\n"); 144 printf(" /tor[=HOST:PORT] (Tor mode; default SOCKS5 proxy is 127.0.0.1:9050)\n");
145 printf(" /managedtor[=PATH] (start Tor, create a hidden service, advertise its onion)\n"); 145 printf(" /managedtor[=PATH] (start Tor, create a hidden service, advertise its onion)\n");
146 printf(" /oniononly (do not fall back to rendezvous when a .btf onion dial fails)\n");
146 printf(" /btfseed=ADDRESS:ENCHEX (extra bootstrap peer, repeatable)\n"); 147 printf(" /btfseed=ADDRESS:ENCHEX (extra bootstrap peer, repeatable)\n");
147 printf("\n"); 148 printf("\n");
148 printf("Wallet:\n"); 149 printf("Wallet:\n");
@@ -278,6 +279,7 @@ static void ParseStartupArguments(int argc, char* argv[])
278 string btfConnect = argval2(argc, argv, "/connectbtf", "-connectbtf"); 279 string btfConnect = argval2(argc, argv, "/connectbtf", "-connectbtf");
279 if (!btfConnect.empty()) 280 if (!btfConnect.empty())
280 strBtfConnect = btfConnect; 281 strBtfConnect = btfConnect;
282 fBtfOnionOnly = arg(argc, argv, "/oniononly") || arg(argc, argv, "-oniononly");
281 283
282 string rvRelay = argval2(argc, argv, "/rvrelay", "-rvrelay"); 284 string rvRelay = argval2(argc, argv, "/rvrelay", "-rvrelay");
283 if (!rvRelay.empty()) 285 if (!rvRelay.empty())
@@ -360,6 +362,8 @@ static void ParseStartupArguments(int argc, char* argv[])
360 BtfSocks5ProxyName().c_str()); 362 BtfSocks5ProxyName().c_str());
361 } 363 }
362 } 364 }
365 if (fBtfOnionOnly && !BtfSocks5ProxyEnabled())
366 fprintf(stderr, "Warning: /oniononly without /tor, /managedtor, or /socks cannot dial .onion peers\n");
363 367
364 // /btfseed=ADDRESS:ENCHEX -- extra bootstrap peers, repeatable. Useful for 368 // /btfseed=ADDRESS:ENCHEX -- extra bootstrap peers, repeatable. Useful for
365 // testing the seed path and for private networks that ship no compiled list. 369 // testing the seed path and for private networks that ship no compiled list.
diff --git a/src/net.cpp b/src/net.cpp
index 6bc3c1c..997a2bd 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -65,6 +65,8 @@ deque<pair<int64, CInv> > vRelayExpiration;
65CCriticalSection cs_mapRelay; 65CCriticalSection cs_mapRelay;
66map<CInv, int64> mapAlreadyAskedFor; 66map<CInv, int64> mapAlreadyAskedFor;
67string strBtfConnect; // .btf peer to keep connected to (from /connectbtf) 67string strBtfConnect; // .btf peer to keep connected to (from /connectbtf)
68bool fBtfOnionOnly = false; // require direct onion transport for .btf peers
69static const int BTF_ONION_CONNECT_TIMEOUT_SECS = 120;
68 70
69int nPeersWatched = 0; 71int nPeersWatched = 0;
70int64 nBlocksReceived = 0; 72int64 nBlocksReceived = 0;
@@ -1022,7 +1024,8 @@ static CNode* ConnectNodeBtfTail(const string& strBtfAddr, const unsigned char p
1022 if (SplitHostPort(strOnionNorm, strOnionHost, nOnionPort)) 1024 if (SplitHostPort(strOnionNorm, strOnionHost, nOnionPort))
1023 { 1025 {
1024 BtfChurnNoteDialAttempt(strBtfAddr, strOnionNorm); 1026 BtfChurnNoteDialAttempt(strBtfAddr, strOnionNorm);
1025 SOCKET hOnionSocket = BtfConnectSocket(strOnionHost, nOnionPort, SOCK_ONION_PEER, 20); 1027 SOCKET hOnionSocket = BtfConnectSocket(strOnionHost, nOnionPort, SOCK_ONION_PEER,
1028 BTF_ONION_CONNECT_TIMEOUT_SECS);
1026 if (hOnionSocket != INVALID_SOCKET) 1029 if (hOnionSocket != INVALID_SOCKET)
1027 { 1030 {
1028 if (fDebug) 1031 if (fDebug)
@@ -1040,12 +1043,23 @@ static CNode* ConnectNodeBtfTail(const string& strBtfAddr, const unsigned char p
1040 return pnode; 1043 return pnode;
1041 } 1044 }
1042 if (fDebug) 1045 if (fDebug)
1043 LogPrint("net", "ConnectNodeBtf: direct onion to %s at %s failed, falling back to rendezvous\n", 1046 LogPrint("net", "ConnectNodeBtf: direct onion to %s at %s failed%s\n",
1044 strBtfAddr.c_str(), strOnionNorm.c_str()); 1047 strBtfAddr.c_str(), strOnionNorm.c_str(),
1048 fBtfOnionOnly ? "" : ", falling back to rendezvous");
1045 BtfChurnNoteDialResult(strBtfAddr, strOnionNorm, false); 1049 BtfChurnNoteDialResult(strBtfAddr, strOnionNorm, false);
1050 if (fBtfOnionOnly)
1051 return NULL;
1046 } 1052 }
1047 } 1053 }
1048 1054
1055 if (fBtfOnionOnly)
1056 {
1057 if (fDebug)
1058 LogPrint("net", "ConnectNodeBtf: onion-only mode refused rendezvous fallback for %s\n",
1059 strBtfAddr.c_str());
1060 return NULL;
1061 }
1062
1049 BtfChurnNoteDialAttempt(strBtfAddr, strMeeting); 1063 BtfChurnNoteDialAttempt(strBtfAddr, strMeeting);
1050 string strHost; 1064 string strHost;
1051 unsigned short nPort = 0; 1065 unsigned short nPort = 0;
@@ -2012,6 +2026,11 @@ bool StartNode(string& strError)
2012 // IP address, and port for the socket that is being bound 2026 // IP address, and port for the socket that is being bound
2013 int nRetryLimit = 15; 2027 int nRetryLimit = 15;
2014 struct sockaddr_in sockaddr = addrLocalHost.GetSockAddr(); 2028 struct sockaddr_in sockaddr = addrLocalHost.GetSockAddr();
2029 // Keep addrLocalHost as this node's advertised address, but listen on all
2030 // local interfaces. Managed Tor forwards hidden-service traffic to
2031 // 127.0.0.1:<port>, and binding only to the LAN address made that onion
2032 // endpoint unreachable even though the P2P listener was running.
2033 sockaddr.sin_addr.s_addr = INADDR_ANY;
2015 if (bind(hListenSocket, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) == SOCKET_ERROR) 2034 if (bind(hListenSocket, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) == SOCKET_ERROR)
2016 { 2035 {
2017 int nErr = WSAGetLastError(); 2036 int nErr = WSAGetLastError();
diff --git a/src/net.h b/src/net.h
index 9300169..984588b 100644
--- a/src/net.h
+++ b/src/net.h
@@ -33,6 +33,7 @@ CNode* ConnectNodeBtfResolved(const string& strBtfAddr, const string& strMeeting
33void ThreadBtfAccept(void* parg); 33void ThreadBtfAccept(void* parg);
34void ThreadBtfConnect(void* parg); 34void ThreadBtfConnect(void* parg);
35extern string strBtfConnect; 35extern string strBtfConnect;
36extern bool fBtfOnionOnly;
36// Bootstrap seeds added at runtime with /btfseed=ADDRESS:ENCHEX (repeatable). 37// Bootstrap seeds added at runtime with /btfseed=ADDRESS:ENCHEX (repeatable).
37// The compiled-in list lives in net.cpp; these are appended to it. 38// The compiled-in list lives in net.cpp; these are appended to it.
38extern std::vector<std::pair<std::string, std::string> > vBtfExtraSeeds; 39extern std::vector<std::pair<std::string, std::string> > vBtfExtraSeeds;
diff --git a/src/nostr.cpp b/src/nostr.cpp
index 0c1b4b6..35fde43 100644
--- a/src/nostr.cpp
+++ b/src/nostr.cpp
@@ -1764,11 +1764,12 @@ void ThreadNostrSeed(void* parg)
1764 LogPrint("nostr", "Nostr: node pubkey = %s\n", key.PubKeyHex().c_str()); 1764 LogPrint("nostr", "Nostr: node pubkey = %s\n", key.PubKeyHex().c_str());
1765 LogPrint("nostr", "Nostr: node .btf address = %s\n", key.BtfAddress().c_str()); 1765 LogPrint("nostr", "Nostr: node .btf address = %s\n", key.BtfAddress().c_str());
1766 1766
1767 // The self-test below publishes a real descriptor, and PublishDescriptor now 1767 // The self-test below publishes a real descriptor. Rendezvous-only nodes
1768 // refuses to publish before ThreadBtfAccept has registered a rendezvous. Give 1768 // need ThreadBtfAccept to register first; Tor nodes can publish as soon as
1769 // registration a bounded head start so the test measures the publish/resolve 1769 // their signed onion endpoint exists, with rendezvous marked pending.
1770 // chain instead of losing the race against it. 1770 for (int i = 0; i < 60 && !fShutdown &&
1771 for (int i = 0; i < 60 && !fShutdown && BtfActiveRelay().empty(); i++) 1771 BtfActiveRelay().empty() &&
1772 BtfLocalOnionEndpoint().empty(); i++)
1772 Sleep(1000); 1773 Sleep(1000);
1773 1774
1774 // One-time self-test of the rendezvous: publish our descriptor to a relay, 1775 // One-time self-test of the rendezvous: publish our descriptor to a relay,
diff --git a/src/proxy.cpp b/src/proxy.cpp
index 7d30911..eed054c 100644
--- a/src/proxy.cpp
+++ b/src/proxy.cpp
@@ -13,6 +13,7 @@ static bool g_fTorProxy = false;
13static std::string g_socks5Host; 13static std::string g_socks5Host;
14static unsigned short g_socks5Port = 0; 14static unsigned short g_socks5Port = 0;
15static const char* DEFAULT_TOR_SOCKS5_PROXY = "127.0.0.1:9050"; 15static const char* DEFAULT_TOR_SOCKS5_PROXY = "127.0.0.1:9050";
16static const int TOR_CONNECT_TIMEOUT_SECS = 120;
16 17
17static void SetSocketTimeout(SOCKET s, int nTimeoutSecs) 18static void SetSocketTimeout(SOCKET s, int nTimeoutSecs)
18{ 19{
@@ -297,10 +298,25 @@ static bool Socks5Connect(SOCKET s, const std::string& destHost,
297 298
298 unsigned char hello[3] = { 0x05, 0x01, 0x00 }; // SOCKS5, one method, no auth 299 unsigned char hello[3] = { 0x05, 0x01, 0x00 }; // SOCKS5, one method, no auth
299 if (!WriteN(s, hello, sizeof(hello))) 300 if (!WriteN(s, hello, sizeof(hello)))
301 {
302 error("SOCKS5 hello write failed for %s:%u\n",
303 destHost.c_str(), (unsigned)destPort);
300 return false; 304 return false;
305 }
301 unsigned char choice[2] = { 0, 0 }; 306 unsigned char choice[2] = { 0, 0 };
302 if (!ReadN(s, choice, sizeof(choice)) || choice[0] != 0x05 || choice[1] != 0x00) 307 if (!ReadN(s, choice, sizeof(choice)))
308 {
309 error("SOCKS5 method selection read failed for %s:%u\n",
310 destHost.c_str(), (unsigned)destPort);
311 return false;
312 }
313 if (choice[0] != 0x05 || choice[1] != 0x00)
314 {
315 error("SOCKS5 proxy rejected no-auth method for %s:%u (version=0x%02x method=0x%02x)\n",
316 destHost.c_str(), (unsigned)destPort,
317 (unsigned)choice[0], (unsigned)choice[1]);
303 return false; 318 return false;
319 }
304 320
305 std::vector<unsigned char> req; 321 std::vector<unsigned char> req;
306 req.reserve(7 + destHost.size()); 322 req.reserve(7 + destHost.size());
@@ -313,11 +329,25 @@ static bool Socks5Connect(SOCKET s, const std::string& destHost,
313 req.push_back((unsigned char)(destPort >> 8)); 329 req.push_back((unsigned char)(destPort >> 8));
314 req.push_back((unsigned char)(destPort & 0xff)); 330 req.push_back((unsigned char)(destPort & 0xff));
315 if (!WriteN(s, &req[0], (int)req.size())) 331 if (!WriteN(s, &req[0], (int)req.size()))
332 {
333 error("SOCKS5 CONNECT request write failed for %s:%u\n",
334 destHost.c_str(), (unsigned)destPort);
316 return false; 335 return false;
336 }
317 337
318 unsigned char rep[4] = {0,0,0,0}; 338 unsigned char rep[4] = {0,0,0,0};
319 if (!ReadN(s, rep, sizeof(rep)) || rep[0] != 0x05) 339 if (!ReadN(s, rep, sizeof(rep)))
340 {
341 error("SOCKS5 CONNECT reply read failed for %s:%u\n",
342 destHost.c_str(), (unsigned)destPort);
320 return false; 343 return false;
344 }
345 if (rep[0] != 0x05)
346 {
347 error("SOCKS5 CONNECT reply has invalid version 0x%02x for %s:%u\n",
348 (unsigned)rep[0], destHost.c_str(), (unsigned)destPort);
349 return false;
350 }
321 if (rep[1] != 0x00) 351 if (rep[1] != 0x00)
322 { 352 {
323 error("SOCKS5 CONNECT to %s:%u failed with reply 0x%02x\n", 353 error("SOCKS5 CONNECT to %s:%u failed with reply 0x%02x\n",
@@ -335,10 +365,20 @@ static bool Socks5Connect(SOCKET s, const std::string& destHost,
335 nAddr = len; 365 nAddr = len;
336 } 366 }
337 else 367 else
368 {
369 error("SOCKS5 CONNECT reply has invalid address type 0x%02x for %s:%u\n",
370 (unsigned)rep[3], destHost.c_str(), (unsigned)destPort);
338 return false; 371 return false;
372 }
339 373
340 std::vector<unsigned char> discard(nAddr + 2); 374 std::vector<unsigned char> discard(nAddr + 2);
341 return ReadN(s, &discard[0], (int)discard.size()); 375 if (!ReadN(s, &discard[0], (int)discard.size()))
376 {
377 error("SOCKS5 CONNECT bind address read failed for %s:%u\n",
378 destHost.c_str(), (unsigned)destPort);
379 return false;
380 }
381 return true;
342} 382}
343 383
344SOCKET BtfConnectSocket(const std::string& destHost, unsigned short destPort, 384SOCKET BtfConnectSocket(const std::string& destHost, unsigned short destPort,
@@ -347,9 +387,11 @@ SOCKET BtfConnectSocket(const std::string& destHost, unsigned short destPort,
347 std::string proxyHost; 387 std::string proxyHost;
348 unsigned short proxyPort = 0; 388 unsigned short proxyPort = 0;
349 bool fProxy = false; 389 bool fProxy = false;
390 bool fTorProxy = false;
350 CRITICAL_BLOCK(cs_socks5) 391 CRITICAL_BLOCK(cs_socks5)
351 { 392 {
352 fProxy = g_fSocks5Proxy; 393 fProxy = g_fSocks5Proxy;
394 fTorProxy = g_fTorProxy;
353 proxyHost = g_socks5Host; 395 proxyHost = g_socks5Host;
354 proxyPort = g_socks5Port; 396 proxyPort = g_socks5Port;
355 } 397 }
@@ -365,6 +407,9 @@ SOCKET BtfConnectSocket(const std::string& destHost, unsigned short destPort,
365 return ConnectDirect(destHost, destPort, nSockSite, nTimeoutSecs); 407 return ConnectDirect(destHost, destPort, nSockSite, nTimeoutSecs);
366 } 408 }
367 409
410 if (fTorProxy && nTimeoutSecs > 0 && nTimeoutSecs < TOR_CONNECT_TIMEOUT_SECS)
411 nTimeoutSecs = TOR_CONNECT_TIMEOUT_SECS;
412
368 SOCKET s = ConnectDirect(proxyHost, proxyPort, nSockSite, nTimeoutSecs); 413 SOCKET s = ConnectDirect(proxyHost, proxyPort, nSockSite, nTimeoutSecs);
369 if (s == INVALID_SOCKET) 414 if (s == INVALID_SOCKET)
370 return INVALID_SOCKET; 415 return INVALID_SOCKET;