/// <summary> /// Start server /// </summary> public void Start() { _proxyServer.BeforeRequest += OnRequest; _proxyServer.BeforeResponse += OnResponse; _proxyServer.GetCustomUpStreamProxyFunc = GetCustomUpStreamProxy; // adding endpoints _thisEndPoint = new IPEndPoint(!string.IsNullOrEmpty(_proxyConfig.LocalAddress) ? IPAddress.Parse(_proxyConfig.LocalAddress) : Utils.LocalMachineIpAddress, _proxyConfig.LocalPort); _proxyServer.AddEndPoint(new ExplicitProxyEndPoint(_thisEndPoint.Address, _thisEndPoint.Port, true)); // starting the server _proxyServer.Start(); Logger.Log(ProxyType.HttpProxy, _thisEndPoint, _thisEndPoint, "Starting proxy server..."); // registering external proxies foreach (var externalProxyRule in _proxyConfig.ExternalProxyRules) { var newProxy = new ExternalProxy { HostName = externalProxyRule.ProxyHost, Port = externalProxyRule.ProxyPort }; if (!string.IsNullOrEmpty(externalProxyRule.ProxyUsername)) { newProxy.UserName = externalProxyRule.ProxyUsername; newProxy.Password = externalProxyRule.ProxyPassword; } if (!_externalProxies.ContainsKey(externalProxyRule.ProxyHost + ":" + externalProxyRule.ProxyPort)) { _externalProxies.Add(externalProxyRule.ProxyHost + ":" + externalProxyRule.ProxyPort, newProxy); } } }
/// <summary> /// Create a Server Connection /// </summary> /// <param name="args"></param> /// <param name="isConnect"></param> /// <returns></returns> private async Task <TcpConnection> GetServerConnection(SessionEventArgs args, bool isConnect) { ExternalProxy customUpStreamHttpProxy = null; ExternalProxy customUpStreamHttpsProxy = null; if (args.WebSession.Request.IsHttps) { if (GetCustomUpStreamHttpsProxyFunc != null) { customUpStreamHttpsProxy = await GetCustomUpStreamHttpsProxyFunc(args); } } else { if (GetCustomUpStreamHttpProxyFunc != null) { customUpStreamHttpProxy = await GetCustomUpStreamHttpProxyFunc(args); } } args.CustomUpStreamHttpProxyUsed = customUpStreamHttpProxy; args.CustomUpStreamHttpsProxyUsed = customUpStreamHttpsProxy; return(await tcpConnectionFactory.CreateClient(this, args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.WebSession.Request.HttpVersion, args.IsHttps, isConnect, customUpStreamHttpProxy ?? UpStreamHttpProxy, customUpStreamHttpsProxy ?? UpStreamHttpsProxy)); }
/// <summary> /// Gets the connection cache key. /// </summary> /// <param name="args">The session event arguments.</param> /// <param name="applicationProtocol"></param> /// <returns></returns> internal async Task <string> GetConnectionCacheKey(ProxyServer server, SessionEventArgsBase args, SslApplicationProtocol applicationProtocol) { List <SslApplicationProtocol> applicationProtocols = null; if (applicationProtocol != default) { applicationProtocols = new List <SslApplicationProtocol> { applicationProtocol }; } ExternalProxy customUpStreamProxy = null; bool isHttps = args.IsHttps; if (server.GetCustomUpStreamProxyFunc != null) { customUpStreamProxy = await server.GetCustomUpStreamProxyFunc(args); } args.CustomUpStreamProxyUsed = customUpStreamProxy; return(GetConnectionCacheKey( args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, isHttps, applicationProtocols, server, args.WebSession.UpStreamEndPoint ?? server.UpStreamEndPoint, customUpStreamProxy ?? (isHttps ? server.UpStreamHttpsProxy : server.UpStreamHttpProxy))); }
/// <summary> /// Gets the connection cache key. /// </summary> /// <param name="args">The session event arguments.</param> /// <param name="applicationProtocol"></param> /// <returns></returns> private async Task <string> getConnectionCacheKey(SessionEventArgsBase args, SslApplicationProtocol applicationProtocol) { List <SslApplicationProtocol> applicationProtocols = null; if (applicationProtocol != default) { applicationProtocols = new List <SslApplicationProtocol> { applicationProtocol }; } ExternalProxy customUpStreamProxy = null; bool isHttps = args.IsHttps; if (GetCustomUpStreamProxyFunc != null) { customUpStreamProxy = await GetCustomUpStreamProxyFunc(args); } args.CustomUpStreamProxyUsed = customUpStreamProxy; return(tcpConnectionFactory.GetConnectionCacheKey( args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, isHttps, applicationProtocols, this, args.WebSession.UpStreamEndPoint ?? UpStreamEndPoint, customUpStreamProxy ?? (isHttps ? UpStreamHttpsProxy : UpStreamHttpProxy))); }
/// <summary> /// implement any cleanup here /// </summary> public void Dispose() { httpResponseHandler = null; CustomUpStreamProxyUsed = null; WebSession.FinishSession(); }
internal string GetConnectionCacheKey(string remoteHostName, int remotePort, bool isHttps, List <SslApplicationProtocol> applicationProtocols, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy) { // http version is ignored since its an application level decision b/w HTTP 1.0/1.1 // also when doing connect request MS Edge browser sends http 1.0 but uses 1.1 after server sends 1.1 its response. // That can create cache miss for same server connection unnecessarily especially when prefetching with Connect. // http version 2 is separated using applicationProtocols below. var cacheKeyBuilder = new StringBuilder($"{remoteHostName}-{remotePort}-" + // when creating Tcp client isConnect won't matter $"{isHttps}-"); if (applicationProtocols != null) { foreach (var protocol in applicationProtocols.OrderBy(x => x)) { cacheKeyBuilder.Append($"{protocol}-"); } } cacheKeyBuilder.Append(upStreamEndPoint != null ? $"{upStreamEndPoint.Address}-{upStreamEndPoint.Port}-" : string.Empty); cacheKeyBuilder.Append(externalProxy != null ? $"{externalProxy.GetCacheKey()}-" : string.Empty); return(cacheKeyBuilder.ToString()); }
/// <summary> /// implement any cleanup here /// </summary> public void Dispose() { httpResponseHandler = null; CustomUpStreamHttpProxyUsed = null; CustomUpStreamHttpsProxyUsed = null; WebSession.Dispose(); }
public ExternalProxy GetProxy(Uri destination) { IList <string> proxies; if (GetAutoProxies(destination, out proxies)) { if (proxies == null) { return(null); } string proxyStr = proxies[0]; int port = 80; if (proxyStr.Contains(":")) { var parts = proxyStr.Split(new[] { ':' }, 2); proxyStr = parts[0]; port = int.Parse(parts[1]); } // TODO: Apply authorization var systemProxy = new ExternalProxy { HostName = proxyStr, Port = port, }; return(systemProxy); } if (proxy?.IsBypassed(destination) == true) { return(null); } var protocolType = ProxyInfo.ParseProtocolType(destination.Scheme); if (protocolType.HasValue) { HttpSystemProxyValue value = null; if (ProxyInfo?.Proxies?.TryGetValue(protocolType.Value, out value) == true) { var systemProxy = new ExternalProxy { HostName = value.HostName, Port = value.Port, }; return(systemProxy); } } return(null); }
/// <summary> /// Gets the system up stream proxy /// </summary> private Task <ExternalProxy> GetCustomUpStreamProxy(SessionEventArgs e) { try { // use a custom external proxy? foreach (var proxyRule in _proxyConfig.ExternalProxyRules) { var reg = new Regex(proxyRule.UrlMatch, RegexOptions.IgnoreCase); if (reg.Match(e.WebSession.Request.RequestUri.AbsoluteUri).Success) { ExternalProxy proxy = null; // proxyIgnore if (proxyRule.ProxyHost.Equals("NoProxy")) { break; } // try to use the system proxy? if (proxyRule.ProxyHost.Equals("SystemWebProxy")) { var webProxy = WebRequest.GetSystemWebProxy().GetProxy(e.WebSession.Request.RequestUri); proxy = new ExternalProxy { HostName = webProxy.Host, Port = webProxy.Port }; } // use a configured external proxy if (_externalProxies.ContainsKey(proxyRule.ProxyHost + ":" + proxyRule.ProxyPort)) { proxy = _externalProxies[proxyRule.ProxyHost + ":" + proxyRule.ProxyPort]; } if (proxy != null) { Logger.Log(ProxyType.HttpProxy, e.ClientEndPoint, _thisEndPoint, $"EXTERNALPROXY new connexion ({_proxyServer.ServerConnectionCount}) to {e.WebSession.Request.RequestUri.Host}:{e.WebSession.Request.RequestUri.Port} using {proxy.HostName}:{proxy.Port}"); return(Task.FromResult(proxy)); } } } } catch (Exception ex) { ErrorHandler.LogErrors(ex, "GetCustomUpStreamProxy"); } return(Task.FromResult(new ExternalProxy { HostName = e.WebSession.Request.RequestUri.Host, Port = e.WebSession.Request.RequestUri.Port })); }
/// <summary> /// Gets the system up stream proxy. /// </summary> /// <param name="sessionEventArgs">The <see cref="SessionEventArgs"/> instance containing the event data.</param> /// <returns><see cref="ExternalProxy"/> instance containing valid proxy configuration from PAC/WAPD scripts if any exists.</returns> private Task <ExternalProxy> GetSystemUpStreamProxy(SessionEventArgs sessionEventArgs) { // Use built-in WebProxy class to handle PAC/WAPD scripts. var systemProxyResolver = new WebProxy(); var systemProxyUri = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri); // TODO: Apply authorization var systemProxy = new ExternalProxy { HostName = systemProxyUri.Host, Port = systemProxyUri.Port }; return(Task.FromResult(systemProxy)); }
/// <summary> /// Create a server connection. /// </summary> /// <param name="args">The session event arguments.</param> /// <param name="isConnect">Is this a CONNECT request.</param> /// <param name="applicationProtocols"></param> /// <param name="cancellationToken">The cancellation token for this async task.</param> /// <returns></returns> internal async Task <TcpServerConnection> GetServerConnection(ProxyServer server, SessionEventArgsBase args, bool isConnect, List <SslApplicationProtocol> applicationProtocols, bool noCache, CancellationToken cancellationToken) { ExternalProxy customUpStreamProxy = null; bool isHttps = args.IsHttps; if (server.GetCustomUpStreamProxyFunc != null) { customUpStreamProxy = await server.GetCustomUpStreamProxyFunc(args); } args.CustomUpStreamProxyUsed = customUpStreamProxy; return(await GetServerConnection( args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.WebSession.Request.HttpVersion, isHttps, applicationProtocols, isConnect, server, args.WebSession.UpStreamEndPoint ?? server.UpStreamEndPoint, customUpStreamProxy ?? (isHttps ? server.UpStreamHttpsProxy : server.UpStreamHttpProxy), noCache, cancellationToken)); }
/// <summary> /// Create a server connection. /// </summary> /// <param name="args">The session event arguments.</param> /// <param name="isConnect">Is this a CONNECT request.</param> /// <param name="applicationProtocols"></param> /// <param name="cancellationToken">The cancellation token for this async task.</param> /// <returns></returns> private async Task <TcpServerConnection> GetServerConnection(SessionEventArgsBase args, bool isConnect, List <SslApplicationProtocol> applicationProtocols, CancellationToken cancellationToken) { ExternalProxy customUpStreamProxy = null; bool isHttps = args.IsHttps; if (GetCustomUpStreamProxyFunc != null) { customUpStreamProxy = await GetCustomUpStreamProxyFunc(args); } args.CustomUpStreamProxyUsed = customUpStreamProxy; return(await tcpConnectionFactory.GetClient( args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.WebSession.Request.HttpVersion, isHttps, applicationProtocols, isConnect, this, args.WebSession.UpStreamEndPoint ?? UpStreamEndPoint, customUpStreamProxy ?? (isHttps ? args.WebSession.Request.UpStreamHttpsProxy ?? UpStreamHttpsProxy : args.WebSession.Request.UpStreamHttpProxy ?? UpStreamHttpProxy), cancellationToken)); }
/// <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> /// Gets a TCP connection to server from connection pool. /// </summary> /// <param name="remoteHostName">The remote hostname.</param> /// <param name="remotePort">The remote port.</param> /// <param name="httpVersion">The http version to use.</param> /// <param name="isHttps">Is this a HTTPS request.</param> /// <param name="applicationProtocols">The list of HTTPS application level protocol to negotiate if needed.</param> /// <param name="isConnect">Is this a CONNECT request.</param> /// <param name="proxyServer">The current ProxyServer instance.</param> /// <param name="upStreamEndPoint">The local upstream endpoint to make request via.</param> /// <param name="externalProxy">The external proxy to make request via.</param> /// <param name="noCache">Not from cache/create new connection.</param> /// <param name="cancellationToken">The cancellation token for this async task.</param> /// <returns></returns> internal async Task <TcpServerConnection> GetServerConnection(string remoteHostName, int remotePort, Version httpVersion, bool isHttps, List <SslApplicationProtocol> applicationProtocols, bool isConnect, ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy, bool noCache, CancellationToken cancellationToken) { var cacheKey = GetConnectionCacheKey(remoteHostName, remotePort, isHttps, applicationProtocols, proxyServer, upStreamEndPoint, externalProxy); if (proxyServer.EnableConnectionPool && !noCache) { if (cache.TryGetValue(cacheKey, out var existingConnections)) { while (existingConnections.Count > 0) { if (existingConnections.TryDequeue(out var recentConnection)) { //+3 seconds for potential delay after getting connection var cutOff = DateTime.Now.AddSeconds(-1 * proxyServer.ConnectionTimeOutSeconds + 3); if (recentConnection.LastAccess > cutOff && isGoodConnection(recentConnection.TcpClient)) { return(recentConnection); } disposalBag.Add(recentConnection); } } } } var connection = await createServerConnection(remoteHostName, remotePort, httpVersion, isHttps, applicationProtocols, isConnect, proxyServer, upStreamEndPoint, externalProxy, cancellationToken); connection.CacheKey = cacheKey; return(connection); }
/// <summary> /// Creates a TCP connection to server /// </summary> /// <param name="remoteHostName">The remote hostname.</param> /// <param name="remotePort">The remote port.</param> /// <param name="httpVersion">The http version to use.</param> /// <param name="isHttps">Is this a HTTPS request.</param> /// <param name="applicationProtocols">The list of HTTPS application level protocol to negotiate if needed.</param> /// <param name="isConnect">Is this a CONNECT request.</param> /// <param name="proxyServer">The current ProxyServer instance.</param> /// <param name="session">The http session.</param> /// <param name="upStreamEndPoint">The local upstream endpoint to make request via.</param> /// <param name="externalProxy">The external proxy to make request via.</param> /// <param name="cancellationToken">The cancellation token for this async task.</param> /// <returns></returns> private async Task <TcpServerConnection> createServerConnection(string remoteHostName, int remotePort, Version httpVersion, bool isHttps, List <SslApplicationProtocol> applicationProtocols, bool isConnect, ProxyServer proxyServer, SessionEventArgsBase session, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy, CancellationToken cancellationToken) { //deny connection to proxy end points to avoid infinite connection loop. if (Server.ProxyEndPoints.Any(x => x.Port == remotePort) && NetworkHelper.IsLocalIpAddress(remoteHostName)) { throw new Exception($"A client is making HTTP request to one of the listening ports of this proxy {remoteHostName}:{remotePort}"); } if (externalProxy != null) { if (Server.ProxyEndPoints.Any(x => x.Port == externalProxy.Port) && NetworkHelper.IsLocalIpAddress(externalProxy.HostName)) { throw new Exception($"A client is making HTTP request via external proxy to one of the listening ports of this proxy {remoteHostName}:{remotePort}"); } } bool useUpstreamProxy = false; // check if external proxy is set for HTTP/HTTPS if (externalProxy != null && !(externalProxy.HostName == remoteHostName && externalProxy.Port == remotePort)) { useUpstreamProxy = true; // check if we need to ByPass if (externalProxy.BypassLocalhost && NetworkHelper.IsLocalIpAddress(remoteHostName)) { useUpstreamProxy = false; } } TcpClient tcpClient = null; CustomBufferedStream stream = null; SslApplicationProtocol negotiatedApplicationProtocol = default; try { tcpClient = new TcpClient(upStreamEndPoint) { NoDelay = proxyServer.NoDelay, ReceiveTimeout = proxyServer.ConnectionTimeOutSeconds * 1000, SendTimeout = proxyServer.ConnectionTimeOutSeconds * 1000, LingerState = new LingerOption(true, proxyServer.TcpTimeWaitSeconds) }; //linux has a bug with socket reuse in .net core. if (proxyServer.ReuseSocket && RunTime.IsWindows) { tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); } var hostname = useUpstreamProxy ? externalProxy.HostName : remoteHostName; var port = useUpstreamProxy ? externalProxy.Port : remotePort; var ipAddresses = await Dns.GetHostAddressesAsync(hostname); if (ipAddresses == null || ipAddresses.Length == 0) { throw new Exception($"Could not resolve the hostname {hostname}"); } if (session != null) { session.TimeLine["Dns Resolved"] = DateTime.Now; } for (int i = 0; i < ipAddresses.Length; i++) { try { await tcpClient.ConnectAsync(ipAddresses[i], port); break; } catch (Exception e) { if (i == ipAddresses.Length - 1) { throw new Exception($"Could not establish connection to {hostname}", e); } } } if (session != null) { session.TimeLine["Connection Established"] = DateTime.Now; } await proxyServer.InvokeConnectionCreateEvent(tcpClient, false); stream = new CustomBufferedStream(tcpClient.GetStream(), proxyServer.BufferPool, proxyServer.BufferSize); if (useUpstreamProxy && (isConnect || isHttps)) { var writer = new HttpRequestWriter(stream, proxyServer.BufferPool, proxyServer.BufferSize); var connectRequest = new ConnectRequest { OriginalUrl = $"{remoteHostName}:{remotePort}", HttpVersion = httpVersion }; connectRequest.Headers.AddHeader(KnownHeaders.Connection, KnownHeaders.ConnectionKeepAlive); if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null) { connectRequest.Headers.AddHeader(HttpHeader.ProxyConnectionKeepAlive); connectRequest.Headers.AddHeader( HttpHeader.GetProxyAuthorizationHeader(externalProxy.UserName, externalProxy.Password)); } await writer.WriteRequestAsync(connectRequest, cancellationToken : cancellationToken); string httpStatus = await stream.ReadLineAsync(cancellationToken); Response.ParseResponseLine(httpStatus, out _, out int statusCode, out string statusDescription); if (statusCode != 200 && !statusDescription.EqualsIgnoreCase("OK") && !statusDescription.EqualsIgnoreCase("Connection Established")) { throw new Exception("Upstream proxy failed to create a secure tunnel"); } await stream.ReadAndIgnoreAllLinesAsync(cancellationToken); } if (isHttps) { var sslStream = new SslStream(stream, false, proxyServer.ValidateServerCertificate, proxyServer.SelectClientCertificate); stream = new CustomBufferedStream(sslStream, proxyServer.BufferPool, proxyServer.BufferSize); var options = new SslClientAuthenticationOptions { ApplicationProtocols = applicationProtocols, TargetHost = remoteHostName, ClientCertificates = null, EnabledSslProtocols = proxyServer.SupportedSslProtocols, CertificateRevocationCheckMode = proxyServer.CheckCertificateRevocation }; await sslStream.AuthenticateAsClientAsync(options, cancellationToken); #if NETCOREAPP2_1 negotiatedApplicationProtocol = sslStream.NegotiatedApplicationProtocol; #endif if (session != null) { session.TimeLine["HTTPS Established"] = DateTime.Now; } } } catch (Exception) { stream?.Dispose(); tcpClient?.Close(); throw; } return(new TcpServerConnection(proxyServer, tcpClient) { UpStreamProxy = externalProxy, UpStreamEndPoint = upStreamEndPoint, HostName = remoteHostName, Port = remotePort, IsHttps = isHttps, NegotiatedApplicationProtocol = negotiatedApplicationProtocol, UseUpstreamProxy = useUpstreamProxy, StreamWriter = new HttpRequestWriter(stream, proxyServer.BufferPool, proxyServer.BufferSize), Stream = stream, Version = httpVersion }); }
/// <summary> /// Creates a TCP connection to server /// </summary> /// <param name="server"></param> /// <param name="remoteHostName"></param> /// <param name="remotePort"></param> /// <param name="httpVersion"></param> /// <param name="isHttps"></param> /// <param name="isConnect"></param> /// <param name="upStreamEndPoint"></param> /// <param name="externalProxy"></param> /// <returns></returns> internal async Task <TcpConnection> CreateClient(ProxyServer server, string remoteHostName, int remotePort, Version httpVersion, bool isHttps, bool isConnect, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy) { bool useUpstreamProxy = false; //check if external proxy is set for HTTP/HTTPS if (externalProxy != null && !(externalProxy.HostName == remoteHostName && externalProxy.Port == remotePort)) { useUpstreamProxy = true; //check if we need to ByPass if (externalProxy.BypassLocalhost && NetworkHelper.IsLocalIpAddress(remoteHostName)) { useUpstreamProxy = false; } } TcpClient client = null; CustomBufferedStream stream = null; try { client = new TcpClient(upStreamEndPoint); //If this proxy uses another external proxy then create a tunnel request for HTTP/HTTPS connections if (useUpstreamProxy) { await client.ConnectAsync(externalProxy.HostName, externalProxy.Port); } else { await client.ConnectAsync(remoteHostName, remotePort); } stream = new CustomBufferedStream(client.GetStream(), server.BufferSize); if (useUpstreamProxy && (isConnect || isHttps)) { var writer = new HttpRequestWriter(stream, server.BufferSize); await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}"); await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}"); await writer.WriteLineAsync($"{KnownHeaders.Connection}: {KnownHeaders.ConnectionKeepAlive}"); if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null) { await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer); await writer.WriteLineAsync(KnownHeaders.ProxyAuthorization + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes( externalProxy.UserName + ":" + externalProxy.Password))); } await writer.WriteLineAsync(); using (var reader = new CustomBinaryReader(stream, server.BufferSize)) { string result = await reader.ReadLineAsync(); if (!new[] { "200 OK", "connection established" }.Any(s => result.ContainsIgnoreCase(s))) { throw new Exception("Upstream proxy failed to create a secure tunnel"); } await reader.ReadAndIgnoreAllLinesAsync(); } } if (isHttps) { var sslStream = new SslStream(stream, false, server.ValidateServerCertificate, server.SelectClientCertificate); stream = new CustomBufferedStream(sslStream, server.BufferSize); await sslStream.AuthenticateAsClientAsync(remoteHostName, null, server.SupportedSslProtocols, server.CheckCertificateRevocation); } client.ReceiveTimeout = server.ConnectionTimeOutSeconds * 1000; client.SendTimeout = server.ConnectionTimeOutSeconds * 1000; } catch (Exception) { stream?.Dispose(); client?.Close(); throw; } return(new TcpConnection(server) { UpStreamProxy = externalProxy, UpStreamEndPoint = upStreamEndPoint, HostName = remoteHostName, Port = remotePort, IsHttps = isHttps, UseUpstreamProxy = useUpstreamProxy, TcpClient = client, StreamReader = new CustomBinaryReader(stream, server.BufferSize), StreamWriter = new HttpRequestWriter(stream, server.BufferSize), Stream = stream, Version = httpVersion }); }
/// <summary> /// Create connection to a particular host/port optionally with SSL and a particular HTTP version /// </summary> /// <param name="hostname"></param> /// <param name="port"></param> /// <param name="isHttps"></param> /// <param name="version"></param> /// <returns></returns> private async Task <TcpConnectionCache> CreateClient(string hostname, int port, bool isHttps, Version version, ExternalProxy upStreamHttpProxy, ExternalProxy upStreamHttpsProxy, int bufferSize, SslProtocols supportedSslProtocols, RemoteCertificateValidationCallback remoteCertificateValidationCallBack, LocalCertificateSelectionCallback localCertificateSelectionCallback) { TcpClient client; Stream stream; if (isHttps) { SslStream sslStream = null; //If this proxy uses another external proxy then create a tunnel request for HTTPS connections if (upStreamHttpsProxy != null) { client = new TcpClient(upStreamHttpsProxy.HostName, upStreamHttpsProxy.Port); stream = (Stream)client.GetStream(); using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true)) { await writer.WriteLineAsync(string.Format("CONNECT {0}:{1} {2}", hostname, port, version)); await writer.WriteLineAsync(string.Format("Host: {0}:{1}", hostname, port)); await writer.WriteLineAsync("Connection: Keep-Alive"); await writer.WriteLineAsync(); await writer.FlushAsync(); writer.Close(); } using (var reader = new CustomBinaryReader(stream)) { var result = await reader.ReadLineAsync(); if (!result.ToLower().Contains("200 connection established")) { throw new Exception("Upstream proxy failed to create a secure tunnel"); } await reader.ReadAllLinesAsync(); } } else { client = new TcpClient(hostname, port); stream = (Stream)client.GetStream(); } try { sslStream = new SslStream(stream, true, remoteCertificateValidationCallBack, localCertificateSelectionCallback); await sslStream.AuthenticateAsClientAsync(hostname, null, supportedSslProtocols, false); stream = (Stream)sslStream; } catch { if (sslStream != null) { sslStream.Dispose(); } throw; } } else { if (upStreamHttpProxy != null) { client = new TcpClient(upStreamHttpProxy.HostName, upStreamHttpProxy.Port); stream = (Stream)client.GetStream(); } else { client = new TcpClient(hostname, port); stream = (Stream)client.GetStream(); } } return(new TcpConnectionCache() { HostName = hostname, port = port, IsHttps = isHttps, TcpClient = client, StreamReader = new CustomBinaryReader(stream), Stream = stream, Version = version }); }
private async Task HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args, ExternalProxy customUpStreamHttpProxy, ExternalProxy customUpStreamHttpsProxy, bool CloseConnection) { try { if (connection == null) { if (args.WebSession.Request.RequestUri.Scheme == "http") { if (GetCustomUpStreamHttpProxyFunc != null) { customUpStreamHttpProxy = await GetCustomUpStreamHttpProxyFunc(args).ConfigureAwait(false); } } else { if (GetCustomUpStreamHttpsProxyFunc != null) { customUpStreamHttpsProxy = await GetCustomUpStreamHttpsProxyFunc(args).ConfigureAwait(false); } } args.CustomUpStreamHttpProxyUsed = customUpStreamHttpProxy; args.CustomUpStreamHttpsProxyUsed = customUpStreamHttpsProxy; connection = await tcpConnectionFactory.CreateClient(BUFFER_SIZE, ConnectionTimeOutSeconds, args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.WebSession.Request.HttpVersion, args.IsHttps, SupportedSslProtocols, new RemoteCertificateValidationCallback(ValidateServerCertificate), new LocalCertificateSelectionCallback(SelectClientCertificate), customUpStreamHttpProxy ?? UpStreamHttpProxy, customUpStreamHttpsProxy ?? UpStreamHttpsProxy, args.ProxyClient.ClientStream, UpStreamEndPoint); } args.WebSession.Request.RequestLocked = true; //If request was cancelled by user then dispose the client if (args.WebSession.Request.CancelRequest) { Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args); return; } //if expect continue is enabled then send the headers first //and see if server would return 100 conitinue if (args.WebSession.Request.ExpectContinue) { args.WebSession.SetConnection(connection); await args.WebSession.SendRequest(Enable100ContinueBehaviour); } //If 100 continue was the response inform that to the client if (Enable100ContinueBehaviour) { if (args.WebSession.Request.Is100Continue) { await WriteResponseStatus(args.WebSession.Response.HttpVersion, "100", "Continue", args.ProxyClient.ClientStreamWriter); await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); } else if (args.WebSession.Request.ExpectationFailed) { await WriteResponseStatus(args.WebSession.Response.HttpVersion, "417", "Expectation Failed", args.ProxyClient.ClientStreamWriter); await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); } } //If expect continue is not enabled then set the connectio and send request headers if (!args.WebSession.Request.ExpectContinue) { args.WebSession.SetConnection(connection); await args.WebSession.SendRequest(Enable100ContinueBehaviour); } //If request was modified by user if (args.WebSession.Request.RequestBodyRead) { if (args.WebSession.Request.ContentEncoding != null) { args.WebSession.Request.RequestBody = await GetCompressedResponseBody(args.WebSession.Request.ContentEncoding, args.WebSession.Request.RequestBody); } //chunked send is not supported as of now args.WebSession.Request.ContentLength = args.WebSession.Request.RequestBody.Length; var newStream = args.WebSession.ServerConnection.Stream; await newStream.WriteAsync(args.WebSession.Request.RequestBody, 0, args.WebSession.Request.RequestBody.Length); } else { if (!args.WebSession.Request.ExpectationFailed) { //If its a post/put/patch request, then read the client html body and send it to server var method = args.WebSession.Request.Method.ToUpper(); if (method == "POST" || method == "PUT" || method == "PATCH") { await SendClientRequestBody(args); } } } //If not expectation failed response was returned by server then parse response if (!args.WebSession.Request.ExpectationFailed) { await HandleHttpSessionResponse(args); } //if connection is closing exit if (args.WebSession.Response.ResponseKeepAlive == false) { Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args); return; } } catch (Exception e) { ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request (internal)", e, args)); Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args); return; } if (CloseConnection) { //dispose connection?.Dispose(); } }
/// <summary> /// Gets a TCP connection to server from connection pool. /// </summary> /// <param name="remoteHostName">The remote hostname.</param> /// <param name="remotePort">The remote port.</param> /// <param name="httpVersion">The http version to use.</param> /// <param name="isHttps">Is this a HTTPS request.</param> /// <param name="applicationProtocols">The list of HTTPS application level protocol to negotiate if needed.</param> /// <param name="isConnect">Is this a CONNECT request.</param> /// <param name="proxyServer">The current ProxyServer instance.</param> /// <param name="upStreamEndPoint">The local upstream endpoint to make request via.</param> /// <param name="externalProxy">The external proxy to make request via.</param> /// <param name="cancellationToken">The cancellation token for this async task.</param> /// <returns></returns> internal async Task <TcpServerConnection> GetClient(string remoteHostName, int remotePort, Version httpVersion, bool isHttps, List <SslApplicationProtocol> applicationProtocols, bool isConnect, ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy, CancellationToken cancellationToken) { string cacheKey = null; var cacheKeyBuilder = new StringBuilder($"{remoteHostName}-{remotePort}" + $"-{(httpVersion == null ? string.Empty : httpVersion.ToString())}" + $"-{isHttps}-{isConnect}-"); if (applicationProtocols != null) { foreach (var protocol in applicationProtocols) { cacheKeyBuilder.Append($"{protocol}-"); } } cacheKeyBuilder.Append(upStreamEndPoint != null ? $"{upStreamEndPoint.Address}-{upStreamEndPoint.Port}-" : string.Empty); cacheKeyBuilder.Append(externalProxy != null ? $"{externalProxy.GetCacheKey()}-" : string.Empty); cacheKey = cacheKeyBuilder.ToString(); if (proxyServer.EnableConnectionPool) { if (cache.TryGetValue(cacheKey, out var existingConnections)) { while (existingConnections.TryDequeue(out var recentConnection)) { //+3 seconds for potential delay after getting connection var cutOff = DateTime.Now.AddSeconds((-1 * proxyServer.ConnectionTimeOutSeconds) + 3); if (recentConnection.LastAccess > cutOff && IsGoodConnection(recentConnection.TcpClient)) { return(recentConnection); } disposalBag.Add(recentConnection); } } } var connection = await CreateClient(remoteHostName, remotePort, httpVersion, isHttps, applicationProtocols, isConnect, proxyServer, upStreamEndPoint, externalProxy, cancellationToken); connection.CacheKey = cacheKey; return(connection); }
public async Task Nested_Proxy_Farm_Without_Connection_Cache_Should_Not_Hang() { var rnd = new Random(); var testSuite = new TestSuite(); var server = testSuite.GetServer(); server.HandleRequest((context) => { return(context.Response.WriteAsync("I am server. I received your greetings.")); }); var proxies2 = new List <ProxyServer>(); //create a level 2 upstream proxy farm that forwards to server for (int i = 0; i < 10; i++) { var proxy2 = testSuite.GetProxy(); proxy2.ProxyBasicAuthenticateFunc += (_, _, _) => { return(Task.FromResult(true)); }; proxies2.Add(proxy2); } var proxies1 = new List <ProxyServer>(); //create a level 1 upstream proxy farm that forwards to level 2 farm for (int i = 0; i < 10; i++) { var proxy1 = testSuite.GetProxy(); proxy1.EnableConnectionPool = false; var proxy2 = proxies2[rnd.Next() % proxies2.Count]; proxy1.GetCustomUpStreamProxyFunc += async(_) => { var proxy = new ExternalProxy() { HostName = "localhost", Port = proxy2.ProxyEndPoints[0].Port, ProxyType = ExternalProxyType.Http, UserName = "******", Password = "******" }; return(await Task.FromResult(proxy)); }; proxies1.Add(proxy1); } var tasks = new List <Task>(); //send multiple concurrent requests from client => proxy farm 1 => proxy farm 2 => server for (int j = 0; j < 10_000; j++) { var task = Task.Run(async() => { try { var proxy = proxies1[rnd.Next() % proxies1.Count]; using var client = testSuite.GetClient(proxy); //tests should not keep hanging for 30 mins. client.Timeout = TimeSpan.FromMinutes(30); await client.PostAsync(new Uri(server.ListeningHttpsUrl), new StringContent("hello server. I am a client.")); } //if error is thrown because of server getting overloaded its okay. //But client.PostAsync should'nt hang in all cases. catch { } }); tasks.Add(task); } await Task.WhenAll(tasks); }
/// <summary> /// Get a TcpConnection to the specified host, port optionally HTTPS and a particular HTTP version /// </summary> /// <param name="hostname"></param> /// <param name="port"></param> /// <param name="isHttps"></param> /// <param name="version"></param> /// <returns></returns> internal async Task <TcpConnectionCache> GetClient(string hostname, int port, bool isHttps, Version version, ExternalProxy upStreamHttpProxy, ExternalProxy upStreamHttpsProxy, int bufferSize, SslProtocols supportedSslProtocols, RemoteCertificateValidationCallback remoteCertificateValidationCallBack, LocalCertificateSelectionCallback localCertificateSelectionCallback) { List <TcpConnectionCache> cachedConnections = null; TcpConnectionCache cached = null; //Get a unique string to identify this connection var key = GetConnectionKey(hostname, port, isHttps, version); while (true) { await connectionAccessLock.WaitAsync(); try { connectionCache.TryGetValue(key, out cachedConnections); if (cachedConnections != null && cachedConnections.Count > 0) { cached = cachedConnections.First(); cachedConnections.Remove(cached); } else { cached = null; } } finally { connectionAccessLock.Release(); } if (cached != null && !cached.TcpClient.Client.IsConnected()) { cached.TcpClient.Client.Dispose(); cached.TcpClient.Close(); continue; } if (cached == null) { break; } } if (cached == null) { cached = await CreateClient(hostname, port, isHttps, version, upStreamHttpProxy, upStreamHttpsProxy, bufferSize, supportedSslProtocols, remoteCertificateValidationCallBack, localCertificateSelectionCallback); } if (cachedConnections == null || cachedConnections.Count() <= 2) { var task = CreateClient(hostname, port, isHttps, version, upStreamHttpProxy, upStreamHttpsProxy, bufferSize, supportedSslProtocols, remoteCertificateValidationCallBack, localCertificateSelectionCallback) .ContinueWith(async(x) => { if (x.Status == TaskStatus.RanToCompletion) { await ReleaseClient(x.Result); } }); } return(cached); }
/// <summary> /// This is the core request handler method for a particular connection from client /// </summary> /// <param name="client"></param> /// <param name="httpCmd"></param> /// <param name="clientStream"></param> /// <param name="clientStreamReader"></param> /// <param name="clientStreamWriter"></param> /// <param name="httpsHostName"></param> /// <returns></returns> private async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream, CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName, ProxyEndPoint endPoint, List <HttpHeader> connectHeaders, ExternalProxy customUpStreamHttpProxy = null, ExternalProxy customUpStreamHttpsProxy = null) { TcpConnection connection = null; //Loop through each subsequest request on this particular client connection //(assuming HTTP connection is kept alive by client) while (true) { if (string.IsNullOrEmpty(httpCmd)) { Dispose(clientStream, clientStreamReader, clientStreamWriter, null); break; } var args = new SessionEventArgs(BUFFER_SIZE, HandleHttpSessionResponse); args.ProxyClient.TcpClient = client; args.WebSession.ConnectHeaders = connectHeaders; args.WebSession.ProcessId = new Lazy <int>(() => { var remoteEndPoint = (IPEndPoint)args.ProxyClient.TcpClient.Client.RemoteEndPoint; //If client is localhost get the process id if (NetworkHelper.IsLocalIpAddress(remoteEndPoint.Address)) { return(NetworkHelper.GetProcessIdFromPort(remoteEndPoint.Port, endPoint.IpV6Enabled)); } //can't access process Id of remote request from remote machine return(-1); }); try { //break up the line into three components (method, remote URL & Http Version) var httpCmdSplit = httpCmd.Split(ProxyConstants.SpaceSplit, 3); var httpMethod = httpCmdSplit[0]; //find the request HTTP version Version httpVersion = new Version(1, 1); if (httpCmdSplit.Length == 3) { var httpVersionString = httpCmdSplit[2].ToLower().Trim(); if (httpVersionString == "http/1.0") { httpVersion = new Version(1, 0); } } //Read the request headers in to unique and non-unique header collections string tmpLine; while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync())) { var header = tmpLine.Split(ProxyConstants.ColonSplit, 2); var newHeader = new HttpHeader(header[0], header[1]); //if header exist in non-unique header collection add it there if (args.WebSession.Request.NonUniqueRequestHeaders.ContainsKey(newHeader.Name)) { args.WebSession.Request.NonUniqueRequestHeaders[newHeader.Name].Add(newHeader); } //if header is alread in unique header collection then move both to non-unique collection else if (args.WebSession.Request.RequestHeaders.ContainsKey(newHeader.Name)) { var existing = args.WebSession.Request.RequestHeaders[newHeader.Name]; var nonUniqueHeaders = new List <HttpHeader>(); nonUniqueHeaders.Add(existing); nonUniqueHeaders.Add(newHeader); args.WebSession.Request.NonUniqueRequestHeaders.Add(newHeader.Name, nonUniqueHeaders); args.WebSession.Request.RequestHeaders.Remove(newHeader.Name); } //add to unique header collection else { args.WebSession.Request.RequestHeaders.Add(newHeader.Name, newHeader); } } var httpRemoteUri = new Uri(httpsHostName == null ? httpCmdSplit[1] : (string.Concat("https://", args.WebSession.Request.Host == null ? httpsHostName : args.WebSession.Request.Host, httpCmdSplit[1]))); args.WebSession.Request.RequestUri = httpRemoteUri; args.WebSession.Request.Method = httpMethod.Trim().ToUpper(); args.WebSession.Request.HttpVersion = httpVersion; args.ProxyClient.ClientStream = clientStream; args.ProxyClient.ClientStreamReader = clientStreamReader; args.ProxyClient.ClientStreamWriter = clientStreamWriter; if (httpsHostName == null && (await CheckAuthorization(clientStreamWriter, args.WebSession.Request.RequestHeaders.Values) == false)) { Dispose(clientStream, clientStreamReader, clientStreamWriter, args); break; } PrepareRequestHeaders(args.WebSession.Request.RequestHeaders, args.WebSession); args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority; //If user requested interception do it if (BeforeRequest != null) { Delegate[] invocationList = BeforeRequest.GetInvocationList(); Task[] handlerTasks = new Task[invocationList.Length]; for (int i = 0; i < invocationList.Length; i++) { handlerTasks[i] = ((Func <object, SessionEventArgs, Task>)invocationList[i])(null, args); } await Task.WhenAll(handlerTasks); } //if upgrading to websocket then relay the requet without reading the contents if (args.WebSession.Request.UpgradeToWebSocket) { await TcpHelper.SendRaw(BUFFER_SIZE, ConnectionTimeOutSeconds, httpRemoteUri.Host, httpRemoteUri.Port, httpCmd, httpVersion, args.WebSession.Request.RequestHeaders, args.IsHttps, SupportedSslProtocols, new RemoteCertificateValidationCallback(ValidateServerCertificate), new LocalCertificateSelectionCallback(SelectClientCertificate), clientStream, tcpConnectionFactory, UpStreamEndPoint); Dispose(clientStream, clientStreamReader, clientStreamWriter, args); break; } //construct the web request that we are going to issue on behalf of the client. await HandleHttpSessionRequestInternal(connection, args, customUpStreamHttpProxy, customUpStreamHttpsProxy, false).ConfigureAwait(false); if (args.WebSession.Request.CancelRequest) { break; } //if connection is closing exit if (args.WebSession.Response.ResponseKeepAlive == false) { break; } // read the next request httpCmd = await clientStreamReader.ReadLineAsync(); } catch (Exception e) { ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request", e, args)); Dispose(clientStream, clientStreamReader, clientStreamWriter, args); break; } } if (connection != null) { //dispose connection.Dispose(); } }
/// <summary> /// Creates a TCP connection to server /// </summary> /// <param name="bufferSize"></param> /// <param name="connectionTimeOutSeconds"></param> /// <param name="remoteHostName"></param> /// <param name="httpCmd"></param> /// <param name="httpVersion"></param> /// <param name="isHttps"></param> /// <param name="remotePort"></param> /// <param name="supportedSslProtocols"></param> /// <param name="remoteCertificateValidationCallback"></param> /// <param name="localCertificateSelectionCallback"></param> /// <param name="externalHttpProxy"></param> /// <param name="externalHttpsProxy"></param> /// <param name="clientStream"></param> /// <param name="upStreamEndPoint"></param> /// <returns></returns> internal async Task <TcpConnection> CreateClient(int bufferSize, int connectionTimeOutSeconds, string remoteHostName, int remotePort, Version httpVersion, bool isHttps, SslProtocols supportedSslProtocols, RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback, ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy, Stream clientStream, EndPoint upStreamEndPoint) { TcpClient client; Stream stream; if (isHttps) { SslStream sslStream = null; //If this proxy uses another external proxy then create a tunnel request for HTTPS connections if (externalHttpsProxy != null && externalHttpsProxy.HostName != remoteHostName) { client = new TcpClient(); client.Client.Bind(upStreamEndPoint); await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port); stream = client.GetStream(); using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true) { NewLine = ProxyConstants.NewLine }) { await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}"); await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}"); await writer.WriteLineAsync("Connection: Keep-Alive"); if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null) { await writer.WriteLineAsync("Proxy-Connection: keep-alive"); await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password))); } await writer.WriteLineAsync(); await writer.FlushAsync(); writer.Close(); } using (var reader = new CustomBinaryReader(stream)) { var result = await reader.ReadLineAsync(); if (!new string[] { "200 OK", "connection established" }.Any(s => result.ToLower().Contains(s.ToLower()))) { throw new Exception("Upstream proxy failed to create a secure tunnel"); } await reader.ReadAllLinesAsync(); } } else { client = new TcpClient(); client.Client.Bind(upStreamEndPoint); await client.ConnectAsync(remoteHostName, remotePort); stream = client.GetStream(); } try { sslStream = new SslStream(stream, true, remoteCertificateValidationCallback, localCertificateSelectionCallback); await sslStream.AuthenticateAsClientAsync(remoteHostName, null, supportedSslProtocols, false); stream = sslStream; } catch { sslStream?.Dispose(); throw; } } else { if (externalHttpProxy != null && externalHttpProxy.HostName != remoteHostName) { client = new TcpClient(); client.Client.Bind(upStreamEndPoint); await client.ConnectAsync(externalHttpProxy.HostName, externalHttpProxy.Port); stream = client.GetStream(); } else { client = new TcpClient(); client.Client.Bind(upStreamEndPoint); await client.ConnectAsync(remoteHostName, remotePort); stream = client.GetStream(); } } client.ReceiveTimeout = connectionTimeOutSeconds * 1000; client.SendTimeout = connectionTimeOutSeconds * 1000; stream.ReadTimeout = connectionTimeOutSeconds * 1000; stream.WriteTimeout = connectionTimeOutSeconds * 1000; return(new TcpConnection() { UpStreamHttpProxy = externalHttpProxy, UpStreamHttpsProxy = externalHttpsProxy, HostName = remoteHostName, Port = remotePort, IsHttps = isHttps, TcpClient = client, StreamReader = new CustomBinaryReader(stream), Stream = stream, Version = httpVersion }); }
/// <summary> /// Creates a TCP connection to server /// </summary> /// <param name="server"></param> /// <param name="remoteHostName"></param> /// <param name="remotePort"></param> /// <param name="httpVersion"></param> /// <param name="isHttps"></param> /// <param name="externalHttpProxy"></param> /// <param name="externalHttpsProxy"></param> /// <returns></returns> internal async Task <TcpConnection> CreateClient(ProxyServer server, string remoteHostName, int remotePort, Version httpVersion, bool isHttps, ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy) { bool useHttpProxy = false; //check if external proxy is set for HTTP if (!isHttps && externalHttpProxy != null && !(externalHttpProxy.HostName == remoteHostName && externalHttpProxy.Port == remotePort)) { useHttpProxy = true; //check if we need to ByPass if (externalHttpProxy.BypassLocalhost && NetworkHelper.IsLocalIpAddress(remoteHostName)) { useHttpProxy = false; } } bool useHttpsProxy = false; //check if external proxy is set for HTTPS if (isHttps && externalHttpsProxy != null && !(externalHttpsProxy.HostName == remoteHostName && externalHttpsProxy.Port == remotePort)) { useHttpsProxy = true; //check if we need to ByPass if (externalHttpsProxy.BypassLocalhost && NetworkHelper.IsLocalIpAddress(remoteHostName)) { useHttpsProxy = false; } } TcpClient client = null; CustomBufferedStream stream = null; try { if (isHttps) { //If this proxy uses another external proxy then create a tunnel request for HTTPS connections if (useHttpsProxy) { client = new TcpClient(server.UpStreamEndPoint); await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port); stream = new CustomBufferedStream(client.GetStream(), server.BufferSize); using (var writer = new StreamWriter(stream, Encoding.ASCII, server.BufferSize, true) { NewLine = ProxyConstants.NewLine }) { await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}"); await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}"); await writer.WriteLineAsync("Connection: Keep-Alive"); if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null) { await writer.WriteLineAsync("Proxy-Connection: keep-alive"); await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password))); } await writer.WriteLineAsync(); await writer.FlushAsync(); writer.Close(); } using (var reader = new CustomBinaryReader(stream, server.BufferSize)) { var result = await reader.ReadLineAsync(); if (!new[] { "200 OK", "connection established" }.Any(s => result.ContainsIgnoreCase(s))) { throw new Exception("Upstream proxy failed to create a secure tunnel"); } await reader.ReadAndIgnoreAllLinesAsync(); } } else { client = new TcpClient(server.UpStreamEndPoint); await client.ConnectAsync(remoteHostName, remotePort); stream = new CustomBufferedStream(client.GetStream(), server.BufferSize); } var sslStream = new SslStream(stream, false, server.ValidateServerCertificate, server.SelectClientCertificate); stream = new CustomBufferedStream(sslStream, server.BufferSize); await sslStream.AuthenticateAsClientAsync(remoteHostName, null, server.SupportedSslProtocols, server.CheckCertificateRevocation); } else { if (useHttpProxy) { client = new TcpClient(server.UpStreamEndPoint); await client.ConnectAsync(externalHttpProxy.HostName, externalHttpProxy.Port); stream = new CustomBufferedStream(client.GetStream(), server.BufferSize); } else { client = new TcpClient(server.UpStreamEndPoint); await client.ConnectAsync(remoteHostName, remotePort); stream = new CustomBufferedStream(client.GetStream(), server.BufferSize); } } client.ReceiveTimeout = server.ConnectionTimeOutSeconds * 1000; client.SendTimeout = server.ConnectionTimeOutSeconds * 1000; } catch (Exception) { stream?.Dispose(); client?.Close(); throw; } Interlocked.Increment(ref server.serverConnectionCount); return(new TcpConnection { UpStreamHttpProxy = externalHttpProxy, UpStreamHttpsProxy = externalHttpsProxy, HostName = remoteHostName, Port = remotePort, IsHttps = isHttps, TcpClient = client, StreamReader = new CustomBinaryReader(stream, server.BufferSize), Stream = stream, Version = httpVersion }); }
/// <summary> /// Creates a TCP connection to server /// </summary> /// <param name="bufferSize"></param> /// <param name="connectionTimeOutSeconds"></param> /// <param name="remoteHostName"></param> /// <param name="httpCmd"></param> /// <param name="httpVersion"></param> /// <param name="isHttps"></param> /// <param name="remotePort"></param> /// <param name="supportedSslProtocols"></param> /// <param name="remoteCertificateValidationCallback"></param> /// <param name="localCertificateSelectionCallback"></param> /// <param name="externalHttpProxy"></param> /// <param name="externalHttpsProxy"></param> /// <param name="clientStream"></param> /// <returns></returns> internal async Task <TcpConnection> CreateClient(int bufferSize, int connectionTimeOutSeconds, string remoteHostName, int remotePort, Version httpVersion, bool isHttps, SslProtocols supportedSslProtocols, RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback, ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy, Stream clientStream) { TcpClient client; Stream stream; if (isHttps) { SslStream sslStream = null; //If this proxy uses another external proxy then create a tunnel request for HTTPS connections if (externalHttpsProxy != null) { client = new TcpClient(externalHttpsProxy.HostName, externalHttpsProxy.Port); stream = client.GetStream(); using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true)) { await writer.WriteLineAsync(string.Format("CONNECT {0}:{1} {2}", remoteHostName, remotePort, httpVersion)); await writer.WriteLineAsync(string.Format("Host: {0}:{1}", remoteHostName, remotePort)); await writer.WriteLineAsync("Connection: Keep-Alive"); await writer.WriteLineAsync(); await writer.FlushAsync(); writer.Close(); } using (var reader = new CustomBinaryReader(stream)) { var result = await reader.ReadLineAsync(); if (!result.ToLower().Contains("200 connection established")) { throw new Exception("Upstream proxy failed to create a secure tunnel"); } await reader.ReadAllLinesAsync(); } } else { client = new TcpClient(remoteHostName, remotePort); stream = client.GetStream(); } try { sslStream = new SslStream(stream, true, remoteCertificateValidationCallback, localCertificateSelectionCallback); await sslStream.AuthenticateAsClientAsync(remoteHostName, null, supportedSslProtocols, false); stream = sslStream; } catch { if (sslStream != null) { sslStream.Dispose(); } throw; } } else { if (externalHttpProxy != null) { client = new TcpClient(externalHttpProxy.HostName, externalHttpProxy.Port); stream = client.GetStream(); } else { client = new TcpClient(remoteHostName, remotePort); stream = client.GetStream(); } } client.ReceiveTimeout = connectionTimeOutSeconds * 1000; client.SendTimeout = connectionTimeOutSeconds * 1000; stream.ReadTimeout = connectionTimeOutSeconds * 1000; stream.WriteTimeout = connectionTimeOutSeconds * 1000; return(new TcpConnection() { HostName = remoteHostName, port = remotePort, IsHttps = isHttps, TcpClient = client, StreamReader = new CustomBinaryReader(stream), Stream = stream }); }