Commit 69aa70c1 authored by Jehonathan Thomas's avatar Jehonathan Thomas Committed by GitHub

Merge pull request #230 from justcoding121/develop

Beta 
parents e8e41ca3 fe81398b
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
......@@ -17,6 +18,12 @@ namespace Titanium.Web.Proxy.Examples.Basic
public ProxyTestController()
{
proxyServer = new ProxyServer();
//generate root certificate without storing it in file system
//proxyServer.CertificateEngine = Network.CertificateEngine.BouncyCastle;
//proxyServer.CertificateManager.CreateTrustedRootCertificate(false);
//proxyServer.CertificateManager.TrustRootCertificate();
proxyServer.ExceptionFunc = exception => Console.WriteLine(exception.Message);
proxyServer.TrustRootCertificate = true;
......@@ -40,7 +47,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
//Exclude Https addresses you don't want to proxy
//Useful for clients that use certificate pinning
//for example google.com and dropbox.com
// ExcludedHttpsHostNameRegex = new List<string>() { "google.com", "dropbox.com" }
ExcludedHttpsHostNameRegex = new List<string>() { "dropbox.com" }
//Include Https addresses you want to proxy (others will be excluded)
//for example github.com
......@@ -92,18 +99,22 @@ namespace Titanium.Web.Proxy.Examples.Basic
proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection;
proxyServer.Stop();
//remove the generated certificates
//proxyServer.CertificateManager.RemoveTrustedRootCertificates();
}
//intecept & cancel redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine("Active Client Connections:" + ((ProxyServer) sender).ClientConnectionCount);
Console.WriteLine(e.WebSession.Request.Url);
//read request headers
var requestHeaders = e.WebSession.Request.RequestHeaders;
var method = e.WebSession.Request.Method.ToUpper();
if ((method == "POST" || method == "PUT" || method == "PATCH"))
if (method == "POST" || method == "PUT" || method == "PATCH")
{
//Get/Set request body bytes
byte[] bodyBytes = await e.GetRequestBody();
......@@ -116,30 +127,32 @@ namespace Titanium.Web.Proxy.Examples.Basic
requestBodyHistory[e.Id] = bodyString;
}
//To cancel a request with a custom HTML content
//Filter URL
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
{
await e.Ok("<!DOCTYPE html>" +
"<html><body><h1>" +
"Website Blocked" +
"</h1>" +
"<p>Blocked by titanium web proxy.</p>" +
"</body>" +
"</html>");
}
////To cancel a request with a custom HTML content
////Filter URL
//if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
//{
// await e.Ok("<!DOCTYPE html>" +
// "<html><body><h1>" +
// "Website Blocked" +
// "</h1>" +
// "<p>Blocked by titanium web proxy.</p>" +
// "</body>" +
// "</html>");
//}
//Redirect example
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
{
await e.Redirect("https://www.paypal.com");
}
////Redirect example
//if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
//{
// await e.Redirect("https://www.paypal.com");
//}
}
//Modify response
public async Task OnResponse(object sender, SessionEventArgs e)
{
if(requestBodyHistory.ContainsKey(e.Id))
Console.WriteLine("Active Server Connections:" + (sender as ProxyServer).ServerConnectionCount);
if (requestBodyHistory.ContainsKey(e.Id))
{
//access request body by looking up the shared dictionary using requestId
var requestBody = requestBodyHistory[e.Id];
......@@ -149,14 +162,14 @@ namespace Titanium.Web.Proxy.Examples.Basic
var responseHeaders = e.WebSession.Response.ResponseHeaders;
// print out process id of current session
Console.WriteLine($"PID: {e.WebSession.ProcessId.Value}");
//Console.WriteLine($"PID: {e.WebSession.ProcessId.Value}");
//if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST")
{
if (e.WebSession.Response.ResponseStatusCode == "200")
{
if (e.WebSession.Response.ContentType!=null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html"))
if (e.WebSession.Response.ContentType != null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html"))
{
byte[] bodyBytes = await e.GetResponseBody();
await e.SetResponseBody(bodyBytes);
......
......@@ -16,7 +16,7 @@ namespace Titanium.Web.Proxy
/// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param>
/// <returns></returns>
private bool ValidateServerCertificate(
internal bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
......@@ -65,7 +65,7 @@ namespace Titanium.Web.Proxy
/// <param name="remoteCertificate"></param>
/// <param name="acceptableIssuers"></param>
/// <returns></returns>
private X509Certificate SelectClientCertificate(
internal X509Certificate SelectClientCertificate(
object sender,
string targetHost,
X509CertificateCollection localCertificates,
......
......@@ -93,7 +93,7 @@ namespace Titanium.Web.Proxy.EventArguments
{
//GET request don't have a request body to read
var method = WebSession.Request.Method.ToUpper();
if ((method != "POST" && method != "PUT" && method != "PATCH"))
if (method != "POST" && method != "PUT" && method != "PATCH")
{
throw new BodyNotFoundException("Request don't have a body. " +
"Please verify that this request is a Http POST/PUT/PATCH and request " +
......@@ -411,6 +411,7 @@ namespace Titanium.Web.Proxy.EventArguments
{
response.ResponseHeaders = headers;
}
response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseBody = result;
......
using System.Net.Sockets;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Extensions
{
......@@ -32,5 +33,25 @@ namespace Titanium.Web.Proxy.Extensions
client.Blocking = blockingState;
}
}
/// <summary>
/// Gets the local port from a native TCP row object.
/// </summary>
/// <param name="tcpRow">The TCP row.</param>
/// <returns>The local port</returns>
internal static int GetLocalPort(this NativeMethods.TcpRow tcpRow)
{
return (tcpRow.localPort1 << 8) + tcpRow.localPort2 + (tcpRow.localPort3 << 24) + (tcpRow.localPort4 << 16);
}
/// <summary>
/// Gets the remote port from a native TCP row object.
/// </summary>
/// <param name="tcpRow">The TCP row.</param>
/// <returns>The remote port</returns>
internal static int GetRemotePort(this NativeMethods.TcpRow tcpRow)
{
return (tcpRow.remotePort1 << 8) + tcpRow.remotePort2 + (tcpRow.remotePort3 << 24) + (tcpRow.remotePort4 << 16);
}
}
}
......@@ -15,27 +15,14 @@ namespace Titanium.Web.Proxy.Helpers
{
private readonly CustomBufferedStream stream;
private readonly int bufferSize;
private readonly byte[] staticBuffer;
private readonly Encoding encoding;
[ThreadStatic]
private static byte[] staticBufferField;
private byte[] staticBuffer
{
get
{
if (staticBufferField == null || staticBufferField.Length != bufferSize)
{
staticBufferField = new byte[bufferSize];
}
return staticBufferField;
}
}
internal CustomBinaryReader(CustomBufferedStream stream, int bufferSize)
{
this.stream = stream;
staticBuffer = new byte[bufferSize];
this.bufferSize = bufferSize;
//default to UTF-8
......@@ -122,10 +109,13 @@ namespace Titanium.Web.Proxy.Helpers
{
int bytesToRead = bufferSize;
var buffer = staticBuffer;
if (totalBytesToRead < bufferSize)
{
bytesToRead = (int) totalBytesToRead;
buffer = new byte[bytesToRead];
}
var buffer = staticBuffer;
int bytesRead;
var totalBytesRead = 0;
......
......@@ -168,6 +168,7 @@ namespace Titanium.Web.Proxy.Helpers
if (bufferLength > 0)
{
await destination.WriteAsync(streamBuffer, bufferPos, bufferLength, cancellationToken);
bufferLength = 0;
}
await baseStream.CopyToAsync(destination, bufferSize, cancellationToken);
......@@ -307,6 +308,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <returns>
/// A task that represents the asynchronous write operation.
/// </returns>
[DebuggerStepThrough]
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return baseStream.WriteAsync(buffer, offset, count, cancellationToken);
......
......@@ -8,8 +8,7 @@ namespace Titanium.Web.Proxy.Helpers
{
private static int FindProcessIdFromLocalPort(int port, IpVersion ipVersion)
{
var tcpRow = TcpHelper.GetExtendedTcpTable(ipVersion).FirstOrDefault(
row => row.LocalEndPoint.Port == port);
var tcpRow = TcpHelper.GetTcpRowByLocalPort(ipVersion, port);
return tcpRow?.ProcessId ?? 0;
}
......
......@@ -93,21 +93,21 @@ namespace Titanium.Web.Proxy.Helpers
var ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6;
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, (int) NativeMethods.TcpTableType.OwnerPidAll, 0) != 0)
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) != 0)
{
try
{
tcpTable = Marshal.AllocHGlobal(tcpTableLength);
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, ipVersionValue, (int) NativeMethods.TcpTableType.OwnerPidAll, 0) == 0)
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) == 0)
{
NativeMethods.TcpTable table = (NativeMethods.TcpTable) Marshal.PtrToStructure(tcpTable, typeof(NativeMethods.TcpTable));
NativeMethods.TcpTable table = (NativeMethods.TcpTable)Marshal.PtrToStructure(tcpTable, typeof(NativeMethods.TcpTable));
IntPtr rowPtr = (IntPtr) ((long) tcpTable + Marshal.SizeOf(table.length));
IntPtr rowPtr = (IntPtr)((long)tcpTable + Marshal.SizeOf(table.length));
for (int i = 0; i < table.length; ++i)
{
tcpRows.Add(new TcpRow((NativeMethods.TcpRow) Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow))));
rowPtr = (IntPtr) ((long) rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow)));
tcpRows.Add(new TcpRow((NativeMethods.TcpRow)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow))));
rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow)));
}
}
}
......@@ -123,30 +123,71 @@ namespace Titanium.Web.Proxy.Helpers
return new TcpTable(tcpRows);
}
/// <summary>
/// Gets the TCP row by local port number.
/// </summary>
/// <returns><see cref="TcpRow"/>.</returns>
internal static TcpRow GetTcpRowByLocalPort(IpVersion ipVersion, int localPort)
{
IntPtr tcpTable = IntPtr.Zero;
int tcpTableLength = 0;
var ipVersionValue = ipVersion == IpVersion.Ipv4 ? NativeMethods.AfInet : NativeMethods.AfInet6;
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, false, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) != 0)
{
try
{
tcpTable = Marshal.AllocHGlobal(tcpTableLength);
if (NativeMethods.GetExtendedTcpTable(tcpTable, ref tcpTableLength, true, ipVersionValue, (int)NativeMethods.TcpTableType.OwnerPidAll, 0) == 0)
{
NativeMethods.TcpTable table = (NativeMethods.TcpTable)Marshal.PtrToStructure(tcpTable, typeof(NativeMethods.TcpTable));
IntPtr rowPtr = (IntPtr)((long)tcpTable + Marshal.SizeOf(table.length));
for (int i = 0; i < table.length; ++i)
{
var tcpRow = (NativeMethods.TcpRow)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.TcpRow));
if (tcpRow.GetLocalPort() == localPort)
{
return new TcpRow(tcpRow);
}
rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(typeof(NativeMethods.TcpRow)));
}
}
}
finally
{
if (tcpTable != IntPtr.Zero)
{
Marshal.FreeHGlobal(tcpTable);
}
}
}
return null;
}
/// <summary>
/// relays the input clientStream to the server at the specified host name and port with the given httpCmd and headers as prefix
/// Usefull for websocket requests
/// </summary>
/// <param name="bufferSize"></param>
/// <param name="connectionTimeOutSeconds"></param>
/// <param name="server"></param>
/// <param name="remoteHostName"></param>
/// <param name="remotePort"></param>
/// <param name="httpCmd"></param>
/// <param name="httpVersion"></param>
/// <param name="requestHeaders"></param>
/// <param name="isHttps"></param>
/// <param name="remotePort"></param>
/// <param name="supportedProtocols"></param>
/// <param name="remoteCertificateValidationCallback"></param>
/// <param name="localCertificateSelectionCallback"></param>
/// <param name="clientStream"></param>
/// <param name="tcpConnectionFactory"></param>
/// <param name="upStreamEndPoint"></param>
/// <returns></returns>
internal static async Task SendRaw(int bufferSize, int connectionTimeOutSeconds,
string remoteHostName, int remotePort, string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders,
bool isHttps, SslProtocols supportedProtocols,
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
Stream clientStream, TcpConnectionFactory tcpConnectionFactory, IPEndPoint upStreamEndPoint)
internal static async Task SendRaw(ProxyServer server,
string remoteHostName, int remotePort,
string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders,
bool isHttps,
Stream clientStream, TcpConnectionFactory tcpConnectionFactory)
{
//prepare the prefix content
StringBuilder sb = null;
......@@ -172,11 +213,10 @@ namespace Titanium.Web.Proxy.Helpers
sb.Append(ProxyConstants.NewLine);
}
var tcpConnection = await tcpConnectionFactory.CreateClient(bufferSize, connectionTimeOutSeconds,
var tcpConnection = await tcpConnectionFactory.CreateClient(server,
remoteHostName, remotePort,
httpVersion, isHttps,
supportedProtocols, remoteCertificateValidationCallback, localCertificateSelectionCallback,
null, null, clientStream, upStreamEndPoint);
null, null, clientStream);
try
{
......@@ -192,6 +232,7 @@ namespace Titanium.Web.Proxy.Helpers
finally
{
tcpConnection.Dispose();
server.ServerConnectionCount--;
}
}
}
......
using System.Collections.Generic;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Http
{
internal static class HeaderParser
{
internal static async Task ReadHeaders(CustomBinaryReader reader,
Dictionary<string, List<HttpHeader>> nonUniqueResponseHeaders,
Dictionary<string, HttpHeader> headers)
{
string tmpLine;
while (!string.IsNullOrEmpty(tmpLine = await reader.ReadLineAsync()))
{
var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);
var newHeader = new HttpHeader(header[0], header[1]);
//if header exist in non-unique header collection add it there
if (nonUniqueResponseHeaders.ContainsKey(newHeader.Name))
{
nonUniqueResponseHeaders[newHeader.Name].Add(newHeader);
}
//if header is alread in unique header collection then move both to non-unique collection
else if (headers.ContainsKey(newHeader.Name))
{
var existing = headers[newHeader.Name];
var nonUniqueHeaders = new List<HttpHeader> { existing, newHeader };
nonUniqueResponseHeaders.Add(newHeader.Name, nonUniqueHeaders);
headers.Remove(newHeader.Name);
}
//add to unique header collection
else
{
headers.Add(newHeader.Name, newHeader);
}
}
}
}
}
......@@ -170,10 +170,10 @@ namespace Titanium.Web.Proxy.Http
var httpVersion = httpResult[0].Trim().ToLower();
var version = new Version(1, 1);
if (0 == string.CompareOrdinal(httpVersion, "http/1.0"))
var version = HttpHeader.Version11;
if (string.Equals(httpVersion, "HTTP/1.0", StringComparison.OrdinalIgnoreCase))
{
version = new Version(1, 0);
version = HttpHeader.Version10;
}
Response.HttpVersion = version;
......@@ -192,7 +192,8 @@ namespace Titanium.Web.Proxy.Http
await ReceiveResponse();
return;
}
else if (Response.ResponseStatusCode.Equals("417")
if (Response.ResponseStatusCode.Equals("417")
&& Response.ResponseStatusDescription.Equals("expectation failed", StringComparison.CurrentCultureIgnoreCase))
{
//read next line after expectation failed response
......@@ -204,36 +205,8 @@ namespace Titanium.Web.Proxy.Http
return;
}
//Read the Response headers
//Read the response headers in to unique and non-unique header collections
string tmpLine;
while (!string.IsNullOrEmpty(tmpLine = await ServerConnection.StreamReader.ReadLineAsync()))
{
var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);
var newHeader = new HttpHeader(header[0], header[1]);
//if header exist in non-unique header collection add it there
if (Response.NonUniqueResponseHeaders.ContainsKey(newHeader.Name))
{
Response.NonUniqueResponseHeaders[newHeader.Name].Add(newHeader);
}
//if header is alread in unique header collection then move both to non-unique collection
else if (Response.ResponseHeaders.ContainsKey(newHeader.Name))
{
var existing = Response.ResponseHeaders[newHeader.Name];
var nonUniqueHeaders = new List<HttpHeader> {existing, newHeader};
Response.NonUniqueResponseHeaders.Add(newHeader.Name, nonUniqueHeaders);
Response.ResponseHeaders.Remove(newHeader.Name);
}
//add to unique header collection
else
{
Response.ResponseHeaders.Add(newHeader.Name, newHeader);
}
}
await HeaderParser.ReadHeaders(ServerConnection.StreamReader, Response.NonUniqueResponseHeaders, Response.ResponseHeaders);
}
}
}
......@@ -242,8 +242,8 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
internal string RequestBodyString { get; set; }
internal bool RequestBodyRead { get; set; }
internal bool RequestLocked { get; set; }
/// <summary>
......
......@@ -9,6 +9,10 @@ namespace Titanium.Web.Proxy.Models
/// </summary>
public class HttpHeader
{
internal static Version Version10 = new Version(1, 0);
internal static Version Version11 = new Version(1, 1);
/// <summary>
/// Constructor.
/// </summary>
......
......@@ -29,9 +29,9 @@ namespace Titanium.Web.Proxy.Network
/// <summary>
/// A class to manage SSL certificates used by this proxy server
/// </summary>
internal class CertificateManager : IDisposable
public class CertificateManager : IDisposable
{
public CertificateEngine Engine
internal CertificateEngine Engine
{
get { return engine; }
set
......@@ -164,10 +164,13 @@ namespace Titanium.Web.Proxy.Network
/// <summary>
/// Attempts to create a RootCertificate
/// </summary>
/// <returns>true if succeeded, else false</returns>
internal bool CreateTrustedRootCertificate()
/// <param name="persistToFile">if set to <c>true</c> try to load/save the certificate from rootCert.pfx.</param>
/// <returns>
/// true if succeeded, else false
/// </returns>
public bool CreateTrustedRootCertificate(bool persistToFile = true)
{
if (RootCertificate == null)
if (persistToFile && RootCertificate == null)
{
RootCertificate = LoadRootCertificate();
}
......@@ -186,7 +189,7 @@ namespace Titanium.Web.Proxy.Network
exceptionFunc(e);
}
if (RootCertificate != null)
if (persistToFile && RootCertificate != null)
{
try
{
......@@ -205,7 +208,7 @@ namespace Titanium.Web.Proxy.Network
/// <summary>
/// Trusts the root certificate.
/// </summary>
internal void TrustRootCertificate()
public void TrustRootCertificate()
{
//current user
TrustRootCertificate(StoreLocation.CurrentUser);
......@@ -214,6 +217,18 @@ namespace Titanium.Web.Proxy.Network
TrustRootCertificate(StoreLocation.LocalMachine);
}
/// <summary>
/// Removes the trusted certificates.
/// </summary>
public void RemoveTrustedRootCertificates()
{
//current user
RemoveTrustedRootCertificates(StoreLocation.CurrentUser);
//current system
RemoveTrustedRootCertificates(StoreLocation.LocalMachine);
}
/// <summary>
/// Create an SSL certificate
/// </summary>
......@@ -336,6 +351,46 @@ namespace Titanium.Web.Proxy.Network
}
}
/// <summary>
/// Remove the Root Certificate trust
/// </summary>
/// <param name="storeLocation"></param>
/// <returns></returns>
internal void RemoveTrustedRootCertificates(StoreLocation storeLocation)
{
if (RootCertificate == null)
{
exceptionFunc(
new Exception("Could not set root certificate"
+ " as system proxy since it is null or empty."));
return;
}
X509Store x509RootStore = new X509Store(StoreName.Root, storeLocation);
var x509PersonalStore = new X509Store(StoreName.My, storeLocation);
try
{
x509RootStore.Open(OpenFlags.ReadWrite);
x509PersonalStore.Open(OpenFlags.ReadWrite);
x509RootStore.Remove(RootCertificate);
x509PersonalStore.Remove(RootCertificate);
}
catch (Exception e)
{
exceptionFunc(
new Exception("Failed to make system trust root certificate "
+ $" for {storeLocation} store location. You may need admin rights.", e));
}
finally
{
x509RootStore.Close();
x509PersonalStore.Close();
}
}
public void Dispose()
{
}
......
......@@ -55,10 +55,10 @@ namespace Titanium.Web.Proxy.Network.Tcp
{
Stream?.Close();
Stream?.Dispose();
StreamReader?.Dispose();
TcpClient.LingerState = new LingerOption(true, 0);
TcpClient.Close();
TcpClient?.Close();
}
}
}
......@@ -6,43 +6,35 @@ using System.IO;
using System.Net.Security;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using System.Security.Authentication;
using System.Linq;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Network.Tcp
{
using System.Net;
/// <summary>
/// A class that manages Tcp Connection to server used by this proxy server
/// </summary>
internal class TcpConnectionFactory
{
/// <summary>
/// Creates a TCP connection to server
/// </summary>
/// <param name="bufferSize"></param>
/// <param name="connectionTimeOutSeconds"></param>
/// <param name="server"></param>
/// <param name="remoteHostName"></param>
/// <param name="remotePort"></param>
/// <param name="httpVersion"></param>
/// <param name="isHttps"></param>
/// <param name="remotePort"></param>
/// <param name="supportedSslProtocols"></param>
/// <param name="remoteCertificateValidationCallback"></param>
/// <param name="localCertificateSelectionCallback"></param>
/// <param name="externalHttpProxy"></param>
/// <param name="externalHttpsProxy"></param>
/// <param name="clientStream"></param>
/// <param name="upStreamEndPoint"></param>
/// <returns></returns>
internal async Task<TcpConnection> CreateClient(int bufferSize, int connectionTimeOutSeconds,
internal async Task<TcpConnection> CreateClient(ProxyServer server,
string remoteHostName, int remotePort, Version httpVersion,
bool isHttps, SslProtocols supportedSslProtocols,
RemoteCertificateValidationCallback remoteCertificateValidationCallback, LocalCertificateSelectionCallback localCertificateSelectionCallback,
bool isHttps,
ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy,
Stream clientStream, IPEndPoint upStreamEndPoint)
Stream clientStream)
{
TcpClient client;
CustomBufferedStream stream;
......@@ -59,11 +51,11 @@ namespace Titanium.Web.Proxy.Network.Tcp
//If this proxy uses another external proxy then create a tunnel request for HTTPS connections
if (useHttpsProxy)
{
client = new TcpClient(upStreamEndPoint);
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port);
stream = new CustomBufferedStream(client.GetStream(), bufferSize);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true) {NewLine = ProxyConstants.NewLine})
using (var writer = new StreamWriter(stream, Encoding.ASCII, server.BufferSize, true) {NewLine = ProxyConstants.NewLine})
{
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
......@@ -79,7 +71,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
writer.Close();
}
using (var reader = new CustomBinaryReader(stream, bufferSize))
using (var reader = new CustomBinaryReader(stream, server.BufferSize))
{
var result = await reader.ReadLineAsync();
......@@ -93,19 +85,19 @@ namespace Titanium.Web.Proxy.Network.Tcp
}
else
{
client = new TcpClient(upStreamEndPoint);
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(remoteHostName, remotePort);
stream = new CustomBufferedStream(client.GetStream(), bufferSize);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
try
{
sslStream = new SslStream(stream, true, remoteCertificateValidationCallback,
localCertificateSelectionCallback);
sslStream = new SslStream(stream, true, server.ValidateServerCertificate,
server.SelectClientCertificate);
await sslStream.AuthenticateAsClientAsync(remoteHostName, null, supportedSslProtocols, false);
await sslStream.AuthenticateAsClientAsync(remoteHostName, null, server.SupportedSslProtocols, false);
stream = new CustomBufferedStream(sslStream, bufferSize);
stream = new CustomBufferedStream(sslStream, server.BufferSize);
}
catch
{
......@@ -118,24 +110,24 @@ namespace Titanium.Web.Proxy.Network.Tcp
{
if (useHttpProxy)
{
client = new TcpClient(upStreamEndPoint);
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(externalHttpProxy.HostName, externalHttpProxy.Port);
stream = new CustomBufferedStream(client.GetStream(), bufferSize);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
else
{
client = new TcpClient(upStreamEndPoint);
client = new TcpClient(server.UpStreamEndPoint);
await client.ConnectAsync(remoteHostName, remotePort);
stream = new CustomBufferedStream(client.GetStream(), bufferSize);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
}
client.ReceiveTimeout = connectionTimeOutSeconds * 1000;
client.SendTimeout = connectionTimeOutSeconds * 1000;
client.ReceiveTimeout = server.ConnectionTimeOutSeconds * 1000;
client.SendTimeout = server.ConnectionTimeOutSeconds * 1000;
stream.ReadTimeout = connectionTimeOutSeconds * 1000;
stream.WriteTimeout = connectionTimeOutSeconds * 1000;
client.LingerState = new LingerOption(true, 0);
server.ServerConnectionCount++;
return new TcpConnection
{
......@@ -145,7 +137,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
Port = remotePort,
IsHttps = isHttps,
TcpClient = client,
StreamReader = new CustomBinaryReader(stream, bufferSize),
StreamReader = new CustomBinaryReader(stream, server.BufferSize),
Stream = stream,
Version = httpVersion
};
......
using System.Net;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Network.Tcp
......@@ -17,24 +18,42 @@ namespace Titanium.Web.Proxy.Network.Tcp
{
ProcessId = tcpRow.owningPid;
int localPort = (tcpRow.localPort1 << 8) + (tcpRow.localPort2) + (tcpRow.localPort3 << 24) + (tcpRow.localPort4 << 16);
long localAddress = tcpRow.localAddr;
LocalEndPoint = new IPEndPoint(localAddress, localPort);
LocalPort = tcpRow.GetLocalPort();
LocalAddress = tcpRow.localAddr;
int remotePort = (tcpRow.remotePort1 << 8) + (tcpRow.remotePort2) + (tcpRow.remotePort3 << 24) + (tcpRow.remotePort4 << 16);
long remoteAddress = tcpRow.remoteAddr;
RemoteEndPoint = new IPEndPoint(remoteAddress, remotePort);
RemotePort = tcpRow.GetRemotePort();
RemoteAddress = tcpRow.remoteAddr;
}
/// <summary>
/// Gets the local end point address.
/// </summary>
internal long LocalAddress { get; }
/// <summary>
/// Gets the local end point port.
/// </summary>
internal int LocalPort { get; }
/// <summary>
/// Gets the local end point.
/// </summary>
internal IPEndPoint LocalEndPoint { get; }
internal IPEndPoint LocalEndPoint => new IPEndPoint(LocalAddress, LocalPort);
/// <summary>
/// Gets the remote end point address.
/// </summary>
internal long RemoteAddress { get; }
/// <summary>
/// Gets the remote end point port.
/// </summary>
internal int RemotePort { get; }
/// <summary>
/// Gets the remote end point.
/// </summary>
internal IPEndPoint RemoteEndPoint { get; }
internal IPEndPoint RemoteEndPoint => new IPEndPoint(RemoteAddress, RemotePort);
/// <summary>
/// Gets the process identifier.
......
......@@ -19,78 +19,36 @@ namespace Titanium.Web.Proxy
return true;
}
var httpHeaders = headers as HttpHeader[] ?? headers.ToArray();
var httpHeaders = headers as ICollection<HttpHeader> ?? headers.ToArray();
try
{
if (httpHeaders.All(t => t.Name != "Proxy-Authorization"))
{
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Required", clientStreamWriter);
var response = new Response
{
ResponseHeaders = new Dictionary<string, HttpHeader>
{
{
"Proxy-Authenticate",
new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"")
},
{"Proxy-Connection", new HttpHeader("Proxy-Connection", "close")}
}
};
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Required");
return false;
}
var header = httpHeaders.FirstOrDefault(t => t.Name == "Proxy-Authorization");
if (null == header) throw new NullReferenceException();
if (header == null) throw new NullReferenceException();
var headerValue = header.Value.Trim();
if (!headerValue.StartsWith("basic", StringComparison.CurrentCultureIgnoreCase))
{
//Return not authorized
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Invalid", clientStreamWriter);
var response = new Response
{
ResponseHeaders = new Dictionary<string, HttpHeader>
{
{
"Proxy-Authenticate",
new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"")
},
{"Proxy-Connection", new HttpHeader("Proxy-Connection", "close")}
}
};
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
return false;
}
headerValue = headerValue.Substring(5).Trim();
var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(headerValue));
if (decoded.Contains(":") == false)
{
//Return not authorized
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Invalid", clientStreamWriter);
var response = new Response
{
ResponseHeaders = new Dictionary<string, HttpHeader>
{
{
"Proxy-Authenticate",
new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"")
},
{"Proxy-Connection", new HttpHeader("Proxy-Connection", "close")}
}
};
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
return false;
}
var username = decoded.Substring(0, decoded.IndexOf(':'));
var password = decoded.Substring(decoded.IndexOf(':') + 1);
return await AuthenticateUserFunc(username, password);
......@@ -98,9 +56,16 @@ namespace Titanium.Web.Proxy
catch (Exception e)
{
ExceptionFunc(new ProxyAuthorizationException("Error whilst authorizing request", e, httpHeaders));
//Return not authorized
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Invalid", clientStreamWriter);
await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
return false;
}
}
private async Task SendAuthentication407Response(StreamWriter clientStreamWriter, string description)
{
await WriteResponseStatus(HttpHeader.Version11, "407", description, clientStreamWriter);
var response = new Response
{
ResponseHeaders = new Dictionary<string, HttpHeader>
......@@ -112,8 +77,6 @@ namespace Titanium.Web.Proxy
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
return false;
}
}
}
}
......@@ -24,11 +24,6 @@ namespace Titanium.Web.Proxy
/// </summary>
private bool proxyRunning { get; set; }
/// <summary>
/// Manages certificates used by this proxy
/// </summary>
private CertificateManager certificateManager { get; set; }
/// <summary>
/// An default exception log func
/// </summary>
......@@ -64,13 +59,18 @@ namespace Titanium.Web.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; }
get { return CertificateManager.RootCertificate; }
set { CertificateManager.RootCertificate = value; }
}
/// <summary>
......@@ -79,8 +79,8 @@ namespace Titanium.Web.Proxy
/// </summary>
public string RootCertificateIssuerName
{
get { return certificateManager.Issuer; }
set { certificateManager.RootCertificateName = value; }
get { return CertificateManager.Issuer; }
set { CertificateManager.Issuer = value; }
}
/// <summary>
......@@ -92,8 +92,8 @@ namespace Titanium.Web.Proxy
/// </summary>
public string RootCertificateName
{
get { return certificateManager.RootCertificateName; }
set { certificateManager.Issuer = value; }
get { return CertificateManager.RootCertificateName; }
set { CertificateManager.RootCertificateName = value; }
}
/// <summary>
......@@ -121,8 +121,8 @@ namespace Titanium.Web.Proxy
/// </summary>
public CertificateEngine CertificateEngine
{
get { return certificateManager.Engine; }
set { certificateManager.Engine = value; }
get { return CertificateManager.Engine; }
set { CertificateManager.Engine = value; }
}
/// <summary>
......@@ -226,6 +226,17 @@ namespace Titanium.Web.Proxy
public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls
| SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
/// <summary>
/// Total number of active client connections
/// </summary>
public int ClientConnectionCount { get; private set; }
/// <summary>
/// Total number of active server connections
/// </summary>
public int ServerConnectionCount { get; internal set; }
/// <summary>
/// Constructor
/// </summary>
......@@ -241,7 +252,7 @@ namespace Titanium.Web.Proxy
public ProxyServer(string rootCertificateName, string rootCertificateIssuerName)
{
//default values
ConnectionTimeOutSeconds = 120;
ConnectionTimeOutSeconds = 30;
CertificateCacheTimeOutMinutes = 60;
ProxyEndPoints = new List<ProxyEndPoint>();
......@@ -251,7 +262,7 @@ namespace Titanium.Web.Proxy
new FireFoxProxySettingsManager();
#endif
certificateManager = new CertificateManager(ExceptionFunc);
CertificateManager = new CertificateManager(ExceptionFunc);
if (rootCertificateName != null)
{
RootCertificateName = rootCertificateName;
......@@ -356,7 +367,7 @@ namespace Titanium.Web.Proxy
EnsureRootCertificate();
//If certificate was trusted by the machine
if (certificateManager.CertValidated)
if (CertificateManager.CertValidated)
{
systemProxySettingsManager.SetHttpsProxy(
Equals(endPoint.IpAddress, IPAddress.Any) |
......@@ -435,7 +446,7 @@ namespace Titanium.Web.Proxy
Listen(endPoint);
}
certificateManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes);
CertificateManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes);
proxyRunning = true;
}
......@@ -468,7 +479,7 @@ namespace Titanium.Web.Proxy
ProxyEndPoints.Clear();
certificateManager?.StopClearIdleCertificates();
CertificateManager?.StopClearIdleCertificates();
proxyRunning = false;
}
......@@ -483,7 +494,7 @@ namespace Titanium.Web.Proxy
Stop();
}
certificateManager?.Dispose();
CertificateManager?.Dispose();
}
/// <summary>
......@@ -495,7 +506,7 @@ namespace Titanium.Web.Proxy
endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
endPoint.Listener.Start();
endPoint.Port = ((IPEndPoint) endPoint.Listener.LocalEndpoint).Port;
endPoint.Port = ((IPEndPoint)endPoint.Listener.LocalEndpoint).Port;
// accept clients asynchronously
endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
}
......@@ -543,13 +554,13 @@ namespace Titanium.Web.Proxy
private void EnsureRootCertificate()
{
if (!certificateManager.CertValidated)
if (!CertificateManager.CertValidated)
{
certificateManager.CreateTrustedRootCertificate();
CertificateManager.CreateTrustedRootCertificate();
if (TrustRootCertificate)
{
certificateManager.TrustRootCertificate();
CertificateManager.TrustRootCertificate();
}
}
}
......@@ -560,7 +571,7 @@ namespace Titanium.Web.Proxy
/// <param name="asyn"></param>
private void OnAcceptConnection(IAsyncResult asyn)
{
var endPoint = (ProxyEndPoint) asyn.AsyncState;
var endPoint = (ProxyEndPoint)asyn.AsyncState;
TcpClient tcpClient = null;
......@@ -581,11 +592,18 @@ namespace Titanium.Web.Proxy
//Other errors are discarded to keep proxy running
}
if (tcpClient != null)
{
Task.Run(async () =>
{
ClientConnectionCount++;
//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);
try
{
if (endPoint.GetType() == typeof(TransparentProxyEndPoint))
......@@ -599,15 +617,8 @@ namespace Titanium.Web.Proxy
}
finally
{
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.Close();
}
ClientConnectionCount--;
tcpClient?.Close();
}
});
}
......
This diff is collapsed.
......@@ -9,6 +9,7 @@ using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Network.Tcp;
namespace Titanium.Web.Proxy
{
......@@ -21,14 +22,14 @@ namespace Titanium.Web.Proxy
/// Called asynchronously when a request was successfully and we received the response
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private async Task HandleHttpSessionResponse(SessionEventArgs args)
/// <returns>true if no errors</returns>
private async Task<bool> HandleHttpSessionResponse(SessionEventArgs args)
{
try
{
//read response & headers from server
await args.WebSession.ReceiveResponse();
try
{
if (!args.WebSession.Response.ResponseBodyRead)
{
args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream;
......@@ -44,7 +45,7 @@ namespace Titanium.Web.Proxy
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>) invocationList[i])(this, args);
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(this, args);
}
await Task.WhenAll(handlerTasks);
......@@ -52,8 +53,15 @@ namespace Titanium.Web.Proxy
if (args.ReRequest)
{
await HandleHttpSessionRequestInternal(null, args, null, null, true);
return;
if(args.WebSession.ServerConnection != null)
{
args.WebSession.ServerConnection.Dispose();
ServerConnectionCount--;
}
var connection = await GetServerConnection(args);
var result = await HandleHttpSessionRequestInternal(null, args, true);
return result;
}
args.WebSession.Response.ResponseLocked = true;
......@@ -125,14 +133,16 @@ namespace Titanium.Web.Proxy
}
catch (Exception e)
{
ExceptionFunc(new ProxyHttpException("Error occured wilst handling session response", e, args));
ExceptionFunc(new ProxyHttpException("Error occured whilst handling session response", e, args));
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter, args);
args.ProxyClient.ClientStreamWriter, args.WebSession.ServerConnection);
return false;
}
finally
{
args.Dispose();
}
return true;
}
/// <summary>
......@@ -224,19 +234,21 @@ namespace Titanium.Web.Proxy
/// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param>
/// <param name="args"></param>
private void Dispose(Stream clientStream, CustomBinaryReader clientStreamReader,
StreamWriter clientStreamWriter, IDisposable args)
/// <param name="serverConnection"></param>
private void Dispose(Stream clientStream,
CustomBinaryReader clientStreamReader,
StreamWriter clientStreamWriter,
TcpConnection serverConnection)
{
ServerConnectionCount--;
clientStream?.Close();
clientStream?.Dispose();
clientStreamReader?.Dispose();
clientStreamWriter?.Close();
clientStreamWriter?.Dispose();
args?.Dispose();
serverConnection?.Dispose();
}
}
}
......@@ -77,6 +77,7 @@
<Compile Include="Helpers\CustomBufferedStream.cs" />
<Compile Include="Helpers\Network.cs" />
<Compile Include="Helpers\RunTime.cs" />
<Compile Include="Http\HeaderParser.cs" />
<Compile Include="Http\Responses\GenericResponse.cs" />
<Compile Include="Network\CachedCertificate.cs" />
<Compile Include="Network\Certificate\WinCertificateMaker.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