shill: implement timeout for DHCP requests

BUG=chromium-os:30689
TEST=new unit tests, manual (see below)

Manual testing:
- Start shill.
- ff_debug +dhcp
- Plug USB-Ethernet into a switch (to get carrier), but without
  an upstream connection for the switch. Plug dongle into USB
  port.
- Wait 30 seconds.
- Check log file, find "Timed out waiting for DHCP lease on eth0",
  and "Service Ethernet state Configuring -> Disconnected".

Change-Id: Ifc27539ec7191b060f615eb9dec61c9fdab07267
Reviewed-on: https://gerrit.chromium.org/gerrit/22302
Commit-Ready: mukesh agrawal <quiche@chromium.org>
Reviewed-by: mukesh agrawal <quiche@chromium.org>
Tested-by: mukesh agrawal <quiche@chromium.org>
diff --git a/dhcp_config.cc b/dhcp_config.cc
index 9d87da3..e1f610d 100644
--- a/dhcp_config.cc
+++ b/dhcp_config.cc
@@ -41,6 +41,7 @@
     "var/lib/dhcpcd/dhcpcd-%s.lease";
 const char DHCPConfig::kDHCPCDPathFormatPID[] =
     "var/run/dhcpcd/dhcpcd-%s.pid";
+const int DHCPConfig::kDHCPTimeoutSeconds = 30;
 const int DHCPConfig::kMinMTU = 576;
 const char DHCPConfig::kReasonBound[] = "BOUND";
 const char DHCPConfig::kReasonFail[] = "FAIL";
@@ -67,7 +68,9 @@
       arp_gateway_(arp_gateway),
       pid_(0),
       child_watch_tag_(0),
+      lease_acquisition_timeout_seconds_(kDHCPTimeoutSeconds),
       root_("/"),
+      weak_ptr_factory_(this),
       dispatcher_(dispatcher),
       glib_(glib) {
   SLOG(DHCP, 2) << __func__ << ": " << device_name;
@@ -104,6 +107,7 @@
     return false;
   }
   proxy_->Rebind(device_name());
+  StartDHCPTimeout();
   return true;
 }
 
@@ -146,6 +150,11 @@
   UpdateProperties(properties, true);
 }
 
+void DHCPConfig::UpdateProperties(const Properties &properties, bool success) {
+  StopDHCPTimeout();
+  IPConfig::UpdateProperties(properties, success);
+}
+
 bool DHCPConfig::Start() {
   SLOG(DHCP, 2) << __func__ << ": " << device_name();
 
@@ -184,6 +193,7 @@
   provider_->BindPID(pid_, this);
   CHECK(!child_watch_tag_);
   child_watch_tag_ = glib_->ChildWatchAdd(pid_, ChildWatchCallback, this);
+  StartDHCPTimeout();
   return true;
 }
 
@@ -208,6 +218,7 @@
     if (ret != pid_)
       PLOG(ERROR);
   }
+  StopDHCPTimeout();
 }
 
 bool DHCPConfig::Restart() {
@@ -324,4 +335,22 @@
       base::StringPrintf(kDHCPCDPathFormatPID, device_name().c_str())), false);
 }
 
+void DHCPConfig::StartDHCPTimeout() {
+  lease_acquisition_timeout_callback_.Reset(
+      Bind(&DHCPConfig::ProcessDHCPTimeout, weak_ptr_factory_.GetWeakPtr()));
+  dispatcher_->PostDelayedTask(
+      lease_acquisition_timeout_callback_.callback(),
+      lease_acquisition_timeout_seconds_ * 1000);
+}
+
+void DHCPConfig::StopDHCPTimeout() {
+  lease_acquisition_timeout_callback_.Cancel();
+}
+
+void DHCPConfig::ProcessDHCPTimeout() {
+  LOG(ERROR) << "Timed out waiting for DHCP lease on " << device_name() << " "
+             << "(after " << lease_acquisition_timeout_seconds_ << " seconds).";
+  UpdateProperties(IPConfig::Properties(), false);
+}
+
 }  // namespace shill