| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 | using System;using System.Net.Sockets;using System.Net;using System.Threading;using Renci.SshNet.Abstractions;using Renci.SshNet.Common;namespace Renci.SshNet{    public partial class ForwardedPortLocal    {        private Socket _listener;        private CountdownEvent _pendingChannelCountdown;        partial void InternalStart()        {            var addr = DnsAbstraction.GetHostAddresses(BoundHost)[0];            var ep = new IPEndPoint(addr, (int) BoundPort);            _listener = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp) {NoDelay = true};            _listener.Bind(ep);            _listener.Listen(5);            // update bound port (in case original was passed as zero)            BoundPort = (uint)((IPEndPoint)_listener.LocalEndPoint).Port;            Session.ErrorOccured += Session_ErrorOccured;            Session.Disconnected += Session_Disconnected;            InitializePendingChannelCountdown();            // consider port started when we're listening for inbound connections            _status = ForwardedPortStatus.Started;            StartAccept(null);        }        private void StartAccept(SocketAsyncEventArgs e)        {            if (e == null)            {                e = new SocketAsyncEventArgs();                e.Completed += AcceptCompleted;            }            else            {                // clear the socket as we're reusing the context object                e.AcceptSocket = null;            }            // only accept new connections while we are started            if (IsStarted)            {                try                {                    if (!_listener.AcceptAsync(e))                    {                        AcceptCompleted(null, e);                    }                }                catch (ObjectDisposedException)                {                    if (_status == ForwardedPortStatus.Stopped || _status == ForwardedPortStatus.Stopped)                    {                        // ignore ObjectDisposedException while stopping or stopped                        return;                    }                    throw;                }            }        }        private void AcceptCompleted(object sender, SocketAsyncEventArgs e)        {            if (e.SocketError == SocketError.OperationAborted || e.SocketError == SocketError.NotSocket)            {                // server was stopped                return;            }            // capture client socket            var clientSocket = e.AcceptSocket;            if (e.SocketError != SocketError.Success)            {                // accept new connection                StartAccept(e);                // dispose broken client socket                CloseClientSocket(clientSocket);                return;            }            // accept new connection            StartAccept(e);            // process connection            ProcessAccept(clientSocket);        }        private void ProcessAccept(Socket clientSocket)        {            // close the client socket if we're no longer accepting new connections            if (!IsStarted)            {                CloseClientSocket(clientSocket);                return;            }            // capture the countdown event that we're adding a count to, as we need to make sure that we'll be signaling            // that same instance; the instance field for the countdown event is re-initialized when the port is restarted            // and at that time there may still be pending requests            var pendingChannelCountdown = _pendingChannelCountdown;            pendingChannelCountdown.AddCount();            try            {                var originatorEndPoint = (IPEndPoint) clientSocket.RemoteEndPoint;                RaiseRequestReceived(originatorEndPoint.Address.ToString(),                    (uint)originatorEndPoint.Port);                using (var channel = Session.CreateChannelDirectTcpip())                {                    channel.Exception += Channel_Exception;                    channel.Open(Host, Port, this, clientSocket);                    channel.Bind();                    channel.Close();                }            }            catch (Exception exp)            {                RaiseExceptionEvent(exp);                CloseClientSocket(clientSocket);            }            finally            {                // take into account that CountdownEvent has since been disposed; when stopping the port we                // wait for a given time for the channels to close, but once that timeout period has elapsed                // the CountdownEvent will be disposed                try                {                    pendingChannelCountdown.Signal();                }                catch (ObjectDisposedException)                {                }            }        }        /// <summary>        /// Initializes the <see cref="CountdownEvent"/>.        /// </summary>        /// <remarks>        /// <para>        /// When the port is started for the first time, a <see cref="CountdownEvent"/> is created with an initial count        /// of <c>1</c>.        /// </para>        /// <para>        /// On subsequent (re)starts, we'll dispose the current <see cref="CountdownEvent"/> and create a new one with        /// initial count of <c>1</c>.        /// </para>        /// </remarks>        private void InitializePendingChannelCountdown()        {            var original = Interlocked.Exchange(ref _pendingChannelCountdown, new CountdownEvent(1));            if (original != null)            {                original.Dispose();            }        }        private static void CloseClientSocket(Socket clientSocket)        {            if (clientSocket.Connected)            {                try                {                    clientSocket.Shutdown(SocketShutdown.Send);                }                catch (Exception)                {                    // ignore exception when client socket was already closed                }            }            clientSocket.Dispose();        }        /// <summary>        /// Interrupts the listener, and unsubscribes from <see cref="Session"/> events.        /// </summary>        partial void StopListener()        {            // close listener socket            var listener = _listener;            if (listener != null)            {                listener.Dispose();            }            // unsubscribe from session events            var session = Session;            if (session != null)            {                session.ErrorOccured -= Session_ErrorOccured;                session.Disconnected -= Session_Disconnected;            }        }        /// <summary>        /// Waits for pending channels to close.        /// </summary>        /// <param name="timeout">The maximum time to wait for the pending channels to close.</param>        partial void InternalStop(TimeSpan timeout)        {            _pendingChannelCountdown.Signal();            _pendingChannelCountdown.Wait(timeout);        }        partial void InternalDispose(bool disposing)        {            if (disposing)            {                var listener = _listener;                if (listener != null)                {                    _listener = null;                    listener.Dispose();                }                var pendingRequestsCountdown = _pendingChannelCountdown;                if (pendingRequestsCountdown != null)                {                    _pendingChannelCountdown = null;                    pendingRequestsCountdown.Dispose();                }            }        }        private void Session_Disconnected(object sender, EventArgs e)        {            var session = Session;            if (session != null)            {                StopPort(session.ConnectionInfo.Timeout);            }        }        private void Session_ErrorOccured(object sender, ExceptionEventArgs e)        {            var session = Session;            if (session != null)            {                StopPort(session.ConnectionInfo.Timeout);            }        }        private void Channel_Exception(object sender, ExceptionEventArgs e)        {            RaiseExceptionEvent(e.Exception);        }    }}
 |