This commit is contained in:
2024-02-23 06:57:07 -05:00
commit 8fb7082f56
104 changed files with 284139 additions and 0 deletions

View File

@@ -0,0 +1,329 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.IO;
namespace Tor.Proxy
{
/// <summary>
/// A class containing a reference to a connection proxy client.
/// </summary>
internal sealed class Connection : IDisposable
{
private readonly Client client;
private volatile bool disposed;
private ConnectionDisposedCallback disposedCallback;
private Dictionary<string, string> headers;
private string host;
private string http;
private string method;
private int port;
private byte[] post;
private Socket socket;
/// <summary>
/// Initializes a new instance of the <see cref="Connection"/> class.
/// </summary>
/// <param name="client">The client hosting the proxy .</param>
/// <param name="socket">The socket belonging to the connection.</param>
/// <param name="disposeCallback">A callback method raised when the connection is disposed.</param>
public Connection(Client client, Socket socket, ConnectionDisposedCallback disposeCallback)
{
this.client = client;
this.disposed = false;
this.disposedCallback = disposeCallback;
this.headers = null;
this.host = null;
this.http = "HTTP/1.1";
this.method = null;
this.port = 80;
this.post = null;
this.socket = socket;
this.GetHeaderData();
}
#region Properties
/// <summary>
/// Gets the header values provided with the request.
/// </summary>
public Dictionary<string, string> Headers
{
get { return headers; }
}
/// <summary>
/// Gets the target host of the HTTP request.
/// </summary>
public string Host
{
get { return host; }
}
/// <summary>
/// Gets the HTTP version sent with the request.
/// </summary>
public string HTTP
{
get { return http; }
}
/// <summary>
/// Gets the method requested for the HTTP request (GET, POST, PUT, DELETE, CONNECT).
/// </summary>
public string Method
{
get { return method; }
}
/// <summary>
/// Gets the target port number of the HTTP request.
/// </summary>
public int Port
{
get { return port; }
}
/// <summary>
/// Gets the POST data.
/// </summary>
public byte[] Post
{
get { return post; }
}
/// <summary>
/// Gets the socket connected to the proxy client.
/// </summary>
public Socket Socket
{
get { return socket; }
}
#endregion
#region System.IDisposable
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
private void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing)
{
if (socket != null)
{
try
{
socket.Shutdown(SocketShutdown.Both);
}
catch { }
socket.Dispose();
socket = null;
}
disposed = true;
if (disposedCallback != null)
disposedCallback(this);
}
}
#endregion
/// <summary>
/// Gets the header block which was dispatched with the original socket request.
/// </summary>
/// <returns>A <see cref="System.String"/> containing the header data.</returns>
public string GetHeader()
{
StringBuilder header = new StringBuilder();
header.Append(method);
header.Append("\r\n");
foreach (KeyValuePair<string, string> value in headers)
{
if (value.Key.StartsWith("proxy", StringComparison.CurrentCultureIgnoreCase))
continue;
header.AppendFormat("{0}: {1}\r\n", value.Key, value.Value);
}
return header.Append("\r\n").ToString();
}
/// <summary>
/// Process the connection request by reading the header information from the HTTP request, and connecting to the tor server
/// and dispatching the request to the relevant host.
/// </summary>
private void GetHeaderData()
{
try
{
StringBuilder builder = new StringBuilder();
using (StreamReader reader = new StreamReader(new NetworkStream(socket, false)))
{
for (string line = reader.ReadLine(); line != null; line = reader.ReadLine())
{
builder.Append(line);
builder.Append("\r\n");
if (line.Trim().Length == 0)
break;
}
}
using (StringReader reader = new StringReader(builder.ToString()))
{
method = reader.ReadLine();
if (method == null)
throw new InvalidOperationException("The proxy connection did not supply a valid HTTP header");
headers = new Dictionary<string, string>();
for (string line = reader.ReadLine(); line != null; line = reader.ReadLine())
{
string trimmed = line.Trim();
if (trimmed.Length == 0)
break;
string[] parts = trimmed.Split(new[] { ':' }, 2);
if (parts.Length == 1)
continue;
headers[parts[0].Trim()] = parts[1].Trim();
}
if (method.StartsWith("POST", StringComparison.CurrentCultureIgnoreCase))
{
if (!headers.ContainsKey("Content-Length"))
throw new InvalidOperationException("The proxy connection is a POST method but contains no content length");
long contentLength = long.Parse(headers["Content-Length"]);
using (MemoryStream memory = new MemoryStream())
{
long read = 0;
byte[] buffer = new byte[512];
while (contentLength > read)
{
int received = socket.Receive(buffer, 0, buffer.Length, SocketFlags.None);
if (received <= 0)
throw new InvalidOperationException("The proxy connection was terminated while reading POST data");
memory.Write(buffer, 0, received);
read += received;
}
post = memory.ToArray();
}
}
if (method.StartsWith("CONNECT", StringComparison.CurrentCultureIgnoreCase))
{
string[] connectTargets = method.Split(' ');
if (connectTargets.Length < 3)
throw new InvalidOperationException("The proxy connection supplied a CONNECT command with insufficient parameters");
http = connectTargets[2];
string connectTarget = connectTargets[1];
string[] connectParams = connectTarget.Split(':');
if (connectParams.Length == 2)
{
host = connectParams[0];
port = int.Parse(connectParams[1]);
}
else
{
host = connectParams[0];
port = 443;
}
}
else
{
if (!headers.ContainsKey("Host"))
throw new InvalidOperationException("The proxy connection did not supply a connection host");
string connectTarget = headers["Host"];
string[] connectParams = connectTarget.Split(':');
if (connectParams.Length == 1)
host = connectParams[0];
else
{
host = connectParams[0];
port = int.Parse(connectParams[1]);
}
}
}
}
catch (Exception exception)
{
throw new TorException("The proxy connection failed to process", exception);
}
}
/// <summary>
/// Writes a buffer of data to the connected client socket.
/// </summary>
/// <param name="data">The data to send to the client socket.</param>
public void Write(string data)
{
Write(Encoding.ASCII.GetBytes(data));
}
/// <summary>
/// Writes a buffer of data to the connected client socket.
/// </summary>
/// <param name="data">The data to send to the client socket.</param>
/// <param name="parameters">An optional list of parameters to format into the data.</param>
public void Write(string data, params object[] parameters)
{
data = string.Format(data, parameters);
Write(Encoding.ASCII.GetBytes(data));
}
/// <summary>
/// Writes a buffer of data to the connected client socket.
/// </summary>
/// <param name="buffer">The data to send to the client socket.</param>
public void Write(byte[] buffer)
{
socket.Send(buffer, 0, buffer.Length, SocketFlags.None);
}
}
/// <summary>
/// A delegate event handler representing a method raised when a connection is disposed.
/// </summary>
/// <param name="connection">The connection which was disposed.</param>
internal delegate void ConnectionDisposedCallback(Connection connection);
}

View File

@@ -0,0 +1,284 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using Tor.Net;
namespace Tor.Proxy
{
/// <summary>
/// A class containing the methods and logic needed for routing a HTTP request through a specified SOCKS5 proxy.
/// </summary>
internal sealed class ConnectionProcessor : IDisposable
{
private readonly Client client;
private readonly Connection connection;
private readonly object synchronize;
private byte[] connectionBuffer;
private byte[] destinationBuffer;
private volatile bool disposed;
private ConnectionProcessorDisposedCallback disposedCallback;
private ForwardSocket destinationSocket;
/// <summary>
/// Initializes a new instance of the <see cref="ConnectionProcessor"/> class.
/// </summary>
/// <param name="client">The client hosting the proxy connection.</param>
/// <param name="connection">The connection associated with the connected client.</param>
public ConnectionProcessor(Client client, Connection connection, ConnectionProcessorDisposedCallback disposedCallback)
{
this.client = client;
this.connection = connection;
this.connectionBuffer = new byte[2048];
this.destinationBuffer = new byte[2048];
this.destinationSocket = null;
this.disposed = false;
this.disposedCallback = disposedCallback;
this.synchronize = new object();
}
#region System.IDisposable
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
private void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing)
{
Shutdown();
disposed = true;
if (disposedCallback != null)
disposedCallback(this);
}
}
#endregion
#region System.Net.Sockets.Socket
/// <summary>
/// Called when the connected client socket has dispatched data.
/// </summary>
/// <param name="ar">The asynchronous result object for the asynchronous method.</param>
private void OnConnectionSocketReceive(IAsyncResult ar)
{
try
{
if (connection != null && connection.Socket != null)
{
int received = connection.Socket.EndReceive(ar);
if (received > 0)
{
if (destinationSocket != null)
destinationSocket.BeginSend(connectionBuffer, 0, received, SocketFlags.None, OnDestinationSocketSent, destinationSocket);
return;
}
}
}
catch { }
Dispose();
}
/// <summary>
/// Called when the connection client socket has completed sending data.
/// </summary>
/// <param name="ar">The asynchronous result object for the asynchronous method.</param>
private void OnConnectionSocketSent(IAsyncResult ar)
{
try
{
if (connection != null && connection.Socket != null)
{
int dispatched = connection.Socket.EndSend(ar);
if (dispatched > 0)
{
if (destinationSocket != null)
destinationSocket.BeginReceive(destinationBuffer, 0, destinationBuffer.Length, SocketFlags.None, OnDestinationSocketReceive, destinationSocket);
return;
}
}
}
catch { }
Dispose();
}
/// <summary>
/// Called when the forwarding socket has connected to the proxy connection.
/// </summary>
/// <param name="ar">The asynchronous result object for the asynchronous method.</param>
private void OnSocketConnected(IAsyncResult result)
{
try
{
destinationSocket.EndConnect(result);
if (connection.Method.StartsWith("CONNECT", StringComparison.CurrentCultureIgnoreCase))
connection.Write("{0} 200 Connection established\r\nProxy-Agent: Tor Socks5 Proxy\r\n\r\n", connection.HTTP);
else
{
string header = connection.GetHeader();
destinationSocket.Send(Encoding.ASCII.GetBytes(header));
if (connection.Post != null)
destinationSocket.Send(connection.Post);
}
ExchangeBuffers();
}
catch (Exception exception)
{
throw new TorException("The connection processor failed to finalize instructions", exception);
}
}
/// <summary>
/// Called when the destination socket has dispatched data.
/// </summary>
/// <param name="ar">The asynchronous result object for the asynchronous method.</param>
private void OnDestinationSocketReceive(IAsyncResult result)
{
try
{
if (destinationSocket != null)
{
int received = destinationSocket.EndReceive(result);
if (received > 0)
{
if (connection != null && connection.Socket != null)
connection.Socket.BeginSend(destinationBuffer, 0, received, SocketFlags.None, OnConnectionSocketSent, connection.Socket);
return;
}
}
}
catch { }
Dispose();
}
/// <summary>
/// Called when the destination socket has completed sending data.
/// </summary>
/// <param name="ar">The asynchronous result object for the asynchronous method.</param>
private void OnDestinationSocketSent(IAsyncResult ar)
{
try
{
if (destinationSocket != null)
{
int dispatched = destinationSocket.EndSend(ar);
if (dispatched > 0)
{
if (connection != null && connection.Socket != null)
connection.Socket.BeginReceive(connectionBuffer, 0, connectionBuffer.Length, SocketFlags.None, OnConnectionSocketReceive, connection.Socket);
return;
}
}
}
catch { }
Dispose();
}
#endregion
/// <summary>
/// Starts the process of routing the request by parsing the request information of the client and
/// creating a destination socket as required.
/// </summary>
public void Start()
{
if (disposed)
throw new ObjectDisposedException("this");
lock (synchronize)
{
if (destinationSocket != null)
return;
destinationSocket = new ForwardSocket(client);
destinationSocket.ProxyAddress = client.GetClientAddress();
destinationSocket.ProxyPort = client.Configuration.SocksPort;
string proxyConnection;
if (connection.Headers.TryGetValue("Proxy-Connection", out proxyConnection) && proxyConnection.Equals("keep-alive", StringComparison.CurrentCultureIgnoreCase))
destinationSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 1);
destinationSocket.BeginConnect(connection.Host, connection.Port, OnSocketConnected, null);
}
}
/// <summary>
/// Starts the process of routing buffers between the connected client and destination socket.
/// </summary>
private void ExchangeBuffers()
{
try
{
connection.Socket.BeginReceive(connectionBuffer, 0, connectionBuffer.Length, SocketFlags.None, OnConnectionSocketReceive, connection.Socket);
destinationSocket.BeginReceive(destinationBuffer, 0, destinationBuffer.Length, SocketFlags.None, OnDestinationSocketReceive, destinationSocket);
}
catch (Exception exception)
{
throw new TorException("The connection processor could not begin exchanging data between the connection client and destination sockets", exception);
}
}
/// <summary>
/// Shuts down the connection processor by terminating the proxy connection.
/// </summary>
public void Shutdown()
{
lock (synchronize)
{
if (destinationSocket != null)
{
try
{
destinationSocket.Shutdown(SocketShutdown.Both);
}
catch { }
destinationSocket.Dispose();
destinationSocket = null;
}
}
}
}
/// <summary>
/// A delegate event handler representing a method raised when a connection processor is disposed.
/// </summary>
/// <param name="processor">The connection processor which was disposed.</param>
internal delegate void ConnectionProcessorDisposedCallback(ConnectionProcessor processor);
}

View File

@@ -0,0 +1,351 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;
namespace Tor.Proxy
{
/// <summary>
/// A class responsible for mediating the authentication and dispatch process of a SOCKS5 protocol connection.
/// </summary>
internal sealed class Socks5Processor
{
private readonly Socks5AsyncResult asyncResult;
private readonly ForwardSocket socket;
private byte[] buffer;
private ProcessorCallback callback;
private string endAddress;
private IPEndPoint endPoint;
private int endPort;
private int finalLength;
/// <summary>
/// Initializes a new instance of the <see cref="Socks5Processor"/> class.
/// </summary>
/// <param name="socket">The socket which is connected to the proxy network.</param>
public Socks5Processor(ForwardSocket socket)
{
this.asyncResult = new Socks5AsyncResult();
this.buffer = new byte[512];
this.endAddress = null;
this.endPoint = null;
this.endPort = 0;
this.finalLength = 0;
this.socket = socket;
}
#region System.Net.Sockets.Socket
/// <summary>
/// Called when the forwarded socket has connected to the destination IP address and port.
/// </summary>
/// <param name="ar">The asynchronous result object for the asynchronous method.</param>
private void OnSocketConnect(IAsyncResult ar)
{
try
{
buffer[0] = 5;
buffer[1] = 1;
buffer[2] = 0;
socket.EndConnect(ar);
socket.BeginSend(buffer, 0, 3, SocketFlags.None, OnSocketSendHandshake, socket);
}
catch
{
if (callback != null)
callback(false);
}
}
/// <summary>
/// Called when the socket receives the response to a connect request.
/// </summary>
/// <param name="ar">The asynchronous result object for the asynchronous method.</param>
private void OnSocketReceiveConnect(IAsyncResult ar)
{
try
{
int received = socket.EndReceive(ar);
if (received != 5 || buffer[0] != 5 || buffer[1] != 0)
throw new Exception();
int length = 0;
switch (buffer[3])
{
case 1:
length = 5;
break;
case 3:
length = buffer[4] + 2;
break;
case 4:
length = 17;
break;
default:
throw new Exception();
}
finalLength = length;
socket.BeginReceive(buffer, 1, length, SocketFlags.None, OnSocketReceiveConnectFinal, socket);
}
catch
{
if (callback != null)
callback(false);
}
}
/// <summary>
/// Called when the socket receives the final response to a connect request.
/// </summary>
/// <param name="ar">The asynchronous result object for the asynchronous method.</param>
private void OnSocketReceiveConnectFinal(IAsyncResult ar)
{
try
{
int received = socket.EndReceive(ar);
if (finalLength < received)
throw new Exception();
if (finalLength > received)
{
finalLength -= received;
socket.BeginReceive(buffer, 0, finalLength, SocketFlags.None, OnSocketReceiveConnectFinal, socket);
return;
}
if (callback != null)
callback(true);
}
catch
{
if (callback != null)
callback(false);
}
}
/// <summary>
/// Called when the socket receives the response to a handshake retrieve request.
/// </summary>
/// <param name="ar">The asynchronous result object for the asynchronous method.</param>
private void OnSocketReceiveHandshake(IAsyncResult ar)
{
try
{
int received = socket.EndReceive(ar);
if (received == 0 || buffer[0] != 5 || buffer[1] == 255 || buffer[1] != 0)
throw new Exception();
int length = 0;
if (endPoint != null)
{
buffer[0] = 5;
buffer[1] = 1;
buffer[2] = 0;
buffer[3] = 1;
byte[] address = endPoint.Address.GetAddressBytes();
byte[] port = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)endPoint.Port));
Array.Copy(address, 0, buffer, 4, 4);
Array.Copy(port, 0, buffer, 8, 2);
length = 10;
}
else
{
buffer[0] = 5;
buffer[1] = 1;
buffer[2] = 0;
buffer[3] = 3;
buffer[4] = (byte)endAddress.Length;
byte[] address = Encoding.ASCII.GetBytes(endAddress);
byte[] port = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)endPort));
Array.Copy(address, 0, buffer, 5, address.Length);
Array.Copy(port, 0, buffer, address.Length + 5, 2);
length = address.Length + 7;
}
socket.BeginSend(buffer, 0, length, SocketFlags.None, OnSocketSendConnect, socket);
}
catch
{
if (callback != null)
callback(false);
}
}
/// <summary>
/// Called when the socket completes sending a request to connect to an IP end point.
/// </summary>
/// <param name="ar">The asynchronous result object for the asynchronous method.</param>
private void OnSocketSendConnect(IAsyncResult ar)
{
try
{
socket.EndSend(ar);
socket.BeginReceive(buffer, 0, 5, SocketFlags.None, OnSocketReceiveConnect, socket);
}
catch
{
if (callback != null)
callback(false);
}
}
/// <summary>
/// Called when the socket has completed sending the handshake request.
/// </summary>
/// <param name="ar">The asynchronous result object for the asynchronous method.</param>
private void OnSocketSendHandshake(IAsyncResult ar)
{
try
{
socket.EndSend(ar);
socket.BeginReceive(buffer, 0, 2, SocketFlags.None, OnSocketReceiveHandshake, socket);
}
catch
{
if (callback != null)
callback(false);
}
}
#endregion
/// <summary>
/// Starts processing the connection by establishing relevant protocol commands and resolving the address.
/// </summary>
/// <param name="remoteEP">The remote end-point targetted for connection.</param>
/// <param name="callback">The method raised once the connection process has completed or failed.</param>
public Socks5AsyncResult BeginProcessing(IPEndPoint remoteEP, ProcessorCallback callback)
{
if (remoteEP == null)
throw new ArgumentNullException("remoteEP");
this.callback = callback;
this.endPoint = remoteEP;
socket.BeginConnectInternal(new IPEndPoint(IPAddress.Parse(socket.ProxyAddress), socket.ProxyPort), OnSocketConnect, socket);
return asyncResult;
}
/// <summary>
/// Starts processing the connection by establishing relevant protocol commands and resolving the address.
/// </summary>
/// <param name="host">The address of the remote host to connect to.</param>
/// <param name="port">The port number of the remote host to connect to.</param>
/// <param name="callback">The method raised once the connection process has completed or failed.</param>
public Socks5AsyncResult BeginProcessing(string host, int port, ProcessorCallback callback)
{
if (host == null)
throw new ArgumentNullException("host");
this.callback = callback;
this.endAddress = host;
this.endPort = port;
socket.BeginConnectInternal(new IPEndPoint(IPAddress.Parse(socket.ProxyAddress), socket.ProxyPort), OnSocketConnect, socket);
return asyncResult;
}
}
/// <summary>
/// A class which contains information regarding the state of a forward processor.
/// </summary>
internal sealed class Socks5AsyncResult : IAsyncResult
{
private readonly ManualResetEvent asyncWaitHandle;
private object asyncState;
private bool completed;
/// <summary>
/// Initializes a new instance of the <see cref="Socks5AsyncResult"/> class.
/// </summary>
public Socks5AsyncResult()
{
this.asyncWaitHandle = new ManualResetEvent(false);
this.asyncState = null;
this.completed = false;
}
#region Properties
/// <summary>
/// Gets a user-defined object that qualifies or contains information about an asynchronous operation.
/// </summary>
public object AsyncState
{
get { return asyncState; }
}
/// <summary>
/// Gets a <see cref="T:System.Threading.WaitHandle" /> that is used to wait for an asynchronous operation to complete.
/// </summary>
public WaitHandle AsyncWaitHandle
{
get { return asyncWaitHandle; }
}
/// <summary>
/// Gets a value that indicates whether the asynchronous operation completed synchronously.
/// </summary>
public bool CompletedSynchronously
{
get { return false; }
}
/// <summary>
/// Gets a value that indicates whether the asynchronous operation has completed.
/// </summary>
public bool IsCompleted
{
get { return completed; }
}
#endregion
/// <summary>
/// Resets the asynchronous result.
/// </summary>
public void Reset()
{
completed = false;
asyncWaitHandle.Reset();
}
/// <summary>
/// Sets the asynchronous result to completed.
/// </summary>
public void Set()
{
asyncState = null;
completed = true;
asyncWaitHandle.Set();
}
}
/// <summary>
/// A delegate event handler which encapsulates the callback method raised once a processor has completed.
/// </summary>
/// <param name="success"><c>true</c> if the connection succeeds; otherwise, <c>false</c>.</param>
internal delegate void ProcessorCallback(bool success);
}

View File

@@ -0,0 +1,287 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Diagnostics;
using System.Net.NetworkInformation;
namespace Tor.Proxy
{
/// <summary>
/// A class containing the logic for the hosted HTTP proxy, which listens for connections to delegate to the tor network.
/// </summary>
[DebuggerStepThrough]
public sealed class Proxy : MarshalByRefObject, IDisposable
{
private readonly Client client;
private readonly object synchronize;
private List<Connection> connections;
private volatile bool disposed;
private int port;
private List<ConnectionProcessor> processors;
private Socket socket;
private volatile bool suppressDispose;
private Socks5Proxy webProxy;
/// <summary>
/// Initializes a new instance of the <see cref="Proxy"/> class.
/// </summary>
/// <param name="client">The client for which this object instance belongs.</param>
internal Proxy(Client client)
{
this.client = client;
this.connections = new List<Connection>();
this.webProxy = null;
this.port = 8182;
this.processors = new List<ConnectionProcessor>();
this.socket = null;
this.suppressDispose = false;
this.synchronize = new object();
this.Start();
}
/// <summary>
/// Finalizes an instance of the <see cref="Proxy"/> class.
/// </summary>
~Proxy()
{
Dispose(false);
}
#region Properties
/// <summary>
/// Gets the address of the proxy which can be used for manually creating <see cref="WebProxy"/> objects.
/// </summary>
public string Address
{
get { return string.Format("http://127.0.0.1:{0}", port); }
}
/// <summary>
/// Gets a value indicating whether the proxy socket is bound to the listen port.
/// </summary>
public bool IsRunning
{
get { lock (synchronize) return socket != null && socket.IsBound; }
}
/// <summary>
/// Gets or sets the port number which the client will listen on for HTTP proxy connections. This value defaults to 8182, but can be
/// changed depending on firewall restrictions. The port number must be available in order to host the HTTP proxy.
/// </summary>
public int Port
{
get { return port; }
set
{
if (port != value)
{
port = value;
Shutdown();
Start();
}
}
}
/// <summary>
/// Gets an <see cref="IWebProxy"/> which can be used in HTTP requests. This will be <c>null</c> if the proxy could not be hosted
/// on the specified port number.
/// </summary>
public IWebProxy WebProxy
{
get
{
if (disposed)
throw new ObjectDisposedException("this");
lock (synchronize)
return webProxy;
}
}
#endregion
#region System.IDisposable
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
private void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing)
{
lock (synchronize)
{
suppressDispose = true;
foreach (ConnectionProcessor processor in processors)
processor.Dispose();
foreach (Connection connection in connections)
connection.Dispose();
connections.Clear();
processors.Clear();
}
Shutdown();
disposed = true;
}
}
#endregion
#region Tor.Net.ForwardSocket
/// <summary>
/// Called when the internal listener socket accepts a TCP connection.
/// </summary>
/// <param name="ar">The asynchronous result object for this callback.</param>
private void OnSocketAccept(IAsyncResult ar)
{
try
{
Socket accepted = socket.EndAccept(ar);
if (client != null)
{
Connection connection = new Connection(client, accepted, OnConnectionDisposed);
lock (synchronize)
connections.Add(connection);
ConnectionProcessor processor = new ConnectionProcessor(client, connection, OnConnectionProcessorDisposed);
lock (synchronize)
processors.Add(processor);
processor.Start();
}
}
catch
{
}
try
{
if (socket != null)
socket.BeginAccept(OnSocketAccept, socket);
}
catch
{
}
}
#endregion
#region Tor.Proxy.Connection
/// <summary>
/// Called when a connection has been disposed.
/// </summary>
/// <param name="connection">The connection which was disposed.</param>
private void OnConnectionDisposed(Connection connection)
{
if (connection == null || suppressDispose)
return;
lock (synchronize)
connections.Remove(connection);
}
#endregion
#region Tor.Proxy.ConnectionProcessor
/// <summary>
/// Called when a connection processor has been disposed.
/// </summary>
/// <param name="processor">The connection processor which was disposed.</param>
private void OnConnectionProcessorDisposed(ConnectionProcessor processor)
{
if (processor == null || suppressDispose)
return;
lock (synchronize)
processors.Remove(processor);
}
#endregion
/// <summary>
/// Starts the proxy by creating a TCP listener on the specified proxy port number.
/// </summary>
private void Start()
{
if (disposed)
throw new ObjectDisposedException("this");
lock (synchronize)
{
if (socket != null)
return;
try
{
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), port));
socket.Listen(25);
socket.BeginAccept(OnSocketAccept, socket);
webProxy = new Socks5Proxy(client);
}
catch
{
if (socket != null)
{
socket.Dispose();
socket = null;
}
}
}
}
/// <summary>
/// Shuts down the TCP listener, releasing any resources associated with it.
/// </summary>
private void Shutdown()
{
lock (synchronize)
{
if (socket == null)
return;
try
{
socket.Shutdown(SocketShutdown.Both);
}
catch { }
socket.Dispose();
socket = null;
webProxy = null;
}
}
}
}

View File

@@ -0,0 +1,294 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
namespace Tor.Proxy
{
/// <summary>
/// A class which extends the <see cref="Socket"/> class implementation for automated SOCKS5 protocol support.
/// </summary>
internal sealed class ForwardSocket : Socket
{
private readonly Client client;
private Socks5AsyncResult asyncResult;
private AsyncCallback connectCallback;
private Socks5Processor processor;
private string proxyAddress;
private int proxyPort;
/// <summary>
/// Initializes a new instance of the <see cref="ForwardSocket"/> class.
/// </summary>
/// <param name="client">The client hosting the proxy connection.</param>
/// <param name="connection">The connection associated with the originating client connection.</param>
public ForwardSocket(Client client, Connection connection) : base(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
{
this.asyncResult = null;
this.client = client;
this.connectCallback = null;
this.processor = null;
this.proxyAddress = null;
this.proxyPort = -1;
}
#region Properties
/// <summary>
/// Gets or sets the address of the SOCKS5 proxy host.
/// </summary>
public string ProxyAddress
{
get { return proxyAddress; }
set { proxyAddress = value; }
}
/// <summary>
/// Gets or sets the port number of the SOCKS5 proxy host.
/// </summary>
public int ProxyPort
{
get { return proxyPort; }
set { proxyPort = value; }
}
#endregion
#region System.Net.Dns
/// <summary>
/// Called when the DNS resolved a host.
/// </summary>
/// <param name="ar">The asynchronous result object for the asynchronous method.</param>
private void OnDnsGetHostEntry(IAsyncResult ar)
{
try
{
IPHostEntry entry = Dns.EndGetHostEntry(ar);
DNSResolveState state = (DNSResolveState)ar.AsyncState;
base.BeginConnect(new IPEndPoint(entry.AddressList[0], state.Port), OnSocketBeginConnect, state.State);
}
catch (Exception exception)
{
throw new TorException("The fowarding socket failed to resolve a hostname", exception);
}
}
#endregion
#region System.Net.Sockets.Socket
/// <summary>
/// Begins an asynchronous request for a remote host connection.
/// </summary>
/// <param name="remoteEP">An <see cref="T:System.Net.EndPoint" /> that represents the remote host.</param>
/// <param name="callback">The <see cref="T:System.AsyncCallback" /> delegate.</param>
/// <param name="state">An object that contains state information for this request.</param>
/// <returns>
/// An <see cref="T:System.IAsyncResult" /> that references the asynchronous connection.
/// </returns>
/// <PermissionSet>
/// <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
/// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
/// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
/// <IPermission class="System.Diagnostics.PerformanceCounterPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
/// <IPermission class="System.Net.SocketPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
/// </PermissionSet>
public new IAsyncResult BeginConnect(EndPoint remoteEP, AsyncCallback callback, object state)
{
if (remoteEP == null)
throw new ArgumentNullException("remoteEP");
if (callback == null)
throw new ArgumentNullException("callback");
if (proxyAddress == null || proxyPort == -1)
return base.BeginConnect(remoteEP, callback, state);
else
{
connectCallback = callback;
processor = new Socks5Processor(this);
asyncResult = processor.BeginProcessing((IPEndPoint)remoteEP, OnConnectionEstablished);
return asyncResult;
}
}
/// <summary>
/// Begins an asynchronous request for a remote host connection.
/// </summary>
/// <param name="host">The host address to connect to.</param>
/// <param name="port">The port number to connect to.</param>
/// <param name="callback">The <see cref="T:System.AsyncCallback" /> delegate.</param>
/// <param name="state">An object that contains state information for this request.</param>
/// <returns>
/// An <see cref="T:System.IAsyncResult" /> that references the asynchronous connection.
/// </returns>
public new IAsyncResult BeginConnect(string host, int port, AsyncCallback callback, object state)
{
if (host == null)
throw new ArgumentNullException("host");
if (callback == null)
throw new ArgumentNullException("callback");
connectCallback = callback;
if (proxyAddress == null || proxyPort == -1)
return BeginDNSResolve(host, port, callback, state);
else
{
processor = new Socks5Processor(this);
asyncResult = processor.BeginProcessing(host, port, OnConnectionEstablished);
return asyncResult;
}
}
/// <summary>
/// Establishes a connection to a remote host.
/// </summary>
/// <param name="remoteEP">An <see cref="T:System.Net.EndPoint" /> that represents the remote device.</param>
/// <PermissionSet>
/// <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
/// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
/// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
/// <IPermission class="System.Diagnostics.PerformanceCounterPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
/// <IPermission class="System.Net.SocketPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
/// </PermissionSet>
public new void Connect(EndPoint remoteEP)
{
if (remoteEP == null)
throw new ArgumentNullException("remoteEP");
if (proxyAddress == null || proxyPort == -1)
base.Connect(remoteEP);
else
{
processor = new Socks5Processor(this);
asyncResult = processor.BeginProcessing((IPEndPoint)remoteEP, OnConnectionEstablished);
asyncResult.AsyncWaitHandle.WaitOne();
}
}
/// <summary>
/// Ends a pending asynchronous connection request.
/// </summary>
/// <param name="asyncResult">An <see cref="T:System.IAsyncResult" /> that stores state information and any user defined data for this asynchronous operation.</param>
/// <PermissionSet>
/// <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
/// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
/// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence" />
/// </PermissionSet>
public new void EndConnect(IAsyncResult asyncResult)
{
if (asyncResult == null)
throw new ArgumentNullException("asyncResult");
if (asyncResult.IsCompleted)
return;
throw new InvalidOperationException("The socket has not yet completed processing, this is an invalid call");
}
/// <summary>
/// Called when the socket has connected following a DNS resolution.
/// </summary>
/// <param name="ar">The asynchronous result object for the asynchronous method.</param>
private void OnSocketBeginConnect(IAsyncResult ar)
{
try
{
base.EndConnect(ar);
asyncResult.Set();
if (connectCallback != null)
connectCallback(asyncResult);
}
catch (Exception exception)
{
throw new TorException("The forwarding socket failed to complete the connect operation", exception);
}
}
#endregion
/// <summary>
/// Begins an asynchronous request for a remote host connection.
/// </summary>
/// <param name="remoteEP">An <see cref="T:System.Net.EndPoint" /> that represents the remote host.</param>
/// <param name="callback">The <see cref="T:System.AsyncCallback" /> delegate.</param>
/// <param name="state">An object that contains state information for this request.</param>
/// <returns>
/// An <see cref="T:System.IAsyncResult" /> that references the asynchronous connection.
/// </returns>
internal IAsyncResult BeginConnectInternal(EndPoint remoteEP, AsyncCallback callback, object state)
{
return base.BeginConnect(remoteEP, callback, state);
}
/// <summary>
/// Begins an asynchronous request to resolve the DNS information for a host.
/// </summary>
/// <param name="host">The host address to resolve.</param>
/// <param name="port">The port number of the associated host.</param>
/// <returns>An <see cref="T:System.IAsyncResult" /> that references the asynchronous request.</returns>
private IAsyncResult BeginDNSResolve(string host, int port, AsyncCallback callback, object state)
{
DNSResolveState dnsState;
try
{
dnsState = new DNSResolveState();
dnsState.Callback = callback;
dnsState.Host = host;
dnsState.Port = port;
dnsState.State = state;
asyncResult = new Socks5AsyncResult();
Dns.BeginGetHostEntry(host, OnDnsGetHostEntry, dnsState);
return asyncResult;
}
catch (Exception exception)
{
throw new TorException("The forwarding socket failed to resolve a host address", exception);
}
}
/// <summary>
/// Called when the connection has been established to the proxy provider.
/// </summary>
/// <param name="success"><c>true</c> if the connection succeeds; otherwise, <c>false</c>.</param>
private void OnConnectionEstablished(bool success)
{
if (success)
{
asyncResult.Set();
if (connectCallback != null)
connectCallback(asyncResult);
}
else
{
Close();
}
}
/// <summary>
/// A structure which stores information regarding a DNS resolution request.
/// </summary>
private struct DNSResolveState
{
public AsyncCallback Callback;
public string Host;
public int Port;
public object State;
}
}
}

View File

@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
namespace Tor.Proxy
{
/// <summary>
/// A class which implements the <see cref="IWebProxy"/> interface to provide routing of HTTP requests.
/// </summary>
public sealed class Socks5Proxy : IWebProxy
{
private readonly Client client;
private ICredentials credentials;
/// <summary>
/// Initializes a new instance of the <see cref="Socks5Proxy"/> class.
/// </summary>
/// <param name="client">The client for which this object instance belongs.</param>
internal Socks5Proxy(Client client)
{
this.client = client;
}
#region Properties
/// <summary>
/// The credentials to submit to the proxy server for authentication.
/// </summary>
public ICredentials Credentials
{
get { return credentials; }
set { credentials = value; }
}
#endregion
/// <summary>
/// Returns the URI of a proxy.
/// </summary>
/// <param name="destination">A <see cref="T:System.Uri" /> that specifies the requested Internet resource.</param>
/// <returns>
/// A <see cref="T:System.Uri" /> instance that contains the URI of the proxy used to contact <paramref name="destination" />.
/// </returns>
public Uri GetProxy(Uri destination)
{
return new Uri(string.Format("http://127.0.0.1:{0}", client.Proxy.Port));
}
/// <summary>
/// Indicates that the proxy should not be used for the specified host.
/// </summary>
/// <param name="host">The <see cref="T:System.Uri" /> of the host to check for proxy use.</param>
/// <returns>
/// true if the proxy server should not be used for <paramref name="host" />; otherwise, false.
/// </returns>
public bool IsBypassed(Uri host)
{
return client.Proxy.IsRunning;
}
}
}