shill: Add a query-route callback to RoutingTable::RequestRoutToHost.

The callback will be used by VPN connections to handle underlying
connection disconnects gracefully.

BUG=chromium-os:28080
TEST=unit tests

Change-Id: I1217ae58f7626c41680a81d0f6261b883e17df85
Reviewed-on: https://gerrit.chromium.org/gerrit/22103
Tested-by: Darin Petkov <petkov@chromium.org>
Reviewed-by: Paul Stewart <pstew@chromium.org>
Commit-Ready: Darin Petkov <petkov@chromium.org>
diff --git a/routing_table_unittest.cc b/routing_table_unittest.cc
index 4879a5b..db09f13 100644
--- a/routing_table_unittest.cc
+++ b/routing_table_unittest.cc
@@ -20,11 +20,14 @@
 #include "shill/rtnl_handler.h"
 #include "shill/rtnl_message.h"
 
+using base::Bind;
 using base::Callback;
+using base::Unretained;
 using base::hash_map;
-using std::queue;
+using std::deque;
 using std::vector;
 using testing::_;
+using testing::Field;
 using testing::Invoke;
 using testing::Return;
 using testing::StrictMock;
@@ -58,7 +61,7 @@
     return &routing_table_->tables_;
   }
 
-  queue<RoutingTable::Query> *GetQueries() {
+  deque<RoutingTable::Query> *GetQueries() {
     return &routing_table_->route_queries_;
   }
 
@@ -98,6 +101,19 @@
   static const uint32 kTestRequestSeq;
   static const int kTestRouteTag;
 
+  class QueryCallbackTarget {
+   public:
+    QueryCallbackTarget()
+        : callback_(Bind(&QueryCallbackTarget::CallTarget, Unretained(this))) {}
+
+    MOCK_METHOD2(CallTarget,
+                 void(int interface_index, const RoutingTableEntry &entry));
+    const RoutingTable::Query::Callback &callback() { return callback_; }
+
+   private:
+    RoutingTable::Query::Callback callback_;
+  };
+
   RoutingTable *routing_table_;
   TestEventDispatcher dispatcher_;
   StrictMock<MockRTNLHandler> rtnl_handler_;
@@ -121,6 +137,8 @@
 const uint32 RoutingTableTest::kTestRequestSeq = 456;
 const int RoutingTableTest::kTestRouteTag = 789;
 
+namespace {
+
 MATCHER_P4(IsRoutingPacket, mode, index, entry, flags, "") {
   const RTNLMessage::RouteStatus &status = arg->route_status();
 
@@ -149,6 +167,8 @@
       priority == entry.metric;
 }
 
+}  // namespace
+
 void RoutingTableTest::SendRouteEntry(RTNLMessage::Mode mode,
                                       uint32 interface_index,
                                       const RoutingTableEntry &entry) {
@@ -509,9 +529,11 @@
               SendMessage(IsRoutingQuery(destination_address,
                                          kTestDeviceIndex0)))
       .WillOnce(Invoke(this, &RoutingTableTest::SetSequenceForMessage));
-  EXPECT_TRUE(routing_table_->RequestRouteToHost(destination_address,
-                                                 kTestDeviceIndex0,
-                                                 kTestRouteTag));
+  EXPECT_TRUE(
+      routing_table_->RequestRouteToHost(destination_address,
+                                         kTestDeviceIndex0,
+                                         kTestRouteTag,
+                                         RoutingTable::Query::Callback()));
 
   IPAddress gateway_address(IPAddress::kFamilyIPv4);
   gateway_address.SetAddressFromString(kTestGatewayAddress4);
@@ -538,8 +560,7 @@
                                 kTestRequestSeq,
                                 RTPROT_UNSPEC);
 
-  hash_map<int, vector<RoutingTableEntry> > *tables =
-    GetRoutingTables();
+  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());
@@ -549,6 +570,8 @@
   // This entry's tag should match the tag we requested.
   EXPECT_EQ(kTestRouteTag, (*tables)[kTestDeviceIndex0][0].tag);
 
+  EXPECT_TRUE(GetQueries()->empty());
+
   // Ask to flush routes with our tag.  We should see a delete message sent.
   EXPECT_CALL(rtnl_handler_,
               SendMessage(IsRoutingPacket(RTNLMessage::kModeDelete,
@@ -571,9 +594,11 @@
               SendMessage(IsRoutingQuery(destination_address,
                                          kTestDeviceIndex0)))
       .WillOnce(Invoke(this, &RoutingTableTest::SetSequenceForMessage));
-  EXPECT_TRUE(routing_table_->RequestRouteToHost(destination_address,
-                                                 kTestDeviceIndex0,
-                                                 kTestRouteTag));
+  EXPECT_TRUE(
+      routing_table_->RequestRouteToHost(destination_address,
+                                         kTestDeviceIndex0,
+                                         kTestRouteTag,
+                                         RoutingTable::Query::Callback()));
 
   // Don't specify a gateway address.
   IPAddress gateway_address(IPAddress::kFamilyIPv4);
@@ -596,16 +621,21 @@
                                 entry,
                                 kTestRequestSeq,
                                 RTPROT_UNSPEC);
+  EXPECT_TRUE(GetQueries()->empty());
 }
 
 TEST_F(RoutingTableTest, RequestHostRouteBadSequence) {
   IPAddress destination_address(IPAddress::kFamilyIPv4);
   destination_address.SetAddressFromString(kTestRemoteAddress4);
+  QueryCallbackTarget target;
+  EXPECT_CALL(target, CallTarget(_, _)).Times(0);
   EXPECT_CALL(rtnl_handler_, SendMessage(_))
       .WillOnce(Invoke(this, &RoutingTableTest::SetSequenceForMessage));
-  EXPECT_TRUE(routing_table_->RequestRouteToHost(destination_address,
-                                                 kTestDeviceIndex0,
-                                                 kTestRouteTag));
+  EXPECT_TRUE(
+      routing_table_->RequestRouteToHost(destination_address,
+                                         kTestDeviceIndex0,
+                                         kTestRouteTag,
+                                         target.callback()));
   EXPECT_FALSE(GetQueries()->empty());
 
   RoutingTableEntry entry(destination_address,
@@ -620,7 +650,7 @@
   SendRouteEntryWithSeqAndProto(RTNLMessage::kModeAdd,
                                 kTestDeviceIndex0,
                                 entry,
-                                kTestRequestSeq-1,
+                                kTestRequestSeq - 1,
                                 RTPROT_UNSPEC);
   EXPECT_FALSE(GetQueries()->empty());
 
@@ -629,9 +659,108 @@
   SendRouteEntryWithSeqAndProto(RTNLMessage::kModeAdd,
                                 kTestDeviceIndex0,
                                 entry,
-                                kTestRequestSeq+1,
+                                kTestRequestSeq + 1,
                                 RTPROT_UNSPEC);
   EXPECT_TRUE(GetQueries()->empty());
 }
 
+TEST_F(RoutingTableTest, RequestHostRouteWithCallback) {
+  IPAddress destination_address(IPAddress::kFamilyIPv4);
+
+  EXPECT_CALL(rtnl_handler_, SendMessage(_))
+      .WillOnce(Invoke(this, &RoutingTableTest::SetSequenceForMessage));
+  QueryCallbackTarget target;
+  EXPECT_TRUE(
+      routing_table_->RequestRouteToHost(
+          destination_address, -1, kTestRouteTag, target.callback()));
+
+  IPAddress gateway_address(IPAddress::kFamilyIPv4);
+  gateway_address.SetAddressFromString(kTestGatewayAddress4);
+  IPAddress local_address(IPAddress::kFamilyIPv4);
+
+  const int kMetric = 10;
+  RoutingTableEntry entry(destination_address,
+                          local_address,
+                          gateway_address,
+                          kMetric,
+                          RT_SCOPE_UNIVERSE,
+                          true);
+
+  EXPECT_CALL(rtnl_handler_, SendMessage(_));
+  EXPECT_CALL(target,
+              CallTarget(kTestDeviceIndex0,
+                         Field(&RoutingTableEntry::tag, kTestRouteTag)));
+  SendRouteEntryWithSeqAndProto(RTNLMessage::kModeAdd,
+                                kTestDeviceIndex0,
+                                entry,
+                                kTestRequestSeq,
+                                RTPROT_UNSPEC);
+}
+
+TEST_F(RoutingTableTest, RequestHostRouteWithoutGatewayWithCallback) {
+  IPAddress destination_address(IPAddress::kFamilyIPv4);
+
+  EXPECT_CALL(rtnl_handler_, SendMessage(_))
+      .WillOnce(Invoke(this, &RoutingTableTest::SetSequenceForMessage));
+  QueryCallbackTarget target;
+  EXPECT_TRUE(
+      routing_table_->RequestRouteToHost(
+          destination_address, -1, kTestRouteTag, target.callback()));
+
+  IPAddress gateway_address(IPAddress::kFamilyIPv4);
+  IPAddress local_address(IPAddress::kFamilyIPv4);
+
+  const int kMetric = 10;
+  RoutingTableEntry entry(destination_address,
+                          local_address,
+                          gateway_address,
+                          kMetric,
+                          RT_SCOPE_UNIVERSE,
+                          true);
+
+  EXPECT_CALL(target,
+              CallTarget(kTestDeviceIndex0,
+                         Field(&RoutingTableEntry::tag, kTestRouteTag)));
+  SendRouteEntryWithSeqAndProto(RTNLMessage::kModeAdd,
+                                kTestDeviceIndex0,
+                                entry,
+                                kTestRequestSeq,
+                                RTPROT_UNSPEC);
+}
+
+TEST_F(RoutingTableTest, CancelQueryCallback) {
+  IPAddress destination_address(IPAddress::kFamilyIPv4);
+  destination_address.SetAddressFromString(kTestRemoteAddress4);
+  QueryCallbackTarget target1;
+  QueryCallbackTarget target2;
+  QueryCallbackTarget target3;
+  EXPECT_CALL(rtnl_handler_, SendMessage(_)).Times(4);
+  EXPECT_TRUE(
+      routing_table_->RequestRouteToHost(destination_address,
+                                         kTestDeviceIndex0,
+                                         kTestRouteTag,
+                                         target1.callback()));
+  EXPECT_TRUE(
+      routing_table_->RequestRouteToHost(destination_address,
+                                         kTestDeviceIndex0,
+                                         kTestRouteTag,
+                                         target2.callback()));
+  EXPECT_TRUE(
+      routing_table_->RequestRouteToHost(destination_address,
+                                         kTestDeviceIndex0,
+                                         kTestRouteTag,
+                                         target2.callback()));
+  EXPECT_TRUE(
+      routing_table_->RequestRouteToHost(destination_address,
+                                         kTestDeviceIndex0,
+                                         kTestRouteTag,
+                                         target3.callback()));
+  ASSERT_EQ(4, GetQueries()->size());
+  routing_table_->CancelQueryCallback(target2.callback());
+  EXPECT_TRUE(GetQueries()->at(0).callback.Equals(target1.callback()));
+  EXPECT_TRUE(GetQueries()->at(1).callback.is_null());
+  EXPECT_TRUE(GetQueries()->at(2).callback.is_null());
+  EXPECT_TRUE(GetQueries()->at(3).callback.Equals(target3.callback()));
+}
+
 }  // namespace shill