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(