summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorA. Jesse Jiryu Davis <jesse@mongodb.com>2019-04-23 17:27:07 -0400
committerA. Jesse Jiryu Davis <jesse@mongodb.com>2019-04-24 21:55:52 -0400
commit5e346eb4b9bb3ec678d4eac36b3f0a18c1c10939 (patch)
tree4af3ebd9563c61399458158d88faf7e6dc796ff4 /src/mongo
parentb9c656f6e2d0b0b7676927538ed7ac3278c23322 (diff)
downloadmongo-5e346eb4b9bb3ec678d4eac36b3f0a18c1c10939.tar.gz
SERVER-4999 Normalize all hostnames to lowercase
Hostnames passed to replSetInitiate, replSetReconfig, addShard, etc. are all normalized by replacing ASCII uppercase characters with lowercase characters, consistent with how MongoDB drivers treat hostnames. Fixes undefined behavior in mongo::str::toLower().
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/client/replica_set_monitor_scan_test.cpp38
-rw-r--r--src/mongo/db/repl/isself_test.cpp4
-rw-r--r--src/mongo/db/repl/repl_set_config_test.cpp19
-rw-r--r--src/mongo/db/repl/repl_set_heartbeat_response_test.cpp2
-rw-r--r--src/mongo/db/s/config/sharding_catalog_manager_add_shard_test.cpp4
-rw-r--r--src/mongo/db/s/config/sharding_catalog_manager_create_database_test.cpp6
-rw-r--r--src/mongo/dbtests/replica_set_monitor_test.cpp30
-rw-r--r--src/mongo/unittest/unittest.h5
-rw-r--r--src/mongo/util/net/hostandport.cpp7
-rw-r--r--src/mongo/util/net/hostandport.h2
-rw-r--r--src/mongo/util/net/hostandport_test.cpp24
-rw-r--r--src/mongo/util/str.h3
12 files changed, 132 insertions, 12 deletions
diff --git a/src/mongo/client/replica_set_monitor_scan_test.cpp b/src/mongo/client/replica_set_monitor_scan_test.cpp
index 02af7f2b7e6..6518d1b4a98 100644
--- a/src/mongo/client/replica_set_monitor_scan_test.cpp
+++ b/src/mongo/client/replica_set_monitor_scan_test.cpp
@@ -1023,6 +1023,44 @@ TEST_F(CoreScanTest, TwoPrimaries2ndHasOlderConfigVersion) {
ASSERT_EQUALS(state->configVersion, 2);
}
+// U+2603, Snowman, encoded as UTF-8.
+#define SNOWMAN "\xe2\x98\x83"
+
+TEST_F(CoreScanTest, CaseInsensitive) {
+ const auto host0 = "a";
+ const auto host1 = SNOWMAN "B";
+ const auto host2 = "c" SNOWMAN;
+ const std::vector<HostAndPort> seeds = {
+ HostAndPort(host0), HostAndPort(host1), HostAndPort(host2)};
+ const MongoURI uri(ConnectionString::forReplicaSet("name", seeds));
+ auto state = makeState(basicUri);
+ Refresher refresher(state);
+
+ // mock all replies
+ for (size_t i = 0; i != basicSeeds.size(); ++i) {
+ refresher.receivedIsMaster(HostAndPort(seeds[i]),
+ -1,
+ BSON("setName"
+ << "name"
+ << "ismaster"
+ << (i == 0)
+ << "secondary"
+ << (i != 0)
+ << "hosts"
+ // Different order and capitalization.
+ << BSON_ARRAY("A"
+ << "C" SNOWMAN
+ << SNOWMAN "b")
+ << "ok"
+ << true));
+ }
+
+ ASSERT_EQUALS(state->nodes.size(), static_cast<size_t>(3));
+ for (const auto& seed : seeds) {
+ ASSERT(state->findNode(HostAndPort(seed)));
+ }
+}
+
using MaxStalenessMSTest = ReplicaSetMonitorTest;
/**
diff --git a/src/mongo/db/repl/isself_test.cpp b/src/mongo/db/repl/isself_test.cpp
index 511ef122ecf..bbb03d0b26c 100644
--- a/src/mongo/db/repl/isself_test.cpp
+++ b/src/mongo/db/repl/isself_test.cpp
@@ -79,6 +79,10 @@ TEST_F(ServiceContextTest, DetectsSameHostIPv6) {
#endif
}
+TEST_F(ServiceContextTest, DetectsSameHostDifferentCapitalization) {
+ ASSERT(isSelf(HostAndPort("LoCalHOST", serverGlobalParams.port), getGlobalServiceContext()));
+}
+
} // namespace
} // namespace repl
diff --git a/src/mongo/db/repl/repl_set_config_test.cpp b/src/mongo/db/repl/repl_set_config_test.cpp
index 7f3af1345bb..14295fc86fd 100644
--- a/src/mongo/db/repl/repl_set_config_test.cpp
+++ b/src/mongo/db/repl/repl_set_config_test.cpp
@@ -975,6 +975,25 @@ TEST(ReplSetConfig, ValidateFailsWithDuplicateMemberId) {
ASSERT_EQUALS(ErrorCodes::BadValue, status);
}
+TEST(ReplSetConfig, ValidateFailsWithDuplicateHostnameCaseInsensitive) {
+ ReplSetConfig config;
+ Status status = config.initialize(BSON("_id"
+ << "rs0"
+ << "version"
+ << 1
+ << "protocolVersion"
+ << 1
+ << "members"
+ << BSON_ARRAY(BSON("_id" << 0 << "host"
+ << "localhost:12345")
+ << BSON("_id" << 1 << "host"
+ << "LOCALHOST:12345"))));
+ ASSERT_OK(status);
+
+ status = config.validate();
+ ASSERT_EQUALS(ErrorCodes::BadValue, status);
+}
+
TEST(ReplSetConfig, ValidateFailsWithInvalidMember) {
ReplSetConfig config;
Status status = config.initialize(BSON("_id"
diff --git a/src/mongo/db/repl/repl_set_heartbeat_response_test.cpp b/src/mongo/db/repl/repl_set_heartbeat_response_test.cpp
index 72c7d31190a..cd9c331dcd3 100644
--- a/src/mongo/db/repl/repl_set_heartbeat_response_test.cpp
+++ b/src/mongo/db/repl/repl_set_heartbeat_response_test.cpp
@@ -118,7 +118,7 @@ TEST(ReplSetHeartbeatResponse, DefaultConstructThenSlowlyBuildToFullObj) {
ASSERT_EQUALS(Timestamp(0, 10), hbResponseObj["durableOpTime"]["ts"].timestamp());
ASSERT_EQUALS(config.toBSON().toString(), hbResponseObj["config"].Obj().toString());
ASSERT_EQUALS(2, hbResponseObj["state"].numberLong());
- ASSERT_EQUALS("syncTarget:27017", hbResponseObj["syncingTo"].String());
+ ASSERT_EQUALS_CI("syncTarget:27017", hbResponseObj["syncingTo"].String());
initializeResult =
hbResponseObjRoundTripChecker.initialize(hbResponseObj, 0, /*requireWallTime*/ true);
diff --git a/src/mongo/db/s/config/sharding_catalog_manager_add_shard_test.cpp b/src/mongo/db/s/config/sharding_catalog_manager_add_shard_test.cpp
index ce9460cf3e6..23a107a43b5 100644
--- a/src/mongo/db/s/config/sharding_catalog_manager_add_shard_test.cpp
+++ b/src/mongo/db/s/config/sharding_catalog_manager_add_shard_test.cpp
@@ -287,7 +287,7 @@ protected:
auto foundShard = assertGet(getShardDoc(operationContext(), expectedShard.getName()));
ASSERT_EQUALS(expectedShard.getName(), foundShard.getName());
- ASSERT_EQUALS(expectedShard.getHost(), foundShard.getHost());
+ ASSERT_EQUALS_CI(expectedShard.getHost(), foundShard.getHost());
ASSERT_EQUALS(expectedShard.getMaxSizeMB(), foundShard.getMaxSizeMB());
ASSERT_EQUALS(expectedShard.getDraining(), foundShard.getDraining());
ASSERT_EQUALS((int)expectedShard.getState(), (int)foundShard.getState());
@@ -332,7 +332,7 @@ protected:
auto logEntry = assertGet(ChangeLogType::fromBSON(logEntryBSON));
ASSERT_EQUALS(addedShard.getName(), logEntry.getDetails()["name"].String());
- ASSERT_EQUALS(addedShard.getHost(), logEntry.getDetails()["host"].String());
+ ASSERT_EQUALS_CI(addedShard.getHost(), logEntry.getDetails()["host"].String());
}
void forwardAddShardNetwork(Date_t when) {
diff --git a/src/mongo/db/s/config/sharding_catalog_manager_create_database_test.cpp b/src/mongo/db/s/config/sharding_catalog_manager_create_database_test.cpp
index 59fe4af9e98..895783e6254 100644
--- a/src/mongo/db/s/config/sharding_catalog_manager_create_database_test.cpp
+++ b/src/mongo/db/s/config/sharding_catalog_manager_create_database_test.cpp
@@ -106,7 +106,7 @@ TEST_F(CreateDatabaseTest, createDatabaseSuccess) {
// Return size information about first shard
onCommand([&](const RemoteCommandRequest& request) {
- ASSERT_EQUALS(s0.getHost(), request.target.toString());
+ ASSERT_EQUALS(HostAndPort(s0.getHost()), request.target);
ASSERT_EQUALS("admin", request.dbname);
std::string cmdName = request.cmdObj.firstElement().fieldName();
ASSERT_EQUALS("listDatabases", cmdName);
@@ -121,7 +121,7 @@ TEST_F(CreateDatabaseTest, createDatabaseSuccess) {
// Return size information about second shard
onCommand([&](const RemoteCommandRequest& request) {
- ASSERT_EQUALS(s1.getHost(), request.target.toString());
+ ASSERT_EQUALS(HostAndPort(s1.getHost()), request.target);
ASSERT_EQUALS("admin", request.dbname);
std::string cmdName = request.cmdObj.firstElement().fieldName();
ASSERT_EQUALS("listDatabases", cmdName);
@@ -136,7 +136,7 @@ TEST_F(CreateDatabaseTest, createDatabaseSuccess) {
// Return size information about third shard
onCommand([&](const RemoteCommandRequest& request) {
- ASSERT_EQUALS(s2.getHost(), request.target.toString());
+ ASSERT_EQUALS(HostAndPort(s2.getHost()), request.target);
ASSERT_EQUALS("admin", request.dbname);
std::string cmdName = request.cmdObj.firstElement().fieldName();
ASSERT_EQUALS("listDatabases", cmdName);
diff --git a/src/mongo/dbtests/replica_set_monitor_test.cpp b/src/mongo/dbtests/replica_set_monitor_test.cpp
index 89ae6bd1085..f168d7bbad9 100644
--- a/src/mongo/dbtests/replica_set_monitor_test.cpp
+++ b/src/mongo/dbtests/replica_set_monitor_test.cpp
@@ -29,9 +29,6 @@
#include "mongo/platform/basic.h"
-#include <set>
-#include <vector>
-
#include "mongo/base/init.h"
#include "mongo/client/connpool.h"
#include "mongo/client/dbclient_rs.h"
@@ -41,6 +38,11 @@
#include "mongo/dbtests/mock/mock_replica_set.h"
#include "mongo/unittest/unittest.h"
+#include <boost/algorithm/string/case_conv.hpp>
+
+#include <set>
+#include <vector>
+
namespace mongo {
namespace {
@@ -373,5 +375,27 @@ TEST_F(TwoNodeWithTags, SecDownRetryExpiredTimeout) {
monitor.reset();
}
+// Tests that hostnames with different capitalization are considered equal.
+TEST_F(TwoNodeWithTags, CaseInsensitive) {
+ const auto primaryOnly = mongo::ReadPreference::PrimaryOnly;
+ const auto secondaryOnly = mongo::ReadPreference::SecondaryOnly;
+
+ MockReplicaSet* replSet = getReplSet();
+
+ // Initialize with upcased primary hostname.
+ set<HostAndPort> seedList;
+ seedList.insert(HostAndPort(boost::algorithm::to_lower_copy(replSet->getPrimary())));
+ auto monitor = ReplicaSetMonitor::createIfNeeded(replSet->getSetName(), seedList);
+ monitor->runScanForMockReplicaSet();
+
+ auto selectMember = [&](mongo::ReadPreference pref) {
+ return monitor->getHostOrRefresh(ReadPreferenceSetting(pref, TagSet()), Milliseconds(1))
+ .get()
+ .toString();
+ };
+
+ ASSERT_EQ(selectMember(primaryOnly), replSet->getPrimary());
+ ASSERT_EQ(selectMember(secondaryOnly), replSet->getSecondaries()[0]);
+}
} // namespace
} // namespace mongo
diff --git a/src/mongo/unittest/unittest.h b/src/mongo/unittest/unittest.h
index d00338348a9..9eba2c2bc3c 100644
--- a/src/mongo/unittest/unittest.h
+++ b/src/mongo/unittest/unittest.h
@@ -113,6 +113,11 @@
#define ASSERT_APPROX_EQUAL(a, b, ABSOLUTE_ERR) ASSERT_LTE(std::abs((a) - (b)), ABSOLUTE_ERR)
/**
+ * ASCII case-insensitive comparison.
+ */
+#define ASSERT_EQUALS_CI(a, b) ASSERT_EQUALS(str::toLower(a), str::toLower(b))
+
+/**
* Assert a function call returns its input unchanged.
*/
#define ASSERT_IDENTITY(INPUT, FUNCTION) \
diff --git a/src/mongo/util/net/hostandport.cpp b/src/mongo/util/net/hostandport.cpp
index d81a81fab36..5ff178b9bff 100644
--- a/src/mongo/util/net/hostandport.cpp
+++ b/src/mongo/util/net/hostandport.cpp
@@ -58,7 +58,9 @@ HostAndPort::HostAndPort(StringData text) {
uassertStatusOK(initialize(text));
}
-HostAndPort::HostAndPort(const std::string& h, int p) : _host(h), _port(p) {}
+// Normalize hostname by lowercasing ASCII uppercase characters. Ignore non-ASCII characters.
+// Assume input is ASCII or UTF-8.
+HostAndPort::HostAndPort(const std::string& h, int p) : _host(str::toLower(h)), _port(p) {}
HostAndPort::HostAndPort(SockAddr addr) : _addr(std::move(addr)) {
uassertStatusOK(initialize(_addr->toString(true)));
@@ -207,7 +209,8 @@ Status HostAndPort::initialize(StringData s) {
} else {
port = -1;
}
- _host = hostPart.toString();
+
+ _host = str::toLower(hostPart);
_port = port;
return Status::OK();
}
diff --git a/src/mongo/util/net/hostandport.h b/src/mongo/util/net/hostandport.h
index 03e3ef2ee5e..08d44a95319 100644
--- a/src/mongo/util/net/hostandport.h
+++ b/src/mongo/util/net/hostandport.h
@@ -53,6 +53,8 @@ class StatusWith;
* Composed of some name component, followed optionally by a colon and a numeric port. The name
* might be an IPv4 or IPv6 address or a relative or fully qualified host name, or an absolute
* path to a unix socket.
+ *
+ * Hostnames are converted to lowercase.
*/
struct HostAndPort {
/**
diff --git a/src/mongo/util/net/hostandport_test.cpp b/src/mongo/util/net/hostandport_test.cpp
index 69163791b3a..a1149fcf967 100644
--- a/src/mongo/util/net/hostandport_test.cpp
+++ b/src/mongo/util/net/hostandport_test.cpp
@@ -39,6 +39,9 @@
namespace mongo {
namespace {
+// U+2603, Snowman, encoded as UTF-8.
+#define SNOWMAN "\xe2\x98\x83"
+
TEST(HostAndPort, BasicLessThanComparison) {
// Not less than self.
ASSERT_FALSE(HostAndPort("a", 1) < HostAndPort("a", 1));
@@ -50,6 +53,12 @@ TEST(HostAndPort, BasicLessThanComparison) {
// Then, order by port number.
ASSERT_LESS_THAN(HostAndPort("a", 1), HostAndPort("a", 2));
ASSERT_FALSE(HostAndPort("a", 2) < HostAndPort("a", 1));
+
+ // Case insensitive.
+ ASSERT_LESS_THAN(HostAndPort("A", 1), HostAndPort("a", 2));
+ ASSERT_LESS_THAN(HostAndPort("a", 1), HostAndPort("A", 2));
+ ASSERT_FALSE(HostAndPort("A", 2) < HostAndPort("a", 1));
+ ASSERT_FALSE(HostAndPort("a", 2) < HostAndPort("A", 1));
}
TEST(HostAndPort, BasicEquality) {
@@ -59,11 +68,23 @@ TEST(HostAndPort, BasicEquality) {
ASSERT_FALSE(HostAndPort("a", 1) != HostAndPort("a", 1));
ASSERT_NOT_EQUALS(HostAndPort("b", 1), HostAndPort("a", 1));
+ // Case insensitive.
+ ASSERT_EQUALS(HostAndPort("A", 1), HostAndPort("a", 1));
+ ASSERT_FALSE(HostAndPort("A", 1) != HostAndPort("a", 1));
+ ASSERT_NOT_EQUALS(HostAndPort("B", 1), HostAndPort("a", 1));
+
// Comparison on port field
ASSERT_FALSE(HostAndPort("a", 1) == HostAndPort("a", 2));
ASSERT_NOT_EQUALS(HostAndPort("a", 1), HostAndPort("a", 2));
}
+TEST(HostAndPort, CaseNormalization) {
+ ASSERT_EQUALS(HostAndPort(SNOWMAN "A", 1), HostAndPort(SNOWMAN "a", 1));
+ ASSERT_EQUALS(HostAndPort("A" SNOWMAN, 1), HostAndPort("a" SNOWMAN, 1));
+ ASSERT_NOT_EQUALS(HostAndPort(SNOWMAN "B", 1), HostAndPort(SNOWMAN "a", 1));
+ ASSERT_NOT_EQUALS(HostAndPort("B" SNOWMAN, 1), HostAndPort("a" SNOWMAN, 1));
+}
+
TEST(HostAndPort, ImplicitPortSelection) {
ASSERT_EQUALS(HostAndPort("a", -1), HostAndPort("a", int(ServerGlobalParams::DefaultDBPort)));
ASSERT_EQUALS(int(ServerGlobalParams::DefaultDBPort), HostAndPort("a", -1).port());
@@ -106,6 +127,9 @@ TEST(HostAndPort, StaticParseFunction) {
HostAndPort("abc.def", 3421));
ASSERT_EQUALS(unittest::assertGet(HostAndPort::parse("[243:1bc]:21")),
HostAndPort("243:1bc", 21));
+ ASSERT_EQUALS(unittest::assertGet(HostAndPort::parse("aBcD:21")), HostAndPort("abcd", 21));
+ ASSERT_EQUALS(unittest::assertGet(HostAndPort::parse("aB" SNOWMAN "cD:21")),
+ HostAndPort("ab" SNOWMAN "cd", 21));
}
TEST(HostAndPort, RoundTripAbility) {
diff --git a/src/mongo/util/str.h b/src/mongo/util/str.h
index bddfff282b2..853fee0c53c 100644
--- a/src/mongo/util/str.h
+++ b/src/mongo/util/str.h
@@ -302,7 +302,8 @@ inline std::string toLower(StringData input) {
for (std::string::size_type i = 0; i < sz; i++) {
char c = input[i];
- copy[i] = (char)tolower((int)c);
+ // See https://en.cppreference.com/w/cpp/string/byte/tolower
+ copy[i] = static_cast<char>(tolower(static_cast<unsigned char>(c)));
}
copy[sz] = 0;
return copy;