Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

CSHARP-734: SOCKS5 Proxy Support #1731

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

Open
papafe wants to merge 87 commits into mongodb:main
base: main
Choose a base branch
Loading
from papafe:csharp734
Open

Conversation

Copy link
Contributor

@papafe papafe commented Jul 16, 2025
edited
Loading

No description provided.

@papafe papafe marked this pull request as ready for review August 12, 2025 14:54
@papafe papafe requested a review from a team as a code owner August 12, 2025 14:54
sb.Append(Authentication switch
{
Socks5AuthenticationSettings.UsernamePasswordAuthenticationSettings up =>
$"UsernamePassword (Username: {up.Username}, Password: {up.Password})",
Copy link
Member

@sanych-sun sanych-sun Aug 13, 2025

Choose a reason for hiding this comment

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

MongoUrlBuilder.ToString is being used to build the Url from provided parameters, so having passwords there is probably the expected behavior (I suppose we ought to have another method for that, something like BuildUrl). However this class is user-facing settings class, there is a bigger chances it could be converted to string and logged or even worse outputted as a part of exception. I've checked MongoClientSettings - it does not look like it can leak passwords in the similar way.

get => _proxyPort;
set
{
if (value is < 0 or > 65535)
Copy link
Member

@sanych-sun sanych-sun Aug 13, 2025

Choose a reason for hiding this comment

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

We have Ensure.IsNullOrBetween if this is what you are looking for.

Copy link
Contributor

@BorisDog BorisDog left a comment

Choose a reason for hiding this comment

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

Looks great overall!

@papafe papafe requested a review from BorisDog August 20, 2025 08:07
Copy link
Contributor

@BorisDog BorisDog left a comment

Choose a reason for hiding this comment

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

Few minor comment + unaddressed comment

@papafe papafe force-pushed the csharp734 branch 2 times, most recently from 0d0d06f to fc7c610 Compare August 21, 2025 09:48
@papafe papafe requested a review from BorisDog August 21, 2025 09:51
Comment on lines 192 to 202
var acceptedMethod = buffer[1];
if (acceptedMethod == MethodUsernamePassword)
{
if (!useAuth)
{
throw new IOException("SOCKS5 proxy requires authentication, but no credentials were provided.");
}

return true;
}
Copy link
Member

Choose a reason for hiding this comment

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

As i understand, If the server cannot find any acceptable authentication methods offered by the client, it replies with 0xFF ("no acceptable methods"). The only way the server can select username/password (0x02) is if the client offered it. A case where the server replies with 0x02 but the client did not offer it should not occur in a compliant implementation - that would indicate a protocol violation by the server.

To handle this defensively, what do you think about throwing an error such as:
"Unexpected server response: server replied with but expected one of "?

This would highlight the unexpected behavior to users and, in edge cases where a proxy forces 0x02, might help discourage clients from inadvertently sending credentials.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this should not happen unless the server is behaving badly, I'm trying to be defensive here.

Regarding the message, I agree, it's a little vague, I'll try to rephrase it so it highlights the issue better.

return false;
}

private static int CreateAuthenticationRequest(byte[] buffer, Socks5AuthenticationSettings authenticationSettings)
Copy link
Member

@vbabanin vbabanin Aug 22, 2025
edited
Loading

Choose a reason for hiding this comment

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

[nitpick] The method signature looks generic, but internally it casts from Socks5AuthenticationSettings to UsernamePasswordAuthenticationSettings, so it effectively only works with the 0x02 method. Would it make sense to make the signature explicit (e.g. only accept UsernamePasswordAuthenticationSettings)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we can keep the method generic, but throw an exception if the authentication is not for the correct type (that should not happen), so we keep the method open to extension in the future if needed.

Comment on lines 227 to 241
private static void ProcessAuthenticationResponse(byte[] buffer)
{
if (buffer[0] != SubnegotiationVersion || buffer[1] != Socks5Success)
{
throw new IOException($"SOCKS5 authentication failed. Version: {buffer[0]}, Status: {buffer[1]}.");
}
}
Copy link
Member

@vbabanin vbabanin Aug 22, 2025
edited
Loading

Choose a reason for hiding this comment

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

According to the RFC-1929, authentication only fails when buffer[1] != Socks5Success. If buffer[0] != SubnegotiationVersion (despite the client specifying 1), that suggests a protocol-noncompliant response from the server/proxy.

The RFC states:

"The VER field contains the current version of the subnegotiation, which is X'01'."

I'd propose making the failure reason explicit here. When buffer[0] != SubnegotiationVersion, we should indicate that this is an unexpected server response rather than a credentials error. For example: "SOCKS5 auth subnegotiation: unexpected VER from server". This would also make troubleshooting easier. What do you think?

BorisDog reacted with thumbs up emoji
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree!

try
{
var written = Encoding.UTF8.GetBytes(input, 0, input.Length, buffer, offset);
return checked((byte)written);
Copy link
Member

@vbabanin vbabanin Aug 22, 2025
edited
Loading

Choose a reason for hiding this comment

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

Casting username or password length to byte works correctly if the int values are ≤ 0xFF. Otherwise, we end up reporting incorrect lengths and writing more bytes than the proxy expects, which could silently corrupt data and result in unexpected responses from the server.

I’d propose enforcing a maximum length (in UTF-8 bytes, not chars/code points) for username and password in UsernamePasswordAuthenticationSettings. That way, it is validate at the API edge and fail fast with a clear error message.

The same applies to var hostLength = EncodeString(targetHost, buffer, 5, nameof(targetHost));. Per RFC 1035 §2.3.4, the maximum domain name length is 255 bytes (0xFF), which i'd propose enforcing in Socks5ProxySettings.

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is what the checked statement is doing, it will throw an OverflowException in case the value cannot be safely converted to byte.
I agree that probably we can be a little more user friendly here, instead of throwing a generic exception. I'll try to add some more validation to our settings class.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added.


if (buffer[1] != Socks5Success)
{
throw new IOException($"SOCKS5 connect failed. Error code: {buffer[1]}");
Copy link
Member

Choose a reason for hiding this comment

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

Should we provide a more user-friendly message here indicating what type of error the server returned?

The RFC defines the following failure reply types:

0x01 General SOCKS server failure
0x02 Connection not allowed by ruleset
0x03 Network unreachable
0x04 Host unreachable
0x05 Connection refused
0x06 TTL expired
0x07 Command not supported
0x08 Address type not supported

BorisDog reacted with thumbs up emoji
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense! Added.

@papafe papafe requested a review from vbabanin August 29, 2025 09:00
@rstam rstam self-requested a review September 2, 2025 16:15
Copy link
Contributor

@BorisDog BorisDog left a comment

Choose a reason for hiding this comment

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

Few minor comments

BorisDog
BorisDog previously approved these changes Sep 3, 2025
Copy link
Contributor

@BorisDog BorisDog left a comment

Choose a reason for hiding this comment

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

LGTM + few minor comments

BorisDog
BorisDog previously approved these changes Sep 4, 2025
Copy link
Contributor

@BorisDog BorisDog left a comment

Choose a reason for hiding this comment

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

LGTM!

papafe added 27 commits September 4, 2025 19:54
Copy link
Member

@sanych-sun sanych-sun left a comment

Choose a reason for hiding this comment

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

LGTM

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

@sanych-sun sanych-sun sanych-sun approved these changes

@BorisDog BorisDog BorisDog left review comments

@vbabanin vbabanin Awaiting requested review from vbabanin

@rstam rstam Awaiting requested review from rstam

Assignees
No one assigned
Labels
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

AltStyle によって変換されたページ (->オリジナル) /