shill: vpn: Create a IPv6 blackhole route for IPv4 L2TP/IPsec VPN.

BUG=chromium-os:34383
TEST=Tested the following:
1. Build and run unit tests.
2. Manually test IPv4 LT2P/IPsec VPN on an network interface with IPv4
   and IPv6 addresses as follows:
   - Before connecting to the VPN, run `ip -6 route` to verify that no
     blackhole route exists. Confirm via http://ipv6test.google.com that
     IPv6 connectivity is available.
   - After connecting to the VPN, run `ip -6 route` to verify that the
     blackhole route is installed. Confirm via
     http://ipv6test.google.com that IPv6 connectivity is not available.
   - After disconnecting from the VPN, run `ip -6 route` to verify that
     no blackhole route exists. Confirm via http://ipv6test.google.com
     that IPv6 connectivity is available.

Change-Id: I7ae4fab5319b5f06a6a3f5a28f439551f2825044
Reviewed-on: https://gerrit.chromium.org/gerrit/34053
Commit-Ready: Ben Chan <benchan@chromium.org>
Reviewed-by: Ben Chan <benchan@chromium.org>
Tested-by: Ben Chan <benchan@chromium.org>
diff --git a/connection.cc b/connection.cc
index 20496fb..bc38e23 100644
--- a/connection.cc
+++ b/connection.cc
@@ -175,6 +175,12 @@
   // Install any explicitly configured routes at the default metric.
   routing_table_->ConfigureRoutes(interface_index_, config, kDefaultMetric);
 
+  if (properties.blackhole_ipv6) {
+    routing_table_->CreateBlackholeRoute(interface_index_,
+                                         IPAddress::kFamilyIPv6,
+                                         kDefaultMetric);
+  }
+
   // Save a copy of the last non-null DNS config.
   if (!config->properties().dns_servers.empty()) {
     dns_servers_ = config->properties().dns_servers;
diff --git a/connection.h b/connection.h
index 187b421..0de31c0 100644
--- a/connection.h
+++ b/connection.h
@@ -116,6 +116,7 @@
   FRIEND_TEST(ConnectionTest, InitState);
   FRIEND_TEST(ConnectionTest, OnRouteQueryResponse);
   FRIEND_TEST(ConnectionTest, RequestHostRoute);
+  FRIEND_TEST(ConnectionTest, BlackholeIPv6);
   FRIEND_TEST(VPNServiceTest, OnConnectionDisconnected);
 
   static const uint32 kDefaultMetric;
diff --git a/connection_unittest.cc b/connection_unittest.cc
index 8faff31..f4ac6f9 100644
--- a/connection_unittest.cc
+++ b/connection_unittest.cc
@@ -539,6 +539,22 @@
   AddDestructorExpectations();
 }
 
+TEST_F(ConnectionTest, BlackholeIPv6) {
+  properties_.blackhole_ipv6 = true;
+  UpdateProperties();
+  EXPECT_CALL(*device_info_, HasOtherAddress(_, _))
+      .WillOnce(Return(false));
+  EXPECT_CALL(rtnl_handler_, AddInterfaceAddress(_, _, _, _));
+  EXPECT_CALL(routing_table_, SetDefaultRoute(_, _, _));
+  EXPECT_CALL(routing_table_, ConfigureRoutes(_, _, _));
+  EXPECT_CALL(routing_table_,
+              CreateBlackholeRoute(kTestDeviceInterfaceIndex0,
+                                   IPAddress::kFamilyIPv6,
+                                   Connection::kDefaultMetric))
+      .WillOnce(Return(true));
+  connection_->UpdateFromIPConfig(ipconfig_);
+}
+
 TEST_F(ConnectionTest, PinHostRoute) {
   static const char kGateway[] = "10.242.2.13";
   static const char kNetwork[] = "10.242.2.1";
diff --git a/ipconfig.h b/ipconfig.h
index d40e27b..68dd696 100644
--- a/ipconfig.h
+++ b/ipconfig.h
@@ -38,6 +38,7 @@
   struct Properties {
     Properties() : address_family(IPAddress::kFamilyUnknown),
                    subnet_prefix(0),
+                   blackhole_ipv6(false),
                    mtu(0) {}
 
     IPAddress::Family address_family;
@@ -54,6 +55,7 @@
     // route installed.  This is usually the external IP address of the VPN
     // server.
     std::string trusted_ip;
+    bool blackhole_ipv6;
     int32 mtu;
     std::vector<Route> routes;
   };
diff --git a/ipconfig_unittest.cc b/ipconfig_unittest.cc
index 16051cb..3c191b3 100644
--- a/ipconfig_unittest.cc
+++ b/ipconfig_unittest.cc
@@ -79,6 +79,7 @@
   properties.domain_name = "foo.org";
   properties.domain_search.push_back("zoo.org");
   properties.domain_search.push_back("zoo.com");
+  properties.blackhole_ipv6 = true;
   properties.mtu = 700;
   ipconfig_->UpdateProperties(properties, true);
   EXPECT_EQ("1.2.3.4", ipconfig_->properties().address);
@@ -92,6 +93,7 @@
   EXPECT_EQ("zoo.org", ipconfig_->properties().domain_search[0]);
   EXPECT_EQ("zoo.com", ipconfig_->properties().domain_search[1]);
   EXPECT_EQ("foo.org", ipconfig_->properties().domain_name);
+  EXPECT_TRUE(ipconfig_->properties().blackhole_ipv6);
   EXPECT_EQ(700, ipconfig_->properties().mtu);
 }
 
diff --git a/l2tp_ipsec_driver.cc b/l2tp_ipsec_driver.cc
index 9476b87..be81308 100644
--- a/l2tp_ipsec_driver.cc
+++ b/l2tp_ipsec_driver.cc
@@ -372,6 +372,11 @@
       SLOG(VPN, 2) << "Key ignored.";
     }
   }
+
+  // There is no IPv6 support for L2TP/IPsec VPN at this moment, so create a
+  // blackhole route for IPv6 traffic after establishing a IPv4 VPN.
+  // TODO(benchan): Generalize this when IPv6 support is added.
+  properties->blackhole_ipv6 = true;
 }
 
 void L2TPIPSecDriver::Notify(
diff --git a/l2tp_ipsec_driver_unittest.cc b/l2tp_ipsec_driver_unittest.cc
index bd72f76..4908658 100644
--- a/l2tp_ipsec_driver_unittest.cc
+++ b/l2tp_ipsec_driver_unittest.cc
@@ -490,6 +490,7 @@
   EXPECT_EQ("1.1.1.1", props.dns_servers[0]);
   EXPECT_EQ("2.2.2.2", props.dns_servers[1]);
   EXPECT_EQ("ppp0", interface_name);
+  EXPECT_TRUE(props.blackhole_ipv6);
 }
 
 namespace {
diff --git a/mock_routing_table.h b/mock_routing_table.h
index f456b31..43fcca1 100644
--- a/mock_routing_table.h
+++ b/mock_routing_table.h
@@ -30,6 +30,9 @@
   MOCK_METHOD3(ConfigureRoutes, bool(int interface_index,
                                      const IPConfigRefPtr &ipconfig,
                                      uint32 metric));
+  MOCK_METHOD3(CreateBlackholeRoute, bool(int interface_index,
+                                          const IPAddress::Family &family,
+                                          uint32 metric));
   MOCK_METHOD3(CreateLinkRoute, bool(int interface_index,
                                      const IPAddress &local_address,
                                      const IPAddress &remote_address));
diff --git a/openvpn_driver_unittest.cc b/openvpn_driver_unittest.cc
index 8407893..e7f9345 100644
--- a/openvpn_driver_unittest.cc
+++ b/openvpn_driver_unittest.cc
@@ -434,6 +434,7 @@
   EXPECT_EQ(kGateway2, props.routes[1].gateway);
   EXPECT_EQ(kNetmask2, props.routes[1].netmask);
   EXPECT_EQ(kNetwork2, props.routes[1].host);
+  EXPECT_FALSE(props.blackhole_ipv6);
 }
 
 TEST_F(OpenVPNDriverTest, InitOptionsNoHost) {
diff --git a/routing_table.cc b/routing_table.cc
index a3418f5..31f2096 100644
--- a/routing_table.cc
+++ b/routing_table.cc
@@ -532,6 +532,41 @@
   return true;
 }
 
+bool RoutingTable::CreateBlackholeRoute(int interface_index,
+                                        const IPAddress::Family &family,
+                                        uint32 metric) {
+
+  SLOG(Route, 2) << base::StringPrintf(
+      "%s: index %d family %s metric %d",
+      __func__, interface_index,
+      IPAddress::GetAddressFamilyName(family).c_str(), metric);
+
+  RTNLMessage message(
+      RTNLMessage::kTypeRoute,
+      RTNLMessage::kModeAdd,
+      NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL,
+      0,
+      0,
+      0,
+      family);
+
+  message.set_route_status(RTNLMessage::RouteStatus(
+      0,
+      0,
+      RT_TABLE_MAIN,
+      RTPROT_BOOT,
+      RT_SCOPE_UNIVERSE,
+      RTN_BLACKHOLE,
+      0));
+
+  message.SetAttribute(RTA_PRIORITY,
+                       ByteString::CreateFromCPUUInt32(metric));
+  message.SetAttribute(RTA_OIF,
+                       ByteString::CreateFromCPUUInt32(interface_index));
+
+  return rtnl_handler_->SendMessage(&message);
+}
+
 bool RoutingTable::CreateLinkRoute(int interface_index,
                                    const IPAddress &local_address,
                                    const IPAddress &remote_address) {
diff --git a/routing_table.h b/routing_table.h
index f3216a3..7a9fdd6 100644
--- a/routing_table.h
+++ b/routing_table.h
@@ -77,6 +77,12 @@
                                const IPConfigRefPtr &ipconfig,
                                uint32 metric);
 
+  // Create a blackhole route for a given IP family.  Returns true
+  // on successfully sending the route request, false othrewise.
+  virtual bool CreateBlackholeRoute(int interface_index,
+                                    const IPAddress::Family &family,
+                                    uint32 metric);
+
   // Create a route to a link-attached remote host.  |remote_address|
   // must be directly reachable from |local_address|.  Returns true
   // on successfully sending the route request, false othrewise.
diff --git a/routing_table_unittest.cc b/routing_table_unittest.cc
index 52c105e..d78555b 100644
--- a/routing_table_unittest.cc
+++ b/routing_table_unittest.cc
@@ -157,6 +157,29 @@
 
 namespace {
 
+MATCHER_P3(IsBlackholeRoutingPacket, index, family, metric, "") {
+  const RTNLMessage::RouteStatus &status = arg->route_status();
+
+  uint32 oif;
+  uint32 priority;
+
+  return
+      arg->type() == RTNLMessage::kTypeRoute &&
+      arg->family() == family &&
+      arg->flags() == (NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL) &&
+      status.table == RT_TABLE_MAIN &&
+      status.protocol == RTPROT_BOOT &&
+      status.scope == RT_SCOPE_UNIVERSE &&
+      status.type == RTN_BLACKHOLE &&
+      !arg->HasAttribute(RTA_DST) &&
+      !arg->HasAttribute(RTA_SRC) &&
+      !arg->HasAttribute(RTA_GATEWAY) &&
+      arg->GetAttribute(RTA_OIF).ConvertToCPUUInt32(&oif) &&
+      oif == index &&
+      arg->GetAttribute(RTA_PRIORITY).ConvertToCPUUInt32(&priority) &&
+      priority == metric;
+}
+
 MATCHER_P4(IsRoutingPacket, mode, index, entry, flags, "") {
   const RTNLMessage::RouteStatus &status = arg->route_status();
 
@@ -773,6 +796,18 @@
                                 RTPROT_UNSPEC);
 }
 
+TEST_F(RoutingTableTest, CreateBlackholeRoute) {
+  const uint32 kMetric = 2;
+  EXPECT_CALL(rtnl_handler_,
+              SendMessage(IsBlackholeRoutingPacket(kTestDeviceIndex0,
+                                                   IPAddress::kFamilyIPv6,
+                                                   kMetric)))
+      .Times(1);
+  EXPECT_TRUE(routing_table_->CreateBlackholeRoute(kTestDeviceIndex0,
+                                                   IPAddress::kFamilyIPv6,
+                                                   kMetric));
+}
+
 TEST_F(RoutingTableTest, CreateLinkRoute) {
   IPAddress local_address(IPAddress::kFamilyIPv4);
   ASSERT_TRUE(local_address.SetAddressFromString(kTestNetAddress0));