From d2b86473a267c960a1f0c012a983176e5d962187 Mon Sep 17 00:00:00 2001 From: Nsable-sh Date: Thu, 13 Aug 2026 11:11:59 -0300 Subject: net: add signed direct onion peer endpoints --- src/nostr.cpp | 76 ++++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 62 insertions(+), 14 deletions(-) (limited to 'src/nostr.cpp') diff --git a/src/nostr.cpp b/src/nostr.cpp index d286767..0c1b4b6 100644 --- a/src/nostr.cpp +++ b/src/nostr.cpp @@ -112,6 +112,8 @@ vector vBtfMeetingRelays = { }; string strBtfActiveRelay; static CCriticalSection cs_activeRelay; +static string strBtfOnionEndpoint; +static CCriticalSection cs_onionEndpoint; // Only the rendezvous we actually registered at (BtfSetActiveRelay, called from // ThreadBtfAccept once registration succeeds). Empty until then, on purpose: it @@ -133,6 +135,26 @@ void BtfSetActiveRelay(const string& relay) strBtfActiveRelay = relay; } +bool BtfSetLocalOnionEndpoint(const string& endpoint, string& errOut) +{ + string norm; + if (!btf::NormalizeOnionEndpoint(endpoint, norm)) + { + errOut = "expected v3 onion endpoint HOST.onion:PORT"; + return false; + } + CRITICAL_BLOCK(cs_onionEndpoint) + strBtfOnionEndpoint = norm; + return true; +} + +string BtfLocalOnionEndpoint() +{ + CRITICAL_BLOCK(cs_onionEndpoint) + return strBtfOnionEndpoint; + return ""; +} + // A relay this node advertises on Nostr for others to discover (/announcerelay). string strBtfAnnounceRelay; // Relays learned from other operators' Nostr announcements. @@ -370,10 +392,16 @@ std::string BtfLocalDescriptor() string meeting; CRITICAL_BLOCK(cs_activeRelay) meeting = strBtfActiveRelay; + string onion = BtfLocalOnionEndpoint(); if (meeting.empty()) - return ""; + { + if (onion.empty()) + return ""; + meeting = "rendezvous-pending"; + } return btf::SignDescriptor(g_nostrKey.ctx, g_nostrKey.seckey, - g_nostrKey.EncPubHex(), meeting, (uint64_t)GetTime()); + g_nostrKey.EncPubHex(), meeting, (uint64_t)GetTime(), + onion); } // The shared secp256k1 context, so net.cpp can verify descriptors that arrive @@ -806,17 +834,21 @@ static void PublishDescriptor(CWebSocket& ws, CNostrKey& key) // meeting_node is the rendezvous relay this node's hidden service // (ThreadBtfAccept in net.cpp) is registered at -- where clients dial us. string meeting_node = BtfActiveRelay(); + string onion = BtfLocalOnionEndpoint(); if (meeting_node.empty()) { - // Not registered anywhere yet. Publishing now would advertise a meeting - // node we can't be reached at, and peers would waste dials on it. The - // ThreadNostrSeed loop republishes on every pass, so skipping costs - // nothing but the delay until ThreadBtfAccept registers. - LogPrint("nostr", "Nostr: skipping descriptor publish, no rendezvous registered yet\n"); - return; + if (onion.empty()) + { + // Not registered anywhere yet, and no direct onion endpoint exists. + // Publishing now would advertise a meeting node we can't be reached at. + LogPrint("nostr", "Nostr: skipping descriptor publish, no rendezvous registered yet\n"); + return; + } + meeting_node = "rendezvous-pending"; } string desc = btf::SignDescriptor(key.ctx, key.seckey, key.EncPubHex(), - meeting_node, (uint64_t)GetTime()); + meeting_node, (uint64_t)GetTime(), + onion); if (desc.empty()) return; json tags = json::array({ json::array({ "d", BTF_DESC_DTAG }) }); @@ -1018,7 +1050,7 @@ static void HandlePoolAnnouncement(const json& ev) // Resolve a `.btf` address via a relay: fetch the descriptor published by that // address's key and verify it self-certifies under the decoded pubkey. static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr, - btf::Descriptor& out) + btf::Descriptor& out, string* rawOut = NULL) { unsigned char pk[32]; if (!btf::ParseAddress(btfAddr, pk)) @@ -1045,7 +1077,11 @@ static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr, const json& ev = j[2]; if (ev.contains("content") && btf::VerifyDescriptor(ctx, ev["content"].get(), pk, out)) + { + if (rawOut) + *rawOut = ev["content"].get(); return true; + } } else if (t == "EOSE") break; @@ -1057,8 +1093,10 @@ static bool ResolveDescriptor(CWebSocket& ws, void* ctx, const string& btfAddr, // public discovery relays for the owner's self-certified descriptor. Used by // ConnectNodeBtf in net.cpp. bool BtfResolve(const std::string& btfAddr, std::string& meetingHostPort, - unsigned char enc_pub[32]) + unsigned char enc_pub[32], std::string* onionHostPort) { + if (onionHostPort) + onionHostPort->clear(); if (!EnsureNostrKey()) return false; for (int i = 0; i < nNostrRelays && !fShutdown; i++) @@ -1076,10 +1114,15 @@ bool BtfResolve(const std::string& btfAddr, std::string& meetingHostPort, } lease.fOk = true; if (d.meeting_node.empty() || d.meeting_node == "rendezvous-pending") - continue; // descriptor predates the owner's relay config + { + if (!onionHostPort || d.onion.empty()) + continue; // caller needs rendezvous, or descriptor has no direct path + } if (!HexDecode(d.enc, enc_pub, 32)) continue; meetingHostPort = d.meeting_node; + if (onionHostPort) + *onionHostPort = d.onion; return true; } CATCH_PRINT_EXCEPTION("BtfResolve") @@ -1168,12 +1211,15 @@ bool BtfResolveMany(const std::vector& btfAddrs, btf::Descriptor d; if (!btf::VerifyDescriptor(g_nostrKey.ctx, ev["content"].get(), pk, d)) continue; - if (d.meeting_node.empty() || d.meeting_node == "rendezvous-pending") + if ((d.meeting_node.empty() || d.meeting_node == "rendezvous-pending") && + d.onion.empty()) continue; BtfResolvedPeer rp; if (!HexDecode(d.enc, rp.enc_pub, 32)) continue; rp.meetingHostPort = d.meeting_node; + rp.onionHostPort = d.onion; + rp.descriptor = ev["content"].get(); out[ai->second] = rp; pending.erase(ai->second); } @@ -1650,11 +1696,13 @@ static void ConnectDiscoveredBtfPeers() DialCtx* ctx = (DialCtx*)arg; string addr = ctx->addr; string meeting = ctx->rp.meetingHostPort; + string onion = ctx->rp.onionHostPort; + string desc = ctx->rp.descriptor; unsigned char enc_pub[32]; memcpy(enc_pub, ctx->rp.enc_pub, 32); delete ctx; - CNode* pnode = ConnectNodeBtfResolved(addr, meeting, enc_pub); + CNode* pnode = ConnectNodeBtfResolved(addr, meeting, onion, enc_pub, desc); CRITICAL_BLOCK(cs_btfPeerState) { if (pnode) -- cgit v1.2.3 From 5309170e567f4a8655b3b622e7b26b6d8b382942 Mon Sep 17 00:00:00 2001 From: Nsable-sh Date: Thu, 13 Aug 2026 15:24:10 -0300 Subject: net: add onion-only btf dial mode --- docs/tor.md | 28 ++++++ scripts/smoke-managed-tor-onion.ps1 | 174 ++++++++++++++++++++++++++++++++++++ src/main_gui.cpp | 4 + src/net.cpp | 25 +++++- src/net.h | 1 + src/nostr.cpp | 11 +-- src/proxy.cpp | 51 ++++++++++- 7 files changed, 283 insertions(+), 11 deletions(-) create mode 100644 scripts/smoke-managed-tor-onion.ps1 (limited to 'src/nostr.cpp') 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. Tor owns the private key under `HiddenServiceDir`; back it up if you want the onion name to stay stable. + +## Release smoke test + +After building a Windows binary with managed Tor support, run the two-node smoke +test before cutting a release: + +```powershell +powershell -ExecutionPolicy Bypass -File scripts\smoke-managed-tor-onion.ps1 ` + -Bitflash .\src\bitflash.exe ` + -Tor C:\path\to\tor.exe +``` + +The script creates two temporary datadirs, starts node A with `-managedtor`, +waits for it to publish its `.btf` identity and generated `.onion` endpoint, +then starts node B with its own `-managedtor`, `-oniononly`, and +`-connectbtf=`. It passes only when node B logs that it connected to +node A `via direct onion`; rendezvous fallback cannot satisfy the test. + +By default the script waits 120 seconds after node A's descriptor self-resolves +before it starts node B. Tor can create the local `hostname` before the hidden +service descriptor is visible enough for client circuits, and early dials often +fail with SOCKS5 reply `0x01`. Override this with `-OnionWarmupSec N` when +testing a slow or already-warmed Tor environment. + +This test still uses Nostr for descriptor discovery, because that is how nodes +learn each other's signed `.btf` descriptors. It does not require a Bitflash +rendezvous relay to carry the P2P connection: once the descriptor includes the +signed 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 @@ +param( + [string]$Bitflash, + [string]$Tor, + [string]$WorkDir, + [int]$TimeoutSec = 600, + [int]$OnionWarmupSec = 120 +) + +$ErrorActionPreference = "Stop" + +function Resolve-RepoRoot { + $scriptDir = Split-Path -Parent $PSCommandPath + return (Resolve-Path (Join-Path $scriptDir "..")).Path +} + +function Get-FreeTcpPort { + $listener = [Net.Sockets.TcpListener]::new([Net.IPAddress]::Loopback, 0) + $listener.Start() + try { + return ([Net.IPEndPoint]$listener.LocalEndpoint).Port + } + finally { + $listener.Stop() + } +} + +function Wait-RegexInFile { + param( + [string]$Path, + [string]$Pattern, + [datetime]$Deadline, + [string]$Label + ) + + while ((Get-Date) -lt $Deadline) { + if (Test-Path $Path) { + $text = Get-Content -Raw -ErrorAction SilentlyContinue $Path + if ($null -ne $text) { + $m = [regex]::Match($text, $Pattern) + if ($m.Success) { + return $m + } + } + } + Start-Sleep -Milliseconds 500 + } + throw "timed out waiting for $Label" +} + +function Stop-TestProcesses { + param( + [System.Diagnostics.Process[]]$Processes, + [string[]]$DataDirKeys + ) + + foreach ($p in $Processes) { + try { + $p.Refresh() + if (!$p.HasExited) { + Stop-Process -Id $p.Id -Force + } + } + catch {} + } + + foreach ($key in $DataDirKeys) { + if ([string]::IsNullOrWhiteSpace($key)) { + continue + } + try { + Get-CimInstance Win32_Process -Filter "name = 'tor.exe'" | + Where-Object { $_.CommandLine -like "*$key*" } | + ForEach-Object { Stop-Process -Id $_.ProcessId -Force } + } + catch {} + } +} + +$repo = Resolve-RepoRoot +if ([string]::IsNullOrWhiteSpace($Bitflash)) { + $Bitflash = Join-Path $repo "src\bitflash.exe" +} +if ([string]::IsNullOrWhiteSpace($Tor)) { + $candidates = @( + (Join-Path $repo "src\tor\tor.exe"), + (Join-Path $repo "Bitflash-$(Select-String -Path (Join-Path $repo 'Makefile') -Pattern '^VERSION[ ]*:=' | ForEach-Object { ($_.Line -split ':=')[1].Trim() })-windows\tor\tor.exe") + ) + foreach ($candidate in $candidates) { + if (Test-Path $candidate) { + $Tor = $candidate + break + } + } +} +if ([string]::IsNullOrWhiteSpace($WorkDir)) { + $WorkDir = Join-Path $env:TEMP ("bitflash-managed-tor-two-node-" + (Get-Date -Format "yyyyMMddHHmmss")) +} + +if (!(Test-Path $Bitflash)) { + throw "Bitflash binary not found: $Bitflash" +} +if (!(Test-Path $Tor)) { + throw "Tor binary not found. Pass -Tor or build the Windows-with-Tor package." +} + +New-Item -ItemType Directory -Force -Path $WorkDir | Out-Null +$dataA = Join-Path $WorkDir "node-a" +$dataB = Join-Path $WorkDir "node-b" +New-Item -ItemType Directory -Force -Path $dataA | Out-Null +New-Item -ItemType Directory -Force -Path $dataB | Out-Null + +$portA = Get-FreeTcpPort +$portB = Get-FreeTcpPort +$deadline = (Get-Date).AddSeconds($TimeoutSec) +$oldPath = $env:PATH +$env:PATH = "C:\msys64\ucrt64\bin;C:\msys64\usr\bin;" + $env:PATH + +$procA = $null +$procB = $null +$dataKeys = @((Split-Path $WorkDir -Leaf)) + +try { + $argsA = @( + "-datadir=$dataA", + "-port=$portA", + "-managedtor=$Tor", + "-nogui", + "-debug", + "-checkblocks=0" + ) + $procA = Start-Process -FilePath $Bitflash -ArgumentList $argsA -WorkingDirectory (Split-Path $Bitflash -Parent) -PassThru -WindowStyle Hidden + $logA = Join-Path $dataA "debug.log" + + $btfMatch = Wait-RegexInFile -Path $logA -Pattern "Nostr: node \.btf address = ([a-z2-7]+\.btf)" -Deadline $deadline -Label "node A .btf address" + $btfA = $btfMatch.Groups[1].Value + + $onionMatch = Wait-RegexInFile -Path $logA -Pattern "Managed Tor hidden service ready: ([a-z2-7]{56}\.onion:\d+)" -Deadline $deadline -Label "node A managed onion" + $onionA = $onionMatch.Groups[1].Value + + Wait-RegexInFile -Path $logA -Pattern "Nostr: \.btf self-resolve OK via .*" -Deadline $deadline -Label "node A descriptor self-resolve" | Out-Null + if ($OnionWarmupSec -gt 0) { + Write-Host "Waiting $OnionWarmupSec second(s) for Tor hidden-service descriptor propagation" + Start-Sleep -Seconds $OnionWarmupSec + } + + $argsB = @( + "-datadir=$dataB", + "-port=$portB", + "-managedtor=$Tor", + "-oniononly", + "-connectbtf=$btfA", + "-nogui", + "-debug", + "-checkblocks=0" + ) + $procB = Start-Process -FilePath $Bitflash -ArgumentList $argsB -WorkingDirectory (Split-Path $Bitflash -Parent) -PassThru -WindowStyle Hidden + $logB = Join-Path $dataB "debug.log" + + 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 + + Write-Host "Managed Tor two-node onion smoke test passed" + Write-Host " node A .btf: $btfA" + Write-Host " node A onion: $onionA" + Write-Host " node A port: $portA" + Write-Host " node B port: $portB" + Write-Host " work dir: $WorkDir" +} +finally { + $env:PATH = $oldPath + $procs = @() + if ($null -ne $procA) { $procs += $procA } + if ($null -ne $procB) { $procs += $procB } + Stop-TestProcesses -Processes $procs -DataDirKeys $dataKeys +} 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() printf(" /socks=HOST:PORT (SOCKS5 proxy for Nostr, .btf relay, and onion dials)\n"); printf(" /tor[=HOST:PORT] (Tor mode; default SOCKS5 proxy is 127.0.0.1:9050)\n"); printf(" /managedtor[=PATH] (start Tor, create a hidden service, advertise its onion)\n"); + printf(" /oniononly (do not fall back to rendezvous when a .btf onion dial fails)\n"); printf(" /btfseed=ADDRESS:ENCHEX (extra bootstrap peer, repeatable)\n"); printf("\n"); printf("Wallet:\n"); @@ -278,6 +279,7 @@ static void ParseStartupArguments(int argc, char* argv[]) string btfConnect = argval2(argc, argv, "/connectbtf", "-connectbtf"); if (!btfConnect.empty()) strBtfConnect = btfConnect; + fBtfOnionOnly = arg(argc, argv, "/oniononly") || arg(argc, argv, "-oniononly"); string rvRelay = argval2(argc, argv, "/rvrelay", "-rvrelay"); if (!rvRelay.empty()) @@ -360,6 +362,8 @@ static void ParseStartupArguments(int argc, char* argv[]) BtfSocks5ProxyName().c_str()); } } + if (fBtfOnionOnly && !BtfSocks5ProxyEnabled()) + fprintf(stderr, "Warning: /oniononly without /tor, /managedtor, or /socks cannot dial .onion peers\n"); // /btfseed=ADDRESS:ENCHEX -- extra bootstrap peers, repeatable. Useful for // 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 > vRelayExpiration; CCriticalSection cs_mapRelay; map mapAlreadyAskedFor; string strBtfConnect; // .btf peer to keep connected to (from /connectbtf) +bool fBtfOnionOnly = false; // require direct onion transport for .btf peers +static const int BTF_ONION_CONNECT_TIMEOUT_SECS = 120; int nPeersWatched = 0; int64 nBlocksReceived = 0; @@ -1022,7 +1024,8 @@ static CNode* ConnectNodeBtfTail(const string& strBtfAddr, const unsigned char p if (SplitHostPort(strOnionNorm, strOnionHost, nOnionPort)) { BtfChurnNoteDialAttempt(strBtfAddr, strOnionNorm); - SOCKET hOnionSocket = BtfConnectSocket(strOnionHost, nOnionPort, SOCK_ONION_PEER, 20); + SOCKET hOnionSocket = BtfConnectSocket(strOnionHost, nOnionPort, SOCK_ONION_PEER, + BTF_ONION_CONNECT_TIMEOUT_SECS); if (hOnionSocket != INVALID_SOCKET) { if (fDebug) @@ -1040,12 +1043,23 @@ static CNode* ConnectNodeBtfTail(const string& strBtfAddr, const unsigned char p return pnode; } if (fDebug) - LogPrint("net", "ConnectNodeBtf: direct onion to %s at %s failed, falling back to rendezvous\n", - strBtfAddr.c_str(), strOnionNorm.c_str()); + LogPrint("net", "ConnectNodeBtf: direct onion to %s at %s failed%s\n", + strBtfAddr.c_str(), strOnionNorm.c_str(), + fBtfOnionOnly ? "" : ", falling back to rendezvous"); BtfChurnNoteDialResult(strBtfAddr, strOnionNorm, false); + if (fBtfOnionOnly) + return NULL; } } + if (fBtfOnionOnly) + { + if (fDebug) + LogPrint("net", "ConnectNodeBtf: onion-only mode refused rendezvous fallback for %s\n", + strBtfAddr.c_str()); + return NULL; + } + BtfChurnNoteDialAttempt(strBtfAddr, strMeeting); string strHost; unsigned short nPort = 0; @@ -2012,6 +2026,11 @@ bool StartNode(string& strError) // IP address, and port for the socket that is being bound int nRetryLimit = 15; struct sockaddr_in sockaddr = addrLocalHost.GetSockAddr(); + // Keep addrLocalHost as this node's advertised address, but listen on all + // local interfaces. Managed Tor forwards hidden-service traffic to + // 127.0.0.1:, and binding only to the LAN address made that onion + // endpoint unreachable even though the P2P listener was running. + sockaddr.sin_addr.s_addr = INADDR_ANY; if (bind(hListenSocket, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) == SOCKET_ERROR) { 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 void ThreadBtfAccept(void* parg); void ThreadBtfConnect(void* parg); extern string strBtfConnect; +extern bool fBtfOnionOnly; // Bootstrap seeds added at runtime with /btfseed=ADDRESS:ENCHEX (repeatable). // The compiled-in list lives in net.cpp; these are appended to it. extern std::vector > 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) LogPrint("nostr", "Nostr: node pubkey = %s\n", key.PubKeyHex().c_str()); LogPrint("nostr", "Nostr: node .btf address = %s\n", key.BtfAddress().c_str()); - // The self-test below publishes a real descriptor, and PublishDescriptor now - // refuses to publish before ThreadBtfAccept has registered a rendezvous. Give - // registration a bounded head start so the test measures the publish/resolve - // chain instead of losing the race against it. - for (int i = 0; i < 60 && !fShutdown && BtfActiveRelay().empty(); i++) + // The self-test below publishes a real descriptor. Rendezvous-only nodes + // need ThreadBtfAccept to register first; Tor nodes can publish as soon as + // their signed onion endpoint exists, with rendezvous marked pending. + for (int i = 0; i < 60 && !fShutdown && + BtfActiveRelay().empty() && + BtfLocalOnionEndpoint().empty(); i++) Sleep(1000); // 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; static std::string g_socks5Host; static unsigned short g_socks5Port = 0; static const char* DEFAULT_TOR_SOCKS5_PROXY = "127.0.0.1:9050"; +static const int TOR_CONNECT_TIMEOUT_SECS = 120; static void SetSocketTimeout(SOCKET s, int nTimeoutSecs) { @@ -297,10 +298,25 @@ static bool Socks5Connect(SOCKET s, const std::string& destHost, unsigned char hello[3] = { 0x05, 0x01, 0x00 }; // SOCKS5, one method, no auth if (!WriteN(s, hello, sizeof(hello))) + { + error("SOCKS5 hello write failed for %s:%u\n", + destHost.c_str(), (unsigned)destPort); return false; + } unsigned char choice[2] = { 0, 0 }; - if (!ReadN(s, choice, sizeof(choice)) || choice[0] != 0x05 || choice[1] != 0x00) + if (!ReadN(s, choice, sizeof(choice))) + { + error("SOCKS5 method selection read failed for %s:%u\n", + destHost.c_str(), (unsigned)destPort); + return false; + } + if (choice[0] != 0x05 || choice[1] != 0x00) + { + error("SOCKS5 proxy rejected no-auth method for %s:%u (version=0x%02x method=0x%02x)\n", + destHost.c_str(), (unsigned)destPort, + (unsigned)choice[0], (unsigned)choice[1]); return false; + } std::vector req; req.reserve(7 + destHost.size()); @@ -313,11 +329,25 @@ static bool Socks5Connect(SOCKET s, const std::string& destHost, req.push_back((unsigned char)(destPort >> 8)); req.push_back((unsigned char)(destPort & 0xff)); if (!WriteN(s, &req[0], (int)req.size())) + { + error("SOCKS5 CONNECT request write failed for %s:%u\n", + destHost.c_str(), (unsigned)destPort); return false; + } unsigned char rep[4] = {0,0,0,0}; - if (!ReadN(s, rep, sizeof(rep)) || rep[0] != 0x05) + if (!ReadN(s, rep, sizeof(rep))) + { + error("SOCKS5 CONNECT reply read failed for %s:%u\n", + destHost.c_str(), (unsigned)destPort); return false; + } + if (rep[0] != 0x05) + { + error("SOCKS5 CONNECT reply has invalid version 0x%02x for %s:%u\n", + (unsigned)rep[0], destHost.c_str(), (unsigned)destPort); + return false; + } if (rep[1] != 0x00) { 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, nAddr = len; } else + { + error("SOCKS5 CONNECT reply has invalid address type 0x%02x for %s:%u\n", + (unsigned)rep[3], destHost.c_str(), (unsigned)destPort); return false; + } std::vector discard(nAddr + 2); - return ReadN(s, &discard[0], (int)discard.size()); + if (!ReadN(s, &discard[0], (int)discard.size())) + { + error("SOCKS5 CONNECT bind address read failed for %s:%u\n", + destHost.c_str(), (unsigned)destPort); + return false; + } + return true; } SOCKET BtfConnectSocket(const std::string& destHost, unsigned short destPort, @@ -347,9 +387,11 @@ SOCKET BtfConnectSocket(const std::string& destHost, unsigned short destPort, std::string proxyHost; unsigned short proxyPort = 0; bool fProxy = false; + bool fTorProxy = false; CRITICAL_BLOCK(cs_socks5) { fProxy = g_fSocks5Proxy; + fTorProxy = g_fTorProxy; proxyHost = g_socks5Host; proxyPort = g_socks5Port; } @@ -365,6 +407,9 @@ SOCKET BtfConnectSocket(const std::string& destHost, unsigned short destPort, return ConnectDirect(destHost, destPort, nSockSite, nTimeoutSecs); } + if (fTorProxy && nTimeoutSecs > 0 && nTimeoutSecs < TOR_CONNECT_TIMEOUT_SECS) + nTimeoutSecs = TOR_CONNECT_TIMEOUT_SECS; + SOCKET s = ConnectDirect(proxyHost, proxyPort, nSockSite, nTimeoutSecs); if (s == INVALID_SOCKET) return INVALID_SOCKET; -- cgit v1.2.3