public CertificateValidationEventArgs(RequestStateBase state, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
     : base(state)
 {
     Certificate     = certificate;
     Chain           = chain;
     SslPolicyErrors = sslPolicyErrors;
 }
예제 #2
0
 public CertificateSelectionEventArgs(RequestStateBase state, string targetHost,
                                      X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers) : base(state)
 {
     TargetHost        = targetHost;
     LocalCertificates = localCertificates;
     RemoteCertificate = remoteCertificate;
     AcceptableIssuers = acceptableIssuers;
 }
예제 #3
0
        /// <summary>
        ///     Create a server connection.
        /// </summary>
        /// <param name="proxyServer">The proxy server.</param>
        /// <param name="session">The session event arguments.</param>
        /// <param name="isConnect">Is this a CONNECT request.</param>
        /// <param name="applicationProtocols"></param>
        /// <param name="noCache">if set to <c>true</c> [no cache].</param>
        /// <param name="cancellationToken">The cancellation token for this async task.</param>
        /// <returns></returns>
        internal async Task <TcpServerConnection> GetServerConnection(RequestStateBase state, bool isConnect,
                                                                      List <SslApplicationProtocol>?applicationProtocols, bool noCache, CancellationToken cancellationToken)
        {
            var            proxyServer         = state.Server;
            var            session             = state.session !;
            IExternalProxy?customUpStreamProxy = null;

            bool isHttps = session.IsHttps;

            if (proxyServer.GetCustomUpStreamProxyFunc != null)
            {
                customUpStreamProxy = await proxyServer.GetCustomUpStreamProxyFunc(session);
            }

            session.CustomUpStreamProxyUsed = customUpStreamProxy;

            var    request = session.HttpClient.Request;
            string host;
            int    port;

            if (request.Authority.Length > 0)
            {
                var authority = request.Authority;
                int idx       = authority.IndexOf((byte)':');
                if (idx == -1)
                {
                    host = authority.GetString();
                    port = 80;
                }
                else
                {
                    host = authority.Slice(0, idx).GetString();
                    port = int.Parse(authority.Slice(idx + 1).GetString());
                }
            }
            else
            {
                var uri = request.RequestUri;
                host = uri.Host;
                port = uri.Port;
            }

            return(await GetServerConnection(
                       state, host, port,
                       session.HttpClient.Request.HttpVersion,
                       isHttps, applicationProtocols, isConnect,
                       session.HttpClient.UpStreamEndPoint ?? proxyServer.UpStreamEndPoint,
                       customUpStreamProxy ?? (isHttps ? proxyServer.UpStreamHttpsProxy : proxyServer.UpStreamHttpProxy),
                       noCache, cancellationToken));
        }
예제 #4
0
        /// <summary>
        ///     Create a server connection.
        /// </summary>
        /// <param name="proxyServer">The proxy server.</param>
        /// <param name="session">The session event arguments.</param>
        /// <param name="isConnect">Is this a CONNECT request.</param>
        /// <param name="applicationProtocol"></param>
        /// <param name="noCache">if set to <c>true</c> [no cache].</param>
        /// <param name="cancellationToken">The cancellation token for this async task.</param>
        /// <returns></returns>
        internal Task <TcpServerConnection> GetServerConnection(RequestStateBase state, bool isConnect,
                                                                SslApplicationProtocol applicationProtocol, bool noCache, CancellationToken cancellationToken)
        {
            List <SslApplicationProtocol>?applicationProtocols = null;

            if (applicationProtocol != default)
            {
                applicationProtocols = new List <SslApplicationProtocol> {
                    applicationProtocol
                };
            }

            return(GetServerConnection(state, isConnect, applicationProtocols, noCache, cancellationToken));
        }
        /// <summary>
        ///     Initializes a new instance of the <see cref="SessionEventArgsBase" /> class.
        /// </summary>
        private protected SessionEventArgsBase(RequestStateBase state, ProxyEndPoint endPoint,
                                               HttpClientStream clientStream, ConnectRequest?connectRequest, Request request, CancellationTokenSource cancellationTokenSource)
            : base(state)
        {
            var server = state.Server;

            BufferPool    = server.BufferPool;
            ExceptionFunc = server.ExceptionFunc;
            TimeLine["Session Created"] = DateTime.Now;

            CancellationTokenSource = cancellationTokenSource;

            ClientStream  = clientStream;
            HttpClient    = new HttpWebClient(connectRequest, request, new Lazy <int>(() => clientStream.Connection.GetProcessId(endPoint)));
            LocalEndPoint = endPoint;
            EnableWinAuth = server.EnableWinAuth && isWindowsAuthenticationSupported;
        }
예제 #6
0
        /// <summary>
        ///     Gets a TCP connection to server from connection pool.
        /// </summary>
        /// <param name="proxyServer">The current ProxyServer instance.</param>
        /// <param name="remoteHostName">The remote hostname.</param>
        /// <param name="remotePort">The remote port.</param>
        /// <param name="httpVersion">The http version to use.</param>
        /// <param name="isHttps">Is this a HTTPS request.</param>
        /// <param name="applicationProtocols">The list of HTTPS application level protocol to negotiate if needed.</param>
        /// <param name="isConnect">Is this a CONNECT request.</param>
        /// <param name="sessionArgs">The session event arguments.</param>
        /// <param name="upStreamEndPoint">The local upstream endpoint to make request via.</param>
        /// <param name="externalProxy">The external proxy to make request via.</param>
        /// <param name="noCache">Not from cache/create new connection.</param>
        /// <param name="cancellationToken">The cancellation token for this async task.</param>
        /// <returns></returns>
        internal async Task <TcpServerConnection> GetServerConnection(RequestStateBase state, string remoteHostName, int remotePort,
                                                                      Version httpVersion, bool isHttps, List <SslApplicationProtocol>?applicationProtocols, bool isConnect,
                                                                      IPEndPoint?upStreamEndPoint, IExternalProxy?externalProxy,
                                                                      bool noCache, CancellationToken cancellationToken)
        {
            var session     = state.session;
            var sslProtocol = session?.ClientConnection.SslProtocol ?? SslProtocols.None;
            var cacheKey    = GetConnectionCacheKey(remoteHostName, remotePort,
                                                    isHttps, applicationProtocols, upStreamEndPoint, externalProxy);
            var proxyServer = state.Server;

            if (proxyServer.EnableConnectionPool && !noCache)
            {
                if (cache.TryGetValue(cacheKey, out var existingConnections))
                {
                    // +3 seconds for potential delay after getting connection
                    var cutOff = DateTime.Now.AddSeconds(-proxyServer.ConnectionTimeOutSeconds + 3);
                    while (existingConnections.Count > 0)
                    {
                        if (existingConnections.TryDequeue(out var recentConnection))
                        {
                            if (recentConnection.LastAccess > cutOff &&
                                recentConnection.TcpClient.IsGoodConnection())
                            {
                                return(recentConnection);
                            }

                            disposalBag.Add(recentConnection);
                        }
                    }
                }
            }

            var connection = await createServerConnection(remoteHostName, remotePort, httpVersion, isHttps, sslProtocol,
                                                          applicationProtocols, isConnect, state, upStreamEndPoint, externalProxy, cacheKey, cancellationToken);

            return(connection);
        }
예제 #7
0
 /// <summary>
 /// Constructor to initialize the proxy
 /// </summary>
 internal SessionEventArgs(RequestStateBase state, ProxyEndPoint endPoint, HttpClientStream clientStream, ConnectRequest?connectRequest, CancellationTokenSource cancellationTokenSource)
     : base(state, endPoint, clientStream, connectRequest, new Request(), cancellationTokenSource)
 {
 }
예제 #8
0
        /// <summary>
        ///     Creates a TCP connection to server
        /// </summary>
        /// <param name="remoteHostName">The remote hostname.</param>
        /// <param name="remotePort">The remote port.</param>
        /// <param name="httpVersion">The http version to use.</param>
        /// <param name="isHttps">Is this a HTTPS request.</param>
        /// <param name="sslProtocol">The SSL protocol.</param>
        /// <param name="applicationProtocols">The list of HTTPS application level protocol to negotiate if needed.</param>
        /// <param name="isConnect">Is this a CONNECT request.</param>
        /// <param name="proxyServer">The current ProxyServer instance.</param>
        /// <param name="sessionArgs">The http session.</param>
        /// <param name="upStreamEndPoint">The local upstream endpoint to make request via.</param>
        /// <param name="externalProxy">The external proxy to make request via.</param>
        /// <param name="cacheKey">The connection cache key</param>
        /// <param name="cancellationToken">The cancellation token for this async task.</param>
        /// <returns></returns>
        private async Task <TcpServerConnection> createServerConnection(string remoteHostName, int remotePort,
                                                                        Version httpVersion, bool isHttps, SslProtocols sslProtocol, List <SslApplicationProtocol>?applicationProtocols, bool isConnect,
                                                                        RequestStateBase state, IPEndPoint?upStreamEndPoint, IExternalProxy?externalProxy, string cacheKey,
                                                                        CancellationToken cancellationToken)
        {
            var proxyServer = state.Server;
            var sessionArgs = state.session;

            // deny connection to proxy end points to avoid infinite connection loop.
            if (Server.ProxyEndPoints.Any(x => x.Port == remotePort) &&
                NetworkHelper.IsLocalIpAddress(remoteHostName))
            {
                throw new Exception($"A client is making HTTP request to one of the listening ports of this proxy {remoteHostName}:{remotePort}");
            }

            if (externalProxy != null)
            {
                if (Server.ProxyEndPoints.Any(x => x.Port == externalProxy.Port) &&
                    NetworkHelper.IsLocalIpAddress(externalProxy.HostName))
                {
                    throw new Exception($"A client is making HTTP request via external proxy to one of the listening ports of this proxy {remoteHostName}:{remotePort}");
                }
            }

            bool useUpstreamProxy1 = false;

            // check if external proxy is set for HTTP/HTTPS
            if (externalProxy != null && !(externalProxy.HostName == remoteHostName && externalProxy.Port == remotePort))
            {
                useUpstreamProxy1 = true;

                // check if we need to ByPass
                if (externalProxy.BypassLocalhost && NetworkHelper.IsLocalIpAddress(remoteHostName))
                {
                    useUpstreamProxy1 = false;
                }
            }

            if (!useUpstreamProxy1)
            {
                externalProxy = null;
            }

            TcpClient?       tcpClient = null;
            HttpServerStream?stream    = null;

            SslApplicationProtocol negotiatedApplicationProtocol = default;

            bool retry = true;
            var  enabledSslProtocols = sslProtocol;

retry:
            try
            {
                string hostname = externalProxy != null ? externalProxy.HostName : remoteHostName;
                int    port     = externalProxy?.Port ?? remotePort;

                var ipAddresses = await Dns.GetHostAddressesAsync(hostname);

                if (ipAddresses == null || ipAddresses.Length == 0)
                {
                    throw new Exception($"Could not resolve the hostname {hostname}");
                }

                if (sessionArgs != null)
                {
                    sessionArgs.TimeLine["Dns Resolved"] = DateTime.Now;
                }

                Array.Sort(ipAddresses, (x, y) => x.AddressFamily.CompareTo(y.AddressFamily));

                Exception?lastException = null;
                for (int i = 0; i < ipAddresses.Length; i++)
                {
                    try
                    {
                        var ipAddress = ipAddresses[i];
                        if (upStreamEndPoint == null)
                        {
                            tcpClient = new TcpClient(ipAddress.AddressFamily);
                        }
                        else
                        {
                            tcpClient = new TcpClient(upStreamEndPoint);
                        }

                        tcpClient.NoDelay        = proxyServer.NoDelay;
                        tcpClient.ReceiveTimeout = proxyServer.ConnectionTimeOutSeconds * 1000;
                        tcpClient.SendTimeout    = proxyServer.ConnectionTimeOutSeconds * 1000;
                        tcpClient.LingerState    = new LingerOption(true, proxyServer.TcpTimeWaitSeconds);

                        if (proxyServer.ReuseSocket && RunTime.IsSocketReuseAvailable)
                        {
                            tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
                        }

                        var connectTask = tcpClient.ConnectAsync(ipAddress, port);
                        await Task.WhenAny(connectTask, Task.Delay(proxyServer.ConnectTimeOutSeconds * 1000));

                        if (!connectTask.IsCompleted || !tcpClient.Connected)
                        {
                            // here we can just do some cleanup and let the loop continue since
                            // we will either get a connection or wind up with a null tcpClient
                            // which will throw
                            try
                            {
                                connectTask.Dispose();
                            }
                            catch
                            {
                                // ignore
                            }
                            try
                            {
#if NET45
                                tcpClient?.Close();
#else
                                tcpClient?.Dispose();
#endif
                                tcpClient = null;
                            }
                            catch
                            {
                                // ignore
                            }

                            continue;
                        }

                        break;
                    }
                    catch (Exception e)
                    {
                        // dispose the current TcpClient and try the next address
                        lastException = e;
#if NET45
                        tcpClient?.Close();
#else
                        tcpClient?.Dispose();
#endif
                        tcpClient = null;
                    }
                }

                if (tcpClient == null)
                {
                    if (sessionArgs != null && proxyServer.CustomUpStreamProxyFailureFunc != null)
                    {
                        var newUpstreamProxy = await proxyServer.CustomUpStreamProxyFailureFunc(sessionArgs);

                        if (newUpstreamProxy != null)
                        {
                            sessionArgs.CustomUpStreamProxyUsed = newUpstreamProxy;
                            sessionArgs.TimeLine["Retrying Upstream Proxy Connection"] = DateTime.Now;
                            return(await createServerConnection(remoteHostName, remotePort, httpVersion, isHttps, sslProtocol, applicationProtocols, isConnect, state, upStreamEndPoint, externalProxy, cacheKey, cancellationToken));
                        }
                    }

                    throw new Exception($"Could not establish connection to {hostname}", lastException);
                }

                if (sessionArgs != null)
                {
                    sessionArgs.TimeLine["Connection Established"] = DateTime.Now;
                }

                await proxyServer.InvokeServerConnectionCreateEvent(tcpClient);

                stream = new HttpServerStream(tcpClient.GetStream(), proxyServer.BufferPool);

                if (externalProxy != null && (isConnect || isHttps))
                {
                    var authority      = $"{remoteHostName}:{remotePort}".GetByteString();
                    var connectRequest = new ConnectRequest(authority)
                    {
                        IsHttps           = isHttps,
                        RequestUriString8 = authority,
                        HttpVersion       = httpVersion
                    };

                    connectRequest.Headers.AddHeader(KnownHeaders.Connection, KnownHeaders.ConnectionKeepAlive);

                    if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
                    {
                        connectRequest.Headers.AddHeader(HttpHeader.ProxyConnectionKeepAlive);
                        connectRequest.Headers.AddHeader(HttpHeader.GetProxyAuthorizationHeader(externalProxy.UserName, externalProxy.Password));
                    }

                    await stream.WriteRequestAsync(connectRequest, cancellationToken);

                    var httpStatus = await stream.ReadResponseStatus(cancellationToken);

                    if (httpStatus.StatusCode != 200 && !httpStatus.Description.EqualsIgnoreCase("OK") &&
                        !httpStatus.Description.EqualsIgnoreCase("Connection Established"))
                    {
                        throw new Exception("Upstream proxy failed to create a secure tunnel");
                    }

                    await stream.ReadAndIgnoreAllLinesAsync(cancellationToken);
                }

                if (isHttps)
                {
                    var sslStream = new SslStream(stream, false, state.ValidateServerCertificate,
                                                  state.SelectClientCertificate);
                    stream = new HttpServerStream(sslStream, proxyServer.BufferPool);

                    var options = new SslClientAuthenticationOptions
                    {
                        ApplicationProtocols           = applicationProtocols,
                        TargetHost                     = remoteHostName,
                        ClientCertificates             = null !,
                        EnabledSslProtocols            = enabledSslProtocols,
                        CertificateRevocationCheckMode = proxyServer.CheckCertificateRevocation
                    };
                    await sslStream.AuthenticateAsClientAsync(options, cancellationToken);

#if NETSTANDARD2_1
                    negotiatedApplicationProtocol = sslStream.NegotiatedApplicationProtocol;
#endif

                    if (sessionArgs != null)
                    {
                        sessionArgs.TimeLine["HTTPS Established"] = DateTime.Now;
                    }
                }
            }
            catch (IOException ex) when(ex.HResult == unchecked ((int)0x80131620) && retry && enabledSslProtocols >= SslProtocols.Tls11)
            {
                stream?.Dispose();
                tcpClient?.Close();

                enabledSslProtocols = SslProtocols.Tls;
                retry = false;
                goto retry;
            }
            catch (Exception)
            {
                stream?.Dispose();
                tcpClient?.Close();
                throw;
            }

            return(new TcpServerConnection(proxyServer, tcpClient, stream, remoteHostName, remotePort, isHttps,
                                           negotiatedApplicationProtocol, httpVersion, externalProxy, upStreamEndPoint, cacheKey));
        }
예제 #9
0
 public CertificateSelectionEventArgs(RequestStateBase state)
     : base(state)
 {
 }
 internal TunnelConnectSessionEventArgs(RequestStateBase state, ProxyEndPoint endPoint, ConnectRequest connectRequest,
                                        TcpClientConnection clientConnection, HttpClientStream clientStream, CancellationTokenSource cancellationTokenSource)
     : base(state, endPoint, clientStream, connectRequest, connectRequest, cancellationTokenSource)
 {
 }
예제 #11
0
 internal BeforeSslAuthenticateEventArgs(RequestStateBase state, CancellationTokenSource taskCancellationSource, string sniHostName)
     : base(state)
 {
     TaskCancellationSource = taskCancellationSource;
     SniHostName            = sniHostName;
 }
 public ProxyEventArgsBase(RequestStateBase state)
 {
     this.State = state;
 }
 internal static ExtendedProxyState Extended(this RequestStateBase state)
 {
     return((ExtendedProxyState)state);
 }