commit 14565a17e2c1508b8c55ccea02fd085ed50432ae Author: chrispr Date: Sat Jun 11 16:42:18 2022 -0400 Initial project commit diff --git a/CircuitLoadTest/CircuitLoadTest.csproj b/CircuitLoadTest/CircuitLoadTest.csproj new file mode 100644 index 0000000..fc2401a --- /dev/null +++ b/CircuitLoadTest/CircuitLoadTest.csproj @@ -0,0 +1,52 @@ + + + + Debug + x86 + 8.0.30703 + 2.0 + {124BEFA2-D0CB-4D6D-AD53-166DE405CC60} + Exe + Properties + CircuitLoadTest + CircuitLoadTest + v2.0 + 512 + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + \ No newline at end of file diff --git a/CircuitLoadTest/Program.cs b/CircuitLoadTest/Program.cs new file mode 100644 index 0000000..f60b62c --- /dev/null +++ b/CircuitLoadTest/Program.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Data.SqlClient; +using System.IO; +using System.Net.Sockets; + +namespace CircuitLoadTest +{ + class Program + { + static void Main(string[] args) + { + /* + String file = System.IO.File.ReadAllText("C:\\Projects\\Fun\\Tor\\Hermes\\bin\\Debug\\circ.debug.csv"); + StringReader reader = new StringReader(file); + using (SqlConnection conn = new SqlConnection("Data Source=MAIN;Initial Catalog=Test;User Id=sa;Password=Sm0kers@Gy0;")) + { + conn.Open(); + while (true) + { + String line = reader.ReadLine(); + if (String.IsNullOrEmpty(line)) + break; + String[] parts = line.Split(','); + String sqlRequest = String.Format(@"MERGE dbo.TorTest AS target + USING (SELECT {0}, '{1}') AS source (CircuitID, [Status]) + ON (target.CircuitID = source.CircuitID) + WHEN MATCHED AND ([Status] = 'CLOSED' OR [Status] = 'FAILED') THEN + UPDATE SET Closed = 1 + WHEN NOT MATCHED AND ([Status] = 'BUILT') THEN + INSERT (CircuitID, Closed) + VALUES (source.CircuitID, 0);", parts[0], parts[1]); + new SqlCommand(sqlRequest, conn).ExecuteNonQuery(); + } + }*/ + //Socket s = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp); + //s.Bind(new System.Net.IPEndPoint(System.Net.IPAddress.IPv6Any, 1234)); + ////s.Listen(10); + //Console.ReadLine(); + } + } +} diff --git a/CircuitLoadTest/Properties/AssemblyInfo.cs b/CircuitLoadTest/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6ad7df7 --- /dev/null +++ b/CircuitLoadTest/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("CircuitLoadTest")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("CircuitLoadTest")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ff92e3e4-01be-4da8-8b2c-3e14600692b0")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Hermes/App.config b/Hermes/App.config new file mode 100644 index 0000000..657dd9e --- /dev/null +++ b/Hermes/App.config @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Hermes/CircuitManagers/CircuitManagerBase.cs b/Hermes/CircuitManagers/CircuitManagerBase.cs new file mode 100644 index 0000000..28dca61 --- /dev/null +++ b/Hermes/CircuitManagers/CircuitManagerBase.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Hermes.Objects; +using System.Threading; + +namespace Hermes.CircuitManagers +{ + public abstract class CircuitManagerBase + { + #region Circuits + public virtual void AddCircuit(Circuit circuit) + { + _ciruitLock.AcquireWriterLock(_lockTimeout); + _circuits.Add(circuit); + _ciruitLock.ReleaseWriterLock(); + } + public virtual void RemoveCircuit(Circuit circuit) + { + _ciruitLock.AcquireWriterLock(_lockTimeout); + if (circuit != null && circuit.AttachedStreams.Count > 0) + { + circuit.AttachedStreams.ForEach(p => p.Attached = false); + circuit.AttachedStreams.Clear(); + } + _circuits.Remove(circuit); + _ciruitLock.ReleaseWriterLock(); + } + public virtual Circuit GetCircuit(Int32 circuitID) + { + _ciruitLock.AcquireReaderLock(_lockTimeout); + Circuit circ = _circuits.FirstOrDefault(p => p.CircuitID.Equals(circuitID)); + _ciruitLock.ReleaseReaderLock(); + return circ; + } + public IEnumerable Circuits + { + get + { + _ciruitLock.AcquireReaderLock(_lockTimeout); + IEnumerable array = _circuits.ToArray(); + _ciruitLock.ReleaseReaderLock(); + return array; + } + } + #endregion + #region Streams + public virtual void RemoveStream(Int32 circuitID, Int32 streamID) + { + _streamLock.AcquireReaderLock(_lockTimeout); + Stream stream = _streams.FirstOrDefault(p => p.StreamID.Equals(streamID)); + _streamLock.ReleaseReaderLock(); + if (stream != null) + RemoveStream(circuitID, stream); + } + public virtual void RemoveStream(Int32 circuitID, Stream stream) + { + _streamLock.AcquireWriterLock(_lockTimeout); + _streams.Remove(stream); + _streamLock.ReleaseWriterLock(); + _ciruitLock.AcquireReaderLock(_lockTimeout); + Circuit circ = _circuits.FirstOrDefault(p => p.CircuitID.Equals(circuitID)); + if (circ != null) + { + circ.AttachedStreams.Remove(stream); + stream.Attached = false; + } + _ciruitLock.ReleaseReaderLock(); + } + public virtual void RemoveStream(Stream stream) + { + RemoveStream(stream.CircuitID, stream); + } + public virtual Stream GetStream(Int32 streamID) + { + _streamLock.AcquireReaderLock(_lockTimeout); + Stream stream = _streams.FirstOrDefault(p => p.StreamID.Equals(streamID)); + _streamLock.ReleaseReaderLock(); + return stream; + } + public IEnumerable Streams + { + get + { + _streamLock.AcquireReaderLock(_lockTimeout); + IEnumerable array = _streams.ToArray(); + _streamLock.ReleaseReaderLock(); + return array; + } + } + #endregion + + public virtual void AssignStreamToCircuit(Stream stream, Circuit circuit) + { + _ciruitLock.AcquireWriterLock(_lockTimeout); + try + { + circuit.AttachedStreams.Add(stream); + stream.Circuit = circuit; + stream.Attached = true; + } + catch (Exception e) + { throw e; } + finally + { + _ciruitLock.ReleaseWriterLock(); + } + _streamLock.AcquireWriterLock(_lockTimeout); + _streams.Add(stream); + _streamLock.ReleaseWriterLock(); + } + protected ReaderWriterLock _ciruitLock = new ReaderWriterLock(); + protected ReaderWriterLock _streamLock = new ReaderWriterLock(); + protected List _circuits = new List(); + protected List _streams = new List(); + protected Int32 _lockTimeout = 5000; + } +} diff --git a/Hermes/CircuitManagers/EventBasedCircuitManager.cs b/Hermes/CircuitManagers/EventBasedCircuitManager.cs new file mode 100644 index 0000000..58640d0 --- /dev/null +++ b/Hermes/CircuitManagers/EventBasedCircuitManager.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using TorControlLibrary; +using TorControlLibrary.Responses; +using Hermes.Objects; +using Hermes.Selectors; +using TorControlLibrary.Exceptions; + +namespace Hermes.CircuitManagers +{ + public abstract class EventBasedCircuitManager : CircuitManagerBase + { + public IRouteSelector RouteSelector { get; set; } + protected ControlConnection _controlConnection; + public Int32 ExpectedCircuits { get; set; } + public Int32 CircuitCount { get { return _circuits.Count(); } } + public void Initialize(ControlConnection controlConnection,IRouteSelector RouteSelector, Dictionary parameters) + { + PrepareControlConnection(controlConnection, RouteSelector); + if(parameters.ContainsKey("ExpectedCircuits")) + ExpectedCircuits = Int32.Parse(parameters["ExpectedCircuits"]); + } + public void Initialize(ControlConnection controlConnection, IRouteSelector RouteSelector) + { + PrepareControlConnection(controlConnection, RouteSelector); + } + protected virtual void PrepareControlConnection(ControlConnection connection, IRouteSelector RouteSelector) + { + _controlConnection = connection; + this.RouteSelector = RouteSelector; + _controlConnection.ServerEvent += new Action(connection_ServerEvent); + } + + void connection_ServerEvent(EventResponse obj) + { + switch (obj.EventType) + { + case "CIRC": + switch (obj.Action) + { + case "LAUNCHED": + OnCircuitLaunched(obj); + break; + case "EXTENDED": + OnCircuitExtended(obj); + break; + case "BUILT": + OnCircuitBuilt(obj); + break; + case "CLOSED": + OnCircuitClosed(obj); + break; + case "FAILED": + OnCircuitFailed(obj); + break; + } + break; + + case "STREAM": + switch (obj.Action) + { + case "NEW": + OnStreamNew(obj); + break; + case "NEWRESOLVE": + case "NEWRESOLVED": + OnStreamNewResolved(obj); + break; + case "SENTCONNECT": + OnStreamSentConnect(obj); + break; + case "SENTRESOLVE": + OnStreamSentResolve(obj); + break; + case "SUCCEEDED": + OnStreamSucceeded(obj); + break; + case "DETACHED": + OnStreamDetached(obj); + break; + case "CLOSED": + OnStreamClosed(obj); + break; + case "FAILED": + OnStreamFailed(obj); + break; + } + break; + } + } + #region Internal Events + /// + /// the circuit has just been started; no work has been done yet to build it. + /// + /// + protected virtual void OnCircuitLaunched(EventResponse response) { } + /// + /// the circuit has just been extended a single step. + /// + /// + protected virtual void OnCircuitExtended(EventResponse response) { } + /// + /// the circuit is finished. + /// + /// + protected virtual void OnCircuitBuilt(EventResponse response) { } + /// + /// the circuit could not be built, and has been abandoned. + /// + /// + protected virtual void OnCircuitFailed(EventResponse response) { } + /// + /// a successfully built circuit is now closed. + /// + /// + protected virtual void OnCircuitClosed(EventResponse response) { } + + /// + /// an application has asked for an anonymous connection + /// + /// + protected virtual void OnStreamNew(EventResponse response) { } + /// + /// an application has asked for an anonymous hostname lookup + /// + /// + protected virtual void OnStreamNewResolved(EventResponse response) { } + /// + /// the stream has been attached to a circuit, and we have sent a connection request down the circuit + /// + /// + protected virtual void OnStreamSentConnect(EventResponse response) { } + /// + /// the stream has been attached to a circuit, and we have sent a lookup request down the circuit + /// + /// + protected virtual void OnStreamSentResolve(EventResponse response) { } + /// + /// the stream has been connected, or the lookup request has been answered + /// + /// + protected virtual void OnStreamSucceeded(EventResponse response) { } + /// + /// the stream was detached from its circuit, but could be reattached to another. + /// + /// + protected virtual void OnStreamDetached(EventResponse response) { } + /// + /// the stream closed normally + /// + /// + protected virtual void OnStreamClosed(EventResponse response) { } + /// + /// the stream failed and cannot be retried + /// + /// + protected virtual void OnStreamFailed(EventResponse response) { } + + #endregion + #region EventResponse Parsing and Router processing + protected Circuit ParseCircuitResponse(EventResponse resp) + { + String[] parts = resp.EventInformation.Split(' '); + String[] routers = parts[2].Split(','); + Circuit circ = new Circuit(); + foreach (String router in routers) + circ.AddNextRouter(RouteSelector.FindRouter(router)); + + circ.CircuitID = resp.ID; + if (resp.EventInformation.Contains("PURPOSE=")) + circ.Purpose = resp.EventInformation.Substring(resp.EventInformation.IndexOf("PURPOSE=") + 8); + + return circ; + } + #endregion + public void RequestNewCircuit() + { + lock (this) + { + try + { + _controlConnection.CreateCircuit( + RouteSelector.CreateNewCircuit().ToString() + ); + return; + } + catch (TorException e) + { + //Remove and retry + throw e; + } + } + RequestNewCircuit(); + } + } +} diff --git a/Hermes/CircuitManagers/HostSectionedCircuitManager.cs b/Hermes/CircuitManagers/HostSectionedCircuitManager.cs new file mode 100644 index 0000000..b520d25 --- /dev/null +++ b/Hermes/CircuitManagers/HostSectionedCircuitManager.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Hermes.Objects; + +namespace Hermes.CircuitManagers +{ + public class HostSectionedCircuitManager : OneShotRoundRobinCircuitManager + { + public Dictionary DestinationDictionary { get; set; } + + public HostSectionedCircuitManager() + { + DestinationDictionary = new Dictionary(); + } + protected override void AssignStream(Objects.Stream stream) + { + Circuit circ = null; + lock (DestinationDictionary) + { + if (DestinationDictionary.ContainsKey(stream.DestinationHost)) + circ = DestinationDictionary[stream.DestinationHost]; + else + { + circ = GetCircuitForStream(stream); + while (circ == null) + { //There is not yet a circuit + //Wait for more circuits to build + System.Threading.Thread.Sleep(5000); + circ = GetCircuitForStream(stream); + } + DestinationDictionary.Add(stream.DestinationHost, circ); + } + } + try + { + AssignStreamToCircuit(stream, circ); + } + catch (TorControlLibrary.Exceptions.TorUnknownCircuitException e) + { //The circuit has become invalidated, most likely due to it closing. + //cleanup + RemoveCircuit(circ); + //try + //{ + // _controlConnection.CloseCircuit(circ.CircuitID); + //} + //catch (Exception) { } + //Call this function again, with hopefully better results next time + AssignStream(stream); + } + catch (TorControlLibrary.Exceptions.TorUnknownStreamException e) + { //Stream no longer exists, clean up and exit + RemoveStream(stream); + try + { + _controlConnection.CloseStream(stream.StreamID); + } + catch (Exception) { } + } + } + public override void RemoveCircuit(Circuit circuit) + { + lock (DestinationDictionary) + { + if (DestinationDictionary.ContainsValue(circuit)) + DestinationDictionary.Remove(DestinationDictionary.First(p => p.Value.Equals(circuit)).Key); + } + base.RemoveCircuit(circuit); + } + } +} diff --git a/Hermes/CircuitManagers/ICircuitManager.cs b/Hermes/CircuitManagers/ICircuitManager.cs new file mode 100644 index 0000000..9839b1d --- /dev/null +++ b/Hermes/CircuitManagers/ICircuitManager.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Hermes.Objects; +using Hermes.Selectors; + +namespace Hermes.CircuitManagers +{ + public interface ICircuitManager + { + void Initialize(TorControlLibrary.ControlConnection controlConnection, IRouteSelector RouteSelector, Dictionary parameters); + void Initialize(TorControlLibrary.ControlConnection controlConnection, IRouteSelector RouteSelector); + + //Circuit management + void AddCircuit(Circuit circuit); + Circuit GetCircuit(Int32 circuitID); + void RemoveCircuit(Circuit circuit); + void RequestNewCircuit(); + + //Stream Management + void RemoveStream(Int32 circuitID, Int32 streamID); + } +} diff --git a/Hermes/CircuitManagers/OneShotCircuitManager.cs b/Hermes/CircuitManagers/OneShotCircuitManager.cs new file mode 100644 index 0000000..4d574ad --- /dev/null +++ b/Hermes/CircuitManagers/OneShotCircuitManager.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Hermes.Objects; + +namespace Hermes.CircuitManagers +{ + /// + /// One of the most basic circuit managers, using randomization to select the next viable circuit + /// + public class OneShotRoundRobinCircuitManager : EventBasedCircuitManager, ICircuitManager + { + #region Event Overrides + protected override void OnCircuitBuilt(TorControlLibrary.Responses.EventResponse response) + { + //Add the completely built circuit + AddCircuit(ParseCircuitResponse(response)); + } + protected override void OnCircuitClosed(TorControlLibrary.Responses.EventResponse response) + { + Objects.Circuit circ = GetCircuit(response.ID); + RemoveCircuit(circ); + if (circ.Purpose.Equals("USER") || circ.Purpose.Equals("GENERAL")) + RequestNewCircuit(); + } + protected override void OnCircuitFailed(TorControlLibrary.Responses.EventResponse response) + { + Objects.Circuit circ = GetCircuit(response.ID); + if(circ != null) + RemoveCircuit(circ); + + if (response.EventInformation.Contains("PURPOSE=USER") || response.EventInformation.Contains("PURPOSE=GENERAL")) + RequestNewCircuit(); + } + protected override void OnStreamClosed(TorControlLibrary.Responses.EventResponse response) + { + Stream stream = Stream.ParseStream(response); + RemoveStream(stream); + } + protected override void OnStreamDetached(TorControlLibrary.Responses.EventResponse response) + { + Stream stream = Stream.ParseStream(response); + if (stream.CircuitID != 0) + stream.Circuit = GetCircuit(stream.CircuitID); + + RemoveStream(stream); + + if (stream.Purpose.Equals("USER") || stream.Purpose.Equals("GENERAL")) + AssignStream(stream); + } + protected override void OnStreamFailed(TorControlLibrary.Responses.EventResponse response) + { + Stream stream = Stream.ParseStream(response); + if (stream.CircuitID != 0) + stream.Circuit = GetCircuit(stream.CircuitID); + + RemoveStream(stream); + } + protected override void OnStreamNew(TorControlLibrary.Responses.EventResponse response) + { + Stream stream = Stream.ParseStream(response); + if (stream.CircuitID != 0) + stream.Circuit = GetCircuit(stream.CircuitID); + + if (stream.Purpose.Equals("USER") || stream.Purpose.Equals("GENERAL")) + AssignStream(stream); + } + #endregion + protected virtual void AssignStream(Stream stream) + { + Circuit circ = GetCircuitForStream(stream); + while (circ == null) + { + //Wait for more circuits to build + System.Threading.Thread.Sleep(5000); + circ = GetCircuitForStream(stream); + } + try + { + AssignStreamToCircuit(stream, circ); + } + catch (TorControlLibrary.Exceptions.TorUnknownCircuitException e) + { //The circuit has become invalidated, we need to try again + //cleanup + RemoveCircuit(circ); + try + { + _controlConnection.CloseCircuit(circ.CircuitID); + } + catch (Exception) { } + //Call this function again, with hopefully better results next time + AssignStream(stream); + } + } + public override void AssignStreamToCircuit(Stream stream, Circuit circuit) + { + //Handles internal objects + base.AssignStreamToCircuit(stream, circuit); + _controlConnection.AttachStream(stream.StreamID, circuit.CircuitID); + } + /// + /// This method chooses the circuit to attach to the specified stream + /// + /// + /// + protected virtual Circuit GetCircuitForStream(Stream stream) + { + //Round Robin the next circuit + if (stream.Destination != null) + return Circuits.Where(p => p.Routers.Last().Value.ExitPolicy.IsAcceptableDestination(stream.Destination) && p.AttachedStreams.Count == Circuits.Min(z => z.AttachedStreams.Count)).FirstOrDefault(); + else + return Circuits.Where(p => p.AttachedStreams.Count == Circuits.Min(z => z.AttachedStreams.Count)).FirstOrDefault(); + } + } +} diff --git a/Hermes/CircuitManagers/OneShotRoundRobinCircuitManager.cs b/Hermes/CircuitManagers/OneShotRoundRobinCircuitManager.cs new file mode 100644 index 0000000..b599757 --- /dev/null +++ b/Hermes/CircuitManagers/OneShotRoundRobinCircuitManager.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Hermes.Objects; +using TorControlLibrary.Responses; + +namespace Hermes.CircuitManagers +{ + /// + /// One of the most basic circuit managers, using randomization to select the next viable circuit + /// + public class OneShotRoundRobinCircuitManager : EventBasedCircuitManager, ICircuitManager + { + #region Event Overrides + protected override void OnCircuitBuilt(TorControlLibrary.Responses.EventResponse response) + { + //Add the completely built circuit + AddCircuit(ParseCircuitResponse(response)); + } + protected override void OnCircuitClosed(TorControlLibrary.Responses.EventResponse response) + { + Objects.Circuit circ = GetCircuit(response.ID); + if(circ != null) + RemoveCircuit(circ); + + if (response.EventInformation.Contains("PURPOSE=USER") || response.EventInformation.Contains("PURPOSE=GENERAL") && CircuitCount < ExpectedCircuits) + RequestNewCircuit(); + } + protected override void OnCircuitFailed(TorControlLibrary.Responses.EventResponse response) + { + Objects.Circuit circ = GetCircuit(response.ID); + if(circ != null) + RemoveCircuit(circ); + + if (response.EventInformation.Contains("PURPOSE=USER") || response.EventInformation.Contains("PURPOSE=GENERAL") && CircuitCount < ExpectedCircuits) + RequestNewCircuit(); + } + protected override void OnStreamClosed(TorControlLibrary.Responses.EventResponse response) + { + Stream stream = Stream.ParseStream(response); + RemoveStream(stream); + } + protected override void OnStreamDetached(TorControlLibrary.Responses.EventResponse response) + { + Stream stream = Stream.ParseStream(response); + if (stream.CircuitID != 0) + stream.Circuit = GetCircuit(stream.CircuitID); + + RemoveStream(stream); + + if (stream.Purpose != null && (stream.Purpose.Equals("USER") || stream.Purpose.Equals("GENERAL"))) + AssignStream(stream); + } + protected override void OnStreamFailed(TorControlLibrary.Responses.EventResponse response) + { + Stream stream = Stream.ParseStream(response); + if (stream.CircuitID != 0) + stream.Circuit = GetCircuit(stream.CircuitID); + + RemoveStream(stream); + } + protected override void OnStreamNewResolved(EventResponse response) + { + OnStreamNew(response); + //base.OnStreamNewResolved(response); + } + protected override void OnStreamNew(TorControlLibrary.Responses.EventResponse response) + { + Stream stream = Stream.ParseStream(response); + if (stream.CircuitID != 0) + stream.Circuit = GetCircuit(stream.CircuitID); + + if (stream.Purpose.Equals("USER") || stream.Purpose.Equals("GENERAL")) + AssignStream(stream); + } + #endregion + protected virtual void AssignStream(Stream stream) + { + Circuit circ = GetCircuitForStream(stream); + while (circ == null) + { + //Wait for more circuits to build + System.Threading.Thread.Sleep(5000); + circ = GetCircuitForStream(stream); + } + try + { + AssignStreamToCircuit(stream, circ); + } + catch (TorControlLibrary.Exceptions.TorUnknownCircuitException e) + { //The circuit has become invalidated, we need to try again + //cleanup + RemoveCircuit(circ); + try + { + _controlConnection.CloseCircuit(circ.CircuitID); + } + catch (Exception) { } + //Call this function again, with hopefully better results next time + AssignStream(stream); + } + catch (TorControlLibrary.Exceptions.TorUnknownStreamException e) + { //Stream no longer exists, clean up and exit + RemoveStream(stream); + try + { + _controlConnection.CloseStream(stream.StreamID); + } + catch (Exception) { } + } + } + public override void AssignStreamToCircuit(Stream stream, Circuit circuit) + { + //Handles internal objects + base.AssignStreamToCircuit(stream, circuit); + _controlConnection.AttachStream(stream.StreamID, circuit.CircuitID); + } + /// + /// This method chooses the circuit to attach to the specified stream + /// + /// + /// + protected virtual Circuit GetCircuitForStream(Stream stream) + { + Circuit circ; + //Round Robin the next circuit + if (stream.Destination != null) + { + circ = Circuits.OrderBy(p => p.AttachedStreams.Count).FirstOrDefault(p => p.Routers.Last().Value.ExitPolicy.IsAcceptableDestination(stream.Destination)); + if (circ != null) + return circ; + } + //return a circuit that won't necessarily exit successfully to avoid deadlocks and cycle for new circuits + return Circuits.OrderBy(p => p.AttachedStreams.Count).FirstOrDefault(); + } + } +} diff --git a/Hermes/CircuitManagers/PersistedCircuitManager.cs b/Hermes/CircuitManagers/PersistedCircuitManager.cs new file mode 100644 index 0000000..7757425 --- /dev/null +++ b/Hermes/CircuitManagers/PersistedCircuitManager.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using System.Threading.Tasks; + +namespace Hermes.CircuitManagers +{ + public class PersistedCircuitManager : EventBasedCircuitManager + { + internal List PersistedCircuitConnections { get; set; } + + internal void SendMaintenanceRequest(PersistanceStream stream) + { + //Connections are persistent by default + const String request = "GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n"; + + stream.Writer.Write(request); + String response = stream.Reader.ReadToEnd(); + } + private void persistanceManagementThread() + { + PersistedCircuitConnections.ForEach(p => + { + if (DateTime.Now.Subtract(p.LastRefreshed) > new TimeSpan(0, 1, 0)) + { + Action task = new Action(SendMaintenanceRequest); + task.BeginInvoke(p, null, null); + p.LastRefreshed = DateTime.Now; + } + }); + System.Threading.Thread.Sleep(1000); + } + internal class PersistanceStream + { + public PersistanceStream(Stream stream) + { + LastRefreshed = DateTime.Now; + Stream = stream; + Writer = new StreamWriter(Stream); + Reader = new StreamReader(Stream); + } + public DateTime LastRefreshed { get; set; } + public Stream Stream { get; set; } + public StreamWriter Writer { get; set; } + public StreamReader Reader { get; set; } + } + } +} diff --git a/Hermes/Filters/BandwidthObservedFilter.cs b/Hermes/Filters/BandwidthObservedFilter.cs new file mode 100644 index 0000000..cc8aeff --- /dev/null +++ b/Hermes/Filters/BandwidthObservedFilter.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Hermes.Objects; + +namespace Hermes.Filters +{ + class BandwidthObservedFilter : RouterFilter + { + public BandwidthObservedFilter() { } + public BandwidthObservedFilter(RouterFilter previousFilter) + : base(previousFilter) { } + + public override void Initialize(TorControlLibrary.ControlConnection connection, Dictionary parameters) + { + Initialize(connection); + Int32 speed; + if (Int32.TryParse(parameters["MinimumObservableSpeed"], out speed)) + _minimumSpeed = speed; + } + public override IEnumerable FilterRouters(IEnumerable fullRouterSet) + { + IEnumerable routers = base.FilterRouters(fullRouterSet); + List bandwidthRouters = new List(); + foreach (Router router in routers) + { + try + { + TorControlLibrary.Responses.RouterDescription description; + //if (!String.IsNullOrEmpty(router.NickName) && !router.NickName.Equals("Unnamed")) + // description = Connection.GetRouterDescriptionByNickname(router.NickName); + //else + description = Connection.GetRouterDescriptionByNickname(String.Format("${0}", router.Identity)); + if (description.BandwidthObserved > _minimumSpeed) + bandwidthRouters.Add(router); + } + catch (Exception) + { //Router description not available + } + } + return bandwidthRouters; + } + private Int32 _minimumSpeed = 1000; + } +} diff --git a/Hermes/Filters/CountryFilter.cs b/Hermes/Filters/CountryFilter.cs new file mode 100644 index 0000000..2cf7b68 --- /dev/null +++ b/Hermes/Filters/CountryFilter.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Hermes.Objects; +using Misc.Geolocation.API; + +namespace Hermes.Filters +{ + class CountryFilter : RouterFilter + { + public CountryFilter() { } + public CountryFilter(RouterFilter previousFilter) + : base(previousFilter) { } + + public override void Initialize(TorControlLibrary.ControlConnection connection, Dictionary parameters) + { + Initialize(connection); + CountryCodes = new List(parameters["CountryCode"].Split(',')); + } + public override IEnumerable FilterRouters(IEnumerable fullRouterSet) + { + Location routerLocation; + LookupService ls = new LookupService("GeoLiteCity.dat", LookupService.GEOIP_STANDARD); + List routers = new List(base.FilterRouters(fullRouterSet)); + List countryRouters = new List(); + foreach (Router router in routers) + { + routerLocation = ls.getLocation(router.Address); + if(routerLocation != null) + if(CountryCodes.Contains(routerLocation.countryCode)) + countryRouters.Add(router); + } + return countryRouters; + } + public List CountryCodes { get; set; } + } +} diff --git a/Hermes/Filters/ExitPolicyFilter.cs b/Hermes/Filters/ExitPolicyFilter.cs new file mode 100644 index 0000000..22d1b34 --- /dev/null +++ b/Hermes/Filters/ExitPolicyFilter.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net; +using Hermes.Objects; + +namespace Hermes.Filters +{ + class ExitPolicyFilter : RouterFilter + { + public ExitPolicyFilter() { } + public ExitPolicyFilter(RouterFilter previousFilter) + : base(previousFilter) { } + + public override void Initialize(TorControlLibrary.ControlConnection connection, Dictionary parameters) + { + String ep = parameters["ExitEndPoint"]; + Initialize(connection); + + Endpoint = new IPEndPoint(IPAddress.Parse(ep.Substring(0, ep.IndexOf(":"))), + Int32.Parse(ep.Substring(ep.IndexOf(":") + 1, ep.Length - (ep.IndexOf(":") + 1)))); + } + public override IEnumerable FilterRouters(IEnumerable fullRouterSet) + { + List routers = new List(); + foreach (Router router in base.FilterRouters(fullRouterSet)) + { + try + { + if (!router.Flags.Contains("Exit")) + { //non-exits are still added so circuits aren't composed of exit-only. + routers.Add(router); + continue; + } + if (router.ExitPolicy == null) + router.ExitPolicy = Connection.GetRouterDescriptionByNickname(String.Format("${0}", router.Identity)).ExitPolicy; + if (router.ExitPolicy.IsAcceptableDestination(Endpoint)) + routers.Add(router); + } + catch (Exception) { } + } + return routers; + } + public IPEndPoint Endpoint { get; set; } + } +} diff --git a/Hermes/Filters/FlagFilter.cs b/Hermes/Filters/FlagFilter.cs new file mode 100644 index 0000000..b5581bf --- /dev/null +++ b/Hermes/Filters/FlagFilter.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Hermes.Objects; + +namespace Hermes.Filters +{ + class FlagFilter : RouterFilter + { + public FlagFilter() { } + public FlagFilter(RouterFilter previousFilter) + : base(previousFilter) { } + + public override IEnumerable FilterRouters(IEnumerable fullRouterSet) + { + Routers = new List(base.FilterRouters(fullRouterSet)); + return FilterByFlags(); + } + + public override void Initialize(TorControlLibrary.ControlConnection connection, Dictionary parameters) + { + Initialize(connection); + if (!parameters.ContainsKey("Flags")) + throw new ApplicationException(); + if (!parameters.ContainsKey("AntiFlags")) + throw new ApplicationException(); + + Flags = new List(parameters["Flags"].Split(',')); + AntiFlags = new List(parameters["AntiFlags"].Split(',')); + } + protected virtual IEnumerable FilterByFlags() + { + foreach (String flag in Flags) + Routers.RemoveAll(p => !p.Flags.Contains(flag)); + + foreach (String flag in AntiFlags) + Routers.RemoveAll(p => p.Flags.Contains(flag)); + return Routers; + } + public List Flags { get; set; } + public List AntiFlags { get; set; } + } +} diff --git a/Hermes/Filters/IRouterFilter.cs b/Hermes/Filters/IRouterFilter.cs new file mode 100644 index 0000000..7bd7fa8 --- /dev/null +++ b/Hermes/Filters/IRouterFilter.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Hermes.Objects; +using TorControlLibrary; + +namespace Hermes.Filters +{ + public interface IRouterFilter + { + IEnumerable FilterRouters(IEnumerable fullRouterSet); + void Initialize(ControlConnection connection, Dictionary parameters); + void Initialize(ControlConnection connection); + } +} diff --git a/Hermes/Filters/ProximityFilter.cs b/Hermes/Filters/ProximityFilter.cs new file mode 100644 index 0000000..3965836 --- /dev/null +++ b/Hermes/Filters/ProximityFilter.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Misc.Geolocation.API; +using Hermes.Objects; + +namespace Hermes.Filters +{ + class ProximityFilter : RouterFilter + { + public ProximityFilter() { } + public ProximityFilter(RouterFilter previousFilter) : base(previousFilter) { } + + public override void Initialize(TorControlLibrary.ControlConnection connection, Dictionary parameters) + { + Initialize(connection); + + String proximity = parameters["Proximity"]; + if (!Double.TryParse(proximity, out maximumDistance)) + throw new FormatException("The proximity argument was unable to be parsed in the ProximityFilter class."); + sourceAddress = parameters["SourceAddress"]; + if (String.IsNullOrEmpty(sourceAddress)) + throw new FormatException("Source Address was not specified for the ProximityFilter."); + } + public override IEnumerable FilterRouters(IEnumerable fullRouterSet) + { + Location routerLocation; + LookupService ls = new LookupService("GeoLiteCity.dat", LookupService.GEOIP_STANDARD); + Location sourceLocation = ls.getLocation(sourceAddress); + List routers = new List(base.FilterRouters(fullRouterSet)); + List proximityRouters = new List(); + foreach (Router router in routers) + { + routerLocation = ls.getLocation(router.Address); + if(routerLocation != null) + if (routerLocation.distance(sourceLocation) <= maximumDistance) + proximityRouters.Add(router); + } + return proximityRouters; + } + private double maximumDistance = 0; + private String sourceAddress = String.Empty; + } +} diff --git a/Hermes/Filters/RouterFitler.cs b/Hermes/Filters/RouterFitler.cs new file mode 100644 index 0000000..3200dc9 --- /dev/null +++ b/Hermes/Filters/RouterFitler.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Hermes.Objects; +using TorControlLibrary; + +namespace Hermes.Filters +{ + abstract class RouterFilter : IRouterFilter + { + public RouterFilter() { } + public RouterFilter(RouterFilter previousFilter) + { + PreviousFilter = previousFilter; + } + + public virtual IEnumerable FilterRouters(IEnumerable fullRouterSet) + { + if (PreviousFilter != null) + return PreviousFilter.FilterRouters(fullRouterSet); + else + return fullRouterSet; + } + + public abstract void Initialize(ControlConnection connection, Dictionary parameters); + + public virtual void Initialize(ControlConnection connection) + { + Connection = connection; + } + public List Routers { get; set; } + protected RouterFilter PreviousFilter { get; set; } + public ControlConnection Connection { get; set; } + } +} diff --git a/Hermes/Filters/RouterStatusBasedFilter.cs b/Hermes/Filters/RouterStatusBasedFilter.cs new file mode 100644 index 0000000..33232d4 --- /dev/null +++ b/Hermes/Filters/RouterStatusBasedFilter.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Hermes.Objects; +using TorControlLibrary; +using TorControlLibrary.Responses; + +namespace Hermes.Filters +{ + [Obsolete()] + public abstract class RouterStatusBasedFilter + { + public RouterStatusBasedFilter() + { } + public List GetFilteredRouterList() + { + if(_routerStatuses.Count == 0) + PopulateRouterStatuses(); + return ParseRouterStatus(FilterRouters()); + } + protected void PopulateRouterStatuses() + { + if (!Connection.Connected) + Connection.Connect(); + _routerStatuses = Connection.GetAllRouterStatusInfo(); + } + protected List ParseRouterStatus(List statuses) + { + List routers = new List(); + foreach (RouterStatusResponse resp in statuses) + routers.Add(new Router(resp)); + //{ + // Address = resp.Address, + // Digest = resp.Digest, + // Identity = resp.Identity, + // NickName = resp.NickName, + // Publication = resp.Publication, + // Flags = new List(resp.Flags) + //}); + return routers; + } + public abstract List FilterRouters(); + public ControlConnection Connection { get; set; } + protected List _routerStatuses = new List(); + } +} diff --git a/Hermes/Filters/WhitelistFilter.cs b/Hermes/Filters/WhitelistFilter.cs new file mode 100644 index 0000000..4cf2597 --- /dev/null +++ b/Hermes/Filters/WhitelistFilter.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Hermes.Objects; + +namespace Hermes.Filters +{ + class WhitelistFilter : RouterFilter + { + public WhitelistFilter() { } + public WhitelistFilter(RouterFilter previousFilter) + : base(previousFilter) { } + + public override void Initialize(TorControlLibrary.ControlConnection connection, Dictionary parameters) + { + Initialize(connection); + _whitelistedRouters = new List(parameters["WhitelistedRouters"].Split(',')); + } + public override IEnumerable FilterRouters(IEnumerable fullRouterSet) + { + List routers = new List(base.FilterRouters(fullRouterSet)); + return routers.Where(p => _whitelistedRouters.Contains(p.NickName)); + } + private List _whitelistedRouters; + } +} diff --git a/Hermes/Hermes.csproj b/Hermes/Hermes.csproj new file mode 100644 index 0000000..69dc182 --- /dev/null +++ b/Hermes/Hermes.csproj @@ -0,0 +1,98 @@ + + + + Debug + x86 + 8.0.30703 + 2.0 + {3DE6415A-1DED-4F75-9202-39BDF012BB5A} + Exe + Properties + Hermes + Hermes + v4.8 + 512 + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + .\Misc.Geolocation.API.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {EF216A86-F64F-4C76-9A86-EEE8E5484EF9} + TorControlLibrary + + + + + + + + + + + \ No newline at end of file diff --git a/Hermes/Misc.Geolocation.API.dll b/Hermes/Misc.Geolocation.API.dll new file mode 100644 index 0000000..bd00864 Binary files /dev/null and b/Hermes/Misc.Geolocation.API.dll differ diff --git a/Hermes/Objects/Circuit.cs b/Hermes/Objects/Circuit.cs new file mode 100644 index 0000000..246b6ff --- /dev/null +++ b/Hermes/Objects/Circuit.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using TorControlLibrary.Responses; + +namespace Hermes.Objects +{ + public class Circuit + { + public static Circuit ParseCircuit(EventResponse response) + { + Circuit circ = new Circuit() { CircuitID = response.ID }; + String[] circParts = response.EventInformation.Split(' '); + //650 CIRC 21414 BUILT Strangelove,KiwiBirdSuperstar,svengunnarsrelay PURPOSE=GENERAL + if(circParts.Length >= 3) + { + foreach (String routerName in circParts[2].Split(',')) + if (routerName.StartsWith("$")) + circ.AddNextRouter(new Router() { Identity = routerName }); + else + circ.AddNextRouter(new Router() { NickName = routerName }); + } + //Start at the first section of additional circuit information, and look for the purpose + for (int i = 3; i < circParts.Length; i++) + if (circParts[i].StartsWith("PURPOSE")) + circ.Purpose = circParts[i].Substring(circParts[i].IndexOf('=') + 1); + return circ; + } + public Circuit() + { + Routers = new SortedList(); + AttachedStreams = new List(); + } + public override String ToString() + { + String routerCircuit = String.Empty; + for (int i = 0; i < Routers.Count; i++) + routerCircuit += String.Format("${0},", Routers[i + 1].Identity); + routerCircuit = routerCircuit.TrimEnd(new[] { ',' }); + return routerCircuit; + } + public void AddNextRouter(Router router) + { + Routers.Add(Routers.Count + 1, router); + } + public Int32 CircuitID { get; set; } + public SortedList Routers { get; set; } + public List AttachedStreams { get; set; } + public String Purpose { get; set; } + } +} diff --git a/Hermes/Objects/Router.cs b/Hermes/Objects/Router.cs new file mode 100644 index 0000000..51245f2 --- /dev/null +++ b/Hermes/Objects/Router.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net; +using TorControlLibrary.Responses; +using TorControlLibrary; + +namespace Hermes.Objects +{ + public class Router : IEquatable + { + public Router() + { } + public Router(RouterStatusResponse resp) + { + Address = resp.Address; + Digest = resp.Digest; + Identity = resp.Identity; + NickName = resp.NickName; + Publication = resp.Publication; + Flags = new List(resp.Flags); + } + public static List ParseRouterStatusResponses(List responses) + { + List routers = new List(); + foreach (RouterStatusResponse resp in responses) + routers.Add(new Router(resp)); + return routers; + } + public void AddDescription(RouterDescription desc) + { + BandwidthObserved = desc.BandwidthObserved; + BandwidthAverage = desc.BandwidthAverage; + BandwidthBurst = desc.BandwidthBurst; + Uptime = desc.Uptime; + ExitPolicy = desc.ExitPolicy; + } + + #region IEquatable Members + public bool Equals(Router other) + { + return this.Digest.Equals(other.Digest) || this.NickName.Equals(other.NickName) || this.Identity.Equals(other.Identity); + } + public override bool Equals(object obj) + { + if (obj == null || !(obj is Router)) + return false; + return Equals((Router)obj); + } + public override int GetHashCode() + { + return base.GetHashCode(); + } + #endregion + + public String NickName { get; set; } + public String Identity { get; set; } + public String Digest { get; set; } + public DateTime Publication { get; set; } + public IPAddress Address { get; set; } + public List Flags { get; set; } + //Extended information properties gathered from router description command + public Int32 BandwidthObserved { get; set; } + public Int32 BandwidthAverage { get; set; } + public Int32 BandwidthBurst { get; set; } + public TimeSpan Uptime { get; set; } + public ExitPolicy ExitPolicy { get; set; } + } +} diff --git a/Hermes/Objects/Socks5Stream.cs b/Hermes/Objects/Socks5Stream.cs new file mode 100644 index 0000000..7ed0928 --- /dev/null +++ b/Hermes/Objects/Socks5Stream.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net.Sockets; +using System.IO; + +namespace Hermes.Objects +{ + public sealed class Socks5Stream : NetworkStream + { + public Socks5Stream(String proxyAddress, Int16 proxyPort, String destinationAddress, Int16 destinationPort, String socksUsername = null, String socksPassword = null) + : base(ConnectToSocks5Server(proxyAddress, proxyPort)) + { + //This class is now connected to the SOCKS server + byte[] buffer = new byte[1500]; + BinaryReader reader = new BinaryReader(this); + BinaryWriter writer = new BinaryWriter(this); + MemoryStream bufferStream = new MemoryStream(buffer); + BinaryWriter bufferWriter = new BinaryWriter(bufferStream); + + //Initial negotiation - provide available authentication methods 0x00 = no auth, 0x02 = user/pass + if(socksUsername == null || socksPassword == null) + writer.Write(new byte[] { 0x05, 0x01, 0x00 }); + else + writer.Write(new byte[] { 0x05, 0x02, 0x00, 0x02 }); + + reader.Read(buffer, 0, buffer.Length); + if (buffer[1] == 0x02) + { //Authentication required + bufferStream.Seek(0, SeekOrigin.Begin); + + bufferWriter.Write((byte)0x01); //Version 1 + bufferWriter.Write((byte)socksUsername.Length); + bufferWriter.Write(Encoding.ASCII.GetBytes(socksUsername)); + bufferWriter.Write((byte)socksPassword.Length); + bufferWriter.Write(Encoding.ASCII.GetBytes(socksPassword)); + writer.Write(buffer, 0, 3 + socksUsername.Length + socksPassword.Length); + + reader.Read(buffer, 0, buffer.Length); + if (buffer[1] != 0x00) + throw new ApplicationException("The provided SOCKS credentials were not accepted by the server."); + } + //Authenticated, issue the CONNECT command + bufferStream.Seek(0, SeekOrigin.Begin); + bufferWriter.Write((byte)0x05); //Version + bufferWriter.Write((byte)0x01); //CONNECT (versus BIND, UDP ASSOCIATE) + bufferWriter.Write((byte)0x00); //Reserved + bufferWriter.Write((byte)0x03); //Domain address (IP will be resolved just as well) + bufferWriter.Write((byte)destinationAddress.Length); + bufferWriter.Write(Encoding.ASCII.GetBytes(destinationAddress)); + bufferWriter.Write(System.Net.IPAddress.HostToNetworkOrder(destinationPort)); + writer.Write(buffer,0,5 + destinationAddress.Length + 2); //5 = first 5 bytes, 2 = port size + + reader.Read(buffer, 0, buffer.Length); + if(buffer[1] != 0x00) + switch (buffer[1]) + { + case 0x01: + throw new ApplicationException("The SOCKS server provided the error: General SOCKS server failure"); + case 0x02: + throw new ApplicationException("The SOCKS server provided the error: connection not allowed by ruleset"); + case 0x03: + throw new ApplicationException("The SOCKS server provided the error: Network unreachable"); + case 0x04: + throw new ApplicationException("The SOCKS server provided the error: Host unreachable"); + case 0x05: + throw new ApplicationException("The SOCKS server provided the error: Connection refused"); + case 0x06: + throw new ApplicationException("The SOCKS server provided the error: TTL expired"); + case 0x07: + throw new ApplicationException("The SOCKS server provided the error: Command not supported"); + case 0x08: + throw new ApplicationException("The SOCKS server provided the error: Address type not supported"); + default: + throw new ApplicationException(String.Format("The SOCKS server provided an unrecognized error of type: {0}",buffer[1])); + } + //At this point, the stream is connected to the end destination. + } + + private static Socket ConnectToSocks5Server(String proxyAddress, Int16 proxyPort) + { + Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + try + { + sock.Connect(proxyAddress, proxyPort); + } + catch(Exception e) + { + throw new ApplicationException(String.Format("There was a problem connecting with the socks server at {0}:{1}",proxyAddress,proxyPort)); + } + return sock; + } + } +} diff --git a/Hermes/Objects/Stream.cs b/Hermes/Objects/Stream.cs new file mode 100644 index 0000000..49a8bb1 --- /dev/null +++ b/Hermes/Objects/Stream.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using TorControlLibrary.Responses; +using System.Net; + +namespace Hermes.Objects +{ + public class Stream + { + public static Stream ParseStream(EventResponse resp) + { + Stream stream = new Stream() { StreamID = resp.ID, Attached = false }; + String[] parts = resp.EventInformation.Split(' '); + Int32 circid; + + if (Int32.TryParse(parts[2], out circid)) + stream.CircuitID = circid; + if (parts.Length > 3 && parts[3].Contains(':')) + { + stream.DestinationHost = parts[3].Substring(0, parts[3].IndexOf(':')); + stream.DestinationPort = Int32.Parse(parts[3].Substring(parts[3].IndexOf(':') + 1)); + } + if (resp.EventInformation.Contains("PURPOSE=")) + { + Int32 startIndex = resp.EventInformation.IndexOf("PURPOSE=") + 8; + Int32 length = resp.EventInformation.IndexOf(' ', startIndex) - startIndex; + stream.Purpose = resp.EventInformation.Substring(startIndex, length); + } + + return stream; + } + public Int32 StreamID { get; set; } + public Circuit Circuit { get; set; } + public Int32 CircuitID { get; set; } + public Boolean Attached { get; set; } + public String DestinationHost { get; set; } + public Int32 DestinationPort { get; set; } + public IPEndPoint Destination + { + get + { + //TODO: This is a big security risk for anonymity, need to figure out a way to get this through tor + IPAddress[] addresses = Dns.GetHostAddresses(DestinationHost); + if (addresses.Count() > 0) + { + return new IPEndPoint(addresses.First(), DestinationPort); + } + else + return null; + } + } + public String Purpose { get; set; } + } +} diff --git a/Hermes/Program.cs b/Hermes/Program.cs new file mode 100644 index 0000000..5f86149 --- /dev/null +++ b/Hermes/Program.cs @@ -0,0 +1,361 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using TorControlLibrary; +using TorControlLibrary.Responses; +using Misc.Geolocation.API; +using System.Configuration; +using System.Text.RegularExpressions; +using Hermes.Filters; +using Hermes.Selectors; +using Hermes.Objects; +using TorControlLibrary.Exceptions; +using Hermes.CircuitManagers; + +namespace Hermes +{ + class Program + { + static IRouterFilter RouterFilter; + static IRouteSelector RouteSelector; + static List RouterList; + static ControlConnection ControlConnection; + //static CircuitManager CircuitManager; + static ICircuitManager CircuitManager; + + static void Main(string[] args) + { + IPAddress ip = IPAddress.Parse("127.0.0.1"); + + ControlConnection = new TorControlLibrary.ControlConnection(); + //ControlConnection.Connect(new IPEndPoint(IPAddress.Parse("192.168.1.100"), 9090)); + ControlConnection.Connect(); + + //CircuitManager = (ICircuitManager)new OneShotRoundRobinCircuitManager(); //new Hermes.CircuitManager(); + //CircuitManager = (ICircuitManager)new HostSectionedCircuitManager(); + CircuitManager = GetCircuitManager(); + //CircuitManager.Initialize(ControlConnection, RouteSelector); + //ControlConnection.ServerEvent += new Action(ControlConnection_ServerEvent); + RouterFilter = BuildRouterFilter(); + DisableAutomaticCircuitsAndStreamAttachment(); + InitializeRouterListAndSelector(CircuitManager); + Console.WriteLine(string.Format("There are {0} routers in the list. {1} of them are exit nodes.", RouterList.Count, RouterList.Count(p => p.Flags.Contains("Exit")))); + + + + CloseExistingCircuitsAndStreams(); + + Console.WriteLine("Hermes started.."); + ControlConnection.RequestEvents(new List() { "EXTENDED STREAM", "EXTENDED CIRC" }); + + Int32 circuits = Int32.Parse(ConfigurationManager.AppSettings["Circuits"]); + var settings = new Dictionary() { {"ExpectedCircuits", circuits.ToString() } }; + CircuitManager.Initialize(ControlConnection, RouteSelector, settings); + //Create some initial circuits + for (int i = 0; i < circuits; i++) + { + //CreateNewCircuit(); + CircuitManager.RequestNewCircuit(); + System.Threading.Thread.Sleep(250); + } + int step = 0; + while (true) + { + if (Console.KeyAvailable) + break; + + //30 minutes + if (step == 1800) + { + RefreshActiveRouters(); + step = 0; + } + else + step++; + System.Threading.Thread.Sleep(1000); + } + //Wait for a single key input, then quit + Console.ReadKey(); + EnableAutomaticcircuitsAndStreamAttachment(); + ControlConnection.Close(); + } + static void InitializeRouterListAndSelector(ICircuitManager circuitManager) + { + //Get router responses + TimeSpan defaultTimeout = ControlConnection.Timeout; + ControlConnection.Timeout = TimeSpan.FromMinutes(5); + IEnumerable routers = Router.ParseRouterStatusResponses(ControlConnection.GetAllRouterStatusInfo()); + routers = RouterFilter.FilterRouters(routers); + ControlConnection.Timeout = defaultTimeout; + RouterList = new List(routers); + + String routerSelectorName = ConfigurationManager.AppSettings["RouteSelector"]; + Type type = Type.GetType(String.Format("Hermes.Selectors.{0}", routerSelectorName)); + if (type == null) + throw new ApplicationException(String.Format("RouteSelector of type {0} not found.", routerSelectorName)); + + RouteSelector = (IRouteSelector)Activator.CreateInstance(type); + RouteSelector.Initialize(ControlConnection, circuitManager); + RouteSelector.SetEffectiveRouterList(RouterList); + } + static void RefreshActiveRouters() + { + IEnumerable routers = Router.ParseRouterStatusResponses(ControlConnection.GetAllRouterStatusInfo()); + routers = RouterFilter.FilterRouters(routers); + RouterList = new List(routers); + RouteSelector.SetEffectiveRouterList(RouterList); + } + static ICircuitManager GetCircuitManager() + { + ICircuitManager manager; + String circuitManagerName = ConfigurationManager.AppSettings["CircuitManager"]; + Type type = Type.GetType(String.Format("Hermes.CircuitManagers.{0}", circuitManagerName)); + if (type == null) + throw new ApplicationException(String.Format("CircuitManager of type {0} not found.", circuitManagerName)); + manager = (ICircuitManager)Activator.CreateInstance(type); + return manager; + } + static IRouterFilter BuildRouterFilter() + { + String filterNames = ConfigurationManager.AppSettings["Filters"]; + object activatedFilter = null; + RouterFilter previousFilter = null; + RouterFilter firstFilter = null; + RouterFilter CurrentFilter = null; + foreach (String filterName in filterNames.Split(',')) + { + Type type = Type.GetType(String.Format("Hermes.Filters.{0}",filterName)); + if (type != null) + { + if (previousFilter != null) + activatedFilter = Activator.CreateInstance(type, new object[] { previousFilter }); + else + { + activatedFilter = Activator.CreateInstance(type); + firstFilter = (RouterFilter)activatedFilter; + } + + CurrentFilter = (RouterFilter)activatedFilter; + + Dictionary paramDictionary = new Dictionary(); + foreach (string key in ConfigurationManager.AppSettings.AllKeys) + paramDictionary.Add(key, ConfigurationManager.AppSettings[key]); + + CurrentFilter.Initialize(ControlConnection, paramDictionary); + previousFilter = CurrentFilter; + } + } + return CurrentFilter; + } + static void DisableAutomaticCircuitsAndStreamAttachment() + { + ControlConnection.SetConfigurationEntry("__DisablePredictedCircuits", "1"); + ControlConnection.SetConfigurationEntry("MaxOnionsPending", "0"); + ControlConnection.SetConfigurationEntry("newcircuitperiod", "999999999"); + ControlConnection.SetConfigurationEntry("maxcircuitdirtiness", "999999999"); + ControlConnection.SetConfigurationEntry("__LeaveStreamsUnattached", "1"); + //Hack to avoid having to handle microdescriptors atm TODO: Fix TorLibrary to use Microdescriptors + ControlConnection.SetConfigurationEntry("UseMicrodescriptors", "0"); + ControlConnection.SetConfigurationEntry("FetchUselessDescriptors", "1"); + } + static void EnableAutomaticcircuitsAndStreamAttachment() + { + ControlConnection.SendRawCommand("RESETCONF __DisablePredictedCircuits"); + ControlConnection.SendRawCommand("RESETCONF MaxOnionsPending"); + ControlConnection.SendRawCommand("RESETCONF newcircuitperiod"); + ControlConnection.SendRawCommand("RESETCONF maxcircuitdirtiness"); + ControlConnection.SendRawCommand("RESETCONF __LeaveStreamsUnattached"); + ControlConnection.SendRawCommand("RESETCONF UseMicrodescriptors"); + ControlConnection.SendRawCommand("RESETCONF FetchUselessDescriptors"); + } + static void CloseExistingCircuitsAndStreams() + { + List circuits = ControlConnection.GetCircuitStatuses(); + foreach (CircuitStatusResponse cirResponse in circuits) + ControlConnection.CloseCircuit(cirResponse.CircuitID); + + List streams = ControlConnection.GetStreamStatuses(); + foreach (StreamStatusResponse streamResponse in streams) + ControlConnection.CloseStream(streamResponse.StreamID); + } + /* + static void ControlConnection_ServerEvent(EventResponse obj) + { + switch (obj.EventType) + { + case "STREAM": + switch (obj.Action) + { + case "NEW": + if (obj.EventInformation.EndsWith("PURPOSE=USER")) + ReattachStream(obj); + else + internalCircuits++; + break; + case "DETACHED": + DetachStream(obj); + ReattachStream(obj); + break; + case "CLOSED": + DetachStream(obj); + break; + case "FAILED": + DetachStream(obj); + break; + } + break; + case "CIRC": + switch (obj.Action) + { + case "BUILT": + AddCircuit(obj); + break; + case "CLOSED": + CircuitManager.RemoveCircuit(CircuitManager.GetCircuit(obj.ID)); + if (internalCircuits == 0) + CreateNewCircuit(); + else + internalCircuits--; + break; + case "FAILED": + CircuitManager.RemoveCircuit(CircuitManager.GetCircuit(obj.ID)); + if (internalCircuits == 0) + CreateNewCircuit(); + else + internalCircuits--; + break; + } + break; + } + } + static void CreateNewCircuit() + { + try + { + lock (CircuitManager) + { + ControlConnection.CreateCircuit( + RouteSelector.CreateNewCircuit().ToString() + ); + } + } + catch (TorException) + { + //fails due to no longer online router + CreateNewCircuit(); + } + } + static void AddCircuit(EventResponse resp) + { + String[] parts = resp.EventInformation.Split(' '); + String[] routers = parts[2].Split(','); + Circuit circ = new Circuit(); + foreach (String router in routers) + circ.AddNextRouter(FindRouter(router)); + circ.CircuitID = resp.ID; + CircuitManager.AddCircuit(circ); + } + static Router FindRouter(String identifier) + { + lock (RouterList) + { + try + { + if (identifier.StartsWith("$")) + return RouterList.First(p => p.Identity.Equals(identifier.Substring(1))); + else + return RouterList.First(p => p.NickName.Equals(identifier)); + } + catch (InvalidOperationException) + { //Not in router list + RouterStatusResponse resp = ControlConnection.GetRouterStatusInfoByNickname(identifier); + Router router = new Router(resp); + RouterList.Add(GetDescriptions(new [] { router}).First()); + return router; + } + } + } + static IEnumerable GetDescriptions(IEnumerable routers) + { + List descriptedRouters = new List(); + foreach (Router router in routers) + { + if (router.ExitPolicy == null) + { + try + { + RouterDescription desc = ControlConnection.GetRouterDescriptionByNickname(String.Format("${0}", router.Identity)); + router.AddDescription(desc); + descriptedRouters.Add(router); + } + catch (Exception) + { + router.ExitPolicy = new ExitPolicy(); + } + } + else + descriptedRouters.Add(router); + } + return descriptedRouters; + } + static void ReattachStream(EventResponse resp, IEnumerable exclusionList = null) + { + if (resp.EventInformation.Contains(".exit")) + return; + Stream stream = Stream.ParseStream(resp); + + + if(stream.CircuitID != 0) + stream.Circuit = CircuitManager.GetCircuit(stream.CircuitID); + + AttachStream(stream, exclusionList); + } + static void AttachStream(Stream stream, IEnumerable exclusionList) + { + Circuit circ = CircuitManager.AssignStreamToCircuit(stream, exclusionList, stream.Destination); + try + { + //Actually send request to attach + ControlConnection.AttachStream(stream.StreamID, circ.CircuitID); + } + catch (TorControlLibrary.Exceptions.TorUnknownCircuitException) + { //Circuit is bad + CircuitManager.RemoveCircuit(circ); + //Try again + try + { + CreateNewCircuit(); + ControlConnection.CloseCircuit(circ.CircuitID); + } //sometimes things get so broke, this fails, resulting in it never attaching a stream again. + catch (Exception) { } + AttachStream(stream, exclusionList); + } + catch (TorControlLibrary.Exceptions.TorUnknownStreamException) + { + + } + catch (TorControlLibrary.Exceptions.TorOneCircuitException) + { + //circuit is bad + CircuitManager.RemoveCircuit(circ); + try + { + CreateNewCircuit(); + ControlConnection.CloseCircuit(circ.CircuitID); + } + catch (Exception) { } + //Try again + AttachStream(stream, exclusionList); + } + } + static void DetachStream(EventResponse obj) + { + string[] parts = obj.EventInformation.Split(' '); + Int32 circuitId; + if(Int32.TryParse(parts[2],out circuitId) && circuitId != 0) + CircuitManager.RemoveStream(circuitId, obj.ID); + }*/ + } +} diff --git a/Hermes/Properties/AssemblyInfo.cs b/Hermes/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4b99a7e --- /dev/null +++ b/Hermes/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Hermes")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Hermes")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2011")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("1f2e8ef5-e389-4c81-888b-4c650c3c5108")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Hermes/Selectors/IRouteSelector.cs b/Hermes/Selectors/IRouteSelector.cs new file mode 100644 index 0000000..9b865dd --- /dev/null +++ b/Hermes/Selectors/IRouteSelector.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Hermes.Objects; +using TorControlLibrary; +using Hermes.CircuitManagers; + +namespace Hermes.Selectors +{ + public interface IRouteSelector + { + void Initialize(ControlConnection connection, ICircuitManager circuitManager, Dictionary parameters); + void Initialize(ControlConnection connection, ICircuitManager circuitManager); + void SetEffectiveRouterList(List routers); + Circuit CreateNewCircuit(); + Circuit CreateNewCircuit(System.Net.IPEndPoint destination); + Router FindRouter(String identifier); + List RouterPool { get; set; } + } +} diff --git a/Hermes/Selectors/RandomTwoRouterCircuits.cs b/Hermes/Selectors/RandomTwoRouterCircuits.cs new file mode 100644 index 0000000..8824d75 --- /dev/null +++ b/Hermes/Selectors/RandomTwoRouterCircuits.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Hermes.Objects; +using TorControlLibrary; +using TorControlLibrary.Responses; + +namespace Hermes.Selectors +{ + class RandomTwoRouterCircuits : RouteSelectorBase, IRouteSelector + { + + public Circuit CreateNewCircuit() + { + Circuit circuit = new Circuit(); + Random rand = new Random(); + GetPoolReaderLock(); + { + IEnumerable nonExits = RouterPool.Where(p => !p.Flags.Contains("Exit")); + circuit.AddNextRouter(nonExits.ElementAt(rand.Next(nonExits.Count()))); + IEnumerable exits = RouterPool.Where(p => p.Flags.Contains("Exit")); + circuit.AddNextRouter(exits.ElementAt(rand.Next(exits.Count()))); + } + ReleasePoolReaderLock(); + return circuit; + } + public Circuit CreateNewCircuit(System.Net.IPEndPoint destination) + { + Random rand = new Random(); + Circuit circuit = new Circuit(); + GetPoolReaderLock(); + { + IEnumerable nonExits = RouterPool.Where(p => !p.Flags.Contains("Exit")); + circuit.AddNextRouter(nonExits.ElementAt(rand.Next(nonExits.Count()))); + IEnumerable exits = RouterPool.Where(p => p.Flags.Contains("Exit") && p.ExitPolicy != null && p.ExitPolicy.IsAcceptableDestination(destination)); + circuit.AddNextRouter(exits.ElementAt(rand.Next(exits.Count()))); + } + ReleasePoolReaderLock(); + return circuit; + } + + } +} diff --git a/Hermes/Selectors/RouteSelectorBase.cs b/Hermes/Selectors/RouteSelectorBase.cs new file mode 100644 index 0000000..c77d263 --- /dev/null +++ b/Hermes/Selectors/RouteSelectorBase.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Hermes.Objects; +using TorControlLibrary.Responses; +using TorControlLibrary; +using Hermes.CircuitManagers; +using System.Threading; + +namespace Hermes.Selectors +{ + public abstract class RouteSelectorBase + { + public RouteSelectorBase() + { RouterPool = new List(); } + + public virtual void Initialize(ControlConnection connection, ICircuitManager circuitManager, Dictionary parameters) + { + Initialize(connection, circuitManager); + } + + public virtual void Initialize(ControlConnection connection, ICircuitManager circuitManager) + { + _controlConnection = connection; + _circuitManager = circuitManager; + } + + public virtual void SetEffectiveRouterList(List routers) + { + GetPoolWriterLock(); + { + RouterPool = routers; + RouterPool.ForEach(p => PopulateRouterDescription(ref p)); + } + ReleasePoolWriterLock(); + } + public virtual Router FindRouter(String identifier) + { + Router r; + lock (this) + { + GetPoolReaderLock(); + { + try + { + if (identifier.Contains("=")) //Contains the Identity hash as well as the nickname $ident=nickname + { + identifier = identifier.Substring(1, identifier.IndexOf('=') - 1); + r = RouterPool.First(p => p.Identity.Equals(identifier)); + } + else if (identifier.Contains("~")) // Contains the identity hash, but also includes the "Unnamed" nickname + { + identifier = identifier.Substring(1, identifier.IndexOf('~') - 1); + r = RouterPool.First(p => p.Identity.Equals(identifier)); + } + else if (identifier.StartsWith("$")) //Just the identity hash + { + identifier = identifier.Substring(1); + r = RouterPool.First(p => p.Identity.Equals(identifier)); + } + else //just the nickname + r = RouterPool.First(p => p.NickName.Equals(identifier)); + } + catch (InvalidOperationException) + { //Not in router list + //TODO: UpgradeToWriterLock() + ReleasePoolReaderLock(); + GetPoolWriterLock(); + { + RouterStatusResponse resp = _controlConnection.GetRouterStatusInfoByNickname(identifier); + Router router = new Router(resp); + PopulateRouterDescription(ref router); + RouterPool.Add(router); + r = router; + } + ReleasePoolWriterLock(); + } + } + ReleasePoolReaderLock(); + } + return r; + } + protected virtual void PopulateRouterDescription(ref Router router) + { + //If the Exit Policy is populated, then the router already has been described + if (router.ExitPolicy == null) + { + try + { + RouterDescription desc = _controlConnection.GetRouterDescriptionByNickname(String.Format("${0}", router.Identity)); + router.AddDescription(desc); + } + catch (Exception) + { + router.ExitPolicy = new ExitPolicy(); + } + } + } + protected void GetPoolReaderLock() + { + _routerPoolLock.TryEnterReadLock(5000); + } + protected void GetPoolWriterLock() + { + _routerPoolLock.TryEnterWriteLock(6000); + } + protected void ReleasePoolReaderLock() + { + if(_routerPoolLock.IsReadLockHeld) + _routerPoolLock.ExitReadLock(); + } + protected void ReleasePoolWriterLock() + { + if(_routerPoolLock.IsWriteLockHeld) + _routerPoolLock.ExitWriteLock(); + } + public List RouterPool { get; set; } + protected ControlConnection _controlConnection; + protected ICircuitManager _circuitManager; + private ReaderWriterLockSlim _routerPoolLock = new ReaderWriterLockSlim(); + } +} diff --git a/Hermes/Selectors/UniqueHighestSpeedSingleRouterSelector.cs b/Hermes/Selectors/UniqueHighestSpeedSingleRouterSelector.cs new file mode 100644 index 0000000..9d68bbd --- /dev/null +++ b/Hermes/Selectors/UniqueHighestSpeedSingleRouterSelector.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Hermes.Objects; + +namespace Hermes.Selectors +{ + public class UniqueHighestSpeedSingleRouterSelector : UniqueRouteSelector, IRouteSelector + { + #region IRouteSelector Members + + public Circuit CreateNewCircuit() + { + Circuit circuit = new Circuit(); + GetPoolReaderLock(); + { + lock (UsedRouters) + { + IEnumerable exits = RouterPool.Where(p => p.Flags.Contains("Exit") && !UsedRouters.Contains(p)); + if (exits.Count() == 0) + exits = RouterPool.Where(p => p.Flags.Contains("Exit")); + + circuit.AddNextRouter(exits.OrderByDescending(p => p.BandwidthObserved).First()); + UsedRouters.AddRange(circuit.Routers.Values.ToList()); + } + } + ReleasePoolReaderLock(); + return circuit; + } + + public Circuit CreateNewCircuit(System.Net.IPEndPoint destination) + { + Circuit circuit = new Circuit(); + GetPoolReaderLock(); + { + lock (UsedRouters) + { + IEnumerable exits = RouterPool.Where(p => p.Flags.Contains("Exit") && p.ExitPolicy != null && p.ExitPolicy.IsAcceptableDestination(destination) && !UsedRouters.Contains(p)); + if (exits.Count() == 0) + { + exits = RouterPool.Where(p => p.Flags.Contains("Exit") && p.ExitPolicy != null && p.ExitPolicy.IsAcceptableDestination(destination)); + circuit.AddNextRouter(exits.OrderByDescending(p => p.BandwidthObserved).First()); + } + else + circuit.AddNextRouter(exits.OrderByDescending(p => p.BandwidthObserved).First()); + + UsedRouters.AddRange(circuit.Routers.Values.ToList()); + } + } + ReleasePoolReaderLock(); + return circuit; + } + + #endregion + } +} diff --git a/Hermes/Selectors/UniqueRouteSelector.cs b/Hermes/Selectors/UniqueRouteSelector.cs new file mode 100644 index 0000000..9ff0e5f --- /dev/null +++ b/Hermes/Selectors/UniqueRouteSelector.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Hermes.Objects; +using Hermes.CircuitManagers; + +namespace Hermes.Selectors +{ + public abstract class UniqueRouteSelector : RouteSelectorBase + { + protected List UsedRouters; + public UniqueRouteSelector() + { + UsedRouters = new List(); + } + public override void Initialize(TorControlLibrary.ControlConnection connection, ICircuitManager circuitManager) + { + connection.ServerEvent += new Action(connection_ServerEvent); + base.Initialize(connection, circuitManager); + } + + void connection_ServerEvent(TorControlLibrary + .Responses.EventResponse obj) + { + if(obj.EventType == "CIRC") + switch (obj.Action) + { + case "CLOSED": + case "FAILED": + lock(UsedRouters) + { + Circuit circ = _circuitManager.GetCircuit(obj.ID); + if (circ != null) + { + foreach (Router r in circ.Routers.Values) + UsedRouters.Remove(r); + } + } + break; + } + } + } +} diff --git a/Hermes/Selectors/UniqueRouterHighestSpeedTwoRouterSelector.cs b/Hermes/Selectors/UniqueRouterHighestSpeedTwoRouterSelector.cs new file mode 100644 index 0000000..e405a09 --- /dev/null +++ b/Hermes/Selectors/UniqueRouterHighestSpeedTwoRouterSelector.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Hermes.Objects; + +namespace Hermes.Selectors +{ + class UniqueHighestSpeedTwoNodeSelector : UniqueRouteSelector, IRouteSelector + { + public UniqueHighestSpeedTwoNodeSelector() + : base() + {} + + #region IRouteSelector Members + + public Circuit CreateNewCircuit() + { + Random rand = new Random(); + Circuit circuit = new Circuit(); + GetPoolReaderLock(); + { + lock (UsedRouters) + { + //Try to use unused routers + IEnumerable nonExits = RouterPool.Where(p => !p.Flags.Contains("Exit") && !UsedRouters.Contains(p)); + if (nonExits.Count() == 0) + { + nonExits = RouterPool.Where(p => !p.Flags.Contains("Exit")); + circuit.AddNextRouter(nonExits.ElementAt(rand.Next(nonExits.Count()))); + } + else + circuit.AddNextRouter(nonExits.OrderByDescending(p => p.BandwidthObserved).First()); + + IEnumerable exits = RouterPool.Where(p => p.Flags.Contains("Exit") && !UsedRouters.Contains(p)); + if (exits.Count() == 0) + { + exits = RouterPool.Where(p => p.Flags.Contains("Exit")); + circuit.AddNextRouter(exits.ElementAt(rand.Next(exits.Count()))); + } + else + circuit.AddNextRouter(exits.OrderByDescending(p => p.BandwidthObserved).First()); + + UsedRouters.AddRange(circuit.Routers.Values.ToList()); + } + } + ReleasePoolReaderLock(); + return circuit; + } + public Circuit CreateNewCircuit(System.Net.IPEndPoint destination) + { + Random rand = new Random(); + Circuit circuit = new Circuit(); + + GetPoolReaderLock(); + { + lock (UsedRouters) + { + //Try to use unused routers + IEnumerable nonExits = RouterPool.Where(p => !p.Flags.Contains("Exit") && !UsedRouters.Contains(p)); + if (nonExits.Count() == 0) + { + nonExits = RouterPool.Where(p => !p.Flags.Contains("Exit")); + circuit.AddNextRouter(nonExits.ElementAt(rand.Next(nonExits.Count()))); + } + else + circuit.AddNextRouter(nonExits.OrderByDescending(p => p.BandwidthObserved).First()); + + IEnumerable exits = RouterPool.Where(p => p.Flags.Contains("Exit") && p.ExitPolicy != null && p.ExitPolicy.IsAcceptableDestination(destination) && !UsedRouters.Contains(p)); + if (exits.Count() == 0) + { + exits = RouterPool.Where(p => p.Flags.Contains("Exit") && p.ExitPolicy != null && p.ExitPolicy.IsAcceptableDestination(destination)); + circuit.AddNextRouter(exits.ElementAt(rand.Next(exits.Count()))); + } + else + circuit.AddNextRouter(exits.OrderByDescending(p => p.BandwidthObserved).First()); + + UsedRouters.AddRange(circuit.Routers.Values.ToList()); + } + } + ReleasePoolReaderLock(); + return circuit; + } + + #endregion + } +} diff --git a/Local.testsettings b/Local.testsettings new file mode 100644 index 0000000..4d6aa4f --- /dev/null +++ b/Local.testsettings @@ -0,0 +1,10 @@ + + + These are default test settings for a local test run. + + + + + + + \ No newline at end of file diff --git a/Tor.sln b/Tor.sln new file mode 100644 index 0000000..e466831 --- /dev/null +++ b/Tor.sln @@ -0,0 +1,80 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TorControlLibrary", "TorControlLibrary\TorControlLibrary.csproj", "{EF216A86-F64F-4C76-9A86-EEE8E5484EF9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TorLibraryTest", "TorLibraryTest\TorLibraryTest.csproj", "{020A074C-B1F5-498A-8C1D-B1D90C79D50D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{312F224D-6CC1-480A-971F-A485FEA8C867}" + ProjectSection(SolutionItems) = preProject + Local.testsettings = Local.testsettings + Tor.vsmdi = Tor.vsmdi + TraceAndTestImpact.testsettings = TraceAndTestImpact.testsettings + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hermes", "Hermes\Hermes.csproj", "{3DE6415A-1DED-4F75-9202-39BDF012BB5A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CircuitLoadTest", "CircuitLoadTest\CircuitLoadTest.csproj", "{124BEFA2-D0CB-4D6D-AD53-166DE405CC60}" +EndProject +Global + GlobalSection(TestCaseManagementSettings) = postSolution + CategoryFile = Tor.vsmdi + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EF216A86-F64F-4C76-9A86-EEE8E5484EF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EF216A86-F64F-4C76-9A86-EEE8E5484EF9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EF216A86-F64F-4C76-9A86-EEE8E5484EF9}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {EF216A86-F64F-4C76-9A86-EEE8E5484EF9}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {EF216A86-F64F-4C76-9A86-EEE8E5484EF9}.Debug|x86.ActiveCfg = Debug|Any CPU + {EF216A86-F64F-4C76-9A86-EEE8E5484EF9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EF216A86-F64F-4C76-9A86-EEE8E5484EF9}.Release|Any CPU.Build.0 = Release|Any CPU + {EF216A86-F64F-4C76-9A86-EEE8E5484EF9}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {EF216A86-F64F-4C76-9A86-EEE8E5484EF9}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {EF216A86-F64F-4C76-9A86-EEE8E5484EF9}.Release|x86.ActiveCfg = Release|Any CPU + {020A074C-B1F5-498A-8C1D-B1D90C79D50D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {020A074C-B1F5-498A-8C1D-B1D90C79D50D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {020A074C-B1F5-498A-8C1D-B1D90C79D50D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {020A074C-B1F5-498A-8C1D-B1D90C79D50D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {020A074C-B1F5-498A-8C1D-B1D90C79D50D}.Debug|x86.ActiveCfg = Debug|Any CPU + {020A074C-B1F5-498A-8C1D-B1D90C79D50D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {020A074C-B1F5-498A-8C1D-B1D90C79D50D}.Release|Any CPU.Build.0 = Release|Any CPU + {020A074C-B1F5-498A-8C1D-B1D90C79D50D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {020A074C-B1F5-498A-8C1D-B1D90C79D50D}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {020A074C-B1F5-498A-8C1D-B1D90C79D50D}.Release|x86.ActiveCfg = Release|Any CPU + {3DE6415A-1DED-4F75-9202-39BDF012BB5A}.Debug|Any CPU.ActiveCfg = Debug|x86 + {3DE6415A-1DED-4F75-9202-39BDF012BB5A}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {3DE6415A-1DED-4F75-9202-39BDF012BB5A}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {3DE6415A-1DED-4F75-9202-39BDF012BB5A}.Debug|x86.ActiveCfg = Debug|x86 + {3DE6415A-1DED-4F75-9202-39BDF012BB5A}.Debug|x86.Build.0 = Debug|x86 + {3DE6415A-1DED-4F75-9202-39BDF012BB5A}.Release|Any CPU.ActiveCfg = Release|x86 + {3DE6415A-1DED-4F75-9202-39BDF012BB5A}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {3DE6415A-1DED-4F75-9202-39BDF012BB5A}.Release|Mixed Platforms.Build.0 = Release|x86 + {3DE6415A-1DED-4F75-9202-39BDF012BB5A}.Release|x86.ActiveCfg = Release|x86 + {3DE6415A-1DED-4F75-9202-39BDF012BB5A}.Release|x86.Build.0 = Release|x86 + {124BEFA2-D0CB-4D6D-AD53-166DE405CC60}.Debug|Any CPU.ActiveCfg = Debug|x86 + {124BEFA2-D0CB-4D6D-AD53-166DE405CC60}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {124BEFA2-D0CB-4D6D-AD53-166DE405CC60}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {124BEFA2-D0CB-4D6D-AD53-166DE405CC60}.Debug|x86.ActiveCfg = Debug|x86 + {124BEFA2-D0CB-4D6D-AD53-166DE405CC60}.Debug|x86.Build.0 = Debug|x86 + {124BEFA2-D0CB-4D6D-AD53-166DE405CC60}.Release|Any CPU.ActiveCfg = Release|x86 + {124BEFA2-D0CB-4D6D-AD53-166DE405CC60}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {124BEFA2-D0CB-4D6D-AD53-166DE405CC60}.Release|Mixed Platforms.Build.0 = Release|x86 + {124BEFA2-D0CB-4D6D-AD53-166DE405CC60}.Release|x86.ActiveCfg = Release|x86 + {124BEFA2-D0CB-4D6D-AD53-166DE405CC60}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(SubversionScc) = preSolution + Svn-Managed = True + Manager = AnkhSVN - Subversion Support for Visual Studio + EndGlobalSection +EndGlobal diff --git a/Tor.vsmdi b/Tor.vsmdi new file mode 100644 index 0000000..c3af2e1 --- /dev/null +++ b/Tor.vsmdi @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/TorControlLibrary/ControlConnection.cs b/TorControlLibrary/ControlConnection.cs new file mode 100644 index 0000000..e6ec760 --- /dev/null +++ b/TorControlLibrary/ControlConnection.cs @@ -0,0 +1,382 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net; +using System.IO; +using System.Net.Sockets; +using System.Threading; +using TorControlLibrary.Responses; +using TorControlLibrary.Exceptions; + +namespace TorControlLibrary +{ + public class ControlConnection + { + public ControlConnection() + { + Host = new IPEndPoint(IPAddress.Loopback, 9051); + Timeout = new TimeSpan(0, 0, 90); + HashedControlPassword = String.Empty; + } + public void Connect(IPEndPoint host) + { + Host = host; + Connect(); + } + public void Connect(String authenticationPassword) + { + HashedControlPassword = authenticationPassword; + Connect(); + } + public void Connect(IPEndPoint host, String hashedControlPassword) + { + Host = host; + HashedControlPassword = hashedControlPassword; + Connect(); + } + public void Connect() + { + sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + sock.Connect(Host); + socketStream = new NetworkStream(sock); + socketReader = new StreamReader(socketStream); + socketWriter = new StreamWriter(socketStream); + socketWriter.AutoFlush = true; + + recvThread = new Thread(new ParameterizedThreadStart(receiveLoop)); + recvThread.IsBackground = true; + recvThread.Name = "Tor Control Library Receive Loop"; + recvThread.Start(); + + Authenticate(); + } + public void Close() + { + sock.Close(); + } + protected void Authenticate() + { + CommandResponse resp = SendCommand(String.Format("AUTHENTICATE \"{0}\"",HashedControlPassword)); + if (resp.StatusCode != 250) + throw new TorAuthenticationException() { ErrorDescription = "There was a problem authenticating with the Tor control port.", HashedPassword = _hashedControlPassword, ServerResponse = resp.Response, StatusCode = resp.StatusCode }; + } + public List GetCircuitStatuses() + { + List circuitResponses = new List(); + CommandResponse resp = SendCommand("GETINFO circuit-status"); + StringReader sr = new StringReader(resp.Response); + String line = sr.ReadLine(); + if (!line.Contains("circuit-status")) + throw TorException.Parse(resp.Response); + if (line[3] == '+' || line.EndsWith("=")) + line = sr.ReadLine(); + while (line != null) + { + if (line.Equals(".") || line.Equals("250 OK")) + break; + if (line.Contains("circuit-status")) + line = line.Substring(line.IndexOf('=') + 1); + circuitResponses.Add(CircuitStatusResponse.Parse(line)); + line = sr.ReadLine(); + } + return circuitResponses; + } + public List GetStreamStatuses() + { + List streamResponses = new List(); + CommandResponse resp = SendCommand("GETINFO stream-status"); + StringReader sr = new StringReader(resp.Response); + String line = sr.ReadLine(); + if (!line.Contains("stream-status")) + throw TorException.Parse(resp.Response); + if (line[3] == '+' || line.EndsWith("=")) + line = sr.ReadLine(); + while (line != null) + { + if (line.Equals(".") || line.Equals("250 OK")) + break; + if (line.Contains("stream-status")) + line = line.Substring(line.IndexOf('=') + 1); + streamResponses.Add(StreamStatusResponse.Parse(line)); + line = sr.ReadLine(); + } + return streamResponses; + } + public List GetAllRouterStatusInfo() + { + List routerResponses = new List(); + CommandResponse resp = SendCommand("GETINFO ns/all"); + StringReader sr = new StringReader(resp.Response); + String line = sr.ReadLine(), routerLine = String.Empty, flagLine = String.Empty; + if (!line.Contains("ns/all")) + throw TorException.Parse(resp.Response); + + while (line != null) + { + line = sr.ReadLine(); + if (line.Equals(".")) + break; + switch (line.Substring(0, 1)) + { + case "r": + routerLine = line; + continue; + case "s": + flagLine = line; + routerResponses.Add(RouterStatusResponse.Parse(routerLine, flagLine)); + break; + default: + //I'm not currently parsing "w" or "p" lines. p doesn't describe a complete exit policy, and "w" doesn't describe all the available bandwidth metrics. That's retrieved via GETINFO desc + continue; + } + + } + sr.Close(); + return routerResponses; + } + public RouterStatusResponse GetRouterStatusInfoByNickname(String nickname) + { + RouterStatusResponse response = null; + CommandResponse resp = SendCommand(String.Format("GETINFO ns/name/{0}", nickname)); + StringReader sr = new StringReader(resp.Response); + String line = sr.ReadLine(), routerLine = String.Empty, flagLine = String.Empty; + if (!line.Contains("ns/name")) + throw TorException.Parse(resp.Response); + + while (line != null) + { + line = sr.ReadLine(); + if (line == null || line.Equals(".")) + break; + switch (line.Substring(0, 1)) + { + case "r": + routerLine = line; + continue; + case "s": + flagLine = line; + response = RouterStatusResponse.Parse(routerLine, flagLine); + break; + default: + //I'm not currently parsing "w" or "p" lines. p doesn't describe a complete exit policy, and "w" doesn't describe all the available bandwidth metrics. That's retrieved via GETINFO desc + continue; + } + + } + sr.Close(); + return response; + } + public RouterDescription GetRouterDescriptionByNickname(String nickname) + { + RouterDescription routerDescription = new RouterDescription(nickname); + + CommandResponse resp = SendCommand(String.Format("GETINFO desc/name/{0}", nickname)); + if (resp.StatusCode != 250) + throw TorException.Parse(resp.Response); + using (StringReader reader = new StringReader(resp.Response)) + { + string line; + String policy = String.Empty; + while ((line = reader.ReadLine()) != null) + { + if (!line.Contains(' ')) continue; + Int32 firstSpace = line.IndexOf(' '); + string key = line.Substring(0, firstSpace).Trim(); + string value = line.Substring(firstSpace, line.Length - firstSpace).Trim(); + + switch (key) + { + case "uptime": + routerDescription.Uptime = new TimeSpan(0, 0, Int32.Parse(value)); + break; + case "bandwidth": + String[] values = value.Split(' '); + routerDescription.BandwidthAverage = Int32.Parse(values[0]); + routerDescription.BandwidthBurst = Int32.Parse(values[1]); + routerDescription.BandwidthObserved = Int32.Parse(values[2]); + break; + case "reject": + case "accept": + policy += line + Environment.NewLine; + break; + default: + break; + } + } + routerDescription.ExitPolicy = new ExitPolicy(policy); + } + return routerDescription; + } + public RouterStatusResponse GetRouterStatusInfoByID(String identity) + { + CommandResponse resp = SendCommand(String.Format("GETINFO ns/id/{0}", identity)); + StringReader sr = new StringReader(resp.Response); + String line = sr.ReadLine(); + if (!line.Contains("ns/id")) + throw TorException.Parse(resp.Response); + + line = sr.ReadLine(); + String flagLine = sr.ReadLine(); + RouterStatusResponse routerResponse = RouterStatusResponse.Parse(line, flagLine); + sr.Close(); + return routerResponse; + } + public void SetConfigurationEntry(String key, String value) + { + SendCommand(String.Format("SETCONF {0}={1}",key,value)); + } + /// + /// Starts the process of creating a new circuit + /// NOTE: The circuit is not usable until the BUILT event occurs + /// + /// The new circuit ID + /// + public Int32 CreateCircuit(String circuit) + { + CommandResponse resp = SendCommand(String.Format("EXTENDCIRCUIT 0 {0}", circuit)); + if (resp.StatusCode == 250) + { + string[] parts = resp.Response.Split(' '); + Int32 circuitID; + if(Int32.TryParse(parts[parts.Length - 1],out circuitID)) + return circuitID; + } + return 0; + } + public void AttachStream(Int32 streamId, Int32 circuitId) + { + SendCommand(String.Format("ATTACHSTREAM {0} {1}", streamId, circuitId)); + } + public void CloseCircuit(Int32 circuitID) + { + SendCommand(String.Format("CLOSECIRCUIT {0}", circuitID)); + } + public void CloseStream(Int32 streamID) + { + SendCommand(String.Format("CLOSESTREAM {0} 1", streamID)); + } + + public CommandResponse SendRawCommand(String command) + { + return SendCommand(command); + } + public void RequestEvents(List events) + { + SendRawCommand(String.Format("SETEVENTS {0}", String.Join(" ", events))); + } + private CommandResponse SendCommand(String command) + { + CommandResponse resp; + lock (requestLock) + { + socketWriter.WriteLine(command); + resp = GetLastCommandResponse(); + } + return resp; + } + private CommandResponse GetLastCommandResponse() + { + if (responseEvent.WaitOne(Timeout)) + { + CommandResponse resp = responseQueue.Dequeue(); + if (!resp.StatusCode.ToString().StartsWith("2")) + throw TorException.Parse(resp.Response); + else return resp; + } + else + throw new TimeoutException(); + } + private void receiveLoop(object param) + { + String line; + while (sock.Connected) + { + try + { + line = socketReader.ReadLine(); + } + catch (IOException e) + { + if (e.InnerException is SocketException && + ((SocketException)e.InnerException).SocketErrorCode == SocketError.ConnectionAborted) + break; + else + throw new ApplicationException(); + } + if (line == null) + break; + if (line.Equals("") || line.Length < 5) + continue; + switch (line.Substring(0, 3)) + { + //Command response + case "250": + case "246": + case "249": + CommandResponse resp = new CommandResponse(Int32.Parse(line.Substring(0,3)),line); + + while (!line.Equals("250 OK") && !line.StartsWith("250 ")) + { + line = socketReader.ReadLine(); + resp.AppendResponse(line); + } + responseQueue.Enqueue(resp); + responseEvent.Set(); + break; + //server-generated events + case "650": + EventResponse eventResp = EventResponse.Parse(line); + if(ServerEvent != null) + foreach (Action serverEventResponse in ServerEvent.GetInvocationList()) + { + serverEventResponse.BeginInvoke(eventResp, null, null); + } + break; + default: + if (line.StartsWith("5")) + { + responseQueue.Enqueue(new ErrorResponse(Int32.Parse(line.Substring(0, 3)), line)); + responseEvent.Set(); + } + else + throw new ApplicationException(); + break; + } + } + } + + public IPEndPoint Host { get; set; } + public TimeSpan Timeout { get; set; } + public event Action ServerEvent; + public Boolean Connected + { + get + { + return sock.Connected; + } + } + public String HashedControlPassword + { + get + { + return _hashedControlPassword; + } + set + { + if (!String.IsNullOrEmpty(value) && !value.StartsWith("16:")) + throw new TorException() { ErrorDescription = "You must pass the already hashed version of the tor control port's password. You can obtain this by running the following command: tor.exe --hash-password mypass" }; + _hashedControlPassword = value; + } + } + private String _hashedControlPassword; + private StreamReader socketReader; + private StreamWriter socketWriter; + private NetworkStream socketStream; + private Socket sock; + private Thread recvThread; + private readonly object requestLock = new object(); + private AutoResetEvent responseEvent = new AutoResetEvent(false); + private Queue responseQueue = new Queue(); + } +} diff --git a/TorControlLibrary/Exceptions/TorAuthenticationException.cs b/TorControlLibrary/Exceptions/TorAuthenticationException.cs new file mode 100644 index 0000000..b5e6845 --- /dev/null +++ b/TorControlLibrary/Exceptions/TorAuthenticationException.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TorControlLibrary.Exceptions +{ + public class TorAuthenticationException : TorException + { + public String HashedPassword { get; set; } + public String ServerResponse { get; set; } + } +} diff --git a/TorControlLibrary/Exceptions/TorException.cs b/TorControlLibrary/Exceptions/TorException.cs new file mode 100644 index 0000000..c93e039 --- /dev/null +++ b/TorControlLibrary/Exceptions/TorException.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TorControlLibrary.Exceptions +{ + public class TorException : System.Exception + { + public static TorException Parse(String errorLine) + { + TorException ex = null; + switch (errorLine.Substring(0, 3)) + { + case "552": + if (errorLine.Contains("Unknown stream")) + ex = new TorUnknownStreamException() + { + StreamID = Int32.Parse(errorLine.Substring(errorLine.IndexOf('"') + 1, errorLine.Length - (errorLine.LastIndexOf('"') + 1))) + }; + else if (errorLine.Contains("Unknown circuit")) + ex = new TorUnknownCircuitException() + { + CircuitID = Int32.Parse(errorLine.Substring(errorLine.IndexOf('"') + 1, errorLine.Length - (errorLine.LastIndexOf('"') + 1))) + }; + else + ex = new TorException(); + break; + case "551": + if (errorLine.Contains("one-hop circuit")) + { + ex = new TorOneCircuitException(); + } + break; + case "515": + ex = new TorAuthenticationException(); + break; + default: + throw new ApplicationException("Unhandled exception from TOR control port"); + } + ex.ErrorDescription = errorLine.Substring(4); + ex.StatusCode = Int32.Parse(errorLine.Substring(0, 3)); + return ex; + } + public Int32 StatusCode { get; set; } + public String ErrorDescription { get; set; } + } +} diff --git a/TorControlLibrary/Exceptions/TorOneCircuitException.cs b/TorControlLibrary/Exceptions/TorOneCircuitException.cs new file mode 100644 index 0000000..0e887e0 --- /dev/null +++ b/TorControlLibrary/Exceptions/TorOneCircuitException.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TorControlLibrary.Exceptions +{ + public class TorOneCircuitException : TorException + { + } +} diff --git a/TorControlLibrary/Exceptions/TorUnknownCircuitException.cs b/TorControlLibrary/Exceptions/TorUnknownCircuitException.cs new file mode 100644 index 0000000..c089409 --- /dev/null +++ b/TorControlLibrary/Exceptions/TorUnknownCircuitException.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TorControlLibrary.Exceptions +{ + public class TorUnknownCircuitException : TorException + { + public Int32 CircuitID { get; set; } + } +} diff --git a/TorControlLibrary/Exceptions/TorUnknownStreamException.cs b/TorControlLibrary/Exceptions/TorUnknownStreamException.cs new file mode 100644 index 0000000..03c44fb --- /dev/null +++ b/TorControlLibrary/Exceptions/TorUnknownStreamException.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TorControlLibrary.Exceptions +{ + public class TorUnknownStreamException : TorException + { + public Int32 StreamID { get; set; } + } +} diff --git a/TorControlLibrary/ExitPolicy.cs b/TorControlLibrary/ExitPolicy.cs new file mode 100644 index 0000000..b4d103f --- /dev/null +++ b/TorControlLibrary/ExitPolicy.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using System.Net; + +namespace TorControlLibrary +{ + public class ExitPolicy + { + public ExitPolicy() { } + public ExitPolicy(String policy) + { + ParsePolicy(policy); + } + public void ParsePolicy(String policy) + { + int count = 0; + StringReader sr = new StringReader(policy); + while (true) + { + String line = sr.ReadLine(); + if (String.IsNullOrEmpty(line)) + break; + Policy.Add(count++, new PolicyLine(line)); + } + } + public Boolean IsAcceptableDestination(IPEndPoint destination) + { + return checkAddress((UInt32)System.Net.IPAddress.HostToNetworkOrder(BitConverter.ToInt32(destination.Address.GetAddressBytes(), 0)), (UInt16)destination.Port); + } + + public Boolean IsAcceptableDestination(IPAddress address, UInt16 port) + { + return checkAddress((UInt32)System.Net.IPAddress.HostToNetworkOrder(BitConverter.ToInt32(address.GetAddressBytes(), 0)), port); + } + private Boolean checkAddress(UInt32 address, UInt16 port) + { + //the first match dictates the appropriate action + for (int i = 0; i < Policy.Count; i++) + { + if (Policy[i].AddressRangeStart <= address && + Policy[i].AddressRangeEnd >= address) + if (Policy[i].PortRangeStart <= port && + Policy[i].PortRangeEnd >= port) + return Policy[i].Action.Equals("accept"); + } + return false; + } + private SortedList Policy = new SortedList(); + + class PolicyLine + { + public PolicyLine(String line) + { + Int32 ptr = line.IndexOf(' '); + Action = line.Substring(0, ptr); + ptr++; + //Address + String address = line.Substring(ptr, line.IndexOf(':') - ptr); + if (address.Equals("*")) + { + AddressRangeStart = UInt32.MinValue; + AddressRangeEnd = UInt32.MaxValue; + } + else + { + if (address.Contains("/")) + { //range + String[] rangeParts = address.Split('/'); + System.Net.IPAddress ipAddress = System.Net.IPAddress.Parse(rangeParts[0]); + + UInt32 mask = ((UInt32)0xFFFFFFFF << (32 - Int32.Parse(rangeParts[1]))); + AddressRangeStart = BitConverter.ToUInt32(ipAddress.GetAddressBytes(), 0); + //Cast to int to avoid long overload + AddressRangeStart = (UInt32)System.Net.IPAddress.HostToNetworkOrder((int)AddressRangeStart); + + AddressRangeEnd = AddressRangeStart | ~(UInt32)mask; + } + else + { //Single IP + System.Net.IPAddress ipAddress = System.Net.IPAddress.Parse(address); + AddressRangeStart = BitConverter.ToUInt32(ipAddress.GetAddressBytes(),0); + AddressRangeEnd = BitConverter.ToUInt32(ipAddress.GetAddressBytes(), 0); + } + } + //Port + ptr = line.IndexOf(':') + 1; + String port = line.Substring(ptr); + if (port.Equals("*")) + { + PortRangeStart = UInt16.MinValue; + PortRangeEnd = UInt16.MaxValue; + } + else + { + if (port.Contains("-")) + { //range + String[] portParts = port.Split('-'); + PortRangeStart = UInt16.Parse(portParts[0]); + PortRangeEnd = UInt16.Parse(portParts[1]); + } + else + { + PortRangeStart = UInt16.Parse(port); + PortRangeEnd = UInt16.Parse(port); + } + } + } + public String Action { get; set; } + public UInt32 AddressRangeStart { get; set; } + public UInt32 AddressRangeEnd { get; set; } + public UInt16 PortRangeStart { get; set; } + public UInt16 PortRangeEnd { get; set; } + } + } +} diff --git a/TorControlLibrary/Properties/AssemblyInfo.cs b/TorControlLibrary/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..401fa75 --- /dev/null +++ b/TorControlLibrary/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TorControlLibrary")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("TorControlLibrary")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2011")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f259a6ba-c1f0-4849-ada7-324eddd89739")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TorControlLibrary/Responses/CircuitStatusResponse.cs b/TorControlLibrary/Responses/CircuitStatusResponse.cs new file mode 100644 index 0000000..e76ccb4 --- /dev/null +++ b/TorControlLibrary/Responses/CircuitStatusResponse.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TorControlLibrary.Responses +{ + public class CircuitStatusResponse : CommandResponse + { + public CircuitStatusResponse() + { + Nodes = new SortedList(); + } + public static CircuitStatusResponse Parse(String line) + { + CircuitStatusResponse resp = new CircuitStatusResponse(); + resp.PopulateData(line, resp); + return resp; + } + protected SortedList ParseNodes(String data) + { + SortedList nodes = new SortedList(); + String[] nodeList = data.Split(','); + for (int i = 0; i < nodeList.Length; i++) + nodes.Add(i, nodeList[i]); + return nodes; + } + [ParseAttribute(1)] + public Int32 CircuitID { get; set; } + [ParseAttribute(2)] + public String Status { get; set; } + [ParseAttribute(3, CustomParser="ParseNodes")] + public SortedList Nodes { get; set; } + [ParseAttribute(4)] + public String Purpose { get; set; } + } +} diff --git a/TorControlLibrary/Responses/CommandResponse.cs b/TorControlLibrary/Responses/CommandResponse.cs new file mode 100644 index 0000000..5b97a8d --- /dev/null +++ b/TorControlLibrary/Responses/CommandResponse.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; + +namespace TorControlLibrary.Responses +{ + public class CommandResponse + { + public CommandResponse() + { + } + public CommandResponse(Int32 status, String response) + { + StatusCode = status; + Response = response.EndsWith(Environment.NewLine) ? response : response + Environment.NewLine; + } + public virtual void AppendResponse(String line) + { + Response += line + Environment.NewLine; + } + + protected virtual void PopulateData(String dataLine, CommandResponse response) + { + String[] parts = dataLine.Split(' '); + List props = response.GetType().GetProperties().Where(p => p.GetCustomAttributes(typeof(ParseAttribute), true).Any()) + .OrderBy(p => ((ParseAttribute)p.GetCustomAttributes(typeof(ParseAttribute), true).FirstOrDefault()).Order).ToList(); + + foreach (PropertyInfo prop in props) + { + ParseAttribute attribute = (ParseAttribute)prop.GetCustomAttributes(true).FirstOrDefault(); + String value = String.Empty; + for (int i = 0; i < (attribute.DataElementsToRead == 0 ? 1 : attribute.DataElementsToRead ); i++) + value += parts[(i + attribute.Order) - 1] + " "; + value = value.Trim(); + + MethodInfo parseMethod; + if (!String.IsNullOrWhiteSpace(attribute.CustomParser)) + { + parseMethod = response.GetType().GetMethod(attribute.CustomParser,BindingFlags.NonPublic | BindingFlags.Instance); + if (parseMethod != null) + prop.SetValue(response, parseMethod.Invoke(response, new object[] { value }), null); + } + else if (prop.PropertyType == typeof(string)) + prop.SetValue(response, value, null); + else + { + parseMethod = prop.PropertyType.GetMethod("Parse", new Type[] { typeof(String) }); + prop.SetValue(response, parseMethod.Invoke(null, new object[] { value }), null); + } + } + } + public Int32 StatusCode { get; set; } + public String Response { get; set; } + } +} diff --git a/TorControlLibrary/Responses/ErrorResponse.cs b/TorControlLibrary/Responses/ErrorResponse.cs new file mode 100644 index 0000000..a8168e7 --- /dev/null +++ b/TorControlLibrary/Responses/ErrorResponse.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TorControlLibrary.Responses +{ + public class ErrorResponse : CommandResponse + { + public ErrorResponse(Int32 errorCode, String line) + : base(errorCode, line) + { } + } +} diff --git a/TorControlLibrary/Responses/EventResponse.cs b/TorControlLibrary/Responses/EventResponse.cs new file mode 100644 index 0000000..076e5a3 --- /dev/null +++ b/TorControlLibrary/Responses/EventResponse.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TorControlLibrary.Responses +{ + public class EventResponse + { + public static EventResponse Parse(String line) + { + EventResponse resp = new EventResponse(); + string[] parts = line.Split(' '); + Int32 id; + resp.EventType = parts[1]; + if (Int32.TryParse(parts[2], out id)) + resp.ID = id; + resp.Action = parts[3]; + for (int i = 2; i < parts.Length; i++) + resp.EventInformation += parts[i] + ' '; + resp.EventInformation = resp.EventInformation.Trim(); + return resp; + } + public String EventType { get; set; } + public String Action { get; set; } + public Int32 ID { get; set; } + public String EventInformation { get; set; } + } +} diff --git a/TorControlLibrary/Responses/EventTypes.cs b/TorControlLibrary/Responses/EventTypes.cs new file mode 100644 index 0000000..2d74fe3 --- /dev/null +++ b/TorControlLibrary/Responses/EventTypes.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TorControlLibrary.Responses +{ + public enum EventTypes + { + + } +} diff --git a/TorControlLibrary/Responses/ParseAttribute.cs b/TorControlLibrary/Responses/ParseAttribute.cs new file mode 100644 index 0000000..da07597 --- /dev/null +++ b/TorControlLibrary/Responses/ParseAttribute.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TorControlLibrary.Responses +{ + public class ParseAttribute : System.Attribute + { + public ParseAttribute(Int32 order) + { + Order = order; + } + public Int32 DataElementsToRead { get; set; } + public String CustomParser { get; set; } + public readonly Int32 Order; + } +} diff --git a/TorControlLibrary/Responses/RouterDescription.cs b/TorControlLibrary/Responses/RouterDescription.cs new file mode 100644 index 0000000..eda8dfb --- /dev/null +++ b/TorControlLibrary/Responses/RouterDescription.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TorControlLibrary.Responses +{ + public class RouterDescription : CommandResponse + { + public RouterDescription(String nickname) + { + this.NickName = nickname; + } + + public Int32 BandwidthObserved { get; set; } + public Int32 BandwidthAverage { get; set; } + public Int32 BandwidthBurst { get; set; } + public TimeSpan Uptime { get; set; } + public String NickName { get; set; } + public ExitPolicy ExitPolicy { get; set; } + } +} diff --git a/TorControlLibrary/Responses/RouterStatusResponse.cs b/TorControlLibrary/Responses/RouterStatusResponse.cs new file mode 100644 index 0000000..4d09bf7 --- /dev/null +++ b/TorControlLibrary/Responses/RouterStatusResponse.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TorControlLibrary.Responses +{ + public class RouterStatusResponse : CommandResponse + { + public static RouterStatusResponse Parse(String routerLine, String flagLine) + { + RouterStatusResponse resp = new RouterStatusResponse(); + + resp.PopulateData(routerLine, resp); + resp.Flags = flagLine.Substring(2).Split(' '); + return resp; + } + protected String ParseEncodedHashes(String value) + { + if (value.Length % 2 != 0) + value += "="; + Byte[] digest = Convert.FromBase64String(value); + String hash = String.Empty; + for (int i = 0; i < digest.Length; i++) + hash += String.Format("{0:X2}", digest[i]); + return hash; + } + //Starts at 2, line is r + [ParseAttribute(2)] + public String NickName { get; set; } + [ParseAttribute(3, CustomParser="ParseEncodedHashes")] + public String Identity { get; set; } + [ParseAttribute(4, CustomParser = "ParseEncodedHashes")] + public String Digest { get; set; } + [ParseAttribute(5,DataElementsToRead=2)] + public DateTime Publication { get; set; } + [ParseAttribute(7)] + public System.Net.IPAddress Address { get; set; } + [ParseAttribute(8)] + public Int32 ORPort { get; set; } + [ParseAttribute(9)] + public Int32 DirPort { get; set; } + public String[] Flags { get; set; } + } +} diff --git a/TorControlLibrary/Responses/StreamStatusResponse.cs b/TorControlLibrary/Responses/StreamStatusResponse.cs new file mode 100644 index 0000000..90f3365 --- /dev/null +++ b/TorControlLibrary/Responses/StreamStatusResponse.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net; +namespace TorControlLibrary.Responses +{ + public class StreamStatusResponse : CommandResponse + { + public static StreamStatusResponse Parse(String line) + { + StreamStatusResponse resp = new StreamStatusResponse(); + resp.PopulateData(line, resp); + return resp; + } + [Obsolete()] + protected IPEndPoint ParseIPEndPoint(String data) + { + return new IPEndPoint(IPAddress.Parse(data.Substring(0, data.IndexOf(':'))), + Int32.Parse(data.Substring(data.IndexOf(':') + 1))); + } + [ParseAttribute(1)] + public Int32 StreamID { get; set; } + [ParseAttribute(2)] + public String StreamStatus { get; set; } + [ParseAttribute(3)] + public Int32 CircuitID { get; set; } + [ParseAttribute(4)] + public String Target { get; set; } + //[ParseAttribute(4, CustomParser="ParseIPEndPoint")] + //public IPEndPoint Target { get; set; } + } +} diff --git a/TorControlLibrary/TorControlLibrary.csproj b/TorControlLibrary/TorControlLibrary.csproj new file mode 100644 index 0000000..1c532bb --- /dev/null +++ b/TorControlLibrary/TorControlLibrary.csproj @@ -0,0 +1,73 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {EF216A86-F64F-4C76-9A86-EEE8E5484EF9} + Library + Properties + TorControlLibrary + TorControlLibrary + v4.0 + 512 + Svn + Svn + Svn + SubversionScc + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TorLibraryTest/App.config b/TorLibraryTest/App.config new file mode 100644 index 0000000..ee0dedc --- /dev/null +++ b/TorLibraryTest/App.config @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TorLibraryTest/ControlConnectionUnitTests.cs b/TorLibraryTest/ControlConnectionUnitTests.cs new file mode 100644 index 0000000..1dc1797 --- /dev/null +++ b/TorLibraryTest/ControlConnectionUnitTests.cs @@ -0,0 +1,111 @@ +using System; +using System.Text; +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TorControlLibrary; +using TorControlLibrary.Responses; +using System.Threading; +using Hermes.Objects; + +namespace TorLibraryTest +{ + [TestClass] + public class ControlConnectionUnitTests + { + [TestMethod] + public void Connect() + { + ControlConnection connection = new ControlConnection(); + connection.Connect(); + connection.Close(); + } + [TestMethod] + public void GetCircuitStatuses() + { + ControlConnection connection = new ControlConnection(); + connection.Connect(); + List circuits = connection.GetCircuitStatuses(); + connection.Close(); + } + [TestMethod] + public void GetStreamStatuses() + { + ControlConnection connection = new ControlConnection(); + connection.Connect(); + List circuits = connection.GetStreamStatuses(); + connection.Close(); + } + [TestMethod] + public void GetRouterStatuses() + { + ControlConnection connection = new ControlConnection(); + connection.Connect(); + List routerInfo = connection.GetAllRouterStatusInfo(); + connection.Close(); + } + [TestMethod] + public void GetRouterStatusByName() + { + ControlConnection connection = new ControlConnection(); + connection.Connect(); + RouterStatusResponse routerInfo = connection.GetRouterStatusInfoByNickname("moria1"); + connection.Close(); + Assert.AreEqual("moria1", routerInfo.NickName); + } + [TestMethod] + public void GetRouterDescriptionByName() + { + ControlConnection connection = new ControlConnection(); + connection.Connect(); + RouterDescription routerInfo = connection.GetRouterDescriptionByNickname("desync"); + connection.Close(); + Assert.AreEqual("desync", routerInfo.NickName); + } + [TestMethod] + public void GetRouterStatusByID() + { + ControlConnection connection = new ControlConnection(); + connection.Connect(); + RouterStatusResponse routerInfo = connection.GetRouterStatusInfoByID("03C0FB35B3A0D3D12410475C5FDB8F525B7342CD"); + connection.Close(); + Assert.AreEqual("zagon", routerInfo.NickName); + } + [TestMethod] + public void SendRawCommand() + { + ControlConnection connection = new ControlConnection(); + connection.Connect(); + CommandResponse resp = connection.SendRawCommand("getinfo version"); + Assert.AreEqual(250, resp.StatusCode); + Assert.IsTrue(resp.Response.Contains("version")); + } + [TestMethod] + public void SimpleEventTest() + { + ControlConnection connection = new ControlConnection(); + connection.Connect(); + connection.ServerEvent += new Action(connection_ServerEvent); + connection.SendRawCommand("SETEVENTS BW"); + Assert.IsTrue(eventTest.WaitOne(60000)); + connection.Close(); + } + [TestMethod] + public void SocksTest() + { + Socks5Stream stream = new Socks5Stream("localhost", 9050, "www.google.com", 80); + System.IO.StreamWriter writer = new System.IO.StreamWriter(stream); + System.IO.StreamReader reader = new System.IO.StreamReader(stream); + writer.Write("GET / HTTP 1.1\r\nHost: www.google.com\r\n\r\n"); + writer.Flush(); + String results = reader.ReadLine(); + } + + void connection_ServerEvent(EventResponse obj) + { + if (obj.EventType.Equals("BW")) + eventTest.Set(); + } + private AutoResetEvent eventTest = new AutoResetEvent(false); + } +} diff --git a/TorLibraryTest/Properties/AssemblyInfo.cs b/TorLibraryTest/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..ae481f1 --- /dev/null +++ b/TorLibraryTest/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TorLibraryTest")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("TorLibraryTest")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2011")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("e3648bd4-b12a-46ee-8dda-e53d4ed94cd5")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TorLibraryTest/TorLibraryTest.csproj b/TorLibraryTest/TorLibraryTest.csproj new file mode 100644 index 0000000..dee905e --- /dev/null +++ b/TorLibraryTest/TorLibraryTest.csproj @@ -0,0 +1,72 @@ + + + + Debug + AnyCPU + + + 2.0 + {020A074C-B1F5-498A-8C1D-B1D90C79D50D} + Library + Properties + TorLibraryTest + TorLibraryTest + v4.0 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + 3.5 + + + + + False + + + + + + + + + {3DE6415A-1DED-4F75-9202-39BDF012BB5A} + Hermes + + + {EF216A86-F64F-4C76-9A86-EEE8E5484EF9} + TorControlLibrary + + + + + + + + \ No newline at end of file diff --git a/TraceAndTestImpact.testsettings b/TraceAndTestImpact.testsettings new file mode 100644 index 0000000..a80f425 --- /dev/null +++ b/TraceAndTestImpact.testsettings @@ -0,0 +1,21 @@ + + + These are test settings for Trace and Test Impact. + + + + + + + + + + + + + + + + + + \ No newline at end of file