shill: do not report suspend readiness on gateway ARP

Pass a boolean from DHCPConfig::ProcessEventSignal to
IPConfig::UpdateProperties and Device::OnIPConfigUpdated that
indicates if a gateway ARP was received, and use this boolean
to determine whether or not to report suspend readiness when
WiFi::OnIPConfigUpdated is called in dark resume. This allows
us to accept both a Gateway ARP and BOUND event during dark
resume, and only report suspend readiness when the latter event
is received.

BUG=chromium:442610
TEST=Compile shill and run unit tests using:

P2_TEST_FILTER="shill::*" FEATURES="test" USE="clang asan" \
  emerge-samus shill

Manual testing as follows:

 1) Boot into a samus test image.
 2) Run 'ff_debug +wifi; ff_debug --level -3'
 3) Run 'echo 0 > /var/lib/power_manager/disable_dark_resume; restart \
    powerd'
 4) Enable all wake on WiFi features with the following command:
     dbus-send --system --print-reply --dest=org.chromium.flimflam \
       /device/wlan0 org.chromium.flimflam.Device.SetProperty \
         string:WakeOnWiFiFeaturesEnabled \
           variant:string:"packet_and_ssid"
 5) Set the wake-to-scan frequency to 30 seconds with the
    following command:
     dbus-send --system --print-reply --dest=org.chromium.flimflam \
       /device/wlan0 org.chromium.flimflam.Device.SetProperty \
         string:WakeToScanFrequency variant:uint32:30
 6) Connect samus to an AP.
 7) Disable the AP. Verify that the samus is fully disconnected by the WiFi
    icon state (a cross over empty WiFi bars).
 8) Run 'powerd_dbus_suspend' to suspend the system.
 9) Wait ~10 seconds for the system to fully suspend, then immediately re-enable
    the AP.
10) Wait for another ~20 seconds for the system to enter dark resume (light bar
    will turn on in dark resume).
11) After the dark resume concludes (~10 more seconds; light bar should turn
    off), wait another 30 seconds and verify that the system does not enter
    dark resume again (i.e. light bar does not light up again). This signifies
    that the system has established a connection in the last dark resume. If
    the system enters dark resume, repeat this step until the system no longer
    enters dark resume again.
12) Wake the samus by pressing any way, and verify that the WiFi icon is in the
    fully connected state.
13) Open /var/log/net.log
14) Search for the log messages containing "Gateway ARP received" and
    "IPv4 DHCP lease obtained". Verify that the former is logged before the
    latter, and that the two events are logged within the same second.
15) Verify that the log message containing the substring "OnDHCPLeaseObtained"
    appears immediately after the "IPv4 DHCP lease obtained message".

Change-Id: I8185c8683ed1a5e65779d67cca100faafbad6654
Reviewed-on: https://chromium-review.googlesource.com/236099
Tested-by: Samuel Tan <samueltan@chromium.org>
Reviewed-by: Paul Stewart <pstew@chromium.org>
Commit-Queue: Samuel Tan <samueltan@chromium.org>
diff --git a/shill/connection_unittest.cc b/shill/connection_unittest.cc
index 8b66b68..b348c44 100644
--- a/shill/connection_unittest.cc
+++ b/shill/connection_unittest.cc
@@ -112,11 +112,11 @@
   }
 
   void UpdateProperties() {
-    ipconfig_->UpdateProperties(properties_);
+    ipconfig_->UpdateProperties(properties_, true);
   }
 
   void UpdateIPv6Properties() {
-    ip6config_->UpdateProperties(ipv6_properties_);
+    ip6config_->UpdateProperties(ipv6_properties_, true);
   }
 
   bool PinHostRoute(ConnectionRefPtr connection,
diff --git a/shill/device.cc b/shill/device.cc
index a3bc215..b20ffa9 100644
--- a/shill/device.cc
+++ b/shill/device.cc
@@ -686,7 +686,7 @@
   ipconfig_ = new IPConfig(control_interface_, link_name_);
   ipconfig_->set_properties(properties);
   dispatcher_->PostTask(Bind(&Device::OnIPConfigUpdated,
-                             weak_ptr_factory_.GetWeakPtr(), ipconfig_));
+                             weak_ptr_factory_.GetWeakPtr(), ipconfig_, true));
 }
 
 void Device::DestroyIPConfigLease(const string &name) {
@@ -746,7 +746,7 @@
     // If the parameters contain an IP address, apply them now and bring
     // the interface up.  When DHCP information arrives, it will supplement
     // the static information.
-    OnIPConfigUpdated(ipconfig_);
+    OnIPConfigUpdated(ipconfig_, true);
   } else {
     // Either |ipconfig_| has just been created in AcquireIPConfig() or
     // we're being called by OnIPConfigRefreshed().  In either case a
@@ -808,7 +808,8 @@
   StartTrafficMonitor();
 }
 
-void Device::OnIPConfigUpdated(const IPConfigRefPtr &ipconfig) {
+void Device::OnIPConfigUpdated(const IPConfigRefPtr &ipconfig,
+                               bool /*new_lease_acquired*/) {
   SLOG(this, 2) << __func__;
   if (selected_service_) {
     ipconfig->ApplyStaticIPParameters(
diff --git a/shill/device.h b/shill/device.h
index 861bda4..18c0245 100644
--- a/shill/device.h
+++ b/shill/device.h
@@ -427,7 +427,8 @@
   void AssignIPConfig(const IPConfig::Properties &properties);
 
   // Callback invoked on successful IP configuration updates.
-  virtual void OnIPConfigUpdated(const IPConfigRefPtr &ipconfig);
+  virtual void OnIPConfigUpdated(const IPConfigRefPtr &ipconfig,
+                                 bool new_lease_acquired);
 
   // Called when IPv6 configuration changes.
   virtual void OnIPv6ConfigUpdated();
diff --git a/shill/device_unittest.cc b/shill/device_unittest.cc
index f87400d..2ac8ab2 100644
--- a/shill/device_unittest.cc
+++ b/shill/device_unittest.cc
@@ -182,7 +182,7 @@
   static const int kDeviceInterfaceIndex;
 
   void OnIPConfigUpdated(const IPConfigRefPtr &ipconfig) {
-    device_->OnIPConfigUpdated(ipconfig);
+    device_->OnIPConfigUpdated(ipconfig, true);
   }
 
   void OnIPConfigFailed(const IPConfigRefPtr &ipconfig) {
@@ -988,11 +988,11 @@
   IPConfig::Properties properties;
   properties.vendor_encapsulated_options =
       Tethering::kAndroidVendorEncapsulatedOptions;
-  device_->ipconfig_->UpdateProperties(properties);
+  device_->ipconfig_->UpdateProperties(properties, true);
   EXPECT_TRUE(device_->IsConnectedViaTether());
 
   properties.vendor_encapsulated_options = "Some other non-empty value";
-  device_->ipconfig_->UpdateProperties(properties);
+  device_->ipconfig_->UpdateProperties(properties, true);
   EXPECT_FALSE(device_->IsConnectedViaTether());
 }
 
diff --git a/shill/dhcp_config.cc b/shill/dhcp_config.cc
index d580478..268c019 100644
--- a/shill/dhcp_config.cc
+++ b/shill/dhcp_config.cc
@@ -223,10 +223,10 @@
     // 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);
+    IPConfig::UpdateProperties(properties, false);
     is_gateway_arp_active_ = true;
   } else {
-    UpdateProperties(properties);
+    UpdateProperties(properties, true);
     is_gateway_arp_active_ = false;
   }
 }
@@ -274,7 +274,8 @@
   }
 }
 
-void DHCPConfig::UpdateProperties(const Properties &properties) {
+void DHCPConfig::UpdateProperties(const Properties &properties,
+                                  bool new_lease_acquired) {
   StopAcquisitionTimeout();
   if (properties.lease_duration_seconds) {
     UpdateLeaseExpirationTime(properties.lease_duration_seconds);
@@ -284,7 +285,7 @@
     ResetLeaseExpirationTime();
     StopExpirationTimeout();
   }
-  IPConfig::UpdateProperties(properties);
+  IPConfig::UpdateProperties(properties, new_lease_acquired);
 }
 
 void DHCPConfig::NotifyFailure() {
diff --git a/shill/dhcp_config.h b/shill/dhcp_config.h
index ad05692..36d04dd 100644
--- a/shill/dhcp_config.h
+++ b/shill/dhcp_config.h
@@ -74,7 +74,8 @@
 
  protected:
   // Overrides base clase implementation.
-  virtual void UpdateProperties(const Properties &properties);
+  virtual void UpdateProperties(const Properties &properties,
+                                bool new_lease_acquired);
   virtual void NotifyFailure();
 
  private:
diff --git a/shill/dhcp_config_unittest.cc b/shill/dhcp_config_unittest.cc
index ab5f54c..1cb5f38 100644
--- a/shill/dhcp_config_unittest.cc
+++ b/shill/dhcp_config_unittest.cc
@@ -396,7 +396,8 @@
     ip_config_ = config_;
   }
 
-  MOCK_METHOD1(SuccessCallback, void(const IPConfigRefPtr &ipconfig));
+  MOCK_METHOD2(SuccessCallback,
+               void(const IPConfigRefPtr &ipconfig, bool new_lease_acquired));
   MOCK_METHOD1(FailureCallback, void(const IPConfigRefPtr &ipconfig));
 
   // The mock methods above take IPConfigRefPtr because this is the type
@@ -416,7 +417,7 @@
   DHCPConfig::Configuration conf;
   conf[DHCPConfig::kConfigurationKeyIPAddress].writer().append_uint32(
       0x01020304);
-  EXPECT_CALL(*this, SuccessCallback(_)).Times(0);
+  EXPECT_CALL(*this, SuccessCallback(_, _)).Times(0);
   EXPECT_CALL(*this, FailureCallback(ConfigRef()));
   config_->lease_acquisition_timeout_callback_.Reset(base::Bind(&DoNothing));
   config_->lease_expiration_callback_.Reset(base::Bind(&DoNothing));
@@ -447,7 +448,7 @@
       }
       config_->lease_acquisition_timeout_callback_.Reset(
           base::Bind(&DoNothing));
-      EXPECT_CALL(*this, SuccessCallback(ConfigRef()));
+      EXPECT_CALL(*this, SuccessCallback(ConfigRef(), true));
       EXPECT_CALL(*this, FailureCallback(_)).Times(0);
       config_->ProcessEventSignal(reason, conf);
       string failure_message = string(reason) + " failed with lease time " +
@@ -493,7 +494,7 @@
   // the lease after accepting other network parameters from the DHCP
   // IPConfig properties.  We need to ensure that no callbacks are left
   // running inadvertently as a result.
-  EXPECT_CALL(*this, SuccessCallback(ConfigRef()))
+  EXPECT_CALL(*this, SuccessCallback(ConfigRef(), true))
       .WillOnce(InvokeWithoutArgs(this, &DHCPConfigTest::StopInstance));
   config_->ProcessEventSignal(DHCPConfig::kReasonBound, conf);
   EXPECT_TRUE(Mock::VerifyAndClearExpectations(this));
@@ -506,7 +507,7 @@
   conf[DHCPConfig::kConfigurationKeyIPAddress].writer().append_uint32(
       0x01020304);
   static const char kReasonUnknown[] = "UNKNOWN_REASON";
-  EXPECT_CALL(*this, SuccessCallback(_)).Times(0);
+  EXPECT_CALL(*this, SuccessCallback(_, _)).Times(0);
   EXPECT_CALL(*this, FailureCallback(_)).Times(0);
   config_->lease_acquisition_timeout_callback_.Reset(base::Bind(&DoNothing));
   config_->ProcessEventSignal(kReasonUnknown, conf);
@@ -519,7 +520,7 @@
   DHCPConfig::Configuration conf;
   conf[DHCPConfig::kConfigurationKeyIPAddress].writer().append_uint32(
       0x01020304);
-  EXPECT_CALL(*this, SuccessCallback(ConfigRef()));
+  EXPECT_CALL(*this, SuccessCallback(ConfigRef(), false));
   EXPECT_CALL(*this, FailureCallback(_)).Times(0);
   EXPECT_CALL(*minijail_, RunAndDestroy(_, _, _)).WillOnce(Return(true));
   config_->Start();
@@ -535,14 +536,14 @@
   EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
   EXPECT_CALL(log, Log(_, _, ::testing::EndsWith(
       "Continuing to use our previous lease, due to gateway-ARP.")));
-  EXPECT_CALL(*this, SuccessCallback(_)).Times(0);
+  EXPECT_CALL(*this, SuccessCallback(_, _)).Times(0);
   EXPECT_CALL(*this, FailureCallback(_)).Times(0);
   config_->lease_acquisition_timeout_callback_.callback().Run();
   Mock::VerifyAndClearExpectations(this);
   EXPECT_TRUE(config_->is_gateway_arp_active_);
 
   // An official reply from a DHCP server should reset our GatewayArp state.
-  EXPECT_CALL(*this, SuccessCallback(ConfigRef()));
+  EXPECT_CALL(*this, SuccessCallback(ConfigRef(), true));
   EXPECT_CALL(*this, FailureCallback(_)).Times(0);
   config_->ProcessEventSignal(DHCPConfig::kReasonRenew, conf);
   Mock::VerifyAndClearExpectations(this);
@@ -565,7 +566,7 @@
 
   // If the timeout gets called, we should lose the lease since GatewayArp
   // is not active any more.
-  EXPECT_CALL(*this, SuccessCallback(_)).Times(0);
+  EXPECT_CALL(*this, SuccessCallback(_, _)).Times(0);
   EXPECT_CALL(*this, FailureCallback(ConfigRef()));
   config_->lease_acquisition_timeout_callback_.callback().Run();
   Mock::VerifyAndClearExpectations(this);
@@ -642,7 +643,7 @@
 }
 
 TEST_F(DHCPConfigCallbackTest, RequestIPTimeout) {
-  EXPECT_CALL(*this, SuccessCallback(_)).Times(0);
+  EXPECT_CALL(*this, SuccessCallback(_, _)).Times(0);
   EXPECT_CALL(*this, FailureCallback(ConfigRef()));
   config_->lease_acquisition_timeout_seconds_ = 0;
   config_->pid_ = 567;
@@ -704,7 +705,7 @@
 }
 
 TEST_F(DHCPConfigCallbackTest, StartTimeout) {
-  EXPECT_CALL(*this, SuccessCallback(_)).Times(0);
+  EXPECT_CALL(*this, SuccessCallback(_, _)).Times(0);
   EXPECT_CALL(*this, FailureCallback(ConfigRef()));
   config_->lease_acquisition_timeout_seconds_ = 0;
   config_->proxy_.reset(proxy_.release());
diff --git a/shill/ipconfig.cc b/shill/ipconfig.cc
index aac022a..95de3c7 100644
--- a/shill/ipconfig.cc
+++ b/shill/ipconfig.cc
@@ -145,7 +145,8 @@
   return true;
 }
 
-void IPConfig::UpdateProperties(const Properties &properties) {
+void IPConfig::UpdateProperties(const Properties &properties,
+                                bool new_lease_acquired) {
   // Take a reference of this instance to make sure we don't get destroyed in
   // the middle of this call. (The |update_callback_| may cause a reference
   // to be dropped. See, e.g., EthernetService::Disconnect and
@@ -155,7 +156,7 @@
   properties_ = properties;
 
   if (!update_callback_.is_null()) {
-    update_callback_.Run(this);
+    update_callback_.Run(this, new_lease_acquired);
   }
   EmitChanges();
 }
@@ -183,7 +184,7 @@
   }
 }
 
-void IPConfig::RegisterUpdateCallback(const Callback &callback) {
+void IPConfig::RegisterUpdateCallback(const UpdateCallback &callback) {
   update_callback_ = callback;
 }
 
diff --git a/shill/ipconfig.h b/shill/ipconfig.h
index cc9e674..e98dd76 100644
--- a/shill/ipconfig.h
+++ b/shill/ipconfig.h
@@ -80,6 +80,7 @@
     kReleaseReasonStaticIP
   };
 
+  typedef base::Callback<void(const IPConfigRefPtr&, bool)> UpdateCallback;
   typedef base::Callback<void(const IPConfigRefPtr&)> Callback;
 
   IPConfig(ControlInterface *control_interface, const std::string &device_name);
@@ -96,10 +97,11 @@
 
   // Registers a callback that's executed every time the configuration
   // properties are acquired. Takes ownership of |callback|.  Pass NULL
-  // to remove a callback. The callback's argument is a pointer to this IP
+  // to remove a callback. The callback's first argument is a pointer to this IP
   // configuration instance allowing clients to more easily manage multiple IP
-  // configurations.
-  void RegisterUpdateCallback(const Callback &callback);
+  // configurations. The callback's second argument is a boolean indicating
+  // whether or not a DHCP lease was acquired from the server.
+  void RegisterUpdateCallback(const UpdateCallback &callback);
 
   // Registers a callback that's executed every time the configuration
   // properties fail to be acquired. Takes ownership of |callback|.  Pass NULL
@@ -173,7 +175,8 @@
 
   // Updates the IP configuration properties and notifies registered listeners
   // about the event.
-  virtual void UpdateProperties(const Properties &properties);
+  virtual void UpdateProperties(const Properties &properties,
+                                bool new_lease_acquired);
 
   // Notifies registered listeners that the configuration process has failed.
   virtual void NotifyFailure();
@@ -213,7 +216,7 @@
   const uint serial_;
   std::unique_ptr<IPConfigAdaptorInterface> adaptor_;
   Properties properties_;
-  Callback update_callback_;
+  UpdateCallback update_callback_;
   Callback failure_callback_;
   Callback refresh_callback_;
   Callback expire_callback_;
diff --git a/shill/ipconfig_unittest.cc b/shill/ipconfig_unittest.cc
index 0b16789..1005a77 100644
--- a/shill/ipconfig_unittest.cc
+++ b/shill/ipconfig_unittest.cc
@@ -45,11 +45,13 @@
   IPConfigTest() : ipconfig_(new IPConfig(&control_, kDeviceName)) {
     ipconfig_->time_ = &time_;
   }
-  void DropRef(const IPConfigRefPtr &/*ipconfig*/) {
+  void DropRef(const IPConfigRefPtr & /*ipconfig*/,
+               bool /*new_lease_acquired*/) {
     ipconfig_ = nullptr;
   }
 
-  MOCK_METHOD1(OnIPConfigUpdated, void(const IPConfigRefPtr &ipconfig));
+  MOCK_METHOD2(OnIPConfigUpdated,
+               void(const IPConfigRefPtr &ipconfig, bool new_lease_acquired));
   MOCK_METHOD1(OnIPConfigFailed, void(const IPConfigRefPtr &ipconfig));
   MOCK_METHOD1(OnIPConfigRefreshed, void(const IPConfigRefPtr &ipconfig));
   MOCK_METHOD1(OnIPConfigExpired, void(const IPConfigRefPtr &ipconfig));
@@ -60,7 +62,7 @@
   }
 
   void UpdateProperties(const IPConfig::Properties &properties) {
-    ipconfig_->UpdateProperties(properties);
+    ipconfig_->UpdateProperties(properties, true);
   }
 
   void NotifyFailure() {
@@ -160,28 +162,28 @@
   ipconfig_->RegisterExpireCallback(
       Bind(&IPConfigTest::OnIPConfigExpired, Unretained(this)));
 
-  EXPECT_CALL(*this, OnIPConfigUpdated(ipconfig_));
+  EXPECT_CALL(*this, OnIPConfigUpdated(ipconfig_, true));
   EXPECT_CALL(*this, OnIPConfigFailed(ipconfig_)).Times(0);
   EXPECT_CALL(*this, OnIPConfigRefreshed(ipconfig_)).Times(0);
   EXPECT_CALL(*this, OnIPConfigExpired(ipconfig_)).Times(0);
   UpdateProperties(IPConfig::Properties());
   Mock::VerifyAndClearExpectations(this);
 
-  EXPECT_CALL(*this, OnIPConfigUpdated(ipconfig_)).Times(0);
+  EXPECT_CALL(*this, OnIPConfigUpdated(ipconfig_, true)).Times(0);
   EXPECT_CALL(*this, OnIPConfigFailed(ipconfig_));
   EXPECT_CALL(*this, OnIPConfigRefreshed(ipconfig_)).Times(0);
   EXPECT_CALL(*this, OnIPConfigExpired(ipconfig_)).Times(0);
   NotifyFailure();
   Mock::VerifyAndClearExpectations(this);
 
-  EXPECT_CALL(*this, OnIPConfigUpdated(ipconfig_)).Times(0);
+  EXPECT_CALL(*this, OnIPConfigUpdated(ipconfig_, true)).Times(0);
   EXPECT_CALL(*this, OnIPConfigFailed(ipconfig_)).Times(0);
   EXPECT_CALL(*this, OnIPConfigRefreshed(ipconfig_));
   EXPECT_CALL(*this, OnIPConfigExpired(ipconfig_)).Times(0);
   ipconfig_->Refresh(nullptr);
   Mock::VerifyAndClearExpectations(this);
 
-  EXPECT_CALL(*this, OnIPConfigUpdated(ipconfig_)).Times(0);
+  EXPECT_CALL(*this, OnIPConfigUpdated(ipconfig_, true)).Times(0);
   EXPECT_CALL(*this, OnIPConfigFailed(ipconfig_)).Times(0);
   EXPECT_CALL(*this, OnIPConfigRefreshed(ipconfig_)).Times(0);
   EXPECT_CALL(*this, OnIPConfigExpired(ipconfig_));
diff --git a/shill/routing_table_unittest.cc b/shill/routing_table_unittest.cc
index 4034764..688c71c 100644
--- a/shill/routing_table_unittest.cc
+++ b/shill/routing_table_unittest.cc
@@ -485,7 +485,7 @@
   IPConfig::Properties properties;
   properties.address_family = IPAddress::kFamilyIPv4;
   vector<IPConfig::Route> &routes = properties.routes;
-  ipconfig->UpdateProperties(properties);
+  ipconfig->UpdateProperties(properties, true);
 
   const int kMetric = 10;
   EXPECT_TRUE(routing_table_->ConfigureRoutes(kTestDeviceIndex0,
@@ -497,7 +497,7 @@
   route.netmask = kTestRemoteNetmask4;
   route.gateway = kTestGatewayAddress4;
   routes.push_back(route);
-  ipconfig->UpdateProperties(properties);
+  ipconfig->UpdateProperties(properties, true);
 
   IPAddress destination_address(IPAddress::kFamilyIPv4);
   IPAddress source_address(IPAddress::kFamilyIPv4);
@@ -530,7 +530,7 @@
   routes.push_back(route);
   route.host = kTestRemoteNetwork4;
   routes.push_back(route);
-  ipconfig->UpdateProperties(properties);
+  ipconfig->UpdateProperties(properties, true);
 
   EXPECT_CALL(rtnl_handler_,
               SendMessage(IsRoutingPacket(RTNLMessage::kModeAdd,
diff --git a/shill/virtual_device.cc b/shill/virtual_device.cc
index 1a3f8c8..25208a9 100644
--- a/shill/virtual_device.cc
+++ b/shill/virtual_device.cc
@@ -66,7 +66,7 @@
     set_ipconfig(new IPConfig(control_interface(), link_name()));
   }
   ipconfig()->set_properties(properties);
-  OnIPConfigUpdated(ipconfig());
+  OnIPConfigUpdated(ipconfig(), true);
 }
 
 void VirtualDevice::DropConnection() {
diff --git a/shill/wifi/wifi.cc b/shill/wifi/wifi.cc
index 3041f11..129f2a6 100644
--- a/shill/wifi/wifi.cc
+++ b/shill/wifi/wifi.cc
@@ -2877,14 +2877,24 @@
   return false;
 }
 
-void WiFi::OnIPConfigUpdated(const IPConfigRefPtr &ipconfig) {
-  Device::OnIPConfigUpdated(ipconfig);
-  SLOG(this, 2) << __func__ << ": " << "IPv4 DHCP lease obtained";
-  uint32_t time_to_next_lease_renewal;
-  bool have_dhcp_lease =
-      TimeToNextDHCPLeaseRenewal(&time_to_next_lease_renewal);
-  wake_on_wifi_->OnDHCPLeaseObtained(have_dhcp_lease,
-                                     time_to_next_lease_renewal);
+void WiFi::OnIPConfigUpdated(const IPConfigRefPtr &ipconfig,
+                             bool new_lease_acquired) {
+  Device::OnIPConfigUpdated(ipconfig, new_lease_acquired);
+  if (new_lease_acquired) {
+    SLOG(this, 2) << __func__ << ": "
+                  << "IPv4 DHCP lease obtained";
+    uint32_t time_to_next_lease_renewal;
+    bool have_dhcp_lease =
+        TimeToNextDHCPLeaseRenewal(&time_to_next_lease_renewal);
+    wake_on_wifi_->OnDHCPLeaseObtained(have_dhcp_lease,
+                                       time_to_next_lease_renewal);
+  } else {
+    SLOG(this, 2) << __func__ << ": "
+                  << "Gateway ARP received";
+    // Do nothing since we are waiting until the DHCP lease is actually
+    // obtained.
+    return;
+  }
 }
 
 void WiFi::OnIPv6ConfigUpdated() {
diff --git a/shill/wifi/wifi.h b/shill/wifi/wifi.h
index 7135428..ffb4588 100644
--- a/shill/wifi/wifi.h
+++ b/shill/wifi/wifi.h
@@ -531,7 +531,8 @@
 
   // In addition to calling the implementations of these functions in Device,
   // calls WakeOnWiFi::PrepareForWakeOnWiFiBeforeSuspend.
-  void OnIPConfigUpdated(const IPConfigRefPtr &ipconfig) override;
+  void OnIPConfigUpdated(const IPConfigRefPtr &ipconfig,
+                         bool new_lease_acquired) override;
   void OnIPv6ConfigUpdated() override;
 
   // Returns true iff the WiFi device is connected to the current service.
diff --git a/shill/wifi/wifi_unittest.cc b/shill/wifi/wifi_unittest.cc
index a8397c0..a1e4390 100644
--- a/shill/wifi/wifi_unittest.cc
+++ b/shill/wifi/wifi_unittest.cc
@@ -683,7 +683,10 @@
                  uint16_t frequency,
                  const char *mode);
   void ReportIPConfigComplete() {
-    wifi_->OnIPConfigUpdated(dhcp_config_);
+    wifi_->OnIPConfigUpdated(dhcp_config_, true);
+  }
+  void ReportIPConfigCompleteGatewayArpReceived() {
+    wifi_->OnIPConfigUpdated(dhcp_config_, false);
   }
   void ReportIPv6ConfigComplete() {
     wifi_->OnIPv6ConfigUpdated();
@@ -4008,7 +4011,7 @@
   ReportStateChanged(WPASupplicant::kInterfaceStateAssociating);
 }
 
-TEST_F(WiFiMainTest, OnIPConfigUpdated_InvokeOnDHCPLeaseObtained) {
+TEST_F(WiFiMainTest, OnIPConfigUpdated_InvokesOnDHCPLeaseObtained) {
   ScopedMockLog log;
   EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
   ScopeLogger::GetInstance()->EnableScopesByName("wifi");
@@ -4022,6 +4025,12 @@
   EXPECT_CALL(*wake_on_wifi_, OnDHCPLeaseObtained(_, _));
   ReportIPv6ConfigComplete();
 
+  // Do not call WakeOnWiFi::OnDHCPLeaseObtained if the IP config update was
+  // triggered by a gateway ARP.
+  EXPECT_CALL(log, Log(_, _, HasSubstr("Gateway ARP received")));
+  EXPECT_CALL(*wake_on_wifi_, OnDHCPLeaseObtained(_, _)).Times(0);
+  ReportIPConfigCompleteGatewayArpReceived();
+
   ScopeLogger::GetInstance()->EnableScopesByName("-wifi");
   ScopeLogger::GetInstance()->set_verbose_level(0);
 }