Exemple #1
0
        /// <summary>
        ///     Create a server connection.
        /// </summary>
        /// <param name="server">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(ProxyServer server, SessionEventArgsBase session, bool isConnect,
                                                                SslApplicationProtocol applicationProtocol, bool noCache, CancellationToken cancellationToken)
        {
            List <SslApplicationProtocol>?applicationProtocols = null;

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

            return(GetServerConnection(server, session, isConnect, applicationProtocols, noCache, cancellationToken));
        }
        public void ToString_Rendering_Succeeds()
        {
            const string           expected = "hello";
            SslApplicationProtocol protocol = new SslApplicationProtocol(expected);

            Assert.Equal(expected, protocol.ToString());

            byte[] bytes = new byte[] { 0x0B, 0xEE };
            protocol = new SslApplicationProtocol(bytes);
            Assert.Equal("0x0b 0xee", protocol.ToString());

            protocol = default;
            Assert.Null(protocol.ToString());
        }
Exemple #3
0
        private unsafe IntPtr SessionOpen(SslApplicationProtocol alpnProtocol)
        {
            ReadOnlyMemory <byte> memory = alpnProtocol.Protocol;

            using MemoryHandle h = memory.Pin();

            var quicBuffer = new MsQuicNativeMethods.QuicBuffer()
            {
                Buffer = (byte *)h.Pointer,
                Length = (uint)memory.Length
            };

            return(SessionOpen(&quicBuffer, 1));
        }
Exemple #4
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="applicationProtocol"></param>
        /// <param name="cancellationToken">The cancellation token for this async task.</param>
        /// <returns></returns>
        private Task <TcpServerConnection> getServerConnection(SessionEventArgsBase args, bool isConnect,
                                                               SslApplicationProtocol applicationProtocol, bool noCache, CancellationToken cancellationToken)
        {
            List <SslApplicationProtocol> applicationProtocols = null;

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

            return(getServerConnection(args, isConnect, applicationProtocols, noCache, cancellationToken));
        }
        protected override void ConfigurePipeline(IChannelHandlerContext ctx, SslApplicationProtocol protocol)
        {
            if (SslApplicationProtocol.Http2.Equals(protocol))
            {
                ctx.Pipeline.AddLast(Http2FrameCodecBuilder.ForServer().Build(), new HelloWorldHttp2Handler());
                return;
            }

            if (SslApplicationProtocol.Http11.Equals(protocol))
            {
                ctx.Pipeline.AddLast(new HttpServerCodec(),
                                     new HttpObjectAggregator(MAX_CONTENT_LENGTH),
                                     new Http2Helloworld.Server.HelloWorldHttp1Handler("ALPN Negotiation"));
                return;
            }

            throw new InvalidOperationException("unknown protocol: " + protocol);
        }
Exemple #6
0
        internal TcpServerConnection(ProxyServer proxyServer, Socket tcpSocket, HttpServerStream stream,
                                     string hostName, int port, bool isHttps, SslApplicationProtocol negotiatedApplicationProtocol,
                                     Version version, IExternalProxy?upStreamProxy, IPEndPoint?upStreamEndPoint, string cacheKey)
        {
            TcpSocket        = tcpSocket;
            LastAccess       = DateTime.UtcNow;
            this.proxyServer = proxyServer;
            this.proxyServer.UpdateServerConnectionCount(true);
            Stream   = stream;
            HostName = hostName;
            Port     = port;
            IsHttps  = isHttps;
            NegotiatedApplicationProtocol = negotiatedApplicationProtocol;
            Version          = version;
            UpStreamProxy    = upStreamProxy;
            UpStreamEndPoint = upStreamEndPoint;

            CacheKey = cacheKey;
        }
        private async Task <RetryResult> handleHttpSessionRequest(string httpCmd, SessionEventArgs args,
                                                                  TcpServerConnection serverConnection, SslApplicationProtocol sslApplicationProtocol,
                                                                  CancellationToken cancellationToken, CancellationTokenSource cancellationTokenSource)
        {
            //a connection generator task with captured parameters via closure.
            Func <Task <TcpServerConnection> > generator = () =>
                                                           tcpConnectionFactory.GetServerConnection(this, args, isConnect: false,
                                                                                                    applicationProtocol: sslApplicationProtocol,
                                                                                                    noCache: false, cancellationToken: cancellationToken);

            //for connection pool, retry fails until cache is exhausted.
            return(await retryPolicy <ServerConnectionException>().ExecuteAsync(async(connection) =>
            {
                args.TimeLine["Connection Ready"] = DateTime.Now;

                if (args.HttpClient.Request.UpgradeToWebSocket)
                {
                    // if upgrading to websocket then relay the request without reading the contents
                    await handleWebSocketUpgrade(httpCmd, args, args.HttpClient.Request,
                                                 args.HttpClient.Response, args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamWriter,
                                                 connection, cancellationTokenSource, cancellationToken);
                    return false;
                }

                // construct the web request that we are going to issue on behalf of the client.
                await handleHttpSessionRequest(connection, args);
                return true;
            }, generator, serverConnection));
        }
Exemple #8
0
        private async Task <RetryResult> handleHttpSessionRequest(SessionEventArgs args,
                                                                  TcpServerConnection?serverConnection, SslApplicationProtocol sslApplicationProtocol,
                                                                  CancellationToken cancellationToken, CancellationTokenSource cancellationTokenSource)
        {
            args.HttpClient.Request.Locked = true;

            // do not cache server connections for WebSockets
            bool noCache = args.HttpClient.Request.UpgradeToWebSocket;

            if (noCache)
            {
                serverConnection = null;
            }

            // a connection generator task with captured parameters via closure.
            Func <Task <TcpServerConnection> > generator = () =>
                                                           tcpConnectionFactory.GetServerConnection(this,
                                                                                                    args,
                                                                                                    false,
                                                                                                    sslApplicationProtocol,
                                                                                                    noCache,
                                                                                                    cancellationToken);

            // for connection pool, retry fails until cache is exhausted.
            return(await retryPolicy <ServerConnectionException>().ExecuteAsync(async connection =>
            {
                // set the connection and send request headers
                args.HttpClient.SetConnection(connection);

                args.TimeLine["Connection Ready"] = DateTime.UtcNow;

                if (args.HttpClient.Request.UpgradeToWebSocket)
                {
                    // connectRequest can be null for SOCKS connection
                    if (args.HttpClient.ConnectRequest != null)
                    {
                        args.HttpClient.ConnectRequest !.TunnelType = TunnelType.Websocket;
                    }

                    // if upgrading to websocket then relay the request without reading the contents
                    await handleWebSocketUpgrade(args, args.ClientStream, connection, cancellationTokenSource, cancellationToken);
                    return false;
                }

                // construct the web request that we are going to issue on behalf of the client.
                await handleHttpSessionRequest(args);
                return true;
            }, generator, serverConnection));
        }
 internal static bool TryGetNegotiatedApplicationProtocol(this SslStream sslStream, out SslApplicationProtocol value)
 {
     try
     {
         value = sslStream.NegotiatedApplicationProtocol;
         return(true);
     }
     catch (PlatformNotSupportedException)
     {
         value = default;
         return(false);
         // System.PlatformNotSupportedException: https://github.com/mono/mono/issues/12880
         //       at System.Net.Security.SslStream.get_NegotiatedApplicationProtocol () [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-// 02/android/release/mcs/class/System/System.Net.Security/SslStream.cs:387
         //         at (wrapper remoting-invoke-with-check) System.Net.Security.SslStream.get_NegotiatedApplicationProtocol()
     }
 }
Exemple #10
0
 public SslListenerBuilder WithAlpnProtocol(SslApplicationProtocol proto)
 {
     (_options.ApplicationProtocols ?? (_options.ApplicationProtocols = new List <SslApplicationProtocol>())).Add
         (proto);
     return(this);
 }
Exemple #11
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,
                                                                        ProxyServer proxyServer, SessionEventArgsBase sessionArgs, IPEndPoint?upStreamEndPoint, IExternalProxy?externalProxy, string cacheKey,
                                                                        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}");
                }
            }

            if (isHttps && sslProtocol == SslProtocols.None)
            {
                sslProtocol = proxyServer.SupportedSslProtocols;
            }

            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;
            }

            Socket?          tcpServerSocket = null;
            HttpServerStream?stream          = null;

            SslApplicationProtocol negotiatedApplicationProtocol = default;

            bool retry = true;
            var  enabledSslProtocols = sslProtocol;

retry:
            try
            {
                bool   socks    = externalProxy != null && externalProxy.ProxyType != ExternalProxyType.Http;
                string hostname = remoteHostName;
                int    port     = remotePort;

                if (externalProxy != null && externalProxy.ProxyType == ExternalProxyType.Http)
                {
                    hostname = externalProxy.HostName;
                    port     = externalProxy.Port;
                }

                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.UtcNow;
                }

                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];
                        var addressFamily = upStreamEndPoint?.AddressFamily ?? ipAddress.AddressFamily;

                        if (socks)
                        {
                            var proxySocket = new ProxySocket.ProxySocket(addressFamily, SocketType.Stream, ProtocolType.Tcp);
                            proxySocket.ProxyType = externalProxy !.ProxyType == ExternalProxyType.Socks4
                                ? ProxyTypes.Socks4
                                : ProxyTypes.Socks5;

                            var proxyIpAddresses = await Dns.GetHostAddressesAsync(externalProxy.HostName);

                            proxySocket.ProxyEndPoint = new IPEndPoint(proxyIpAddresses[0], externalProxy.Port);
                            if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
                            {
                                proxySocket.ProxyUser = externalProxy.UserName;
                                proxySocket.ProxyPass = externalProxy.Password;
                            }

                            tcpServerSocket = proxySocket;
                        }
                        else
                        {
                            tcpServerSocket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp);
                        }

                        if (upStreamEndPoint != null)
                        {
                            tcpServerSocket.Bind(upStreamEndPoint);
                        }

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

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

                        var connectTask = socks
                            ? ProxySocketConnectionTaskFactory.CreateTask((ProxySocket.ProxySocket)tcpServerSocket, ipAddress, port)
                            : SocketConnectionTaskFactory.CreateTask(tcpServerSocket, ipAddress, port);

                        await Task.WhenAny(connectTask, Task.Delay(proxyServer.ConnectTimeOutSeconds * 1000, cancellationToken));

                        if (!connectTask.IsCompleted || !tcpServerSocket.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
                                tcpServerSocket?.Close();
#else
                                tcpServerSocket?.Dispose();
#endif
                                tcpServerSocket = null;
                            }
                            catch
                            {
                                // ignore
                            }

                            continue;
                        }

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

                if (tcpServerSocket == 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.UtcNow;
                            return(await createServerConnection(remoteHostName, remotePort, httpVersion, isHttps, sslProtocol, applicationProtocols, isConnect, proxyServer, sessionArgs, upStreamEndPoint, externalProxy, cacheKey, cancellationToken));
                        }
                    }

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

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

                await proxyServer.InvokeServerConnectionCreateEvent(tcpServerSocket);

                stream = new HttpServerStream(new NetworkStream(tcpServerSocket, true), proxyServer.BufferPool, cancellationToken);

                if ((externalProxy != null && externalProxy.ProxyType == ExternalProxyType.Http) && (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,
                                                  (sender, certificate, chain, sslPolicyErrors) =>
                                                  proxyServer.ValidateServerCertificate(sender, sessionArgs, certificate, chain,
                                                                                        sslPolicyErrors),
                                                  (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) =>
                                                  proxyServer.SelectClientCertificate(sender, sessionArgs, targetHost, localCertificates,
                                                                                      remoteCertificate, acceptableIssuers));
                    stream = new HttpServerStream(sslStream, proxyServer.BufferPool, cancellationToken);

                    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.UtcNow;
                    }
                }
            }
            catch (IOException ex) when(ex.HResult == unchecked ((int)0x80131620) && retry && enabledSslProtocols >= SslProtocols.Tls11)
            {
                stream?.Dispose();
                tcpServerSocket?.Close();

                // Specifying Tls11 and/or Tls12 will disable the usage of Ssl3, even if it has been included.
                // https://docs.microsoft.com/en-us/dotnet/api/system.servicemodel.tcptransportsecurity.sslprotocols?view=dotnet-plat-ext-3.1
                enabledSslProtocols = proxyServer.SupportedSslProtocols & (SslProtocols)0xff;

                if (enabledSslProtocols == SslProtocols.None)
                {
                    throw;
                }

                retry = false;
                goto retry;
            }
            catch (AuthenticationException ex) when(ex.HResult == unchecked ((int)0x80131501) && retry && enabledSslProtocols >= SslProtocols.Tls11)
            {
                stream?.Dispose();
                tcpServerSocket?.Close();

                // Specifying Tls11 and/or Tls12 will disable the usage of Ssl3, even if it has been included.
                // https://docs.microsoft.com/en-us/dotnet/api/system.servicemodel.tcptransportsecurity.sslprotocols?view=dotnet-plat-ext-3.1
                enabledSslProtocols = proxyServer.SupportedSslProtocols & (SslProtocols)0xff;

                if (enabledSslProtocols == SslProtocols.None)
                {
                    throw;
                }

                retry = false;
                goto retry;
            }
            catch (Exception)
            {
                stream?.Dispose();
                tcpServerSocket?.Close();
                throw;
            }

            return(new TcpServerConnection(proxyServer, tcpServerSocket, stream, remoteHostName, remotePort, isHttps,
                                           negotiatedApplicationProtocol, httpVersion, externalProxy, upStreamEndPoint, cacheKey));
        }
Exemple #12
0
        private async Task <RetryResult> handleHttpSessionRequest(SessionEventArgs args,
                                                                  TcpServerConnection?serverConnection, SslApplicationProtocol sslApplicationProtocol,
                                                                  CancellationToken cancellationToken, CancellationTokenSource cancellationTokenSource)
        {
            args.HttpClient.Request.Locked = true;

            // do not cache server connections for WebSockets
            bool noCache = args.HttpClient.Request.UpgradeToWebSocket;

            if (noCache)
            {
                serverConnection = null;
            }

            // a connection generator task with captured parameters via closure.
            Func <Task <TcpServerConnection> > generator = () =>
                                                           tcpConnectionFactory.GetServerConnection(this,
                                                                                                    args,
                                                                                                    false,
                                                                                                    sslApplicationProtocol,
                                                                                                    noCache,
                                                                                                    cancellationToken);

            /// Retry with new connection if the initial stream.WriteAsync call to server fails.
            /// i.e if request line and headers failed to get send.
            /// Do not retry after reading data from client stream,
            /// because subsequent try will not have data to read from client
            /// and will hang at clientStream.ReadAsync call.
            /// So, throw RetryableServerConnectionException only when we are sure we can retry safely.
            return(await retryPolicy <RetryableServerConnectionException>().ExecuteAsync(async connection =>
            {
                // set the connection and send request headers
                args.HttpClient.SetConnection(connection);

                args.TimeLine["Connection Ready"] = DateTime.UtcNow;

                if (args.HttpClient.Request.UpgradeToWebSocket)
                {
                    // connectRequest can be null for SOCKS connection
                    if (args.HttpClient.ConnectRequest != null)
                    {
                        args.HttpClient.ConnectRequest !.TunnelType = TunnelType.Websocket;
                    }

                    // if upgrading to websocket then relay the request without reading the contents
                    await handleWebSocketUpgrade(args, args.ClientStream, connection, cancellationTokenSource, cancellationToken);
                    return false;
                }

                // construct the web request that we are going to issue on behalf of the client.
                await handleHttpSessionRequest(args);
                return true;
            }, generator, serverConnection));
        }
Exemple #13
0
            protected override void ConfigurePipeline(IChannelHandlerContext context, SslApplicationProtocol protocol)
            {
                if (SslApplicationProtocol.Http2.Equals(protocol))
                {
                    var p = context.Pipeline;
                    p.AddLast(_self._connectionHandler);
                    _self.ConfigureEndOfPipeline(p);
                    return;
                }

                context.CloseAsync();
                throw new InvalidOperationException($"Unknown protocol: {protocol}");
            }