/// <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 }); }
/// <summary> /// This is called when client is aware of proxy /// So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy /// </summary> /// <param name="endPoint">The explicit endpoint.</param> /// <param name="clientConnection">The client connection.</param> /// <returns>The task.</returns> private async Task handleClient(ExplicitProxyEndPoint endPoint, TcpClientConnection clientConnection) { var cancellationTokenSource = new CancellationTokenSource(); var cancellationToken = cancellationTokenSource.Token; var clientStream = new CustomBufferedStream(clientConnection.GetStream(), BufferPool, BufferSize); var clientStreamWriter = new HttpResponseWriter(clientStream, BufferPool, BufferSize); Task <TcpServerConnection> prefetchConnectionTask = null; bool closeServerConnection = false; bool calledRequestHandler = false; SslStream sslStream = null; try { string connectHostname = null; TunnelConnectSessionEventArgs connectArgs = null; // Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request) if (await HttpHelper.IsConnectMethod(clientStream, BufferPool, BufferSize, cancellationToken) == 1) { // read the first line HTTP command string httpCmd = await clientStream.ReadLineAsync(cancellationToken); if (string.IsNullOrEmpty(httpCmd)) { return; } Request.ParseRequestLine(httpCmd, out string _, out string httpUrl, out var version); var httpRemoteUri = new Uri("http://" + httpUrl); connectHostname = httpRemoteUri.Host; var connectRequest = new ConnectRequest { RequestUri = httpRemoteUri, OriginalUrl = httpUrl, HttpVersion = version }; await HeaderParser.ReadHeaders(clientStream, connectRequest.Headers, cancellationToken); connectArgs = new TunnelConnectSessionEventArgs(this, endPoint, connectRequest, cancellationTokenSource); connectArgs.ProxyClient.Connection = clientConnection; connectArgs.ProxyClient.ClientStream = clientStream; await endPoint.InvokeBeforeTunnelConnectRequest(this, connectArgs, ExceptionFunc); // filter out excluded host names bool decryptSsl = endPoint.DecryptSsl && connectArgs.DecryptSsl; if (connectArgs.DenyConnect) { if (connectArgs.HttpClient.Response.StatusCode == 0) { connectArgs.HttpClient.Response = new Response { HttpVersion = HttpHeader.Version11, StatusCode = (int)HttpStatusCode.Forbidden, StatusDescription = "Forbidden" }; } // send the response await clientStreamWriter.WriteResponseAsync(connectArgs.HttpClient.Response, cancellationToken : cancellationToken); return; } if (await checkAuthorization(connectArgs) == false) { await endPoint.InvokeBeforeTunnelConnectResponse(this, connectArgs, ExceptionFunc); // send the response await clientStreamWriter.WriteResponseAsync(connectArgs.HttpClient.Response, cancellationToken : cancellationToken); return; } // write back successful CONNECT response var response = ConnectResponse.CreateSuccessfulConnectResponse(version); // Set ContentLength explicitly to properly handle HTTP 1.0 response.ContentLength = 0; response.Headers.FixProxyHeaders(); connectArgs.HttpClient.Response = response; await clientStreamWriter.WriteResponseAsync(response, cancellationToken : cancellationToken); var clientHelloInfo = await SslTools.PeekClientHello(clientStream, BufferPool, cancellationToken); bool isClientHello = clientHelloInfo != null; if (isClientHello) { connectRequest.TunnelType = TunnelType.Https; connectRequest.ClientHelloInfo = clientHelloInfo; } await endPoint.InvokeBeforeTunnelConnectResponse(this, connectArgs, ExceptionFunc, isClientHello); if (decryptSsl && isClientHello) { connectRequest.RequestUri = new Uri("https://" + httpUrl); bool http2Supported = false; var alpn = clientHelloInfo.GetAlpn(); if (alpn != null && alpn.Contains(SslApplicationProtocol.Http2)) { // test server HTTP/2 support try { // todo: this is a hack, because Titanium does not support HTTP protocol changing currently var connection = await tcpConnectionFactory.GetServerConnection(this, connectArgs, isConnect : true, applicationProtocols : SslExtensions.Http2ProtocolAsList, noCache : true, cancellationToken : cancellationToken); http2Supported = connection.NegotiatedApplicationProtocol == SslApplicationProtocol.Http2; //release connection back to pool instead of closing when connection pool is enabled. await tcpConnectionFactory.Release(connection, true); } catch (Exception) { // ignore } } if (EnableTcpServerConnectionPrefetch) { IPAddress[] ipAddresses = null; try { //make sure the host can be resolved before creating the prefetch task ipAddresses = await Dns.GetHostAddressesAsync(connectArgs.HttpClient.Request.RequestUri.Host); } catch (SocketException) { } if (ipAddresses != null && ipAddresses.Length > 0) { //don't pass cancellation token here //it could cause floating server connections when client exits prefetchConnectionTask = tcpConnectionFactory.GetServerConnection(this, connectArgs, isConnect: true, applicationProtocols: null, noCache: false, cancellationToken: CancellationToken.None); } } X509Certificate2 certificate = null; try { sslStream = new SslStream(clientStream, false); string certName = HttpHelper.GetWildCardDomainName(connectHostname); certificate = endPoint.GenericCertificate ?? await CertificateManager.CreateServerCertificate(certName); // Successfully managed to authenticate the client using the fake certificate var options = new SslServerAuthenticationOptions(); if (EnableHttp2 && http2Supported) { options.ApplicationProtocols = clientHelloInfo.GetAlpn(); if (options.ApplicationProtocols == null || options.ApplicationProtocols.Count == 0) { options.ApplicationProtocols = SslExtensions.Http11ProtocolAsList; } } options.ServerCertificate = certificate; options.ClientCertificateRequired = false; options.EnabledSslProtocols = SupportedSslProtocols; options.CertificateRevocationCheckMode = X509RevocationMode.NoCheck; await sslStream.AuthenticateAsServerAsync(options, cancellationToken); #if NETCOREAPP2_1 clientConnection.NegotiatedApplicationProtocol = sslStream.NegotiatedApplicationProtocol; #endif // HTTPS server created - we can now decrypt the client's traffic clientStream = new CustomBufferedStream(sslStream, BufferPool, BufferSize); clientStreamWriter = new HttpResponseWriter(clientStream, BufferPool, BufferSize); } catch (Exception e) { var certName = certificate?.GetNameInfo(X509NameType.SimpleName, false); throw new ProxyConnectException( $"Couldn't authenticate host '{connectHostname}' with certificate '{certName}'.", e, connectArgs); } if (await HttpHelper.IsConnectMethod(clientStream, BufferPool, BufferSize, cancellationToken) == -1) { decryptSsl = false; } if (!decryptSsl) { await tcpConnectionFactory.Release(prefetchConnectionTask, true); prefetchConnectionTask = null; } } if (cancellationTokenSource.IsCancellationRequested) { throw new Exception("Session was terminated by user."); } // Hostname is excluded or it is not an HTTPS connect if (!decryptSsl || !isClientHello) { if (!isClientHello) { connectRequest.TunnelType = TunnelType.Websocket; } // create new connection to server. // If we detected that client tunnel CONNECTs without SSL by checking for empty client hello then // this connection should not be HTTPS. var connection = await tcpConnectionFactory.GetServerConnection(this, connectArgs, isConnect : true, applicationProtocols : SslExtensions.Http2ProtocolAsList, noCache : true, cancellationToken : cancellationToken); try { if (isClientHello) { int available = clientStream.Available; if (available > 0) { // send the buffered data var data = BufferPool.GetBuffer(BufferSize); try { await clientStream.ReadAsync(data, 0, available, cancellationToken); // clientStream.Available should be at most BufferSize because it is using the same buffer size await connection.StreamWriter.WriteAsync(data, 0, available, true, cancellationToken); } finally { BufferPool.ReturnBuffer(data); } } var serverHelloInfo = await SslTools.PeekServerHello(connection.Stream, BufferPool, cancellationToken); ((ConnectResponse)connectArgs.HttpClient.Response).ServerHelloInfo = serverHelloInfo; } await TcpHelper.SendRaw(clientStream, connection.Stream, BufferPool, BufferSize, (buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); }, connectArgs.CancellationTokenSource, ExceptionFunc); } finally { await tcpConnectionFactory.Release(connection, true); } return; } } if (connectArgs != null && await HttpHelper.IsPriMethod(clientStream, BufferPool, BufferSize, cancellationToken) == 1) { // todo string httpCmd = await clientStream.ReadLineAsync(cancellationToken); if (httpCmd == "PRI * HTTP/2.0") { connectArgs.HttpClient.ConnectRequest.TunnelType = TunnelType.Http2; // HTTP/2 Connection Preface string line = await clientStream.ReadLineAsync(cancellationToken); if (line != string.Empty) { throw new Exception($"HTTP/2 Protocol violation. Empty string expected, '{line}' received"); } line = await clientStream.ReadLineAsync(cancellationToken); if (line != "SM") { throw new Exception($"HTTP/2 Protocol violation. 'SM' expected, '{line}' received"); } line = await clientStream.ReadLineAsync(cancellationToken); if (line != string.Empty) { throw new Exception($"HTTP/2 Protocol violation. Empty string expected, '{line}' received"); } var connection = await tcpConnectionFactory.GetServerConnection(this, connectArgs, isConnect : true, applicationProtocols : SslExtensions.Http2ProtocolAsList, noCache : true, cancellationToken : cancellationToken); try { await connection.StreamWriter.WriteLineAsync("PRI * HTTP/2.0", cancellationToken); await connection.StreamWriter.WriteLineAsync(cancellationToken); await connection.StreamWriter.WriteLineAsync("SM", cancellationToken); await connection.StreamWriter.WriteLineAsync(cancellationToken); #if NETCOREAPP2_1 await Http2Helper.SendHttp2(clientStream, connection.Stream, BufferSize, (buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); }, () => new SessionEventArgs(this, endPoint, cancellationTokenSource) { ProxyClient = { Connection = clientConnection }, HttpClient = { ConnectRequest = connectArgs?.HttpClient.ConnectRequest }, UserData = connectArgs?.UserData }, async args => { await invokeBeforeRequest(args); }, async args => { await invokeBeforeResponse(args); }, connectArgs.CancellationTokenSource, clientConnection.Id, ExceptionFunc); #endif } finally { await tcpConnectionFactory.Release(connection, true); } } } calledRequestHandler = true; // Now create the request await handleHttpSessionRequest(endPoint, clientConnection, clientStream, clientStreamWriter, cancellationTokenSource, connectHostname, connectArgs, prefetchConnectionTask); } catch (ProxyException e) { closeServerConnection = true; onException(clientStream, e); } catch (IOException e) { closeServerConnection = true; onException(clientStream, new Exception("Connection was aborted", e)); } catch (SocketException e) { closeServerConnection = true; onException(clientStream, new Exception("Could not connect", e)); } catch (Exception e) { closeServerConnection = true; onException(clientStream, new Exception("Error occured in whilst handling the client", e)); } finally { if (!calledRequestHandler) { await tcpConnectionFactory.Release(prefetchConnectionTask, closeServerConnection); } sslStream?.Dispose(); clientStream.Dispose(); if (!cancellationTokenSource.IsCancellationRequested) { cancellationTokenSource.Cancel(); } } }
/// <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> /// This is the core request handler method for a particular connection from client. /// Will create new session (request/response) sequence until /// client/server abruptly terminates connection or by normal HTTP termination. /// </summary> /// <param name="endPoint">The proxy endpoint.</param> /// <param name="clientConnection">The client connection.</param> /// <param name="clientStream">The client stream.</param> /// <param name="clientStreamWriter">The client stream writer.</param> /// <param name="cancellationTokenSource">The cancellation token source for this async task.</param> /// <param name="httpsConnectHostname"> /// The https hostname as appeared in CONNECT request if this is a HTTPS request from /// explicit endpoint. /// </param> /// <param name="connectRequest">The Connect request if this is a HTTPS request from explicit endpoint.</param> private async Task HandleHttpSessionRequest(ProxyEndPoint endPoint, TcpClientConnection clientConnection, CustomBufferedStream clientStream, HttpResponseWriter clientStreamWriter, CancellationTokenSource cancellationTokenSource, string httpsConnectHostname, ConnectRequest connectRequest) { var cancellationToken = cancellationTokenSource.Token; TcpServerConnection serverConnection = null; bool serverConnectionClose = false; try { // Loop through each subsequest request on this particular client connection // (assuming HTTP connection is kept alive by client) while (true) { // read the request line string httpCmd = await clientStream.ReadLineAsync(cancellationToken); if (string.IsNullOrEmpty(httpCmd)) { return; } var args = new SessionEventArgs(BufferSize, endPoint, cancellationTokenSource, ExceptionFunc) { ProxyClient = { ClientConnection = clientConnection }, WebSession = { ConnectRequest = connectRequest } }; try { try { Request.ParseRequestLine(httpCmd, out string httpMethod, out string httpUrl, out var version); // Read the request headers in to unique and non-unique header collections await HeaderParser.ReadHeaders(clientStream, args.WebSession.Request.Headers, cancellationToken); Uri httpRemoteUri; if (uriSchemeRegex.IsMatch(httpUrl)) { try { httpRemoteUri = new Uri(httpUrl); } catch (Exception ex) { throw new Exception($"Invalid URI: '{httpUrl}'", ex); } } else { string host = args.WebSession.Request.Host ?? httpsConnectHostname; string hostAndPath = host; if (httpUrl.StartsWith("/")) { hostAndPath += httpUrl; } string url = string.Concat(httpsConnectHostname == null ? "http://" : "https://", hostAndPath); try { httpRemoteUri = new Uri(url); } catch (Exception ex) { throw new Exception($"Invalid URI: '{url}'", ex); } } var request = args.WebSession.Request; request.RequestUri = httpRemoteUri; request.OriginalUrl = httpUrl; request.Method = httpMethod; request.HttpVersion = version; args.ProxyClient.ClientStream = clientStream; args.ProxyClient.ClientStreamWriter = clientStreamWriter; if (!args.IsTransparent) { // proxy authorization check if (httpsConnectHostname == null && await CheckAuthorization(args) == false) { await InvokeBeforeResponse(args); // send the response await clientStreamWriter.WriteResponseAsync(args.WebSession.Response, cancellationToken : cancellationToken); return; } PrepareRequestHeaders(request.Headers); request.Host = request.RequestUri.Authority; } // if win auth is enabled // we need a cache of request body // so that we can send it after authentication in WinAuthHandler.cs if (isWindowsAuthenticationEnabledAndSupported && request.HasBody) { await args.GetRequestBody(cancellationToken); } request.OriginalHasBody = request.HasBody; // If user requested interception do it await InvokeBeforeRequest(args); var response = args.WebSession.Response; if (request.CancelRequest) { // syphon out the request body from client before setting the new body await args.SyphonOutBodyAsync(true, cancellationToken); await HandleHttpSessionResponse(args); if (!response.KeepAlive) { return; } continue; } // create a new connection if hostname/upstream end point changes if (serverConnection != null && (!serverConnection.HostName.EqualsIgnoreCase(request.RequestUri.Host) || args.WebSession.UpStreamEndPoint?.Equals(serverConnection.UpStreamEndPoint) == false)) { tcpConnectionFactory.Release(serverConnection, true); serverConnection = null; } if (serverConnection == null) { serverConnection = await GetServerConnection(args, false, clientConnection.NegotiatedApplicationProtocol, cancellationToken); } // if upgrading to websocket then relay the requet without reading the contents if (request.UpgradeToWebSocket) { // prepare the prefix content await serverConnection.StreamWriter.WriteLineAsync(httpCmd, cancellationToken); await serverConnection.StreamWriter.WriteHeadersAsync(request.Headers, cancellationToken : cancellationToken); string httpStatus = await serverConnection.Stream.ReadLineAsync(cancellationToken); Response.ParseResponseLine(httpStatus, out var responseVersion, out int responseStatusCode, out string responseStatusDescription); response.HttpVersion = responseVersion; response.StatusCode = responseStatusCode; response.StatusDescription = responseStatusDescription; await HeaderParser.ReadHeaders(serverConnection.Stream, response.Headers, cancellationToken); if (!args.IsTransparent) { await clientStreamWriter.WriteResponseAsync(response, cancellationToken : cancellationToken); } // If user requested call back then do it if (!args.WebSession.Response.Locked) { await InvokeBeforeResponse(args); } await TcpHelper.SendRaw(clientStream, serverConnection.Stream, BufferSize, (buffer, offset, count) => { args.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); }, cancellationTokenSource, ExceptionFunc); return; } // construct the web request that we are going to issue on behalf of the client. await HandleHttpSessionRequestInternal(serverConnection, args); if (args.WebSession.ServerConnection == null) { return; } // if connection is closing exit if (!response.KeepAlive) { serverConnectionClose = true; return; } if (cancellationTokenSource.IsCancellationRequested) { throw new Exception("Session was terminated by user."); } } catch (Exception e) when(!(e is ProxyHttpException)) { throw new ProxyHttpException("Error occured whilst handling session request", e, args); } } catch (Exception e) { args.Exception = e; serverConnectionClose = true; throw; } finally { await InvokeAfterResponse(args); args.Dispose(); } } } finally { tcpConnectionFactory.Release(serverConnection, serverConnectionClose || !EnableConnectionPool); } }
/// <summary> /// This is the core request handler method for a particular connection from client. /// Will create new session (request/response) sequence until /// client/server abruptly terminates connection or by normal HTTP termination. /// </summary> /// <param name="endPoint">The proxy endpoint.</param> /// <param name="clientConnection">The client connection.</param> /// <param name="clientStream">The client stream.</param> /// <param name="clientStreamWriter">The client stream writer.</param> /// <param name="cancellationTokenSource">The cancellation token source for this async task.</param> /// <param name="httpsConnectHostname"> /// The https hostname as appeared in CONNECT request if this is a HTTPS request from /// explicit endpoint. /// </param> /// <param name="connectRequest">The Connect request if this is a HTTPS request from explicit endpoint.</param> /// <param name="prefetchConnectionTask">Prefetched server connection for current client using Connect/SNI headers.</param> private async Task handleHttpSessionRequest(ProxyEndPoint endPoint, TcpClientConnection clientConnection, CustomBufferedStream clientStream, HttpResponseWriter clientStreamWriter, CancellationTokenSource cancellationTokenSource, string httpsConnectHostname, TunnelConnectSessionEventArgs connectArgs, Task <TcpServerConnection> prefetchConnectionTask = null) { var connectRequest = connectArgs?.HttpClient.ConnectRequest; var prefetchTask = prefetchConnectionTask; TcpServerConnection connection = null; bool closeServerConnection = false; try { var cancellationToken = cancellationTokenSource.Token; // Loop through each subsequest request on this particular client connection // (assuming HTTP connection is kept alive by client) while (true) { // read the request line string httpCmd = await clientStream.ReadLineAsync(cancellationToken); if (string.IsNullOrEmpty(httpCmd)) { return; } var args = new SessionEventArgs(this, endPoint, cancellationTokenSource) { ProxyClient = { Connection = clientConnection }, HttpClient = { ConnectRequest = connectRequest }, UserData = connectArgs?.UserData }; try { try { Request.ParseRequestLine(httpCmd, out string httpMethod, out string httpUrl, out var version); // Read the request headers in to unique and non-unique header collections await HeaderParser.ReadHeaders(clientStream, args.HttpClient.Request.Headers, cancellationToken); Uri httpRemoteUri; if (ProxyConstants.UriSchemeRegex.IsMatch(httpUrl)) { try { httpRemoteUri = new Uri(httpUrl); } catch (Exception ex) { throw new Exception($"Invalid URI: '{httpUrl}'", ex); } } else { string host = args.HttpClient.Request.Host ?? httpsConnectHostname; string hostAndPath = host; if (httpUrl.StartsWith("/")) { hostAndPath += httpUrl; } string url = string.Concat(httpsConnectHostname == null ? "http://" : "https://", hostAndPath); try { httpRemoteUri = new Uri(url); } catch (Exception ex) { throw new Exception($"Invalid URI: '{url}'", ex); } } var request = args.HttpClient.Request; request.RequestUri = httpRemoteUri; request.OriginalUrl = httpUrl; request.Method = httpMethod; request.HttpVersion = version; args.ProxyClient.ClientStream = clientStream; args.ProxyClient.ClientStreamWriter = clientStreamWriter; if (!args.IsTransparent) { // proxy authorization check if (httpsConnectHostname == null && await checkAuthorization(args) == false) { await invokeBeforeResponse(args); // send the response await clientStreamWriter.WriteResponseAsync(args.HttpClient.Response, cancellationToken : cancellationToken); return; } prepareRequestHeaders(request.Headers); request.Host = request.RequestUri.Authority; } // if win auth is enabled // we need a cache of request body // so that we can send it after authentication in WinAuthHandler.cs if (isWindowsAuthenticationEnabledAndSupported && request.HasBody) { await args.GetRequestBody(cancellationToken); } //we need this to syphon out data from connection if API user changes them. request.SetOriginalHeaders(); args.TimeLine["Request Received"] = DateTime.Now; // If user requested interception do it await invokeBeforeRequest(args); var response = args.HttpClient.Response; if (request.CancelRequest) { if (!(Enable100ContinueBehaviour && request.ExpectContinue)) { // syphon out the request body from client before setting the new body await args.SyphonOutBodyAsync(true, cancellationToken); } await handleHttpSessionResponse(args); if (!response.KeepAlive) { return; } continue; } //If prefetch task is available. if (connection == null && prefetchTask != null) { connection = await prefetchTask; prefetchTask = null; } // create a new connection if cache key changes. // only gets hit when connection pool is disabled. // or when prefetch task has a unexpectedly different connection. if (connection != null && (await tcpConnectionFactory.GetConnectionCacheKey(this, args, clientConnection.NegotiatedApplicationProtocol) != connection.CacheKey)) { await tcpConnectionFactory.Release(connection); connection = null; } var result = await handleHttpSessionRequest(httpCmd, args, connection, clientConnection.NegotiatedApplicationProtocol, cancellationToken, cancellationTokenSource); //update connection to latest used connection = result.LatestConnection; closeServerConnection = !result.Continue; //throw if exception happened if (!result.IsSuccess) { throw result.Exception; } if (!result.Continue) { return; } //user requested if (args.HttpClient.CloseServerConnection) { closeServerConnection = true; return; } // if connection is closing exit if (!response.KeepAlive) { closeServerConnection = true; return; } if (cancellationTokenSource.IsCancellationRequested) { throw new Exception("Session was terminated by user."); } //Release server connection for each HTTP session instead of per client connection. //This will be more efficient especially when client is idly holding server connection //between sessions without using it. //Do not release authenticated connections for performance reasons. //Otherwise it will keep authenticating per session. if (EnableConnectionPool && connection != null && !connection.IsWinAuthenticated) { await tcpConnectionFactory.Release(connection); connection = null; } } catch (Exception e) when(!(e is ProxyHttpException)) { throw new ProxyHttpException("Error occured whilst handling session request", e, args); } } catch (Exception e) { args.Exception = e; closeServerConnection = true; throw; } finally { await invokeAfterResponse(args); args.Dispose(); } } } finally { await tcpConnectionFactory.Release(connection, closeServerConnection); await tcpConnectionFactory.Release(prefetchTask, closeServerConnection); } }
/// <summary> /// This is the core request handler method for a particular connection from client. /// Will create new session (request/response) sequence until /// client/server abruptly terminates connection or by normal HTTP termination. /// </summary> /// <param name="endPoint">The proxy endpoint.</param> /// <param name="clientConnection">The client connection.</param> /// <param name="clientStream">The client stream.</param> /// <param name="clientStreamWriter">The client stream writer.</param> /// <param name="cancellationTokenSource">The cancellation token source for this async task.</param> /// <param name="httpsConnectHostname"> /// The https hostname as appeared in CONNECT request if this is a HTTPS request from /// explicit endpoint. /// </param> /// <param name="connectRequest">The Connect request if this is a HTTPS request from explicit endpoint.</param> /// <param name="prefetchConnectionTask">Prefetched server connection for current client using Connect/SNI headers.</param> private async Task handleHttpSessionRequest(ProxyEndPoint endPoint, TcpClientConnection clientConnection, CustomBufferedStream clientStream, HttpResponseWriter clientStreamWriter, CancellationTokenSource cancellationTokenSource, string httpsConnectHostname, ConnectRequest connectRequest, Task <TcpServerConnection> prefetchConnectionTask = null) { var prefetchTask = prefetchConnectionTask; TcpServerConnection connection = null; bool closeServerConnection = false; try { var cancellationToken = cancellationTokenSource.Token; System.Diagnostics.Debug.WriteLine("{2}:HttpSessionRequestHandler: ReqURL: {0}{1}", connectRequest == null ? string.Empty : connectRequest.RequestUri.ToString(), string.Empty, System.Threading.Thread.CurrentThread.ManagedThreadId); // Loop through each subsequest request on this particular client connection // (assuming HTTP connection is kept alive by client) while (true) { // read the request line string httpCmd = await clientStream.ReadLineAsync(cancellationToken); if (string.IsNullOrEmpty(httpCmd)) { return; } var args = new SessionEventArgs(this, endPoint, cancellationTokenSource) { ProxyClient = { ClientConnection = clientConnection }, WebSession = { ConnectRequest = connectRequest } }; try { try { string httpMethod; string httpUrl; Version version; Request.ParseRequestLine(httpCmd, out httpMethod, out httpUrl, out version); // Read the request headers in to unique and non-unique header collections await HeaderParser.ReadHeaders(clientStream, args.WebSession.Request.Headers, cancellationToken); Uri httpRemoteUri; if (uriSchemeRegex.IsMatch(httpUrl)) { try { httpRemoteUri = new Uri(httpUrl); } catch (Exception ex) { throw new Exception($"Invalid URI: '{httpUrl}'", ex); } } else { string host = args.WebSession.Request.Host ?? httpsConnectHostname; string hostAndPath = host; if (httpUrl.StartsWith("/")) { hostAndPath += httpUrl; } string url = string.Concat(httpsConnectHostname == null ? "http://" : "https://", hostAndPath); try { httpRemoteUri = new Uri(url); } catch (Exception ex) { throw new Exception($"Invalid URI: '{url}'", ex); } } var request = args.WebSession.Request; request.RequestUri = httpRemoteUri; request.OriginalUrl = httpUrl; request.Method = httpMethod; request.HttpVersion = version; args.ProxyClient.ClientStream = clientStream; args.ProxyClient.ClientStreamWriter = clientStreamWriter; if (!args.IsTransparent) { // proxy authorization check if (httpsConnectHostname == null && await checkAuthorization(args) == false) { await invokeBeforeResponse(args); // send the response await clientStreamWriter.WriteResponseAsync(args.WebSession.Response, cancellationToken : cancellationToken); return; } prepareRequestHeaders(request.Headers); request.Host = request.RequestUri.Authority; } // if win auth is enabled // we need a cache of request body // so that we can send it after authentication in WinAuthHandler.cs if (isWindowsAuthenticationEnabledAndSupported && request.HasBody) { await args.GetRequestBody(cancellationToken); } //we need this to syphon out data from connection if API user changes them. request.SetOriginalHeaders(); // If user requested interception do it await invokeBeforeRequest(args); var response = args.WebSession.Response; if (request.CancelRequest) { // syphon out the request body from client before setting the new body await args.SyphonOutBodyAsync(true, cancellationToken); await handleHttpSessionResponse(args); if (!response.KeepAlive) { return; } continue; } //If prefetch task is available. if (connection == null && prefetchTask != null) { connection = await prefetchTask; prefetchTask = null; } // create a new connection if cache key changes. // only gets hit when connection pool is disabled. // or when prefetch task has a unexpectedly different connection. if (connection != null && (await tcpConnectionFactory.GetConnectionCacheKey(this, args, clientConnection.NegotiatedApplicationProtocol) != connection.CacheKey)) { await tcpConnectionFactory.Release(connection); connection = null; } //a connection generator task with captured parameters via closure. Func <Task <TcpServerConnection> > generator = () => tcpConnectionFactory.GetServerConnection(this, args, isConnect: false, applicationProtocol: clientConnection.NegotiatedApplicationProtocol, noCache: false, cancellationToken: cancellationToken); //for connection pool, retry fails until cache is exhausted. var result = await retryPolicy <ServerConnectionException>().ExecuteAsync(async(serverConnection) => { // if upgrading to websocket then relay the request without reading the contents if (request.UpgradeToWebSocket) { await handleWebSocketUpgrade(httpCmd, args, request, response, clientStream, clientStreamWriter, serverConnection, cancellationTokenSource, cancellationToken); closeServerConnection = true; return(false); } // construct the web request that we are going to issue on behalf of the client. await handleHttpSessionRequestInternal(serverConnection, args); return(true); }, generator, connection); //update connection to latest used connection = result.LatestConnection; //throw if exception happened if (!result.IsSuccess) { throw result.Exception; } if (!result.Continue) { return; } //user requested if (args.WebSession.CloseServerConnection) { closeServerConnection = true; return; } // if connection is closing exit if (!response.KeepAlive) { closeServerConnection = true; return; } if (cancellationTokenSource.IsCancellationRequested) { throw new Exception("Session was terminated by user."); } //Get/release server connection for each HTTP session instead of per client connection. //This will be more efficient especially when client is idly holding server connection //between sessions without using it. //Do not release authenticated connections for performance reasons. //Otherwise it will keep authenticating per session. if (EnableConnectionPool && connection != null && !connection.IsWinAuthenticated) { await tcpConnectionFactory.Release(connection); connection = null; } } catch (Exception e) when(!(e is ProxyHttpException)) { throw new ProxyHttpException("Error occured whilst handling session request", e, args); } } catch (Exception e) { args.Exception = e; closeServerConnection = true; throw; } finally { await invokeAfterResponse(args); args.Dispose(); } } } finally { await tcpConnectionFactory.Release(connection, closeServerConnection); await tcpConnectionFactory.Release(prefetchTask, closeServerConnection); } }
/// <summary> /// This is called when client is aware of proxy /// So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy /// </summary> /// <param name="endPoint">The explicit endpoint.</param> /// <param name="clientConnection">The client connection.</param> /// <returns>The task.</returns> private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClientConnection clientConnection) { var cancellationTokenSource = new CancellationTokenSource(); var cancellationToken = cancellationTokenSource.Token; var clientStream = new CustomBufferedStream(clientConnection.GetStream(), BufferSize); var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize); try { string connectHostname = null; TunnelConnectSessionEventArgs connectArgs = null; // Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request) if (await HttpHelper.IsConnectMethod(clientStream) == 1) { // read the first line HTTP command string httpCmd = await clientStream.ReadLineAsync(cancellationToken); if (string.IsNullOrEmpty(httpCmd)) { return; } Request.ParseRequestLine(httpCmd, out string _, out string httpUrl, out var version); var httpRemoteUri = new Uri("http://" + httpUrl); connectHostname = httpRemoteUri.Host; var connectRequest = new ConnectRequest { RequestUri = httpRemoteUri, OriginalUrl = httpUrl, HttpVersion = version }; await HeaderParser.ReadHeaders(clientStream, connectRequest.Headers, cancellationToken); connectArgs = new TunnelConnectSessionEventArgs(BufferSize, endPoint, connectRequest, cancellationTokenSource, ExceptionFunc); connectArgs.ProxyClient.ClientConnection = clientConnection; connectArgs.ProxyClient.ClientStream = clientStream; await endPoint.InvokeBeforeTunnelConnectRequest(this, connectArgs, ExceptionFunc); // filter out excluded host names bool decryptSsl = endPoint.DecryptSsl && connectArgs.DecryptSsl; if (connectArgs.DenyConnect) { if (connectArgs.WebSession.Response.StatusCode == 0) { connectArgs.WebSession.Response = new Response { HttpVersion = HttpHeader.Version11, StatusCode = (int)HttpStatusCode.Forbidden, StatusDescription = "Forbidden" }; } // send the response await clientStreamWriter.WriteResponseAsync(connectArgs.WebSession.Response, cancellationToken : cancellationToken); return; } if (await CheckAuthorization(connectArgs) == false) { await endPoint.InvokeBeforeTunnectConnectResponse(this, connectArgs, ExceptionFunc); // send the response await clientStreamWriter.WriteResponseAsync(connectArgs.WebSession.Response, cancellationToken : cancellationToken); return; } // write back successfull CONNECT response var response = ConnectResponse.CreateSuccessfullConnectResponse(version); // Set ContentLength explicitly to properly handle HTTP 1.0 response.ContentLength = 0; response.Headers.FixProxyHeaders(); connectArgs.WebSession.Response = response; await clientStreamWriter.WriteResponseAsync(response, cancellationToken : cancellationToken); var clientHelloInfo = await SslTools.PeekClientHello(clientStream, cancellationToken); bool isClientHello = clientHelloInfo != null; if (isClientHello) { connectRequest.ClientHelloInfo = clientHelloInfo; } await endPoint.InvokeBeforeTunnectConnectResponse(this, connectArgs, ExceptionFunc, isClientHello); if (decryptSsl && isClientHello) { connectRequest.RequestUri = new Uri("https://" + httpUrl); bool http2Supproted = false; var alpn = clientHelloInfo.GetAlpn(); if (alpn != null && alpn.Contains(SslApplicationProtocol.Http2)) { // test server HTTP/2 support // todo: this is a hack, because Titanium does not support HTTP protocol changing currently using (var connection = await GetServerConnection(connectArgs, true, SslExtensions.Http2ProtocolAsList, cancellationToken)) { http2Supproted = connection.NegotiatedApplicationProtocol == SslApplicationProtocol.Http2; } } SslStream sslStream = null; try { sslStream = new SslStream(clientStream); string certName = HttpHelper.GetWildCardDomainName(connectHostname); var certificate = endPoint.GenericCertificate ?? await CertificateManager.CreateCertificateAsync(certName); // Successfully managed to authenticate the client using the fake certificate var options = new SslServerAuthenticationOptions(); if (http2Supproted) { options.ApplicationProtocols = clientHelloInfo.GetAlpn(); if (options.ApplicationProtocols == null || options.ApplicationProtocols.Count == 0) { options.ApplicationProtocols = SslExtensions.Http11ProtocolAsList; } } options.ServerCertificate = certificate; options.ClientCertificateRequired = false; options.EnabledSslProtocols = SupportedSslProtocols; options.CertificateRevocationCheckMode = X509RevocationMode.NoCheck; await sslStream.AuthenticateAsServerAsync(options, cancellationToken); #if NETCOREAPP2_1 clientConnection.NegotiatedApplicationProtocol = sslStream.NegotiatedApplicationProtocol; #endif // HTTPS server created - we can now decrypt the client's traffic clientStream = new CustomBufferedStream(sslStream, BufferSize); clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize); } catch (Exception e) { sslStream?.Dispose(); throw new ProxyConnectException( $"Could'nt authenticate client '{connectHostname}' with fake certificate.", e, connectArgs); } if (await HttpHelper.IsConnectMethod(clientStream) == -1) { decryptSsl = false; } } if (cancellationTokenSource.IsCancellationRequested) { throw new Exception("Session was terminated by user."); } // Hostname is excluded or it is not an HTTPS connect if (!decryptSsl || !isClientHello) { // create new connection using (var connection = await GetServerConnection(connectArgs, true, clientConnection.NegotiatedApplicationProtocol, cancellationToken)) { if (isClientHello) { int available = clientStream.Available; if (available > 0) { // send the buffered data var data = BufferPool.GetBuffer(BufferSize); try { // clientStream.Available sbould be at most BufferSize because it is using the same buffer size await clientStream.ReadAsync(data, 0, available, cancellationToken); await connection.StreamWriter.WriteAsync(data, 0, available, true, cancellationToken); } finally { BufferPool.ReturnBuffer(data); } } var serverHelloInfo = await SslTools.PeekServerHello(connection.Stream, cancellationToken); ((ConnectResponse)connectArgs.WebSession.Response).ServerHelloInfo = serverHelloInfo; } await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize, (buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); }, connectArgs.CancellationTokenSource, ExceptionFunc); } return; } } if (connectArgs != null && await HttpHelper.IsPriMethod(clientStream) == 1) { // todo string httpCmd = await clientStream.ReadLineAsync(cancellationToken); if (httpCmd == "PRI * HTTP/2.0") { // HTTP/2 Connection Preface string line = await clientStream.ReadLineAsync(cancellationToken); if (line != string.Empty) { throw new Exception($"HTTP/2 Protocol violation. Empty string expected, '{line}' received"); } line = await clientStream.ReadLineAsync(cancellationToken); if (line != "SM") { throw new Exception($"HTTP/2 Protocol violation. 'SM' expected, '{line}' received"); } line = await clientStream.ReadLineAsync(cancellationToken); if (line != string.Empty) { throw new Exception($"HTTP/2 Protocol violation. Empty string expected, '{line}' received"); } // create new connection using (var connection = await GetServerConnection(connectArgs, true, SslExtensions.Http2ProtocolAsList, cancellationToken)) { await connection.StreamWriter.WriteLineAsync("PRI * HTTP/2.0", cancellationToken); await connection.StreamWriter.WriteLineAsync(cancellationToken); await connection.StreamWriter.WriteLineAsync("SM", cancellationToken); await connection.StreamWriter.WriteLineAsync(cancellationToken); #if NETCOREAPP2_1 await Http2Helper.SendHttp2(clientStream, connection.Stream, BufferSize, (buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); }, connectArgs.CancellationTokenSource, clientConnection.Id, ExceptionFunc); #endif } } } // Now create the request await HandleHttpSessionRequest(endPoint, clientConnection, clientStream, clientStreamWriter, cancellationTokenSource, connectHostname, connectArgs?.WebSession.ConnectRequest); } catch (ProxyException e) { OnException(clientStream, e); } catch (IOException e) { OnException(clientStream, new Exception("Connection was aborted", e)); } catch (SocketException e) { OnException(clientStream, new Exception("Could not connect", e)); } catch (Exception e) { OnException(clientStream, new Exception("Error occured in whilst handling the client", e)); } finally { clientStream.Dispose(); if (!cancellationTokenSource.IsCancellationRequested) { cancellationTokenSource.Cancel(); } } }