shill: connection: Work around broken netmasks

It has been observed in the field that some network infrastructures
send a gateway/netmask pair that is inconsistent and prevents the
client from contacting the gateway.  Work around this by expanding
the netmask, assuming that this parameter is incorrect.  However,
use sane defaults for maximum expansion of the netmask, so that
we do not completely break things.

BUG=chromium-os:29416
TEST=New unit tests.

Change-Id: Id4730a8c1555fb09033175bdf2bfba1abe93a125
Reviewed-on: https://gerrit.chromium.org/gerrit/20465
Commit-Ready: Paul Stewart <pstew@chromium.org>
Reviewed-by: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/connection.cc b/connection.cc
index 50ac978..9f1582d 100644
--- a/connection.cc
+++ b/connection.cc
@@ -74,10 +74,25 @@
     return;
   }
 
+  IPAddress gateway_address(properties.address_family);
+  if (!properties.gateway.empty() &&
+      !gateway_address.SetAddressFromString(properties.gateway)) {
+    LOG(ERROR) << "Gateway address " << properties.peer_address
+               << " is invalid";
+    return;
+  }
+
+  FixGatewayReachability(&local, gateway_address);
+
   rtnl_handler_->AddInterfaceAddress(interface_index_, local, broadcast, peer);
 
-  routing_table_->SetDefaultRoute(interface_index_, config,
-                                  GetMetric(is_default_));
+  if (gateway_address.IsValid()) {
+    routing_table_->SetDefaultRoute(interface_index_, gateway_address,
+                                    GetMetric(is_default_));
+  } else if (!peer.IsValid()) {
+    LOG(WARNING) << "No gateway or peer address was provided for this "
+                 << "connection.  Expect limited network connectivity.";
+  }
 
   // Install any explicitly configured routes at the default metric.
   routing_table_->ConfigureRoutes(interface_index_, config, kDefaultMetric);
@@ -158,6 +173,36 @@
   return true;
 }
 
+// static
+void Connection::FixGatewayReachability(IPAddress *local,
+                                        const IPAddress &gateway) {
+  if (!gateway.IsValid() || local->CanReachAddress(gateway)) {
+    return;
+  }
+
+  LOG(WARNING) << "Gateway "
+               << gateway.ToString()
+               << " is unreachable from local address/prefix "
+               << local->ToString() << "/" << local->prefix();
+
+  size_t original_prefix = local->prefix();
+  size_t prefix = original_prefix - 1;
+  for (; prefix >= local->GetMinPrefixLength(); --prefix) {
+    local->set_prefix(prefix);
+    if (local->CanReachAddress(gateway)) {
+      break;
+    }
+  }
+
+  if (prefix < local->GetMinPrefixLength()) {
+    // Restore the original prefix since we cannot find a better one.
+    local->set_prefix(original_prefix);
+    LOG(WARNING) << "Expect limited network connectivity.";
+  } else {
+    LOG(WARNING) << "Mitigating this by setting local prefix to " << prefix;
+  }
+}
+
 uint32 Connection::GetMetric(bool is_default) {
   // If this is not the default route, assign a metric based on the interface
   // index.  This way all non-default routes (even to the same gateway IP) end