diff options
author | Sara Golemon <sara.golemon@mongodb.com> | 2017-08-17 10:38:37 -0400 |
---|---|---|
committer | Sara Golemon <sara.golemon@mongodb.com> | 2017-08-18 12:07:00 -0400 |
commit | 1ee537356bbd98a6c037e40b7d4f04283a11741d (patch) | |
tree | 9e911181ebe9667e60546f965c01fa8ab971765d | |
parent | 52007c747ca483bba871c5c71684b18717222019 (diff) | |
download | mongo-1ee537356bbd98a6c037e40b7d4f04283a11741d.tar.gz |
SERVER-30588 Bind all addresses returned by getaddroinfo()
-rw-r--r-- | jstests/noPassthrough/hostname_bind_ips.js | 2 | ||||
-rw-r--r-- | src/mongo/transport/transport_layer_asio.cpp | 75 | ||||
-rw-r--r-- | src/mongo/util/net/sockaddr.cpp | 119 | ||||
-rw-r--r-- | src/mongo/util/net/sockaddr.h | 30 |
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; |