From 3ef3277fe58ded85554b5704ea14e41d1c46bdeb Mon Sep 17 00:00:00 2001 From: Ruri Date: Sat, 7 Sep 2024 22:09:13 +0200 Subject: [PATCH] Refactored RuriLib.Proxies and RuriLib.Proxies.Tests --- RuriLib.Proxies.Tests/ProxyClientTests.cs | 119 +++-- .../RuriLib.Proxies.Tests.csproj | 1 + RuriLib.Proxies/Clients/HttpProxyClient.cs | 198 ++++---- RuriLib.Proxies/Clients/NoProxyClient.cs | 50 +- RuriLib.Proxies/Clients/Socks4ProxyClient.cs | 205 ++++---- RuriLib.Proxies/Clients/Socks4aProxyClient.cs | 103 ++-- RuriLib.Proxies/Clients/Socks5ProxyClient.cs | 466 +++++++++--------- RuriLib.Proxies/Exceptions/ProxyException.cs | 39 +- RuriLib.Proxies/Helpers/HostHelper.cs | 71 ++- RuriLib.Proxies/Helpers/PortHelper.cs | 11 +- RuriLib.Proxies/ProxyClient.cs | 133 +++-- RuriLib.Proxies/ProxySettings.cs | 54 +- RuriLib.Proxies/RuriLib.Proxies.csproj | 1 + 13 files changed, 735 insertions(+), 716 deletions(-) diff --git a/RuriLib.Proxies.Tests/ProxyClientTests.cs b/RuriLib.Proxies.Tests/ProxyClientTests.cs index b0e602d7b..2889b3ace 100644 --- a/RuriLib.Proxies.Tests/ProxyClientTests.cs +++ b/RuriLib.Proxies.Tests/ProxyClientTests.cs @@ -8,82 +8,79 @@ using System.Threading; using RuriLib.Proxies.Exceptions; -namespace RuriLib.Proxies.Tests +namespace RuriLib.Proxies.Tests; + +public class ProxyClientTests { - public class ProxyClientTests + [Fact] + public async Task ConnectAsync_NoProxyClient_Http() { - [Fact] - public async Task ConnectAsync_NoProxyClient_Http() - { - var settings = new ProxySettings(); - var proxy = new NoProxyClient(settings); + var settings = new ProxySettings(); + var proxy = new NoProxyClient(settings); - var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); - var client = await proxy.ConnectAsync("example.com", 80, null, cts.Token); + var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + var client = await proxy.ConnectAsync("example.com", 80, null, cts.Token); - var response = await GetResponseAsync(client, BuildSampleGetRequest(), cts.Token); - Assert.Contains("Example Domain", response); - } + var response = await GetResponseAsync(client, BuildSampleGetRequest(), cts.Token); + Assert.Contains("Example Domain", response); + } - /* - [Fact] - public async Task ConnectAsync_HttpProxyClient_Http() - { - var settings = new ProxySettings() { Host = "127.0.0.1", Port = 8888 }; - var proxy = new HttpProxyClient(settings); + [Fact(Skip = "This test requires a local HTTP proxy server")] + public async Task ConnectAsync_HttpProxyClient_Http() + { + var settings = new ProxySettings { Host = "127.0.0.1", Port = 8888 }; + var proxy = new HttpProxyClient(settings); - var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); - var client = await proxy.ConnectAsync("example.com", 80, null, cts.Token); + var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + var client = await proxy.ConnectAsync("example.com", 80, null, cts.Token); - var response = await GetResponseAsync(client, BuildSampleGetRequest(), cts.Token); - Assert.Contains("Example Domain", response); - } - */ + var response = await GetResponseAsync(client, BuildSampleGetRequest(), cts.Token); + Assert.Contains("Example Domain", response); + } - [Fact] - public async Task ConnectAsync_HttpProxyClient_Invalid() - { - // Set an invalid proxy - var settings = new ProxySettings() { Host = "example.com", Port = 80 }; - var proxy = new HttpProxyClient(settings); + [Fact] + public async Task ConnectAsync_HttpProxyClient_Invalid() + { + // Set an invalid proxy + var settings = new ProxySettings { Host = "example.com", Port = 80 }; + var proxy = new HttpProxyClient(settings); - var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); - await Assert.ThrowsAsync(async () => await proxy.ConnectAsync("example.com", 80, null, cts.Token)); - } + var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + await Assert.ThrowsAsync(async () => await proxy.ConnectAsync("example.com", 80, null, cts.Token)); + } - private static async Task GetResponseAsync(TcpClient client, string request, - CancellationToken cancellationToken = default) - { - using var netStream = client.GetStream(); - using var memory = new MemoryStream(); + private static async Task GetResponseAsync(TcpClient client, string request, + CancellationToken cancellationToken = default) + { + await using var netStream = client.GetStream(); + using var memory = new MemoryStream(); - // Send the data - var requestBytes = Encoding.ASCII.GetBytes(request); - await netStream.WriteAsync(requestBytes.AsMemory(0, requestBytes.Length), cancellationToken); + // Send the data + var requestBytes = Encoding.ASCII.GetBytes(request); + await netStream.WriteAsync(requestBytes.AsMemory(0, requestBytes.Length), cancellationToken); - // Read the response - await netStream.CopyToAsync(memory, cancellationToken); - memory.Position = 0; - var data = memory.ToArray(); - return Encoding.UTF8.GetString(data); - } + // Read the response + await netStream.CopyToAsync(memory, cancellationToken); + memory.Position = 0; + var data = memory.ToArray(); + return Encoding.UTF8.GetString(data); + } - private static string BuildSampleGetRequest() + private static string BuildSampleGetRequest() + { + var requestLines = new[] { - var requestLines = new string[] - { - "GET / HTTP/1.1", - "Host: example.com", + "GET / HTTP/1.1", + "Host: example.com", - // We need this otherwise the server will default to Keep-Alive and - // not close the stream leaving us hanging... - "Connection: Close", - "Accept: */*", - string.Empty, - string.Empty - }; + // We need this otherwise the server will default to Keep-Alive and + // not close the stream leaving us hanging... + "Connection: Close", + "Accept: */*", + string.Empty, + string.Empty + }; - return string.Join("\r\n", requestLines); - } + return string.Join("\r\n", requestLines); } } diff --git a/RuriLib.Proxies.Tests/RuriLib.Proxies.Tests.csproj b/RuriLib.Proxies.Tests/RuriLib.Proxies.Tests.csproj index 53ba69fa8..b59bd60bf 100644 --- a/RuriLib.Proxies.Tests/RuriLib.Proxies.Tests.csproj +++ b/RuriLib.Proxies.Tests/RuriLib.Proxies.Tests.csproj @@ -4,6 +4,7 @@ net8.0 false + enable diff --git a/RuriLib.Proxies/Clients/HttpProxyClient.cs b/RuriLib.Proxies/Clients/HttpProxyClient.cs index 00c8a510f..39e32cd4b 100644 --- a/RuriLib.Proxies/Clients/HttpProxyClient.cs +++ b/RuriLib.Proxies/Clients/HttpProxyClient.cs @@ -9,140 +9,146 @@ using RuriLib.Proxies.Helpers; using RuriLib.Proxies.Exceptions; -namespace RuriLib.Proxies.Clients +namespace RuriLib.Proxies.Clients; + +/// +/// A client that provides proxies connections via HTTP proxies. +/// +public class HttpProxyClient : ProxyClient { /// - /// A client that provides proxies connections via HTTP proxies. + /// The HTTP version to send in the first line of the request to the proxy. + /// By default it's 1.1 + /// + public string ProtocolVersion { get; set; } = "1.1"; + + /// + /// Creates an HTTP proxy client given the proxy . /// - public class HttpProxyClient : ProxyClient + public HttpProxyClient(ProxySettings settings) : base(settings) { - /// - /// The HTTP version to send in the first line of the request to the proxy. - /// By default it's 1.1 - /// - public string ProtocolVersion { get; set; } = "1.1"; - - /// - /// Creates an HTTP proxy client given the proxy . - /// - public HttpProxyClient(ProxySettings settings) : base(settings) - { - } + } - /// - protected async override Task CreateConnectionAsync(TcpClient client, string destinationHost, int destinationPort, - CancellationToken cancellationToken = default) + /// + protected override async Task CreateConnectionAsync(TcpClient client, string destinationHost, int destinationPort, + CancellationToken cancellationToken = default) + { + ArgumentException.ThrowIfNullOrEmpty(destinationHost); + + if (!PortHelper.ValidateTcpPort(destinationPort)) { - if (string.IsNullOrEmpty(destinationHost)) - { - throw new ArgumentException(null, nameof(destinationHost)); - } + throw new ArgumentOutOfRangeException(nameof(destinationPort)); + } - if (!PortHelper.ValidateTcpPort(destinationPort)) - { - throw new ArgumentOutOfRangeException(nameof(destinationPort)); - } + if (client is not { Connected: true }) + { + throw new SocketException(); + } - if (client == null || !client.Connected) - { - throw new SocketException(); - } + HttpStatusCode statusCode; - HttpStatusCode statusCode; + try + { + var nStream = client.GetStream(); - try - { - var nStream = client.GetStream(); + await RequestConnectionAsync(nStream, destinationHost, destinationPort, cancellationToken).ConfigureAwait(false); + statusCode = await ReceiveResponseAsync(nStream, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + client.Close(); - await RequestConnectionAsync(nStream, destinationHost, destinationPort, cancellationToken).ConfigureAwait(false); - statusCode = await ReceiveResponseAsync(nStream, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) + if (ex is IOException || ex is SocketException) { - client.Close(); - - if (ex is IOException || ex is SocketException) - { - throw new ProxyException("Error while working with proxy", ex); - } - - throw; + throw new ProxyException("Error while working with proxy", ex); } - if (statusCode != HttpStatusCode.OK) - { - client.Close(); - throw new ProxyException("The proxy didn't reply with 200 OK"); - } + throw; } - private async Task RequestConnectionAsync(Stream nStream, string destinationHost, int destinationPort, - CancellationToken cancellationToken = default) + if (statusCode != HttpStatusCode.OK) { - var commandBuilder = new StringBuilder(); + client.Close(); + throw new ProxyException("The proxy didn't reply with 200 OK"); + } + } - commandBuilder.AppendFormat("CONNECT {0}:{1} HTTP/{2}\r\n{3}\r\n", destinationHost, destinationPort, ProtocolVersion, GenerateAuthorizationHeader()); + private async Task RequestConnectionAsync(Stream nStream, string destinationHost, int destinationPort, + CancellationToken cancellationToken = default) + { + var commandBuilder = new StringBuilder(); - var buffer = Encoding.ASCII.GetBytes(commandBuilder.ToString()); + commandBuilder.AppendFormat( + "CONNECT {0}:{1} HTTP/{2}\r\n{3}\r\n", + destinationHost, destinationPort, ProtocolVersion, GenerateAuthorizationHeader()); - await nStream.WriteAsync(buffer.AsMemory(0, buffer.Length), cancellationToken).ConfigureAwait(false); - } - - private string GenerateAuthorizationHeader() - { - if (Settings.Credentials == null || string.IsNullOrEmpty(Settings.Credentials.UserName)) - { - return string.Empty; - } + var buffer = Encoding.ASCII.GetBytes(commandBuilder.ToString()); - var data = Convert.ToBase64String(Encoding.UTF8.GetBytes( - $"{Settings.Credentials.UserName}:{Settings.Credentials.Password}")); + await nStream.WriteAsync(buffer.AsMemory(0, buffer.Length), cancellationToken).ConfigureAwait(false); + } - return $"Proxy-Authorization: Basic {data}\r\n"; + private string GenerateAuthorizationHeader() + { + if (Settings.Credentials == null || string.IsNullOrEmpty(Settings.Credentials.UserName)) + { + return string.Empty; } - private static async Task ReceiveResponseAsync(NetworkStream nStream, CancellationToken cancellationToken = default) - { - var buffer = new byte[50]; - var responseBuilder = new StringBuilder(); + var data = Convert.ToBase64String(Encoding.UTF8.GetBytes( + $"{Settings.Credentials.UserName}:{Settings.Credentials.Password}")); - using var waitCts = new CancellationTokenSource(TimeSpan.FromMilliseconds(nStream.ReadTimeout)); + return $"Proxy-Authorization: Basic {data}\r\n"; + } - while (!nStream.DataAvailable) - { - // Throw default exception if the operation was cancelled by the user - cancellationToken.ThrowIfCancellationRequested(); + private static async Task ReceiveResponseAsync(NetworkStream nStream, CancellationToken cancellationToken = default) + { + var buffer = new byte[50]; + var responseBuilder = new StringBuilder(); - // Throw a custom exception if we timed out - if (waitCts.Token.IsCancellationRequested) - throw new ProxyException("Timed out while waiting for data from proxy"); + using var waitCts = new CancellationTokenSource(TimeSpan.FromMilliseconds(nStream.ReadTimeout)); - await Task.Delay(100, cancellationToken).ConfigureAwait(false); - } + while (!nStream.DataAvailable) + { + // Throw default exception if the operation was cancelled by the user + cancellationToken.ThrowIfCancellationRequested(); - do + // Throw a custom exception if we timed out + if (waitCts.Token.IsCancellationRequested) { - var bytesRead = await nStream.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken).ConfigureAwait(false); - responseBuilder.Append(Encoding.ASCII.GetString(buffer, 0, bytesRead)); + throw new ProxyException("Timed out while waiting for data from proxy"); } - while (nStream.DataAvailable); - var response = responseBuilder.ToString(); + await Task.Delay(100, cancellationToken).ConfigureAwait(false); + } - if (response.Length == 0) - throw new ProxyException("Received empty response"); + do + { + var bytesRead = await nStream.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken).ConfigureAwait(false); + responseBuilder.Append(Encoding.ASCII.GetString(buffer, 0, bytesRead)); + } + while (nStream.DataAvailable); - // Check if the response is a correct HTTP response - var match = Regex.Match(response, "HTTP/[0-9\\.]* ([0-9]{3})"); + var response = responseBuilder.ToString(); - if (!match.Success) - throw new ProxyException("Received wrong HTTP response from proxy"); + if (response.Length == 0) + { + throw new ProxyException("Received empty response"); + } + + // Check if the response is a correct HTTP response + var match = Regex.Match(response, "HTTP/[0-9\\.]* ([0-9]{3})"); - if (!Enum.TryParse(match.Groups[1].Value, out HttpStatusCode statusCode)) - throw new ProxyException("Invalid HTTP status code"); + if (!match.Success) + { + throw new ProxyException("Received wrong HTTP response from proxy"); + } - return statusCode; + if (!Enum.TryParse(match.Groups[1].Value, out HttpStatusCode statusCode)) + { + throw new ProxyException("Invalid HTTP status code"); } + + return statusCode; } } diff --git a/RuriLib.Proxies/Clients/NoProxyClient.cs b/RuriLib.Proxies/Clients/NoProxyClient.cs index 43362e24b..f6bd13bc8 100644 --- a/RuriLib.Proxies/Clients/NoProxyClient.cs +++ b/RuriLib.Proxies/Clients/NoProxyClient.cs @@ -4,41 +4,37 @@ using System.Threading; using System.Threading.Tasks; -namespace RuriLib.Proxies.Clients +namespace RuriLib.Proxies.Clients; + +/// +/// A dummy client that does not proxy the connection. +/// +public class NoProxyClient : ProxyClient { /// - /// A dummy client that does not proxy the connection. + /// Provides unproxied connections. /// - public class NoProxyClient : ProxyClient + public NoProxyClient(ProxySettings? settings = null) : base(settings ?? new ProxySettings()) { - /// - /// Provides unproxied connections. - /// - public NoProxyClient(ProxySettings settings = null) : base(settings ?? new ProxySettings()) - { - - } - /// - protected override Task CreateConnectionAsync(TcpClient client, string destinationHost, int destinationPort, - CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(destinationHost)) - { - throw new ArgumentException(null, nameof(destinationHost)); - } + } - if (!PortHelper.ValidateTcpPort(destinationPort)) - { - throw new ArgumentOutOfRangeException(nameof(destinationPort)); - } + /// + protected override Task CreateConnectionAsync(TcpClient client, string destinationHost, int destinationPort, + CancellationToken cancellationToken = default) + { + ArgumentException.ThrowIfNullOrEmpty(destinationHost); - if (client == null || !client.Connected) - { - throw new SocketException(); - } + if (!PortHelper.ValidateTcpPort(destinationPort)) + { + throw new ArgumentOutOfRangeException(nameof(destinationPort)); + } - return Task.CompletedTask; + if (client is not { Connected: true }) + { + throw new SocketException(); } + + return Task.CompletedTask; } } diff --git a/RuriLib.Proxies/Clients/Socks4ProxyClient.cs b/RuriLib.Proxies/Clients/Socks4ProxyClient.cs index 7622eb0fe..617ff0b6e 100644 --- a/RuriLib.Proxies/Clients/Socks4ProxyClient.cs +++ b/RuriLib.Proxies/Clients/Socks4ProxyClient.cs @@ -9,133 +9,134 @@ using System.Threading.Tasks; using System.Threading; -namespace RuriLib.Proxies.Clients +namespace RuriLib.Proxies.Clients; + +internal static class Socks4Constants { - static internal class Socks4Constants - { - public const byte VersionNumber = 4; - public const byte CommandConnect = 0x01; - public const byte CommandBind = 0x02; - public const byte CommandReplyRequestGranted = 0x5a; - public const byte CommandReplyRequestRejectedOrFailed = 0x5b; - public const byte CommandReplyRequestRejectedCannotConnectToIdentd = 0x5c; - public const byte CommandReplyRequestRejectedDifferentIdentd = 0x5d; - } + public const byte VersionNumber = 4; + public const byte CommandConnect = 0x01; + public const byte CommandBind = 0x02; + public const byte CommandReplyRequestGranted = 0x5a; + public const byte CommandReplyRequestRejectedOrFailed = 0x5b; + public const byte CommandReplyRequestRejectedCannotConnectToIdentd = 0x5c; + public const byte CommandReplyRequestRejectedDifferentIdentd = 0x5d; +} +/// +/// A client that provides proxies connections via SOCKS4 proxies. +/// +public class Socks4ProxyClient : ProxyClient +{ /// - /// A client that provides proxies connections via SOCKS4 proxies. + /// Creates an SOCKS4 proxy client given the proxy . /// - public class Socks4ProxyClient : ProxyClient + public Socks4ProxyClient(ProxySettings settings) : base(settings) { - /// - /// Creates an SOCKS4 proxy client given the proxy . - /// - public Socks4ProxyClient(ProxySettings settings) : base(settings) - { - - } - - /// - protected async override Task CreateConnectionAsync(TcpClient client, string destinationHost, int destinationPort, - CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(destinationHost)) - { - throw new ArgumentException(null, nameof(destinationHost)); - } - if (!PortHelper.ValidateTcpPort(destinationPort)) - { - throw new ArgumentOutOfRangeException(nameof(destinationPort)); - } - - if (client == null || !client.Connected) - { - throw new SocketException(); - } - - try - { - await RequestConnectionAsync(client.GetStream(), CommandConnect, destinationHost, destinationPort, cancellationToken) - .ConfigureAwait(false); - } - catch (Exception ex) - { - client.Close(); + } - if (ex is IOException || ex is SocketException) - { - throw new ProxyException("Error while working with proxy", ex); - } + /// + protected override async Task CreateConnectionAsync(TcpClient client, string destinationHost, int destinationPort, + CancellationToken cancellationToken = default) + { + ArgumentException.ThrowIfNullOrEmpty(destinationHost); - throw; - } + if (!PortHelper.ValidateTcpPort(destinationPort)) + { + throw new ArgumentOutOfRangeException(nameof(destinationPort)); } - /// - /// Requests SOCKS4 connection. - /// - protected async virtual Task RequestConnectionAsync(NetworkStream nStream, byte command, string destinationHost, int destinationPort, - CancellationToken cancellationToken = default) + if (client is not { Connected: true }) { - var dstIp = await HostHelper.GetIPAddressBytesAsync(destinationHost); - var dstPort = HostHelper.GetPortBytes(destinationPort); + throw new SocketException(); + } - var userId = Array.Empty(); + try + { + await RequestConnectionAsync(client.GetStream(), CommandConnect, destinationHost, destinationPort, cancellationToken) + .ConfigureAwait(false); + } + catch (Exception ex) + { + client.Close(); - // Set the credentials if needed - if (Settings.Credentials != null && !string.IsNullOrEmpty(Settings.Credentials.UserName)) + if (ex is IOException or SocketException) { - userId = Encoding.ASCII.GetBytes(Settings.Credentials.UserName); + throw new ProxyException("Error while working with proxy", ex); } - // REQUEST GRANT - // +----+----+----+----+----+----+----+----+----+----+....+----+ - // | VN | CD | DSTPORT | DSTIP | USERID |NULL| - // +----+----+----+----+----+----+----+----+----+----+....+----+ - // 1 1 2 4 variable 1 - var request = new byte[9 + userId.Length]; + throw; + } + } - request[0] = VersionNumber; - request[1] = command; - dstPort.CopyTo(request, 2); - dstIp.CopyTo(request, 4); - userId.CopyTo(request, 8); - request[8 + userId.Length] = 0x00; + /// + /// Requests SOCKS4 connection. + /// + protected virtual async Task RequestConnectionAsync(NetworkStream nStream, byte command, string destinationHost, int destinationPort, + CancellationToken cancellationToken = default) + { + var dstIp = await HostHelper.GetIpAddressBytesAsync(destinationHost); + var dstPort = HostHelper.GetPortBytes(destinationPort); - await nStream.WriteAsync(request.AsMemory(0, request.Length), cancellationToken).ConfigureAwait(false); + var userId = Array.Empty(); - // READ RESPONSE - // +----+----+----+----+----+----+----+----+ - // | VN | CD | DSTPORT | DSTIP | - // +----+----+----+----+----+----+----+----+ - // 1 1 2 4 - var response = new byte[8]; + // Set the credentials if needed + if (Settings.Credentials != null && !string.IsNullOrEmpty(Settings.Credentials.UserName)) + { + userId = Encoding.ASCII.GetBytes(Settings.Credentials.UserName); + } - await nStream.ReadAsync(response.AsMemory(0, response.Length), cancellationToken).ConfigureAwait(false); + // REQUEST GRANT + // +----+----+----+----+----+----+----+----+----+----+....+----+ + // | VN | CD | DSTPORT | DSTIP | USERID |NULL| + // +----+----+----+----+----+----+----+----+----+----+....+----+ + // 1 1 2 4 variable 1 + var request = new byte[9 + userId.Length]; + + request[0] = VersionNumber; + request[1] = command; + dstPort.CopyTo(request, 2); + dstIp.CopyTo(request, 4); + userId.CopyTo(request, 8); + request[8 + userId.Length] = 0x00; + + await nStream.WriteAsync(request.AsMemory(0, request.Length), cancellationToken).ConfigureAwait(false); + + // READ RESPONSE + // +----+----+----+----+----+----+----+----+ + // | VN | CD | DSTPORT | DSTIP | + // +----+----+----+----+----+----+----+----+ + // 1 1 2 4 + var response = new byte[8]; + + var bytesRead = await nStream.ReadAsync(response.AsMemory(0, response.Length), cancellationToken).ConfigureAwait(false); + + if (bytesRead != response.Length) + { + throw new ProxyException("The proxy server did not respond correctly"); + } - var reply = response[1]; + var reply = response[1]; - if (reply != CommandReplyRequestGranted) - { - HandleCommandError(reply); - } + if (reply != CommandReplyRequestGranted) + { + HandleCommandError(reply); } + } - /// - /// Handles a command error. - /// - protected static void HandleCommandError(byte command) + /// + /// Handles a command error. + /// + protected static void HandleCommandError(byte command) + { + var errorMessage = command switch { - var errorMessage = command switch - { - CommandReplyRequestRejectedOrFailed => "Request rejected or failed", - CommandReplyRequestRejectedCannotConnectToIdentd => "Request rejected: cannot connect to identd", - CommandReplyRequestRejectedDifferentIdentd => "Request rejected: different identd", - _ => "Unknown socks error" - }; + CommandReplyRequestRejectedOrFailed => "Request rejected or failed", + CommandReplyRequestRejectedCannotConnectToIdentd => "Request rejected: cannot connect to identd", + CommandReplyRequestRejectedDifferentIdentd => "Request rejected: different identd", + _ => "Unknown socks error" + }; - throw new ProxyException(errorMessage); - } + throw new ProxyException(errorMessage); } } diff --git a/RuriLib.Proxies/Clients/Socks4aProxyClient.cs b/RuriLib.Proxies/Clients/Socks4aProxyClient.cs index 10092dced..9b2d7632c 100644 --- a/RuriLib.Proxies/Clients/Socks4aProxyClient.cs +++ b/RuriLib.Proxies/Clients/Socks4aProxyClient.cs @@ -3,73 +3,78 @@ using System.Net.Sockets; using System.Threading.Tasks; using System.Threading; +using RuriLib.Proxies.Exceptions; using RuriLib.Proxies.Helpers; using static RuriLib.Proxies.Clients.Socks4Constants; -namespace RuriLib.Proxies.Clients +namespace RuriLib.Proxies.Clients; + +/// +/// A client that provides proxies connections via SOCKS4a proxies. +/// +public class Socks4aProxyClient : Socks4ProxyClient { /// - /// A client that provides proxies connections via SOCKS4a proxies. + /// Creates an SOCKS4a proxy client given the proxy . /// - public class Socks4aProxyClient : Socks4ProxyClient + public Socks4aProxyClient(ProxySettings settings) : base(settings) { - /// - /// Creates an SOCKS4a proxy client given the proxy . - /// - public Socks4aProxyClient(ProxySettings settings) : base(settings) - { - } + } - /// - protected async override Task RequestConnectionAsync(NetworkStream nStream, byte command, string destinationHost, int destinationPort, - CancellationToken cancellationToken = default) - { - var dstPort = HostHelper.GetPortBytes(destinationPort); - var userId = Array.Empty(); + /// + protected override async Task RequestConnectionAsync(NetworkStream nStream, byte command, string destinationHost, int destinationPort, + CancellationToken cancellationToken = default) + { + var dstPort = HostHelper.GetPortBytes(destinationPort); + var userId = Array.Empty(); - if (Settings.Credentials != null && !string.IsNullOrEmpty(Settings.Credentials.UserName)) - { - userId = Encoding.ASCII.GetBytes(Settings.Credentials.UserName); - } + if (Settings.Credentials != null && !string.IsNullOrEmpty(Settings.Credentials.UserName)) + { + userId = Encoding.ASCII.GetBytes(Settings.Credentials.UserName); + } - var dstAddr = Encoding.ASCII.GetBytes(destinationHost); + var dstAddr = Encoding.ASCII.GetBytes(destinationHost); - // REQUEST GRANT - // +----+----+----+----+----+----+----+----+----+----+....+----+----+----+....+----+ - // | VN | CD | DSTPORT | DSTIP | USERID |NULL| DSTADDR |NULL| - // +----+----+----+----+----+----+----+----+----+----+....+----+----+----+....+----+ - // 1 1 2 4 variable 1 variable 1 - var request = new byte[10 + userId.Length + dstAddr.Length]; + // REQUEST GRANT + // +----+----+----+----+----+----+----+----+----+----+....+----+----+----+....+----+ + // | VN | CD | DSTPORT | DSTIP | USERID |NULL| DSTADDR |NULL| + // +----+----+----+----+----+----+----+----+----+----+....+----+----+----+....+----+ + // 1 1 2 4 variable 1 variable 1 + var request = new byte[10 + userId.Length + dstAddr.Length]; - request[0] = VersionNumber; - request[1] = command; - dstPort.CopyTo(request, 2); - byte[] dstIp = { 0, 0, 0, 1 }; - dstIp.CopyTo(request, 4); - userId.CopyTo(request, 8); - request[8 + userId.Length] = 0x00; - dstAddr.CopyTo(request, 9 + userId.Length); - request[9 + userId.Length + dstAddr.Length] = 0x00; + request[0] = VersionNumber; + request[1] = command; + dstPort.CopyTo(request, 2); + byte[] dstIp = { 0, 0, 0, 1 }; + dstIp.CopyTo(request, 4); + userId.CopyTo(request, 8); + request[8 + userId.Length] = 0x00; + dstAddr.CopyTo(request, 9 + userId.Length); + request[9 + userId.Length + dstAddr.Length] = 0x00; - await nStream.WriteAsync(request.AsMemory(0, request.Length), cancellationToken).ConfigureAwait(false); + await nStream.WriteAsync(request.AsMemory(0, request.Length), cancellationToken).ConfigureAwait(false); - // READ RESPONSE - // +----+----+----+----+----+----+----+----+ - // | VN | CD | DSTPORT | DSTIP | - // +----+----+----+----+----+----+----+----+ - // 1 1 2 4 - var response = new byte[8]; + // READ RESPONSE + // +----+----+----+----+----+----+----+----+ + // | VN | CD | DSTPORT | DSTIP | + // +----+----+----+----+----+----+----+----+ + // 1 1 2 4 + var response = new byte[8]; - await nStream.ReadAsync(response.AsMemory(0, 8), cancellationToken).ConfigureAwait(false); + var bytesRead = await nStream.ReadAsync(response.AsMemory(0, 8), cancellationToken).ConfigureAwait(false); - var reply = response[1]; + if (bytesRead != response.Length) + { + throw new ProxyException("The proxy server did not respond correctly"); + } + + var reply = response[1]; - // Если запрос не выполнен. - if (reply != CommandReplyRequestGranted) - { - HandleCommandError(reply); - } + // Если запрос не выполнен. + if (reply != CommandReplyRequestGranted) + { + HandleCommandError(reply); } } } diff --git a/RuriLib.Proxies/Clients/Socks5ProxyClient.cs b/RuriLib.Proxies/Clients/Socks5ProxyClient.cs index 26085be7b..9b6ce73bf 100644 --- a/RuriLib.Proxies/Clients/Socks5ProxyClient.cs +++ b/RuriLib.Proxies/Clients/Socks5ProxyClient.cs @@ -9,271 +9,287 @@ using System.Threading.Tasks; using static RuriLib.Proxies.Clients.Socks5Constants; -namespace RuriLib.Proxies.Clients +namespace RuriLib.Proxies.Clients; + +internal static class Socks5Constants { - static internal class Socks5Constants - { - public const byte VersionNumber = 5; - public const byte Reserved = 0x00; - public const byte AuthMethodNoAuthenticationRequired = 0x00; - public const byte AuthMethodGssapi = 0x01; - public const byte AuthMethodUsernamePassword = 0x02; - public const byte AuthMethodIanaAssignedRangeBegin = 0x03; - public const byte AuthMethodIanaAssignedRangeEnd = 0x7f; - public const byte AuthMethodReservedRangeBegin = 0x80; - public const byte AuthMethodReservedRangeEnd = 0xfe; - public const byte AuthMethodReplyNoAcceptableMethods = 0xff; - public const byte CommandConnect = 0x01; - public const byte CommandBind = 0x02; - public const byte CommandUdpAssociate = 0x03; - public const byte CommandReplySucceeded = 0x00; - public const byte CommandReplyGeneralSocksServerFailure = 0x01; - public const byte CommandReplyConnectionNotAllowedByRuleset = 0x02; - public const byte CommandReplyNetworkUnreachable = 0x03; - public const byte CommandReplyHostUnreachable = 0x04; - public const byte CommandReplyConnectionRefused = 0x05; - public const byte CommandReplyTTLExpired = 0x06; - public const byte CommandReplyCommandNotSupported = 0x07; - public const byte CommandReplyAddressTypeNotSupported = 0x08; - public const byte AddressTypeIPV4 = 0x01; - public const byte AddressTypeDomainName = 0x03; - public const byte AddressTypeIPV6 = 0x04; - } + public const byte VersionNumber = 5; + public const byte Reserved = 0x00; + public const byte AuthMethodNoAuthenticationRequired = 0x00; + public const byte AuthMethodGssapi = 0x01; + public const byte AuthMethodUsernamePassword = 0x02; + public const byte AuthMethodIanaAssignedRangeBegin = 0x03; + public const byte AuthMethodIanaAssignedRangeEnd = 0x7f; + public const byte AuthMethodReservedRangeBegin = 0x80; + public const byte AuthMethodReservedRangeEnd = 0xfe; + public const byte AuthMethodReplyNoAcceptableMethods = 0xff; + public const byte CommandConnect = 0x01; + public const byte CommandBind = 0x02; + public const byte CommandUdpAssociate = 0x03; + public const byte CommandReplySucceeded = 0x00; + public const byte CommandReplyGeneralSocksServerFailure = 0x01; + public const byte CommandReplyConnectionNotAllowedByRuleset = 0x02; + public const byte CommandReplyNetworkUnreachable = 0x03; + public const byte CommandReplyHostUnreachable = 0x04; + public const byte CommandReplyConnectionRefused = 0x05; + public const byte CommandReplyTTLExpired = 0x06; + public const byte CommandReplyCommandNotSupported = 0x07; + public const byte CommandReplyAddressTypeNotSupported = 0x08; + public const byte AddressTypeIPV4 = 0x01; + public const byte AddressTypeDomainName = 0x03; + public const byte AddressTypeIPV6 = 0x04; +} +/// +/// A client that provides proxies connections via SOCKS5 proxies. +/// +public class Socks5ProxyClient : ProxyClient +{ /// - /// A client that provides proxies connections via SOCKS5 proxies. + /// Creates an SOCKS5 proxy client given the proxy . /// - public class Socks5ProxyClient : ProxyClient + public Socks5ProxyClient(ProxySettings settings) : base(settings) { - /// - /// Creates an SOCKS5 proxy client given the proxy . - /// - public Socks5ProxyClient(ProxySettings settings) : base(settings) - { + } + + /// + protected override async Task CreateConnectionAsync(TcpClient client, string destinationHost, int destinationPort, + CancellationToken cancellationToken = default) + { + ArgumentException.ThrowIfNullOrEmpty(destinationHost); + + if (!PortHelper.ValidateTcpPort(destinationPort)) + { + throw new ArgumentOutOfRangeException(nameof(destinationPort)); } - /// - protected async override Task CreateConnectionAsync(TcpClient client, string destinationHost, int destinationPort, - CancellationToken cancellationToken = default) + if (client is not { Connected: true }) { - if (string.IsNullOrEmpty(destinationHost)) - { - throw new ArgumentException(null, nameof(destinationHost)); - } + throw new SocketException(); + } - if (!PortHelper.ValidateTcpPort(destinationPort)) - { - throw new ArgumentOutOfRangeException(nameof(destinationPort)); - } + try + { + var nStream = client.GetStream(); - if (client == null || !client.Connected) - { - throw new SocketException(); - } + await NegotiateAsync(nStream, cancellationToken).ConfigureAwait(false); + await RequestConnectionAsync(nStream, CommandConnect, destinationHost, destinationPort, cancellationToken) + .ConfigureAwait(false); + } + catch (Exception ex) + { + client.Close(); - try + if (ex is IOException or SocketException) { - var nStream = client.GetStream(); - - await NegotiateAsync(nStream, cancellationToken).ConfigureAwait(false); - await RequestConnectionAsync(nStream, CommandConnect, destinationHost, destinationPort, cancellationToken) - .ConfigureAwait(false); + throw new ProxyException("Error while working with proxy", ex); } - catch (Exception ex) - { - client.Close(); - if (ex is IOException || ex is SocketException) - { - throw new ProxyException("Error while working with proxy", ex); - } - - throw; - } + throw; } + } - private async Task NegotiateAsync(NetworkStream nStream, CancellationToken cancellationToken = default) + private async Task NegotiateAsync(NetworkStream nStream, CancellationToken cancellationToken = default) + { + var authMethod = Settings.Credentials != null && !string.IsNullOrEmpty(Settings.Credentials.UserName) + ? AuthMethodUsernamePassword + : AuthMethodNoAuthenticationRequired; + + // INITIATE NEGOTIATION + // +----+----------+----------+ + // |VER | NMETHODS | METHODS | + // +----+----------+----------+ + // | 1 | 1 | 1 to 255 | + // +----+----------+----------+ + var request = new byte[3]; + + request[0] = VersionNumber; + request[1] = 1; + request[2] = authMethod; + + await nStream.WriteAsync(request.AsMemory(0, request.Length), cancellationToken).ConfigureAwait(false); + + // READ RESPONSE + // +----+--------+ + // |VER | METHOD | + // +----+--------+ + // | 1 | 1 | + // +----+--------+ + var response = new byte[2]; + + var bytesRead = await nStream.ReadAsync(response.AsMemory(0, response.Length), cancellationToken).ConfigureAwait(false); + + if (bytesRead != response.Length) { - var authMethod = Settings.Credentials != null && !string.IsNullOrEmpty(Settings.Credentials.UserName) - ? AuthMethodUsernamePassword - : AuthMethodNoAuthenticationRequired; - - // INITIATE NEGOTIATION - // +----+----------+----------+ - // |VER | NMETHODS | METHODS | - // +----+----------+----------+ - // | 1 | 1 | 1 to 255 | - // +----+----------+----------+ - var request = new byte[3]; - - request[0] = VersionNumber; - request[1] = 1; - request[2] = authMethod; - - await nStream.WriteAsync(request.AsMemory(0, request.Length), cancellationToken).ConfigureAwait(false); - - // READ RESPONSE - // +----+--------+ - // |VER | METHOD | - // +----+--------+ - // | 1 | 1 | - // +----+--------+ - var response = new byte[2]; - - await nStream.ReadAsync(response.AsMemory(0, response.Length), cancellationToken).ConfigureAwait(false); + throw new ProxyException("The proxy server did not respond correctly"); + } - var reply = response[1]; + var reply = response[1]; - if (authMethod == AuthMethodUsernamePassword && reply == AuthMethodUsernamePassword) - { - await SendUsernameAndPasswordAsync(nStream, cancellationToken).ConfigureAwait(false); - } - else if (reply != CommandReplySucceeded) - { - HandleCommandError(reply); - } + if (authMethod == AuthMethodUsernamePassword && reply == AuthMethodUsernamePassword) + { + await SendUsernameAndPasswordAsync(nStream, cancellationToken).ConfigureAwait(false); + } + else if (reply != CommandReplySucceeded) + { + HandleCommandError(reply); } + } - private async Task SendUsernameAndPasswordAsync(NetworkStream nStream, CancellationToken cancellationToken = default) + private async Task SendUsernameAndPasswordAsync(NetworkStream nStream, CancellationToken cancellationToken = default) + { + if (Settings.Credentials is null) { - var uname = string.IsNullOrEmpty(Settings.Credentials.UserName) - ? Array.Empty() - : Encoding.ASCII.GetBytes(Settings.Credentials.UserName); - - var passwd = string.IsNullOrEmpty(Settings.Credentials.Password) - ? Array.Empty() - : Encoding.ASCII.GetBytes(Settings.Credentials.Password); - - // SEND CREDENTIALS - // +----+------+----------+------+----------+ - // |VER | ULEN | UNAME | PLEN | PASSWD | - // +----+------+----------+------+----------+ - // | 1 | 1 | 1 to 255 | 1 | 1 to 255 | - // +----+------+----------+------+----------+ - var request = new byte[uname.Length + passwd.Length + 3]; - - request[0] = 1; - request[1] = (byte)uname.Length; - uname.CopyTo(request, 2); - request[2 + uname.Length] = (byte)passwd.Length; - passwd.CopyTo(request, 3 + uname.Length); - - await nStream.WriteAsync(request.AsMemory(0, request.Length), cancellationToken).ConfigureAwait(false); - - // READ RESPONSE - // +----+--------+ - // |VER | STATUS | - // +----+--------+ - // | 1 | 1 | - // +----+--------+ - var response = new byte[2]; - - await nStream.ReadAsync(response.AsMemory(0, response.Length), cancellationToken).ConfigureAwait(false); - - var reply = response[1]; - - if (reply != CommandReplySucceeded) - { - throw new ProxyException("Unable to authenticate proxy-server"); - } + throw new ProxyException("No credentials provided"); } + + var uname = string.IsNullOrEmpty(Settings.Credentials.UserName) + ? [] + : Encoding.ASCII.GetBytes(Settings.Credentials.UserName); + + var passwd = string.IsNullOrEmpty(Settings.Credentials.Password) + ? [] + : Encoding.ASCII.GetBytes(Settings.Credentials.Password); + + // SEND CREDENTIALS + // +----+------+----------+------+----------+ + // |VER | ULEN | UNAME | PLEN | PASSWD | + // +----+------+----------+------+----------+ + // | 1 | 1 | 1 to 255 | 1 | 1 to 255 | + // +----+------+----------+------+----------+ + var request = new byte[uname.Length + passwd.Length + 3]; + + request[0] = 1; + request[1] = (byte)uname.Length; + uname.CopyTo(request, 2); + request[2 + uname.Length] = (byte)passwd.Length; + passwd.CopyTo(request, 3 + uname.Length); + + await nStream.WriteAsync(request.AsMemory(0, request.Length), cancellationToken).ConfigureAwait(false); + + // READ RESPONSE + // +----+--------+ + // |VER | STATUS | + // +----+--------+ + // | 1 | 1 | + // +----+--------+ + var response = new byte[2]; + + var bytesRead = await nStream.ReadAsync(response.AsMemory(0, response.Length), cancellationToken).ConfigureAwait(false); + + if (bytesRead != response.Length) + { + throw new ProxyException("The proxy server did not respond correctly"); + } + + var reply = response[1]; - private static async Task RequestConnectionAsync(NetworkStream nStream, byte command, string destinationHost, int destinationPort, - CancellationToken cancellationToken = default) + if (reply != CommandReplySucceeded) { - var aTyp = GetAddressType(destinationHost); - var dstAddr = GetHostAddressBytes(aTyp, destinationHost); - var dstPort = HostHelper.GetPortBytes(destinationPort); - - // REQUEST GRANT - // +----+-----+-------+------+----------+----------+ - // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | - // +----+-----+-------+------+----------+----------+ - // | 1 | 1 | X'00' | 1 | Variable | 2 | - // +----+-----+-------+------+----------+----------+ - var request = new byte[4 + dstAddr.Length + 2]; - - request[0] = VersionNumber; - request[1] = command; - request[2] = Reserved; - request[3] = aTyp; - dstAddr.CopyTo(request, 4); - dstPort.CopyTo(request, 4 + dstAddr.Length); - - await nStream.WriteAsync(request.AsMemory(0, request.Length), cancellationToken).ConfigureAwait(false); - - // READ RESPONSE - // +----+-----+-------+------+----------+----------+ - // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | - // +----+-----+-------+------+----------+----------+ - // | 1 | 1 | X'00' | 1 | Variable | 2 | - // +----+-----+-------+------+----------+----------+ - var response = new byte[255]; - - await nStream.ReadAsync(response.AsMemory(0, response.Length), cancellationToken).ConfigureAwait(false); - - var reply = response[1]; - if (reply != CommandReplySucceeded) - { - HandleCommandError(reply); - } + throw new ProxyException("Unable to authenticate proxy-server"); } + } - private static byte GetAddressType(string host) + private static async Task RequestConnectionAsync(NetworkStream nStream, byte command, string destinationHost, int destinationPort, + CancellationToken cancellationToken = default) + { + var aTyp = GetAddressType(destinationHost); + var dstAddr = GetHostAddressBytes(aTyp, destinationHost); + var dstPort = HostHelper.GetPortBytes(destinationPort); + + // REQUEST GRANT + // +----+-----+-------+------+----------+----------+ + // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | + // +----+-----+-------+------+----------+----------+ + // | 1 | 1 | X'00' | 1 | Variable | 2 | + // +----+-----+-------+------+----------+----------+ + var request = new byte[4 + dstAddr.Length + 2]; + + request[0] = VersionNumber; + request[1] = command; + request[2] = Reserved; + request[3] = aTyp; + dstAddr.CopyTo(request, 4); + dstPort.CopyTo(request, 4 + dstAddr.Length); + + await nStream.WriteAsync(request.AsMemory(0, request.Length), cancellationToken).ConfigureAwait(false); + + // READ RESPONSE + // +----+-----+-------+------+----------+----------+ + // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | + // +----+-----+-------+------+----------+----------+ + // | 1 | 1 | X'00' | 1 | Variable | 2 | + // +----+-----+-------+------+----------+----------+ + var response = new byte[255]; + + var bytesRead = await nStream.ReadAsync(response.AsMemory(0, response.Length), cancellationToken).ConfigureAwait(false); + + if (bytesRead != response.Length) + { + throw new ProxyException("The proxy server did not respond correctly"); + } + + var reply = response[1]; + if (reply != CommandReplySucceeded) { + HandleCommandError(reply); + } + } - if (!IPAddress.TryParse(host, out var ipAddr)) - { - return AddressTypeDomainName; - } + private static byte GetAddressType(string host) + { - return ipAddr.AddressFamily switch - { - AddressFamily.InterNetwork => AddressTypeIPV4, - AddressFamily.InterNetworkV6 => AddressTypeIPV6, - _ => throw new ProxyException(string.Format("Not supported address type {0}", host)) - }; + if (!IPAddress.TryParse(host, out var ipAddr)) + { + return AddressTypeDomainName; } - private static void HandleCommandError(byte command) + return ipAddr.AddressFamily switch { - var errorMessage = command switch - { - AuthMethodReplyNoAcceptableMethods => "Auth failed: not acceptable method", - CommandReplyGeneralSocksServerFailure => "General socks server failure", - CommandReplyConnectionNotAllowedByRuleset => "Connection not allowed by ruleset", - CommandReplyNetworkUnreachable => "Network unreachable", - CommandReplyHostUnreachable => "Host unreachable", - CommandReplyConnectionRefused => "Connection refused", - CommandReplyTTLExpired => "TTL Expired", - CommandReplyCommandNotSupported => "Command not supported", - CommandReplyAddressTypeNotSupported => "Address type not supported", - _ => "Unknown socks error" - }; - - throw new ProxyException(errorMessage); - } + AddressFamily.InterNetwork => AddressTypeIPV4, + AddressFamily.InterNetworkV6 => AddressTypeIPV6, + _ => throw new ProxyException($"Not supported address type {host}") + }; + } - private static byte[] GetHostAddressBytes(byte addressType, string host) + private static void HandleCommandError(byte command) + { + var errorMessage = command switch { - switch (addressType) - { - case AddressTypeIPV4: - case AddressTypeIPV6: - return IPAddress.Parse(host).GetAddressBytes(); + AuthMethodReplyNoAcceptableMethods => "Auth failed: not acceptable method", + CommandReplyGeneralSocksServerFailure => "General socks server failure", + CommandReplyConnectionNotAllowedByRuleset => "Connection not allowed by ruleset", + CommandReplyNetworkUnreachable => "Network unreachable", + CommandReplyHostUnreachable => "Host unreachable", + CommandReplyConnectionRefused => "Connection refused", + CommandReplyTTLExpired => "TTL Expired", + CommandReplyCommandNotSupported => "Command not supported", + CommandReplyAddressTypeNotSupported => "Address type not supported", + _ => "Unknown socks error" + }; + + throw new ProxyException(errorMessage); + } - case AddressTypeDomainName: - var bytes = new byte[host.Length + 1]; + private static byte[] GetHostAddressBytes(byte addressType, string host) + { + switch (addressType) + { + case AddressTypeIPV4: + case AddressTypeIPV6: + return IPAddress.Parse(host).GetAddressBytes(); - bytes[0] = (byte)host.Length; - Encoding.ASCII.GetBytes(host).CopyTo(bytes, 1); + case AddressTypeDomainName: + var bytes = new byte[host.Length + 1]; - return bytes; + bytes[0] = (byte)host.Length; + Encoding.ASCII.GetBytes(host).CopyTo(bytes, 1); - default: - return null; - } + return bytes; + + default: + throw new ProxyException($"Not supported address type {host}"); } } } diff --git a/RuriLib.Proxies/Exceptions/ProxyException.cs b/RuriLib.Proxies/Exceptions/ProxyException.cs index a41b17674..36f50ad37 100644 --- a/RuriLib.Proxies/Exceptions/ProxyException.cs +++ b/RuriLib.Proxies/Exceptions/ProxyException.cs @@ -1,28 +1,27 @@ using System; -namespace RuriLib.Proxies.Exceptions +namespace RuriLib.Proxies.Exceptions; + +/// +/// Represents errors that occur during proxy execution. +/// +public class ProxyException : Exception { /// - /// Represents errors that occur during proxy execution. + /// Initializes a new instance of the with a specified error message + /// and a reference to the inner exception that is the cause of this exception. /// - public class ProxyException : Exception - { - /// - /// Initializes a new instance of the with a specified error message - /// and a reference to the inner exception that is the cause of this exception. - /// - /// The error message that explains the reason for the exception. - public ProxyException(string message) - : base(message) { } + /// The error message that explains the reason for the exception. + public ProxyException(string message) + : base(message) { } - /// - /// Initializes a new instance of the with a specified error message - /// and a reference to the inner exception that is the cause of this exception. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a reference. - public ProxyException(string message, Exception innerException) - : base(message, innerException) { } + /// + /// Initializes a new instance of the with a specified error message + /// and a reference to the inner exception that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a reference. + public ProxyException(string message, Exception innerException) + : base(message, innerException) { } - } } diff --git a/RuriLib.Proxies/Helpers/HostHelper.cs b/RuriLib.Proxies/Helpers/HostHelper.cs index baa2feaeb..2c9c8288e 100644 --- a/RuriLib.Proxies/Helpers/HostHelper.cs +++ b/RuriLib.Proxies/Helpers/HostHelper.cs @@ -4,57 +4,56 @@ using System.Net.Sockets; using System.Threading.Tasks; -namespace RuriLib.Proxies.Helpers +namespace RuriLib.Proxies.Helpers; + +internal static class HostHelper { - static internal class HostHelper + public static byte[] GetPortBytes(int port) { - public static byte[] GetPortBytes(int port) - { - var array = new byte[2]; + var array = new byte[2]; - array[0] = (byte)(port / 256); - array[1] = (byte)(port % 256); + array[0] = (byte)(port / 256); + array[1] = (byte)(port % 256); - return array; - } + return array; + } - public static async Task GetIPAddressBytesAsync(string destinationHost, bool preferIpv4 = true) + public static async Task GetIpAddressBytesAsync(string destinationHost, bool preferIpv4 = true) + { + if (IPAddress.TryParse(destinationHost, out var ipAddr)) { - if (!IPAddress.TryParse(destinationHost, out var ipAddr)) + return ipAddr.GetAddressBytes(); + } + + try + { + var ips = await Dns.GetHostAddressesAsync(destinationHost).ConfigureAwait(false); + + if (ips.Length > 0) { - try + if (preferIpv4) { - var ips = await Dns.GetHostAddressesAsync(destinationHost).ConfigureAwait(false); - - if (ips.Length > 0) + foreach (var ip in ips) { - if (preferIpv4) + var ipBytes = ip.GetAddressBytes(); + if (ipBytes.Length == 4) { - foreach (var ip in ips) - { - var ipBytes = ip.GetAddressBytes(); - if (ipBytes.Length == 4) - { - return ipBytes; - } - } + return ipBytes; } - - ipAddr = ips[0]; } } - catch (Exception ex) - { - if (ex is SocketException || ex is ArgumentException) - { - throw new ProxyException("Failed to get host address", ex); - } - - throw; - } + } + } + catch (Exception ex) + { + if (ex is SocketException or ArgumentException) + { + throw new ProxyException("Failed to get host address", ex); } - return ipAddr.GetAddressBytes(); + throw; } + + throw new ProxyException("Failed to get host address"); } } diff --git a/RuriLib.Proxies/Helpers/PortHelper.cs b/RuriLib.Proxies/Helpers/PortHelper.cs index c7d802e22..f97b9d499 100644 --- a/RuriLib.Proxies/Helpers/PortHelper.cs +++ b/RuriLib.Proxies/Helpers/PortHelper.cs @@ -1,8 +1,7 @@ -namespace RuriLib.Proxies.Helpers +namespace RuriLib.Proxies.Helpers; + +internal static class PortHelper { - static internal class PortHelper - { - public static bool ValidateTcpPort(int port) - => port >= 1 && port <= 65535; - } + public static bool ValidateTcpPort(int port) + => port is >= 1 and <= 65535; } diff --git a/RuriLib.Proxies/ProxyClient.cs b/RuriLib.Proxies/ProxyClient.cs index 006dcf71a..80fecda52 100644 --- a/RuriLib.Proxies/ProxyClient.cs +++ b/RuriLib.Proxies/ProxyClient.cs @@ -6,88 +6,87 @@ using RuriLib.Proxies.Clients; using System.Security; -namespace RuriLib.Proxies +namespace RuriLib.Proxies; + +/// +/// Can produce proxied instances. +/// +public abstract class ProxyClient { /// - /// Can produce proxied instances. + /// The proxy settings. + /// + public ProxySettings Settings { get; } + + /// + /// Instantiates a proxy client with the given . /// - public abstract class ProxyClient + protected ProxyClient(ProxySettings settings) { - /// - /// The proxy settings. - /// - public ProxySettings Settings { get; } + Settings = settings ?? throw new ArgumentNullException(nameof(settings)); + } - /// - /// Instantiates a proxy client with the given . - /// - public ProxyClient(ProxySettings settings) + /// /// + /// Create a proxied to the destination host. + /// + /// The host you want to connect to + /// The port on which the host is listening + /// A token to cancel the connection attempt + /// A instance (if null, a new one will be created) + /// Value of is or empty. + /// Value of less than 1 or greater than 65535. + /// Error while working with the proxy. + public async Task ConnectAsync(string destinationHost, int destinationPort, TcpClient? tcpClient = null, + CancellationToken cancellationToken = default) + { + var client = tcpClient ?? new TcpClient { - Settings = settings ?? throw new ArgumentNullException(nameof(settings)); - } + ReceiveTimeout = (int)Settings.ReadWriteTimeOut.TotalMilliseconds, + SendTimeout = (int)Settings.ReadWriteTimeOut.TotalMilliseconds + }; - /// /// - /// Create a proxied to the destination host. - /// - /// The host you want to connect to - /// The port on which the host is listening - /// A token to cancel the connection attempt - /// A instance (if null, a new one will be created) - /// Value of is or empty. - /// Value of less than 1 or greater than 65535. - /// Error while working with the proxy. - public async Task ConnectAsync(string destinationHost, int destinationPort, TcpClient tcpClient = null, - CancellationToken cancellationToken = default) - { - var client = tcpClient ?? new TcpClient() - { - ReceiveTimeout = (int)Settings.ReadWriteTimeOut.TotalMilliseconds, - SendTimeout = (int)Settings.ReadWriteTimeOut.TotalMilliseconds - }; + var host = Settings.Host; + var port = Settings.Port; - var host = Settings.Host; - var port = Settings.Port; + // NoProxy case, connect directly to the server without proxy + if (this is NoProxyClient) + { + host = destinationHost; + port = destinationPort; + } - // NoProxy case, connect directly to the server without proxy - if (this is NoProxyClient) - { - host = destinationHost; - port = destinationPort; - } + // Try to connect to the proxy (or directly to the server in the NoProxy case) + try + { + using var timeoutCts = new CancellationTokenSource(Settings.ConnectTimeout); + using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken); + await client.ConnectAsync(host, port, linkedCts.Token).ConfigureAwait(false); - // Try to connect to the proxy (or directly to the server in the NoProxy case) - try - { - using var timeoutCts = new CancellationTokenSource(Settings.ConnectTimeout); - using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken); - await client.ConnectAsync(host, port, linkedCts.Token).ConfigureAwait(false); + await CreateConnectionAsync(client, destinationHost, destinationPort, cancellationToken) + .ConfigureAwait(false); + } + catch (Exception ex) + { + client.Close(); - await CreateConnectionAsync(client, destinationHost, destinationPort, cancellationToken) - .ConfigureAwait(false); - } - catch (Exception ex) + if (ex is SocketException or SecurityException) { - client.Close(); - - if (ex is SocketException or SecurityException) - { - throw new ProxyException($"Failed to connect to {(this is NoProxyClient ? "server" : "proxy-server")}", ex); - } - - throw; + throw new ProxyException($"Failed to connect to {(this is NoProxyClient ? "server" : "proxy-server")}", ex); } - return client; + throw; } - /// - /// Proxy protocol specific connection. - /// - /// The that can be used to connect to the proxy over TCP - /// The target host that the proxy needs to connect to - /// The target port that the proxy needs to connect to - /// A token to cancel operations - protected virtual Task CreateConnectionAsync(TcpClient tcpClient, string destinationHost, int destinationPort, - CancellationToken cancellationToken = default) => throw new NotImplementedException(); + return client; } + + /// + /// Proxy protocol specific connection. + /// + /// The that can be used to connect to the proxy over TCP + /// The target host that the proxy needs to connect to + /// The target port that the proxy needs to connect to + /// A token to cancel operations + protected virtual Task CreateConnectionAsync(TcpClient tcpClient, string destinationHost, int destinationPort, + CancellationToken cancellationToken = default) => throw new NotImplementedException(); } diff --git a/RuriLib.Proxies/ProxySettings.cs b/RuriLib.Proxies/ProxySettings.cs index 80e36244b..2e09d2a8e 100644 --- a/RuriLib.Proxies/ProxySettings.cs +++ b/RuriLib.Proxies/ProxySettings.cs @@ -1,38 +1,38 @@ using System; using System.Net; -namespace RuriLib.Proxies +namespace RuriLib.Proxies; + +/// +/// Settings for . +/// +public class ProxySettings { /// - /// Settings for . + /// Gets or sets the credentials to submit to the proxy server for authentication. + /// If no credentials are needed, set this to . /// - public class ProxySettings - { - /// - /// Gets or sets the credentials to submit to the proxy server for authentication. - /// - public NetworkCredential Credentials { get; set; } + public NetworkCredential? Credentials { get; set; } - /// - /// The hostname or ip of the proxy server. - /// - public string Host { get; set; } + /// + /// The hostname or ip of the proxy server. + /// + public string Host { get; set; } = string.Empty; - /// - /// The port on which the proxy server is listening. - /// - public int Port { get; set; } + /// + /// The port on which the proxy server is listening. + /// + public int Port { get; set; } - /// - /// Gets or sets the amount of time the - /// will wait to connect to the proxy server. - /// - public TimeSpan ConnectTimeout { get; set; } = TimeSpan.FromSeconds(5); + /// + /// Gets or sets the amount of time the + /// will wait to connect to the proxy server. + /// + public TimeSpan ConnectTimeout { get; set; } = TimeSpan.FromSeconds(5); - /// - /// Gets or sets the amount of time the - /// will wait for read or wait data from the proxy server. - /// - public TimeSpan ReadWriteTimeOut { get; set; } = TimeSpan.FromSeconds(10); - } + /// + /// Gets or sets the amount of time the + /// will wait for read or wait data from the proxy server. + /// + public TimeSpan ReadWriteTimeOut { get; set; } = TimeSpan.FromSeconds(10); } diff --git a/RuriLib.Proxies/RuriLib.Proxies.csproj b/RuriLib.Proxies/RuriLib.Proxies.csproj index 85b33fc92..e6ca5c9fb 100644 --- a/RuriLib.Proxies/RuriLib.Proxies.csproj +++ b/RuriLib.Proxies/RuriLib.Proxies.csproj @@ -9,6 +9,7 @@ 1.0.2 Ruri True + enable