Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions specifications/connection-string/tests/invalid-uris.json
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,15 @@
"auth": null,
"options": null
},
{
"description": "srvAllowedHostsSuffix with non-SRV URI",
"uri": "mongodb://localhost:27017/?srvAllowedHostsSuffix=.mongodb.net",
"valid": false,
"warning": null,
"hosts": null,
"auth": null,
"options": null
},
{
"description": "Username with password containing an unescaped percent sign",
"uri": "mongodb://alice%foo:bar@127.0.0.1",
Expand Down
8 changes: 8 additions & 0 deletions specifications/connection-string/tests/invalid-uris.yml
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,14 @@ tests:
hosts: ~
auth: ~
options: ~
-
description: "srvAllowedHostsSuffix with non-SRV URI"
uri: "mongodb://localhost:27017/?srvAllowedHostsSuffix=.mongodb.net"
valid: false
warning: ~
hosts: ~
auth: ~
options: ~
-
description: "Username with password containing an unescaped percent sign"
uri: "mongodb://alice%foo:bar@127.0.0.1"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"uri": "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=.BUILD.10GEN.CC",
"seeds": [
"localhost.build.10gen.cc:27017"
],
"options": {
"ssl": true
},
"ping": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Tests that srvAllowedHostsSuffix comparison is case-insensitive (ASCII case folding per RFC 4343).
# The suffix .BUILD.10GEN.CC is uppercase but should match the returned host localhost.build.10gen.cc.
uri: "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=.BUILD.10GEN.CC"
seeds:
- localhost.build.10gen.cc:27017
options:
ssl: true
ping: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"uri": "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=test.build.10gen.cc",
"seeds": [],
"hosts": [],
"error": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# DNS record for test12.test.build.10gen.cc returns localhost.build.10gen.cc which would not match test.build.10gen.cc
uri: "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=test.build.10gen.cc"
seeds: []
hosts: []
error: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"uri": "mongodb+srv://test1.test.build.10gen.cc/?srvAllowedHostsSuffix=.",
"seeds": [],
"hosts": [],
"error": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# srvAllowedHostsSuffix MUST name at least one label. A value of "." is empty once the
# leading and trailing "." are stripped, so it names no domain at all and MUST raise an
# error rather than being treated as a suffix that every host matches.
uri: "mongodb+srv://test1.test.build.10gen.cc/?srvAllowedHostsSuffix=."
seeds: []
hosts: []
error: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"uri": "mongodb+srv://test1.test.build.10gen.cc/?srvAllowedHostsSuffix=.cc",
"seeds": [],
"hosts": [],
"error": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# srvAllowedHostsSuffix SHOULD NOT be a public suffix. "cc" is an ordinary rule in the
# Public Suffix List, and isn't two or more `.` separated labels so `.cc` MUST raise an error.
uri: "mongodb+srv://test1.test.build.10gen.cc/?srvAllowedHostsSuffix=.cc"
seeds: []
hosts: []
error: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"uri": "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=.build.10gen.cc.",
"seeds": [
"localhost.build.10gen.cc:27017"
],
"options": {
"ssl": true
},
"ping": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# A trailing dot in srvAllowedHostsSuffix must be stripped before comparison,
# so ".build.10gen.cc." is equivalent to ".build.10gen.cc".
uri: "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=.build.10gen.cc."
seeds:
- localhost.build.10gen.cc:27017
options:
ssl: true
ping: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"uri": "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=.build.10gen.cc",
"seeds": [
"localhost.build.10gen.cc:27017"
],
"options": {
"ssl": true
},
"ping": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Also tests trailing dot normalization: DNS returns targets with a trailing dot
# (localhost.build.10gen.cc.) which must be stripped before suffix comparison.
uri: "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=.build.10gen.cc"
seeds:
- localhost.build.10gen.cc:27017
options:
ssl: true
ping: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"uri": "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=uild.10gen.cc",
"seeds": [],
"hosts": [],
"error": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# dot should be prepended to `srvAllowedHostsSuffix` causing the host to be .uild.10gen.cc which does not match any available DNS records
uri: "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=uild.10gen.cc"
seeds: []
hosts: []
error: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"uri": "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=build.10gen.cc",
"seeds": [
"localhost.build.10gen.cc:27017"
],
"options": {
"ssl": true
},
"ping": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
uri: "mongodb+srv://test12.test.build.10gen.cc/?srvAllowedHostsSuffix=build.10gen.cc"
seeds:
- localhost.build.10gen.cc:27017
options:
ssl: true
ping: false
5 changes: 5 additions & 0 deletions src/MongoDB.Driver/ClusterKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ internal sealed class ClusterKey
private readonly TimeSpan _serverSelectionTimeout;
private readonly TimeSpan _socketTimeout;
private readonly Socks5ProxySettings _socks5ProxySettings;
private readonly string _srvAllowedHostsSuffix;
private readonly int _srvMaxHosts;
private readonly string _srvServiceName;
private readonly SslSettings _sslSettings;
Expand Down Expand Up @@ -97,6 +98,7 @@ public ClusterKey(
TimeSpan serverSelectionTimeout,
TimeSpan socketTimeout,
Socks5ProxySettings socks5ProxySettings,
string srvAllowedHostsSuffix,
int srvMaxHosts,
string srvServiceName,
SslSettings sslSettings,
Expand Down Expand Up @@ -135,6 +137,7 @@ public ClusterKey(
_serverSelectionTimeout = serverSelectionTimeout;
_socketTimeout = socketTimeout;
_socks5ProxySettings = socks5ProxySettings;
_srvAllowedHostsSuffix = srvAllowedHostsSuffix;
_srvMaxHosts = srvMaxHosts;
_srvServiceName = srvServiceName;
_sslSettings = sslSettings;
Expand Down Expand Up @@ -177,6 +180,7 @@ public ClusterKey(
public TimeSpan ServerSelectionTimeout { get { return _serverSelectionTimeout; } }
public TimeSpan SocketTimeout { get { return _socketTimeout; } }
public Socks5ProxySettings Socks5ProxySettings { get { return _socks5ProxySettings; } }
public string SrvAllowedHostsSuffix { get { return _srvAllowedHostsSuffix; } }
public int SrvMaxHosts { get { return _srvMaxHosts; } }
public string SrvServiceName { get { return _srvServiceName; } }
public SslSettings SslSettings { get { return _sslSettings; } }
Expand Down Expand Up @@ -234,6 +238,7 @@ public override bool Equals(object obj)
_serverSelectionTimeout == rhs._serverSelectionTimeout &&
_socketTimeout == rhs._socketTimeout &&
object.Equals(_socks5ProxySettings, rhs._socks5ProxySettings) &&
_srvAllowedHostsSuffix == rhs._srvAllowedHostsSuffix &&
_srvMaxHosts == rhs._srvMaxHosts &&
_srvServiceName == rhs.SrvServiceName &&
object.Equals(_sslSettings, rhs._sslSettings) &&
Expand Down
3 changes: 2 additions & 1 deletion src/MongoDB.Driver/ClusterRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ private ClusterSettings ConfigureCluster(ClusterSettings settings, ClusterKey cl
maxServerSelectionWaitQueueSize: clusterKey.WaitQueueSize,
serverApi: clusterKey.ServerApi,
serverSelectionTimeout: clusterKey.ServerSelectionTimeout,
scheme: clusterKey.Scheme);
scheme: clusterKey.Scheme)
.WithSrvAllowedHostsSuffix(clusterKey.SrvAllowedHostsSuffix);
}

private ConnectionPoolSettings ConfigureConnectionPool(ConnectionPoolSettings settings, ClusterKey clusterKey)
Expand Down
36 changes: 21 additions & 15 deletions src/MongoDB.Driver/Core/Clusters/DnsMonitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,14 @@ namespace MongoDB.Driver.Core.Clusters
{
internal class DnsMonitor : IDnsMonitor
{
#region static
private static string EnsureLookupDomainNameIsValid(string lookupDomainName)
{
Ensure.IsNotNull(lookupDomainName, nameof(lookupDomainName));
Ensure.That(lookupDomainName.Count(c => c == '.') >= 2, "LookupDomainName must have at least three components.", nameof(lookupDomainName));
return lookupDomainName;
}
#endregion

// private fields
private readonly CancellationToken _cancellationToken;
private readonly IDnsMonitoringCluster _cluster;
private readonly IDnsResolver _dnsResolver;
private readonly string _lookupDomainName;
private bool _processDnsResultHasEverBeenCalled;
private readonly string _service;
private readonly string _srvParentDomain;
private DnsMonitorState _state;
private Exception _unhandledException;

Expand All @@ -54,15 +46,28 @@ public DnsMonitor(IDnsMonitoringCluster cluster,
IDnsResolver dnsResolver,
string srvServiceName,
string lookupDomainName,
string srvAllowedHostsSuffix,
IEventSubscriber eventSubscriber,
ILogger<LogCategories.SDAM> logger,
CancellationToken cancellationToken)
{
_cluster = Ensure.IsNotNull(cluster, nameof(cluster));
_dnsResolver = Ensure.IsNotNull(dnsResolver, nameof(dnsResolver));
_lookupDomainName = EnsureLookupDomainNameIsValid(lookupDomainName);
Ensure.IsNotNullOrEmpty(lookupDomainName, nameof(lookupDomainName));

// the domain that resolved hosts are validated against carries the same normalization
// the hosts themselves do. The query name is built from the normalized form as well,
// since DNS is case insensitive and expects A-labels on the wire.
if (!ConnectionString.TryNormalizeHostName(lookupDomainName, out _lookupDomainName))
{
throw new MongoConfigurationException($"Unable to parse {lookupDomainName} as a hostname.");
}

_cancellationToken = cancellationToken;
_service = $"_{srvServiceName}._tcp." + _lookupDomainName;
_srvParentDomain = srvAllowedHostsSuffix == null
? null
: ConnectionString.NormalizeSrvAllowedHostsSuffix(srvAllowedHostsSuffix);
_state = DnsMonitorState.Created;

_eventLogger = logger.ToEventLogger(eventSubscriber);
Expand Down Expand Up @@ -133,14 +138,15 @@ private List<DnsEndPoint> GetValidEndPoints(List<SrvRecord> srvRecords)

foreach (var srvRecord in srvRecords)
{
var endPoint = srvRecord.EndPoint;
// DNS names are case-insensitive, and SDAM requires host names to be normalized to lower-case
var host = srvRecord.EndPoint.Host.ToLowerInvariant();
if (host.EndsWith(".", StringComparison.Ordinal))
if (!ConnectionString.TryNormalizeHostName(endPoint.Host, out var host))
{
host = host.Substring(0, host.Length - 1);
_eventLogger.LogAndPublish(new SdamInformationEvent("Unable to parse host returned by DNS SRV lookup: {0}.", endPoint.Host));
continue;
}

var endPoint = new DnsEndPoint(host, srvRecord.EndPoint.Port);
endPoint = new DnsEndPoint(host, endPoint.Port);
if (IsValidHost(endPoint))
{
validEndPoints.Add(endPoint);
Expand All @@ -156,7 +162,7 @@ private List<DnsEndPoint> GetValidEndPoints(List<SrvRecord> srvRecords)

private bool IsValidHost(DnsEndPoint endPoint)
{
return ConnectionString.HasValidParentDomain(_lookupDomainName, endPoint);
return ConnectionString.HasValidParentDomain(_lookupDomainName, endPoint, _srvParentDomain);
}

private void Monitor()
Expand Down
4 changes: 2 additions & 2 deletions src/MongoDB.Driver/Core/Clusters/DnsMonitorFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ public DnsMonitorFactory(IEventSubscriber eventSubscriber, ILoggerFactory logger
_loggerFactory = loggerFactory;
}

public IDnsMonitor CreateDnsMonitor(IDnsMonitoringCluster cluster, string srvServiceName, string lookupDomainName, CancellationToken cancellationToken)
public IDnsMonitor CreateDnsMonitor(IDnsMonitoringCluster cluster, string srvServiceName, string lookupDomainName, string srvAllowedHostsSuffix, CancellationToken cancellationToken)
{
var dnsResolver = DnsClientWrapper.Instance;
return new DnsMonitor(cluster, dnsResolver, srvServiceName, lookupDomainName, _eventSubscriber, _loggerFactory?.CreateLogger<LogCategories.SDAM>(), cancellationToken);
return new DnsMonitor(cluster, dnsResolver, srvServiceName, lookupDomainName, srvAllowedHostsSuffix, _eventSubscriber, _loggerFactory?.CreateLogger<LogCategories.SDAM>(), cancellationToken);
}
}
}
2 changes: 1 addition & 1 deletion src/MongoDB.Driver/Core/Clusters/IDnsMonitorFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ namespace MongoDB.Driver.Core.Clusters
{
internal interface IDnsMonitorFactory
{
IDnsMonitor CreateDnsMonitor(IDnsMonitoringCluster cluster, string srvServiceName, string lookupDomainName, CancellationToken cancellationToken);
IDnsMonitor CreateDnsMonitor(IDnsMonitoringCluster cluster, string srvServiceName, string lookupDomainName, string srvAllowedHostsSuffix, CancellationToken cancellationToken);
}
}
2 changes: 1 addition & 1 deletion src/MongoDB.Driver/Core/Clusters/LoadBalancedCluster.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ public void Initialize()
// _server will be created after srv resolving
var dnsEndPoint = (DnsEndPoint)endPoint;
var lookupDomainName = dnsEndPoint.Host;
var monitor = _dnsMonitorFactory.CreateDnsMonitor(this, _settings.SrvServiceName, lookupDomainName, _dnsMonitorCancellationTokenSource.Token);
var monitor = _dnsMonitorFactory.CreateDnsMonitor(this, _settings.SrvServiceName, lookupDomainName, _settings.SrvAllowedHostsSuffix, _dnsMonitorCancellationTokenSource.Token);
_dnsMonitorThread = monitor.Start();
}

Expand Down
2 changes: 1 addition & 1 deletion src/MongoDB.Driver/Core/Clusters/MultiServerCluster.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ public override void Initialize()
{
var dnsEndPoint = (DnsEndPoint)Settings.EndPoints.Single();
var lookupDomainName = dnsEndPoint.Host;
var dnsMonitor = _dnsMonitorFactory.CreateDnsMonitor(this, Settings.SrvServiceName, lookupDomainName, _monitorServersCancellationTokenSource.Token);
var dnsMonitor = _dnsMonitorFactory.CreateDnsMonitor(this, Settings.SrvServiceName, lookupDomainName, Settings.SrvAllowedHostsSuffix, _monitorServersCancellationTokenSource.Token);
_dnsMonitorThread = dnsMonitor.Start(); // store the Thread for use as evidence when testing that the DnsMonitor was started
}
}
Expand Down
Loading