OLD | NEW |
---|---|
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "net/base/network_change_notifier_linux.h" | 5 #include "net/base/network_change_notifier_linux.h" |
6 | 6 |
7 #include <errno.h> | 7 #include <errno.h> |
8 #include <sys/socket.h> | 8 #include <sys/socket.h> |
9 | 9 |
10 #include "base/bind.h" | 10 #include "base/bind.h" |
11 #include "base/callback.h" | |
11 #include "base/callback_old.h" | 12 #include "base/callback_old.h" |
12 #include "base/compiler_specific.h" | 13 #include "base/compiler_specific.h" |
13 #include "base/eintr_wrapper.h" | 14 #include "base/eintr_wrapper.h" |
14 #include "base/file_util.h" | 15 #include "base/file_util.h" |
15 #include "base/files/file_path_watcher.h" | 16 #include "base/files/file_path_watcher.h" |
17 #include "base/memory/weak_ptr.h" | |
18 #include "base/synchronization/condition_variable.h" | |
19 #include "base/synchronization/lock.h" | |
16 #include "base/task.h" | 20 #include "base/task.h" |
21 #include "base/threading/platform_thread.h" | |
17 #include "base/threading/thread.h" | 22 #include "base/threading/thread.h" |
23 #include "dbus/bus.h" | |
24 #include "dbus/message.h" | |
25 #include "dbus/object_proxy.h" | |
18 #include "net/base/net_errors.h" | 26 #include "net/base/net_errors.h" |
19 #include "net/base/network_change_notifier_netlink_linux.h" | 27 #include "net/base/network_change_notifier_netlink_linux.h" |
20 | 28 |
21 using ::base::files::FilePathWatcher; | 29 using ::base::files::FilePathWatcher; |
22 | 30 |
23 namespace net { | 31 namespace net { |
24 | 32 |
25 namespace { | 33 namespace { |
26 | 34 |
27 const int kInvalidSocket = -1; | 35 const int kInvalidSocket = -1; |
28 | 36 |
37 const char kNetworkManagerServiceName[] = "org.freedesktop.NetworkManager"; | |
38 const char kNetworkManagerPath[] = "/org/freedesktop/NetworkManager"; | |
39 const char kNetworkManagerInterface[] = "org.freedesktop.NetworkManager"; | |
40 | |
41 // http://projects.gnome.org/NetworkManager/developers/spec-08.html#type-NM_STAT E | |
42 enum { | |
43 NM_LEGACY_STATE_UNKNOWN = 0, | |
44 NM_LEGACY_STATE_ASLEEP = 1, | |
45 NM_LEGACY_STATE_CONNECTING = 2, | |
46 NM_LEGACY_STATE_CONNECTED = 3, | |
47 NM_LEGACY_STATE_DISCONNECTED = 4 | |
48 }; | |
49 | |
50 // http://projects.gnome.org/NetworkManager/developers/migrating-to-09/spec.html #type-NM_STATE | |
51 enum { | |
52 NM_STATE_UNKNOWN = 0, | |
53 NM_STATE_ASLEEP = 10, | |
54 NM_STATE_DISCONNECTED = 20, | |
55 NM_STATE_DISCONNECTING = 30, | |
56 NM_STATE_CONNECTING = 40, | |
57 NM_STATE_CONNECTED_LOCAL = 50, | |
58 NM_STATE_CONNECTED_SITE = 60, | |
59 NM_STATE_CONNECTED_GLOBAL = 70 | |
60 }; | |
61 | |
62 // A wrapper around NetworkManager's D-Bus API. | |
63 class NetworkManagerApi { | |
64 public: | |
65 typedef base::Callback<void(void)> NotificationCallback; | |
66 | |
67 explicit NetworkManagerApi(NotificationCallback notification_callback) | |
68 : online_state_(UNINITIALIZED), | |
69 initial_state_cv_(&online_state_lock_), | |
70 notification_callback_(notification_callback), | |
71 helper_thread_id_(base::kInvalidThreadId), | |
satorux1
2011/10/13 06:58:05
Not sure if it helps, but saving the origin thread
adamk
2011/10/13 18:14:06
Nothing should run on the origin thread except the
| |
72 ALLOW_THIS_IN_INITIALIZER_LIST(ptr_factory_(this)) { } | |
73 | |
74 ~NetworkManagerApi() { } | |
75 | |
76 // Should be called on a helper thread which must be of type IO. | |
77 void Init(); | |
78 | |
79 // Must be called by the helper thread's CleanUp() method. | |
80 void CleanUp(); | |
81 | |
82 // Implementation of NetworkChangeNotifierLinux::IsCurrentlyOffline(). | |
83 // Safe to call from any thread, but will block until Init() has completed. | |
84 bool IsCurrentlyOffline(); | |
85 | |
86 private: | |
87 enum OnlineState { | |
88 UNINITIALIZED = -1, | |
89 OFFLINE = 0, | |
90 ONLINE = 1 | |
91 }; | |
92 | |
93 void NotifyObserversOfOnlineStateChange() { | |
94 notification_callback_.Run(); | |
95 } | |
96 | |
97 // Callbacks for D-Bus API. | |
98 void OnStateChanged(dbus::Signal* signal); | |
99 void OnConnected(const std::string&, const std::string&, bool success) { | |
100 LOG_IF(WARNING, !success) << "Failed to set up offline state detection"; | |
101 } | |
102 | |
103 // Synchronously gets the current state from the NetworkManager. | |
104 // Should be called on a helper thread. | |
105 uint32 GetCurrentState(dbus::ObjectProxy* proxy); | |
106 | |
107 // Converts a NetworkManager state uint to our enum. | |
108 // Always returns either ONLINE or OFFLINE. | |
109 static OnlineState TranslateState(uint32 state); | |
110 | |
111 OnlineState online_state_; | |
112 base::Lock online_state_lock_; | |
113 base::ConditionVariable initial_state_cv_; | |
114 | |
115 NotificationCallback notification_callback_; | |
116 | |
117 scoped_refptr<dbus::Bus> system_bus_; | |
118 | |
119 base::PlatformThreadId helper_thread_id_; | |
120 | |
121 base::WeakPtrFactory<NetworkManagerApi> ptr_factory_; | |
122 | |
123 DISALLOW_COPY_AND_ASSIGN(NetworkManagerApi); | |
124 }; | |
125 | |
126 void NetworkManagerApi::Init() { | |
127 DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_IO); | |
satorux1
2011/10/13 06:58:05
You might want to add a comment why TYPE_IO matter
adamk
2011/10/13 18:14:06
Done.
| |
128 helper_thread_id_ = base::PlatformThread::CurrentId(); | |
129 | |
130 dbus::Bus::Options options; | |
131 options.bus_type = dbus::Bus::SYSTEM; | |
132 options.connection_type = dbus::Bus::PRIVATE; | |
133 system_bus_ = new dbus::Bus(options); | |
134 | |
135 dbus::ObjectProxy* proxy = | |
136 system_bus_->GetObjectProxy(kNetworkManagerServiceName, | |
137 kNetworkManagerPath); | |
138 OnlineState online_state = TranslateState(GetCurrentState(proxy)); | |
139 { | |
140 base::AutoLock lock(online_state_lock_); | |
141 online_state_ = online_state; | |
142 initial_state_cv_.Signal(); | |
143 } | |
144 | |
145 proxy->ConnectToSignal( | |
146 kNetworkManagerInterface, | |
147 "StateChanged", | |
148 base::Bind(&NetworkManagerApi::OnStateChanged, ptr_factory_.GetWeakPtr()), | |
149 base::Bind(&NetworkManagerApi::OnConnected, ptr_factory_.GetWeakPtr())); | |
150 } | |
151 | |
152 void NetworkManagerApi::CleanUp() { | |
153 DCHECK_EQ(helper_thread_id_, base::PlatformThread::CurrentId()); | |
154 ptr_factory_.InvalidateWeakPtrs(); | |
155 } | |
156 | |
157 void NetworkManagerApi::OnStateChanged(dbus::Signal* signal) { | |
158 DCHECK_EQ(helper_thread_id_, base::PlatformThread::CurrentId()); | |
159 DCHECK(signal); | |
160 dbus::MessageReader reader(signal); | |
161 uint32 state; | |
162 if (!reader.PopUint32(&state)) { | |
163 LOG(WARNING) << "Unexpected response for NetworkManager State request: " | |
164 << signal->ToString(); | |
165 return; | |
166 } | |
167 OnlineState new_online_state = TranslateState(state); | |
168 { | |
169 base::AutoLock lock(online_state_lock_); | |
170 if (new_online_state != online_state_) | |
171 online_state_ = new_online_state; | |
172 else | |
173 return; | |
174 } | |
175 // Can't notify while we hold the lock | |
176 NotifyObserversOfOnlineStateChange(); | |
177 } | |
178 | |
179 uint32 NetworkManagerApi::GetCurrentState(dbus::ObjectProxy* proxy) { | |
180 DCHECK_EQ(helper_thread_id_, base::PlatformThread::CurrentId()); | |
181 dbus::MethodCall method_call(DBUS_INTERFACE_PROPERTIES, "Get"); | |
182 dbus::MessageWriter builder(&method_call); | |
183 builder.AppendString(kNetworkManagerInterface); | |
184 builder.AppendString("State"); | |
185 scoped_ptr<dbus::Response> response( | |
186 proxy->CallMethodAndBlock(&method_call, | |
187 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); | |
188 if (!response.get()) | |
189 return NM_STATE_UNKNOWN; | |
190 | |
191 dbus::MessageReader reader(response.get()); | |
192 uint32 state; | |
193 if (!reader.PopUint32(&state)) { | |
194 LOG(WARNING) << "Unexpected response for NetworkManager State request: " | |
195 << response->ToString(); | |
196 return NM_STATE_UNKNOWN; | |
197 } | |
198 | |
199 return state; | |
200 } | |
201 | |
202 NetworkManagerApi::OnlineState NetworkManagerApi::TranslateState(uint32 state) { | |
203 switch (state) { | |
204 case NM_LEGACY_STATE_CONNECTED: | |
205 case NM_STATE_CONNECTED_SITE: | |
206 case NM_STATE_CONNECTED_GLOBAL: | |
207 // Definitely connected | |
208 return ONLINE; | |
209 case NM_LEGACY_STATE_DISCONNECTED: | |
210 case NM_STATE_DISCONNECTED: | |
211 // Definitely disconnected | |
212 return OFFLINE; | |
213 case NM_STATE_CONNECTED_LOCAL: | |
214 // Local networking only; I'm treating this as offline (keybuk) | |
215 return OFFLINE; | |
216 case NM_LEGACY_STATE_CONNECTING: | |
217 case NM_STATE_DISCONNECTING: | |
218 case NM_STATE_CONNECTING: | |
219 // In-flight change to connection status currently underway | |
220 return OFFLINE; | |
221 case NM_LEGACY_STATE_ASLEEP: | |
222 case NM_STATE_ASLEEP: | |
223 // Networking disabled or no devices on system | |
224 return OFFLINE; | |
225 default: | |
226 // Unknown status | |
227 return ONLINE; | |
228 } | |
229 } | |
230 | |
231 bool NetworkManagerApi::IsCurrentlyOffline() { | |
232 base::AutoLock lock(online_state_lock_); | |
233 while (online_state_ == UNINITIALIZED) { | |
234 initial_state_cv_.Wait(); | |
235 } | |
236 return online_state_ == OFFLINE; | |
237 } | |
238 | |
29 class DNSWatchDelegate : public FilePathWatcher::Delegate { | 239 class DNSWatchDelegate : public FilePathWatcher::Delegate { |
30 public: | 240 public: |
31 explicit DNSWatchDelegate(Callback0::Type* callback) | 241 explicit DNSWatchDelegate(Callback0::Type* callback) |
32 : callback_(callback) {} | 242 : callback_(callback) {} |
33 virtual ~DNSWatchDelegate() {} | 243 virtual ~DNSWatchDelegate() {} |
34 // FilePathWatcher::Delegate interface | 244 // FilePathWatcher::Delegate interface |
35 virtual void OnFilePathChanged(const FilePath& path) OVERRIDE; | 245 virtual void OnFilePathChanged(const FilePath& path) OVERRIDE; |
36 virtual void OnFilePathError(const FilePath& path) OVERRIDE; | 246 virtual void OnFilePathError(const FilePath& path) OVERRIDE; |
37 private: | 247 private: |
38 scoped_ptr<Callback0::Type> callback_; | 248 scoped_ptr<Callback0::Type> callback_; |
(...skipping 15 matching lines...) Expand all Loading... | |
54 class NetworkChangeNotifierLinux::Thread | 264 class NetworkChangeNotifierLinux::Thread |
55 : public base::Thread, public MessageLoopForIO::Watcher { | 265 : public base::Thread, public MessageLoopForIO::Watcher { |
56 public: | 266 public: |
57 Thread(); | 267 Thread(); |
58 virtual ~Thread(); | 268 virtual ~Thread(); |
59 | 269 |
60 // MessageLoopForIO::Watcher: | 270 // MessageLoopForIO::Watcher: |
61 virtual void OnFileCanReadWithoutBlocking(int fd); | 271 virtual void OnFileCanReadWithoutBlocking(int fd); |
62 virtual void OnFileCanWriteWithoutBlocking(int /* fd */); | 272 virtual void OnFileCanWriteWithoutBlocking(int /* fd */); |
63 | 273 |
274 // Plumbing for NetworkChangeNotifier::IsCurrentlyOffline. | |
275 // Safe to call from any thread. | |
276 bool IsCurrentlyOffline() { | |
277 return network_manager_api_.IsCurrentlyOffline(); | |
278 } | |
279 | |
64 protected: | 280 protected: |
65 // base::Thread | 281 // base::Thread |
66 virtual void Init(); | 282 virtual void Init(); |
67 virtual void CleanUp(); | 283 virtual void CleanUp(); |
68 | 284 |
69 private: | 285 private: |
70 void NotifyObserversOfIPAddressChange() { | 286 void NotifyObserversOfIPAddressChange() { |
71 NetworkChangeNotifier::NotifyObserversOfIPAddressChange(); | 287 NetworkChangeNotifier::NotifyObserversOfIPAddressChange(); |
72 } | 288 } |
73 | 289 |
74 void NotifyObserversOfDNSChange() { | 290 void NotifyObserversOfDNSChange() { |
75 NetworkChangeNotifier::NotifyObserversOfDNSChange(); | 291 NetworkChangeNotifier::NotifyObserversOfDNSChange(); |
76 } | 292 } |
77 | 293 |
294 static void NotifyObserversOfOnlineStateChange() { | |
295 NetworkChangeNotifier::NotifyObserversOfOnlineStateChange(); | |
296 } | |
297 | |
78 // Starts listening for netlink messages. Also handles the messages if there | 298 // Starts listening for netlink messages. Also handles the messages if there |
79 // are any available on the netlink socket. | 299 // are any available on the netlink socket. |
80 void ListenForNotifications(); | 300 void ListenForNotifications(); |
81 | 301 |
82 // Attempts to read from the netlink socket into |buf| of length |len|. | 302 // Attempts to read from the netlink socket into |buf| of length |len|. |
83 // Returns the bytes read on synchronous success and ERR_IO_PENDING if the | 303 // Returns the bytes read on synchronous success and ERR_IO_PENDING if the |
84 // recv() would block. Otherwise, it returns a net error code. | 304 // recv() would block. Otherwise, it returns a net error code. |
85 int ReadNotificationMessage(char* buf, size_t len); | 305 int ReadNotificationMessage(char* buf, size_t len); |
86 | 306 |
87 // The netlink socket descriptor. | 307 // The netlink socket descriptor. |
88 int netlink_fd_; | 308 int netlink_fd_; |
89 MessageLoopForIO::FileDescriptorWatcher netlink_watcher_; | 309 MessageLoopForIO::FileDescriptorWatcher netlink_watcher_; |
90 | 310 |
91 // Technically only needed for ChromeOS, but it's ugly to #ifdef out. | 311 // Technically only needed for ChromeOS, but it's ugly to #ifdef out. |
92 ScopedRunnableMethodFactory<Thread> method_factory_; | 312 ScopedRunnableMethodFactory<Thread> method_factory_; |
93 | 313 |
94 // Used to watch for changes to /etc/resolv.conf and /etc/hosts. | 314 // Used to watch for changes to /etc/resolv.conf and /etc/hosts. |
95 scoped_ptr<base::files::FilePathWatcher> resolv_file_watcher_; | 315 scoped_ptr<base::files::FilePathWatcher> resolv_file_watcher_; |
96 scoped_ptr<base::files::FilePathWatcher> hosts_file_watcher_; | 316 scoped_ptr<base::files::FilePathWatcher> hosts_file_watcher_; |
97 scoped_refptr<DNSWatchDelegate> file_watcher_delegate_; | 317 scoped_refptr<DNSWatchDelegate> file_watcher_delegate_; |
98 | 318 |
319 // Used to detect online/offline state changes. | |
320 NetworkManagerApi network_manager_api_; | |
321 | |
99 DISALLOW_COPY_AND_ASSIGN(Thread); | 322 DISALLOW_COPY_AND_ASSIGN(Thread); |
100 }; | 323 }; |
101 | 324 |
102 NetworkChangeNotifierLinux::Thread::Thread() | 325 NetworkChangeNotifierLinux::Thread::Thread() |
103 : base::Thread("NetworkChangeNotifier"), | 326 : base::Thread("NetworkChangeNotifier"), |
104 netlink_fd_(kInvalidSocket), | 327 netlink_fd_(kInvalidSocket), |
105 ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) { | 328 ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), |
329 network_manager_api_( | |
330 base::Bind(&NetworkChangeNotifierLinux::Thread | |
331 ::NotifyObserversOfOnlineStateChange)) { | |
106 } | 332 } |
107 | 333 |
108 NetworkChangeNotifierLinux::Thread::~Thread() {} | 334 NetworkChangeNotifierLinux::Thread::~Thread() {} |
109 | 335 |
110 void NetworkChangeNotifierLinux::Thread::Init() { | 336 void NetworkChangeNotifierLinux::Thread::Init() { |
111 resolv_file_watcher_.reset(new FilePathWatcher); | 337 resolv_file_watcher_.reset(new FilePathWatcher); |
112 hosts_file_watcher_.reset(new FilePathWatcher); | 338 hosts_file_watcher_.reset(new FilePathWatcher); |
113 file_watcher_delegate_ = new DNSWatchDelegate(NewCallback(this, | 339 file_watcher_delegate_ = new DNSWatchDelegate(NewCallback(this, |
114 &NetworkChangeNotifierLinux::Thread::NotifyObserversOfDNSChange)); | 340 &NetworkChangeNotifierLinux::Thread::NotifyObserversOfDNSChange)); |
115 if (!resolv_file_watcher_->Watch( | 341 if (!resolv_file_watcher_->Watch( |
116 FilePath(FILE_PATH_LITERAL("/etc/resolv.conf")), | 342 FilePath(FILE_PATH_LITERAL("/etc/resolv.conf")), |
117 file_watcher_delegate_.get())) { | 343 file_watcher_delegate_.get())) { |
118 LOG(ERROR) << "Failed to setup watch for /etc/resolv.conf"; | 344 LOG(ERROR) << "Failed to setup watch for /etc/resolv.conf"; |
119 } | 345 } |
120 if (!hosts_file_watcher_->Watch(FilePath(FILE_PATH_LITERAL("/etc/hosts")), | 346 if (!hosts_file_watcher_->Watch(FilePath(FILE_PATH_LITERAL("/etc/hosts")), |
121 file_watcher_delegate_.get())) { | 347 file_watcher_delegate_.get())) { |
122 LOG(ERROR) << "Failed to setup watch for /etc/hosts"; | 348 LOG(ERROR) << "Failed to setup watch for /etc/hosts"; |
123 } | 349 } |
124 netlink_fd_ = InitializeNetlinkSocket(); | 350 netlink_fd_ = InitializeNetlinkSocket(); |
125 if (netlink_fd_ < 0) { | 351 if (netlink_fd_ < 0) { |
126 netlink_fd_ = kInvalidSocket; | 352 netlink_fd_ = kInvalidSocket; |
127 return; | 353 return; |
128 } | 354 } |
129 ListenForNotifications(); | 355 ListenForNotifications(); |
356 | |
357 network_manager_api_.Init(); | |
130 } | 358 } |
131 | 359 |
132 void NetworkChangeNotifierLinux::Thread::CleanUp() { | 360 void NetworkChangeNotifierLinux::Thread::CleanUp() { |
133 if (netlink_fd_ != kInvalidSocket) { | 361 if (netlink_fd_ != kInvalidSocket) { |
134 if (HANDLE_EINTR(close(netlink_fd_)) != 0) | 362 if (HANDLE_EINTR(close(netlink_fd_)) != 0) |
135 PLOG(ERROR) << "Failed to close socket"; | 363 PLOG(ERROR) << "Failed to close socket"; |
136 netlink_fd_ = kInvalidSocket; | 364 netlink_fd_ = kInvalidSocket; |
137 netlink_watcher_.StopWatchingFileDescriptor(); | 365 netlink_watcher_.StopWatchingFileDescriptor(); |
138 } | 366 } |
139 // Kill watchers early to make sure they won't try to call | 367 // Kill watchers early to make sure they won't try to call |
140 // into us via the delegate during destruction. | 368 // into us via the delegate during destruction. |
141 resolv_file_watcher_.reset(); | 369 resolv_file_watcher_.reset(); |
142 hosts_file_watcher_.reset(); | 370 hosts_file_watcher_.reset(); |
371 | |
372 network_manager_api_.CleanUp(); | |
143 } | 373 } |
144 | 374 |
145 void NetworkChangeNotifierLinux::Thread::OnFileCanReadWithoutBlocking(int fd) { | 375 void NetworkChangeNotifierLinux::Thread::OnFileCanReadWithoutBlocking(int fd) { |
146 DCHECK_EQ(fd, netlink_fd_); | 376 DCHECK_EQ(fd, netlink_fd_); |
147 ListenForNotifications(); | 377 ListenForNotifications(); |
148 } | 378 } |
149 | 379 |
150 void NetworkChangeNotifierLinux::Thread::OnFileCanWriteWithoutBlocking( | 380 void NetworkChangeNotifierLinux::Thread::OnFileCanWriteWithoutBlocking( |
151 int /* fd */) { | 381 int /* fd */) { |
152 NOTREACHED(); | 382 NOTREACHED(); |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
211 notifier_thread_->StartWithOptions(thread_options); | 441 notifier_thread_->StartWithOptions(thread_options); |
212 } | 442 } |
213 | 443 |
214 NetworkChangeNotifierLinux::~NetworkChangeNotifierLinux() { | 444 NetworkChangeNotifierLinux::~NetworkChangeNotifierLinux() { |
215 // We don't need to explicitly Stop(), but doing so allows us to sanity- | 445 // We don't need to explicitly Stop(), but doing so allows us to sanity- |
216 // check that the notifier thread shut down properly. | 446 // check that the notifier thread shut down properly. |
217 notifier_thread_->Stop(); | 447 notifier_thread_->Stop(); |
218 } | 448 } |
219 | 449 |
220 bool NetworkChangeNotifierLinux::IsCurrentlyOffline() const { | 450 bool NetworkChangeNotifierLinux::IsCurrentlyOffline() const { |
221 // TODO(eroman): http://crbug.com/53473 | 451 return notifier_thread_->IsCurrentlyOffline(); |
222 return false; | |
223 } | 452 } |
224 | 453 |
225 } // namespace net | 454 } // namespace net |
OLD | NEW |