move socketaddress to folly
authorDave Watson <davejwatson@fb.com>
Fri, 12 Sep 2014 18:30:59 +0000 (11:30 -0700)
committerdcsommer <dcsommer@fb.com>
Fri, 19 Sep 2014 22:30:59 +0000 (15:30 -0700)
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

folly/Makefile.am
folly/SocketAddress.cpp [new file with mode: 0644]
folly/SocketAddress.h [new file with mode: 0644]
folly/test/SocketAddressTest.cpp [new file with mode: 0644]

index b105bea0ef4c6d2237fbb523d2d0ef4a4d01f189..e0363e4accfc68487f67f6fd5295028afac867d1 100644 (file)
@@ -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 (file)
index 0000000..b8ce1ea
--- /dev/null
@@ -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 <folly/SocketAddress.h>
+
+#include <folly/Hash.h>
+
+#include <boost/functional/hash.hpp>
+#include <boost/static_assert.hpp>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sstream>
+#include <string>
+
+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 "<host>:<port>" or just "<port>",
+ * 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 \"<host>:<port>\"");
+      }
+      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<const struct sockaddr_in*>(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<const struct sockaddr_in6*>(address));
+  } else if (address->sa_family == AF_UNIX) {
+    setFromSockaddr(reinterpret_cast<const struct sockaddr_un*>(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<char*>(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 "<uninitialized address>";
+    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 "<anonymous unix address>";
+      }
+
+      if (storage_.un.addr->sun_path[0] == '\0') {
+        // Linux supports an abstract namespace for unix socket addresses
+        return "<abstract unix address>";
+      }
+
+      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), "<unknown address family %d>",
+               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<std::string>(
+      "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<std::string>(
+      "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 (file)
index 0000000..ac1771c
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <features.h>
+#include <netdb.h>
+#include <cstddef>
+#include <iostream>
+#include <string>
+
+#include <folly/IPAddress.h>
+
+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 "<ip>:<port>", or
+   * just as "<port>".  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 "<ip>:<port>".  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 "<host>:<port>".  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 "<IP>:<port>".
+   */
+  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<char*>(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<SocketAddress>
+template<>
+struct hash<folly::SocketAddress> {
+  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 (file)
index 0000000..8165f99
--- /dev/null
@@ -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 <folly/SocketAddress.h>
+
+#include <gtest/gtest.h>
+#include <iostream>
+#include <sstream>
+
+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<sockaddr_in*>(&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<sockaddr_in*>(&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<sockaddr_in*>(&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(), "<anonymous unix address>");
+  EXPECT_EQ(addr1.describe(), "<anonymous unix address>");
+  EXPECT_EQ(peer0.describe(), "<anonymous unix address>");
+  EXPECT_EQ(peer1.describe(), "<anonymous unix address>");
+
+  // 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<sockaddr*>(&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<sockaddr*>(&clientAddr),
+              clientBindAddr->getActualSize());
+    REQUIRE_ERRNO(rc == 0, "failed to bind to client socket");
+  }
+
+  sockaddr_storage listenAddr;
+  listenAddrRet->getAddress(&listenAddr);
+  rc = connect(clientSock, reinterpret_cast<sockaddr*>(&listenAddr),
+               listenAddrRet->getActualSize());
+  REQUIRE_ERRNO(rc == 0, "failed to connect");
+
+  sockaddr_storage acceptAddr;
+  socklen_t acceptAddrLen = sizeof(acceptAddr);
+  int serverSock = accept(listenSock, reinterpret_cast<sockaddr*>(&acceptAddr), &acceptAddrLen);
+  REQUIRE_ERRNO(serverSock > 0, "failed to accept");
+  acceptAddrRet->setFromSockaddr(reinterpret_cast<sockaddr*>(&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(), "");
+}