| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 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 // | |
| 5 // This implementation of NetworkChangeNotifier's offline state detection | |
| 6 // depends on D-Bus and NetworkManager, and is known to work on at least | |
| 7 // GNOME version 2.30. If D-Bus or NetworkManager are unavailable, this | |
| 8 // implementation will always behave as if it is online. | |
| 9 | 4 |
| 10 #include "net/base/network_change_notifier_linux.h" | 5 #include "net/base/network_change_notifier_linux.h" |
| 11 | 6 |
| 12 #include <resolv.h> | |
| 13 | |
| 14 #include "base/bind.h" | 7 #include "base/bind.h" |
| 15 #include "base/bind_helpers.h" | |
| 16 #include "base/callback.h" | |
| 17 #include "base/compiler_specific.h" | 8 #include "base/compiler_specific.h" |
| 18 #include "base/memory/weak_ptr.h" | |
| 19 #include "base/synchronization/lock.h" | |
| 20 #include "base/synchronization/waitable_event.h" | |
| 21 #include "base/threading/platform_thread.h" | |
| 22 #include "base/threading/thread.h" | 9 #include "base/threading/thread.h" |
| 23 #include "base/threading/thread_restrictions.h" | |
| 24 #include "dbus/bus.h" | |
| 25 #include "dbus/message.h" | |
| 26 #include "dbus/object_proxy.h" | |
| 27 #include "net/base/address_tracker_linux.h" | 10 #include "net/base/address_tracker_linux.h" |
| 28 #include "net/base/net_errors.h" | |
| 29 #include "net/dns/dns_config_service.h" | 11 #include "net/dns/dns_config_service.h" |
| 30 | 12 |
| 31 namespace net { | 13 namespace net { |
| 32 | 14 |
| 33 namespace { | |
| 34 | |
| 35 const char kNetworkManagerServiceName[] = "org.freedesktop.NetworkManager"; | |
| 36 const char kNetworkManagerPath[] = "/org/freedesktop/NetworkManager"; | |
| 37 const char kNetworkManagerInterface[] = "org.freedesktop.NetworkManager"; | |
| 38 | |
| 39 // http://projects.gnome.org/NetworkManager/developers/spec-08.html#type-NM_STAT
E | |
| 40 enum { | |
| 41 NM_LEGACY_STATE_UNKNOWN = 0, | |
| 42 NM_LEGACY_STATE_ASLEEP = 1, | |
| 43 NM_LEGACY_STATE_CONNECTING = 2, | |
| 44 NM_LEGACY_STATE_CONNECTED = 3, | |
| 45 NM_LEGACY_STATE_DISCONNECTED = 4 | |
| 46 }; | |
| 47 | |
| 48 // http://projects.gnome.org/NetworkManager/developers/migrating-to-09/spec.html
#type-NM_STATE | |
| 49 enum { | |
| 50 NM_STATE_UNKNOWN = 0, | |
| 51 NM_STATE_ASLEEP = 10, | |
| 52 NM_STATE_DISCONNECTED = 20, | |
| 53 NM_STATE_DISCONNECTING = 30, | |
| 54 NM_STATE_CONNECTING = 40, | |
| 55 NM_STATE_CONNECTED_LOCAL = 50, | |
| 56 NM_STATE_CONNECTED_SITE = 60, | |
| 57 NM_STATE_CONNECTED_GLOBAL = 70 | |
| 58 }; | |
| 59 | |
| 60 } // namespace | |
| 61 | |
| 62 // A wrapper around NetworkManager's D-Bus API. | |
| 63 class NetworkManagerApi { | |
| 64 public: | |
| 65 NetworkManagerApi(const base::Closure& notification_callback, dbus::Bus* bus) | |
| 66 : is_offline_(false), | |
| 67 offline_state_initialized_(true /*manual_reset*/, false), | |
| 68 notification_callback_(notification_callback), | |
| 69 helper_thread_id_(base::kInvalidThreadId), | |
| 70 ALLOW_THIS_IN_INITIALIZER_LIST(ptr_factory_(this)), | |
| 71 system_bus_(bus) { } | |
| 72 | |
| 73 ~NetworkManagerApi() { } | |
| 74 | |
| 75 // Should be called on a helper thread which must be of type IO. | |
| 76 void Init(); | |
| 77 | |
| 78 // Must be called by the helper thread's CleanUp() method. | |
| 79 void CleanUp(); | |
| 80 | |
| 81 // Implementation of NetworkChangeNotifierLinux::GetCurrentConnectionType(). | |
| 82 // Safe to call from any thread, but will block until Init() has completed. | |
| 83 NetworkChangeNotifier::ConnectionType GetCurrentConnectionType(); | |
| 84 | |
| 85 private: | |
| 86 // Callbacks for D-Bus API. | |
| 87 void OnInitialResponse(dbus::Response* response) { | |
| 88 HandleResponse(response); | |
| 89 offline_state_initialized_.Signal(); | |
| 90 } | |
| 91 | |
| 92 void OnSignaled(dbus::Signal* signal); | |
| 93 | |
| 94 void OnConnected(const std::string&, const std::string&, bool success) { | |
| 95 if (!success) { | |
| 96 DLOG(WARNING) << "Failed to set up offline state detection"; | |
| 97 offline_state_initialized_.Signal(); | |
| 98 } | |
| 99 } | |
| 100 | |
| 101 // Helper for OnInitialResponse. | |
| 102 void HandleResponse(dbus::Response* response); | |
| 103 | |
| 104 // Converts a NetworkManager state uint to a bool. | |
| 105 static bool StateIsOffline(uint32 state); | |
| 106 | |
| 107 bool is_offline_; | |
| 108 base::Lock is_offline_lock_; | |
| 109 base::WaitableEvent offline_state_initialized_; | |
| 110 | |
| 111 base::Closure notification_callback_; | |
| 112 | |
| 113 base::PlatformThreadId helper_thread_id_; | |
| 114 | |
| 115 base::WeakPtrFactory<NetworkManagerApi> ptr_factory_; | |
| 116 | |
| 117 scoped_refptr<dbus::Bus> system_bus_; | |
| 118 | |
| 119 DISALLOW_COPY_AND_ASSIGN(NetworkManagerApi); | |
| 120 }; | |
| 121 | |
| 122 void NetworkManagerApi::Init() { | |
| 123 // D-Bus requires an IO MessageLoop. | |
| 124 DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_IO); | |
| 125 helper_thread_id_ = base::PlatformThread::CurrentId(); | |
| 126 | |
| 127 if (!system_bus_) { | |
| 128 dbus::Bus::Options options; | |
| 129 options.bus_type = dbus::Bus::SYSTEM; | |
| 130 options.connection_type = dbus::Bus::PRIVATE; | |
| 131 system_bus_ = new dbus::Bus(options); | |
| 132 } | |
| 133 | |
| 134 // Ignore ServiceUnknown errors to avoid log spam: http://crbug.com/109696. | |
| 135 dbus::ObjectProxy* proxy = system_bus_->GetObjectProxyWithOptions( | |
| 136 kNetworkManagerServiceName, dbus::ObjectPath(kNetworkManagerPath), | |
| 137 dbus::ObjectProxy::IGNORE_SERVICE_UNKNOWN_ERRORS); | |
| 138 | |
| 139 // Get the initial state asynchronously. | |
| 140 dbus::MethodCall method_call(DBUS_INTERFACE_PROPERTIES, "Get"); | |
| 141 dbus::MessageWriter builder(&method_call); | |
| 142 builder.AppendString(kNetworkManagerInterface); | |
| 143 builder.AppendString("State"); | |
| 144 proxy->CallMethod( | |
| 145 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, | |
| 146 base::Bind(&NetworkManagerApi::OnInitialResponse, | |
| 147 ptr_factory_.GetWeakPtr())); | |
| 148 | |
| 149 // And sign up for notifications. | |
| 150 proxy->ConnectToSignal( | |
| 151 kNetworkManagerInterface, | |
| 152 "StateChanged", | |
| 153 base::Bind(&NetworkManagerApi::OnSignaled, ptr_factory_.GetWeakPtr()), | |
| 154 base::Bind(&NetworkManagerApi::OnConnected, ptr_factory_.GetWeakPtr())); | |
| 155 } | |
| 156 | |
| 157 void NetworkManagerApi::CleanUp() { | |
| 158 DCHECK_EQ(helper_thread_id_, base::PlatformThread::CurrentId()); | |
| 159 ptr_factory_.InvalidateWeakPtrs(); | |
| 160 } | |
| 161 | |
| 162 void NetworkManagerApi::HandleResponse(dbus::Response* response) { | |
| 163 DCHECK_EQ(helper_thread_id_, base::PlatformThread::CurrentId()); | |
| 164 if (!response) { | |
| 165 DVLOG(1) << "No response received for initial state request"; | |
| 166 return; | |
| 167 } | |
| 168 dbus::MessageReader reader(response); | |
| 169 uint32 state = 0; | |
| 170 if (!reader.PopVariantOfUint32(&state)) { | |
| 171 DLOG(WARNING) << "Unexpected response for NetworkManager State request: " | |
| 172 << response->ToString(); | |
| 173 return; | |
| 174 } | |
| 175 { | |
| 176 base::AutoLock lock(is_offline_lock_); | |
| 177 is_offline_ = StateIsOffline(state); | |
| 178 } | |
| 179 } | |
| 180 | |
| 181 void NetworkManagerApi::OnSignaled(dbus::Signal* signal) { | |
| 182 DCHECK_EQ(helper_thread_id_, base::PlatformThread::CurrentId()); | |
| 183 dbus::MessageReader reader(signal); | |
| 184 uint32 state = 0; | |
| 185 if (!reader.PopUint32(&state)) { | |
| 186 DLOG(WARNING) << "Unexpected signal for NetworkManager StateChanged: " | |
| 187 << signal->ToString(); | |
| 188 return; | |
| 189 } | |
| 190 bool new_is_offline = StateIsOffline(state); | |
| 191 { | |
| 192 base::AutoLock lock(is_offline_lock_); | |
| 193 if (is_offline_ != new_is_offline) | |
| 194 is_offline_ = new_is_offline; | |
| 195 else | |
| 196 return; | |
| 197 } | |
| 198 notification_callback_.Run(); | |
| 199 } | |
| 200 | |
| 201 bool NetworkManagerApi::StateIsOffline(uint32 state) { | |
| 202 switch (state) { | |
| 203 case NM_LEGACY_STATE_CONNECTED: | |
| 204 case NM_STATE_CONNECTED_SITE: | |
| 205 case NM_STATE_CONNECTED_GLOBAL: | |
| 206 // Definitely connected | |
| 207 return false; | |
| 208 case NM_LEGACY_STATE_DISCONNECTED: | |
| 209 case NM_STATE_DISCONNECTED: | |
| 210 // Definitely disconnected | |
| 211 return true; | |
| 212 case NM_STATE_CONNECTED_LOCAL: | |
| 213 // Local networking only; I'm treating this as offline (keybuk) | |
| 214 return true; | |
| 215 case NM_LEGACY_STATE_CONNECTING: | |
| 216 case NM_STATE_DISCONNECTING: | |
| 217 case NM_STATE_CONNECTING: | |
| 218 // In-flight change to connection status currently underway | |
| 219 return true; | |
| 220 case NM_LEGACY_STATE_ASLEEP: | |
| 221 case NM_STATE_ASLEEP: | |
| 222 // Networking disabled or no devices on system | |
| 223 return true; | |
| 224 default: | |
| 225 // Unknown status | |
| 226 return false; | |
| 227 } | |
| 228 } | |
| 229 | |
| 230 NetworkChangeNotifier::ConnectionType | |
| 231 NetworkManagerApi::GetCurrentConnectionType() { | |
| 232 // http://crbug.com/125097 | |
| 233 base::ThreadRestrictions::ScopedAllowWait allow_wait; | |
| 234 offline_state_initialized_.Wait(); | |
| 235 base::AutoLock lock(is_offline_lock_); | |
| 236 // TODO(droger): Return something more detailed than CONNECTION_UNKNOWN. | |
| 237 return is_offline_ ? NetworkChangeNotifier::CONNECTION_NONE : | |
| 238 NetworkChangeNotifier::CONNECTION_UNKNOWN; | |
| 239 } | |
| 240 | |
| 241 class NetworkChangeNotifierLinux::Thread : public base::Thread { | 15 class NetworkChangeNotifierLinux::Thread : public base::Thread { |
| 242 public: | 16 public: |
| 243 explicit Thread(dbus::Bus* bus); | 17 Thread(); |
| 244 virtual ~Thread(); | 18 virtual ~Thread(); |
| 245 | 19 |
| 246 // Plumbing for NetworkChangeNotifier::GetCurrentConnectionType. | 20 // Plumbing for NetworkChangeNotifier::GetCurrentConnectionType. |
| 247 // Safe to call from any thread. | 21 // Safe to call from any thread. |
| 248 NetworkChangeNotifier::ConnectionType GetCurrentConnectionType() { | 22 NetworkChangeNotifier::ConnectionType GetCurrentConnectionType() { |
| 249 return network_manager_api_.GetCurrentConnectionType(); | 23 return address_tracker_.GetCurrentConnectionType(); |
| 250 } | 24 } |
| 251 | 25 |
| 252 const internal::AddressTrackerLinux* address_tracker() const { | 26 const internal::AddressTrackerLinux* address_tracker() const { |
| 253 return &address_tracker_; | 27 return &address_tracker_; |
| 254 } | 28 } |
| 255 | 29 |
| 256 protected: | 30 protected: |
| 257 // base::Thread | 31 // base::Thread |
| 258 virtual void Init() OVERRIDE; | 32 virtual void Init() OVERRIDE; |
| 259 virtual void CleanUp() OVERRIDE; | 33 virtual void CleanUp() OVERRIDE; |
| 260 | 34 |
| 261 private: | 35 private: |
| 262 // Used to detect online/offline state changes. | |
| 263 NetworkManagerApi network_manager_api_; | |
| 264 | |
| 265 scoped_ptr<DnsConfigService> dns_config_service_; | 36 scoped_ptr<DnsConfigService> dns_config_service_; |
| 37 // Used to detect online/offline state and IP address changes. |
| 266 internal::AddressTrackerLinux address_tracker_; | 38 internal::AddressTrackerLinux address_tracker_; |
| 267 | 39 |
| 268 DISALLOW_COPY_AND_ASSIGN(Thread); | 40 DISALLOW_COPY_AND_ASSIGN(Thread); |
| 269 }; | 41 }; |
| 270 | 42 |
| 271 NetworkChangeNotifierLinux::Thread::Thread(dbus::Bus* bus) | 43 NetworkChangeNotifierLinux::Thread::Thread() |
| 272 : base::Thread("NetworkChangeNotifier"), | 44 : base::Thread("NetworkChangeNotifier"), |
| 273 network_manager_api_( | |
| 274 base::Bind(&NetworkChangeNotifier:: | |
| 275 NotifyObserversOfConnectionTypeChange), | |
| 276 bus), | |
| 277 address_tracker_( | 45 address_tracker_( |
| 278 base::Bind(&NetworkChangeNotifier:: | 46 base::Bind(&NetworkChangeNotifier:: |
| 279 NotifyObserversOfIPAddressChange)) { | 47 NotifyObserversOfIPAddressChange), |
| 48 base::Bind(&NetworkChangeNotifier:: |
| 49 NotifyObserversOfConnectionTypeChange)) { |
| 280 } | 50 } |
| 281 | 51 |
| 282 NetworkChangeNotifierLinux::Thread::~Thread() { | 52 NetworkChangeNotifierLinux::Thread::~Thread() { |
| 283 DCHECK(!Thread::IsRunning()); | 53 DCHECK(!Thread::IsRunning()); |
| 284 } | 54 } |
| 285 | 55 |
| 286 void NetworkChangeNotifierLinux::Thread::Init() { | 56 void NetworkChangeNotifierLinux::Thread::Init() { |
| 287 network_manager_api_.Init(); | |
| 288 address_tracker_.Init(); | 57 address_tracker_.Init(); |
| 289 dns_config_service_ = DnsConfigService::CreateSystemService(); | 58 dns_config_service_ = DnsConfigService::CreateSystemService(); |
| 290 dns_config_service_->WatchConfig( | 59 dns_config_service_->WatchConfig( |
| 291 base::Bind(&NetworkChangeNotifier::SetDnsConfig)); | 60 base::Bind(&NetworkChangeNotifier::SetDnsConfig)); |
| 292 } | 61 } |
| 293 | 62 |
| 294 void NetworkChangeNotifierLinux::Thread::CleanUp() { | 63 void NetworkChangeNotifierLinux::Thread::CleanUp() { |
| 295 network_manager_api_.CleanUp(); | |
| 296 dns_config_service_.reset(); | 64 dns_config_service_.reset(); |
| 297 } | 65 } |
| 298 | 66 |
| 299 NetworkChangeNotifierLinux* NetworkChangeNotifierLinux::Create() { | 67 NetworkChangeNotifierLinux* NetworkChangeNotifierLinux::Create() { |
| 300 return new NetworkChangeNotifierLinux(NULL); | 68 return new NetworkChangeNotifierLinux(); |
| 301 } | 69 } |
| 302 | 70 |
| 303 NetworkChangeNotifierLinux* NetworkChangeNotifierLinux::CreateForTest( | 71 NetworkChangeNotifierLinux::NetworkChangeNotifierLinux() |
| 304 dbus::Bus* bus) { | 72 : notifier_thread_(new Thread()) { |
| 305 return new NetworkChangeNotifierLinux(bus); | |
| 306 } | |
| 307 | |
| 308 NetworkChangeNotifierLinux::NetworkChangeNotifierLinux(dbus::Bus* bus) | |
| 309 : notifier_thread_(new Thread(bus)) { | |
| 310 // We create this notifier thread because the notification implementation | 73 // We create this notifier thread because the notification implementation |
| 311 // needs a MessageLoopForIO, and there's no guarantee that | 74 // needs a MessageLoopForIO, and there's no guarantee that |
| 312 // MessageLoop::current() meets that criterion. | 75 // MessageLoop::current() meets that criterion. |
| 313 base::Thread::Options thread_options(MessageLoop::TYPE_IO, 0); | 76 base::Thread::Options thread_options(MessageLoop::TYPE_IO, 0); |
| 314 notifier_thread_->StartWithOptions(thread_options); | 77 notifier_thread_->StartWithOptions(thread_options); |
| 315 } | 78 } |
| 316 | 79 |
| 317 NetworkChangeNotifierLinux::~NetworkChangeNotifierLinux() { | 80 NetworkChangeNotifierLinux::~NetworkChangeNotifierLinux() { |
| 318 // Stopping from here allows us to sanity- check that the notifier | 81 // Stopping from here allows us to sanity- check that the notifier |
| 319 // thread shut down properly. | 82 // thread shut down properly. |
| 320 notifier_thread_->Stop(); | 83 notifier_thread_->Stop(); |
| 321 } | 84 } |
| 322 | 85 |
| 323 NetworkChangeNotifier::ConnectionType | 86 NetworkChangeNotifier::ConnectionType |
| 324 NetworkChangeNotifierLinux::GetCurrentConnectionType() const { | 87 NetworkChangeNotifierLinux::GetCurrentConnectionType() const { |
| 325 return notifier_thread_->GetCurrentConnectionType(); | 88 return notifier_thread_->GetCurrentConnectionType(); |
| 326 } | 89 } |
| 327 | 90 |
| 328 const internal::AddressTrackerLinux* | 91 const internal::AddressTrackerLinux* |
| 329 NetworkChangeNotifierLinux::GetAddressTrackerInternal() const { | 92 NetworkChangeNotifierLinux::GetAddressTrackerInternal() const { |
| 330 return notifier_thread_->address_tracker(); | 93 return notifier_thread_->address_tracker(); |
| 331 } | 94 } |
| 332 | 95 |
| 333 } // namespace net | 96 } // namespace net |
| OLD | NEW |