chromium/net/base/network_change_notifier_apple_unittest.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/base/network_change_notifier_apple.h"

#include <optional>
#include <string>

#include "base/apple/scoped_cftyperef.h"
#include "base/location.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread.h"
#include "net/base/features.h"
#include "net/base/network_change_notifier.h"
#include "net/base/network_config_watcher_apple.h"
#include "net/test/test_with_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace net {
namespace {

static const char kIPv4PrivateAddrString1[] = "192.168.0.1";
static const char kIPv4PrivateAddrString2[] = "192.168.0.2";

static const char kIPv6PublicAddrString1[] =
    "2401:fa00:4:1000:be30:5b30:50e5:c0";
static const char kIPv6PublicAddrString2[] =
    "2401:fa00:4:1000:be30:5b30:50e5:c1";
static const char kIPv6LinkLocalAddrString1[] = "fe80::0:1:1:1";
static const char kIPv6LinkLocalAddrString2[] = "fe80::0:2:2:2";

class TestIPAddressObserver : public NetworkChangeNotifier::IPAddressObserver {
 public:
  TestIPAddressObserver() { NetworkChangeNotifier::AddIPAddressObserver(this); }

  TestIPAddressObserver(const TestIPAddressObserver&) = delete;
  TestIPAddressObserver& operator=(const TestIPAddressObserver&) = delete;

  ~TestIPAddressObserver() override {
    NetworkChangeNotifier::RemoveIPAddressObserver(this);
  }

  // Implements NetworkChangeNotifier::IPAddressObserver:
  void OnIPAddressChanged() override { ip_address_changed_ = true; }

  bool ip_address_changed() const { return ip_address_changed_; }

 private:
  bool ip_address_changed_ = false;
};

}  // namespace

class NetworkChangeNotifierAppleTest : public WithTaskEnvironment,
                                       public ::testing::TestWithParam<bool> {
 public:
  NetworkChangeNotifierAppleTest() {
    if (ReduceIPAddressChangeNotificationEnabled()) {
      feature_list_.InitWithFeatures(
          /*enabled_features=*/{features::kReduceIPAddressChangeNotification},
          /*disabled_features=*/{});
    } else {
      feature_list_.InitWithFeatures(
          /*enabled_features=*/{},
          /*disabled_features=*/{features::kReduceIPAddressChangeNotification});
    }
  }
  NetworkChangeNotifierAppleTest(const NetworkChangeNotifierAppleTest&) =
      delete;
  NetworkChangeNotifierAppleTest& operator=(
      const NetworkChangeNotifierAppleTest&) = delete;
  ~NetworkChangeNotifierAppleTest() override = default;

  void TearDown() override { RunUntilIdle(); }

 protected:
  bool ReduceIPAddressChangeNotificationEnabled() const { return GetParam(); }

  std::unique_ptr<NetworkChangeNotifierApple>
  CreateNetworkChangeNotifierApple() {
    auto notifier = std::make_unique<NetworkChangeNotifierApple>();
    base::RunLoop run_loop;
    notifier->SetCallbacksForTest(
        run_loop.QuitClosure(),
        base::BindRepeating(
            [](std::optional<NetworkInterfaceList>* network_interface_list,
               NetworkInterfaceList* list_out, int) {
              if (!network_interface_list->has_value()) {
                return false;
              }
              *list_out = **network_interface_list;
              return true;
            },
            &network_interface_list_),
        base::BindRepeating(
            [](std::string* ipv4_primary_interface_name, SCDynamicStoreRef)
                -> std::string { return *ipv4_primary_interface_name; },
            &ipv4_primary_interface_name_),
        base::BindRepeating(
            [](std::string* ipv6_primary_interface_name, SCDynamicStoreRef)
                -> std::string { return *ipv6_primary_interface_name; },
            &ipv6_primary_interface_name_));
    run_loop.Run();
    return notifier;
  }

  void SimulateDynamicStoreCallback(NetworkChangeNotifierApple& notifier,
                                    CFStringRef entity) {
    base::RunLoop run_loop;
    notifier.config_watcher_->GetNotifierThreadForTest()
        ->task_runner()
        ->PostTask(
            FROM_HERE, base::BindLambdaForTesting([&]() {
              base::apple::ScopedCFTypeRef<CFMutableArrayRef> array(
                  CFArrayCreateMutable(nullptr,
                                       /*capacity=*/0, &kCFTypeArrayCallBacks));
              base::apple::ScopedCFTypeRef<CFStringRef> entry_key(
                  SCDynamicStoreKeyCreateNetworkGlobalEntity(
                      nullptr, kSCDynamicStoreDomainState, entity));
              CFArrayAppendValue(array.get(), entry_key.get());
              notifier.OnNetworkConfigChange(array.get());
              run_loop.Quit();
            }));
    run_loop.Run();
  }

 protected:
  std::optional<NetworkInterfaceList> network_interface_list_ =
      NetworkInterfaceList();
  std::string ipv4_primary_interface_name_ = "en0";
  std::string ipv6_primary_interface_name_ = "en0";

 private:
  // Allows us to allocate our own NetworkChangeNotifier for unit testing.
  NetworkChangeNotifier::DisableForTest disable_for_test_;
  base::test::ScopedFeatureList feature_list_;
};

INSTANTIATE_TEST_SUITE_P(
    All,
    NetworkChangeNotifierAppleTest,
    ::testing::Values(true, false),
    [](const testing::TestParamInfo<bool>& info) {
      return info.param ? "ReduceIPAddressChangeNotificationEnabled"
                        : "ReduceIPAddressChangeNotificationDisabled";
    });

TEST_P(NetworkChangeNotifierAppleTest, NoInterfaceChange) {
  net::IPAddress ip_address;
  EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv4PrivateAddrString1));
  network_interface_list_->push_back(net::NetworkInterface(
      "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));

  std::unique_ptr<NetworkChangeNotifierApple> notifier =
      CreateNetworkChangeNotifierApple();

  // Simulate OnNetworkConfigChange callback without any change in
  // NetworkInterfaceList
  TestIPAddressObserver observer;
  SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
  RunUntilIdle();
  // When kReduceIPAddressChangeNotification feature is enabled, we ignores
  // the OnNetworkConfigChange callback without any network interface change.
  EXPECT_EQ(observer.ip_address_changed(),
            !ReduceIPAddressChangeNotificationEnabled());
}

TEST_P(NetworkChangeNotifierAppleTest, IPv4AddressChange) {
  net::IPAddress ip_address;
  EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv4PrivateAddrString1));
  network_interface_list_->push_back(net::NetworkInterface(
      "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));

  std::unique_ptr<NetworkChangeNotifierApple> notifier =
      CreateNetworkChangeNotifierApple();

  // Simulate OnNetworkConfigChange callback with IPv4 address change.
  EXPECT_TRUE((*network_interface_list_)[0].address.AssignFromIPLiteral(
      kIPv4PrivateAddrString2));
  TestIPAddressObserver observer;
  SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
  RunUntilIdle();
  EXPECT_TRUE(observer.ip_address_changed());
}

TEST_P(NetworkChangeNotifierAppleTest, PublicIPv6AddressChange) {
  net::IPAddress ip_address;
  EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv6PublicAddrString1));
  network_interface_list_->push_back(net::NetworkInterface(
      "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address, 64, net::IP_ADDRESS_ATTRIBUTE_NONE));

  std::unique_ptr<NetworkChangeNotifierApple> notifier =
      CreateNetworkChangeNotifierApple();

  // Simulate OnNetworkConfigChange callback with a public IPv6 address change.
  EXPECT_TRUE((*network_interface_list_)[0].address.AssignFromIPLiteral(
      kIPv6PublicAddrString2));
  TestIPAddressObserver observer;
  SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv6);
  RunUntilIdle();
  EXPECT_TRUE(observer.ip_address_changed());
}

TEST_P(NetworkChangeNotifierAppleTest,
       LinkLocalIPv6AddressChangeOnPrimaryInterface) {
  net::IPAddress ip_address;
  EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv6LinkLocalAddrString1));
  network_interface_list_->push_back(net::NetworkInterface(
      "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address, 64, net::IP_ADDRESS_ATTRIBUTE_NONE));

  std::unique_ptr<NetworkChangeNotifierApple> notifier =
      CreateNetworkChangeNotifierApple();

  // Simulate OnNetworkConfigChange callback with a link local IPv6 address
  // change on the primary interface "en0".
  EXPECT_TRUE((*network_interface_list_)[0].address.AssignFromIPLiteral(
      kIPv6LinkLocalAddrString2));
  TestIPAddressObserver observer;
  SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
  RunUntilIdle();
  EXPECT_TRUE(observer.ip_address_changed());
}

TEST_P(NetworkChangeNotifierAppleTest,
       LinkLocalIPv6AddressChangeOnNonPrimaryInterface) {
  net::IPAddress ip_address1;
  EXPECT_TRUE(ip_address1.AssignFromIPLiteral(kIPv4PrivateAddrString1));
  network_interface_list_->push_back(net::NetworkInterface(
      "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address1, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));

  net::IPAddress ip_address2;
  EXPECT_TRUE(ip_address2.AssignFromIPLiteral(kIPv6LinkLocalAddrString1));
  network_interface_list_->push_back(net::NetworkInterface(
      "en1", "en1", 2, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address2, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));

  std::unique_ptr<NetworkChangeNotifierApple> notifier =
      CreateNetworkChangeNotifierApple();

  // Simulate OnNetworkConfigChange callback with a link local IPv6 address
  // change on the non-primary interface "en1".
  EXPECT_TRUE((*network_interface_list_)[1].address.AssignFromIPLiteral(
      kIPv6LinkLocalAddrString2));
  TestIPAddressObserver observer;
  SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
  RunUntilIdle();
  // When kReduceIPAddressChangeNotification feature is enabled, we ignores
  // the link local IPv6 address change on the non-primary interface.
  EXPECT_EQ(observer.ip_address_changed(),
            !ReduceIPAddressChangeNotificationEnabled());
}

TEST_P(NetworkChangeNotifierAppleTest, NewInterfaceWithIpV4) {
  net::IPAddress ip_address;
  EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv4PrivateAddrString1));
  network_interface_list_->push_back(net::NetworkInterface(
      "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));

  std::unique_ptr<NetworkChangeNotifierApple> notifier =
      CreateNetworkChangeNotifierApple();

  // Simulate OnNetworkConfigChange callback with a new interface with a IPv4
  // address.
  net::IPAddress ip_address2;
  EXPECT_TRUE(ip_address2.AssignFromIPLiteral(kIPv4PrivateAddrString2));
  network_interface_list_->push_back(net::NetworkInterface(
      "en1", "en1", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address2, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));

  TestIPAddressObserver observer;
  SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
  RunUntilIdle();
  EXPECT_TRUE(observer.ip_address_changed());
}

TEST_P(NetworkChangeNotifierAppleTest, NewInterfaceWithLinkLocalIpV6) {
  net::IPAddress ip_address;
  EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv4PrivateAddrString1));
  network_interface_list_->push_back(net::NetworkInterface(
      "en0", "en0", 2, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));

  std::unique_ptr<NetworkChangeNotifierApple> notifier =
      CreateNetworkChangeNotifierApple();

  // Simulate OnNetworkConfigChange callback with a new interface with a link
  // local IPv6 address.
  net::IPAddress ip_address2;
  EXPECT_TRUE(ip_address2.AssignFromIPLiteral(kIPv6LinkLocalAddrString1));
  EXPECT_FALSE(ip_address2.IsPubliclyRoutable());
  network_interface_list_->push_back(net::NetworkInterface(
      "en1", "en1", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address2, 64, net::IP_ADDRESS_ATTRIBUTE_NONE));

  TestIPAddressObserver observer;
  SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
  RunUntilIdle();
  // When kReduceIPAddressChangeNotification feature is enabled, we ignores
  // the new link local IPv6 interface.
  EXPECT_EQ(observer.ip_address_changed(),
            !ReduceIPAddressChangeNotificationEnabled());
}

TEST_P(NetworkChangeNotifierAppleTest, NewInterfaceWithPublicIpV6) {
  net::IPAddress ip_address;
  EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv4PrivateAddrString1));
  network_interface_list_->push_back(net::NetworkInterface(
      "en0", "en0", 2, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));

  std::unique_ptr<NetworkChangeNotifierApple> notifier =
      CreateNetworkChangeNotifierApple();

  // Simulate OnNetworkConfigChange callback with a new interface with a
  // public IPv6 address.
  net::IPAddress ip_address2;
  EXPECT_TRUE(ip_address2.AssignFromIPLiteral(kIPv6PublicAddrString1));
  EXPECT_TRUE(ip_address2.IsPubliclyRoutable());
  network_interface_list_->push_back(net::NetworkInterface(
      "en1", "en1", 2, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address2, 64, net::IP_ADDRESS_ATTRIBUTE_NONE));

  TestIPAddressObserver observer;
  SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
  RunUntilIdle();
  EXPECT_TRUE(observer.ip_address_changed());
}

TEST_P(NetworkChangeNotifierAppleTest, IPv4PrimaryInterfaceChange) {
  net::IPAddress ip_address;
  EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv4PrivateAddrString1));
  network_interface_list_->push_back(net::NetworkInterface(
      "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));
  net::IPAddress ip_address2;
  EXPECT_TRUE(ip_address2.AssignFromIPLiteral(kIPv4PrivateAddrString2));
  network_interface_list_->push_back(net::NetworkInterface(
      "en1", "en1", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address2, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));

  std::unique_ptr<NetworkChangeNotifierApple> notifier =
      CreateNetworkChangeNotifierApple();

  // Simulate OnNetworkConfigChange callback for the IPv4 primary interface
  // change.
  TestIPAddressObserver observer;
  ipv4_primary_interface_name_ = "en1";
  SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
  RunUntilIdle();
  EXPECT_TRUE(observer.ip_address_changed());
}

TEST_P(NetworkChangeNotifierAppleTest, IPv6PrimaryInterfaceChange) {
  net::IPAddress ip_address;
  EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv6PublicAddrString1));
  network_interface_list_->push_back(net::NetworkInterface(
      "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));
  net::IPAddress ip_address2;
  EXPECT_TRUE(ip_address2.AssignFromIPLiteral(kIPv6PublicAddrString2));
  network_interface_list_->push_back(net::NetworkInterface(
      "en1", "en1", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
      ip_address2, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));

  std::unique_ptr<NetworkChangeNotifierApple> notifier =
      CreateNetworkChangeNotifierApple();

  // Simulate OnNetworkConfigChange callback for the IPv6 primary interface
  // change.
  TestIPAddressObserver observer;
  ipv6_primary_interface_name_ = "en1";
  SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv6);
  RunUntilIdle();
  EXPECT_TRUE(observer.ip_address_changed());
}

}  // namespace net