shill: Add routing table RTNL message handler

Maintain a copy of the routing table state.  This achieved by
a singleton that maintains an in-process copy of the routing
table on a per-interface basis.  It offers the ability for
other modules to make modifications to the routing table,
centered around setting the default route for an interface or
modifying its metric (priority), but also providing more
granular control for services/devices that may need more control,
like VPN.

BUG=chromium-os:17277
TEST=New unittest

Change-Id: Ifcda0ab2cb8775bca677979ca98ac3fcf7adfdde
Reviewed-on: http://gerrit.chromium.org/gerrit/5163
Reviewed-by: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/Makefile b/Makefile
index 925b130..fd510a9 100644
--- a/Makefile
+++ b/Makefile
@@ -112,6 +112,7 @@
 	profile_dbus_adaptor.o \
 	property_store.o \
 	proxy_factory.o \
+	routing_table.o \
 	rtnl_handler.o \
 	rtnl_listener.o \
 	rtnl_message.o \
@@ -160,6 +161,7 @@
 	profile_unittest.o \
 	property_accessor_unittest.o \
 	property_store_unittest.o \
+	routing_table_unittest.o \
 	rtnl_handler_unittest.o \
 	rtnl_listener_unittest.o \
 	rtnl_message_unittest.o \
diff --git a/device_info_unittest.cc b/device_info_unittest.cc
index 7eac572..5f3fa76 100644
--- a/device_info_unittest.cc
+++ b/device_info_unittest.cc
@@ -140,9 +140,11 @@
 
 TEST_F(DeviceInfoTest, DeviceEnumerationReverse) {
   // Start our own private device_info _after_ RTNLHandler has been started
-  StartRTNLHandler();
+  // TODO(pstew): Find out why this EXPECT_CALL needed to be moved above
+  //              StartRTNLHandler()
   EXPECT_CALL(sockets_, SendTo(kTestSocket, _, _, 0, _, sizeof(sockaddr_nl)))
       .WillOnce(Return(0));
+  StartRTNLHandler();
   device_info_.Start();
 
   AddLink();
diff --git a/ip_address.h b/ip_address.h
index f931aee..7ff79fe 100644
--- a/ip_address.h
+++ b/ip_address.h
@@ -49,6 +49,11 @@
     return family_ == b.family_ && address_.Equals(b.address_);
   }
 
+  void Clone(const IPAddress &b) {
+    family_ = b.family_;
+    address_ = b.address_;
+  }
+
  private:
   Family family_;
   ByteString address_;
diff --git a/ipconfig.h b/ipconfig.h
index de4f32b..e892c3d 100644
--- a/ipconfig.h
+++ b/ipconfig.h
@@ -28,7 +28,6 @@
 // class.
 class IPConfig : public base::RefCounted<IPConfig> {
  public:
-
   struct Properties {
     Properties() : address_family(IPAddress::kAddressFamilyUnknown),
                    subnet_cidr(0),
@@ -97,6 +96,7 @@
   FRIEND_TEST(DeviceTest, DestroyIPConfig);
   FRIEND_TEST(IPConfigTest, UpdateCallback);
   FRIEND_TEST(IPConfigTest, UpdateProperties);
+  FRIEND_TEST(RoutingTableTest, RouteAddDelete);
 
   static const char kStorageType[];
   static const char kType[];
diff --git a/routing_table.cc b/routing_table.cc
new file mode 100644
index 0000000..a058b0f
--- /dev/null
+++ b/routing_table.cc
@@ -0,0 +1,337 @@
+// Copyright (c) 2011 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.
+
+#include "shill/routing_table.h"
+
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <netinet/ether.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <base/callback_old.h>
+#include <base/file_path.h>
+#include <base/file_util.h>
+#include <base/hash_tables.h>
+#include <base/logging.h>
+#include <base/memory/scoped_ptr.h>
+#include <base/stl_util-inl.h>
+#include <base/stringprintf.h>
+
+#include "shill/byte_string.h"
+#include "shill/routing_table_entry.h"
+#include "shill/rtnl_handler.h"
+#include "shill/rtnl_listener.h"
+#include "shill/rtnl_message.h"
+
+using std::string;
+using std::vector;
+
+namespace shill {
+
+// static
+const char RoutingTable::kRouteFlushPath4[] = "/proc/sys/net/ipv4/route/flush";
+const char RoutingTable::kRouteFlushPath6[] = "/proc/sys/net/ipv6/route/flush";
+
+RoutingTable::RoutingTable()
+    : route_callback_(NewCallback(this, &RoutingTable::RouteMsgHandler)),
+      route_listener_(NULL) {
+  VLOG(2) << __func__;
+}
+
+RoutingTable::~RoutingTable() {}
+
+RoutingTable* RoutingTable::GetInstance() {
+  return Singleton<RoutingTable>::get();
+}
+
+void RoutingTable::Start() {
+  VLOG(2) << __func__;
+
+  route_listener_.reset(
+      new RTNLListener(RTNLHandler::kRequestRoute, route_callback_.get()));
+  RTNLHandler::GetInstance()->RequestDump(
+      RTNLHandler::kRequestRoute | RTNLHandler::kRequestRoute6);
+}
+
+void RoutingTable::Stop() {
+  VLOG(2) << __func__;
+
+  route_listener_.reset();
+}
+
+bool RoutingTable::AddRoute(int interface_index,
+                            const RoutingTableEntry &entry) {
+  VLOG(2) << __func__;
+
+  CHECK(!entry.from_rtnl);
+  if (!ApplyRoute(interface_index,
+                  entry,
+                  RTNLMessage::kMessageModeAdd,
+                  NLM_F_CREATE | NLM_F_EXCL)) {
+    return false;
+  }
+  tables_[interface_index].push_back(entry);
+  return true;
+}
+
+bool RoutingTable::GetDefaultRoute(int interface_index,
+                                   IPAddress::Family family,
+                                   RoutingTableEntry *entry) {
+  VLOG(2) << __func__;
+
+  base::hash_map<int, vector<RoutingTableEntry> >::iterator table =
+    tables_.find(interface_index);
+
+  if (table == tables_.end()) {
+    return false;
+  }
+
+  vector<RoutingTableEntry>::iterator nent;
+
+  for (nent = table->second.begin(); nent != table->second.end(); ++nent) {
+    if (nent->dst.IsDefault() && nent->dst.family() == family) {
+      *entry = *nent;
+      return true;
+    }
+  }
+
+  return false;
+}
+
+bool RoutingTable::SetDefaultRoute(int interface_index,
+                                   const IPConfigRefPtr &ipconfig,
+                                   uint32 metric) {
+  const IPConfig::Properties &ipconfig_props = ipconfig->properties();
+  RoutingTableEntry old_entry;
+
+  VLOG(2) << __func__;
+
+  IPAddress gateway_address(ipconfig_props.address_family);
+  if (!gateway_address.SetAddressFromString(ipconfig_props.gateway)) {
+    return false;
+  }
+
+  if (GetDefaultRoute(interface_index,
+                      ipconfig_props.address_family,
+                      &old_entry)) {
+    if (old_entry.gateway.Equals(gateway_address)) {
+      if (old_entry.metric != metric) {
+        old_entry.metric = metric;
+        ApplyRoute(interface_index, old_entry, RTNLMessage::kMessageModeAdd,
+                   NLM_F_CREATE | NLM_F_REPLACE);
+      }
+      return true;
+    } else {
+      ApplyRoute(interface_index,
+                 old_entry,
+                 RTNLMessage::kMessageModeDelete,
+                 0);
+    }
+  }
+
+  IPAddress default_address(ipconfig_props.address_family);
+  default_address.SetAddressToDefault();
+
+  return AddRoute(interface_index,
+                  RoutingTableEntry(default_address,
+                                    0,
+                                    default_address,
+                                    0,
+                                    gateway_address,
+                                    metric,
+                                    RT_SCOPE_UNIVERSE,
+                                    false));
+}
+
+void RoutingTable::FlushRoutes(int interface_index) {
+  VLOG(2) << __func__;
+
+  base::hash_map<int, vector<RoutingTableEntry> >::iterator table =
+    tables_.find(interface_index);
+
+  if (table == tables_.end()) {
+    return;
+  }
+
+  vector<RoutingTableEntry>::iterator nent;
+
+  for (nent = table->second.begin(); nent != table->second.end(); ++nent) {
+    ApplyRoute(interface_index, *nent, RTNLMessage::kMessageModeDelete, 0);
+  }
+}
+
+void RoutingTable::ResetTable(int interface_index) {
+  tables_.erase(interface_index);
+}
+
+void RoutingTable::SetDefaultMetric(int interface_index, uint32 metric) {
+  RoutingTableEntry entry;
+
+  VLOG(2) << __func__;
+
+  if (GetDefaultRoute(interface_index, IPAddress::kAddressFamilyIPv4, &entry) &&
+      entry.metric != metric) {
+    entry.metric = metric;
+    ApplyRoute(interface_index, entry, RTNLMessage::kMessageModeAdd,
+               NLM_F_CREATE | NLM_F_REPLACE);
+  }
+
+  if (GetDefaultRoute(interface_index, IPAddress::kAddressFamilyIPv6, &entry) &&
+      entry.metric != metric) {
+    entry.metric = metric;
+    ApplyRoute(interface_index, entry, RTNLMessage::kMessageModeAdd,
+               NLM_F_CREATE | NLM_F_REPLACE);
+  }
+}
+
+void RoutingTable::RouteMsgHandler(struct nlmsghdr *hdr) {
+  RTNLMessage msg;
+
+  // TODO(pstew): When RTNLHandler starts using RTNLMessages, this goes away
+  if (!msg.Decode(ByteString(reinterpret_cast<unsigned char *>(hdr),
+                             hdr->nlmsg_len))) {
+    return;
+  }
+
+  VLOG(2) << __func__;
+
+  if (msg.type() != RTNLMessage::kMessageTypeRoute ||
+      msg.family() == IPAddress::kAddressFamilyUnknown ||
+      !msg.HasAttribute(RTA_OIF)) {
+    return;
+  }
+
+  const RTNLMessage::RouteStatus &route_status = msg.route_status();
+
+  if (route_status.type != RTN_UNICAST ||
+      route_status.protocol != RTPROT_BOOT ||
+      route_status.table != RT_TABLE_MAIN) {
+    return;
+  }
+
+  uint32 interface_index = 0;
+  if (!msg.GetAttribute(RTA_OIF).ConvertToCPUUInt32(&interface_index)) {
+    return;
+  }
+
+  uint32 metric = 0;
+  if (msg.HasAttribute(RTA_PRIORITY)) {
+    msg.GetAttribute(RTA_PRIORITY).ConvertToCPUUInt32(&metric);
+  }
+
+  IPAddress default_addr(msg.family());
+  default_addr.SetAddressToDefault();
+
+  ByteString dst_bytes(default_addr.address());
+  if (msg.HasAttribute(RTA_DST)) {
+    dst_bytes = msg.GetAttribute(RTA_DST);
+  }
+  ByteString src_bytes(default_addr.address());
+  if (msg.HasAttribute(RTA_SRC)) {
+    src_bytes = msg.GetAttribute(RTA_SRC);
+  }
+  ByteString gateway_bytes(default_addr.address());
+  if (msg.HasAttribute(RTA_GATEWAY)) {
+    gateway_bytes = msg.GetAttribute(RTA_GATEWAY);
+  }
+
+  RoutingTableEntry entry(
+      IPAddress(msg.family(), dst_bytes),
+      route_status.dst_prefix,
+      IPAddress(msg.family(), src_bytes),
+      route_status.src_prefix,
+      IPAddress(msg.family(), gateway_bytes),
+      metric,
+      route_status.scope,
+      true);
+
+  vector<RoutingTableEntry> &table = tables_[interface_index];
+  vector<RoutingTableEntry>::iterator nent;
+  for (nent = table.begin(); nent != table.end(); ++nent) {
+    if (nent->dst.Equals(entry.dst) &&
+        nent->dst_prefix == entry.dst_prefix &&
+        nent->src.Equals(entry.src) &&
+        nent->src_prefix == entry.src_prefix &&
+        nent->gateway.Equals(entry.gateway) &&
+        nent->scope == entry.scope) {
+      if (msg.mode() == RTNLMessage::kMessageModeDelete) {
+        table.erase(nent);
+      } else {
+        nent->from_rtnl = true;
+        nent->metric = entry.metric;
+      }
+      return;
+    }
+  }
+
+  if (msg.mode() == RTNLMessage::kMessageModeAdd) {
+    table.push_back(entry);
+  }
+}
+
+bool RoutingTable::ApplyRoute(uint32 interface_index,
+                              const RoutingTableEntry &entry,
+                              RTNLMessage::MessageMode mode,
+                              unsigned int flags) {
+  VLOG(2) << base::StringPrintf("%s: index %d mode %d flags 0x%x",
+                                __func__, interface_index, mode, flags);
+
+  RTNLMessage msg(
+      RTNLMessage::kMessageTypeRoute,
+      mode,
+      flags,
+      0,
+      0,
+      0,
+      entry.dst.family());
+
+  msg.set_route_status(RTNLMessage::RouteStatus(
+      entry.dst_prefix,
+      entry.src_prefix,
+      RT_TABLE_MAIN,
+      RTPROT_BOOT,
+      entry.scope,
+      RTN_UNICAST,
+      0));
+
+  msg.SetAttribute(RTA_DST, entry.dst.address());
+  if (!entry.src.IsDefault()) {
+    msg.SetAttribute(RTA_SRC, entry.src.address());
+  }
+  if (!entry.gateway.IsDefault()) {
+    msg.SetAttribute(RTA_GATEWAY, entry.gateway.address());
+  }
+  msg.SetAttribute(RTA_PRIORITY, ByteString::CreateFromCPUUInt32(entry.metric));
+  msg.SetAttribute(RTA_OIF, ByteString::CreateFromCPUUInt32(interface_index));
+
+  return RTNLHandler::GetInstance()->SendMessage(&msg);
+}
+
+bool RoutingTable::FlushCache() {
+  static const char *kPaths[2] = { kRouteFlushPath4, kRouteFlushPath6 };
+  bool ret = true;
+
+  VLOG(2) << __func__;
+
+  for (size_t i = 0; i < arraysize(kPaths); ++i) {
+    if (file_util::WriteFile(FilePath(kPaths[i]), "-1", 2) != 2) {
+      LOG(ERROR) << base::StringPrintf("Cannot write to route flush file %s",
+                                       kPaths[i]);
+      ret = false;
+    }
+  }
+
+  return ret;
+}
+
+}  // namespace shill
diff --git a/routing_table.h b/routing_table.h
new file mode 100644
index 0000000..f754250
--- /dev/null
+++ b/routing_table.h
@@ -0,0 +1,88 @@
+// Copyright (c) 2011 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_ROUTING_TABLE_
+#define SHILL_ROUTING_TABLE_
+
+#include <string>
+
+#include <base/callback_old.h>
+#include <base/hash_tables.h>
+#include <base/memory/ref_counted.h>
+#include <base/memory/singleton.h>
+#include <base/memory/scoped_ptr.h>
+
+#include "shill/ip_address.h"
+#include "shill/refptr_types.h"
+#include "shill/rtnl_message.h"
+
+struct nlmsghdr;
+
+namespace shill {
+
+class RoutingTableEntry;
+class RTNLListener;
+
+// This singleton maintains an in-process copy of the routing table on
+// a per-interface basis.  It offers the ability for other modules to
+// make modifications to the routing table, centered around setting the
+// default route for an interface or modifying its metric (priority).
+class RoutingTable {
+ public:
+  // Since this is a singleton, use RoutingTable::GetInstance()->Foo()
+  static RoutingTable *GetInstance();
+
+  void Start();
+  void Stop();
+
+  // Add an entry to the routing table
+  bool AddRoute(int interface_index, const RoutingTableEntry &entry);
+
+  // Get the default route associated with an interface of a given addr family
+  bool GetDefaultRoute(int interface_index,
+                       IPAddress::Family family,
+                       RoutingTableEntry *entry);
+
+  // Set the default route for an interface, given an ipconfig entry
+  bool SetDefaultRoute(int interface_index,
+                       const IPConfigRefPtr &ipconfig,
+                       uint32 metric);
+
+  // Remove all routes associated with interface
+  void FlushRoutes(int interface_index);
+
+  // Reset local state for this interface
+  void ResetTable(int interface_index);
+
+  // Set the metric (priority) on existing default routes for an interface
+  void SetDefaultMetric(int interface_index, uint32 metric);
+
+ private:
+  friend struct DefaultSingletonTraits<RoutingTable>;
+  friend class RoutingTableTest;
+
+  // Constructor and destructor are private since this is a singleton
+  RoutingTable();
+  ~RoutingTable();
+
+  void RouteMsgHandler(struct nlmsghdr *hdr);
+  bool ApplyRoute(uint32 interface_index,
+                  const RoutingTableEntry &entry,
+                  RTNLMessage::MessageMode mode,
+                  unsigned int flags);
+  bool FlushCache();
+
+  static const char kRouteFlushPath4[];
+  static const char kRouteFlushPath6[];
+
+  base::hash_map<int, std::vector<RoutingTableEntry> > tables_;
+  scoped_ptr<Callback1<struct nlmsghdr *>::Type> route_callback_;
+  scoped_ptr<RTNLListener> route_listener_;
+
+  DISALLOW_COPY_AND_ASSIGN(RoutingTable);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_ROUTING_TABLE_
diff --git a/routing_table_entry.h b/routing_table_entry.h
new file mode 100644
index 0000000..b9d6197
--- /dev/null
+++ b/routing_table_entry.h
@@ -0,0 +1,95 @@
+// Copyright (c) 2011 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_ROUTING_TABLE_ENTRY_
+#define SHILL_ROUTING_TABLE_ENTRY_
+
+#include <base/basictypes.h>
+
+#include "shill/ip_address.h"
+
+namespace shill {
+
+// Holds table entries for routing.  These are held in an STL vector
+// in the RoutingTable object, hence the need for copy contructor and
+// operator=.
+struct RoutingTableEntry {
+ public:
+  RoutingTableEntry()
+      : dst(IPAddress::kAddressFamilyUnknown),
+        dst_prefix(0),
+        src(IPAddress::kAddressFamilyUnknown),
+        src_prefix(0),
+        gateway(IPAddress::kAddressFamilyUnknown),
+        metric(0),
+        scope(0),
+        from_rtnl(false) {}
+
+  RoutingTableEntry(const IPAddress &dst_in,
+                    unsigned char dst_prefix_in,
+                    const IPAddress &src_in,
+                    unsigned char src_prefix_in,
+                    const IPAddress &gateway_in,
+                    uint32 metric_in,
+                    unsigned char scope_in,
+                    bool from_rtnl_in)
+      : dst(dst_in.family(), dst_in.address()),
+        dst_prefix(dst_prefix_in),
+        src(src_in.family(), src_in.address()),
+        src_prefix(src_prefix_in),
+        gateway(gateway_in.family(), gateway_in.address()),
+        metric(metric_in),
+        scope(scope_in),
+        from_rtnl(from_rtnl_in) {}
+
+  RoutingTableEntry(const RoutingTableEntry &b)
+      : dst(b.dst.family(), b.dst.address()),
+        dst_prefix(b.dst_prefix),
+        src(b.src.family(), b.src.address()),
+        src_prefix(b.src_prefix),
+        gateway(b.gateway.family(), b.gateway.address()),
+        metric(b.metric),
+        scope(b.scope),
+        from_rtnl(b.from_rtnl) {}
+
+  RoutingTableEntry &operator=(const RoutingTableEntry &b) {
+    dst.Clone(b.dst);
+    dst_prefix = b.dst_prefix;
+    src.Clone(b.src);
+    src_prefix = b.src_prefix;
+    gateway.Clone(b.gateway);
+    metric = b.metric;
+    scope = b.scope;
+    from_rtnl = b.from_rtnl;
+
+    return *this;
+  }
+
+  ~RoutingTableEntry() {}
+
+  bool Equals(const RoutingTableEntry &b) {
+    return (dst.Equals(b.dst) &&
+            dst_prefix == b.dst_prefix &&
+            src.Equals(b.src) &&
+            src_prefix == b.src_prefix &&
+            gateway.Equals(b.gateway) &&
+            metric == b.metric &&
+            scope == b.scope &&
+            from_rtnl == b.from_rtnl);
+  }
+
+  IPAddress dst;
+  unsigned char dst_prefix;
+  IPAddress src;
+  unsigned char src_prefix;
+  IPAddress gateway;
+  uint32 metric;
+  unsigned char scope;
+  bool from_rtnl;
+};
+
+}  // namespace shill
+
+
+#endif  // SHILL_ROUTING_TABLE_ENTRY_
diff --git a/routing_table_unittest.cc b/routing_table_unittest.cc
new file mode 100644
index 0000000..333b81c
--- /dev/null
+++ b/routing_table_unittest.cc
@@ -0,0 +1,373 @@
+// Copyright (c) 2011 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.
+
+#include <sys/socket.h>
+#include <linux/rtnetlink.h>
+
+#include <base/logging.h>
+#include <base/stl_util-inl.h>
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+#include "shill/byte_string.h"
+#include "shill/mock_control.h"
+#include "shill/mock_sockets.h"
+#include "shill/routing_table.h"
+#include "shill/routing_table_entry.h"
+#include "shill/rtnl_handler.h"
+#include "shill/rtnl_message.h"
+
+using testing::_;
+using testing::Return;
+using testing::Test;
+
+namespace shill {
+
+class TestEventDispatcher : public EventDispatcher {
+ public:
+  virtual IOInputHandler *CreateInputHandler(
+      int fd,
+      Callback1<InputData*>::Type *callback) {
+    return NULL;
+  }
+};
+
+class RoutingTableTest : public Test {
+ public:
+  RoutingTableTest() : routing_table_(RoutingTable::GetInstance()) {}
+
+  base::hash_map<int, std::vector<RoutingTableEntry> > *GetRoutingTables() {
+    return &routing_table_->tables_;
+  }
+
+  void SendRouteMsg(RTNLMessage::MessageMode mode,
+                    uint32 interface_index,
+                    const RoutingTableEntry &entry);
+
+ protected:
+  static const int kTestSocket;
+  static const uint32 kTestDeviceIndex0;
+  static const uint32 kTestDeviceIndex1;
+  static const char kTestDeviceName0[];
+  static const char kTestNetAddress0[];
+  static const char kTestNetAddress1[];
+
+  void StartRTNLHandler();
+  void StopRTNLHandler();
+
+  MockSockets sockets_;
+  RoutingTable *routing_table_;
+  TestEventDispatcher dispatcher_;
+};
+
+const int RoutingTableTest::kTestSocket = 123;
+const uint32 RoutingTableTest::kTestDeviceIndex0 = 12345;
+const uint32 RoutingTableTest::kTestDeviceIndex1 = 67890;
+const char RoutingTableTest::kTestDeviceName0[] = "test-device0";
+const char RoutingTableTest::kTestNetAddress0[] = "192.168.1.1";
+const char RoutingTableTest::kTestNetAddress1[] = "192.168.1.2";
+
+
+MATCHER_P4(IsRoutingPacket, mode, index, entry, flags, "") {
+  // NB: Matchers don't get passed multiple arguments, so we can
+  //     get the address of a Send(), its length, but not both.
+  //     We have to punt and assume the length is correct -- which
+  //     should already be tested in rtnl_message_unittest.
+  struct nlmsghdr hdr;
+  memcpy(&hdr, arg, sizeof(hdr));
+
+  RTNLMessage msg;
+  if (!msg.Decode(ByteString(reinterpret_cast<const unsigned char *>(arg),
+                             hdr.nlmsg_len))) {
+    return false;
+  }
+
+  const RTNLMessage::RouteStatus &status = msg.route_status();
+
+  uint32 oif;
+  uint32 priority;
+
+  return
+    msg.type() == RTNLMessage::kMessageTypeRoute &&
+    msg.family() == entry.gateway.family() &&
+    msg.flags() == flags &&
+    status.table == RT_TABLE_MAIN &&
+    status.protocol == RTPROT_BOOT &&
+    status.scope == entry.scope &&
+    status.type == RTN_UNICAST &&
+    msg.HasAttribute(RTA_DST) &&
+    IPAddress(msg.family(),
+              msg.GetAttribute(RTA_DST)).Equals(entry.dst) &&
+    !msg.HasAttribute(RTA_SRC) &&
+    msg.HasAttribute(RTA_GATEWAY) &&
+    IPAddress(msg.family(),
+              msg.GetAttribute(RTA_GATEWAY)).Equals(entry.gateway) &&
+    msg.GetAttribute(RTA_OIF).ConvertToCPUUInt32(&oif) &&
+    oif == index &&
+    msg.GetAttribute(RTA_PRIORITY).ConvertToCPUUInt32(&priority) &&
+    priority == entry.metric;
+}
+
+void RoutingTableTest::SendRouteMsg(RTNLMessage::MessageMode mode,
+                                    uint32 interface_index,
+                                    const RoutingTableEntry &entry) {
+  RTNLMessage msg(
+      RTNLMessage::kMessageTypeRoute,
+      mode,
+      0,
+      0,
+      0,
+      0,
+      entry.dst.family());
+
+  msg.set_route_status(RTNLMessage::RouteStatus(
+      entry.dst_prefix,
+      entry.src_prefix,
+      RT_TABLE_MAIN,
+      RTPROT_BOOT,
+      entry.scope,
+      RTN_UNICAST,
+      0));
+
+  msg.SetAttribute(RTA_DST, entry.dst.address());
+  if (!entry.src.IsDefault()) {
+    msg.SetAttribute(RTA_SRC, entry.src.address());
+  }
+  if (!entry.gateway.IsDefault()) {
+    msg.SetAttribute(RTA_GATEWAY, entry.gateway.address());
+  }
+  msg.SetAttribute(RTA_PRIORITY, ByteString::CreateFromCPUUInt32(entry.metric));
+  msg.SetAttribute(RTA_OIF, ByteString::CreateFromCPUUInt32(interface_index));
+
+  ByteString msgdata = msg.Encode();
+  EXPECT_NE(0, msgdata.GetLength());
+
+  InputData data(msgdata.GetData(), msgdata.GetLength());
+  RTNLHandler::GetInstance()->ParseRTNL(&data);
+}
+
+void RoutingTableTest::StartRTNLHandler() {
+  EXPECT_CALL(sockets_, Socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE))
+      .WillOnce(Return(kTestSocket));
+  EXPECT_CALL(sockets_, Bind(kTestSocket, _, sizeof(sockaddr_nl)))
+      .WillOnce(Return(0));
+  RTNLHandler::GetInstance()->Start(&dispatcher_, &sockets_);
+}
+
+void RoutingTableTest::StopRTNLHandler() {
+  EXPECT_CALL(sockets_, Close(_)).WillOnce(Return(0));
+  RTNLHandler::GetInstance()->Stop();
+}
+
+TEST_F(RoutingTableTest, RouteAddDelete) {
+  EXPECT_CALL(sockets_, SendTo(kTestSocket, _, _, 0, _, sizeof(sockaddr_nl)));
+  StartRTNLHandler();
+  routing_table_->Start();
+
+  // Expect the tables to be empty by default
+  EXPECT_EQ(0, GetRoutingTables()->size());
+
+  IPAddress default_address(IPAddress::kAddressFamilyIPv4);
+  default_address.SetAddressToDefault();
+
+  IPAddress gateway_address0(IPAddress::kAddressFamilyIPv4);
+  gateway_address0.SetAddressFromString(kTestNetAddress0);
+
+  int metric = 10;
+
+  RoutingTableEntry entry0(default_address,
+                           0,
+                           default_address,
+                           0,
+                           gateway_address0,
+                           metric,
+                           RT_SCOPE_UNIVERSE,
+                           true);
+  // Add a single entry
+  SendRouteMsg(RTNLMessage::kMessageModeAdd,
+               kTestDeviceIndex0,
+               entry0);
+
+  base::hash_map<int, std::vector<RoutingTableEntry> > *tables =
+    GetRoutingTables();
+
+  // Should have a single table, which should in turn have a single entry
+  EXPECT_EQ(1, tables->size());
+  EXPECT_TRUE(ContainsKey(*tables, kTestDeviceIndex0));
+  EXPECT_EQ(1, (*tables)[kTestDeviceIndex0].size());
+
+  RoutingTableEntry test_entry = (*tables)[kTestDeviceIndex0][0];
+  EXPECT_TRUE(entry0.Equals(test_entry));
+
+  // Add a second entry for a different interface
+  SendRouteMsg(RTNLMessage::kMessageModeAdd,
+               kTestDeviceIndex1,
+               entry0);
+
+  // Should have two tables, which should have a single entry each
+  EXPECT_EQ(2, tables->size());
+  EXPECT_TRUE(ContainsKey(*tables, kTestDeviceIndex1));
+  EXPECT_EQ(1, (*tables)[kTestDeviceIndex0].size());
+  EXPECT_EQ(1, (*tables)[kTestDeviceIndex1].size());
+
+  test_entry = (*tables)[kTestDeviceIndex1][0];
+  EXPECT_TRUE(entry0.Equals(test_entry));
+
+  IPAddress gateway_address1(IPAddress::kAddressFamilyIPv4);
+  gateway_address1.SetAddressFromString(kTestNetAddress1);
+
+  RoutingTableEntry entry1(default_address,
+                           0,
+                           default_address,
+                           0,
+                           gateway_address1,
+                           metric,
+                           RT_SCOPE_UNIVERSE,
+                           true);
+
+  // Add a second gateway route to the second interface
+  SendRouteMsg(RTNLMessage::kMessageModeAdd,
+               kTestDeviceIndex1,
+               entry1);
+
+  // Should have two tables, one of which has a single entry, the other has two
+  EXPECT_EQ(2, tables->size());
+  EXPECT_EQ(1, (*tables)[kTestDeviceIndex0].size());
+  EXPECT_EQ(2, (*tables)[kTestDeviceIndex1].size());
+
+  test_entry = (*tables)[kTestDeviceIndex1][1];
+  EXPECT_TRUE(entry1.Equals(test_entry));
+
+  // Remove the first gateway route from the second interface
+  SendRouteMsg(RTNLMessage::kMessageModeDelete,
+               kTestDeviceIndex1,
+               entry0);
+
+  // We should be back to having one route per table
+  EXPECT_EQ(2, tables->size());
+  EXPECT_EQ(1, (*tables)[kTestDeviceIndex0].size());
+  EXPECT_EQ(1, (*tables)[kTestDeviceIndex1].size());
+
+  test_entry = (*tables)[kTestDeviceIndex1][0];
+  EXPECT_TRUE(entry1.Equals(test_entry));
+
+  // Send a duplicate of the second gatway route message, changing the metric
+  RoutingTableEntry entry2(entry1);
+  entry2.metric++;
+  SendRouteMsg(RTNLMessage::kMessageModeAdd,
+               kTestDeviceIndex1,
+               entry2);
+
+  // Routing table size shouldn't change, but the new metric should match
+  EXPECT_EQ(1, (*tables)[kTestDeviceIndex1].size());
+  test_entry = (*tables)[kTestDeviceIndex1][0];
+  EXPECT_TRUE(entry2.Equals(test_entry));
+
+  // Find a matching entry
+  EXPECT_TRUE(routing_table_->GetDefaultRoute(kTestDeviceIndex1,
+                                              IPAddress::kAddressFamilyIPv4,
+                                              &test_entry));
+  EXPECT_TRUE(entry2.Equals(test_entry));
+
+  // Test that a search for a non-matching family fails
+  EXPECT_FALSE(routing_table_->GetDefaultRoute(kTestDeviceIndex1,
+                                               IPAddress::kAddressFamilyIPv6,
+                                               &test_entry));
+
+  // Remove last entry from an existing interface and test that we now fail
+  SendRouteMsg(RTNLMessage::kMessageModeDelete,
+               kTestDeviceIndex1,
+               entry2);
+
+  EXPECT_FALSE(routing_table_->GetDefaultRoute(kTestDeviceIndex1,
+                                               IPAddress::kAddressFamilyIPv4,
+                                               &test_entry));
+
+  // Add a route from an IPConfig entry
+  MockControl control;
+  IPConfigRefPtr ipconfig(new IPConfig(&control, kTestDeviceName0));
+  IPConfig::Properties properties;
+  properties.address_family = IPAddress::kAddressFamilyIPv4;
+  properties.gateway = kTestNetAddress0;
+  properties.address = kTestNetAddress1;
+  ipconfig->UpdateProperties(properties, true);
+
+  EXPECT_CALL(sockets_,
+              Send(kTestSocket,
+                   IsRoutingPacket(RTNLMessage::kMessageModeAdd,
+                                   kTestDeviceIndex1,
+                                   entry0,
+                                   NLM_F_CREATE | NLM_F_EXCL),
+                   _,
+                   0));
+  EXPECT_TRUE(routing_table_->SetDefaultRoute(kTestDeviceIndex1,
+                                              ipconfig,
+                                              metric));
+
+  // The table entry should look much like entry0, except with from_rtnl = false
+  RoutingTableEntry entry3(entry0);
+  entry3.from_rtnl = false;
+  EXPECT_TRUE(routing_table_->GetDefaultRoute(kTestDeviceIndex1,
+                                              IPAddress::kAddressFamilyIPv4,
+                                              &test_entry));
+  EXPECT_TRUE(entry3.Equals(test_entry));
+
+  // Setting the same route on the interface with a different metric should
+  // push the route with different flags to indicate we are replacing it.
+  RoutingTableEntry entry4(entry3);
+  entry4.metric += 10;
+  EXPECT_CALL(sockets_,
+              Send(kTestSocket,
+                   IsRoutingPacket(RTNLMessage::kMessageModeAdd,
+                                   kTestDeviceIndex1,
+                                   entry4,
+                                   NLM_F_CREATE | NLM_F_REPLACE),
+                   _,
+                   0));
+  EXPECT_TRUE(routing_table_->SetDefaultRoute(kTestDeviceIndex1,
+                                              ipconfig,
+                                              entry4.metric));
+
+  // Test that removing the table causes the route to disappear
+  routing_table_->ResetTable(kTestDeviceIndex1);
+  EXPECT_FALSE(ContainsKey(*tables, kTestDeviceIndex1));
+  EXPECT_FALSE(routing_table_->GetDefaultRoute(kTestDeviceIndex1,
+                                               IPAddress::kAddressFamilyIPv4,
+                                               &test_entry));
+  EXPECT_EQ(1, GetRoutingTables()->size());
+
+  // When we set the metric on an existing route, a new add message appears
+  RoutingTableEntry entry5(entry4);
+  entry5.metric += 10;
+  EXPECT_CALL(sockets_,
+              Send(kTestSocket,
+                   IsRoutingPacket(RTNLMessage::kMessageModeAdd,
+                                   kTestDeviceIndex0,
+                                   entry5,
+                                   NLM_F_CREATE | NLM_F_REPLACE),
+                   _,
+                   0));
+  routing_table_->SetDefaultMetric(kTestDeviceIndex0, entry5.metric);
+
+  // Ask to flush table0.  We should see a delete message sent
+  EXPECT_CALL(sockets_,
+              Send(kTestSocket,
+                   IsRoutingPacket(RTNLMessage::kMessageModeDelete,
+                                   kTestDeviceIndex0,
+                                   entry0,
+                                   0),
+                   _,
+                   0));
+  routing_table_->FlushRoutes(kTestDeviceIndex0);
+
+  // Test that the routing table size returns to zero
+  EXPECT_EQ(1, GetRoutingTables()->size());
+  routing_table_->ResetTable(kTestDeviceIndex0);
+  EXPECT_EQ(0, GetRoutingTables()->size());
+
+  routing_table_->Stop();
+  StopRTNLHandler();
+}
+
+}  // namespace shill
diff --git a/rtnl_handler.cc b/rtnl_handler.cc
index 443d1cf..cd4d9f8 100644
--- a/rtnl_handler.cc
+++ b/rtnl_handler.cc
@@ -23,6 +23,7 @@
 #include "shill/ipconfig.h"
 #include "shill/rtnl_handler.h"
 #include "shill/rtnl_listener.h"
+#include "shill/rtnl_message.h"
 #include "shill/shill_event.h"
 #include "shill/sockets.h"
 
@@ -176,17 +177,27 @@
   req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
   req.hdr.nlmsg_pid = 0;
   req.hdr.nlmsg_seq = request_sequence_;
-  req.msg.rtgen_family = AF_INET;
 
   if ((request_flags_ & kRequestLink) != 0) {
+    req.msg.rtgen_family = AF_INET;
     req.hdr.nlmsg_type = RTM_GETLINK;
     flag = kRequestLink;
   } else if ((request_flags_ & kRequestAddr) != 0) {
+    req.msg.rtgen_family = AF_INET;
     req.hdr.nlmsg_type = RTM_GETADDR;
     flag = kRequestAddr;
   } else if ((request_flags_ & kRequestRoute) != 0) {
+    req.msg.rtgen_family = AF_INET;
     req.hdr.nlmsg_type = RTM_GETROUTE;
     flag = kRequestRoute;
+  } else if ((request_flags_ & kRequestAddr6) != 0) {
+    req.msg.rtgen_family = AF_INET6;
+    req.hdr.nlmsg_type = RTM_GETADDR;
+    flag = kRequestAddr6;
+  } else if ((request_flags_ & kRequestRoute6) != 0) {
+    req.msg.rtgen_family = AF_INET6;
+    req.hdr.nlmsg_type = RTM_GETROUTE;
+    flag = kRequestRoute6;
   } else {
     in_request_ = false;
     return;
@@ -291,8 +302,9 @@
   } else if (properties.address_family == IPAddress::kAddressFamilyIPv6) {
     address_family = AF_INET6;
     address_size = sizeof(struct in6_addr);
-  } else
+  } else {
     return false;
+  }
 
   request_sequence_++;
   memset(&req, 0, sizeof(req));
@@ -362,4 +374,23 @@
   return ifr.ifr_ifindex;
 }
 
+bool RTNLHandler::SendMessage(RTNLMessage *message) {
+  message->set_seq(request_sequence_++);
+  ByteString msgdata = message->Encode();
+
+  if (msgdata.GetLength() == 0) {
+    return false;
+  }
+
+  if (sockets_->Send(rtnl_socket_,
+                     msgdata.GetData(),
+                     msgdata.GetLength(),
+                     0) < 0) {
+    PLOG(ERROR) << "RTNL send failed: " << strerror(errno);
+    return false;
+  }
+
+  return true;
+}
+
 }  // namespace shill
diff --git a/rtnl_handler.h b/rtnl_handler.h
index 62a7389..4a5f118 100644
--- a/rtnl_handler.h
+++ b/rtnl_handler.h
@@ -26,6 +26,7 @@
 namespace shill {
 
 class IPConfig;
+class RTNLMessage;
 class Sockets;
 
 // This singleton class is responsible for interacting with the RTNL subsystem.
@@ -42,6 +43,8 @@
   static const int kRequestLink = 1;
   static const int kRequestAddr = 2;
   static const int kRequestRoute = 4;
+  static const int kRequestAddr6 = 8;
+  static const int kRequestRoute6 = 16;
 
   // Since this is a singleton, use RTNHandler::GetInstance()->Foo()
   static RTNLHandler *GetInstance();
@@ -85,10 +88,14 @@
   // determine the index.
   int GetInterfaceIndex(const std::string &interface_name);
 
+  // Send a formatted RTNL message.  The sequence number in the message is set.
+  bool SendMessage(RTNLMessage *message);
+
  private:
   friend class DeviceInfoTest;
   friend class ModemTest;
   friend class RTNLHandlerTest;
+  friend class RoutingTableTest;
   friend struct DefaultSingletonTraits<RTNLHandler>;
   FRIEND_TEST(RTNLListenerTest, NoRun);
   FRIEND_TEST(RTNLListenerTest, Run);