// 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 #include #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 { 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 CreateNetworkChangeNotifierApple() { auto notifier = std::make_unique(); base::RunLoop run_loop; notifier->SetCallbacksForTest( run_loop.QuitClosure(), base::BindRepeating( [](std::optional* 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 array( CFArrayCreateMutable(nullptr, /*capacity=*/0, &kCFTypeArrayCallBacks)); base::apple::ScopedCFTypeRef 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 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& 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 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 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 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 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 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 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 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 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 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 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