-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Conversation
Tagging subscribers to this area: @dotnet/ncl Issue Details@MihaZupan Opening earlier for opinions. Code not normalized (like exceptions). I'm not sure how to implement the GSSAPI authentication (https://tools.ietf.org/html/rfc1961). It seems covering more scope. Manually tested with a local socks5 proxy and working. However, I don't have an instance running with authentication support. No idea about how to test it in CI.
|
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocksHelper.cs
Outdated
Show resolved
Hide resolved
Thanks for working on this!
I only had support for username/pass authentiation in mind. I have not seen a single request for GSSAPI support in the past (from my HttpToSocks5Proxy project) - I would treat it as out-of-scope for this PR. I realize this is a draft PR, but would you be interested in adding support for Socks4(a) as well? It should only affect the implementation in the |
Sure. I opened it early just to get some opinion. |
4/4a added. |
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs
Show resolved
Hide resolved
Since SOCKS6 is still draft, more complex, and may not have a existing server, I think it shouldn't be supported now. Can we start to normalize this, for example which exception to throw? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since SOCKS6 is still draft, more complex, and may not have a existing server, I think it shouldn't be supported now.
Agreed.
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocksHelper.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocksHelper.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocksHelper.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocksHelper.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The host
could be an IP address - we have to check for this and use the appropriate address type for requests.
If IPv4, use that address type. Socks4a need not be used (it will be the same as 4).
If IPv6:
- Socks5: Use the IPv6 address type
- Socks4/4a: check
IPAddress.IsIPv4MappedToIPv6
- if it is not, can we still use the Socks4a and pretend it's a domain name?
If it's a domain name:
- Socks5/Socks4a: Use the domain name
- Socks4: Try to resolve to IPv4
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocksHelper.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocksHelper.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocksHelper.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Http/tests/FunctionalTests/Socks/SocksProxyTest.cs
Show resolved
Hide resolved
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocksHelper.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocksHelper.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocksHelper.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocksHelper.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocksHelper.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocksHelper.cs
Outdated
Show resolved
Hide resolved
Failures are all #51346 |
string username = Encoding.UTF8.GetString(usernameBuffer.AsSpan(0, usernameBytes)); | ||
if (username != _username) | ||
{ | ||
ns.WriteByte(4); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need for async?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a test only server. I just want it to be simple.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There isn't a helper to write 1 byte asynchronously. So I'm not willing to bother with it.
} | ||
} | ||
|
||
ns.WriteByte(4); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need for async?
} | ||
catch | ||
{ | ||
Debug.Assert(Encoding.UTF8.GetByteCount(chars) > 255); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assert might fail if you pass in invalid Unicode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you mean? Afaik the default Encoding.UTF8 doesn't throw but instead replaces chars with U+FFFD
while (buffer.Length != 0) | ||
{ | ||
int bytesRead = async | ||
? await stream.ReadAsync(buffer).ConfigureAwait(false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this have cancellation support?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is using the same approach as HttpConnection
, where we register for cancellation once and have the callback dispose the stream. All other calls to stream Read/Write don't pass the CT and instead rely on the Dispose to wake them up.
runtime/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocksHelper.cs
Line 34 in 95400e2
using (cancellationToken.Register(s => ((Stream)s!).Dispose(), stream)) |
totalLength += hostLength + 1; | ||
} | ||
|
||
await WriteAsync(stream, buffer.AsMemory(0, totalLength), async).ConfigureAwait(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, does this need cancellation?
{ | ||
if (password != null && username == null) | ||
{ | ||
throw new ArgumentException("Password must be used together with username.", nameof(password)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it valid to have a non-null username and a null password?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes in case of socks4(a), which only uses the username as an identifier, but doesn't actually do user/pass auth.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rfc1929 spec implies username and password shouldn't be 0 length. Should we add a guard for that? Looks overkill.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't bother, we can let the server decide whether to reject it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm once feedback addressed.
/azp run runtime-libraries-coreclr outerloop |
Azure Pipelines successfully started running 1 pipeline(s). |
Thanks @huoyaoyuan! |
Thank you @huoyaoyuan for pushing it through and @MihaZupan for your help! |
Closes #17740
@MihaZupan Opening earlier for opinions. Code not normalized (like exceptions).
I'm not sure how to implement the GSSAPI authentication (https://tools.ietf.org/html/rfc1961). It seems covering more scope.
Manually tested with a local socks5 proxy and working. However, I don't have an instance running with authentication support. No idea about how to test it in CI.