Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@zHaytam
Last active March 29, 2024 07:47
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save zHaytam/3730d512eb5eaf37fb3bd3d176185541 to your computer and use it in GitHub Desktop.
Save zHaytam/3730d512eb5eaf37fb3bd3d176185541 to your computer and use it in GitHub Desktop.
A Socks5 implementation in .NET Core (C# 8)
using System;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace Socks
{
public static class Socks5
{
public static async Task<Socket> Connect(Func<Socket> socketFactory, Socks5Options options)
{
if (options == null)
throw new ArgumentNullException(nameof(options));
var socket = socketFactory();
await socket.ConnectAsync(options.ProxyHost, options.ProxyPort);
await SelectAuth(socket, options);
await Connect(socket, options);
return socket;
}
private static async Task SelectAuth(Socket socket, Socks5Options options)
{
/*
+----+----------+----------+
| VER | NMETHODS | METHODS |
+----+----------+----------+
| 1 | 1 | 1 to 255 |
+----+----------+----------+
*/
var buffer = new byte[4] {
5,
2,
Socks5Constants.AuthMethodNoAuthenticationRequired, Socks5Constants.AuthMethodUsernamePassword
};
await socket.SendAsync(buffer, SocketFlags.None);
/*
+-----+--------+
| VER | METHOD |
+-----+--------+
| 1 | 1 |
+-----+--------+
*/
var response = new byte[2];
var read = await socket.ReceiveAsync(response, SocketFlags.None);
if (read != 2)
throw new SocksocketException($"Failed to select an authentication method, the server sent {read} bytes.");
if (response[1] == Socks5Constants.AuthMethodReplyNoAcceptableMethods)
{
socket.Close();
throw new SocksocketException("The proxy destination does not accept the supported proxy client authentication methods.");
}
if (response[1] == Socks5Constants.AuthMethodUsernamePassword && options.Auth == AuthType.None)
{
socket.Close();
throw new SocksocketException("The proxy destination requires a username and password for authentication.");
}
if (response[1] == Socks5Constants.AuthMethodNoAuthenticationRequired)
return;
await PerformAuth(socket, options);
}
private static async Task PerformAuth(Socket socket, Socks5Options options)
{
/*
+-----+------+----------+------+----------+
| VER | ULEN | UNAME | PLEN | PASSWD |
+----+-------+----------+------+----------+
| 1 | 1 | 1 to 255 | 1 | 1 to 255 |
+----+-------+----------+------+----------+
*/
var buffer = ConstructAuthBuffer(options.Credentials.Username, options.Credentials.Password);
await socket.SendAsync(buffer, SocketFlags.None);
/*
+----+--------+
|VER | STATUS |
+----+--------+
| 1 | 1 |
+----+--------+
*/
var response = new byte[2];
var read = await socket.ReceiveAsync(response, SocketFlags.None);
if (read != 2)
throw new SocksocketException($"Failed to perform authentication, the server sent {read} bytes.");
if (response[1] != 0)
{
socket.Close();
throw new SocksocketException("Proxy authentication failed.");
}
}
private static async Task Connect(Socket socket, Socks5Options options)
{
/*
+-----+-----+-------+------+----------+----------+
| VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+--- -+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+-----+-----+-------+------+----------+----------+
*/
var addressType = GetDestAddressType(options.DestinationHost);
var destAddr = GetDestAddressBytes(addressType, options.DestinationHost);
var destPort = GetDestPortBytes(options.DestinationPort);
var buffer = new byte[6 + options.DestinationHost.Length];
buffer[0] = 5;
buffer[1] = Socks5Constants.CmdConnect;
buffer[2] = Socks5Constants.Reserved;
buffer[3] = addressType;
destAddr.CopyTo(buffer, 4);
destPort.CopyTo(buffer, 4 + destAddr.Length);
await socket.SendAsync(buffer, SocketFlags.None);
/*
+---- +-----+-------+------+----------+----------+
| VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
+-----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+-----+-----+-------+------+----------+----------+
*/
var response = new byte[255];
await socket.ReceiveAsync(response, SocketFlags.None);
if (response[1] != Socks5Constants.CmdReplySucceeded)
HandleProxyCommandError(response, options.DestinationHost, options.DestinationPort);
}
private static void HandleProxyCommandError(byte[] response, string destinationHost, int destinationPort)
{
var replyCode = response[1];
var proxyErrorText = replyCode switch
{
Socks5Constants.CmdReplyGeneralSocksServerFailure => "a general socks destination failure occurred",
Socks5Constants.CmdReplyConnectionNotAllowedByRuleset => "the connection is not allowed by proxy destination rule set",
Socks5Constants.CmdReplyNetworkUnreachable => "the network was unreachable",
Socks5Constants.CmdReplyHostUnreachable => "the host was unreachable",
Socks5Constants.CmdReplyConnectionRefused => "the connection was refused by the remote network",
Socks5Constants.CmdReplyTtlExpired => "the time to live (TTL) has expired",
Socks5Constants.CmdReplyCommandNotSupported => "the command issued by the proxy client is not supported by the proxy destination",
Socks5Constants.CmdReplyAddressTypeNotSupported => "the address type specified is not supported",
_ => string.Format(CultureInfo.InvariantCulture,
"an unknown SOCKS reply with the code value '{0}' was received",
replyCode.ToString(CultureInfo.InvariantCulture)),
};
string exceptionMsg = string.Format(CultureInfo.InvariantCulture,
"proxy error: {0} for destination host {1} port number {2}.",
proxyErrorText, destinationHost, destinationPort);
throw new SocksocketException(exceptionMsg);
}
private static byte[] ConstructAuthBuffer(string username, string password)
{
var credentials = new byte[3 + username.Length + password.Length];
credentials[0] = 0x01;
credentials[1] = (byte)username.Length;
Array.Copy(Encoding.ASCII.GetBytes(username), 0, credentials, 2, username.Length);
credentials[username.Length + 2] = (byte)password.Length;
Array.Copy(Encoding.ASCII.GetBytes(password), 0, credentials, 2, password.Length);
return credentials;
}
private static byte GetDestAddressType(string host)
{
if (!IPAddress.TryParse(host, out var ipAddr))
return Socks5Constants.AddrtypeDomainName;
switch (ipAddr.AddressFamily)
{
case AddressFamily.InterNetwork:
return Socks5Constants.AddrtypeIpv4;
case AddressFamily.InterNetworkV6:
return Socks5Constants.AddrtypeIpv6;
default:
throw new SocksocketException(
string.Format("The host addess {0} of type '{1}' is not a supported address type.\n" +
"The supported types are InterNetwork and InterNetworkV6.", host,
Enum.GetName(typeof(AddressFamily), ipAddr.AddressFamily)));
}
}
private static byte[] GetDestAddressBytes(byte addressType, string host)
{
switch (addressType)
{
case Socks5Constants.AddrtypeIpv4:
case Socks5Constants.AddrtypeIpv6:
return IPAddress.Parse(host).GetAddressBytes();
case Socks5Constants.AddrtypeDomainName:
byte[] bytes = new byte[host.Length + 1];
bytes[0] = Convert.ToByte(host.Length);
Encoding.ASCII.GetBytes(host).CopyTo(bytes, 1);
return bytes;
default:
return null;
}
}
private static byte[] GetDestPortBytes(int value)
{
return new byte[2]
{
Convert.ToByte(value / 256),
Convert.ToByte(value % 256)
};
}
}
}
namespace Socks
{
public class Socks5Constants
{
public const byte Reserved = 0x00;
public const byte AuthNumberOfAuthMethodsSupported = 2;
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 CmdConnect = 0x01;
public const byte CmdBind = 0x02;
public const byte CmdUdpAssociate = 0x03;
public const byte CmdReplySucceeded = 0x00;
public const byte CmdReplyGeneralSocksServerFailure = 0x01;
public const byte CmdReplyConnectionNotAllowedByRuleset = 0x02;
public const byte CmdReplyNetworkUnreachable = 0x03;
public const byte CmdReplyHostUnreachable = 0x04;
public const byte CmdReplyConnectionRefused = 0x05;
public const byte CmdReplyTtlExpired = 0x06;
public const byte CmdReplyCommandNotSupported = 0x07;
public const byte CmdReplyAddressTypeNotSupported = 0x08;
public const byte AddrtypeIpv4 = 0x01;
public const byte AddrtypeDomainName = 0x03;
public const byte AddrtypeIpv6 = 0x04;
}
}
namespace Socks
{
public class Socks5Options
{
public string ProxyHost { get; }
public int ProxyPort { get; }
public string DestinationHost { get; }
public int DestinationPort { get; }
public AuthType? Auth { get; }
public (string Username, string Password) Credentials { get; }
public Socks5Options(string proxyHost, int proxyPort, string destHost, int destPort)
{
ProxyHost = proxyHost;
ProxyPort = proxyPort;
DestinationHost = destHost;
DestinationPort = destPort;
Auth = AuthType.None;
}
public Socks5Options(string proxyHost, string destHost, int destPort) : this(proxyHost, 1080, destHost, destPort) { }
public Socks5Options(string proxyHost, int proxyPort, string destHost, int destPort, string username,
string password) : this(proxyHost, proxyPort, destHost, destPort)
{
Auth = AuthType.UsernamePassword;
Credentials = (username, password);
}
public Socks5Options(string proxyHost, string destHost, int destPort, string username, string password) :
this(proxyHost, 1080, destHost, destPort, username, password)
{ }
}
public enum AuthType
{
None,
UsernamePassword
}
}
@CerebralMischief
Copy link

Thank you for sharing this code!

@AlexanderKrustev
Copy link

Hello! Impressive. I am trying to make a .net app able to communicate with databases on-premises via SAP Cloud Connector using Socks5 prot. Do you have any implementation of this Socks5 in app ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment