Unverified Commit 4c5d4cd7 authored by honfika's avatar honfika Committed by GitHub

Merge pull request #712 from justcoding121/master

beta
parents 8cbf0f43 b67ad5d5
...@@ -90,8 +90,12 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -90,8 +90,12 @@ namespace Titanium.Web.Proxy.Examples.Basic
//}; //};
//proxyServer.AddEndPoint(transparentEndPoint); //proxyServer.AddEndPoint(transparentEndPoint);
//proxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 }; //proxyServer.UpStreamHttpProxy = new ExternalProxy("localhost", 8888);
//proxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 }; //proxyServer.UpStreamHttpsProxy = new ExternalProxy("localhost", 8888);
// SOCKS proxy
//proxyServer.UpStreamHttpProxy = new ExternalProxy("46.63.0.17", 4145) { ProxyType = ExternalProxyType.Socks4 };
//proxyServer.UpStreamHttpsProxy = new ExternalProxy("46.63.0.17", 4145) { ProxyType = ExternalProxyType.Socks4 };
foreach (var endPoint in proxyServer.ProxyEndPoints) foreach (var endPoint in proxyServer.ProxyEndPoints)
{ {
......
...@@ -5,24 +5,6 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -5,24 +5,6 @@ namespace Titanium.Web.Proxy.Extensions
{ {
internal static class TcpExtensions internal static class TcpExtensions
{ {
internal static void CloseSocket(this TcpClient tcpClient)
{
if (tcpClient == null)
{
return;
}
try
{
tcpClient.Close();
}
catch
{
// ignored
}
}
/// <summary> /// <summary>
/// Check if a TcpClient is good to be used. /// Check if a TcpClient is good to be used.
/// This only checks if send is working so local socket is still connected. /// This only checks if send is working so local socket is still connected.
...@@ -30,13 +12,11 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -30,13 +12,11 @@ namespace Titanium.Web.Proxy.Extensions
/// So in our case we should retry with new connection from pool if first read after getting the connection fails. /// So in our case we should retry with new connection from pool if first read after getting the connection fails.
/// https://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected(v=vs.110).aspx /// https://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected(v=vs.110).aspx
/// </summary> /// </summary>
/// <param name="client"></param> /// <param name="socket"></param>
/// <returns></returns> /// <returns></returns>
internal static bool IsGoodConnection(this TcpClient client) internal static bool IsGoodConnection(this Socket socket)
{ {
var socket = client.Client; if (!socket.Connected)
if (!client.Connected || !socket.Connected)
{ {
return false; return false;
} }
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
using System.Net; using System.Net;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.Tcp; using Titanium.Web.Proxy.Network.Tcp;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
...@@ -103,10 +104,14 @@ namespace Titanium.Web.Proxy.Http ...@@ -103,10 +104,14 @@ namespace Titanium.Web.Proxy.Http
{ {
var upstreamProxy = Connection.UpStreamProxy; var upstreamProxy = Connection.UpStreamProxy;
bool useUpstreamProxy = upstreamProxy != null && Connection.IsHttps == false; bool useUpstreamProxy = upstreamProxy != null && upstreamProxy.ProxyType == ExternalProxyType.Http &&
!Connection.IsHttps;
var serverStream = Connection.Stream; var serverStream = Connection.Stream;
string? upstreamProxyUserName = null;
string? upstreamProxyPassword = null;
string url; string url;
if (!useUpstreamProxy || isTransparent) if (!useUpstreamProxy || isTransparent)
{ {
...@@ -115,19 +120,13 @@ namespace Titanium.Web.Proxy.Http ...@@ -115,19 +120,13 @@ namespace Titanium.Web.Proxy.Http
else else
{ {
url = Request.RequestUri.ToString(); url = Request.RequestUri.ToString();
}
string? upstreamProxyUserName = null;
string? upstreamProxyPassword = null;
// Send Authentication to Upstream proxy if needed // Send Authentication to Upstream proxy if needed
if (!isTransparent && upstreamProxy != null if (!string.IsNullOrEmpty(upstreamProxy!.UserName) && upstreamProxy.Password != null)
&& Connection.IsHttps == false {
&& !string.IsNullOrEmpty(upstreamProxy.UserName) upstreamProxyUserName = upstreamProxy.UserName;
&& upstreamProxy.Password != null) upstreamProxyPassword = upstreamProxy.Password;
{ }
upstreamProxyUserName = upstreamProxy.UserName;
upstreamProxyPassword = upstreamProxy.Password;
} }
// prepare the request & headers // prepare the request & headers
......
...@@ -25,6 +25,8 @@ namespace Titanium.Web.Proxy.Models ...@@ -25,6 +25,8 @@ namespace Titanium.Web.Proxy.Models
/// </summary> /// </summary>
public bool BypassLocalhost { get; set; } public bool BypassLocalhost { get; set; }
public ExternalProxyType ProxyType { get; set; }
/// <summary> /// <summary>
/// Username. /// Username.
/// </summary> /// </summary>
...@@ -111,4 +113,16 @@ namespace Titanium.Web.Proxy.Models ...@@ -111,4 +113,16 @@ namespace Titanium.Web.Proxy.Models
return $"{HostName}:{Port}"; return $"{HostName}:{Port}";
} }
} }
public enum ExternalProxyType
{
/// <summary>A HTTP/HTTPS proxy server.</summary>
Http,
/// <summary>A SOCKS4[A] proxy server.</summary>
Socks4,
/// <summary>A SOCKS5 proxy server.</summary>
Socks5
}
} }
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
/// </summary> /// </summary>
bool BypassLocalhost { get; set; } bool BypassLocalhost { get; set; }
ExternalProxyType ProxyType { get; set; }
/// <summary> /// <summary>
/// Username. /// Username.
/// </summary> /// </summary>
......
...@@ -18,9 +18,9 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -18,9 +18,9 @@ namespace Titanium.Web.Proxy.Network.Tcp
{ {
public object ClientUserData { get; set; } public object ClientUserData { get; set; }
internal TcpClientConnection(ProxyServer proxyServer, TcpClient tcpClient) internal TcpClientConnection(ProxyServer proxyServer, Socket tcpClientSocket)
{ {
this.tcpClient = tcpClient; this.tcpClientSocket = tcpClientSocket;
this.proxyServer = proxyServer; this.proxyServer = proxyServer;
this.proxyServer.UpdateClientConnectionCount(true); this.proxyServer.UpdateClientConnectionCount(true);
} }
...@@ -29,21 +29,21 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -29,21 +29,21 @@ namespace Titanium.Web.Proxy.Network.Tcp
public Guid Id { get; } = Guid.NewGuid(); public Guid Id { get; } = Guid.NewGuid();
public EndPoint LocalEndPoint => tcpClient.Client.LocalEndPoint; public EndPoint LocalEndPoint => tcpClientSocket.LocalEndPoint;
public EndPoint RemoteEndPoint => tcpClient.Client.RemoteEndPoint; public EndPoint RemoteEndPoint => tcpClientSocket.RemoteEndPoint;
internal SslProtocols SslProtocol { get; set; } internal SslProtocols SslProtocol { get; set; }
internal SslApplicationProtocol NegotiatedApplicationProtocol { get; set; } internal SslApplicationProtocol NegotiatedApplicationProtocol { get; set; }
private readonly TcpClient tcpClient; private readonly Socket tcpClientSocket;
private int? processId; private int? processId;
public Stream GetStream() public Stream GetStream()
{ {
return tcpClient.GetStream(); return new NetworkStream(tcpClientSocket, true);
} }
public int GetProcessId(ProxyEndPoint endPoint) public int GetProcessId(ProxyEndPoint endPoint)
...@@ -86,7 +86,15 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -86,7 +86,15 @@ namespace Titanium.Web.Proxy.Network.Tcp
// This way we can push tcp Time_Wait to client side when possible. // This way we can push tcp Time_Wait to client side when possible.
await Task.Delay(1000); await Task.Delay(1000);
proxyServer.UpdateClientConnectionCount(false); proxyServer.UpdateClientConnectionCount(false);
tcpClient.CloseSocket();
try
{
tcpClientSocket.Close();
}
catch
{
// ignore
}
}); });
} }
} }
......
...@@ -15,6 +15,7 @@ using Titanium.Web.Proxy.Extensions; ...@@ -15,6 +15,7 @@ using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.ProxySocket;
namespace Titanium.Web.Proxy.Network.Tcp namespace Titanium.Web.Proxy.Network.Tcp
{ {
...@@ -85,6 +86,8 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -85,6 +86,8 @@ namespace Titanium.Web.Proxy.Network.Tcp
cacheKeyBuilder.Append(externalProxy.HostName); cacheKeyBuilder.Append(externalProxy.HostName);
cacheKeyBuilder.Append("-"); cacheKeyBuilder.Append("-");
cacheKeyBuilder.Append(externalProxy.Port); cacheKeyBuilder.Append(externalProxy.Port);
cacheKeyBuilder.Append("-");
cacheKeyBuilder.Append(externalProxy.ProxyType);
if (externalProxy.UseDefaultCredentials) if (externalProxy.UseDefaultCredentials)
{ {
...@@ -114,7 +117,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -114,7 +117,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
applicationProtocols = new List<SslApplicationProtocol> { applicationProtocol }; applicationProtocols = new List<SslApplicationProtocol> { applicationProtocol };
} }
IExternalProxy? customUpStreamProxy = session.CustomUpStreamProxy; var customUpStreamProxy = session.CustomUpStreamProxy;
bool isHttps = session.IsHttps; bool isHttps = session.IsHttps;
if (customUpStreamProxy == null && server.GetCustomUpStreamProxyFunc != null) if (customUpStreamProxy == null && server.GetCustomUpStreamProxyFunc != null)
...@@ -125,12 +128,10 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -125,12 +128,10 @@ namespace Titanium.Web.Proxy.Network.Tcp
session.CustomUpStreamProxyUsed = customUpStreamProxy; session.CustomUpStreamProxyUsed = customUpStreamProxy;
var uri = session.HttpClient.Request.RequestUri; var uri = session.HttpClient.Request.RequestUri;
return GetConnectionCacheKey( var upStreamEndPoint = session.HttpClient.UpStreamEndPoint ?? server.UpStreamEndPoint;
uri.Host, var upStreamProxy = customUpStreamProxy ?? (isHttps ? server.UpStreamHttpsProxy : server.UpStreamHttpProxy);
uri.Port, return GetConnectionCacheKey(uri.Host, uri.Port, isHttps, applicationProtocols, upStreamEndPoint,
isHttps, applicationProtocols, upStreamProxy);
session.HttpClient.UpStreamEndPoint ?? server.UpStreamEndPoint,
customUpStreamProxy ?? (isHttps ? server.UpStreamHttpsProxy : server.UpStreamHttpProxy));
} }
...@@ -169,7 +170,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -169,7 +170,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
internal async Task<TcpServerConnection> GetServerConnection(ProxyServer proxyServer, SessionEventArgsBase session, bool isConnect, internal async Task<TcpServerConnection> GetServerConnection(ProxyServer proxyServer, SessionEventArgsBase session, bool isConnect,
List<SslApplicationProtocol>? applicationProtocols, bool noCache, CancellationToken cancellationToken) List<SslApplicationProtocol>? applicationProtocols, bool noCache, CancellationToken cancellationToken)
{ {
IExternalProxy? customUpStreamProxy = session.CustomUpStreamProxy; var customUpStreamProxy = session.CustomUpStreamProxy;
bool isHttps = session.IsHttps; bool isHttps = session.IsHttps;
if (customUpStreamProxy == null && proxyServer.GetCustomUpStreamProxyFunc != null) if (customUpStreamProxy == null && proxyServer.GetCustomUpStreamProxyFunc != null)
...@@ -204,13 +205,10 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -204,13 +205,10 @@ namespace Titanium.Web.Proxy.Network.Tcp
port = uri.Port; port = uri.Port;
} }
return await GetServerConnection( var upStreamEndPoint = session.HttpClient.UpStreamEndPoint ?? proxyServer.UpStreamEndPoint;
proxyServer, host, port, var upStreamProxy = customUpStreamProxy ?? (isHttps ? proxyServer.UpStreamHttpsProxy : proxyServer.UpStreamHttpProxy);
session.HttpClient.Request.HttpVersion, return await GetServerConnection(proxyServer, host, port, session.HttpClient.Request.HttpVersion, isHttps,
isHttps, applicationProtocols, isConnect, applicationProtocols, isConnect, session, upStreamEndPoint, upStreamProxy, noCache, cancellationToken);
session, session.HttpClient.UpStreamEndPoint ?? proxyServer.UpStreamEndPoint,
customUpStreamProxy ?? (isHttps ? proxyServer.UpStreamHttpsProxy : proxyServer.UpStreamHttpProxy),
noCache, cancellationToken);
} }
/// <summary> /// <summary>
...@@ -249,7 +247,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -249,7 +247,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
if (existingConnections.TryDequeue(out var recentConnection)) if (existingConnections.TryDequeue(out var recentConnection))
{ {
if (recentConnection.LastAccess > cutOff if (recentConnection.LastAccess > cutOff
&& recentConnection.TcpClient.IsGoodConnection()) && recentConnection.TcpSocket.IsGoodConnection())
{ {
return recentConnection; return recentConnection;
} }
...@@ -323,7 +321,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -323,7 +321,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
externalProxy = null; externalProxy = null;
} }
TcpClient? tcpClient = null; Socket? tcpServerSocket = null;
HttpServerStream? stream = null; HttpServerStream? stream = null;
SslApplicationProtocol negotiatedApplicationProtocol = default; SslApplicationProtocol negotiatedApplicationProtocol = default;
...@@ -334,8 +332,15 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -334,8 +332,15 @@ namespace Titanium.Web.Proxy.Network.Tcp
retry: retry:
try try
{ {
string hostname = externalProxy != null ? externalProxy.HostName : remoteHostName; bool socks = externalProxy != null && externalProxy.ProxyType != ExternalProxyType.Http;
int port = externalProxy?.Port ?? remotePort; string hostname = remoteHostName;
int port = remotePort;
if (externalProxy != null && externalProxy.ProxyType == ExternalProxyType.Http)
{
hostname = externalProxy.HostName;
port = externalProxy.Port;
}
var ipAddresses = await Dns.GetHostAddressesAsync(hostname); var ipAddresses = await Dns.GetHostAddressesAsync(hostname);
if (ipAddresses == null || ipAddresses.Length == 0) if (ipAddresses == null || ipAddresses.Length == 0)
...@@ -356,28 +361,46 @@ retry: ...@@ -356,28 +361,46 @@ retry:
try try
{ {
var ipAddress = ipAddresses[i]; var ipAddress = ipAddresses[i];
if (upStreamEndPoint == null) var addressFamily = upStreamEndPoint?.AddressFamily ?? ipAddress.AddressFamily;
if (socks)
{ {
tcpClient = new TcpClient(ipAddress.AddressFamily); var proxySocket = new ProxySocket.ProxySocket(addressFamily, SocketType.Stream, ProtocolType.Tcp);
proxySocket.ProxyType = externalProxy!.ProxyType == ExternalProxyType.Socks4
? ProxyTypes.Socks4
: ProxyTypes.Socks5;
var proxyIpAddresses = await Dns.GetHostAddressesAsync(externalProxy.HostName);
proxySocket.ProxyEndPoint = new IPEndPoint(proxyIpAddresses[0], externalProxy.Port);
if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
{
proxySocket.ProxyUser = externalProxy.UserName;
proxySocket.ProxyPass = externalProxy.Password;
}
tcpServerSocket = proxySocket;
} }
else else
{ {
tcpClient = new TcpClient(upStreamEndPoint); tcpServerSocket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp);
} }
tcpClient.NoDelay = proxyServer.NoDelay; tcpServerSocket.NoDelay = proxyServer.NoDelay;
tcpClient.ReceiveTimeout = proxyServer.ConnectionTimeOutSeconds * 1000; tcpServerSocket.ReceiveTimeout = proxyServer.ConnectionTimeOutSeconds * 1000;
tcpClient.SendTimeout = proxyServer.ConnectionTimeOutSeconds * 1000; tcpServerSocket.SendTimeout = proxyServer.ConnectionTimeOutSeconds * 1000;
tcpClient.LingerState = new LingerOption(true, proxyServer.TcpTimeWaitSeconds); tcpServerSocket.LingerState = new LingerOption(true, proxyServer.TcpTimeWaitSeconds);
if (proxyServer.ReuseSocket && RunTime.IsSocketReuseAvailable) if (proxyServer.ReuseSocket && RunTime.IsSocketReuseAvailable)
{ {
tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); tcpServerSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
} }
var connectTask = tcpClient.ConnectAsync(ipAddress, port); var connectTask = socks
await Task.WhenAny(connectTask, Task.Delay(proxyServer.ConnectTimeOutSeconds * 1000)); ? ProxySocketConnectionTaskFactory.CreateTask((ProxySocket.ProxySocket)tcpServerSocket, ipAddress, port)
if (!connectTask.IsCompleted || !tcpClient.Connected) : SocketConnectionTaskFactory.CreateTask(tcpServerSocket, ipAddress, port);
await Task.WhenAny(connectTask, Task.Delay(proxyServer.ConnectTimeOutSeconds * 1000, cancellationToken));
if (!connectTask.IsCompleted || !tcpServerSocket.Connected)
{ {
// here we can just do some cleanup and let the loop continue since // here we can just do some cleanup and let the loop continue since
// we will either get a connection or wind up with a null tcpClient // we will either get a connection or wind up with a null tcpClient
...@@ -393,11 +416,11 @@ retry: ...@@ -393,11 +416,11 @@ retry:
try try
{ {
#if NET45 #if NET45
tcpClient?.Close(); tcpServerSocket?.Close();
#else #else
tcpClient?.Dispose(); tcpServerSocket?.Dispose();
#endif #endif
tcpClient = null; tcpServerSocket = null;
} }
catch catch
{ {
...@@ -414,15 +437,15 @@ retry: ...@@ -414,15 +437,15 @@ retry:
// dispose the current TcpClient and try the next address // dispose the current TcpClient and try the next address
lastException = e; lastException = e;
#if NET45 #if NET45
tcpClient?.Close(); tcpServerSocket?.Close();
#else #else
tcpClient?.Dispose(); tcpServerSocket?.Dispose();
#endif #endif
tcpClient = null; tcpServerSocket = null;
} }
} }
if (tcpClient == null) if (tcpServerSocket == null)
{ {
if (sessionArgs != null && proxyServer.CustomUpStreamProxyFailureFunc != null) if (sessionArgs != null && proxyServer.CustomUpStreamProxyFailureFunc != null)
{ {
...@@ -443,11 +466,11 @@ retry: ...@@ -443,11 +466,11 @@ retry:
sessionArgs.TimeLine["Connection Established"] = DateTime.Now; sessionArgs.TimeLine["Connection Established"] = DateTime.Now;
} }
await proxyServer.InvokeServerConnectionCreateEvent(tcpClient); await proxyServer.InvokeServerConnectionCreateEvent(tcpServerSocket);
stream = new HttpServerStream(tcpClient.GetStream(), proxyServer.BufferPool, cancellationToken); stream = new HttpServerStream(new NetworkStream(tcpServerSocket, true), proxyServer.BufferPool, cancellationToken);
if (externalProxy != null && (isConnect || isHttps)) if ((externalProxy != null && externalProxy.ProxyType == ExternalProxyType.Http) && (isConnect || isHttps))
{ {
var authority = $"{remoteHostName}:{remotePort}".GetByteString(); var authority = $"{remoteHostName}:{remotePort}".GetByteString();
var connectRequest = new ConnectRequest(authority) var connectRequest = new ConnectRequest(authority)
...@@ -511,7 +534,7 @@ retry: ...@@ -511,7 +534,7 @@ retry:
catch (IOException ex) when (ex.HResult == unchecked((int)0x80131620) && retry && enabledSslProtocols >= SslProtocols.Tls11) catch (IOException ex) when (ex.HResult == unchecked((int)0x80131620) && retry && enabledSslProtocols >= SslProtocols.Tls11)
{ {
stream?.Dispose(); stream?.Dispose();
tcpClient?.Close(); tcpServerSocket?.Close();
enabledSslProtocols = SslProtocols.Tls; enabledSslProtocols = SslProtocols.Tls;
retry = false; retry = false;
...@@ -520,11 +543,11 @@ retry: ...@@ -520,11 +543,11 @@ retry:
catch (Exception) catch (Exception)
{ {
stream?.Dispose(); stream?.Dispose();
tcpClient?.Close(); tcpServerSocket?.Close();
throw; throw;
} }
return new TcpServerConnection(proxyServer, tcpClient, stream, remoteHostName, remotePort, isHttps, return new TcpServerConnection(proxyServer, tcpServerSocket, stream, remoteHostName, remotePort, isHttps,
negotiatedApplicationProtocol, httpVersion, externalProxy, upStreamEndPoint, cacheKey); negotiatedApplicationProtocol, httpVersion, externalProxy, upStreamEndPoint, cacheKey);
} }
...@@ -698,5 +721,43 @@ retry: ...@@ -698,5 +721,43 @@ retry:
} }
} }
} }
static class SocketConnectionTaskFactory
{
static IAsyncResult beginConnect(IPAddress address, int port, AsyncCallback requestCallback,
object state)
{
return ((Socket)state).BeginConnect(address, port, requestCallback, state);
}
static void endConnect(IAsyncResult asyncResult)
{
((Socket)asyncResult.AsyncState).EndConnect(asyncResult);
}
public static Task CreateTask(Socket socket, IPAddress ipAddress, int port)
{
return Task.Factory.FromAsync(beginConnect, endConnect, ipAddress, port, state: socket);
}
}
static class ProxySocketConnectionTaskFactory
{
static IAsyncResult beginConnect(IPAddress address, int port, AsyncCallback requestCallback,
object state)
{
return ((ProxySocket.ProxySocket)state).BeginConnect(address, port, requestCallback, state);
}
static void endConnect(IAsyncResult asyncResult)
{
((ProxySocket.ProxySocket)asyncResult.AsyncState).EndConnect(asyncResult);
}
public static Task CreateTask(ProxySocket.ProxySocket socket, IPAddress ipAddress, int port)
{
return Task.Factory.FromAsync(beginConnect, endConnect, ipAddress, port, state: socket);
}
}
} }
} }
...@@ -16,11 +16,11 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -16,11 +16,11 @@ namespace Titanium.Web.Proxy.Network.Tcp
{ {
public Guid Id { get; } = Guid.NewGuid(); public Guid Id { get; } = Guid.NewGuid();
internal TcpServerConnection(ProxyServer proxyServer, TcpClient tcpClient, HttpServerStream stream, internal TcpServerConnection(ProxyServer proxyServer, Socket tcpSocket, HttpServerStream stream,
string hostName, int port, bool isHttps, SslApplicationProtocol negotiatedApplicationProtocol, string hostName, int port, bool isHttps, SslApplicationProtocol negotiatedApplicationProtocol,
Version version, IExternalProxy? upStreamProxy, IPEndPoint? upStreamEndPoint, string cacheKey) Version version, IExternalProxy? upStreamProxy, IPEndPoint? upStreamEndPoint, string cacheKey)
{ {
TcpClient = tcpClient; TcpSocket = tcpSocket;
LastAccess = DateTime.Now; LastAccess = DateTime.Now;
this.proxyServer = proxyServer; this.proxyServer = proxyServer;
this.proxyServer.UpdateServerConnectionCount(true); this.proxyServer.UpdateServerConnectionCount(true);
...@@ -63,7 +63,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -63,7 +63,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <summary> /// <summary>
/// The TcpClient. /// The TcpClient.
/// </summary> /// </summary>
internal TcpClient TcpClient { get; } internal Socket TcpSocket { get; }
/// <summary> /// <summary>
/// Used to write lines to server /// Used to write lines to server
...@@ -98,7 +98,15 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -98,7 +98,15 @@ namespace Titanium.Web.Proxy.Network.Tcp
await Task.Delay(1000); await Task.Delay(1000);
proxyServer.UpdateServerConnectionCount(false); proxyServer.UpdateServerConnectionCount(false);
Stream.Dispose(); Stream.Dispose();
TcpClient.CloseSocket();
try
{
TcpSocket.Close();
}
catch
{
// ignore
}
}); });
} }
......
...@@ -359,12 +359,12 @@ namespace Titanium.Web.Proxy ...@@ -359,12 +359,12 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// Customize TcpClient used for client connection upon create. /// Customize TcpClient used for client connection upon create.
/// </summary> /// </summary>
public event AsyncEventHandler<TcpClient>? OnClientConnectionCreate; public event AsyncEventHandler<Socket>? OnClientConnectionCreate;
/// <summary> /// <summary>
/// Customize TcpClient used for server connection upon create. /// Customize TcpClient used for server connection upon create.
/// </summary> /// </summary>
public event AsyncEventHandler<TcpClient>? OnServerConnectionCreate; public event AsyncEventHandler<Socket>? OnServerConnectionCreate;
/// <summary> /// <summary>
/// Customize the minimum ThreadPool size (increase it on a server) /// Customize the minimum ThreadPool size (increase it on a server)
...@@ -733,12 +733,12 @@ namespace Titanium.Web.Proxy ...@@ -733,12 +733,12 @@ namespace Titanium.Web.Proxy
{ {
var endPoint = (ProxyEndPoint)asyn.AsyncState; var endPoint = (ProxyEndPoint)asyn.AsyncState;
TcpClient? tcpClient = null; Socket? tcpClient = null;
try try
{ {
// based on end point type call appropriate request handlers // based on end point type call appropriate request handlers
tcpClient = endPoint.Listener!.EndAcceptTcpClient(asyn); tcpClient = endPoint.Listener!.EndAcceptSocket(asyn);
tcpClient.NoDelay = NoDelay; tcpClient.NoDelay = NoDelay;
} }
catch (ObjectDisposedException) catch (ObjectDisposedException)
...@@ -784,19 +784,19 @@ namespace Titanium.Web.Proxy ...@@ -784,19 +784,19 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// Handle the client. /// Handle the client.
/// </summary> /// </summary>
/// <param name="tcpClient">The client.</param> /// <param name="tcpClientSocket">The client socket.</param>
/// <param name="endPoint">The proxy endpoint.</param> /// <param name="endPoint">The proxy endpoint.</param>
/// <returns>The task.</returns> /// <returns>The task.</returns>
private async Task handleClient(TcpClient tcpClient, ProxyEndPoint endPoint) private async Task handleClient(Socket tcpClientSocket, ProxyEndPoint endPoint)
{ {
tcpClient.ReceiveTimeout = ConnectionTimeOutSeconds * 1000; tcpClientSocket.ReceiveTimeout = ConnectionTimeOutSeconds * 1000;
tcpClient.SendTimeout = ConnectionTimeOutSeconds * 1000; tcpClientSocket.SendTimeout = ConnectionTimeOutSeconds * 1000;
tcpClient.LingerState = new LingerOption(true, TcpTimeWaitSeconds); tcpClientSocket.LingerState = new LingerOption(true, TcpTimeWaitSeconds);
await InvokeClientConnectionCreateEvent(tcpClient); await InvokeClientConnectionCreateEvent(tcpClientSocket);
using (var clientConnection = new TcpClientConnection(this, tcpClient)) using (var clientConnection = new TcpClientConnection(this, tcpClientSocket))
{ {
if (endPoint is TransparentProxyEndPoint tep) if (endPoint is TransparentProxyEndPoint tep)
{ {
...@@ -867,28 +867,28 @@ namespace Titanium.Web.Proxy ...@@ -867,28 +867,28 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// Invoke client tcp connection events if subscribed by API user. /// Invoke client tcp connection events if subscribed by API user.
/// </summary> /// </summary>
/// <param name="client">The TcpClient object.</param> /// <param name="clientSocket">The TcpClient object.</param>
/// <returns></returns> /// <returns></returns>
internal async Task InvokeClientConnectionCreateEvent(TcpClient client) internal async Task InvokeClientConnectionCreateEvent(Socket clientSocket)
{ {
// client connection created // client connection created
if (OnClientConnectionCreate != null) if (OnClientConnectionCreate != null)
{ {
await OnClientConnectionCreate.InvokeAsync(this, client, ExceptionFunc); await OnClientConnectionCreate.InvokeAsync(this, clientSocket, ExceptionFunc);
} }
} }
/// <summary> /// <summary>
/// Invoke server tcp connection events if subscribed by API user. /// Invoke server tcp connection events if subscribed by API user.
/// </summary> /// </summary>
/// <param name="client">The TcpClient object.</param> /// <param name="serverSocket">The Socket object.</param>
/// <returns></returns> /// <returns></returns>
internal async Task InvokeServerConnectionCreateEvent(TcpClient client) internal async Task InvokeServerConnectionCreateEvent(Socket serverSocket)
{ {
// server connection created // server connection created
if (OnServerConnectionCreate != null) if (OnServerConnectionCreate != null)
{ {
await OnServerConnectionCreate.InvokeAsync(this, client, ExceptionFunc); await OnServerConnectionCreate.InvokeAsync(this, serverSocket, ExceptionFunc);
} }
} }
......
/*
Copyright © 2002, The KPD-Team
All rights reserved.
http://www.mentalis.org/
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Neither the name of the KPD-Team, nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Net;
using System.Net.Sockets;
namespace Titanium.Web.Proxy.ProxySocket.Authentication
{
/// <summary>
/// Implements a SOCKS authentication scheme.
/// </summary>
/// <remarks>This is an abstract class; it must be inherited.</remarks>
internal abstract class AuthMethod
{
/// <summary>
/// Initializes an AuthMethod instance.
/// </summary>
/// <param name="server">The socket connection with the proxy server.</param>
public AuthMethod(Socket server)
{
Server = server;
}
/// <summary>
/// Authenticates the user.
/// </summary>
/// <exception cref="ProxyException">Authentication with the proxy server failed.</exception>
/// <exception cref="ProtocolViolationException">The proxy server uses an invalid protocol.</exception>
/// <exception cref="SocketException">An operating system error occurs while accessing the Socket.</exception>
/// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
public abstract void Authenticate();
/// <summary>
/// Authenticates the user asynchronously.
/// </summary>
/// <param name="callback">The method to call when the authentication is complete.</param>
/// <exception cref="ProxyException">Authentication with the proxy server failed.</exception>
/// <exception cref="ProtocolViolationException">The proxy server uses an invalid protocol.</exception>
/// <exception cref="SocketException">An operating system error occurs while accessing the Socket.</exception>
/// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
public abstract void BeginAuthenticate(HandShakeComplete callback);
/// <summary>
/// Gets or sets the socket connection with the proxy server.
/// </summary>
/// <value>The socket connection with the proxy server.</value>
protected Socket Server
{
get
{
return _server;
}
set
{
if (value == null)
throw new ArgumentNullException();
_server = value;
}
}
/// <summary>
/// Gets or sets a byt array that can be used to store data.
/// </summary>
/// <value>A byte array to store data.</value>
protected byte[] Buffer
{
get
{
return _buffer;
}
set
{
_buffer = value;
}
}
/// <summary>
/// Gets or sets the number of bytes that have been received from the remote proxy server.
/// </summary>
/// <value>An integer that holds the number of bytes that have been received from the remote proxy server.</value>
protected int Received
{
get
{
return _received;
}
set
{
_received = value;
}
}
// private variables
/// <summary>Holds the value of the Buffer property.</summary>
private byte[] _buffer;
/// <summary>Holds the value of the Server property.</summary>
private Socket _server;
/// <summary>Holds the address of the method to call when the proxy has authenticated the client.</summary>
protected HandShakeComplete CallBack;
/// <summary>Holds the value of the Received property.</summary>
private int _received;
}
}
/*
Copyright © 2002, The KPD-Team
All rights reserved.
http://www.mentalis.org/
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Neither the name of the KPD-Team, nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Net.Sockets;
namespace Titanium.Web.Proxy.ProxySocket.Authentication
{
/// <summary>
/// This class implements the 'No Authentication' scheme.
/// </summary>
internal sealed class AuthNone : AuthMethod
{
/// <summary>
/// Initializes an AuthNone instance.
/// </summary>
/// <param name="server">The socket connection with the proxy server.</param>
public AuthNone(Socket server) : base(server) { }
/// <summary>
/// Authenticates the user.
/// </summary>
public override void Authenticate()
{
return; // Do Nothing
}
/// <summary>
/// Authenticates the user asynchronously.
/// </summary>
/// <param name="callback">The method to call when the authentication is complete.</param>
/// <remarks>This method immediately calls the callback method.</remarks>
public override void BeginAuthenticate(HandShakeComplete callback)
{
callback(null);
}
}
}
/*
Copyright © 2002, The KPD-Team
All rights reserved.
http://www.mentalis.org/
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Neither the name of the KPD-Team, nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Net.Sockets;
using System.Text;
namespace Titanium.Web.Proxy.ProxySocket.Authentication
{
/// <summary>
/// This class implements the 'username/password authentication' scheme.
/// </summary>
internal sealed class AuthUserPass : AuthMethod
{
/// <summary>
/// Initializes a new AuthUserPass instance.
/// </summary>
/// <param name="server">The socket connection with the proxy server.</param>
/// <param name="user">The username to use.</param>
/// <param name="pass">The password to use.</param>
/// <exception cref="ArgumentNullException"><c>user</c> -or- <c>pass</c> is null.</exception>
public AuthUserPass(Socket server, string user, string pass) : base(server)
{
Username = user;
Password = pass;
}
/// <summary>
/// Creates an array of bytes that has to be sent if the user wants to authenticate with the username/password authentication scheme.
/// </summary>
/// <returns>An array of bytes that has to be sent if the user wants to authenticate with the username/password authentication scheme.</returns>
private byte[] GetAuthenticationBytes()
{
byte[] buffer = new byte[3 + Username.Length + Password.Length];
buffer[0] = 1;
buffer[1] = (byte)Username.Length;
Array.Copy(Encoding.ASCII.GetBytes(Username), 0, buffer, 2, Username.Length);
buffer[Username.Length + 2] = (byte)Password.Length;
Array.Copy(Encoding.ASCII.GetBytes(Password), 0, buffer, Username.Length + 3, Password.Length);
return buffer;
}
private int GetAuthenticationLength()
{
return 3 + Username.Length + Password.Length;
}
/// <summary>
/// Starts the authentication process.
/// </summary>
public override void Authenticate()
{
if (Server.Send(GetAuthenticationBytes()) < GetAuthenticationLength())
{
throw new SocketException(10054);
}
;
byte[] buffer = new byte[2];
int received = 0;
while (received != 2)
{
int recv = Server.Receive(buffer, received, 2 - received, SocketFlags.None);
if (recv == 0)
throw new SocketException(10054);
received += recv;
}
if (buffer[1] != 0)
{
Server.Close();
throw new ProxyException("Username/password combination rejected.");
}
return;
}
/// <summary>
/// Starts the asynchronous authentication process.
/// </summary>
/// <param name="callback">The method to call when the authentication is complete.</param>
public override void BeginAuthenticate(HandShakeComplete callback)
{
CallBack = callback;
Server.BeginSend(GetAuthenticationBytes(), 0, GetAuthenticationLength(), SocketFlags.None,
new AsyncCallback(this.OnSent), Server);
return;
}
/// <summary>
/// Called when the authentication bytes have been sent.
/// </summary>
/// <param name="ar">Stores state information for this asynchronous operation as well as any user-defined data.</param>
private void OnSent(IAsyncResult ar)
{
try
{
if (Server.EndSend(ar) < GetAuthenticationLength())
throw new SocketException(10054);
Buffer = new byte[2];
Server.BeginReceive(Buffer, 0, 2, SocketFlags.None, new AsyncCallback(this.OnReceive), Server);
}
catch (Exception e)
{
CallBack(e);
}
}
/// <summary>
/// Called when the socket received an authentication reply.
/// </summary>
/// <param name="ar">Stores state information for this asynchronous operation as well as any user-defined data.</param>
private void OnReceive(IAsyncResult ar)
{
try
{
int recv = Server.EndReceive(ar);
if (recv <= 0)
throw new SocketException(10054);
Received += recv;
if (Received == Buffer.Length)
if (Buffer[1] == 0)
CallBack(null);
else
throw new ProxyException("Username/password combination not accepted.");
else
Server.BeginReceive(Buffer, Received, Buffer.Length - Received, SocketFlags.None,
new AsyncCallback(this.OnReceive), Server);
}
catch (Exception e)
{
CallBack(e);
}
}
/// <summary>
/// Gets or sets the username to use when authenticating with the proxy server.
/// </summary>
/// <value>The username to use when authenticating with the proxy server.</value>
/// <exception cref="ArgumentNullException">The specified value is null.</exception>
private string Username
{
get
{
return _username;
}
set
{
_username = value ?? throw new ArgumentNullException();
}
}
/// <summary>
/// Gets or sets the password to use when authenticating with the proxy server.
/// </summary>
/// <value>The password to use when authenticating with the proxy server.</value>
/// <exception cref="ArgumentNullException">The specified value is null.</exception>
private string Password
{
get
{
return _password;
}
set
{
_password = value ?? throw new ArgumentNullException();
}
}
// private variables
/// <summary>Holds the value of the Username property.</summary>
private string _username;
/// <summary>Holds the value of the Password property.</summary>
private string _password;
}
}
/*
Copyright © 2002, The KPD-Team
All rights reserved.
http://www.mentalis.org/
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Neither the name of the KPD-Team, nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace Titanium.Web.Proxy.ProxySocket
{
/// <summary>
/// Implements the HTTPS (CONNECT) protocol.
/// </summary>
internal sealed class HttpsHandler : SocksHandler
{
/// <summary>
/// Initializes a new HttpsHandler instance.
/// </summary>
/// <param name="server">The socket connection with the proxy server.</param>
/// <exception cref="ArgumentNullException"><c>server</c> is null.</exception>
public HttpsHandler(Socket server) : this(server, "") { }
/// <summary>
/// Initializes a new HttpsHandler instance.
/// </summary>
/// <param name="server">The socket connection with the proxy server.</param>
/// <param name="user">The username to use.</param>
/// <exception cref="ArgumentNullException"><c>server</c> -or- <c>user</c> is null.</exception>
public HttpsHandler(Socket server, string user) : this(server, user, "") { }
/// <summary>
/// Initializes a new HttpsHandler instance.
/// </summary>
/// <param name="server">The socket connection with the proxy server.</param>
/// <param name="user">The username to use.</param>
/// <param name="pass">The password to use.</param>
/// <exception cref="ArgumentNullException"><c>server</c> -or- <c>user</c> -or- <c>pass</c> is null.</exception>
public HttpsHandler(Socket server, string user, string pass) : base(server, user)
{
Password = pass;
}
/// <summary>
/// Creates an array of bytes that has to be sent when the user wants to connect to a specific IPEndPoint.
/// </summary>
/// <returns>An array of bytes that has to be sent when the user wants to connect to a specific IPEndPoint.</returns>
private byte[] GetConnectBytes(string host, int port)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine(string.Format("CONNECT {0}:{1} HTTP/1.1", host, port));
sb.AppendLine(string.Format("Host: {0}:{1}", host, port));
if (!string.IsNullOrEmpty(Username))
{
string auth =
Convert.ToBase64String(Encoding.ASCII.GetBytes(String.Format("{0}:{1}", Username, Password)));
sb.AppendLine(string.Format("Proxy-Authorization: Basic {0}", auth));
}
sb.AppendLine();
byte[] buffer = Encoding.ASCII.GetBytes(sb.ToString());
return buffer;
}
/// <summary>
/// Verifies that proxy server successfully connected to requested host
/// </summary>
/// <param name="buffer">Input data array</param>
private void VerifyConnectHeader(byte[] buffer)
{
string header = Encoding.ASCII.GetString(buffer);
if ((!header.StartsWith("HTTP/1.1 ", StringComparison.OrdinalIgnoreCase) &&
!header.StartsWith("HTTP/1.0 ", StringComparison.OrdinalIgnoreCase)) || !header.EndsWith(" "))
throw new ProtocolViolationException();
string code = header.Substring(9, 3);
if (code != "200")
throw new ProxyException("Invalid HTTP status. Code: " + code);
}
/// <summary>
/// Starts negotiating with the SOCKS server.
/// </summary>
/// <param name="remoteEP">The IPEndPoint to connect to.</param>
/// <exception cref="ArgumentNullException"><c>remoteEP</c> is null.</exception>
/// <exception cref="ProxyException">The proxy rejected the request.</exception>
/// <exception cref="SocketException">An operating system error occurs while accessing the Socket.</exception>
/// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
/// <exception cref="ProtocolViolationException">The proxy server uses an invalid protocol.</exception>
public override void Negotiate(IPEndPoint remoteEP)
{
if (remoteEP == null)
throw new ArgumentNullException();
Negotiate(remoteEP.Address.ToString(), remoteEP.Port);
}
/// <summary>
/// Starts negotiating with the SOCKS server.
/// </summary>
/// <param name="host">The host to connect to.</param>
/// <param name="port">The port to connect to.</param>
/// <exception cref="ArgumentNullException"><c>host</c> is null.</exception>
/// <exception cref="ArgumentException"><c>port</c> is invalid.</exception>
/// <exception cref="ProxyException">The proxy rejected the request.</exception>
/// <exception cref="SocketException">An operating system error occurs while accessing the Socket.</exception>
/// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
/// <exception cref="ProtocolViolationException">The proxy server uses an invalid protocol.</exception>
public override void Negotiate(string host, int port)
{
if (host == null)
throw new ArgumentNullException();
if (port <= 0 || port > 65535 || host.Length > 255)
throw new ArgumentException();
byte[] buffer = GetConnectBytes(host, port);
if (Server.Send(buffer, 0, buffer.Length, SocketFlags.None) < buffer.Length)
{
throw new SocketException(10054);
}
buffer = ReadBytes(13);
VerifyConnectHeader(buffer);
// Read bytes 1 by 1 until we reach "\r\n\r\n"
int receivedNewlineChars = 0;
buffer = new byte[1];
while (receivedNewlineChars < 4)
{
int recv = Server.Receive(buffer, 0, 1, SocketFlags.None);
if (recv == 0)
{
throw new SocketException(10054);
}
byte b = buffer[0];
if (b == (receivedNewlineChars % 2 == 0 ? '\r' : '\n'))
receivedNewlineChars++;
else
receivedNewlineChars = b == '\r' ? 1 : 0;
}
}
/// <summary>
/// Starts negotiating asynchronously with the HTTPS server.
/// </summary>
/// <param name="remoteEP">An IPEndPoint that represents the remote device.</param>
/// <param name="callback">The method to call when the negotiation is complete.</param>
/// <param name="proxyEndPoint">The IPEndPoint of the HTTPS proxy server.</param>
/// <returns>An IAsyncProxyResult that references the asynchronous connection.</returns>
public override IAsyncProxyResult BeginNegotiate(IPEndPoint remoteEP, HandShakeComplete callback,
IPEndPoint proxyEndPoint)
{
return BeginNegotiate(remoteEP.Address.ToString(), remoteEP.Port, callback, proxyEndPoint);
}
/// <summary>
/// Starts negotiating asynchronously with the HTTPS server.
/// </summary>
/// <param name="host">The host to connect to.</param>
/// <param name="port">The port to connect to.</param>
/// <param name="callback">The method to call when the negotiation is complete.</param>
/// <param name="proxyEndPoint">The IPEndPoint of the HTTPS proxy server.</param>
/// <returns>An IAsyncProxyResult that references the asynchronous connection.</returns>
public override IAsyncProxyResult BeginNegotiate(string host, int port, HandShakeComplete callback,
IPEndPoint proxyEndPoint)
{
ProtocolComplete = callback;
Buffer = GetConnectBytes(host, port);
Server.BeginConnect(proxyEndPoint, new AsyncCallback(this.OnConnect), Server);
AsyncResult = new IAsyncProxyResult();
return AsyncResult;
}
/// <summary>
/// Called when the socket is connected to the remote server.
/// </summary>
/// <param name="ar">Stores state information for this asynchronous operation as well as any user-defined data.</param>
private void OnConnect(IAsyncResult ar)
{
try
{
Server.EndConnect(ar);
}
catch (Exception e)
{
ProtocolComplete(e);
return;
}
try
{
Server.BeginSend(Buffer, 0, Buffer.Length, SocketFlags.None, new AsyncCallback(this.OnConnectSent),
null);
}
catch (Exception e)
{
ProtocolComplete(e);
}
}
/// <summary>
/// Called when the connect request bytes have been sent.
/// </summary>
/// <param name="ar">Stores state information for this asynchronous operation as well as any user-defined data.</param>
private void OnConnectSent(IAsyncResult ar)
{
try
{
HandleEndSend(ar, Buffer.Length);
Buffer = new byte[13];
Received = 0;
Server.BeginReceive(Buffer, 0, 13, SocketFlags.None, new AsyncCallback(this.OnConnectReceive), Server);
}
catch (Exception e)
{
ProtocolComplete(e);
}
}
/// <summary>
/// Called when an connect reply has been received.
/// </summary>
/// <param name="ar">Stores state information for this asynchronous operation as well as any user-defined data.</param>
private void OnConnectReceive(IAsyncResult ar)
{
try
{
HandleEndReceive(ar);
}
catch (Exception e)
{
ProtocolComplete(e);
return;
}
try
{
if (Received < 13)
{
Server.BeginReceive(Buffer, Received, 13 - Received, SocketFlags.None,
new AsyncCallback(this.OnConnectReceive), Server);
}
else
{
VerifyConnectHeader(Buffer);
ReadUntilHeadersEnd(true);
}
}
catch (Exception e)
{
ProtocolComplete(e);
}
}
/// <summary>
/// Reads socket buffer byte by byte until we reach "\r\n\r\n".
/// </summary>
/// <param name="readFirstByte"></param>
private void ReadUntilHeadersEnd(bool readFirstByte)
{
while (Server.Available > 0 && _receivedNewlineChars < 4)
{
if (!readFirstByte)
readFirstByte = false;
else
{
int recv = Server.Receive(Buffer, 0, 1, SocketFlags.None);
if (recv == 0)
throw new SocketException(10054);
}
if (Buffer[0] == (_receivedNewlineChars % 2 == 0 ? '\r' : '\n'))
_receivedNewlineChars++;
else
_receivedNewlineChars = Buffer[0] == '\r' ? 1 : 0;
}
if (_receivedNewlineChars == 4)
{
ProtocolComplete(null);
}
else
{
Server.BeginReceive(Buffer, 0, 1, SocketFlags.None, new AsyncCallback(this.OnEndHeadersReceive),
Server);
}
}
// I think we should never reach this function in practice
// But let's define it just in case
/// <summary>
/// Called when additional headers have been received.
/// </summary>
/// <param name="ar">Stores state information for this asynchronous operation as well as any user-defined data.</param>
private void OnEndHeadersReceive(IAsyncResult ar)
{
try
{
HandleEndReceive(ar);
ReadUntilHeadersEnd(false);
}
catch (Exception e)
{
ProtocolComplete(e);
}
}
/// <summary>
/// Gets or sets the password to use when authenticating with the HTTPS server.
/// </summary>
/// <value>The password to use when authenticating with the HTTPS server.</value>
private string Password
{
get
{
return _password;
}
set
{
_password = value ?? throw new ArgumentNullException();
}
}
// private variables
/// <summary>Holds the value of the Password property.</summary>
private string _password;
/// <summary>Holds the count of newline characters received.</summary>
private int _receivedNewlineChars;
}
}
/*
Copyright © 2002, The KPD-Team
All rights reserved.
http://www.mentalis.org/
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Neither the name of the KPD-Team, nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Threading;
namespace Titanium.Web.Proxy.ProxySocket
{
/// <summary>
/// A class that implements the IAsyncResult interface. Objects from this class are returned by the BeginConnect method of the ProxySocket class.
/// </summary>
internal class IAsyncProxyResult : IAsyncResult
{
/// <summary>Initializes the internal variables of this object</summary>
/// <param name="stateObject">An object that contains state information for this request.</param>
internal IAsyncProxyResult(object stateObject = null)
{
_stateObject = stateObject;
_completed = false;
if (_waitHandle != null)
_waitHandle.Reset();
}
/// <summary>Initializes the internal variables of this object</summary>
internal void Reset()
{
_stateObject = null;
_completed = true;
if (_waitHandle != null)
_waitHandle.Set();
}
/// <summary>Gets a value that indicates whether the server has completed processing the call. It is illegal for the server to use any client supplied resources outside of the agreed upon sharing semantics after it sets the IsCompleted property to "true". Thus, it is safe for the client to destroy the resources after IsCompleted property returns "true".</summary>
/// <value>A boolean that indicates whether the server has completed processing the call.</value>
public bool IsCompleted
{
get
{
return _completed;
}
}
/// <summary>Gets a value that indicates whether the BeginXXXX call has been completed synchronously. If this is detected in the AsyncCallback delegate, it is probable that the thread that called BeginInvoke is the current thread.</summary>
/// <value>Returns false.</value>
public bool CompletedSynchronously
{
get
{
return false;
}
}
/// <summary>Gets an object that was passed as the state parameter of the BeginXXXX method call.</summary>
/// <value>The object that was passed as the state parameter of the BeginXXXX method call.</value>
public object AsyncState
{
get
{
return _stateObject;
}
}
/// <summary>
/// The AsyncWaitHandle property returns the WaitHandle that can use to perform a WaitHandle.WaitOne or WaitAny or WaitAll. The object which implements IAsyncResult need not derive from the System.WaitHandle classes directly. The WaitHandle wraps its underlying synchronization primitive and should be signaled after the call is completed. This enables the client to wait for the call to complete instead polling. The Runtime supplies a number of waitable objects that mirror Win32 synchronization primitives e.g. ManualResetEvent, AutoResetEvent and Mutex.
/// WaitHandle supplies methods that support waiting for such synchronization objects to become signaled with "any" or "all" semantics i.e. WaitHandle.WaitOne, WaitAny and WaitAll. Such methods are context aware to avoid deadlocks. The AsyncWaitHandle can be allocated eagerly or on demand. It is the choice of the IAsyncResult implementer.
///</summary>
/// <value>The WaitHandle associated with this asynchronous result.</value>
public WaitHandle AsyncWaitHandle
{
get
{
if (_waitHandle == null)
_waitHandle = new ManualResetEvent(false);
return _waitHandle;
}
}
// private variables
/// <summary>Used internally to represent the state of the asynchronous request</summary>
private bool _completed;
/// <summary>Holds the value of the StateObject property.</summary>
private object _stateObject;
/// <summary>Holds the value of the WaitHandle property.</summary>
private ManualResetEvent _waitHandle;
}
}
/*
Copyright © 2002, The KPD-Team
All rights reserved.
http://www.mentalis.org/
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Neither the name of the KPD-Team, nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
namespace Titanium.Web.Proxy.ProxySocket
{
/// <summary>
/// The exception that is thrown when a proxy error occurs.
/// </summary>
[Serializable]
internal class ProxyException : Exception
{
/// <summary>
/// Initializes a new instance of the ProxyException class.
/// </summary>
public ProxyException() : this("An error occured while talking to the proxy server.") { }
/// <summary>
/// Initializes a new instance of the ProxyException class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public ProxyException(string message) : base(message) { }
/// <summary>
/// Initializes a new instance of the ProxyException class.
/// </summary>
/// <param name="socks5Error">The error number returned by a SOCKS5 server.</param>
public ProxyException(int socks5Error) : this(ProxyException.Socks5ToString(socks5Error)) { }
/// <summary>
/// Converts a SOCKS5 error number to a human readable string.
/// </summary>
/// <param name="socks5Error">The error number returned by a SOCKS5 server.</param>
/// <returns>A string representation of the specified SOCKS5 error number.</returns>
public static string Socks5ToString(int socks5Error)
{
switch (socks5Error)
{
case 0:
return "Connection succeeded.";
case 1:
return "General SOCKS server failure.";
case 2:
return "Connection not allowed by ruleset.";
case 3:
return "Network unreachable.";
case 4:
return "Host unreachable.";
case 5:
return "Connection refused.";
case 6:
return "TTL expired.";
case 7:
return "Command not supported.";
case 8:
return "Address type not supported.";
default:
return "Unspecified SOCKS error.";
}
}
}
}
/*
Copyright © 2002, The KPD-Team
All rights reserved.
http://www.mentalis.org/
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Neither the name of the KPD-Team, nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Net;
using System.Net.Sockets;
// Implements a number of classes to allow Sockets to connect trough a firewall.
namespace Titanium.Web.Proxy.ProxySocket
{
/// <summary>
/// Specifies the type of proxy servers that an instance of the ProxySocket class can use.
/// </summary>
internal enum ProxyTypes
{
/// <summary>No proxy server; the ProxySocket object behaves exactly like an ordinary Socket object.</summary>
None,
/// <summary>A HTTPS (CONNECT) proxy server.</summary>
Https,
/// <summary>A SOCKS4[A] proxy server.</summary>
Socks4,
/// <summary>A SOCKS5 proxy server.</summary>
Socks5
}
/// <summary>
/// Implements a Socket class that can connect trough a SOCKS proxy server.
/// </summary>
/// <remarks>This class implements SOCKS4[A] and SOCKS5.<br>It does not, however, implement the BIND commands, so you cannot .</br></remarks>
internal class ProxySocket : Socket
{
/// <summary>
/// Initializes a new instance of the ProxySocket class.
/// </summary>
/// <param name="addressFamily">One of the AddressFamily values.</param>
/// <param name="socketType">One of the SocketType values.</param>
/// <param name="protocolType">One of the ProtocolType values.</param>
/// <exception cref="SocketException">The combination of addressFamily, socketType, and protocolType results in an invalid socket.</exception>
public ProxySocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType) : this(
addressFamily, socketType, protocolType, "")
{
}
/// <summary>
/// Initializes a new instance of the ProxySocket class.
/// </summary>
/// <param name="addressFamily">One of the AddressFamily values.</param>
/// <param name="socketType">One of the SocketType values.</param>
/// <param name="protocolType">One of the ProtocolType values.</param>
/// <param name="proxyUsername">The username to use when authenticating with the proxy server.</param>
/// <exception cref="SocketException">The combination of addressFamily, socketType, and protocolType results in an invalid socket.</exception>
/// <exception cref="ArgumentNullException"><c>proxyUsername</c> is null.</exception>
public ProxySocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType,
string proxyUsername) : this(addressFamily, socketType, protocolType, proxyUsername, "")
{
}
/// <summary>
/// Initializes a new instance of the ProxySocket class.
/// </summary>
/// <param name="addressFamily">One of the AddressFamily values.</param>
/// <param name="socketType">One of the SocketType values.</param>
/// <param name="protocolType">One of the ProtocolType values.</param>
/// <param name="proxyUsername">The username to use when authenticating with the proxy server.</param>
/// <param name="proxyPassword">The password to use when authenticating with the proxy server.</param>
/// <exception cref="SocketException">The combination of addressFamily, socketType, and protocolType results in an invalid socket.</exception>
/// <exception cref="ArgumentNullException"><c>proxyUsername</c> -or- <c>proxyPassword</c> is null.</exception>
public ProxySocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType,
string proxyUsername, string proxyPassword) : base(addressFamily, socketType, protocolType)
{
ProxyUser = proxyUsername;
ProxyPass = proxyPassword;
ToThrow = new InvalidOperationException();
}
/// <summary>
/// Establishes a connection to a remote device.
/// </summary>
/// <param name="remoteEP">An EndPoint that represents the remote device.</param>
/// <exception cref="ArgumentNullException">The remoteEP parameter is a null reference (Nothing in Visual Basic).</exception>
/// <exception cref="SocketException">An operating system error occurs while accessing the Socket.</exception>
/// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
/// <exception cref="ProxyException">An error occurred while talking to the proxy server.</exception>
public new void Connect(EndPoint remoteEP)
{
if (remoteEP == null)
throw new ArgumentNullException("<remoteEP> cannot be null.");
if (this.ProtocolType != ProtocolType.Tcp || ProxyType == ProxyTypes.None || ProxyEndPoint == null)
base.Connect(remoteEP);
else
{
base.Connect(ProxyEndPoint);
if (ProxyType == ProxyTypes.Https)
(new HttpsHandler(this, ProxyUser, ProxyPass)).Negotiate((IPEndPoint)remoteEP);
else if (ProxyType == ProxyTypes.Socks4)
(new Socks4Handler(this, ProxyUser)).Negotiate((IPEndPoint)remoteEP);
else if (ProxyType == ProxyTypes.Socks5)
(new Socks5Handler(this, ProxyUser, ProxyPass)).Negotiate((IPEndPoint)remoteEP);
}
}
/// <summary>
/// Establishes a connection to a remote device.
/// </summary>
/// <param name="host">The remote host to connect to.</param>
/// <param name="port">The remote port to connect to.</param>
/// <exception cref="ArgumentNullException">The host parameter is a null reference (Nothing in Visual Basic).</exception>
/// <exception cref="ArgumentException">The port parameter is invalid.</exception>
/// <exception cref="SocketException">An operating system error occurs while accessing the Socket.</exception>
/// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
/// <exception cref="ProxyException">An error occurred while talking to the proxy server.</exception>
/// <remarks>If you use this method with a SOCKS4 server, it will let the server resolve the hostname. Not all SOCKS4 servers support this 'remote DNS' though.</remarks>
public new void Connect(string host, int port)
{
if (host == null)
throw new ArgumentNullException("<host> cannot be null.");
if (port <= 0 || port > 65535)
throw new ArgumentException("Invalid port.");
if (this.ProtocolType != ProtocolType.Tcp || ProxyType == ProxyTypes.None || ProxyEndPoint == null)
base.Connect(new IPEndPoint(Dns.GetHostEntry(host).AddressList[0], port));
else
{
base.Connect(ProxyEndPoint);
if (ProxyType == ProxyTypes.Https)
(new HttpsHandler(this, ProxyUser, ProxyPass)).Negotiate(host, port);
else if (ProxyType == ProxyTypes.Socks4)
(new Socks4Handler(this, ProxyUser)).Negotiate(host, port);
else if (ProxyType == ProxyTypes.Socks5)
(new Socks5Handler(this, ProxyUser, ProxyPass)).Negotiate(host, port);
}
}
/// <summary>
/// Begins an asynchronous request for a connection to a network device.
/// </summary>
/// <param name="address">An EndPoint address that represents the remote device.</param>
/// <param name="port">An EndPoint port that represents the remote device.</param>
/// <param name="callback">The AsyncCallback delegate.</param>
/// <param name="state">An object that contains state information for this request.</param>
/// <returns>An IAsyncResult that references the asynchronous connection.</returns>
/// <exception cref="ArgumentNullException">The remoteEP parameter is a null reference (Nothing in Visual Basic).</exception>
/// <exception cref="SocketException">An operating system error occurs while creating the Socket.</exception>
/// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
public new IAsyncResult BeginConnect(IPAddress address, int port, AsyncCallback callback, object state)
{
var remoteEP = new IPEndPoint(address, port);
return BeginConnect(remoteEP, callback, state);
}
/// <summary>
/// Begins an asynchronous request for a connection to a network device.
/// </summary>
/// <param name="remoteEP">An EndPoint that represents the remote device.</param>
/// <param name="callback">The AsyncCallback delegate.</param>
/// <param name="state">An object that contains state information for this request.</param>
/// <returns>An IAsyncResult that references the asynchronous connection.</returns>
/// <exception cref="ArgumentNullException">The remoteEP parameter is a null reference (Nothing in Visual Basic).</exception>
/// <exception cref="SocketException">An operating system error occurs while creating the Socket.</exception>
/// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
public new IAsyncResult BeginConnect(EndPoint remoteEP, AsyncCallback callback, object state)
{
if (remoteEP == null)
throw new ArgumentNullException();
if (this.ProtocolType != ProtocolType.Tcp || ProxyType == ProxyTypes.None || ProxyEndPoint == null)
{
return base.BeginConnect(remoteEP, callback, state);
}
else
{
CallBack = callback;
if (ProxyType == ProxyTypes.Https)
{
AsyncResult = (new HttpsHandler(this, ProxyUser, ProxyPass)).BeginNegotiate((IPEndPoint)remoteEP,
new HandShakeComplete(this.OnHandShakeComplete), ProxyEndPoint);
return AsyncResult;
}
else if (ProxyType == ProxyTypes.Socks4)
{
AsyncResult = (new Socks4Handler(this, ProxyUser)).BeginNegotiate((IPEndPoint)remoteEP,
new HandShakeComplete(this.OnHandShakeComplete), ProxyEndPoint);
return AsyncResult;
}
else if (ProxyType == ProxyTypes.Socks5)
{
AsyncResult = (new Socks5Handler(this, ProxyUser, ProxyPass)).BeginNegotiate((IPEndPoint)remoteEP,
new HandShakeComplete(this.OnHandShakeComplete), ProxyEndPoint);
return AsyncResult;
}
return null;
}
}
/// <summary>
/// Begins an asynchronous request for a connection to a network device.
/// </summary>
/// <param name="host">The host to connect to.</param>
/// <param name="port">The port on the remote host to connect to.</param>
/// <param name="callback">The AsyncCallback delegate.</param>
/// <param name="state">An object that contains state information for this request.</param>
/// <returns>An IAsyncResult that references the asynchronous connection.</returns>
/// <exception cref="ArgumentNullException">The host parameter is a null reference (Nothing in Visual Basic).</exception>
/// <exception cref="ArgumentException">The port parameter is invalid.</exception>
/// <exception cref="SocketException">An operating system error occurs while creating the Socket.</exception>
/// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
public new IAsyncResult BeginConnect(string host, int port, AsyncCallback callback, object state)
{
if (host == null)
throw new ArgumentNullException();
if (port <= 0 || port > 65535)
throw new ArgumentException();
CallBack = callback;
if (this.ProtocolType != ProtocolType.Tcp || ProxyType == ProxyTypes.None || ProxyEndPoint == null)
{
RemotePort = port;
AsyncResult = BeginDns(host, new HandShakeComplete(this.OnHandShakeComplete));
return AsyncResult;
}
else
{
if (ProxyType == ProxyTypes.Https)
{
AsyncResult = (new HttpsHandler(this, ProxyUser, ProxyPass)).BeginNegotiate(host, port,
new HandShakeComplete(this.OnHandShakeComplete), ProxyEndPoint);
return AsyncResult;
}
else if (ProxyType == ProxyTypes.Socks4)
{
AsyncResult = (new Socks4Handler(this, ProxyUser)).BeginNegotiate(host, port,
new HandShakeComplete(this.OnHandShakeComplete), ProxyEndPoint);
return AsyncResult;
}
else if (ProxyType == ProxyTypes.Socks5)
{
AsyncResult = (new Socks5Handler(this, ProxyUser, ProxyPass)).BeginNegotiate(host, port,
new HandShakeComplete(this.OnHandShakeComplete), ProxyEndPoint);
return AsyncResult;
}
return null;
}
}
/// <summary>
/// Ends a pending asynchronous connection request.
/// </summary>
/// <param name="asyncResult">Stores state information for this asynchronous operation as well as any user-defined data.</param>
/// <exception cref="ArgumentNullException">The asyncResult parameter is a null reference (Nothing in Visual Basic).</exception>
/// <exception cref="ArgumentException">The asyncResult parameter was not returned by a call to the BeginConnect method.</exception>
/// <exception cref="SocketException">An operating system error occurs while accessing the Socket.</exception>
/// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
/// <exception cref="InvalidOperationException">EndConnect was previously called for the asynchronous connection.</exception>
/// <exception cref="ProxyException">The proxy server refused the connection.</exception>
public new void EndConnect(IAsyncResult asyncResult)
{
if (asyncResult == null)
throw new ArgumentNullException();
// In case we called Socket.BeginConnect() directly
if (!(asyncResult is IAsyncProxyResult))
{
base.EndConnect(asyncResult);
return;
}
if (!asyncResult.IsCompleted)
asyncResult.AsyncWaitHandle.WaitOne();
if (ToThrow != null)
throw ToThrow;
return;
}
/// <summary>
/// Begins an asynchronous request to resolve a DNS host name or IP address in dotted-quad notation to an IPAddress instance.
/// </summary>
/// <param name="host">The host to resolve.</param>
/// <param name="callback">The method to call when the hostname has been resolved.</param>
/// <returns>An IAsyncResult instance that references the asynchronous request.</returns>
/// <exception cref="SocketException">There was an error while trying to resolve the host.</exception>
internal IAsyncProxyResult BeginDns(string host, HandShakeComplete callback)
{
try
{
Dns.BeginGetHostEntry(host, new AsyncCallback(this.OnResolved), this);
return new IAsyncProxyResult();
}
catch
{
throw new SocketException();
}
}
/// <summary>
/// Called when the specified hostname has been resolved.
/// </summary>
/// <param name="asyncResult">The result of the asynchronous operation.</param>
private void OnResolved(IAsyncResult asyncResult)
{
try
{
IPHostEntry dns = Dns.EndGetHostEntry(asyncResult);
base.BeginConnect(new IPEndPoint(dns.AddressList[0], RemotePort), new AsyncCallback(this.OnConnect),
State);
}
catch (Exception e)
{
OnHandShakeComplete(e);
}
}
/// <summary>
/// Called when the Socket is connected to the remote host.
/// </summary>
/// <param name="asyncResult">The result of the asynchronous operation.</param>
private void OnConnect(IAsyncResult asyncResult)
{
try
{
base.EndConnect(asyncResult);
OnHandShakeComplete(null);
}
catch (Exception e)
{
OnHandShakeComplete(e);
}
}
/// <summary>
/// Called when the Socket has finished talking to the proxy server and is ready to relay data.
/// </summary>
/// <param name="error">The error to throw when the EndConnect method is called.</param>
private void OnHandShakeComplete(Exception error)
{
if (error != null)
this.Close();
ToThrow = error;
AsyncResult.Reset();
if (CallBack != null)
CallBack(AsyncResult);
}
/// <summary>
/// Gets or sets the EndPoint of the proxy server.
/// </summary>
/// <value>An IPEndPoint object that holds the IP address and the port of the proxy server.</value>
public IPEndPoint ProxyEndPoint
{
get
{
return _proxyEndPoint;
}
set
{
_proxyEndPoint = value;
}
}
/// <summary>
/// Gets or sets the type of proxy server to use.
/// </summary>
/// <value>One of the ProxyTypes values.</value>
public ProxyTypes ProxyType
{
get
{
return _proxyType;
}
set
{
_proxyType = value;
}
}
/// <summary>
/// Gets or sets a user-defined object.
/// </summary>
/// <value>The user-defined object.</value>
private object State
{
get
{
return _state;
}
set
{
_state = value;
}
}
/// <summary>
/// Gets or sets the username to use when authenticating with the proxy.
/// </summary>
/// <value>A string that holds the username that's used when authenticating with the proxy.</value>
/// <exception cref="ArgumentNullException">The specified value is null.</exception>
public string? ProxyUser
{
get
{
return _proxyUser;
}
set
{
_proxyUser = value ?? throw new ArgumentNullException();
}
}
/// <summary>
/// Gets or sets the password to use when authenticating with the proxy.
/// </summary>
/// <value>A string that holds the password that's used when authenticating with the proxy.</value>
/// <exception cref="ArgumentNullException">The specified value is null.</exception>
public string? ProxyPass
{
get
{
return _proxyPass;
}
set
{
_proxyPass = value ?? throw new ArgumentNullException();
}
}
/// <summary>
/// Gets or sets the asynchronous result object.
/// </summary>
/// <value>An instance of the IAsyncProxyResult class.</value>
private IAsyncProxyResult AsyncResult
{
get
{
return _asyncResult;
}
set
{
_asyncResult = value;
}
}
/// <summary>
/// Gets or sets the exception to throw when the EndConnect method is called.
/// </summary>
/// <value>An instance of the Exception class (or subclasses of Exception).</value>
private Exception ToThrow
{
get
{
return _toThrow;
}
set
{
_toThrow = value;
}
}
/// <summary>
/// Gets or sets the remote port the user wants to connect to.
/// </summary>
/// <value>An integer that specifies the port the user wants to connect to.</value>
private int RemotePort
{
get
{
return _remotePort;
}
set
{
_remotePort = value;
}
}
// private variables
/// <summary>Holds the value of the State property.</summary>
private object _state;
/// <summary>Holds the value of the ProxyEndPoint property.</summary>
private IPEndPoint _proxyEndPoint;
/// <summary>Holds the value of the ProxyType property.</summary>
private ProxyTypes _proxyType = ProxyTypes.None;
/// <summary>Holds the value of the ProxyUser property.</summary>
private string? _proxyUser;
/// <summary>Holds the value of the ProxyPass property.</summary>
private string? _proxyPass;
/// <summary>Holds a pointer to the method that should be called when the Socket is connected to the remote device.</summary>
private AsyncCallback CallBack;
/// <summary>Holds the value of the AsyncResult property.</summary>
private IAsyncProxyResult _asyncResult;
/// <summary>Holds the value of the ToThrow property.</summary>
private Exception _toThrow;
/// <summary>Holds the value of the RemotePort property.</summary>
private int _remotePort;
}
}
/*
Copyright © 2002, The KPD-Team
All rights reserved.
http://www.mentalis.org/
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Neither the name of the KPD-Team, nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace Titanium.Web.Proxy.ProxySocket
{
/// <summary>
/// Implements the SOCKS4[A] protocol.
/// </summary>
internal sealed class Socks4Handler : SocksHandler
{
/// <summary>
/// Initializes a new instance of the SocksHandler class.
/// </summary>
/// <param name="server">The socket connection with the proxy server.</param>
/// <param name="user">The username to use when authenticating with the server.</param>
/// <exception cref="ArgumentNullException"><c>server</c> -or- <c>user</c> is null.</exception>
public Socks4Handler(Socket server, string user) : base(server, user) { }
/// <summary>
/// Creates an array of bytes that has to be sent when the user wants to connect to a specific host/port combination.
/// </summary>
/// <param name="host">The host to connect to.</param>
/// <param name="port">The port to connect to.</param>
/// <returns>An array of bytes that has to be sent when the user wants to connect to a specific host/port combination.</returns>
/// <remarks>Resolving the host name will be done at server side. Do note that some SOCKS4 servers do not implement this functionality.</remarks>
/// <exception cref="ArgumentNullException"><c>host</c> is null.</exception>
/// <exception cref="ArgumentException"><c>port</c> is invalid.</exception>
private byte[] GetHostPortBytes(string host, int port)
{
if (host == null)
throw new ArgumentNullException();
if (port <= 0 || port > 65535)
throw new ArgumentException();
byte[] connect = new byte[10 + Username.Length + host.Length];
connect[0] = 4;
connect[1] = 1;
Array.Copy(PortToBytes(port), 0, connect, 2, 2);
connect[4] = connect[5] = connect[6] = 0;
connect[7] = 1;
Array.Copy(Encoding.ASCII.GetBytes(Username), 0, connect, 8, Username.Length);
connect[8 + Username.Length] = 0;
Array.Copy(Encoding.ASCII.GetBytes(host), 0, connect, 9 + Username.Length, host.Length);
connect[9 + Username.Length + host.Length] = 0;
return connect;
}
/// <summary>
/// Creates an array of bytes that has to be sent when the user wants to connect to a specific IPEndPoint.
/// </summary>
/// <param name="remoteEP">The IPEndPoint to connect to.</param>
/// <returns>An array of bytes that has to be sent when the user wants to connect to a specific IPEndPoint.</returns>
/// <exception cref="ArgumentNullException"><c>remoteEP</c> is null.</exception>
private byte[] GetEndPointBytes(IPEndPoint remoteEP)
{
if (remoteEP == null)
throw new ArgumentNullException();
byte[] connect = new byte[9 + Username.Length];
connect[0] = 4;
connect[1] = 1;
Array.Copy(PortToBytes(remoteEP.Port), 0, connect, 2, 2);
Array.Copy(remoteEP.Address.GetAddressBytes(), 0, connect, 4, 4);
Array.Copy(Encoding.ASCII.GetBytes(Username), 0, connect, 8, Username.Length);
connect[8 + Username.Length] = 0;
return connect;
}
/// <summary>
/// Starts negotiating with the SOCKS server.
/// </summary>
/// <param name="host">The host to connect to.</param>
/// <param name="port">The port to connect to.</param>
/// <exception cref="ArgumentNullException"><c>host</c> is null.</exception>
/// <exception cref="ArgumentException"><c>port</c> is invalid.</exception>
/// <exception cref="ProxyException">The proxy rejected the request.</exception>
/// <exception cref="SocketException">An operating system error occurs while accessing the Socket.</exception>
/// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
public override void Negotiate(string host, int port)
{
Negotiate(GetHostPortBytes(host, port));
}
/// <summary>
/// Starts negotiating with the SOCKS server.
/// </summary>
/// <param name="remoteEP">The IPEndPoint to connect to.</param>
/// <exception cref="ArgumentNullException"><c>remoteEP</c> is null.</exception>
/// <exception cref="ProxyException">The proxy rejected the request.</exception>
/// <exception cref="SocketException">An operating system error occurs while accessing the Socket.</exception>
/// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
public override void Negotiate(IPEndPoint remoteEP)
{
Negotiate(GetEndPointBytes(remoteEP));
}
/// <summary>
/// Starts negotiating with the SOCKS server.
/// </summary>
/// <param name="connect">The bytes to send when trying to authenticate.</param>
/// <exception cref="ArgumentNullException"><c>connect</c> is null.</exception>
/// <exception cref="ArgumentException"><c>connect</c> is too small.</exception>
/// <exception cref="ProxyException">The proxy rejected the request.</exception>
/// <exception cref="SocketException">An operating system error occurs while accessing the Socket.</exception>
/// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
private void Negotiate(byte[] connect)
{
if (connect == null)
throw new ArgumentNullException();
if (connect.Length < 2)
throw new ArgumentException();
if (Server.Send(connect) < connect.Length)
throw new SocketException(10054);
byte[] buffer = ReadBytes(8);
if (buffer[1] != 90)
{
Server.Close();
throw new ProxyException("Negotiation failed.");
}
}
/// <summary>
/// Starts negotiating asynchronously with a SOCKS proxy server.
/// </summary>
/// <param name="host">The remote server to connect to.</param>
/// <param name="port">The remote port to connect to.</param>
/// <param name="callback">The method to call when the connection has been established.</param>
/// <param name="proxyEndPoint">The IPEndPoint of the SOCKS proxy server.</param>
/// <returns>An IAsyncProxyResult that references the asynchronous connection.</returns>
public override IAsyncProxyResult BeginNegotiate(string host, int port, HandShakeComplete callback,
IPEndPoint proxyEndPoint)
{
ProtocolComplete = callback;
Buffer = GetHostPortBytes(host, port);
Server.BeginConnect(proxyEndPoint, new AsyncCallback(this.OnConnect), Server);
AsyncResult = new IAsyncProxyResult();
return AsyncResult;
}
/// <summary>
/// Starts negotiating asynchronously with a SOCKS proxy server.
/// </summary>
/// <param name="remoteEP">An IPEndPoint that represents the remote device.</param>
/// <param name="callback">The method to call when the connection has been established.</param>
/// <param name="proxyEndPoint">The IPEndPoint of the SOCKS proxy server.</param>
/// <returns>An IAsyncProxyResult that references the asynchronous connection.</returns>
public override IAsyncProxyResult BeginNegotiate(IPEndPoint remoteEP, HandShakeComplete callback,
IPEndPoint proxyEndPoint)
{
ProtocolComplete = callback;
Buffer = GetEndPointBytes(remoteEP);
Server.BeginConnect(proxyEndPoint, new AsyncCallback(this.OnConnect), Server);
AsyncResult = new IAsyncProxyResult();
return AsyncResult;
}
/// <summary>
/// Called when the Socket is connected to the remote proxy server.
/// </summary>
/// <param name="ar">Stores state information for this asynchronous operation as well as any user-defined data.</param>
private void OnConnect(IAsyncResult ar)
{
try
{
Server.EndConnect(ar);
}
catch (Exception e)
{
ProtocolComplete(e);
return;
}
try
{
Server.BeginSend(Buffer, 0, Buffer.Length, SocketFlags.None, new AsyncCallback(this.OnSent), Server);
}
catch (Exception e)
{
ProtocolComplete(e);
}
}
/// <summary>
/// Called when the Socket has sent the handshake data.
/// </summary>
/// <param name="ar">Stores state information for this asynchronous operation as well as any user-defined data.</param>
private void OnSent(IAsyncResult ar)
{
try
{
HandleEndSend(ar, Buffer.Length);
}
catch (Exception e)
{
ProtocolComplete(e);
return;
}
try
{
Buffer = new byte[8];
Received = 0;
Server.BeginReceive(Buffer, 0, Buffer.Length, SocketFlags.None, new AsyncCallback(this.OnReceive),
Server);
}
catch (Exception e)
{
ProtocolComplete(e);
}
}
/// <summary>
/// Called when the Socket has received a reply from the remote proxy server.
/// </summary>
/// <param name="ar">Stores state information for this asynchronous operation as well as any user-defined data.</param>
private void OnReceive(IAsyncResult ar)
{
try
{
HandleEndReceive(ar);
if (Received == 8)
{
if (Buffer[1] == 90)
ProtocolComplete(null);
else
{
Server.Close();
ProtocolComplete(new ProxyException("Negotiation failed."));
}
}
else
{
Server.BeginReceive(Buffer, Received, Buffer.Length - Received, SocketFlags.None,
new AsyncCallback(this.OnReceive), Server);
}
}
catch (Exception e)
{
ProtocolComplete(e);
}
}
}
}
/*
Copyright © 2002, The KPD-Team
All rights reserved.
http://www.mentalis.org/
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Neither the name of the KPD-Team, nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using Titanium.Web.Proxy.ProxySocket.Authentication;
namespace Titanium.Web.Proxy.ProxySocket
{
/// <summary>
/// Implements the SOCKS5 protocol.
/// </summary>
internal sealed class Socks5Handler : SocksHandler
{
/// <summary>
/// Initializes a new Socks5Handler instance.
/// </summary>
/// <param name="server">The socket connection with the proxy server.</param>
/// <exception cref="ArgumentNullException"><c>server</c> is null.</exception>
public Socks5Handler(Socket server) : this(server, "") { }
/// <summary>
/// Initializes a new Socks5Handler instance.
/// </summary>
/// <param name="server">The socket connection with the proxy server.</param>
/// <param name="user">The username to use.</param>
/// <exception cref="ArgumentNullException"><c>server</c> -or- <c>user</c> is null.</exception>
public Socks5Handler(Socket server, string user) : this(server, user, "") { }
/// <summary>
/// Initializes a new Socks5Handler instance.
/// </summary>
/// <param name="server">The socket connection with the proxy server.</param>
/// <param name="user">The username to use.</param>
/// <param name="pass">The password to use.</param>
/// <exception cref="ArgumentNullException"><c>server</c> -or- <c>user</c> -or- <c>pass</c> is null.</exception>
public Socks5Handler(Socket server, string user, string pass) : base(server, user)
{
Password = pass;
}
/// <summary>
/// Starts the synchronous authentication process.
/// </summary>
/// <exception cref="ProxyException">Authentication with the proxy server failed.</exception>
/// <exception cref="ProtocolViolationException">The proxy server uses an invalid protocol.</exception>
/// <exception cref="SocketException">An operating system error occurs while accessing the Socket.</exception>
/// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
private void Authenticate()
{
if (Server.Send(new byte[] { 5, 2, 0, 2 }) < 4)
throw new SocketException(10054);
byte[] buffer = ReadBytes(2);
if (buffer[1] == 255)
throw new ProxyException("No authentication method accepted.");
AuthMethod authenticate;
switch (buffer[1])
{
case 0:
authenticate = new AuthNone(Server);
break;
case 2:
authenticate = new AuthUserPass(Server, Username, Password);
break;
default:
throw new ProtocolViolationException();
}
authenticate.Authenticate();
}
/// <summary>
/// Creates an array of bytes that has to be sent when the user wants to connect to a specific host/port combination.
/// </summary>
/// <param name="host">The host to connect to.</param>
/// <param name="port">The port to connect to.</param>
/// <returns>An array of bytes that has to be sent when the user wants to connect to a specific host/port combination.</returns>
/// <exception cref="ArgumentNullException"><c>host</c> is null.</exception>
/// <exception cref="ArgumentException"><c>port</c> or <c>host</c> is invalid.</exception>
private byte[] GetHostPortBytes(string host, int port)
{
if (host == null)
throw new ArgumentNullException();
if (port <= 0 || port > 65535 || host.Length > 255)
throw new ArgumentException();
byte[] connect = new byte[7 + host.Length];
connect[0] = 5;
connect[1] = 1;
connect[2] = 0; //reserved
connect[3] = 3;
connect[4] = (byte)host.Length;
Array.Copy(Encoding.ASCII.GetBytes(host), 0, connect, 5, host.Length);
Array.Copy(PortToBytes(port), 0, connect, host.Length + 5, 2);
return connect;
}
/// <summary>
/// Creates an array of bytes that has to be sent when the user wants to connect to a specific IPEndPoint.
/// </summary>
/// <param name="remoteEP">The IPEndPoint to connect to.</param>
/// <returns>An array of bytes that has to be sent when the user wants to connect to a specific IPEndPoint.</returns>
/// <exception cref="ArgumentNullException"><c>remoteEP</c> is null.</exception>
private byte[] GetEndPointBytes(IPEndPoint remoteEP)
{
if (remoteEP == null)
throw new ArgumentNullException();
byte[] connect = new byte[10];
connect[0] = 5;
connect[1] = 1;
connect[2] = 0; //reserved
connect[3] = 1;
Array.Copy(remoteEP.Address.GetAddressBytes(), 0, connect, 4, 4);
Array.Copy(PortToBytes(remoteEP.Port), 0, connect, 8, 2);
return connect;
}
/// <summary>
/// Starts negotiating with the SOCKS server.
/// </summary>
/// <param name="host">The host to connect to.</param>
/// <param name="port">The port to connect to.</param>
/// <exception cref="ArgumentNullException"><c>host</c> is null.</exception>
/// <exception cref="ArgumentException"><c>port</c> is invalid.</exception>
/// <exception cref="ProxyException">The proxy rejected the request.</exception>
/// <exception cref="SocketException">An operating system error occurs while accessing the Socket.</exception>
/// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
/// <exception cref="ProtocolViolationException">The proxy server uses an invalid protocol.</exception>
public override void Negotiate(string host, int port)
{
Negotiate(GetHostPortBytes(host, port));
}
/// <summary>
/// Starts negotiating with the SOCKS server.
/// </summary>
/// <param name="remoteEP">The IPEndPoint to connect to.</param>
/// <exception cref="ArgumentNullException"><c>remoteEP</c> is null.</exception>
/// <exception cref="ProxyException">The proxy rejected the request.</exception>
/// <exception cref="SocketException">An operating system error occurs while accessing the Socket.</exception>
/// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
/// <exception cref="ProtocolViolationException">The proxy server uses an invalid protocol.</exception>
public override void Negotiate(IPEndPoint remoteEP)
{
Negotiate(GetEndPointBytes(remoteEP));
}
/// <summary>
/// Starts negotiating with the SOCKS server.
/// </summary>
/// <param name="connect">The bytes to send when trying to authenticate.</param>
/// <exception cref="ArgumentNullException"><c>connect</c> is null.</exception>
/// <exception cref="ArgumentException"><c>connect</c> is too small.</exception>
/// <exception cref="ProxyException">The proxy rejected the request.</exception>
/// <exception cref="SocketException">An operating system error occurs while accessing the Socket.</exception>
/// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
/// <exception cref="ProtocolViolationException">The proxy server uses an invalid protocol.</exception>
private void Negotiate(byte[] connect)
{
Authenticate();
if (Server.Send(connect) < connect.Length)
throw new SocketException(10054);
byte[] buffer = ReadBytes(4);
if (buffer[1] != 0)
{
Server.Close();
throw new ProxyException(buffer[1]);
}
switch (buffer[3])
{
case 1:
buffer = ReadBytes(6); //IPv4 address with port
break;
case 3:
buffer = ReadBytes(1);
buffer = ReadBytes(buffer[0] + 2); //domain name with port
break;
case 4:
buffer = ReadBytes(18); //IPv6 address with port
break;
default:
Server.Close();
throw new ProtocolViolationException();
}
}
/// <summary>
/// Starts negotiating asynchronously with the SOCKS server.
/// </summary>
/// <param name="host">The host to connect to.</param>
/// <param name="port">The port to connect to.</param>
/// <param name="callback">The method to call when the negotiation is complete.</param>
/// <param name="proxyEndPoint">The IPEndPoint of the SOCKS proxy server.</param>
/// <returns>An IAsyncProxyResult that references the asynchronous connection.</returns>
public override IAsyncProxyResult BeginNegotiate(string host, int port, HandShakeComplete callback,
IPEndPoint proxyEndPoint)
{
ProtocolComplete = callback;
HandShake = GetHostPortBytes(host, port);
Server.BeginConnect(proxyEndPoint, new AsyncCallback(this.OnConnect), Server);
AsyncResult = new IAsyncProxyResult();
return AsyncResult;
}
/// <summary>
/// Starts negotiating asynchronously with the SOCKS server.
/// </summary>
/// <param name="remoteEP">An IPEndPoint that represents the remote device.</param>
/// <param name="callback">The method to call when the negotiation is complete.</param>
/// <param name="proxyEndPoint">The IPEndPoint of the SOCKS proxy server.</param>
/// <returns>An IAsyncProxyResult that references the asynchronous connection.</returns>
public override IAsyncProxyResult BeginNegotiate(IPEndPoint remoteEP, HandShakeComplete callback,
IPEndPoint proxyEndPoint)
{
ProtocolComplete = callback;
HandShake = GetEndPointBytes(remoteEP);
Server.BeginConnect(proxyEndPoint, new AsyncCallback(this.OnConnect), Server);
AsyncResult = new IAsyncProxyResult();
return AsyncResult;
}
/// <summary>
/// Called when the socket is connected to the remote server.
/// </summary>
/// <param name="ar">Stores state information for this asynchronous operation as well as any user-defined data.</param>
private void OnConnect(IAsyncResult ar)
{
try
{
Server.EndConnect(ar);
}
catch (Exception e)
{
ProtocolComplete(e);
return;
}
try
{
Server.BeginSend(new byte[] { 5, 2, 0, 2 }, 0, 4, SocketFlags.None, new AsyncCallback(this.OnAuthSent),
Server);
}
catch (Exception e)
{
ProtocolComplete(e);
}
}
/// <summary>
/// Called when the authentication bytes have been sent.
/// </summary>
/// <param name="ar">Stores state information for this asynchronous operation as well as any user-defined data.</param>
private void OnAuthSent(IAsyncResult ar)
{
try
{
HandleEndSend(ar, 4);
}
catch (Exception e)
{
ProtocolComplete(e);
return;
}
try
{
Buffer = new byte[1024];
Received = 0;
Server.BeginReceive(Buffer, 0, Buffer.Length, SocketFlags.None, new AsyncCallback(this.OnAuthReceive),
Server);
}
catch (Exception e)
{
ProtocolComplete(e);
}
}
/// <summary>
/// Called when an authentication reply has been received.
/// </summary>
/// <param name="ar">Stores state information for this asynchronous operation as well as any user-defined data.</param>
private void OnAuthReceive(IAsyncResult ar)
{
try
{
HandleEndReceive(ar);
}
catch (Exception e)
{
ProtocolComplete(e);
return;
}
try
{
if (Received < 2)
{
Server.BeginReceive(Buffer, Received, Buffer.Length - Received, SocketFlags.None,
new AsyncCallback(this.OnAuthReceive), Server);
}
else
{
AuthMethod authenticate;
switch (Buffer[1])
{
case 0:
authenticate = new AuthNone(Server);
break;
case 2:
authenticate = new AuthUserPass(Server, Username, Password);
break;
default:
ProtocolComplete(new SocketException());
return;
}
authenticate.BeginAuthenticate(new HandShakeComplete(this.OnAuthenticated));
}
}
catch (Exception e)
{
ProtocolComplete(e);
}
}
/// <summary>
/// Called when the socket has been successfully authenticated with the server.
/// </summary>
/// <param name="e">The exception that has occurred while authenticating, or <em>null</em> if no error occurred.</param>
private void OnAuthenticated(Exception e)
{
if (e != null)
{
ProtocolComplete(e);
return;
}
try
{
Server.BeginSend(HandShake, 0, HandShake.Length, SocketFlags.None, new AsyncCallback(this.OnSent),
Server);
}
catch (Exception ex)
{
ProtocolComplete(ex);
}
}
/// <summary>
/// Called when the connection request has been sent.
/// </summary>
/// <param name="ar">Stores state information for this asynchronous operation as well as any user-defined data.</param>
private void OnSent(IAsyncResult ar)
{
try
{
HandleEndSend(ar, HandShake.Length);
}
catch (Exception e)
{
ProtocolComplete(e);
return;
}
try
{
Buffer = new byte[5];
Received = 0;
Server.BeginReceive(Buffer, 0, Buffer.Length, SocketFlags.None, new AsyncCallback(this.OnReceive),
Server);
}
catch (Exception e)
{
ProtocolComplete(e);
}
}
/// <summary>
/// Called when a connection reply has been received.
/// </summary>
/// <param name="ar">Stores state information for this asynchronous operation as well as any user-defined data.</param>
private void OnReceive(IAsyncResult ar)
{
try
{
HandleEndReceive(ar);
}
catch (Exception e)
{
ProtocolComplete(e);
return;
}
try
{
if (Received == Buffer.Length)
ProcessReply(Buffer);
else
Server.BeginReceive(Buffer, Received, Buffer.Length - Received, SocketFlags.None,
new AsyncCallback(this.OnReceive), Server);
}
catch (Exception e)
{
ProtocolComplete(e);
}
}
/// <summary>
/// Processes the received reply.
/// </summary>
/// <param name="buffer">The received reply</param>
/// <exception cref="ProtocolViolationException">The received reply is invalid.</exception>
private void ProcessReply(byte[] buffer)
{
switch (buffer[3])
{
case 1:
Buffer = new byte[5]; //IPv4 address with port - 1 byte
break;
case 3:
Buffer = new byte[buffer[4] + 2]; //domain name with port
break;
case 4:
buffer = new byte[17]; //IPv6 address with port - 1 byte
break;
default:
throw new ProtocolViolationException();
}
Received = 0;
Server.BeginReceive(Buffer, 0, Buffer.Length, SocketFlags.None, new AsyncCallback(this.OnReadLast), Server);
}
/// <summary>
/// Called when the last bytes are read from the socket.
/// </summary>
/// <param name="ar">Stores state information for this asynchronous operation as well as any user-defined data.</param>
private void OnReadLast(IAsyncResult ar)
{
try
{
HandleEndReceive(ar);
}
catch (Exception e)
{
ProtocolComplete(e);
return;
}
try
{
if (Received == Buffer.Length)
ProtocolComplete(null);
else
Server.BeginReceive(Buffer, Received, Buffer.Length - Received, SocketFlags.None,
new AsyncCallback(this.OnReadLast), Server);
}
catch (Exception e)
{
ProtocolComplete(e);
}
}
/// <summary>
/// Gets or sets the password to use when authenticating with the SOCKS5 server.
/// </summary>
/// <value>The password to use when authenticating with the SOCKS5 server.</value>
private string Password
{
get
{
return _password;
}
set
{
if (value == null)
throw new ArgumentNullException();
_password = value;
}
}
/// <summary>
/// Gets or sets the bytes to use when sending a connect request to the proxy server.
/// </summary>
/// <value>The array of bytes to use when sending a connect request to the proxy server.</value>
private byte[] HandShake
{
get
{
return _handShake;
}
set
{
_handShake = value;
}
}
// private variables
/// <summary>Holds the value of the Password property.</summary>
private string _password;
/// <summary>Holds the value of the HandShake property.</summary>
private byte[] _handShake;
}
}
/*
Copyright © 2002, The KPD-Team
All rights reserved.
http://www.mentalis.org/
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Neither the name of the KPD-Team, nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Net;
using System.Net.Sockets;
namespace Titanium.Web.Proxy.ProxySocket
{
/// <summary>
/// References the callback method to be called when the protocol negotiation is completed.
/// </summary>
internal delegate void HandShakeComplete(Exception error);
/// <summary>
/// Implements a specific version of the SOCKS protocol. This is an abstract class; it must be inherited.
/// </summary>
internal abstract class SocksHandler
{
/// <summary>
/// Initializes a new instance of the SocksHandler class.
/// </summary>
/// <param name="server">The socket connection with the proxy server.</param>
/// <param name="user">The username to use when authenticating with the server.</param>
/// <exception cref="ArgumentNullException"><c>server</c> -or- <c>user</c> is null.</exception>
public SocksHandler(Socket server, string user)
{
Server = server;
Username = user;
}
/// <summary>
/// Converts a port number to an array of bytes.
/// </summary>
/// <param name="port">The port to convert.</param>
/// <returns>An array of two bytes that represents the specified port.</returns>
protected byte[] PortToBytes(int port)
{
byte[] ret = new byte[2];
ret[0] = (byte)(port / 256);
ret[1] = (byte)(port % 256);
return ret;
}
/// <summary>
/// Converts an IP address to an array of bytes.
/// </summary>
/// <param name="address">The IP address to convert.</param>
/// <returns>An array of four bytes that represents the specified IP address.</returns>
protected byte[] AddressToBytes(long address)
{
byte[] ret = new byte[4];
ret[0] = (byte)(address % 256);
ret[1] = (byte)((address / 256) % 256);
ret[2] = (byte)((address / 65536) % 256);
ret[3] = (byte)(address / 16777216);
return ret;
}
/// <summary>
/// Reads a specified number of bytes from the Server socket.
/// </summary>
/// <param name="count">The number of bytes to return.</param>
/// <returns>An array of bytes.</returns>
/// <exception cref="ArgumentException">The number of bytes to read is invalid.</exception>
/// <exception cref="SocketException">An operating system error occurs while accessing the Socket.</exception>
/// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
protected byte[] ReadBytes(int count)
{
if (count <= 0)
throw new ArgumentException();
byte[] buffer = new byte[count];
int received = 0;
while (received != count)
{
int recv = Server.Receive(buffer, received, count - received, SocketFlags.None);
if (recv == 0)
{
throw new SocketException(10054);
}
received += recv;
}
return buffer;
}
/// <summary>
/// Reads number of received bytes and ensures that socket was not shut down
/// </summary>
/// <param name="ar">IAsyncResult for receive operation</param>
/// <returns></returns>
protected void HandleEndReceive(IAsyncResult ar)
{
int recv = Server.EndReceive(ar);
if (recv <= 0)
throw new SocketException(10054);
Received += recv;
}
/// <summary>
/// Verifies that whole buffer was sent successfully
/// </summary>
/// <param name="ar">IAsyncResult for receive operation</param>
/// <param name="expectedLength">Length of buffer that was sent</param>
/// <returns></returns>
protected void HandleEndSend(IAsyncResult ar, int expectedLength)
{
if (Server.EndSend(ar) < expectedLength)
throw new SocketException(10054);
}
/// <summary>
/// Gets or sets the socket connection with the proxy server.
/// </summary>
/// <value>A Socket object that represents the connection with the proxy server.</value>
/// <exception cref="ArgumentNullException">The specified value is null.</exception>
protected Socket Server
{
get
{
return _server;
}
set
{
if (value == null)
throw new ArgumentNullException();
_server = value;
}
}
/// <summary>
/// Gets or sets the username to use when authenticating with the proxy server.
/// </summary>
/// <value>A string that holds the username to use when authenticating with the proxy server.</value>
/// <exception cref="ArgumentNullException">The specified value is null.</exception>
protected string Username
{
get
{
return _username;
}
set
{
if (value == null)
throw new ArgumentNullException();
_username = value;
}
}
/// <summary>
/// Gets or sets the return value of the BeginConnect call.
/// </summary>
/// <value>An IAsyncProxyResult object that is the return value of the BeginConnect call.</value>
protected IAsyncProxyResult AsyncResult
{
get
{
return _asyncResult;
}
set
{
_asyncResult = value;
}
}
/// <summary>
/// Gets or sets a byte buffer.
/// </summary>
/// <value>An array of bytes.</value>
protected byte[] Buffer
{
get
{
return _buffer;
}
set
{
_buffer = value;
}
}
/// <summary>
/// Gets or sets the number of bytes that have been received from the remote proxy server.
/// </summary>
/// <value>An integer that holds the number of bytes that have been received from the remote proxy server.</value>
protected int Received
{
get
{
return _received;
}
set
{
_received = value;
}
}
// private variables
/// <summary>Holds the value of the Server property.</summary>
private Socket _server;
/// <summary>Holds the value of the Username property.</summary>
private string _username;
/// <summary>Holds the value of the AsyncResult property.</summary>
private IAsyncProxyResult _asyncResult;
/// <summary>Holds the value of the Buffer property.</summary>
private byte[] _buffer;
/// <summary>Holds the value of the Received property.</summary>
private int _received;
/// <summary>Holds the address of the method to call when the SOCKS protocol has been completed.</summary>
protected HandShakeComplete ProtocolComplete;
/// <summary>
/// Starts negotiating with a SOCKS proxy server.
/// </summary>
/// <param name="host">The remote server to connect to.</param>
/// <param name="port">The remote port to connect to.</param>
public abstract void Negotiate(string host, int port);
/// <summary>
/// Starts negotiating with a SOCKS proxy server.
/// </summary>
/// <param name="remoteEP">The remote endpoint to connect to.</param>
public abstract void Negotiate(IPEndPoint remoteEP);
/// <summary>
/// Starts negotiating asynchronously with a SOCKS proxy server.
/// </summary>
/// <param name="remoteEP">An IPEndPoint that represents the remote device. </param>
/// <param name="callback">The method to call when the connection has been established.</param>
/// <param name="proxyEndPoint">The IPEndPoint of the SOCKS proxy server.</param>
/// <returns>An IAsyncProxyResult that references the asynchronous connection.</returns>
public abstract IAsyncProxyResult BeginNegotiate(IPEndPoint remoteEP, HandShakeComplete callback,
IPEndPoint proxyEndPoint);
/// <summary>
/// Starts negotiating asynchronously with a SOCKS proxy server.
/// </summary>
/// <param name="host">The remote server to connect to.</param>
/// <param name="port">The remote port to connect to.</param>
/// <param name="callback">The method to call when the connection has been established.</param>
/// <param name="proxyEndPoint">The IPEndPoint of the SOCKS proxy server.</param>
/// <returns>An IAsyncProxyResult that references the asynchronous connection.</returns>
public abstract IAsyncProxyResult BeginNegotiate(string host, int port, HandShakeComplete callback,
IPEndPoint proxyEndPoint);
}
}
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