1// Copyright 2012 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 <netinet/in.h> 8#include <resolv.h> 9 10#include "base/apple/bridging.h" 11#include "base/apple/foundation_util.h" 12#include "base/feature_list.h" 13#include "base/functional/bind.h" 14#include "base/functional/callback.h" 15#include "base/logging.h" 16#include "base/memory/scoped_policy.h" 17#include "base/metrics/histogram_macros.h" 18#include "base/strings/string_number_conversions.h" 19#include "base/strings/sys_string_conversions.h" 20#include "base/task/sequenced_task_runner.h" 21#include "base/task/task_traits.h" 22#include "base/threading/thread_restrictions.h" 23#include "build/build_config.h" 24#include "net/base/features.h" 25#include "net/base/network_interfaces_getifaddrs.h" 26#include "net/dns/dns_config_service.h" 27#include "net/log/net_log.h" 28 29#if BUILDFLAG(IS_IOS) 30#import <CoreTelephony/CTTelephonyNetworkInfo.h> 31#endif 32 33namespace net { 34namespace { 35// The maximum number of seconds to wait for the connection type to be 36// determined. 37const double kMaxWaitForConnectionTypeInSeconds = 2.0; 38 39#if BUILDFLAG(IS_MAC) 40std::string GetPrimaryInterfaceName(SCDynamicStoreRef store, 41 CFStringRef entity) { 42 base::apple::ScopedCFTypeRef<CFStringRef> netkey( 43 SCDynamicStoreKeyCreateNetworkGlobalEntity( 44 nullptr, kSCDynamicStoreDomainState, entity)); 45 base::apple::ScopedCFTypeRef<CFPropertyListRef> netdict_value( 46 SCDynamicStoreCopyValue(store, netkey.get())); 47 CFDictionaryRef netdict = 48 base::apple::CFCast<CFDictionaryRef>(netdict_value.get()); 49 if (!netdict) { 50 return ""; 51 } 52 CFStringRef primary_if_name_ref = 53 base::apple::GetValueFromDictionary<CFStringRef>( 54 netdict, kSCDynamicStorePropNetPrimaryInterface); 55 if (!primary_if_name_ref) { 56 return ""; 57 } 58 return base::SysCFStringRefToUTF8(primary_if_name_ref); 59} 60 61std::string GetIPv4PrimaryInterfaceName(SCDynamicStoreRef store) { 62 return GetPrimaryInterfaceName(store, kSCEntNetIPv4); 63} 64 65std::string GetIPv6PrimaryInterfaceName(SCDynamicStoreRef store) { 66 return GetPrimaryInterfaceName(store, kSCEntNetIPv6); 67} 68 69std::optional<net::NetworkInterfaceList> 70GetNetworkInterfaceListForNetworkChangeCheck( 71 base::RepeatingCallback<bool(net::NetworkInterfaceList*, int)> 72 get_network_list_callback, 73 const std::string& ipv6_primary_interface_name) { 74 net::NetworkInterfaceList interfaces; 75 if (!get_network_list_callback.Run( 76 &interfaces, net::EXCLUDE_HOST_SCOPE_VIRTUAL_INTERFACES)) { 77 return std::nullopt; 78 } 79 std::erase_if(interfaces, [&ipv6_primary_interface_name]( 80 const net::NetworkInterface& interface) { 81 return interface.address.IsIPv6() && 82 !interface.address.IsPubliclyRoutable() && 83 (interface.name != ipv6_primary_interface_name); 84 }); 85 return interfaces; 86} 87 88base::Value::Dict GetNetworkInterfaceValueDict( 89 const NetworkInterface& interface) { 90 base::Value::Dict dict; 91 dict.Set("name", interface.name); 92 dict.Set("friendly_name", interface.friendly_name); 93 dict.Set("interface_index", static_cast<int>(interface.interface_index)); 94 dict.Set("type", static_cast<int>(interface.type)); 95 dict.Set("address", interface.address.ToString()); 96 dict.Set("prefix_length", static_cast<int>(interface.prefix_length)); 97 dict.Set("ip_address_attributes", interface.ip_address_attributes); 98 if (interface.mac_address) { 99 dict.Set("mac_address", base::HexEncode(*interface.mac_address)); 100 } 101 return dict; 102} 103 104base::Value::List GetNetworkInterfacesValueList( 105 const NetworkInterfaceList& interfaces) { 106 base::Value::List list; 107 for (const NetworkInterface& interface : interfaces) { 108 list.Append(GetNetworkInterfaceValueDict(interface)); 109 } 110 return list; 111} 112 113base::Value::Dict NetLogOsConfigChangedParams( 114 const std::string& result, 115 bool net_ipv4_key_found, 116 bool net_ipv6_key_found, 117 bool net_interface_key_found, 118 bool reduce_ip_address_change_notification, 119 const std::string& old_ipv4_primary_interface_name, 120 const std::string& old_ipv6_primary_interface_name, 121 const std::string& new_ipv4_primary_interface_name, 122 const std::string& new_ipv6_primary_interface_name, 123 const std::optional<NetworkInterfaceList>& old_interfaces, 124 const std::optional<NetworkInterfaceList>& new_interfaces) { 125 base::Value::Dict dict; 126 dict.Set("result", result); 127 dict.Set("net_ipv4_key", net_ipv4_key_found); 128 dict.Set("net_ipv6_key", net_ipv6_key_found); 129 dict.Set("net_interface_key", net_interface_key_found); 130 dict.Set("reduce_notification", reduce_ip_address_change_notification); 131 dict.Set("old_ipv4_interface", old_ipv4_primary_interface_name); 132 dict.Set("old_ipv6_interface", old_ipv6_primary_interface_name); 133 dict.Set("new_ipv4_interface", new_ipv4_primary_interface_name); 134 dict.Set("new_ipv6_interface", new_ipv6_primary_interface_name); 135 if (old_interfaces) { 136 dict.Set("old_interfaces", GetNetworkInterfacesValueList(*old_interfaces)); 137 } 138 if (new_interfaces) { 139 dict.Set("new_interfaces", GetNetworkInterfacesValueList(*new_interfaces)); 140 } 141 return dict; 142} 143 144#endif // BUILDFLAG(IS_MAC) 145 146} // namespace 147 148static bool CalculateReachability(SCNetworkConnectionFlags flags) { 149 bool reachable = flags & kSCNetworkFlagsReachable; 150 bool connection_required = flags & kSCNetworkFlagsConnectionRequired; 151 return reachable && !connection_required; 152} 153 154NetworkChangeNotifierApple::NetworkChangeNotifierApple() 155 : NetworkChangeNotifier(NetworkChangeCalculatorParamsMac()), 156 initial_connection_type_cv_(&connection_type_lock_), 157 forwarder_(this), 158#if BUILDFLAG(IS_MAC) 159 reduce_ip_address_change_notification_(base::FeatureList::IsEnabled( 160 features::kReduceIPAddressChangeNotification)), 161 get_network_list_callback_(base::BindRepeating(&GetNetworkList)), 162 get_ipv4_primary_interface_name_callback_( 163 base::BindRepeating(&GetIPv4PrimaryInterfaceName)), 164 get_ipv6_primary_interface_name_callback_( 165 base::BindRepeating(&GetIPv6PrimaryInterfaceName)), 166#endif // BUILDFLAG(IS_MAC) 167 net_log_(net::NetLogWithSource::Make( 168 net::NetLog::Get(), 169 net::NetLogSourceType::NETWORK_CHANGE_NOTIFIER)) { 170 // Must be initialized after the rest of this object, as it may call back into 171 // SetInitialConnectionType(). 172 config_watcher_ = std::make_unique<NetworkConfigWatcherApple>(&forwarder_); 173} 174 175NetworkChangeNotifierApple::~NetworkChangeNotifierApple() { 176 ClearGlobalPointer(); 177 // Delete the ConfigWatcher to join the notifier thread, ensuring that 178 // StartReachabilityNotifications() has an opportunity to run to completion. 179 config_watcher_.reset(); 180 181 // Now that StartReachabilityNotifications() has either run to completion or 182 // never run at all, unschedule reachability_ if it was previously scheduled. 183 if (reachability_.get() && run_loop_.get()) { 184 SCNetworkReachabilityUnscheduleFromRunLoop( 185 reachability_.get(), run_loop_.get(), kCFRunLoopCommonModes); 186 } 187} 188 189// static 190NetworkChangeNotifier::NetworkChangeCalculatorParams 191NetworkChangeNotifierApple::NetworkChangeCalculatorParamsMac() { 192 NetworkChangeCalculatorParams params; 193 // Delay values arrived at by simple experimentation and adjusted so as to 194 // produce a single signal when switching between network connections. 195 params.ip_address_offline_delay_ = base::Milliseconds(500); 196 params.ip_address_online_delay_ = base::Milliseconds(500); 197 params.connection_type_offline_delay_ = base::Milliseconds(1000); 198 params.connection_type_online_delay_ = base::Milliseconds(500); 199 return params; 200} 201 202NetworkChangeNotifier::ConnectionType 203NetworkChangeNotifierApple::GetCurrentConnectionType() const { 204 // https://crbug.com/125097 205 base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_wait; 206 base::AutoLock lock(connection_type_lock_); 207 208 if (connection_type_initialized_) 209 return connection_type_; 210 211 // Wait up to a limited amount of time for the connection type to be 212 // determined, to avoid blocking the main thread indefinitely. Since 213 // ConditionVariables are susceptible to spurious wake-ups, each call to 214 // TimedWait can spuriously return even though the connection type hasn't been 215 // initialized and the timeout hasn't been reached; so TimedWait must be 216 // called repeatedly until either the timeout is reached or the connection 217 // type has been determined. 218 base::TimeDelta remaining_time = 219 base::Seconds(kMaxWaitForConnectionTypeInSeconds); 220 base::TimeTicks end_time = base::TimeTicks::Now() + remaining_time; 221 while (remaining_time.is_positive()) { 222 initial_connection_type_cv_.TimedWait(remaining_time); 223 if (connection_type_initialized_) 224 return connection_type_; 225 226 remaining_time = end_time - base::TimeTicks::Now(); 227 } 228 229 return CONNECTION_UNKNOWN; 230} 231 232void NetworkChangeNotifierApple::Forwarder::Init() { 233 net_config_watcher_->SetInitialConnectionType(); 234} 235 236// static 237NetworkChangeNotifier::ConnectionType 238NetworkChangeNotifierApple::CalculateConnectionType( 239 SCNetworkConnectionFlags flags) { 240 bool reachable = CalculateReachability(flags); 241 if (!reachable) 242 return CONNECTION_NONE; 243 244#if BUILDFLAG(IS_IOS) 245 if (!(flags & kSCNetworkReachabilityFlagsIsWWAN)) { 246 return CONNECTION_WIFI; 247 } 248 if (@available(iOS 12, *)) { 249 CTTelephonyNetworkInfo* info = [[CTTelephonyNetworkInfo alloc] init]; 250 NSDictionary<NSString*, NSString*>* 251 service_current_radio_access_technology = 252 info.serviceCurrentRadioAccessTechnology; 253 NSSet<NSString*>* technologies_2g = [NSSet 254 setWithObjects:CTRadioAccessTechnologyGPRS, CTRadioAccessTechnologyEdge, 255 CTRadioAccessTechnologyCDMA1x, nil]; 256 NSSet<NSString*>* technologies_3g = 257 [NSSet setWithObjects:CTRadioAccessTechnologyWCDMA, 258 CTRadioAccessTechnologyHSDPA, 259 CTRadioAccessTechnologyHSUPA, 260 CTRadioAccessTechnologyCDMAEVDORev0, 261 CTRadioAccessTechnologyCDMAEVDORevA, 262 CTRadioAccessTechnologyCDMAEVDORevB, 263 CTRadioAccessTechnologyeHRPD, nil]; 264 NSSet<NSString*>* technologies_4g = 265 [NSSet setWithObjects:CTRadioAccessTechnologyLTE, nil]; 266 // TODO: Use constants from CoreTelephony once Cronet builds with XCode 12.1 267 NSSet<NSString*>* technologies_5g = 268 [NSSet setWithObjects:@"CTRadioAccessTechnologyNRNSA", 269 @"CTRadioAccessTechnologyNR", nil]; 270 int best_network = 0; 271 for (NSString* service in service_current_radio_access_technology) { 272 if (!service_current_radio_access_technology[service]) { 273 continue; 274 } 275 int current_network = 0; 276 277 NSString* network_type = service_current_radio_access_technology[service]; 278 279 if ([technologies_2g containsObject:network_type]) { 280 current_network = 2; 281 } else if ([technologies_3g containsObject:network_type]) { 282 current_network = 3; 283 } else if ([technologies_4g containsObject:network_type]) { 284 current_network = 4; 285 } else if ([technologies_5g containsObject:network_type]) { 286 current_network = 5; 287 } else { 288 // New technology? 289 NOTREACHED() << "Unknown network technology: " << network_type; 290 } 291 if (current_network > best_network) { 292 // iOS is supposed to use the best network available. 293 best_network = current_network; 294 } 295 } 296 switch (best_network) { 297 case 2: 298 return CONNECTION_2G; 299 case 3: 300 return CONNECTION_3G; 301 case 4: 302 return CONNECTION_4G; 303 case 5: 304 return CONNECTION_5G; 305 default: 306 // Default to CONNECTION_3G to not change existing behavior. 307 return CONNECTION_3G; 308 } 309 } else { 310 return CONNECTION_3G; 311 } 312 313#else 314 return ConnectionTypeFromInterfaces(); 315#endif 316} 317 318void NetworkChangeNotifierApple::Forwarder::StartReachabilityNotifications() { 319 net_config_watcher_->StartReachabilityNotifications(); 320} 321 322void NetworkChangeNotifierApple::Forwarder::SetDynamicStoreNotificationKeys( 323 base::apple::ScopedCFTypeRef<SCDynamicStoreRef> store) { 324 net_config_watcher_->SetDynamicStoreNotificationKeys(std::move(store)); 325} 326 327void NetworkChangeNotifierApple::Forwarder::OnNetworkConfigChange( 328 CFArrayRef changed_keys) { 329 net_config_watcher_->OnNetworkConfigChange(changed_keys); 330} 331 332void NetworkChangeNotifierApple::Forwarder::CleanUpOnNotifierThread() { 333 net_config_watcher_->CleanUpOnNotifierThread(); 334} 335 336void NetworkChangeNotifierApple::SetInitialConnectionType() { 337 // Called on notifier thread. 338 339 // Try to reach 0.0.0.0. This is the approach taken by Firefox: 340 // 341 // http://mxr.mozilla.org/mozilla2.0/source/netwerk/system/mac/nsNetworkLinkService.mm 342 // 343 // From my (adamk) testing on Snow Leopard, 0.0.0.0 344 // seems to be reachable if any network connection is available. 345 struct sockaddr_in addr = {0}; 346 addr.sin_len = sizeof(addr); 347 addr.sin_family = AF_INET; 348 reachability_.reset(SCNetworkReachabilityCreateWithAddress( 349 kCFAllocatorDefault, reinterpret_cast<struct sockaddr*>(&addr))); 350 351 SCNetworkConnectionFlags flags; 352 ConnectionType connection_type = CONNECTION_UNKNOWN; 353 if (SCNetworkReachabilityGetFlags(reachability_.get(), &flags)) { 354 connection_type = CalculateConnectionType(flags); 355 } else { 356 LOG(ERROR) << "Could not get initial network connection type," 357 << "assuming online."; 358 } 359 { 360 base::AutoLock lock(connection_type_lock_); 361 connection_type_ = connection_type; 362 connection_type_initialized_ = true; 363 initial_connection_type_cv_.Broadcast(); 364 } 365} 366 367void NetworkChangeNotifierApple::StartReachabilityNotifications() { 368 // Called on notifier thread. 369 run_loop_.reset(CFRunLoopGetCurrent(), base::scoped_policy::RETAIN); 370 371 DCHECK(reachability_); 372 SCNetworkReachabilityContext reachability_context = { 373 0, // version 374 this, // user data 375 nullptr, // retain 376 nullptr, // release 377 nullptr // description 378 }; 379 if (!SCNetworkReachabilitySetCallback( 380 reachability_.get(), &NetworkChangeNotifierApple::ReachabilityCallback, 381 &reachability_context)) { 382 LOG(DFATAL) << "Could not set network reachability callback"; 383 reachability_.reset(); 384 } else if (!SCNetworkReachabilityScheduleWithRunLoop( 385 reachability_.get(), run_loop_.get(), kCFRunLoopCommonModes)) { 386 LOG(DFATAL) << "Could not schedule network reachability on run loop"; 387 reachability_.reset(); 388 } 389} 390 391void NetworkChangeNotifierApple::SetDynamicStoreNotificationKeys( 392 base::apple::ScopedCFTypeRef<SCDynamicStoreRef> store) { 393#if BUILDFLAG(IS_IOS) 394 // SCDynamicStore API does not exist on iOS. 395 NOTREACHED(); 396#elif BUILDFLAG(IS_MAC) 397 NSArray* notification_keys = @[ 398 base::apple::CFToNSOwnershipCast(SCDynamicStoreKeyCreateNetworkGlobalEntity( 399 nullptr, kSCDynamicStoreDomainState, kSCEntNetInterface)), 400 base::apple::CFToNSOwnershipCast(SCDynamicStoreKeyCreateNetworkGlobalEntity( 401 nullptr, kSCDynamicStoreDomainState, kSCEntNetIPv4)), 402 base::apple::CFToNSOwnershipCast(SCDynamicStoreKeyCreateNetworkGlobalEntity( 403 nullptr, kSCDynamicStoreDomainState, kSCEntNetIPv6)), 404 ]; 405 406 // Set the notification keys. This starts us receiving notifications. 407 bool ret = SCDynamicStoreSetNotificationKeys( 408 store.get(), base::apple::NSToCFPtrCast(notification_keys), 409 /*patterns=*/nullptr); 410 // TODO(willchan): Figure out a proper way to handle this rather than crash. 411 CHECK(ret); 412 413 if (reduce_ip_address_change_notification_) { 414 store_ = std::move(store); 415 ipv4_primary_interface_name_ = 416 get_ipv4_primary_interface_name_callback_.Run(store_.get()); 417 ipv6_primary_interface_name_ = 418 get_ipv6_primary_interface_name_callback_.Run(store_.get()); 419 interfaces_for_network_change_check_ = 420 GetNetworkInterfaceListForNetworkChangeCheck( 421 get_network_list_callback_, ipv6_primary_interface_name_); 422 } 423 if (initialized_callback_for_test_) { 424 std::move(initialized_callback_for_test_).Run(); 425 } 426#endif // BUILDFLAG(IS_IOS) / BUILDFLAG(IS_MAC) 427} 428 429void NetworkChangeNotifierApple::OnNetworkConfigChange(CFArrayRef changed_keys) { 430#if BUILDFLAG(IS_IOS) 431 // SCDynamicStore API does not exist on iOS. 432 NOTREACHED(); 433#elif BUILDFLAG(IS_MAC) 434 DCHECK_EQ(run_loop_.get(), CFRunLoopGetCurrent()); 435 436 bool net_ipv4_key_found = false; 437 bool net_ipv6_key_found = false; 438 bool net_interface_key_found = false; 439 for (CFIndex i = 0; i < CFArrayGetCount(changed_keys); ++i) { 440 CFStringRef key = 441 static_cast<CFStringRef>(CFArrayGetValueAtIndex(changed_keys, i)); 442 if (CFStringHasSuffix(key, kSCEntNetIPv4)) { 443 net_ipv4_key_found = true; 444 } 445 if (CFStringHasSuffix(key, kSCEntNetIPv6)) { 446 net_ipv6_key_found = true; 447 } 448 if (net_ipv4_key_found || net_ipv6_key_found) { 449 break; 450 } 451 if (CFStringHasSuffix(key, kSCEntNetInterface)) { 452 net_interface_key_found = true; 453 // TODO(willchan): Does not appear to be working. Look into this. 454 // Perhaps this isn't needed anyway. 455 } else { 456 NOTREACHED(); 457 } 458 } 459 if (!(net_ipv4_key_found || net_ipv6_key_found)) { 460 net_log_.AddEvent(net::NetLogEventType::NETWORK_MAC_OS_CONFIG_CHANGED, [&] { 461 return NetLogOsConfigChangedParams( 462 "DoNotNotify_NoIPAddressChange", net_ipv4_key_found, 463 net_ipv6_key_found, net_interface_key_found, 464 reduce_ip_address_change_notification_, ipv4_primary_interface_name_, 465 ipv6_primary_interface_name_, "", "", 466 interfaces_for_network_change_check_, std::nullopt); 467 }); 468 return; 469 } 470 if (!reduce_ip_address_change_notification_) { 471 net_log_.AddEvent(net::NetLogEventType::NETWORK_MAC_OS_CONFIG_CHANGED, [&] { 472 return NetLogOsConfigChangedParams( 473 "Notify_NoReduce", net_ipv4_key_found, net_ipv6_key_found, 474 net_interface_key_found, reduce_ip_address_change_notification_, 475 ipv4_primary_interface_name_, ipv6_primary_interface_name_, "", "", 476 interfaces_for_network_change_check_, std::nullopt); 477 }); 478 NotifyObserversOfIPAddressChange(); 479 return; 480 } 481 // When the ReduceIPAddressChangeNotification feature is enabled, we notifies 482 // the IP address change only when: 483 // - The list of network interfaces has changed, excluding local IPv6 484 // addresses of non-primary interfaces. 485 // - or the primary interface name (for IPv4 and IPv6) has changed. 486 std::string ipv4_primary_interface_name = 487 get_ipv4_primary_interface_name_callback_.Run(store_.get()); 488 std::string ipv6_primary_interface_name = 489 get_ipv6_primary_interface_name_callback_.Run(store_.get()); 490 std::optional<NetworkInterfaceList> interfaces = 491 GetNetworkInterfaceListForNetworkChangeCheck(get_network_list_callback_, 492 ipv6_primary_interface_name); 493 if (interfaces_for_network_change_check_ && interfaces && 494 interfaces_for_network_change_check_.value() == interfaces.value() && 495 ipv4_primary_interface_name_ == ipv4_primary_interface_name && 496 ipv6_primary_interface_name_ == ipv6_primary_interface_name) { 497 net_log_.AddEvent(net::NetLogEventType::NETWORK_MAC_OS_CONFIG_CHANGED, [&] { 498 return NetLogOsConfigChangedParams( 499 "DoNotNotify_NoChange", net_ipv4_key_found, net_ipv6_key_found, 500 net_interface_key_found, reduce_ip_address_change_notification_, 501 ipv4_primary_interface_name_, ipv6_primary_interface_name_, 502 ipv4_primary_interface_name, ipv6_primary_interface_name, 503 interfaces_for_network_change_check_, interfaces); 504 }); 505 return; 506 } 507 net_log_.AddEvent(net::NetLogEventType::NETWORK_MAC_OS_CONFIG_CHANGED, [&] { 508 return NetLogOsConfigChangedParams( 509 "Notify_Changed", net_ipv4_key_found, net_ipv6_key_found, 510 net_interface_key_found, reduce_ip_address_change_notification_, 511 ipv4_primary_interface_name_, ipv6_primary_interface_name_, 512 ipv4_primary_interface_name, ipv6_primary_interface_name, 513 interfaces_for_network_change_check_, interfaces); 514 }); 515 ipv4_primary_interface_name_ = std::move(ipv4_primary_interface_name); 516 ipv6_primary_interface_name_ = std::move(ipv6_primary_interface_name); 517 interfaces_for_network_change_check_ = std::move(interfaces); 518 NotifyObserversOfIPAddressChange(); 519#endif // BUILDFLAG(IS_IOS) 520} 521 522void NetworkChangeNotifierApple::CleanUpOnNotifierThread() { 523#if BUILDFLAG(IS_MAC) 524 store_.reset(); 525#endif // BUILDFLAG(IS_MAC) 526} 527 528// static 529void NetworkChangeNotifierApple::ReachabilityCallback( 530 SCNetworkReachabilityRef target, 531 SCNetworkConnectionFlags flags, 532 void* notifier) { 533 NetworkChangeNotifierApple* notifier_apple = 534 static_cast<NetworkChangeNotifierApple*>(notifier); 535 536 DCHECK_EQ(notifier_apple->run_loop_.get(), CFRunLoopGetCurrent()); 537 538 ConnectionType new_type = CalculateConnectionType(flags); 539 ConnectionType old_type; 540 { 541 base::AutoLock lock(notifier_apple->connection_type_lock_); 542 old_type = notifier_apple->connection_type_; 543 notifier_apple->connection_type_ = new_type; 544 } 545 if (old_type != new_type) { 546 NotifyObserversOfConnectionTypeChange(); 547 double max_bandwidth_mbps = 548 NetworkChangeNotifier::GetMaxBandwidthMbpsForConnectionSubtype( 549 new_type == CONNECTION_NONE ? SUBTYPE_NONE : SUBTYPE_UNKNOWN); 550 NotifyObserversOfMaxBandwidthChange(max_bandwidth_mbps, new_type); 551 } 552 553#if BUILDFLAG(IS_IOS) 554 // On iOS, the SCDynamicStore API does not exist, and we use the reachability 555 // API to detect IP address changes instead. 556 NotifyObserversOfIPAddressChange(); 557#endif // BUILDFLAG(IS_IOS) 558} 559 560#if BUILDFLAG(IS_MAC) 561void NetworkChangeNotifierApple::SetCallbacksForTest( 562 base::OnceClosure initialized_callback, 563 base::RepeatingCallback<bool(NetworkInterfaceList*, int)> 564 get_network_list_callback, 565 base::RepeatingCallback<std::string(SCDynamicStoreRef)> 566 get_ipv4_primary_interface_name_callback, 567 base::RepeatingCallback<std::string(SCDynamicStoreRef)> 568 get_ipv6_primary_interface_name_callback) { 569 initialized_callback_for_test_ = std::move(initialized_callback); 570 get_network_list_callback_ = std::move(get_network_list_callback); 571 get_ipv4_primary_interface_name_callback_ = 572 std::move(get_ipv4_primary_interface_name_callback); 573 get_ipv6_primary_interface_name_callback_ = 574 std::move(get_ipv6_primary_interface_name_callback); 575} 576#endif // BUILDFLAG(IS_MAC) 577 578} // namespace net 579