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;