summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorADAM David Alan Martin <adam.martin@10gen.com>2018-06-26 15:25:32 -0400
committerADAM David Alan Martin <adam.martin@10gen.com>2018-06-26 16:35:39 -0400
commit181c43bd006666b07441bb3be61b7324ef7dcc80 (patch)
tree6b9b232b0d0af97adff4f73d4d732909479592a7
parent5abdd989fbf8f21f9fc01addaf45e661ec793c81 (diff)
downloadmongo-181c43bd006666b07441bb3be61b7324ef7dcc80.tar.gz
SERVER-34563 Handle DNS names correctly in SRV record processing.
The current implementation of DNS name processing uses raw string processing. This change alters the mechanism to use a proper DNS name type which parses the hostname for proper processing.
-rw-r--r--src/mongo/client/mongo_uri.cpp64
-rw-r--r--src/mongo/client/mongo_uri_test.cpp151
-rw-r--r--src/mongo/util/SConscript10
-rw-r--r--src/mongo/util/dns_name.h451
-rw-r--r--src/mongo/util/dns_name_test.cpp266
-rw-r--r--src/mongo/util/dns_query.h4
6 files changed, 865 insertions, 81 deletions
diff --git a/src/mongo/client/mongo_uri.cpp b/src/mongo/client/mongo_uri.cpp
index 91a069eb703..47ea195c362 100644
--- a/src/mongo/client/mongo_uri.cpp
+++ b/src/mongo/client/mongo_uri.cpp
@@ -45,6 +45,7 @@
#include "mongo/client/sasl_client_authenticate.h"
#include "mongo/db/namespace_string.h"
#include "mongo/stdx/utility.h"
+#include "mongo/util/dns_name.h"
#include "mongo/util/dns_query.h"
#include "mongo/util/hex.h"
#include "mongo/util/mongoutils/str.h"
@@ -234,28 +235,12 @@ MongoURI::OptionsMap addTXTOptions(std::map<std::string, std::string> options,
return {std::make_move_iterator(begin(options)), std::make_move_iterator(end(options))};
}
-
-std::string stripHost(const std::string& hostname) {
- return hostname.substr(hostname.find('.') + 1);
-}
-
-bool isWithinDomain(std::string hostname, std::string domain) {
- auto removeFQDNRoot = [](std::string name) -> std::string {
- if (name.back() == '.') {
- name.pop_back();
- }
- return name;
- };
- hostname = stripHost(removeFQDNRoot(std::move(hostname)));
- domain = removeFQDNRoot(std::move(domain));
- return hostname == domain;
-}
} // namespace
MongoURI MongoURI::parseImpl(const std::string& url) {
const StringData urlSD(url);
- // 1. Validate and remove the scheme prefix mongodb://
+ // 1. Validate and remove the scheme prefix `mongodb://` or `mongodb+srv://`
const bool isSeedlist = urlSD.startsWith(kURISRVPrefix);
if (!(urlSD.startsWith(kURIPrefix) || isSeedlist)) {
return MongoURI(uassertStatusOK(ConnectionString::parse(url)));
@@ -365,27 +350,40 @@ MongoURI MongoURI::parseImpl(const std::string& url) {
uasserted(ErrorCodes::FailedToParse,
"Only a single server may be specified with a mongo+srv:// url.");
}
- const int dots = std::count(begin(canonicalHost), end(canonicalHost), '.');
- const int requiredDots = (canonicalHost.back() == '.') + 2;
- if (dots < requiredDots) {
+
+ const mongo::dns::HostName host(canonicalHost);
+
+ if (host.nameComponents().size() < 3) {
uasserted(ErrorCodes::FailedToParse,
"A server specified with a mongo+srv:// url must have at least 3 hostname "
"components separated by dots ('.')");
}
- const auto domain = stripHost(canonicalHost);
- auto srvEntries = dns::lookupSRVRecords("_mongodb._tcp." + canonicalHost);
+
+ const mongo::dns::HostName srvSubdomain("_mongodb._tcp");
+
+ const auto srvEntries =
+ dns::lookupSRVRecords(srvSubdomain.resolvedIn(host).canonicalName());
+
+ auto makeFQDN = [](dns::HostName hostName) {
+ hostName.forceQualification();
+ return hostName;
+ };
+
+ const mongo::dns::HostName domain = makeFQDN(host.parentDomain());
servers.clear();
- std::transform(std::make_move_iterator(begin(srvEntries)),
- std::make_move_iterator(end(srvEntries)),
- back_inserter(servers),
- [&domain](auto&& srv) {
- if (!isWithinDomain(srv.host, domain)) {
- uasserted(ErrorCodes::FailedToParse,
- "Hostname "s + srv.host + " is not within the domain "s +
- domain);
- }
- return HostAndPort(std::move(srv.host), srv.port);
- });
+ using std::begin;
+ using std::end;
+ std::transform(
+ begin(srvEntries), end(srvEntries), back_inserter(servers), [&domain](auto&& srv) {
+ const dns::HostName target(srv.host); // FQDN
+
+ if (!domain.contains(target)) {
+ uasserted(ErrorCodes::FailedToParse,
+ str::stream() << "Hostname " << target << " is not within the domain "
+ << domain);
+ }
+ return HostAndPort(srv.host, srv.port);
+ });
}
// 6. Split the auth database and connection options string by the first, unescaped ?,
diff --git a/src/mongo/client/mongo_uri_test.cpp b/src/mongo/client/mongo_uri_test.cpp
index b8734f2d57a..9584bfd6f9a 100644
--- a/src/mongo/client/mongo_uri_test.cpp
+++ b/src/mongo/client/mongo_uri_test.cpp
@@ -603,6 +603,7 @@ TEST(MongoURI, srvRecordTest) {
using namespace mongo;
enum Expectation : bool { success = true, failure = false };
const struct {
+ int lineNumber;
std::string uri;
std::string user;
std::string password;
@@ -612,7 +613,8 @@ TEST(MongoURI, srvRecordTest) {
Expectation expectation;
} tests[] = {
// Test some non-SRV URIs to make sure that they do not perform expansions
- {"mongodb://test1.test.build.10gen.cc:12345/",
+ {__LINE__,
+ "mongodb://test1.test.build.10gen.cc:12345/",
"",
"",
"",
@@ -620,7 +622,8 @@ TEST(MongoURI, srvRecordTest) {
{},
success},
- {"mongodb://test6.test.build.10gen.cc:12345/",
+ {__LINE__,
+ "mongodb://test6.test.build.10gen.cc:12345/",
"",
"",
"",
@@ -629,7 +632,8 @@ TEST(MongoURI, srvRecordTest) {
success},
// Test a sample URI against each provided testing DNS entry
- {"mongodb+srv://test1.test.build.10gen.cc/",
+ {__LINE__,
+ "mongodb+srv://test1.test.build.10gen.cc/",
"",
"",
"",
@@ -638,7 +642,8 @@ TEST(MongoURI, srvRecordTest) {
success},
// Test a sample URI against each provided testing DNS entry
- {"mongodb+srv://test1.test.build.10gen.cc/?ssl=false",
+ {__LINE__,
+ "mongodb+srv://test1.test.build.10gen.cc/?ssl=false",
"",
"",
"",
@@ -646,7 +651,36 @@ TEST(MongoURI, srvRecordTest) {
{{"ssl", "false"}},
success},
- {"mongodb+srv://user:password@test2.test.build.10gen.cc/"
+ // Test a sample URI against the need for deep DNS relation
+ {__LINE__,
+ "mongodb+srv://test18.test.build.10gen.cc/?replicaSet=repl0",
+ "",
+ "",
+ "",
+ {
+ {"localhost.sub.test.build.10gen.cc.", 27017},
+ },
+ {
+ {"ssl", "true"}, {"replicaSet", "repl0"},
+ },
+ success},
+
+ // Test a sample URI with FQDN against the need for deep DNS relation
+ {__LINE__,
+ "mongodb+srv://test18.test.build.10gen.cc./?replicaSet=repl0",
+ "",
+ "",
+ "",
+ {
+ {"localhost.sub.test.build.10gen.cc.", 27017},
+ },
+ {
+ {"ssl", "true"}, {"replicaSet", "repl0"},
+ },
+ success},
+
+ {__LINE__,
+ "mongodb+srv://user:password@test2.test.build.10gen.cc/"
"database?someOption=someValue&someOtherOption=someOtherValue",
"user",
"password",
@@ -656,7 +690,8 @@ TEST(MongoURI, srvRecordTest) {
success},
- {"mongodb+srv://user:password@test3.test.build.10gen.cc/"
+ {__LINE__,
+ "mongodb+srv://user:password@test3.test.build.10gen.cc/"
"database?someOption=someValue&someOtherOption=someOtherValue",
"user",
"password",
@@ -666,7 +701,8 @@ TEST(MongoURI, srvRecordTest) {
success},
- {"mongodb+srv://user:password@test5.test.build.10gen.cc/"
+ {__LINE__,
+ "mongodb+srv://user:password@test5.test.build.10gen.cc/"
"database?someOption=someValue&someOtherOption=someOtherValue",
"user",
"password",
@@ -679,7 +715,8 @@ TEST(MongoURI, srvRecordTest) {
{"ssl", "true"}},
success},
- {"mongodb+srv://user:password@test5.test.build.10gen.cc/"
+ {__LINE__,
+ "mongodb+srv://user:password@test5.test.build.10gen.cc/"
"database?someOption=someValue&authSource=anotherDB&someOtherOption=someOtherValue",
"user",
"password",
@@ -693,11 +730,19 @@ TEST(MongoURI, srvRecordTest) {
{"ssl", "true"}},
success},
- {"mongodb+srv://test6.test.build.10gen.cc/", "", "", "", {}, {}, failure},
+ {__LINE__, "mongodb+srv://test6.test.build.10gen.cc/", "", "", "", {}, {}, failure},
- {"mongodb+srv://test6.test.build.10gen.cc/database", "", "", "database", {}, {}, failure},
+ {__LINE__,
+ "mongodb+srv://test6.test.build.10gen.cc/database",
+ "",
+ "",
+ "database",
+ {},
+ {},
+ failure},
- {"mongodb+srv://test6.test.build.10gen.cc/?authSource=anotherDB",
+ {__LINE__,
+ "mongodb+srv://test6.test.build.10gen.cc/?authSource=anotherDB",
"",
"",
"",
@@ -705,7 +750,8 @@ TEST(MongoURI, srvRecordTest) {
{},
failure},
- {"mongodb+srv://test6.test.build.10gen.cc/?irrelevantOption=irrelevantValue",
+ {__LINE__,
+ "mongodb+srv://test6.test.build.10gen.cc/?irrelevantOption=irrelevantValue",
"",
"",
"",
@@ -714,7 +760,8 @@ TEST(MongoURI, srvRecordTest) {
failure},
- {"mongodb+srv://test6.test.build.10gen.cc/"
+ {__LINE__,
+ "mongodb+srv://test6.test.build.10gen.cc/"
"?irrelevantOption=irrelevantValue&authSource=anotherDB",
"",
"",
@@ -723,7 +770,8 @@ TEST(MongoURI, srvRecordTest) {
{},
failure},
- {"mongodb+srv://test7.test.build.10gen.cc./?irrelevantOption=irrelevantValue",
+ {__LINE__,
+ "mongodb+srv://test7.test.build.10gen.cc./?irrelevantOption=irrelevantValue",
"",
"",
"",
@@ -731,11 +779,12 @@ TEST(MongoURI, srvRecordTest) {
{},
failure},
- {"mongodb+srv://test7.test.build.10gen.cc./", "", "", "", {}, {}, failure},
+ {__LINE__, "mongodb+srv://test7.test.build.10gen.cc./", "", "", "", {}, {}, failure},
- {"mongodb+srv://test8.test.build.10gen.cc./", "", "", "", {}, {}, failure},
+ {__LINE__, "mongodb+srv://test8.test.build.10gen.cc./", "", "", "", {}, {}, failure},
- {"mongodb+srv://test10.test.build.10gen.cc./?irrelevantOption=irrelevantValue",
+ {__LINE__,
+ "mongodb+srv://test10.test.build.10gen.cc./?irrelevantOption=irrelevantValue",
"",
"",
"",
@@ -743,7 +792,8 @@ TEST(MongoURI, srvRecordTest) {
{},
failure},
- {"mongodb+srv://test11.test.build.10gen.cc./?irrelevantOption=irrelevantValue",
+ {__LINE__,
+ "mongodb+srv://test11.test.build.10gen.cc./?irrelevantOption=irrelevantValue",
"",
"",
"",
@@ -751,36 +801,39 @@ TEST(MongoURI, srvRecordTest) {
{},
failure},
- {"mongodb+srv://test12.test.build.10gen.cc./", "", "", "", {}, {}, failure},
- {"mongodb+srv://test13.test.build.10gen.cc./", "", "", "", {}, {}, failure},
- {"mongodb+srv://test14.test.build.10gen.cc./", "", "", "", {}, {}, failure},
- {"mongodb+srv://test15.test.build.10gen.cc./", "", "", "", {}, {}, failure},
- {"mongodb+srv://test16.test.build.10gen.cc./", "", "", "", {}, {}, failure},
- {"mongodb+srv://test17.test.build.10gen.cc./", "", "", "", {}, {}, failure},
- {"mongodb+srv://test18.test.build.10gen.cc./", "", "", "", {}, {}, failure},
- {"mongodb+srv://test19.test.build.10gen.cc./", "", "", "", {}, {}, failure},
-
- {"mongodb+srv://test12.test.build.10gen.cc/", "", "", "", {}, {}, failure},
- {"mongodb+srv://test13.test.build.10gen.cc/", "", "", "", {}, {}, failure},
- {"mongodb+srv://test14.test.build.10gen.cc/", "", "", "", {}, {}, failure},
- {"mongodb+srv://test15.test.build.10gen.cc/", "", "", "", {}, {}, failure},
- {"mongodb+srv://test16.test.build.10gen.cc/", "", "", "", {}, {}, failure},
- {"mongodb+srv://test17.test.build.10gen.cc/", "", "", "", {}, {}, failure},
- {"mongodb+srv://test18.test.build.10gen.cc/", "", "", "", {}, {}, failure},
- {"mongodb+srv://test19.test.build.10gen.cc/", "", "", "", {}, {}, failure},
+ {__LINE__, "mongodb+srv://test12.test.build.10gen.cc./", "", "", "", {}, {}, failure},
+ {__LINE__, "mongodb+srv://test13.test.build.10gen.cc./", "", "", "", {}, {}, failure},
+ {__LINE__, "mongodb+srv://test14.test.build.10gen.cc./", "", "", "", {}, {}, failure},
+ {__LINE__, "mongodb+srv://test15.test.build.10gen.cc./", "", "", "", {}, {}, failure},
+ {__LINE__, "mongodb+srv://test16.test.build.10gen.cc./", "", "", "", {}, {}, failure},
+ {__LINE__, "mongodb+srv://test17.test.build.10gen.cc./", "", "", "", {}, {}, failure},
+ {__LINE__, "mongodb+srv://test19.test.build.10gen.cc./", "", "", "", {}, {}, failure},
+
+ {__LINE__, "mongodb+srv://test12.test.build.10gen.cc/", "", "", "", {}, {}, failure},
+ {__LINE__, "mongodb+srv://test13.test.build.10gen.cc/", "", "", "", {}, {}, failure},
+ {__LINE__, "mongodb+srv://test14.test.build.10gen.cc/", "", "", "", {}, {}, failure},
+ {__LINE__, "mongodb+srv://test15.test.build.10gen.cc/", "", "", "", {}, {}, failure},
+ {__LINE__, "mongodb+srv://test16.test.build.10gen.cc/", "", "", "", {}, {}, failure},
+ {__LINE__, "mongodb+srv://test17.test.build.10gen.cc/", "", "", "", {}, {}, failure},
+ {__LINE__, "mongodb+srv://test19.test.build.10gen.cc/", "", "", "", {}, {}, failure},
};
for (const auto& test : tests) {
auto rs = MongoURI::parse(test.uri);
if (test.expectation == failure) {
- ASSERT_FALSE(rs.getStatus().isOK()) << "Failing URI: " << test.uri;
+ ASSERT_FALSE(rs.getStatus().isOK()) << "Failing URI: " << test.uri
+ << " data on line: " << test.lineNumber;
continue;
}
- ASSERT_OK(rs.getStatus());
+ ASSERT_OK(rs.getStatus()) << "Failed on URI: " << test.uri
+ << " data on line: " << test.lineNumber;
auto rv = rs.getValue();
- ASSERT_EQ(rv.getUser(), test.user);
- ASSERT_EQ(rv.getPassword(), test.password);
- ASSERT_EQ(rv.getDatabase(), test.database);
+ ASSERT_EQ(rv.getUser(), test.user) << "Failed on URI: " << test.uri
+ << " data on line: " << test.lineNumber;
+ ASSERT_EQ(rv.getPassword(), test.password) << "Failed on URI: " << test.uri
+ << " data on line : " << test.lineNumber;
+ ASSERT_EQ(rv.getDatabase(), test.database) << "Failed on URI: " << test.uri
+ << " data on line : " << test.lineNumber;
std::vector<std::pair<std::string, std::string>> options(begin(rv.getOptions()),
end(rv.getOptions()));
std::sort(begin(options), end(options));
@@ -793,12 +846,16 @@ TEST(MongoURI, srvRecordTest) {
mongo::unittest::log() << "Option: \"" << options[i].first << "="
<< options[i].second << "\" doesn't equal: \""
<< expectedOptions[i].first << "="
- << expectedOptions[i].second << "\"" << std::endl;
- std::cerr << "Failing URI: \"" << test.uri << "\"" << std::endl;
+ << expectedOptions[i].second << "\""
+ << " data on line: " << test.lineNumber << std::endl;
+ std::cerr << "Failing URI: \"" << test.uri << "\""
+ << " data on line: " << test.lineNumber << std::endl;
ASSERT(false);
}
}
- ASSERT_EQ(options.size(), expectedOptions.size()) << "Failing URI: " << test.uri;
+ ASSERT_EQ(options.size(), expectedOptions.size()) << "Failing URI: "
+ << " data on line: " << test.lineNumber
+ << test.uri;
std::vector<HostAndPort> hosts(begin(rv.getServers()), end(rv.getServers()));
std::sort(begin(hosts), end(hosts));
@@ -806,9 +863,13 @@ TEST(MongoURI, srvRecordTest) {
std::sort(begin(expectedHosts), end(expectedHosts));
for (std::size_t i = 0; i < std::min(hosts.size(), expectedHosts.size()); ++i) {
- ASSERT_EQ(hosts[i], expectedHosts[i]);
+ ASSERT_EQ(hosts[i], expectedHosts[i]) << "Failed on URI: " << test.uri
+ << " at host number" << i
+ << " data on line: " << test.lineNumber;
}
- ASSERT_TRUE(hosts.size() == expectedHosts.size());
+ ASSERT_TRUE(hosts.size() == expectedHosts.size())
+ << "Failed on URI: " << test.uri << " Found " << hosts.size() << " hosts, expected "
+ << expectedHosts.size() << " data on line: " << test.lineNumber;
}
}
diff --git a/src/mongo/util/SConscript b/src/mongo/util/SConscript
index 54caf685b28..c9414e0c281 100644
--- a/src/mongo/util/SConscript
+++ b/src/mongo/util/SConscript
@@ -492,7 +492,15 @@ env.CppUnitTest(
LIBDEPS=[
'dns_query',
"$BUILD_DIR/mongo/base",
- ]
+ ],
+)
+
+env.CppUnitTest(
+ target='dns_name_test',
+ source=['dns_name_test.cpp'],
+ LIBDEPS=[
+ "$BUILD_DIR/mongo/base",
+ ],
)
env.Library(
diff --git a/src/mongo/util/dns_name.h b/src/mongo/util/dns_name.h
new file mode 100644
index 00000000000..cb4533b03ed
--- /dev/null
+++ b/src/mongo/util/dns_name.h
@@ -0,0 +1,451 @@
+/**
+ * Copyright (C) 2018 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <cctype>
+#include <iostream>
+#include <iterator>
+#include <sstream>
+#include <tuple>
+#include <vector>
+
+#include "mongo/base/error_codes.h"
+#include "mongo/base/string_data.h"
+#include "mongo/bson/util/builder.h"
+#include "mongo/util/assert_util.h"
+#include "mongo/util/mongoutils/str.h"
+
+namespace mongo {
+namespace dns {
+namespace detail_dns_host_name {
+using std::begin;
+using std::end;
+using std::rbegin;
+using std::rend;
+
+class HostName;
+bool operator==(const HostName& lhs, const HostName& rhs);
+
+/**
+ * A `dns::HostName` represents a DNS Hostname in a form which is suitable for programatic
+ * manipulation.
+ *
+ * Oftentimes it is inappropriate to operate on a domain name (DNS Hostname) as a string. Besides
+ * the obvious limitations and cognitive overhead implied by string processing, there are
+ * fundamental semantic conventions in the format of a DNS Hostname which need to be handled
+ * appropriately. This type, `dns::HostName` represents a DNS Hostname in native C++ types and
+ * provides a set of salient member functions which exhibit the expected behavior and semantics of a
+ * DNS Hostname. It is encouraged to rewrite code which handles domain names from raw string form
+ * into code using this type instead. A type which represents and obeys correct DNS Hostname
+ * semantics will help prevent bugs in name handling and resolution.
+ */
+class HostName {
+public:
+ /**
+ * A `dns::HostName` can be either Fully Qualified (FQDN) or a relative name.
+ *
+ * Some member functions of `dns::HostName` may behave differently depending upon whether a
+ * Hostname is fully qualified or not.
+ */
+ enum Qualification : bool { kRelativeName = false, kFullyQualified = true };
+
+public:
+ /**
+ * Constructs a parsed DNS Hostname representation from the specified string.
+ *
+ * A DNS name can be fully qualified (ending in a '.') or unqualified (not ending in a '.').
+ * When the specified `dnsName` string ends in a terminating dot (`'.'`) character, the
+ * constructed `dns::HostName` object will have the `Qualification::kFullyQualified` state,
+ * otherwise it will have the `Qualification::kRelativeName` state. This constructor will parse
+ * the specified name, separating it on the dot (`'.'`) tokens for simpler programatic
+ * processing.
+ *
+ * THROWS: `DBException` with `ErrorCodes::DNSRecordTypeMismatch` as the status value if the
+ * name is ill formatted.
+ */
+ explicit HostName(StringData dnsName) {
+ if (dnsName.empty())
+ uasserted(ErrorCodes::DNSRecordTypeMismatch,
+ "A Domain Name cannot have zero characters");
+
+ if (dnsName[0] == '.')
+ uasserted(ErrorCodes::DNSRecordTypeMismatch,
+ "A Domain Name cannot start with a '.' character.");
+
+ enum ParserState { kFirstLetter, kNonPeriod, kPeriod };
+ ParserState parserState = kFirstLetter;
+
+ std::string name;
+ int idx = -1;
+ for (const char& ch : dnsName) {
+ ++idx;
+ if (ch == '.') {
+ if (parserState == kPeriod) {
+ uasserted(ErrorCodes::DNSRecordTypeMismatch,
+ "A Domain Name cannot have two adjacent '.' characters");
+ }
+ parserState = kPeriod;
+ this->_nameComponents.push_back(std::move(name));
+ name.clear();
+ continue;
+ }
+ if (parserState == kPeriod) {
+ parserState = kFirstLetter;
+ }
+
+
+ invariant(ch != '.');
+
+ // We permit dashes and numbers. We also permit underscores for use with SRV records
+ // and such.
+ if (!(ch == '-' || std::isalnum(ch) || (ch == '_' && parserState == kFirstLetter))) {
+ uasserted(ErrorCodes::DNSRecordTypeMismatch,
+ "A Domain Name cannot have tokens other than dash or alphanumerics.");
+ }
+ // All domain names are represented in lower-case letters, because DNS is case
+ // insensitive.
+ name.push_back(std::tolower(ch));
+ if (parserState == kFirstLetter) {
+ parserState = kNonPeriod;
+ }
+ }
+
+ if (parserState == kPeriod)
+ fullyQualified = kFullyQualified;
+ else {
+ fullyQualified = kRelativeName;
+ _nameComponents.push_back(std::move(name));
+ }
+
+ if (_nameComponents.empty())
+ uasserted(ErrorCodes::DNSRecordTypeMismatch,
+ "A Domain Name cannot have zero name elements");
+
+ checkForValidForm();
+
+ // Reverse all the names, once we've parsed them all in.
+ std::reverse(begin(_nameComponents), end(_nameComponents));
+ }
+
+ /**
+ * Returns whether this DNS Hostname has been fully qualified.
+ *
+ * A DNS Hostname is considered fully qualified, if the canonical specification of its name
+ * includes a trailing `'.'`. Fully Qualified Domain Names (FQDNs) are always resolved against
+ * the root name servers and indicate absolute names. Unqualified names are looked up against
+ * DNS configuration specific prefixes, recursively, until a match is found, which may not be
+ * the corresponding FQDN.
+ *
+ * RETURNS: True if this hostname is an FQDN and false otherwise.
+ */
+ bool isFQDN() const {
+ return fullyQualified;
+ }
+
+ /**
+ * Changes the qualification of this `dns::HostName` to the specified `qualification`.
+ *
+ * An unqualified domain hostname may exist as an artifact of other protocols wherein the actual
+ * qualification of that name is implied to be complete. When operating on such names in
+ * `dns::HostName` form, it may be necessary to alter the qualification after the fact.
+ *
+ * POST: The qualification of `*this` will be changed to the qualification specified by
+ * `qualification`.
+ */
+ void forceQualification(const Qualification qualification = kFullyQualified) {
+ fullyQualified = qualification;
+ }
+
+ /**
+ * Returns the complete canonical name for this `dns::HostName`.
+ *
+ * The canonical form for a DNS Hostname is the complete dotted DNS path, including a trailing
+ * dot (if the domain in question is fully qualified). A DNS Hostname which is fully qualified
+ * (ending in a trailing dot) will not compare equal (in string form) to a DNS Hostname which
+ * has not been fully qualified. This representation may be unsuitable for some use cases which
+ * involve relaxed qualification indications.
+ *
+ * RETURNS: A `std::string` which represents this DNS Hostname in complete canonical form.
+ */
+ std::string canonicalName() const {
+ return str::stream() << *this;
+ }
+
+ /**
+ * Returns the complete name for this `dns::HostName` in a form suitable for use with SSL
+ * certificate names.
+ *
+ * For myriad reasons, SSL certificates do not specify the fully qualified name of any host.
+ * When using `dns::HostName` objects in SSL aware code, it may be necessary to get an
+ * unqualified string form for use in certificate name comparisons.
+ *
+ * RETURNS: A `std::string` which represents this Hostname without a trailing dot (`'.'`).
+ */
+ std::string noncanonicalName() const {
+ StringBuilder sb;
+ streamUnqualified(sb);
+ return sb.str();
+ }
+
+ /**
+ * Returns the number of subdomain components in this `dns::HostName`.
+ *
+ * A DNS Hostname is composed of at least one, and sometimes more, subdomains. This function
+ * indicates how many subdomains this `dns::HostName` specifier has. Each subdomain is
+ * separated by a single `'.'` character.
+ *
+ * RETURNS: The number of components in `this->nameComponents()`
+ */
+ std::size_t depth() const {
+ return this->_nameComponents.size();
+ }
+
+ /**
+ * Returns a new `dns::HostName` object which represents the name of the DNS domain in which
+ * this object resides.
+ *
+ * All domains of depth greater than 1 are composed of multiple sub-domains. This function
+ * provides the next-level parent of the domain represented by `*this`.
+ *
+ * PRE: This `dns::HostName` must have at least two subdomains (`this->depth() > 1`).
+ *
+ * NOTE: The behavior of this function is undefined unless its preconditions are met.
+ *
+ * RETURNS: A `dns::HostName` which has one fewer domain specified.
+ */
+ HostName parentDomain() const {
+ if (this->_nameComponents.size() == 1) {
+ uasserted(ErrorCodes::DNSRecordTypeMismatch,
+ "A top level domain has no subdomains in its name");
+ }
+ HostName result = *this;
+ result._nameComponents.pop_back();
+ return result;
+ }
+
+ /**
+ * Returns true if the specified `candidate` Hostname would be resolved within `*this` as a
+ * hostname and false otherwise.
+ *
+ * Two domains can be said to have a "contains" relationship only when when both are Fully
+ * Qualified Domain Names (FQDNs). When either domain or both domains are unqualified, then it
+ * is impossible to know whether one could be resolved within the other correctly.
+ *
+ * PRE: This `this->isFQDN() && candidate.isFQDN()` must be true. Resolving unqualified names
+ * against other unqualified names has some implications on what the `contains` relationship
+ * would indicate. We sidestep those at this time.
+ *
+ * THROWS: `DBException` with `ErrorCodes::DNSRecordTypeMismatch` as the status value if
+ * `!this->isFQDN() || !candidate.isFQDN()`.
+ *
+ * RETURNS: False when `!candidate.isFQDN() || !this->isFQDN()`. False when `this->depth() >=
+ * candidate.depth()`. Otherwise a value equivalent to `[temp = candidate]{ while (temp.depth()
+ * > this->depth()) temp= temp.parentDomain(); return temp; }() == *this;`
+ */
+ bool contains(const HostName& candidate) const {
+ if (!this->isFQDN() || !candidate.isFQDN()) {
+ uasserted(ErrorCodes::DNSRecordTypeMismatch,
+ "Only FQDNs can be checked for subdomain relationships.");
+ }
+ return (_nameComponents.size() < candidate._nameComponents.size()) &&
+ std::equal(
+ begin(_nameComponents), end(_nameComponents), begin(candidate._nameComponents));
+ }
+
+ /**
+ * Returns a new `dns::HostName` which represents the larger (possibly canonical) name that
+ * would be used to lookup `*this` within the domain of the specified `rhs`.
+ *
+ * Unqualified DNS Hostnames can be prepended to other DNS Hostnames to provide a DNS string
+ * which is equivalent to what a resolution of the unqualified name would be in the domain of
+ * the second (possibly qualified) name.
+ *
+ * PRE: `this->isFQDN() == false`.
+ *
+ * RETURNS: A `dns::HostName` which has a `canonicalName()` equivalent to `this->canonicalName()
+ * + rhs.canonicalName()`.
+ *
+ * THROWS: `DBException` with `ErrorCodes::DNSRecordTypeMismatch` as the status value if
+ * `this->isFQDN() == false`.
+ */
+ HostName resolvedIn(const HostName& rhs) const {
+ if (this->fullyQualified)
+ uasserted(
+ ErrorCodes::DNSRecordTypeMismatch,
+ "A fully qualified Domain Name cannot be resolved within another domain name.");
+ HostName result = rhs;
+ result._nameComponents.insert(
+ end(result._nameComponents), begin(this->_nameComponents), end(this->_nameComponents));
+
+ return result;
+ }
+
+ /**
+ * Returns an immutable reference to a `std::vector` of `std::string` which indicates the
+ * canonical path of this `dns::HostName`.
+ *
+ * Sometimes it is necessary to iterate over all of the elements of a domain name string. This
+ * function facilitates such iteration.
+ *
+ * RETURNS: A `const std::vector<std::string>&` which refers to all of the domain name
+ * components of `*this`.
+ */
+
+ const std::vector<std::string>& nameComponents() const& {
+ return this->_nameComponents;
+ }
+
+ std::vector<std::string> nameComponents() && {
+ return std::move(this->_nameComponents);
+ }
+
+ /**
+ * Compares two `dns::HostName` objects.
+ *
+ * Two `dns::HostName` objects compare equal when they both represent the same resolution path.
+ * This means that in addition to the lookup sequence (order of sub domains) being the same, the
+ * qualification of both objects must be the same. For example, `"www.google.com"` would not
+ * compare equal to `"www.google.com."` due to the presence of a trailing dot in the second
+ * case.
+ *
+ * RETURNS: True if `lhs` and `rhs` represent the same DNS path, and false otherwise.
+ */
+ friend bool operator==(const HostName& lhs, const HostName& rhs);
+
+ /**
+ * Compares two `dns::HostName` objects.
+ *
+ * RETURNS: `!(lhs == rhs)`.
+ */
+ friend bool operator!=(const HostName& lhs, const HostName& rhs) {
+ return !(lhs == rhs);
+ }
+
+ /**
+ * Streams a representation of the specified `hostName` to the specified `os` formatting stream.
+ *
+ * A canonical representation of `hostName` (with a trailing dot, `'.'`, when `hostName.isFQDN()
+ * == true`) will be placed into the formatting stream handled by `os`.
+ *
+ * RETURNS: A reference to the specified output stream `os`.
+ */
+ friend std::ostream& operator<<(std::ostream& os, const HostName& hostName) {
+ if (hostName.fullyQualified) {
+ hostName.streamQualified(os);
+ } else {
+ hostName.streamUnqualified(os);
+ }
+
+ return os;
+ }
+
+ friend StringBuilder& operator<<(StringBuilder& os, const HostName& hostName) {
+ if (hostName.fullyQualified) {
+ hostName.streamQualified(os);
+ } else {
+ hostName.streamUnqualified(os);
+ }
+
+ return os;
+ }
+
+private:
+ auto make_equality_lens() const {
+ return std::tie(fullyQualified, _nameComponents);
+ }
+
+ // When printing fully qualified names to a stream, we need to always append a dot.
+ template <typename StreamLike>
+ void streamQualified(StreamLike& os) const {
+ invariant(fullyQualified);
+ streamCore(os);
+ os << '.';
+ }
+
+ // When printing unqualified names to a stream, we omit the trailing dot, even if needed.
+ template <typename StreamLike>
+ void streamUnqualified(StreamLike& os) const {
+ streamCore(os);
+ }
+
+ // All streaming functions boil down into this central handler, for both `StringBuilder` and
+ // `std::ostream`.
+ template <typename StreamLike>
+ void streamCore(StreamLike& os) const {
+ std::for_each(rbegin(_nameComponents),
+ rend(_nameComponents),
+ [ first = true, &os ](const auto& component) mutable {
+ if (!first)
+ os << '.';
+ first = false;
+ os << component;
+ });
+ }
+
+ // If there are exactly 4 name components, and they are not fully qualified, then they cannot be
+ // all numbers. This helper function is used in validating that IPv4 addresses are not passed
+ // to the constructor of this class.
+ void checkForValidForm() const {
+ if (this->_nameComponents.size() != 4)
+ return;
+ if (this->fullyQualified)
+ return;
+
+ for (const auto& name : this->_nameComponents) {
+ // Any letters are good. A hyphen is okay too.
+ if (end(name) != std::find_if(begin(name), end(name), [](const char ch) {
+ return std::isalpha(ch) || ch == '-';
+ }))
+ return;
+ }
+
+ // If we couldn't find any letters or hyphens
+ uasserted(ErrorCodes::DNSRecordTypeMismatch,
+ "A Domain Name cannot be equivalent in form to an IPv4 address");
+ }
+
+ // Hostname components are stored in hierarchy order (reverse order from how a name is read by
+ // humans in text form).
+ std::vector<std::string> _nameComponents;
+
+ // FQDNs and Relative Names are discriminated by this field.
+ Qualification fullyQualified;
+};
+} // detail_dns_host_name
+
+// The `operator==` function has to be defined out-of-line, because it uses `make_equality_lens`
+// which is an auto-deduced return type function defined later in the class body.
+inline bool detail_dns_host_name::operator==(const HostName& lhs, const HostName& rhs) {
+ return lhs.make_equality_lens() == rhs.make_equality_lens();
+}
+
+using detail_dns_host_name::HostName;
+} // namespace dns
+} // namespace mongo
diff --git a/src/mongo/util/dns_name_test.cpp b/src/mongo/util/dns_name_test.cpp
new file mode 100644
index 00000000000..7a8c9d4821c
--- /dev/null
+++ b/src/mongo/util/dns_name_test.cpp
@@ -0,0 +1,266 @@
+/**
+ * Copyright (C) 2018 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+
+#include "mongo/util/dns_name.h"
+
+#include "mongo/stdx/utility.h"
+#include "mongo/unittest/unittest.h"
+
+using namespace std::literals::string_literals;
+
+namespace mongo {
+namespace {
+TEST(DNSNameTest, CorrectParsing) {
+ enum FQDNBool : bool { kIsFQDN = true, kNotFQDN = false };
+ const struct {
+ std::string input;
+ std::vector<std::string> parsedDomains;
+ FQDNBool isFQDN;
+ } tests[] = {
+ {"com."s, {"com"s}, kIsFQDN},
+ {"com"s, {"com"s}, kNotFQDN},
+ {"mongodb.com."s, {"com"s, "mongodb"s}, kIsFQDN},
+ {"mongodb.com"s, {"com"s, "mongodb"s}, kNotFQDN},
+ {"atlas.mongodb.com."s, {"com"s, "mongodb"s, "atlas"s}, kIsFQDN},
+ {"atlas.mongodb.com"s, {"com"s, "mongodb"s, "atlas"s}, kNotFQDN},
+ {"server.atlas.mongodb.com."s, {"com"s, "mongodb"s, "atlas"s, "server"s}, kIsFQDN},
+ {"server.atlas.mongodb.com"s, {"com"s, "mongodb"s, "atlas"s, "server"s}, kNotFQDN},
+ };
+
+ for (const auto& test : tests) {
+ const ::mongo::dns::HostName host(test.input);
+
+ ASSERT_EQ(host.nameComponents().size(), test.parsedDomains.size());
+ for (std::size_t i = 0; i < host.nameComponents().size(); ++i) {
+ ASSERT_EQ(host.nameComponents()[i], test.parsedDomains[i]);
+ }
+ ASSERT(host.isFQDN() == test.isFQDN);
+ }
+}
+
+TEST(DNSNameTest, CanonicalName) {
+ const struct {
+ std::string input;
+ std::string result;
+ } tests[] = {
+ {"com."s, "com."s},
+ {"com"s, "com"s},
+ {"mongodb.com."s, "mongodb.com."s},
+ {"mongodb.com"s, "mongodb.com"s},
+ {"atlas.mongodb.com."s, "atlas.mongodb.com."s},
+ {"atlas.mongodb.com"s, "atlas.mongodb.com"s},
+ {"server.atlas.mongodb.com."s, "server.atlas.mongodb.com."s},
+ {"server.atlas.mongodb.com"s, "server.atlas.mongodb.com"s},
+ };
+
+ for (const auto& test : tests) {
+ const ::mongo::dns::HostName host(test.input);
+
+ ASSERT_EQ(host.canonicalName(), test.result);
+ }
+}
+
+TEST(DNSNameTest, NoncanonicalName) {
+ const struct {
+ std::string input;
+ std::string result;
+ } tests[] = {
+ {"com."s, "com"s},
+ {"com"s, "com"s},
+ {"mongodb.com."s, "mongodb.com"s},
+ {"mongodb.com"s, "mongodb.com"s},
+ {"atlas.mongodb.com."s, "atlas.mongodb.com"s},
+ {"atlas.mongodb.com"s, "atlas.mongodb.com"s},
+ {"server.atlas.mongodb.com."s, "server.atlas.mongodb.com"s},
+ {"server.atlas.mongodb.com"s, "server.atlas.mongodb.com"s},
+ };
+
+ for (const auto& test : tests) {
+ const ::mongo::dns::HostName host(test.input);
+
+ ASSERT_EQ(host.noncanonicalName(), test.result);
+ }
+}
+
+TEST(DNSNameTest, Contains) {
+ enum IsSubdomain : bool { kIsSubdomain = true, kNotSubdomain = false };
+ enum TripsCheck : bool { kFailure = true, kSuccess = false };
+ const struct {
+ std::string domain;
+ std::string subdomain;
+ IsSubdomain isSubdomain;
+ TripsCheck tripsCheck;
+ } tests[] = {
+ {"com."s, "mongodb.com."s, kIsSubdomain, kSuccess},
+ {"com"s, "mongodb.com"s, kIsSubdomain, kFailure},
+ {"com."s, "mongodb.com"s, kNotSubdomain, kFailure},
+ {"com"s, "mongodb.com."s, kNotSubdomain, kFailure},
+
+ {"com."s, "atlas.mongodb.com."s, kIsSubdomain, kSuccess},
+ {"com"s, "atlas.mongodb.com"s, kIsSubdomain, kFailure},
+ {"com."s, "atlas.mongodb.com"s, kNotSubdomain, kFailure},
+ {"com"s, "atlas.mongodb.com."s, kNotSubdomain, kFailure},
+
+ {"org."s, "atlas.mongodb.com."s, kNotSubdomain, kSuccess},
+ {"org"s, "atlas.mongodb.com"s, kNotSubdomain, kFailure},
+ {"org."s, "atlas.mongodb.com"s, kNotSubdomain, kFailure},
+ {"org"s, "atlas.mongodb.com."s, kNotSubdomain, kFailure},
+
+ {"com."s, "com."s, kNotSubdomain, kSuccess},
+ {"com"s, "com."s, kNotSubdomain, kFailure},
+ {"com."s, "com"s, kNotSubdomain, kFailure},
+ {"com"s, "com"s, kNotSubdomain, kFailure},
+
+ {"mongodb.com."s, "mongodb.com."s, kNotSubdomain, kSuccess},
+ {"mongodb.com."s, "mongodb.com"s, kNotSubdomain, kFailure},
+ {"mongodb.com"s, "mongodb.com."s, kNotSubdomain, kFailure},
+ {"mongodb.com"s, "mongodb.com"s, kNotSubdomain, kFailure},
+
+ {"mongodb.com."s, "atlas.mongodb.com."s, kIsSubdomain, kSuccess},
+ {"mongodb.com"s, "atlas.mongodb.com"s, kIsSubdomain, kFailure},
+ {"mongodb.com."s, "atlas.mongodb.com"s, kNotSubdomain, kFailure},
+ {"mongodb.com"s, "atlas.mongodb.com."s, kNotSubdomain, kFailure},
+
+ {"mongodb.com."s, "server.atlas.mongodb.com."s, kIsSubdomain, kSuccess},
+ {"mongodb.com"s, "server.atlas.mongodb.com"s, kIsSubdomain, kFailure},
+ {"mongodb.com."s, "server.atlas.mongodb.com"s, kNotSubdomain, kFailure},
+ {"mongodb.com"s, "server.atlas.mongodb.com."s, kNotSubdomain, kFailure},
+
+ {"mongodb.org."s, "server.atlas.mongodb.com."s, kNotSubdomain, kSuccess},
+ {"mongodb.org"s, "server.atlas.mongodb.com"s, kNotSubdomain, kFailure},
+ {"mongodb.org."s, "server.atlas.mongodb.com"s, kNotSubdomain, kFailure},
+ {"mongodb.org"s, "server.atlas.mongodb.com."s, kNotSubdomain, kFailure},
+ };
+
+ for (const auto& test : tests) {
+ const ::mongo::dns::HostName domain(test.domain);
+ const ::mongo::dns::HostName subdomain(test.subdomain);
+
+ try {
+ ASSERT(test.isSubdomain == domain.contains(subdomain));
+ ASSERT(!test.tripsCheck);
+ } catch (const ExceptionFor<ErrorCodes::DNSRecordTypeMismatch>&) {
+ ASSERT(test.tripsCheck);
+ }
+ }
+}
+
+TEST(DNSNameTest, Resolution) {
+ enum Failure : bool { kFails = true, kSucceeds = false };
+ enum FQDNBool : bool { kIsFQDN = true, kNotFQDN = false };
+ const struct {
+ std::string domain;
+ std::string subdomain;
+ std::string result;
+
+ Failure fails;
+ FQDNBool isFQDN;
+ } tests[] = {
+ {"mongodb.com."s, "atlas"s, "atlas.mongodb.com."s, kSucceeds, kIsFQDN},
+ {"mongodb.com"s, "atlas"s, "atlas.mongodb.com"s, kSucceeds, kNotFQDN},
+
+ {"mongodb.com."s, "server.atlas"s, "server.atlas.mongodb.com."s, kSucceeds, kIsFQDN},
+ {"mongodb.com"s, "server.atlas"s, "server.atlas.mongodb.com"s, kSucceeds, kNotFQDN},
+
+ {"mongodb.com."s, "atlas."s, "FAILS"s, kFails, kNotFQDN},
+ {"mongodb.com"s, "atlas."s, "FAILS"s, kFails, kNotFQDN},
+ };
+
+ for (const auto& test : tests)
+ try {
+ const ::mongo::dns::HostName domain(test.domain);
+ const ::mongo::dns::HostName subdomain(test.subdomain);
+ const ::mongo::dns::HostName resolved = [&] {
+ try {
+ const ::mongo::dns::HostName rv = subdomain.resolvedIn(domain);
+ return rv;
+ } catch (const ExceptionFor<ErrorCodes::DNSRecordTypeMismatch>&) {
+ ASSERT(test.fails);
+ throw;
+ }
+ }();
+ ASSERT(!test.fails);
+
+ ASSERT_EQ(test.result, resolved.canonicalName());
+ ASSERT(test.isFQDN == resolved.isFQDN());
+ } catch (const ExceptionFor<ErrorCodes::DNSRecordTypeMismatch>&) {
+ ASSERT(test.fails);
+ }
+}
+
+TEST(DNSNameTest, ForceQualification) {
+ enum FQDNBool : bool { kIsFQDN = true, kNotFQDN = false };
+ using Qualification = ::mongo::dns::HostName::Qualification;
+ const struct {
+ std::string domain;
+ FQDNBool startedFQDN;
+ ::mongo::dns::HostName::Qualification forced;
+ FQDNBool becameFQDN;
+ std::string becameCanonical;
+ } tests[] = {
+ {"mongodb.com."s, kIsFQDN, Qualification::kFullyQualified, kIsFQDN, "mongodb.com."s},
+ {"mongodb.com"s, kNotFQDN, Qualification::kFullyQualified, kIsFQDN, "mongodb.com."s},
+
+ {"atlas.mongodb.com."s,
+ kIsFQDN,
+ Qualification::kFullyQualified,
+ kIsFQDN,
+ "atlas.mongodb.com."s},
+ {"atlas.mongodb.com"s,
+ kNotFQDN,
+ Qualification::kFullyQualified,
+ kIsFQDN,
+ "atlas.mongodb.com."s},
+
+ {"mongodb.com."s, kIsFQDN, Qualification::kRelativeName, kNotFQDN, "mongodb.com"s},
+ {"mongodb.com"s, kNotFQDN, Qualification::kRelativeName, kNotFQDN, "mongodb.com"s},
+
+ {"atlas.mongodb.com."s,
+ kIsFQDN,
+ Qualification::kRelativeName,
+ kNotFQDN,
+ "atlas.mongodb.com"s},
+ {"atlas.mongodb.com"s,
+ kNotFQDN,
+ Qualification::kRelativeName,
+ kNotFQDN,
+ "atlas.mongodb.com"s},
+ };
+
+ for (const auto& test : tests) {
+ ::mongo::dns::HostName domain(test.domain);
+ ASSERT(stdx::as_const(domain).isFQDN() == test.startedFQDN);
+ domain.forceQualification(test.forced);
+ ASSERT(stdx::as_const(domain).isFQDN() == test.becameFQDN);
+
+ ASSERT_EQ(stdx::as_const(domain).canonicalName(), test.becameCanonical);
+ }
+}
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/util/dns_query.h b/src/mongo/util/dns_query.h
index e8b203c8f71..53786e15f5c 100644
--- a/src/mongo/util/dns_query.h
+++ b/src/mongo/util/dns_query.h
@@ -96,7 +96,7 @@ std::vector<std::string> lookupTXTRecords(const std::string& service);
/**
* Returns a group of strings containing text from DNS TXT entries for a specified service.
* If the lookup fails because the record doesn't exist, an empty vector is returned.
- * THROWS: `DBException` with `ErrorCodes::DNSProtocolError` as th status value if the DNS lookup
+ * THROWS: `DBException` with `ErrorCodes::DNSProtocolError` as the status value if the DNS lookup
* fails for any other reason.
*/
std::vector<std::string> getTXTRecords(const std::string& service);
@@ -105,7 +105,7 @@ std::vector<std::string> getTXTRecords(const std::string& service);
* Returns a group of strings containing Address entries for a specified service.
* THROWS: `DBException` with `ErrorCodes::DNSHostNotFound` as the status value if the entry is not
* found and `ErrorCodes::DNSProtocolError` as the status value if the DNS lookup fails, for any
- * other reason
+ * other reason.
* NOTE: This function mostly exists to provide an easy test of the OS DNS APIs in our test driver.
*/
std::vector<std::string> lookupARecords(const std::string& service);