Skip to content

Commit

Permalink
Client,Network,Services: add TorClient
Browse files Browse the repository at this point in the history
  • Loading branch information
aarani committed Oct 27, 2023
1 parent 1da50a7 commit 81fb05b
Show file tree
Hide file tree
Showing 7 changed files with 1,366 additions and 38 deletions.
30 changes: 30 additions & 0 deletions NOnion.Tests/TorClientTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;

using NUnit.Framework;

using NOnion.Directory;
using NOnion.Client;

using Microsoft.FSharp.Core;

namespace NOnion.Tests
{
public class TorClientTests
{
private async Task CreateCircuit()
{
var client = await TorClient.BootstrapWithEmbeddedListAsync();
await client.CreateCircuitAsync(3, FSharpOption<Network.CircuitNodeDetail>.None);
}

[Test]
public void CanCreateCircuit()
{
Assert.DoesNotThrowAsync(CreateCircuit);
}
}
}
219 changes: 219 additions & 0 deletions NOnion/Client/TorClient.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
namespace NOnion.Client

open System
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 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<string * int>) =
fallbackList
|> SeqUtils.TakeRandom maximumBootstrapTries
|> Seq.map(fun (ipString, port) ->
let ipAddress = IPAddress.Parse ipString
IPEndPoint(ipAddress, port)
)
|> Seq.toList

static let BootstrapDirectory(ipEndPointList: List<IPEndPoint>) =
async {
let rec tryBootstrap(ipEndPointList: List<IPEndPoint>) =
async {
match ipEndPointList with
| ipEndPoint :: tail ->
try
let! directory =
TorDirectory.Bootstrap
ipEndPoint
(Path.GetTempPath() |> DirectoryInfo)

return directory
with
| :? NOnionException -> return! tryBootstrap tail
| [] -> return failwith "Maximum bootstrap tries reached!"
}

return! tryBootstrap ipEndPointList
}

static let CreateClientFromFallbackString(fallbackListString: string) =
async {
let! directory =
fallbackListString
|> ConvertFallbackIncToList
|> SelectRandomEndpoints
|> BootstrapDirectory

return new TorClient(directory)
}

//FIXME: lock this
let mutable guardsToDispose: List<TorGuard> = List.empty

static member BootstrapWithEmbeddedList() =
async {
let fallbackListString =
EmbeddedResourceUtility.ExtractEmbeddedResourceFileContents(
"fallback_dirs.inc"
)

return! CreateClientFromFallbackString fallbackListString
}

static member BootstrapWithEmbeddedListAsync() =
TorClient.BootstrapWithEmbeddedList() |> Async.StartAsTask

static member BootstrapWithGithub() =
async {
let! fallbackListString =
let urlToTorServerList =
"https://raw.githubusercontent.com/torproject/tor/main/src/app/config/fallback_dirs.inc"

use httpClient = new HttpClient()

httpClient.GetStringAsync urlToTorServerList |> Async.AwaitTask

return! CreateClientFromFallbackString fallbackListString
}

member __.CreateCircuit
(hopsCount: int)
(extendByNodeOpt: Option<CircuitNodeDetail>)
=
async {
let rec createNewGuard() =
async {
let! ipEndPoint, nodeDetail =
directory.GetRouter RouterType.Guard

try
let! guard =
TorGuard.NewClientWithIdentity
ipEndPoint
(nodeDetail.GetIdentityKey() |> Some)

guardsToDispose <- guard :: guardsToDispose
return guard, nodeDetail
with
| :? GuardConnectionFailedException ->
return! createNewGuard()
}

let rec tryCreateCircuit(tryNumber: int) =
async {
if tryNumber > maximumExtendByNodeRetry then
return
raise
<| NOnionException
"Destination node can't be reached"
else
try
let! guard, guardDetail = createNewGuard()
let circuit = TorCircuit guard

do!
circuit.Create guardDetail
|> Async.Ignore<uint16>

let rec extend
(numHopsToExtend: int)
(nodesSoFar: List<CircuitNodeDetail>)
=
async {
if numHopsToExtend > 0 then
let rec findUnusedNode() =
async {
let! _ipEndPoint, nodeDetail =
directory.GetRouter
RouterType.Normal

if (List.contains
nodeDetail
nodesSoFar) then
return! findUnusedNode()
else
return nodeDetail
}

let! newUnusedNode = findUnusedNode()

do!
circuit.Extend newUnusedNode
|> Async.Ignore<uint16>

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<uint16>
with
| :? NOnionException ->
return
raise
DestinationNodeCantBeReachedException
| None -> ()

return circuit
with
| :? DestinationNodeCantBeReachedException ->
return! tryCreateCircuit(tryNumber + 1)
| ex ->
match FSharpUtil.FindException<NOnionException> ex
with
| Some _nonionEx ->
return! tryCreateCircuit tryNumber
| None -> return raise <| FSharpUtil.ReRaise ex
}

let startTryNumber = 1

return! tryCreateCircuit startTryNumber
}

member self.CreateCircuitAsync
(
hopsCount: int,
extendByNode: Option<CircuitNodeDetail>
) =
self.CreateCircuit hopsCount extendByNode |> Async.StartAsTask


interface IDisposable with
member __.Dispose() =
for guard in guardsToDispose do
(guard :> IDisposable).Dispose()
2 changes: 2 additions & 0 deletions NOnion/Exceptions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,5 @@ type NOnionSocketException
"Got socket exception during data transfer",
innerException
)

exception internal DestinationNodeCantBeReachedException
2 changes: 2 additions & 0 deletions NOnion/NOnion.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

<ItemGroup>
<EmbeddedResource Include="auth_dirs.inc" />
<EmbeddedResource Include="fallback_dirs.inc" />
<Compile Include="TorLogger.fs" />
<Compile Include="Constants.fs" />
<Compile Include="DestroyReason.fs" />
Expand Down Expand Up @@ -85,6 +86,7 @@
<Compile Include="Directory\HiddenServiceFirstLayerDescriptorDocument.fs" />
<Compile Include="Directory\HiddenServiceSecondLayerDescriptorDocument.fs" />
<Compile Include="Directory\TorDirectory.fs" />
<Compile Include="Client\TorClient.fs" />
<Compile Include="Services\TorServiceHost.fs" />
<Compile Include="Services\TorServiceClient.fs" />
</ItemGroup>
Expand Down
31 changes: 7 additions & 24 deletions NOnion/Services/TorServiceClient.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -34,6 +35,7 @@ type TorServiceClient =

static member Connect (directory: TorDirectory) (url: string) =
async {

let publicKey, port = HiddenServicesUtility.DecodeOnionUrl url

let getIntroductionPointInfo() =
Expand Down Expand Up @@ -64,36 +66,17 @@ type TorServiceClient =
raise <| DescriptorDownloadFailedException()
| hsDirectory :: tail ->
try
let! guardEndPoint, randomGuardNode =
directory.GetRouter RouterType.Guard

let! _, randomMiddleNode =
directory.GetRouter RouterType.Normal
use torClient = new TorClient(directory)

let! hsDirectoryNode =
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 =
torClient.CreateCircuit
2
(Some hsDirectoryNode)

use dirStream = new TorStream(circuit)

Expand Down
18 changes: 4 additions & 14 deletions NOnion/Services/TorServiceHost.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -379,21 +380,10 @@ type TorServiceHost
directory.GetCircuitNodeDetailByIdentity
directoryToUploadTo

let! guardEndPoint, randomGuardNode =
directory.GetRouter RouterType.Guard
use torClient = new TorClient(directory)

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
let! circuit =
torClient.CreateCircuit 2 (Some hsDirectoryNode)

use dirStream = new TorStream(circuit)
do! dirStream.ConnectToDirectory() |> Async.Ignore
Expand Down
Loading

0 comments on commit 81fb05b

Please sign in to comment.