xref: /aosp_15_r20/external/cronet/net/base/network_config_watcher_apple.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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_config_watcher_apple.h"
6 
7 #include <algorithm>
8 
9 #include "base/compiler_specific.h"
10 #include "base/functional/bind.h"
11 #include "base/logging.h"
12 #include "base/memory/raw_ptr.h"
13 #include "base/memory/weak_ptr.h"
14 #include "base/message_loop/message_pump_type.h"
15 #include "base/metrics/histogram_macros.h"
16 #include "base/task/single_thread_task_runner.h"
17 #include "base/threading/thread.h"
18 #include "base/threading/thread_restrictions.h"
19 #include "build/build_config.h"
20 
21 namespace net {
22 
23 namespace {
24 
25 // SCDynamicStore API does not exist on iOS.
26 #if !BUILDFLAG(IS_IOS)
27 const base::TimeDelta kRetryInterval = base::Seconds(1);
28 const int kMaxRetry = 5;
29 
30 // Called back by OS.  Calls OnNetworkConfigChange().
DynamicStoreCallback(SCDynamicStoreRef,CFArrayRef changed_keys,void * config_delegate)31 void DynamicStoreCallback(SCDynamicStoreRef /* store */,
32                           CFArrayRef changed_keys,
33                           void* config_delegate) {
34   NetworkConfigWatcherApple::Delegate* net_config_delegate =
35       static_cast<NetworkConfigWatcherApple::Delegate*>(config_delegate);
36   net_config_delegate->OnNetworkConfigChange(changed_keys);
37 }
38 #endif  // !BUILDFLAG(IS_IOS)
39 
40 }  // namespace
41 
42 class NetworkConfigWatcherAppleThread : public base::Thread {
43  public:
44   explicit NetworkConfigWatcherAppleThread(
45       NetworkConfigWatcherApple::Delegate* delegate);
46   NetworkConfigWatcherAppleThread(const NetworkConfigWatcherAppleThread&) = delete;
47   NetworkConfigWatcherAppleThread& operator=(
48       const NetworkConfigWatcherAppleThread&) = delete;
49   ~NetworkConfigWatcherAppleThread() override;
50 
51  protected:
52   // base::Thread
53   void Init() override;
54   void CleanUp() override;
55 
56  private:
57   // The SystemConfiguration calls in this function can lead to contention early
58   // on, so we invoke this function later on in startup to keep it fast.
59   void InitNotifications();
60 
61   // Returns whether initializing notifications has succeeded.
62   bool InitNotificationsHelper();
63 
64   base::apple::ScopedCFTypeRef<CFRunLoopSourceRef> run_loop_source_;
65   const raw_ptr<NetworkConfigWatcherApple::Delegate> delegate_;
66 #if !BUILDFLAG(IS_IOS)
67   int num_retry_ = 0;
68 #endif  // !BUILDFLAG(IS_IOS)
69   base::WeakPtrFactory<NetworkConfigWatcherAppleThread> weak_factory_;
70 };
71 
NetworkConfigWatcherAppleThread(NetworkConfigWatcherApple::Delegate * delegate)72 NetworkConfigWatcherAppleThread::NetworkConfigWatcherAppleThread(
73     NetworkConfigWatcherApple::Delegate* delegate)
74     : base::Thread("NetworkConfigWatcher"),
75       delegate_(delegate),
76       weak_factory_(this) {}
77 
~NetworkConfigWatcherAppleThread()78 NetworkConfigWatcherAppleThread::~NetworkConfigWatcherAppleThread() {
79   // This is expected to be invoked during shutdown.
80   base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_thread_join;
81   Stop();
82 }
83 
Init()84 void NetworkConfigWatcherAppleThread::Init() {
85   delegate_->Init();
86 
87   // TODO(willchan): Look to see if there's a better signal for when it's ok to
88   // initialize this, rather than just delaying it by a fixed time.
89   const base::TimeDelta kInitializationDelay = base::Seconds(1);
90   task_runner()->PostDelayedTask(
91       FROM_HERE,
92       base::BindOnce(&NetworkConfigWatcherAppleThread::InitNotifications,
93                      weak_factory_.GetWeakPtr()),
94       kInitializationDelay);
95 }
96 
CleanUp()97 void NetworkConfigWatcherAppleThread::CleanUp() {
98   if (!run_loop_source_.get())
99     return;
100   delegate_->CleanUpOnNotifierThread();
101 
102   CFRunLoopRemoveSource(CFRunLoopGetCurrent(), run_loop_source_.get(),
103                         kCFRunLoopCommonModes);
104   run_loop_source_.reset();
105 }
106 
InitNotifications()107 void NetworkConfigWatcherAppleThread::InitNotifications() {
108   // If initialization fails, retry after a 1s delay.
109   bool success = InitNotificationsHelper();
110 
111 #if !BUILDFLAG(IS_IOS)
112   if (!success && num_retry_ < kMaxRetry) {
113     LOG(ERROR) << "Retrying SystemConfiguration registration in 1 second.";
114     task_runner()->PostDelayedTask(
115         FROM_HERE,
116         base::BindOnce(&NetworkConfigWatcherAppleThread::InitNotifications,
117                        weak_factory_.GetWeakPtr()),
118         kRetryInterval);
119     num_retry_++;
120     return;
121   }
122 
123 #else
124   DCHECK(success);
125 #endif  // !BUILDFLAG(IS_IOS)
126 }
127 
InitNotificationsHelper()128 bool NetworkConfigWatcherAppleThread::InitNotificationsHelper() {
129 #if !BUILDFLAG(IS_IOS)
130   // SCDynamicStore API does not exist on iOS.
131   // Add a run loop source for a dynamic store to the current run loop.
132   SCDynamicStoreContext context = {
133       0,          // Version 0.
134       delegate_,  // User data.
135       nullptr,    // This is not reference counted.  No retain function.
136       nullptr,    // This is not reference counted.  No release function.
137       nullptr,    // No description for this.
138   };
139   base::apple::ScopedCFTypeRef<SCDynamicStoreRef> store(SCDynamicStoreCreate(
140       nullptr, CFSTR("org.chromium"), DynamicStoreCallback, &context));
141   if (!store) {
142     int error = SCError();
143     LOG(ERROR) << "SCDynamicStoreCreate failed with Error: " << error << " - "
144                << SCErrorString(error);
145     return false;
146   }
147   run_loop_source_.reset(
148       SCDynamicStoreCreateRunLoopSource(nullptr, store.get(), 0));
149   if (!run_loop_source_) {
150     int error = SCError();
151     LOG(ERROR) << "SCDynamicStoreCreateRunLoopSource failed with Error: "
152                << error << " - " << SCErrorString(error);
153     return false;
154   }
155   CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source_.get(),
156                      kCFRunLoopCommonModes);
157 #endif  // !BUILDFLAG(IS_IOS)
158 
159   // Set up notifications for interface and IP address changes.
160   delegate_->StartReachabilityNotifications();
161 #if !BUILDFLAG(IS_IOS)
162   delegate_->SetDynamicStoreNotificationKeys(std::move(store));
163 #endif  // !BUILDFLAG(IS_IOS)
164   return true;
165 }
166 
NetworkConfigWatcherApple(Delegate * delegate)167 NetworkConfigWatcherApple::NetworkConfigWatcherApple(Delegate* delegate)
168     : notifier_thread_(
169           std::make_unique<NetworkConfigWatcherAppleThread>(delegate)) {
170   // We create this notifier thread because the notification implementation
171   // needs a thread with a CFRunLoop, and there's no guarantee that
172   // CurrentThread::Get() meets that criterion.
173   base::Thread::Options thread_options(base::MessagePumpType::UI, 0);
174   notifier_thread_->StartWithOptions(std::move(thread_options));
175 }
176 
177 NetworkConfigWatcherApple::~NetworkConfigWatcherApple() = default;
178 
GetNotifierThreadForTest()179 base::Thread* NetworkConfigWatcherApple::GetNotifierThreadForTest() {
180   return notifier_thread_.get();
181 }
182 
183 }  // namespace net
184