Implemented IPAddressV6::getMacAddressFromLinkLocal
authorAngelo Failla <pallotron@fb.com>
Fri, 27 Jan 2017 08:35:27 +0000 (00:35 -0800)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Fri, 27 Jan 2017 08:47:57 +0000 (00:47 -0800)
Summary: It is possible to extract a MAC address from a link-local IPv6 address. This diff provides the logic for it.

Reviewed By: simpkins

Differential Revision: D4461970

fbshipit-source-id: cb4a5d774c3b4a20716d1742c961e02952ddf80d

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

index 9a7758c0e095bbbd66723fd7c0e72df83185ec79..5607a4bfcb37e61eef7d7f7b2e177a9594a4426a 100644 (file)
@@ -136,6 +136,32 @@ IPAddressV6::AddressStorage::AddressStorage(MacAddress mac) {
   bytes_[15] = macBytes[5];
 }
 
+Optional<MacAddress> IPAddressV6::getMacAddressFromLinkLocal() const {
+  // Returned MacAddress must be constructed from a link-local IPv6 address.
+  if (!(addr_.bytes_[0] == 0xfe && addr_.bytes_[1] == 0x80 &&
+        addr_.bytes_[2] == 0x00 && addr_.bytes_[3] == 0x00 &&
+        addr_.bytes_[4] == 0x00 && addr_.bytes_[5] == 0x00 &&
+        addr_.bytes_[6] == 0x00 && addr_.bytes_[7] == 0x00 &&
+        addr_.bytes_[11] == 0xff && addr_.bytes_[12] == 0xfe)) {
+    return folly::none;
+  }
+  // The link-local address uses modified EUI-64 format,
+  // See RFC 4291 sections 2.5.1, 2.5.6, and Appendix A
+  std::array<uint8_t, MacAddress::SIZE> bytes;
+  // Step 1: first 8 bytes are fe:80:00:00:00:00:00:00, and can be stripped
+  // Step 2: invert the universal/local (U/L) flag (bit 7)
+  bytes[0] = addr_.bytes_[8] ^ 0x02;
+  // Step 3: copy thhese bytes are they are
+  bytes[1] = addr_.bytes_[9];
+  bytes[2] = addr_.bytes_[10];
+  // Step 4: strip bytes (0xfffe), which are bytes_[11] and bytes_[12]
+  // Step 5: copy the rest.
+  bytes[3] = addr_.bytes_[13];
+  bytes[4] = addr_.bytes_[14];
+  bytes[5] = addr_.bytes_[15];
+  return Optional<MacAddress>(MacAddress::fromBinary(range(bytes)));
+}
+
 void IPAddressV6::setFromBinary(ByteRange bytes) {
   if (bytes.size() != 16) {
     throw IPAddressFormatException(to<std::string>(
index 8e29f80489b8737ffbd3b450e8ac1a8da6b11a77..a1d0c7cbd3a9cd20d1e5cca951dccd64119fc939 100644 (file)
@@ -25,6 +25,7 @@
 #include <stdexcept>
 
 #include <folly/Hash.h>
+#include <folly/Optional.h>
 #include <folly/Range.h>
 #include <folly/detail/IPAddress.h>
 
@@ -209,6 +210,16 @@ class IPAddressV6 {
    */
   bool isLinkLocal() const;
 
+  /**
+   * Return the mac address if this is a link-local IPv6 address.
+   *
+   * @return an Optional<MacAddress> union representing the mac address.
+   *
+   * If the address is not a link-local one it will return an empty Optional.
+   * You can use Optional::value() to check whether the mac address is not null.
+   */
+  Optional<MacAddress> getMacAddressFromLinkLocal() const;
+
   /**
    * Return true if this is a multicast address.
    */
index 7fcd10d1c9d94437074a15bf35d8d226751b725b..12a588b1ddeb89975ab3ae9ec7c530daf1e6913b 100644 (file)
@@ -906,6 +906,23 @@ TEST(IPAddress, StringFormat) {
   EXPECT_EQ("1.2.3.4", detail::fastIpv4ToString(a4));
 }
 
+TEST(IPAddress, getMacAddressFromLinkLocal) {
+  IPAddressV6 ip6("fe80::f652:14ff:fec5:74d8");
+  EXPECT_TRUE(ip6.getMacAddressFromLinkLocal().hasValue());
+  EXPECT_EQ("f4:52:14:c5:74:d8", ip6.getMacAddressFromLinkLocal()->toString());
+}
+
+TEST(IPAddress, getMacAddressFromLinkLocal_Negative) {
+  IPAddressV6 no_link_local_ip6("2803:6082:a2:4447::1");
+  EXPECT_FALSE(no_link_local_ip6.getMacAddressFromLinkLocal().hasValue());
+  no_link_local_ip6 = IPAddressV6("fe80::f652:14ff:ccc5:74d8");
+  EXPECT_FALSE(no_link_local_ip6.getMacAddressFromLinkLocal().hasValue());
+  no_link_local_ip6 = IPAddressV6("fe80::f652:14ff:ffc5:74d8");
+  EXPECT_FALSE(no_link_local_ip6.getMacAddressFromLinkLocal().hasValue());
+  no_link_local_ip6 = IPAddressV6("fe81::f652:14ff:ffc5:74d8");
+  EXPECT_FALSE(no_link_local_ip6.getMacAddressFromLinkLocal().hasValue());
+}
+
 TEST(IPAddress, LongestCommonPrefix) {
   IPAddress ip10("10.0.0.0");
   IPAddress ip11("11.0.0.0");