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 { /// /// A class containing the logic for the hosted HTTP proxy, which listens for connections to delegate to the tor network. /// [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 connections; private volatile bool disposed; private int port; private List processors; private Socket socket; private volatile bool suppressDispose; private Socks5Proxy webProxy; /// /// Initializes a new instance of the class. /// /// The client for which this object instance belongs. internal Proxy(Client client) { monitorThread=new Thread(new ThreadStart(ThreadProc)); monitorThread.Start(); this.client = client; this.connections = new List(); this.webProxy = null; this.port = 8182; this.processors = new List(); this.socket = null; this.suppressDispose = false; this.synchronize = new object(); this.Start(); } /// /// Finalizes an instance of the class. /// ~Proxy() { Dispose(false); } #region Properties /// /// Gets the address of the proxy which can be used for manually creating objects. /// public string Address { get { return string.Format("http://127.0.0.1:{0}", port); } } /// /// Gets a value indicating whether the proxy socket is bound to the listen port. /// public bool IsRunning { get { lock (synchronize) return socket != null && socket.IsBound; } } /// /// 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. /// public int Port { get { return port; } set { if (port != value) { port = value; Shutdown(); Start(); } } } /// /// Gets an which can be used in HTTP requests. This will be null if the proxy could not be hosted /// on the specified port number. /// public IWebProxy WebProxy { get { if (disposed) throw new ObjectDisposedException("this"); lock (synchronize) return webProxy; } } #endregion #region System.IDisposable /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. 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 /// /// Called when a connection has been disposed. /// /// The connection which was disposed. 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 /// /// Called when a connection processor has been disposed. /// /// The connection processor which was disposed. 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 /// /// Starts the proxy by creating a TCP listener on the specified proxy port number. /// 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; } } } } /// /// Shuts down the TCP listener, releasing any resources associated with it. /// 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 connectionsToRemove=new List(); 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 connectionsProcessorsToRemove=new List(); 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 connectionsToRemove=new List(); 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 connections) { lock(synchronize) { try { List connectionsToRemove=new List(); 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())); } } } } }