Commit 687a8740 authored by Honfika's avatar Honfika

TcpClientConnection class

parent c882467b
......@@ -9,7 +9,6 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
......
......@@ -11,6 +11,7 @@ using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Http.Responses;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
namespace Titanium.Web.Proxy.EventArguments
{
......
......@@ -87,7 +87,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary>
/// Client End Point.
/// </summary>
public IPEndPoint ClientEndPoint => (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint;
public IPEndPoint ClientEndPoint => (IPEndPoint)ProxyClient.ClientConnection.RemoteEndPoint;
/// <summary>
/// A web session corresponding to a single request/response sequence
......
......@@ -15,6 +15,8 @@ using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Network.Tcp;
namespace Titanium.Web.Proxy
{
......@@ -25,14 +27,14 @@ namespace Titanium.Web.Proxy
/// So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
/// </summary>
/// <param name="endPoint">The explicit endpoint.</param>
/// <param name="tcpClient">The client.</param>
/// <param name="clientConnection">The client connection.</param>
/// <returns>The task.</returns>
private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient)
private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClientConnection clientConnection)
{
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
var clientStream = new CustomBufferedStream(clientConnection.GetStream(), BufferSize);
var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
......@@ -67,7 +69,7 @@ namespace Titanium.Web.Proxy
connectArgs = new TunnelConnectSessionEventArgs(BufferSize, endPoint, connectRequest,
cancellationTokenSource, ExceptionFunc);
connectArgs.ProxyClient.TcpClient = tcpClient;
connectArgs.ProxyClient.ClientConnection = clientConnection;
connectArgs.ProxyClient.ClientStream = clientStream;
await endPoint.InvokeBeforeTunnelConnectRequest(this, connectArgs, ExceptionFunc);
......@@ -158,7 +160,7 @@ namespace Titanium.Web.Proxy
options.ApplicationProtocols = clientHelloInfo.GetAlpn();
if (options.ApplicationProtocols == null || options.ApplicationProtocols.Count == 0)
{
options.ApplicationProtocols = Titanium.Web.Proxy.Extensions.SslExtensions.Http11ProtocolAsList;
options.ApplicationProtocols = SslExtensions.Http11ProtocolAsList;
}
}
......@@ -270,13 +272,13 @@ namespace Titanium.Web.Proxy
await TcpHelper.SendHttp2(clientStream, connection.Stream, BufferSize,
(buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); },
(buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); },
connectArgs.CancellationTokenSource, ExceptionFunc);
connectArgs.CancellationTokenSource, clientConnection.Id, ExceptionFunc);
}
}
}
//Now create the request
await HandleHttpSessionRequest(endPoint, tcpClient, clientStream,
await HandleHttpSessionRequest(endPoint, clientConnection, clientStream,
clientStreamWriter, cancellationTokenSource, connectHostname,
connectArgs?.WebSession.ConnectRequest);
}
......
......@@ -265,15 +265,14 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="onDataSend"></param>
/// <param name="onDataReceive"></param>
/// <param name="cancellationTokenSource"></param>
/// <param name="connectionId"></param>
/// <param name="exceptionFunc"></param>
/// <returns></returns>
internal static async Task SendHttp2(Stream clientStream, Stream serverStream, int bufferSize,
Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive,
CancellationTokenSource cancellationTokenSource,
CancellationTokenSource cancellationTokenSource, Guid connectionId,
ExceptionHandler exceptionFunc)
{
var connectionId = Guid.NewGuid();
//Now async relay all server=>client & client=>server data
var sendRelay =
CopyHttp2FrameAsync(clientStream, serverStream, onDataSend, bufferSize, connectionId,
......
......@@ -28,7 +28,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Connection to server
/// </summary>
internal TcpConnection ServerConnection { get; set; }
internal TcpServerConnection ServerConnection { get; set; }
/// <summary>
/// Request ID.
......@@ -69,11 +69,11 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Set the tcp connection to server used by this webclient
/// </summary>
/// <param name="connection">Instance of <see cref="TcpConnection" /></param>
internal void SetConnection(TcpConnection connection)
/// <param name="serverConnection">Instance of <see cref="TcpServerConnection" /></param>
internal void SetConnection(TcpServerConnection serverConnection)
{
connection.LastAccess = DateTime.Now;
ServerConnection = connection;
serverConnection.LastAccess = DateTime.Now;
ServerConnection = serverConnection;
}
/// <summary>
......
#if DEBUG
using System;
using System.IO;
using System.Text;
using System.Threading;
using StreamExtended.Network;
......@@ -15,11 +17,11 @@ namespace Titanium.Web.Proxy.Network
private readonly FileStream fileStreamSent;
public DebugCustomBufferedStream(Stream baseStream, int bufferSize) : base(baseStream, bufferSize)
public DebugCustomBufferedStream(Guid connectionId, string type, Stream baseStream, int bufferSize, bool leaveOpen = false) : base(baseStream, bufferSize, leaveOpen)
{
Counter = Interlocked.Increment(ref counter);
fileStreamSent = new FileStream(Path.Combine(basePath, $"{Counter}_sent.dat"), FileMode.Create);
fileStreamReceived = new FileStream(Path.Combine(basePath, $"{Counter}_received.dat"), FileMode.Create);
fileStreamSent = new FileStream(Path.Combine(basePath, $"{connectionId}_{type}_{Counter}_sent.dat"), FileMode.Create);
fileStreamReceived = new FileStream(Path.Combine(basePath, $"{connectionId}_{type}_{Counter}_received.dat"), FileMode.Create);
}
public int Counter { get; }
......@@ -27,18 +29,42 @@ namespace Titanium.Web.Proxy.Network
protected override void OnDataWrite(byte[] buffer, int offset, int count)
{
fileStreamSent.Write(buffer, offset, count);
Flush();
}
protected override void OnDataRead(byte[] buffer, int offset, int count)
{
fileStreamReceived.Write(buffer, offset, count);
Flush();
}
public void LogException(Exception ex)
{
var data = Encoding.UTF8.GetBytes("EXCEPTION: " + ex);
fileStreamReceived.Write(data, 0, data.Length);
fileStreamReceived.Flush();
}
public override void Flush()
{
fileStreamSent.Flush(true);
fileStreamReceived.Flush(true);
base.Flush();
if (CanWrite)
{
base.Flush();
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
Flush();
fileStreamSent.Dispose();
fileStreamReceived.Dispose();
}
base.Dispose(disposing);
}
}
}
......
using System.Net.Sockets;
using StreamExtended.Network;
using StreamExtended.Network;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Network.Tcp;
namespace Titanium.Web.Proxy.Network
{
......@@ -10,9 +10,9 @@ namespace Titanium.Web.Proxy.Network
internal class ProxyClient
{
/// <summary>
/// TcpClient used to communicate with client
/// TcpClient connection used to communicate with client
/// </summary>
internal TcpClient TcpClient { get; set; }
internal TcpClientConnection ClientConnection { get; set; }
/// <summary>
/// Holds the stream to client
......
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy.Network.Tcp
{
/// <summary>
/// An object that holds TcpConnection to a particular server and port
/// </summary>
internal class TcpClientConnection : IDisposable
{
internal TcpClientConnection(ProxyServer proxyServer, TcpClient tcpClient)
{
this.tcpClient = tcpClient;
this.proxyServer = proxyServer;
this.proxyServer.UpdateClientConnectionCount(true);
}
private ProxyServer proxyServer { get; }
public Guid Id { get; } = Guid.NewGuid();
public EndPoint LocalEndPoint => tcpClient.Client.LocalEndPoint;
public EndPoint RemoteEndPoint => tcpClient.Client.RemoteEndPoint;
private readonly TcpClient tcpClient;
public Stream GetStream()
{
return tcpClient.GetStream();
}
/// <summary>
/// Dispose.
/// </summary>
public void Dispose()
{
tcpClient.CloseSocket();
proxyServer.UpdateClientConnectionCount(false);
}
}
}
......@@ -32,7 +32,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <param name="externalProxy"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
internal async Task<TcpConnection> CreateClient(string remoteHostName, int remotePort,
internal async Task<TcpServerConnection> CreateClient(string remoteHostName, int remotePort,
List<SslApplicationProtocol> applicationProtocols, Version httpVersion, bool decryptSsl, bool isConnect,
ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy,
CancellationToken cancellationToken)
......@@ -52,26 +52,26 @@ namespace Titanium.Web.Proxy.Network.Tcp
}
}
TcpClient client = null;
TcpClient tcpClient = null;
CustomBufferedStream stream = null;
bool http2Supported = false;
try
{
client = new TcpClient(upStreamEndPoint);
tcpClient = new TcpClient(upStreamEndPoint);
//If this proxy uses another external proxy then create a tunnel request for HTTP/HTTPS connections
if (useUpstreamProxy)
{
await client.ConnectAsync(externalProxy.HostName, externalProxy.Port);
await tcpClient.ConnectAsync(externalProxy.HostName, externalProxy.Port);
}
else
{
await client.ConnectAsync(remoteHostName, remotePort);
await tcpClient.ConnectAsync(remoteHostName, remotePort);
}
stream = new CustomBufferedStream(client.GetStream(), proxyServer.BufferSize);
stream = new CustomBufferedStream(tcpClient.GetStream(), proxyServer.BufferSize);
if (useUpstreamProxy && (isConnect || decryptSsl))
{
......@@ -129,17 +129,17 @@ namespace Titanium.Web.Proxy.Network.Tcp
#endif
}
client.ReceiveTimeout = proxyServer.ConnectionTimeOutSeconds * 1000;
client.SendTimeout = proxyServer.ConnectionTimeOutSeconds * 1000;
tcpClient.ReceiveTimeout = proxyServer.ConnectionTimeOutSeconds * 1000;
tcpClient.SendTimeout = proxyServer.ConnectionTimeOutSeconds * 1000;
}
catch (Exception)
{
stream?.Dispose();
client?.Close();
tcpClient?.Close();
throw;
}
return new TcpConnection(proxyServer)
return new TcpServerConnection(proxyServer, tcpClient)
{
UpStreamProxy = externalProxy,
UpStreamEndPoint = upStreamEndPoint,
......@@ -148,7 +148,6 @@ namespace Titanium.Web.Proxy.Network.Tcp
IsHttps = decryptSsl,
IsHttp2Supported = http2Supported,
UseUpstreamProxy = useUpstreamProxy,
TcpClient = client,
StreamWriter = new HttpRequestWriter(stream, proxyServer.BufferSize),
Stream = stream,
Version = httpVersion
......
......@@ -11,10 +11,11 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <summary>
/// An object that holds TcpConnection to a particular server and port
/// </summary>
internal class TcpConnection : IDisposable
internal class TcpServerConnection : IDisposable
{
internal TcpConnection(ProxyServer proxyServer)
internal TcpServerConnection(ProxyServer proxyServer, TcpClient tcpClient)
{
this.tcpClient = tcpClient;
LastAccess = DateTime.Now;
this.proxyServer = proxyServer;
this.proxyServer.UpdateServerConnectionCount(true);
......@@ -44,7 +45,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary>
internal Version Version { get; set; }
internal TcpClient TcpClient { private get; set; }
private readonly TcpClient tcpClient;
/// <summary>
/// Used to write lines to server
......@@ -68,7 +69,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
{
Stream?.Dispose();
TcpClient.CloseSocket();
tcpClient.CloseSocket();
proxyServer.UpdateServerConnectionCount(false);
}
......
......@@ -8,7 +8,6 @@ using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Helpers.WinHttp;
using Titanium.Web.Proxy.Models;
......@@ -648,27 +647,20 @@ namespace Titanium.Web.Proxy
private async Task HandleClient(TcpClient tcpClient, ProxyEndPoint endPoint)
{
UpdateClientConnectionCount(true);
tcpClient.ReceiveTimeout = ConnectionTimeOutSeconds * 1000;
tcpClient.SendTimeout = ConnectionTimeOutSeconds * 1000;
try
using (var clientConnection = new TcpClientConnection(this, tcpClient))
{
if (endPoint is TransparentProxyEndPoint tep)
{
await HandleClient(tep, tcpClient);
await HandleClient(tep, clientConnection);
}
else
{
await HandleClient((ExplicitProxyEndPoint)endPoint, tcpClient);
await HandleClient((ExplicitProxyEndPoint)endPoint, clientConnection);
}
}
finally
{
UpdateClientConnectionCount(false);
tcpClient.CloseSocket();
}
}
/// <summary>
......
using System;
using System.Net;
using System.Net.Sockets;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
......@@ -32,7 +31,7 @@ namespace Titanium.Web.Proxy
/// client/server abruptly terminates connection or by normal HTTP termination.
/// </summary>
/// <param name="endPoint">The proxy endpoint.</param>
/// <param name="client">The client.</param>
/// <param name="clientConnection">The client connection.</param>
/// <param name="clientStream">The client stream.</param>
/// <param name="clientStreamWriter">The client stream writer.</param>
/// <param name="cancellationTokenSource">The cancellation token source for this async task.</param>
......@@ -43,13 +42,13 @@ namespace Titanium.Web.Proxy
/// <param name="connectRequest">The Connect request if this is a HTTPS request from explicit endpoint.</param>
/// <param name="isTransparentEndPoint">Is this a request from transparent endpoint?</param>
/// <returns></returns>
private async Task HandleHttpSessionRequest(ProxyEndPoint endPoint, TcpClient client,
private async Task HandleHttpSessionRequest(ProxyEndPoint endPoint, TcpClientConnection clientConnection,
CustomBufferedStream clientStream, HttpResponseWriter clientStreamWriter,
CancellationTokenSource cancellationTokenSource, string httpsConnectHostname, ConnectRequest connectRequest,
bool isTransparentEndPoint = false)
{
var cancellationToken = cancellationTokenSource.Token;
TcpConnection connection = null;
TcpServerConnection serverConnection = null;
try
{
......@@ -67,7 +66,7 @@ namespace Titanium.Web.Proxy
var args = new SessionEventArgs(BufferSize, endPoint, cancellationTokenSource, ExceptionFunc)
{
ProxyClient = { TcpClient = client },
ProxyClient = { ClientConnection = clientConnection },
WebSession = { ConnectRequest = connectRequest }
};
......@@ -173,29 +172,29 @@ namespace Titanium.Web.Proxy
}
//create a new connection if hostname/upstream end point changes
if (connection != null
&& (!connection.HostName.Equals(request.RequestUri.Host,
if (serverConnection != null
&& (!serverConnection.HostName.Equals(request.RequestUri.Host,
StringComparison.OrdinalIgnoreCase)
|| args.WebSession.UpStreamEndPoint != null
&& !args.WebSession.UpStreamEndPoint.Equals(connection.UpStreamEndPoint)))
&& !args.WebSession.UpStreamEndPoint.Equals(serverConnection.UpStreamEndPoint)))
{
connection.Dispose();
connection = null;
serverConnection.Dispose();
serverConnection = null;
}
if (connection == null)
if (serverConnection == null)
{
connection = await GetServerConnection(args, false, cancellationToken);
serverConnection = await GetServerConnection(args, false, cancellationToken);
}
//if upgrading to websocket then relay the requet without reading the contents
if (request.UpgradeToWebSocket)
{
//prepare the prefix content
await connection.StreamWriter.WriteLineAsync(httpCmd, cancellationToken);
await connection.StreamWriter.WriteHeadersAsync(request.Headers,
await serverConnection.StreamWriter.WriteLineAsync(httpCmd, cancellationToken);
await serverConnection.StreamWriter.WriteHeadersAsync(request.Headers,
cancellationToken: cancellationToken);
string httpStatus = await connection.Stream.ReadLineAsync(cancellationToken);
string httpStatus = await serverConnection.Stream.ReadLineAsync(cancellationToken);
Response.ParseResponseLine(httpStatus, out var responseVersion,
out int responseStatusCode,
......@@ -204,7 +203,7 @@ namespace Titanium.Web.Proxy
response.StatusCode = responseStatusCode;
response.StatusDescription = responseStatusDescription;
await HeaderParser.ReadHeaders(connection.Stream, response.Headers,
await HeaderParser.ReadHeaders(serverConnection.Stream, response.Headers,
cancellationToken);
if (!args.IsTransparent)
......@@ -219,7 +218,7 @@ namespace Titanium.Web.Proxy
await InvokeBeforeResponse(args);
}
await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize,
await TcpHelper.SendRaw(clientStream, serverConnection.Stream, BufferSize,
(buffer, offset, count) => { args.OnDataSent(buffer, offset, count); },
(buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); },
cancellationTokenSource, ExceptionFunc);
......@@ -228,7 +227,7 @@ namespace Titanium.Web.Proxy
}
//construct the web request that we are going to issue on behalf of the client.
await HandleHttpSessionRequestInternal(connection, args);
await HandleHttpSessionRequestInternal(serverConnection, args);
if (args.WebSession.ServerConnection == null)
{
......@@ -265,17 +264,17 @@ namespace Titanium.Web.Proxy
}
finally
{
connection?.Dispose();
serverConnection?.Dispose();
}
}
/// <summary>
/// Handle a specific session (request/response sequence)
/// </summary>
/// <param name="connection">The tcp connection.</param>
/// <param name="serverConnection">The tcp connection.</param>
/// <param name="args">The session event arguments.</param>
/// <returns></returns>
private async Task HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args)
private async Task HandleHttpSessionRequestInternal(TcpServerConnection serverConnection, SessionEventArgs args)
{
try
{
......@@ -289,7 +288,7 @@ namespace Titanium.Web.Proxy
//and see if server would return 100 conitinue
if (request.ExpectContinue)
{
args.WebSession.SetConnection(connection);
args.WebSession.SetConnection(serverConnection);
await args.WebSession.SendRequest(Enable100ContinueBehaviour, args.IsTransparent,
cancellationToken);
}
......@@ -316,7 +315,7 @@ namespace Titanium.Web.Proxy
//If expect continue is not enabled then set the connectio and send request headers
if (!request.ExpectContinue)
{
args.WebSession.SetConnection(connection);
args.WebSession.SetConnection(serverConnection);
await args.WebSession.SendRequest(Enable100ContinueBehaviour, args.IsTransparent,
cancellationToken);
}
......@@ -361,7 +360,7 @@ namespace Titanium.Web.Proxy
/// <param name="isConnect">Is this a CONNECT request.</param>
/// <param name="cancellationToken">The cancellation token for this async task.</param>
/// <returns></returns>
private async Task<TcpConnection> GetServerConnection(SessionEventArgsBase args, bool isConnect,
private async Task<TcpServerConnection> GetServerConnection(SessionEventArgsBase args, bool isConnect,
CancellationToken cancellationToken)
{
ExternalProxy customUpStreamProxy = null;
......
......@@ -11,6 +11,7 @@ using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.Tcp;
namespace Titanium.Web.Proxy
{
......@@ -21,14 +22,14 @@ namespace Titanium.Web.Proxy
/// So for HTTPS requests we would start SSL negotiation right away without expecting a CONNECT request from client
/// </summary>
/// <param name="endPoint">The transparent endpoint.</param>
/// <param name="tcpClient">The client.</param>
/// <param name="clientConnection">The client connection.</param>
/// <returns></returns>
private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient)
private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClientConnection clientConnection)
{
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
var clientStream = new CustomBufferedStream(clientConnection.GetStream(), BufferSize);
var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
......@@ -123,7 +124,7 @@ namespace Titanium.Web.Proxy
//HTTPS server created - we can now decrypt the client's traffic
//Now create the request
await HandleHttpSessionRequest(endPoint, tcpClient, clientStream, clientStreamWriter,
await HandleHttpSessionRequest(endPoint, clientConnection, clientStream, clientStreamWriter,
cancellationTokenSource, isHttps ? httpsHostName : null, null, true);
}
finally
......
......@@ -172,7 +172,7 @@ namespace Titanium.Web.Proxy
// Add custom div to body to clarify that the proxy (not the client browser) failed authentication
string authErrorMessage =
"<div class=\"inserted-by-proxy\"><h2>NTLM authentication through Titanium.Web.Proxy (" +
args.ProxyClient.TcpClient.Client.LocalEndPoint +
args.ProxyClient.ClientConnection.LocalEndPoint +
") failed. Please check credentials.</h2></div>";
string originalErrorMessage =
"<div class=\"inserted-by-proxy\"><h3>Response from remote web server below.</h3></div><br/>";
......
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