From 420e8dba624076bb87f61879a5d3093ce34446f5 Mon Sep 17 00:00:00 2001 From: Afshin Arani Date: Sun, 23 Apr 2023 16:19:49 +0330 Subject: [PATCH 1/7] Client,Network,Services: add TorClient This commit aims to introduce a simple API for end users. Working with network especially Tor routers is flaky and delegating retry logic implementation to users causes NOnion to be unreliable in CI and in normal use, this commit introduces retry logic in some places where it's needed the most. This should hopefully make NOnion more reliable. --- NOnion.Tests/HiddenServicesTests.cs | 59 +- NOnion.Tests/TorClientTests.cs | 47 ++ NOnion/Client/TorClient.fs | 280 +++++++ NOnion/Exceptions.fs | 3 + NOnion/NOnion.fsproj | 2 + NOnion/Services/TorServiceClient.fs | 83 +- NOnion/Services/TorServiceHost.fs | 162 ++-- NOnion/fallback_dirs.inc | 1102 +++++++++++++++++++++++++++ 8 files changed, 1558 insertions(+), 180 deletions(-) create mode 100644 NOnion.Tests/TorClientTests.cs create mode 100644 NOnion/Client/TorClient.fs create mode 100644 NOnion/fallback_dirs.inc diff --git a/NOnion.Tests/HiddenServicesTests.cs b/NOnion.Tests/HiddenServicesTests.cs index f75c1473..72ddbd2e 100644 --- a/NOnion.Tests/HiddenServicesTests.cs +++ b/NOnion.Tests/HiddenServicesTests.cs @@ -16,6 +16,7 @@ using NOnion.Network; using NOnion.Http; using NOnion.Cells.Relay; +using NOnion.Client; using NOnion.Directory; using NOnion.Tests.Utility; using NOnion.Services; @@ -24,6 +25,23 @@ namespace NOnion.Tests { public class HiddenServicesTests { + [OneTimeSetUp] + public void Init() + { + cachePath = + new DirectoryInfo( + Path.Combine( + Path.GetTempPath(), + Path.GetFileNameWithoutExtension( + Path.GetRandomFileName() + ) + ) + ); + cachePath.Create(); + } + + private DirectoryInfo cachePath = null; + /* It's possible that the router returned by GetRandomFallbackDirectory or * GetRandomRoutersForDirectoryBrowsing be inaccessable so we need to continue * retrying if an exceptions happened to make sure the issues are not related @@ -33,11 +51,8 @@ public class HiddenServicesTests private async Task CreateIntroductionCircuit() { - var node = (CircuitNodeDetail.Create)(await CircuitHelper.GetRandomRoutersForDirectoryBrowsingWithRetry()).First(); - using TorGuard guard = await TorGuard.NewClientAsync(node.EndPoint); - var circuit = new TorCircuit(guard); - - await circuit.CreateAsync(CircuitNodeDetail.FastCreate); + using TorClient torClient = await TorClient.BootstrapWithGithubAsync(cachePath); + var circuit = await torClient.CreateCircuitAsync(1, CircuitPurpose.Unknown, FSharpOption.None); await circuit.RegisterAsIntroductionPointAsync(FSharpOption.None, StubCallback, DisconnectionCallback); } @@ -61,12 +76,8 @@ private async Task CreateRendezvousCircuit() var array = new byte[Constants.RendezvousCookieLength]; RandomNumberGenerator.Create().GetNonZeroBytes(array); - var nodes = await CircuitHelper.GetRandomRoutersForDirectoryBrowsingWithRetry(2); - using TorGuard guard = await TorGuard.NewClientAsync(((CircuitNodeDetail.Create)nodes[0]).EndPoint); - var circuit = new TorCircuit(guard); - - await circuit.CreateAsync(nodes[0]); - await circuit.ExtendAsync(nodes[1]); + using TorClient torClient = await TorClient.BootstrapWithGithubAsync(cachePath); + var circuit = await torClient.CreateCircuitAsync(2, CircuitPurpose.Unknown, FSharpOption.None); await circuit.RegisterAsRendezvousPointAsync(array); } @@ -92,10 +103,10 @@ private async Task ReadExact(TorStream stream, byte[] buffer, int off, int public async Task BrowseFacebookOverHS() { - TorDirectory directory = await TorDirectory.BootstrapAsync(FallbackDirectorySelector.GetRandomFallbackDirectory(), new DirectoryInfo(Path.GetTempPath())); + using TorClient torClient = await TorClient.BootstrapWithGithubAsync(cachePath); - var client = await TorServiceClient.ConnectAsync(directory, "facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion"); - var httpClient = new TorHttpClient(client.GetStream(), "facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion"); + var serviceClient = await TorServiceClient.ConnectAsync(torClient, "facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion"); + var httpClient = new TorHttpClient(serviceClient.GetStream(), "facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion"); try { @@ -117,11 +128,11 @@ public void CanBrowseFacebookOverHS() public async Task BrowseFacebookOverHSWithTLS() { - TorDirectory directory = await TorDirectory.BootstrapAsync(FallbackDirectorySelector.GetRandomFallbackDirectory(), new DirectoryInfo(Path.GetTempPath())); - - var client = await TorServiceClient.ConnectAsync(directory, "facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion:443"); + using TorClient torClient = await TorClient.BootstrapWithGithubAsync(cachePath); + + var serviceClient = await TorServiceClient.ConnectAsync(torClient, "facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion:443"); - var sslStream = new SslStream(client.GetStream(), true, (sender, cert, chain, sslPolicyErrors) => true); + var sslStream = new SslStream(serviceClient.GetStream(), true, (sender, cert, chain, sslPolicyErrors) => true); await sslStream.AuthenticateAsClientAsync(string.Empty, null, SslProtocols.Tls12, false); var httpClientOverSslStream = new TorHttpClient(sslStream, "www.facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion"); @@ -147,10 +158,8 @@ public void CanBrowseFacebookOverHSWithTLS() public async Task EstablishAndCommunicateOverHSConnectionOnionStyle() { - int descriptorUploadRetryLimit = 2; - - TorDirectory directory = await TorDirectory.BootstrapAsync(FallbackDirectorySelector.GetRandomFallbackDirectory(), new DirectoryInfo(Path.GetTempPath())); - + using TorClient torClient = await TorClient.BootstrapWithGithubAsync(cachePath); + TorLogger.Log("Finished bootstraping"); SecureRandom random = new SecureRandom(); @@ -158,7 +167,7 @@ public async Task EstablishAndCommunicateOverHSConnectionOnionStyle() kpGen.Init(new Ed25519KeyGenerationParameters(random)); Ed25519PrivateKeyParameters masterPrivateKey = (Ed25519PrivateKeyParameters)kpGen.GenerateKeyPair().Private; - TorServiceHost host = new TorServiceHost(directory, descriptorUploadRetryLimit, TestsRetryCount, FSharpOption.Some(masterPrivateKey)); + TorServiceHost host = new TorServiceHost(torClient, FSharpOption.Some(masterPrivateKey)); await host.StartAsync(); TorLogger.Log("Finished starting HS host"); @@ -175,8 +184,8 @@ public async Task EstablishAndCommunicateOverHSConnectionOnionStyle() var clientSide = Task.Run(async () => { - var client = await TorServiceClient.ConnectAsync(directory, host.ExportUrl()); - var stream = client.GetStream(); + var serviceClient = await TorServiceClient.ConnectAsync(torClient, host.ExportUrl()); + var stream = serviceClient.GetStream(); var lengthBytes = new byte[sizeof(int)]; await ReadExact(stream, lengthBytes, 0, lengthBytes.Length); var length = BitConverter.ToInt32(lengthBytes); diff --git a/NOnion.Tests/TorClientTests.cs b/NOnion.Tests/TorClientTests.cs new file mode 100644 index 00000000..c7cb3ef4 --- /dev/null +++ b/NOnion.Tests/TorClientTests.cs @@ -0,0 +1,47 @@ +using Microsoft.FSharp.Core; +using System.IO; +using System.Threading.Tasks; + +using NUnit.Framework; + +using NOnion.Client; + +namespace NOnion.Tests +{ + public class TorClientTests + { + private async Task BootstrapWithGithub() + { + await TorClient.BootstrapWithGithubAsync(FSharpOption.None); + } + + [Test] + public void CanBootstrapWithGithub() + { + Assert.DoesNotThrowAsync(BootstrapWithGithub); + } + + private async Task BootstrapWithEmbeddedList() + { + await TorClient.BootstrapWithEmbeddedListAsync(FSharpOption.None); + } + + [Test] + public void CanBootstrapWithEmbeddedList() + { + Assert.DoesNotThrowAsync(BootstrapWithEmbeddedList); + } + + private async Task CreateCircuit() + { + using TorClient client = await TorClient.BootstrapWithEmbeddedListAsync(FSharpOption.None); + await client.CreateCircuitAsync(3, CircuitPurpose.Unknown, FSharpOption.None); + } + + [Test] + public void CanCreateCircuit() + { + Assert.DoesNotThrowAsync(CreateCircuit); + } + } +} diff --git a/NOnion/Client/TorClient.fs b/NOnion/Client/TorClient.fs new file mode 100644 index 00000000..b23b02d0 --- /dev/null +++ b/NOnion/Client/TorClient.fs @@ -0,0 +1,280 @@ +namespace NOnion.Client + +open System +open System.Collections.Concurrent +open System.IO +open System.Net +open System.Net.Http +open System.Text.RegularExpressions + +open NOnion.Directory +open NOnion.Utility +open NOnion +open NOnion.Network + +type CircuitPurpose = + | Unknown + | Exit + +type TorClient internal (directory: TorDirectory) = + static let maximumBootstrapTries = 5 + + static let maximumExtendByNodeRetry = 5 + + static let ConvertFallbackIncToList(fallbackIncString: string) = + let ipv4Pattern = "\"([0-9\\.]+)\\sorport=(\\S*)\\sid=(\\S*)\"" + let matches = Regex.Matches(fallbackIncString, ipv4Pattern) + + matches + |> Seq.cast + |> Seq.map(fun (regMatch: Match) -> + regMatch.Groups.[1].Value, int regMatch.Groups.[2].Value + ) + |> Seq.toList + + static let SelectRandomEndpoints(fallbackList: List) = + fallbackList + |> SeqUtils.TakeRandom maximumBootstrapTries + |> Seq.map(fun (ipString, port) -> + let ipAddress = IPAddress.Parse ipString + IPEndPoint(ipAddress, port) + ) + |> Seq.toList + + static let BootstrapDirectory + (cachePath: Option) + (ipEndPointList: List) + = + async { + let rec tryBootstrap(ipEndPointList: List) = + async { + match ipEndPointList with + | ipEndPoint :: tail -> + try + let cacheDirectory = + match cachePath with + | None -> + let tempDir = + DirectoryInfo( + Path.Combine( + Path.GetTempPath(), + Path.GetFileNameWithoutExtension( + Path.GetRandomFileName() + ) + ) + ) + + tempDir.Create() + tempDir + | Some path -> path + + let! directory = + TorDirectory.Bootstrap ipEndPoint cacheDirectory + + return directory + with + | :? NOnionException -> return! tryBootstrap tail + | [] -> return failwith "Maximum bootstrap tries reached!" + } + + return! tryBootstrap ipEndPointList + } + + static let CreateClientFromFallbackString + (fallbackListString: string) + (cachePath: Option) + = + async { + let! directory = + fallbackListString + |> ConvertFallbackIncToList + |> SelectRandomEndpoints + |> BootstrapDirectory cachePath + + return new TorClient(directory) + } + + let guardsToDispose = ConcurrentBag() + + static member AsyncBootstrapWithEmbeddedList + (cachePath: Option) + = + async { + let fallbackListString = + EmbeddedResourceUtility.ExtractEmbeddedResourceFileContents( + "fallback_dirs.inc" + ) + + return! CreateClientFromFallbackString fallbackListString cachePath + } + + static member BootstrapWithEmbeddedListAsync + (cachePath: Option) + = + TorClient.AsyncBootstrapWithEmbeddedList cachePath |> Async.StartAsTask + + static member AsyncBootstrapWithGithub(cachePath: Option) = + async { + // Don't put this inside the fallbackListString or it gets disposed + // before the task gets executed + use httpClient = new HttpClient() + + let! fallbackListString = + let urlToTorServerList = + "https://raw.githubusercontent.com/torproject/tor/main/src/app/config/fallback_dirs.inc" + + httpClient.GetStringAsync urlToTorServerList |> Async.AwaitTask + + return! CreateClientFromFallbackString fallbackListString cachePath + } + + static member BootstrapWithGithubAsync(cachePath: Option) = + TorClient.AsyncBootstrapWithGithub cachePath |> Async.StartAsTask + + member __.Directory = directory + + member internal __.AsyncCreateCircuitWithCallback + (hopsCount: int) + (purpose: CircuitPurpose) + (extendByNodeOpt: Option) + (serviceStream: uint16 -> TorCircuit -> Async) + = + async { + let rec createNewGuard() = + async { + let! ipEndPoint, nodeDetail = + directory.GetRouter RouterType.Guard + + try + let! guard = + TorGuard.NewClientWithIdentity + ipEndPoint + (nodeDetail.GetIdentityKey() |> Some) + + guardsToDispose.Add guard + return guard, nodeDetail + with + | :? GuardConnectionFailedException -> + return! createNewGuard() + } + + let rec tryCreateCircuit(tryNumber: int) = + async { + if tryNumber > maximumExtendByNodeRetry then + return raise <| DestinationNodeCantBeReachedException() + else + try + let! guard, guardDetail = createNewGuard() + let circuit = TorCircuit(guard, serviceStream) + + do! + circuit.Create guardDetail + |> Async.Ignore + + let rec extend + (numHopsToExtend: int) + (nodesSoFar: List) + = + async { + if numHopsToExtend > 0 then + let rec findUnusedNode() = + async { + let! _ipEndPoint, nodeDetail = + if numHopsToExtend = 1 then + match purpose with + | Unknown -> + directory.GetRouter + RouterType.Normal + | Exit -> + directory.GetRouter + RouterType.Exit + else + directory.GetRouter + RouterType.Normal + + if (List.contains + nodeDetail + nodesSoFar) then + return! findUnusedNode() + else + return nodeDetail + } + + let! newUnusedNode = findUnusedNode() + + do! + circuit.Extend newUnusedNode + |> Async.Ignore + + return! + extend + (numHopsToExtend - 1) + (newUnusedNode :: nodesSoFar) + else + () + } + + do! + extend + (hopsCount - 1) + (List.singleton guardDetail) + + match extendByNodeOpt with + | Some extendByNode -> + try + do! + circuit.Extend extendByNode + |> Async.Ignore + with + | :? NOnionException -> + return + raise + <| DestinationNodeCantBeReachedException + () + | None -> () + + return circuit + with + | :? DestinationNodeCantBeReachedException -> + return! tryCreateCircuit(tryNumber + 1) + | ex -> + match FSharpUtil.FindException ex + with + | Some _nonionEx -> + return! tryCreateCircuit tryNumber + | None -> return raise <| FSharpUtil.ReRaise ex + } + + let startTryNumber = 1 + + return! tryCreateCircuit startTryNumber + } + + member self.AsyncCreateCircuit + (hopsCount: int) + (purpose: CircuitPurpose) + (extendByNodeOpt: Option) + = + let noop _ _ = + async { return () } + + self.AsyncCreateCircuitWithCallback + hopsCount + purpose + extendByNodeOpt + noop + + member self.CreateCircuitAsync + ( + hopsCount: int, + purpose: CircuitPurpose, + extendByNode: Option + ) = + self.AsyncCreateCircuit hopsCount purpose extendByNode + |> Async.StartAsTask + + + interface IDisposable with + member __.Dispose() = + for guard in guardsToDispose do + (guard :> IDisposable).Dispose() diff --git a/NOnion/Exceptions.fs b/NOnion/Exceptions.fs index 98c7ab94..757f34ab 100644 --- a/NOnion/Exceptions.fs +++ b/NOnion/Exceptions.fs @@ -53,3 +53,6 @@ type NOnionSocketException "Got socket exception during data transfer", innerException ) + +type DestinationNodeCantBeReachedException() = + inherit NOnionException("Destination node can't be reached") diff --git a/NOnion/NOnion.fsproj b/NOnion/NOnion.fsproj index 904f1ae7..c5e3b282 100644 --- a/NOnion/NOnion.fsproj +++ b/NOnion/NOnion.fsproj @@ -13,6 +13,7 @@ + @@ -85,6 +86,7 @@ + diff --git a/NOnion/Services/TorServiceClient.fs b/NOnion/Services/TorServiceClient.fs index 9264dfbc..074de3e3 100644 --- a/NOnion/Services/TorServiceClient.fs +++ b/NOnion/Services/TorServiceClient.fs @@ -12,6 +12,7 @@ open Org.BouncyCastle.Security open NOnion open NOnion.Cells.Relay +open NOnion.Client open NOnion.Crypto open NOnion.Utility open NOnion.Directory @@ -21,7 +22,7 @@ open NOnion.Network type TorServiceClient = private { - RendezvousGuard: TorGuard + TorClient: TorClient RendezvousCircuit: TorCircuit Stream: TorStream } @@ -29,16 +30,16 @@ type TorServiceClient = member self.GetStream() = self.Stream - static member ConnectAsync (directory: TorDirectory) (url: string) = - TorServiceClient.Connect directory url |> Async.StartAsTask + static member ConnectAsync (client: TorClient) (url: string) = + TorServiceClient.Connect client url |> Async.StartAsTask - static member Connect (directory: TorDirectory) (url: string) = + static member Connect (client: TorClient) (url: string) = async { let publicKey, port = HiddenServicesUtility.DecodeOnionUrl url let getIntroductionPointInfo() = async { - let! networkStatus = directory.GetLiveNetworkStatus() + let! networkStatus = client.Directory.GetLiveNetworkStatus() let periodNum, periodLength = networkStatus.GetTimePeriod() let srv = networkStatus.GetCurrentSRVForClient() @@ -49,7 +50,7 @@ type TorServiceClient = publicKey let! responsibleDirs = - directory.GetResponsibleHiddenServiceDirectories + client.Directory.GetResponsibleHiddenServiceDirectories blindedPublicKey srv periodNum @@ -64,36 +65,16 @@ type TorServiceClient = raise <| DescriptorDownloadFailedException() | hsDirectory :: tail -> try - let! guardEndPoint, randomGuardNode = - directory.GetRouter RouterType.Guard - - let! _, randomMiddleNode = - directory.GetRouter RouterType.Normal - let! hsDirectoryNode = - directory.GetCircuitNodeDetailByIdentity + client.Directory.GetCircuitNodeDetailByIdentity hsDirectory - use! guardNode = - TorGuard.NewClientWithIdentity - guardEndPoint - (randomGuardNode.GetIdentityKey() - |> Some) - - let circuit = TorCircuit guardNode - - do! - circuit.Create randomGuardNode - |> Async.Ignore - - do! - circuit.Extend randomMiddleNode - |> Async.Ignore - try - do! - circuit.Extend hsDirectoryNode - |> Async.Ignore + let! circuit = + client.AsyncCreateCircuit + 2 + CircuitPurpose.Unknown + (Some hsDirectoryNode) use dirStream = new TorStream(circuit) @@ -394,18 +375,14 @@ type TorServiceClient = .Create() .GetNonZeroBytes randomGeneratedCookie - let! endpoint, guardnode = directory.GetRouter RouterType.Guard - let! _, rendezvousNode = directory.GetRouter RouterType.Normal + let! _, rendezvousNode = + client.Directory.GetRouter RouterType.Normal - let! rendezvousGuard = - TorGuard.NewClientWithIdentity - endpoint - (guardnode.GetIdentityKey() |> Some) - - let rendezvousCircuit = TorCircuit rendezvousGuard - - do! rendezvousCircuit.Create guardnode |> Async.Ignore - do! rendezvousCircuit.Extend rendezvousNode |> Async.Ignore + let! rendezvousCircuit = + client.AsyncCreateCircuit + 1 + CircuitPurpose.Unknown + (Some rendezvousNode) do! rendezvousCircuit.RegisterAsRendezvousPoint @@ -438,7 +415,7 @@ type TorServiceClient = ] } - let! networkStatus = directory.GetLiveNetworkStatus() + let! networkStatus = client.Directory.GetLiveNetworkStatus() let periodInfo = networkStatus.GetTimePeriod() let data, macKey = @@ -471,13 +448,11 @@ type TorServiceClient = macKey } - let introCircuit = TorCircuit rendezvousGuard - - do! introCircuit.Create guardnode |> Async.Ignore - - do! - introCircuit.Extend introductionPointNodeDetail - |> Async.Ignore + let! introCircuit = + client.AsyncCreateCircuit + 1 + Unknown + (Some introductionPointNodeDetail) let rendezvousJoin = rendezvousCircuit.WaitingForRendezvousJoin @@ -507,7 +482,7 @@ type TorServiceClient = return { - RendezvousGuard = rendezvousGuard + TorClient = client RendezvousCircuit = rendezvousCircuit Stream = serviceStream } @@ -515,7 +490,3 @@ type TorServiceClient = return failwith "Never happens. GetRouter never returns FastCreate" } - - interface IDisposable with - member self.Dispose() = - (self.RendezvousGuard :> IDisposable).Dispose() diff --git a/NOnion/Services/TorServiceHost.fs b/NOnion/Services/TorServiceHost.fs index 2fb2beab..25f9b595 100644 --- a/NOnion/Services/TorServiceHost.fs +++ b/NOnion/Services/TorServiceHost.fs @@ -19,6 +19,7 @@ open Org.BouncyCastle.Security open NOnion open NOnion.Cells.Relay open NOnion.Crypto +open NOnion.Client open NOnion.Directory open NOnion.Utility open NOnion.Network @@ -36,9 +37,7 @@ type IntroductionPointInfo = type TorServiceHost ( - directory: TorDirectory, - maxDescriptorUploadRetryCount: int, - maxRendezvousConnectRetryCount: int, + client: TorClient, maybeMasterPrivateKey: Option ) = @@ -125,28 +124,19 @@ type TorServiceHost introEncPubKey = async { - let! endPoint, randomNodeDetails = - directory.GetRouter RouterType.Guard - - let! guard = - TorGuard.NewClientWithIdentity - endPoint - (randomNodeDetails.GetIdentityKey() |> Some) - - let rendezvousCircuit = - TorCircuit(guard, self.IncomingServiceStreamCallback) - - do! rendezvousCircuit.Create randomNodeDetails |> Async.Ignore - - do! - rendezvousCircuit.Extend( - CircuitNodeDetail.Create( - rendezvousEndpoint, - onionKey, - rendezvousFingerPrint - ) + let lastNodeDetails = + CircuitNodeDetail.Create( + rendezvousEndpoint, + onionKey, + rendezvousFingerPrint ) - |> Async.Ignore + + let! rendezvousCircuit = + client.AsyncCreateCircuitWithCallback + 2 + CircuitPurpose.Unknown + (Some lastNodeDetails) + self.IncomingServiceStreamCallback do! rendezvousCircuit.Rendezvous @@ -181,7 +171,7 @@ type TorServiceHost introductionPointDetails.EncryptionKey.Private :?> X25519PrivateKeyParameters - let! networkStatus = directory.GetLiveNetworkStatus() + let! networkStatus = client.Directory.GetLiveNetworkStatus() let periodInfo = networkStatus.GetTimePeriod() let decryptedData, macKey = @@ -237,7 +227,7 @@ type TorServiceHost | Some linkSpecifier -> linkSpecifier.Data | None -> failwith "No rendezvous fingerprint found!" - let connectToRendezvousJob = + do! tryConnectingToRendezvous rendezvousEndpoint rendezvousFingerPrint @@ -248,11 +238,6 @@ type TorServiceHost introEncPrivKey introEncPubKey - do! - FSharpUtil.Retry - connectToRendezvousJob - maxRendezvousConnectRetryCount - return () } @@ -265,10 +250,10 @@ type TorServiceHost async { try let! guardEndPoint, guardNodeDetail = - directory.GetRouter RouterType.Guard + client.Directory.GetRouter RouterType.Guard let! _, introNodeDetail = - directory.GetRouter RouterType.Normal + client.Directory.GetRouter RouterType.Normal match introNodeDetail with | FastCreate -> @@ -368,73 +353,52 @@ type TorServiceHost member self.UploadDescriptor (directoryToUploadTo: string) (document: HiddenServiceFirstLayerDescriptorDocument) - (retry: int) = async { - if retry > maxDescriptorUploadRetryCount then - return () - else - try - let! hsDirectoryNode = - directory.GetCircuitNodeDetailByIdentity - directoryToUploadTo - - let! guardEndPoint, randomGuardNode = - directory.GetRouter RouterType.Guard - - let! _, randomMiddleNode = - directory.GetRouter RouterType.Normal - - use! guardNode = - TorGuard.NewClientWithIdentity - guardEndPoint - (randomGuardNode.GetIdentityKey() |> Some) - - let circuit = TorCircuit guardNode - do! circuit.Create randomGuardNode |> Async.Ignore - do! circuit.Extend randomMiddleNode |> Async.Ignore - do! circuit.Extend hsDirectoryNode |> Async.Ignore - - use dirStream = new TorStream(circuit) - do! dirStream.ConnectToDirectory() |> Async.Ignore - - let! _response = - TorHttpClient( - dirStream, - Constants.DefaultHttpHost - ) - .PostString - (sprintf - "/tor/hs/%i/publish" - Constants.HiddenServices.Version) - (document.ToString()) - - TorLogger.Log( - sprintf - "TorServiceHost: descriptor uploaded to node with identity %s" - directoryToUploadTo - ) - - return () - with - | :? UnsuccessfulHttpResponseException -> - // During testing, after migration to microdescriptor, we saw instances of - // 404 error msg when trying to publish our descriptors which mean for - // some reason we're trying to upload descriptor to a directory that - // is not a hidden service directory, there is no point in retrying here. - return () - | ex -> - TorLogger.Log( - sprintf - "TorServiceHost: hs descriptor upload failed, ex=%s" - (ex.ToString()) + try + let! hsDirectoryNode = + client.Directory.GetCircuitNodeDetailByIdentity + directoryToUploadTo + + let! circuit = + client.AsyncCreateCircuit + 2 + CircuitPurpose.Unknown + (Some hsDirectoryNode) + + use dirStream = new TorStream(circuit) + do! dirStream.ConnectToDirectory() |> Async.Ignore + + let! _response = + TorHttpClient( + dirStream, + Constants.DefaultHttpHost ) + .PostString + (sprintf + "/tor/hs/%i/publish" + Constants.HiddenServices.Version) + (document.ToString()) + + TorLogger.Log( + sprintf + "TorServiceHost: descriptor uploaded to node with identity %s" + directoryToUploadTo + ) - return! - self.UploadDescriptor - directoryToUploadTo - document - (retry + 1) + return () + with + | :? DestinationNodeCantBeReachedException + | :? UnsuccessfulHttpResponseException -> + // During testing, after migration to microdescriptor, we saw instances of + // 404 error msg when trying to publish our descriptors which mean for + // some reason we're trying to upload descriptor to a directory that + // is not a hidden service directory, there is no point in retrying here. + + // TorClient tries multiple times with different circuit to connect to + // the directory, if destination node can't be reached with any circuit + // we stop trying. + return () } member self.BuildAndUploadDescriptor @@ -450,7 +414,7 @@ type TorServiceHost (masterPublicKey.GetEncoded()) let! responsibleDirs = - directory.GetResponsibleHiddenServiceDirectories + client.Directory.GetResponsibleHiddenServiceDirectories blindedPublicKey srv periodNum @@ -698,7 +662,7 @@ type TorServiceHost let jobs = responsibleDirs - |> Seq.map(fun dir -> self.UploadDescriptor dir outerWrapper 1) + |> Seq.map(fun dir -> self.UploadDescriptor dir outerWrapper) do! Async.Parallel( @@ -756,7 +720,7 @@ type TorServiceHost //TODO: this should refresh every 60-120min member self.KeepDescriptorsUpToDate() = async { - let! networkStatus = directory.GetLiveNetworkStatus() + let! networkStatus = client.Directory.GetLiveNetworkStatus() let firstDescriptorBuildJob = self.UpdateFirstDescriptor networkStatus diff --git a/NOnion/fallback_dirs.inc b/NOnion/fallback_dirs.inc new file mode 100644 index 00000000..1302099e --- /dev/null +++ b/NOnion/fallback_dirs.inc @@ -0,0 +1,1102 @@ +/* type=fallback */ +/* version=4.0.0 */ +/* timestamp=20210412000000 */ +/* source=offer-list */ +// +// Generated on: Thu, 12 Jan 2023 16:00:16 +0000 + +"142.132.165.154 orport=443 id=6D9E22C1F4F0E99867F98F2546A9B76D5F08B4CF" +" ipv6=[2a01:4f8:1c1c:43c::1]:443" +/* nickname=libreapp01 */ +/* extrainfo=0 */ +/* ===== */ +, +"18.18.82.18 orport=9001 id=BF54EE3193751481579BA7CC7D8E1DF0A01AFB30" +/* nickname=gesdm */ +/* extrainfo=0 */ +/* ===== */ +, +"72.92.146.128 orport=9001 id=C5E420FAF05680EE590542AE7216C77602FE68DC" +/* nickname=TreeFiddy */ +/* extrainfo=0 */ +/* ===== */ +, +"116.203.17.238 orport=9001 id=28090710ABE433A47021F22208B3EC245A912900" +/* nickname=dismail */ +/* extrainfo=0 */ +/* ===== */ +, +"116.203.50.182 orport=8080 id=00E1649E69FF91D7F01E74A5E62EF14F7D9915E4" +" ipv6=[2a01:4f8:1c1c:b16b::1]:8080" +/* nickname=dragonhoard */ +/* extrainfo=0 */ +/* ===== */ +, +"144.76.154.13 orport=9001 id=10A5EFCCD2FB9C1A4AC20FB779A5DB11B58957A7" +" ipv6=[2a01:4f8:200:2211::2]:9001" +/* nickname=eridanus */ +/* extrainfo=0 */ +/* ===== */ +, +"94.46.171.245 orport=9001 id=904F36E7AFE6346F5D1D66971F920FFCC47DF120" +/* nickname=sunandfun03 */ +/* extrainfo=0 */ +/* ===== */ +, +"176.9.38.121 orport=9100 id=8284C8A45D22F2981C4B6287C7FB4367116E7CCE" +" ipv6=[2a01:4f8:161:353a::2]:9100" +/* nickname=Turik */ +/* extrainfo=0 */ +/* ===== */ +, +"78.31.67.22 orport=9001 id=6982D789D3875C21433D6EE10838AC5FDC6BA82C" +/* nickname=tor2lhvmct */ +/* extrainfo=0 */ +/* ===== */ +, +"185.220.101.206 orport=443 id=EB9DD80E64DD829A5F7C7ACA5D5FEADFEBFDD847" +" ipv6=[2a0b:f4c2:2:1::206]:443" +/* nickname=ForPrivacyNET */ +/* extrainfo=0 */ +/* ===== */ +, +"185.175.158.198 orport=9090 id=A489D37070A5081D814C1F112139EEF0DDC03A48" +/* nickname=Trurangers2 */ +/* extrainfo=0 */ +/* ===== */ +, +"87.62.96.246 orport=9032 id=E384748293FC4429E2B427360DB4F9D4C3D619D1" +/* nickname=PXArelay02 */ +/* extrainfo=0 */ +/* ===== */ +, +"94.130.246.106 orport=9001 id=1C0736CF3744A3B87C2D2269B8BD3388C7E60552" +" ipv6=[2a01:4f8:10b:3344:106::106]:9001" +/* nickname=FreedomFries2 */ +/* extrainfo=0 */ +/* ===== */ +, +"95.214.54.80 orport=443 id=5A79BD5CC6C128D7D8DFB4969B0246794F117FC6" +" ipv6=[2a03:cfc0:8000:7::5fd6:3647]:443" +/* nickname=bauruine */ +/* extrainfo=0 */ +/* ===== */ +, +"185.220.101.72 orport=9100 id=D3DFB8F9A878F44ED80E2B34F794FDF6334FC5F9" +/* nickname=CCCStuttgartBer */ +/* extrainfo=0 */ +/* ===== */ +, +"51.15.150.228 orport=443 id=46E0487EEEFD694CE625CC6E12D032395C01DB82" +/* nickname=NTH100 */ +/* extrainfo=0 */ +/* ===== */ +, +"51.15.75.120 orport=444 id=0040A5B04C7E309D37CBE7EDB2B72D3E15D057C1" +" ipv6=[2001:bc8:1860:1329::1]:444" +/* nickname=dc6jgk11d */ +/* extrainfo=0 */ +/* ===== */ +, +"185.181.229.77 orport=9001 id=44730B2450213BC3E2DAA4854458D134F0644FF2" +/* nickname=RedLightDistrict */ +/* extrainfo=0 */ +/* ===== */ +, +"91.219.29.94 orport=9001 id=09E0BD68FBAEB7C10107DE67624D25B007FF26A2" +/* nickname=Unnamed */ +/* extrainfo=0 */ +/* ===== */ +, +"51.81.56.195 orport=443 id=EEDB9FEFC9165F9B41B515A282F95A574A807FBA" +/* nickname=trash1 */ +/* extrainfo=0 */ +/* ===== */ +, +"89.147.108.35 orport=9105 id=B30D36FBA3DD300E11916A086D7166A6BE3169FE" +/* nickname=LessIsMore */ +/* extrainfo=0 */ +/* ===== */ +, +"5.255.98.7 orport=443 id=37788F0C00728A44ED8202780D853223EDCA3D3F" +" ipv6=[2a04:52c0:103:ba24::1]:443" +/* nickname=vhult4 */ +/* extrainfo=0 */ +/* ===== */ +, +"144.91.125.15 orport=9001 id=F15A2A08BF91017DCFB6042171635661E13A8256" +" ipv6=[2a02:c207:2033:4966::1]:9001" +/* nickname=WitchNode */ +/* extrainfo=0 */ +/* ===== */ +, +"172.98.15.136 orport=9001 id=518B031A3DF503E08F6F815DB194DBD7B15C9C57" +/* nickname=FrozenChosen */ +/* extrainfo=0 */ +/* ===== */ +, +"138.201.123.109 orport=9001 id=2C13A54E3E8A6AFB18E0DE5890E5B08AAF5B0F36" +/* nickname=history */ +/* extrainfo=0 */ +/* ===== */ +, +"216.250.119.225 orport=9001 id=A56B3845D290791CB26BCE89FE4CE08CF7DC8A2C" +/* nickname=QECm1r4 */ +/* extrainfo=0 */ +/* ===== */ +, +"89.58.54.129 orport=443 id=3B642FF7FE3915C42E20445A1C725A75BAA0A9E3" +" ipv6=[2a03:4000:69:e40::1]:443" +/* nickname=TorRelay1a */ +/* extrainfo=0 */ +/* ===== */ +, +"185.232.70.209 orport=443 id=D36EFFB24481694E384787FAAB80F56AAA5CCD9A" +" ipv6=[2a03:4000:4e:f48:54ac:99ff:fee4:b238]:443" +/* nickname=08eRPfaL2Relay2 */ +/* extrainfo=0 */ +/* ===== */ +, +"109.202.212.1 orport=9001 id=196ABA69C056520E041FAE26EFB329940AB110AF" +" ipv6=[2001:1620:4:d401:29:64aa:f77f:d8e0]:9001" +/* nickname=habo01 */ +/* extrainfo=0 */ +/* ===== */ +, +"185.195.71.3 orport=443 id=3C5915348D731505C48112F4F03235FDE7B8C837" +/* nickname=AccessNow001 */ +/* extrainfo=0 */ +/* ===== */ +, +"212.83.43.93 orport=443 id=B86137AE9681701901C6720E55C16805B46BD8E3" +" ipv6=[2a00:f48:2000:1038:72a7:e81a:29ff:abba]:443" +/* nickname=BeastieJoy60 */ +/* extrainfo=0 */ +/* ===== */ +, +"79.137.196.223 orport=9091 id=47A77C3BC4E50D6DE2D3BF24046C57A72ED1E45F" +/* nickname=VitaminCoin */ +/* extrainfo=0 */ +/* ===== */ +, +"91.208.162.197 orport=8081 id=D3D2E42D8E625D36489015AB1E8081CE520DF2FE" +" ipv6=[2001:678:6d4:5100::a1b]:8081" +/* nickname=pissnissemoldova */ +/* extrainfo=0 */ +/* ===== */ +, +"45.91.77.77 orport=443 id=C2B6ADF4EFEA73CA2EFB2789E2B74C1034C1F5F2" +" ipv6=[2a0f:4ac4:42::704:c0ca:1eaf]:443" +/* nickname=c0ca1eaf */ +/* extrainfo=0 */ +/* ===== */ +, +"82.66.61.19 orport=995 id=40FA09C151C3893B7018DEF55A9854BC9768B82C" +" ipv6=[2a01:e0a:5d6:6de0:acab:3:3:3]:995" +/* nickname=Abeille */ +/* extrainfo=0 */ +/* ===== */ +, +"185.243.218.27 orport=8443 id=5B9086D4BFB9EA36C95897DAED72FC3973847B43" +" ipv6=[2a03:94e0:ffff:185:243:218:0:27]:8443" +/* nickname=bauruine */ +/* extrainfo=0 */ +/* ===== */ +, +"212.83.61.218 orport=443 id=D0562BB74A5CC8872D11B222677009A8922FC38C" +/* nickname=mitropoulos */ +/* extrainfo=0 */ +/* ===== */ +, +"37.200.99.251 orport=9001 id=F6EC46933CE8D4FAD5CCDAA8B1C5A377685FC521" +" ipv6=[2a00:1158:3::1ba]:9001" +/* nickname=JPsi2 */ +/* extrainfo=0 */ +/* ===== */ +, +"94.230.208.147 orport=8443 id=311A4533F7A2415F42346A6C8FA77E6FD279594C" +" ipv6=[2a02:418:6017::147]:8443" +/* nickname=DigiGesTor3e2 */ +/* extrainfo=0 */ +/* ===== */ +, +"54.36.120.156 orport=443 id=D0273C8566CC9AECE4C762376C9B066FE0F1DADD" +/* nickname=Kimchi */ +/* extrainfo=0 */ +/* ===== */ +, +"5.45.102.119 orport=9100 id=189C44DD06312D6DF8FB57A944E6819FF245740C" +" ipv6=[2a03:4000:6:608:942a:42ff:fe77:728c]:9100" +/* nickname=Quetzalcoatl */ +/* extrainfo=0 */ +/* ===== */ +, +"199.249.230.74 orport=443 id=9C1E7D92115D431385B8CAEA6A7C15FB89CE236B" +" ipv6=[2620:7:6001::ffff:c759:e64a]:80" +/* nickname=QuintexAirVPN21 */ +/* extrainfo=0 */ +/* ===== */ +, +"145.239.206.31 orport=8001 id=912A7C57E05606AF602EC63D1D06BBD5C7AAE516" +" ipv6=[2001:41d0:800:b1f::]:8001" +/* nickname=0x3d02 */ +/* extrainfo=0 */ +/* ===== */ +, +"144.217.90.187 orport=9001 id=7040C1F5728746C5FB5E12845101A26EE8636D7E" +" ipv6=[2607:5300:201:3100::5b0d]:9001" +/* nickname=SaruTorUmidanuki */ +/* extrainfo=0 */ +/* ===== */ +, +"138.201.250.33 orport=9011 id=2BA2C8E96B2590E1072AECE2BDB5C48921BF8510" +/* nickname=storm */ +/* extrainfo=0 */ +/* ===== */ +, +"87.118.116.103 orport=443 id=26C28F29B611DF4DE23ACF5D9DC1EB4895EF5E8B" +" ipv6=[2001:1b60:3:221:4134:101:0:1]:443" +/* nickname=artikel5ev4 */ +/* extrainfo=0 */ +/* ===== */ +, +"199.249.230.110 orport=443 id=7FA8E7E44F1392A4E40FFC3B69DB3B00091B7FD3" +" ipv6=[2620:7:6001::110]:80" +/* nickname=Quintex20 */ +/* extrainfo=0 */ +/* ===== */ +, +"199.249.230.82 orport=443 id=A0DB820FEC87C0405F7BF05DEE5E4ADED2BB9904" +" ipv6=[2620:7:6001::ffff:c759:e652]:80" +/* nickname=QuintexAirVPN29 */ +/* extrainfo=0 */ +/* ===== */ +, +"5.2.72.110 orport=9001 id=5847D5A01C47166143F738C7703344517B39EB10" +/* nickname=7342426259276943 */ +/* extrainfo=0 */ +/* ===== */ +, +"50.39.143.177 orport=59090 id=A1C515432EF6BF2E699A6184ED78DF0B9A595655" +/* nickname=lilonionboi */ +/* extrainfo=0 */ +/* ===== */ +, +"174.128.250.163 orport=443 id=F7A052D4EEA2F4BC942DFB054AF2DC54A2A37E5D" +/* nickname=rockstars1 */ +/* extrainfo=0 */ +/* ===== */ +, +"163.172.213.212 orport=10010 id=CB7DB13172E15D4AD7F9404667021DF7DF6E9A4A" +/* nickname=noconname */ +/* extrainfo=0 */ +/* ===== */ +, +"65.108.10.141 orport=9001 id=7B46F20449D6F25150E189428B62E1E3BA5848A9" +" ipv6=[2a01:4f9:6a:4642::2]:9001" +/* nickname=galtlandeu */ +/* extrainfo=0 */ +/* ===== */ +, +"185.112.156.72 orport=443 id=089E8959D386C4DC283FFDA2B590A65FCE85C8B4" +/* nickname=TheAdoptee */ +/* extrainfo=0 */ +/* ===== */ +, +"15.235.29.234 orport=9001 id=1B3098A711D00ECD428576EB2E868ED4D22C96B1" +/* nickname=BSSP08 */ +/* extrainfo=0 */ +/* ===== */ +, +"135.125.202.252 orport=9001 id=720ABE4554C55EE6F6099491CA55D1F5550512C5" +/* nickname=slotor02 */ +/* extrainfo=0 */ +/* ===== */ +, +"135.148.52.231 orport=443 id=BA6E064596B86AF9F55F0603A82C90E958E86E7A" +/* nickname=twoandtwoone */ +/* extrainfo=0 */ +/* ===== */ +, +"92.222.79.186 orport=443 id=78AA54A1B4169242310499DD81C72EF519B070F3" +/* nickname=CebolaServer */ +/* extrainfo=0 */ +/* ===== */ +, +"213.144.135.21 orport=443 id=BB5FAE50BCE5B13C810CD17A931A0498E4681D41" +" ipv6=[2a02:168:6a16:1130::32:101]:443" +/* nickname=YoYuD1N01NoExit */ +/* extrainfo=0 */ +/* ===== */ +, +"139.162.166.237 orport=443 id=8BEA5AB8F95DE1920775A1A8F4C34B947A7DE505" +/* nickname=shadowdancer */ +/* extrainfo=0 */ +/* ===== */ +, +"95.216.101.247 orport=443 id=38CC95A8CE92A591D4A5779359BEFFBA13FA1B88" +" ipv6=[2a01:4f9:2b:151f:95:216:101:247]:443" +/* nickname=TykRelay02 */ +/* extrainfo=0 */ +/* ===== */ +, +"191.252.111.55 orport=443 id=1D1FA50D605FDC8F6DC39A0A60A7233DD35D0001" +/* nickname=K4M1K4Z3 */ +/* extrainfo=0 */ +/* ===== */ +, +"131.188.40.188 orport=11180 id=EBE718E1A49EE229071702964F8DB1F318075FF8" +" ipv6=[2001:638:a000:4140::ffff:188]:11180" +/* nickname=fluxe4 */ +/* extrainfo=0 */ +/* ===== */ +, +"50.236.201.218 orport=9001 id=B5053ABFF845C96B1DD8F45DCF32E6BE1E63F127" +/* nickname=Stephen304 */ +/* extrainfo=0 */ +/* ===== */ +, +"5.182.211.119 orport=9001 id=DD7BD3E5BD0BA48A8C70B0CDF017FA5988B87E27" +/* nickname=Assange014nl */ +/* extrainfo=0 */ +/* ===== */ +, +"209.51.188.48 orport=443 id=CEE804FA03A87A65CAA6BCB3A250B5ED923C08BD" +" ipv6=[2001:470:142:5::48]:443" +/* nickname=FSF */ +/* extrainfo=0 */ +/* ===== */ +, +"185.228.136.146 orport=9100 id=F0D01EB1FDC508279AB3412AF3FC950BB1DA2AD1" +" ipv6=[2a03:4000:23:50:c8ed:bcff:fe13:ee61]:9100" +/* nickname=Quetzalcoatl */ +/* extrainfo=0 */ +/* ===== */ +, +"89.58.52.189 orport=9001 id=C3277FBEAB946672D468E90A16BB027B4CACC537" +" ipv6=[2a03:4000:69:d5d::2]:9001" +/* nickname=itrickl02 */ +/* extrainfo=0 */ +/* ===== */ +, +"188.40.147.177 orport=443 id=1D702DA43D588FE9D308D3879A6F5E61BB2ECCFA" +/* nickname=schumacher */ +/* extrainfo=0 */ +/* ===== */ +, +"82.220.38.150 orport=443 id=A4DE3FC1ACEC3767F5C4049BCEF57317E7B0583C" +/* nickname=eclipse12 */ +/* extrainfo=0 */ +/* ===== */ +, +"65.39.97.13 orport=9001 id=0D1AD0392583CBC22B5F712165D9D752D35F0699" +/* nickname=TorNodeAlpha */ +/* extrainfo=0 */ +/* ===== */ +, +"23.137.251.61 orport=8443 id=B558F456FB410E6CDF3D33AC5EB5305D66DA8B19" +" ipv6=[2602:fc24:13:e::fefe]:8443" +/* nickname=bauruine */ +/* extrainfo=0 */ +/* ===== */ +, +"81.6.40.139 orport=995 id=E36536404200A74930DB165858BD5BB554D2BEA2" +" ipv6=[2a02:168:2000:5f:7b5c:fee4:84b2:30b]:995" +/* nickname=naiveTorer */ +/* extrainfo=0 */ +/* ===== */ +, +"185.194.141.178 orport=9100 id=1B6BCBCDB384364B6FB4F3576CA70AECFC083641" +" ipv6=[2a03:4000:1c:7e3:e8aa:c8ff:fe36:66ee]:9100" +/* nickname=Quetzalcoatl */ +/* extrainfo=0 */ +/* ===== */ +, +"185.220.101.206 orport=8443 id=ADF0D51946DA3294C1F242B0ACADC91FF5F058EF" +" ipv6=[2a0b:f4c2:2:1::206]:8443" +/* nickname=ForPrivacyNET */ +/* extrainfo=0 */ +/* ===== */ +, +"158.69.207.216 orport=9001 id=6565F31D9EC0C7DFFEA1920BE3BA4C73EF35B5C4" +" ipv6=[2607:5300:201:3000::dfc]:9001" +/* nickname=oscar */ +/* extrainfo=0 */ +/* ===== */ +, +"199.249.230.141 orport=443 id=B7ECD9C6A910A170B55165742049CBCC777494F2" +" ipv6=[2620:7:6001::141]:80" +/* nickname=Quintex52 */ +/* extrainfo=0 */ +/* ===== */ +, +"178.175.148.205 orport=9001 id=8769AF6DCC45DAB9C219CD9F464EAE3268550CFD" +" ipv6=[2a00:1dc0:caff:ad::e746]:9001" +/* nickname=yellowsun */ +/* extrainfo=0 */ +/* ===== */ +, +"51.15.59.15 orport=444 id=B1F926DA3895A89AF288623F5A4F913979299C53" +" ipv6=[2001:bc8:1860:1419::1]:444" +/* nickname=artikel5ev13b */ +/* extrainfo=0 */ +/* ===== */ +, +"192.42.116.17 orport=443 id=0485027A0A349D454D978F6C1CECDD29EA17769A" +" ipv6=[2001:67c:6ec:203:218:33ff:fe44:5517]:443" +/* nickname=hviv117 */ +/* extrainfo=0 */ +/* ===== */ +, +"193.108.118.218 orport=443 id=79D9E66BB2FDBF25E846B635D8248FE1194CFD26" +" ipv6=[2604:86c0:f001:2:9baf:37c2:e99e:babe]:443" +/* nickname=BeastieJoy65 */ +/* extrainfo=0 */ +/* ===== */ +, +"83.78.216.58 orport=43633 id=7ACCD860B360118F43275FCF215F6A4C9D5E016B" +/* nickname=bauruine */ +/* extrainfo=0 */ +/* ===== */ +, +"23.154.177.3 orport=443 id=76CA636C1D33E3E8630B7AC22A1D07420FCE8761" +/* nickname=UnredactedManning */ +/* extrainfo=0 */ +/* ===== */ +, +"213.149.82.60 orport=9001 id=45D8C1EEFF044043AA6806C4B9130F8F189EF316" +" ipv6=[2a02:2488:4211:3400::3]:9001" +/* nickname=Feuermagier */ +/* extrainfo=0 */ +/* ===== */ +, +"49.12.225.94 orport=9001 id=EE2A9108A9EDBFCB5C44F2993267573188176AA4" +/* nickname=myzwiebel03 */ +/* extrainfo=0 */ +/* ===== */ +, +"172.107.202.142 orport=443 id=036E015AD84D3C605666E3A3306B1E3E18A89482" +/* nickname=parsel */ +/* extrainfo=0 */ +/* ===== */ +, +"94.199.217.225 orport=443 id=05D01D243BB76468B80670CCF8F07F5E9296736D" +/* nickname=madblock */ +/* extrainfo=0 */ +/* ===== */ +, +"185.233.100.23 orport=443 id=F47B13BFCE4EF48CDEF6C4D7C7A99208EBB972B5" +" ipv6=[2a0c:e300::23]:443" +/* nickname=Elenagb */ +/* extrainfo=0 */ +/* ===== */ +, +"213.135.244.242 orport=24071 id=2D938F19EAF660D902C656B5E6002F39B45C4BE4" +/* nickname=VoxBox */ +/* extrainfo=0 */ +/* ===== */ +, +"45.132.246.38 orport=9001 id=431702B3A68A6015F9955DD4FD0129175B43EA0F" +" ipv6=[2a03:4000:48:182:74dd:c1ff:fea8:d21e]:9001" +/* nickname=bituman */ +/* extrainfo=0 */ +/* ===== */ +, +"82.223.23.176 orport=443 id=C22A591FF4EE577C408DAFD26C75302615E5165E" +" ipv6=[2001:ba0:1800:8188::1]:443" +/* nickname=FrankyThePooper */ +/* extrainfo=0 */ +/* ===== */ +, +"23.175.32.11 orport=443 id=56BAF2F6CAE76B1AC6C1F08C148D04C219E85E70" +" ipv6=[2606:d680:cafe:80:23:175:32:11]:443" +/* nickname=Wat1E1TorNodeIo */ +/* extrainfo=0 */ +/* ===== */ +, +"91.203.5.165 orport=443 id=AF1F15819AC766D6508A2B05DA989E99AB511F9F" +/* nickname=duchin */ +/* extrainfo=0 */ +/* ===== */ +, +"96.126.105.219 orport=5353 id=60A5547B2203DD2E148EF9BDD6FF3891081C5DF4" +" ipv6=[2600:3c03::f03c:91ff:fe93:5318]:5353" +/* nickname=HotPotato */ +/* extrainfo=0 */ +/* ===== */ +, +"212.32.240.165 orport=9001 id=CFDF99EE1923D870329F8DAE54398FD45409F01F" +/* nickname=UlhasTor */ +/* extrainfo=0 */ +/* ===== */ +, +"185.140.251.125 orport=9001 id=215CC28D7E273AE3308F041E45A8EE3BA6D85658" +/* nickname=HoarseSupernova */ +/* extrainfo=0 */ +/* ===== */ +, +"38.145.211.218 orport=9010 id=3FAFFA13BC88AB28EBA464A902FDED2EA453A581" +" ipv6=[2605:f700:40:401::2efa:261a]:9010" +/* nickname=edaks */ +/* extrainfo=0 */ +/* ===== */ +, +"217.112.131.24 orport=443 id=7896A8075D51F60B950D8E63AAC2899731060843" +/* nickname=licinius */ +/* extrainfo=0 */ +/* ===== */ +, +"46.38.232.203 orport=443 id=8B3A07D9155E76BB4AE922C8F99AE3AB7D88DD23" +/* nickname=ConcordiaConstanzia */ +/* extrainfo=0 */ +/* ===== */ +, +"194.76.227.152 orport=9001 id=2F98E853A570AC7A79B4082364B781AD67705074" +/* nickname=FireMateria */ +/* extrainfo=0 */ +/* ===== */ +, +"192.99.43.171 orport=9001 id=C92ECAF73512E2CCB15827A192B7AF3E9DBC896E" +/* nickname=Unnamed */ +/* extrainfo=0 */ +/* ===== */ +, +"104.244.72.20 orport=9001 id=799E0B28F45548F545668A78DF04CD23490EC585" +" ipv6=[2605:6400:30:efe6:1313:cafe:dead:beef]:9001" +/* nickname=Hermes */ +/* extrainfo=0 */ +/* ===== */ +, +"89.58.4.238 orport=9001 id=4745ACB16234385EF1694D530E109F7A573E30C6" +" ipv6=[2a03:4000:5e:d48:acab::10]:9001" +/* nickname=tweinode3 */ +/* extrainfo=0 */ +/* ===== */ +, +"51.15.54.117 orport=443 id=547E6E68ADE1B6F492C44443588A939610401DFB" +" ipv6=[2001:bc8:1820:c0d::1]:443" +/* nickname=taki */ +/* extrainfo=0 */ +/* ===== */ +, +"37.235.48.247 orport=7654 id=1F2077BF01CAF23F819D4892A89883196ABA842A" +" ipv6=[2a03:f80:48:37:235:48:247:1]:7654" +/* nickname=plutoa */ +/* extrainfo=0 */ +/* ===== */ +, +"51.159.184.219 orport=9001 id=6BE5AA34A5C391724677A3FDC2AA77B3768F6E26" +" ipv6=[2001:bc8:1200:4137::1]:9001" +/* nickname=tormaumauhosting */ +/* extrainfo=0 */ +/* ===== */ +, +"85.239.34.40 orport=9001 id=8E97A9FDBC262A2DB1C87B53F12CF1925866F355" +" ipv6=[2001:678:6d4:7410::12e]:9001" +/* nickname=itsnotjesse */ +/* extrainfo=0 */ +/* ===== */ +, +"198.251.68.144 orport=9001 id=83AEDBDB4BE3AD0ED91850BF1A521B843077759E" +/* nickname=focaltohr */ +/* extrainfo=0 */ +/* ===== */ +, +"89.58.3.65 orport=443 id=FF8B7CAD5F508972509D79F933FB24D2F524AB75" +" ipv6=[2a03:4000:5d:b8f:1478:68ff:fec4:27c7]:443" +/* nickname=einNettesRelay */ +/* extrainfo=0 */ +/* ===== */ +, +"172.106.112.50 orport=443 id=996F4CFD78130203B80E854A4EF6CA2355C6C72C" +/* nickname=Reeses1 */ +/* extrainfo=0 */ +/* ===== */ +, +"37.252.190.176 orport=443 id=81A59766272894D27FE8375C4F83A6BA453671EF" +/* nickname=chutney */ +/* extrainfo=0 */ +/* ===== */ +, +"185.25.51.138 orport=9001 id=D2380745937A994FE1E19859CBEBF181DBB80443" +" ipv6=[2a04:2180:0:1::fb6f:72f2]:9001" +/* nickname=IndigoMagick */ +/* extrainfo=0 */ +/* ===== */ +, +"93.95.231.115 orport=9001 id=3417F1F24A7CA4033DB514610321A1A9F410CC31" +/* nickname=MetalsAG */ +/* extrainfo=0 */ +/* ===== */ +, +"104.192.3.74 orport=443 id=849626A2A3DD1364E8C51423D6F3213A3AE16FFD" +" ipv6=[2605:aa80:0:9::3]:443" +/* nickname=OnionsHaveLayers */ +/* extrainfo=0 */ +/* ===== */ +, +"193.108.117.103 orport=9001 id=438F3EA4C9FB0DB63F5377A3271AB5435FAD7E04" +/* nickname=dolemite */ +/* extrainfo=0 */ +/* ===== */ +, +"68.67.32.33 orport=9001 id=4338C8026D468B811D3EB11AE9E421E2089B8239" +/* nickname=kgXuCTCWVMALFMb74Ld */ +/* extrainfo=0 */ +/* ===== */ +, +"199.249.230.118 orport=443 id=5F4CD12099AF20FAF9ADFDCEC65316A376D0201C" +" ipv6=[2620:7:6001::118]:80" +/* nickname=QuintexAirVPN7 */ +/* extrainfo=0 */ +/* ===== */ +, +"140.78.100.42 orport=5443 id=9D970B7FBAC353D8F6049AD4E0CEE06BBDE4E17E" +/* nickname=INSRelay42at5443 */ +/* extrainfo=0 */ +/* ===== */ +, +"190.211.254.182 orport=9001 id=3B557E3F0C29D4339A904AD8C641F582151FEF71" +/* nickname=ForestIsland */ +/* extrainfo=0 */ +/* ===== */ +, +"74.82.47.194 orport=9001 id=80E23F24D5BE0195D2827557D260D1676DEA5451" +" ipv6=[2001:470:1:908::9001]:9001" +/* nickname=deadbabecafe */ +/* extrainfo=0 */ +/* ===== */ +, +"130.61.174.201 orport=9001 id=519351E3D54202933F85E608D88484A5DC4E4EF0" +/* nickname=bigman */ +/* extrainfo=0 */ +/* ===== */ +, +"185.220.101.203 orport=8443 id=8F11B2E253CEC4C5C463BF38AB1CA645B7294D52" +" ipv6=[2a0b:f4c2:2:1::203]:8443" +/* nickname=ForPrivacyNET */ +/* extrainfo=0 */ +/* ===== */ +, +"185.241.208.204 orport=9000 id=9A8902B985E2F58BC740671040E7165AC904DD40" +/* nickname=Aramis */ +/* extrainfo=0 */ +/* ===== */ +, +"85.208.144.164 orport=443 id=B13C2C569F3FD0C530B7D96E5FF7933DF7A0E834" +" ipv6=[2a09:8740:0:3::13:4008]:443" +/* nickname=porte */ +/* extrainfo=0 */ +/* ===== */ +, +"185.14.30.57 orport=9001 id=58FC2AAB3792AC37897D34331F4F4E00341DEC0C" +" ipv6=[2a02:27ab:0:2::22]:9001" +/* nickname=zwewwlNL1 */ +/* extrainfo=0 */ +/* ===== */ +, +"193.56.240.157 orport=443 id=8E6225BC8A770DF63B20A2FDAC1ABCD795A18987" +/* nickname=skaalz */ +/* extrainfo=0 */ +/* ===== */ +, +"185.244.195.103 orport=9100 id=DFA97DED4CE79FF6F31DAF917C2810CCE8729E9D" +" ipv6=[2a03:4000:27:713:4489:4cff:feab:96fc]:9100" +/* nickname=Quetzalcoatl */ +/* extrainfo=0 */ +/* ===== */ +, +"155.248.212.228 orport=443 id=E5E553F51D82035A2CE555DBC7D883FAA32ED0B5" +" ipv6=[2603:c024:c000:e400:5d76:a308:5c3a:b70]:443" +/* nickname=yyzz1 */ +/* extrainfo=0 */ +/* ===== */ +, +"95.216.27.105 orport=9001 id=0B45375A2CE8065E8A649D52CC35F39D128745A8" +" ipv6=[2a01:4f9:2a:1b96:2::2]:443" +/* nickname=Ranlvor */ +/* extrainfo=0 */ +/* ===== */ +, +"185.220.101.68 orport=9000 id=42438E63C78CF0624BD7D212524FFE292D8FE3D5" +/* nickname=CCCStuttgartBer */ +/* extrainfo=0 */ +/* ===== */ +, +"103.28.52.93 orport=443 id=C5A6FEE5BC3BE19F5B9EB086CA95DAD393D8A4F6" +/* nickname=jivin */ +/* extrainfo=0 */ +/* ===== */ +, +"82.66.10.17 orport=19001 id=8F7521CEDA9AB705A42254E1E829268DEF57C70E" +/* nickname=bigbrother23470 */ +/* extrainfo=0 */ +/* ===== */ +, +"185.247.226.98 orport=443 id=9AB4B4F5B279DC611BEB62E4528EB91F59A6BB14" +" ipv6=[2a06:1700:2:17:0:5232:2d:4432]:443" +/* nickname=R2D2 */ +/* extrainfo=0 */ +/* ===== */ +, +"178.27.106.248 orport=9031 id=9C305BC09852C7CB62E9A41F9ECA108BBFB23521" +/* nickname=Feidhlim04 */ +/* extrainfo=0 */ +/* ===== */ +, +"140.78.100.28 orport=5443 id=0DBA891A70AE95D4AD77593A936E6C04ABF2E7CE" +/* nickname=INSRelay28at5443 */ +/* extrainfo=0 */ +/* ===== */ +, +"65.108.136.183 orport=443 id=C3F7F3E1E32A64B22B2F0734E7C7A312F993D102" +" ipv6=[2a01:4f9:6b:3408::4]:443" +/* nickname=arbitraryKenzie4 */ +/* extrainfo=0 */ +/* ===== */ +, +"51.15.185.201 orport=443 id=1079E628FC6B0025656AC024F2D9975C441498CD" +/* nickname=PoochySloochy */ +/* extrainfo=0 */ +/* ===== */ +, +"171.25.193.234 orport=80 id=CF1C1804C33CD69D8A75587FABC63D5D0E2980FA" +" ipv6=[2001:67c:289c:2::234]:80" +/* nickname=DFRI10 */ +/* extrainfo=0 */ +/* ===== */ +, +"199.249.230.105 orport=443 id=9F2856F6D2B89AD4EF6D5723FAB167DB5A53519A" +" ipv6=[2620:7:6001::105]:80" +/* nickname=Quintex15 */ +/* extrainfo=0 */ +/* ===== */ +, +"89.236.112.100 orport=443 id=00CCE6A84E6D63A1A42E105839BC8ED5D4B16669" +/* nickname=effiorg1984 */ +/* extrainfo=0 */ +/* ===== */ +, +"209.209.11.184 orport=31289 id=21FFF594CFE691A4A03B828E9597A9F74F878053" +" ipv6=[2602:ffd5:1:112::1]:31288" +/* nickname=Woodman */ +/* extrainfo=0 */ +/* ===== */ +, +"93.95.230.85 orport=443 id=3B20B5D120AB8CC1780F43216DC9C6051ED5C387" +/* nickname=Unnamed */ +/* extrainfo=0 */ +/* ===== */ +, +"84.158.138.52 orport=10222 id=C4C462506D54EDC8EFC8E88E32F4AE014755035A" +/* nickname=s0yb3an */ +/* extrainfo=0 */ +/* ===== */ +, +"89.147.108.62 orport=443 id=F3A9588FB45F76DA4DE5B350C425C130F6FFA983" +/* nickname=Reichsfunkmast2 */ +/* extrainfo=0 */ +/* ===== */ +, +"46.4.103.29 orport=9001 id=46DB04323499DD535956531DF2BF7B03EB2AB15D" +" ipv6=[2a01:4f8:140:94d6::2]:9001" +/* nickname=mickymouse */ +/* extrainfo=0 */ +/* ===== */ +, +"199.249.230.168 orport=443 id=F664E5E50B4D216E5940DA7E9CF653F5F9DC561B" +" ipv6=[2620:7:6001::168]:80" +/* nickname=Quintex79 */ +/* extrainfo=0 */ +/* ===== */ +, +"185.239.222.245 orport=443 id=3DCECAF7089B1C2CE3EA9504EE05CE754F4CF9A8" +" ipv6=[2a09:2681:101:9001::6]:443" +/* nickname=BM06 */ +/* extrainfo=0 */ +/* ===== */ +, +"94.16.117.97 orport=9000 id=13FBC97516DC854399E70BC7CA9A4513FFD4F08C" +" ipv6=[2a03:4000:29:1:d4b9:91ff:fe6e:e48a]:9000" +/* nickname=Quetzalcoatl */ +/* extrainfo=0 */ +/* ===== */ +, +"46.163.76.170 orport=9001 id=3E2C933BE54585C760FD6D2F3FA7A33E373A7145" +" ipv6=[2a01:488:66:1000:2ea3:4caa:0:1]:9001" +/* nickname=judas */ +/* extrainfo=0 */ +/* ===== */ +, +"204.85.191.8 orport=443 id=7C0AA4E3B73E407E9F5FEB1912F8BE26D8AA124D" +/* nickname=ibibUNC0 */ +/* extrainfo=0 */ +/* ===== */ +, +"109.190.177.33 orport=9999 id=A8874E2C45F445DBA462A914ED8D3AF045734FFB" +/* nickname=computel */ +/* extrainfo=0 */ +/* ===== */ +, +"176.10.119.69 orport=9001 id=FFC3F4BE4D5C392246DC7A37B256A6158C3D8562" +/* nickname=euler */ +/* extrainfo=0 */ +/* ===== */ +, +"162.251.116.106 orport=443 id=24A818D9F1E09F1845FC1589DE5AAF15C8E0867E" +/* nickname=goingin */ +/* extrainfo=0 */ +/* ===== */ +, +"198.50.238.128 orport=443 id=C7946D9A192BBE44C1C8A926A8B135AC495D6636" +/* nickname=t0adwarri0r */ +/* extrainfo=0 */ +/* ===== */ +, +"157.90.92.115 orport=9001 id=AC633C90E126E0BCA96F14ECE5D222B586FA0D56" +" ipv6=[2a01:4f8:252:3df0::2]:9001" +/* nickname=SODrelay */ +/* extrainfo=0 */ +/* ===== */ +, +"132.145.22.208 orport=443 id=A8FB73D917B7C2B851A358729359E13EBA5978FA" +" ipv6=[2603:c020:c007:cab:5235:2d:534b:31]:443" +/* nickname=R5SK1 */ +/* extrainfo=0 */ +/* ===== */ +, +"91.203.144.194 orport=443 id=B1B687C3C4EF46249D638DCE77DDC7AAA39F2996" +/* nickname=yetiready */ +/* extrainfo=0 */ +/* ===== */ +, +"204.85.191.9 orport=443 id=C59E079437340E3AD14E6785C0A91A5B6F328566" +/* nickname=ibibUNC1 */ +/* extrainfo=0 */ +/* ===== */ +, +"46.232.250.51 orport=443 id=D1C60F9BCF2DBA07A775978F66C9927D3A9490BB" +" ipv6=[2a03:4000:2b:673:24da:28ff:feb5:e5c5]:443" +/* nickname=juggernautrelay */ +/* extrainfo=0 */ +/* ===== */ +, +"45.155.168.206 orport=443 id=6F9B7403B4CD1F48269730D1F9ED0E7A75C1F049" +/* nickname=WizardFair */ +/* extrainfo=0 */ +/* ===== */ +, +"185.243.218.27 orport=443 id=A5E42F1A3AFA948A7F2FDB1954A4CF6C6489D418" +" ipv6=[2a03:94e0:ffff:185:243:218:0:27]:443" +/* nickname=bauruine */ +/* extrainfo=0 */ +/* ===== */ +, +"51.75.129.204 orport=443 id=2CE9BE1FC88B9D0FA03F387C9E4F000B5D4B2AE9" +/* nickname=ridin */ +/* extrainfo=0 */ +/* ===== */ +, +"188.68.36.28 orport=9001 id=7260949459C2D4D4A5ABC3AC10C1AD6939E6525C" +" ipv6=[2a03:4000:13:7c3:a401:f1ff:fe45:7ffc]:9001" +/* nickname=Piratenpartei05 */ +/* extrainfo=0 */ +/* ===== */ +, +"82.149.227.124 orport=8080 id=7A6EC43FD4CD5990230FCE48EC37AFC578E36CE6" +" ipv6=[2a01:440:108:11:82:149:227:124]:8080" +/* nickname=cryzrelay03 */ +/* extrainfo=0 */ +/* ===== */ +, +"135.148.100.90 orport=443 id=728F97D5BCB131698814D8C713C2220C6E7267DE" +/* nickname=CanisLatrans */ +/* extrainfo=0 */ +/* ===== */ +, +"185.183.157.127 orport=9100 id=34B80D703F4D6350146B684E66D962A23A830117" +" ipv6=[2a03:4000:1d:b56:5850:c5ff:feca:c5b0]:9100" +/* nickname=Quetzalcoatl */ +/* extrainfo=0 */ +/* ===== */ +, +"179.43.159.194 orport=9001 id=617C95FCF5F00E98E73E35A71C066ED20614F26D" +/* nickname=FreeExit */ +/* extrainfo=0 */ +/* ===== */ +, +"185.244.195.157 orport=9100 id=69042D6B301F080105D11478A5BC848EB0B5D5DB" +" ipv6=[2a03:4000:27:747:4804:22ff:fe7a:e606]:9100" +/* nickname=Quetzalcoatl */ +/* extrainfo=0 */ +/* ===== */ +, +"178.254.6.130 orport=443 id=7A2D44E76934D709CBF0E0AE8FEB132B61B6F35D" +" ipv6=[2a00:6800:3:35b::2]:443" +/* nickname=zensursula */ +/* extrainfo=0 */ +/* ===== */ +, +"142.132.204.165 orport=4080 id=6CBA90E31188A65E2B6AC1EE8412E8DE571ECAD6" +" ipv6=[2a01:4f8:261:50da::2]:4080" +/* nickname=spicyavocado */ +/* extrainfo=0 */ +/* ===== */ +, +"66.183.173.29 orport=22 id=E8FA2C9B690F3BC16ADE9A4803CE2C51132F10A6" +/* nickname=tengu */ +/* extrainfo=0 */ +/* ===== */ +, +"31.171.154.165 orport=9001 id=A1177D4BF4698A74B926AE1E0FC533578FA55667" +/* nickname=Tirana */ +/* extrainfo=0 */ +/* ===== */ +, +"91.134.147.134 orport=9001 id=2AD82F3964D325B3FE2FF74E980FB006374EF190" +/* nickname=Unnamed */ +/* extrainfo=0 */ +/* ===== */ +, +"193.105.73.80 orport=9001 id=9DC8B0282A8D3C45212167C454B503243BC93957" +/* nickname=akira */ +/* extrainfo=0 */ +/* ===== */ +, +"140.78.100.22 orport=5443 id=69D7FEF9B0026393C2FD73E897C71C102ABACA5C" +/* nickname=INSRelay22at5443 */ +/* extrainfo=0 */ +/* ===== */ +, +"23.129.64.217 orport=443 id=41C80C52AC82295A4D4308D30DCCD3D4ABC4F66C" +" ipv6=[2620:18c:0:192::217]:443" +/* nickname=EO */ +/* extrainfo=0 */ +/* ===== */ +, +"65.108.253.128 orport=443 id=0B53BF919B9A01ED62ED10E51292ACA50DDB10EB" +" ipv6=[2a01:4f9:c011:9e93::1]:443" +/* nickname=l0z3rzb3d4m3d */ +/* extrainfo=0 */ +/* ===== */ +, +"198.98.60.107 orport=443 id=23C64D7C96C8390013C9E583230E81E70F81F756" +/* nickname=BingusBongus */ +/* extrainfo=0 */ +/* ===== */ +, +"188.68.40.46 orport=9100 id=2EFC2B8BC724CF435C14066087936BE7CA3C57A3" +" ipv6=[2a03:4000:17:5c:24df:84ff:fe54:82aa]:9100" +/* nickname=Quetzalcoatl */ +/* extrainfo=0 */ +/* ===== */ +, +"138.201.35.6 orport=9001 id=94B710C41B9E8FB592D0916E71B5A89FF4995996" +" ipv6=[2a01:4f8:171:369a::2]:9001" +/* nickname=torrelay */ +/* extrainfo=0 */ +/* ===== */ +, +"51.81.208.164 orport=443 id=756576CF5DA387CDD7EABCCA3F8EAEF933CB4486" +/* nickname=screenslaver */ +/* extrainfo=0 */ +/* ===== */ +, +"89.147.109.91 orport=443 id=7155DE90C1C3C9BF4D637580C7F027E57227BD30" +/* nickname=Unnamed */ +/* extrainfo=0 */ +/* ===== */ +, +"152.70.197.164 orport=443 id=F882E4A4B73447C561617005C8E692B0A7080802" +/* nickname=screwTheNSA */ +/* extrainfo=0 */ +/* ===== */ +, +"163.172.211.16 orport=443 id=05F5062943054646CC7A65532CE52598052628FA" +/* nickname=FreeUyghur */ +/* extrainfo=0 */ +/* ===== */ +, +"174.128.250.164 orport=80 id=5197FC89F7A1623CA90D6E0254ABCCBC6D85A86E" +/* nickname=ready2 */ +/* extrainfo=0 */ +/* ===== */ +, +"13.211.32.165 orport=9001 id=956CD533B7C331C675D30A50900A2E9777D42782" +/* nickname=BlueMonkey */ +/* extrainfo=0 */ +/* ===== */ +, +"116.12.180.237 orport=7443 id=10805A3833774B812D07EB7D1D75A54021590F56" +/* nickname=tommyboy */ +/* extrainfo=0 */ +/* ===== */ +, +"212.192.246.55 orport=443 id=7B24FA67347BEAEC6923D884EACF1C123180DE32" +/* nickname=0x616e6f6e */ +/* extrainfo=0 */ +/* ===== */ +, +"150.246.240.221 orport=443 id=E3F7DA7CC7D5B6CC085799AE0404AA4D2D503E50" +/* nickname=90377Sedna */ +/* extrainfo=0 */ +/* ===== */ +, +"5.189.155.39 orport=9001 id=00962D2DD0B9BF3A6AF1D5EB201132388ACA1424" +/* nickname=BlackBeluga */ +/* extrainfo=0 */ +/* ===== */ +, +"104.244.72.91 orport=9001 id=BD140758135A15605996CCEE3BBFA4127F97B233" +/* nickname=Hydra44 */ +/* extrainfo=0 */ +/* ===== */ +, +"103.109.100.207 orport=9001 id=D08C694485A0031692FAFE8C3205FBBBDBCD9402" +/* nickname=thelastvampire */ +/* extrainfo=0 */ +/* ===== */ +, +"171.25.193.80 orport=80 id=491B4E55B4FD4FDEC63B229B0A3E59868FCA1F1F" +" ipv6=[2001:67c:289c:4::79]:80" +/* nickname=DFRI22 */ +/* extrainfo=0 */ +/* ===== */ +, +"199.249.230.152 orport=443 id=4EE488AC0742BC6B747BB637A5635CE14E877F39" +" ipv6=[2620:7:6001::152]:80" +/* nickname=Quintex63 */ +/* extrainfo=0 */ +/* ===== */ +, +"188.68.224.83 orport=443 id=A6BBD33695A4E3C4545BA370605A4DCD87D98BEE" +/* nickname=ATLANTIS */ +/* extrainfo=0 */ +/* ===== */ +, +"37.133.161.205 orport=9001 id=96C36107664B4406C38CD8676AEA85E4D8BCE825" +/* nickname=tor4win */ +/* extrainfo=0 */ +/* ===== */ +, +"198.180.150.9 orport=9001 id=60E4C5E306D2DB22890EE24A09F9B6C30AF396A8" +" ipv6=[2001:418:8006::9]:9001" +/* nickname=rgiad */ +/* extrainfo=0 */ +/* ===== */ +, +"129.151.246.99 orport=9001 id=439CD12F87CEB496D2601B5DC1FF5186BD9AC2D1" +/* nickname=tallinn21 */ +/* extrainfo=0 */ +/* ===== */ +, +"130.162.218.212 orport=9001 id=944E947CA80D1CF8B2EBE3C3247B2C11931438B8" +/* nickname=Perch */ +/* extrainfo=0 */ +/* ===== */ +, +"86.115.2.103 orport=9001 id=E816C4D4CDA9810D2CB1371257462FF6BCE4DF91" +/* nickname=homebox57 */ +/* extrainfo=0 */ +/* ===== */ +, From dc53d6913ae4ac95220c46b1e0576038bc85b8dc Mon Sep 17 00:00:00 2001 From: Afshin Arani Date: Sun, 29 Oct 2023 19:20:22 +0330 Subject: [PATCH 2/7] README: add F# documentation --- README.md | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/README.md b/README.md index d4463f7c..0392ccc9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ + NOnion ------------------------------- [![Build Status](https://github.com/nblockchain/NOnion/actions/workflows/CI.yml/badge.svg?branch=master&event=push)](https://github.com/nblockchain/NOnion/actions/workflows/CI.yml) @@ -5,6 +6,7 @@ NOnion _Unofficial_ work in progress .NET TOR client library (implemented in F#) - [How do I add this to my project?](#how-do-i-add-this-to-my-project) +- [Getting Started](#getting-started) - [Contributing](#contributing) - [Contributors](https://github.com/nblockchain/NOnion/graphs/contributors) - [License](https://github.com/nblockchain/NOnion/blob/master/LICENSE) @@ -23,6 +25,76 @@ Install via NuGet: > Install-Package NOnion ``` +# Getting Started (F#) + +## Bootstrapping + +To utilize the Tor network, start by bootstrapping your directory. This step involves downloading the most recent information about available routers and their identity keys. + +To initiate the bootstrapping process, you require a list of initial nodes for communication. In NOnion, there are two methods to obtain this list: +1- [Download from Github](https://github.com/torproject/tor/blob/main/src/app/config/fallback_dirs.inc) +2- Utilize the embedded list in the NOnion binary (Note: this list could potentially be outdated) + +Based on what option you choose use one of the following commands to bootstrap a TorClient object: +``` +let! torClient = TorClient.AsyncBootstrapWithEmbeddedList None +``` +### OR +``` +let! torClient = TorClient.AsyncBootstrapWithGithub None +``` +## Browsing the web + +To route your traffic through Tor, you require a circuit, which consists of multiple hops or routers. NOnion simplifies this process for you. Just specify the desired length of your circuit and use the following command: +``` +let hopCount = 3 // any length you want +let! circuit = torClient.AsyncCreateCircuit hopCount Exit None +``` +Once the circuit is established, generate a stream to channel your traffic and establish a connection with your intended destination. +``` +let address = "google.com" // Hostname you want to connect to +let port = 80 // Port you want to connect it + +do! + stream.ConnectToOutside address port + |> Async.Ignore +``` +Now, utilize the stream just like any other `System.IO.Stream`: +``` +do! stream.AsyncWrite [|1uy; 2uy; 3uy; 4uy|] +``` + +## Connecting to hidden services + +To connect to a hidden service using the Tor network, use the folowing command: +``` +let onionAddress = "facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion" + +let! serviceClient = TorServiceClient.Connect torClient onionAddress +let stream = serviceClient.GetStream() +``` +Now, utilize the stream just like any other `System.IO.Stream`: +``` +do! + sprintf "GET / HTTP/1.0\r\n\r\n" + |> System.Text.Encoding.ASCII.GetBytes + |> stream.AsyncWrite +``` +## Hosting a hidden service +Start a hidden service, use the following command: +``` +let serviceHost = new TorServiceHost(torClient, None) +do! serviceHost.Start() +``` +*Note: use TorServiceHost's `maybeMasterPrivateKey` parameter to supply your existing bouncy castle private key* + +Now use the following command to wait for incoming streams/clients: +``` +let! incomingStream = serviceHost.AcceptClient() +``` +Now, utilize the stream just like any other `System.IO.Stream`. + +#### Everything mentioned above can be accomplished in C#. Our test project is also written in C#, so feel free to examine it. # Contributing Don't underestimate the power of your contribution - even a small pull request can make a big difference in a small project, so submit yours today! From 1938e690faf09c1b6bdb27603917ad18b415bffd Mon Sep 17 00:00:00 2001 From: Afshin Arani Date: Sun, 3 Dec 2023 18:04:02 +0200 Subject: [PATCH 3/7] README: change fork url --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d4463f7c..28e3dfcd 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ NOnion ------------------------------- -[![Build Status](https://github.com/nblockchain/NOnion/actions/workflows/CI.yml/badge.svg?branch=master&event=push)](https://github.com/nblockchain/NOnion/actions/workflows/CI.yml) +[![Build Status](https://github.com/aarani/NOnion/actions/workflows/CI.yml/badge.svg?branch=master&event=push)](https://github.com/aarani/NOnion/actions/workflows/CI.yml) _Unofficial_ work in progress .NET TOR client library (implemented in F#) - [How do I add this to my project?](#how-do-i-add-this-to-my-project) - [Contributing](#contributing) -- [Contributors](https://github.com/nblockchain/NOnion/graphs/contributors) -- [License](https://github.com/nblockchain/NOnion/blob/master/LICENSE) +- [Contributors](https://github.com/aarani/NOnion/graphs/contributors) +- [License](https://github.com/aarani/NOnion/blob/master/LICENSE) # How do I add this to my project? From 51805742b22dc6eaffb962abfecdd564ad7971db Mon Sep 17 00:00:00 2001 From: Afshin Arani Date: Sun, 10 Dec 2023 18:45:08 +0200 Subject: [PATCH 4/7] Services,Tests: create HS streams on demand (#10) There are multiple usecases when you need multiple streams for your communication with HS client (e.g sending multiple http requests), this was not supported before and this commit fixes that. --- NOnion.Tests/HiddenServicesTests.cs | 9 +++++---- NOnion/Services/TorServiceClient.fs | 21 +++++++++++++-------- README.md | 2 +- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/NOnion.Tests/HiddenServicesTests.cs b/NOnion.Tests/HiddenServicesTests.cs index 72ddbd2e..2223639b 100644 --- a/NOnion.Tests/HiddenServicesTests.cs +++ b/NOnion.Tests/HiddenServicesTests.cs @@ -106,7 +106,8 @@ public async Task BrowseFacebookOverHS() using TorClient torClient = await TorClient.BootstrapWithGithubAsync(cachePath); var serviceClient = await TorServiceClient.ConnectAsync(torClient, "facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion"); - var httpClient = new TorHttpClient(serviceClient.GetStream(), "facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion"); + var stream = await serviceClient.GetStreamAsync(); + var httpClient = new TorHttpClient(stream, "facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion"); try { @@ -131,8 +132,8 @@ public async Task BrowseFacebookOverHSWithTLS() using TorClient torClient = await TorClient.BootstrapWithGithubAsync(cachePath); var serviceClient = await TorServiceClient.ConnectAsync(torClient, "facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion:443"); - - var sslStream = new SslStream(serviceClient.GetStream(), true, (sender, cert, chain, sslPolicyErrors) => true); + var stream = await serviceClient.GetStreamAsync(); + var sslStream = new SslStream(stream, true, (sender, cert, chain, sslPolicyErrors) => true); await sslStream.AuthenticateAsClientAsync(string.Empty, null, SslProtocols.Tls12, false); var httpClientOverSslStream = new TorHttpClient(sslStream, "www.facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion"); @@ -185,7 +186,7 @@ public async Task EstablishAndCommunicateOverHSConnectionOnionStyle() var clientSide = Task.Run(async () => { var serviceClient = await TorServiceClient.ConnectAsync(torClient, host.ExportUrl()); - var stream = serviceClient.GetStream(); + var stream = await serviceClient.GetStreamAsync(); var lengthBytes = new byte[sizeof(int)]; await ReadExact(stream, lengthBytes, 0, lengthBytes.Length); var length = BitConverter.ToInt32(lengthBytes); diff --git a/NOnion/Services/TorServiceClient.fs b/NOnion/Services/TorServiceClient.fs index 074de3e3..d32995fc 100644 --- a/NOnion/Services/TorServiceClient.fs +++ b/NOnion/Services/TorServiceClient.fs @@ -24,11 +24,21 @@ type TorServiceClient = { TorClient: TorClient RendezvousCircuit: TorCircuit - Stream: TorStream + Port: int } member self.GetStream() = - self.Stream + async { + // We can't use the "use" keyword since this stream needs + // to outlive this function. + let serviceStream = new TorStream(self.RendezvousCircuit) + do! serviceStream.ConnectToService self.Port |> Async.Ignore + + return serviceStream + } + + member self.GetStreamAsync() = + self.GetStream() |> Async.StartAsTask static member ConnectAsync (client: TorClient) (url: string) = TorServiceClient.Connect client url |> Async.StartAsTask @@ -475,16 +485,11 @@ type TorServiceClient = Async.Parallel [ introduceJob; rendezvousJoin ] |> Async.Ignore - // We can't use the "use" keyword since this stream needs - // to outlive this function. - let serviceStream = new TorStream(rendezvousCircuit) - do! serviceStream.ConnectToService port |> Async.Ignore - return { TorClient = client RendezvousCircuit = rendezvousCircuit - Stream = serviceStream + Port = port } | _ -> return diff --git a/README.md b/README.md index d73da86e..e10cd808 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ To connect to a hidden service using the Tor network, use the folowing command: let onionAddress = "facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion" let! serviceClient = TorServiceClient.Connect torClient onionAddress -let stream = serviceClient.GetStream() +let! stream = serviceClient.GetStream() ``` Now, utilize the stream just like any other `System.IO.Stream`: ``` From f488d76ae3d79c07f38cd55c95d8a2e8454bf59d Mon Sep 17 00:00:00 2001 From: Afshin Arani Date: Sun, 31 Mar 2024 17:00:46 +0300 Subject: [PATCH 5/7] NOnion: use tor official mirror (#13) Tor Github mirror has been removed [1], this commit changes the URLs to the official GitLab. [1] https://github.com/torproject/tor/commi/27d4ba90f6dbf0c80d518a358b9600ae789509e4 --- NOnion.Tests/FallbackDirectorySelector.cs | 2 +- NOnion.Tests/HiddenServicesTests.cs | 10 +++++----- NOnion.Tests/TorClientTests.cs | 8 ++++---- NOnion/Client/TorClient.fs | 8 ++++---- README.md | 4 ++-- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/NOnion.Tests/FallbackDirectorySelector.cs b/NOnion.Tests/FallbackDirectorySelector.cs index a63d18b3..1bed1455 100644 --- a/NOnion.Tests/FallbackDirectorySelector.cs +++ b/NOnion.Tests/FallbackDirectorySelector.cs @@ -14,7 +14,7 @@ static internal IPEndPoint GetRandomFallbackDirectory() { if (fallbackDirectories == null) { - var urlToTorServerList = "https://raw.githubusercontent.com/torproject/tor/main/src/app/config/fallback_dirs.inc"; + var urlToTorServerList = "https://gitlab.torproject.org/tpo/core/tor/-/raw/main/src/app/config/fallback_dirs.inc"; using var webClient = new WebClient(); var fetchedInfo = webClient.DownloadString(urlToTorServerList); diff --git a/NOnion.Tests/HiddenServicesTests.cs b/NOnion.Tests/HiddenServicesTests.cs index 2223639b..5c09f617 100644 --- a/NOnion.Tests/HiddenServicesTests.cs +++ b/NOnion.Tests/HiddenServicesTests.cs @@ -51,7 +51,7 @@ public void Init() private async Task CreateIntroductionCircuit() { - using TorClient torClient = await TorClient.BootstrapWithGithubAsync(cachePath); + using TorClient torClient = await TorClient.BootstrapWithGitlabAsync(cachePath); var circuit = await torClient.CreateCircuitAsync(1, CircuitPurpose.Unknown, FSharpOption.None); await circuit.RegisterAsIntroductionPointAsync(FSharpOption.None, StubCallback, DisconnectionCallback); } @@ -76,7 +76,7 @@ private async Task CreateRendezvousCircuit() var array = new byte[Constants.RendezvousCookieLength]; RandomNumberGenerator.Create().GetNonZeroBytes(array); - using TorClient torClient = await TorClient.BootstrapWithGithubAsync(cachePath); + using TorClient torClient = await TorClient.BootstrapWithGitlabAsync(cachePath); var circuit = await torClient.CreateCircuitAsync(2, CircuitPurpose.Unknown, FSharpOption.None); await circuit.RegisterAsRendezvousPointAsync(array); } @@ -103,7 +103,7 @@ private async Task ReadExact(TorStream stream, byte[] buffer, int off, int public async Task BrowseFacebookOverHS() { - using TorClient torClient = await TorClient.BootstrapWithGithubAsync(cachePath); + using TorClient torClient = await TorClient.BootstrapWithGitlabAsync(cachePath); var serviceClient = await TorServiceClient.ConnectAsync(torClient, "facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion"); var stream = await serviceClient.GetStreamAsync(); @@ -129,7 +129,7 @@ public void CanBrowseFacebookOverHS() public async Task BrowseFacebookOverHSWithTLS() { - using TorClient torClient = await TorClient.BootstrapWithGithubAsync(cachePath); + using TorClient torClient = await TorClient.BootstrapWithGitlabAsync(cachePath); var serviceClient = await TorServiceClient.ConnectAsync(torClient, "facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion:443"); var stream = await serviceClient.GetStreamAsync(); @@ -159,7 +159,7 @@ public void CanBrowseFacebookOverHSWithTLS() public async Task EstablishAndCommunicateOverHSConnectionOnionStyle() { - using TorClient torClient = await TorClient.BootstrapWithGithubAsync(cachePath); + using TorClient torClient = await TorClient.BootstrapWithGitlabAsync(cachePath); TorLogger.Log("Finished bootstraping"); diff --git a/NOnion.Tests/TorClientTests.cs b/NOnion.Tests/TorClientTests.cs index c7cb3ef4..d017ca16 100644 --- a/NOnion.Tests/TorClientTests.cs +++ b/NOnion.Tests/TorClientTests.cs @@ -10,15 +10,15 @@ namespace NOnion.Tests { public class TorClientTests { - private async Task BootstrapWithGithub() + private async Task BootstrapWithGitlab() { - await TorClient.BootstrapWithGithubAsync(FSharpOption.None); + await TorClient.BootstrapWithGitlabAsync(FSharpOption.None); } [Test] - public void CanBootstrapWithGithub() + public void CanBootstrapWithGitlab() { - Assert.DoesNotThrowAsync(BootstrapWithGithub); + Assert.DoesNotThrowAsync(BootstrapWithGitlab); } private async Task BootstrapWithEmbeddedList() diff --git a/NOnion/Client/TorClient.fs b/NOnion/Client/TorClient.fs index b23b02d0..dc8dde56 100644 --- a/NOnion/Client/TorClient.fs +++ b/NOnion/Client/TorClient.fs @@ -113,7 +113,7 @@ type TorClient internal (directory: TorDirectory) = = TorClient.AsyncBootstrapWithEmbeddedList cachePath |> Async.StartAsTask - static member AsyncBootstrapWithGithub(cachePath: Option) = + static member AsyncBootstrapWithGitlab(cachePath: Option) = async { // Don't put this inside the fallbackListString or it gets disposed // before the task gets executed @@ -121,15 +121,15 @@ type TorClient internal (directory: TorDirectory) = let! fallbackListString = let urlToTorServerList = - "https://raw.githubusercontent.com/torproject/tor/main/src/app/config/fallback_dirs.inc" + "https://gitlab.torproject.org/tpo/core/tor/-/raw/main/src/app/config/fallback_dirs.inc" httpClient.GetStringAsync urlToTorServerList |> Async.AwaitTask return! CreateClientFromFallbackString fallbackListString cachePath } - static member BootstrapWithGithubAsync(cachePath: Option) = - TorClient.AsyncBootstrapWithGithub cachePath |> Async.StartAsTask + static member BootstrapWithGitlabAsync(cachePath: Option) = + TorClient.AsyncBootstrapWithGitlab cachePath |> Async.StartAsTask member __.Directory = directory diff --git a/README.md b/README.md index e10cd808..d425d13b 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Install via NuGet: To utilize the Tor network, start by bootstrapping your directory. This step involves downloading the most recent information about available routers and their identity keys. To initiate the bootstrapping process, you require a list of initial nodes for communication. In NOnion, there are two methods to obtain this list: -1- [Download from Github](https://github.com/torproject/tor/blob/main/src/app/config/fallback_dirs.inc) +1- [Download from GitLab](https://gitlab.torproject.org/tpo/core/tor/-/raw/main/src/app/config/fallback_dirs.inc) 2- Utilize the embedded list in the NOnion binary (Note: this list could potentially be outdated) Based on what option you choose use one of the following commands to bootstrap a TorClient object: @@ -41,7 +41,7 @@ let! torClient = TorClient.AsyncBootstrapWithEmbeddedList None ``` ### OR ``` -let! torClient = TorClient.AsyncBootstrapWithGithub None +let! torClient = TorClient.AsyncBootstrapWithGitlab None ``` ## Browsing the web From f110b6f048ddc23b7ac206a7a6501e6b630823c8 Mon Sep 17 00:00:00 2001 From: Afshin Arani Date: Sun, 31 Mar 2024 17:13:37 +0300 Subject: [PATCH 6/7] CI: remove commitlint sanity check (#12) --- .github/workflows/CI.yml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 61e40c54..32dcbe7e 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -59,21 +59,6 @@ jobs: dotnet fantomless --recurse . git diff --exit-code - - name: Install dependencies of commitlint - run: | - sudo apt update - sudo apt install --yes git npm - - name: Pull our commitlint configuration - run: | - git clone https://github.com/nblockchain/conventions.git - rm -rf ./conventions/.git/ - - name: Validate current commit (last commit) with commitlint - if: github.event_name == 'push' - run: ./conventions/commitlint.sh --from HEAD~1 --to HEAD --verbose - - name: Validate PR commits with commitlint - if: github.event_name == 'pull_request' - run: ./conventions/commitlint.sh --from ${{ github.event.pull_request.head.sha }}~${{ github.event.pull_request.commits }} --to ${{ github.event.pull_request.head.sha }} --verbose - package: name: Package (Nuget) needs: sanity_check From b585eb27e304d52a026f334cf748697d83ec79ee Mon Sep 17 00:00:00 2001 From: Afshin Arani Date: Mon, 24 Apr 2023 01:36:11 +0330 Subject: [PATCH 7/7] Proxy,Tests: add TorProxy This commit adds a helper class that provides an HTTP proxy for easy usage of NOnion alongside things like HttpClient that don't support communicating over custom streams. --- NOnion.Tests/TorProxyTests.cs | 115 +++++++++++ NOnion/NOnion.fsproj | 1 + NOnion/Network/TorCircuit.fs | 7 + NOnion/Proxy/TorProxy.fs | 246 ++++++++++++++++++++++++ NOnion/Utility/HiddenServicesUtility.fs | 40 ++-- 5 files changed, 392 insertions(+), 17 deletions(-) create mode 100644 NOnion.Tests/TorProxyTests.cs create mode 100644 NOnion/Proxy/TorProxy.fs diff --git a/NOnion.Tests/TorProxyTests.cs b/NOnion.Tests/TorProxyTests.cs new file mode 100644 index 00000000..9c75bd34 --- /dev/null +++ b/NOnion.Tests/TorProxyTests.cs @@ -0,0 +1,115 @@ +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + +using Newtonsoft.Json; +using NUnit.Framework; + +using NOnion.Proxy; + +namespace NOnion.Tests +{ + internal class TorProxyTests + { + private const int MaximumRetry = 3; + + private class TorProjectCheckResult + { + [JsonProperty("IsTor")] + internal bool IsTor { get; set; } + + [JsonProperty("IP")] + internal string IP { get; set; } + } + + [Test] + [Retry(MaximumRetry)] + public void CanProxyTorProjectExitNodeCheck() + { + Assert.DoesNotThrowAsync(ProxyTorProjectExitNodeCheck); + } + + private async Task ProxyTorProjectExitNodeCheck() + { + using (await TorProxy.StartAsync(IPAddress.Loopback, 20000)) + { + var handler = new HttpClientHandler + { + Proxy = new WebProxy("http://localhost:20000") + }; + + var client = new HttpClient(handler); + var resultStr = await client.GetStringAsync("https://check.torproject.org/api/ip"); + var result = JsonConvert.DeserializeObject(resultStr); + Assert.IsTrue(result.IsTor); + } + } + + [Test] + [Retry(MaximumRetry)] + public void CanProxyHttps() + { + Assert.DoesNotThrowAsync(ProxyHttps); + } + + private async Task ProxyHttps() + { + using (await TorProxy.StartAsync(IPAddress.Loopback, 20000)) + { + var handler = new HttpClientHandler + { + Proxy = new WebProxy("http://localhost:20000") + }; + + var client = new HttpClient(handler); + var googleResponse = await client.GetAsync("https://google.com"); + Assert.That(googleResponse.StatusCode > 0); + } + } + + [Test] + [Retry(MaximumRetry)] + public void CanProxyHttp() + { + Assert.DoesNotThrowAsync(ProxyHttp); + } + + private async Task ProxyHttp() + { + using (await TorProxy.StartAsync(IPAddress.Loopback, 20000)) + { + var handler = new HttpClientHandler + { + Proxy = new WebProxy("http://localhost:20000") + }; + + var client = new HttpClient(handler); + var googleResponse = await client.GetAsync("http://google.com/search?q=Http+Test"); + Assert.That(googleResponse.StatusCode > 0); + } + } + + [Test] + [Retry(MaximumRetry)] + public void CanProxyHiddenService() + { + Assert.DoesNotThrowAsync(ProxyHiddenService); + } + + private async Task ProxyHiddenService() + { + using (await TorProxy.StartAsync(IPAddress.Loopback, 20000)) + { + var handler = new HttpClientHandler + { + Proxy = new WebProxy("http://localhost:20000"), + ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true + }; + + var client = new HttpClient(handler); + var facebookResponse = await client.GetAsync("https://facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion"); + Assert.That(facebookResponse.StatusCode > 0); + } + } + } +} diff --git a/NOnion/NOnion.fsproj b/NOnion/NOnion.fsproj index c5e3b282..f93ff533 100644 --- a/NOnion/NOnion.fsproj +++ b/NOnion/NOnion.fsproj @@ -89,6 +89,7 @@ + diff --git a/NOnion/Network/TorCircuit.fs b/NOnion/Network/TorCircuit.fs index 39edec50..2c72bb75 100644 --- a/NOnion/Network/TorCircuit.fs +++ b/NOnion/Network/TorCircuit.fs @@ -1072,6 +1072,13 @@ and TorCircuit failwith "Should not happen: can't get circuitId for non-initialized circuit." + member __.IsActive = + match circuitState with + | Ready _ + | ReadyAsIntroductionPoint _ + | ReadyAsRendezvousPoint _ -> true + | _ -> false + member __.GetLastNode() = async { let! lastNodeResult = diff --git a/NOnion/Proxy/TorProxy.fs b/NOnion/Proxy/TorProxy.fs new file mode 100644 index 00000000..28c7dc74 --- /dev/null +++ b/NOnion/Proxy/TorProxy.fs @@ -0,0 +1,246 @@ +namespace NOnion.Proxy + +open FSharpx.Collections +open System +open System.IO +open System.Net +open System.Net.Sockets +open System.Text +open System.Threading + +open NOnion +open NOnion.Client +open NOnion.Network +open NOnion.Services + +type TorProxy private (listener: TcpListener, torClient: TorClient) = + let mutable lastActiveCircuitOpt: Option = None + + let handleConnection(client: TcpClient) = + async { + let! cancelToken = Async.CancellationToken + cancelToken.ThrowIfCancellationRequested() + + let stream = client.GetStream() + + let readHeaders() = + async { + let stringBuilder = StringBuilder() + // minimum request 16 bytes: GET / HTTP/1.1\r\n\r\n + let preReadLen = 18 + let! buffer = stream.AsyncRead preReadLen + + buffer + |> Encoding.ASCII.GetString + |> stringBuilder.Append + |> ignore + + let rec innerReadRest() = + async { + if stringBuilder.ToString().EndsWith("\r\n\r\n") then + return () + else + let! newByte = stream.AsyncRead 1 + + newByte + |> Encoding.ASCII.GetString + |> stringBuilder.Append + |> ignore + + return! innerReadRest() + } + + do! innerReadRest() + + return stringBuilder.ToString() + } + + let! headers = readHeaders() + + let headerLines = + headers.Split( + [| "\r\n" |], + StringSplitOptions.RemoveEmptyEntries + ) + + match Seq.tryHeadTail headerLines with + | Some(firstLine, restOfHeaders) -> + let firstLineParts = firstLine.Split(' ') + + let method = firstLineParts.[0] + let url = firstLineParts.[1] + let protocolVersion = firstLineParts.[2] + + if protocolVersion <> "HTTP/1.1" then + return failwith "TorProxy: protocol version mismatch" + + let rec copySourceToDestination + (source: Stream) + (dest: Stream) + = + async { + do! source.CopyToAsync dest |> Async.AwaitTask + + // CopyToAsync returns when source is closed so we can close dest + dest.Close() + } + + let createStreamToDestination(parsedUrl: Uri) = + async { + if parsedUrl.DnsSafeHost.EndsWith(".onion") then + let! client = + TorServiceClient.Connect + torClient + (sprintf + "%s:%i" + parsedUrl.DnsSafeHost + parsedUrl.Port) + + return! client.GetStream() + else + let! circuit = + match lastActiveCircuitOpt with + | Some lastActiveCircuit when + lastActiveCircuit.IsActive + -> + async { + TorLogger.Log + "TorProxy: we had active circuit, no need to recreate" + + return lastActiveCircuit + } + | _ -> + async { + TorLogger.Log + "TorProxy: we didn't have an active circuit, recreating..." + + let! circuit = + torClient.AsyncCreateCircuit + 3 + CircuitPurpose.Exit + None + + lastActiveCircuitOpt <- Some circuit + return circuit + } + + let torStream = new TorStream(circuit) + + do! + torStream.ConnectToOutside + parsedUrl.DnsSafeHost + parsedUrl.Port + |> Async.Ignore + + return torStream + } + + if method <> "CONNECT" then + let parsedUrl = Uri url + + use! torStream = createStreamToDestination parsedUrl + + let firstLineToRetransmit = + sprintf + "%s %s HTTP/1.1\r\n" + method + parsedUrl.PathAndQuery + + let headersToForwardLines = + restOfHeaders + |> Seq.filter(fun header -> + not(header.StartsWith "Proxy-") + ) + |> Seq.map(fun header -> sprintf "%s\r\n" header) + + let headersToForward = + String.Join(String.Empty, headersToForwardLines) + + do! + Encoding.ASCII.GetBytes firstLineToRetransmit + |> torStream.AsyncWrite + + do! + Encoding.ASCII.GetBytes headersToForward + |> torStream.AsyncWrite + + do! Encoding.ASCII.GetBytes "\r\n" |> torStream.AsyncWrite + + return! + [ + copySourceToDestination torStream stream + copySourceToDestination stream torStream + ] + |> Async.Parallel + |> Async.Ignore + else + let parsedUrl = Uri <| sprintf "http://%s" url + + use! torStream = createStreamToDestination parsedUrl + + let connectResponse = + "HTTP/1.1 200 Connection Established\r\nConnection: close\r\n\r\n" + + do! + Encoding.ASCII.GetBytes connectResponse + |> stream.AsyncWrite + + return! + [ + copySourceToDestination torStream stream + copySourceToDestination stream torStream + ] + |> Async.Parallel + |> Async.Ignore + | None -> + return failwith "TorProxy: incomplete http header detected" + + } + + let rec acceptConnections() = + async { + let! cancelToken = Async.CancellationToken + cancelToken.ThrowIfCancellationRequested() + + let! client = listener.AcceptTcpClientAsync() |> Async.AwaitTask + + Async.Start(handleConnection client, cancelToken) + + return! acceptConnections() + } + + let shutdownToken = new CancellationTokenSource() + + static member Start (localAddress: IPAddress) (port: int) = + async { + let! client = TorClient.AsyncBootstrapWithEmbeddedList None + let listener = TcpListener(localAddress, port) + let proxy = new TorProxy(listener, client) + proxy.StartListening() + return proxy + } + + static member StartAsync(localAddress: IPAddress, port: int) = + TorProxy.Start localAddress port |> Async.StartAsTask + + member private self.StartListening() = + listener.Start() + + Async.Start(acceptConnections(), shutdownToken.Token) + + member __.GetNewIdentity() = + async { + let! newCircuit = + torClient.AsyncCreateCircuit 3 CircuitPurpose.Exit None + + lastActiveCircuitOpt <- Some newCircuit + } + + member self.GetNewIdentityAsync() = + self.GetNewIdentity() |> Async.StartAsTask + + interface IDisposable with + member __.Dispose() = + shutdownToken.Cancel() + listener.Stop() + (torClient :> IDisposable).Dispose() diff --git a/NOnion/Utility/HiddenServicesUtility.fs b/NOnion/Utility/HiddenServicesUtility.fs index e729f32c..1281e1e0 100644 --- a/NOnion/Utility/HiddenServicesUtility.fs +++ b/NOnion/Utility/HiddenServicesUtility.fs @@ -110,20 +110,26 @@ module HiddenServicesUtility = //Add a fake protocol let parsedUrl = Uri(sprintf "http://%s" url) - //Remove .onion suffix and decode - let keyBytesOpt = - parsedUrl.DnsSafeHost.Split '.' - |> Seq.tryHead - |> Option.map Base32Util.DecodeBase32 - - // PublicKey (32 bytes) + Checksum (2 bytes) + Version (1 byte) - let expectedOnionUrlLength = - Constants.HiddenServices.OnionUrl.PublicKeyLength - + Constants.HiddenServices.OnionUrl.ChecksumLength - + 1 - - match keyBytesOpt with - | Some keyBytes when keyBytes.Length = expectedOnionUrlLength -> - keyBytes.[0 .. Constants.HiddenServices.OnionUrl.PublicKeyLength - 1], - parsedUrl.Port - | _ -> failwith "Invalid onion service url" + let urlParts = parsedUrl.DnsSafeHost.Split '.' + + if urlParts.Length < 2 then + failwith "Invalid onion service url" + else + //Remove subdomains and .onion suffix and decode + let keyBytesOpt = + urlParts + |> Seq.tryItem(urlParts.Length - 2) + |> Option.map Base32Util.DecodeBase32 + + // PublicKey (32 bytes) + Checksum (2 bytes) + Version (1 byte) + let expectedOnionUrlLength = + Constants.HiddenServices.OnionUrl.PublicKeyLength + + Constants.HiddenServices.OnionUrl.ChecksumLength + + 1 + + match keyBytesOpt with + | Some keyBytes when keyBytes.Length = expectedOnionUrlLength -> + keyBytes.[0 .. Constants.HiddenServices.OnionUrl.PublicKeyLength + - 1], + parsedUrl.Port + | _ -> failwith "Unable to decode onion service url"