shill: vpn: Parse route options through openvpn notify callback.

BUG=chromium-os:27056
TEST=unit tests

Change-Id: Ia69e87c040936a7375cb31a69b3724f39be580d6
Reviewed-on: https://gerrit.chromium.org/gerrit/17314
Commit-Ready: Darin Petkov <petkov@chromium.org>
Reviewed-by: Darin Petkov <petkov@chromium.org>
Tested-by: Darin Petkov <petkov@chromium.org>
diff --git a/ipconfig.h b/ipconfig.h
index ed2fc35..b3afed2 100644
--- a/ipconfig.h
+++ b/ipconfig.h
@@ -17,6 +17,7 @@
 #include "shill/ip_address.h"
 #include "shill/property_store.h"
 #include "shill/refptr_types.h"
+#include "shill/routing_table_entry.h"
 
 namespace shill {
 class ControlInterface;
@@ -28,6 +29,12 @@
 // class.
 class IPConfig : public base::RefCounted<IPConfig> {
  public:
+  struct Route {
+    std::string host;
+    std::string netmask;
+    std::string gateway;
+  };
+
   struct Properties {
     Properties() : address_family(IPAddress::kFamilyUnknown),
                    subnet_cidr(0),
@@ -44,6 +51,7 @@
     std::string method;
     std::string peer_address;
     int32 mtu;
+    std::vector<Route> routes;
   };
 
   IPConfig(ControlInterface *control_interface, const std::string &device_name);
diff --git a/openvpn_driver.cc b/openvpn_driver.cc
index 9ffaf62..de28daf 100644
--- a/openvpn_driver.cc
+++ b/openvpn_driver.cc
@@ -86,6 +86,7 @@
     const map<string, string> &configuration,
     IPConfig::Properties *properties) {
   ForeignOptions foreign_options;
+  RouteOptions routes;
   string trusted_ip;
   properties->address_family = IPAddress::kFamilyIPv4;
   for (map<string, string>::const_iterator it = configuration.begin();
@@ -122,13 +123,15 @@
         LOG(ERROR) << "Ignored unexpected foreign option suffix: " << suffix;
       }
     } else if (StartsWithASCII(key, kOpenVPNRouteOptionPrefix, false)) {
-      // TODO(petkov): Process the route.
+      ParseRouteOption(key.substr(strlen(kOpenVPNRouteOptionPrefix)),
+                       value, &routes);
     } else {
       VLOG(2) << "Key ignored.";
     }
   }
   // TODO(petkov): If gateway and trusted_ip, pin a host route to VPN server.
   ParseForeignOptions(foreign_options, properties);
+  SetRoutes(routes, properties);
 }
 
 // static
@@ -156,6 +159,52 @@
   }
 }
 
+// static
+IPConfig::Route *OpenVPNDriver::GetRouteOptionEntry(
+    const string &prefix, const string &key, RouteOptions *routes) {
+  int order = 0;
+  if (!StartsWithASCII(key, prefix, false) ||
+      !base::StringToInt(key.substr(prefix.size()), &order)) {
+    return NULL;
+  }
+  return &(*routes)[order];
+}
+
+// static
+void OpenVPNDriver::ParseRouteOption(
+    const string &key, const string &value, RouteOptions *routes) {
+  IPConfig::Route *route = GetRouteOptionEntry("network_", key, routes);
+  if (route) {
+    route->host = value;
+    return;
+  }
+  route = GetRouteOptionEntry("netmask_", key, routes);
+  if (route) {
+    route->netmask = value;
+    return;
+  }
+  route = GetRouteOptionEntry("gateway_", key, routes);
+  if (route) {
+    route->gateway = value;
+    return;
+  }
+  LOG(WARNING) << "Unknown route option ignored: " << key;
+}
+
+// static
+void OpenVPNDriver::SetRoutes(const RouteOptions &routes,
+                              IPConfig::Properties *properties) {
+  for (RouteOptions::const_iterator it = routes.begin();
+       it != routes.end(); ++it) {
+    const IPConfig::Route &route = it->second;
+    if (route.host.empty() || route.netmask.empty() || route.gateway.empty()) {
+      LOG(WARNING) << "Ignoring incomplete route: " << it->first;
+      continue;
+    }
+    properties->routes.push_back(route);
+  }
+}
+
 void OpenVPNDriver::Connect(Error *error) {
   if (!device_info_->CreateTunnelInterface(&tunnel_interface_)) {
     Error::PopulateAndLog(
diff --git a/openvpn_driver.h b/openvpn_driver.h
index bcefc36..6201e01 100644
--- a/openvpn_driver.h
+++ b/openvpn_driver.h
@@ -57,15 +57,19 @@
   FRIEND_TEST(OpenVPNDriverTest, AppendValueOption);
   FRIEND_TEST(OpenVPNDriverTest, ClaimInterface);
   FRIEND_TEST(OpenVPNDriverTest, Connect);
+  FRIEND_TEST(OpenVPNDriverTest, GetRouteOptionEntry);
   FRIEND_TEST(OpenVPNDriverTest, InitOptions);
   FRIEND_TEST(OpenVPNDriverTest, InitOptionsNoHost);
   FRIEND_TEST(OpenVPNDriverTest, ParseForeignOption);
   FRIEND_TEST(OpenVPNDriverTest, ParseForeignOptions);
   FRIEND_TEST(OpenVPNDriverTest, ParseIPConfiguration);
+  FRIEND_TEST(OpenVPNDriverTest, ParseRouteOption);
+  FRIEND_TEST(OpenVPNDriverTest, SetRoutes);
 
   // The map is a sorted container that allows us to iterate through the options
   // in order.
   typedef std::map<int, std::string> ForeignOptions;
+  typedef std::map<int, IPConfig::Route> RouteOptions;
 
   static void ParseIPConfiguration(
       const std::map<std::string, std::string> &configuration,
@@ -74,6 +78,14 @@
                                   IPConfig::Properties *properties);
   static void ParseForeignOption(const std::string &option,
                                  IPConfig::Properties *properties);
+  static IPConfig::Route *GetRouteOptionEntry(const std::string &prefix,
+                                              const std::string &key,
+                                              RouteOptions *routes);
+  static void ParseRouteOption(const std::string &key,
+                               const std::string &value,
+                               RouteOptions *routes);
+  static void SetRoutes(const RouteOptions &routes,
+                        IPConfig::Properties *properties);
 
   void InitOptions(std::vector<std::string> *options, Error *error);
 
diff --git a/openvpn_driver_unittest.cc b/openvpn_driver_unittest.cc
index 514ed20..e4fc075 100644
--- a/openvpn_driver_unittest.cc
+++ b/openvpn_driver_unittest.cc
@@ -51,6 +51,12 @@
   static const char kOption2[];
   static const char kProperty2[];
   static const char kValue2[];
+  static const char kGateway1[];
+  static const char kNetmask1[];
+  static const char kNetwork1[];
+  static const char kGateway2[];
+  static const char kNetmask2[];
+  static const char kNetwork2[];
 
   void SetArgs() {
     driver_.args_ = args_;
@@ -77,6 +83,12 @@
 const char OpenVPNDriverTest::kOption2[] = "--openvpn-option2";
 const char OpenVPNDriverTest::kProperty2[] = "OpenVPN.SomeProperty2";
 const char OpenVPNDriverTest::kValue2[] = "some-property-value2";
+const char OpenVPNDriverTest::kGateway1[] = "10.242.2.13";
+const char OpenVPNDriverTest::kNetmask1[] = "255.255.255.255";
+const char OpenVPNDriverTest::kNetwork1[] = "10.242.2.1";
+const char OpenVPNDriverTest::kGateway2[] = "10.242.2.14";
+const char OpenVPNDriverTest::kNetmask2[] = "255.255.0.0";
+const char OpenVPNDriverTest::kNetwork2[] = "192.168.0.0";
 
 void OpenVPNDriverTest::Notify(const string &/*reason*/,
                                const map<string, string> &/*dict*/) {}
@@ -123,6 +135,68 @@
   EXPECT_TRUE(driver_.Notify("up", dict));
 }
 
+TEST_F(OpenVPNDriverTest, GetRouteOptionEntry) {
+  OpenVPNDriver::RouteOptions routes;
+  EXPECT_EQ(NULL, OpenVPNDriver::GetRouteOptionEntry("foo", "bar", &routes));
+  EXPECT_TRUE(routes.empty());
+  EXPECT_EQ(NULL, OpenVPNDriver::GetRouteOptionEntry("foo", "foo", &routes));
+  EXPECT_TRUE(routes.empty());
+  EXPECT_EQ(NULL, OpenVPNDriver::GetRouteOptionEntry("foo", "fooZ", &routes));
+  EXPECT_TRUE(routes.empty());
+  IPConfig::Route *route =
+      OpenVPNDriver::GetRouteOptionEntry("foo", "foo12", &routes);
+  EXPECT_EQ(1, routes.size());
+  EXPECT_EQ(route, &routes[12]);
+  route = OpenVPNDriver::GetRouteOptionEntry("foo", "foo13", &routes);
+  EXPECT_EQ(2, routes.size());
+  EXPECT_EQ(route, &routes[13]);
+}
+
+TEST_F(OpenVPNDriverTest, ParseRouteOption) {
+  OpenVPNDriver::RouteOptions routes;
+  OpenVPNDriver::ParseRouteOption("foo", "bar", &routes);
+  EXPECT_TRUE(routes.empty());
+  OpenVPNDriver::ParseRouteOption("gateway_2", kGateway2, &routes);
+  OpenVPNDriver::ParseRouteOption("netmask_2", kNetmask2, &routes);
+  OpenVPNDriver::ParseRouteOption("network_2", kNetwork2, &routes);
+  EXPECT_EQ(1, routes.size());
+  OpenVPNDriver::ParseRouteOption("gateway_1", kGateway1, &routes);
+  OpenVPNDriver::ParseRouteOption("netmask_1", kNetmask1, &routes);
+  OpenVPNDriver::ParseRouteOption("network_1", kNetwork1, &routes);
+  EXPECT_EQ(2, routes.size());
+  EXPECT_EQ(kGateway1, routes[1].gateway);
+  EXPECT_EQ(kNetmask1, routes[1].netmask);
+  EXPECT_EQ(kNetwork1, routes[1].host);
+  EXPECT_EQ(kGateway2, routes[2].gateway);
+  EXPECT_EQ(kNetmask2, routes[2].netmask);
+  EXPECT_EQ(kNetwork2, routes[2].host);
+}
+
+TEST_F(OpenVPNDriverTest, SetRoutes) {
+  OpenVPNDriver::RouteOptions routes;
+  routes[1].gateway = "1.2.3.4";
+  routes[1].host= "1.2.3.4";
+  routes[2].host = "2.3.4.5";
+  routes[2].netmask = "255.0.0.0";
+  routes[3].netmask = "255.0.0.0";
+  routes[3].gateway = "1.2.3.5";
+  routes[5].host = kNetwork2;
+  routes[5].netmask = kNetmask2;
+  routes[5].gateway = kGateway2;
+  routes[4].host = kNetwork1;
+  routes[4].netmask = kNetmask1;
+  routes[4].gateway = kGateway1;
+  IPConfig::Properties props;
+  OpenVPNDriver::SetRoutes(routes, &props);
+  ASSERT_EQ(2, props.routes.size());
+  EXPECT_EQ(kGateway1, props.routes[0].gateway);
+  EXPECT_EQ(kNetmask1, props.routes[0].netmask);
+  EXPECT_EQ(kNetwork1, props.routes[0].host);
+  EXPECT_EQ(kGateway2, props.routes[1].gateway);
+  EXPECT_EQ(kNetmask2, props.routes[1].netmask);
+  EXPECT_EQ(kNetwork2, props.routes[1].host);
+}
+
 TEST_F(OpenVPNDriverTest, ParseForeignOption) {
   IPConfig::Properties props;
   OpenVPNDriver::ParseForeignOption("", &props);
@@ -165,6 +239,12 @@
   config["foreign_option_2"] = "dhcp-option DNS 4.4.4.4";
   config["foreign_option_1"] = "dhcp-option DNS 1.1.1.1";
   config["foreign_option_3"] = "dhcp-option DNS 2.2.2.2";
+  config["route_network_2"] = kNetwork2;
+  config["route_network_1"] = kNetwork1;
+  config["route_netmask_2"] = kNetmask2;
+  config["route_netmask_1"] = kNetmask1;
+  config["route_gateway_2"] = kGateway2;
+  config["route_gateway_1"] = kGateway1;
   config["foo"] = "bar";
   IPConfig::Properties props;
   OpenVPNDriver::ParseIPConfiguration(config, &props);
@@ -179,6 +259,13 @@
   EXPECT_EQ("1.1.1.1", props.dns_servers[0]);
   EXPECT_EQ("4.4.4.4", props.dns_servers[1]);
   EXPECT_EQ("2.2.2.2", props.dns_servers[2]);
+  ASSERT_EQ(2, props.routes.size());
+  EXPECT_EQ(kGateway1, props.routes[0].gateway);
+  EXPECT_EQ(kNetmask1, props.routes[0].netmask);
+  EXPECT_EQ(kNetwork1, props.routes[0].host);
+  EXPECT_EQ(kGateway2, props.routes[1].gateway);
+  EXPECT_EQ(kNetmask2, props.routes[1].netmask);
+  EXPECT_EQ(kNetwork2, props.routes[1].host);
 }
 
 TEST_F(OpenVPNDriverTest, InitOptionsNoHost) {