diff options
| author | Nsable-sh <nsable-sh@noreply.git.bitflash.network> | 2026-08-13 15:24:10 -0300 |
|---|---|---|
| committer | Nsable-sh <nsable-sh@noreply.git.bitflash.network> | 2026-08-13 15:58:42 -0300 |
| commit | 5309170e567f4a8655b3b622e7b26b6d8b382942 (patch) | |
| tree | 3d7fa8c459fb3b493b878f68b85cb53b7c16659e | |
| parent | 8e21e8ca0b8d50c29376bc2f0a89575baa477213 (diff) | |
net: add onion-only btf dial mode
| -rw-r--r-- | docs/tor.md | 28 | ||||
| -rw-r--r-- | scripts/smoke-managed-tor-onion.ps1 | 174 | ||||
| -rw-r--r-- | src/main_gui.cpp | 4 | ||||
| -rw-r--r-- | src/net.cpp | 25 | ||||
| -rw-r--r-- | src/net.h | 1 | ||||
| -rw-r--r-- | src/nostr.cpp | 11 | ||||
| -rw-r--r-- | src/proxy.cpp | 51 |
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 | ||
| 202 | Tor owns the private key under `HiddenServiceDir`; back it up if you want the | 202 | Tor owns the private key under `HiddenServiceDir`; back it up if you want the |
| 203 | onion name to stay stable. | 203 | onion name to stay stable. |
| 204 | |||
| 205 | ## Release smoke test | ||
| 206 | |||
| 207 | After building a Windows binary with managed Tor support, run the two-node smoke | ||
| 208 | test before cutting a release: | ||
| 209 | |||
| 210 | ```powershell | ||
| 211 | powershell -ExecutionPolicy Bypass -File scripts\smoke-managed-tor-onion.ps1 ` | ||
| 212 | -Bitflash .\src\bitflash.exe ` | ||
| 213 | -Tor C:\path\to\tor.exe | ||
| 214 | ``` | ||
| 215 | |||
| 216 | The script creates two temporary datadirs, starts node A with `-managedtor`, | ||
| 217 | waits for it to publish its `.btf` identity and generated `.onion` endpoint, | ||
| 218 | then starts node B with its own `-managedtor`, `-oniononly`, and | ||
| 219 | `-connectbtf=<node-a>`. It passes only when node B logs that it connected to | ||
| 220 | node A `via direct onion`; rendezvous fallback cannot satisfy the test. | ||
| 221 | |||
| 222 | By default the script waits 120 seconds after node A's descriptor self-resolves | ||
| 223 | before it starts node B. Tor can create the local `hostname` before the hidden | ||
| 224 | service descriptor is visible enough for client circuits, and early dials often | ||
| 225 | fail with SOCKS5 reply `0x01`. Override this with `-OnionWarmupSec N` when | ||
| 226 | testing a slow or already-warmed Tor environment. | ||
| 227 | |||
| 228 | This test still uses Nostr for descriptor discovery, because that is how nodes | ||
| 229 | learn each other's signed `.btf` descriptors. It does not require a Bitflash | ||
| 230 | rendezvous relay to carry the P2P connection: once the descriptor includes the | ||
| 231 | 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 @@ | |||
| 1 | param( | ||
| 2 | [string]$Bitflash, | ||
| 3 | [string]$Tor, | ||
| 4 | [string]$WorkDir, | ||
| 5 | [int]$TimeoutSec = 600, | ||
| 6 | [int]$OnionWarmupSec = 120 | ||
| 7 | ) | ||
| 8 | |||
| 9 | $ErrorActionPreference = "Stop" | ||
| 10 | |||
| 11 | function Resolve-RepoRoot { | ||
| 12 | $scriptDir = Split-Path -Parent $PSCommandPath | ||
| 13 | return (Resolve-Path (Join-Path $scriptDir "..")).Path | ||
| 14 | } | ||
| 15 | |||
| 16 | function 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 | |||
| 27 | function 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 | |||
| 50 | function 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 | ||
| 80 | if ([string]::IsNullOrWhiteSpace($Bitflash)) { | ||
| 81 | $Bitflash = Join-Path $repo "src\bitflash.exe" | ||
| 82 | } | ||
| 83 | if ([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 | } | ||
| 95 | if ([string]::IsNullOrWhiteSpace($WorkDir)) { | ||
| 96 | $WorkDir = Join-Path $env:TEMP ("bitflash-managed-tor-two-node-" + (Get-Date -Format "yyyyMMddHHmmss")) | ||
| 97 | } | ||
| 98 | |||
| 99 | if (!(Test-Path $Bitflash)) { | ||
| 100 | throw "Bitflash binary not found: $Bitflash" | ||
| 101 | } | ||
| 102 | if (!(Test-Path $Tor)) { | ||
| 103 | throw "Tor binary not found. Pass -Tor or build the Windows-with-Tor package." | ||
| 104 | } | ||
| 105 | |||
| 106 | New-Item -ItemType Directory -Force -Path $WorkDir | Out-Null | ||
| 107 | $dataA = Join-Path $WorkDir "node-a" | ||
| 108 | $dataB = Join-Path $WorkDir "node-b" | ||
| 109 | New-Item -ItemType Directory -Force -Path $dataA | Out-Null | ||
| 110 | New-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 | |||
| 122 | try { | ||
| 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 | } | ||
| 168 | finally { | ||
| 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; | |||
| 65 | CCriticalSection cs_mapRelay; | 65 | CCriticalSection cs_mapRelay; |
| 66 | map<CInv, int64> mapAlreadyAskedFor; | 66 | map<CInv, int64> mapAlreadyAskedFor; |
| 67 | string strBtfConnect; // .btf peer to keep connected to (from /connectbtf) | 67 | string strBtfConnect; // .btf peer to keep connected to (from /connectbtf) |
| 68 | bool fBtfOnionOnly = false; // require direct onion transport for .btf peers | ||
| 69 | static const int BTF_ONION_CONNECT_TIMEOUT_SECS = 120; | ||
| 68 | 70 | ||
| 69 | int nPeersWatched = 0; | 71 | int nPeersWatched = 0; |
| 70 | int64 nBlocksReceived = 0; | 72 | int64 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(); |
| @@ -33,6 +33,7 @@ CNode* ConnectNodeBtfResolved(const string& strBtfAddr, const string& strMeeting | |||
| 33 | void ThreadBtfAccept(void* parg); | 33 | void ThreadBtfAccept(void* parg); |
| 34 | void ThreadBtfConnect(void* parg); | 34 | void ThreadBtfConnect(void* parg); |
| 35 | extern string strBtfConnect; | 35 | extern string strBtfConnect; |
| 36 | extern 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. |
| 38 | extern std::vector<std::pair<std::string, std::string> > vBtfExtraSeeds; | 39 | extern 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; | |||
| 13 | static std::string g_socks5Host; | 13 | static std::string g_socks5Host; |
| 14 | static unsigned short g_socks5Port = 0; | 14 | static unsigned short g_socks5Port = 0; |
| 15 | static const char* DEFAULT_TOR_SOCKS5_PROXY = "127.0.0.1:9050"; | 15 | static const char* DEFAULT_TOR_SOCKS5_PROXY = "127.0.0.1:9050"; |
| 16 | static const int TOR_CONNECT_TIMEOUT_SECS = 120; | ||
| 16 | 17 | ||
| 17 | static void SetSocketTimeout(SOCKET s, int nTimeoutSecs) | 18 | static 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 | ||
| 344 | SOCKET BtfConnectSocket(const std::string& destHost, unsigned short destPort, | 384 | SOCKET 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; |