/// <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()); }
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)); }
/// <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); }
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)); }
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() } }
public SslListenerBuilder WithAlpnProtocol(SslApplicationProtocol proto) { (_options.ApplicationProtocols ?? (_options.ApplicationProtocols = new List <SslApplicationProtocol>())).Add (proto); return(this); }
/// <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)); }
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)); }
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}"); }