network_config_watcher_mac.cc 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "net/base/network_config_watcher_mac.h"
  5. #include <algorithm>
  6. #include "base/bind.h"
  7. #include "base/compiler_specific.h"
  8. #include "base/logging.h"
  9. #include "base/memory/raw_ptr.h"
  10. #include "base/memory/weak_ptr.h"
  11. #include "base/message_loop/message_pump_type.h"
  12. #include "base/metrics/histogram_macros.h"
  13. #include "base/task/single_thread_task_runner.h"
  14. #include "base/threading/thread.h"
  15. #include "base/threading/thread_restrictions.h"
  16. #include "build/build_config.h"
  17. namespace net {
  18. namespace {
  19. // SCDynamicStore API does not exist on iOS.
  20. #if !BUILDFLAG(IS_IOS)
  21. const base::TimeDelta kRetryInterval = base::Seconds(1);
  22. const int kMaxRetry = 5;
  23. // Maps SCError to an enum for UMA logging. These values are persisted to logs,
  24. // and should not be renumbered. Added to investigate https://crbug.com/547877.
  25. enum class SCStatusCode {
  26. // Unmapped error codes.
  27. SC_UNKNOWN = 0,
  28. // These map to the corresponding SCError.
  29. SC_OK = 1,
  30. SC_FAILED = 2,
  31. SC_INVALID_ARGUMENT = 3,
  32. SC_ACCESS_ERROR = 4,
  33. SC_NO_KEY = 5,
  34. SC_KEY_EXISTS = 6,
  35. SC_LOCKED = 7,
  36. SC_NEED_LOCK = 8,
  37. SC_NO_STORE_SESSION = 9,
  38. SC_NO_STORE_SERVER = 10,
  39. SC_NOTIFIER_ACTIVE = 11,
  40. SC_NO_PREFS_SESSION = 12,
  41. SC_PREFS_BUSY = 13,
  42. SC_NO_CONFIG_FILE = 14,
  43. SC_NO_LINK = 15,
  44. SC_STALE = 16,
  45. SC_MAX_LINK = 17,
  46. SC_REACHABILITY_UNKNOWN = 18,
  47. SC_CONNECTION_NO_SERVICE = 19,
  48. SC_CONNECTION_IGNORE = 20,
  49. // Maximum value for histogram bucket.
  50. SC_COUNT,
  51. };
  52. SCStatusCode ConvertToSCStatusCode(int sc_error) {
  53. switch (sc_error) {
  54. case kSCStatusOK:
  55. return SCStatusCode::SC_OK;
  56. case kSCStatusFailed:
  57. return SCStatusCode::SC_FAILED;
  58. case kSCStatusInvalidArgument:
  59. return SCStatusCode::SC_INVALID_ARGUMENT;
  60. case kSCStatusAccessError:
  61. return SCStatusCode::SC_ACCESS_ERROR;
  62. case kSCStatusNoKey:
  63. return SCStatusCode::SC_NO_KEY;
  64. case kSCStatusKeyExists:
  65. return SCStatusCode::SC_KEY_EXISTS;
  66. case kSCStatusLocked:
  67. return SCStatusCode::SC_LOCKED;
  68. case kSCStatusNeedLock:
  69. return SCStatusCode::SC_NEED_LOCK;
  70. case kSCStatusNoStoreSession:
  71. return SCStatusCode::SC_NO_STORE_SESSION;
  72. case kSCStatusNoStoreServer:
  73. return SCStatusCode::SC_NO_STORE_SERVER;
  74. case kSCStatusNotifierActive:
  75. return SCStatusCode::SC_NOTIFIER_ACTIVE;
  76. case kSCStatusNoPrefsSession:
  77. return SCStatusCode::SC_NO_PREFS_SESSION;
  78. case kSCStatusPrefsBusy:
  79. return SCStatusCode::SC_PREFS_BUSY;
  80. case kSCStatusNoConfigFile:
  81. return SCStatusCode::SC_NO_CONFIG_FILE;
  82. case kSCStatusNoLink:
  83. return SCStatusCode::SC_NO_LINK;
  84. case kSCStatusStale:
  85. return SCStatusCode::SC_STALE;
  86. case kSCStatusMaxLink:
  87. return SCStatusCode::SC_MAX_LINK;
  88. case kSCStatusReachabilityUnknown:
  89. return SCStatusCode::SC_REACHABILITY_UNKNOWN;
  90. case kSCStatusConnectionNoService:
  91. return SCStatusCode::SC_CONNECTION_NO_SERVICE;
  92. case kSCStatusConnectionIgnore:
  93. return SCStatusCode::SC_CONNECTION_IGNORE;
  94. default:
  95. return SCStatusCode::SC_UNKNOWN;
  96. }
  97. }
  98. // Called back by OS. Calls OnNetworkConfigChange().
  99. void DynamicStoreCallback(SCDynamicStoreRef /* store */,
  100. CFArrayRef changed_keys,
  101. void* config_delegate) {
  102. NetworkConfigWatcherMac::Delegate* net_config_delegate =
  103. static_cast<NetworkConfigWatcherMac::Delegate*>(config_delegate);
  104. net_config_delegate->OnNetworkConfigChange(changed_keys);
  105. }
  106. #endif // !BUILDFLAG(IS_IOS)
  107. } // namespace
  108. class NetworkConfigWatcherMacThread : public base::Thread {
  109. public:
  110. explicit NetworkConfigWatcherMacThread(
  111. NetworkConfigWatcherMac::Delegate* delegate);
  112. NetworkConfigWatcherMacThread(const NetworkConfigWatcherMacThread&) = delete;
  113. NetworkConfigWatcherMacThread& operator=(
  114. const NetworkConfigWatcherMacThread&) = delete;
  115. ~NetworkConfigWatcherMacThread() override;
  116. protected:
  117. // base::Thread
  118. void Init() override;
  119. void CleanUp() override;
  120. private:
  121. // The SystemConfiguration calls in this function can lead to contention early
  122. // on, so we invoke this function later on in startup to keep it fast.
  123. void InitNotifications();
  124. // Returns whether initializing notifications has succeeded.
  125. bool InitNotificationsHelper();
  126. base::ScopedCFTypeRef<CFRunLoopSourceRef> run_loop_source_;
  127. const raw_ptr<NetworkConfigWatcherMac::Delegate> delegate_;
  128. #if !BUILDFLAG(IS_IOS)
  129. int num_retry_ = 0;
  130. #endif // !BUILDFLAG(IS_IOS)
  131. base::WeakPtrFactory<NetworkConfigWatcherMacThread> weak_factory_;
  132. };
  133. NetworkConfigWatcherMacThread::NetworkConfigWatcherMacThread(
  134. NetworkConfigWatcherMac::Delegate* delegate)
  135. : base::Thread("NetworkConfigWatcher"),
  136. delegate_(delegate),
  137. weak_factory_(this) {}
  138. NetworkConfigWatcherMacThread::~NetworkConfigWatcherMacThread() {
  139. // This is expected to be invoked during shutdown.
  140. base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_thread_join;
  141. Stop();
  142. }
  143. void NetworkConfigWatcherMacThread::Init() {
  144. delegate_->Init();
  145. // TODO(willchan): Look to see if there's a better signal for when it's ok to
  146. // initialize this, rather than just delaying it by a fixed time.
  147. const base::TimeDelta kInitializationDelay = base::Seconds(1);
  148. task_runner()->PostDelayedTask(
  149. FROM_HERE,
  150. base::BindOnce(&NetworkConfigWatcherMacThread::InitNotifications,
  151. weak_factory_.GetWeakPtr()),
  152. kInitializationDelay);
  153. }
  154. void NetworkConfigWatcherMacThread::CleanUp() {
  155. if (!run_loop_source_.get())
  156. return;
  157. CFRunLoopRemoveSource(CFRunLoopGetCurrent(), run_loop_source_.get(),
  158. kCFRunLoopCommonModes);
  159. run_loop_source_.reset();
  160. }
  161. void NetworkConfigWatcherMacThread::InitNotifications() {
  162. // If initialization fails, retry after a 1s delay.
  163. bool success = InitNotificationsHelper();
  164. #if !BUILDFLAG(IS_IOS)
  165. if (!success && num_retry_ < kMaxRetry) {
  166. LOG(ERROR) << "Retrying SystemConfiguration registration in 1 second.";
  167. task_runner()->PostDelayedTask(
  168. FROM_HERE,
  169. base::BindOnce(&NetworkConfigWatcherMacThread::InitNotifications,
  170. weak_factory_.GetWeakPtr()),
  171. kRetryInterval);
  172. num_retry_++;
  173. return;
  174. }
  175. // There are kMaxRetry + 2 buckets. The 0 bucket is where no retry is
  176. // performed. The kMaxRetry + 1 bucket is where all retries have failed.
  177. int histogram_bucket = num_retry_;
  178. if (!success) {
  179. DCHECK_EQ(kMaxRetry, num_retry_);
  180. histogram_bucket = kMaxRetry + 1;
  181. }
  182. UMA_HISTOGRAM_EXACT_LINEAR(
  183. "Net.NetworkConfigWatcherMac.SCDynamicStore.NumRetry", histogram_bucket,
  184. kMaxRetry + 2);
  185. #else
  186. DCHECK(success);
  187. #endif // !BUILDFLAG(IS_IOS)
  188. }
  189. bool NetworkConfigWatcherMacThread::InitNotificationsHelper() {
  190. #if !BUILDFLAG(IS_IOS)
  191. // SCDynamicStore API does not exist on iOS.
  192. // Add a run loop source for a dynamic store to the current run loop.
  193. SCDynamicStoreContext context = {
  194. 0, // Version 0.
  195. delegate_, // User data.
  196. nullptr, // This is not reference counted. No retain function.
  197. nullptr, // This is not reference counted. No release function.
  198. nullptr, // No description for this.
  199. };
  200. base::ScopedCFTypeRef<SCDynamicStoreRef> store(SCDynamicStoreCreate(
  201. nullptr, CFSTR("org.chromium"), DynamicStoreCallback, &context));
  202. if (!store) {
  203. int error = SCError();
  204. LOG(ERROR) << "SCDynamicStoreCreate failed with Error: " << error << " - "
  205. << SCErrorString(error);
  206. UMA_HISTOGRAM_ENUMERATION(
  207. "Net.NetworkConfigWatcherMac.SCDynamicStore.Create",
  208. ConvertToSCStatusCode(error), SCStatusCode::SC_COUNT);
  209. return false;
  210. }
  211. run_loop_source_.reset(
  212. SCDynamicStoreCreateRunLoopSource(nullptr, store.get(), 0));
  213. if (!run_loop_source_) {
  214. int error = SCError();
  215. LOG(ERROR) << "SCDynamicStoreCreateRunLoopSource failed with Error: "
  216. << error << " - " << SCErrorString(error);
  217. UMA_HISTOGRAM_ENUMERATION(
  218. "Net.NetworkConfigWatcherMac.SCDynamicStore.Create.RunLoopSource",
  219. ConvertToSCStatusCode(error), SCStatusCode::SC_COUNT);
  220. return false;
  221. }
  222. CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source_.get(),
  223. kCFRunLoopCommonModes);
  224. #endif // !BUILDFLAG(IS_IOS)
  225. // Set up notifications for interface and IP address changes.
  226. delegate_->StartReachabilityNotifications();
  227. #if !BUILDFLAG(IS_IOS)
  228. delegate_->SetDynamicStoreNotificationKeys(store.get());
  229. #endif // !BUILDFLAG(IS_IOS)
  230. return true;
  231. }
  232. NetworkConfigWatcherMac::NetworkConfigWatcherMac(Delegate* delegate)
  233. : notifier_thread_(
  234. std::make_unique<NetworkConfigWatcherMacThread>(delegate)) {
  235. // We create this notifier thread because the notification implementation
  236. // needs a thread with a CFRunLoop, and there's no guarantee that
  237. // CurrentThread::Get() meets that criterion.
  238. base::Thread::Options thread_options(base::MessagePumpType::UI, 0);
  239. notifier_thread_->StartWithOptions(std::move(thread_options));
  240. }
  241. NetworkConfigWatcherMac::~NetworkConfigWatcherMac() = default;
  242. } // namespace net