shill: Connection: UnPin routes on destruction

Tag pinned routes with the interface index associated with the request,
so they can be removed when the connection is destroyed.  Also move
PinHostRoute() out of the VPN code and into the Connection.

BUG=chromium-os:29911
TEST=New unit tests

Change-Id: I46019255276469929642db4a6395e64f53e3b7d5
Reviewed-on: https://gerrit.chromium.org/gerrit/20982
Commit-Ready: Paul Stewart <pstew@chromium.org>
Reviewed-by: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/connection.cc b/connection.cc
index a5314ca..71cec11 100644
--- a/connection.cc
+++ b/connection.cc
@@ -45,6 +45,7 @@
 
   DCHECK(!routing_request_count_);
   routing_table_->FlushRoutes(interface_index_);
+  routing_table_->FlushRoutesWithTag(interface_index_);
   device_info_->FlushAddresses(interface_index_);
 }
 
@@ -52,6 +53,11 @@
   SLOG(Connection, 2) << __func__ << " " << interface_name_;
 
   const IPConfig::Properties &properties = config->properties();
+  if (!properties.trusted_ip.empty() && !PinHostRoute(properties)) {
+    LOG(ERROR) << "Unable to pin host route to " << properties.trusted_ip;
+    return;
+  }
+
   IPAddress local(properties.address_family);
   if (!local.SetAddressFromString(properties.address)) {
     LOG(ERROR) << "Local address " << properties.address << " is invalid";
@@ -166,7 +172,10 @@
 
   // Do not set interface_index_ since this may not be the
   // default route through which this destination can be found.
-  if (!routing_table_->RequestRouteToHost(address_prefix, -1)) {
+  // However, we should tag the created route with our interface
+  // index so we can clean this route up when this connection closes.
+  if (!routing_table_->RequestRouteToHost(address_prefix, -1,
+                                          interface_index_)) {
     LOG(ERROR) << "Could not request route to " << address.ToString();
     return false;
   }
@@ -211,4 +220,20 @@
   return is_default ? kDefaultMetric : kNonDefaultMetricBase + interface_index_;
 }
 
+bool Connection::PinHostRoute(const IPConfig::Properties &properties) {
+  SLOG(Connection, 2) << __func__;
+  if (properties.gateway.empty() || properties.trusted_ip.empty()) {
+    return false;
+  }
+
+  IPAddress trusted_ip(properties.address_family);
+  if (!trusted_ip.SetAddressFromString(properties.trusted_ip)) {
+    LOG(ERROR) << "Failed to parse trusted_ip "
+               << properties.trusted_ip << "; ignored.";
+    return false;
+  }
+
+  return RequestHostRoute(trusted_ip);
+}
+
 }  // namespace shill
diff --git a/connection.h b/connection.h
index 4c55a2b..ff638cf 100644
--- a/connection.h
+++ b/connection.h
@@ -11,6 +11,7 @@
 #include <base/memory/ref_counted.h>
 #include <gtest/gtest_prod.h>  // for FRIEND_TEST
 
+#include "shill/ipconfig.h"
 #include "shill/refptr_types.h"
 #include "shill/technology.h"
 
@@ -72,6 +73,7 @@
   static void FixGatewayReachability(IPAddress *local,
                                      const IPAddress &gateway);
   uint32 GetMetric(bool is_default);
+  bool PinHostRoute(const IPConfig::Properties &config);
 
   bool is_default_;
   int routing_request_count_;
diff --git a/connection_unittest.cc b/connection_unittest.cc
index 3acc866..5c74f28 100644
--- a/connection_unittest.cc
+++ b/connection_unittest.cc
@@ -87,6 +87,9 @@
 
   virtual void TearDown() {
     EXPECT_CALL(*device_info_, FlushAddresses(kTestDeviceInterfaceIndex0));
+    EXPECT_CALL(routing_table_, FlushRoutes(kTestDeviceInterfaceIndex0));
+    EXPECT_CALL(routing_table_, FlushRoutesWithTag(kTestDeviceInterfaceIndex0));
+    connection_ = NULL;
   }
 
   void ReplaceSingletons(ConnectionRefPtr connection) {
@@ -99,6 +102,11 @@
     ipconfig_->UpdateProperties(properties_, true);
   }
 
+  bool PinHostRoute(ConnectionRefPtr connection,
+                    const IPConfig::Properties &properties) {
+    return connection->PinHostRoute(properties);
+  }
+
  protected:
   scoped_ptr<StrictMock<MockDeviceInfo> > device_info_;
   ConnectionRefPtr connection_;
@@ -301,12 +309,14 @@
 
   // The destructor will remove the routes and addresses.
   EXPECT_CALL(routing_table_, FlushRoutes(kTestDeviceInterfaceIndex0));
+  EXPECT_CALL(routing_table_, FlushRoutesWithTag(kTestDeviceInterfaceIndex0));
   EXPECT_CALL(*device_info_.get(),
               FlushAddresses(kTestDeviceInterfaceIndex0));
 }
 
 TEST_F(ConnectionTest, Destructor) {
   EXPECT_CALL(routing_table_, FlushRoutes(kTestDeviceInterfaceIndex1));
+  EXPECT_CALL(routing_table_, FlushRoutesWithTag(kTestDeviceInterfaceIndex1));
   EXPECT_CALL(*device_info_, FlushAddresses(kTestDeviceInterfaceIndex1));
   {
     ConnectionRefPtr connection(new Connection(kTestDeviceInterfaceIndex1,
@@ -329,12 +339,58 @@
   ASSERT_TRUE(address.SetAddressFromString(kIPAddress0));
   size_t prefix_len = address.GetLength() * 8;
   EXPECT_CALL(routing_table_, RequestRouteToHost(
-      IsIPAddress(address, prefix_len), -1))
+      IsIPAddress(address, prefix_len), -1, kTestDeviceInterfaceIndex0))
       .WillOnce(Return(true));
   EXPECT_TRUE(connection->RequestHostRoute(address));
 
   // The destructor will remove the routes and addresses.
   EXPECT_CALL(routing_table_, FlushRoutes(kTestDeviceInterfaceIndex0));
+  EXPECT_CALL(routing_table_, FlushRoutesWithTag(kTestDeviceInterfaceIndex0));
+  EXPECT_CALL(*device_info_.get(),
+              FlushAddresses(kTestDeviceInterfaceIndex0));
+}
+
+TEST_F(ConnectionTest, PinHostRoute) {
+  static const char kGateway[] = "10.242.2.13";
+  static const char kNetwork[] = "10.242.2.1";
+
+  ConnectionRefPtr connection(new Connection(kTestDeviceInterfaceIndex0,
+                                             kTestDeviceName0,
+                                             Technology::kUnknown,
+                                             device_info_.get()));
+  ReplaceSingletons(connection);
+
+  IPConfig::Properties props;
+  props.address_family = IPAddress::kFamilyIPv4;
+  EXPECT_FALSE(PinHostRoute(connection, props));
+
+  props.gateway = kGateway;
+  EXPECT_FALSE(PinHostRoute(connection, props));
+
+  props.gateway.clear();
+  props.trusted_ip = "xxx";
+  EXPECT_FALSE(PinHostRoute(connection, props));
+
+  props.gateway = kGateway;
+  EXPECT_FALSE(PinHostRoute(connection, props));
+
+  props.trusted_ip = kNetwork;
+  IPAddress address(IPAddress::kFamilyIPv4);
+  ASSERT_TRUE(address.SetAddressFromString(kNetwork));
+  size_t prefix_len = address.GetLength() * 8;
+  EXPECT_CALL(routing_table_, RequestRouteToHost(
+      IsIPAddress(address, prefix_len), -1, kTestDeviceInterfaceIndex0))
+      .WillOnce(Return(false));
+  EXPECT_FALSE(PinHostRoute(connection, props));
+
+  EXPECT_CALL(routing_table_, RequestRouteToHost(
+      IsIPAddress(address, prefix_len), -1, kTestDeviceInterfaceIndex0))
+      .WillOnce(Return(true));
+  EXPECT_TRUE(PinHostRoute(connection, props));
+
+  // The destructor will remove the routes and addresses.
+  EXPECT_CALL(routing_table_, FlushRoutes(kTestDeviceInterfaceIndex0));
+  EXPECT_CALL(routing_table_, FlushRoutesWithTag(kTestDeviceInterfaceIndex0));
   EXPECT_CALL(*device_info_.get(),
               FlushAddresses(kTestDeviceInterfaceIndex0));
 }
diff --git a/l2tp_ipsec_driver.cc b/l2tp_ipsec_driver.cc
index 34b2525..93ed0f3 100644
--- a/l2tp_ipsec_driver.cc
+++ b/l2tp_ipsec_driver.cc
@@ -394,7 +394,6 @@
   }
   device_->SetEnabled(true);
   device_->SelectService(service_);
-  PinHostRoute(properties);
   device_->UpdateIPConfig(properties);
 }
 
diff --git a/l2tp_ipsec_driver_unittest.cc b/l2tp_ipsec_driver_unittest.cc
index fa786b2..52d1bf2 100644
--- a/l2tp_ipsec_driver_unittest.cc
+++ b/l2tp_ipsec_driver_unittest.cc
@@ -12,13 +12,11 @@
 #include "shill/event_dispatcher.h"
 #include "shill/nice_mock_control.h"
 #include "shill/mock_adaptors.h"
-#include "shill/mock_connection.h"
 #include "shill/mock_device_info.h"
 #include "shill/mock_glib.h"
 #include "shill/mock_manager.h"
 #include "shill/mock_metrics.h"
 #include "shill/mock_nss.h"
-#include "shill/mock_service.h"
 #include "shill/mock_vpn.h"
 #include "shill/mock_vpn_service.h"
 #include "shill/vpn.h"
@@ -447,20 +445,9 @@
 
 TEST_F(L2TPIPSecDriverTest, Notify) {
   map<string, string> config;
-  static const char kPeer[] = "99.88.77.66";
   config["INTERNAL_IFNAME"] = kInterfaceName;
-  config["GATEWAY_ADDRESS"] = "192.168.1.1";
-  config["LNS_ADDRESS"] = kPeer;
-  scoped_refptr<MockService> service(
-      new NiceMock<MockService>(&control_, &dispatcher_, &metrics_, &manager_));
-  scoped_refptr<MockConnection> connection(
-      new StrictMock<MockConnection>(&device_info_));
-  service->set_mock_connection(connection);
   EXPECT_CALL(device_info_, GetIndex(kInterfaceName))
       .WillOnce(Return(kInterfaceIndex));
-  EXPECT_CALL(manager_, GetDefaultService()).WillOnce(Return(service));
-  EXPECT_CALL(*connection, RequestHostRoute(IsIPAddress(kPeer)))
-      .WillOnce(Return(true));
   EXPECT_CALL(*device_, SetEnabled(true));
   EXPECT_CALL(*device_, UpdateIPConfig(_));
   driver_->device_ = device_;
diff --git a/mock_routing_table.h b/mock_routing_table.h
index a174ac2..2e69349 100644
--- a/mock_routing_table.h
+++ b/mock_routing_table.h
@@ -31,11 +31,13 @@
                                      const IPConfigRefPtr &ipconfig,
                                      uint32 metric));
   MOCK_METHOD1(FlushRoutes, void(int interface_index));
+  MOCK_METHOD1(FlushRoutesWithTag, void(int tag));
   MOCK_METHOD0(FlushCache, bool());
   MOCK_METHOD1(ResetTable, void(int interface_index));
   MOCK_METHOD2(SetDefaultMetric, void(int interface_index, uint32 metric));
-  MOCK_METHOD2(RequestRouteToHost, bool(const IPAddress &addresss,
-                                        int interface_index));
+  MOCK_METHOD3(RequestRouteToHost, bool(const IPAddress &addresss,
+                                        int interface_index,
+                                        int tag));
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockRoutingTable);
diff --git a/openvpn_driver.cc b/openvpn_driver.cc
index 2422864..ff2c428 100644
--- a/openvpn_driver.cc
+++ b/openvpn_driver.cc
@@ -251,8 +251,6 @@
   }
   IPConfig::Properties properties;
   ParseIPConfiguration(dict, &properties);
-  PinHostRoute(properties);
-
   device_->UpdateIPConfig(properties);
 }
 
diff --git a/openvpn_driver.h b/openvpn_driver.h
index e73fabd..b2b7ba5 100644
--- a/openvpn_driver.h
+++ b/openvpn_driver.h
@@ -14,6 +14,7 @@
 #include <gtest/gtest_prod.h>  // for FRIEND_TEST
 
 #include "shill/glib.h"
+#include "shill/ipconfig.h"
 #include "shill/refptr_types.h"
 #include "shill/rpc_task.h"
 #include "shill/service.h"
diff --git a/openvpn_driver_unittest.cc b/openvpn_driver_unittest.cc
index 3513252..21d37ad 100644
--- a/openvpn_driver_unittest.cc
+++ b/openvpn_driver_unittest.cc
@@ -16,14 +16,12 @@
 #include "shill/error.h"
 #include "shill/ipconfig.h"
 #include "shill/mock_adaptors.h"
-#include "shill/mock_connection.h"
 #include "shill/mock_device_info.h"
 #include "shill/mock_glib.h"
 #include "shill/mock_manager.h"
 #include "shill/mock_metrics.h"
 #include "shill/mock_nss.h"
 #include "shill/mock_openvpn_management_server.h"
-#include "shill/mock_service.h"
 #include "shill/mock_store.h"
 #include "shill/mock_vpn.h"
 #include "shill/mock_vpn_service.h"
@@ -188,17 +186,6 @@
 
 TEST_F(OpenVPNDriverTest, Notify) {
   map<string, string> config;
-  static const char kPeer[] = "99.88.77.66";
-  config["route_vpn_gateway"] = "192.168.1.1";
-  config["trusted_ip"] = kPeer;
-  scoped_refptr<MockService> service(
-      new NiceMock<MockService>(&control_, &dispatcher_, &metrics_, &manager_));
-  scoped_refptr<MockConnection> connection(
-      new StrictMock<MockConnection>(&device_info_));
-  service->set_mock_connection(connection);
-  EXPECT_CALL(manager_, GetDefaultService()).WillOnce(Return(service));
-  EXPECT_CALL(*connection, RequestHostRoute(IsIPAddress(kPeer)))
-      .WillOnce(Return(true));
   driver_->device_ = device_;
   EXPECT_CALL(*device_, UpdateIPConfig(_));
   driver_->Notify("up", config);
diff --git a/routing_table.cc b/routing_table.cc
index e227b07..b75b683 100644
--- a/routing_table.cc
+++ b/routing_table.cc
@@ -237,6 +237,24 @@
   table->second.clear();
 }
 
+void RoutingTable::FlushRoutesWithTag(int tag) {
+  SLOG(Route, 2) << __func__;
+
+  base::hash_map<int, vector<RoutingTableEntry> >::iterator table;
+  for (table = tables_.begin(); table != tables_.end(); ++table) {
+    vector<RoutingTableEntry>::iterator nent;
+
+    for (nent = table->second.begin(); nent != table->second.end();) {
+      if (nent->tag == tag) {
+        ApplyRoute(table->first, *nent, RTNLMessage::kModeDelete, 0);
+        nent = table->second.erase(nent);
+      } else {
+        ++nent;
+      }
+    }
+  }
+}
+
 void RoutingTable::ResetTable(int interface_index) {
   tables_.erase(interface_index);
 }
@@ -321,30 +339,31 @@
     return;
   }
 
-  if (!route_query_sequences_.empty() &&
+  if (!route_queries_.empty() &&
       message.route_status().protocol == RTPROT_UNSPEC) {
     SLOG(Route, 3) << __func__ << ": Message seq: " << message.seq()
                    << " mode " << message.mode()
-                   << ", next query seq: " << route_query_sequences_.front();
+                   << ", next query seq: " << route_queries_.front().sequence;
 
     // Purge queries that have expired (sequence number of this message is
     // greater than that of the head of the route query sequence).  Do the
     // math in a way that's roll-over independent.
-    while (route_query_sequences_.front() - message.seq() > kuint32max / 2) {
+    while (route_queries_.front().sequence - message.seq() > kuint32max / 2) {
       LOG(ERROR) << __func__ << ": Purging un-replied route request sequence "
-                 << route_query_sequences_.front()
+                 << route_queries_.front().sequence
                  << " (< " << message.seq() << ")";
-      route_query_sequences_.pop();
-      if (route_query_sequences_.empty())
+      route_queries_.pop();
+      if (route_queries_.empty())
         return;
     }
 
-    if (route_query_sequences_.front() == message.seq()) {
+    if (route_queries_.front().sequence == message.seq()) {
       SLOG(Route, 2) << __func__ << ": Adding host route to "
                      << entry.dst.ToString();
-      route_query_sequences_.pop();
       RoutingTableEntry add_entry(entry);
       add_entry.from_rtnl = false;
+      add_entry.tag = route_queries_.front().tag;
+      route_queries_.pop();
       AddRoute(interface_index, add_entry);
     }
     return;
@@ -466,7 +485,8 @@
 }
 
 bool RoutingTable::RequestRouteToHost(const IPAddress &address,
-                                      int interface_index) {
+                                      int interface_index,
+                                      int tag) {
   RTNLMessage message(
       RTNLMessage::kTypeRoute,
       RTNLMessage::kModeQuery,
@@ -492,7 +512,7 @@
 
   // Save the sequence number of the request so we can create a route for
   // this host when we get a reply.
-  route_query_sequences_.push(message.seq());
+  route_queries_.push(Query(message.seq(), tag));
 
   return true;
 }
diff --git a/routing_table.h b/routing_table.h
index d558a35..7996261 100644
--- a/routing_table.h
+++ b/routing_table.h
@@ -32,6 +32,14 @@
 // default route for an interface or modifying its metric (priority).
 class RoutingTable {
  public:
+  struct Query {
+    Query() : sequence(0), tag(0) {}
+    Query(uint32 sequence_in, int tag_in)
+        : sequence(sequence_in), tag(tag_in) {}
+    uint32 sequence;
+    int tag;
+  };
+
   virtual ~RoutingTable();
 
   static RoutingTable *GetInstance();
@@ -65,6 +73,10 @@
   // Route entries are immediately purged from our copy of the routing table.
   virtual void FlushRoutes(int interface_index);
 
+  // Iterate over all routing tables removing routes tagged with |tag|.
+  // Route entries are immediately purged from our copy of the routing table.
+  virtual void FlushRoutesWithTag(int tag);
+
   // Flush the routing cache for all interfaces.
   virtual bool FlushCache();
 
@@ -75,9 +87,13 @@
   virtual void SetDefaultMetric(int interface_index, uint32 metric);
 
   // Get the default route to |destination| through |interface_index| and
-  // create a host route to that destination.
+  // create a host route to that destination.  When creating the route,
+  // tag our local entry with |tag|, so we can remove it later.  Connections
+  // use their interface index as the tag, so that as they are destroyed,
+  // they can remove all their dependent routes.
   virtual bool RequestRouteToHost(const IPAddress &destination,
-                                  int interface_index);
+                                  int interface_index,
+                                  int tag);
 
  protected:
   RoutingTable();
@@ -113,7 +129,7 @@
 
   base::Callback<void(const RTNLMessage &)> route_callback_;
   scoped_ptr<RTNLListener> route_listener_;
-  std::queue<uint32> route_query_sequences_;
+  std::queue<Query> route_queries_;
 
   // Cache singleton pointer for performance and test purposes.
   RTNLHandler *rtnl_handler_;
diff --git a/routing_table_entry.h b/routing_table_entry.h
index 9cf41de..8a42de7 100644
--- a/routing_table_entry.h
+++ b/routing_table_entry.h
@@ -16,13 +16,16 @@
 // operator=.
 struct RoutingTableEntry {
  public:
+  static const int kDefaultTag = -1;
+
   RoutingTableEntry()
       : dst(IPAddress::kFamilyUnknown),
         src(IPAddress::kFamilyUnknown),
         gateway(IPAddress::kFamilyUnknown),
         metric(0),
         scope(0),
-        from_rtnl(false) {}
+        from_rtnl(false),
+        tag(kDefaultTag) {}
 
   RoutingTableEntry(const IPAddress &dst_in,
                     const IPAddress &src_in,
@@ -35,7 +38,23 @@
         gateway(gateway_in),
         metric(metric_in),
         scope(scope_in),
-        from_rtnl(from_rtnl_in) {}
+        from_rtnl(from_rtnl_in),
+        tag(kDefaultTag) {}
+
+  RoutingTableEntry(const IPAddress &dst_in,
+                    const IPAddress &src_in,
+                    const IPAddress &gateway_in,
+                    uint32 metric_in,
+                    unsigned char scope_in,
+                    bool from_rtnl_in,
+                    int tag_in)
+      : dst(dst_in),
+        src(src_in),
+        gateway(gateway_in),
+        metric(metric_in),
+        scope(scope_in),
+        from_rtnl(from_rtnl_in),
+        tag(tag_in) {}
 
   RoutingTableEntry(const RoutingTableEntry &b)
       : dst(b.dst),
@@ -43,7 +62,8 @@
         gateway(b.gateway),
         metric(b.metric),
         scope(b.scope),
-        from_rtnl(b.from_rtnl) {}
+        from_rtnl(b.from_rtnl),
+        tag(b.tag) {}
 
   RoutingTableEntry &operator=(const RoutingTableEntry &b) {
     dst = b.dst;
@@ -52,6 +72,7 @@
     metric = b.metric;
     scope = b.scope;
     from_rtnl = b.from_rtnl;
+    tag = b.tag;
 
     return *this;
   }
@@ -64,7 +85,8 @@
             gateway.Equals(b.gateway) &&
             metric == b.metric &&
             scope == b.scope &&
-            from_rtnl == b.from_rtnl);
+            from_rtnl == b.from_rtnl &&
+            tag == b.tag);
   }
 
   IPAddress dst;
@@ -73,6 +95,7 @@
   uint32 metric;
   unsigned char scope;
   bool from_rtnl;
+  int tag;
 };
 
 }  // namespace shill
diff --git a/routing_table_unittest.cc b/routing_table_unittest.cc
index 49be863..e74f02d 100644
--- a/routing_table_unittest.cc
+++ b/routing_table_unittest.cc
@@ -21,6 +21,8 @@
 #include "shill/rtnl_message.h"
 
 using base::Callback;
+using base::hash_map;
+using std::queue;
 using std::vector;
 using testing::_;
 using testing::Invoke;
@@ -41,7 +43,7 @@
 
 class RoutingTableTest : public Test {
  public:
-  RoutingTableTest() : routing_table_(RoutingTable::GetInstance()) {}
+  RoutingTableTest() : routing_table_(new RoutingTable()) {}
 
   virtual void SetUp() {
     routing_table_->rtnl_handler_ = &rtnl_handler_;
@@ -52,12 +54,12 @@
     RTNLHandler::GetInstance()->Stop();
   }
 
-  base::hash_map<int, std::vector<RoutingTableEntry> > *GetRoutingTables() {
+  hash_map<int, vector<RoutingTableEntry> > *GetRoutingTables() {
     return &routing_table_->tables_;
   }
 
-  std::queue<uint32> *GetQuerySequences() {
-    return &routing_table_->route_query_sequences_;
+  queue<RoutingTable::Query> *GetQueries() {
+    return &routing_table_->route_queries_;
   }
 
   void SendRouteEntry(RTNLMessage::Mode mode,
@@ -94,6 +96,7 @@
   static const char kTestRemoteNetwork4[];
   static const int kTestRemotePrefix4;
   static const uint32 kTestRequestSeq;
+  static const int kTestRouteTag;
 
   RoutingTable *routing_table_;
   TestEventDispatcher dispatcher_;
@@ -116,6 +119,7 @@
 const char RoutingTableTest::kTestRemoteNetwork4[] = "192.168.100.0";
 const int RoutingTableTest::kTestRemotePrefix4 = 24;
 const uint32 RoutingTableTest::kTestRequestSeq = 456;
+const int RoutingTableTest::kTestRouteTag = 789;
 
 MATCHER_P4(IsRoutingPacket, mode, index, entry, flags, "") {
   const RTNLMessage::RouteStatus &status = arg->route_status();
@@ -220,7 +224,7 @@
                  kTestDeviceIndex0,
                  entry0);
 
-  base::hash_map<int, std::vector<RoutingTableEntry> > *tables =
+  hash_map<int, vector<RoutingTableEntry> > *tables =
     GetRoutingTables();
 
   // We should have a single table, which should in turn have a single entry.
@@ -506,7 +510,8 @@
                                          kTestDeviceIndex0)))
       .WillOnce(Invoke(this, &RoutingTableTest::SetSequenceForMessage));
   EXPECT_TRUE(routing_table_->RequestRouteToHost(destination_address,
-                                                 kTestDeviceIndex0));
+                                                 kTestDeviceIndex0,
+                                                 kTestRouteTag));
 
   IPAddress gateway_address(IPAddress::kFamilyIPv4);
   gateway_address.SetAddressFromString(kTestGatewayAddress4);
@@ -532,6 +537,29 @@
                                 entry,
                                 kTestRequestSeq,
                                 RTPROT_UNSPEC);
+
+  hash_map<int, vector<RoutingTableEntry> > *tables =
+    GetRoutingTables();
+
+  // We 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());
+
+  // This entry's tag should match the tag we requested.
+  EXPECT_EQ(kTestRouteTag, (*tables)[kTestDeviceIndex0][0].tag);
+
+  // Ask to flush routes with our tag.  We should see a delete message sent.
+  EXPECT_CALL(rtnl_handler_,
+              SendMessage(IsRoutingPacket(RTNLMessage::kModeDelete,
+                                          kTestDeviceIndex0,
+                                          entry,
+                                          0)));
+
+  routing_table_->FlushRoutesWithTag(kTestRouteTag);
+
+  // After flushing routes for this tag, we should end up with no routes.
+  EXPECT_EQ(0, (*tables)[kTestDeviceIndex0].size());
 }
 
 TEST_F(RoutingTableTest, RequestHostRouteBadSequence) {
@@ -540,8 +568,9 @@
   EXPECT_CALL(rtnl_handler_, SendMessage(_))
       .WillOnce(Invoke(this, &RoutingTableTest::SetSequenceForMessage));
   EXPECT_TRUE(routing_table_->RequestRouteToHost(destination_address,
-                                                 kTestDeviceIndex0));
-  EXPECT_FALSE(GetQuerySequences()->empty());
+                                                 kTestDeviceIndex0,
+                                                 kTestRouteTag));
+  EXPECT_FALSE(GetQueries()->empty());
 
   RoutingTableEntry entry(destination_address,
                           destination_address,
@@ -557,7 +586,7 @@
                                 entry,
                                 kTestRequestSeq-1,
                                 RTPROT_UNSPEC);
-  EXPECT_FALSE(GetQuerySequences()->empty());
+  EXPECT_FALSE(GetQueries()->empty());
 
   // Try a sequence arriving after the one RoutingTable is looking for.
   // This should cause the request to be purged.
@@ -566,7 +595,7 @@
                                 entry,
                                 kTestRequestSeq+1,
                                 RTPROT_UNSPEC);
-  EXPECT_TRUE(GetQuerySequences()->empty());
+  EXPECT_TRUE(GetQueries()->empty());
 }
 
 }  // namespace shill
diff --git a/vpn_driver.cc b/vpn_driver.cc
index 921318c..1253217 100644
--- a/vpn_driver.cc
+++ b/vpn_driver.cc
@@ -138,27 +138,4 @@
   return provider_properties;
 }
 
-bool VPNDriver::PinHostRoute(const IPConfig::Properties &properties) {
-  SLOG(VPN, 2) << __func__;
-  if (properties.gateway.empty() || properties.trusted_ip.empty()) {
-    return false;
-  }
-
-  IPAddress trusted_ip(properties.address_family);
-  if (!trusted_ip.SetAddressFromString(properties.trusted_ip)) {
-    LOG(ERROR) << "Failed to parse trusted_ip "
-               << properties.trusted_ip << "; ignored.";
-    return false;
-  }
-
-  ServiceRefPtr default_service = manager_->GetDefaultService();
-  if (!default_service) {
-    LOG(ERROR) << "No default service exists.";
-    return false;
-  }
-
-  CHECK(default_service->connection());
-  return default_service->connection()->RequestHostRoute(trusted_ip);
-}
-
 }  // namespace shill
diff --git a/vpn_driver.h b/vpn_driver.h
index 85e8d2f..237438c 100644
--- a/vpn_driver.h
+++ b/vpn_driver.h
@@ -11,7 +11,6 @@
 #include <gtest/gtest_prod.h>  // for FRIEND_TEST
 
 #include "shill/accessor_interface.h"
-#include "shill/ipconfig.h"
 #include "shill/key_value_store.h"
 #include "shill/refptr_types.h"
 
@@ -56,11 +55,7 @@
 
   Manager *manager() const { return manager_; }
 
-  bool PinHostRoute(const IPConfig::Properties &properties);
-
  private:
-  FRIEND_TEST(VPNDriverTest, PinHostRoute);
-
   Stringmap GetProvider(Error *error);
   void ClearMappedProperty(const size_t &index, Error *error);
   std::string GetMappedProperty(const size_t &index, Error *error);
diff --git a/vpn_driver_unittest.cc b/vpn_driver_unittest.cc
index 837da01..041c4a0 100644
--- a/vpn_driver_unittest.cc
+++ b/vpn_driver_unittest.cc
@@ -278,53 +278,4 @@
   }
 }
 
-namespace {
-MATCHER_P(IsIPAddress, address, "") {
-  IPAddress ip_address(IPAddress::kFamilyIPv4);
-  EXPECT_TRUE(ip_address.SetAddressFromString(address));
-  return ip_address.Equals(arg);
-}
-}  // namespace
-
-TEST_F(VPNDriverTest, PinHostRoute) {
-  static const char kGateway[] = "10.242.2.13";
-  static const char kNetwork[] = "10.242.2.1";
-
-  IPConfig::Properties props;
-  props.address_family = IPAddress::kFamilyIPv4;
-  EXPECT_FALSE(driver_.PinHostRoute(props));
-
-  props.gateway = kGateway;
-  EXPECT_FALSE(driver_.PinHostRoute(props));
-
-  props.gateway.clear();
-  props.trusted_ip = "xxx";
-  EXPECT_FALSE(driver_.PinHostRoute(props));
-
-  props.gateway = kGateway;
-  EXPECT_FALSE(driver_.PinHostRoute(props));
-
-  props.trusted_ip = kNetwork;
-  EXPECT_CALL(manager_, GetDefaultService())
-      .WillOnce(Return(reinterpret_cast<Service *>(NULL)));
-  EXPECT_FALSE(driver_.PinHostRoute(props));
-
-  scoped_refptr<MockService> service(
-      new NiceMock<MockService>(&control_, &dispatcher_, &metrics_, &manager_));
-  scoped_refptr<MockConnection> connection(
-      new StrictMock<MockConnection>(&device_info_));
-  service->set_mock_connection(connection);
-  EXPECT_CALL(manager_, GetDefaultService())
-      .WillOnce(Return(service));
-
-  EXPECT_CALL(*connection, RequestHostRoute(IsIPAddress(kNetwork)))
-      .WillOnce(Return(false));
-  EXPECT_FALSE(driver_.PinHostRoute(props));
-
-  EXPECT_CALL(manager_, GetDefaultService()).WillOnce(Return(service));
-  EXPECT_CALL(*connection, RequestHostRoute(IsIPAddress(kNetwork)))
-      .WillOnce(Return(true));
-  EXPECT_TRUE(driver_.PinHostRoute(props));
-}
-
 }  // namespace shill