shill: add IPv6Disabled device property

Add a readwrite Device property IPv6Disabled to disable and
reenable IPv6 on a device without restarting shill or disrupting
IPv4. The force disabling of IPv6 on cellular devices is also
migrated into using this property.

BUG=b:160374783
TEST=unit, manual on atlas through dbus-send
     dbus-send --system --dest=org.chromium.flimflam --print-reply
     /device/eth0 org.chromium.flimflam.Device.SetProperty
     string:IPv6Disabled boolean:false

Change-Id: Ia30854d7637aab84d0cbbb3da2b0a79281421a71
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2279395
Tested-by: Taoyu Li <taoyl@chromium.org>
Commit-Queue: Alex Khouderchah <akhouderchah@chromium.org>
Reviewed-by: Hugo Benichi <hugobenichi@google.com>
Reviewed-by: Alex Khouderchah <akhouderchah@chromium.org>
diff --git a/shill/cellular/cellular.cc b/shill/cellular/cellular.cc
index 2c403a6..306e046 100644
--- a/shill/cellular/cellular.cc
+++ b/shill/cellular/cellular.cc
@@ -165,6 +165,9 @@
       weak_ptr_factory_(this) {
   RegisterProperties();
 
+  Error error;
+  SetIPv6Disabled(IsIPv6DisabledByDefault(), &error);
+
   // TODO(pprabhu) Split MobileOperatorInfo into a context that stores the
   // costly database, and lighter objects that |Cellular| can own.
   // crbug.com/363874
@@ -363,7 +366,7 @@
   // this device using the old IP, we need to make sure we prevent further
   // packets from going out.
   if (manager() && manager()->device_info() && socket_destroyer_) {
-    DisableIPv6();
+    StopIPv6();
 
     for (const auto& address :
          manager()->device_info()->GetAddresses(interface_index())) {
@@ -474,7 +477,7 @@
   capability_->Reset(error, callback);
 }
 
-bool Cellular::IsIPv6Allowed() const {
+bool Cellular::IsIPv6DisabledByDefault() const {
   // A cellular device is disabled before the system goes into suspend mode.
   // However, outstanding TCP sockets may not be nuked when the associated
   // network interface goes down. When the system resumes from suspend, the
@@ -485,7 +488,7 @@
   // the same modem within a connection session and may drop the connection.
   // Here we disable IPv6 support on cellular devices to work around the issue.
   //
-  return false;
+  return true;
 }
 
 void Cellular::DropConnection() {
@@ -577,7 +580,7 @@
   }
 
   // Re-enable IPv6 so we can renegotiate an IP address.
-  EnableIPv6();
+  StartIPv6();
 
   // TODO(quiche): Consider if this should be conditional. If, e.g.,
   // the device was still disabling when we suspended, will trying to
diff --git a/shill/cellular/cellular.h b/shill/cellular/cellular.h
index 15f40c2..d44763e 100644
--- a/shill/cellular/cellular.h
+++ b/shill/cellular/cellular.h
@@ -196,7 +196,7 @@
                  Error* error,
                  const ResultCallback& callback) override;
   void Reset(Error* error, const ResultCallback& callback) override;
-  bool IsIPv6Allowed() const override;
+  bool IsIPv6DisabledByDefault() const override;
   void DropConnection() override;
   void SetServiceState(Service::ConnectState state) override;
   void SetServiceFailure(Service::ConnectFailure failure_state) override;
diff --git a/shill/device.cc b/shill/device.cc
index 1779bf2..50e1414 100644
--- a/shill/device.cc
+++ b/shill/device.cc
@@ -23,9 +23,9 @@
 #include <base/files/file_util.h>
 #include <base/memory/ref_counted.h>
 #include <base/stl_util.h>
-#include <base/strings/stringprintf.h>
 #include <base/strings/string_number_conversions.h>
 #include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
 #include <chromeos/dbus/service_constants.h>
 
 #include "shill/connection.h"
@@ -152,6 +152,7 @@
       rtnl_handler_(RTNLHandler::GetInstance()),
       time_(Time::GetInstance()),
       last_link_monitor_failed_time_(0),
+      ipv6_disabled_(false),
       is_loose_routing_(false),
       is_multi_homed_(false),
       fixed_ip_params_(false),
@@ -182,6 +183,8 @@
   // kDBusObjectProperty: Register in Cellular
 
   store_.RegisterConstString(kInterfaceProperty, &link_name_);
+  HelpRegisterDerivedBool(kIPv6DisabledProperty, &Device::GetIPv6Disabled,
+                          &Device::SetIPv6Disabled, &Device::ClearIPv6Disabled);
   HelpRegisterConstDerivedRpcIdentifier(
       kSelectedServiceProperty, &Device::GetSelectedServiceRpcIdentifier);
   HelpRegisterConstDerivedRpcIdentifiers(kIPConfigsProperty,
@@ -287,20 +290,40 @@
   }
 }
 
-bool Device::IsIPv6Allowed() const {
+bool Device::GetIPv6Disabled(Error* error) {
+  return ipv6_disabled_;
+}
+
+bool Device::SetIPv6Disabled(const bool& disabling, Error* /*error*/) {
+  if (disabling == ipv6_disabled_)
+    return false;
+  ipv6_disabled_ = disabling;
+  if (ipv6_disabled_) {
+    StopIPv6();
+  } else {
+    StartIPv6();
+  }
   return true;
 }
 
-void Device::DisableIPv6() {
+void Device::ClearIPv6Disabled(Error* error) {
+  SetIPv6Disabled(IsIPv6DisabledByDefault(), error);
+}
+
+bool Device::IsIPv6DisabledByDefault() const {
+  return false;
+}
+
+void Device::StopIPv6() {
   SLOG(this, 2) << __func__;
   SetIPFlag(IPAddress::kFamilyIPv6, kIPFlagDisableIPv6, "1");
 }
 
-void Device::EnableIPv6() {
+void Device::StartIPv6() {
   SLOG(this, 2) << __func__;
-  if (!IsIPv6Allowed()) {
+  if (ipv6_disabled_) {
     LOG(INFO) << "Skip enabling IPv6 on " << link_name_
-              << " as it is not allowed.";
+              << " as it is disabled.";
     return;
   }
   SetIPFlag(IPAddress::kFamilyIPv6, kIPFlagDisableIPv6, "0");
@@ -512,7 +535,7 @@
 }
 
 void Device::DestroyIPConfig() {
-  DisableIPv6();
+  StopIPv6();
   bool ipconfig_changed = false;
   if (ipconfig_) {
     ipconfig_->ReleaseIP(IPConfig::kReleaseReasonDisconnect);
@@ -772,7 +795,7 @@
 
 bool Device::AcquireIPConfigWithLeaseName(const string& lease_name) {
   DestroyIPConfig();
-  EnableIPv6();
+  StartIPv6();
   bool arp_gateway = manager_->GetArpGateway() && ShouldUseArpGateway();
   DHCPConfigRefPtr dhcp_config;
   if (selected_service_) {
@@ -849,7 +872,7 @@
 
 void Device::AssignIPConfig(const IPConfig::Properties& properties) {
   DestroyIPConfig();
-  EnableIPv6();
+  StartIPv6();
   ipconfig_ = new IPConfig(control_interface(), link_name_);
   ipconfig_->set_properties(properties);
   dispatcher()->PostTask(FROM_HERE, Bind(&Device::OnIPConfigUpdated,
@@ -860,6 +883,15 @@
   dhcp_provider_->DestroyLease(name);
 }
 
+void Device::HelpRegisterDerivedBool(const string& name,
+                                     bool (Device::*get)(Error* error),
+                                     bool (Device::*set)(const bool&, Error*),
+                                     void (Device::*clear)(Error*)) {
+  store_.RegisterDerivedBool(
+      name,
+      BoolAccessor(new CustomAccessor<Device, bool>(this, get, set, clear)));
+}
+
 void Device::HelpRegisterConstDerivedString(
     const string& name, string (Device::*get)(Error* error)) {
   store_.RegisterDerivedString(
diff --git a/shill/device.h b/shill/device.h
index 57a7f1a..aa7f086 100644
--- a/shill/device.h
+++ b/shill/device.h
@@ -124,13 +124,13 @@
   virtual void Reset(Error* error, const ResultCallback& callback);
   virtual void RefreshIPConfig(Error* error);
 
-  // Returns true if IPv6 is allowed and should be enabled when the device
+  // Returns false if IPv6 is allowed and should be enabled when the device
   // tries to acquire an IP configuration. The default implementation allows
   // IPv6, which can be overridden by a derived class.
-  virtual bool IsIPv6Allowed() const;
+  virtual bool IsIPv6DisabledByDefault() const;
 
-  mockable void DisableIPv6();
-  mockable void EnableIPv6();
+  void StopIPv6();
+  void StartIPv6();
   mockable void EnableIPv6Privacy();
 
   // Returns true if the selected service on the device (if any) is connected.
@@ -301,7 +301,8 @@
   // Programs the NIC to wake on every packet of IP protocol type belonging to
   // |packet_types|. |error| indicates the result of the operation.
   virtual void AddWakeOnPacketOfTypes(
-      const std::vector<std::string>& packet_types, Error* error);
+      const std::vector<std::string>& packet_types,
+      Error* error);
   // Removes a rule previously programmed into the NIC to wake the system from
   // suspend upon receiving packets from |ip_endpoint|. |error| indicates the
   // result of the operation.
@@ -311,7 +312,8 @@
   // protocol type belonging to |packet_types|.
   // |error| indicates the result of the operation.
   virtual void RemoveWakeOnPacketOfTypes(
-      const std::vector<std::string>& packet_types, Error* error);
+      const std::vector<std::string>& packet_types,
+      Error* error);
   // Removes all wake-on-packet rules programmed into the NIC. |error| indicates
   // the result of the operation.
   virtual void RemoveAllWakeOnPacketConnections(Error* error);
@@ -371,7 +373,6 @@
   FRIEND_TEST(DeviceTest, DestroyIPConfig);
   FRIEND_TEST(DeviceTest, DestroyIPConfigNULL);
   FRIEND_TEST(DeviceTest, ConfigWithMinimumMTU);
-  FRIEND_TEST(DeviceTest, EnableIPv6);
   FRIEND_TEST(DeviceTest, GetProperties);
   FRIEND_TEST(DeviceTest, IPConfigUpdatedFailureWithIPv6Config);
   FRIEND_TEST(DeviceTest, IPConfigUpdatedFailureWithIPv6Connection);
@@ -393,6 +394,8 @@
   FRIEND_TEST(DeviceTest, SetEnabledPersistent);
   FRIEND_TEST(DeviceTest, ShouldUseArpGateway);
   FRIEND_TEST(DeviceTest, Start);
+  FRIEND_TEST(DeviceTest, StartIPv6);
+  FRIEND_TEST(DeviceTest, StartIPv6Disabled);
   FRIEND_TEST(DeviceTest, Stop);
   FRIEND_TEST(DeviceTest, StopWithFixedIpParams);
   FRIEND_TEST(DeviceTest, StopWithNetworkInterfaceDisabledAfterward);
@@ -522,6 +525,16 @@
   // Indicates if the selected service is configured with a static IP address.
   bool IsUsingStaticIP() const;
 
+  // RPC getter, setter, and clear method for the "IPv6Disabled" property.
+  bool GetIPv6Disabled(Error* error);
+  bool SetIPv6Disabled(const bool& connect, Error* error);
+  void ClearIPv6Disabled(Error* error);
+
+  void HelpRegisterDerivedBool(const std::string& name,
+                               bool (Device::*get)(Error* error),
+                               bool (Device::*set)(const bool& value,
+                                                   Error* error),
+                               void (Device::*clear)(Error* error));
   void HelpRegisterConstDerivedString(const std::string& name,
                                       std::string (Device::*get)(Error*));
 
@@ -861,6 +874,8 @@
 
   std::unique_ptr<PortalDetector> connection_tester_;
 
+  bool ipv6_disabled_;
+
   // Track whether packets from non-optimal routes will be accepted by this
   // device.  This is referred to as "loose mode" (see RFC3704).
   bool is_loose_routing_;
diff --git a/shill/device_test.cc b/shill/device_test.cc
index e4085d0..f461d61 100644
--- a/shill/device_test.cc
+++ b/shill/device_test.cc
@@ -80,8 +80,6 @@
              int interface_index,
              Technology technology)
       : Device(manager, link_name, address, interface_index, technology) {
-    ON_CALL(*this, IsIPv6Allowed())
-        .WillByDefault(Invoke(this, &TestDevice::DeviceIsIPv6Allowed));
     ON_CALL(*this, SetIPFlag(_, _, _))
         .WillByDefault(Invoke(this, &TestDevice::DeviceSetIPFlag));
     ON_CALL(*this, IsTrafficMonitorEnabled())
@@ -107,7 +105,6 @@
     DCHECK(error);
   }
 
-  MOCK_METHOD(bool, IsIPv6Allowed, (), (const, override));
   MOCK_METHOD(bool, IsTrafficMonitorEnabled, (), (const, override));
   MOCK_METHOD(bool,
               ShouldBringNetworkInterfaceDownAfterDisabled,
@@ -128,8 +125,6 @@
               (const PortalDetector::Result&, const PortalDetector::Result&),
               (override));
 
-  virtual bool DeviceIsIPv6Allowed() const { return Device::IsIPv6Allowed(); }
-
   virtual bool DeviceIsTrafficMonitorEnabled() const {
     return Device::IsTrafficMonitorEnabled();
   }
@@ -465,7 +460,7 @@
   device_->AcquireIPConfig();
 }
 
-TEST_F(DeviceTest, EnableIPv6) {
+TEST_F(DeviceTest, StartIPv6) {
   EXPECT_CALL(*device_,
               SetIPFlag(IPAddress::kFamilyIPv6,
                         StrEq(Device::kIPFlagDisableIPv6), StrEq("0")))
@@ -475,13 +470,21 @@
       SetIPFlag(IPAddress::kFamilyIPv6,
                 StrEq(Device::kIPFlagAcceptRouterAdvertisements), StrEq("2")))
       .WillOnce(Return(true));
-  device_->EnableIPv6();
+  device_->StartIPv6();
 }
 
-TEST_F(DeviceTest, EnableIPv6NotAllowed) {
-  EXPECT_CALL(*device_, IsIPv6Allowed()).WillOnce(Return(false));
+TEST_F(DeviceTest, StartIPv6Disabled) {
+  Error error;
+  EXPECT_CALL(*device_,
+              SetIPFlag(IPAddress::kFamilyIPv6,
+                        StrEq(Device::kIPFlagDisableIPv6), StrEq("1")))
+      .WillOnce(Return(true));
+  device_->SetIPv6Disabled(true, &error);
+  Mock::VerifyAndClearExpectations(device_.get());
   EXPECT_CALL(*device_, SetIPFlag(_, _, _)).Times(0);
-  device_->EnableIPv6();
+  device_->StartIPv6();
+  Mock::VerifyAndClearExpectations(device_.get());
+  device_->SetIPv6Disabled(false, &error);
 }
 
 TEST_F(DeviceTest, MultiHomed) {
diff --git a/shill/doc/device-api.txt b/shill/doc/device-api.txt
index d4a1946..a7c1a61 100644
--- a/shill/doc/device-api.txt
+++ b/shill/doc/device-api.txt
@@ -542,6 +542,12 @@
 			these IPConfigs, a PropertyChanged signal will be
 			emitted for the Device's IPConfig property.
 
+		boolean IPv6Disabled [readwrite]
+
+			A switch to force disabling IPv6 on the device.
+			Disabling and enabling this switch has no impact on
+			IPv4 connectivity.
+
 		int32 LinkMonitorResponseTime [readonly]
 
 			Latest running average of the link monitor response
diff --git a/shill/mock_device.h b/shill/mock_device.h
index a766131..3ed68dd 100644
--- a/shill/mock_device.h
+++ b/shill/mock_device.h
@@ -45,8 +45,6 @@
   MOCK_METHOD(void, Scan, (Error*, const std::string&), (override));
   MOCK_METHOD(bool, Load, (const StoreInterface*), (override));
   MOCK_METHOD(bool, Save, (StoreInterface*), (override));
-  MOCK_METHOD(void, DisableIPv6, (), (override));
-  MOCK_METHOD(void, EnableIPv6, (), (override));
   MOCK_METHOD(void, EnableIPv6Privacy, (), (override));
   MOCK_METHOD(void, SetLooseRouting, (bool), (override));
   MOCK_METHOD(void, SetIsMultiHomed, (bool), (override));
diff --git a/system_api/dbus/shill/dbus-constants.h b/system_api/dbus/shill/dbus-constants.h
index c893dc2..9fe7a69 100644
--- a/system_api/dbus/shill/dbus-constants.h
+++ b/system_api/dbus/shill/dbus-constants.h
@@ -321,6 +321,7 @@
 // Base Device property names.
 const char kAddressProperty[] = "Address";  // Also used for IPConfig.
 const char kIPConfigsProperty[] = "IPConfigs";
+const char kIPv6DisabledProperty[] = "IPv6Disabled";
 const char kInterfaceProperty[] = "Interface";  // Network interface name.
 const char kLinkMonitorResponseTimeProperty[] = "LinkMonitorResponseTime";
 // kNameProperty: Defined above for Service. DEPRECATED (crbug.com/1011136).