From 35d51d2364b96aea60e913654e3eb71d1846269f Mon Sep 17 00:00:00 2001 From: Afshin Arani Date: Tue, 25 Apr 2023 22:32:54 +0330 Subject: [PATCH] Tests: introduce HS compat test with TOR client This commit introduces a test to make sure our hidden service hosts are accessible by official tor client. Testing only with NOnion's TorServiceClient can cause mask problems because it shares lots of code with TorServiceHost (especially crypto stuff) and mistakes there can go unnoticed. Upgrade to .NET 6 was necessary to be able to use SocksV5 (which is what tor gives us) as proxy for HttpClient. Due to some problem with NUnit not writing test-by-test output (whether it passed or failed), I had to upgrade even furthur into .NET 7. NUnit was also updated to latest version because it was causing issue where some unrelated tests would fail in .NET7 (maybe the Retry attributed wasn't working). --- .github/workflows/CI.yml | 26 ++++++++-- NOnion.Tests/HiddenServicesTests.cs | 74 ++++++++++++++++++++++++++++- NOnion.Tests/NOnion.Tests.csproj | 8 ++-- 3 files changed, 98 insertions(+), 10 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 61e40c54..d7366be0 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -6,20 +6,36 @@ jobs: build_and_test: name: Build & Test runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + include: + - dotnet_version: "3.1.x" + flag: "" + - dotnet_version: "7.0.x" + flag: "-p:NET7=true" steps: - uses: actions/checkout@v2 with: submodules: recursive - - name: Setup .NET Core SDK 3.1.x + - name: Setup .NET SDK ${{ matrix.dotnet_version }} uses: actions/setup-dotnet@v1.7.2 with: - dotnet-version: '3.1.x' + dotnet-version: ${{ matrix.dotnet_version }} - name: Install dependencies - run: dotnet restore + run: dotnet restore ${{ matrix.flag }} - name: Build - run: dotnet build --configuration Release --no-restore + run: dotnet build --configuration Release --no-restore ${{ matrix.flag }} + # Please keep in mind that NOnion DOES NOT require tor client to function + # tor client is only installed here for testing purposes. + - name: Install Tor and wait for startup + run: | + sudo apt install tor + echo -e "SOCKSPort 127.0.0.1:9050" | sudo tee -a /etc/tor/torrc + sudo systemctl restart tor + sleep 2m - name: Test - run: dotnet test --no-restore --verbosity normal + run: dotnet test --no-restore --verbosity normal ${{ matrix.flag }} sanity_check: name: Sanity Check diff --git a/NOnion.Tests/HiddenServicesTests.cs b/NOnion.Tests/HiddenServicesTests.cs index 2079c957..a675ed2d 100644 --- a/NOnion.Tests/HiddenServicesTests.cs +++ b/NOnion.Tests/HiddenServicesTests.cs @@ -2,9 +2,12 @@ using System; using System.IO; using System.Linq; +using System.Net; +using System.Net.Http; using System.Net.Security; using System.Security.Authentication; using System.Security.Cryptography; +using System.Text; using System.Threading.Tasks; using NUnit.Framework; @@ -133,7 +136,7 @@ public void CanBrowseFacebookOverHSWithTLS() Assert.DoesNotThrowAsync(BrowseFacebookOverHSWithTLS); } - public async Task EstablishAndCommunicateOverHSConnectionOnionStyle() + private async Task<(TorDirectory, TorServiceHost)> BootstrapDirectoryAndStartHost() { int descriptorUploadRetryLimit = 2; @@ -151,6 +154,13 @@ public async Task EstablishAndCommunicateOverHSConnectionOnionStyle() TorLogger.Log("Finished starting HS host"); + return (directory, host); + } + + public async Task EstablishAndCommunicateOverHSConnectionOnionStyle() + { + (var directory, var host) = await BootstrapDirectoryAndStartHost(); + var dataToSendAndReceive = new byte[] { 1, 2, 3, 4 }; var serverSide = @@ -185,6 +195,68 @@ public void CanEstablishAndCommunicateOverHSConnectionOnionStyle() { Assert.DoesNotThrowAsync(EstablishAndCommunicateOverHSConnectionOnionStyle); } + +#if NET6_0_OR_GREATER + [Test] + [Retry(TestsRetryCount)] + public void CanConnectToHiddenServiceUsingTorClient() + { + Assert.DoesNotThrowAsync(ConnectToHiddenServiceUsingTorClient); + } + + private async Task ConnectToHiddenServiceUsingTorClient() + { + (_, var host) = await BootstrapDirectoryAndStartHost(); + + var stringToSendAndReceive = + "We are using tor!"; + + var serverSide = + Task.Run(async () => { + var stream = await host.AcceptClientAsync(); + + var httpResponse = + "HTTP/1.1 200 OK\r\n" + + "Server: NOnion\r\n" + + $"Content-Length: {stringToSendAndReceive.Length}\r\n" + + "Connection: close\r\n" + + "Content-Type: text/plain" + + "\r\n" + + "\r\n" + + stringToSendAndReceive + + "\r\n"; + var httpResponseBytes = + Encoding.ASCII.GetBytes(httpResponse); + + await stream.WriteAsync(httpResponseBytes, 0, httpResponseBytes.Length); + await stream.EndAsync(); + }); + + var clientSide = + Task.Run(async () => { + var handler = new HttpClientHandler + { + Proxy = new WebProxy(new Uri("socks5://localhost:9050")) + }; + + TestContext.Progress.WriteLine("Trying to connect to hidden service..."); + using (handler) + using (var httpClient = new HttpClient(handler)) + { + // Sometimes tor client takes a while to bootstrap and stalls + // the request. + httpClient.Timeout = TimeSpan.FromMinutes(20); + var result = await httpClient.GetStringAsync("http://" + host.ExportUrl()); + Assert.AreEqual(result, stringToSendAndReceive); + } + } + ); + + await TaskUtils.WhenAllFailFast(serverSide, clientSide); + + ((IDisposable)host).Dispose(); + } +#endif } } diff --git a/NOnion.Tests/NOnion.Tests.csproj b/NOnion.Tests/NOnion.Tests.csproj index e9a8683d..2813f0e1 100644 --- a/NOnion.Tests/NOnion.Tests.csproj +++ b/NOnion.Tests/NOnion.Tests.csproj @@ -1,14 +1,14 @@  - netcoreapp3.1 - + netcoreapp3.1 + net7.0 false - - + +