490 lines
17 KiB
C#
490 lines
17 KiB
C#
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;
|
|
using MarketData;
|
|
using System.Collections.Concurrent;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
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 Thread monitorThread=null;
|
|
private volatile bool threadRun=true;
|
|
private int refreshAfter=120000;
|
|
|
|
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)
|
|
{
|
|
monitorThread=new Thread(new ThreadStart(ThreadProc));
|
|
monitorThread.Start();
|
|
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)
|
|
{
|
|
|
|
if(threadRun)
|
|
{
|
|
threadRun=false;
|
|
if(null!=monitorThread)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[Proxy:Dispose]Thread state is '{0}'. Joining main thread...",Utility.ThreadStateToString(monitorThread)));
|
|
monitorThread.Join(5000);
|
|
monitorThread=null;
|
|
}
|
|
}
|
|
|
|
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
|
|
|
|
private void OnSocketAccept(IAsyncResult ar)
|
|
{
|
|
try
|
|
{
|
|
Socket accepted = null;
|
|
if (client != null)
|
|
{
|
|
Task workerTask= Task.Factory.StartNew( () =>
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[Proxy:OnSocketAccept] Accepting incoming..."));
|
|
accepted = socket.EndAccept(ar);
|
|
if (socket != null)socket.BeginAccept(OnSocketAccept, socket);
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[Proxy:OnSocketAccept] Accepted connection:{0}->{1}",accepted.LocalEndPoint,accepted.RemoteEndPoint));
|
|
});
|
|
workerTask.ContinueWith((continuation) =>
|
|
{
|
|
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(Exception exception)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString());
|
|
}
|
|
|
|
|
|
}
|
|
|
|
#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);
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[Proxy:OnConnectionDisposed] Removed Connection for {0}:{1}. There are {2} connections.",connection.Host,connection.Port,connections.Count));
|
|
}
|
|
}
|
|
|
|
#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);
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[Proxy:OnConnectionProcessorDisposed] Removed Connection Processor. There are {0} connection processors remaining.",processors.Count));
|
|
}
|
|
}
|
|
|
|
#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(30); // 25
|
|
socket.BeginAccept(OnSocketAccept, socket);
|
|
|
|
webProxy = new Socks5Proxy(client);
|
|
}
|
|
catch(Exception exception)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[Proxy:Start] Exception:{0}",exception.ToString()));
|
|
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;
|
|
}
|
|
}
|
|
private void ThreadProc()
|
|
{
|
|
int quantums=0;
|
|
int quantumInterval=1000;
|
|
while(threadRun)
|
|
{
|
|
Thread.Sleep(quantumInterval);
|
|
if(!threadRun) break;
|
|
quantums+=quantumInterval;
|
|
if(quantums>refreshAfter)
|
|
{
|
|
quantums=0;
|
|
lock(synchronize)
|
|
{
|
|
MonitorDisposedConnections();
|
|
MonitorDisposedConnectionProcessors();
|
|
MonitorIdleConnections();
|
|
}
|
|
}
|
|
}
|
|
MDTrace.WriteLine(LogLevel.DEBUG,"[Proxy:ThreadProc]Thread ended.");
|
|
}
|
|
|
|
private void MonitorDisposedConnections()
|
|
{
|
|
List<Connection> connectionsToRemove=new List<Connection>();
|
|
lock(synchronize)
|
|
{
|
|
try
|
|
{
|
|
foreach(Connection connection in connections)
|
|
{
|
|
if(connection.IsDisposed())
|
|
{
|
|
connectionsToRemove.Add(connection);
|
|
}
|
|
}
|
|
if(0!=connectionsToRemove.Count)MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[Proxy:MonitorDisposedConnections] Removing {0} disposed connections.",connectionsToRemove.Count));
|
|
foreach(Connection connectionToRemove in connectionsToRemove)
|
|
{
|
|
connections.Remove(connectionToRemove);
|
|
}
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[Proxy:MonitorDisposedConnections] There are {0} remaining connections.",connections.Count));
|
|
}
|
|
catch(Exception exception)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[Proxy:MonitorDisposedConnections] Exception:{0}",exception.ToString()));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void MonitorDisposedConnectionProcessors()
|
|
{
|
|
List<ConnectionProcessor> connectionsProcessorsToRemove=new List<ConnectionProcessor>();
|
|
lock(synchronize)
|
|
{
|
|
try
|
|
{
|
|
foreach(ConnectionProcessor connectionProcessor in processors)
|
|
{
|
|
if(connectionProcessor.IsDisposed())
|
|
{
|
|
connectionsProcessorsToRemove.Add(connectionProcessor);
|
|
}
|
|
}
|
|
if(0!=connectionsProcessorsToRemove.Count)MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[Proxy:MonitorDisposedConnectionProcessors] Removing {0} disposed connection processors.",connectionsProcessorsToRemove.Count));
|
|
foreach(ConnectionProcessor connectionProcessorToRemove in connectionsProcessorsToRemove)
|
|
{
|
|
processors.Remove(connectionProcessorToRemove);
|
|
}
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[Proxy:MonitorDisposedConnectionProcessors] There are {0} remaining connection processors.",processors.Count));
|
|
}
|
|
catch(Exception exception)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[Proxy:MonitorDisposedConnectionProcessors] Exception:{0}",exception.ToString()));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Idle connections where no activity for 5 minutes
|
|
// 1000*60*5
|
|
private void MonitorIdleConnections()
|
|
{
|
|
List<Connection> connectionsToRemove=new List<Connection>();
|
|
lock(synchronize)
|
|
{
|
|
try
|
|
{
|
|
connectionsToRemove = connections.Where(x => x.Profiler.ElapsedTime()>300000 && x.IsSocketClosed()).ToList();
|
|
if(null!=connectionsToRemove)
|
|
{
|
|
if(0!=connectionsToRemove.Count)MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[Proxy:MonitorIdleConnections] Disposing {0} idle connections",connectionsToRemove.Count));
|
|
foreach(Connection connection in connectionsToRemove)
|
|
{
|
|
RemoveConnection(connection);
|
|
}
|
|
if(0==processors.Count && 0!=connections.Count)
|
|
{
|
|
RemoveOrphanedConnections(connections);
|
|
}
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[Proxy:MonitorIdleConnections] There are {0} remaining connections.",connections.Count));
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[Proxy:MonitorIdleConnections] There are {0} remaining connection processors.",processors.Count));
|
|
}
|
|
}
|
|
catch(Exception exception)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[Proxy:MonitorIdleConnections] Exception:{0}",exception.ToString()));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove connections that are considered to be inactive and the socket is considered to be closed.
|
|
private void RemoveOrphanedConnections(List<Connection> connections)
|
|
{
|
|
lock(synchronize)
|
|
{
|
|
try
|
|
{
|
|
List<Connection> connectionsToRemove=new List<Connection>();
|
|
foreach(Connection connection in connections)
|
|
{
|
|
if(connection.IsSocketClosed())
|
|
{
|
|
connectionsToRemove.Add(connection);
|
|
}
|
|
else
|
|
{
|
|
UInt64 lingerTimeMinutes = connection.Profiler.ElapsedTime()/1000/60;
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[Proxy:RemoveOrphanedConnections] Orphaned Connection {0}:{1}. Socket reports active data but there has been no buffer exchange for {2} minutes.",connection.Host,connection.Port,lingerTimeMinutes));
|
|
if(lingerTimeMinutes>5)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[Proxy:RemoveOrphanedConnections] Disposing Orphaned Connection {0}:{1}.",connection.Host,connection.Port));
|
|
connectionsToRemove.Add(connection);
|
|
}
|
|
}
|
|
}
|
|
foreach(Connection connection in connectionsToRemove)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[Proxy:RemoveOrphanedConnections] Removing orphaned connection {0}:{1} .",connection.Host,connection.Port));
|
|
connection.Dispose();
|
|
connections.Remove(connection);
|
|
}
|
|
}
|
|
catch(Exception exception)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[Proxy:RemoveOrphanedConnections] Exception:{0}",exception.ToString()));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove the connection and associated connection processor
|
|
private void RemoveConnection(Connection connection)
|
|
{
|
|
lock(synchronize)
|
|
{
|
|
try
|
|
{
|
|
ConnectionProcessor connectionProcessor = processors.Where(x => x.Connection == connection).FirstOrDefault();
|
|
if(null!=connectionProcessor)
|
|
{
|
|
connectionProcessor.Dispose();
|
|
processors.Remove(connectionProcessor);
|
|
}
|
|
connection.Dispose();
|
|
connections.Remove(connection);
|
|
}
|
|
catch(Exception exception)
|
|
{
|
|
MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[Proxy:RemoveConnection] Exception:{0}",exception.ToString()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|