summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSara Golemon <sara.golemon@mongodb.com>2017-08-17 10:38:37 -0400
committerSara Golemon <sara.golemon@mongodb.com>2017-08-18 12:07:00 -0400
commit1ee537356bbd98a6c037e40b7d4f04283a11741d (patch)
tree9e911181ebe9667e60546f965c01fa8ab971765d
parent52007c747ca483bba871c5c71684b18717222019 (diff)
downloadmongo-1ee537356bbd98a6c037e40b7d4f04283a11741d.tar.gz
SERVER-30588 Bind all addresses returned by getaddroinfo()
-rw-r--r--jstests/noPassthrough/hostname_bind_ips.js2
-rw-r--r--src/mongo/transport/transport_layer_asio.cpp75
-rw-r--r--src/mongo/util/net/sockaddr.cpp119
-rw-r--r--src/mongo/util/net/sockaddr.h30
4 files changed, 154 insertions, 72 deletions
diff --git a/jstests/noPassthrough/hostname_bind_ips.js b/jstests/noPassthrough/hostname_bind_ips.js
index 295d10db4ce..57ee1698ff5 100644
--- a/jstests/noPassthrough/hostname_bind_ips.js
+++ b/jstests/noPassthrough/hostname_bind_ips.js
@@ -1,7 +1,7 @@
(function() {
'use strict';
- var proc = MongoRunner.runMongod({bind_ip: "localhost,::1", "ipv6": "", waitForConnect: false});
+ var proc = MongoRunner.runMongod({bind_ip: "localhost", "ipv6": "", waitForConnect: false});
assert.neq(proc, null);
assert.soon(function() {
diff --git a/src/mongo/transport/transport_layer_asio.cpp b/src/mongo/transport/transport_layer_asio.cpp
index 15220f3abc4..9751c49eddb 100644
--- a/src/mongo/transport/transport_layer_asio.cpp
+++ b/src/mongo/transport/transport_layer_asio.cpp
@@ -165,52 +165,61 @@ Status TransportLayerASIO::setup() {
warning() << "Skipping empty bind address";
continue;
}
- SockAddr addr(StringData(ip),
- _listenerOptions.port,
- _listenerOptions.enableIPv6 ? AF_UNSPEC : AF_INET);
- asio::generic::stream_protocol::endpoint endpoint(addr.raw(), addr.addressSize);
+
+ const auto addrs = SockAddr::createAll(
+ ip, _listenerOptions.port, _listenerOptions.enableIPv6 ? AF_UNSPEC : AF_INET);
+ if (addrs.empty()) {
+ warning() << "Found no addresses for " << ip;
+ continue;
+ }
+
+ for (const auto& addr : addrs) {
+ asio::generic::stream_protocol::endpoint endpoint(addr.raw(), addr.addressSize);
#ifndef _WIN32
- if (addr.getType() == AF_UNIX) {
- if (::unlink(ip.c_str()) == -1 && errno != ENOENT) {
- error() << "Failed to unlink socket file " << ip << " "
- << errnoWithDescription(errno);
- fassertFailedNoTrace(40486);
+ if (addr.getType() == AF_UNIX) {
+ if (::unlink(ip.c_str()) == -1 && errno != ENOENT) {
+ error() << "Failed to unlink socket file " << ip << " "
+ << errnoWithDescription(errno);
+ fassertFailedNoTrace(40486);
+ }
}
- }
#endif
- if (addr.getType() == AF_INET6 && !_listenerOptions.enableIPv6) {
- error() << "Specified ipv6 bind address, but ipv6 is disabled";
- fassertFailedNoTrace(40488);
- }
+ if (addr.getType() == AF_INET6 && !_listenerOptions.enableIPv6) {
+ error() << "Specified ipv6 bind address, but ipv6 is disabled";
+ fassertFailedNoTrace(40488);
+ }
- GenericAcceptor acceptor(*_ioContext);
- acceptor.open(endpoint.protocol());
- acceptor.set_option(GenericAcceptor::reuse_address(true));
+ GenericAcceptor acceptor(*_ioContext);
+ acceptor.open(endpoint.protocol());
+ acceptor.set_option(GenericAcceptor::reuse_address(true));
- acceptor.non_blocking(true, ec);
- if (ec) {
- return errorCodeToStatus(ec);
- }
+ acceptor.non_blocking(true, ec);
+ if (ec) {
+ return errorCodeToStatus(ec);
+ }
- acceptor.bind(endpoint, ec);
- if (ec) {
- return errorCodeToStatus(ec);
- }
+ acceptor.bind(endpoint, ec);
+ if (ec) {
+ return errorCodeToStatus(ec);
+ }
#ifndef _WIN32
- if (addr.getType() == AF_UNIX) {
- if (::chmod(ip.c_str(), serverGlobalParams.unixSocketPermissions) == -1) {
- error() << "Failed to chmod socket file " << ip << " "
- << errnoWithDescription(errno);
- fassertFailedNoTrace(40487);
+ if (addr.getType() == AF_UNIX) {
+ if (::chmod(ip.c_str(), serverGlobalParams.unixSocketPermissions) == -1) {
+ error() << "Failed to chmod socket file " << ip << " "
+ << errnoWithDescription(errno);
+ fassertFailedNoTrace(40487);
+ }
}
- }
#endif
- _acceptors.emplace_back(std::move(acceptor));
+ _acceptors.emplace_back(std::move(acceptor));
+ }
}
- invariant(!_acceptors.empty());
+ if (_acceptors.empty()) {
+ return Status(ErrorCodes::SocketException, "No available addresses/ports to bind to");
+ }
#ifdef MONGO_CONFIG_SSL
const auto& sslParams = getSSLGlobalParams();
diff --git a/src/mongo/util/net/sockaddr.cpp b/src/mongo/util/net/sockaddr.cpp
index 0b3ad9c0ec0..2f021d863cc 100644
--- a/src/mongo/util/net/sockaddr.cpp
+++ b/src/mongo/util/net/sockaddr.cpp
@@ -53,6 +53,37 @@
namespace mongo {
namespace {
constexpr int SOCK_FAMILY_UNKNOWN_ERROR = 13078;
+
+using AddrInfo = std::unique_ptr<addrinfo, decltype(&freeaddrinfo)>;
+
+std::pair<int, AddrInfo> resolveAddrInfo(const std::string& hostOrIp,
+ int port,
+ sa_family_t familyHint) {
+ addrinfo hints;
+ memset(&hints, 0, sizeof(addrinfo));
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags |= AI_NUMERICHOST; // first pass tries w/o DNS lookup
+ hints.ai_family = familyHint;
+ addrinfo* addrs = nullptr;
+
+ ItoA portStr(port);
+ int ret = getaddrinfo(hostOrIp.c_str(), StringData(portStr).rawData(), &hints, &addrs);
+
+// old C compilers on IPv6-capable hosts return EAI_NODATA error
+#ifdef EAI_NODATA
+ int nodata = (ret == EAI_NODATA);
+#else
+ int nodata = false;
+#endif
+ if ((ret == EAI_NONAME) || nodata) {
+ // iporhost isn't an IP address, allow DNS lookup
+ hints.ai_flags &= ~AI_NUMERICHOST;
+ ret = getaddrinfo(hostOrIp.c_str(), StringData(portStr).rawData(), &hints, &addrs);
+ }
+
+ return {ret, AddrInfo(addrs, &freeaddrinfo)};
+}
+
} // namespace
std::string getAddrInfoStrError(int code) {
@@ -80,6 +111,18 @@ SockAddr::SockAddr(int sourcePort) {
_isValid = true;
}
+void SockAddr::initUnixDomainSocket(const std::string& path, int port) {
+#ifdef _WIN32
+ uassert(13080, "no unix socket support on windows", false);
+#endif
+ uassert(
+ 13079, "path to unix socket too long", path.size() < sizeof(as<sockaddr_un>().sun_path));
+ as<sockaddr_un>().sun_family = AF_UNIX;
+ strcpy(as<sockaddr_un>().sun_path, path.c_str());
+ addressSize = sizeof(sockaddr_un);
+ _isValid = true;
+}
+
SockAddr::SockAddr(StringData target, int port, sa_family_t familyHint)
: _hostOrIp(target.toString()) {
if (_hostOrIp == "localhost") {
@@ -87,48 +130,18 @@ SockAddr::SockAddr(StringData target, int port, sa_family_t familyHint)
}
if (mongoutils::str::contains(_hostOrIp, '/')) {
-#ifdef _WIN32
- uassert(13080, "no unix socket support on windows", false);
-#endif
- uassert(13079,
- "path to unix socket too long",
- _hostOrIp.size() < sizeof(as<sockaddr_un>().sun_path));
- as<sockaddr_un>().sun_family = AF_UNIX;
- strcpy(as<sockaddr_un>().sun_path, _hostOrIp.c_str());
- addressSize = sizeof(sockaddr_un);
- _isValid = true;
+ initUnixDomainSocket(_hostOrIp, port);
return;
}
- addrinfo* addrs = NULL;
- addrinfo hints;
- memset(&hints, 0, sizeof(addrinfo));
- hints.ai_socktype = SOCK_STREAM;
- // hints.ai_flags = AI_ADDRCONFIG; // This is often recommended but don't do it.
- // SERVER-1579
- hints.ai_flags |= AI_NUMERICHOST; // first pass tries w/o DNS lookup
- hints.ai_family = familyHint;
-
- ItoA portStr(port);
- int ret = getaddrinfo(_hostOrIp.c_str(), StringData(portStr).rawData(), &hints, &addrs);
-
-// old C compilers on IPv6-capable hosts return EAI_NODATA error
-#ifdef EAI_NODATA
- int nodata = (ret == EAI_NODATA);
-#else
- int nodata = false;
-#endif
- if ((ret == EAI_NONAME || nodata)) {
- // iporhost isn't an IP address, allow DNS lookup
- hints.ai_flags &= ~AI_NUMERICHOST;
- ret = getaddrinfo(_hostOrIp.c_str(), StringData(portStr).rawData(), &hints, &addrs);
- }
+ auto addrErr = resolveAddrInfo(_hostOrIp, port, familyHint);
- if (ret) {
+ if (addrErr.first) {
// we were unsuccessful
if (_hostOrIp != "0.0.0.0") { // don't log if this as it is a
// CRT construction and log() may not work yet.
- log() << "getaddrinfo(\"" << _hostOrIp << "\") failed: " << getAddrInfoStrError(ret);
+ log() << "getaddrinfo(\"" << _hostOrIp
+ << "\") failed: " << getAddrInfoStrError(addrErr.first);
_isValid = false;
return;
}
@@ -136,14 +149,46 @@ SockAddr::SockAddr(StringData target, int port, sa_family_t familyHint)
return;
}
- // TODO: handle other addresses in linked list;
+ // This throws away all but the first address.
+ // Use SockAddr::createAll() to get all addresses.
+ const auto* addrs = addrErr.second.get();
fassert(16501, addrs->ai_addrlen <= sizeof(sa));
memcpy(&sa, addrs->ai_addr, addrs->ai_addrlen);
addressSize = addrs->ai_addrlen;
- freeaddrinfo(addrs);
_isValid = true;
}
+std::vector<SockAddr> SockAddr::createAll(StringData target, int port, sa_family_t familyHint) {
+ std::string hostOrIp = target.toString();
+ if (mongoutils::str::contains(hostOrIp, '/')) {
+ std::vector<SockAddr> ret = {SockAddr()};
+ ret[0].initUnixDomainSocket(hostOrIp, port);
+ // Currently, this is always valid since initUnixDomainSocket()
+ // will uassert() on failure. Be defensive against future changes.
+ return ret[0].isValid() ? ret : std::vector<SockAddr>();
+ }
+
+ auto addrErr = resolveAddrInfo(hostOrIp, port, familyHint);
+ if (addrErr.first) {
+ log() << "getaddrinfo(\"" << hostOrIp
+ << "\") failed: " << getAddrInfoStrError(addrErr.first);
+ return {};
+ }
+
+ std::vector<SockAddr> ret;
+ struct sockaddr_storage storage;
+ memset(&storage, 0, sizeof(storage));
+ for (const auto* addrs = addrErr.second.get(); addrs; addrs = addrs->ai_next) {
+ fassert(40594, addrs->ai_addrlen <= sizeof(struct sockaddr_storage));
+ // Make a temp copy in a local sockaddr_storage so that the
+ // SockAddr constructor below can copy the entire buffer
+ // without over-running addrinfo's storage
+ memcpy(&storage, addrs->ai_addr, addrs->ai_addrlen);
+ ret.emplace_back(storage, addrs->ai_addrlen);
+ }
+ return ret;
+}
+
SockAddr::SockAddr(struct sockaddr_storage& other, socklen_t size)
: addressSize(size), _hostOrIp(), sa(other), _isValid(true) {
_hostOrIp = toString(true);
diff --git a/src/mongo/util/net/sockaddr.h b/src/mongo/util/net/sockaddr.h
index 1475d98be1c..ba74655a570 100644
--- a/src/mongo/util/net/sockaddr.h
+++ b/src/mongo/util/net/sockaddr.h
@@ -29,6 +29,7 @@
#pragma once
#include <string>
+#include <vector>
#ifndef _WIN32
@@ -71,10 +72,35 @@ struct SockAddr {
explicit SockAddr(int sourcePort); /* listener side */
- explicit SockAddr(StringData ip, int port, sa_family_t familyHint);
+ /**
+ * Initialize a SockAddr for a given IP or Hostname.
+ *
+ * If target fails to resolve/parse, SockAddr.isValid() may return false,
+ * or the resulting SockAddr may be equivalent to SockAddr(port).
+ *
+ * If target is a unix domain socket, a uassert() exception will be thrown
+ * on windows or if addr exceeds maximum path length.
+ *
+ * If target resolves to more than one address, only the first address
+ * will be used. Others will be discarded.
+ * SockAddr::createAll() is recommended for capturing all addresses.
+ */
+ explicit SockAddr(StringData target, int port, sa_family_t familyHint);
explicit SockAddr(struct sockaddr_storage& other, socklen_t size);
+ /**
+ * Resolve an ip or hostname to a vector of SockAddr objects.
+ *
+ * Works similar to SockAddr(StringData, int, sa_family_t) above,
+ * however all addresses returned from ::getaddrinfo() are used,
+ * it never falls-open to SockAddr(port),
+ * and isInvalid() SockAddrs are excluded.
+ *
+ * May return an empty vector.
+ */
+ static std::vector<SockAddr> createAll(StringData target, int port, sa_family_t familyHint);
+
template <typename T>
T& as() {
return *(T*)(&sa);
@@ -123,6 +149,8 @@ struct SockAddr {
socklen_t addressSize;
private:
+ void initUnixDomainSocket(const std::string& path, int port);
+
std::string _hostOrIp;
struct sockaddr_storage sa;
bool _isValid;