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 ...@@ -98,13 +98,15 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary> /// <summary>
/// Constructor to initialize the proxy /// Constructor to initialize the proxy
/// </summary> /// </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.bufferSize = bufferSize;
this.httpResponseHandler = httpResponseHandler; this.httpResponseHandler = httpResponseHandler;
ProxyClient = new ProxyClient(); ProxyClient = new ProxyClient();
WebSession = new HttpWebClient(); WebSession = new HttpWebClient(bufferSize);
WebSession.ProcessId = new Lazy<int>(() => WebSession.ProcessId = new Lazy<int>(() =>
{ {
......
using Titanium.Web.Proxy.Models; using System.Net;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
...@@ -6,7 +7,8 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -6,7 +7,8 @@ namespace Titanium.Web.Proxy.EventArguments
{ {
public bool IsHttpsConnect { get; set; } 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; ...@@ -3,7 +3,7 @@ using System.Security.Cryptography.X509Certificates;
namespace Titanium.Web.Proxy.Extensions namespace Titanium.Web.Proxy.Extensions
{ {
internal static class DotNetStandardExtensions internal static class DotNet45Extensions
{ {
#if NET45 #if NET45
/// <summary> /// <summary>
......
...@@ -17,9 +17,9 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -17,9 +17,9 @@ namespace Titanium.Web.Proxy.Extensions
/// <param name="input"></param> /// <param name="input"></param>
/// <param name="output"></param> /// <param name="output"></param>
/// <param name="onCopy"></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) while (true)
{ {
int num = await input.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); int num = await input.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
......
...@@ -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>
......
...@@ -4,7 +4,8 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -4,7 +4,8 @@ namespace Titanium.Web.Proxy.Helpers
{ {
sealed class HttpRequestWriter : HttpWriter 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 ...@@ -12,7 +12,8 @@ namespace Titanium.Web.Proxy.Helpers
{ {
sealed class HttpResponseWriter : HttpWriter 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 ...@@ -8,7 +8,8 @@ namespace Titanium.Web.Proxy.Helpers
{ {
abstract class HttpWriter : StreamWriter 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; NewLine = ProxyConstants.NewLine;
} }
......
...@@ -116,12 +116,13 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -116,12 +116,13 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="onDataSend"></param> /// <param name="onDataSend"></param>
/// <param name="onDataReceive"></param> /// <param name="onDataReceive"></param>
/// <returns></returns> /// <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 //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); await Task.WhenAll(sendRelay, receiveRelay);
} }
......
...@@ -13,6 +13,8 @@ namespace Titanium.Web.Proxy.Http ...@@ -13,6 +13,8 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
public class HttpWebClient : IDisposable public class HttpWebClient : IDisposable
{ {
private int bufferSize;
/// <summary> /// <summary>
/// Connection to server /// Connection to server
/// </summary> /// </summary>
...@@ -23,6 +25,11 @@ namespace Titanium.Web.Proxy.Http ...@@ -23,6 +25,11 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
public Guid RequestId { get; } public Guid RequestId { get; }
/// <summary>
/// Override UpStreamEndPoint for this request; Local NIC via request is made
/// </summary>
public IPEndPoint UpStreamEndPoint { get; set; }
/// <summary> /// <summary>
/// Headers passed with Connect. /// Headers passed with Connect.
/// </summary> /// </summary>
...@@ -49,8 +56,10 @@ namespace Titanium.Web.Proxy.Http ...@@ -49,8 +56,10 @@ namespace Titanium.Web.Proxy.Http
/// </summary> /// </summary>
public bool IsHttps => Request.IsHttps; public bool IsHttps => Request.IsHttps;
internal HttpWebClient() internal HttpWebClient(int bufferSize)
{ {
this.bufferSize = bufferSize;
RequestId = Guid.NewGuid(); RequestId = Guid.NewGuid();
Request = new Request(); Request = new Request();
Response = new Response(); Response = new Response();
...@@ -77,7 +86,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -77,7 +86,7 @@ namespace Titanium.Web.Proxy.Http
byte[] requestBytes; byte[] requestBytes;
using (var ms = new MemoryStream()) using (var ms = new MemoryStream())
using (var writer = new HttpRequestWriter(ms)) using (var writer = new HttpRequestWriter(ms, bufferSize))
{ {
var upstreamProxy = ServerConnection.UpStreamHttpProxy; var upstreamProxy = ServerConnection.UpStreamHttpProxy;
......
using StreamExtended.Network; using StreamExtended.Network;
using System; using System;
using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
...@@ -25,6 +26,11 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -25,6 +26,11 @@ namespace Titanium.Web.Proxy.Network.Tcp
internal bool UseUpstreamProxy { get; set; } internal bool UseUpstreamProxy { get; set; }
/// <summary>
/// Local NIC via connection is made
/// </summary>
internal IPEndPoint UpStreamEndPoint { get; set; }
/// <summary> /// <summary>
/// Http version /// Http version
/// </summary> /// </summary>
......
using StreamExtended.Network; using StreamExtended.Network;
using System; using System;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Security; using System.Net.Security;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
...@@ -17,19 +18,21 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -17,19 +18,21 @@ namespace Titanium.Web.Proxy.Network.Tcp
internal class TcpConnectionFactory internal class TcpConnectionFactory
{ {
/// <summary> /// <summary>
/// Creates a TCP connection to server /// Creates a TCP connection to server
/// </summary> /// </summary>
/// <param name="server"></param> /// <param name="server"></param>
/// <param name="remoteHostName"></param> /// <param name="remoteHostName"></param>
/// <param name="remotePort"></param> /// <param name="remotePort"></param>
/// <param name="httpVersion"></param> /// <param name="httpVersion"></param>
/// <param name="isConnect"></param>
/// <param name="isHttps"></param> /// <param name="isHttps"></param>
/// <param name="isConnect"></param>
/// <param name="upStreamEndPoint"></param>
/// <param name="externalHttpProxy"></param> /// <param name="externalHttpProxy"></param>
/// <param name="externalHttpsProxy"></param> /// <param name="externalHttpsProxy"></param>
/// <returns></returns> /// <returns></returns>
internal async Task<TcpConnection> CreateClient(ProxyServer server, string remoteHostName, int remotePort, Version httpVersion, bool isHttps, internal async Task<TcpConnection> CreateClient(ProxyServer server,
bool isConnect, ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy) string remoteHostName, int remotePort, Version httpVersion, bool isHttps,
bool isConnect, IPEndPoint upStreamEndPoint, ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy)
{ {
bool useUpstreamProxy = false; bool useUpstreamProxy = false;
var externalProxy = isHttps ? externalHttpsProxy : externalHttpProxy; var externalProxy = isHttps ? externalHttpsProxy : externalHttpProxy;
...@@ -52,10 +55,10 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -52,10 +55,10 @@ namespace Titanium.Web.Proxy.Network.Tcp
try try
{ {
#if NET45 #if NET45
client = new TcpClient(server.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
...@@ -72,7 +75,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -72,7 +75,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
if (useUpstreamProxy && (isConnect || isHttps)) 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($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}"); await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
...@@ -128,6 +131,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -128,6 +131,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
{ {
UpStreamHttpProxy = externalHttpProxy, UpStreamHttpProxy = externalHttpProxy,
UpStreamHttpsProxy = externalHttpsProxy, UpStreamHttpsProxy = externalHttpsProxy,
UpStreamEndPoint = upStreamEndPoint,
HostName = remoteHostName, HostName = remoteHostName,
Port = remotePort, Port = remotePort,
IsHttps = isHttps, IsHttps = isHttps,
......
...@@ -6,11 +6,11 @@ using System.Runtime.InteropServices; ...@@ -6,11 +6,11 @@ using System.Runtime.InteropServices;
// set of attributes. Change these attribute values to modify the information // set of attributes. Change these attribute values to modify the information
// associated with an assembly. // associated with an assembly.
[assembly: AssemblyTitle("Titanium.Web.Proxy.Properties")] [assembly: AssemblyTitle("Titanium.Web.Proxy")]
[assembly: AssemblyDescription("")] [assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")] [assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Titanium.Web.Proxy.Properties")] [assembly: AssemblyProduct("Titanium.Web.Proxy")]
[assembly: AssemblyCopyright("Copyright © Titanium 2015-2017")] [assembly: AssemblyCopyright("Copyright © Titanium 2015-2017")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
......
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);
}
}
}
...@@ -37,7 +37,7 @@ namespace Titanium.Web.Proxy ...@@ -37,7 +37,7 @@ namespace Titanium.Web.Proxy
var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize); var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
var clientStreamWriter = new HttpResponseWriter(clientStream); var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
Uri httpRemoteUri; Uri httpRemoteUri;
...@@ -86,7 +86,7 @@ namespace Titanium.Web.Proxy ...@@ -86,7 +86,7 @@ namespace Titanium.Web.Proxy
await HeaderParser.ReadHeaders(clientStreamReader, connectRequest.RequestHeaders); await HeaderParser.ReadHeaders(clientStreamReader, connectRequest.RequestHeaders);
var connectArgs = new TunnelConnectSessionEventArgs(endPoint); var connectArgs = new TunnelConnectSessionEventArgs(BufferSize, endPoint);
connectArgs.WebSession.Request = connectRequest; connectArgs.WebSession.Request = connectRequest;
connectArgs.ProxyClient.TcpClient = tcpClient; connectArgs.ProxyClient.TcpClient = tcpClient;
connectArgs.ProxyClient.ClientStream = clientStream; connectArgs.ProxyClient.ClientStream = clientStream;
...@@ -147,7 +147,7 @@ namespace Titanium.Web.Proxy ...@@ -147,7 +147,7 @@ namespace Titanium.Web.Proxy
clientStreamReader.Dispose(); clientStreamReader.Dispose();
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new HttpResponseWriter(clientStream); clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
} }
catch catch
{ {
...@@ -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,
(buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); });
UpdateServerConnectionCount(false);
} }
return; return;
...@@ -243,7 +249,7 @@ namespace Titanium.Web.Proxy ...@@ -243,7 +249,7 @@ namespace Titanium.Web.Proxy
} }
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new HttpResponseWriter(clientStream); clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
//now read the request line //now read the request line
string httpCmd = await clientStreamReader.ReadLineAsync(); string httpCmd = await clientStreamReader.ReadLineAsync();
...@@ -354,12 +360,15 @@ namespace Titanium.Web.Proxy ...@@ -354,12 +360,15 @@ namespace Titanium.Web.Proxy
break; break;
} }
//create a new connection if hostname changes //create a new connection if hostname/upstream end point changes
if (connection != null && !connection.HostName.Equals(args.WebSession.Request.RequestUri.Host, StringComparison.OrdinalIgnoreCase)) 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(); connection.Dispose();
UpdateServerConnectionCount(false);
connection = null; connection = null;
UpdateServerConnectionCount(false);
} }
if (connection == null) if (connection == null)
...@@ -374,7 +383,7 @@ namespace Titanium.Web.Proxy ...@@ -374,7 +383,7 @@ namespace Titanium.Web.Proxy
var requestHeaders = args.WebSession.Request.RequestHeaders; var requestHeaders = args.WebSession.Request.RequestHeaders;
byte[] requestBytes; byte[] requestBytes;
using (var ms = new MemoryStream()) using (var ms = new MemoryStream())
using (var writer = new HttpRequestWriter(ms)) using (var writer = new HttpRequestWriter(ms, BufferSize))
{ {
writer.WriteLine(httpCmd); writer.WriteLine(httpCmd);
writer.WriteHeaders(requestHeaders); writer.WriteHeaders(requestHeaders);
...@@ -402,7 +411,7 @@ namespace Titanium.Web.Proxy ...@@ -402,7 +411,7 @@ namespace Titanium.Web.Proxy
await BeforeResponse.InvokeParallelAsync(this, args, ExceptionFunc); 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); }); (buffer, offset, count) => { args.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); });
args.Dispose(); args.Dispose();
...@@ -599,6 +608,7 @@ namespace Titanium.Web.Proxy ...@@ -599,6 +608,7 @@ namespace Titanium.Web.Proxy
args.WebSession.Request.RequestUri.Port, args.WebSession.Request.RequestUri.Port,
args.WebSession.Request.HttpVersion, args.WebSession.Request.HttpVersion,
args.IsHttps, isConnect, args.IsHttps, isConnect,
args.WebSession.UpStreamEndPoint ?? UpStreamEndPoint,
customUpStreamHttpProxy ?? UpStreamHttpProxy, customUpStreamHttpProxy ?? UpStreamHttpProxy,
customUpStreamHttpsProxy ?? UpStreamHttpsProxy); customUpStreamHttpsProxy ?? UpStreamHttpsProxy);
} }
......
...@@ -3,6 +3,10 @@ ...@@ -3,6 +3,10 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard1.6</TargetFramework> <TargetFramework>netstandard1.6</TargetFramework>
<RootNamespace>Titanium.Web.Proxy</RootNamespace> <RootNamespace>Titanium.Web.Proxy</RootNamespace>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>StrongNameKey.snk</AssemblyOriginatorKeyFile>
<DelaySign>False</DelaySign>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
...@@ -10,7 +14,6 @@ ...@@ -10,7 +14,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Remove="Properties\AssemblyInfo.cs" />
<Compile Remove="Helpers\WinHttp\NativeMethods.WinHttp.cs" /> <Compile Remove="Helpers\WinHttp\NativeMethods.WinHttp.cs" />
<Compile Remove="Helpers\WinHttp\WinHttpHandle.cs" /> <Compile Remove="Helpers\WinHttp\WinHttpHandle.cs" />
<Compile Remove="Helpers\WinHttp\WinHttpWebProxyFinder.cs" /> <Compile Remove="Helpers\WinHttp\WinHttpWebProxyFinder.cs" />
......
...@@ -84,7 +84,7 @@ ...@@ -84,7 +84,7 @@
<Compile Include="Exceptions\ProxyException.cs" /> <Compile Include="Exceptions\ProxyException.cs" />
<Compile Include="Exceptions\ProxyHttpException.cs" /> <Compile Include="Exceptions\ProxyHttpException.cs" />
<Compile Include="Extensions\ByteArrayExtensions.cs" /> <Compile Include="Extensions\ByteArrayExtensions.cs" />
<Compile Include="Extensions\DotNetStandardExtensions.cs" /> <Compile Include="Extensions\DotNet45Extensions.cs" />
<Compile Include="Extensions\FuncExtensions.cs" /> <Compile Include="Extensions\FuncExtensions.cs" />
<Compile Include="Extensions\HttpWebRequestExtensions.cs" /> <Compile Include="Extensions\HttpWebRequestExtensions.cs" />
<Compile Include="Extensions\HttpWebResponseExtensions.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