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

Conversation

huoyaoyuan
Copy link
Member

@huoyaoyuan huoyaoyuan commented Feb 28, 2021

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.

@ghost
Copy link

ghost commented Feb 28, 2021

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Issue Details

#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.

Author: huoyaoyuan
Assignees: -
Labels:

area-System.Net.Http

Milestone: -

@MihaZupan
Copy link
Member

@MihaZupan Opening earlier for opinions. Code not normalized (like exceptions).

Thanks for working on this!

I'm not sure how to implement the GSSAPI authentication (https://tools.ietf.org/html/rfc1961). It seems covering more scope.

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.
It turns out that socks proxy servers often don't end up requesting authentication at all.

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 SocksHelper (based on the scheme in the proxy uri).
There are for some edge-cases, like having to resolve the IP address ahead-of-time for Socks4, since it does not support domain names.

@huoyaoyuan
Copy link
Member Author

I realize this is a draft PR, but would you be interested in adding support for Socks4(a) as well?

Sure. I opened it early just to get some opinion.

@huoyaoyuan
Copy link
Member Author

4/4a added.
SOCKS6 has been draft for a long time and I'm also interested. Does it need to be draft PR until finalization?

@huoyaoyuan huoyaoyuan changed the title Draft implementation of SOCKS5 proxy support Draft implementation of SOCKS4/4a/5 proxy support Mar 1, 2021
Base automatically changed from master to main March 1, 2021 09:08
@huoyaoyuan
Copy link
Member Author

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?

Copy link
Member

@MihaZupan MihaZupan left a 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.

Copy link
Member

@MihaZupan MihaZupan left a 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

@MihaZupan
Copy link
Member

Failures are all #51346

string username = Encoding.UTF8.GetString(usernameBuffer.AsSpan(0, usernameBytes));
if (username != _username)
{
ns.WriteByte(4);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for async?

Copy link
Member Author

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.

Copy link
Member Author

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);
Copy link
Contributor

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);
Copy link
Contributor

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.

Copy link
Member

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)
Copy link
Contributor

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?

Copy link
Member

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.

using (cancellationToken.Register(s => ((Stream)s!).Dispose(), stream))

totalLength += hostLength + 1;
}

await WriteAsync(stream, buffer.AsMemory(0, totalLength), async).ConfigureAwait(false);
Copy link
Contributor

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));
Copy link
Contributor

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?

Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Contributor

@scalablecory scalablecory left a 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.

@MihaZupan
Copy link
Member

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@MihaZupan MihaZupan merged commit 32d1a25 into dotnet:main Apr 22, 2021
@MihaZupan
Copy link
Member

Thanks @huoyaoyuan!

@huoyaoyuan huoyaoyuan deleted the socks-proxy branch April 22, 2021 17:56
@karelz
Copy link
Member

karelz commented Apr 22, 2021

Thank you @huoyaoyuan for pushing it through and @MihaZupan for your help!

@karelz karelz added this to the 6.0.0 milestone May 20, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Jun 19, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for Socks4/5 proxy in HttpClient/WebRequests
5 participants