Commit 58ec1bb3 authored by justcoding121's avatar justcoding121

Fix server connection count; fix UpstreamEndPoint

parent 494b0e6f
...@@ -5,34 +5,6 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -5,34 +5,6 @@ namespace Titanium.Web.Proxy.Extensions
{ {
internal static class TcpExtensions internal static class TcpExtensions
{ {
/// <summary>
/// verifies if the underlying socket is connected before using a TcpClient connection
/// </summary>
/// <param name="client"></param>
/// <returns></returns>
internal static bool IsConnected(this Socket client)
{
// This is how you can determine whether a socket is still connected.
bool blockingState = client.Blocking;
try
{
var tmp = new byte[1];
client.Blocking = false;
client.Send(tmp, 0, 0);
return true;
}
catch (SocketException e)
{
// 10035 == WSAEWOULDBLOCK
return e.SocketErrorCode == SocketError.WouldBlock;
}
finally
{
client.Blocking = blockingState;
}
}
#if NET45 #if NET45
/// <summary> /// <summary>
......
...@@ -58,7 +58,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -58,7 +58,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
client = new TcpClient(upStreamEndPoint); client = new TcpClient(upStreamEndPoint);
#else #else
client = new TcpClient(); client = new TcpClient();
client.Client.Bind(server.UpStreamEndPoint); client.Client.Bind(upStreamEndPoint);
#endif #endif
//If this proxy uses another external proxy then create a tunnel request for HTTP/HTTPS connections //If this proxy uses another external proxy then create a tunnel request for HTTP/HTTPS connections
......
using StreamExtended.Network; using StreamExtended.Network;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Authentication; using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
#if NET45 #if NET45
using Titanium.Web.Proxy.Helpers.WinHttp; using Titanium.Web.Proxy.Helpers.WinHttp;
#endif #endif
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Network.Tcp; using Titanium.Web.Proxy.Network.Tcp;
#if NET45 #if NET45
using Titanium.Web.Proxy.Network.WinAuth.Security; using Titanium.Web.Proxy.Network.WinAuth.Security;
#endif #endif
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
/// <summary> /// <summary>
/// Proxy Server Main class /// Proxy Server Main class
/// </summary> /// </summary>
public partial class ProxyServer : IDisposable public partial class ProxyServer : IDisposable
{ {
#if NET45 #if NET45
internal static readonly string UriSchemeHttp = Uri.UriSchemeHttp; internal static readonly string UriSchemeHttp = Uri.UriSchemeHttp;
internal static readonly string UriSchemeHttps = Uri.UriSchemeHttps; internal static readonly string UriSchemeHttps = Uri.UriSchemeHttps;
#else #else
internal const string UriSchemeHttp = "http"; internal const string UriSchemeHttp = "http";
internal const string UriSchemeHttps = "https"; internal const string UriSchemeHttps = "https";
#endif #endif
/// <summary> /// <summary>
/// Is the proxy currently running /// Is the proxy currently running
/// </summary> /// </summary>
private bool proxyRunning { get; set; } private bool proxyRunning { get; set; }
/// <summary> /// <summary>
/// An default exception log func /// An default exception log func
/// </summary> /// </summary>
private readonly Lazy<Action<Exception>> defaultExceptionFunc = new Lazy<Action<Exception>>(() => (e => { })); private readonly Lazy<Action<Exception>> defaultExceptionFunc = new Lazy<Action<Exception>>(() => (e => { }));
/// <summary> /// <summary>
/// backing exception func for exposed public property /// backing exception func for exposed public property
/// </summary> /// </summary>
private Action<Exception> exceptionFunc; private Action<Exception> exceptionFunc;
/// <summary> /// <summary>
/// Backing field for corresponding public property /// Backing field for corresponding public property
/// </summary> /// </summary>
private bool trustRootCertificate; private bool trustRootCertificate;
/// <summary> /// <summary>
/// Backing field for corresponding public property /// Backing field for corresponding public property
/// </summary> /// </summary>
private int clientConnectionCount; private int clientConnectionCount;
/// <summary> /// <summary>
/// Backing field for corresponding public property /// Backing field for corresponding public property
/// </summary> /// </summary>
private int serverConnectionCount; private int serverConnectionCount;
/// <summary> /// <summary>
/// A object that creates tcp connection to server /// A object that creates tcp connection to server
/// </summary> /// </summary>
private TcpConnectionFactory tcpConnectionFactory { get; } private TcpConnectionFactory tcpConnectionFactory { get; }
#if NET45 #if NET45
private WinHttpWebProxyFinder systemProxyResolver; private WinHttpWebProxyFinder systemProxyResolver;
/// <summary> /// <summary>
/// Manage system proxy settings /// Manage system proxy settings
/// </summary> /// </summary>
private SystemProxyManager systemProxySettingsManager { get; } private SystemProxyManager systemProxySettingsManager { get; }
/// <summary> /// <summary>
/// Set firefox to use default system proxy /// Set firefox to use default system proxy
/// </summary> /// </summary>
private readonly FireFoxProxySettingsManager firefoxProxySettingsManager = new FireFoxProxySettingsManager(); private readonly FireFoxProxySettingsManager firefoxProxySettingsManager = new FireFoxProxySettingsManager();
#endif #endif
/// <summary> /// <summary>
/// Buffer size used throughout this proxy /// Buffer size used throughout this proxy
/// </summary> /// </summary>
public int BufferSize { get; set; } = 8192; public int BufferSize { get; set; } = 8192;
/// <summary> /// <summary>
/// Manages certificates used by this proxy /// Manages certificates used by this proxy
/// </summary> /// </summary>
public CertificateManager CertificateManager { get; } public CertificateManager CertificateManager { get; }
/// <summary> /// <summary>
/// The root certificate /// The root certificate
/// </summary> /// </summary>
public X509Certificate2 RootCertificate public X509Certificate2 RootCertificate
{ {
get { return CertificateManager.RootCertificate; } get { return CertificateManager.RootCertificate; }
set { CertificateManager.RootCertificate = value; } set { CertificateManager.RootCertificate = value; }
} }
/// <summary> /// <summary>
/// Name of the root certificate issuer /// Name of the root certificate issuer
/// (This is valid only when RootCertificate property is not set) /// (This is valid only when RootCertificate property is not set)
/// </summary> /// </summary>
public string RootCertificateIssuerName public string RootCertificateIssuerName
{ {
get { return CertificateManager.Issuer; } get { return CertificateManager.Issuer; }
set { CertificateManager.Issuer = value; } set { CertificateManager.Issuer = value; }
} }
/// <summary> /// <summary>
/// Name of the root certificate /// Name of the root certificate
/// (This is valid only when RootCertificate property is not set) /// (This is valid only when RootCertificate property is not set)
/// If no certificate is provided then a default Root Certificate will be created and used /// If no certificate is provided then a default Root Certificate will be created and used
/// The provided root certificate will be stored in proxy exe directory with the private key /// The provided root certificate will be stored in proxy exe directory with the private key
/// Root certificate file will be named as "rootCert.pfx" /// Root certificate file will be named as "rootCert.pfx"
/// </summary> /// </summary>
public string RootCertificateName public string RootCertificateName
{ {
get { return CertificateManager.RootCertificateName; } get { return CertificateManager.RootCertificateName; }
set { CertificateManager.RootCertificateName = value; } set { CertificateManager.RootCertificateName = value; }
} }
/// <summary> /// <summary>
/// Trust the RootCertificate used by this proxy server /// Trust the RootCertificate used by this proxy server
/// Note that this do not make the client trust the certificate! /// Note that this do not make the client trust the certificate!
/// This would import the root certificate to the certificate store of machine that runs this proxy server /// This would import the root certificate to the certificate store of machine that runs this proxy server
/// </summary> /// </summary>
public bool TrustRootCertificate public bool TrustRootCertificate
{ {
get { return trustRootCertificate; } get { return trustRootCertificate; }
set set
{ {
trustRootCertificate = value; trustRootCertificate = value;
if (value) if (value)
{ {
EnsureRootCertificate(); EnsureRootCertificate();
} }
} }
} }
/// <summary> /// <summary>
/// Select Certificate Engine /// Select Certificate Engine
/// Optionally set to BouncyCastle /// Optionally set to BouncyCastle
/// Mono only support BouncyCastle and it is the default /// Mono only support BouncyCastle and it is the default
/// </summary> /// </summary>
public CertificateEngine CertificateEngine public CertificateEngine CertificateEngine
{ {
get { return CertificateManager.Engine; } get { return CertificateManager.Engine; }
set { CertificateManager.Engine = value; } set { CertificateManager.Engine = value; }
} }
/// <summary> /// <summary>
/// Should we check for certificare revocation during SSL authentication to servers /// Should we check for certificare revocation during SSL authentication to servers
/// Note: If enabled can reduce performance (Default disabled) /// Note: If enabled can reduce performance (Default disabled)
/// </summary> /// </summary>
public bool CheckCertificateRevocation { get; set; } public bool CheckCertificateRevocation { get; set; }
/// <summary> /// <summary>
/// Does this proxy uses the HTTP protocol 100 continue behaviour strictly? /// Does this proxy uses the HTTP protocol 100 continue behaviour strictly?
/// Broken 100 contunue implementations on server/client may cause problems if enabled /// Broken 100 contunue implementations on server/client may cause problems if enabled
/// </summary> /// </summary>
public bool Enable100ContinueBehaviour { get; set; } public bool Enable100ContinueBehaviour { get; set; }
/// <summary> /// <summary>
/// Minutes certificates should be kept in cache when not used /// Minutes certificates should be kept in cache when not used
/// </summary> /// </summary>
public int CertificateCacheTimeOutMinutes { get; set; } public int CertificateCacheTimeOutMinutes { get; set; }
/// <summary> /// <summary>
/// Seconds client/server connection are to be kept alive when waiting for read/write to complete /// Seconds client/server connection are to be kept alive when waiting for read/write to complete
/// </summary> /// </summary>
public int ConnectionTimeOutSeconds { get; set; } public int ConnectionTimeOutSeconds { get; set; }
/// <summary> /// <summary>
/// Intercept request to server /// Intercept request to server
/// </summary> /// </summary>
public event Func<object, SessionEventArgs, Task> BeforeRequest; public event Func<object, SessionEventArgs, Task> BeforeRequest;
/// <summary> /// <summary>
/// Intercept response from server /// Intercept response from server
/// </summary> /// </summary>
public event Func<object, SessionEventArgs, Task> BeforeResponse; public event Func<object, SessionEventArgs, Task> BeforeResponse;
/// <summary> /// <summary>
/// Intercept tunnel connect reques /// Intercept tunnel connect reques
/// </summary> /// </summary>
public event Func<object, TunnelConnectSessionEventArgs, Task> TunnelConnectRequest; public event Func<object, TunnelConnectSessionEventArgs, Task> TunnelConnectRequest;
/// <summary> /// <summary>
/// Intercept tunnel connect response /// Intercept tunnel connect response
/// </summary> /// </summary>
public event Func<object, TunnelConnectSessionEventArgs, Task> TunnelConnectResponse; public event Func<object, TunnelConnectSessionEventArgs, Task> TunnelConnectResponse;
/// <summary> /// <summary>
/// Occurs when client connection count changed. /// Occurs when client connection count changed.
/// </summary> /// </summary>
public event EventHandler ClientConnectionCountChanged; public event EventHandler ClientConnectionCountChanged;
/// <summary> /// <summary>
/// Occurs when server connection count changed. /// Occurs when server connection count changed.
/// </summary> /// </summary>
public event EventHandler ServerConnectionCountChanged; public event EventHandler ServerConnectionCountChanged;
/// <summary> /// <summary>
/// External proxy for Http /// External proxy for Http
/// </summary> /// </summary>
public ExternalProxy UpStreamHttpProxy { get; set; } public ExternalProxy UpStreamHttpProxy { get; set; }
/// <summary> /// <summary>
/// External proxy for Http /// External proxy for Http
/// </summary> /// </summary>
public ExternalProxy UpStreamHttpsProxy { get; set; } public ExternalProxy UpStreamHttpsProxy { get; set; }
/// <summary> /// <summary>
/// Local adapter/NIC endpoint (where proxy makes request via) /// Local adapter/NIC endpoint (where proxy makes request via)
/// default via any IP addresses of this machine /// default via any IP addresses of this machine
/// </summary> /// </summary>
public IPEndPoint UpStreamEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0); public IPEndPoint UpStreamEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0);
/// <summary> /// <summary>
/// Is the proxy currently running /// Is the proxy currently running
/// </summary> /// </summary>
public bool ProxyRunning => proxyRunning; public bool ProxyRunning => proxyRunning;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether requests will be chained to upstream gateway. /// Gets or sets a value indicating whether requests will be chained to upstream gateway.
/// </summary> /// </summary>
public bool ForwardToUpstreamGateway { get; set; } public bool ForwardToUpstreamGateway { get; set; }
/// <summary> /// <summary>
/// Enable disable Windows Authentication (NTLM/Kerberos) /// Enable disable Windows Authentication (NTLM/Kerberos)
/// Note: NTLM/Kerberos will always send local credentials of current user /// Note: NTLM/Kerberos will always send local credentials of current user
/// who is running the proxy process. This is because a man /// who is running the proxy process. This is because a man
/// in middle attack is not currently supported /// in middle attack is not currently supported
/// (which would require windows delegation enabled for this server process) /// (which would require windows delegation enabled for this server process)
/// </summary> /// </summary>
public bool EnableWinAuth { get; set; } public bool EnableWinAuth { get; set; }
/// <summary> /// <summary>
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication /// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication
/// </summary> /// </summary>
public event Func<object, CertificateValidationEventArgs, Task> ServerCertificateValidationCallback; public event Func<object, CertificateValidationEventArgs, Task> ServerCertificateValidationCallback;
/// <summary> /// <summary>
/// Callback tooverride client certificate during SSL mutual authentication /// Callback tooverride client certificate during SSL mutual authentication
/// </summary> /// </summary>
public event Func<object, CertificateSelectionEventArgs, Task> ClientCertificateSelectionCallback; public event Func<object, CertificateSelectionEventArgs, Task> ClientCertificateSelectionCallback;
/// <summary> /// <summary>
/// Callback for error events in proxy /// Callback for error events in proxy
/// </summary> /// </summary>
public Action<Exception> ExceptionFunc public Action<Exception> ExceptionFunc
{ {
get { return exceptionFunc ?? defaultExceptionFunc.Value; } get { return exceptionFunc ?? defaultExceptionFunc.Value; }
set { exceptionFunc = value; } set { exceptionFunc = value; }
} }
/// <summary> /// <summary>
/// A callback to authenticate clients /// A callback to authenticate clients
/// Parameters are username, password provided by client /// Parameters are username, password provided by client
/// return true for successful authentication /// return true for successful authentication
/// </summary> /// </summary>
public Func<string, string, Task<bool>> AuthenticateUserFunc { get; set; } public Func<string, string, Task<bool>> AuthenticateUserFunc { get; set; }
/// <summary> /// <summary>
/// Realm used during Proxy Basic Authentication /// Realm used during Proxy Basic Authentication
/// </summary> /// </summary>
public string ProxyRealm { get; set; } = "TitaniumProxy"; public string ProxyRealm { get; set; } = "TitaniumProxy";
/// <summary> /// <summary>
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP requests /// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP requests
/// return the ExternalProxy object with valid credentials /// return the ExternalProxy object with valid credentials
/// </summary> /// </summary>
public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpProxyFunc { get; set; } public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpProxyFunc { get; set; }
/// <summary> /// <summary>
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTPS requests /// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTPS requests
/// return the ExternalProxy object with valid credentials /// return the ExternalProxy object with valid credentials
/// </summary> /// </summary>
public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpsProxyFunc { get; set; } public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpsProxyFunc { get; set; }
/// <summary> /// <summary>
/// A list of IpAddress and port this proxy is listening to /// A list of IpAddress and port this proxy is listening to
/// </summary> /// </summary>
public List<ProxyEndPoint> ProxyEndPoints { get; set; } public List<ProxyEndPoint> ProxyEndPoints { get; set; }
/// <summary> /// <summary>
/// List of supported Ssl versions /// List of supported Ssl versions
/// </summary> /// </summary>
#if NET45 #if NET45
public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3; public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
#else #else
public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12; public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12;
#endif #endif
/// <summary> /// <summary>
/// Total number of active client connections /// Total number of active client connections
/// </summary> /// </summary>
public int ClientConnectionCount => clientConnectionCount; public int ClientConnectionCount => clientConnectionCount;
/// <summary> /// <summary>
/// Total number of active server connections /// Total number of active server connections
/// </summary> /// </summary>
public int ServerConnectionCount => serverConnectionCount; public int ServerConnectionCount => serverConnectionCount;
/// <summary> /// <summary>
/// Constructor /// Constructor
/// </summary> /// </summary>
public ProxyServer() : this(null, null) public ProxyServer() : this(null, null)
{ {
} }
/// <summary> /// <summary>
/// Constructor. /// Constructor.
/// </summary> /// </summary>
/// <param name="rootCertificateName">Name of root certificate.</param> /// <param name="rootCertificateName">Name of root certificate.</param>
/// <param name="rootCertificateIssuerName">Name of root certificate issuer.</param> /// <param name="rootCertificateIssuerName">Name of root certificate issuer.</param>
public ProxyServer(string rootCertificateName, string rootCertificateIssuerName) public ProxyServer(string rootCertificateName, string rootCertificateIssuerName)
{ {
//default values //default values
ConnectionTimeOutSeconds = 30; ConnectionTimeOutSeconds = 30;
CertificateCacheTimeOutMinutes = 60; CertificateCacheTimeOutMinutes = 60;
ProxyEndPoints = new List<ProxyEndPoint>(); ProxyEndPoints = new List<ProxyEndPoint>();
tcpConnectionFactory = new TcpConnectionFactory(); tcpConnectionFactory = new TcpConnectionFactory();
#if NET45 #if NET45
if (!RunTime.IsRunningOnMono) if (!RunTime.IsRunningOnMono)
{ {
systemProxySettingsManager = new SystemProxyManager(); systemProxySettingsManager = new SystemProxyManager();
} }
#endif #endif
CertificateManager = new CertificateManager(ExceptionFunc); CertificateManager = new CertificateManager(ExceptionFunc);
if (rootCertificateName != null) if (rootCertificateName != null)
{ {
RootCertificateName = rootCertificateName; RootCertificateName = rootCertificateName;
} }
if (rootCertificateIssuerName != null) if (rootCertificateIssuerName != null)
{ {
RootCertificateIssuerName = rootCertificateIssuerName; RootCertificateIssuerName = rootCertificateIssuerName;
} }
} }
/// <summary> /// <summary>
/// Add a proxy end point /// Add a proxy end point
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
public void AddEndPoint(ProxyEndPoint endPoint) public void AddEndPoint(ProxyEndPoint endPoint)
{ {
if (ProxyEndPoints.Any(x => x.IpAddress.Equals(endPoint.IpAddress) && endPoint.Port != 0 && x.Port == endPoint.Port)) if (ProxyEndPoints.Any(x => x.IpAddress.Equals(endPoint.IpAddress) && endPoint.Port != 0 && x.Port == endPoint.Port))
{ {
throw new Exception("Cannot add another endpoint to same port & ip address"); throw new Exception("Cannot add another endpoint to same port & ip address");
} }
ProxyEndPoints.Add(endPoint); ProxyEndPoints.Add(endPoint);
if (proxyRunning) if (proxyRunning)
{ {
Listen(endPoint); Listen(endPoint);
} }
} }
/// <summary> /// <summary>
/// Remove a proxy end point /// Remove a proxy end point
/// Will throw error if the end point does'nt exist /// Will throw error if the end point does'nt exist
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
public void RemoveEndPoint(ProxyEndPoint endPoint) public void RemoveEndPoint(ProxyEndPoint endPoint)
{ {
if (ProxyEndPoints.Contains(endPoint) == false) if (ProxyEndPoints.Contains(endPoint) == false)
{ {
throw new Exception("Cannot remove endPoints not added to proxy"); throw new Exception("Cannot remove endPoints not added to proxy");
} }
ProxyEndPoints.Remove(endPoint); ProxyEndPoints.Remove(endPoint);
if (proxyRunning) if (proxyRunning)
{ {
QuitListen(endPoint); QuitListen(endPoint);
} }
} }
#if NET45 #if NET45
/// <summary> /// <summary>
/// Set the given explicit end point as the default proxy server for current machine /// Set the given explicit end point as the default proxy server for current machine
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint) public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint)
{ {
SetAsSystemProxy(endPoint, ProxyProtocolType.Http); SetAsSystemProxy(endPoint, ProxyProtocolType.Http);
} }
/// <summary> /// <summary>
/// Set the given explicit end point as the default proxy server for current machine /// Set the given explicit end point as the default proxy server for current machine
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint) public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint)
{ {
SetAsSystemProxy(endPoint, ProxyProtocolType.Https); SetAsSystemProxy(endPoint, ProxyProtocolType.Https);
} }
/// <summary> /// <summary>
/// Set the given explicit end point as the default proxy server for current machine /// Set the given explicit end point as the default proxy server for current machine
/// </summary> /// </summary>
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
/// <param name="protocolType"></param> /// <param name="protocolType"></param>
public void SetAsSystemProxy(ExplicitProxyEndPoint endPoint, ProxyProtocolType protocolType) public void SetAsSystemProxy(ExplicitProxyEndPoint endPoint, ProxyProtocolType protocolType)
{ {
if (RunTime.IsRunningOnMono) if (RunTime.IsRunningOnMono)
{ {
throw new Exception("Mono Runtime do not support system proxy settings."); throw new Exception("Mono Runtime do not support system proxy settings.");
} }
ValidateEndPointAsSystemProxy(endPoint); ValidateEndPointAsSystemProxy(endPoint);
bool isHttp = (protocolType & ProxyProtocolType.Http) > 0; bool isHttp = (protocolType & ProxyProtocolType.Http) > 0;
bool isHttps = (protocolType & ProxyProtocolType.Https) > 0; bool isHttps = (protocolType & ProxyProtocolType.Https) > 0;
if (isHttps) if (isHttps)
{ {
if (!endPoint.EnableSsl) if (!endPoint.EnableSsl)
{ {
throw new Exception("Endpoint do not support Https connections"); throw new Exception("Endpoint do not support Https connections");
} }
EnsureRootCertificate(); EnsureRootCertificate();
//If certificate was trusted by the machine //If certificate was trusted by the machine
if (!CertificateManager.CertValidated) if (!CertificateManager.CertValidated)
{ {
protocolType = protocolType & ~ProxyProtocolType.Https; protocolType = protocolType & ~ProxyProtocolType.Https;
isHttps = false; isHttps = false;
} }
} }
//clear any settings previously added //clear any settings previously added
if (isHttp) if (isHttp)
{ {
ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpProxy = false); ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpProxy = false);
} }
if (isHttps) if (isHttps)
{ {
ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpsProxy = false); ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpsProxy = false);
} }
systemProxySettingsManager.SetProxy( systemProxySettingsManager.SetProxy(
Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Any) |
Equals(endPoint.IpAddress, IPAddress.Loopback) Equals(endPoint.IpAddress, IPAddress.Loopback)
? "127.0.0.1" ? "127.0.0.1"
: endPoint.IpAddress.ToString(), : endPoint.IpAddress.ToString(),
endPoint.Port, endPoint.Port,
protocolType); protocolType);
if (isHttp) if (isHttp)
{ {
endPoint.IsSystemHttpsProxy = true; endPoint.IsSystemHttpsProxy = true;
} }
if (isHttps) if (isHttps)
{ {
endPoint.IsSystemHttpsProxy = true; endPoint.IsSystemHttpsProxy = true;
} }
firefoxProxySettingsManager.UseSystemProxy(); firefoxProxySettingsManager.UseSystemProxy();
string proxyType = null; string proxyType = null;
switch (protocolType) switch (protocolType)
{ {
case ProxyProtocolType.Http: case ProxyProtocolType.Http:
proxyType = "HTTP"; proxyType = "HTTP";
break; break;
case ProxyProtocolType.Https: case ProxyProtocolType.Https:
proxyType = "HTTPS"; proxyType = "HTTPS";
break; break;
case ProxyProtocolType.AllHttp: case ProxyProtocolType.AllHttp:
proxyType = "HTTP and HTTPS"; proxyType = "HTTP and HTTPS";
break; break;
} }
if (protocolType != ProxyProtocolType.None) if (protocolType != ProxyProtocolType.None)
{ {
Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System {2} Proxy", endPoint.IpAddress, endPoint.Port, proxyType); Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System {2} Proxy", endPoint.IpAddress, endPoint.Port, proxyType);
} }
} }
/// <summary> /// <summary>
/// Remove any HTTP proxy setting of current machien /// Remove any HTTP proxy setting of current machien
/// </summary> /// </summary>
public void DisableSystemHttpProxy() public void DisableSystemHttpProxy()
{ {
DisableSystemProxy(ProxyProtocolType.Http); DisableSystemProxy(ProxyProtocolType.Http);
} }
/// <summary> /// <summary>
/// Remove any HTTPS proxy setting for current machine /// Remove any HTTPS proxy setting for current machine
/// </summary> /// </summary>
public void DisableSystemHttpsProxy() public void DisableSystemHttpsProxy()
{ {
DisableSystemProxy(ProxyProtocolType.Https); DisableSystemProxy(ProxyProtocolType.Https);
} }
/// <summary> /// <summary>
/// Remove the specified proxy settings for current machine /// Remove the specified proxy settings for current machine
/// </summary> /// </summary>
public void DisableSystemProxy(ProxyProtocolType protocolType) public void DisableSystemProxy(ProxyProtocolType protocolType)
{ {
if (RunTime.IsRunningOnMono) if (RunTime.IsRunningOnMono)
{ {
throw new Exception("Mono Runtime do not support system proxy settings."); throw new Exception("Mono Runtime do not support system proxy settings.");
} }
systemProxySettingsManager.RemoveProxy(protocolType); systemProxySettingsManager.RemoveProxy(protocolType);
} }
/// <summary> /// <summary>
/// Clear all proxy settings for current machine /// Clear all proxy settings for current machine
/// </summary> /// </summary>
public void DisableAllSystemProxies() public void DisableAllSystemProxies()
{ {
if (RunTime.IsRunningOnMono) if (RunTime.IsRunningOnMono)
{ {
throw new Exception("Mono Runtime do not support system proxy settings."); throw new Exception("Mono Runtime do not support system proxy settings.");
} }
systemProxySettingsManager.DisableAllProxy(); systemProxySettingsManager.DisableAllProxy();
} }
#endif #endif
/// <summary> /// <summary>
/// Start this proxy server /// Start this proxy server
/// </summary> /// </summary>
public void Start() public void Start()
{ {
if (proxyRunning) if (proxyRunning)
{ {
throw new Exception("Proxy is already running."); throw new Exception("Proxy is already running.");
} }
#if NET45 #if NET45
//clear any system proxy settings which is pointing to our own endpoint //clear any system proxy settings which is pointing to our own endpoint (causing a cycle)
//due to non gracious proxy shutdown before //due to non gracious proxy shutdown before or something else
if (systemProxySettingsManager != null) if (systemProxySettingsManager != null)
{ {
var proxyInfo = systemProxySettingsManager.GetProxyInfoFromRegistry(); var proxyInfo = systemProxySettingsManager.GetProxyInfoFromRegistry();
if (proxyInfo.Proxies != null) if (proxyInfo.Proxies != null)
{ {
var protocolToRemove = ProxyProtocolType.None; var protocolToRemove = ProxyProtocolType.None;
foreach (var proxy in proxyInfo.Proxies.Values) foreach (var proxy in proxyInfo.Proxies.Values)
{ {
if (proxy.HostName == "127.0.0.1" && ProxyEndPoints.Any(x => x.Port == proxy.Port)) if ((proxy.HostName == "127.0.0.1"
{ || proxy.HostName.Equals("localhost", StringComparison.OrdinalIgnoreCase))
protocolToRemove |= proxy.ProtocolType; && ProxyEndPoints.Any(x => x.Port == proxy.Port))
} {
} protocolToRemove |= proxy.ProtocolType;
}
if (protocolToRemove != ProxyProtocolType.None) }
{
//do not restore to any of listening address when we quit if (protocolToRemove != ProxyProtocolType.None)
systemProxySettingsManager.RemoveProxy(protocolToRemove, false); {
} //do not restore to any of listening address when we quit
} systemProxySettingsManager.RemoveProxy(protocolToRemove, false);
} }
}
if (ForwardToUpstreamGateway }
&& GetCustomUpStreamHttpProxyFunc == null && GetCustomUpStreamHttpsProxyFunc == null
&& systemProxySettingsManager != null) if (ForwardToUpstreamGateway
{ && GetCustomUpStreamHttpProxyFunc == null && GetCustomUpStreamHttpsProxyFunc == null
// Use WinHttp to handle PAC/WAPD scripts. && systemProxySettingsManager != null)
systemProxyResolver = new WinHttpWebProxyFinder(); {
systemProxyResolver.LoadFromIE(); // Use WinHttp to handle PAC/WAPD scripts.
systemProxyResolver = new WinHttpWebProxyFinder();
GetCustomUpStreamHttpProxyFunc = GetSystemUpStreamProxy; systemProxyResolver.LoadFromIE();
GetCustomUpStreamHttpsProxyFunc = GetSystemUpStreamProxy;
} GetCustomUpStreamHttpProxyFunc = GetSystemUpStreamProxy;
#endif GetCustomUpStreamHttpsProxyFunc = GetSystemUpStreamProxy;
}
foreach (var endPoint in ProxyEndPoints) #endif
{
Listen(endPoint); foreach (var endPoint in ProxyEndPoints)
} {
Listen(endPoint);
CertificateManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes); }
#if NET45 CertificateManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes);
if (!RunTime.IsRunningOnMono)
{ #if NET45
//clear orphaned windows auth states every 2 minutes if (!RunTime.IsRunningOnMono)
WinAuthEndPoint.ClearIdleStates(2); {
} //clear orphaned windows auth states every 2 minutes
#endif WinAuthEndPoint.ClearIdleStates(2);
}
proxyRunning = true; #endif
}
proxyRunning = true;
}
/// <summary>
/// Stop this proxy server
/// </summary> /// <summary>
public void Stop() /// Stop this proxy server
{ /// </summary>
if (!proxyRunning) public void Stop()
{ {
throw new Exception("Proxy is not running."); if (!proxyRunning)
} {
throw new Exception("Proxy is not running.");
#if NET45 }
if (!RunTime.IsRunningOnMono)
{ #if NET45
bool setAsSystemProxy = ProxyEndPoints.OfType<ExplicitProxyEndPoint>().Any(x => x.IsSystemHttpProxy || x.IsSystemHttpsProxy); if (!RunTime.IsRunningOnMono)
{
if (setAsSystemProxy) bool setAsSystemProxy = ProxyEndPoints.OfType<ExplicitProxyEndPoint>().Any(x => x.IsSystemHttpProxy || x.IsSystemHttpsProxy);
{
systemProxySettingsManager.RestoreOriginalSettings(); if (setAsSystemProxy)
} {
} systemProxySettingsManager.RestoreOriginalSettings();
#endif }
}
foreach (var endPoint in ProxyEndPoints) #endif
{
QuitListen(endPoint); foreach (var endPoint in ProxyEndPoints)
} {
QuitListen(endPoint);
ProxyEndPoints.Clear(); }
CertificateManager?.StopClearIdleCertificates(); ProxyEndPoints.Clear();
proxyRunning = false; CertificateManager?.StopClearIdleCertificates();
}
proxyRunning = false;
/// <summary> }
/// Handle dispose of a client/server session
/// </summary> /// <summary>
/// <param name="clientStream"></param> /// Handle dispose of a client/server session
/// <param name="clientStreamReader"></param> /// </summary>
/// <param name="clientStreamWriter"></param> /// <param name="clientStream"></param>
/// <param name="serverConnection"></param> /// <param name="clientStreamReader"></param>
private void Dispose(CustomBufferedStream clientStream, CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, TcpConnection serverConnection) /// <param name="clientStreamWriter"></param>
{ /// <param name="serverConnection"></param>
clientStream?.Dispose(); private void Dispose(CustomBufferedStream clientStream, CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, TcpConnection serverConnection)
{
clientStreamReader?.Dispose(); clientStream?.Dispose();
clientStreamWriter?.Dispose();
clientStreamReader?.Dispose();
if (serverConnection != null) clientStreamWriter?.Dispose();
{
serverConnection.Dispose(); if (serverConnection != null)
UpdateServerConnectionCount(false); {
} serverConnection.Dispose();
} serverConnection = null;
UpdateServerConnectionCount(false);
/// <summary> }
/// Dispose Proxy. }
/// </summary>
public void Dispose() /// <summary>
{ /// Dispose Proxy.
if (proxyRunning) /// </summary>
{ public void Dispose()
Stop(); {
} if (proxyRunning)
{
CertificateManager?.Dispose(); Stop();
} }
#if NET45 CertificateManager?.Dispose();
/// <summary> }
/// Listen on the given end point on local machine
/// </summary> #if NET45
/// <param name="endPoint"></param> /// <summary>
private void Listen(ProxyEndPoint endPoint) /// Listen on the given end point on local machine
{ /// </summary>
endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port); /// <param name="endPoint"></param>
endPoint.Listener.Start(); private void Listen(ProxyEndPoint endPoint)
{
endPoint.Port = ((IPEndPoint)endPoint.Listener.LocalEndpoint).Port; endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
// accept clients asynchronously endPoint.Listener.Start();
endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
} endPoint.Port = ((IPEndPoint)endPoint.Listener.LocalEndpoint).Port;
#else // accept clients asynchronously
private async void Listen(ProxyEndPoint endPoint) endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
{ }
endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port); #else
endPoint.Listener.Start(); private async void Listen(ProxyEndPoint endPoint)
{
endPoint.Port = ((IPEndPoint)endPoint.Listener.LocalEndpoint).Port; endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
endPoint.Listener.Start();
while (true)
{ endPoint.Port = ((IPEndPoint)endPoint.Listener.LocalEndpoint).Port;
TcpClient tcpClient = await endPoint.Listener.AcceptTcpClientAsync();
if (tcpClient != null) while (true)
Task.Run(async () => HandleClient(tcpClient, endPoint)); {
} TcpClient tcpClient = await endPoint.Listener.AcceptTcpClientAsync();
if (tcpClient != null)
} Task.Run(async () => HandleClient(tcpClient, endPoint));
#endif }
/// <summary> }
/// Verifiy if its safe to set this end point as System proxy #endif
/// </summary>
/// <param name="endPoint"></param> /// <summary>
private void ValidateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint) /// Verifiy if its safe to set this end point as System proxy
{ /// </summary>
if (endPoint == null) /// <param name="endPoint"></param>
throw new ArgumentNullException(nameof(endPoint)); private void ValidateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint)
if (ProxyEndPoints.Contains(endPoint) == false) {
{ if (endPoint == null)
throw new Exception("Cannot set endPoints not added to proxy as system proxy"); throw new ArgumentNullException(nameof(endPoint));
} if (ProxyEndPoints.Contains(endPoint) == false)
{
if (!proxyRunning) throw new Exception("Cannot set endPoints not added to proxy as system proxy");
{ }
throw new Exception("Cannot set system proxy settings before proxy has been started.");
} if (!proxyRunning)
} {
throw new Exception("Cannot set system proxy settings before proxy has been started.");
#if NET45 }
/// <summary> }
/// Gets the system up stream proxy.
/// </summary> #if NET45
/// <param name="sessionEventArgs">The <see cref="SessionEventArgs"/> instance containing the event data.</param> /// <summary>
/// <returns><see cref="ExternalProxy"/> instance containing valid proxy configuration from PAC/WAPD scripts if any exists.</returns> /// Gets the system up stream proxy.
private Task<ExternalProxy> GetSystemUpStreamProxy(SessionEventArgs sessionEventArgs) /// </summary>
{ /// <param name="sessionEventArgs">The <see cref="SessionEventArgs"/> instance containing the event data.</param>
var proxy = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri); /// <returns><see cref="ExternalProxy"/> instance containing valid proxy configuration from PAC/WAPD scripts if any exists.</returns>
return Task.FromResult(proxy); private Task<ExternalProxy> GetSystemUpStreamProxy(SessionEventArgs sessionEventArgs)
} {
#endif var proxy = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri);
return Task.FromResult(proxy);
private void EnsureRootCertificate() }
{ #endif
if (!CertificateManager.CertValidated)
{ private void EnsureRootCertificate()
CertificateManager.CreateTrustedRootCertificate(); {
if (!CertificateManager.CertValidated)
if (TrustRootCertificate) {
{ CertificateManager.CreateTrustedRootCertificate();
CertificateManager.TrustRootCertificate();
} if (TrustRootCertificate)
} {
} CertificateManager.TrustRootCertificate();
}
#if NET45 }
/// <summary> }
/// When a connection is received from client act
/// </summary> #if NET45
/// <param name="asyn"></param> /// <summary>
private void OnAcceptConnection(IAsyncResult asyn) /// When a connection is received from client act
{ /// </summary>
var endPoint = (ProxyEndPoint)asyn.AsyncState; /// <param name="asyn"></param>
private void OnAcceptConnection(IAsyncResult asyn)
TcpClient tcpClient = null; {
var endPoint = (ProxyEndPoint)asyn.AsyncState;
try
{ TcpClient tcpClient = null;
//based on end point type call appropriate request handlers
tcpClient = endPoint.Listener.EndAcceptTcpClient(asyn); try
} {
catch (ObjectDisposedException) //based on end point type call appropriate request handlers
{ tcpClient = endPoint.Listener.EndAcceptTcpClient(asyn);
// The listener was Stop()'d, disposing the underlying socket and }
// triggering the completion of the callback. We're already exiting, catch (ObjectDisposedException)
// so just return. {
return; // The listener was Stop()'d, disposing the underlying socket and
} // triggering the completion of the callback. We're already exiting,
catch // so just return.
{ return;
//Other errors are discarded to keep proxy running }
} catch
{
if (tcpClient != null) //Other errors are discarded to keep proxy running
{ }
Task.Run(async () =>
{ if (tcpClient != null)
await HandleClient(tcpClient, endPoint); {
}); Task.Run(async () =>
} {
await HandleClient(tcpClient, endPoint);
// Get the listener that handles the client request. });
endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint); }
}
#endif // Get the listener that handles the client request.
endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
private async Task HandleClient(TcpClient tcpClient, ProxyEndPoint endPoint) }
{ #endif
UpdateClientConnectionCount(true);
private async Task HandleClient(TcpClient tcpClient, ProxyEndPoint endPoint)
tcpClient.ReceiveTimeout = ConnectionTimeOutSeconds * 1000; {
tcpClient.SendTimeout = ConnectionTimeOutSeconds * 1000; UpdateClientConnectionCount(true);
try tcpClient.ReceiveTimeout = ConnectionTimeOutSeconds * 1000;
{ tcpClient.SendTimeout = ConnectionTimeOutSeconds * 1000;
if (endPoint.GetType() == typeof(TransparentProxyEndPoint))
{ try
await HandleClient(endPoint as TransparentProxyEndPoint, tcpClient); {
} if (endPoint.GetType() == typeof(TransparentProxyEndPoint))
else {
{ await HandleClient(endPoint as TransparentProxyEndPoint, tcpClient);
await HandleClient(endPoint as ExplicitProxyEndPoint, tcpClient); }
} else
} {
finally await HandleClient(endPoint as ExplicitProxyEndPoint, tcpClient);
{ }
UpdateClientConnectionCount(false); }
finally
try {
{ UpdateClientConnectionCount(false);
if (tcpClient != null)
{ try
//This line is important! {
//contributors please don't remove it without discussion if (tcpClient != null)
//It helps to avoid eventual deterioration of performance due to TCP port exhaustion {
//due to default TCP CLOSE_WAIT timeout for 4 minutes //This line is important!
tcpClient.LingerState = new LingerOption(true, 0); //contributors please don't remove it without discussion
tcpClient.Dispose(); //It helps to avoid eventual deterioration of performance due to TCP port exhaustion
} //due to default TCP CLOSE_WAIT timeout for 4 minutes
} tcpClient.LingerState = new LingerOption(true, 0);
catch tcpClient.Dispose();
{ }
} }
} catch
} {
}
/// <summary> }
/// Quit listening on the given end point }
/// </summary>
/// <param name="endPoint"></param> /// <summary>
private void QuitListen(ProxyEndPoint endPoint) /// Quit listening on the given end point
{ /// </summary>
endPoint.Listener.Stop(); /// <param name="endPoint"></param>
endPoint.Listener.Server.Dispose(); private void QuitListen(ProxyEndPoint endPoint)
} {
endPoint.Listener.Stop();
internal void UpdateClientConnectionCount(bool increment) endPoint.Listener.Server.Dispose();
{ }
if (increment)
{ internal void UpdateClientConnectionCount(bool increment)
Interlocked.Increment(ref clientConnectionCount); {
} if (increment)
else {
{ Interlocked.Increment(ref clientConnectionCount);
Interlocked.Decrement(ref clientConnectionCount); }
} else
{
ClientConnectionCountChanged?.Invoke(this, EventArgs.Empty); Interlocked.Decrement(ref clientConnectionCount);
} }
internal void UpdateServerConnectionCount(bool increment) ClientConnectionCountChanged?.Invoke(this, EventArgs.Empty);
{ }
if (increment)
{ internal void UpdateServerConnectionCount(bool increment)
Interlocked.Increment(ref serverConnectionCount); {
} if (increment)
else {
{ Interlocked.Increment(ref serverConnectionCount);
Interlocked.Decrement(ref serverConnectionCount); }
} else
{
ServerConnectionCountChanged?.Invoke(this, EventArgs.Empty); Interlocked.Decrement(ref serverConnectionCount);
} }
}
} ServerConnectionCountChanged?.Invoke(this, EventArgs.Empty);
}
}
}
...@@ -164,24 +164,30 @@ namespace Titanium.Web.Proxy ...@@ -164,24 +164,30 @@ namespace Titanium.Web.Proxy
//create new connection //create new connection
using (var connection = await GetServerConnection(connectArgs, true)) using (var connection = await GetServerConnection(connectArgs, true))
{ {
if (isClientHello) try
{ {
if (clientStream.Available > 0) if (isClientHello)
{ {
//send the buffered data if (clientStream.Available > 0)
var data = new byte[clientStream.Available]; {
await clientStream.ReadAsync(data, 0, data.Length); //send the buffered data
await connection.Stream.WriteAsync(data, 0, data.Length); var data = new byte[clientStream.Available];
await connection.Stream.FlushAsync(); await clientStream.ReadAsync(data, 0, data.Length);
await connection.Stream.WriteAsync(data, 0, data.Length);
await connection.Stream.FlushAsync();
}
var serverHelloInfo = await SslTools.PeekServerHello(connection.Stream);
((ConnectResponse)connectArgs.WebSession.Response).ServerHelloInfo = serverHelloInfo;
} }
var serverHelloInfo = await SslTools.PeekServerHello(connection.Stream); await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize,
((ConnectResponse)connectArgs.WebSession.Response).ServerHelloInfo = serverHelloInfo; (buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); });
}
finally
{
UpdateServerConnectionCount(false);
} }
await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize,
(buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); });
UpdateServerConnectionCount(false);
} }
return; return;
...@@ -361,8 +367,8 @@ namespace Titanium.Web.Proxy ...@@ -361,8 +367,8 @@ namespace Titanium.Web.Proxy
&& !args.WebSession.UpStreamEndPoint.Equals(connection.UpStreamEndPoint)))) && !args.WebSession.UpStreamEndPoint.Equals(connection.UpStreamEndPoint))))
{ {
connection.Dispose(); connection.Dispose();
UpdateServerConnectionCount(false);
connection = null; connection = null;
UpdateServerConnectionCount(false);
} }
if (connection == null) if (connection == null)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment