network_change_notifier_mac.cc revision c407dc5cd9bdc5668497f21b26b09d988ab439de
1// Copyright (c) 2010 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
5#include "net/base/network_change_notifier_mac.h"
6
7#include <SystemConfiguration/SCDynamicStoreKey.h>
8#include <SystemConfiguration/SCSchemaDefinitions.h>
9#include <algorithm>
10
11#include "base/thread.h"
12
13// We only post tasks to a child thread we own, so we don't need refcounting.
14DISABLE_RUNNABLE_METHOD_REFCOUNT(net::NetworkChangeNotifierMac);
15
16namespace net {
17
18NetworkChangeNotifierMac::NetworkChangeNotifierMac()
19    : notifier_thread_(new base::Thread("NetworkChangeNotifier")) {
20  // We create this notifier thread because the notification implementation
21  // needs a thread with a CFRunLoop, and there's no guarantee that
22  // MessageLoop::current() meets that criterion.
23  base::Thread::Options thread_options(MessageLoop::TYPE_UI, 0);
24  notifier_thread_->StartWithOptions(thread_options);
25  // TODO(willchan): Look to see if there's a better signal for when it's ok to
26  // initialize this, rather than just delaying it by a fixed time.
27  const int kNotifierThreadInitializationDelayMS = 1000;
28  notifier_thread_->message_loop()->PostDelayedTask(FROM_HERE,
29      NewRunnableMethod(this, &NetworkChangeNotifierMac::Init),
30      kNotifierThreadInitializationDelayMS);
31}
32
33NetworkChangeNotifierMac::~NetworkChangeNotifierMac() {
34  // We don't need to explicitly Stop(), but doing so allows us to sanity-
35  // check that the notifier thread shut down properly.
36  notifier_thread_->Stop();
37  DCHECK(run_loop_source_ == NULL);
38}
39
40// static
41void NetworkChangeNotifierMac::DynamicStoreCallback(
42    SCDynamicStoreRef /* store */,
43    CFArrayRef changed_keys,
44    void* config) {
45  NetworkChangeNotifierMac* net_config =
46      static_cast<NetworkChangeNotifierMac*>(config);
47  net_config->OnNetworkConfigChange(changed_keys);
48}
49
50void NetworkChangeNotifierMac::WillDestroyCurrentMessageLoop() {
51  DCHECK(notifier_thread_ != NULL);
52  // We can't check the notifier_thread_'s message_loop(), as it's now 0.
53  // DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current());
54
55  DCHECK(run_loop_source_ != NULL);
56  CFRunLoopRemoveSource(CFRunLoopGetCurrent(), run_loop_source_.get(),
57                        kCFRunLoopCommonModes);
58  run_loop_source_.reset();
59}
60
61void NetworkChangeNotifierMac::Init() {
62  DCHECK(notifier_thread_ != NULL);
63  DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current());
64
65  // Add a run loop source for a dynamic store to the current run loop.
66  SCDynamicStoreContext context = {
67    0,     // Version 0.
68    this,  // User data.
69    NULL,  // This is not reference counted.  No retain function.
70    NULL,  // This is not reference counted.  No release function.
71    NULL,  // No description for this.
72  };
73  scoped_cftyperef<SCDynamicStoreRef> store(SCDynamicStoreCreate(
74      NULL, CFSTR("org.chromium"), DynamicStoreCallback, &context));
75  run_loop_source_.reset(SCDynamicStoreCreateRunLoopSource(
76      NULL, store.get(), 0));
77  CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source_.get(),
78                     kCFRunLoopCommonModes);
79
80  // Set up notifications for interface and IP address changes.
81  scoped_cftyperef<CFMutableArrayRef> notification_keys(
82      CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks));
83  scoped_cftyperef<CFStringRef> key(SCDynamicStoreKeyCreateNetworkGlobalEntity(
84      NULL, kSCDynamicStoreDomainState, kSCEntNetInterface));
85  CFArrayAppendValue(notification_keys.get(), key.get());
86  key.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity(
87      NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4));
88  CFArrayAppendValue(notification_keys.get(), key.get());
89  key.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity(
90      NULL, kSCDynamicStoreDomainState, kSCEntNetIPv6));
91  CFArrayAppendValue(notification_keys.get(), key.get());
92
93  // Set the notification keys.  This starts us receiving notifications.
94  bool ret = SCDynamicStoreSetNotificationKeys(
95      store.get(), notification_keys.get(), NULL);
96  // TODO(willchan): Figure out a proper way to handle this rather than crash.
97  CHECK(ret);
98
99  MessageLoop::current()->AddDestructionObserver(this);
100}
101
102void NetworkChangeNotifierMac::OnNetworkConfigChange(CFArrayRef changed_keys) {
103  DCHECK(notifier_thread_ != NULL);
104  DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current());
105
106  for (CFIndex i = 0; i < CFArrayGetCount(changed_keys); ++i) {
107    CFStringRef key = static_cast<CFStringRef>(
108        CFArrayGetValueAtIndex(changed_keys, i));
109    if (CFStringHasSuffix(key, kSCEntNetIPv4) ||
110        CFStringHasSuffix(key, kSCEntNetIPv6)) {
111      NotifyObserversOfIPAddressChange();
112      return;
113    }
114    if (CFStringHasSuffix(key, kSCEntNetInterface)) {
115      // TODO(willchan): Does not appear to be working.  Look into this.
116      // Perhaps this isn't needed anyway.
117    } else {
118      NOTREACHED();
119    }
120  }
121}
122
123}  // namespace net
124