1 // Copyright 2024 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/base/network_change_notifier_apple.h"
6
7 #include <optional>
8 #include <string>
9
10 #include "base/apple/scoped_cftyperef.h"
11 #include "base/location.h"
12 #include "base/test/bind.h"
13 #include "base/test/scoped_feature_list.h"
14 #include "base/threading/thread.h"
15 #include "net/base/features.h"
16 #include "net/base/network_change_notifier.h"
17 #include "net/base/network_config_watcher_apple.h"
18 #include "net/test/test_with_task_environment.h"
19 #include "testing/gmock/include/gmock/gmock.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21
22 namespace net {
23 namespace {
24
25 static const char kIPv4PrivateAddrString1[] = "192.168.0.1";
26 static const char kIPv4PrivateAddrString2[] = "192.168.0.2";
27
28 static const char kIPv6PublicAddrString1[] =
29 "2401:fa00:4:1000:be30:5b30:50e5:c0";
30 static const char kIPv6PublicAddrString2[] =
31 "2401:fa00:4:1000:be30:5b30:50e5:c1";
32 static const char kIPv6LinkLocalAddrString1[] = "fe80::0:1:1:1";
33 static const char kIPv6LinkLocalAddrString2[] = "fe80::0:2:2:2";
34
35 class TestIPAddressObserver : public NetworkChangeNotifier::IPAddressObserver {
36 public:
TestIPAddressObserver()37 TestIPAddressObserver() { NetworkChangeNotifier::AddIPAddressObserver(this); }
38
39 TestIPAddressObserver(const TestIPAddressObserver&) = delete;
40 TestIPAddressObserver& operator=(const TestIPAddressObserver&) = delete;
41
~TestIPAddressObserver()42 ~TestIPAddressObserver() override {
43 NetworkChangeNotifier::RemoveIPAddressObserver(this);
44 }
45
46 // Implements NetworkChangeNotifier::IPAddressObserver:
OnIPAddressChanged()47 void OnIPAddressChanged() override { ip_address_changed_ = true; }
48
ip_address_changed() const49 bool ip_address_changed() const { return ip_address_changed_; }
50
51 private:
52 bool ip_address_changed_ = false;
53 };
54
55 } // namespace
56
57 class NetworkChangeNotifierAppleTest : public WithTaskEnvironment,
58 public ::testing::TestWithParam<bool> {
59 public:
NetworkChangeNotifierAppleTest()60 NetworkChangeNotifierAppleTest() {
61 if (ReduceIPAddressChangeNotificationEnabled()) {
62 feature_list_.InitWithFeatures(
63 /*enabled_features=*/{features::kReduceIPAddressChangeNotification},
64 /*disabled_features=*/{});
65 } else {
66 feature_list_.InitWithFeatures(
67 /*enabled_features=*/{},
68 /*disabled_features=*/{features::kReduceIPAddressChangeNotification});
69 }
70 }
71 NetworkChangeNotifierAppleTest(const NetworkChangeNotifierAppleTest&) =
72 delete;
73 NetworkChangeNotifierAppleTest& operator=(
74 const NetworkChangeNotifierAppleTest&) = delete;
75 ~NetworkChangeNotifierAppleTest() override = default;
76
TearDown()77 void TearDown() override { RunUntilIdle(); }
78
79 protected:
ReduceIPAddressChangeNotificationEnabled() const80 bool ReduceIPAddressChangeNotificationEnabled() const { return GetParam(); }
81
82 std::unique_ptr<NetworkChangeNotifierApple>
CreateNetworkChangeNotifierApple()83 CreateNetworkChangeNotifierApple() {
84 auto notifier = std::make_unique<NetworkChangeNotifierApple>();
85 base::RunLoop run_loop;
86 notifier->SetCallbacksForTest(
87 run_loop.QuitClosure(),
88 base::BindRepeating(
89 [](std::optional<NetworkInterfaceList>* network_interface_list,
90 NetworkInterfaceList* list_out, int) {
91 if (!network_interface_list->has_value()) {
92 return false;
93 }
94 *list_out = **network_interface_list;
95 return true;
96 },
97 &network_interface_list_),
98 base::BindRepeating(
99 [](std::string* ipv4_primary_interface_name, SCDynamicStoreRef)
100 -> std::string { return *ipv4_primary_interface_name; },
101 &ipv4_primary_interface_name_),
102 base::BindRepeating(
103 [](std::string* ipv6_primary_interface_name, SCDynamicStoreRef)
104 -> std::string { return *ipv6_primary_interface_name; },
105 &ipv6_primary_interface_name_));
106 run_loop.Run();
107 return notifier;
108 }
109
SimulateDynamicStoreCallback(NetworkChangeNotifierApple & notifier,CFStringRef entity)110 void SimulateDynamicStoreCallback(NetworkChangeNotifierApple& notifier,
111 CFStringRef entity) {
112 base::RunLoop run_loop;
113 notifier.config_watcher_->GetNotifierThreadForTest()
114 ->task_runner()
115 ->PostTask(
116 FROM_HERE, base::BindLambdaForTesting([&]() {
117 base::apple::ScopedCFTypeRef<CFMutableArrayRef> array(
118 CFArrayCreateMutable(nullptr,
119 /*capacity=*/0, &kCFTypeArrayCallBacks));
120 base::apple::ScopedCFTypeRef<CFStringRef> entry_key(
121 SCDynamicStoreKeyCreateNetworkGlobalEntity(
122 nullptr, kSCDynamicStoreDomainState, entity));
123 CFArrayAppendValue(array.get(), entry_key.get());
124 notifier.OnNetworkConfigChange(array.get());
125 run_loop.Quit();
126 }));
127 run_loop.Run();
128 }
129
130 protected:
131 std::optional<NetworkInterfaceList> network_interface_list_ =
132 NetworkInterfaceList();
133 std::string ipv4_primary_interface_name_ = "en0";
134 std::string ipv6_primary_interface_name_ = "en0";
135
136 private:
137 // Allows us to allocate our own NetworkChangeNotifier for unit testing.
138 NetworkChangeNotifier::DisableForTest disable_for_test_;
139 base::test::ScopedFeatureList feature_list_;
140 };
141
142 INSTANTIATE_TEST_SUITE_P(
143 All,
144 NetworkChangeNotifierAppleTest,
145 ::testing::Values(true, false),
__anoncef054190602(const testing::TestParamInfo<bool>& info) 146 [](const testing::TestParamInfo<bool>& info) {
147 return info.param ? "ReduceIPAddressChangeNotificationEnabled"
148 : "ReduceIPAddressChangeNotificationDisabled";
149 });
150
TEST_P(NetworkChangeNotifierAppleTest,NoInterfaceChange)151 TEST_P(NetworkChangeNotifierAppleTest, NoInterfaceChange) {
152 net::IPAddress ip_address;
153 EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv4PrivateAddrString1));
154 network_interface_list_->push_back(net::NetworkInterface(
155 "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
156 ip_address, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));
157
158 std::unique_ptr<NetworkChangeNotifierApple> notifier =
159 CreateNetworkChangeNotifierApple();
160
161 // Simulate OnNetworkConfigChange callback without any change in
162 // NetworkInterfaceList
163 TestIPAddressObserver observer;
164 SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
165 RunUntilIdle();
166 // When kReduceIPAddressChangeNotification feature is enabled, we ignores
167 // the OnNetworkConfigChange callback without any network interface change.
168 EXPECT_EQ(observer.ip_address_changed(),
169 !ReduceIPAddressChangeNotificationEnabled());
170 }
171
TEST_P(NetworkChangeNotifierAppleTest,IPv4AddressChange)172 TEST_P(NetworkChangeNotifierAppleTest, IPv4AddressChange) {
173 net::IPAddress ip_address;
174 EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv4PrivateAddrString1));
175 network_interface_list_->push_back(net::NetworkInterface(
176 "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
177 ip_address, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));
178
179 std::unique_ptr<NetworkChangeNotifierApple> notifier =
180 CreateNetworkChangeNotifierApple();
181
182 // Simulate OnNetworkConfigChange callback with IPv4 address change.
183 EXPECT_TRUE((*network_interface_list_)[0].address.AssignFromIPLiteral(
184 kIPv4PrivateAddrString2));
185 TestIPAddressObserver observer;
186 SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
187 RunUntilIdle();
188 EXPECT_TRUE(observer.ip_address_changed());
189 }
190
TEST_P(NetworkChangeNotifierAppleTest,PublicIPv6AddressChange)191 TEST_P(NetworkChangeNotifierAppleTest, PublicIPv6AddressChange) {
192 net::IPAddress ip_address;
193 EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv6PublicAddrString1));
194 network_interface_list_->push_back(net::NetworkInterface(
195 "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
196 ip_address, 64, net::IP_ADDRESS_ATTRIBUTE_NONE));
197
198 std::unique_ptr<NetworkChangeNotifierApple> notifier =
199 CreateNetworkChangeNotifierApple();
200
201 // Simulate OnNetworkConfigChange callback with a public IPv6 address change.
202 EXPECT_TRUE((*network_interface_list_)[0].address.AssignFromIPLiteral(
203 kIPv6PublicAddrString2));
204 TestIPAddressObserver observer;
205 SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv6);
206 RunUntilIdle();
207 EXPECT_TRUE(observer.ip_address_changed());
208 }
209
TEST_P(NetworkChangeNotifierAppleTest,LinkLocalIPv6AddressChangeOnPrimaryInterface)210 TEST_P(NetworkChangeNotifierAppleTest,
211 LinkLocalIPv6AddressChangeOnPrimaryInterface) {
212 net::IPAddress ip_address;
213 EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv6LinkLocalAddrString1));
214 network_interface_list_->push_back(net::NetworkInterface(
215 "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
216 ip_address, 64, net::IP_ADDRESS_ATTRIBUTE_NONE));
217
218 std::unique_ptr<NetworkChangeNotifierApple> notifier =
219 CreateNetworkChangeNotifierApple();
220
221 // Simulate OnNetworkConfigChange callback with a link local IPv6 address
222 // change on the primary interface "en0".
223 EXPECT_TRUE((*network_interface_list_)[0].address.AssignFromIPLiteral(
224 kIPv6LinkLocalAddrString2));
225 TestIPAddressObserver observer;
226 SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
227 RunUntilIdle();
228 EXPECT_TRUE(observer.ip_address_changed());
229 }
230
TEST_P(NetworkChangeNotifierAppleTest,LinkLocalIPv6AddressChangeOnNonPrimaryInterface)231 TEST_P(NetworkChangeNotifierAppleTest,
232 LinkLocalIPv6AddressChangeOnNonPrimaryInterface) {
233 net::IPAddress ip_address1;
234 EXPECT_TRUE(ip_address1.AssignFromIPLiteral(kIPv4PrivateAddrString1));
235 network_interface_list_->push_back(net::NetworkInterface(
236 "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
237 ip_address1, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));
238
239 net::IPAddress ip_address2;
240 EXPECT_TRUE(ip_address2.AssignFromIPLiteral(kIPv6LinkLocalAddrString1));
241 network_interface_list_->push_back(net::NetworkInterface(
242 "en1", "en1", 2, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
243 ip_address2, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));
244
245 std::unique_ptr<NetworkChangeNotifierApple> notifier =
246 CreateNetworkChangeNotifierApple();
247
248 // Simulate OnNetworkConfigChange callback with a link local IPv6 address
249 // change on the non-primary interface "en1".
250 EXPECT_TRUE((*network_interface_list_)[1].address.AssignFromIPLiteral(
251 kIPv6LinkLocalAddrString2));
252 TestIPAddressObserver observer;
253 SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
254 RunUntilIdle();
255 // When kReduceIPAddressChangeNotification feature is enabled, we ignores
256 // the link local IPv6 address change on the non-primary interface.
257 EXPECT_EQ(observer.ip_address_changed(),
258 !ReduceIPAddressChangeNotificationEnabled());
259 }
260
TEST_P(NetworkChangeNotifierAppleTest,NewInterfaceWithIpV4)261 TEST_P(NetworkChangeNotifierAppleTest, NewInterfaceWithIpV4) {
262 net::IPAddress ip_address;
263 EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv4PrivateAddrString1));
264 network_interface_list_->push_back(net::NetworkInterface(
265 "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
266 ip_address, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));
267
268 std::unique_ptr<NetworkChangeNotifierApple> notifier =
269 CreateNetworkChangeNotifierApple();
270
271 // Simulate OnNetworkConfigChange callback with a new interface with a IPv4
272 // address.
273 net::IPAddress ip_address2;
274 EXPECT_TRUE(ip_address2.AssignFromIPLiteral(kIPv4PrivateAddrString2));
275 network_interface_list_->push_back(net::NetworkInterface(
276 "en1", "en1", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
277 ip_address2, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));
278
279 TestIPAddressObserver observer;
280 SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
281 RunUntilIdle();
282 EXPECT_TRUE(observer.ip_address_changed());
283 }
284
TEST_P(NetworkChangeNotifierAppleTest,NewInterfaceWithLinkLocalIpV6)285 TEST_P(NetworkChangeNotifierAppleTest, NewInterfaceWithLinkLocalIpV6) {
286 net::IPAddress ip_address;
287 EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv4PrivateAddrString1));
288 network_interface_list_->push_back(net::NetworkInterface(
289 "en0", "en0", 2, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
290 ip_address, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));
291
292 std::unique_ptr<NetworkChangeNotifierApple> notifier =
293 CreateNetworkChangeNotifierApple();
294
295 // Simulate OnNetworkConfigChange callback with a new interface with a link
296 // local IPv6 address.
297 net::IPAddress ip_address2;
298 EXPECT_TRUE(ip_address2.AssignFromIPLiteral(kIPv6LinkLocalAddrString1));
299 EXPECT_FALSE(ip_address2.IsPubliclyRoutable());
300 network_interface_list_->push_back(net::NetworkInterface(
301 "en1", "en1", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
302 ip_address2, 64, net::IP_ADDRESS_ATTRIBUTE_NONE));
303
304 TestIPAddressObserver observer;
305 SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
306 RunUntilIdle();
307 // When kReduceIPAddressChangeNotification feature is enabled, we ignores
308 // the new link local IPv6 interface.
309 EXPECT_EQ(observer.ip_address_changed(),
310 !ReduceIPAddressChangeNotificationEnabled());
311 }
312
TEST_P(NetworkChangeNotifierAppleTest,NewInterfaceWithPublicIpV6)313 TEST_P(NetworkChangeNotifierAppleTest, NewInterfaceWithPublicIpV6) {
314 net::IPAddress ip_address;
315 EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv4PrivateAddrString1));
316 network_interface_list_->push_back(net::NetworkInterface(
317 "en0", "en0", 2, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
318 ip_address, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));
319
320 std::unique_ptr<NetworkChangeNotifierApple> notifier =
321 CreateNetworkChangeNotifierApple();
322
323 // Simulate OnNetworkConfigChange callback with a new interface with a
324 // public IPv6 address.
325 net::IPAddress ip_address2;
326 EXPECT_TRUE(ip_address2.AssignFromIPLiteral(kIPv6PublicAddrString1));
327 EXPECT_TRUE(ip_address2.IsPubliclyRoutable());
328 network_interface_list_->push_back(net::NetworkInterface(
329 "en1", "en1", 2, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
330 ip_address2, 64, net::IP_ADDRESS_ATTRIBUTE_NONE));
331
332 TestIPAddressObserver observer;
333 SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
334 RunUntilIdle();
335 EXPECT_TRUE(observer.ip_address_changed());
336 }
337
TEST_P(NetworkChangeNotifierAppleTest,IPv4PrimaryInterfaceChange)338 TEST_P(NetworkChangeNotifierAppleTest, IPv4PrimaryInterfaceChange) {
339 net::IPAddress ip_address;
340 EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv4PrivateAddrString1));
341 network_interface_list_->push_back(net::NetworkInterface(
342 "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
343 ip_address, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));
344 net::IPAddress ip_address2;
345 EXPECT_TRUE(ip_address2.AssignFromIPLiteral(kIPv4PrivateAddrString2));
346 network_interface_list_->push_back(net::NetworkInterface(
347 "en1", "en1", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
348 ip_address2, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));
349
350 std::unique_ptr<NetworkChangeNotifierApple> notifier =
351 CreateNetworkChangeNotifierApple();
352
353 // Simulate OnNetworkConfigChange callback for the IPv4 primary interface
354 // change.
355 TestIPAddressObserver observer;
356 ipv4_primary_interface_name_ = "en1";
357 SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv4);
358 RunUntilIdle();
359 EXPECT_TRUE(observer.ip_address_changed());
360 }
361
TEST_P(NetworkChangeNotifierAppleTest,IPv6PrimaryInterfaceChange)362 TEST_P(NetworkChangeNotifierAppleTest, IPv6PrimaryInterfaceChange) {
363 net::IPAddress ip_address;
364 EXPECT_TRUE(ip_address.AssignFromIPLiteral(kIPv6PublicAddrString1));
365 network_interface_list_->push_back(net::NetworkInterface(
366 "en0", "en0", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
367 ip_address, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));
368 net::IPAddress ip_address2;
369 EXPECT_TRUE(ip_address2.AssignFromIPLiteral(kIPv6PublicAddrString2));
370 network_interface_list_->push_back(net::NetworkInterface(
371 "en1", "en1", 1, net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
372 ip_address2, 0, net::IP_ADDRESS_ATTRIBUTE_NONE));
373
374 std::unique_ptr<NetworkChangeNotifierApple> notifier =
375 CreateNetworkChangeNotifierApple();
376
377 // Simulate OnNetworkConfigChange callback for the IPv6 primary interface
378 // change.
379 TestIPAddressObserver observer;
380 ipv6_primary_interface_name_ = "en1";
381 SimulateDynamicStoreCallback(*notifier, kSCEntNetIPv6);
382 RunUntilIdle();
383 EXPECT_TRUE(observer.ip_address_changed());
384 }
385
386 } // namespace net
387