public void HttpSocket3TimeNativeSocket(int count) { var ep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 50651); var req = new HttpRequest { Uri = new Uri("http://127.0.0.1:50651/x2.html") }; var reader = new HttpResponseReader(); var sw = Stopwatch.StartNew(); for (var i = 0; i < count; i++) { HttpResponse r; using (var socket = new Socket(SocketType.Stream, ProtocolType.Tcp)){ socket.Connect(ep); using (var s = new NetworkStream(socket)){ var w = new HttpRequestWriter(s); w.Write(req); r = reader.Read(s); } Assert.Greater(r.StringData.Length, 10); } } sw.Stop(); Console.WriteLine(sw.Elapsed); }
internal TcpServerConnection(ProxyServer proxyServer, TcpClient tcpClient, CustomBufferedStream stream) { this.tcpClient = tcpClient; LastAccess = DateTime.Now; this.proxyServer = proxyServer; this.proxyServer.UpdateServerConnectionCount(true); StreamWriter = new HttpRequestWriter(stream, proxyServer.BufferPool); Stream = stream; }
string Execute(HttpRequest request) { var ms = new MemoryStream(); var hrw = new HttpRequestWriter(ms); hrw.Write(request); var size = ms.Position; ms.Position = 0; var buffer = new byte[size]; ms.Read(buffer, 0, (int)size); return(Encoding.UTF8.GetString(buffer)); }
/// <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> /// Creates a TCP connection to server /// </summary> /// <param name="remoteHostName">The remote hostname.</param> /// <param name="remotePort">The remote port.</param> /// <param name="httpVersion">The http version to use.</param> /// <param name="isHttps">Is this a HTTPS request.</param> /// <param name="applicationProtocols">The list of HTTPS application level protocol to negotiate if needed.</param> /// <param name="isConnect">Is this a CONNECT request.</param> /// <param name="proxyServer">The current ProxyServer instance.</param> /// <param name="session">The http session.</param> /// <param name="upStreamEndPoint">The local upstream endpoint to make request via.</param> /// <param name="externalProxy">The external proxy to make request via.</param> /// <param name="cancellationToken">The cancellation token for this async task.</param> /// <returns></returns> private async Task <TcpServerConnection> createServerConnection(string remoteHostName, int remotePort, Version httpVersion, bool isHttps, List <SslApplicationProtocol> applicationProtocols, bool isConnect, ProxyServer proxyServer, SessionEventArgsBase session, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy, CancellationToken cancellationToken) { //deny connection to proxy end points to avoid infinite connection loop. if (Server.ProxyEndPoints.Any(x => x.Port == remotePort) && NetworkHelper.IsLocalIpAddress(remoteHostName)) { throw new Exception($"A client is making HTTP request to one of the listening ports of this proxy {remoteHostName}:{remotePort}"); } if (externalProxy != null) { if (Server.ProxyEndPoints.Any(x => x.Port == externalProxy.Port) && NetworkHelper.IsLocalIpAddress(externalProxy.HostName)) { throw new Exception($"A client is making HTTP request via external proxy to one of the listening ports of this proxy {remoteHostName}:{remotePort}"); } } bool useUpstreamProxy = false; // check if external proxy is set for HTTP/HTTPS if (externalProxy != null && !(externalProxy.HostName == remoteHostName && externalProxy.Port == remotePort)) { useUpstreamProxy = true; // check if we need to ByPass if (externalProxy.BypassLocalhost && NetworkHelper.IsLocalIpAddress(remoteHostName)) { useUpstreamProxy = false; } } TcpClient tcpClient = null; CustomBufferedStream stream = null; SslApplicationProtocol negotiatedApplicationProtocol = default; try { tcpClient = new TcpClient(upStreamEndPoint) { NoDelay = proxyServer.NoDelay, ReceiveTimeout = proxyServer.ConnectionTimeOutSeconds * 1000, SendTimeout = proxyServer.ConnectionTimeOutSeconds * 1000, LingerState = new LingerOption(true, proxyServer.TcpTimeWaitSeconds) }; //linux has a bug with socket reuse in .net core. if (proxyServer.ReuseSocket && RunTime.IsWindows) { tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); } var hostname = useUpstreamProxy ? externalProxy.HostName : remoteHostName; var port = useUpstreamProxy ? externalProxy.Port : remotePort; var ipAddresses = await Dns.GetHostAddressesAsync(hostname); if (ipAddresses == null || ipAddresses.Length == 0) { throw new Exception($"Could not resolve the hostname {hostname}"); } if (session != null) { session.TimeLine["Dns Resolved"] = DateTime.Now; } for (int i = 0; i < ipAddresses.Length; i++) { try { await tcpClient.ConnectAsync(ipAddresses[i], port); break; } catch (Exception e) { if (i == ipAddresses.Length - 1) { throw new Exception($"Could not establish connection to {hostname}", e); } } } if (session != null) { session.TimeLine["Connection Established"] = DateTime.Now; } await proxyServer.InvokeConnectionCreateEvent(tcpClient, false); stream = new CustomBufferedStream(tcpClient.GetStream(), proxyServer.BufferPool, proxyServer.BufferSize); if (useUpstreamProxy && (isConnect || isHttps)) { var writer = new HttpRequestWriter(stream, proxyServer.BufferPool, proxyServer.BufferSize); var connectRequest = new ConnectRequest { OriginalUrl = $"{remoteHostName}:{remotePort}", HttpVersion = httpVersion }; connectRequest.Headers.AddHeader(KnownHeaders.Connection, KnownHeaders.ConnectionKeepAlive); if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null) { connectRequest.Headers.AddHeader(HttpHeader.ProxyConnectionKeepAlive); connectRequest.Headers.AddHeader( HttpHeader.GetProxyAuthorizationHeader(externalProxy.UserName, externalProxy.Password)); } await writer.WriteRequestAsync(connectRequest, cancellationToken : cancellationToken); string httpStatus = await stream.ReadLineAsync(cancellationToken); Response.ParseResponseLine(httpStatus, out _, out int statusCode, out string statusDescription); if (statusCode != 200 && !statusDescription.EqualsIgnoreCase("OK") && !statusDescription.EqualsIgnoreCase("Connection Established")) { throw new Exception("Upstream proxy failed to create a secure tunnel"); } await stream.ReadAndIgnoreAllLinesAsync(cancellationToken); } if (isHttps) { var sslStream = new SslStream(stream, false, proxyServer.ValidateServerCertificate, proxyServer.SelectClientCertificate); stream = new CustomBufferedStream(sslStream, proxyServer.BufferPool, proxyServer.BufferSize); var options = new SslClientAuthenticationOptions { ApplicationProtocols = applicationProtocols, TargetHost = remoteHostName, ClientCertificates = null, EnabledSslProtocols = proxyServer.SupportedSslProtocols, CertificateRevocationCheckMode = proxyServer.CheckCertificateRevocation }; await sslStream.AuthenticateAsClientAsync(options, cancellationToken); #if NETCOREAPP2_1 negotiatedApplicationProtocol = sslStream.NegotiatedApplicationProtocol; #endif if (session != null) { session.TimeLine["HTTPS Established"] = DateTime.Now; } } } catch (Exception) { stream?.Dispose(); tcpClient?.Close(); throw; } return(new TcpServerConnection(proxyServer, tcpClient) { UpStreamProxy = externalProxy, UpStreamEndPoint = upStreamEndPoint, HostName = remoteHostName, Port = remotePort, IsHttps = isHttps, NegotiatedApplicationProtocol = negotiatedApplicationProtocol, UseUpstreamProxy = useUpstreamProxy, StreamWriter = new HttpRequestWriter(stream, proxyServer.BufferPool, proxyServer.BufferSize), Stream = stream, Version = httpVersion }); }
/// <summary> /// Creates a TCP connection to server /// </summary> /// <param name="server"></param> /// <param name="remoteHostName"></param> /// <param name="remotePort"></param> /// <param name="httpVersion"></param> /// <param name="isHttps"></param> /// <param name="isConnect"></param> /// <param name="upStreamEndPoint"></param> /// <param name="externalProxy"></param> /// <returns></returns> internal async Task <TcpConnection> CreateClient(ProxyServer server, string remoteHostName, int remotePort, Version httpVersion, bool isHttps, bool isConnect, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy) { bool useUpstreamProxy = false; //check if external proxy is set for HTTP/HTTPS if (externalProxy != null && !(externalProxy.HostName == remoteHostName && externalProxy.Port == remotePort)) { useUpstreamProxy = true; //check if we need to ByPass if (externalProxy.BypassLocalhost && NetworkHelper.IsLocalIpAddress(remoteHostName)) { useUpstreamProxy = false; } } TcpClient client = null; CustomBufferedStream stream = null; try { client = new TcpClient(upStreamEndPoint); //If this proxy uses another external proxy then create a tunnel request for HTTP/HTTPS connections if (useUpstreamProxy) { await client.ConnectAsync(externalProxy.HostName, externalProxy.Port); } else { await client.ConnectAsync(remoteHostName, remotePort); } stream = new CustomBufferedStream(client.GetStream(), server.BufferSize); if (useUpstreamProxy && (isConnect || isHttps)) { var writer = new HttpRequestWriter(stream, server.BufferSize); await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}"); await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}"); await writer.WriteLineAsync($"{KnownHeaders.Connection}: {KnownHeaders.ConnectionKeepAlive}"); if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null) { await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer); await writer.WriteLineAsync(KnownHeaders.ProxyAuthorization + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes( externalProxy.UserName + ":" + externalProxy.Password))); } await writer.WriteLineAsync(); using (var reader = new CustomBinaryReader(stream, server.BufferSize)) { string result = await reader.ReadLineAsync(); if (!new[] { "200 OK", "connection established" }.Any(s => result.ContainsIgnoreCase(s))) { throw new Exception("Upstream proxy failed to create a secure tunnel"); } await reader.ReadAndIgnoreAllLinesAsync(); } } if (isHttps) { var sslStream = new SslStream(stream, false, server.ValidateServerCertificate, server.SelectClientCertificate); stream = new CustomBufferedStream(sslStream, server.BufferSize); await sslStream.AuthenticateAsClientAsync(remoteHostName, null, server.SupportedSslProtocols, server.CheckCertificateRevocation); } client.ReceiveTimeout = server.ConnectionTimeOutSeconds * 1000; client.SendTimeout = server.ConnectionTimeOutSeconds * 1000; } catch (Exception) { stream?.Dispose(); client?.Close(); throw; } return(new TcpConnection(server) { UpStreamProxy = externalProxy, UpStreamEndPoint = upStreamEndPoint, HostName = remoteHostName, Port = remotePort, IsHttps = isHttps, UseUpstreamProxy = useUpstreamProxy, TcpClient = client, StreamReader = new CustomBinaryReader(stream, server.BufferSize), StreamWriter = new HttpRequestWriter(stream, server.BufferSize), Stream = stream, Version = httpVersion }); }
/// <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="client"></param> /// <param name="httpCmd"></param> /// <param name="clientStream"></param> /// <param name="clientStreamReader"></param> /// <param name="clientStreamWriter"></param> /// <param name="httpsConnectHostname"></param> /// <param name="endPoint"></param> /// <param name="connectRequest"></param> /// <param name="isTransparentEndPoint"></param> /// <returns></returns> private async Task <bool> HandleHttpSessionRequest(TcpClient client, string httpCmd, CustomBufferedStream clientStream, CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, string httpsConnectHostname, ProxyEndPoint endPoint, ConnectRequest connectRequest, bool isTransparentEndPoint = false) { bool disposed = false; TcpConnection connection = null; //Loop through each subsequest request on this particular client connection //(assuming HTTP connection is kept alive by client) while (true) { if (string.IsNullOrEmpty(httpCmd)) { break; } var args = new SessionEventArgs(BufferSize, endPoint, HandleHttpSessionResponse) { ProxyClient = { TcpClient = client }, WebSession = { ConnectRequest = connectRequest } }; 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(clientStreamReader, args.WebSession.Request.RequestHeaders); var httpRemoteUri = new Uri(httpsConnectHostname == null ? isTransparentEndPoint ? string.Concat("http://", args.WebSession.Request.Host, httpUrl) : httpUrl : string.Concat("https://", args.WebSession.Request.Host ?? httpsConnectHostname, httpUrl)); args.WebSession.Request.RequestUri = httpRemoteUri; args.WebSession.Request.OriginalRequestUrl = httpUrl; args.WebSession.Request.Method = httpMethod; args.WebSession.Request.HttpVersion = version; args.ProxyClient.ClientStream = clientStream; args.ProxyClient.ClientStreamReader = clientStreamReader; args.ProxyClient.ClientStreamWriter = clientStreamWriter; //proxy authorization check if (httpsConnectHostname == null && await CheckAuthorization(clientStreamWriter, args) == false) { args.Dispose(); break; } PrepareRequestHeaders(args.WebSession.Request.RequestHeaders); args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority; #if NET45 //if win auth is enabled //we need a cache of request body //so that we can send it after authentication in WinAuthHandler.cs if (EnableWinAuth && !RunTime.IsRunningOnMono && args.WebSession.Request.HasBody) { await args.GetRequestBody(); } #endif //If user requested interception do it if (BeforeRequest != null) { await BeforeRequest.InvokeParallelAsync(this, args, ExceptionFunc); } if (args.WebSession.Request.CancelRequest) { args.Dispose(); break; } //create a new connection if hostname changes if (connection != null && !connection.HostName.Equals(args.WebSession.Request.RequestUri.Host, StringComparison.OrdinalIgnoreCase)) { connection.Dispose(); UpdateServerConnectionCount(false); connection = null; } if (connection == null) { connection = await GetServerConnection(args, false); } //if upgrading to websocket then relay the requet without reading the contents if (args.WebSession.Request.UpgradeToWebSocket) { //prepare the prefix content var requestHeaders = args.WebSession.Request.RequestHeaders; byte[] requestBytes; using (var ms = new MemoryStream()) using (var writer = new HttpRequestWriter(ms)) { writer.WriteLine(httpCmd); writer.WriteHeaders(requestHeaders); requestBytes = ms.ToArray(); } await connection.Stream.WriteAsync(requestBytes, 0, requestBytes.Length); string httpStatus = await connection.StreamReader.ReadLineAsync(); Version responseVersion; int responseStatusCode; string responseStatusDescription; Response.ParseResponseLine(httpStatus, out responseVersion, out responseStatusCode, out responseStatusDescription); args.WebSession.Response.HttpVersion = responseVersion; args.WebSession.Response.ResponseStatusCode = responseStatusCode; args.WebSession.Response.ResponseStatusDescription = responseStatusDescription; await HeaderParser.ReadHeaders(connection.StreamReader, args.WebSession.Response.ResponseHeaders); await clientStreamWriter.WriteResponseAsync(args.WebSession.Response); //If user requested call back then do it if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked) { await BeforeResponse.InvokeParallelAsync(this, args, ExceptionFunc); } await TcpHelper.SendRaw(clientStream, connection.Stream, (buffer, offset, count) => { args.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); }); args.Dispose(); break; } //construct the web request that we are going to issue on behalf of the client. disposed = await HandleHttpSessionRequestInternal(connection, args, false); if (disposed) { //already disposed inside above method args.Dispose(); break; } //if connection is closing exit if (args.WebSession.Response.ResponseKeepAlive == false) { args.Dispose(); break; } args.Dispose(); // read the next request httpCmd = await clientStreamReader.ReadLineAsync(); } catch (Exception e) { ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request", e, args)); break; } } if (!disposed) { Dispose(clientStream, clientStreamReader, clientStreamWriter, connection); } return(true); }
/// <summary> /// Prepare and send the http(s) request /// </summary> /// <returns></returns> internal async Task SendRequest(bool enable100ContinueBehaviour) { var stream = ServerConnection.Stream; byte[] requestBytes; using (var ms = new MemoryStream()) using (var writer = new HttpRequestWriter(ms, bufferSize)) { var upstreamProxy = ServerConnection.UpStreamProxy; bool useUpstreamProxy = upstreamProxy != null && ServerConnection.IsHttps == false; //prepare the request & headers if (useUpstreamProxy) { writer.WriteLine($"{Request.Method} {Request.OriginalUrl} HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}"); } else { writer.WriteLine($"{Request.Method} {Request.RequestUri.PathAndQuery} HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}"); } //Send Authentication to Upstream proxy if needed if (upstreamProxy != null && ServerConnection.IsHttps == false && !string.IsNullOrEmpty(upstreamProxy.UserName) && upstreamProxy.Password != null) { HttpHeader.ProxyConnectionKeepAlive.WriteToStream(writer); HttpHeader.GetProxyAuthorizationHeader(upstreamProxy.UserName, upstreamProxy.Password).WriteToStream(writer); } //write request headers foreach (var header in Request.Headers) { if (header.Name != "Proxy-Authorization") { header.WriteToStream(writer); } } writer.WriteLine(); writer.Flush(); requestBytes = ms.ToArray(); } await stream.WriteAsync(requestBytes, 0, requestBytes.Length); await stream.FlushAsync(); if (enable100ContinueBehaviour) { if (Request.ExpectContinue) { string httpStatus = await ServerConnection.StreamReader.ReadLineAsync(); Version version; int responseStatusCode; string responseStatusDescription; Response.ParseResponseLine(httpStatus, out version, out responseStatusCode, out responseStatusDescription); //find if server is willing for expect continue if (responseStatusCode == (int)HttpStatusCode.Continue && responseStatusDescription.Equals("continue", StringComparison.CurrentCultureIgnoreCase)) { Request.Is100Continue = true; await ServerConnection.StreamReader.ReadLineAsync(); } else if (responseStatusCode == (int)HttpStatusCode.ExpectationFailed && responseStatusDescription.Equals("expectation failed", StringComparison.CurrentCultureIgnoreCase)) { Request.ExpectationFailed = true; await ServerConnection.StreamReader.ReadLineAsync(); } } } }