From a09ba4f504f330bb2b033ea2bf9d8a0cb9a292cf Mon Sep 17 00:00:00 2001
From: Ferdinando Papale <4850119+papafe@users.noreply.github.com>
Date: Tue, 24 Jun 2025 14:00:01 +0200
Subject: [PATCH 1/9] CSHARP-734: SOCKS5 Proxy Support
---
.../Core/Configuration/ConnectionString.cs | 76 +++++++++++++++++
src/MongoDB.Driver/MongoUrl.cs | 28 +++++++
src/MongoDB.Driver/MongoUrlBuilder.cs | 82 +++++++++++++++++++
3 files changed, 186 insertions(+)
diff --git a/src/MongoDB.Driver/Core/Configuration/ConnectionString.cs b/src/MongoDB.Driver/Core/Configuration/ConnectionString.cs
index 79349c035cc..0236f0b68e7 100644
--- a/src/MongoDB.Driver/Core/Configuration/ConnectionString.cs
+++ b/src/MongoDB.Driver/Core/Configuration/ConnectionString.cs
@@ -92,6 +92,10 @@ public sealed class ConnectionString
private string _replicaSet;
private bool? _retryReads;
private bool? _retryWrites;
+ private string _proxyHost;
+ private int? _proxyPort;
+ private string _proxyUsername;
+ private string _proxyPassword;
private ConnectionStringScheme _scheme;
private ServerMonitoringMode? _serverMonitoringMode;
private TimeSpan? _serverSelectionTimeout;
@@ -356,6 +360,26 @@ public string Password
get { return _password; }
}
+ ///
+ /// Gets the proxy host.
+ ///
+ public string ProxyHost => _proxyHost;
+
+ ///
+ /// Gets the proxy port.
+ ///
+ public int? ProxyPort => _proxyPort;
+
+ ///
+ /// Gets the proxy username.
+ ///
+ public string ProxyUsername => _proxyUsername;
+
+ ///
+ /// Gets the proxy password.
+ ///
+ public string ProxyPassword => _proxyPassword;
+
///
/// Gets the read concern level.
///
@@ -903,6 +927,29 @@ private void Parse()
}
}
+ if (string.IsNullOrEmpty(_proxyHost))
+ {
+ if (_proxyPort is not null)
+ {
+ throw new MongoConfigurationException("proxyPort cannot be specified without proxyHost.");
+ }
+
+ if (!string.IsNullOrEmpty(_proxyUsername))
+ {
+ throw new MongoConfigurationException("proxyUsername cannot be specified without proxyHost.");
+ }
+
+ if (!string.IsNullOrEmpty(_proxyPassword))
+ {
+ throw new MongoConfigurationException("proxyPassword cannot be specified without proxyHost.");
+ }
+ }
+
+ if (string.IsNullOrEmpty(_proxyUsername) != string.IsNullOrEmpty(_proxyPassword))
+ {
+ throw new MongoConfigurationException("proxyUsername and proxyPassword must both be specified or neither.");
+ }
+
string ProtectConnectionString(string connectionString)
{
var protectedString = Regex.Replace(connectionString, @"(?<=://)[^/]*(?=@)", "");
@@ -995,6 +1042,35 @@ private void ParseOption(string name, string value)
case "minpoolsize":
_minPoolSize = ParseInt32(name, value);
break;
+ case "proxyhost":
+ _proxyHost = value;
+ if (_proxyHost.Length == 0)
+ {
+ throw new MongoConfigurationException("proxyHost cannot be empty.");
+ }
+ break;
+ case "proxyport":
+ var proxyPortValue = ParseInt32(name, value);
+ if (proxyPortValue is < 0 or > 65535)
+ {
+ throw new MongoConfigurationException("proxyPort must be between 0 and 65535.");
+ }
+ _proxyPort = proxyPortValue;
+ break;
+ case "proxyusername":
+ _proxyUsername = value;
+ if (_proxyUsername.Length == 0)
+ {
+ throw new MongoConfigurationException("proxyUsername cannot be empty.");
+ }
+ break;
+ case "proxypassword":
+ _proxyPassword = value;
+ if (_proxyPassword.Length == 0)
+ {
+ throw new MongoConfigurationException("proxyPassword cannot be empty.");
+ }
+ break;
case "readconcernlevel":
_readConcernLevel = ParseEnum(name, value);
break;
diff --git a/src/MongoDB.Driver/MongoUrl.cs b/src/MongoDB.Driver/MongoUrl.cs
index 2c1446fd57c..13a3e46a0d5 100644
--- a/src/MongoDB.Driver/MongoUrl.cs
+++ b/src/MongoDB.Driver/MongoUrl.cs
@@ -64,6 +64,10 @@ public class MongoUrl : IEquatable
private readonly bool? _retryReads;
private readonly bool? _retryWrites;
private readonly TimeSpan _localThreshold;
+ private readonly string _proxyHost;
+ private readonly int? _proxyPort;
+ private readonly string _proxyUsername;
+ private readonly string _proxyPassword;
private readonly ConnectionStringScheme _scheme;
private readonly IEnumerable _servers;
private readonly ServerMonitoringMode? _serverMonitoringMode;
@@ -117,6 +121,10 @@ internal MongoUrl(MongoUrlBuilder builder)
_maxConnectionPoolSize = builder.MaxConnectionPoolSize;
_minConnectionPoolSize = builder.MinConnectionPoolSize;
_password = builder.Password;
+ _proxyHost = builder.ProxyHost;
+ _proxyPort = builder.ProxyPort;
+ _proxyUsername = builder.ProxyUsername;
+ _proxyPassword = builder.ProxyPassword;
_readConcernLevel = builder.ReadConcernLevel;
_readPreference = builder.ReadPreference;
_replicaSetName = builder.ReplicaSetName;
@@ -358,6 +366,26 @@ public string Password
get { return _password; }
}
+ ///
+ /// Gets the proxy host.
+ ///
+ public string ProxyHost => _proxyHost;
+
+ ///
+ /// Gets the proxy port.
+ ///
+ public int ProxyPort => _proxyPort;
+
+ ///
+ /// Gets the proxy username.
+ ///
+ public string ProxyUsername => _proxyUsername;
+
+ ///
+ /// Gets the proxy password.
+ ///
+ public string ProxyPassword => _proxyPassword;
+
///
/// Gets the read concern level.
///
diff --git a/src/MongoDB.Driver/MongoUrlBuilder.cs b/src/MongoDB.Driver/MongoUrlBuilder.cs
index 3858228cbf6..c50288823c2 100644
--- a/src/MongoDB.Driver/MongoUrlBuilder.cs
+++ b/src/MongoDB.Driver/MongoUrlBuilder.cs
@@ -60,6 +60,10 @@ public class MongoUrlBuilder
private string _replicaSetName;
private bool? _retryReads;
private bool? _retryWrites;
+ private string _proxyHost;
+ private int? _proxyPort;
+ private string _proxyUsername;
+ private string _proxyPassword;
private ConnectionStringScheme _scheme;
private IEnumerable _servers;
private ServerMonitoringMode? _serverMonitoringMode;
@@ -104,6 +108,10 @@ public MongoUrlBuilder()
_maxConnectionPoolSize = MongoDefaults.MaxConnectionPoolSize;
_minConnectionPoolSize = MongoDefaults.MinConnectionPoolSize;
_password = null;
+ _proxyHost = null;
+ _proxyPort = null;
+ _proxyUsername = null;
+ _proxyPassword = null;
_readConcernLevel = null;
_readPreference = null;
_replicaSetName = null;
@@ -438,6 +446,59 @@ public string Password
set { _password = value; }
}
+ ///
+ ///
+ ///
+ public string ProxyHost
+ {
+ get => _proxyHost;
+ set
+ {
+ _proxyHost = Ensure.IsNotNullOrEmpty(value, nameof(ProxyHost));
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public int? ProxyPort
+ {
+ get => _proxyPort;
+ set
+ {
+ if (value is < 0 or > 65535)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "ProxyPort must be between 0 and 65535.");
+ }
+ _proxyPort = value;
+ }
+ }
+
+ ///
+ ///
+ ///
+ public string ProxyUsername
+ {
+ get => _proxyUsername;
+ set
+ {
+ _proxyUsername = Ensure.IsNotNullOrEmpty(value, nameof(ProxyUsername));
+ }
+ }
+
+ ///
+ ///
+ ///
+ public string ProxyPassword
+ {
+ get => _proxyPassword;
+ set
+ {
+ _proxyPassword = Ensure.IsNotNullOrEmpty(value, nameof(ProxyPassword));
+ }
+ }
+
///
/// Gets or sets the read concern level.
///
@@ -760,6 +821,7 @@ public MongoUrl ToMongoUrl()
/// The canonical URL.
public override string ToString()
{
+ //TODO Need to add options here too
StringBuilder url = new StringBuilder();
if (_scheme == ConnectionStringScheme.MongoDB)
{
@@ -980,6 +1042,22 @@ public override string ToString()
{
query.AppendFormat("retryWrites={0}&", JsonConvert.ToString(_retryWrites.Value));
}
+ if(!string.IsNullOrEmpty(_proxyHost))
+ {
+ query.AppendFormat("proxyHost={0}&", _proxyHost);
+ }
+ if (_proxyPort.HasValue)
+ {
+ query.AppendFormat("proxyPort={0}&", _proxyPort);
+ }
+ if (!string.IsNullOrEmpty(_proxyUsername))
+ {
+ query.AppendFormat("proxyUsername={0}&", _proxyUsername);
+ }
+ if (!string.IsNullOrEmpty(_proxyPassword))
+ {
+ query.AppendFormat("proxyPassword={0}&", _proxyPassword);
+ }
if (_srvMaxHosts.HasValue)
{
query.AppendFormat("srvMaxHosts={0}&", _srvMaxHosts);
@@ -1026,6 +1104,10 @@ private void InitializeFromConnectionString(ConnectionString connectionString)
_maxConnectionPoolSize = connectionString.MaxPoolSize.GetValueOrDefault(MongoDefaults.MaxConnectionPoolSize);
_minConnectionPoolSize = connectionString.MinPoolSize.GetValueOrDefault(MongoDefaults.MinConnectionPoolSize);
_password = connectionString.Password;
+ _proxyHost = connectionString.ProxyHost;
+ _proxyPort = connectionString.ProxyPort;
+ _proxyUsername = connectionString.ProxyUsername;
+ _proxyPassword = connectionString.ProxyPassword;
_readConcernLevel = connectionString.ReadConcernLevel;
if (connectionString.ReadPreference.HasValue || connectionString.ReadPreferenceTags != null || connectionString.MaxStaleness.HasValue)
{
From 669618430bcbde0d26fb2decf919b927bf130eae Mon Sep 17 00:00:00 2001
From: Ferdinando Papale <4850119+papafe@users.noreply.github.com>
Date: Tue, 24 Jun 2025 14:02:22 +0200
Subject: [PATCH 2/9] Small fix
---
src/MongoDB.Driver/MongoUrl.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/MongoDB.Driver/MongoUrl.cs b/src/MongoDB.Driver/MongoUrl.cs
index 13a3e46a0d5..912c0b15c49 100644
--- a/src/MongoDB.Driver/MongoUrl.cs
+++ b/src/MongoDB.Driver/MongoUrl.cs
@@ -374,7 +374,7 @@ public string Password
///
/// Gets the proxy port.
///
- public int ProxyPort => _proxyPort;
+ public int? ProxyPort => _proxyPort;
///
/// Gets the proxy username.
From b3020b5a27e7968c1afbcf28fcd977494114a2a8 Mon Sep 17 00:00:00 2001
From: Ferdinando Papale <4850119+papafe@users.noreply.github.com>
Date: Tue, 24 Jun 2025 15:54:28 +0200
Subject: [PATCH 3/9] Added options to MongoClient settings
---
src/MongoDB.Driver/MongoClientSettings.cs | 121 ++++++++++++++++++++++
1 file changed, 121 insertions(+)
diff --git a/src/MongoDB.Driver/MongoClientSettings.cs b/src/MongoDB.Driver/MongoClientSettings.cs
index d7da362ca92..52ff666f3db 100644
--- a/src/MongoDB.Driver/MongoClientSettings.cs
+++ b/src/MongoDB.Driver/MongoClientSettings.cs
@@ -59,6 +59,10 @@ public class MongoClientSettings : IEquatable, IInheritable
private TimeSpan _maxConnectionLifeTime;
private int _maxConnectionPoolSize;
private int _minConnectionPoolSize;
+ private string _proxyHost;
+ private int? _proxyPort;
+ private string _proxyUsername;
+ private string _proxyPassword;
private ReadConcern _readConcern;
private UTF8Encoding _readEncoding;
private ReadPreference _readPreference;
@@ -110,6 +114,10 @@ public MongoClientSettings()
_maxConnectionLifeTime = MongoDefaults.MaxConnectionLifeTime;
_maxConnectionPoolSize = MongoDefaults.MaxConnectionPoolSize;
_minConnectionPoolSize = MongoDefaults.MinConnectionPoolSize;
+ _proxyHost = null;
+ _proxyPort = null;
+ _proxyUsername = null;
+ _proxyPassword = null;
_readConcern = ReadConcern.Default;
_readEncoding = null;
_readPreference = ReadPreference.Primary;
@@ -428,6 +436,64 @@ public int MinConnectionPoolSize
}
}
+ ///
+ /// Gets or sets the proxy host.
+ ///
+ public string ProxyHost
+ {
+ get => _proxyHost;
+ set
+ {
+ if (_isFrozen) { throw new InvalidOperationException("MongoClientSettings is frozen."); }
+ _proxyHost = Ensure.IsNotNullOrEmpty(value, nameof(ProxyHost));
+ }
+ }
+
+ ///
+ /// Gets or sets the proxy port.
+ ///
+ public int? ProxyPort
+ {
+ get => _proxyPort;
+ set
+ {
+ if (_isFrozen) { throw new InvalidOperationException("MongoClientSettings is frozen."); }
+
+ if (value is < 0 or > 65535)
+ {
+ throw new MongoConfigurationException("ProxyPort must be between 0 and 65535.");
+ }
+
+ _proxyPort = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the proxy username.
+ ///
+ public string ProxyUsername
+ {
+ get => _proxyUsername;
+ set
+ {
+ if (_isFrozen) { throw new InvalidOperationException("MongoClientSettings is frozen."); }
+ _proxyUsername = Ensure.IsNotNullOrEmpty(value, nameof(ProxyUsername));
+ }
+ }
+
+ ///
+ /// Gets or sets the proxy password.
+ ///
+ public string ProxyPassword
+ {
+ get => _proxyPassword;
+ set
+ {
+ if (_isFrozen) { throw new InvalidOperationException("MongoClientSettings is frozen."); }
+ _proxyPassword = Ensure.IsNotNullOrEmpty(value, nameof(ProxyPassword));
+ }
+ }
+
///
/// Gets or sets the read concern.
///
@@ -863,6 +929,10 @@ public static MongoClientSettings FromUrl(MongoUrl url)
clientSettings.MaxConnectionLifeTime = url.MaxConnectionLifeTime;
clientSettings.MaxConnectionPoolSize = ConnectionStringConversions.GetEffectiveMaxConnections(url.MaxConnectionPoolSize);
clientSettings.MinConnectionPoolSize = url.MinConnectionPoolSize;
+ clientSettings.ProxyHost = url.ProxyHost;
+ clientSettings.ProxyPort = url.ProxyPort;
+ clientSettings.ProxyUsername = url.ProxyUsername;
+ clientSettings.ProxyPassword = url.ProxyPassword;
clientSettings.ReadConcern = new ReadConcern(url.ReadConcernLevel);
clientSettings.ReadEncoding = null; // ReadEncoding must be provided in code
clientSettings.ReadPreference = (url.ReadPreference == null) ? ReadPreference.Primary : url.ReadPreference;
@@ -920,6 +990,10 @@ public MongoClientSettings Clone()
clone._maxConnectionLifeTime = _maxConnectionLifeTime;
clone._maxConnectionPoolSize = _maxConnectionPoolSize;
clone._minConnectionPoolSize = _minConnectionPoolSize;
+ clone._proxyHost = _proxyHost;
+ clone._proxyPort = _proxyPort;
+ clone._proxyUsername = _proxyUsername;
+ clone._proxyPassword = _proxyPassword;
clone._readConcern = _readConcern;
clone._readEncoding = _readEncoding;
clone._readPreference = _readPreference;
@@ -989,6 +1063,10 @@ public override bool Equals(object obj)
_maxConnectionLifeTime == rhs._maxConnectionLifeTime &&
_maxConnectionPoolSize == rhs._maxConnectionPoolSize &&
_minConnectionPoolSize == rhs._minConnectionPoolSize &&
+ _proxyHost == rhs._proxyHost &&
+ _proxyPort == rhs._proxyPort &&
+ _proxyUsername == rhs._proxyUsername &&
+ _proxyPassword == rhs._proxyPassword &&
object.Equals(_readEncoding, rhs._readEncoding) &&
object.Equals(_readConcern, rhs._readConcern) &&
object.Equals(_readPreference, rhs._readPreference) &&
@@ -1076,6 +1154,10 @@ public override int GetHashCode()
.Hash(_maxConnectionLifeTime)
.Hash(_maxConnectionPoolSize)
.Hash(_minConnectionPoolSize)
+ .Hash(_proxyHost)
+ .Hash(_proxyPort)
+ .Hash(_proxyUsername)
+ .Hash(_proxyPassword)
.Hash(_readConcern)
.Hash(_readEncoding)
.Hash(_readPreference)
@@ -1145,6 +1227,22 @@ public override string ToString()
sb.AppendFormat("MaxConnectionLifeTime={0};", _maxConnectionLifeTime);
sb.AppendFormat("MaxConnectionPoolSize={0};", _maxConnectionPoolSize);
sb.AppendFormat("MinConnectionPoolSize={0};", _minConnectionPoolSize);
+ if (_proxyHost != null)
+ {
+ sb.AppendFormat("ProxyHost={0};", _proxyHost);
+ }
+ if (_proxyPort != null)
+ {
+ sb.AppendFormat("ProxyPort={0};", _proxyPort.Value);
+ }
+ if (_proxyUsername != null)
+ {
+ sb.AppendFormat("ProxyUsername={0};", _proxyUsername);
+ }
+ if (_proxyPassword != null)
+ {
+ sb.AppendFormat("ProxyPassword={0};", _proxyPassword);
+ }
if (_readEncoding != null)
{
sb.Append("ReadEncoding=UTF8Encoding;");
@@ -1297,6 +1395,29 @@ private void ThrowIfSettingsAreInvalid()
throw new InvalidOperationException("Load balanced mode cannot be used with direct connection.");
}
}
+
+ if (string.IsNullOrEmpty(_proxyHost))
+ {
+ if (_proxyPort is not null)
+ {
+ throw new InvalidOperationException("ProxyPort cannot be specified without ProxyHost.");
+ }
+
+ if (!string.IsNullOrEmpty(_proxyUsername))
+ {
+ throw new InvalidOperationException("ProxyUsername cannot be specified without ProxyHost.");
+ }
+
+ if (!string.IsNullOrEmpty(_proxyPassword))
+ {
+ throw new InvalidOperationException("ProxyPassword cannot be specified without ProxyHost.");
+ }
+ }
+
+ if (string.IsNullOrEmpty(_proxyUsername) != string.IsNullOrEmpty(_proxyPassword))
+ {
+ throw new InvalidOperationException("ProxyUsername and ProxyPassword must both be specified or neither.");
+ }
}
}
}
From 7931f70bae5bfa6df744a852ce2767bcefa132bc Mon Sep 17 00:00:00 2001
From: Ferdinando Papale <4850119+papafe@users.noreply.github.com>
Date: Tue, 24 Jun 2025 15:54:42 +0200
Subject: [PATCH 4/9] Added uri options test for proxy options
---
.../uri-options/tests/proxy-options.json | 139 ++++++++++++++++++
.../uri-options/tests/proxy-options.yml | 121 +++++++++++++++
2 files changed, 260 insertions(+)
create mode 100644 specifications/uri-options/tests/proxy-options.json
create mode 100644 specifications/uri-options/tests/proxy-options.yml
diff --git a/specifications/uri-options/tests/proxy-options.json b/specifications/uri-options/tests/proxy-options.json
new file mode 100644
index 00000000000..585546ead7f
--- /dev/null
+++ b/specifications/uri-options/tests/proxy-options.json
@@ -0,0 +1,139 @@
+{
+ "tests": [
+ {
+ "description": "proxyPort without proxyHost",
+ "uri": "mongodb://localhost/?proxyPort=1080",
+ "valid": false,
+ "warning": false,
+ "hosts": null,
+ "auth": null,
+ "options": null
+ },
+ {
+ "description": "proxyUsername without proxyHost",
+ "uri": "mongodb://localhost/?proxyUsername=abc",
+ "valid": false,
+ "warning": false,
+ "hosts": null,
+ "auth": null,
+ "options": null
+ },
+ {
+ "description": "proxyPassword without proxyHost",
+ "uri": "mongodb://localhost/?proxyPassword=def",
+ "valid": false,
+ "warning": false,
+ "hosts": null,
+ "auth": null,
+ "options": null
+ },
+ {
+ "description": "all other proxy options without proxyHost",
+ "uri": "mongodb://localhost/?proxyPort=1080&proxyUsername=abc&proxyPassword=def",
+ "valid": false,
+ "warning": false,
+ "hosts": null,
+ "auth": null,
+ "options": null
+ },
+ {
+ "description": "proxyUsername without proxyPassword",
+ "uri": "mongodb://localhost/?proxyHost=localhost&proxyUsername=abc",
+ "valid": false,
+ "warning": false,
+ "hosts": null,
+ "auth": null,
+ "options": null
+ },
+ {
+ "description": "proxyPassword without proxyUsername",
+ "uri": "mongodb://localhost/?proxyHost=localhost&proxyPassword=def",
+ "valid": false,
+ "warning": false,
+ "hosts": null,
+ "auth": null,
+ "options": null
+ },
+ {
+ "description": "multiple proxyHost parameters",
+ "uri": "mongodb://localhost/?proxyHost=localhost&proxyHost=localhost2",
+ "valid": false,
+ "warning": false,
+ "hosts": null,
+ "auth": null,
+ "options": null
+ },
+ {
+ "description": "multiple proxyPort parameters",
+ "uri": "mongodb://localhost/?proxyHost=localhost&proxyPort=1234&proxyPort=12345",
+ "valid": false,
+ "warning": false,
+ "hosts": null,
+ "auth": null,
+ "options": null
+ },
+ {
+ "description": "multiple proxyUsername parameters",
+ "uri": "mongodb://localhost/?proxyHost=localhost&proxyUsername=abc&proxyUsername=def&proxyPassword=123",
+ "valid": false,
+ "warning": false,
+ "hosts": null,
+ "auth": null,
+ "options": null
+ },
+ {
+ "description": "multiple proxyPassword parameters",
+ "uri": "mongodb://localhost/?proxyHost=localhost&proxyUsername=abc&proxyPassword=123&proxyPassword=456",
+ "valid": false,
+ "warning": false,
+ "hosts": null,
+ "auth": null,
+ "options": null
+ },
+ {
+ "description": "only host present",
+ "uri": "mongodb://localhost/?proxyHost=localhost",
+ "valid": true,
+ "warning": false,
+ "hosts": null,
+ "auth": null,
+ "options": {}
+ },
+ {
+ "description": "host and default port present",
+ "uri": "mongodb://localhost/?proxyHost=localhost&proxyPort=1080",
+ "valid": true,
+ "warning": false,
+ "hosts": null,
+ "auth": null,
+ "options": {}
+ },
+ {
+ "description": "host and non-default port present",
+ "uri": "mongodb://localhost/?proxyHost=localhost&proxyPort=12345",
+ "valid": true,
+ "warning": false,
+ "hosts": null,
+ "auth": null,
+ "options": {}
+ },
+ {
+ "description": "replicaset, host and non-default port present",
+ "uri": "mongodb://rs1,rs2,rs3/?proxyHost=localhost&proxyPort=12345",
+ "valid": true,
+ "warning": false,
+ "hosts": null,
+ "auth": null,
+ "options": {}
+ },
+ {
+ "description": "all options present",
+ "uri": "mongodb://rs1,rs2,rs3/?proxyHost=localhost&proxyPort=12345&proxyUsername=asdf&proxyPassword=qwerty",
+ "valid": true,
+ "warning": false,
+ "hosts": null,
+ "auth": null,
+ "options": {}
+ }
+ ]
+}
diff --git a/specifications/uri-options/tests/proxy-options.yml b/specifications/uri-options/tests/proxy-options.yml
new file mode 100644
index 00000000000..a97863dd599
--- /dev/null
+++ b/specifications/uri-options/tests/proxy-options.yml
@@ -0,0 +1,121 @@
+tests:
+ -
+ description: "proxyPort without proxyHost"
+ uri: "mongodb://localhost/?proxyPort=1080"
+ valid: false
+ warning: false
+ hosts: ~
+ auth: ~
+ options: ~
+ -
+ description: "proxyUsername without proxyHost"
+ uri: "mongodb://localhost/?proxyUsername=abc"
+ valid: false
+ warning: false
+ hosts: ~
+ auth: ~
+ options: ~
+ -
+ description: "proxyPassword without proxyHost"
+ uri: "mongodb://localhost/?proxyPassword=def"
+ valid: false
+ warning: false
+ hosts: ~
+ auth: ~
+ options: ~
+ -
+ description: "all other proxy options without proxyHost"
+ uri: "mongodb://localhost/?proxyPort=1080&proxyUsername=abc&proxyPassword=def"
+ valid: false
+ warning: false
+ hosts: ~
+ auth: ~
+ options: ~
+ -
+ description: "proxyUsername without proxyPassword"
+ uri: "mongodb://localhost/?proxyHost=localhost&proxyUsername=abc"
+ valid: false
+ warning: false
+ hosts: ~
+ auth: ~
+ options: ~
+ -
+ description: "proxyPassword without proxyUsername"
+ uri: "mongodb://localhost/?proxyHost=localhost&proxyPassword=def"
+ valid: false
+ warning: false
+ hosts: ~
+ auth: ~
+ options: ~
+ -
+ description: "multiple proxyHost parameters"
+ uri: "mongodb://localhost/?proxyHost=localhost&proxyHost=localhost2"
+ valid: false
+ warning: false
+ hosts: ~
+ auth: ~
+ options: ~
+ -
+ description: "multiple proxyPort parameters"
+ uri: "mongodb://localhost/?proxyHost=localhost&proxyPort=1234&proxyPort=12345"
+ valid: false
+ warning: false
+ hosts: ~
+ auth: ~
+ options: ~
+ -
+ description: "multiple proxyUsername parameters"
+ uri: "mongodb://localhost/?proxyHost=localhost&proxyUsername=abc&proxyUsername=def&proxyPassword=123"
+ valid: false
+ warning: false
+ hosts: ~
+ auth: ~
+ options: ~
+ -
+ description: "multiple proxyPassword parameters"
+ uri: "mongodb://localhost/?proxyHost=localhost&proxyUsername=abc&proxyPassword=123&proxyPassword=456"
+ valid: false
+ warning: false
+ hosts: ~
+ auth: ~
+ options: ~
+ -
+ description: "only host present"
+ uri: "mongodb://localhost/?proxyHost=localhost"
+ valid: true
+ warning: false
+ hosts: ~
+ auth: ~
+ options: {}
+ -
+ description: "host and default port present"
+ uri: "mongodb://localhost/?proxyHost=localhost&proxyPort=1080"
+ valid: true
+ warning: false
+ hosts: ~
+ auth: ~
+ options: {}
+ -
+ description: "host and non-default port present"
+ uri: "mongodb://localhost/?proxyHost=localhost&proxyPort=12345"
+ valid: true
+ warning: false
+ hosts: ~
+ auth: ~
+ options: {}
+ -
+ description: "replicaset, host and non-default port present"
+ uri: "mongodb://rs1,rs2,rs3/?proxyHost=localhost&proxyPort=12345"
+ valid: true
+ warning: false
+ hosts: ~
+ auth: ~
+ options: {}
+ -
+ description: "all options present"
+ uri: "mongodb://rs1,rs2,rs3/?proxyHost=localhost&proxyPort=12345&proxyUsername=asdf&proxyPassword=qwerty"
+ valid: true
+ warning: false
+ hosts: ~
+ auth: ~
+ options: {}
From 186df67e001269acfac3030be3a79b39f416b192 Mon Sep 17 00:00:00 2001
From: Ferdinando Papale <4850119+papafe@users.noreply.github.com>
Date: Tue, 24 Jun 2025 16:05:41 +0200
Subject: [PATCH 5/9] Added additional checks on parsing.
---
.../Core/Configuration/ConnectionString.cs | 20 +++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/src/MongoDB.Driver/Core/Configuration/ConnectionString.cs b/src/MongoDB.Driver/Core/Configuration/ConnectionString.cs
index 0236f0b68e7..eadc83aad9b 100644
--- a/src/MongoDB.Driver/Core/Configuration/ConnectionString.cs
+++ b/src/MongoDB.Driver/Core/Configuration/ConnectionString.cs
@@ -1043,6 +1043,11 @@ private void ParseOption(string name, string value)
_minPoolSize = ParseInt32(name, value);
break;
case "proxyhost":
+ if (!string.IsNullOrEmpty(_proxyHost))
+ {
+ throw new MongoConfigurationException("Multiple proxyHost options are not allowed.");
+ }
+
_proxyHost = value;
if (_proxyHost.Length == 0)
{
@@ -1050,6 +1055,11 @@ private void ParseOption(string name, string value)
}
break;
case "proxyport":
+ if (_proxyPort != null)
+ {
+ throw new MongoConfigurationException("Multiple proxyPort options are not allowed.");
+ }
+
var proxyPortValue = ParseInt32(name, value);
if (proxyPortValue is < 0 or > 65535)
{
@@ -1058,6 +1068,11 @@ private void ParseOption(string name, string value)
_proxyPort = proxyPortValue;
break;
case "proxyusername":
+ if (!string.IsNullOrEmpty(_proxyUsername))
+ {
+ throw new MongoConfigurationException("Multiple proxyUsername options are not allowed.");
+ }
+
_proxyUsername = value;
if (_proxyUsername.Length == 0)
{
@@ -1065,6 +1080,11 @@ private void ParseOption(string name, string value)
}
break;
case "proxypassword":
+ if (!string.IsNullOrEmpty(_proxyPassword))
+ {
+ throw new MongoConfigurationException("Multiple proxyPassword options are not allowed.");
+ }
+
_proxyPassword = value;
if (_proxyPassword.Length == 0)
{
From 567bfa78aaa6f7a08c57cfbb5fd94906545927ae Mon Sep 17 00:00:00 2001
From: Ferdinando Papale <4850119+papafe@users.noreply.github.com>
Date: Wed, 16 Jul 2025 18:37:52 +0200
Subject: [PATCH 6/9] Small fix
---
src/MongoDB.Driver/MongoClientSettings.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/MongoDB.Driver/MongoClientSettings.cs b/src/MongoDB.Driver/MongoClientSettings.cs
index 52ff666f3db..90b4635e2ed 100644
--- a/src/MongoDB.Driver/MongoClientSettings.cs
+++ b/src/MongoDB.Driver/MongoClientSettings.cs
@@ -445,7 +445,7 @@ public string ProxyHost
set
{
if (_isFrozen) { throw new InvalidOperationException("MongoClientSettings is frozen."); }
- _proxyHost = Ensure.IsNotNullOrEmpty(value, nameof(ProxyHost));
+ _proxyHost = value;
}
}
@@ -477,7 +477,7 @@ public string ProxyUsername
set
{
if (_isFrozen) { throw new InvalidOperationException("MongoClientSettings is frozen."); }
- _proxyUsername = Ensure.IsNotNullOrEmpty(value, nameof(ProxyUsername));
+ _proxyUsername = value;
}
}
@@ -490,7 +490,7 @@ public string ProxyPassword
set
{
if (_isFrozen) { throw new InvalidOperationException("MongoClientSettings is frozen."); }
- _proxyPassword = Ensure.IsNotNullOrEmpty(value, nameof(ProxyPassword));
+ _proxyPassword = value;
}
}
From feaa948caff1980fa81de58b80326083eecc28c8 Mon Sep 17 00:00:00 2001
From: Ferdinando Papale <4850119+papafe@users.noreply.github.com>
Date: Thu, 17 Jul 2025 11:49:03 +0200
Subject: [PATCH 7/9] Added base implementation
---
.../Core/Configuration/TcpStreamSettings.cs | 45 +++-
.../Core/Connections/Socks5Helper.cs | 221 ++++++++++++++++++
.../Core/Connections/TcpStreamFactory.cs | 14 +-
3 files changed, 274 insertions(+), 6 deletions(-)
create mode 100644 src/MongoDB.Driver/Core/Connections/Socks5Helper.cs
diff --git a/src/MongoDB.Driver/Core/Configuration/TcpStreamSettings.cs b/src/MongoDB.Driver/Core/Configuration/TcpStreamSettings.cs
index 5a6550fa592..e59e9e92f03 100644
--- a/src/MongoDB.Driver/Core/Configuration/TcpStreamSettings.cs
+++ b/src/MongoDB.Driver/Core/Configuration/TcpStreamSettings.cs
@@ -33,6 +33,10 @@ public class TcpStreamSettings
private readonly int _sendBufferSize;
private readonly Action _socketConfigurator;
private readonly TimeSpan? _writeTimeout;
+ private readonly string _proxyHost;
+ private readonly int? _proxyPort;
+ private readonly string _proxyUsername;
+ private readonly string _proxyPassword;
// constructors
///
@@ -45,6 +49,10 @@ public class TcpStreamSettings
/// Size of the send buffer.
/// The socket configurator.
/// The write timeout.
+ /// //TODO
+ ///
+ ///
+ ///
public TcpStreamSettings(
Optional addressFamily = default(Optional),
Optional connectTimeout = default(Optional),
@@ -52,7 +60,11 @@ public TcpStreamSettings(
Optional receiveBufferSize = default(Optional),
Optional sendBufferSize = default(Optional),
Optional> socketConfigurator = default(Optional>),
- Optional writeTimeout = default(Optional))
+ Optional writeTimeout = default(Optional),
+ Optional proxyHost = default(Optional),
+ Optional proxyPort = default(Optional),
+ Optional proxyUsername = default(Optional),
+ Optional proxyPassword = default(Optional))
{
_addressFamily = addressFamily.WithDefault(AddressFamily.InterNetwork);
_connectTimeout = Ensure.IsInfiniteOrGreaterThanOrEqualToZero(connectTimeout.WithDefault(Timeout.InfiniteTimeSpan), "connectTimeout");
@@ -61,6 +73,10 @@ public TcpStreamSettings(
_sendBufferSize = Ensure.IsGreaterThanZero(sendBufferSize.WithDefault(64 * 1024), "sendBufferSize");
_socketConfigurator = socketConfigurator.WithDefault(null);
_writeTimeout = Ensure.IsNullOrInfiniteOrGreaterThanOrEqualToZero(writeTimeout.WithDefault(null), "writeTimeout");
+ _proxyHost = proxyHost.WithDefault(null);
+ _proxyPort = proxyPort.WithDefault(null);
+ _proxyUsername = proxyUsername.WithDefault(null);
+ _proxyPassword = proxyPassword.WithDefault(null);
}
internal TcpStreamSettings(TcpStreamSettings other)
@@ -72,6 +88,10 @@ internal TcpStreamSettings(TcpStreamSettings other)
_sendBufferSize = other.SendBufferSize;
_socketConfigurator = other.SocketConfigurator;
_writeTimeout = other.WriteTimeout;
+ _proxyHost = other._proxyHost;
+ _proxyPort = other._proxyPort;
+ _proxyUsername = other._proxyUsername;
+ _proxyPassword = other._proxyPassword;
}
// properties
@@ -152,6 +172,28 @@ public TimeSpan? WriteTimeout
get { return _writeTimeout; }
}
+ //TODO Add xml docs
+ ///
+ ///
+ ///
+ public string ProxyHost => _proxyHost;
+ ///
+ ///
+ ///
+ public int? ProxyPort => _proxyPort;
+ ///
+ ///
+ ///
+ public string ProxyUsername => _proxyUsername;
+ ///
+ ///
+ ///
+ public string ProxyPassword => _proxyPassword;
+
+ //TODO We can decide to remove this
+ internal bool UseProxy => !string.IsNullOrEmpty(_proxyHost) && _proxyPort.HasValue;
+
+
// methods
///
/// Returns a new TcpStreamSettings instance with some settings changed.
@@ -172,6 +214,7 @@ public TcpStreamSettings With(
Optional sendBufferSize = default(Optional),
Optional> socketConfigurator = default(Optional>),
Optional writeTimeout = default(Optional))
+ //TODO Need to add proxy settings
{
return new TcpStreamSettings(
addressFamily: addressFamily.WithDefault(_addressFamily),
diff --git a/src/MongoDB.Driver/Core/Connections/Socks5Helper.cs b/src/MongoDB.Driver/Core/Connections/Socks5Helper.cs
new file mode 100644
index 00000000000..fda6f2b0c96
--- /dev/null
+++ b/src/MongoDB.Driver/Core/Connections/Socks5Helper.cs
@@ -0,0 +1,221 @@
+/* Copyright 2010-present MongoDB Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Buffers;
+using System.Buffers.Binary;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using MongoDB.Driver.GridFS;
+
+namespace MongoDB.Driver.Core.Connections
+{
+ internal static class Socks5Helper
+ {
+ // Schemas for requests/responses are taken from the following RFCs:
+ // SOCKS Protocol Version 5 - https://datatracker.ietf.org/doc/html/rfc1928
+ // Username/Password Authentication for SOCKS V5 - https://datatracker.ietf.org/doc/html/rfc1929
+
+ private const byte ProtocolVersion5 = 0x05;
+ private const byte SubnegotiationVersion = 0x01;
+ private const byte CmdConnect = 0x01;
+ private const byte MethodNoAuth = 0x00;
+ private const byte MethodUsernamePassword = 0x02;
+ private const byte AddressTypeIPv4 = 0x01;
+ private const byte AddressTypeDomain = 0x03;
+ private const byte AddressTypeIPv6 = 0x04;
+ private const byte Socks5Success = 0x00;
+
+ private const int BufferSize = 512;
+
+ public static void PerformSocks5Handshake(Stream stream, string targetHost, int targetPort, string proxyUsername, string proxyPassword, CancellationToken cancellationToken)
+ {
+ var buffer = ArrayPool.Shared.Rent(BufferSize);
+ try
+ {
+ var useAuth = !string.IsNullOrEmpty(proxyUsername) && !string.IsNullOrEmpty(proxyPassword);
+
+ // Greeting request
+ // +----+----------+----------+
+ // |VER | NMETHODS | METHODS |
+ // +----+----------+----------+
+ // | 1 | 1 | 1 to 255 |
+ // +----+----------+----------+
+ buffer[0] = ProtocolVersion5;
+
+ if (!useAuth)
+ {
+ buffer[1] = 1;
+ buffer[2] = MethodNoAuth;
+ }
+ else
+ {
+ buffer[1] = 2;
+ buffer[2] = MethodNoAuth;
+ buffer[3] = MethodUsernamePassword;
+ }
+
+ stream.Write(buffer, 0, useAuth ? 4 : 3);
+ stream.Flush();
+
+ // Greeting response
+ // +----+--------+
+ // |VER | METHOD |
+ // +----+--------+
+ // | 1 | 1 |
+ // +----+--------+
+
+ stream.ReadBytes(buffer, 0,2, cancellationToken);
+
+ VerifyProtocolVersion(buffer[0]);
+
+ var method = buffer[1];
+ if (method == MethodUsernamePassword)
+ {
+ if (!useAuth)
+ {
+ //We should not reach here
+ throw new IOException("SOCKS5 proxy requires authentication, but no credentials were provided.");
+ }
+
+ // Authentication request
+ // +----+------+----------+------+----------+
+ // |VER | ULEN | UNAME | PLEN | PASSWD |
+ // +----+------+----------+------+----------+
+ // | 1 | 1 | 1 to 255 | 1 | 1 to 255 |
+ // +----+------+----------+------+----------+
+ buffer[0] = SubnegotiationVersion;
+ var usernameLength = EncodeString(proxyUsername, buffer.AsSpan(2), nameof(proxyUsername));
+ buffer[1] = usernameLength;
+ var passwordLength = EncodeString(proxyPassword, buffer.AsSpan(3 + usernameLength), nameof(proxyPassword));
+ buffer[2 + usernameLength] = passwordLength;
+
+ var authLength = 3 + usernameLength + passwordLength;
+ stream.Write(buffer, 0, authLength);
+ stream.Flush();
+
+ // Authentication response
+ // +----+--------+
+ // |VER | STATUS |
+ // +----+--------+
+ // | 1 | 1 |
+ // +----+--------+
+ stream.ReadBytes(buffer, 0,2, cancellationToken);
+ if (buffer[0] != SubnegotiationVersion || buffer[1] != Socks5Success)
+ {
+ throw new IOException("SOCKS5 authentication failed.");
+ }
+ }
+ else if (method != MethodNoAuth)
+ {
+ throw new IOException("SOCKS5 proxy requires unsupported authentication method.");
+ }
+
+ // Connect request
+ // +----+-----+-------+------+----------+----------+
+ // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+ // +----+-----+-------+------+----------+----------+
+ // | 1 | 1 | X'00' | 1 | Variable | 2 |
+ // +----+-----+-------+------+----------+----------+
+ buffer[0] = ProtocolVersion5;
+ buffer[1] = CmdConnect;
+ buffer[2] = 0x00;
+ var addressLength = 0;
+
+ if (IPAddress.TryParse(targetHost, out var ip))
+ {
+ switch (ip.AddressFamily)
+ {
+ case AddressFamily.InterNetwork:
+ buffer[3] = AddressTypeIPv4;
+ ip.TryWriteBytes(buffer.AsSpan(4), out _);
+ addressLength = 4;
+ break;
+ case AddressFamily.InterNetworkV6:
+ buffer[3] = AddressTypeIPv6;
+ ip.TryWriteBytes(buffer.AsSpan(4), out _);
+ addressLength = 16;
+ break;
+ default:
+ throw new IOException("Invalid target host address family. Only IPv4 and IPv6 are supported.");
+ }
+ }
+ else
+ {
+ buffer[3] = AddressTypeDomain;
+ var hostLength = EncodeString(targetHost, buffer.AsSpan(5), nameof(targetHost));
+ buffer[4] = hostLength;
+ addressLength = hostLength + 1;
+ }
+
+ BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(addressLength + 4), (ushort)targetPort);
+
+ stream.Write(buffer, 0, addressLength + 6);
+ stream.Flush();
+
+ // Connect response
+ // +----+-----+-------+------+----------+----------+
+ // |VER | REP | RSV | ATYP | DST.ADDR | DST.PORT |
+ // +----+-----+-------+------+----------+----------+
+ // | 1 | 1 | X'00' | 1 | Variable | 2 |
+ // +----+-----+-------+------+----------+----------+
+ stream.ReadBytes(buffer, 0,5, cancellationToken);
+ VerifyProtocolVersion(buffer[0]);
+ if (buffer[1] != Socks5Success)
+ {
+ throw new IOException($"SOCKS5 connect failed with code 0x{buffer[1]:X2}");
+ }
+
+ var skip = buffer[3] switch
+ {
+ AddressTypeIPv4 => 4 + 2,
+ AddressTypeIPv6 => 16 + 2,
+ AddressTypeDomain => buffer[4] + 1 + 2,
+ _ => throw new IOException("Unknown address type in SOCKS5 reply.")
+ };
+
+ stream.ReadBytes(buffer, 0, skip, cancellationToken);
+ // Address and port in response are ignored
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(buffer);
+ }
+ }
+
+ private static void VerifyProtocolVersion(byte version)
+ {
+ if (version != ProtocolVersion5)
+ {
+ throw new IOException("Invalid SOCKS version in method selection response.");
+ }
+ }
+
+ private static byte EncodeString(ReadOnlySpan chars, Span buffer, string parameterName)
+ {
+ try
+ {
+ return checked((byte)Encoding.UTF8.GetBytes(chars, buffer));
+ }
+ catch
+ {
+ throw new IOException($"The {parameterName} could not be encoded as UTF-8.");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs b/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs
index 9bb93eea097..5fcfebc982f 100644
--- a/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs
+++ b/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs
@@ -48,19 +48,23 @@ public TcpStreamFactory(TcpStreamSettings settings)
// methods
public Stream CreateStream(EndPoint endPoint, CancellationToken cancellationToken)
{
+ EndPoint actualEndPoint;
+
+ actualEndPoint = _settings.UseProxy ? new DnsEndPoint(_settings.ProxyHost, _settings.ProxyPort.Value) : endPoint;
+
#if NET472
- var socket = CreateSocket(endPoint);
- Connect(socket, endPoint, cancellationToken);
+ var socket = CreateSocket(actualEndPoint);
+ Connect(socket, actualEndPoint, cancellationToken);
return CreateNetworkStream(socket);
#else
- var resolved = ResolveEndPoints(endPoint);
+ var resolved = ResolveEndPoints(actualEndPoint);
for (int i = 0; i < resolved.Length; i++)
{
try
{
var socket = CreateSocket(resolved[i]);
Connect(socket, resolved[i], cancellationToken);
- return CreateNetworkStream(socket);
+ var stream = CreateNetworkStream(socket);
}
catch
{
@@ -74,7 +78,7 @@ public Stream CreateStream(EndPoint endPoint, CancellationToken cancellationToke
}
// we should never get here...
- throw new InvalidOperationException("Unabled to resolve endpoint.");
+ throw new InvalidOperationException("Unable to resolve endpoint.");
#endif
}
From f8bd6a3547108e7b5eb7e44a696b8aec9d3cf4ab Mon Sep 17 00:00:00 2001
From: Ferdinando Papale <4850119+papafe@users.noreply.github.com>
Date: Thu, 17 Jul 2025 17:25:15 +0200
Subject: [PATCH 8/9] Improvements plus initial tests
---
src/MongoDB.Driver/ClusterRegistry.cs | 1 +
.../Configuration/ClusterBuilderExtensions.cs | 9 ++++
.../Core/Configuration/TcpStreamSettings.cs | 17 +++++--
.../Core/Connections/Socks5Helper.cs | 41 ++++++++++++++-
.../Core/Connections/TcpStreamFactory.cs | 30 +++++------
.../socks5-support/Socks5SupportProseTests.cs | 50 +++++++++++++++++++
6 files changed, 129 insertions(+), 19 deletions(-)
create mode 100644 tests/MongoDB.Driver.Tests/Specifications/socks5-support/Socks5SupportProseTests.cs
diff --git a/src/MongoDB.Driver/ClusterRegistry.cs b/src/MongoDB.Driver/ClusterRegistry.cs
index 3359cd4b612..2bb28a139ff 100644
--- a/src/MongoDB.Driver/ClusterRegistry.cs
+++ b/src/MongoDB.Driver/ClusterRegistry.cs
@@ -172,6 +172,7 @@ private TcpStreamSettings ConfigureTcp(TcpStreamSettings settings, ClusterKey cl
receiveBufferSize: clusterKey.ReceiveBufferSize,
sendBufferSize: clusterKey.SendBufferSize,
writeTimeout: clusterKey.SocketTimeout);
+ //TODO Maybe need to add proxy settings to clusterKey as well
}
internal IClusterInternal GetOrCreateCluster(ClusterKey clusterKey)
diff --git a/src/MongoDB.Driver/Core/Configuration/ClusterBuilderExtensions.cs b/src/MongoDB.Driver/Core/Configuration/ClusterBuilderExtensions.cs
index 3fe6c4a3da2..1b9cdd59009 100644
--- a/src/MongoDB.Driver/Core/Configuration/ClusterBuilderExtensions.cs
+++ b/src/MongoDB.Driver/Core/Configuration/ClusterBuilderExtensions.cs
@@ -118,6 +118,15 @@ public static ClusterBuilder ConfigureWithConnectionString(
builder = builder.ConfigureTcp(s => s.With(addressFamily: AddressFamily.InterNetworkV6));
}
+ if (connectionString.ProxyHost != null)
+ {
+ builder = builder.ConfigureTcp(s => s.With(
+ proxyHost: connectionString.ProxyHost,
+ proxyPort: connectionString.ProxyPort,
+ proxyUsername: connectionString.ProxyUsername,
+ proxyPassword: connectionString.ProxyPassword));
+ }
+
if (connectionString.SocketTimeout != null)
{
builder = builder.ConfigureTcp(s => s.With(
diff --git a/src/MongoDB.Driver/Core/Configuration/TcpStreamSettings.cs b/src/MongoDB.Driver/Core/Configuration/TcpStreamSettings.cs
index e59e9e92f03..c738bfedd31 100644
--- a/src/MongoDB.Driver/Core/Configuration/TcpStreamSettings.cs
+++ b/src/MongoDB.Driver/Core/Configuration/TcpStreamSettings.cs
@@ -205,6 +205,10 @@ public TimeSpan? WriteTimeout
/// Size of the send buffer.
/// The socket configurator.
/// The write timeout.
+ /// //TODO Add docs
+ ///
+ ///
+ ///
/// A new TcpStreamSettings instance.
public TcpStreamSettings With(
Optional addressFamily = default(Optional),
@@ -213,8 +217,11 @@ public TcpStreamSettings With(
Optional receiveBufferSize = default(Optional),
Optional sendBufferSize = default(Optional),
Optional> socketConfigurator = default(Optional>),
- Optional writeTimeout = default(Optional))
- //TODO Need to add proxy settings
+ Optional writeTimeout = default(Optional),
+ Optional proxyHost = default(Optional),
+ Optional proxyPort = default(Optional),
+ Optional proxyUsername = default(Optional),
+ Optional proxyPassword = default(Optional))
{
return new TcpStreamSettings(
addressFamily: addressFamily.WithDefault(_addressFamily),
@@ -223,7 +230,11 @@ public TcpStreamSettings With(
receiveBufferSize: receiveBufferSize.WithDefault(_receiveBufferSize),
sendBufferSize: sendBufferSize.WithDefault(_sendBufferSize),
socketConfigurator: socketConfigurator.WithDefault(_socketConfigurator),
- writeTimeout: writeTimeout.WithDefault(_writeTimeout));
+ writeTimeout: writeTimeout.WithDefault(_writeTimeout),
+ proxyHost: proxyHost.WithDefault(_proxyHost),
+ proxyPort: proxyPort.WithDefault(_proxyPort),
+ proxyUsername: proxyUsername.WithDefault(_proxyUsername),
+ proxyPassword: proxyPassword.WithDefault(_proxyPassword));
}
}
}
diff --git a/src/MongoDB.Driver/Core/Connections/Socks5Helper.cs b/src/MongoDB.Driver/Core/Connections/Socks5Helper.cs
index fda6f2b0c96..50e731803f9 100644
--- a/src/MongoDB.Driver/Core/Connections/Socks5Helper.cs
+++ b/src/MongoDB.Driver/Core/Connections/Socks5Helper.cs
@@ -21,6 +21,7 @@
using System.Net.Sockets;
using System.Text;
using System.Threading;
+using MongoDB.Driver.Core.Misc;
using MongoDB.Driver.GridFS;
namespace MongoDB.Driver.Core.Connections
@@ -43,8 +44,11 @@ internal static class Socks5Helper
private const int BufferSize = 512;
- public static void PerformSocks5Handshake(Stream stream, string targetHost, int targetPort, string proxyUsername, string proxyPassword, CancellationToken cancellationToken)
+ //TODO Make an async version of this method
+ public static void PerformSocks5Handshake(Stream stream, EndPoint endPoint, string proxyUsername, string proxyPassword, CancellationToken cancellationToken)
{
+ var (targetHost, targetPort) = endPoint.GetHostAndPort();
+
var buffer = ArrayPool.Shared.Rent(BufferSize);
try
{
@@ -100,9 +104,18 @@ public static void PerformSocks5Handshake(Stream stream, string targetHost, int
// | 1 | 1 | 1 to 255 | 1 | 1 to 255 |
// +----+------+----------+------+----------+
buffer[0] = SubnegotiationVersion;
+ //TODO Maybe it can be extracted to a method?
+#if NET472
+ var usernameLength = EncodeString(proxyUsername, buffer, 2, nameof(proxyUsername));
+#else
var usernameLength = EncodeString(proxyUsername, buffer.AsSpan(2), nameof(proxyUsername));
+#endif
buffer[1] = usernameLength;
+#if NET472
+ var passwordLength = EncodeString(proxyPassword, buffer, 3 + usernameLength, nameof(proxyPassword));
+#else
var passwordLength = EncodeString(proxyPassword, buffer.AsSpan(3 + usernameLength), nameof(proxyPassword));
+#endif
buffer[2 + usernameLength] = passwordLength;
var authLength = 3 + usernameLength + passwordLength;
@@ -137,18 +150,23 @@ public static void PerformSocks5Handshake(Stream stream, string targetHost, int
buffer[2] = 0x00;
var addressLength = 0;
+ //TODO Can we avoid doing this...?
if (IPAddress.TryParse(targetHost, out var ip))
{
switch (ip.AddressFamily)
{
case AddressFamily.InterNetwork:
buffer[3] = AddressTypeIPv4;
+#if !NET472
ip.TryWriteBytes(buffer.AsSpan(4), out _);
+#endif
addressLength = 4;
break;
case AddressFamily.InterNetworkV6:
buffer[3] = AddressTypeIPv6;
+#if !NET472
ip.TryWriteBytes(buffer.AsSpan(4), out _);
+#endif
addressLength = 16;
break;
default:
@@ -158,7 +176,11 @@ public static void PerformSocks5Handshake(Stream stream, string targetHost, int
else
{
buffer[3] = AddressTypeDomain;
+#if NET472
+ var hostLength = EncodeString(targetHost, buffer, 5, nameof(targetHost));
+#else
var hostLength = EncodeString(targetHost, buffer.AsSpan(5), nameof(targetHost));
+#endif
buffer[4] = hostLength;
addressLength = hostLength + 1;
}
@@ -206,10 +228,24 @@ private static void VerifyProtocolVersion(byte version)
}
}
- private static byte EncodeString(ReadOnlySpan chars, Span buffer, string parameterName)
+#if NET472
+ private static byte EncodeString(string input, byte[] buffer, int offset, string parameterName)
{
try
{
+ var written = Encoding.UTF8.GetBytes(input, 0, input.Length, buffer, offset);
+ return checked((byte)written);
+ }
+ catch
+ {
+ throw new IOException($"The {parameterName} could not be encoded as UTF-8.");
+ }
+ }
+#else
+ private static byte EncodeString(ReadOnlySpan chars, Span buffer, string parameterName)
+ {
+ try
+ { //TODO Maybe we should remove checked?
return checked((byte)Encoding.UTF8.GetBytes(chars, buffer));
}
catch
@@ -217,5 +253,6 @@ private static byte EncodeString(ReadOnlySpan chars, Span buffer, st
throw new IOException($"The {parameterName} could not be encoded as UTF-8.");
}
}
+#endif
}
}
\ No newline at end of file
diff --git a/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs b/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs
index 5fcfebc982f..f57cf10f519 100644
--- a/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs
+++ b/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs
@@ -65,6 +65,12 @@ public Stream CreateStream(EndPoint endPoint, CancellationToken cancellationToke
var socket = CreateSocket(resolved[i]);
Connect(socket, resolved[i], cancellationToken);
var stream = CreateNetworkStream(socket);
+
+ //TODO Need to do the same for the async version and for net472
+ if (_settings.UseProxy)
+ {
+ Socks5Helper.PerformSocks5Handshake(stream, endPoint, _settings.ProxyUsername, _settings.ProxyPassword, cancellationToken);
+ }
}
catch
{
@@ -262,20 +268,18 @@ private Socket CreateSocket(EndPoint endPoint)
private EndPoint[] ResolveEndPoints(EndPoint initial)
{
- var dnsInitial = initial as DnsEndPoint;
- if (dnsInitial == null)
+ if (initial is not DnsEndPoint dnsInitial)
{
- return new[] { initial };
+ return [initial];
}
- IPAddress address;
- if (IPAddress.TryParse(dnsInitial.Host, out address))
+ if (IPAddress.TryParse(dnsInitial.Host, out var address))
{
- return new[] { new IPEndPoint(address, dnsInitial.Port) };
+ return [new IPEndPoint(address, dnsInitial.Port)];
}
var preferred = initial.AddressFamily;
- if (preferred == AddressFamily.Unspecified || preferred == AddressFamily.Unknown)
+ if (preferred is AddressFamily.Unspecified or AddressFamily.Unknown)
{
preferred = _settings.AddressFamily;
}
@@ -289,20 +293,18 @@ private EndPoint[] ResolveEndPoints(EndPoint initial)
private async Task ResolveEndPointsAsync(EndPoint initial)
{
- var dnsInitial = initial as DnsEndPoint;
- if (dnsInitial == null)
+ if (initial is not DnsEndPoint dnsInitial)
{
- return new[] { initial };
+ return [initial];
}
- IPAddress address;
- if (IPAddress.TryParse(dnsInitial.Host, out address))
+ if (IPAddress.TryParse(dnsInitial.Host, out var address))
{
- return new[] { new IPEndPoint(address, dnsInitial.Port) };
+ return [new IPEndPoint(address, dnsInitial.Port)];
}
var preferred = initial.AddressFamily;
- if (preferred == AddressFamily.Unspecified || preferred == AddressFamily.Unknown)
+ if (preferred is AddressFamily.Unspecified or AddressFamily.Unknown)
{
preferred = _settings.AddressFamily;
}
diff --git a/tests/MongoDB.Driver.Tests/Specifications/socks5-support/Socks5SupportProseTests.cs b/tests/MongoDB.Driver.Tests/Specifications/socks5-support/Socks5SupportProseTests.cs
new file mode 100644
index 00000000000..39ce99039df
--- /dev/null
+++ b/tests/MongoDB.Driver.Tests/Specifications/socks5-support/Socks5SupportProseTests.cs
@@ -0,0 +1,50 @@
+/* Copyright 2010-present MongoDB Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using MongoDB.Bson;
+using MongoDB.Driver.Core.TestHelpers.Logging;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace MongoDB.Driver.Tests.Specifications.socks5_support
+{
+ [Trait("Category", "Integration")]
+ public class Socks5SupportProseTests(ITestOutputHelper testOutputHelper) : LoggableTestClass(testOutputHelper)
+ {
+
+ [Theory]
+ [InlineData("mongodb:///?proxyHost=localhost&proxyPort=1080&directConnection=true", false)]
+ [InlineData("mongodb:///?proxyHost=localhost&proxyPort=1081&directConnection=true", true)]
+ // [InlineData("mongodb:///?proxyHost=localhost&proxyPort=1080", false)]
+ // [InlineData("mongodb:///?proxyHost=localhost&proxyPort=1081", true)]
+ // [InlineData("mongodb:///?proxyHost=localhost&proxyPort=1080&proxyUsername=nonexistentuser&proxyPassword=badauth&directConnection=true", false)]
+ // [InlineData("mongodb:///?proxyHost=localhost&proxyPort=1081&proxyUsername=nonexistentuser&proxyPassword=badauth&directConnection=true", true)]
+ // [InlineData("mongodb:///?proxyHost=localhost&proxyPort=1081&proxyUsername=nonexistentuser&proxyPassword=badauth", true)]
+ // [InlineData("mongodb:///?proxyHost=localhost&proxyPort=1080&proxyUsername=username&proxyPassword=p4ssw0rd&directConnection=true", true)]
+ // [InlineData("mongodb:///?proxyHost=localhost&proxyPort=1081&directConnection=true", true)]
+ // [InlineData("mongodb:///?proxyHost=localhost&proxyPort=1080&proxyUsername=username&proxyPassword=p4ssw0rd", true)]
+ // [InlineData("mongodb:///?proxyHost=localhost&proxyPort=1081", true)]
+ public void TestConnectionStrings(string connectionString, bool expectedResult)
+ {
+ var client = new MongoClient(connectionString);
+ var database = client.GetDatabase("admin");
+
+ var command = new BsonDocument("hello", 1);
+ var result = database.RunCommand(command);
+ }
+
+ }
+}
\ No newline at end of file
From 3665a29076802dfcdcd520cab245ff311f5e160a Mon Sep 17 00:00:00 2001
From: Ferdinando Papale <4850119+papafe@users.noreply.github.com>
Date: Fri, 18 Jul 2025 08:23:25 +0200
Subject: [PATCH 9/9] Various corrections
---
src/MongoDB.Driver/ClusterKey.cs | 20 +++++++++++++++++
src/MongoDB.Driver/ClusterRegistry.cs | 7 ++++--
.../Core/Configuration/TcpStreamSettings.cs | 22 +++++++++++++++++++
.../Core/Connections/Socks5Helper.cs | 6 ++---
.../Core/Connections/TcpStreamFactory.cs | 2 ++
src/MongoDB.Driver/MongoClientSettings.cs | 5 +++++
tests/MongoDB.Driver.Tests/ClusterKeyTests.cs | 8 +++++++
.../socks5-support/Socks5SupportProseTests.cs | 8 ++++---
8 files changed, 70 insertions(+), 8 deletions(-)
diff --git a/src/MongoDB.Driver/ClusterKey.cs b/src/MongoDB.Driver/ClusterKey.cs
index d208a7b60e4..f0bc568ea6b 100644
--- a/src/MongoDB.Driver/ClusterKey.cs
+++ b/src/MongoDB.Driver/ClusterKey.cs
@@ -46,6 +46,10 @@ internal sealed class ClusterKey
private readonly TimeSpan _maxConnectionLifeTime;
private readonly int _maxConnectionPoolSize;
private readonly int _minConnectionPoolSize;
+ private readonly string _proxyHost;
+ private readonly int? _proxyPort;
+ private readonly string _proxyUsername;
+ private readonly string _proxyPassword;
private readonly int _receiveBufferSize;
private readonly string _replicaSetName;
private readonly ConnectionStringScheme _scheme;
@@ -84,6 +88,10 @@ public ClusterKey(
TimeSpan maxConnectionLifeTime,
int maxConnectionPoolSize,
int minConnectionPoolSize,
+ string proxyHost,
+ int? proxyPort,
+ string proxyUsername,
+ string proxyPassword,
int receiveBufferSize,
string replicaSetName,
ConnectionStringScheme scheme,
@@ -120,6 +128,10 @@ public ClusterKey(
_maxConnectionLifeTime = maxConnectionLifeTime;
_maxConnectionPoolSize = maxConnectionPoolSize;
_minConnectionPoolSize = minConnectionPoolSize;
+ _proxyHost = proxyHost;
+ _proxyPort = proxyPort;
+ _proxyUsername = proxyUsername;
+ _proxyPassword = proxyPassword;
_receiveBufferSize = receiveBufferSize;
_replicaSetName = replicaSetName;
_scheme = scheme;
@@ -160,6 +172,10 @@ public ClusterKey(
public TimeSpan MaxConnectionLifeTime { get { return _maxConnectionLifeTime; } }
public int MaxConnectionPoolSize { get { return _maxConnectionPoolSize; } }
public int MinConnectionPoolSize { get { return _minConnectionPoolSize; } }
+ public string ProxyHost { get { return _proxyHost; } }
+ public int? ProxyPort { get { return _proxyPort; } }
+ public string ProxyUsername { get { return _proxyUsername; } }
+ public string ProxyPassword { get { return _proxyPassword; } }
public int ReceiveBufferSize { get { return _receiveBufferSize; } }
public string ReplicaSetName { get { return _replicaSetName; } }
public ConnectionStringScheme Scheme { get { return _scheme; } }
@@ -215,6 +231,10 @@ public override bool Equals(object obj)
_maxConnectionLifeTime == rhs._maxConnectionLifeTime &&
_maxConnectionPoolSize == rhs._maxConnectionPoolSize &&
_minConnectionPoolSize == rhs._minConnectionPoolSize &&
+ _proxyHost == rhs._proxyHost &&
+ _proxyPort == rhs._proxyPort &&
+ _proxyUsername == rhs._proxyUsername &&
+ _proxyPassword == rhs._proxyPassword &&
_receiveBufferSize == rhs._receiveBufferSize &&
_replicaSetName == rhs._replicaSetName &&
_scheme == rhs._scheme &&
diff --git a/src/MongoDB.Driver/ClusterRegistry.cs b/src/MongoDB.Driver/ClusterRegistry.cs
index 2bb28a139ff..5332811765e 100644
--- a/src/MongoDB.Driver/ClusterRegistry.cs
+++ b/src/MongoDB.Driver/ClusterRegistry.cs
@@ -171,8 +171,11 @@ private TcpStreamSettings ConfigureTcp(TcpStreamSettings settings, ClusterKey cl
readTimeout: clusterKey.SocketTimeout,
receiveBufferSize: clusterKey.ReceiveBufferSize,
sendBufferSize: clusterKey.SendBufferSize,
- writeTimeout: clusterKey.SocketTimeout);
- //TODO Maybe need to add proxy settings to clusterKey as well
+ writeTimeout: clusterKey.SocketTimeout,
+ proxyHost: clusterKey.ProxyHost,
+ proxyPort: clusterKey.ProxyPort,
+ proxyUsername: clusterKey.ProxyUsername,
+ proxyPassword: clusterKey.ProxyPassword);
}
internal IClusterInternal GetOrCreateCluster(ClusterKey clusterKey)
diff --git a/src/MongoDB.Driver/Core/Configuration/TcpStreamSettings.cs b/src/MongoDB.Driver/Core/Configuration/TcpStreamSettings.cs
index c738bfedd31..dbd0e77e22b 100644
--- a/src/MongoDB.Driver/Core/Configuration/TcpStreamSettings.cs
+++ b/src/MongoDB.Driver/Core/Configuration/TcpStreamSettings.cs
@@ -79,6 +79,28 @@ public TcpStreamSettings(
_proxyPassword = proxyPassword.WithDefault(null);
}
+ // ///
+ // ///
+ // ///
+ // ///
+ // ///
+ // ///
+ // ///
+ // ///
+ // ///
+ // ///
+ // public TcpStreamSettings(
+ // Optional addressFamily,
+ // Optional connectTimeout,
+ // Optional readTimeout,
+ // Optional receiveBufferSize,
+ // Optional sendBufferSize,
+ // Optional> socketConfigurator,
+ // Optional writeTimeout)
+ // {
+ //
+ // }
+
internal TcpStreamSettings(TcpStreamSettings other)
{
_addressFamily = other.AddressFamily;
diff --git a/src/MongoDB.Driver/Core/Connections/Socks5Helper.cs b/src/MongoDB.Driver/Core/Connections/Socks5Helper.cs
index 50e731803f9..8e4b05d190d 100644
--- a/src/MongoDB.Driver/Core/Connections/Socks5Helper.cs
+++ b/src/MongoDB.Driver/Core/Connections/Socks5Helper.cs
@@ -205,9 +205,9 @@ public static void PerformSocks5Handshake(Stream stream, EndPoint endPoint, stri
var skip = buffer[3] switch
{
- AddressTypeIPv4 => 4 + 2,
- AddressTypeIPv6 => 16 + 2,
- AddressTypeDomain => buffer[4] + 1 + 2,
+ AddressTypeIPv4 => 5,
+ AddressTypeIPv6 => 17,
+ AddressTypeDomain => buffer[4] + 2,
_ => throw new IOException("Unknown address type in SOCKS5 reply.")
};
diff --git a/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs b/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs
index f57cf10f519..2af5b33885e 100644
--- a/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs
+++ b/src/MongoDB.Driver/Core/Connections/TcpStreamFactory.cs
@@ -71,6 +71,8 @@ public Stream CreateStream(EndPoint endPoint, CancellationToken cancellationToke
{
Socks5Helper.PerformSocks5Handshake(stream, endPoint, _settings.ProxyUsername, _settings.ProxyPassword, cancellationToken);
}
+
+ return stream;
}
catch
{
diff --git a/src/MongoDB.Driver/MongoClientSettings.cs b/src/MongoDB.Driver/MongoClientSettings.cs
index 90b4635e2ed..ea2e5796ae8 100644
--- a/src/MongoDB.Driver/MongoClientSettings.cs
+++ b/src/MongoDB.Driver/MongoClientSettings.cs
@@ -1188,6 +1188,7 @@ public override int GetHashCode()
/// A string representation of the settings.
public override string ToString()
{
+ //TODO Need to add proxy here
if (_isFrozen)
{
return _frozenStringRepresentation;
@@ -1310,6 +1311,10 @@ internal ClusterKey ToClusterKey()
_maxConnectionLifeTime,
_maxConnectionPoolSize,
_minConnectionPoolSize,
+ _proxyHost,
+ _proxyPort,
+ _proxyUsername,
+ _proxyPassword,
MongoDefaults.TcpReceiveBufferSize, // TODO: add ReceiveBufferSize to MongoClientSettings?
_replicaSetName,
_scheme,
diff --git a/tests/MongoDB.Driver.Tests/ClusterKeyTests.cs b/tests/MongoDB.Driver.Tests/ClusterKeyTests.cs
index 481da3442c7..ddb15dbd789 100644
--- a/tests/MongoDB.Driver.Tests/ClusterKeyTests.cs
+++ b/tests/MongoDB.Driver.Tests/ClusterKeyTests.cs
@@ -259,6 +259,10 @@ private ClusterKey CreateSubject(string notEqualFieldName = null)
maxConnectionLifeTime,
maxConnectionPoolSize,
minConnectionPoolSize,
+ null, //TODO Need to add correct proxy for tests
+ null,
+ null,
+ null,
receiveBufferSize,
replicaSetName,
scheme,
@@ -344,6 +348,10 @@ internal ClusterKey CreateSubjectWith(
maxConnectionLifeTime,
maxConnectionPoolSize,
minConnectionPoolSize,
+ null, //TODO Add correct proxy for tests
+ null,
+ null,
+ null,
receiveBufferSize,
replicaSetName,
scheme,
diff --git a/tests/MongoDB.Driver.Tests/Specifications/socks5-support/Socks5SupportProseTests.cs b/tests/MongoDB.Driver.Tests/Specifications/socks5-support/Socks5SupportProseTests.cs
index 39ce99039df..4fcf8919061 100644
--- a/tests/MongoDB.Driver.Tests/Specifications/socks5-support/Socks5SupportProseTests.cs
+++ b/tests/MongoDB.Driver.Tests/Specifications/socks5-support/Socks5SupportProseTests.cs
@@ -25,9 +25,9 @@ namespace MongoDB.Driver.Tests.Specifications.socks5_support
public class Socks5SupportProseTests(ITestOutputHelper testOutputHelper) : LoggableTestClass(testOutputHelper)
{
- [Theory]
- [InlineData("mongodb:///?proxyHost=localhost&proxyPort=1080&directConnection=true", false)]
- [InlineData("mongodb:///?proxyHost=localhost&proxyPort=1081&directConnection=true", true)]
+ // [Theory]
+ // [InlineData("mongodb:///?proxyHost=localhost&proxyPort=1080&directConnection=true", false)]
+ // [InlineData("mongodb:///?proxyHost=localhost&proxyPort=1081&directConnection=true", true)]
// [InlineData("mongodb:///?proxyHost=localhost&proxyPort=1080", false)]
// [InlineData("mongodb:///?proxyHost=localhost&proxyPort=1081", true)]
// [InlineData("mongodb:///?proxyHost=localhost&proxyPort=1080&proxyUsername=nonexistentuser&proxyPassword=badauth&directConnection=true", false)]
@@ -39,11 +39,13 @@ public class Socks5SupportProseTests(ITestOutputHelper testOutputHelper) : Logga
// [InlineData("mongodb:///?proxyHost=localhost&proxyPort=1081", true)]
public void TestConnectionStrings(string connectionString, bool expectedResult)
{
+ connectionString = connectionString.Replace("", "localhost:27017");
var client = new MongoClient(connectionString);
var database = client.GetDatabase("admin");
var command = new BsonDocument("hello", 1);
var result = database.RunCommand(command);
+ Assert.NotEmpty(result);
}
}