예제 #1
0
        /// <summary>
        /// Start server
        /// </summary>
        public void Start()
        {
            _proxyServer.BeforeRequest             += OnRequest;
            _proxyServer.BeforeResponse            += OnResponse;
            _proxyServer.GetCustomUpStreamProxyFunc = GetCustomUpStreamProxy;

            // adding endpoints
            _thisEndPoint = new IPEndPoint(!string.IsNullOrEmpty(_proxyConfig.LocalAddress) ? IPAddress.Parse(_proxyConfig.LocalAddress) : Utils.LocalMachineIpAddress, _proxyConfig.LocalPort);
            _proxyServer.AddEndPoint(new ExplicitProxyEndPoint(_thisEndPoint.Address, _thisEndPoint.Port, true));

            // starting the server
            _proxyServer.Start();

            Logger.Log(ProxyType.HttpProxy, _thisEndPoint, _thisEndPoint, "Starting proxy server...");

            // registering external proxies
            foreach (var externalProxyRule in _proxyConfig.ExternalProxyRules)
            {
                var newProxy = new ExternalProxy {
                    HostName = externalProxyRule.ProxyHost,
                    Port     = externalProxyRule.ProxyPort
                };
                if (!string.IsNullOrEmpty(externalProxyRule.ProxyUsername))
                {
                    newProxy.UserName = externalProxyRule.ProxyUsername;
                    newProxy.Password = externalProxyRule.ProxyPassword;
                }

                if (!_externalProxies.ContainsKey(externalProxyRule.ProxyHost + ":" + externalProxyRule.ProxyPort))
                {
                    _externalProxies.Add(externalProxyRule.ProxyHost + ":" + externalProxyRule.ProxyPort, newProxy);
                }
            }
        }
        /// <summary>
        /// Create a Server Connection
        /// </summary>
        /// <param name="args"></param>
        /// <param name="isConnect"></param>
        /// <returns></returns>
        private async Task <TcpConnection> GetServerConnection(SessionEventArgs args, bool isConnect)
        {
            ExternalProxy customUpStreamHttpProxy  = null;
            ExternalProxy customUpStreamHttpsProxy = null;

            if (args.WebSession.Request.IsHttps)
            {
                if (GetCustomUpStreamHttpsProxyFunc != null)
                {
                    customUpStreamHttpsProxy = await GetCustomUpStreamHttpsProxyFunc(args);
                }
            }
            else
            {
                if (GetCustomUpStreamHttpProxyFunc != null)
                {
                    customUpStreamHttpProxy = await GetCustomUpStreamHttpProxyFunc(args);
                }
            }

            args.CustomUpStreamHttpProxyUsed  = customUpStreamHttpProxy;
            args.CustomUpStreamHttpsProxyUsed = customUpStreamHttpsProxy;

            return(await tcpConnectionFactory.CreateClient(this,
                                                           args.WebSession.Request.RequestUri.Host,
                                                           args.WebSession.Request.RequestUri.Port,
                                                           args.WebSession.Request.HttpVersion,
                                                           args.IsHttps, isConnect,
                                                           customUpStreamHttpProxy ?? UpStreamHttpProxy,
                                                           customUpStreamHttpsProxy ?? UpStreamHttpsProxy));
        }
예제 #3
0
        /// <summary>
        ///     Gets the connection cache key.
        /// </summary>
        /// <param name="args">The session event arguments.</param>
        /// <param name="applicationProtocol"></param>
        /// <returns></returns>
        internal async Task <string> GetConnectionCacheKey(ProxyServer server, SessionEventArgsBase args,
                                                           SslApplicationProtocol applicationProtocol)
        {
            List <SslApplicationProtocol> applicationProtocols = null;

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

            ExternalProxy customUpStreamProxy = null;

            bool isHttps = args.IsHttps;

            if (server.GetCustomUpStreamProxyFunc != null)
            {
                customUpStreamProxy = await server.GetCustomUpStreamProxyFunc(args);
            }

            args.CustomUpStreamProxyUsed = customUpStreamProxy;

            return(GetConnectionCacheKey(
                       args.WebSession.Request.RequestUri.Host,
                       args.WebSession.Request.RequestUri.Port,
                       isHttps, applicationProtocols,
                       server, args.WebSession.UpStreamEndPoint ?? server.UpStreamEndPoint,
                       customUpStreamProxy ?? (isHttps ? server.UpStreamHttpsProxy : server.UpStreamHttpProxy)));
        }
예제 #4
0
        /// <summary>
        ///     Gets the connection cache key.
        /// </summary>
        /// <param name="args">The session event arguments.</param>
        /// <param name="applicationProtocol"></param>
        /// <returns></returns>
        private async Task <string> getConnectionCacheKey(SessionEventArgsBase args,
                                                          SslApplicationProtocol applicationProtocol)
        {
            List <SslApplicationProtocol> applicationProtocols = null;

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

            ExternalProxy customUpStreamProxy = null;

            bool isHttps = args.IsHttps;

            if (GetCustomUpStreamProxyFunc != null)
            {
                customUpStreamProxy = await GetCustomUpStreamProxyFunc(args);
            }

            args.CustomUpStreamProxyUsed = customUpStreamProxy;

            return(tcpConnectionFactory.GetConnectionCacheKey(
                       args.WebSession.Request.RequestUri.Host,
                       args.WebSession.Request.RequestUri.Port,
                       isHttps, applicationProtocols,
                       this, args.WebSession.UpStreamEndPoint ?? UpStreamEndPoint,
                       customUpStreamProxy ?? (isHttps ? UpStreamHttpsProxy : UpStreamHttpProxy)));
        }
예제 #5
0
        /// <summary>
        /// implement any cleanup here
        /// </summary>
        public void Dispose()
        {
            httpResponseHandler     = null;
            CustomUpStreamProxyUsed = null;

            WebSession.FinishSession();
        }
        internal string GetConnectionCacheKey(string remoteHostName, int remotePort,
                                              bool isHttps, List <SslApplicationProtocol> applicationProtocols,
                                              IPEndPoint upStreamEndPoint, ExternalProxy externalProxy)
        {
            // http version is ignored since its an application level decision b/w HTTP 1.0/1.1
            // also when doing connect request MS Edge browser sends http 1.0 but uses 1.1 after server sends 1.1 its response.
            // That can create cache miss for same server connection unnecessarily especially when prefetching with Connect.
            // http version 2 is separated using applicationProtocols below.
            var cacheKeyBuilder = new StringBuilder($"{remoteHostName}-{remotePort}-" +
                                                    // when creating Tcp client isConnect won't matter
                                                    $"{isHttps}-");

            if (applicationProtocols != null)
            {
                foreach (var protocol in applicationProtocols.OrderBy(x => x))
                {
                    cacheKeyBuilder.Append($"{protocol}-");
                }
            }

            cacheKeyBuilder.Append(upStreamEndPoint != null
                ? $"{upStreamEndPoint.Address}-{upStreamEndPoint.Port}-"
                : string.Empty);
            cacheKeyBuilder.Append(externalProxy != null ? $"{externalProxy.GetCacheKey()}-" : string.Empty);

            return(cacheKeyBuilder.ToString());
        }
예제 #7
0
        /// <summary>
        /// implement any cleanup here
        /// </summary>
        public void Dispose()
        {
            httpResponseHandler          = null;
            CustomUpStreamHttpProxyUsed  = null;
            CustomUpStreamHttpsProxyUsed = null;

            WebSession.Dispose();
        }
예제 #8
0
        public ExternalProxy GetProxy(Uri destination)
        {
            IList <string> proxies;

            if (GetAutoProxies(destination, out proxies))
            {
                if (proxies == null)
                {
                    return(null);
                }

                string proxyStr = proxies[0];
                int    port     = 80;
                if (proxyStr.Contains(":"))
                {
                    var parts = proxyStr.Split(new[] { ':' }, 2);
                    proxyStr = parts[0];
                    port     = int.Parse(parts[1]);
                }

                // TODO: Apply authorization
                var systemProxy = new ExternalProxy
                {
                    HostName = proxyStr,
                    Port     = port,
                };

                return(systemProxy);
            }

            if (proxy?.IsBypassed(destination) == true)
            {
                return(null);
            }

            var protocolType = ProxyInfo.ParseProtocolType(destination.Scheme);

            if (protocolType.HasValue)
            {
                HttpSystemProxyValue value = null;
                if (ProxyInfo?.Proxies?.TryGetValue(protocolType.Value, out value) == true)
                {
                    var systemProxy = new ExternalProxy
                    {
                        HostName = value.HostName,
                        Port     = value.Port,
                    };

                    return(systemProxy);
                }
            }

            return(null);
        }
예제 #9
0
        /// <summary>
        /// Gets the system up stream proxy
        /// </summary>
        private Task <ExternalProxy> GetCustomUpStreamProxy(SessionEventArgs e)
        {
            try {
                // use a custom external proxy?
                foreach (var proxyRule in _proxyConfig.ExternalProxyRules)
                {
                    var reg = new Regex(proxyRule.UrlMatch, RegexOptions.IgnoreCase);
                    if (reg.Match(e.WebSession.Request.RequestUri.AbsoluteUri).Success)
                    {
                        ExternalProxy proxy = null;

                        // proxyIgnore
                        if (proxyRule.ProxyHost.Equals("NoProxy"))
                        {
                            break;
                        }

                        // try to use the system proxy?
                        if (proxyRule.ProxyHost.Equals("SystemWebProxy"))
                        {
                            var webProxy = WebRequest.GetSystemWebProxy().GetProxy(e.WebSession.Request.RequestUri);
                            proxy = new ExternalProxy {
                                HostName = webProxy.Host,
                                Port     = webProxy.Port
                            };
                        }

                        // use a configured external proxy
                        if (_externalProxies.ContainsKey(proxyRule.ProxyHost + ":" + proxyRule.ProxyPort))
                        {
                            proxy = _externalProxies[proxyRule.ProxyHost + ":" + proxyRule.ProxyPort];
                        }

                        if (proxy != null)
                        {
                            Logger.Log(ProxyType.HttpProxy, e.ClientEndPoint, _thisEndPoint, $"EXTERNALPROXY new connexion ({_proxyServer.ServerConnectionCount}) to {e.WebSession.Request.RequestUri.Host}:{e.WebSession.Request.RequestUri.Port} using {proxy.HostName}:{proxy.Port}");
                            return(Task.FromResult(proxy));
                        }
                    }
                }
            } catch (Exception ex) {
                ErrorHandler.LogErrors(ex, "GetCustomUpStreamProxy");
            }

            return(Task.FromResult(new ExternalProxy {
                HostName = e.WebSession.Request.RequestUri.Host,
                Port = e.WebSession.Request.RequestUri.Port
            }));
        }
예제 #10
0
        /// <summary>
        /// Gets the system up stream proxy.
        /// </summary>
        /// <param name="sessionEventArgs">The <see cref="SessionEventArgs"/> instance containing the event data.</param>
        /// <returns><see cref="ExternalProxy"/> instance containing valid proxy configuration from PAC/WAPD scripts if any exists.</returns>
        private Task <ExternalProxy> GetSystemUpStreamProxy(SessionEventArgs sessionEventArgs)
        {
            // Use built-in WebProxy class to handle PAC/WAPD scripts.
            var systemProxyResolver = new WebProxy();

            var systemProxyUri = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri);

            // TODO: Apply authorization
            var systemProxy = new ExternalProxy
            {
                HostName = systemProxyUri.Host,
                Port     = systemProxyUri.Port
            };

            return(Task.FromResult(systemProxy));
        }
예제 #11
0
        /// <summary>
        ///     Create a server connection.
        /// </summary>
        /// <param name="args">The session event arguments.</param>
        /// <param name="isConnect">Is this a CONNECT request.</param>
        /// <param name="applicationProtocols"></param>
        /// <param name="cancellationToken">The cancellation token for this async task.</param>
        /// <returns></returns>
        internal async Task <TcpServerConnection> GetServerConnection(ProxyServer server, SessionEventArgsBase args, bool isConnect,
                                                                      List <SslApplicationProtocol> applicationProtocols, bool noCache, CancellationToken cancellationToken)
        {
            ExternalProxy customUpStreamProxy = null;

            bool isHttps = args.IsHttps;

            if (server.GetCustomUpStreamProxyFunc != null)
            {
                customUpStreamProxy = await server.GetCustomUpStreamProxyFunc(args);
            }

            args.CustomUpStreamProxyUsed = customUpStreamProxy;

            return(await GetServerConnection(
                       args.WebSession.Request.RequestUri.Host,
                       args.WebSession.Request.RequestUri.Port,
                       args.WebSession.Request.HttpVersion,
                       isHttps, applicationProtocols, isConnect,
                       server, args.WebSession.UpStreamEndPoint ?? server.UpStreamEndPoint,
                       customUpStreamProxy ?? (isHttps ? server.UpStreamHttpsProxy : server.UpStreamHttpProxy),
                       noCache, cancellationToken));
        }
        /// <summary>
        ///     Create a server connection.
        /// </summary>
        /// <param name="args">The session event arguments.</param>
        /// <param name="isConnect">Is this a CONNECT request.</param>
        /// <param name="applicationProtocols"></param>
        /// <param name="cancellationToken">The cancellation token for this async task.</param>
        /// <returns></returns>
        private async Task <TcpServerConnection> GetServerConnection(SessionEventArgsBase args, bool isConnect,
                                                                     List <SslApplicationProtocol> applicationProtocols, CancellationToken cancellationToken)
        {
            ExternalProxy customUpStreamProxy = null;

            bool isHttps = args.IsHttps;

            if (GetCustomUpStreamProxyFunc != null)
            {
                customUpStreamProxy = await GetCustomUpStreamProxyFunc(args);
            }

            args.CustomUpStreamProxyUsed = customUpStreamProxy;

            return(await tcpConnectionFactory.GetClient(
                       args.WebSession.Request.RequestUri.Host,
                       args.WebSession.Request.RequestUri.Port,
                       args.WebSession.Request.HttpVersion,
                       isHttps, applicationProtocols, isConnect,
                       this, args.WebSession.UpStreamEndPoint ?? UpStreamEndPoint,
                       customUpStreamProxy ?? (isHttps ? args.WebSession.Request.UpStreamHttpsProxy ?? UpStreamHttpsProxy : args.WebSession.Request.UpStreamHttpProxy ?? UpStreamHttpProxy),
                       cancellationToken));
        }
예제 #13
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="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="upStreamEndPoint">The local upstream endpoint to make request via.</param>
        /// <param name="externalProxy">The external proxy to make request via.</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, List <SslApplicationProtocol> applicationProtocols, bool isConnect,
                                                                        ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy,
                                                                        CancellationToken cancellationToken)
        {
            bool useUpstreamProxy = false;

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

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

            TcpClient            tcpClient = null;
            CustomBufferedStream stream    = null;

            SslApplicationProtocol negotiatedApplicationProtocol = default;

            try
            {
                tcpClient = new TcpClient(upStreamEndPoint)
                {
                    ReceiveTimeout    = proxyServer.ConnectionTimeOutSeconds * 1000,
                    SendTimeout       = proxyServer.ConnectionTimeOutSeconds * 1000,
                    SendBufferSize    = proxyServer.BufferSize,
                    ReceiveBufferSize = proxyServer.BufferSize
                };

                await proxyServer.InvokeConnectionCreateEvent(tcpClient, false);

                // If this proxy uses another external proxy then create a tunnel request for HTTP/HTTPS connections
                if (useUpstreamProxy)
                {
                    await tcpClient.ConnectAsync(externalProxy.HostName, externalProxy.Port);
                }
                else
                {
                    await tcpClient.ConnectAsync(remoteHostName, remotePort);
                }

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

                if (useUpstreamProxy && (isConnect || isHttps))
                {
                    var writer         = new HttpRequestWriter(stream, proxyServer.BufferPool, proxyServer.BufferSize);
                    var connectRequest = new ConnectRequest
                    {
                        OriginalUrl = $"{remoteHostName}:{remotePort}",
                        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 writer.WriteRequestAsync(connectRequest, cancellationToken : cancellationToken);

                    string httpStatus = await stream.ReadLineAsync(cancellationToken);

                    Response.ParseResponseLine(httpStatus, out _, out int statusCode, out string statusDescription);

                    if (statusCode != 200 && !statusDescription.EqualsIgnoreCase("OK") &&
                        !statusDescription.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, proxyServer.ValidateServerCertificate,
                                                  proxyServer.SelectClientCertificate);
                    stream = new CustomBufferedStream(sslStream, proxyServer.BufferPool, proxyServer.BufferSize);

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

#if NETCOREAPP2_1
                    negotiatedApplicationProtocol = sslStream.NegotiatedApplicationProtocol;
#endif
                }
            }
            catch (Exception)
            {
                stream?.Dispose();
                tcpClient?.Close();
                throw;
            }

            return(new TcpServerConnection(proxyServer, tcpClient)
            {
                UpStreamProxy = externalProxy,
                UpStreamEndPoint = upStreamEndPoint,
                HostName = remoteHostName,
                Port = remotePort,
                IsHttps = isHttps,
                NegotiatedApplicationProtocol = negotiatedApplicationProtocol,
                UseUpstreamProxy = useUpstreamProxy,
                StreamWriter = new HttpRequestWriter(stream, proxyServer.BufferPool, proxyServer.BufferSize),
                Stream = stream,
                Version = httpVersion
            });
        }
예제 #14
0
        /// <summary>
        ///     Gets a TCP connection to server from connection pool.
        /// </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="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="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(string remoteHostName, int remotePort,
                                                                      Version httpVersion, bool isHttps, List <SslApplicationProtocol> applicationProtocols, bool isConnect,
                                                                      ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy,
                                                                      bool noCache, CancellationToken cancellationToken)
        {
            var cacheKey = GetConnectionCacheKey(remoteHostName, remotePort,
                                                 isHttps, applicationProtocols,
                                                 proxyServer, upStreamEndPoint, externalProxy);

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

                            if (recentConnection.LastAccess > cutOff &&
                                isGoodConnection(recentConnection.TcpClient))
                            {
                                return(recentConnection);
                            }

                            disposalBag.Add(recentConnection);
                        }
                    }
                }
            }

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

            connection.CacheKey = cacheKey;

            return(connection);
        }
        /// <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="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="session">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="cancellationToken">The cancellation token for this async task.</param>
        /// <returns></returns>
        private async Task <TcpServerConnection> createServerConnection(string remoteHostName, int remotePort,
                                                                        Version httpVersion, bool isHttps, List <SslApplicationProtocol> applicationProtocols, bool isConnect,
                                                                        ProxyServer proxyServer, SessionEventArgsBase session, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy,
                                                                        CancellationToken cancellationToken)
        {
            //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 useUpstreamProxy = false;

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

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

            TcpClient            tcpClient = null;
            CustomBufferedStream stream    = null;

            SslApplicationProtocol negotiatedApplicationProtocol = default;

            try
            {
                tcpClient = new TcpClient(upStreamEndPoint)
                {
                    NoDelay        = proxyServer.NoDelay,
                    ReceiveTimeout = proxyServer.ConnectionTimeOutSeconds * 1000,
                    SendTimeout    = proxyServer.ConnectionTimeOutSeconds * 1000,
                    LingerState    = new LingerOption(true, proxyServer.TcpTimeWaitSeconds)
                };

                //linux has a bug with socket reuse in .net core.
                if (proxyServer.ReuseSocket && RunTime.IsWindows)
                {
                    tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
                }

                var hostname = useUpstreamProxy ? externalProxy.HostName : remoteHostName;
                var port     = useUpstreamProxy ? 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 (session != null)
                {
                    session.TimeLine["Dns Resolved"] = DateTime.Now;
                }

                for (int i = 0; i < ipAddresses.Length; i++)
                {
                    try
                    {
                        await tcpClient.ConnectAsync(ipAddresses[i], port);

                        break;
                    }
                    catch (Exception e)
                    {
                        if (i == ipAddresses.Length - 1)
                        {
                            throw new Exception($"Could not establish connection to {hostname}", e);
                        }
                    }
                }

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

                await proxyServer.InvokeConnectionCreateEvent(tcpClient, false);

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

                if (useUpstreamProxy && (isConnect || isHttps))
                {
                    var writer         = new HttpRequestWriter(stream, proxyServer.BufferPool, proxyServer.BufferSize);
                    var connectRequest = new ConnectRequest
                    {
                        OriginalUrl = $"{remoteHostName}:{remotePort}",
                        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 writer.WriteRequestAsync(connectRequest, cancellationToken : cancellationToken);

                    string httpStatus = await stream.ReadLineAsync(cancellationToken);

                    Response.ParseResponseLine(httpStatus, out _, out int statusCode, out string statusDescription);

                    if (statusCode != 200 && !statusDescription.EqualsIgnoreCase("OK") &&
                        !statusDescription.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, proxyServer.ValidateServerCertificate,
                                                  proxyServer.SelectClientCertificate);
                    stream = new CustomBufferedStream(sslStream, proxyServer.BufferPool, proxyServer.BufferSize);

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

#if NETCOREAPP2_1
                    negotiatedApplicationProtocol = sslStream.NegotiatedApplicationProtocol;
#endif

                    if (session != null)
                    {
                        session.TimeLine["HTTPS Established"] = DateTime.Now;
                    }
                }
            }
            catch (Exception)
            {
                stream?.Dispose();
                tcpClient?.Close();
                throw;
            }

            return(new TcpServerConnection(proxyServer, tcpClient)
            {
                UpStreamProxy = externalProxy,
                UpStreamEndPoint = upStreamEndPoint,
                HostName = remoteHostName,
                Port = remotePort,
                IsHttps = isHttps,
                NegotiatedApplicationProtocol = negotiatedApplicationProtocol,
                UseUpstreamProxy = useUpstreamProxy,
                StreamWriter = new HttpRequestWriter(stream, proxyServer.BufferPool, proxyServer.BufferSize),
                Stream = stream,
                Version = httpVersion
            });
        }
        /// <summary>
        ///  Creates a TCP connection to server
        /// </summary>
        /// <param name="server"></param>
        /// <param name="remoteHostName"></param>
        /// <param name="remotePort"></param>
        /// <param name="httpVersion"></param>
        /// <param name="isHttps"></param>
        /// <param name="isConnect"></param>
        /// <param name="upStreamEndPoint"></param>
        /// <param name="externalProxy"></param>
        /// <returns></returns>
        internal async Task <TcpConnection> CreateClient(ProxyServer server,
                                                         string remoteHostName, int remotePort, Version httpVersion, bool isHttps,
                                                         bool isConnect, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy)
        {
            bool useUpstreamProxy = false;

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

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

            TcpClient            client = null;
            CustomBufferedStream stream = null;

            try
            {
                client = new TcpClient(upStreamEndPoint);

                //If this proxy uses another external proxy then create a tunnel request for HTTP/HTTPS connections
                if (useUpstreamProxy)
                {
                    await client.ConnectAsync(externalProxy.HostName, externalProxy.Port);
                }
                else
                {
                    await client.ConnectAsync(remoteHostName, remotePort);
                }

                stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);

                if (useUpstreamProxy && (isConnect || isHttps))
                {
                    var writer = new HttpRequestWriter(stream, server.BufferSize);
                    await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");

                    await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");

                    await writer.WriteLineAsync($"{KnownHeaders.Connection}: {KnownHeaders.ConnectionKeepAlive}");

                    if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
                    {
                        await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer);

                        await writer.WriteLineAsync(KnownHeaders.ProxyAuthorization + ": Basic " +
                                                    Convert.ToBase64String(Encoding.UTF8.GetBytes(
                                                                               externalProxy.UserName + ":" + externalProxy.Password)));
                    }

                    await writer.WriteLineAsync();

                    using (var reader = new CustomBinaryReader(stream, server.BufferSize))
                    {
                        string result = await reader.ReadLineAsync();

                        if (!new[] { "200 OK", "connection established" }.Any(s => result.ContainsIgnoreCase(s)))
                        {
                            throw new Exception("Upstream proxy failed to create a secure tunnel");
                        }

                        await reader.ReadAndIgnoreAllLinesAsync();
                    }
                }

                if (isHttps)
                {
                    var sslStream = new SslStream(stream, false, server.ValidateServerCertificate, server.SelectClientCertificate);
                    stream = new CustomBufferedStream(sslStream, server.BufferSize);

                    await sslStream.AuthenticateAsClientAsync(remoteHostName, null, server.SupportedSslProtocols, server.CheckCertificateRevocation);
                }

                client.ReceiveTimeout = server.ConnectionTimeOutSeconds * 1000;
                client.SendTimeout    = server.ConnectionTimeOutSeconds * 1000;
            }
            catch (Exception)
            {
                stream?.Dispose();
                client?.Close();
                throw;
            }

            return(new TcpConnection(server)
            {
                UpStreamProxy = externalProxy,
                UpStreamEndPoint = upStreamEndPoint,
                HostName = remoteHostName,
                Port = remotePort,
                IsHttps = isHttps,
                UseUpstreamProxy = useUpstreamProxy,
                TcpClient = client,
                StreamReader = new CustomBinaryReader(stream, server.BufferSize),
                StreamWriter = new HttpRequestWriter(stream, server.BufferSize),
                Stream = stream,
                Version = httpVersion
            });
        }
예제 #17
0
        /// <summary>
        /// Create connection to a particular host/port optionally with SSL and a particular HTTP version
        /// </summary>
        /// <param name="hostname"></param>
        /// <param name="port"></param>
        /// <param name="isHttps"></param>
        /// <param name="version"></param>
        /// <returns></returns>
        private async Task <TcpConnectionCache> CreateClient(string hostname, int port, bool isHttps, Version version,
                                                             ExternalProxy upStreamHttpProxy, ExternalProxy upStreamHttpsProxy, int bufferSize, SslProtocols supportedSslProtocols,
                                                             RemoteCertificateValidationCallback remoteCertificateValidationCallBack, LocalCertificateSelectionCallback localCertificateSelectionCallback)
        {
            TcpClient client;
            Stream    stream;

            if (isHttps)
            {
                SslStream sslStream = null;

                //If this proxy uses another external proxy then create a tunnel request for HTTPS connections
                if (upStreamHttpsProxy != null)
                {
                    client = new TcpClient(upStreamHttpsProxy.HostName, upStreamHttpsProxy.Port);
                    stream = (Stream)client.GetStream();

                    using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true))
                    {
                        await writer.WriteLineAsync(string.Format("CONNECT {0}:{1} {2}", hostname, port, version));

                        await writer.WriteLineAsync(string.Format("Host: {0}:{1}", hostname, port));

                        await writer.WriteLineAsync("Connection: Keep-Alive");

                        await writer.WriteLineAsync();

                        await writer.FlushAsync();

                        writer.Close();
                    }

                    using (var reader = new CustomBinaryReader(stream))
                    {
                        var result = await reader.ReadLineAsync();

                        if (!result.ToLower().Contains("200 connection established"))
                        {
                            throw new Exception("Upstream proxy failed to create a secure tunnel");
                        }

                        await reader.ReadAllLinesAsync();
                    }
                }
                else
                {
                    client = new TcpClient(hostname, port);
                    stream = (Stream)client.GetStream();
                }

                try
                {
                    sslStream = new SslStream(stream, true, remoteCertificateValidationCallBack,
                                              localCertificateSelectionCallback);
                    await sslStream.AuthenticateAsClientAsync(hostname, null, supportedSslProtocols, false);

                    stream = (Stream)sslStream;
                }
                catch
                {
                    if (sslStream != null)
                    {
                        sslStream.Dispose();
                    }
                    throw;
                }
            }
            else
            {
                if (upStreamHttpProxy != null)
                {
                    client = new TcpClient(upStreamHttpProxy.HostName, upStreamHttpProxy.Port);
                    stream = (Stream)client.GetStream();
                }
                else
                {
                    client = new TcpClient(hostname, port);
                    stream = (Stream)client.GetStream();
                }
            }

            return(new TcpConnectionCache()
            {
                HostName = hostname,
                port = port,
                IsHttps = isHttps,
                TcpClient = client,
                StreamReader = new CustomBinaryReader(stream),
                Stream = stream,
                Version = version
            });
        }
예제 #18
0
        private async Task HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args, ExternalProxy customUpStreamHttpProxy, ExternalProxy customUpStreamHttpsProxy, bool CloseConnection)
        {
            try
            {
                if (connection == null)
                {
                    if (args.WebSession.Request.RequestUri.Scheme == "http")
                    {
                        if (GetCustomUpStreamHttpProxyFunc != null)
                        {
                            customUpStreamHttpProxy = await GetCustomUpStreamHttpProxyFunc(args).ConfigureAwait(false);
                        }
                    }
                    else
                    {
                        if (GetCustomUpStreamHttpsProxyFunc != null)
                        {
                            customUpStreamHttpsProxy = await GetCustomUpStreamHttpsProxyFunc(args).ConfigureAwait(false);
                        }
                    }

                    args.CustomUpStreamHttpProxyUsed  = customUpStreamHttpProxy;
                    args.CustomUpStreamHttpsProxyUsed = customUpStreamHttpsProxy;

                    connection = await tcpConnectionFactory.CreateClient(BUFFER_SIZE, ConnectionTimeOutSeconds,
                                                                         args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.WebSession.Request.HttpVersion,
                                                                         args.IsHttps, SupportedSslProtocols,
                                                                         new RemoteCertificateValidationCallback(ValidateServerCertificate),
                                                                         new LocalCertificateSelectionCallback(SelectClientCertificate),
                                                                         customUpStreamHttpProxy ?? UpStreamHttpProxy, customUpStreamHttpsProxy ?? UpStreamHttpsProxy, args.ProxyClient.ClientStream, UpStreamEndPoint);
                }

                args.WebSession.Request.RequestLocked = true;

                //If request was cancelled by user then dispose the client
                if (args.WebSession.Request.CancelRequest)
                {
                    Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args);
                    return;
                }

                //if expect continue is enabled then send the headers first
                //and see if server would return 100 conitinue
                if (args.WebSession.Request.ExpectContinue)
                {
                    args.WebSession.SetConnection(connection);
                    await args.WebSession.SendRequest(Enable100ContinueBehaviour);
                }

                //If 100 continue was the response inform that to the client
                if (Enable100ContinueBehaviour)
                {
                    if (args.WebSession.Request.Is100Continue)
                    {
                        await WriteResponseStatus(args.WebSession.Response.HttpVersion, "100",
                                                  "Continue", args.ProxyClient.ClientStreamWriter);

                        await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
                    }
                    else if (args.WebSession.Request.ExpectationFailed)
                    {
                        await WriteResponseStatus(args.WebSession.Response.HttpVersion, "417",
                                                  "Expectation Failed", args.ProxyClient.ClientStreamWriter);

                        await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
                    }
                }

                //If expect continue is not enabled then set the connectio and send request headers
                if (!args.WebSession.Request.ExpectContinue)
                {
                    args.WebSession.SetConnection(connection);
                    await args.WebSession.SendRequest(Enable100ContinueBehaviour);
                }

                //If request was modified by user
                if (args.WebSession.Request.RequestBodyRead)
                {
                    if (args.WebSession.Request.ContentEncoding != null)
                    {
                        args.WebSession.Request.RequestBody = await GetCompressedResponseBody(args.WebSession.Request.ContentEncoding, args.WebSession.Request.RequestBody);
                    }
                    //chunked send is not supported as of now
                    args.WebSession.Request.ContentLength = args.WebSession.Request.RequestBody.Length;

                    var newStream = args.WebSession.ServerConnection.Stream;
                    await newStream.WriteAsync(args.WebSession.Request.RequestBody, 0, args.WebSession.Request.RequestBody.Length);
                }
                else
                {
                    if (!args.WebSession.Request.ExpectationFailed)
                    {
                        //If its a post/put/patch request, then read the client html body and send it to server
                        var method = args.WebSession.Request.Method.ToUpper();
                        if (method == "POST" || method == "PUT" || method == "PATCH")
                        {
                            await SendClientRequestBody(args);
                        }
                    }
                }

                //If not expectation failed response was returned by server then parse response
                if (!args.WebSession.Request.ExpectationFailed)
                {
                    await HandleHttpSessionResponse(args);
                }

                //if connection is closing exit
                if (args.WebSession.Response.ResponseKeepAlive == false)
                {
                    Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args);
                    return;
                }
            }
            catch (Exception e)
            {
                ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request (internal)", e, args));
                Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args);
                return;
            }

            if (CloseConnection)
            {
                //dispose
                connection?.Dispose();
            }
        }
예제 #19
0
        /// <summary>
        ///     Gets a TCP connection to server from connection pool.
        /// </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="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="upStreamEndPoint">The local upstream endpoint to make request via.</param>
        /// <param name="externalProxy">The external proxy to make request via.</param>
        /// <param name="cancellationToken">The cancellation token for this async task.</param>
        /// <returns></returns>
        internal async Task <TcpServerConnection> GetClient(string remoteHostName, int remotePort,
                                                            Version httpVersion, bool isHttps, List <SslApplicationProtocol> applicationProtocols, bool isConnect,
                                                            ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy,
                                                            CancellationToken cancellationToken)
        {
            string cacheKey = null;

            var cacheKeyBuilder = new StringBuilder($"{remoteHostName}-{remotePort}" +
                                                    $"-{(httpVersion == null ? string.Empty : httpVersion.ToString())}" +
                                                    $"-{isHttps}-{isConnect}-");

            if (applicationProtocols != null)
            {
                foreach (var protocol in applicationProtocols)
                {
                    cacheKeyBuilder.Append($"{protocol}-");
                }
            }

            cacheKeyBuilder.Append(upStreamEndPoint != null
                ? $"{upStreamEndPoint.Address}-{upStreamEndPoint.Port}-"
                : string.Empty);
            cacheKeyBuilder.Append(externalProxy != null ? $"{externalProxy.GetCacheKey()}-" : string.Empty);

            cacheKey = cacheKeyBuilder.ToString();

            if (proxyServer.EnableConnectionPool)
            {
                if (cache.TryGetValue(cacheKey, out var existingConnections))
                {
                    while (existingConnections.TryDequeue(out var recentConnection))
                    {
                        //+3 seconds for potential delay after getting connection
                        var cutOff = DateTime.Now.AddSeconds((-1 * proxyServer.ConnectionTimeOutSeconds) + 3);

                        if (recentConnection.LastAccess > cutOff &&
                            IsGoodConnection(recentConnection.TcpClient))
                        {
                            return(recentConnection);
                        }

                        disposalBag.Add(recentConnection);
                    }
                }
            }

            var connection = await CreateClient(remoteHostName, remotePort, httpVersion, isHttps,
                                                applicationProtocols, isConnect, proxyServer, upStreamEndPoint, externalProxy, cancellationToken);

            connection.CacheKey = cacheKey;

            return(connection);
        }
        public async Task Nested_Proxy_Farm_Without_Connection_Cache_Should_Not_Hang()
        {
            var rnd = new Random();

            var testSuite = new TestSuite();

            var server = testSuite.GetServer();

            server.HandleRequest((context) =>
            {
                return(context.Response.WriteAsync("I am server. I received your greetings."));
            });

            var proxies2 = new List <ProxyServer>();

            //create a level 2 upstream proxy farm that forwards to server
            for (int i = 0; i < 10; i++)
            {
                var proxy2 = testSuite.GetProxy();
                proxy2.ProxyBasicAuthenticateFunc += (_, _, _) =>
                {
                    return(Task.FromResult(true));
                };

                proxies2.Add(proxy2);
            }

            var proxies1 = new List <ProxyServer>();

            //create a level 1 upstream proxy farm that forwards to level 2 farm
            for (int i = 0; i < 10; i++)
            {
                var proxy1 = testSuite.GetProxy();
                proxy1.EnableConnectionPool = false;
                var proxy2 = proxies2[rnd.Next() % proxies2.Count];

                proxy1.GetCustomUpStreamProxyFunc += async(_) =>
                {
                    var proxy = new ExternalProxy()
                    {
                        HostName  = "localhost",
                        Port      = proxy2.ProxyEndPoints[0].Port,
                        ProxyType = ExternalProxyType.Http,
                        UserName  = "******",
                        Password  = "******"
                    };

                    return(await Task.FromResult(proxy));
                };

                proxies1.Add(proxy1);
            }

            var tasks = new List <Task>();

            //send multiple concurrent requests from client => proxy farm 1 => proxy farm 2 => server
            for (int j = 0; j < 10_000; j++)
            {
                var task = Task.Run(async() =>
                {
                    try
                    {
                        var proxy        = proxies1[rnd.Next() % proxies1.Count];
                        using var client = testSuite.GetClient(proxy);

                        //tests should not keep hanging for 30 mins.
                        client.Timeout = TimeSpan.FromMinutes(30);
                        await client.PostAsync(new Uri(server.ListeningHttpsUrl),
                                               new StringContent("hello server. I am a client."));
                    }
                    //if error is thrown because of server getting overloaded its okay.
                    //But client.PostAsync should'nt hang in all cases.
                    catch { }
                });

                tasks.Add(task);
            }

            await Task.WhenAll(tasks);
        }
예제 #21
0
        /// <summary>
        /// Get a TcpConnection to the specified host, port optionally HTTPS and a particular HTTP version
        /// </summary>
        /// <param name="hostname"></param>
        /// <param name="port"></param>
        /// <param name="isHttps"></param>
        /// <param name="version"></param>
        /// <returns></returns>
        internal async Task <TcpConnectionCache> GetClient(string hostname, int port, bool isHttps, Version version,
                                                           ExternalProxy upStreamHttpProxy, ExternalProxy upStreamHttpsProxy, int bufferSize, SslProtocols supportedSslProtocols,
                                                           RemoteCertificateValidationCallback remoteCertificateValidationCallBack, LocalCertificateSelectionCallback localCertificateSelectionCallback)
        {
            List <TcpConnectionCache> cachedConnections = null;
            TcpConnectionCache        cached            = null;

            //Get a unique string to identify this connection
            var key = GetConnectionKey(hostname, port, isHttps, version);

            while (true)
            {
                await connectionAccessLock.WaitAsync();

                try
                {
                    connectionCache.TryGetValue(key, out cachedConnections);

                    if (cachedConnections != null && cachedConnections.Count > 0)
                    {
                        cached = cachedConnections.First();
                        cachedConnections.Remove(cached);
                    }
                    else
                    {
                        cached = null;
                    }
                }
                finally { connectionAccessLock.Release(); }

                if (cached != null && !cached.TcpClient.Client.IsConnected())
                {
                    cached.TcpClient.Client.Dispose();
                    cached.TcpClient.Close();
                    continue;
                }

                if (cached == null)
                {
                    break;
                }
            }


            if (cached == null)
            {
                cached = await CreateClient(hostname, port, isHttps, version, upStreamHttpProxy, upStreamHttpsProxy, bufferSize, supportedSslProtocols,
                                            remoteCertificateValidationCallBack, localCertificateSelectionCallback);
            }

            if (cachedConnections == null || cachedConnections.Count() <= 2)
            {
                var task = CreateClient(hostname, port, isHttps, version, upStreamHttpProxy, upStreamHttpsProxy, bufferSize, supportedSslProtocols,
                                        remoteCertificateValidationCallBack, localCertificateSelectionCallback)
                           .ContinueWith(async(x) => { if (x.Status == TaskStatus.RanToCompletion)
                                                       {
                                                           await ReleaseClient(x.Result);
                                                       }
                                         });
            }

            return(cached);
        }
예제 #22
0
        /// <summary>
        /// This is the core request handler method for a particular connection from client
        /// </summary>
        /// <param name="client"></param>
        /// <param name="httpCmd"></param>
        /// <param name="clientStream"></param>
        /// <param name="clientStreamReader"></param>
        /// <param name="clientStreamWriter"></param>
        /// <param name="httpsHostName"></param>
        /// <returns></returns>
        private async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
                                                    CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName, ProxyEndPoint endPoint, List <HttpHeader> connectHeaders, ExternalProxy customUpStreamHttpProxy = null, ExternalProxy customUpStreamHttpsProxy = null)
        {
            TcpConnection connection = null;

            //Loop through each subsequest request on this particular client connection
            //(assuming HTTP connection is kept alive by client)
            while (true)
            {
                if (string.IsNullOrEmpty(httpCmd))
                {
                    Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
                    break;
                }

                var args = new SessionEventArgs(BUFFER_SIZE, HandleHttpSessionResponse);
                args.ProxyClient.TcpClient     = client;
                args.WebSession.ConnectHeaders = connectHeaders;

                args.WebSession.ProcessId = new Lazy <int>(() =>
                {
                    var remoteEndPoint = (IPEndPoint)args.ProxyClient.TcpClient.Client.RemoteEndPoint;

                    //If client is localhost get the process id
                    if (NetworkHelper.IsLocalIpAddress(remoteEndPoint.Address))
                    {
                        return(NetworkHelper.GetProcessIdFromPort(remoteEndPoint.Port, endPoint.IpV6Enabled));
                    }

                    //can't access process Id of remote request from remote machine
                    return(-1);
                });
                try
                {
                    //break up the line into three components (method, remote URL & Http Version)
                    var httpCmdSplit = httpCmd.Split(ProxyConstants.SpaceSplit, 3);

                    var httpMethod = httpCmdSplit[0];

                    //find the request HTTP version
                    Version httpVersion = new Version(1, 1);
                    if (httpCmdSplit.Length == 3)
                    {
                        var httpVersionString = httpCmdSplit[2].ToLower().Trim();

                        if (httpVersionString == "http/1.0")
                        {
                            httpVersion = new Version(1, 0);
                        }
                    }


                    //Read the request headers in to unique and non-unique header collections
                    string tmpLine;
                    while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync()))
                    {
                        var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);

                        var newHeader = new HttpHeader(header[0], header[1]);

                        //if header exist in non-unique header collection add it there
                        if (args.WebSession.Request.NonUniqueRequestHeaders.ContainsKey(newHeader.Name))
                        {
                            args.WebSession.Request.NonUniqueRequestHeaders[newHeader.Name].Add(newHeader);
                        }
                        //if header is alread in unique header collection then move both to non-unique collection
                        else if (args.WebSession.Request.RequestHeaders.ContainsKey(newHeader.Name))
                        {
                            var existing = args.WebSession.Request.RequestHeaders[newHeader.Name];

                            var nonUniqueHeaders = new List <HttpHeader>();

                            nonUniqueHeaders.Add(existing);
                            nonUniqueHeaders.Add(newHeader);

                            args.WebSession.Request.NonUniqueRequestHeaders.Add(newHeader.Name, nonUniqueHeaders);
                            args.WebSession.Request.RequestHeaders.Remove(newHeader.Name);
                        }
                        //add to unique header collection
                        else
                        {
                            args.WebSession.Request.RequestHeaders.Add(newHeader.Name, newHeader);
                        }
                    }

                    var httpRemoteUri = new Uri(httpsHostName == null ? httpCmdSplit[1]
                        : (string.Concat("https://", args.WebSession.Request.Host == null ?
                                         httpsHostName : args.WebSession.Request.Host, httpCmdSplit[1])));

                    args.WebSession.Request.RequestUri = httpRemoteUri;

                    args.WebSession.Request.Method      = httpMethod.Trim().ToUpper();
                    args.WebSession.Request.HttpVersion = httpVersion;
                    args.ProxyClient.ClientStream       = clientStream;
                    args.ProxyClient.ClientStreamReader = clientStreamReader;
                    args.ProxyClient.ClientStreamWriter = clientStreamWriter;

                    if (httpsHostName == null && (await CheckAuthorization(clientStreamWriter, args.WebSession.Request.RequestHeaders.Values) == false))
                    {
                        Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
                        break;
                    }

                    PrepareRequestHeaders(args.WebSession.Request.RequestHeaders, args.WebSession);
                    args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority;

                    //If user requested interception do it
                    if (BeforeRequest != null)
                    {
                        Delegate[] invocationList = BeforeRequest.GetInvocationList();
                        Task[]     handlerTasks   = new Task[invocationList.Length];

                        for (int i = 0; i < invocationList.Length; i++)
                        {
                            handlerTasks[i] = ((Func <object, SessionEventArgs, Task>)invocationList[i])(null, args);
                        }

                        await Task.WhenAll(handlerTasks);
                    }

                    //if upgrading to websocket then relay the requet without reading the contents
                    if (args.WebSession.Request.UpgradeToWebSocket)
                    {
                        await TcpHelper.SendRaw(BUFFER_SIZE, ConnectionTimeOutSeconds, httpRemoteUri.Host, httpRemoteUri.Port,
                                                httpCmd, httpVersion, args.WebSession.Request.RequestHeaders, args.IsHttps,
                                                SupportedSslProtocols, new RemoteCertificateValidationCallback(ValidateServerCertificate),
                                                new LocalCertificateSelectionCallback(SelectClientCertificate),
                                                clientStream, tcpConnectionFactory, UpStreamEndPoint);

                        Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
                        break;
                    }

                    //construct the web request that we are going to issue on behalf of the client.
                    await HandleHttpSessionRequestInternal(connection, args, customUpStreamHttpProxy, customUpStreamHttpsProxy, false).ConfigureAwait(false);


                    if (args.WebSession.Request.CancelRequest)
                    {
                        break;
                    }

                    //if connection is closing exit
                    if (args.WebSession.Response.ResponseKeepAlive == false)
                    {
                        break;
                    }

                    // read the next request
                    httpCmd = await clientStreamReader.ReadLineAsync();
                }
                catch (Exception e)
                {
                    ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request", e, args));
                    Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
                    break;
                }
            }

            if (connection != null)
            {
                //dispose
                connection.Dispose();
            }
        }
        /// <summary>
        /// Creates a TCP connection to server
        /// </summary>
        /// <param name="bufferSize"></param>
        /// <param name="connectionTimeOutSeconds"></param>
        /// <param name="remoteHostName"></param>
        /// <param name="httpCmd"></param>
        /// <param name="httpVersion"></param>
        /// <param name="isHttps"></param>
        /// <param name="remotePort"></param>
        /// <param name="supportedSslProtocols"></param>
        /// <param name="remoteCertificateValidationCallback"></param>
        /// <param name="localCertificateSelectionCallback"></param>
        /// <param name="externalHttpProxy"></param>
        /// <param name="externalHttpsProxy"></param>
        /// <param name="clientStream"></param>
        /// <param name="upStreamEndPoint"></param>
        /// <returns></returns>
        internal async Task <TcpConnection> CreateClient(int bufferSize, int connectionTimeOutSeconds,
                                                         string remoteHostName, int remotePort, Version httpVersion,
                                                         bool isHttps, SslProtocols supportedSslProtocols,
                                                         RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
                                                         ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy,
                                                         Stream clientStream, EndPoint upStreamEndPoint)
        {
            TcpClient client;
            Stream    stream;

            if (isHttps)
            {
                SslStream sslStream = null;

                //If this proxy uses another external proxy then create a tunnel request for HTTPS connections
                if (externalHttpsProxy != null && externalHttpsProxy.HostName != remoteHostName)
                {
                    client = new TcpClient();
                    client.Client.Bind(upStreamEndPoint);
                    await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port);

                    stream = client.GetStream();

                    using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true)
                    {
                        NewLine = ProxyConstants.NewLine
                    })
                    {
                        await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");

                        await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");

                        await writer.WriteLineAsync("Connection: Keep-Alive");

                        if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null)
                        {
                            await writer.WriteLineAsync("Proxy-Connection: keep-alive");

                            await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password)));
                        }
                        await writer.WriteLineAsync();

                        await writer.FlushAsync();

                        writer.Close();
                    }

                    using (var reader = new CustomBinaryReader(stream))
                    {
                        var result = await reader.ReadLineAsync();


                        if (!new string[] { "200 OK", "connection established" }.Any(s => result.ToLower().Contains(s.ToLower())))
                        {
                            throw new Exception("Upstream proxy failed to create a secure tunnel");
                        }

                        await reader.ReadAllLinesAsync();
                    }
                }
                else
                {
                    client = new TcpClient();
                    client.Client.Bind(upStreamEndPoint);
                    await client.ConnectAsync(remoteHostName, remotePort);

                    stream = client.GetStream();
                }

                try
                {
                    sslStream = new SslStream(stream, true, remoteCertificateValidationCallback,
                                              localCertificateSelectionCallback);

                    await sslStream.AuthenticateAsClientAsync(remoteHostName, null, supportedSslProtocols, false);

                    stream = sslStream;
                }
                catch
                {
                    sslStream?.Dispose();

                    throw;
                }
            }
            else
            {
                if (externalHttpProxy != null && externalHttpProxy.HostName != remoteHostName)
                {
                    client = new TcpClient();
                    client.Client.Bind(upStreamEndPoint);
                    await client.ConnectAsync(externalHttpProxy.HostName, externalHttpProxy.Port);

                    stream = client.GetStream();
                }
                else
                {
                    client = new TcpClient();
                    client.Client.Bind(upStreamEndPoint);
                    await client.ConnectAsync(remoteHostName, remotePort);

                    stream = client.GetStream();
                }
            }

            client.ReceiveTimeout = connectionTimeOutSeconds * 1000;
            client.SendTimeout    = connectionTimeOutSeconds * 1000;

            stream.ReadTimeout  = connectionTimeOutSeconds * 1000;
            stream.WriteTimeout = connectionTimeOutSeconds * 1000;


            return(new TcpConnection()
            {
                UpStreamHttpProxy = externalHttpProxy,
                UpStreamHttpsProxy = externalHttpsProxy,
                HostName = remoteHostName,
                Port = remotePort,
                IsHttps = isHttps,
                TcpClient = client,
                StreamReader = new CustomBinaryReader(stream),
                Stream = stream,
                Version = httpVersion
            });
        }
예제 #24
0
        /// <summary>
        /// Creates a TCP connection to server
        /// </summary>
        /// <param name="server"></param>
        /// <param name="remoteHostName"></param>
        /// <param name="remotePort"></param>
        /// <param name="httpVersion"></param>
        /// <param name="isHttps"></param>
        /// <param name="externalHttpProxy"></param>
        /// <param name="externalHttpsProxy"></param>
        /// <returns></returns>
        internal async Task <TcpConnection> CreateClient(ProxyServer server,
                                                         string remoteHostName, int remotePort, Version httpVersion,
                                                         bool isHttps,
                                                         ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy)
        {
            bool useHttpProxy = false;

            //check if external proxy is set for HTTP
            if (!isHttps && externalHttpProxy != null &&
                !(externalHttpProxy.HostName == remoteHostName &&
                  externalHttpProxy.Port == remotePort))
            {
                useHttpProxy = true;

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

            bool useHttpsProxy = false;

            //check if external proxy is set for HTTPS
            if (isHttps && externalHttpsProxy != null &&
                !(externalHttpsProxy.HostName == remoteHostName &&
                  externalHttpsProxy.Port == remotePort))
            {
                useHttpsProxy = true;

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

            TcpClient            client = null;
            CustomBufferedStream stream = null;

            try
            {
                if (isHttps)
                {
                    //If this proxy uses another external proxy then create a tunnel request for HTTPS connections
                    if (useHttpsProxy)
                    {
                        client = new TcpClient(server.UpStreamEndPoint);
                        await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port);

                        stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);

                        using (var writer = new StreamWriter(stream, Encoding.ASCII, server.BufferSize, true)
                        {
                            NewLine = ProxyConstants.NewLine
                        })
                        {
                            await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");

                            await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");

                            await writer.WriteLineAsync("Connection: Keep-Alive");

                            if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null)
                            {
                                await writer.WriteLineAsync("Proxy-Connection: keep-alive");

                                await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password)));
                            }
                            await writer.WriteLineAsync();

                            await writer.FlushAsync();

                            writer.Close();
                        }

                        using (var reader = new CustomBinaryReader(stream, server.BufferSize))
                        {
                            var result = await reader.ReadLineAsync();

                            if (!new[] { "200 OK", "connection established" }.Any(s => result.ContainsIgnoreCase(s)))
                            {
                                throw new Exception("Upstream proxy failed to create a secure tunnel");
                            }

                            await reader.ReadAndIgnoreAllLinesAsync();
                        }
                    }
                    else
                    {
                        client = new TcpClient(server.UpStreamEndPoint);
                        await client.ConnectAsync(remoteHostName, remotePort);

                        stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
                    }

                    var sslStream = new SslStream(stream, false, server.ValidateServerCertificate, server.SelectClientCertificate);
                    stream = new CustomBufferedStream(sslStream, server.BufferSize);

                    await sslStream.AuthenticateAsClientAsync(remoteHostName, null, server.SupportedSslProtocols, server.CheckCertificateRevocation);
                }
                else
                {
                    if (useHttpProxy)
                    {
                        client = new TcpClient(server.UpStreamEndPoint);
                        await client.ConnectAsync(externalHttpProxy.HostName, externalHttpProxy.Port);

                        stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
                    }
                    else
                    {
                        client = new TcpClient(server.UpStreamEndPoint);
                        await client.ConnectAsync(remoteHostName, remotePort);

                        stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
                    }
                }

                client.ReceiveTimeout = server.ConnectionTimeOutSeconds * 1000;
                client.SendTimeout    = server.ConnectionTimeOutSeconds * 1000;
            }
            catch (Exception)
            {
                stream?.Dispose();
                client?.Close();
                throw;
            }

            Interlocked.Increment(ref server.serverConnectionCount);

            return(new TcpConnection
            {
                UpStreamHttpProxy = externalHttpProxy,
                UpStreamHttpsProxy = externalHttpsProxy,
                HostName = remoteHostName,
                Port = remotePort,
                IsHttps = isHttps,
                TcpClient = client,
                StreamReader = new CustomBinaryReader(stream, server.BufferSize),
                Stream = stream,
                Version = httpVersion
            });
        }
        /// <summary>
        /// Creates a TCP connection to server
        /// </summary>
        /// <param name="bufferSize"></param>
        /// <param name="connectionTimeOutSeconds"></param>
        /// <param name="remoteHostName"></param>
        /// <param name="httpCmd"></param>
        /// <param name="httpVersion"></param>
        /// <param name="isHttps"></param>
        /// <param name="remotePort"></param>
        /// <param name="supportedSslProtocols"></param>
        /// <param name="remoteCertificateValidationCallback"></param>
        /// <param name="localCertificateSelectionCallback"></param>
        /// <param name="externalHttpProxy"></param>
        /// <param name="externalHttpsProxy"></param>
        /// <param name="clientStream"></param>
        /// <returns></returns>
        internal async Task <TcpConnection> CreateClient(int bufferSize, int connectionTimeOutSeconds,
                                                         string remoteHostName, int remotePort, Version httpVersion,
                                                         bool isHttps, SslProtocols supportedSslProtocols,
                                                         RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
                                                         ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy,
                                                         Stream clientStream)
        {
            TcpClient client;
            Stream    stream;

            if (isHttps)
            {
                SslStream sslStream = null;

                //If this proxy uses another external proxy then create a tunnel request for HTTPS connections
                if (externalHttpsProxy != null)
                {
                    client = new TcpClient(externalHttpsProxy.HostName, externalHttpsProxy.Port);
                    stream = client.GetStream();

                    using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true))
                    {
                        await writer.WriteLineAsync(string.Format("CONNECT {0}:{1} {2}", remoteHostName, remotePort, httpVersion));

                        await writer.WriteLineAsync(string.Format("Host: {0}:{1}", remoteHostName, remotePort));

                        await writer.WriteLineAsync("Connection: Keep-Alive");

                        await writer.WriteLineAsync();

                        await writer.FlushAsync();

                        writer.Close();
                    }

                    using (var reader = new CustomBinaryReader(stream))
                    {
                        var result = await reader.ReadLineAsync();

                        if (!result.ToLower().Contains("200 connection established"))
                        {
                            throw new Exception("Upstream proxy failed to create a secure tunnel");
                        }

                        await reader.ReadAllLinesAsync();
                    }
                }
                else
                {
                    client = new TcpClient(remoteHostName, remotePort);
                    stream = client.GetStream();
                }

                try
                {
                    sslStream = new SslStream(stream, true, remoteCertificateValidationCallback,
                                              localCertificateSelectionCallback);

                    await sslStream.AuthenticateAsClientAsync(remoteHostName, null, supportedSslProtocols, false);

                    stream = sslStream;
                }
                catch
                {
                    if (sslStream != null)
                    {
                        sslStream.Dispose();
                    }

                    throw;
                }
            }
            else
            {
                if (externalHttpProxy != null)
                {
                    client = new TcpClient(externalHttpProxy.HostName, externalHttpProxy.Port);
                    stream = client.GetStream();
                }
                else
                {
                    client = new TcpClient(remoteHostName, remotePort);
                    stream = client.GetStream();
                }
            }

            client.ReceiveTimeout = connectionTimeOutSeconds * 1000;
            client.SendTimeout    = connectionTimeOutSeconds * 1000;

            stream.ReadTimeout  = connectionTimeOutSeconds * 1000;
            stream.WriteTimeout = connectionTimeOutSeconds * 1000;

            return(new TcpConnection()
            {
                HostName = remoteHostName,
                port = remotePort,
                IsHttps = isHttps,
                TcpClient = client,
                StreamReader = new CustomBinaryReader(stream),
                Stream = stream
            });
        }