Skip to content

Socks5 - Resolution of host by the proxy #919

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 70 additions & 1 deletion src/Renci.SshNet.Tests/Classes/ConnectionInfoTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -343,5 +343,74 @@ public void AuthenticateShouldThrowArgumentNullExceptionWhenServiceFactoryIsNull
Assert.AreEqual("serviceFactory", ex.ParamName);
}
}
}

[TestMethod]
[TestCategory("ConnectionInfo")]
public void ConstructorShouldThrowArgumentExceptionWhenUsingNoProxyAndNotResolvingHostLocally()
{
try
{
new ConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME,
ProxyTypes.None, null, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD,
HostResolutionMode.ResolvedByProxy,
new KeyboardInteractiveAuthenticationMethod(Resources.USERNAME));
Assert.Fail();
}
catch (ArgumentException ex)
{
Assert.IsNull(ex.InnerException);
Assert.AreEqual("hostResolutionMode", ex.ParamName);
}
}

[TestMethod]
[TestCategory("ConnectionInfo")]
public void ConstructorShouldThrowArgumentExceptionWhenUsingHttproxyAndResolvingHostOnProxy()
{
try
{
new ConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME,
ProxyTypes.Http, Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD,
HostResolutionMode.ResolvedByProxy,
new KeyboardInteractiveAuthenticationMethod(Resources.USERNAME));
Assert.Fail();
}
catch (ArgumentException ex)
{
Assert.IsNull(ex.InnerException);
Assert.AreEqual("hostResolutionMode", ex.ParamName);
}
}

[TestMethod]
[TestCategory("ConnectionInfo")]
public void ConstructorShouldThrowArgumentExceptionWhenUsingSocks4ProxyAndResolvingHostOnProxy()
{
try
{
new ConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME,
ProxyTypes.Socks4, Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD,
HostResolutionMode.ResolvedByProxy,
new KeyboardInteractiveAuthenticationMethod(Resources.USERNAME));
Assert.Fail();
}
catch (ArgumentException ex)
{
Assert.IsNull(ex.InnerException);
Assert.AreEqual("hostResolutionMode", ex.ParamName);
}
}

[TestMethod]
[TestCategory("ConnectionInfo")]
public void ConstructorShouldNotThrowArgumentExceptionWhenUsingSocks5ProxyAndResolvingHostOnProxy()
{
var connectionInfo = new ConnectionInfo(Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME,
ProxyTypes.Socks5, Resources.HOST, int.Parse(Resources.PORT), Resources.USERNAME, Resources.PASSWORD,
HostResolutionMode.ResolvedByProxy,
new KeyboardInteractiveAuthenticationMethod(Resources.USERNAME));

Assert.AreEqual(HostResolutionMode.ResolvedByProxy, connectionInfo.HostResolutionMode);
}
}
}
45 changes: 30 additions & 15 deletions src/Renci.SshNet/Connection/Socks5Connector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ protected override void HandleProxyConnect(IConnectionInfo connectionInfo, Socke
throw new ProxyException("SOCKS5: No acceptable authentication methods were offered.");
}

var connectionRequest = CreateSocks5ConnectionRequest(connectionInfo.Host, (ushort) connectionInfo.Port);
var connectionRequest = CreateSocks5ConnectionRequest(connectionInfo.Host, (ushort) connectionInfo.Port, connectionInfo.HostResolutionMode);
SocketAbstraction.Send(socket, connectionRequest);

// Read Server SOCKS5 version
Expand Down Expand Up @@ -173,10 +173,10 @@ private static byte[] CreateSocks5UserNameAndPasswordAuthenticationRequest(strin
return authenticationRequest;
}

private static byte[] CreateSocks5ConnectionRequest(string hostname, ushort port)
private static byte[] CreateSocks5ConnectionRequest(string hostname, ushort port, HostResolutionMode hostResolutionMode)
{
byte addressType;
var addressBytes = GetSocks5DestinationAddress(hostname, out addressType);
var addressBytes = GetSocks5DestinationAddress(hostname, hostResolutionMode, out addressType);

var connectionRequest = new byte
[
Expand All @@ -188,6 +188,8 @@ private static byte[] CreateSocks5ConnectionRequest(string hostname, ushort port
1 +
// Address type
1 +
// Address Length if type == 0x03
(addressType == 0x03 ? 1 : 0) +
// Address
addressBytes.Length +
// Port number
Expand All @@ -208,6 +210,10 @@ private static byte[] CreateSocks5ConnectionRequest(string hostname, ushort port
// Address type
connectionRequest[index++] = addressType;

// Address Length
if (addressType == 0x03)
connectionRequest[index++] = (byte)addressBytes.Length;

// Address
Buffer.BlockCopy(addressBytes, 0, connectionRequest, index, addressBytes.Length);
index += addressBytes.Length;
Expand All @@ -218,24 +224,33 @@ private static byte[] CreateSocks5ConnectionRequest(string hostname, ushort port
return connectionRequest;
}

private static byte[] GetSocks5DestinationAddress(string hostname, out byte addressType)
private static byte[] GetSocks5DestinationAddress(string hostname, HostResolutionMode hostResolutionMode, out byte addressType)
{
var ip = DnsAbstraction.GetHostAddresses(hostname)[0];

byte[] address;

switch (ip.AddressFamily)
switch (hostResolutionMode)
{
case AddressFamily.InterNetwork:
addressType = 0x01; // IPv4
address = ip.GetAddressBytes();
break;
case AddressFamily.InterNetworkV6:
addressType = 0x04; // IPv6
address = ip.GetAddressBytes();
case HostResolutionMode.ResolvedByProxy:
addressType = 0x03; // Host Name
address = SshData.Ascii.GetBytes(hostname);
break;
default:
throw new ProxyException(string.Format("SOCKS5: IP address '{0}' is not supported.", ip));
var ip = DnsAbstraction.GetHostAddresses(hostname)[0];

switch (ip.AddressFamily)
{
case AddressFamily.InterNetwork:
addressType = 0x01; // IPv4
address = ip.GetAddressBytes();
break;
case AddressFamily.InterNetworkV6:
addressType = 0x04; // IPv6
address = ip.GetAddressBytes();
break;
default:
throw new ProxyException(string.Format("SOCKS5: IP address '{0}' is not supported.", ip));
}
break;
}

return address;
Expand Down
43 changes: 43 additions & 0 deletions src/Renci.SshNet/ConnectionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ public class ConnectionInfo : IConnectionInfoInternal
/// </summary>
public string ProxyPassword { get; private set; }

/// <summary>
/// Resolve host locally or by a socks5 proxy
/// </summary>
public HostResolutionMode HostResolutionMode { get; private set; }

/// <summary>
/// Gets or sets connection timeout.
/// </summary>
Expand Down Expand Up @@ -294,6 +299,31 @@ public ConnectionInfo(string host, int port, string username, params Authenticat
/// <exception cref="ArgumentNullException"><paramref name="authenticationMethods"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">No <paramref name="authenticationMethods"/> specified.</exception>
public ConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, params AuthenticationMethod[] authenticationMethods)
: this(host, port, username, proxyType, proxyHost, proxyPort, proxyUsername, proxyPassword, HostResolutionMode.ResolvedLocally, authenticationMethods)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="ConnectionInfo" /> class.
/// </summary>
/// <param name="host">Connection host.</param>
/// <param name="port">Connection port.</param>
/// <param name="username">Connection username.</param>
/// <param name="proxyType">Type of the proxy.</param>
/// <param name="proxyHost">The proxy host.</param>
/// <param name="proxyPort">The proxy port.</param>
/// <param name="proxyUsername">The proxy username.</param>
/// <param name="proxyPassword">The proxy password.</param>
/// <param name="hostResolutionMode">The resolution mode of addresses, for SOCKS5 proxy</param>
/// <param name="authenticationMethods">The authentication methods.</param>
/// <exception cref="ArgumentNullException"><paramref name="host"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException"><paramref name="username" /> is <c>null</c>, a zero-length string or contains only whitespace characters.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="port" /> is not within <see cref="F:System.Net.IPEndPoint.MinPort" /> and <see cref="F:System.Net.IPEndPoint.MaxPort" />.</exception>
/// <exception cref="ArgumentNullException"><paramref name="proxyType"/> is not <see cref="ProxyTypes.None"/> and <paramref name="proxyHost" /> is <c>null</c>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="proxyType"/> is not <see cref="ProxyTypes.None"/> and <paramref name="proxyPort" /> is not within <see cref="F:System.Net.IPEndPoint.MinPort" /> and <see cref="F:System.Net.IPEndPoint.MaxPort" />.</exception>
/// <exception cref="ArgumentNullException"><paramref name="authenticationMethods"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">No <paramref name="authenticationMethods"/> specified.</exception>
public ConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, HostResolutionMode hostResolutionMode, params AuthenticationMethod[] authenticationMethods)
{
if (host == null)
throw new ArgumentNullException("host");
Expand All @@ -310,6 +340,17 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy
throw new ArgumentNullException("proxyHost");
proxyPort.ValidatePort("proxyPort");
}
else
{
if (hostResolutionMode != HostResolutionMode.ResolvedLocally)
throw new ArgumentException("HostResolutionMode.ResolvedLocally is the only supported value when using no proxy", "hostResolutionMode");
}

if (hostResolutionMode == HostResolutionMode.ResolvedByProxy)
{
if (proxyType != ProxyTypes.Socks5)
throw new ArgumentException("HostResolutionMode.ResolvedByProxy is only supported by SOCKS5 proxies", "hostResolutionMode");
}

if (authenticationMethods == null)
throw new ArgumentNullException("authenticationMethods");
Expand Down Expand Up @@ -431,6 +472,8 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy
ProxyUsername = proxyUsername;
ProxyPassword = proxyPassword;

HostResolutionMode = hostResolutionMode;

AuthenticationMethods = authenticationMethods;
}

Expand Down
14 changes: 14 additions & 0 deletions src/Renci.SshNet/HostResolutionMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Renci.SshNet
{
/// <summary>
/// Specifies the way host names will be resolved when conecting through a proxy.
/// </summary>
public enum HostResolutionMode
{
/// <summary>The host name is resolved by the client and the host IP is sent to the proxy.</summary>
ResolvedLocally,

/// <summary>The host name is sent to the proxy and resolved later.</summary>
ResolvedByProxy
}
}
5 changes: 5 additions & 0 deletions src/Renci.SshNet/IConnectionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ internal interface IConnectionInfo
/// </summary>
string ProxyPassword { get; }

/// <summary>
/// Resolve host locally or by a socks5 proxy
/// </summary>
HostResolutionMode HostResolutionMode { get; }

/// <summary>
/// Gets the number of retry attempts when session channel creation failed.
/// </summary>
Expand Down
33 changes: 25 additions & 8 deletions src/Renci.SshNet/KeyboardInteractiveConnectionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class KeyboardInteractiveConnectionInfo : ConnectionInfo, IDisposable
/// <param name="host">The host.</param>
/// <param name="username">The username.</param>
public KeyboardInteractiveConnectionInfo(string host, string username)
: this(host, DefaultPort, username, ProxyTypes.None, string.Empty, 0, string.Empty, string.Empty)
: this(host, DefaultPort, username, ProxyTypes.None, string.Empty, 0, string.Empty, string.Empty, HostResolutionMode.ResolvedLocally)
{

}
Expand All @@ -39,7 +39,7 @@ public KeyboardInteractiveConnectionInfo(string host, string username)
/// <param name="port">The port.</param>
/// <param name="username">The username.</param>
public KeyboardInteractiveConnectionInfo(string host, int port, string username)
: this(host, port, username, ProxyTypes.None, string.Empty, 0, string.Empty, string.Empty)
: this(host, port, username, ProxyTypes.None, string.Empty, 0, string.Empty, string.Empty, HostResolutionMode.ResolvedLocally)
{

}
Expand All @@ -54,7 +54,7 @@ public KeyboardInteractiveConnectionInfo(string host, int port, string username)
/// <param name="proxyHost">The proxy host.</param>
/// <param name="proxyPort">The proxy port.</param>
public KeyboardInteractiveConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort)
: this(host, port, username, proxyType, proxyHost, proxyPort, string.Empty, string.Empty)
: this(host, port, username, proxyType, proxyHost, proxyPort, string.Empty, string.Empty, HostResolutionMode.ResolvedLocally)
{
}

Expand All @@ -69,7 +69,7 @@ public KeyboardInteractiveConnectionInfo(string host, int port, string username,
/// <param name="proxyPort">The proxy port.</param>
/// <param name="proxyUsername">The proxy username.</param>
public KeyboardInteractiveConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername)
: this(host, port, username, proxyType, proxyHost, proxyPort, proxyUsername, string.Empty)
: this(host, port, username, proxyType, proxyHost, proxyPort, proxyUsername, string.Empty, HostResolutionMode.ResolvedLocally)
{
}

Expand All @@ -82,7 +82,7 @@ public KeyboardInteractiveConnectionInfo(string host, int port, string username,
/// <param name="proxyHost">The proxy host.</param>
/// <param name="proxyPort">The proxy port.</param>
public KeyboardInteractiveConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort)
: this(host, DefaultPort, username, proxyType, proxyHost, proxyPort, string.Empty, string.Empty)
: this(host, DefaultPort, username, proxyType, proxyHost, proxyPort, string.Empty, string.Empty, HostResolutionMode.ResolvedLocally)
{
}

Expand All @@ -96,7 +96,7 @@ public KeyboardInteractiveConnectionInfo(string host, string username, ProxyType
/// <param name="proxyPort">The proxy port.</param>
/// <param name="proxyUsername">The proxy username.</param>
public KeyboardInteractiveConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername)
: this(host, DefaultPort, username, proxyType, proxyHost, proxyPort, proxyUsername, string.Empty)
: this(host, DefaultPort, username, proxyType, proxyHost, proxyPort, proxyUsername, string.Empty, HostResolutionMode.ResolvedLocally)
{
}

Expand All @@ -111,7 +111,7 @@ public KeyboardInteractiveConnectionInfo(string host, string username, ProxyType
/// <param name="proxyUsername">The proxy username.</param>
/// <param name="proxyPassword">The proxy password.</param>
public KeyboardInteractiveConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword)
: this(host, DefaultPort, username, proxyType, proxyHost, proxyPort, proxyUsername, proxyPassword)
: this(host, DefaultPort, username, proxyType, proxyHost, proxyPort, proxyUsername, proxyPassword, HostResolutionMode.ResolvedLocally)
{
}

Expand All @@ -127,7 +127,24 @@ public KeyboardInteractiveConnectionInfo(string host, string username, ProxyType
/// <param name="proxyUsername">The proxy username.</param>
/// <param name="proxyPassword">The proxy password.</param>
public KeyboardInteractiveConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword)
: base(host, port, username, proxyType, proxyHost, proxyPort, proxyUsername, proxyPassword, new KeyboardInteractiveAuthenticationMethod(username))
: this(host, port, username, proxyType, proxyHost, proxyPort, proxyUsername, proxyPassword, HostResolutionMode.ResolvedLocally)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="KeyboardInteractiveConnectionInfo"/> class.
/// </summary>
/// <param name="host">Connection host.</param>
/// <param name="port">Connection port.</param>
/// <param name="username">Connection username.</param>
/// <param name="proxyType">Type of the proxy.</param>
/// <param name="proxyHost">The proxy host.</param>
/// <param name="proxyPort">The proxy port.</param>
/// <param name="proxyUsername">The proxy username.</param>
/// <param name="proxyPassword">The proxy password.</param>
/// <param name="hostResolutionMode">The resolution mode of addresses, for SOCKS5 proxy</param>
public KeyboardInteractiveConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, HostResolutionMode hostResolutionMode)
: base(host, port, username, proxyType, proxyHost, proxyPort, proxyUsername, proxyPassword, hostResolutionMode, new KeyboardInteractiveAuthenticationMethod(username))
{
foreach (var authenticationMethod in AuthenticationMethods)
{
Expand Down
Loading