shill: support of netlink RDNSS message.

Added support to subscribe/parse netlink RDNSS messages.

BUG=chromium:394010
TEST=unit tests, manual test
1. Update code to log the lifetime and DNS server IP addresses from RDNSS
   message.
2. Run network_Ipv6SimpleNegotiation on the DUT.
3. Verify the lifetime and DNS server IP addresses in net.log.

Change-Id: I64ab4de14eaf8fe2ca8b9715adb8dc07511b98ff
Reviewed-on: https://chromium-review.googlesource.com/209913
Reviewed-by: Paul Stewart <pstew@chromium.org>
Commit-Queue: Peter Qiu <zqiu@chromium.org>
Tested-by: Peter Qiu <zqiu@chromium.org>
diff --git a/ndisc.h b/ndisc.h
new file mode 100644
index 0000000..2d7c22a
--- /dev/null
+++ b/ndisc.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHILL_NDISC_H_
+#define SHILL_NDISC_H_
+
+// Neighbor discovery related definitions. This is needed because kernel
+// currently does not export these definitions to the user space.
+
+// Netlink multicast group for neighbor discovery user option message.
+#define RTMGRP_ND_USEROPT 0x80000
+
+// Neighbor Discovery user option header definition.
+struct NDUserOptionHeader {
+  NDUserOptionHeader() {
+    memset(this, 0, sizeof(*this));
+  }
+  uint8 type;
+  uint8 length;
+  uint16 reserved;
+  uint32 lifetime;
+} __attribute__((__packed__));
+
+// Neighbor Discovery user option type definition.
+#define ND_OPT_RDNSS 25       /* RFC 5006 */
+#define ND_OPT_DNSSL 31       /* RFC 6106 */
+
+#endif  // SHILL_NDISC_H_
diff --git a/rtnl_handler.cc b/rtnl_handler.cc
index 77704f5..389582b 100644
--- a/rtnl_handler.cc
+++ b/rtnl_handler.cc
@@ -24,6 +24,7 @@
 #include "shill/ip_address.h"
 #include "shill/ipconfig.h"
 #include "shill/logging.h"
+#include "shill/ndisc.h"
 #include "shill/rtnl_handler.h"
 #include "shill/rtnl_listener.h"
 #include "shill/rtnl_message.h"
@@ -82,7 +83,7 @@
   memset(&addr, 0, sizeof(addr));
   addr.nl_family = AF_NETLINK;
   addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE |
-      RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE;
+      RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE | RTMGRP_ND_USEROPT;
 
   if (sockets->Bind(rtnl_socket_,
                     reinterpret_cast<struct sockaddr *>(&addr),
@@ -275,6 +276,12 @@
         case RTNLMessage::kTypeRoute:
           DispatchEvent(kRequestRoute, msg);
           break;
+        case RTNLMessage::kTypeRdnss:
+          DispatchEvent(kRequestRdnss, msg);
+          break;
+        case RTNLMessage::kTypeDnssl:
+          NOTIMPLEMENTED();
+          break;
         default:
           NOTIMPLEMENTED() << "Unknown RTNL message type.";
       }
diff --git a/rtnl_handler.h b/rtnl_handler.h
index 64fbb76..fec5d24 100644
--- a/rtnl_handler.h
+++ b/rtnl_handler.h
@@ -40,9 +40,11 @@
 // state.
 class RTNLHandler {
  public:
+  // Request mask.
   static const int kRequestLink = 1;
   static const int kRequestAddr = 2;
   static const int kRequestRoute = 4;
+  static const int kRequestRdnss = 8;
 
   virtual ~RTNLHandler();
 
diff --git a/rtnl_message.cc b/rtnl_message.cc
index feaa305..2f3f81a 100644
--- a/rtnl_message.cc
+++ b/rtnl_message.cc
@@ -6,9 +6,11 @@
 
 #include <linux/netlink.h>
 #include <linux/rtnetlink.h>
+#include <netinet/in.h>
 #include <sys/socket.h>
 
 #include "shill/logging.h"
+#include "shill/ndisc.h"
 
 namespace shill {
 
@@ -22,6 +24,7 @@
     struct ifaddrmsg ifa;
     struct rtmsg rtm;
     struct rtgenmsg gen;
+    struct nduseroptmsg nd_user_opt;
   };
 };
 
@@ -70,6 +73,7 @@
   case RTM_NEWLINK:
   case RTM_NEWADDR:
   case RTM_NEWROUTE:
+  case RTM_NEWNDUSEROPT:
     mode = kModeAdd;
     break;
 
@@ -105,6 +109,11 @@
       return false;
     break;
 
+  case RTM_NEWNDUSEROPT:
+    if (!DecodeNdUserOption(hdr, mode, &attr_data, &attr_length))
+      return false;
+    break;
+
   default:
     NOTREACHED();
   }
@@ -194,6 +203,82 @@
   return true;
 }
 
+bool RTNLMessage::DecodeNdUserOption(const RTNLHeader *hdr,
+                                     Mode mode,
+                                     rtattr **attr_data,
+                                     int *attr_length) {
+  if (hdr->hdr.nlmsg_len < NLMSG_LENGTH(sizeof(hdr->nd_user_opt))) {
+    return false;
+  }
+
+  mode_ = mode;
+  interface_index_ = hdr->nd_user_opt.nduseropt_ifindex;
+  family_ = hdr->nd_user_opt.nduseropt_family;
+
+  // Verify IP family.
+  if (family_ != IPAddress::kFamilyIPv6) {
+    return false;
+  }
+  // Verify message must at-least contain the option header.
+  if (hdr->nd_user_opt.nduseropt_opts_len < sizeof(NDUserOptionHeader)) {
+    return false;
+  }
+
+  // Parse the option header.
+  const NDUserOptionHeader *nd_user_option_header =
+      reinterpret_cast<const NDUserOptionHeader *>(
+          reinterpret_cast<const uint8 *>(&hdr->nd_user_opt) +
+          sizeof(struct nduseroptmsg));
+  uint32 lifetime = ntohl(nd_user_option_header->lifetime);
+
+  // Verify option length.
+  // The length field in the header is in units of 8 octets.
+  int opt_len = static_cast<int>(nd_user_option_header->length) * 8;
+  if (opt_len != hdr->nd_user_opt.nduseropt_opts_len) {
+    return false;
+  }
+
+  // Determine option data pointer and data length.
+  const uint8 *option_data =
+      reinterpret_cast<const uint8 *>(nd_user_option_header + 1);
+  int data_len = opt_len - sizeof(NDUserOptionHeader);
+
+  if (nd_user_option_header->type == ND_OPT_DNSSL) {
+    // TODO(zqiu): Parse DNSSL (DNS Search List) option.
+    type_ = kTypeDnssl;
+    return true;
+  } else if (nd_user_option_header->type == ND_OPT_RDNSS) {
+    // Parse RNDSS (Recursive DNS Server) option.
+    type_ = kTypeRdnss;
+    return ParseRdnssOption(option_data, data_len, lifetime);
+  }
+
+  return false;
+}
+
+bool RTNLMessage::ParseRdnssOption(const uint8 *data,
+                                   int length,
+                                   uint32 lifetime) {
+  const int addr_length = IPAddress::GetAddressLength(IPAddress::kFamilyIPv6);
+
+  // Verify data size are multiple of individual address size.
+  if (length % addr_length != 0) {
+    return false;
+  }
+
+  // Parse the DNS server addresses.
+  std::vector<IPAddress> dns_server_addresses;
+  while (length > 0) {
+    dns_server_addresses.push_back(
+        IPAddress(IPAddress::kFamilyIPv6,
+                  ByteString(data, addr_length)));
+    length -= addr_length;
+    data += addr_length;
+  }
+  set_rdnss_option(RdnssOption(lifetime, dns_server_addresses));
+  return true;
+}
+
 ByteString RTNLMessage::Encode() const {
   if (type_ != kTypeLink &&
       type_ != kTypeAddress &&
diff --git a/rtnl_message.h b/rtnl_message.h
index ad18465..1d7ca57 100644
--- a/rtnl_message.h
+++ b/rtnl_message.h
@@ -6,6 +6,7 @@
 #define SHILL_RTNL_MESSAGE_H_
 
 #include <unordered_map>
+#include <vector>
 
 #include <base/basictypes.h>
 #include <base/stl_util.h>
@@ -25,7 +26,9 @@
     kTypeUnknown,
     kTypeLink,
     kTypeAddress,
-    kTypeRoute
+    kTypeRoute,
+    kTypeRdnss,
+    kTypeDnssl
   };
 
   enum Mode {
@@ -100,6 +103,17 @@
     unsigned char flags;
   };
 
+  struct RdnssOption {
+    RdnssOption()
+        : lifetime(0) {}
+    RdnssOption(uint32 lifetime_in,
+                std::vector<IPAddress> addresses_in)
+        : lifetime(lifetime_in),
+          addresses(addresses_in) {}
+    uint32 lifetime;
+    std::vector<IPAddress> addresses;
+  };
+
   // Empty constructor
   RTNLMessage();
   // Build an RTNL message from arguments
@@ -140,6 +154,10 @@
   void set_route_status(const RouteStatus &route_status) {
     route_status_ = route_status;
   }
+  const RdnssOption &rdnss_option() const { return rdnss_option_; }
+  void set_rdnss_option(const RdnssOption &rdnss_option) {
+    rdnss_option_ = rdnss_option;
+  }
   // GLint hates "unsigned short", and I don't blame it, but that's the
   // type that's used in the system headers.  Use uint16 instead and hope
   // that the conversion never ends up truncating on some strange platform.
@@ -168,6 +186,13 @@
                     Mode mode,
                     rtattr **attr_data,
                     int *attr_length);
+  bool DecodeNdUserOption(const RTNLHeader *hdr,
+                       Mode mode,
+                       rtattr **attr_data,
+                       int *attr_length);
+  bool ParseRdnssOption(const uint8 *data,
+                        int length,
+                        uint32 lifetime);
   bool EncodeLink(RTNLHeader *hdr) const;
   bool EncodeAddress(RTNLHeader *hdr) const;
   bool EncodeRoute(RTNLHeader *hdr) const;
@@ -182,6 +207,7 @@
   LinkStatus link_status_;
   AddressStatus address_status_;
   RouteStatus route_status_;
+  RdnssOption rdnss_option_;
   std::unordered_map<uint16, ByteString> attributes_;
 
   DISALLOW_COPY_AND_ASSIGN(RTNLMessage);
diff --git a/rtnl_message_unittest.cc b/rtnl_message_unittest.cc
index 0318bb5..ab043bd 100644
--- a/rtnl_message_unittest.cc
+++ b/rtnl_message_unittest.cc
@@ -349,6 +349,24 @@
   0x04, 0x00, 0x00, 0x00,
 };
 
+// RDNSS notification
+// Lifetime: infinity (0xffffffff)
+// Server addresses: 2001:db8:100:f101::1, 2001:db8:100:f101::2
+const unsigned char kNdRdnssMessage[] = {
+  0x5c, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x0a, 0x00, 0x28, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x86, 0x00, 0x03, 0x00, 0x14, 0x00, 0x01, 0x00,
+  0x19, 0x05, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
+  0x20, 0x01, 0x0d, 0xb8, 0x01, 0x00, 0xf1, 0x01,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+  0x20, 0x01, 0x0d, 0xb8, 0x01, 0x00, 0xf1, 0x01,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+  0x14, 0x00, 0x01, 0x00, 0xfe, 0x80, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x50, 0xf8, 0x86, 0xff,
+};
+
 }  // namespace
 
 class RTNLMessageTest : public Test {
@@ -482,6 +500,37 @@
       EXPECT_FALSE(msg.HasAttribute(RTA_PRIORITY));
     }
   }
+
+  void TestParseRdnss(const ByteString &packet,
+                      RTNLMessage::Mode mode,
+                      int interface_index,
+                      uint32 lifetime,
+                      const std::string &dns_server_addresses) {
+    RTNLMessage msg;
+
+    EXPECT_TRUE(msg.Decode(packet));
+    EXPECT_EQ(RTNLMessage::kTypeRdnss, msg.type());
+    EXPECT_EQ(mode, msg.mode());
+    EXPECT_EQ(interface_index, msg.interface_index());
+
+    RTNLMessage::RdnssOption rdnss = msg.rdnss_option();
+
+    // Format addresses string for verification.
+    std::string addresses;
+    bool first = true;
+    for (auto &ip : rdnss.addresses) {
+      if (!first) {
+        addresses += ", ";
+      } else {
+        first = false;
+      }
+      addresses += ip.ToString();
+    }
+
+    // Verify life time and addresses.
+    EXPECT_EQ(lifetime, rdnss.lifetime);
+    EXPECT_EQ(dns_server_addresses, addresses);
+  }
 };
 
 TEST_F(RTNLMessageTest, NewLinkWlan0) {
@@ -581,6 +630,19 @@
                  kAddRouteIPV4Metric);
 }
 
+TEST_F(RTNLMessageTest, NewRdnssOption) {
+  int interface_index = 1;
+  uint32 lifetime = 0xffffffff;
+  std::string dns_server_addresses =
+      "2001:db8:100:f101::1, 2001:db8:100:f101::2";
+
+  TestParseRdnss(ByteString(kNdRdnssMessage, sizeof(kNdRdnssMessage)),
+                 RTNLMessage::kModeAdd,
+                 interface_index,
+                 lifetime,
+                 dns_server_addresses);
+}
+
 TEST_F(RTNLMessageTest, AddRouteBusted) {
   // RTNLMessage should list parse errors as kMessageUnknown
   RTNLMessage msg;