• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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