From: Dave Watson Date: Fri, 12 Sep 2014 18:30:59 +0000 (-0700) Subject: move socketaddress to folly X-Git-Tag: v0.22.0~348 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=45c8738bf65a76678248189fc8f1f2eb2f35f628;p=folly.git move socketaddress to folly Summary: Goes with IPAddress and MACAddress already in folly. * change namespace, add typedef in thrift. Had to update forward decls to folly namespace. * change exception types to std:: types instead of TTransportException. I fbgs'd for everywhere I could find trying to catch these and updated the type, there looked like there were only ~9 files or so. Test Plan: contbuild. @override-unit-failures Tests already passed in last run Reviewed By: simpkins@fb.com Subscribers: doug, ps, alandau, everstore-dev@, njormrod, fugalh, alikhtarov, bmatheny, jsedgwick FB internal diff: D1556916 --- diff --git a/folly/Makefile.am b/folly/Makefile.am index b105bea0..e0363e4a 100644 --- a/folly/Makefile.am +++ b/folly/Makefile.am @@ -148,6 +148,7 @@ nobase_follyinclude_HEADERS = \ ScopeGuard.h \ SmallLocks.h \ small_vector.h \ + SocketAddress.h \ sorted_vector_types.h \ SpookyHashV1.h \ SpookyHashV2.h \ @@ -246,6 +247,7 @@ libfolly_la_SOURCES = \ MemoryMapping.cpp \ Random.cpp \ SafeAssert.cpp \ + SocketAddress.cpp \ SpookyHashV1.cpp \ SpookyHashV2.cpp \ stats/Instantiations.cpp \ diff --git a/folly/SocketAddress.cpp b/folly/SocketAddress.cpp new file mode 100644 index 00000000..b8ce1ea8 --- /dev/null +++ b/folly/SocketAddress.cpp @@ -0,0 +1,723 @@ +/* + * Copyright 2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __STDC_FORMAT_MACROS + #define __STDC_FORMAT_MACROS +#endif + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +/** + * A structure to free a struct addrinfo when it goes out of scope. + */ +struct ScopedAddrInfo { + explicit ScopedAddrInfo(struct addrinfo* info) : info(info) {} + ~ScopedAddrInfo() { + freeaddrinfo(info); + } + + struct addrinfo* info; +}; + +/** + * A simple data structure for parsing a host-and-port string. + * + * Accepts a string of the form ":" or just "", + * and contains two string pointers to the host and the port portion of the + * string. + * + * The HostAndPort may contain pointers into the original string. It is + * responsible for the user to ensure that the input string is valid for the + * lifetime of the HostAndPort structure. + */ +struct HostAndPort { + HostAndPort(const char* str, bool hostRequired) + : host(nullptr), + port(nullptr), + allocated(nullptr) { + + // Look for the last colon + const char* colon = strrchr(str, ':'); + if (colon == nullptr) { + // No colon, just a port number. + if (hostRequired) { + throw std::invalid_argument( + "expected a host and port string of the " + "form \":\""); + } + port = str; + return; + } + + // We have to make a copy of the string so we can modify it + // and change the colon to a NUL terminator. + allocated = strdup(str); + if (!allocated) { + throw std::bad_alloc(); + } + + char *allocatedColon = allocated + (colon - str); + *allocatedColon = '\0'; + host = allocated; + port = allocatedColon + 1; + // bracketed IPv6 address, remove the brackets + // allocatedColon[-1] is fine, as allocatedColon >= host and + // *allocatedColon != *host therefore allocatedColon > host + if (*host == '[' && allocatedColon[-1] == ']') { + allocatedColon[-1] = '\0'; + ++host; + } + } + + ~HostAndPort() { + free(allocated); + } + + const char* host; + const char* port; + char* allocated; +}; + +} // unnamed namespace + +namespace folly { + +bool SocketAddress::isPrivateAddress() const { + auto family = getFamily(); + if (family == AF_INET || family == AF_INET6) { + return storage_.addr.isPrivate() || + (storage_.addr.isV6() && storage_.addr.asV6().isLinkLocal()); + } else if (family == AF_UNIX) { + // Unix addresses are always local to a host. Return true, + // since this conforms to the semantics of returning true for IP loopback + // addresses. + return true; + } + return false; +} + +bool SocketAddress::isLoopbackAddress() const { + auto family = getFamily(); + if (family == AF_INET || family == AF_INET6) { + return storage_.addr.isLoopback(); + } else if (family == AF_UNIX) { + // Return true for UNIX addresses, since they are always local to a host. + return true; + } + return false; +} + +void SocketAddress::setFromHostPort(const char* host, uint16_t port) { + ScopedAddrInfo results(getAddrInfo(host, port, 0)); + setFromAddrInfo(results.info); +} + +void SocketAddress::setFromIpPort(const char* ip, uint16_t port) { + ScopedAddrInfo results(getAddrInfo(ip, port, AI_NUMERICHOST)); + setFromAddrInfo(results.info); +} + +void SocketAddress::setFromLocalPort(uint16_t port) { + ScopedAddrInfo results(getAddrInfo(nullptr, port, AI_ADDRCONFIG)); + setFromLocalAddr(results.info); +} + +void SocketAddress::setFromLocalPort(const char* port) { + ScopedAddrInfo results(getAddrInfo(nullptr, port, AI_ADDRCONFIG)); + setFromLocalAddr(results.info); +} + +void SocketAddress::setFromLocalIpPort(const char* addressAndPort) { + HostAndPort hp(addressAndPort, false); + ScopedAddrInfo results(getAddrInfo(hp.host, hp.port, + AI_NUMERICHOST | AI_ADDRCONFIG)); + setFromLocalAddr(results.info); +} + +void SocketAddress::setFromIpPort(const char* addressAndPort) { + HostAndPort hp(addressAndPort, true); + ScopedAddrInfo results(getAddrInfo(hp.host, hp.port, AI_NUMERICHOST)); + setFromAddrInfo(results.info); +} + +void SocketAddress::setFromHostPort(const char* hostAndPort) { + HostAndPort hp(hostAndPort, true); + ScopedAddrInfo results(getAddrInfo(hp.host, hp.port, 0)); + setFromAddrInfo(results.info); +} + +void SocketAddress::setFromPath(const char* path, size_t len) { + if (getFamily() != AF_UNIX) { + storage_.un.init(); + external_ = true; + } + + storage_.un.len = offsetof(struct sockaddr_un, sun_path) + len; + if (len > sizeof(storage_.un.addr->sun_path)) { + throw std::invalid_argument( + "socket path too large to fit into sockaddr_un"); + } else if (len == sizeof(storage_.un.addr->sun_path)) { + // Note that there will be no terminating NUL in this case. + // We allow this since getsockname() and getpeername() may return + // Unix socket addresses with paths that fit exactly in sun_path with no + // terminating NUL. + memcpy(storage_.un.addr->sun_path, path, len); + } else { + memcpy(storage_.un.addr->sun_path, path, len + 1); + } +} + +void SocketAddress::setFromPeerAddress(int socket) { + setFromSocket(socket, getpeername); +} + +void SocketAddress::setFromLocalAddress(int socket) { + setFromSocket(socket, getsockname); +} + +void SocketAddress::setFromSockaddr(const struct sockaddr* address) { + if (address->sa_family == AF_INET) { + storage_.addr = folly::IPAddress(address); + port_ = ntohs(((sockaddr_in*)address)->sin_port); + } else if (address->sa_family == AF_INET6) { + storage_.addr = folly::IPAddress(address); + port_ = ntohs(((sockaddr_in6*)address)->sin6_port); + } else if (address->sa_family == AF_UNIX) { + // We need an explicitly specified length for AF_UNIX addresses, + // to be able to distinguish anonymous addresses from addresses + // in Linux's abstract namespace. + throw std::invalid_argument( + "SocketAddress::setFromSockaddr(): the address " + "length must be explicitly specified when " + "setting AF_UNIX addresses"); + } else { + throw std::invalid_argument( + "SocketAddress::setFromSockaddr() called " + "with unsupported address type"); + } + external_ = false; +} + +void SocketAddress::setFromSockaddr(const struct sockaddr* address, + socklen_t addrlen) { + // Check the length to make sure we can access address->sa_family + if (addrlen < (offsetof(struct sockaddr, sa_family) + + sizeof(address->sa_family))) { + throw std::invalid_argument( + "SocketAddress::setFromSockaddr() called " + "with length too short for a sockaddr"); + } + + if (address->sa_family == AF_INET) { + if (addrlen < sizeof(struct sockaddr_in)) { + throw std::invalid_argument( + "SocketAddress::setFromSockaddr() called " + "with length too short for a sockaddr_in"); + } + setFromSockaddr(reinterpret_cast(address)); + } else if (address->sa_family == AF_INET6) { + if (addrlen < sizeof(struct sockaddr_in6)) { + throw std::invalid_argument( + "SocketAddress::setFromSockaddr() called " + "with length too short for a sockaddr_in6"); + } + setFromSockaddr(reinterpret_cast(address)); + } else if (address->sa_family == AF_UNIX) { + setFromSockaddr(reinterpret_cast(address), + addrlen); + } else { + throw std::invalid_argument( + "SocketAddress::setFromSockaddr() called " + "with unsupported address type"); + } +} + +void SocketAddress::setFromSockaddr(const struct sockaddr_in* address) { + assert(address->sin_family == AF_INET); + setFromSockaddr((sockaddr*)address); +} + +void SocketAddress::setFromSockaddr(const struct sockaddr_in6* address) { + assert(address->sin6_family == AF_INET6); + setFromSockaddr((sockaddr*)address); +} + +void SocketAddress::setFromSockaddr(const struct sockaddr_un* address, + socklen_t addrlen) { + assert(address->sun_family == AF_UNIX); + if (addrlen > sizeof(struct sockaddr_un)) { + throw std::invalid_argument( + "SocketAddress::setFromSockaddr() called " + "with length too long for a sockaddr_un"); + } + + prepFamilyChange(AF_UNIX); + memcpy(storage_.un.addr, address, addrlen); + updateUnixAddressLength(addrlen); + + // Fill the rest with 0s, just for safety + if (addrlen < sizeof(struct sockaddr_un)) { + char *p = reinterpret_cast(storage_.un.addr); + memset(p + addrlen, 0, sizeof(struct sockaddr_un) - addrlen); + } +} + +const folly::IPAddress& SocketAddress::getIPAddress() const { + auto family = getFamily(); + if (family != AF_INET && family != AF_INET6) { + throw std::invalid_argument("getIPAddress called on a non-ip address"); + } + return storage_.addr; +} + +socklen_t SocketAddress::getActualSize() const { + switch (getFamily()) { + case AF_UNSPEC: + case AF_INET: + return sizeof(struct sockaddr_in); + case AF_INET6: + return sizeof(struct sockaddr_in6); + case AF_UNIX: + return storage_.un.len; + default: + throw std::invalid_argument( + "SocketAddress::getActualSize() called " + "with unrecognized address family"); + } +} + +std::string SocketAddress::getFullyQualified() const { + auto family = getFamily(); + if (family != AF_INET && family != AF_INET6) { + throw std::invalid_argument("Can't get address str for non ip address"); + } + return storage_.addr.toFullyQualified(); +} + +std::string SocketAddress::getAddressStr() const { + char buf[INET6_ADDRSTRLEN]; + getAddressStr(buf, sizeof(buf)); + return buf; +} + +void SocketAddress::getAddressStr(char* buf, size_t buflen) const { + auto family = getFamily(); + if (family != AF_INET && family != AF_INET6) { + throw std::invalid_argument("Can't get address str for non ip address"); + } + std::string ret = storage_.addr.str(); + size_t len = std::min(buflen, ret.size()); + memcpy(buf, ret.data(), len); + buf[len] = '\0'; +} + +uint16_t SocketAddress::getPort() const { + switch (getFamily()) { + case AF_INET: + case AF_INET6: + return port_; + default: + throw std::invalid_argument( + "SocketAddress::getPort() called on non-IP " + "address"); + } +} + +void SocketAddress::setPort(uint16_t port) { + switch (getFamily()) { + case AF_INET: + case AF_INET6: + port_ = port; + return; + default: + throw std::invalid_argument( + "SocketAddress::setPort() called on non-IP " + "address"); + } +} + +void SocketAddress::convertToIPv4() { + if (!tryConvertToIPv4()) { + throw std::invalid_argument( + "convertToIPv4() called on an addresse that is " + "not an IPv4-mapped address"); + } +} + +bool SocketAddress::tryConvertToIPv4() { + if (!isIPv4Mapped()) { + return false; + } + + storage_.addr = folly::IPAddress::createIPv4(storage_.addr); + return true; +} + +bool SocketAddress::mapToIPv6() { + if (getFamily() != AF_INET) { + return false; + } + + storage_.addr = folly::IPAddress::createIPv6(storage_.addr); + return true; +} + +std::string SocketAddress::getHostStr() const { + return getIpString(0); +} + +std::string SocketAddress::getPath() const { + if (getFamily() != AF_UNIX) { + throw std::invalid_argument( + "SocketAddress: attempting to get path " + "for a non-Unix address"); + } + + if (storage_.un.pathLength() == 0) { + // anonymous address + return std::string(); + } + if (storage_.un.addr->sun_path[0] == '\0') { + // abstract namespace + return std::string(storage_.un.addr->sun_path, storage_.un.pathLength()); + } + + return std::string(storage_.un.addr->sun_path, + strnlen(storage_.un.addr->sun_path, + storage_.un.pathLength())); +} + +std::string SocketAddress::describe() const { + switch (getFamily()) { + case AF_UNSPEC: + return ""; + case AF_INET: + { + char buf[NI_MAXHOST + 16]; + getAddressStr(buf, sizeof(buf)); + size_t iplen = strlen(buf); + snprintf(buf + iplen, sizeof(buf) - iplen, ":%" PRIu16, getPort()); + return buf; + } + case AF_INET6: + { + char buf[NI_MAXHOST + 18]; + buf[0] = '['; + getAddressStr(buf + 1, sizeof(buf) - 1); + size_t iplen = strlen(buf); + snprintf(buf + iplen, sizeof(buf) - iplen, "]:%" PRIu16, getPort()); + return buf; + } + case AF_UNIX: + { + if (storage_.un.pathLength() == 0) { + return ""; + } + + if (storage_.un.addr->sun_path[0] == '\0') { + // Linux supports an abstract namespace for unix socket addresses + return ""; + } + + return std::string(storage_.un.addr->sun_path, + strnlen(storage_.un.addr->sun_path, + storage_.un.pathLength())); + } + default: + { + char buf[64]; + snprintf(buf, sizeof(buf), "", + getFamily()); + return buf; + } + } +} + +bool SocketAddress::operator==(const SocketAddress& other) const { + if (other.getFamily() != getFamily()) { + return false; + } + + switch (getFamily()) { + case AF_INET: + case AF_INET6: + return (other.storage_.addr == storage_.addr) && + (other.port_ == port_); + case AF_UNIX: + { + // anonymous addresses are never equal to any other addresses + if (storage_.un.pathLength() == 0 || + other.storage_.un.pathLength() == 0) { + return false; + } + + if (storage_.un.len != other.storage_.un.len) { + return false; + } + int cmp = memcmp(storage_.un.addr->sun_path, + other.storage_.un.addr->sun_path, + storage_.un.pathLength()); + return cmp == 0; + } + default: + throw std::invalid_argument( + "SocketAddress: unsupported address family " + "for comparison"); + } +} + +bool SocketAddress::prefixMatch(const SocketAddress& other, + unsigned prefixLength) const { + if (other.getFamily() != getFamily()) { + return false; + } + int mask_length = 128; + switch (getFamily()) { + case AF_INET: + mask_length = 32; + // fallthrough + case AF_INET6: + { + auto prefix = folly::IPAddress::longestCommonPrefix( + {storage_.addr, mask_length}, + {other.storage_.addr, mask_length}); + return prefix.second >= prefixLength; + } + default: + return false; + } +} + + +size_t SocketAddress::hash() const { + size_t seed = folly::hash::twang_mix64(getFamily()); + + switch (getFamily()) { + case AF_INET: + case AF_INET6: { + boost::hash_combine(seed, port_); + boost::hash_combine(seed, storage_.addr.hash()); + break; + } + case AF_UNIX: + { + enum { kUnixPathMax = sizeof(storage_.un.addr->sun_path) }; + const char *path = storage_.un.addr->sun_path; + size_t pathLength = storage_.un.pathLength(); + // TODO: this probably could be made more efficient + for (unsigned int n = 0; n < pathLength; ++n) { + boost::hash_combine(seed, folly::hash::twang_mix64(path[n])); + } + break; + } + case AF_UNSPEC: + default: + throw std::invalid_argument( + "SocketAddress: unsupported address family " + "for hashing"); + } + + return seed; +} + +struct addrinfo* SocketAddress::getAddrInfo(const char* host, + uint16_t port, + int flags) { + // getaddrinfo() requires the port number as a string + char portString[sizeof("65535")]; + snprintf(portString, sizeof(portString), "%" PRIu16, port); + + return getAddrInfo(host, portString, flags); +} + +struct addrinfo* SocketAddress::getAddrInfo(const char* host, + const char* port, + int flags) { + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV | flags; + + struct addrinfo *results; + int error = getaddrinfo(host, port, &hints, &results); + if (error != 0) { + auto os = folly::to( + "Failed to resolve address for \"", host, "\": ", + gai_strerror(error), " (error=", error, ")"); + throw std::system_error(error, std::generic_category(), os); + } + + return results; +} + +void SocketAddress::setFromAddrInfo(const struct addrinfo* info) { + setFromSockaddr(info->ai_addr, info->ai_addrlen); +} + +void SocketAddress::setFromLocalAddr(const struct addrinfo* info) { + // If an IPv6 address is present, prefer to use it, since IPv4 addresses + // can be mapped into IPv6 space. + for (const struct addrinfo* ai = info; ai != nullptr; ai = ai->ai_next) { + if (ai->ai_family == AF_INET6) { + setFromSockaddr(ai->ai_addr, ai->ai_addrlen); + return; + } + } + + // Otherwise, just use the first address in the list. + setFromSockaddr(info->ai_addr, info->ai_addrlen); +} + +void SocketAddress::setFromSocket(int socket, + int (*fn)(int, sockaddr*, socklen_t*)) { + // If this was previously an AF_UNIX socket, free the external buffer. + // TODO: It would be smarter to just remember the external buffer, and then + // re-use it or free it depending on if the new address is also a unix + // socket. + if (getFamily() == AF_UNIX) { + storage_.un.free(); + external_ = false; + } + + // Try to put the address into a local storage buffer. + sockaddr_storage tmp_sock; + socklen_t addrLen = sizeof(tmp_sock); + if (fn(socket, (sockaddr*)&tmp_sock, &addrLen) != 0) { + folly::throwSystemError("setFromSocket() failed"); + } + + setFromSockaddr((sockaddr*)&tmp_sock, addrLen); +} + +std::string SocketAddress::getIpString(int flags) const { + char addrString[NI_MAXHOST]; + getIpString(addrString, sizeof(addrString), flags); + return std::string(addrString); +} + +void SocketAddress::getIpString(char *buf, size_t buflen, int flags) const { + auto family = getFamily(); + if (family != AF_INET && + family != AF_INET6) { + throw std::invalid_argument( + "SocketAddress: attempting to get IP address " + "for a non-IP address"); + } + + sockaddr_storage tmp_sock; + storage_.addr.toSockaddrStorage(&tmp_sock, port_); + int rc = getnameinfo((sockaddr*)&tmp_sock, sizeof(sockaddr_storage), + buf, buflen, nullptr, 0, flags); + if (rc != 0) { + auto os = folly::to( + "getnameinfo() failed in getIpString() error = ", + gai_strerror(rc)); + throw std::system_error(rc, std::generic_category(), os); + } +} + +void SocketAddress::updateUnixAddressLength(socklen_t addrlen) { + if (addrlen < offsetof(struct sockaddr_un, sun_path)) { + throw std::invalid_argument( + "SocketAddress: attempted to set a Unix socket " + "with a length too short for a sockaddr_un"); + } + + storage_.un.len = addrlen; + if (storage_.un.pathLength() == 0) { + // anonymous address + return; + } + + if (storage_.un.addr->sun_path[0] == '\0') { + // abstract namespace. honor the specified length + } else { + // Call strnlen(), just in case the length was overspecified. + socklen_t maxLength = addrlen - offsetof(struct sockaddr_un, sun_path); + size_t pathLength = strnlen(storage_.un.addr->sun_path, maxLength); + storage_.un.len = offsetof(struct sockaddr_un, sun_path) + pathLength; + } +} + +bool SocketAddress::operator<(const SocketAddress& other) const { + if (getFamily() != other.getFamily()) { + return getFamily() < other.getFamily(); + } + + switch (getFamily()) { + case AF_INET: + case AF_INET6: { + if (port_ != other.port_) { + return port_ < other.port_; + } + + return + storage_.addr < other.storage_.addr; + } + case AF_UNIX: { + // Anonymous addresses can't be compared to anything else. + // Return that they are never less than anything. + // + // Note that this still meets the requirements for a strict weak + // ordering, so we can use this operator<() with standard C++ containers. + size_t thisPathLength = storage_.un.pathLength(); + if (thisPathLength == 0) { + return false; + } + size_t otherPathLength = other.storage_.un.pathLength(); + if (otherPathLength == 0) { + return true; + } + + // Compare based on path length first, for efficiency + if (thisPathLength != otherPathLength) { + return thisPathLength < otherPathLength; + } + int cmp = memcmp(storage_.un.addr->sun_path, + other.storage_.un.addr->sun_path, + thisPathLength); + return cmp < 0; + } + case AF_UNSPEC: + default: + throw std::invalid_argument( + "SocketAddress: unsupported address family for comparing"); + } +} + +size_t hash_value(const SocketAddress& address) { + return address.hash(); +} + +std::ostream& operator<<(std::ostream& os, const SocketAddress& addr) { + os << addr.describe(); + return os; +} + +} // folly diff --git a/folly/SocketAddress.h b/folly/SocketAddress.h new file mode 100644 index 00000000..ac1771c4 --- /dev/null +++ b/folly/SocketAddress.h @@ -0,0 +1,602 @@ +/* + * Copyright 2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace folly { + +class SocketAddress { + public: + SocketAddress() { + storage_.addr = folly::IPAddress(); + } + + /** + * Construct a SocketAddress from a hostname and port. + * + * Note: If the host parameter is not a numeric IP address, hostname + * resolution will be performed, which can be quite slow. + * + * Raises std::system_error on error. + * + * @param host The IP address (or hostname, if allowNameLookup is true) + * @param port The port (in host byte order) + * @pram allowNameLookup If true, attempt to perform hostname lookup + * if the hostname does not appear to be a numeric IP address. + * This is potentially a very slow operation, so is disabled by + * default. + */ + SocketAddress(const char* host, uint16_t port, + bool allowNameLookup = false) { + // Initialize the address family first, + // since setFromHostPort() and setFromIpPort() will check it. + + if (allowNameLookup) { + setFromHostPort(host, port); + } else { + setFromIpPort(host, port); + } + } + + SocketAddress(const std::string& host, uint16_t port, + bool allowNameLookup = false) { + // Initialize the address family first, + // since setFromHostPort() and setFromIpPort() will check it. + + if (allowNameLookup) { + setFromHostPort(host.c_str(), port); + } else { + setFromIpPort(host.c_str(), port); + } + } + + SocketAddress(const SocketAddress& addr) { + storage_ = addr.storage_; + port_ = addr.port_; + if (addr.getFamily() == AF_UNIX) { + storage_.un.init(addr.storage_.un); + } + external_ = addr.external_; + } + + SocketAddress& operator=(const SocketAddress& addr) { + if (getFamily() != AF_UNIX) { + if (addr.getFamily() != AF_UNIX) { + storage_ = addr.storage_; + } else { + storage_ = addr.storage_; + storage_.un.init(addr.storage_.un); + } + } else { + if (addr.getFamily() == AF_UNIX) { + storage_.un.copy(addr.storage_.un); + } else { + storage_.un.free(); + storage_ = addr.storage_; + } + } + port_ = addr.port_; + external_ = addr.external_; + return *this; + } + + SocketAddress(SocketAddress&& addr) { + storage_ = addr.storage_; + port_ = addr.port_; + external_ = addr.external_; + addr.external_ = false; + } + + SocketAddress& operator=(SocketAddress&& addr) { + std::swap(storage_, addr.storage_); + std::swap(port_, addr.port_); + std::swap(external_, addr.external_); + return *this; + } + + ~SocketAddress() { + if (getFamily() == AF_UNIX) { + storage_.un.free(); + } + } + + bool isInitialized() const { + return (getFamily() != AF_UNSPEC); + } + + /** + * Return whether this address is within private network. + * + * According to RFC1918, the 10/8 prefix, 172.16/12 prefix, and 192.168/16 + * prefix are reserved for private networks. + * fc00::/7 is the IPv6 version, defined in RFC4139. IPv6 link-local + * addresses (fe80::/10) are also considered private addresses. + * + * The loopback addresses 127/8 and ::1 are also regarded as private networks + * for the purpose of this function. + * + * Returns true if this is a private network address, and false otherwise. + */ + bool isPrivateAddress() const; + + /** + * Return whether this address is a loopback address. + */ + bool isLoopbackAddress() const; + + void reset() { + prepFamilyChange(AF_UNSPEC); + } + + /** + * Initialize this SocketAddress from a hostname and port. + * + * Note: If the host parameter is not a numeric IP address, hostname + * resolution will be performed, which can be quite slow. + * + * If the hostname resolves to multiple addresses, only the first will be + * returned. + * + * Raises std::system_error on error. + * + * @param host The hostname or IP address + * @param port The port (in host byte order) + */ + void setFromHostPort(const char* host, uint16_t port); + + void setFromHostPort(const std::string& host, uint16_t port) { + setFromHostPort(host.c_str(), port); + } + + /** + * Initialize this SocketAddress from an IP address and port. + * + * This is similar to setFromHostPort(), but only accepts numeric IP + * addresses. If the IP string does not look like an IP address, it throws a + * std::invalid_argument rather than trying to perform a hostname resolution. + * + * Raises std::system_error on error. + * + * @param ip The IP address, as a human-readable string. + * @param port The port (in host byte order) + */ + void setFromIpPort(const char* ip, uint16_t port); + + void setFromIpPort(const std::string& ip, uint16_t port) { + setFromIpPort(ip.c_str(), port); + } + + /** + * Initialize this SocketAddress from a local port number. + * + * This is intended to be used by server code to determine the address to + * listen on. + * + * If the current machine has any IPv6 addresses configured, an IPv6 address + * will be returned (since connections from IPv4 clients can be mapped to the + * IPv6 address). If the machine does not have any IPv6 addresses, an IPv4 + * address will be returned. + */ + void setFromLocalPort(uint16_t port); + + /** + * Initialize this SocketAddress from a local port number. + * + * This version of setFromLocalPort() accepts the port as a string. A + * std::invalid_argument will be raised if the string does not refer to a port + * number. Non-numeric service port names are not accepted. + */ + void setFromLocalPort(const char* port); + void setFromLocalPort(const std::string& port) { + return setFromLocalPort(port.c_str()); + } + + /** + * Initialize this SocketAddress from a local port number and optional IP + * address. + * + * The addressAndPort string may be specified either as ":", or + * just as "". If the IP is not specified, the address will be + * initialized to 0, so that a server socket bound to this address will + * accept connections on all local IP addresses. + * + * Both the IP address and port number must be numeric. DNS host names and + * non-numeric service port names are not accepted. + */ + void setFromLocalIpPort(const char* addressAndPort); + void setFromLocalIpPort(const std::string& addressAndPort) { + return setFromLocalIpPort(addressAndPort.c_str()); + } + + /** + * Initialize this SocketAddress from an IP address and port number. + * + * The addressAndPort string must be of the form ":". E.g., + * "10.0.0.1:1234". + * + * Both the IP address and port number must be numeric. DNS host names and + * non-numeric service port names are not accepted. + */ + void setFromIpPort(const char* addressAndPort); + void setFromIpPort(const std::string& addressAndPort) { + return setFromIpPort(addressAndPort.c_str()); + } + + /** + * Initialize this SocketAddress from a host name and port number. + * + * The addressAndPort string must be of the form ":". E.g., + * "www.facebook.com:443". + * + * If the host name is not a numeric IP address, a DNS lookup will be + * performed. Beware that the DNS lookup may be very slow. The port number + * must be numeric; non-numeric service port names are not accepted. + */ + void setFromHostPort(const char* hostAndPort); + void setFromHostPort(const std::string& hostAndPort) { + return setFromHostPort(hostAndPort.c_str()); + } + + /** + * Initialize this SocketAddress from a local unix path. + * + * Raises std::invalid_argument on error. + */ + void setFromPath(const char* path) { + setFromPath(path, strlen(path)); + } + + void setFromPath(const std::string& path) { + setFromPath(path.data(), path.length()); + } + + void setFromPath(const char* path, size_t length); + + /** + * Initialize this SocketAddress from a socket's peer address. + * + * Raises std::system_error on error. + */ + void setFromPeerAddress(int socket); + + /** + * Initialize this SocketAddress from a socket's local address. + * + * Raises std::system_error on error. + */ + void setFromLocalAddress(int socket); + + /** + * Initialize this TSocketAddress from a struct sockaddr. + * + * Raises std::system_error on error. + * + * This method is not supported for AF_UNIX addresses. For unix addresses, + * the address length must be explicitly specified. + * + * @param address A struct sockaddr. The size of the address is implied + * from address->sa_family. + */ + void setFromSockaddr(const struct sockaddr* address); + + /** + * Initialize this SocketAddress from a struct sockaddr. + * + * Raises std::system_error on error. + * + * @param address A struct sockaddr. + * @param addrlen The length of address data available. This must be long + * enough for the full address type required by + * address->sa_family. + */ + void setFromSockaddr(const struct sockaddr* address, + socklen_t addrlen); + + /** + * Initialize this SocketAddress from a struct sockaddr_in. + */ + void setFromSockaddr(const struct sockaddr_in* address); + + /** + * Initialize this SocketAddress from a struct sockaddr_in6. + */ + void setFromSockaddr(const struct sockaddr_in6* address); + + /** + * Initialize this SocketAddress from a struct sockaddr_un. + * + * Note that the addrlen parameter is necessary to properly detect anonymous + * addresses, which have 0 valid path bytes, and may not even have a NUL + * character at the start of the path. + * + * @param address A struct sockaddr_un. + * @param addrlen The length of address data. This should include all of + * the valid bytes of sun_path, not including any NUL + * terminator. + */ + void setFromSockaddr(const struct sockaddr_un* address, + socklen_t addrlen); + + + /** + * Fill in a given sockaddr_storage with the ip or unix address. + * + * Returns the actual size of the storage used. + */ + socklen_t getAddress(sockaddr_storage* addr) const { + if (getFamily() != AF_UNIX) { + return storage_.addr.toSockaddrStorage(addr, htons(port_)); + } else { + memcpy(addr, storage_.un.addr, sizeof(*storage_.un.addr)); + return storage_.un.len; + } + } + + const folly::IPAddress& getIPAddress() const; + + // Deprecated: getAddress() above returns the same size as getActualSize() + socklen_t getActualSize() const; + + sa_family_t getFamily() const { + return external_ ? AF_UNIX : storage_.addr.family(); + } + + bool empty() const { + return getFamily() == AF_UNSPEC; + } + + /** + * Get a string representation of the IPv4 or IPv6 address. + * + * Raises std::invalid_argument if an error occurs (for example, if + * the address is not an IPv4 or IPv6 address). + */ + std::string getAddressStr() const; + + /** + * Get a string representation of the IPv4 or IPv6 address. + * + * Raises std::invalid_argument if an error occurs (for example, if + * the address is not an IPv4 or IPv6 address). + */ + void getAddressStr(char* buf, size_t buflen) const; + + /** + * For v4 & v6 addresses, return the fully qualified address string + */ + std::string getFullyQualified() const; + + /** + * Get the IPv4 or IPv6 port for this address. + * + * Raises std::invalid_argument if this is not an IPv4 or IPv6 address. + * + * @return Returns the port, in host byte order. + */ + uint16_t getPort() const; + + /** + * Set the IPv4 or IPv6 port for this address. + * + * Raises std::invalid_argument if this is not an IPv4 or IPv6 address. + */ + void setPort(uint16_t port); + + /** + * Return true if this is an IPv4-mapped IPv6 address. + */ + bool isIPv4Mapped() const { + return (getFamily() == AF_INET6 && + storage_.addr.isIPv4Mapped()); + } + + /** + * Convert an IPv4-mapped IPv6 address to an IPv4 address. + * + * Raises std::invalid_argument if this is not an IPv4-mapped IPv6 address. + */ + void convertToIPv4(); + + /** + * Try to convert an address to IPv4. + * + * This attempts to convert an address to an IPv4 address if possible. + * If the address is an IPv4-mapped IPv6 address, it is converted to an IPv4 + * address and true is returned. Otherwise nothing is done, and false is + * returned. + */ + bool tryConvertToIPv4(); + + /** + * Convert an IPv4 address to IPv6 [::ffff:a.b.c.d] + */ + + bool mapToIPv6(); + + /** + * Get string representation of the host name (or IP address if the host name + * cannot be resolved). + * + * Warning: Using this method is strongly discouraged. It performs a + * DNS lookup, which may block for many seconds. + * + * Raises std::invalid_argument if an error occurs. + */ + std::string getHostStr() const; + + /** + * Get the path name for a Unix domain socket. + * + * Returns a std::string containing the path. For anonymous sockets, an + * empty string is returned. + * + * For addresses in the abstract namespace (Linux-specific), a std::string + * containing binary data is returned. In this case the first character will + * always be a NUL character. + * + * Raises std::invalid_argument if called on a non-Unix domain socket. + */ + std::string getPath() const; + + /** + * Get human-readable string representation of the address. + * + * This prints a string representation of the address, for human consumption. + * For IP addresses, the string is of the form ":". + */ + std::string describe() const; + + bool operator==(const SocketAddress& other) const; + bool operator!=(const SocketAddress& other) const { + return !(*this == other); + } + + /** + * Check whether the first N bits of this address match the first N + * bits of another address. + * @note returns false if the addresses are not from the same + * address family or if the family is neither IPv4 nor IPv6 + */ + bool prefixMatch(const SocketAddress& other, unsigned prefixLength) const; + + /** + * Use this operator for storing maps based on SocketAddress. + */ + bool operator<(const SocketAddress& other) const; + + /** + * Compuate a hash of a SocketAddress. + */ + size_t hash() const; + + private: + /** + * Unix socket addresses require more storage than IPv4 and IPv6 addresses, + * and are comparatively little-used. + * + * Therefore SocketAddress' internal storage_ member variable doesn't + * contain room for a full unix address, to avoid wasting space in the common + * case. When we do need to store a Unix socket address, we use this + * ExternalUnixAddr structure to allocate a struct sockaddr_un separately on + * the heap. + */ + struct ExternalUnixAddr { + struct sockaddr_un *addr; + socklen_t len; + + socklen_t pathLength() const { + return len - offsetof(struct sockaddr_un, sun_path); + } + + void init() { + addr = new sockaddr_un; + addr->sun_family = AF_UNIX; + len = 0; + } + void init(const ExternalUnixAddr &other) { + addr = new sockaddr_un; + len = other.len; + memcpy(addr, other.addr, len); + // Fill the rest with 0s, just for safety + memset(reinterpret_cast(addr) + len, 0, + sizeof(struct sockaddr_un) - len); + } + void copy(const ExternalUnixAddr &other) { + len = other.len; + memcpy(addr, other.addr, len); + } + void free() { + delete addr; + } + }; + + struct addrinfo* getAddrInfo(const char* host, uint16_t port, int flags); + struct addrinfo* getAddrInfo(const char* host, const char* port, int flags); + void setFromAddrInfo(const struct addrinfo* results); + void setFromLocalAddr(const struct addrinfo* results); + void setFromSocket(int socket, int (*fn)(int, struct sockaddr*, socklen_t*)); + std::string getIpString(int flags) const; + void getIpString(char *buf, size_t buflen, int flags) const; + + void updateUnixAddressLength(socklen_t addrlen); + + void prepFamilyChange(sa_family_t newFamily) { + if (newFamily != AF_UNIX) { + if (getFamily() == AF_UNIX) { + storage_.un.free(); + } + external_ = false; + } else { + if (getFamily() != AF_UNIX) { + storage_.un.init(); + } + external_ = true; + } + } + + /* + * storage_ contains room for a full IPv4 or IPv6 address, so they can be + * stored inline without a separate allocation on the heap. + * + * If we need to store a Unix socket address, ExternalUnixAddr is a shim to + * track a struct sockaddr_un allocated separately on the heap. + */ + union { + folly::IPAddress addr{}; + ExternalUnixAddr un; + } storage_; + // IPAddress class does nto save zone or port, and must be saved here + uint16_t port_; + + bool external_{false}; +}; + +/** + * Hash a SocketAddress object. + * + * boost::hash uses hash_value(), so this allows boost::hash to automatically + * work for SocketAddress. + */ +size_t hash_value(const SocketAddress& address); + +std::ostream& operator<<(std::ostream& os, const SocketAddress& addr); + +} + +namespace std { + +// Provide an implementation for std::hash +template<> +struct hash { + size_t operator()( + const folly::SocketAddress& addr) const { + return addr.hash(); + } +}; + +} diff --git a/folly/test/SocketAddressTest.cpp b/folly/test/SocketAddressTest.cpp new file mode 100644 index 00000000..8165f99c --- /dev/null +++ b/folly/test/SocketAddressTest.cpp @@ -0,0 +1,842 @@ +/* + * Copyright 2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +using namespace boost; +using std::string; +using std::cerr; +using std::endl; +using folly::SocketAddress; + +TEST(SocketAddress, Size) { + SocketAddress addr; + EXPECT_EQ(sizeof(addr), 32); +} + +TEST(SocketAddress, ConstructFromIpv4) { + SocketAddress addr("1.2.3.4", 4321); + EXPECT_EQ(addr.getFamily(), AF_INET); + EXPECT_EQ(addr.getAddressStr(), "1.2.3.4"); + EXPECT_EQ(addr.getPort(), 4321); + sockaddr_storage addrStorage; + addr.getAddress(&addrStorage); + const sockaddr_in* inaddr = reinterpret_cast(&addrStorage); + EXPECT_EQ(inaddr->sin_addr.s_addr, htonl(0x01020304)); + EXPECT_EQ(inaddr->sin_port, htons(4321)); +} + +TEST(SocketAddress, IPv4ToStringConversion) { + // testing addresses *.5.5.5, 5.*.5.5, 5.5.*.5, 5.5.5.* + SocketAddress addr; + for (int pos = 0; pos < 4; ++pos) { + for (int i = 0; i < 256; ++i) { + int fragments[] = {5,5,5,5}; + fragments[pos] = i; + std::ostringstream ss; + ss << fragments[0] << "." << fragments[1] << "." + << fragments[2] << "." << fragments[3]; + string ipString = ss.str(); + addr.setFromIpPort(ipString, 1234); + EXPECT_EQ(addr.getAddressStr(), ipString); + } + } +} + +TEST(SocketAddress, SetFromIpv4) { + SocketAddress addr; + addr.setFromIpPort("255.254.253.252", 8888); + EXPECT_EQ(addr.getFamily(), AF_INET); + EXPECT_EQ(addr.getAddressStr(), "255.254.253.252"); + EXPECT_EQ(addr.getPort(), 8888); + sockaddr_storage addrStorage; + addr.getAddress(&addrStorage); + const sockaddr_in* inaddr = reinterpret_cast(&addrStorage); + EXPECT_EQ(inaddr->sin_addr.s_addr, htonl(0xfffefdfc)); + EXPECT_EQ(inaddr->sin_port, htons(8888)); +} + +TEST(SocketAddress, ConstructFromInvalidIpv4) { + EXPECT_THROW(SocketAddress("1.2.3.256", 1234), std::runtime_error); +} + +TEST(SocketAddress, SetFromInvalidIpv4) { + SocketAddress addr("12.34.56.78", 80); + + // Try setting to an invalid value + // Since setFromIpPort() shouldn't allow hostname lookups, setting to + // "localhost" should fail, even if localhost is resolvable + EXPECT_THROW(addr.setFromIpPort("localhost", 1234), + std::runtime_error); + + // Make sure the address still has the old contents + EXPECT_EQ(addr.getFamily(), AF_INET); + EXPECT_EQ(addr.getAddressStr(), "12.34.56.78"); + EXPECT_EQ(addr.getPort(), 80); + sockaddr_storage addrStorage; + addr.getAddress(&addrStorage); + const sockaddr_in* inaddr = reinterpret_cast(&addrStorage); + EXPECT_EQ(inaddr->sin_addr.s_addr, htonl(0x0c22384e)); +} + +TEST(SocketAddress, SetFromHostname) { + // hopefully "localhost" is resolvable on any system that will run the tests + EXPECT_THROW(SocketAddress("localhost", 80), std::runtime_error); + SocketAddress addr("localhost", 80, true); + + SocketAddress addr2; + EXPECT_THROW(addr2.setFromIpPort("localhost", 0), std::runtime_error); + addr2.setFromHostPort("localhost", 0); +} + +TEST(SocketAddress, SetFromStrings) { + SocketAddress addr; + + // Set from a numeric port string + addr.setFromLocalPort("1234"); + EXPECT_EQ(addr.getPort(), 1234); + + // setFromLocalPort() should not accept service names + EXPECT_THROW(addr.setFromLocalPort("http"), std::runtime_error); + + // Call setFromLocalIpPort() with just a port, no IP + addr.setFromLocalIpPort("80"); + EXPECT_EQ(addr.getPort(), 80); + + // Call setFromLocalIpPort() with an IP and port. + addr.setFromLocalIpPort("127.0.0.1:4321"); + EXPECT_EQ(addr.getAddressStr(), "127.0.0.1"); + EXPECT_EQ(addr.getPort(), 4321); + + // setFromIpPort() without an address should fail + EXPECT_THROW(addr.setFromIpPort("4321"), std::invalid_argument); + + // Call setFromIpPort() with an IPv6 address and port + addr.setFromIpPort("2620:0:1cfe:face:b00c::3:65535"); + EXPECT_EQ(addr.getFamily(), AF_INET6); + EXPECT_EQ(addr.getAddressStr(), "2620:0:1cfe:face:b00c::3"); + EXPECT_EQ(addr.getPort(), 65535); + + // Call setFromIpPort() with an IPv4 address and port + addr.setFromIpPort("1.2.3.4:9999"); + EXPECT_EQ(addr.getFamily(), AF_INET); + EXPECT_EQ(addr.getAddressStr(), "1.2.3.4"); + EXPECT_EQ(addr.getPort(), 9999); +} + +TEST(SocketAddress, EqualityAndHash) { + // IPv4 + SocketAddress local1("127.0.0.1", 1234); + EXPECT_EQ(local1, local1); + EXPECT_EQ(local1.hash(), local1.hash()); + + SocketAddress local2("127.0.0.1", 1234); + EXPECT_EQ(local1, local2); + EXPECT_EQ(local1.hash(), local2.hash()); + + SocketAddress local3("127.0.0.1", 4321); + EXPECT_NE(local1, local3); + EXPECT_NE(local1.hash(), local3.hash()); + + SocketAddress other1("1.2.3.4", 1234); + EXPECT_EQ(other1, other1); + EXPECT_EQ(other1.hash(), other1.hash()); + EXPECT_NE(local1, other1); + EXPECT_NE(local1.hash(), other1.hash()); + + SocketAddress other2("4.3.2.1", 1234); + EXPECT_NE(other1.hash(), other2.hash()); + EXPECT_NE(other1.hash(), other2.hash()); + + other2.setFromIpPort("1.2.3.4", 0); + EXPECT_NE(other1.hash(), other2.hash()); + EXPECT_NE(other1.hash(), other2.hash()); + other2.setPort(1234); + EXPECT_EQ(other1.hash(), other2.hash()); + EXPECT_EQ(other1.hash(), other2.hash()); + + // IPv6 + SocketAddress v6_1("2620:0:1c00:face:b00c:0:0:abcd", 1234); + SocketAddress v6_2("2620:0:1c00:face:b00c::abcd", 1234); + SocketAddress v6_3("2620:0:1c00:face:b00c::bcda", 1234); + EXPECT_EQ(v6_1, v6_2); + EXPECT_EQ(v6_1.hash(), v6_2.hash()); + EXPECT_NE(v6_1, v6_3); + EXPECT_NE(v6_1.hash(), v6_3.hash()); + + // IPv4 versus IPv6 comparison + SocketAddress localIPv6("::1", 1234); + // Even though these both refer to localhost, + // IPv4 and IPv6 addresses are never treated as the same address + EXPECT_NE(local1, localIPv6); + EXPECT_NE(local1.hash(), localIPv6.hash()); + + // IPv4-mapped IPv6 addresses are not treated as equal + // to the equivalent IPv4 address + SocketAddress v4("10.0.0.3", 99); + SocketAddress v6_mapped1("::ffff:10.0.0.3", 99); + SocketAddress v6_mapped2("::ffff:0a00:0003", 99); + EXPECT_NE(v4, v6_mapped1); + EXPECT_NE(v4, v6_mapped2); + EXPECT_EQ(v6_mapped1, v6_mapped2); + + // However, after calling convertToIPv4(), the mapped address should now be + // equal to the v4 version. + EXPECT_TRUE(v6_mapped1.isIPv4Mapped()); + v6_mapped1.convertToIPv4(); + EXPECT_EQ(v6_mapped1, v4); + EXPECT_NE(v6_mapped1, v6_mapped2); + + // Unix + SocketAddress unix1; + unix1.setFromPath("/foo"); + SocketAddress unix2; + unix2.setFromPath("/foo"); + SocketAddress unix3; + unix3.setFromPath("/bar"); + SocketAddress unixAnon; + unixAnon.setFromPath(""); + + EXPECT_EQ(unix1, unix2); + EXPECT_EQ(unix1.hash(), unix2.hash()); + EXPECT_NE(unix1, unix3); + EXPECT_NE(unix1, unixAnon); + EXPECT_NE(unix2, unix3); + EXPECT_NE(unix2, unixAnon); + // anonymous addresses aren't equal to any other address, + // including themselves + EXPECT_NE(unixAnon, unixAnon); + + // It isn't strictly required that hashes for different addresses be + // different, but we should have very few collisions. It generally indicates + // a problem if these collide + EXPECT_NE(unix1.hash(), unix3.hash()); + EXPECT_NE(unix1.hash(), unixAnon.hash()); + EXPECT_NE(unix3.hash(), unixAnon.hash()); +} + +TEST(SocketAddress, IsPrivate) { + // IPv4 + SocketAddress addr("9.255.255.255", 0); + EXPECT_TRUE(!addr.isPrivateAddress()); + addr.setFromIpPort("10.0.0.0", 0); + EXPECT_TRUE(addr.isPrivateAddress()); + addr.setFromIpPort("10.255.255.255", 0); + EXPECT_TRUE(addr.isPrivateAddress()); + addr.setFromIpPort("11.0.0.0", 0); + EXPECT_TRUE(!addr.isPrivateAddress()); + + addr.setFromIpPort("172.15.255.255", 0); + EXPECT_TRUE(!addr.isPrivateAddress()); + addr.setFromIpPort("172.16.0.0", 0); + EXPECT_TRUE(addr.isPrivateAddress()); + addr.setFromIpPort("172.31.255.255", 0); + EXPECT_TRUE(addr.isPrivateAddress()); + addr.setFromIpPort("172.32.0.0", 0); + EXPECT_TRUE(!addr.isPrivateAddress()); + + addr.setFromIpPort("192.167.255.255", 0); + EXPECT_TRUE(!addr.isPrivateAddress()); + addr.setFromIpPort("192.168.0.0", 0); + EXPECT_TRUE(addr.isPrivateAddress()); + addr.setFromIpPort("192.168.255.255", 0); + EXPECT_TRUE(addr.isPrivateAddress()); + addr.setFromIpPort("192.169.0.0", 0); + EXPECT_TRUE(!addr.isPrivateAddress()); + + addr.setFromIpPort("126.255.255.255", 0); + EXPECT_TRUE(!addr.isPrivateAddress()); + addr.setFromIpPort("127.0.0.0", 0); + EXPECT_TRUE(addr.isPrivateAddress()); + addr.setFromIpPort("127.0.0.1", 0); + EXPECT_TRUE(addr.isPrivateAddress()); + addr.setFromIpPort("127.255.255.255", 0); + EXPECT_TRUE(addr.isPrivateAddress()); + addr.setFromIpPort("128.0.0.0", 0); + EXPECT_TRUE(!addr.isPrivateAddress()); + + addr.setFromIpPort("1.2.3.4", 0); + EXPECT_TRUE(!addr.isPrivateAddress()); + addr.setFromIpPort("69.171.239.10", 0); + EXPECT_TRUE(!addr.isPrivateAddress()); + + // IPv6 + addr.setFromIpPort("fbff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 0); + EXPECT_TRUE(!addr.isPrivateAddress()); + addr.setFromIpPort("fc00::", 0); + EXPECT_TRUE(addr.isPrivateAddress()); + addr.setFromIpPort("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 0); + EXPECT_TRUE(addr.isPrivateAddress()); + addr.setFromIpPort("fe00::", 0); + EXPECT_TRUE(!addr.isPrivateAddress()); + + addr.setFromIpPort("fe7f:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 0); + EXPECT_TRUE(!addr.isPrivateAddress()); + addr.setFromIpPort("fe80::", 0); + EXPECT_TRUE(addr.isPrivateAddress()); + addr.setFromIpPort("fe80::ffff:ffff:ffff:ffff", 0); + EXPECT_TRUE(addr.isPrivateAddress()); + addr.setFromIpPort("febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 0); + EXPECT_TRUE(addr.isPrivateAddress()); + addr.setFromIpPort("fec0::", 0); + EXPECT_TRUE(!addr.isPrivateAddress()); + + addr.setFromIpPort("::0", 0); + EXPECT_TRUE(!addr.isPrivateAddress()); + addr.setFromIpPort("2620:0:1c00:face:b00c:0:0:abcd", 0); + EXPECT_TRUE(!addr.isPrivateAddress()); + + // IPv4-mapped IPv6 + addr.setFromIpPort("::ffff:127.0.0.1", 0); + EXPECT_TRUE(addr.isPrivateAddress()); + addr.setFromIpPort("::ffff:10.1.2.3", 0); + EXPECT_TRUE(addr.isPrivateAddress()); + addr.setFromIpPort("::ffff:172.24.0.115", 0); + EXPECT_TRUE(addr.isPrivateAddress()); + addr.setFromIpPort("::ffff:192.168.0.1", 0); + EXPECT_TRUE(addr.isPrivateAddress()); + addr.setFromIpPort("::ffff:69.171.239.10", 0); + EXPECT_TRUE(!addr.isPrivateAddress()); + + // Unix sockets are considered private addresses + addr.setFromPath("/tmp/mysock"); + EXPECT_TRUE(addr.isPrivateAddress()); +} + +TEST(SocketAddress, IsLoopback) { + // IPv4 + SocketAddress addr("127.0.0.1", 0); + EXPECT_TRUE(addr.isLoopbackAddress()); + addr.setFromIpPort("127.0.0.0", 0xffff); + EXPECT_TRUE(addr.isLoopbackAddress()); + addr.setFromIpPort("127.1.1.1", 0xffff); + EXPECT_TRUE(addr.isLoopbackAddress()); + addr.setFromIpPort("127.255.255.255", 80); + EXPECT_TRUE(addr.isLoopbackAddress()); + + addr.setFromIpPort("128.0.0.0", 0); + EXPECT_TRUE(!addr.isLoopbackAddress()); + addr.setFromIpPort("126.255.255.255", 0); + EXPECT_TRUE(!addr.isLoopbackAddress()); + addr.setFromIpPort("10.1.2.3", 0); + EXPECT_TRUE(!addr.isLoopbackAddress()); + + // IPv6 + addr.setFromIpPort("::1", 0); + EXPECT_TRUE(addr.isLoopbackAddress()); + addr.setFromIpPort("::0", 0); + EXPECT_TRUE(!addr.isLoopbackAddress()); + addr.setFromIpPort("::2", 0); + EXPECT_TRUE(!addr.isLoopbackAddress()); + + // IPv4-mapped IPv6 + addr.setFromIpPort("::ffff:127.0.0.1", 0); + EXPECT_TRUE(addr.isLoopbackAddress()); + addr.setFromIpPort("::ffff:7f0a:141e", 0); + EXPECT_TRUE(addr.isLoopbackAddress()); + addr.setFromIpPort("::ffff:169.254.0.13", 0); + EXPECT_TRUE(!addr.isLoopbackAddress()); + + // Unix sockets are considered loopback addresses + addr.setFromPath("/tmp/mysock"); + EXPECT_TRUE(addr.isLoopbackAddress()); +} + +void CheckPrefixMatch(const SocketAddress& first, + const SocketAddress& second, unsigned matchingPrefixLen) { + unsigned i; + for (i = 0; i <= matchingPrefixLen; i++) { + EXPECT_TRUE(first.prefixMatch(second, i)); + } + unsigned addrLen = (first.getFamily() == AF_INET6) ? 128 : 32; + for (; i <= addrLen; i++) { + EXPECT_TRUE(!first.prefixMatch(second, i)); + } +} + +TEST(SocketAddress, PrefixMatch) { + // IPv4 + SocketAddress addr1("127.0.0.1", 0); + SocketAddress addr2("127.0.0.1", 0); + CheckPrefixMatch(addr1, addr2, 32); + + addr2.setFromIpPort("127.0.1.1", 0); + CheckPrefixMatch(addr1, addr2, 23); + + addr2.setFromIpPort("1.1.0.127", 0); + CheckPrefixMatch(addr1, addr2, 1); + + // Address family mismatch + addr2.setFromIpPort("::ffff:127.0.0.1", 0); + EXPECT_TRUE(!addr1.prefixMatch(addr2, 1)); + + // IPv6 + addr1.setFromIpPort("2a03:2880:10:8f02:face:b00c:0:25", 0); + CheckPrefixMatch(addr1, addr2, 2); + + addr2.setFromIpPort("2a03:2880:10:8f02:face:b00c:0:25", 0); + CheckPrefixMatch(addr1, addr2, 128); + + addr2.setFromIpPort("2a03:2880:30:8f02:face:b00c:0:25", 0); + CheckPrefixMatch(addr1, addr2, 42); +} + +void CheckFirstLessThanSecond(SocketAddress first, SocketAddress second) { + EXPECT_TRUE(!(first < first)); + EXPECT_TRUE(!(second < second)); + EXPECT_TRUE(first < second); + EXPECT_TRUE(!(first == second)); + EXPECT_TRUE(!(second < first)); +} + +TEST(SocketAddress, CheckComparatorBehavior) { + SocketAddress first, second; + // The following comparison are strict (so if first and second were + // inverted that is ok. + + //IP V4 + + // port comparisions + first.setFromIpPort("128.0.0.0", 0); + second.setFromIpPort("128.0.0.0", 0xFFFF); + CheckFirstLessThanSecond(first, second); + first.setFromIpPort("128.0.0.100", 0); + second.setFromIpPort("128.0.0.0", 0xFFFF); + CheckFirstLessThanSecond(first, second); + + // Address comparisons + first.setFromIpPort("128.0.0.0", 10); + second.setFromIpPort("128.0.0.100", 10); + CheckFirstLessThanSecond(first, second); + + // Comaprision between IPV4 and IPV6 + first.setFromIpPort("128.0.0.0", 0); + second.setFromIpPort("::ffff:127.0.0.1", 0); + CheckFirstLessThanSecond(first, second); + first.setFromIpPort("128.0.0.0", 100); + second.setFromIpPort("::ffff:127.0.0.1", 0); + CheckFirstLessThanSecond(first, second); + + // IPV6 comparisons + + // port comparisions + first.setFromIpPort("::0", 0); + second.setFromIpPort("::0", 0xFFFF); + CheckFirstLessThanSecond(first, second); + first.setFromIpPort("::0", 0); + second.setFromIpPort("::1", 0xFFFF); + CheckFirstLessThanSecond(first, second); + + // Address comparisons + first.setFromIpPort("::0", 10); + second.setFromIpPort("::1", 10); + CheckFirstLessThanSecond(first, second); + + // Unix + first.setFromPath("/foo"); + second.setFromPath("/1234"); + // The exact comparison order doesn't really matter, as long as + // (a < b), (b < a), and (a == b) are consistent. + // This checks our current comparison rules, which checks the path length + // before the path contents. + CheckFirstLessThanSecond(first, second); + first.setFromPath("/1234"); + second.setFromPath("/5678"); + CheckFirstLessThanSecond(first, second); + + // IPv4 vs Unix. + // We currently compare the address family values, and AF_UNIX < AF_INET + first.setFromPath("/foo"); + second.setFromIpPort("127.0.0.1", 80); + CheckFirstLessThanSecond(first, second); +} + +TEST(SocketAddress, Unix) { + SocketAddress addr; + + // Test a small path + addr.setFromPath("foo"); + EXPECT_EQ(addr.getFamily(), AF_UNIX); + EXPECT_EQ(addr.describe(), "foo"); + EXPECT_THROW(addr.getAddressStr(), std::invalid_argument); + EXPECT_THROW(addr.getPort(), std::invalid_argument); + EXPECT_TRUE(addr.isPrivateAddress()); + EXPECT_TRUE(addr.isLoopbackAddress()); + + // Test a path that is too large + const char longPath[] = + "abcdefghijklmnopqrstuvwxyz0123456789" + "abcdefghijklmnopqrstuvwxyz0123456789" + "abcdefghijklmnopqrstuvwxyz0123456789" + "abcdefghijklmnopqrstuvwxyz0123456789"; + EXPECT_THROW(addr.setFromPath(longPath), std::invalid_argument); + // The original address should still be the same + EXPECT_EQ(addr.getFamily(), AF_UNIX); + EXPECT_EQ(addr.describe(), "foo"); + + // Test a path that exactly fits in sockaddr_un + // (not including the NUL terminator) + const char exactLengthPath[] = + "abcdefghijklmnopqrstuvwxyz0123456789" + "abcdefghijklmnopqrstuvwxyz0123456789" + "abcdefghijklmnopqrstuvwxyz0123456789"; + addr.setFromPath(exactLengthPath); + EXPECT_EQ(addr.describe(), exactLengthPath); + + // Test converting a unix socket address to an IPv4 one, then back + addr.setFromHostPort("127.0.0.1", 1234); + EXPECT_EQ(addr.getFamily(), AF_INET); + EXPECT_EQ(addr.describe(), "127.0.0.1:1234"); + addr.setFromPath("/i/am/a/unix/address"); + EXPECT_EQ(addr.getFamily(), AF_UNIX); + EXPECT_EQ(addr.describe(), "/i/am/a/unix/address"); + + // Test copy constructor and assignment operator + { + SocketAddress copy(addr); + EXPECT_EQ(copy, addr); + copy.setFromPath("/abc"); + EXPECT_NE(copy, addr); + copy = addr; + EXPECT_EQ(copy, addr); + copy.setFromIpPort("127.0.0.1", 80); + EXPECT_NE(copy, addr); + copy = addr; + EXPECT_EQ(copy, addr); + } + + { + SocketAddress copy(addr); + EXPECT_EQ(copy, addr); + EXPECT_EQ(copy.describe(), "/i/am/a/unix/address"); + EXPECT_EQ(copy.getPath(), "/i/am/a/unix/address"); + + SocketAddress other("127.0.0.1", 80); + EXPECT_NE(other, addr); + other = copy; + EXPECT_EQ(other, copy); + EXPECT_EQ(other, addr); + EXPECT_EQ(copy, addr); + } + +#if __GXX_EXPERIMENTAL_CXX0X__ + { + SocketAddress copy; + { + // move a unix address into a non-unix address + SocketAddress tmpCopy(addr); + copy = std::move(tmpCopy); + } + EXPECT_EQ(copy, addr); + + copy.setFromPath("/another/path"); + { + // move a unix address into a unix address + SocketAddress tmpCopy(addr); + copy = std::move(tmpCopy); + } + EXPECT_EQ(copy, addr); + + { + // move a non-unix address into a unix address + SocketAddress tmp("127.0.0.1", 80); + copy = std::move(tmp); + } + EXPECT_EQ(copy.getAddressStr(), "127.0.0.1"); + EXPECT_EQ(copy.getPort(), 80); + + copy = addr; + // move construct a unix address + SocketAddress other(std::move(copy)); + EXPECT_EQ(other, addr); + EXPECT_EQ(other.getPath(), addr.getPath()); + } +#endif +} + +TEST(SocketAddress, AnonymousUnix) { + // Create a unix socket pair, and get the addresses. + int fds[2]; + int rc = socketpair(AF_UNIX, SOCK_STREAM, 0, fds); + EXPECT_EQ(rc, 0); + + SocketAddress addr0; + SocketAddress peer0; + SocketAddress addr1; + SocketAddress peer1; + addr0.setFromLocalAddress(fds[0]); + peer0.setFromPeerAddress(fds[0]); + addr1.setFromLocalAddress(fds[1]); + peer1.setFromPeerAddress(fds[1]); + close(fds[0]); + close(fds[1]); + + EXPECT_EQ(addr0.describe(), ""); + EXPECT_EQ(addr1.describe(), ""); + EXPECT_EQ(peer0.describe(), ""); + EXPECT_EQ(peer1.describe(), ""); + + // Anonymous addresses should never compare equal + EXPECT_NE(addr0, addr1); + EXPECT_NE(peer0, peer1); + + // Note that logically addr0 and peer1 are the same, + // but since they are both anonymous we have no way to determine this + EXPECT_NE(addr0, peer1); + // We can't even tell if an anonymous address is equal to itself + EXPECT_NE(addr0, addr0); +} + +#define REQUIRE_ERRNO(cond, msg) \ + if (!(cond)) { \ + int _requireErrnoCopy_ = errno; \ + std::ostringstream _requireMsg_; \ + _requireMsg_ << (msg) << ": " << strerror(_requireErrnoCopy_); \ + ADD_FAILURE(); \ + } + +void testSetFromSocket(const SocketAddress *serverBindAddr, + const SocketAddress *clientBindAddr, + SocketAddress *listenAddrRet, + SocketAddress *acceptAddrRet, + SocketAddress *serverAddrRet, + SocketAddress *serverPeerAddrRet, + SocketAddress *clientAddrRet, + SocketAddress *clientPeerAddrRet) { + int listenSock = socket(serverBindAddr->getFamily(), SOCK_STREAM, 0); + REQUIRE_ERRNO(listenSock > 0, "failed to create listen socket"); + sockaddr_storage laddr; + serverBindAddr->getAddress(&laddr); + socklen_t laddrLen = serverBindAddr->getActualSize(); + int rc = bind(listenSock, reinterpret_cast(&laddr), laddrLen); + REQUIRE_ERRNO(rc == 0, "failed to bind to server socket"); + rc = listen(listenSock, 10); + REQUIRE_ERRNO(rc == 0, "failed to listen"); + + listenAddrRet->setFromLocalAddress(listenSock); + + SocketAddress listenPeerAddr; + EXPECT_THROW(listenPeerAddr.setFromPeerAddress(listenSock), + std::runtime_error); + + // Note that we use the family from serverBindAddr here, since we allow + // clientBindAddr to be nullptr. + int clientSock = socket(serverBindAddr->getFamily(), SOCK_STREAM, 0); + REQUIRE_ERRNO(clientSock > 0, "failed to create client socket"); + if (clientBindAddr != nullptr) { + sockaddr_storage clientAddr; + clientBindAddr->getAddress(&clientAddr); + + rc = bind(clientSock, reinterpret_cast(&clientAddr), + clientBindAddr->getActualSize()); + REQUIRE_ERRNO(rc == 0, "failed to bind to client socket"); + } + + sockaddr_storage listenAddr; + listenAddrRet->getAddress(&listenAddr); + rc = connect(clientSock, reinterpret_cast(&listenAddr), + listenAddrRet->getActualSize()); + REQUIRE_ERRNO(rc == 0, "failed to connect"); + + sockaddr_storage acceptAddr; + socklen_t acceptAddrLen = sizeof(acceptAddr); + int serverSock = accept(listenSock, reinterpret_cast(&acceptAddr), &acceptAddrLen); + REQUIRE_ERRNO(serverSock > 0, "failed to accept"); + acceptAddrRet->setFromSockaddr(reinterpret_cast(&acceptAddr), acceptAddrLen); + + serverAddrRet->setFromLocalAddress(serverSock); + serverPeerAddrRet->setFromPeerAddress(serverSock); + clientAddrRet->setFromLocalAddress(clientSock); + clientPeerAddrRet->setFromPeerAddress(clientSock); + + close(clientSock); + close(serverSock); + close(listenSock); +} + +TEST(SocketAddress, SetFromSocketIPv4) { + SocketAddress serverBindAddr("0.0.0.0", 0); + SocketAddress clientBindAddr("0.0.0.0", 0); + SocketAddress listenAddr; + SocketAddress acceptAddr; + SocketAddress serverAddr; + SocketAddress serverPeerAddr; + SocketAddress clientAddr; + SocketAddress clientPeerAddr; + + testSetFromSocket(&serverBindAddr, &clientBindAddr, + &listenAddr, &acceptAddr, + &serverAddr, &serverPeerAddr, + &clientAddr, &clientPeerAddr); + + // The server socket's local address should have the same port as the listen + // address. The IP will be different, since the listening socket is + // listening on INADDR_ANY, but the server socket will have a concrete IP + // address assigned to it. + EXPECT_EQ(serverAddr.getPort(), listenAddr.getPort()); + + // The client's peer address should always be the same as the server + // socket's address. + EXPECT_EQ(clientPeerAddr, serverAddr); + // The address returned by getpeername() on the server socket should + // be the same as the address returned by accept() + EXPECT_EQ(serverPeerAddr, acceptAddr); + EXPECT_EQ(serverPeerAddr, clientAddr); + EXPECT_EQ(acceptAddr, clientAddr); +} + +/* + * Note this test exercises Linux-specific Unix socket behavior + */ +TEST(SocketAddress, SetFromSocketUnixAbstract) { + // Explicitly binding to an empty path results in an abstract socket + // name being picked for us automatically. + SocketAddress serverBindAddr; + string path(1, 0); + path.append("test address"); + serverBindAddr.setFromPath(path); + SocketAddress clientBindAddr; + clientBindAddr.setFromPath(""); + + SocketAddress listenAddr; + SocketAddress acceptAddr; + SocketAddress serverAddr; + SocketAddress serverPeerAddr; + SocketAddress clientAddr; + SocketAddress clientPeerAddr; + + testSetFromSocket(&serverBindAddr, &clientBindAddr, + &listenAddr, &acceptAddr, + &serverAddr, &serverPeerAddr, + &clientAddr, &clientPeerAddr); + + // The server socket's local address should be the same as the listen + // address. + EXPECT_EQ(serverAddr, listenAddr); + + // The client's peer address should always be the same as the server + // socket's address. + EXPECT_EQ(clientPeerAddr, serverAddr); + + EXPECT_EQ(serverPeerAddr, clientAddr); + // Oddly, the address returned by accept() does not seem to match the address + // returned by getpeername() on the server socket or getsockname() on the + // client socket. + // EXPECT_EQ(serverPeerAddr, acceptAddr); + // EXPECT_EQ(acceptAddr, clientAddr); +} + +TEST(SocketAddress, SetFromSocketUnixExplicit) { + // Pick two temporary path names. + // We use mkstemp() just to avoid warnings about mktemp, + // but we need to remove the file to let the socket code bind to it. + char serverPath[] = "/tmp/SocketAddressTest.server.XXXXXX"; + int serverPathFd = mkstemp(serverPath); + EXPECT_GE(serverPathFd, 0); + char clientPath[] = "/tmp/SocketAddressTest.client.XXXXXX"; + int clientPathFd = mkstemp(clientPath); + EXPECT_GE(clientPathFd, 0); + + int rc = unlink(serverPath); + EXPECT_EQ(rc, 0); + rc = unlink(clientPath); + EXPECT_EQ(rc, 0); + + SocketAddress serverBindAddr; + SocketAddress clientBindAddr; + SocketAddress listenAddr; + SocketAddress acceptAddr; + SocketAddress serverAddr; + SocketAddress serverPeerAddr; + SocketAddress clientAddr; + SocketAddress clientPeerAddr; + try { + serverBindAddr.setFromPath(serverPath); + clientBindAddr.setFromPath(clientPath); + + testSetFromSocket(&serverBindAddr, &clientBindAddr, + &listenAddr, &acceptAddr, + &serverAddr, &serverPeerAddr, + &clientAddr, &clientPeerAddr); + } catch (...) { + // Remove the socket files after we are done + unlink(serverPath); + unlink(clientPath); + throw; + } + unlink(serverPath); + unlink(clientPath); + + // The server socket's local address should be the same as the listen + // address. + EXPECT_EQ(serverAddr, listenAddr); + + // The client's peer address should always be the same as the server + // socket's address. + EXPECT_EQ(clientPeerAddr, serverAddr); + + EXPECT_EQ(serverPeerAddr, clientAddr); + EXPECT_EQ(serverPeerAddr, acceptAddr); + EXPECT_EQ(acceptAddr, clientAddr); +} + +TEST(SocketAddress, SetFromSocketUnixAnonymous) { + // Test an anonymous client talking to a fixed-path unix socket. + char serverPath[] = "/tmp/SocketAddressTest.server.XXXXXX"; + int serverPathFd = mkstemp(serverPath); + EXPECT_GE(serverPathFd, 0); + int rc = unlink(serverPath); + EXPECT_EQ(rc, 0); + + SocketAddress serverBindAddr; + SocketAddress listenAddr; + SocketAddress acceptAddr; + SocketAddress serverAddr; + SocketAddress serverPeerAddr; + SocketAddress clientAddr; + SocketAddress clientPeerAddr; + try { + serverBindAddr.setFromPath(serverPath); + + testSetFromSocket(&serverBindAddr, nullptr, + &listenAddr, &acceptAddr, + &serverAddr, &serverPeerAddr, + &clientAddr, &clientPeerAddr); + } catch (...) { + // Remove the socket file after we are done + unlink(serverPath); + throw; + } + unlink(serverPath); + + // The server socket's local address should be the same as the listen + // address. + EXPECT_EQ(serverAddr, listenAddr); + + // The client's peer address should always be the same as the server + // socket's address. + EXPECT_EQ(clientPeerAddr, serverAddr); + + // Since the client is using an anonymous address, it won't compare equal to + // any other anonymous addresses. Make sure the addresses are anonymous. + EXPECT_EQ(serverPeerAddr.getPath(), ""); + EXPECT_EQ(clientAddr.getPath(), ""); + EXPECT_EQ(acceptAddr.getPath(), ""); +}