shill: DHCPConfig: Enable and accept "GATEWAY-ARP" messages

Speed up the authentication process by enabling dhcpcd to probe
for the default gateway.  This provides a quick means for
verifying that we are on the same network as before.  When this
succeeds, re-configure using our saved lease parameters, but
continue our timeout so that a failed DHCP renew will cause the
network to enter the failed state.

A side effect of this change is that we don't ask dhcpcd to
release our lease on disconnect.  This is not as big a change
as one might think -- in many cases were are already disconnected
from the network by the time this code hits, which means we were
just removing our own memory of the lease instead of actually
freeing the remote resource.

BUG=chromium-os:25717
TEST=Unit tests; manual: switch between two known networks
Change-Id: Ib219033e688aa8c31eb86b45e9dd27a607bb93c9
Reviewed-on: https://gerrit.chromium.org/gerrit/23906
Commit-Ready: Paul Stewart <pstew@chromium.org>
Reviewed-by: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/dhcp_config.cc b/dhcp_config.cc
index ad7a4a8..7e879e4 100644
--- a/dhcp_config.cc
+++ b/dhcp_config.cc
@@ -47,6 +47,7 @@
 const int DHCPConfig::kMinMTU = 576;
 const char DHCPConfig::kReasonBound[] = "BOUND";
 const char DHCPConfig::kReasonFail[] = "FAIL";
+const char DHCPConfig::kReasonGatewayArp[] = "GATEWAY-ARP";
 const char DHCPConfig::kReasonRebind[] = "REBIND";
 const char DHCPConfig::kReasonReboot[] = "REBOOT";
 const char DHCPConfig::kReasonRenew[] = "RENEW";
@@ -119,7 +120,9 @@
   if (!pid_) {
     return true;
   }
-  if (proxy_.get()) {
+  // If we are using gateway unicast ARP to speed up re-connect, don't
+  // give up our leases when we disconnect.
+  if (!arp_gateway_ && proxy_.get()) {
     proxy_->Release(device_name());
   }
   Stop();
@@ -144,13 +147,23 @@
   if (reason != kReasonBound &&
       reason != kReasonRebind &&
       reason != kReasonReboot &&
-      reason != kReasonRenew) {
+      reason != kReasonRenew &&
+      reason != kReasonGatewayArp) {
     LOG(WARNING) << "Event ignored.";
     return;
   }
   IPConfig::Properties properties;
   CHECK(ParseConfiguration(configuration, &properties));
-  UpdateProperties(properties, true);
+  if (reason == kReasonGatewayArp) {
+    // This is a non-authoritative confirmation that we or on the same
+    // network as the one we received a lease on previously.  The DHCP
+    // client is still running, so we should not cancel the timeout
+    // until that completes.  In the meantime, however, we can tentatively
+    // configure our network in anticipation of successful completion.
+    IPConfig::UpdateProperties(properties, true);
+  } else {
+    UpdateProperties(properties, true);
+  }
 }
 
 void DHCPConfig::UpdateProperties(const Properties &properties, bool success) {
@@ -171,6 +184,7 @@
   }
   if (arp_gateway_) {
     args.push_back(const_cast<char *>("-R"));  // ARP for default gateway.
+    args.push_back(const_cast<char *>("-U"));  // Enable unicast ARP on renew.
   }
   string interface_arg(device_name());
   if (lease_file_suffix_ != device_name()) {
diff --git a/dhcp_config.h b/dhcp_config.h
index c850a9f..65e1432 100644
--- a/dhcp_config.h
+++ b/dhcp_config.h
@@ -77,6 +77,7 @@
   FRIEND_TEST(DHCPConfigTest, ProcessEventSignalSuccess);
   FRIEND_TEST(DHCPConfigTest, ProcessEventSignalUnknown);
   FRIEND_TEST(DHCPConfigTest, ReleaseIP);
+  FRIEND_TEST(DHCPConfigTest, ReleaseIPArpGW);
   FRIEND_TEST(DHCPConfigTest, RenewIP);
   FRIEND_TEST(DHCPConfigTest, RequestIP);
   FRIEND_TEST(DHCPConfigTest, RequestIPTimeout);
@@ -110,6 +111,7 @@
   static const char kDHCPCDUser[];
 
   static const char kReasonBound[];
+  static const char kReasonGatewayArp[];
   static const char kReasonFail[];
   static const char kReasonRebind[];
   static const char kReasonReboot[];
diff --git a/dhcp_config_unittest.cc b/dhcp_config_unittest.cc
index 434b57c..31a3b59 100644
--- a/dhcp_config_unittest.cc
+++ b/dhcp_config_unittest.cc
@@ -380,12 +380,22 @@
 
 TEST_F(DHCPConfigTest, ReleaseIP) {
   config_->pid_ = 1 << 18;  // Ensure unknown positive PID.
+  config_->arp_gateway_ = false;
   EXPECT_CALL(*proxy_, Release(kDeviceName)).Times(1);
   config_->proxy_.reset(proxy_.release());
   EXPECT_TRUE(config_->ReleaseIP());
   config_->pid_ = 0;
 }
 
+TEST_F(DHCPConfigTest, ReleaseIPArpGW) {
+  config_->pid_ = 1 << 18;  // Ensure unknown positive PID.
+  config_->arp_gateway_ = true;
+  EXPECT_CALL(*proxy_, Release(kDeviceName)).Times(0);
+  config_->proxy_.reset(proxy_.release());
+  EXPECT_TRUE(config_->ReleaseIP());
+  config_->pid_ = 0;
+}
+
 TEST_F(DHCPConfigTest, RenewIP) {
   EXPECT_TRUE(config_->lease_acquisition_timeout_callback_.IsCancelled());
   config_->pid_ = 456;
diff --git a/ipconfig.h b/ipconfig.h
index 084e30a..b2b425e 100644
--- a/ipconfig.h
+++ b/ipconfig.h
@@ -85,7 +85,11 @@
 
   // Request, renew and release IP configuration. Return true on success, false
   // otherwise. The default implementation always returns false indicating a
-  // failure.
+  // failure.  ReleaseIP is advisory: if we are no longer connected, it is not
+  // possible to properly vacate the lease on the remote server.  Also,
+  // depending on the configuration of the specific IPConfig subclass, we may
+  // end up holding on to the lease so we can resume to the network lease
+  // faster.
   virtual bool RequestIP();
   virtual bool RenewIP();
   virtual bool ReleaseIP();