Add link-local scope handling
authorDave Watson <davejwatson@fb.com>
Fri, 8 Aug 2014 18:28:15 +0000 (11:28 -0700)
committerSara Golemon <sgolemon@fb.com>
Tue, 9 Sep 2014 21:22:22 +0000 (14:22 -0700)
Summary: Also save the link-local scope in the V6 address.  See D1479365 for more details

Test Plan: fbconfig folly/test:network_address_test; fbmake runtests

Reviewed By: simpkins@fb.com

Subscribers: marccelani, doug, ps, bmatheny

FB internal diff: D1486435

folly/IPAddress.cpp
folly/IPAddress.h
folly/IPAddressV6.cpp
folly/IPAddressV6.h
folly/detail/IPAddress.h
folly/test/IPAddressTest.cpp

index b49b0dc86f370eb08c0845d3366b602202181b10..e59caf3b36ac8fa10b989a9fc03d0ee04d54257f 100644 (file)
@@ -146,12 +146,20 @@ IPAddress::IPAddress(StringPiece addr)
   // need to check for V4 address second, since IPv4-mapped IPv6 addresses may
   // contain a period
   if (ip.find(':') != string::npos) {
-    in6_addr ipAddr;
-    if (inet_pton(AF_INET6, ip.c_str(), &ipAddr) != 1) {
-      throwFormatException("inet_pton failed for V6 address");
+    struct addrinfo* result;
+    struct addrinfo hints;
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = AF_INET6;
+    hints.ai_socktype = SOCK_STREAM;
+    hints.ai_flags = AI_NUMERICHOST;
+    if (!getaddrinfo(ip.c_str(), nullptr, &hints, &result)) {
+      struct sockaddr_in6* ipAddr = (struct sockaddr_in6*)result->ai_addr;
+      addr_ = IPAddressV46(IPAddressV6(*ipAddr));
+      family_ = AF_INET6;
+      freeaddrinfo(result);
+    } else {
+      throwFormatException("getsockaddr failed for V6 address");
     }
-    addr_ = IPAddressV46(IPAddressV6(ipAddr));
-    family_ = AF_INET6;
   } else if (ip.find('.') != string::npos) {
     in_addr ipAddr;
     if (inet_pton(AF_INET, ip.c_str(), &ipAddr) != 1) {
@@ -181,7 +189,7 @@ IPAddress::IPAddress(const sockaddr* addr)
     }
     case AF_INET6: {
       const sockaddr_in6 *v6addr = reinterpret_cast<const sockaddr_in6*>(addr);
-      addr_.ipV6Addr = IPAddressV6(v6addr->sin6_addr);
+      addr_.ipV6Addr = IPAddressV6(*v6addr);
       break;
     }
     default:
index 6446836f47050f62e6c2e7305e135e5e4671f862..7820fa47bd7d7029c5565e6d46838a1e813be45c 100644 (file)
@@ -206,6 +206,7 @@ class IPAddress : boost::totally_ordered<IPAddress> {
       sockaddr_in6 *sin = reinterpret_cast<sockaddr_in6*>(dest);
       sin->sin6_addr = asV6().toAddr();
       sin->sin6_port = port;
+      sin->sin6_scope_id = asV6().getScopeId();
       return sizeof(*sin);
     } else {
       throw InvalidAddressFamilyException(family());
index 188a47761021b0bb7b79beb79e8a324130711b07..849c6d63be6fe57eda3922dbd9ce2c6dd977aadc 100644 (file)
@@ -65,7 +65,18 @@ IPAddressV6::IPAddressV6(StringPiece addr) {
     ip = ip.substr(1, ip.size() - 2);
   }
 
-  if (inet_pton(AF_INET6, ip.c_str(), &addr_.in6Addr_) != 1) {
+  struct addrinfo* result;
+  struct addrinfo hints;
+  memset(&hints, 0, sizeof(hints));
+  hints.ai_family = AF_INET6;
+  hints.ai_socktype = SOCK_STREAM;
+  hints.ai_flags = AI_NUMERICHOST;
+  if (!getaddrinfo(ip.c_str(), nullptr, &hints, &result)) {
+    struct sockaddr_in6* ipAddr = (struct sockaddr_in6*)result->ai_addr;
+    addr_.in6Addr_ = ipAddr->sin6_addr;
+    scope_ = ipAddr->sin6_scope_id;
+    freeaddrinfo(result);
+  } else {
     throw IPAddressFormatException("Invalid IPv6 address '", ip, "'");
   }
 }
@@ -76,6 +87,13 @@ IPAddressV6::IPAddressV6(const in6_addr& src)
 {
 }
 
+// sockaddr_in6 constructor
+IPAddressV6::IPAddressV6(const sockaddr_in6& src)
+  : addr_(src.sin6_addr)
+  , scope_(src.sin6_scope_id)
+{
+}
+
 // ByteArray16 constructor
 IPAddressV6::IPAddressV6(const ByteArray16& src)
   : addr_(src)
@@ -108,6 +126,7 @@ void IPAddressV6::setFromBinary(ByteRange bytes) {
                                    "be 16 bytes, got ", bytes.size());
   }
   memcpy(&addr_.in6Addr_.s6_addr, bytes.data(), sizeof(in6_addr));
+  scope_ = 0;
 }
 
 // public
@@ -309,12 +328,17 @@ IPAddressV6 IPAddressV6::mask(size_t numBits) const {
 // public
 string IPAddressV6::str() const {
   char buffer[INET6_ADDRSTRLEN] = {0};
-  if (!inet_ntop(AF_INET6, &addr_.in6Addr_, buffer, INET6_ADDRSTRLEN)) {
+  sockaddr_in6 sock = toSockAddr();
+  if (!getnameinfo(
+        (sockaddr*)&sock, sizeof(sock),
+        buffer, INET6_ADDRSTRLEN,
+        nullptr, 0, NI_NUMERICHOST)) {
+    string ip(buffer);
+    return std::move(ip);
+  } else {
     throw IPAddressFormatException("Invalid address with hex ",
                                    "'", detail::Bytes::toHex(bytes(), 16), "'");
   }
-  string ip(buffer);
-  return std::move(ip);
 }
 
 // public
index 6ef438d58b39d9cc6302ddf5fe0ad0212a12441c..1620fb11b23e3cd1263027caee910ee915db6acc 100644 (file)
@@ -51,6 +51,17 @@ typedef std::array<uint8_t, 16> ByteArray16;
  *                isTeredo, isIPv4Mapped, tryCreateIPv4, type
  *
  * @see IPAddress
+ *
+ * Notes on scope ID parsing:
+ *
+ * getaddrinfo() uses if_nametoindex() to convert interface names
+ * into a numerical index. For instance,
+ * "fe80::202:c9ff:fec1:ee08%eth0" may return scope ID 2 on some
+ * hosts, but other numbers on other hosts. It will fail entirely on
+ * hosts without an eth0 interface.
+ *
+ * Serializing / Deserializing IPAddressB6's on different hosts
+ * that use link-local scoping probably won't work.
  */
 class IPAddressV6 : boost::totally_ordered<IPAddressV6> {
  public:
@@ -90,6 +101,7 @@ class IPAddressV6 : boost::totally_ordered<IPAddressV6> {
 
   // Create an IPAddressV6 from a string
   // @throws IPAddressFormatException
+  //
   explicit IPAddressV6(StringPiece ip);
 
   // ByteArray16 constructor
@@ -98,6 +110,9 @@ class IPAddressV6 : boost::totally_ordered<IPAddressV6> {
   // in6_addr constructor
   explicit IPAddressV6(const in6_addr& src);
 
+  // sockaddr_in6 constructor
+  explicit IPAddressV6(const sockaddr_in6& src);
+
   /**
    * Create a link-local IPAddressV6 from the specified ethernet MAC address.
    */
@@ -209,10 +224,16 @@ class IPAddressV6 : boost::totally_ordered<IPAddressV6> {
   // return underlying in6_addr structure
   in6_addr toAddr() const { return addr_.in6Addr_; }
 
+  uint16_t getScopeId() const { return scope_; }
+  void setScopeId(uint16_t scope) {
+    scope_ = scope;
+  }
+
   sockaddr_in6 toSockAddr() const {
     sockaddr_in6 addr;
     memset(&addr, 0, sizeof(sockaddr_in6));
     addr.sin6_family = AF_INET6;
+    addr.sin6_scope_id = scope_;
     memcpy(&addr.sin6_addr, &addr_.in6Addr_, sizeof(in6_addr));
     return addr;
   }
@@ -294,6 +315,10 @@ class IPAddressV6 : boost::totally_ordered<IPAddressV6> {
     explicit AddressStorage(MacAddress mac);
   } addr_;
 
+  // Link-local scope id.  This should always be 0 for IPAddresses that
+  // are *not* link-local.
+  uint16_t scope_{0};
+
   static const std::array<ByteArray16, 129> masks_;
 
   /**
@@ -315,11 +340,18 @@ void toAppend(IPAddressV6 addr, fbstring* result);
  * Return true if two addresses are equal.
  */
 inline bool operator==(const IPAddressV6& addr1, const IPAddressV6& addr2) {
-  return (std::memcmp(addr1.toAddr().s6_addr, addr2.toAddr().s6_addr, 16) == 0);
+  return (std::memcmp(addr1.toAddr().s6_addr, addr2.toAddr().s6_addr, 16) == 0)
+    && addr1.getScopeId() == addr2.getScopeId();
 }
 // Return true if addr1 < addr2
 inline bool operator<(const IPAddressV6& addr1, const IPAddressV6& addr2) {
-  return (std::memcmp(addr1.toAddr().s6_addr, addr2.toAddr().s6_addr, 16) < 0);
+  auto cmp = std::memcmp(addr1.toAddr().s6_addr,
+                         addr2.toAddr().s6_addr, 16) < 0;
+  if (!cmp) {
+    return addr1.getScopeId() < addr2.getScopeId();
+  } else {
+    return cmp;
+  }
 }
 
 }  // folly
index 18d53a646f5ac14ff13385cda9afb7f1793ae6bd..ba43bf0697b73de6e293685d26bcda2349f15565 100644 (file)
@@ -40,6 +40,7 @@ extern "C" {
 #endif
 
 #include <sys/types.h>
+#include <netdb.h>
 }
 
 #include <folly/Conv.h>
index 952cf4aac902958cd2e688d7ac448e067a03bbb3..bdda3663d3a8e75d89e5d3d39146a505874b355c 100644 (file)
@@ -29,8 +29,8 @@ using namespace std;
 // tests code example
 TEST(IPAddress, CodeExample) {
   EXPECT_EQ(4, sizeof(IPAddressV4));
-  EXPECT_EQ(16, sizeof(IPAddressV6));
-  EXPECT_EQ(20, sizeof(IPAddress));
+  EXPECT_EQ(20, sizeof(IPAddressV6));
+  EXPECT_EQ(24, sizeof(IPAddress));
   IPAddress uninitaddr;
   IPAddress v4addr("192.0.2.129");
   IPAddress v6map("::ffff:192.0.2.129");
@@ -53,6 +53,24 @@ TEST(IPAddress, CodeExample) {
   EXPECT_TRUE(IPAddress::createIPv6(v4addr) == v6map.asV6());
 }
 
+TEST(IPAddress, Scope) {
+  // Test that link-local scope is saved
+  auto str = "fe80::62eb:69ff:fe9b:ba60%eth0";
+  IPAddressV6 a2(str);
+  EXPECT_EQ(str, a2.str());
+
+  sockaddr_in6 sock = a2.toSockAddr();
+  EXPECT_NE(0, sock.sin6_scope_id);
+
+  IPAddress a1(str);
+  EXPECT_EQ(str, a1.str());
+
+  a2.setScopeId(0);
+  EXPECT_NE(a1, a2);
+
+  EXPECT_TRUE(a2 < a1);
+}
+
 TEST(IPAddress, Ordering) {
   IPAddress a1("0.1.1.1");
   IPAddress a2("1.1.1.0");
@@ -272,6 +290,7 @@ TEST(IPAddress, CtorSockaddr) {
   {
     // setup
     sockaddr_in6 addr;
+    memset(&addr, 0, sizeof(addr));
     in6_addr sin_addr;
     ByteArray16 sec{{
       // 2620:0:1cfe:face:b00c::3