Skip to content

Commit

Permalink
Client,Network,Services: add TorClient
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
aarani committed Oct 28, 2023
1 parent 1da50a7 commit 420e8db
Show file tree
Hide file tree
Showing 8 changed files with 1,558 additions and 180 deletions.
59 changes: 34 additions & 25 deletions NOnion.Tests/HiddenServicesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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<CircuitNodeDetail>.None);
await circuit.RegisterAsIntroductionPointAsync(FSharpOption<AsymmetricCipherKeyPair>.None, StubCallback, DisconnectionCallback);
}

Expand All @@ -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<CircuitNodeDetail>.None);
await circuit.RegisterAsRendezvousPointAsync(array);
}

Expand All @@ -92,10 +103,10 @@ private async Task<int> 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
{
Expand All @@ -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");
Expand All @@ -147,18 +158,16 @@ 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();
Ed25519KeyPairGenerator kpGen = new Ed25519KeyPairGenerator();
kpGen.Init(new Ed25519KeyGenerationParameters(random));
Ed25519PrivateKeyParameters masterPrivateKey = (Ed25519PrivateKeyParameters)kpGen.GenerateKeyPair().Private;

TorServiceHost host = new TorServiceHost(directory, descriptorUploadRetryLimit, TestsRetryCount, FSharpOption<Ed25519PrivateKeyParameters>.Some(masterPrivateKey));
TorServiceHost host = new TorServiceHost(torClient, FSharpOption<Ed25519PrivateKeyParameters>.Some(masterPrivateKey));
await host.StartAsync();

TorLogger.Log("Finished starting HS host");
Expand All @@ -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);
Expand Down
47 changes: 47 additions & 0 deletions NOnion.Tests/TorClientTests.cs
Original file line number Diff line number Diff line change
@@ -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<DirectoryInfo>.None);
}

[Test]
public void CanBootstrapWithGithub()
{
Assert.DoesNotThrowAsync(BootstrapWithGithub);
}

private async Task BootstrapWithEmbeddedList()
{
await TorClient.BootstrapWithEmbeddedListAsync(FSharpOption<DirectoryInfo>.None);
}

[Test]
public void CanBootstrapWithEmbeddedList()
{
Assert.DoesNotThrowAsync(BootstrapWithEmbeddedList);
}

private async Task CreateCircuit()
{
using TorClient client = await TorClient.BootstrapWithEmbeddedListAsync(FSharpOption<DirectoryInfo>.None);
await client.CreateCircuitAsync(3, CircuitPurpose.Unknown, FSharpOption<Network.CircuitNodeDetail>.None);
}

[Test]
public void CanCreateCircuit()
{
Assert.DoesNotThrowAsync(CreateCircuit);
}
}
}
Loading

0 comments on commit 420e8db

Please sign in to comment.