Skip to content

Commit 0417b13

Browse files
authored
CSHARP-5200: Allow valid SRV hostnames with less than 3 parts (#1527)
* CSHARP-5200: Allow valid SRV hostnames with less than 3 parts * Some corrections * Small simplification * Corrections according to PR * Permalink * Improved check * Small fix
1 parent 9ef4538 commit 0417b13

File tree

2 files changed

+116
-24
lines changed

2 files changed

+116
-24
lines changed

src/MongoDB.Driver/Core/Configuration/ConnectionString.cs

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1358,33 +1358,24 @@ private void ValidateResolvedHosts(string original, List<string> resolved)
13581358

13591359
internal static bool HasValidParentDomain(string original, DnsEndPoint resolvedEndPoint)
13601360
{
1361-
// Helper functions...
1362-
Func<string, string[]> getParentParts = x => x.Split('.').Skip(1).ToArray();
1363-
1364-
// Indicates whether "a" ends with "b"
1365-
Func<string[], string[], bool> endsWith = (a, b) =>
1366-
{
1367-
if (a.Length < b.Length)
1368-
{
1369-
return false;
1370-
}
1361+
var host = resolvedEndPoint.Host;
13711362

1372-
// loop from back to front making sure that all of b is at the back of a, in order.
1373-
for (int ai = a.Length - 1, bi = b.Length - 1; bi >= 0; ai--, bi--)
1374-
{
1375-
if (a[ai] != b[bi])
1376-
{
1377-
return false;
1378-
}
1379-
}
1363+
var hostDotCount = host.Count(c => c == '.');
1364+
var originalDotCount = original.Count(c => c == '.');
13801365

1381-
return true;
1382-
};
1366+
// If original has less than 3 dot separated parts,
1367+
// the returned hostname must have at least one more domain level than original
1368+
if (originalDotCount < 2)
1369+
{
1370+
return host.Length > original.Length &&
1371+
hostDotCount > originalDotCount &&
1372+
host.EndsWith(original, StringComparison.Ordinal) &&
1373+
host[host.Length - original.Length -1 ] == '.';
1374+
}
13831375

1384-
// make sure that the resolve host ends with domain of the parent.
1385-
var originalParentParts = getParentParts(original);
1386-
var host = resolvedEndPoint.Host;
1387-
return endsWith(getParentParts(host), originalParentParts);
1376+
// We check that the returned hostname has the same domain name as original
1377+
var originalFirstDotIndex = original.IndexOf('.');
1378+
return hostDotCount >= originalDotCount && host.AsSpan().EndsWith(original.AsSpan(originalFirstDotIndex), StringComparison.Ordinal);
13881379
}
13891380

13901381
private void ValidateResolvedOptions(IEnumerable<string> optionNames)
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Collections.Generic;
18+
using System.Net;
19+
using System.Threading;
20+
using System.Threading.Tasks;
21+
using FluentAssertions;
22+
using MongoDB.Driver.Core.Configuration;
23+
using MongoDB.Driver.Core.Misc;
24+
using Xunit;
25+
26+
namespace MongoDB.Driver.Tests.Specifications.initial_dns_seedlist_discovery.prose_tests
27+
{
28+
public class SrvPartsTests
29+
{
30+
// https://github.com/mongodb/specifications/blob/master/source/initial-dns-seedlist-discovery/tests/README.md#1-allow-srvs-with-fewer-than-3--separated-parts
31+
[Theory]
32+
[InlineData("mongodb+srv://localhost")]
33+
[InlineData("mongodb+srv://mongo.local")]
34+
public void Allow_srv_with_fewer_than_3_parts(string uri)
35+
{
36+
var exception = Record.Exception(() => new ConnectionString(uri));
37+
38+
exception.Should().BeNull();
39+
}
40+
41+
// https://github.com/mongodb/specifications/blob/master/source/initial-dns-seedlist-discovery/tests/README.md#2-throw-when-return-address-does-not-end-with-srv-domain
42+
[Theory]
43+
[InlineData("mongodb+srv://localhost", "localhost.mongodb")]
44+
[InlineData("mongodb+srv://mongo.local", "test_1.evil.local")]
45+
[InlineData("mongodb+srv://blogs.mongodb.com", "blogs.evil.com")]
46+
public void Throw_when_return_address_does_not_end_with_srv_domain(string uri, string resolvedHost)
47+
{
48+
var connectionString = new ConnectionString(uri, true, new MockDnsResolver(resolvedHost));
49+
var exception = Record.Exception(() => connectionString.Resolve());
50+
51+
exception.Should().BeOfType<MongoConfigurationException>().
52+
Which.Message.Should().Be("Hosts in the SRV record must have the same parent domain as the seed host.");
53+
}
54+
55+
// https://github.com/mongodb/specifications/blob/master/source/initial-dns-seedlist-discovery/tests/README.md#3-throw-when-return-address-is-identical-to-srv-hostname
56+
[Theory]
57+
[InlineData("mongodb+srv://localhost", "localhost")]
58+
[InlineData("mongodb+srv://mongo.local", "mongo.local")]
59+
public void Throw_when_return_address_is_identical_to_srv_hostname(string uri, string resolvedHost)
60+
{
61+
var connectionString = new ConnectionString(uri, true, new MockDnsResolver(resolvedHost));
62+
var exception = Record.Exception(() => connectionString.Resolve());
63+
64+
exception.Should().BeOfType<MongoConfigurationException>().
65+
Which.Message.Should().Be("Hosts in the SRV record must have the same parent domain as the seed host.");
66+
}
67+
68+
// https://github.com/mongodb/specifications/blob/master/source/initial-dns-seedlist-discovery/tests/README.md#4-throw-when-return-address-does-not-contain--separating-shared-part-of-domain
69+
[Theory]
70+
[InlineData("mongodb+srv://localhost", "test_1.cluster_1localhost")]
71+
[InlineData("mongodb+srv://mongo.local", "test_1.my_hostmongo.local")]
72+
[InlineData("mongodb+srv://blogs.mongodb.com", "cluster.testmongodb.com")]
73+
public void Throw_when_return_address_does_not_contain_dot_separating_shared_part_of_domain(string uri, string resolvedHost)
74+
{
75+
var connectionString = new ConnectionString(uri, true, new MockDnsResolver(resolvedHost));
76+
var exception = Record.Exception(() => connectionString.Resolve());
77+
78+
exception.Should().BeOfType<MongoConfigurationException>().
79+
Which.Message.Should().Be("Hosts in the SRV record must have the same parent domain as the seed host.");
80+
}
81+
82+
private class MockDnsResolver(string dnsEndPointString) : IDnsResolver
83+
{
84+
public List<SrvRecord> ResolveSrvRecords(string service, CancellationToken cancellation)
85+
{
86+
return new List<SrvRecord>
87+
{ new SrvRecord(new DnsEndPoint(dnsEndPointString, 2090), TimeSpan.MaxValue) };
88+
}
89+
90+
public List<TxtRecord> ResolveTxtRecords(string domainName, CancellationToken cancellation)
91+
=> throw new NotImplementedException();
92+
93+
public Task<List<SrvRecord>> ResolveSrvRecordsAsync(string service, CancellationToken cancellation)
94+
=> throw new NotImplementedException();
95+
96+
public Task<List<TxtRecord>> ResolveTxtRecordsAsync(string domainName, CancellationToken cancellation)
97+
=> throw new NotImplementedException();
98+
99+
}
100+
}
101+
}

0 commit comments

Comments
 (0)