shill: add function to check time till DHCP lease renewal

Add public function IPConfig::TimeToLeaseExpiry to check the time
left before the current DHCP lease is due to be renewed.

BUG=chromium:426657
TEST='P2_TEST_FILTER="shill::*" FEATURES="test" USE="clang asan"
emerge-samus shill' succeeds.

Change-Id: I80a912e86c677a98b48a22494e7bbc36a936cf3b
Reviewed-on: https://chromium-review.googlesource.com/225278
Reviewed-by: Samuel Tan <samueltan@chromium.org>
Commit-Queue: Samuel Tan <samueltan@chromium.org>
Tested-by: Samuel Tan <samueltan@chromium.org>
diff --git a/shill/device.cc b/shill/device.cc
index 8fefc1f..b279e6a 100644
--- a/shill/device.cc
+++ b/shill/device.cc
@@ -484,15 +484,18 @@
     addresses_str.push_back(address_str);
   }
 
-  // Setup timer to monitor DNS server lifetime if not infinite lifetime.
-  if (lifetime != ND_OPT_LIFETIME_INFINITY) {
-    StartIPv6DNSServerTimer(lifetime);
-  }
-
   if (!ip6config_) {
     ip6config_ = new IPConfig(control_interface_, link_name_);
   }
 
+  if (lifetime != ND_OPT_LIFETIME_INFINITY) {
+    // Setup timer to monitor DNS server lifetime if not infinite lifetime.
+    StartIPv6DNSServerTimer(lifetime);
+    ip6config_->UpdateLeaseExpirationTime(lifetime);
+  } else {
+    ip6config_->ResetLeaseExpirationTime();
+  }
+
   // Done if no change in server addresses.
   if (ip6config_->properties().dns_servers == addresses_str) {
     SLOG(Device, 2) << __func__ << " IPv6 DNS server list for "
diff --git a/shill/device.h b/shill/device.h
index 8902983..b18f232 100644
--- a/shill/device.h
+++ b/shill/device.h
@@ -324,6 +324,8 @@
   FRIEND_TEST(DeviceTest, OnIPv6AddressChanged);
   FRIEND_TEST(DeviceTest, OnIPv6ConfigurationCompleted);
   FRIEND_TEST(DeviceTest, OnIPv6DnsServerAddressesChanged);
+  FRIEND_TEST(DeviceTest,
+              OnIPv6DnsServerAddressesChanged_LeaseExpirationUpdated);
   FRIEND_TEST(DeviceTest, Save);
   FRIEND_TEST(DeviceTest, SelectedService);
   FRIEND_TEST(DeviceTest, SetEnabledNonPersistent);
diff --git a/shill/device_unittest.cc b/shill/device_unittest.cc
index 6e0599d..58a2e0a 100644
--- a/shill/device_unittest.cc
+++ b/shill/device_unittest.cc
@@ -48,6 +48,7 @@
 #include "shill/mock_store.h"
 #include "shill/mock_time.h"
 #include "shill/mock_traffic_monitor.h"
+#include "shill/ndisc.h"
 #include "shill/portal_detector.h"
 #include "shill/property_store_unittest.h"
 #include "shill/static_ip_parameters.h"
@@ -1029,6 +1030,41 @@
   Mock::VerifyAndClearExpectations(&device_info_);
 }
 
+TEST_F(DeviceTest, OnIPv6DnsServerAddressesChanged_LeaseExpirationUpdated) {
+  MockManager manager(control_interface(),
+                                  dispatcher(),
+                                  metrics(),
+                                  glib());
+  manager.set_mock_device_info(&device_info_);
+  SetManager(&manager);
+
+  scoped_refptr<MockIPConfig> ip6config =
+      new MockIPConfig(control_interface(), kDeviceName);
+  device_->ip6config_ = ip6config;
+
+  // Non-infinite lifetime should trigger an update of the current lease
+  // expiration time.
+  const uint32 kExpiredLifetime = 1;
+  EXPECT_CALL(device_info_,
+              GetIPv6DnsServerAddresses(kDeviceInterfaceIndex, _, _))
+      .WillOnce(DoAll(SetArgPointee<2>(kExpiredLifetime),
+                      Return(true)));
+  EXPECT_CALL(*ip6config, UpdateLeaseExpirationTime(_)).Times(1);
+  EXPECT_CALL(*ip6config, ResetLeaseExpirationTime()).Times(0);
+  device_->OnIPv6DnsServerAddressesChanged();
+
+  // Infinite lifetime should cause a reset of the current lease expiration
+  // time to its default value.
+  const uint32 kExpiredLifetimeInfinity = ND_OPT_LIFETIME_INFINITY;
+  EXPECT_CALL(device_info_,
+              GetIPv6DnsServerAddresses(kDeviceInterfaceIndex, _, _))
+      .WillOnce(DoAll(SetArgPointee<2>(kExpiredLifetimeInfinity),
+                      Return(true)));
+  EXPECT_CALL(*ip6config, UpdateLeaseExpirationTime(_)).Times(0);
+  EXPECT_CALL(*ip6config, ResetLeaseExpirationTime()).Times(1);
+  device_->OnIPv6DnsServerAddressesChanged();
+}
+
 TEST_F(DeviceTest, OnIPv6DnsServerAddressesChanged) {
   StrictMock<MockManager> manager(control_interface(),
                                   dispatcher(),
diff --git a/shill/dhcp_config.cc b/shill/dhcp_config.cc
index ecd479d..0bcb5fa 100644
--- a/shill/dhcp_config.cc
+++ b/shill/dhcp_config.cc
@@ -267,9 +267,11 @@
 void DHCPConfig::UpdateProperties(const Properties &properties) {
   StopAcquisitionTimeout();
   if (properties.lease_duration_seconds) {
+    UpdateLeaseExpirationTime(properties.lease_duration_seconds);
     StartExpirationTimeout(properties.lease_duration_seconds);
   } else {
     LOG(WARNING) << "Lease duration is zero; not starting an expiration timer.";
+    ResetLeaseExpirationTime();
     StopExpirationTimeout();
   }
   IPConfig::UpdateProperties(properties);
diff --git a/shill/ipconfig.cc b/shill/ipconfig.cc
index 1f64df1..7af79c0 100644
--- a/shill/ipconfig.cc
+++ b/shill/ipconfig.cc
@@ -4,12 +4,15 @@
 
 #include "shill/ipconfig.h"
 
+#include <sys/time.h>
+
 #include <chromeos/dbus/service_constants.h>
 
 #include "shill/adaptor_interfaces.h"
 #include "shill/control_interface.h"
 #include "shill/error.h"
 #include "shill/logging.h"
+#include "shill/shill_time.h"
 #include "shill/static_ip_parameters.h"
 
 using base::Callback;
@@ -17,8 +20,15 @@
 
 namespace shill {
 
+namespace {
+
+const time_t kDefaultLeaseExpirationTime = LONG_MAX;
+
+}  // namespace
+
 // static
 const char IPConfig::kType[] = "ip";
+
 // static
 uint IPConfig::global_serial_ = 0;
 
@@ -58,6 +68,8 @@
                              &properties_.vendor_encapsulated_options);
   store_.RegisterConstString(kWebProxyAutoDiscoveryUrlProperty,
                              &properties_.web_proxy_auto_discovery);
+  time_ = Time::GetInstance();
+  current_lease_expiration_time_ = {kDefaultLeaseExpirationTime, 0};
   SLOG(Inet, 2) << __func__ << " device: " << device_name();
 }
 
@@ -100,6 +112,34 @@
   EmitChanges();
 }
 
+void IPConfig::UpdateLeaseExpirationTime(uint32_t new_lease_duration) {
+  struct timeval new_expiration_time;
+  time_->GetTimeBoottime(&new_expiration_time);
+  new_expiration_time.tv_sec += new_lease_duration;
+  current_lease_expiration_time_ = new_expiration_time;
+}
+
+void IPConfig::ResetLeaseExpirationTime() {
+  current_lease_expiration_time_ = {kDefaultLeaseExpirationTime, 0};
+}
+
+bool IPConfig::TimeToLeaseExpiry(uint32_t *time_left) {
+  if (current_lease_expiration_time_.tv_sec == kDefaultLeaseExpirationTime) {
+    LOG(ERROR) << __func__ << ": "
+               << "No current DHCP lease";
+    return false;
+  }
+  struct timeval now;
+  time_->GetTimeBoottime(&now);
+  if (now.tv_sec > current_lease_expiration_time_.tv_sec) {
+    LOG(ERROR) << __func__ << ": "
+               << "Current DHCP lease has already expired";
+    return false;
+  }
+  *time_left = current_lease_expiration_time_.tv_sec - now.tv_sec;
+  return true;
+}
+
 void IPConfig::UpdateProperties(const Properties &properties) {
   // 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
diff --git a/shill/ipconfig.h b/shill/ipconfig.h
index 97ecff8..cda8bcd 100644
--- a/shill/ipconfig.h
+++ b/shill/ipconfig.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 #include <string>
+#include <sys/time.h>
 #include <vector>
 
 #include <base/callback.h>
@@ -23,6 +24,7 @@
 class Error;
 class IPConfigAdaptorInterface;
 class StaticIPParameters;
+class Time;
 
 // IPConfig superclass. Individual IP configuration types will inherit from this
 // class.
@@ -152,6 +154,18 @@
   // static IP parameters were previously applied.
   void RestoreSavedIPParameters(StaticIPParameters *static_ip_parameters);
 
+  // Updates |current_lease_expiration_time_| by adding |new_lease_duration| to
+  // the current time.
+  virtual void UpdateLeaseExpirationTime(uint32_t new_lease_duration);
+
+  // Resets |current_lease_expiration_time_| to its default value.
+  virtual void ResetLeaseExpirationTime();
+
+  // Returns the time left (in seconds) till the current DHCP lease is to be
+  // renewed in |time_left|. Returns false if an error occurs (i.e. current
+  // lease has already expired or no current DHCP lease), true otherwise.
+  bool TimeToLeaseExpiry(uint32_t *time_left);
+
  protected:
   // Inform RPC listeners of changes to our properties. MAY emit
   // changes even on unchanged properties.
@@ -178,6 +192,10 @@
   FRIEND_TEST(DeviceTest, OnIPConfigExpired);
   FRIEND_TEST(IPConfigTest, UpdateCallback);
   FRIEND_TEST(IPConfigTest, UpdateProperties);
+  FRIEND_TEST(IPConfigTest, UpdateLeaseExpirationTime);
+  FRIEND_TEST(IPConfigTest, TimeToLeaseExpiry_NoDHCPLease);
+  FRIEND_TEST(IPConfigTest, TimeToLeaseExpiry_CurrentLeaseExpired);
+  FRIEND_TEST(IPConfigTest, TimeToLeaseExpiry_Success);
   FRIEND_TEST(ResolverTest, Empty);
   FRIEND_TEST(ResolverTest, NonEmpty);
   FRIEND_TEST(RoutingTableTest, ConfigureRoutes);
@@ -199,6 +217,8 @@
   Callback failure_callback_;
   Callback refresh_callback_;
   Callback expire_callback_;
+  struct timeval current_lease_expiration_time_;
+  Time *time_;
 
   DISALLOW_COPY_AND_ASSIGN(IPConfig);
 };
diff --git a/shill/ipconfig_unittest.cc b/shill/ipconfig_unittest.cc
index 31b5248..0aa4469 100644
--- a/shill/ipconfig_unittest.cc
+++ b/shill/ipconfig_unittest.cc
@@ -4,23 +4,31 @@
 
 #include "shill/ipconfig.h"
 
+#include <sys/time.h>
+
 #include <base/bind.h>
 #include <chromeos/dbus/service_constants.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include "shill/logging.h"
 #include "shill/mock_adaptors.h"
 #include "shill/mock_control.h"
+#include "shill/mock_log.h"
 #include "shill/mock_store.h"
+#include "shill/mock_time.h"
 #include "shill/static_ip_parameters.h"
 
 using base::Bind;
 using base::Unretained;
 using std::string;
 using testing::_;
+using testing::EndsWith;
+using testing::DoAll;
 using testing::Mock;
 using testing::Return;
 using testing::SaveArg;
+using testing::SetArgPointee;
 using testing::SetArgumentPointee;
 using testing::StrictMock;
 using testing::Test;
@@ -29,11 +37,14 @@
 
 namespace {
 const char kDeviceName[] = "testdevice";
+const uint32_t kTimeNow = 10;
 }  // namespace
 
 class IPConfigTest : public Test {
  public:
-  IPConfigTest() : ipconfig_(new IPConfig(&control_, kDeviceName)) {}
+  IPConfigTest() : ipconfig_(new IPConfig(&control_, kDeviceName)) {
+    ipconfig_->time_ = &time_;
+  }
   void DropRef(const IPConfigRefPtr &/*ipconfig*/) {
     ipconfig_ = nullptr;
   }
@@ -90,6 +101,7 @@
   }
 
   MockControl control_;
+  MockTime time_;
   IPConfigRefPtr ipconfig_;
 };
 
@@ -226,4 +238,53 @@
   Mock::VerifyAndClearExpectations(adaptor);
 }
 
+TEST_F(IPConfigTest, UpdateLeaseExpirationTime) {
+  const struct timeval expected_time_now = {kTimeNow , 0};
+  uint32_t lease_duration = 1;
+  EXPECT_CALL(time_, GetTimeBoottime(_))
+      .WillOnce(DoAll(SetArgPointee<0>(expected_time_now), Return(0)));
+  ipconfig_->UpdateLeaseExpirationTime(lease_duration);
+  EXPECT_EQ(kTimeNow + lease_duration,
+            ipconfig_->current_lease_expiration_time_.tv_sec);
+}
+
+TEST_F(IPConfigTest, TimeToLeaseExpiry_NoDHCPLease) {
+  ScopedMockLog log;
+  uint32_t time_left = 0;
+  // |current_lease_expiration_time_| has not been set, so expect an error.
+  EXPECT_CALL(log, Log(logging::LOG_ERROR, _,
+                       EndsWith("No current DHCP lease")));
+  EXPECT_FALSE(ipconfig_->TimeToLeaseExpiry(&time_left));
+  EXPECT_EQ(0, time_left);
+}
+
+TEST_F(IPConfigTest, TimeToLeaseExpiry_CurrentLeaseExpired) {
+  ScopedMockLog log;
+  const struct timeval time_now = {kTimeNow, 0};
+  uint32_t time_left = 0;
+  // Set |current_lease_expiration_time_| so it is expired (i.e. earlier than
+  // current time).
+  ipconfig_->current_lease_expiration_time_ = {kTimeNow - 1, 0};
+  EXPECT_CALL(time_, GetTimeBoottime(_))
+      .WillOnce(DoAll(SetArgPointee<0>(time_now), Return(0)));
+  EXPECT_CALL(log, Log(logging::LOG_ERROR, _,
+                       EndsWith("Current DHCP lease has already expired")));
+  EXPECT_FALSE(ipconfig_->TimeToLeaseExpiry(&time_left));
+  EXPECT_EQ(0, time_left);
+}
+
+TEST_F(IPConfigTest, TimeToLeaseExpiry_Success) {
+  const uint32_t expected_time_to_expiry = 10;
+  const struct timeval time_now = {kTimeNow, 0};
+  uint32_t time_left;
+  // Set |current_lease_expiration_time_| so it appears like we already
+  // have obtained a DHCP lease before.
+  ipconfig_->current_lease_expiration_time_ = {
+      kTimeNow + expected_time_to_expiry, 0};
+  EXPECT_CALL(time_, GetTimeBoottime(_))
+      .WillOnce(DoAll(SetArgPointee<0>(time_now), Return(0)));
+  EXPECT_TRUE(ipconfig_->TimeToLeaseExpiry(&time_left));
+  EXPECT_EQ(expected_time_to_expiry, time_left);
+}
+
 }  // namespace shill
diff --git a/shill/mock_ipconfig.h b/shill/mock_ipconfig.h
index d35bbcc..0d97b57 100644
--- a/shill/mock_ipconfig.h
+++ b/shill/mock_ipconfig.h
@@ -29,6 +29,8 @@
   MOCK_METHOD0(EmitChanges, void(void));
   MOCK_METHOD1(UpdateDNSServers,
                void(const std::vector<std::string> &dns_servers));
+  MOCK_METHOD1(UpdateLeaseExpirationTime, void(uint32_t new_lease_duration));
+  MOCK_METHOD0(ResetLeaseExpirationTime, void(void));
 
  private:
   const Properties &real_properties() {