Skip to content
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

SOCKS4/4a/5 proxy support in SocketsHttpHandler #48883

Merged
merged 62 commits into from
Apr 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
5f2407a
Separate method for proxy scheme validation.
huoyaoyuan Feb 27, 2021
7a70c33
Pass socks connection kind.
huoyaoyuan Feb 27, 2021
d0aa8fc
Unauthorized socks5 connection.
huoyaoyuan Feb 28, 2021
b091355
Username and password auth.
huoyaoyuan Feb 28, 2021
98a1d12
Fix response address.
huoyaoyuan Feb 28, 2021
c4cdd8e
Fix proxyUri value and assertion.
huoyaoyuan Feb 28, 2021
b5e3701
Use HttpConnectionKind for SOCKS.
huoyaoyuan Mar 1, 2021
15b2a8f
Handle more connection kind assertions.
huoyaoyuan Mar 1, 2021
56bf28b
SOCKS4/4a support.
huoyaoyuan Mar 1, 2021
a2a80f7
Move version selection into SocksHelper.
huoyaoyuan Mar 1, 2021
1d7fa76
Call sync version of read write.
huoyaoyuan Mar 2, 2021
44cce2e
Cancellation by disposing stream.
huoyaoyuan Mar 2, 2021
140bbc1
Dispose cancellation registration.
huoyaoyuan Mar 3, 2021
694b869
IP addressing for SOCKS5.
huoyaoyuan Mar 3, 2021
e68224a
IP addressing for SOCKS4.
huoyaoyuan Mar 3, 2021
24e1e2d
Wrap write method.
huoyaoyuan Mar 3, 2021
d1a8eae
Cancellation and optimization.
huoyaoyuan Mar 3, 2021
7836231
Optimize.
huoyaoyuan Mar 4, 2021
da3ef58
Apply suggestions from code review
huoyaoyuan Mar 4, 2021
7371407
Clarify logic.
huoyaoyuan Mar 4, 2021
c007769
Remove ssl assertion.
huoyaoyuan Mar 4, 2021
0517a87
SocksException.
huoyaoyuan Mar 4, 2021
ca9babe
Make SocksException derive from IOException.
huoyaoyuan Mar 6, 2021
50a723a
Use binary primitives to write port in BE.
huoyaoyuan Mar 11, 2021
3b6eb5f
Socks loopback test.
huoyaoyuan Mar 11, 2021
4882181
Expand test matrix.
huoyaoyuan Mar 11, 2021
5d21f2b
Try to solve certificate issue.
huoyaoyuan Mar 11, 2021
5ef161c
Pass handler to httpclient.
huoyaoyuan Mar 16, 2021
2df3d41
Merge branch 'main' into socks-proxy
huoyaoyuan Mar 16, 2021
87ef694
Update ConnectToTcpHostAsync.
huoyaoyuan Mar 16, 2021
4462be5
Merge branch 'main' into socks-proxy
MihaZupan Mar 24, 2021
b487d1f
Remove custom self-signed cert use from Socks test
MihaZupan Mar 24, 2021
de78efc
Fix LoopbackSocksServer's parsing of Socks4a domain name
MihaZupan Mar 24, 2021
df1d77f
Only set RequestVersionExact for H2C
MihaZupan Mar 24, 2021
f9465b5
Merge branch 'main' into socks-proxy
MihaZupan Apr 6, 2021
ee02582
Add auth test.
huoyaoyuan Apr 7, 2021
efc32b8
Add IP in test matrix.
huoyaoyuan Apr 7, 2021
ddb2f8d
Only override host when required.
huoyaoyuan Apr 7, 2021
e30ee4e
Don't attempt NT Auth for Socks proxies
MihaZupan Apr 7, 2021
7b68507
Skip HTTP2 ssl test on platforms without ALPN support
MihaZupan Apr 7, 2021
dfb737c
Use NetworkCredential directly
MihaZupan Apr 7, 2021
1bd2c0e
Pass AddressFamily to sync Dns resolution too
MihaZupan Apr 7, 2021
5c26033
Consistently check encoded string lengths
MihaZupan Apr 7, 2021
1308e14
Fix Socks5 user/pass auth
MihaZupan Apr 7, 2021
76b61ce
Add IPv6 test for socks5
MihaZupan Apr 7, 2021
54bde51
Exception nits
MihaZupan Apr 7, 2021
4d7a545
Add exceptional tests.
huoyaoyuan Apr 8, 2021
7a7bae0
Merge branch 'main'
huoyaoyuan Apr 12, 2021
1e5cde4
Fix exceptional test.
huoyaoyuan Apr 12, 2021
8bbb9e8
Fix NRT compilation
huoyaoyuan Apr 13, 2021
32a36b5
Server shouldn't wait for request in exceptional test.
huoyaoyuan Apr 15, 2021
9ed3686
Add exception message to test.
huoyaoyuan Apr 15, 2021
e70eb62
Update auth failure handling.
huoyaoyuan Apr 15, 2021
c79979c
SOCKS4 and 5 uses different auth model, requires different error mess…
huoyaoyuan Apr 15, 2021
bca15fa
Revert accidental indent change.
huoyaoyuan Apr 15, 2021
0ae546d
Expand test matrix to include Sync HTTP1
MihaZupan Apr 15, 2021
b06f145
Read received bytes before returning error response in Socks4 loopback
MihaZupan Apr 16, 2021
7f20191
Use named bool arguments
MihaZupan Apr 16, 2021
2ff779a
Improve exception messages
MihaZupan Apr 16, 2021
c08e717
!IsEmpty => Length != 0
MihaZupan Apr 16, 2021
52df362
Improve exception messages 2
MihaZupan Apr 16, 2021
95400e2
Avoid enforing Socks4 VN value
MihaZupan Apr 16, 2021
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
29 changes: 28 additions & 1 deletion src/libraries/System.Net.Http/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@
<value>Client certificate was not found in the personal (\"MY\") certificate store. In UWP, client certificates are only supported if they have been added to that certificate store.</value>
</data>
<data name="net_http_invalid_proxy_scheme" xml:space="preserve">
<value>Only the 'http' scheme is allowed for proxies.</value>
<value>Only the 'http', 'socks4', 'socks4a' and 'socks5' schemes are allowed for proxies.</value>
</data>
<data name="net_http_request_invalid_char_encoding" xml:space="preserve">
<value>Request headers must contain only ASCII characters.</value>
Expand Down Expand Up @@ -606,6 +606,33 @@
<data name="net_http_synchronous_reads_not_supported" xml:space="preserve">
<value>Synchronous reads are not supported, use ReadAsync instead.</value>
</data>
<data name="net_socks_auth_failed" xml:space="preserve">
<value>Failed to authenticate with the SOCKS server.</value>
</data>
<data name="net_socks_bad_address_type" xml:space="preserve">
<value>SOCKS server returned an unknown address type.</value>
</data>
<data name="net_socks_connection_failed" xml:space="preserve">
<value>SOCKS server failed to connect to the destination.</value>
</data>
<data name="net_socks_ipv6_notsupported" xml:space="preserve">
<value>SOCKS4 does not support IPv6 addresses.</value>
</data>
<data name="net_socks_no_auth_method" xml:space="preserve">
<value>SOCKS server did not return a suitable authentication method.</value>
</data>
<data name="net_socks_no_ipv4_address" xml:space="preserve">
<value>Failed to resolve the destination host to an IPv4 address.</value>
</data>
<data name="net_socks_unexpected_version" xml:space="preserve">
<value>Unexpected SOCKS protocol version. Required {0}, got {1}.</value>
</data>
<data name="net_socks_string_too_long" xml:space="preserve">
<value>Encoding the {0} took more than the maximum of 255 bytes.</value>
</data>
<data name="net_socks_auth_required" xml:space="preserve">
<value>SOCKS server requested username &amp; password authentication.</value>
</data>
<data name="net_http_proxy_tunnel_returned_failure_status_code" xml:space="preserve">
<value>The proxy tunnel request to proxy '{0}' failed with status code '{1}'."</value>
</data>
Expand Down
2 changes: 2 additions & 0 deletions src/libraries/System.Net.Http/src/System.Net.Http.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@
<Compile Include="System\Net\Http\HttpTelemetry.AnyOS.cs" />
<Compile Include="System\Net\Http\HttpUtilities.AnyOS.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\SystemProxyInfo.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\SocksHelper.cs" />
<Compile Include="System\Net\Http\SocketsHttpHandler\SocksException.cs" />
<Compile Include="$(CommonPath)System\Net\NTAuthentication.Common.cs"
Link="Common\System\Net\NTAuthentication.Common.cs" />
<Compile Include="$(CommonPath)System\Net\ContextFlagsPal.cs"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ internal static bool IsNonSecureWebSocketScheme(string scheme) =>
internal static bool IsSecureWebSocketScheme(string scheme) =>
string.Equals(scheme, "wss", StringComparison.OrdinalIgnoreCase);

internal static bool IsSupportedProxyScheme(string scheme) =>
string.Equals(scheme, "http", StringComparison.OrdinalIgnoreCase) || IsSocksScheme(scheme);

internal static bool IsSocksScheme(string scheme) =>
string.Equals(scheme, "socks5", StringComparison.OrdinalIgnoreCase) ||
string.Equals(scheme, "socks4a", StringComparison.OrdinalIgnoreCase) ||
string.Equals(scheme, "socks4", StringComparison.OrdinalIgnoreCase);

// Always specify TaskScheduler.Default to prevent us from using a user defined TaskScheduler.Current.
//
// Since we're not doing any CPU and/or I/O intensive operations, continue on the same thread.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ internal enum HttpConnectionKind : byte
Proxy, // HTTP proxy usage for non-secure (HTTP) requests.
ProxyTunnel, // Non-secure websocket (WS) connection using CONNECT tunneling through proxy.
SslProxyTunnel, // HTTP proxy usage for secure (HTTPS/WSS) requests using SSL and proxy CONNECT.
ProxyConnect // Connection used for proxy CONNECT. Tunnel will be established on top of this.
ProxyConnect, // Connection used for proxy CONNECT. Tunnel will be established on top of this.
SocksTunnel, // SOCKS proxy usage for HTTP requests.
SslSocksTunnel // SOCKS proxy usage for HTTPS requests.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,17 @@ public HttpConnectionPool(HttpConnectionPoolManager poolManager, HttpConnectionK
_http3Enabled = false;
break;

case HttpConnectionKind.SocksTunnel:
case HttpConnectionKind.SslSocksTunnel:
Debug.Assert(host != null);
Debug.Assert(port != 0);
Debug.Assert(proxyUri != null);

_http3Enabled = false; // TODO: SOCKS supports UDP and may be used for HTTP3
break;

default:
Debug.Fail("Unkown HttpConnectionKind in HttpConnectionPool.ctor");
Debug.Fail("Unknown HttpConnectionKind in HttpConnectionPool.ctor");
break;
}

Expand Down Expand Up @@ -293,7 +302,7 @@ private static SslClientAuthenticationOptions ConstructSslOptions(HttpConnection
public HttpAuthority? OriginAuthority => _originAuthority;
public HttpConnectionSettings Settings => _poolManager.Settings;
public HttpConnectionKind Kind => _kind;
public bool IsSecure => _kind == HttpConnectionKind.Https || _kind == HttpConnectionKind.SslProxyTunnel;
public bool IsSecure => _kind == HttpConnectionKind.Https || _kind == HttpConnectionKind.SslProxyTunnel || _kind == HttpConnectionKind.SslSocksTunnel;
public Uri? ProxyUri => _proxyUri;
public ICredentials? ProxyCredentials => _poolManager.ProxyCredentials;
public byte[]? HostHeaderValueBytes => _hostHeaderValueBytes;
Expand All @@ -315,10 +324,10 @@ public byte[] Http2AltSvcOriginUri

Debug.Assert(_originAuthority != null);
sb
.Append(_kind == HttpConnectionKind.Https ? "https://" : "http://")
.Append(IsSecure ? "https://" : "http://")
.Append(_originAuthority.IdnHost);

if (_originAuthority.Port != (_kind == HttpConnectionKind.Https ? DefaultHttpsPort : DefaultHttpPort))
if (_originAuthority.Port != (IsSecure ? DefaultHttpsPort : DefaultHttpPort))
{
sb
.Append(':')
Expand Down Expand Up @@ -523,7 +532,7 @@ public byte[] Http2AltSvcOriginUri
private async ValueTask<(HttpConnectionBase connection, bool isNewConnection)>
GetHttp2ConnectionAsync(HttpRequestMessage request, bool async, CancellationToken cancellationToken)
{
Debug.Assert(_kind == HttpConnectionKind.Https || _kind == HttpConnectionKind.SslProxyTunnel || _kind == HttpConnectionKind.Http);
Debug.Assert(_kind == HttpConnectionKind.Https || _kind == HttpConnectionKind.SslProxyTunnel || _kind == HttpConnectionKind.Http || _kind == HttpConnectionKind.SocksTunnel || _kind == HttpConnectionKind.SslSocksTunnel);

// See if we have an HTTP2 connection
Http2Connection? http2Connection = GetExistingHttp2Connection();
Expand Down Expand Up @@ -579,7 +588,7 @@ public byte[] Http2AltSvcOriginUri

sslStream = stream as SslStream;

if (_kind == HttpConnectionKind.Http)
if (!IsSecure)
{
http2Connection = await ConstructHttp2ConnectionAsync(stream, request, cancellationToken).ConfigureAwait(false);

Expand Down Expand Up @@ -1101,7 +1110,7 @@ internal void BlocklistAuthority(HttpAuthority badAuthority)
Debug.Assert(_altSvcBlocklistTimerCancellation != null);
if (added)
{
_ = Task.Delay(AltSvcBlocklistTimeoutInMilliseconds)
_ = Task.Delay(AltSvcBlocklistTimeoutInMilliseconds)
.ContinueWith(t =>
{
lock (altSvcBlocklist)
Expand Down Expand Up @@ -1217,6 +1226,14 @@ public ValueTask<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool
case HttpConnectionKind.SslProxyTunnel:
stream = await EstablishProxyTunnelAsync(async, request.HasHeaders ? request.Headers : null, cancellationToken).ConfigureAwait(false);
break;

case HttpConnectionKind.SocksTunnel:
case HttpConnectionKind.SslSocksTunnel:
Debug.Assert(_originAuthority != null);
Debug.Assert(_proxyUri != null);
(socket, stream) = await ConnectToTcpHostAsync(_proxyUri.IdnHost, _proxyUri.Port, request, async, cancellationToken).ConfigureAwait(false);
await SocksHelper.EstablishSocksTunnelAsync(stream, _originAuthority.IdnHost, _originAuthority.Port, _proxyUri, ProxyCredentials, async, cancellationToken).ConfigureAwait(false);
break;
huoyaoyuan marked this conversation as resolved.
Show resolved Hide resolved
}

Debug.Assert(stream != null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,20 @@ private HttpConnectionKey GetConnectionKey(HttpRequestMessage request, Uri? prox

if (proxyUri != null)
{
Debug.Assert(HttpUtilities.IsSupportedNonSecureScheme(proxyUri.Scheme));
if (sslHostName == null)
Debug.Assert(HttpUtilities.IsSupportedProxyScheme(proxyUri.Scheme));
if (HttpUtilities.IsSocksScheme(proxyUri.Scheme))
{
// Socks proxy
if (sslHostName != null)
{
return new HttpConnectionKey(HttpConnectionKind.SslSocksTunnel, uri.IdnHost, uri.Port, sslHostName, proxyUri, identity);
}
else
{
return new HttpConnectionKey(HttpConnectionKind.SocksTunnel, uri.IdnHost, uri.Port, null, proxyUri, identity);
}
}
else if (sslHostName == null)
{
if (HttpUtilities.IsNonSecureWebSocketScheme(uri.Scheme))
{
Expand Down Expand Up @@ -391,7 +403,7 @@ public ValueTask<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, $"Exception from {_proxy.GetType().Name}.GetProxy({request.RequestUri}): {ex}");
}

if (proxyUri != null && proxyUri.Scheme != UriScheme.Http)
if (proxyUri != null && !HttpUtilities.IsSupportedProxyScheme(proxyUri.Scheme))
{
throw new NotSupportedException(SR.net_http_invalid_proxy_scheme);
}
MihaZupan marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.IO;

namespace System.Net.Http
{
internal class SocksException : IOException
{
public SocksException(string message) : base(message) { }

public SocksException(string message, Exception innerException) : base(message, innerException) { }
}
}
Loading