shill: DHCPConfig: Configure classless static routes

Parse the classless static route option from dhclient and
set both the default route and static routes.

BUG=chromium-os:25908
TEST=New client autotest network_DhcpClasslessStaticRoute
CQ-DEPEND=I86d7a201ae37bf6d3f108a44650e3176d00b65c6

Change-Id: I6acecb09258229d84d4aa43372a1dc13fbac1df5
Reviewed-on: https://gerrit.chromium.org/gerrit/38203
Tested-by: Paul Stewart <pstew@chromium.org>
Reviewed-by: mukesh agrawal <quiche@chromium.org>
Commit-Ready: Paul Stewart <pstew@chromium.org>
diff --git a/dhcp_config.cc b/dhcp_config.cc
index 05cbdb5..3044c86 100644
--- a/dhcp_config.cc
+++ b/dhcp_config.cc
@@ -8,6 +8,7 @@
 #include <sys/wait.h>
 
 #include <base/file_util.h>
+#include <base/string_split.h>
 #include <base/stringprintf.h>
 #include <chromeos/dbus/service_constants.h>
 
@@ -27,6 +28,8 @@
 
 // static
 const char DHCPConfig::kConfigurationKeyBroadcastAddress[] = "BroadcastAddress";
+const char DHCPConfig::kConfigurationKeyClasslessStaticRoutes[] =
+    "ClasslessStaticRoutes";
 const char DHCPConfig::kConfigurationKeyDNS[] = "DomainNameServers";
 const char DHCPConfig::kConfigurationKeyDomainName[] = "DomainName";
 const char DHCPConfig::kConfigurationKeyDomainSearch[] = "DomainSearch";
@@ -266,11 +269,81 @@
   return "";
 }
 
-bool DHCPConfig::ParseConfiguration(const Configuration& configuration,
+// static
+bool DHCPConfig::ParseClasslessStaticRoutes(const string &classless_routes,
+                                            IPConfig::Properties *properties) {
+  if (classless_routes.empty()) {
+    // It is not an error for this string to be empty.
+    return true;
+  }
+
+  vector<string> route_strings;
+  base::SplitString(classless_routes, ' ', &route_strings);
+  if (route_strings.size() % 2) {
+    LOG(ERROR) << "In " << __func__ << ": Size of route_strings array "
+               << "is a non-even number: " << route_strings.size();
+    return false;
+  }
+
+  vector<IPConfig::Route> routes;
+  vector<string>::iterator route_iterator = route_strings.begin();
+  // Classless routes are a space-delimited array of
+  // "destination/prefix gateway" values.  As such, we iterate twice
+  // for each pass of the loop below.
+  while (route_iterator != route_strings.end()) {
+    const string &destination_as_string(*route_iterator);
+    route_iterator++;
+    IPAddress destination(IPAddress::kFamilyIPv4);
+    if (!destination.SetAddressAndPrefixFromString(
+             destination_as_string)) {
+      LOG(ERROR) << "In " << __func__ << ": Expected an IP address/prefix "
+                 << "but got an unparsable: " << destination_as_string;
+      return false;
+    }
+
+    CHECK(route_iterator != route_strings.end());
+    const string &gateway_as_string(*route_iterator);
+    route_iterator++;
+    IPAddress gateway(IPAddress::kFamilyIPv4);
+    if (!gateway.SetAddressFromString(gateway_as_string)) {
+      LOG(ERROR) << "In " << __func__ << ": Expected a router IP address "
+                 << "but got an unparsable: " << gateway_as_string;
+      return false;
+    }
+
+    if (destination.prefix() == 0 && properties->gateway.empty()) {
+      // If a default route is provided in the classless parameters and
+      // we don't already have one, apply this as the default route.
+      SLOG(DHCP, 2) << "In " << __func__ << ": Setting default gateway to "
+                    << gateway_as_string;
+      CHECK(gateway.IntoString(&properties->gateway));
+    } else {
+      IPConfig::Route route;
+      CHECK(destination.IntoString(&route.host));
+      IPAddress netmask(IPAddress::GetAddressMaskFromPrefix(
+          destination.family(), destination.prefix()));
+      CHECK(netmask.IntoString(&route.netmask));
+      CHECK(gateway.IntoString(&route.gateway));
+      routes.push_back(route);
+      SLOG(DHCP, 2) << "In " << __func__ << ": Adding route to to "
+                    << destination_as_string << " via " << gateway_as_string;
+    }
+  }
+
+  if (!routes.empty()) {
+    properties->routes.swap(routes);
+  }
+
+  return true;
+}
+
+bool DHCPConfig::ParseConfiguration(const Configuration &configuration,
                                     IPConfig::Properties *properties) {
   SLOG(DHCP, 2) << __func__;
   properties->method = flimflam::kTypeDHCP;
   properties->address_family = IPAddress::kFamilyIPv4;
+  string classless_static_routes;
+  bool default_gateway_parse_error = false;
   for (Configuration::const_iterator it = configuration.begin();
        it != configuration.end(); ++it) {
     const string &key = it->first;
@@ -293,11 +366,13 @@
       vector<unsigned int> routers = value.operator vector<unsigned int>();
       if (routers.empty()) {
         LOG(ERROR) << "No routers provided.";
-        return false;
-      }
-      properties->gateway = GetIPv4AddressString(routers[0]);
-      if (properties->gateway.empty()) {
-        return false;
+        default_gateway_parse_error = true;
+      } else {
+        properties->gateway = GetIPv4AddressString(routers[0]);
+        if (properties->gateway.empty()) {
+          LOG(ERROR) << "Failed to parse router parameter provided.";
+          default_gateway_parse_error = true;
+        }
       }
     } else if (key == kConfigurationKeyDNS) {
       vector<unsigned int> servers = value.operator vector<unsigned int>();
@@ -318,10 +393,16 @@
       if (mtu >= kMinMTU) {
         properties->mtu = mtu;
       }
+    } else if (key == kConfigurationKeyClasslessStaticRoutes) {
+      classless_static_routes = value.reader().get_string();
     } else {
       SLOG(DHCP, 2) << "Key ignored.";
     }
   }
+  ParseClasslessStaticRoutes(classless_static_routes, properties);
+  if (default_gateway_parse_error && properties->gateway.empty()) {
+    return false;
+  }
   return true;
 }
 
diff --git a/dhcp_config.h b/dhcp_config.h
index 65e1432..1df1728 100644
--- a/dhcp_config.h
+++ b/dhcp_config.h
@@ -72,6 +72,7 @@
   friend class DHCPConfigTest;
   FRIEND_TEST(DHCPConfigTest, GetIPv4AddressString);
   FRIEND_TEST(DHCPConfigTest, InitProxy);
+  FRIEND_TEST(DHCPConfigTest, ParseClasslessStaticRoutes);
   FRIEND_TEST(DHCPConfigTest, ParseConfiguration);
   FRIEND_TEST(DHCPConfigTest, ProcessEventSignalFail);
   FRIEND_TEST(DHCPConfigTest, ProcessEventSignalSuccess);
@@ -94,6 +95,7 @@
   FRIEND_TEST(DHCPProviderTest, CreateConfig);
 
   static const char kConfigurationKeyBroadcastAddress[];
+  static const char kConfigurationKeyClasslessStaticRoutes[];
   static const char kConfigurationKeyDNS[];
   static const char kConfigurationKeyDomainName[];
   static const char kConfigurationKeyDomainSearch[];
@@ -129,9 +131,17 @@
   // and false otherwise.
   bool Restart();
 
+  // Parses |classless_routes| into |properties|.  Sets the default gateway
+  // if one is supplied and |properties| does not already contain one.  It
+  // also sets the "routes" parameter of the IPConfig properties for all
+  // routes not converted into the default gateway.  Returns true on
+  // success, and false otherwise.
+  static bool ParseClasslessStaticRoutes(const std::string &classless_routes,
+                                         IPConfig::Properties *properties);
+
   // Parses |configuration| into |properties|. Returns true on success, and
   // false otherwise.
-  bool ParseConfiguration(const Configuration& configuration,
+  bool ParseConfiguration(const Configuration &configuration,
                           IPConfig::Properties *properties);
 
   // Returns the string representation of the IP address |address|, or an
diff --git a/dhcp_config_unittest.cc b/dhcp_config_unittest.cc
index 4bfcfb0..dc7bb00 100644
--- a/dhcp_config_unittest.cc
+++ b/dhcp_config_unittest.cc
@@ -183,6 +183,60 @@
   config_->InitProxy(kService);
 }
 
+TEST_F(DHCPConfigTest, ParseClasslessStaticRoutes) {
+  const string kDefaultAddress = "0.0.0.0";
+  const string kDefaultDestination = kDefaultAddress + "/0";
+  const string kRouter0 = "10.0.0.254";
+  const string kAddress1 = "192.168.1.0";
+  const string kDestination1 = kAddress1 + "/24";
+  // Last gateway missing, leaving an odd number of parameters.
+  const string kBrokenClasslessRoutes0 = kDefaultDestination + " " + kRouter0 +
+      " " + kDestination1;
+  IPConfig::Properties properties;
+  EXPECT_FALSE(DHCPConfig::ParseClasslessStaticRoutes(kBrokenClasslessRoutes0,
+                                                      &properties));
+  EXPECT_TRUE(properties.routes.empty());
+  EXPECT_TRUE(properties.gateway.empty());
+
+  // Gateway argument for the second route is malformed, but we were able
+  // to salvage a default gateway.
+  const string kBrokenRouter1 = "10.0.0";
+  const string kBrokenClasslessRoutes1 = kBrokenClasslessRoutes0 + " " +
+      kBrokenRouter1;
+  EXPECT_FALSE(DHCPConfig::ParseClasslessStaticRoutes(kBrokenClasslessRoutes1,
+                                                      &properties));
+  EXPECT_TRUE(properties.routes.empty());
+  EXPECT_EQ(kRouter0, properties.gateway);
+
+  const string kRouter1 = "10.0.0.253";
+  const string kRouter2 = "10.0.0.252";
+  const string kClasslessRoutes0 = kDefaultDestination + " " + kRouter2 + " " +
+      kDestination1 + " " + kRouter1;
+  EXPECT_TRUE(DHCPConfig::ParseClasslessStaticRoutes(kClasslessRoutes0,
+                                                      &properties));
+  // The old default route is preserved.
+  EXPECT_EQ(kRouter0, properties.gateway);
+
+  // The two routes (including the one which would have otherwise been
+  // classified as a default route) are added to the routing table.
+  EXPECT_EQ(2, properties.routes.size());
+  const IPConfig::Route &route0 = properties.routes[0];
+  EXPECT_EQ(kDefaultAddress, route0.host);
+  EXPECT_EQ("0.0.0.0", route0.netmask);
+  EXPECT_EQ(kRouter2, route0.gateway);
+
+  const IPConfig::Route &route1 = properties.routes[1];
+  EXPECT_EQ(kAddress1, route1.host);
+  EXPECT_EQ("255.255.255.0", route1.netmask);
+  EXPECT_EQ(kRouter1, route1.gateway);
+
+  // A malformed routing table should not affect the current table.
+  EXPECT_FALSE(DHCPConfig::ParseClasslessStaticRoutes(kBrokenClasslessRoutes1,
+                                                      &properties));
+  EXPECT_EQ(2, properties.routes.size());
+  EXPECT_EQ(kRouter0, properties.gateway);
+}
+
 TEST_F(DHCPConfigTest, ParseConfiguration) {
   DHCPConfig::Configuration conf;
   conf[DHCPConfig::kConfigurationKeyIPAddress].writer().append_uint32(