Commit 99e3b575 authored by justcoding121's avatar justcoding121

Merge branch 'develop' into beta

parents dd688aa1 38116fb9
......@@ -98,13 +98,15 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary>
/// Constructor to initialize the proxy
/// </summary>
internal SessionEventArgs(int bufferSize, ProxyEndPoint endPoint, Func<SessionEventArgs, Task> httpResponseHandler)
internal SessionEventArgs(int bufferSize,
ProxyEndPoint endPoint,
Func<SessionEventArgs, Task> httpResponseHandler)
{
this.bufferSize = bufferSize;
this.httpResponseHandler = httpResponseHandler;
ProxyClient = new ProxyClient();
WebSession = new HttpWebClient();
WebSession = new HttpWebClient(bufferSize);
WebSession.ProcessId = new Lazy<int>(() =>
{
......
using Titanium.Web.Proxy.Models;
using System.Net;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.EventArguments
{
......@@ -6,7 +7,8 @@ namespace Titanium.Web.Proxy.EventArguments
{
public bool IsHttpsConnect { get; set; }
public TunnelConnectSessionEventArgs(ProxyEndPoint endPoint) : base(0, endPoint, null)
public TunnelConnectSessionEventArgs(int bufferSize, ProxyEndPoint endPoint)
: base(bufferSize, endPoint, null)
{
}
......
......@@ -3,7 +3,7 @@ using System.Security.Cryptography.X509Certificates;
namespace Titanium.Web.Proxy.Extensions
{
internal static class DotNetStandardExtensions
internal static class DotNet45Extensions
{
#if NET45
/// <summary>
......
......@@ -17,9 +17,9 @@ namespace Titanium.Web.Proxy.Extensions
/// <param name="input"></param>
/// <param name="output"></param>
/// <param name="onCopy"></param>
internal static async Task CopyToAsync(this Stream input, Stream output, Action<byte[], int, int> onCopy)
internal static async Task CopyToAsync(this Stream input, Stream output, Action<byte[], int, int> onCopy, int bufferSize)
{
byte[] buffer = new byte[81920];
byte[] buffer = new byte[bufferSize];
while (true)
{
int num = await input.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
......
......@@ -5,34 +5,6 @@ namespace Titanium.Web.Proxy.Extensions
{
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
/// <summary>
......
......@@ -4,7 +4,8 @@ namespace Titanium.Web.Proxy.Helpers
{
sealed class HttpRequestWriter : HttpWriter
{
public HttpRequestWriter(Stream stream) : base(stream, true)
public HttpRequestWriter(Stream stream, int bufferSize)
: base(stream, bufferSize, true)
{
}
}
......
......@@ -12,7 +12,8 @@ namespace Titanium.Web.Proxy.Helpers
{
sealed class HttpResponseWriter : HttpWriter
{
public HttpResponseWriter(Stream stream) : base(stream, true)
public HttpResponseWriter(Stream stream, int bufferSize)
: base(stream, bufferSize, true)
{
}
......
......@@ -8,7 +8,8 @@ namespace Titanium.Web.Proxy.Helpers
{
abstract class HttpWriter : StreamWriter
{
protected HttpWriter(Stream stream, bool leaveOpen) : base(stream, Encoding.ASCII, 1024, leaveOpen)
protected HttpWriter(Stream stream, int bufferSize, bool leaveOpen)
: base(stream, Encoding.ASCII, bufferSize, leaveOpen)
{
NewLine = ProxyConstants.NewLine;
}
......
......@@ -116,12 +116,13 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="onDataSend"></param>
/// <param name="onDataReceive"></param>
/// <returns></returns>
internal static async Task SendRaw(Stream clientStream, Stream serverStream, Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive)
internal static async Task SendRaw(Stream clientStream, Stream serverStream, int bufferSize,
Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive)
{
//Now async relay all server=>client & client=>server data
var sendRelay = clientStream.CopyToAsync(serverStream, onDataSend);
var sendRelay = clientStream.CopyToAsync(serverStream, onDataSend, bufferSize);
var receiveRelay = serverStream.CopyToAsync(clientStream, onDataReceive);
var receiveRelay = serverStream.CopyToAsync(clientStream, onDataReceive, bufferSize);
await Task.WhenAll(sendRelay, receiveRelay);
}
......
......@@ -13,6 +13,8 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
public class HttpWebClient : IDisposable
{
private int bufferSize;
/// <summary>
/// Connection to server
/// </summary>
......@@ -23,6 +25,11 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
public Guid RequestId { get; }
/// <summary>
/// Override UpStreamEndPoint for this request; Local NIC via request is made
/// </summary>
public IPEndPoint UpStreamEndPoint { get; set; }
/// <summary>
/// Headers passed with Connect.
/// </summary>
......@@ -49,8 +56,10 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
public bool IsHttps => Request.IsHttps;
internal HttpWebClient()
internal HttpWebClient(int bufferSize)
{
this.bufferSize = bufferSize;
RequestId = Guid.NewGuid();
Request = new Request();
Response = new Response();
......@@ -77,7 +86,7 @@ namespace Titanium.Web.Proxy.Http
byte[] requestBytes;
using (var ms = new MemoryStream())
using (var writer = new HttpRequestWriter(ms))
using (var writer = new HttpRequestWriter(ms, bufferSize))
{
var upstreamProxy = ServerConnection.UpStreamHttpProxy;
......
using StreamExtended.Network;
using System;
using System.Net;
using System.Net.Sockets;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models;
......@@ -25,6 +26,11 @@ namespace Titanium.Web.Proxy.Network.Tcp
internal bool UseUpstreamProxy { get; set; }
/// <summary>
/// Local NIC via connection is made
/// </summary>
internal IPEndPoint UpStreamEndPoint { get; set; }
/// <summary>
/// Http version
/// </summary>
......
using StreamExtended.Network;
using System;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
......@@ -17,19 +18,21 @@ namespace Titanium.Web.Proxy.Network.Tcp
internal class TcpConnectionFactory
{
/// <summary>
/// Creates a TCP connection to server
/// 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="isConnect"></param>
/// <param name="isHttps"></param>
/// <param name="isConnect"></param>
/// <param name="upStreamEndPoint"></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,
bool isConnect, ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy)
internal async Task<TcpConnection> CreateClient(ProxyServer server,
string remoteHostName, int remotePort, Version httpVersion, bool isHttps,
bool isConnect, IPEndPoint upStreamEndPoint, ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy)
{
bool useUpstreamProxy = false;
var externalProxy = isHttps ? externalHttpsProxy : externalHttpProxy;
......@@ -52,10 +55,10 @@ namespace Titanium.Web.Proxy.Network.Tcp
try
{
#if NET45
client = new TcpClient(server.UpStreamEndPoint);
client = new TcpClient(upStreamEndPoint);
#else
client = new TcpClient();
client.Client.Bind(server.UpStreamEndPoint);
client.Client.Bind(upStreamEndPoint);
#endif
//If this proxy uses another external proxy then create a tunnel request for HTTP/HTTPS connections
......@@ -72,7 +75,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
if (useUpstreamProxy && (isConnect || isHttps))
{
using (var writer = new HttpRequestWriter(stream))
using (var writer = new HttpRequestWriter(stream, server.BufferSize))
{
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
......@@ -128,6 +131,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
{
UpStreamHttpProxy = externalHttpProxy,
UpStreamHttpsProxy = externalHttpsProxy,
UpStreamEndPoint = upStreamEndPoint,
HostName = remoteHostName,
Port = remotePort,
IsHttps = isHttps,
......
......@@ -6,11 +6,11 @@ using System.Runtime.InteropServices;
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Titanium.Web.Proxy.Properties")]
[assembly: AssemblyTitle("Titanium.Web.Proxy")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Titanium.Web.Proxy.Properties")]
[assembly: AssemblyProduct("Titanium.Web.Proxy")]
[assembly: AssemblyCopyright("Copyright © Titanium 2015-2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
......
using StreamExtended.Network;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
#if NET45
using Titanium.Web.Proxy.Helpers.WinHttp;
#endif
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Network.Tcp;
#if NET45
using Titanium.Web.Proxy.Network.WinAuth.Security;
#endif
namespace Titanium.Web.Proxy
{
/// <summary>
/// Proxy Server Main class
/// </summary>
public partial class ProxyServer : IDisposable
{
#if NET45
internal static readonly string UriSchemeHttp = Uri.UriSchemeHttp;
internal static readonly string UriSchemeHttps = Uri.UriSchemeHttps;
#else
internal const string UriSchemeHttp = "http";
internal const string UriSchemeHttps = "https";
#endif
/// <summary>
/// Is the proxy currently running
/// </summary>
private bool proxyRunning { get; set; }
/// <summary>
/// An default exception log func
/// </summary>
private readonly Lazy<Action<Exception>> defaultExceptionFunc = new Lazy<Action<Exception>>(() => (e => { }));
/// <summary>
/// backing exception func for exposed public property
/// </summary>
private Action<Exception> exceptionFunc;
/// <summary>
/// Backing field for corresponding public property
/// </summary>
private bool trustRootCertificate;
/// <summary>
/// Backing field for corresponding public property
/// </summary>
private int clientConnectionCount;
/// <summary>
/// Backing field for corresponding public property
/// </summary>
private int serverConnectionCount;
/// <summary>
/// A object that creates tcp connection to server
/// </summary>
private TcpConnectionFactory tcpConnectionFactory { get; }
#if NET45
private WinHttpWebProxyFinder systemProxyResolver;
/// <summary>
/// Manage system proxy settings
/// </summary>
private SystemProxyManager systemProxySettingsManager { get; }
/// <summary>
/// Set firefox to use default system proxy
/// </summary>
private readonly FireFoxProxySettingsManager firefoxProxySettingsManager = new FireFoxProxySettingsManager();
#endif
/// <summary>
/// Buffer size used throughout this proxy
/// </summary>
public int BufferSize { get; set; } = 8192;
/// <summary>
/// Manages certificates used by this proxy
/// </summary>
public CertificateManager CertificateManager { get; }
/// <summary>
/// The root certificate
/// </summary>
public X509Certificate2 RootCertificate
{
get { return CertificateManager.RootCertificate; }
set { CertificateManager.RootCertificate = value; }
}
/// <summary>
/// Name of the root certificate issuer
/// (This is valid only when RootCertificate property is not set)
/// </summary>
public string RootCertificateIssuerName
{
get { return CertificateManager.Issuer; }
set { CertificateManager.Issuer = value; }
}
/// <summary>
/// Name of the root certificate
/// (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
/// The provided root certificate will be stored in proxy exe directory with the private key
/// Root certificate file will be named as "rootCert.pfx"
/// </summary>
public string RootCertificateName
{
get { return CertificateManager.RootCertificateName; }
set { CertificateManager.RootCertificateName = value; }
}
/// <summary>
/// Trust the RootCertificate used by this proxy server
/// 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
/// </summary>
public bool TrustRootCertificate
{
get { return trustRootCertificate; }
set
{
trustRootCertificate = value;
if (value)
{
EnsureRootCertificate();
}
}
}
/// <summary>
/// Select Certificate Engine
/// Optionally set to BouncyCastle
/// Mono only support BouncyCastle and it is the default
/// </summary>
public CertificateEngine CertificateEngine
{
get { return CertificateManager.Engine; }
set { CertificateManager.Engine = value; }
}
/// <summary>
/// Should we check for certificare revocation during SSL authentication to servers
/// Note: If enabled can reduce performance (Default disabled)
/// </summary>
public bool CheckCertificateRevocation { get; set; }
/// <summary>
/// Does this proxy uses the HTTP protocol 100 continue behaviour strictly?
/// Broken 100 contunue implementations on server/client may cause problems if enabled
/// </summary>
public bool Enable100ContinueBehaviour { get; set; }
/// <summary>
/// Minutes certificates should be kept in cache when not used
/// </summary>
public int CertificateCacheTimeOutMinutes { get; set; }
/// <summary>
/// Seconds client/server connection are to be kept alive when waiting for read/write to complete
/// </summary>
public int ConnectionTimeOutSeconds { get; set; }
/// <summary>
/// Intercept request to server
/// </summary>
public event Func<object, SessionEventArgs, Task> BeforeRequest;
/// <summary>
/// Intercept response from server
/// </summary>
public event Func<object, SessionEventArgs, Task> BeforeResponse;
/// <summary>
/// Intercept tunnel connect reques
/// </summary>
public event Func<object, TunnelConnectSessionEventArgs, Task> TunnelConnectRequest;
/// <summary>
/// Intercept tunnel connect response
/// </summary>
public event Func<object, TunnelConnectSessionEventArgs, Task> TunnelConnectResponse;
/// <summary>
/// Occurs when client connection count changed.
/// </summary>
public event EventHandler ClientConnectionCountChanged;
/// <summary>
/// Occurs when server connection count changed.
/// </summary>
public event EventHandler ServerConnectionCountChanged;
/// <summary>
/// External proxy for Http
/// </summary>
public ExternalProxy UpStreamHttpProxy { get; set; }
/// <summary>
/// External proxy for Http
/// </summary>
public ExternalProxy UpStreamHttpsProxy { get; set; }
/// <summary>
/// Local adapter/NIC endpoint (where proxy makes request via)
/// default via any IP addresses of this machine
/// </summary>
public IPEndPoint UpStreamEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0);
/// <summary>
/// Is the proxy currently running
/// </summary>
public bool ProxyRunning => proxyRunning;
/// <summary>
/// Gets or sets a value indicating whether requests will be chained to upstream gateway.
/// </summary>
public bool ForwardToUpstreamGateway { get; set; }
/// <summary>
/// Enable disable Windows Authentication (NTLM/Kerberos)
/// Note: NTLM/Kerberos will always send local credentials of current user
/// who is running the proxy process. This is because a man
/// in middle attack is not currently supported
/// (which would require windows delegation enabled for this server process)
/// </summary>
public bool EnableWinAuth { get; set; }
/// <summary>
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication
/// </summary>
public event Func<object, CertificateValidationEventArgs, Task> ServerCertificateValidationCallback;
/// <summary>
/// Callback tooverride client certificate during SSL mutual authentication
/// </summary>
public event Func<object, CertificateSelectionEventArgs, Task> ClientCertificateSelectionCallback;
/// <summary>
/// Callback for error events in proxy
/// </summary>
public Action<Exception> ExceptionFunc
{
get { return exceptionFunc ?? defaultExceptionFunc.Value; }
set { exceptionFunc = value; }
}
/// <summary>
/// A callback to authenticate clients
/// Parameters are username, password provided by client
/// return true for successful authentication
/// </summary>
public Func<string, string, Task<bool>> AuthenticateUserFunc { get; set; }
/// <summary>
/// Realm used during Proxy Basic Authentication
/// </summary>
public string ProxyRealm { get; set; } = "TitaniumProxy";
/// <summary>
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP requests
/// return the ExternalProxy object with valid credentials
/// </summary>
public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpProxyFunc { get; set; }
/// <summary>
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTPS requests
/// return the ExternalProxy object with valid credentials
/// </summary>
public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpsProxyFunc { get; set; }
/// <summary>
/// A list of IpAddress and port this proxy is listening to
/// </summary>
using StreamExtended.Network;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
#if NET45
using Titanium.Web.Proxy.Helpers.WinHttp;
#endif
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Network.Tcp;
#if NET45
using Titanium.Web.Proxy.Network.WinAuth.Security;
#endif
namespace Titanium.Web.Proxy
{
/// <summary>
/// Proxy Server Main class
/// </summary>
public partial class ProxyServer : IDisposable
{
#if NET45
internal static readonly string UriSchemeHttp = Uri.UriSchemeHttp;
internal static readonly string UriSchemeHttps = Uri.UriSchemeHttps;
#else
internal const string UriSchemeHttp = "http";
internal const string UriSchemeHttps = "https";
#endif
/// <summary>
/// Is the proxy currently running
/// </summary>
private bool proxyRunning { get; set; }
/// <summary>
/// An default exception log func
/// </summary>
private readonly Lazy<Action<Exception>> defaultExceptionFunc = new Lazy<Action<Exception>>(() => (e => { }));
/// <summary>
/// backing exception func for exposed public property
/// </summary>
private Action<Exception> exceptionFunc;
/// <summary>
/// Backing field for corresponding public property
/// </summary>
private bool trustRootCertificate;
/// <summary>
/// Backing field for corresponding public property
/// </summary>
private int clientConnectionCount;
/// <summary>
/// Backing field for corresponding public property
/// </summary>
private int serverConnectionCount;
/// <summary>
/// A object that creates tcp connection to server
/// </summary>
private TcpConnectionFactory tcpConnectionFactory { get; }
#if NET45
private WinHttpWebProxyFinder systemProxyResolver;
/// <summary>
/// Manage system proxy settings
/// </summary>
private SystemProxyManager systemProxySettingsManager { get; }
/// <summary>
/// Set firefox to use default system proxy
/// </summary>
private readonly FireFoxProxySettingsManager firefoxProxySettingsManager = new FireFoxProxySettingsManager();
#endif
/// <summary>
/// Buffer size used throughout this proxy
/// </summary>
public int BufferSize { get; set; } = 8192;
/// <summary>
/// Manages certificates used by this proxy
/// </summary>
public CertificateManager CertificateManager { get; }
/// <summary>
/// The root certificate
/// </summary>
public X509Certificate2 RootCertificate
{
get { return CertificateManager.RootCertificate; }
set { CertificateManager.RootCertificate = value; }
}
/// <summary>
/// Name of the root certificate issuer
/// (This is valid only when RootCertificate property is not set)
/// </summary>
public string RootCertificateIssuerName
{
get { return CertificateManager.Issuer; }
set { CertificateManager.Issuer = value; }
}
/// <summary>
/// Name of the root certificate
/// (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
/// The provided root certificate will be stored in proxy exe directory with the private key
/// Root certificate file will be named as "rootCert.pfx"
/// </summary>
public string RootCertificateName
{
get { return CertificateManager.RootCertificateName; }
set { CertificateManager.RootCertificateName = value; }
}
/// <summary>
/// Trust the RootCertificate used by this proxy server
/// 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
/// </summary>
public bool TrustRootCertificate
{
get { return trustRootCertificate; }
set
{
trustRootCertificate = value;
if (value)
{
EnsureRootCertificate();
}
}
}
/// <summary>
/// Select Certificate Engine
/// Optionally set to BouncyCastle
/// Mono only support BouncyCastle and it is the default
/// </summary>
public CertificateEngine CertificateEngine
{
get { return CertificateManager.Engine; }
set { CertificateManager.Engine = value; }
}
/// <summary>
/// Should we check for certificare revocation during SSL authentication to servers
/// Note: If enabled can reduce performance (Default disabled)
/// </summary>
public bool CheckCertificateRevocation { get; set; }
/// <summary>
/// Does this proxy uses the HTTP protocol 100 continue behaviour strictly?
/// Broken 100 contunue implementations on server/client may cause problems if enabled
/// </summary>
public bool Enable100ContinueBehaviour { get; set; }
/// <summary>
/// Minutes certificates should be kept in cache when not used
/// </summary>
public int CertificateCacheTimeOutMinutes { get; set; }
/// <summary>
/// Seconds client/server connection are to be kept alive when waiting for read/write to complete
/// </summary>
public int ConnectionTimeOutSeconds { get; set; }
/// <summary>
/// Intercept request to server
/// </summary>
public event Func<object, SessionEventArgs, Task> BeforeRequest;
/// <summary>
/// Intercept response from server
/// </summary>
public event Func<object, SessionEventArgs, Task> BeforeResponse;
/// <summary>
/// Intercept tunnel connect reques
/// </summary>
public event Func<object, TunnelConnectSessionEventArgs, Task> TunnelConnectRequest;
/// <summary>
/// Intercept tunnel connect response
/// </summary>
public event Func<object, TunnelConnectSessionEventArgs, Task> TunnelConnectResponse;
/// <summary>
/// Occurs when client connection count changed.
/// </summary>
public event EventHandler ClientConnectionCountChanged;
/// <summary>
/// Occurs when server connection count changed.
/// </summary>
public event EventHandler ServerConnectionCountChanged;
/// <summary>
/// External proxy for Http
/// </summary>
public ExternalProxy UpStreamHttpProxy { get; set; }
/// <summary>
/// External proxy for Http
/// </summary>
public ExternalProxy UpStreamHttpsProxy { get; set; }
/// <summary>
/// Local adapter/NIC endpoint (where proxy makes request via)
/// default via any IP addresses of this machine
/// </summary>
public IPEndPoint UpStreamEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0);
/// <summary>
/// Is the proxy currently running
/// </summary>
public bool ProxyRunning => proxyRunning;
/// <summary>
/// Gets or sets a value indicating whether requests will be chained to upstream gateway.
/// </summary>
public bool ForwardToUpstreamGateway { get; set; }
/// <summary>
/// Enable disable Windows Authentication (NTLM/Kerberos)
/// Note: NTLM/Kerberos will always send local credentials of current user
/// who is running the proxy process. This is because a man
/// in middle attack is not currently supported
/// (which would require windows delegation enabled for this server process)
/// </summary>
public bool EnableWinAuth { get; set; }
/// <summary>
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication
/// </summary>
public event Func<object, CertificateValidationEventArgs, Task> ServerCertificateValidationCallback;
/// <summary>
/// Callback tooverride client certificate during SSL mutual authentication
/// </summary>
public event Func<object, CertificateSelectionEventArgs, Task> ClientCertificateSelectionCallback;
/// <summary>
/// Callback for error events in proxy
/// </summary>
public Action<Exception> ExceptionFunc
{
get { return exceptionFunc ?? defaultExceptionFunc.Value; }
set { exceptionFunc = value; }
}
/// <summary>
/// A callback to authenticate clients
/// Parameters are username, password provided by client
/// return true for successful authentication
/// </summary>
public Func<string, string, Task<bool>> AuthenticateUserFunc { get; set; }
/// <summary>
/// Realm used during Proxy Basic Authentication
/// </summary>
public string ProxyRealm { get; set; } = "TitaniumProxy";
/// <summary>
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP requests
/// return the ExternalProxy object with valid credentials
/// </summary>
public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpProxyFunc { get; set; }
/// <summary>
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTPS requests
/// return the ExternalProxy object with valid credentials
/// </summary>
public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpsProxyFunc { get; set; }
/// <summary>
/// A list of IpAddress and port this proxy is listening to
/// </summary>
public List<ProxyEndPoint> ProxyEndPoints { get; set; }
/// <summary>
/// List of supported Ssl versions
/// </summary>
#if NET45
public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
#else
public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12;
#endif
/// <summary>
/// Total number of active client connections
/// </summary>
public int ClientConnectionCount => clientConnectionCount;
/// <summary>
/// Total number of active server connections
/// </summary>
public int ServerConnectionCount => serverConnectionCount;
/// <summary>
/// Constructor
/// </summary>
public ProxyServer() : this(null, null)
{
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="rootCertificateName">Name of root certificate.</param>
/// <param name="rootCertificateIssuerName">Name of root certificate issuer.</param>
public ProxyServer(string rootCertificateName, string rootCertificateIssuerName)
{
//default values
ConnectionTimeOutSeconds = 30;
CertificateCacheTimeOutMinutes = 60;
ProxyEndPoints = new List<ProxyEndPoint>();
tcpConnectionFactory = new TcpConnectionFactory();
#if NET45
if (!RunTime.IsRunningOnMono)
{
systemProxySettingsManager = new SystemProxyManager();
}
#endif
CertificateManager = new CertificateManager(ExceptionFunc);
if (rootCertificateName != null)
{
RootCertificateName = rootCertificateName;
}
if (rootCertificateIssuerName != null)
{
RootCertificateIssuerName = rootCertificateIssuerName;
}
}
/// <summary>
/// Add a proxy end point
/// </summary>
/// <param name="endPoint"></param>
public void AddEndPoint(ProxyEndPoint endPoint)
{
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");
}
ProxyEndPoints.Add(endPoint);
if (proxyRunning)
{
Listen(endPoint);
}
}
/// <summary>
/// Remove a proxy end point
/// Will throw error if the end point does'nt exist
/// </summary>
/// <param name="endPoint"></param>
public void RemoveEndPoint(ProxyEndPoint endPoint)
{
if (ProxyEndPoints.Contains(endPoint) == false)
{
throw new Exception("Cannot remove endPoints not added to proxy");
}
ProxyEndPoints.Remove(endPoint);
if (proxyRunning)
{
QuitListen(endPoint);
}
}
#if NET45
/// <summary>
/// Set the given explicit end point as the default proxy server for current machine
/// </summary>
/// <param name="endPoint"></param>
public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint)
{
SetAsSystemProxy(endPoint, ProxyProtocolType.Http);
}
/// <summary>
/// Set the given explicit end point as the default proxy server for current machine
/// </summary>
/// <param name="endPoint"></param>
public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint)
{
SetAsSystemProxy(endPoint, ProxyProtocolType.Https);
}
/// <summary>
/// Set the given explicit end point as the default proxy server for current machine
/// </summary>
/// <param name="endPoint"></param>
/// <param name="protocolType"></param>
public void SetAsSystemProxy(ExplicitProxyEndPoint endPoint, ProxyProtocolType protocolType)
{
if (RunTime.IsRunningOnMono)
{
throw new Exception("Mono Runtime do not support system proxy settings.");
}
ValidateEndPointAsSystemProxy(endPoint);
bool isHttp = (protocolType & ProxyProtocolType.Http) > 0;
bool isHttps = (protocolType & ProxyProtocolType.Https) > 0;
if (isHttps)
{
if (!endPoint.EnableSsl)
{
throw new Exception("Endpoint do not support Https connections");
}
EnsureRootCertificate();
//If certificate was trusted by the machine
if (!CertificateManager.CertValidated)
{
protocolType = protocolType & ~ProxyProtocolType.Https;
isHttps = false;
}
}
//clear any settings previously added
if (isHttp)
{
ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpProxy = false);
}
if (isHttps)
{
ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpsProxy = false);
}
systemProxySettingsManager.SetProxy(
Equals(endPoint.IpAddress, IPAddress.Any) |
Equals(endPoint.IpAddress, IPAddress.Loopback)
? "127.0.0.1"
: endPoint.IpAddress.ToString(),
endPoint.Port,
protocolType);
if (isHttp)
{
endPoint.IsSystemHttpsProxy = true;
}
if (isHttps)
{
endPoint.IsSystemHttpsProxy = true;
}
firefoxProxySettingsManager.UseSystemProxy();
string proxyType = null;
switch (protocolType)
{
case ProxyProtocolType.Http:
proxyType = "HTTP";
break;
case ProxyProtocolType.Https:
proxyType = "HTTPS";
break;
case ProxyProtocolType.AllHttp:
proxyType = "HTTP and HTTPS";
break;
}
if (protocolType != ProxyProtocolType.None)
{
Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System {2} Proxy", endPoint.IpAddress, endPoint.Port, proxyType);
}
}
/// <summary>
/// Remove any HTTP proxy setting of current machien
/// </summary>
public void DisableSystemHttpProxy()
{
DisableSystemProxy(ProxyProtocolType.Http);
}
/// <summary>
/// Remove any HTTPS proxy setting for current machine
/// </summary>
public void DisableSystemHttpsProxy()
{
DisableSystemProxy(ProxyProtocolType.Https);
}
/// <summary>
/// Remove the specified proxy settings for current machine
/// </summary>
public void DisableSystemProxy(ProxyProtocolType protocolType)
{
if (RunTime.IsRunningOnMono)
{
throw new Exception("Mono Runtime do not support system proxy settings.");
}
systemProxySettingsManager.RemoveProxy(protocolType);
}
/// <summary>
/// Clear all proxy settings for current machine
/// </summary>
public void DisableAllSystemProxies()
{
if (RunTime.IsRunningOnMono)
{
throw new Exception("Mono Runtime do not support system proxy settings.");
}
systemProxySettingsManager.DisableAllProxy();
}
#endif
/// <summary>
/// Start this proxy server
/// </summary>
public void Start()
{
if (proxyRunning)
{
throw new Exception("Proxy is already running.");
}
#if NET45
//clear any system proxy settings which is pointing to our own endpoint
//due to non gracious proxy shutdown before
if (systemProxySettingsManager != null)
{
var proxyInfo = systemProxySettingsManager.GetProxyInfoFromRegistry();
if (proxyInfo.Proxies != null)
{
var protocolToRemove = ProxyProtocolType.None;
foreach (var proxy in proxyInfo.Proxies.Values)
{
if (proxy.HostName == "127.0.0.1" && 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
systemProxySettingsManager.RemoveProxy(protocolToRemove, false);
}
}
}
if (ForwardToUpstreamGateway
&& GetCustomUpStreamHttpProxyFunc == null && GetCustomUpStreamHttpsProxyFunc == null
&& systemProxySettingsManager != null)
{
// Use WinHttp to handle PAC/WAPD scripts.
systemProxyResolver = new WinHttpWebProxyFinder();
systemProxyResolver.LoadFromIE();
GetCustomUpStreamHttpProxyFunc = GetSystemUpStreamProxy;
GetCustomUpStreamHttpsProxyFunc = GetSystemUpStreamProxy;
}
#endif
foreach (var endPoint in ProxyEndPoints)
{
Listen(endPoint);
}
CertificateManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes);
#if NET45
if (!RunTime.IsRunningOnMono)
{
//clear orphaned windows auth states every 2 minutes
WinAuthEndPoint.ClearIdleStates(2);
}
#endif
proxyRunning = true;
}
/// <summary>
/// Stop this proxy server
/// </summary>
public void Stop()
{
if (!proxyRunning)
{
throw new Exception("Proxy is not running.");
}
#if NET45
if (!RunTime.IsRunningOnMono)
{
bool setAsSystemProxy = ProxyEndPoints.OfType<ExplicitProxyEndPoint>().Any(x => x.IsSystemHttpProxy || x.IsSystemHttpsProxy);
if (setAsSystemProxy)
{
systemProxySettingsManager.RestoreOriginalSettings();
}
}
#endif
foreach (var endPoint in ProxyEndPoints)
{
QuitListen(endPoint);
}
ProxyEndPoints.Clear();
CertificateManager?.StopClearIdleCertificates();
proxyRunning = false;
}
/// <summary>
/// Handle dispose of a client/server session
/// </summary>
/// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param>
/// <param name="serverConnection"></param>
private void Dispose(CustomBufferedStream clientStream, CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, TcpConnection serverConnection)
{
clientStream?.Dispose();
clientStreamReader?.Dispose();
clientStreamWriter?.Dispose();
if (serverConnection != null)
{
serverConnection.Dispose();
UpdateServerConnectionCount(false);
}
}
/// <summary>
/// Dispose Proxy.
/// </summary>
public void Dispose()
{
if (proxyRunning)
{
Stop();
}
CertificateManager?.Dispose();
}
#if NET45
/// <summary>
/// Listen on the given end point on local machine
/// </summary>
/// <param name="endPoint"></param>
private void Listen(ProxyEndPoint endPoint)
{
endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
endPoint.Listener.Start();
endPoint.Port = ((IPEndPoint)endPoint.Listener.LocalEndpoint).Port;
// accept clients asynchronously
endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
}
#else
private async void Listen(ProxyEndPoint endPoint)
{
endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
endPoint.Listener.Start();
endPoint.Port = ((IPEndPoint)endPoint.Listener.LocalEndpoint).Port;
while (true)
{
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
/// </summary>
/// <param name="endPoint"></param>
private void ValidateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint)
{
if (endPoint == null)
throw new ArgumentNullException(nameof(endPoint));
if (ProxyEndPoints.Contains(endPoint) == false)
{
throw new Exception("Cannot set endPoints not added to proxy as system proxy");
}
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>
/// <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)
{
var proxy = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri);
return Task.FromResult(proxy);
}
#endif
private void EnsureRootCertificate()
{
if (!CertificateManager.CertValidated)
{
CertificateManager.CreateTrustedRootCertificate();
if (TrustRootCertificate)
{
CertificateManager.TrustRootCertificate();
}
}
}
#if NET45
/// <summary>
/// When a connection is received from client act
/// </summary>
/// <param name="asyn"></param>
private void OnAcceptConnection(IAsyncResult asyn)
{
var endPoint = (ProxyEndPoint)asyn.AsyncState;
TcpClient tcpClient = null;
try
{
//based on end point type call appropriate request handlers
tcpClient = endPoint.Listener.EndAcceptTcpClient(asyn);
}
catch (ObjectDisposedException)
{
// The listener was Stop()'d, disposing the underlying socket and
// triggering the completion of the callback. We're already exiting,
// so just return.
return;
}
catch
{
//Other errors are discarded to keep proxy running
}
if (tcpClient != null)
{
Task.Run(async () =>
{
await HandleClient(tcpClient, endPoint);
});
}
// Get the listener that handles the client request.
endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
}
#endif
private async Task HandleClient(TcpClient tcpClient, ProxyEndPoint endPoint)
{
UpdateClientConnectionCount(true);
tcpClient.ReceiveTimeout = ConnectionTimeOutSeconds * 1000;
tcpClient.SendTimeout = ConnectionTimeOutSeconds * 1000;
try
{
if (endPoint.GetType() == typeof(TransparentProxyEndPoint))
{
await HandleClient(endPoint as TransparentProxyEndPoint, tcpClient);
}
else
{
await HandleClient(endPoint as ExplicitProxyEndPoint, tcpClient);
}
}
finally
{
UpdateClientConnectionCount(false);
try
{
if (tcpClient != null)
{
//This line is important!
//contributors please don't remove it without discussion
//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);
tcpClient.Dispose();
}
}
catch
{
}
}
}
/// <summary>
/// Quit listening on the given end point
/// </summary>
/// <param name="endPoint"></param>
private void QuitListen(ProxyEndPoint endPoint)
{
endPoint.Listener.Stop();
endPoint.Listener.Server.Dispose();
}
internal void UpdateClientConnectionCount(bool increment)
{
if (increment)
{
Interlocked.Increment(ref clientConnectionCount);
}
else
{
Interlocked.Decrement(ref clientConnectionCount);
}
ClientConnectionCountChanged?.Invoke(this, EventArgs.Empty);
}
internal void UpdateServerConnectionCount(bool increment)
{
if (increment)
{
Interlocked.Increment(ref serverConnectionCount);
}
else
{
Interlocked.Decrement(ref serverConnectionCount);
}
ServerConnectionCountChanged?.Invoke(this, EventArgs.Empty);
}
}
}
/// <summary>
/// List of supported Ssl versions
/// </summary>
#if NET45
public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
#else
public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12;
#endif
/// <summary>
/// Total number of active client connections
/// </summary>
public int ClientConnectionCount => clientConnectionCount;
/// <summary>
/// Total number of active server connections
/// </summary>
public int ServerConnectionCount => serverConnectionCount;
/// <summary>
/// Constructor
/// </summary>
public ProxyServer() : this(null, null)
{
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="rootCertificateName">Name of root certificate.</param>
/// <param name="rootCertificateIssuerName">Name of root certificate issuer.</param>
public ProxyServer(string rootCertificateName, string rootCertificateIssuerName)
{
//default values
ConnectionTimeOutSeconds = 30;
CertificateCacheTimeOutMinutes = 60;
ProxyEndPoints = new List<ProxyEndPoint>();
tcpConnectionFactory = new TcpConnectionFactory();
#if NET45
if (!RunTime.IsRunningOnMono)
{
systemProxySettingsManager = new SystemProxyManager();
}
#endif
CertificateManager = new CertificateManager(ExceptionFunc);
if (rootCertificateName != null)
{
RootCertificateName = rootCertificateName;
}
if (rootCertificateIssuerName != null)
{
RootCertificateIssuerName = rootCertificateIssuerName;
}
}
/// <summary>
/// Add a proxy end point
/// </summary>
/// <param name="endPoint"></param>
public void AddEndPoint(ProxyEndPoint endPoint)
{
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");
}
ProxyEndPoints.Add(endPoint);
if (proxyRunning)
{
Listen(endPoint);
}
}
/// <summary>
/// Remove a proxy end point
/// Will throw error if the end point does'nt exist
/// </summary>
/// <param name="endPoint"></param>
public void RemoveEndPoint(ProxyEndPoint endPoint)
{
if (ProxyEndPoints.Contains(endPoint) == false)
{
throw new Exception("Cannot remove endPoints not added to proxy");
}
ProxyEndPoints.Remove(endPoint);
if (proxyRunning)
{
QuitListen(endPoint);
}
}
#if NET45
/// <summary>
/// Set the given explicit end point as the default proxy server for current machine
/// </summary>
/// <param name="endPoint"></param>
public void SetAsSystemHttpProxy(ExplicitProxyEndPoint endPoint)
{
SetAsSystemProxy(endPoint, ProxyProtocolType.Http);
}
/// <summary>
/// Set the given explicit end point as the default proxy server for current machine
/// </summary>
/// <param name="endPoint"></param>
public void SetAsSystemHttpsProxy(ExplicitProxyEndPoint endPoint)
{
SetAsSystemProxy(endPoint, ProxyProtocolType.Https);
}
/// <summary>
/// Set the given explicit end point as the default proxy server for current machine
/// </summary>
/// <param name="endPoint"></param>
/// <param name="protocolType"></param>
public void SetAsSystemProxy(ExplicitProxyEndPoint endPoint, ProxyProtocolType protocolType)
{
if (RunTime.IsRunningOnMono)
{
throw new Exception("Mono Runtime do not support system proxy settings.");
}
ValidateEndPointAsSystemProxy(endPoint);
bool isHttp = (protocolType & ProxyProtocolType.Http) > 0;
bool isHttps = (protocolType & ProxyProtocolType.Https) > 0;
if (isHttps)
{
if (!endPoint.EnableSsl)
{
throw new Exception("Endpoint do not support Https connections");
}
EnsureRootCertificate();
//If certificate was trusted by the machine
if (!CertificateManager.CertValidated)
{
protocolType = protocolType & ~ProxyProtocolType.Https;
isHttps = false;
}
}
//clear any settings previously added
if (isHttp)
{
ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpProxy = false);
}
if (isHttps)
{
ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemHttpsProxy = false);
}
systemProxySettingsManager.SetProxy(
Equals(endPoint.IpAddress, IPAddress.Any) |
Equals(endPoint.IpAddress, IPAddress.Loopback)
? "127.0.0.1"
: endPoint.IpAddress.ToString(),
endPoint.Port,
protocolType);
if (isHttp)
{
endPoint.IsSystemHttpsProxy = true;
}
if (isHttps)
{
endPoint.IsSystemHttpsProxy = true;
}
firefoxProxySettingsManager.UseSystemProxy();
string proxyType = null;
switch (protocolType)
{
case ProxyProtocolType.Http:
proxyType = "HTTP";
break;
case ProxyProtocolType.Https:
proxyType = "HTTPS";
break;
case ProxyProtocolType.AllHttp:
proxyType = "HTTP and HTTPS";
break;
}
if (protocolType != ProxyProtocolType.None)
{
Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System {2} Proxy", endPoint.IpAddress, endPoint.Port, proxyType);
}
}
/// <summary>
/// Remove any HTTP proxy setting of current machien
/// </summary>
public void DisableSystemHttpProxy()
{
DisableSystemProxy(ProxyProtocolType.Http);
}
/// <summary>
/// Remove any HTTPS proxy setting for current machine
/// </summary>
public void DisableSystemHttpsProxy()
{
DisableSystemProxy(ProxyProtocolType.Https);
}
/// <summary>
/// Remove the specified proxy settings for current machine
/// </summary>
public void DisableSystemProxy(ProxyProtocolType protocolType)
{
if (RunTime.IsRunningOnMono)
{
throw new Exception("Mono Runtime do not support system proxy settings.");
}
systemProxySettingsManager.RemoveProxy(protocolType);
}
/// <summary>
/// Clear all proxy settings for current machine
/// </summary>
public void DisableAllSystemProxies()
{
if (RunTime.IsRunningOnMono)
{
throw new Exception("Mono Runtime do not support system proxy settings.");
}
systemProxySettingsManager.DisableAllProxy();
}
#endif
/// <summary>
/// Start this proxy server
/// </summary>
public void Start()
{
if (proxyRunning)
{
throw new Exception("Proxy is already running.");
}
#if NET45
//clear any system proxy settings which is pointing to our own endpoint (causing a cycle)
//due to non gracious proxy shutdown before or something else
if (systemProxySettingsManager != null)
{
var proxyInfo = systemProxySettingsManager.GetProxyInfoFromRegistry();
if (proxyInfo.Proxies != null)
{
var protocolToRemove = ProxyProtocolType.None;
foreach (var proxy in proxyInfo.Proxies.Values)
{
if ((proxy.HostName == "127.0.0.1"
|| proxy.HostName.Equals("localhost", StringComparison.OrdinalIgnoreCase))
&& 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
systemProxySettingsManager.RemoveProxy(protocolToRemove, false);
}
}
}
if (ForwardToUpstreamGateway
&& GetCustomUpStreamHttpProxyFunc == null && GetCustomUpStreamHttpsProxyFunc == null
&& systemProxySettingsManager != null)
{
// Use WinHttp to handle PAC/WAPD scripts.
systemProxyResolver = new WinHttpWebProxyFinder();
systemProxyResolver.LoadFromIE();
GetCustomUpStreamHttpProxyFunc = GetSystemUpStreamProxy;
GetCustomUpStreamHttpsProxyFunc = GetSystemUpStreamProxy;
}
#endif
foreach (var endPoint in ProxyEndPoints)
{
Listen(endPoint);
}
CertificateManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes);
#if NET45
if (!RunTime.IsRunningOnMono)
{
//clear orphaned windows auth states every 2 minutes
WinAuthEndPoint.ClearIdleStates(2);
}
#endif
proxyRunning = true;
}
/// <summary>
/// Stop this proxy server
/// </summary>
public void Stop()
{
if (!proxyRunning)
{
throw new Exception("Proxy is not running.");
}
#if NET45
if (!RunTime.IsRunningOnMono)
{
bool setAsSystemProxy = ProxyEndPoints.OfType<ExplicitProxyEndPoint>().Any(x => x.IsSystemHttpProxy || x.IsSystemHttpsProxy);
if (setAsSystemProxy)
{
systemProxySettingsManager.RestoreOriginalSettings();
}
}
#endif
foreach (var endPoint in ProxyEndPoints)
{
QuitListen(endPoint);
}
ProxyEndPoints.Clear();
CertificateManager?.StopClearIdleCertificates();
proxyRunning = false;
}
/// <summary>
/// Handle dispose of a client/server session
/// </summary>
/// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param>
/// <param name="serverConnection"></param>
private void Dispose(CustomBufferedStream clientStream, CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, TcpConnection serverConnection)
{
clientStream?.Dispose();
clientStreamReader?.Dispose();
clientStreamWriter?.Dispose();
if (serverConnection != null)
{
serverConnection.Dispose();
serverConnection = null;
UpdateServerConnectionCount(false);
}
}
/// <summary>
/// Dispose Proxy.
/// </summary>
public void Dispose()
{
if (proxyRunning)
{
Stop();
}
CertificateManager?.Dispose();
}
#if NET45
/// <summary>
/// Listen on the given end point on local machine
/// </summary>
/// <param name="endPoint"></param>
private void Listen(ProxyEndPoint endPoint)
{
endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
endPoint.Listener.Start();
endPoint.Port = ((IPEndPoint)endPoint.Listener.LocalEndpoint).Port;
// accept clients asynchronously
endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
}
#else
private async void Listen(ProxyEndPoint endPoint)
{
endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
endPoint.Listener.Start();
endPoint.Port = ((IPEndPoint)endPoint.Listener.LocalEndpoint).Port;
while (true)
{
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
/// </summary>
/// <param name="endPoint"></param>
private void ValidateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint)
{
if (endPoint == null)
throw new ArgumentNullException(nameof(endPoint));
if (ProxyEndPoints.Contains(endPoint) == false)
{
throw new Exception("Cannot set endPoints not added to proxy as system proxy");
}
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>
/// <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)
{
var proxy = systemProxyResolver.GetProxy(sessionEventArgs.WebSession.Request.RequestUri);
return Task.FromResult(proxy);
}
#endif
private void EnsureRootCertificate()
{
if (!CertificateManager.CertValidated)
{
CertificateManager.CreateTrustedRootCertificate();
if (TrustRootCertificate)
{
CertificateManager.TrustRootCertificate();
}
}
}
#if NET45
/// <summary>
/// When a connection is received from client act
/// </summary>
/// <param name="asyn"></param>
private void OnAcceptConnection(IAsyncResult asyn)
{
var endPoint = (ProxyEndPoint)asyn.AsyncState;
TcpClient tcpClient = null;
try
{
//based on end point type call appropriate request handlers
tcpClient = endPoint.Listener.EndAcceptTcpClient(asyn);
}
catch (ObjectDisposedException)
{
// The listener was Stop()'d, disposing the underlying socket and
// triggering the completion of the callback. We're already exiting,
// so just return.
return;
}
catch
{
//Other errors are discarded to keep proxy running
}
if (tcpClient != null)
{
Task.Run(async () =>
{
await HandleClient(tcpClient, endPoint);
});
}
// Get the listener that handles the client request.
endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
}
#endif
private async Task HandleClient(TcpClient tcpClient, ProxyEndPoint endPoint)
{
UpdateClientConnectionCount(true);
tcpClient.ReceiveTimeout = ConnectionTimeOutSeconds * 1000;
tcpClient.SendTimeout = ConnectionTimeOutSeconds * 1000;
try
{
if (endPoint.GetType() == typeof(TransparentProxyEndPoint))
{
await HandleClient(endPoint as TransparentProxyEndPoint, tcpClient);
}
else
{
await HandleClient(endPoint as ExplicitProxyEndPoint, tcpClient);
}
}
finally
{
UpdateClientConnectionCount(false);
try
{
if (tcpClient != null)
{
//This line is important!
//contributors please don't remove it without discussion
//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);
tcpClient.Dispose();
}
}
catch
{
}
}
}
/// <summary>
/// Quit listening on the given end point
/// </summary>
/// <param name="endPoint"></param>
private void QuitListen(ProxyEndPoint endPoint)
{
endPoint.Listener.Stop();
endPoint.Listener.Server.Dispose();
}
internal void UpdateClientConnectionCount(bool increment)
{
if (increment)
{
Interlocked.Increment(ref clientConnectionCount);
}
else
{
Interlocked.Decrement(ref clientConnectionCount);
}
ClientConnectionCountChanged?.Invoke(this, EventArgs.Empty);
}
internal void UpdateServerConnectionCount(bool increment)
{
if (increment)
{
Interlocked.Increment(ref serverConnectionCount);
}
else
{
Interlocked.Decrement(ref serverConnectionCount);
}
ServerConnectionCountChanged?.Invoke(this, EventArgs.Empty);
}
}
}
......@@ -37,7 +37,7 @@ namespace Titanium.Web.Proxy
var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
var clientStreamWriter = new HttpResponseWriter(clientStream);
var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
Uri httpRemoteUri;
......@@ -86,7 +86,7 @@ namespace Titanium.Web.Proxy
await HeaderParser.ReadHeaders(clientStreamReader, connectRequest.RequestHeaders);
var connectArgs = new TunnelConnectSessionEventArgs(endPoint);
var connectArgs = new TunnelConnectSessionEventArgs(BufferSize, endPoint);
connectArgs.WebSession.Request = connectRequest;
connectArgs.ProxyClient.TcpClient = tcpClient;
connectArgs.ProxyClient.ClientStream = clientStream;
......@@ -147,7 +147,7 @@ namespace Titanium.Web.Proxy
clientStreamReader.Dispose();
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new HttpResponseWriter(clientStream);
clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
}
catch
{
......@@ -164,24 +164,30 @@ namespace Titanium.Web.Proxy
//create new connection
using (var connection = await GetServerConnection(connectArgs, true))
{
if (isClientHello)
try
{
if (clientStream.Available > 0)
if (isClientHello)
{
//send the buffered data
var data = new byte[clientStream.Available];
await clientStream.ReadAsync(data, 0, data.Length);
await connection.Stream.WriteAsync(data, 0, data.Length);
await connection.Stream.FlushAsync();
if (clientStream.Available > 0)
{
//send the buffered data
var data = new byte[clientStream.Available];
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);
((ConnectResponse)connectArgs.WebSession.Response).ServerHelloInfo = serverHelloInfo;
await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize,
(buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); });
}
finally
{
UpdateServerConnectionCount(false);
}
await TcpHelper.SendRaw(clientStream, connection.Stream,
(buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); });
UpdateServerConnectionCount(false);
}
return;
......@@ -243,7 +249,7 @@ namespace Titanium.Web.Proxy
}
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new HttpResponseWriter(clientStream);
clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
//now read the request line
string httpCmd = await clientStreamReader.ReadLineAsync();
......@@ -354,12 +360,15 @@ namespace Titanium.Web.Proxy
break;
}
//create a new connection if hostname changes
if (connection != null && !connection.HostName.Equals(args.WebSession.Request.RequestUri.Host, StringComparison.OrdinalIgnoreCase))
//create a new connection if hostname/upstream end point changes
if (connection != null
&& (!connection.HostName.Equals(args.WebSession.Request.RequestUri.Host, StringComparison.OrdinalIgnoreCase)
|| (args.WebSession.UpStreamEndPoint != null
&& !args.WebSession.UpStreamEndPoint.Equals(connection.UpStreamEndPoint))))
{
connection.Dispose();
UpdateServerConnectionCount(false);
connection = null;
UpdateServerConnectionCount(false);
}
if (connection == null)
......@@ -374,7 +383,7 @@ namespace Titanium.Web.Proxy
var requestHeaders = args.WebSession.Request.RequestHeaders;
byte[] requestBytes;
using (var ms = new MemoryStream())
using (var writer = new HttpRequestWriter(ms))
using (var writer = new HttpRequestWriter(ms, BufferSize))
{
writer.WriteLine(httpCmd);
writer.WriteHeaders(requestHeaders);
......@@ -402,7 +411,7 @@ namespace Titanium.Web.Proxy
await BeforeResponse.InvokeParallelAsync(this, args, ExceptionFunc);
}
await TcpHelper.SendRaw(clientStream, connection.Stream,
await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize,
(buffer, offset, count) => { args.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); });
args.Dispose();
......@@ -599,6 +608,7 @@ namespace Titanium.Web.Proxy
args.WebSession.Request.RequestUri.Port,
args.WebSession.Request.HttpVersion,
args.IsHttps, isConnect,
args.WebSession.UpStreamEndPoint ?? UpStreamEndPoint,
customUpStreamHttpProxy ?? UpStreamHttpProxy,
customUpStreamHttpsProxy ?? UpStreamHttpsProxy);
}
......
......@@ -3,6 +3,10 @@
<PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework>
<RootNamespace>Titanium.Web.Proxy</RootNamespace>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>StrongNameKey.snk</AssemblyOriginatorKeyFile>
<DelaySign>False</DelaySign>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
......@@ -10,7 +14,6 @@
</PropertyGroup>
<ItemGroup>
<Compile Remove="Properties\AssemblyInfo.cs" />
<Compile Remove="Helpers\WinHttp\NativeMethods.WinHttp.cs" />
<Compile Remove="Helpers\WinHttp\WinHttpHandle.cs" />
<Compile Remove="Helpers\WinHttp\WinHttpWebProxyFinder.cs" />
......
......@@ -84,7 +84,7 @@
<Compile Include="Exceptions\ProxyException.cs" />
<Compile Include="Exceptions\ProxyHttpException.cs" />
<Compile Include="Extensions\ByteArrayExtensions.cs" />
<Compile Include="Extensions\DotNetStandardExtensions.cs" />
<Compile Include="Extensions\DotNet45Extensions.cs" />
<Compile Include="Extensions\FuncExtensions.cs" />
<Compile Include="Extensions\HttpWebRequestExtensions.cs" />
<Compile Include="Extensions\HttpWebResponseExtensions.cs" />
......
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