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 5 // Provides wifi scan API binding for suitable for typical linux distributions. 6 // Currently, only the NetworkManager API is used, accessed via D-Bus (in turn 7 // accessed via the GLib wrapper). 8 9 #include "content/browser/geolocation/wifi_data_provider_linux.h" 10 11 #include "base/memory/scoped_ptr.h" 12 #include "base/strings/string_number_conversions.h" 13 #include "base/strings/utf_string_conversions.h" 14 #include "content/browser/geolocation/wifi_data_provider_manager.h" 15 #include "dbus/bus.h" 16 #include "dbus/message.h" 17 #include "dbus/object_path.h" 18 #include "dbus/object_proxy.h" 19 20 namespace content { 21 namespace { 22 // The time periods between successive polls of the wifi data. 23 const int kDefaultPollingIntervalMilliseconds = 10 * 1000; // 10s 24 const int kNoChangePollingIntervalMilliseconds = 2 * 60 * 1000; // 2 mins 25 const int kTwoNoChangePollingIntervalMilliseconds = 10 * 60 * 1000; // 10 mins 26 const int kNoWifiPollingIntervalMilliseconds = 20 * 1000; // 20s 27 28 const char kNetworkManagerServiceName[] = "org.freedesktop.NetworkManager"; 29 const char kNetworkManagerPath[] = "/org/freedesktop/NetworkManager"; 30 const char kNetworkManagerInterface[] = "org.freedesktop.NetworkManager"; 31 32 // From http://projects.gnome.org/NetworkManager/developers/spec.html 33 enum { NM_DEVICE_TYPE_WIFI = 2 }; 34 35 // Wifi API binding to NetworkManager, to allow reuse of the polling behavior 36 // defined in WifiDataProviderCommon. 37 // TODO(joth): NetworkManager also allows for notification based handling, 38 // however this will require reworking of the threading code to run a GLib 39 // event loop (GMainLoop). 40 class NetworkManagerWlanApi : public WifiDataProviderCommon::WlanApiInterface { 41 public: 42 NetworkManagerWlanApi(); 43 virtual ~NetworkManagerWlanApi(); 44 45 // Must be called before any other interface method. Will return false if the 46 // NetworkManager session cannot be created (e.g. not present on this distro), 47 // in which case no other method may be called. 48 bool Init(); 49 50 // Similar to Init() but can inject the bus object. Used for testing. 51 bool InitWithBus(dbus::Bus* bus); 52 53 // WifiDataProviderCommon::WlanApiInterface 54 // 55 // This function makes blocking D-Bus calls, but it's totally fine as 56 // the code runs in "Geolocation" thread, not the browser's UI thread. 57 virtual bool GetAccessPointData(WifiData::AccessPointDataSet* data) OVERRIDE; 58 59 private: 60 // Enumerates the list of available network adapter devices known to 61 // NetworkManager. Return true on success. 62 bool GetAdapterDeviceList(std::vector<dbus::ObjectPath>* device_paths); 63 64 // Given the NetworkManager path to a wireless adapater, dumps the wifi scan 65 // results and appends them to |data|. Returns false if a fatal error is 66 // encountered such that the data set could not be populated. 67 bool GetAccessPointsForAdapter(const dbus::ObjectPath& adapter_path, 68 WifiData::AccessPointDataSet* data); 69 70 // Internal method used by |GetAccessPointsForAdapter|, given a wifi access 71 // point proxy retrieves the named property and returns it. Returns NULL in 72 // a scoped_ptr if the property could not be read. 73 scoped_ptr<dbus::Response> GetAccessPointProperty( 74 dbus::ObjectProxy* proxy, 75 const std::string& property_name); 76 77 scoped_refptr<dbus::Bus> system_bus_; 78 dbus::ObjectProxy* network_manager_proxy_; 79 80 DISALLOW_COPY_AND_ASSIGN(NetworkManagerWlanApi); 81 }; 82 83 // Convert a wifi frequency to the corresponding channel. Adapted from 84 // geolocaiton/wifilib.cc in googleclient (internal to google). 85 int frquency_in_khz_to_channel(int frequency_khz) { 86 if (frequency_khz >= 2412000 && frequency_khz <= 2472000) // Channels 1-13. 87 return (frequency_khz - 2407000) / 5000; 88 if (frequency_khz == 2484000) 89 return 14; 90 if (frequency_khz > 5000000 && frequency_khz < 6000000) // .11a bands. 91 return (frequency_khz - 5000000) / 5000; 92 // Ignore everything else. 93 return AccessPointData().channel; // invalid channel 94 } 95 96 NetworkManagerWlanApi::NetworkManagerWlanApi() 97 : network_manager_proxy_(NULL) { 98 } 99 100 NetworkManagerWlanApi::~NetworkManagerWlanApi() { 101 // Close the connection. 102 system_bus_->ShutdownAndBlock(); 103 } 104 105 bool NetworkManagerWlanApi::Init() { 106 dbus::Bus::Options options; 107 options.bus_type = dbus::Bus::SYSTEM; 108 options.connection_type = dbus::Bus::PRIVATE; 109 return InitWithBus(new dbus::Bus(options)); 110 } 111 112 bool NetworkManagerWlanApi::InitWithBus(dbus::Bus* bus) { 113 system_bus_ = bus; 114 // system_bus_ will own all object proxies created from the bus. 115 network_manager_proxy_ = 116 system_bus_->GetObjectProxy(kNetworkManagerServiceName, 117 dbus::ObjectPath(kNetworkManagerPath)); 118 // Validate the proxy object by checking we can enumerate devices. 119 std::vector<dbus::ObjectPath> adapter_paths; 120 const bool success = GetAdapterDeviceList(&adapter_paths); 121 VLOG(1) << "Init() result: " << success; 122 return success; 123 } 124 125 bool NetworkManagerWlanApi::GetAccessPointData( 126 WifiData::AccessPointDataSet* data) { 127 std::vector<dbus::ObjectPath> device_paths; 128 if (!GetAdapterDeviceList(&device_paths)) { 129 LOG(WARNING) << "Could not enumerate access points"; 130 return false; 131 } 132 int success_count = 0; 133 int fail_count = 0; 134 135 // Iterate the devices, getting APs for each wireless adapter found 136 for (size_t i = 0; i < device_paths.size(); ++i) { 137 const dbus::ObjectPath& device_path = device_paths[i]; 138 VLOG(1) << "Checking device: " << device_path.value(); 139 140 dbus::ObjectProxy* device_proxy = 141 system_bus_->GetObjectProxy(kNetworkManagerServiceName, 142 device_path); 143 144 dbus::MethodCall method_call(DBUS_INTERFACE_PROPERTIES, "Get"); 145 dbus::MessageWriter builder(&method_call); 146 builder.AppendString("org.freedesktop.NetworkManager.Device"); 147 builder.AppendString("DeviceType"); 148 scoped_ptr<dbus::Response> response( 149 device_proxy->CallMethodAndBlock( 150 &method_call, 151 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); 152 if (!response) { 153 LOG(WARNING) << "Failed to get the device type for " 154 << device_path.value(); 155 continue; // Check the next device. 156 } 157 dbus::MessageReader reader(response.get()); 158 uint32 device_type = 0; 159 if (!reader.PopVariantOfUint32(&device_type)) { 160 LOG(WARNING) << "Unexpected response for " << device_type << ": " 161 << response->ToString(); 162 continue; // Check the next device. 163 } 164 VLOG(1) << "Device type: " << device_type; 165 166 if (device_type == NM_DEVICE_TYPE_WIFI) { // Found a wlan adapter 167 if (GetAccessPointsForAdapter(device_path, data)) 168 ++success_count; 169 else 170 ++fail_count; 171 } 172 } 173 // At least one successfull scan overrides any other adapter reporting error. 174 return success_count || fail_count == 0; 175 } 176 177 bool NetworkManagerWlanApi::GetAdapterDeviceList( 178 std::vector<dbus::ObjectPath>* device_paths) { 179 dbus::MethodCall method_call(kNetworkManagerInterface, "GetDevices"); 180 scoped_ptr<dbus::Response> response( 181 network_manager_proxy_->CallMethodAndBlock( 182 &method_call, 183 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); 184 if (!response) { 185 LOG(WARNING) << "Failed to get the device list"; 186 return false; 187 } 188 189 dbus::MessageReader reader(response.get()); 190 if (!reader.PopArrayOfObjectPaths(device_paths)) { 191 LOG(WARNING) << "Unexpected response: " << response->ToString(); 192 return false; 193 } 194 return true; 195 } 196 197 198 bool NetworkManagerWlanApi::GetAccessPointsForAdapter( 199 const dbus::ObjectPath& adapter_path, WifiData::AccessPointDataSet* data) { 200 // Create a proxy object for this wifi adapter, and ask it to do a scan 201 // (or at least, dump its scan results). 202 dbus::ObjectProxy* device_proxy = 203 system_bus_->GetObjectProxy(kNetworkManagerServiceName, 204 adapter_path); 205 dbus::MethodCall method_call( 206 "org.freedesktop.NetworkManager.Device.Wireless", 207 "GetAccessPoints"); 208 scoped_ptr<dbus::Response> response( 209 device_proxy->CallMethodAndBlock( 210 &method_call, 211 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); 212 if (!response) { 213 LOG(WARNING) << "Failed to get access points data for " 214 << adapter_path.value(); 215 return false; 216 } 217 dbus::MessageReader reader(response.get()); 218 std::vector<dbus::ObjectPath> access_point_paths; 219 if (!reader.PopArrayOfObjectPaths(&access_point_paths)) { 220 LOG(WARNING) << "Unexpected response for " << adapter_path.value() << ": " 221 << response->ToString(); 222 return false; 223 } 224 225 VLOG(1) << "Wireless adapter " << adapter_path.value() << " found " 226 << access_point_paths.size() << " access points."; 227 228 for (size_t i = 0; i < access_point_paths.size(); ++i) { 229 const dbus::ObjectPath& access_point_path = access_point_paths[i]; 230 VLOG(1) << "Checking access point: " << access_point_path.value(); 231 232 dbus::ObjectProxy* access_point_proxy = 233 system_bus_->GetObjectProxy(kNetworkManagerServiceName, 234 access_point_path); 235 236 AccessPointData access_point_data; 237 { 238 scoped_ptr<dbus::Response> response( 239 GetAccessPointProperty(access_point_proxy, "Ssid")); 240 if (!response) 241 continue; 242 // The response should contain a variant that contains an array of bytes. 243 dbus::MessageReader reader(response.get()); 244 dbus::MessageReader variant_reader(response.get()); 245 if (!reader.PopVariant(&variant_reader)) { 246 LOG(WARNING) << "Unexpected response for " << access_point_path.value() 247 << ": " << response->ToString(); 248 continue; 249 } 250 const uint8* ssid_bytes = NULL; 251 size_t ssid_length = 0; 252 if (!variant_reader.PopArrayOfBytes(&ssid_bytes, &ssid_length)) { 253 LOG(WARNING) << "Unexpected response for " << access_point_path.value() 254 << ": " << response->ToString(); 255 continue; 256 } 257 std::string ssid(ssid_bytes, ssid_bytes + ssid_length); 258 access_point_data.ssid = base::UTF8ToUTF16(ssid); 259 } 260 261 { // Read the mac address 262 scoped_ptr<dbus::Response> response( 263 GetAccessPointProperty(access_point_proxy, "HwAddress")); 264 if (!response) 265 continue; 266 dbus::MessageReader reader(response.get()); 267 std::string mac; 268 if (!reader.PopVariantOfString(&mac)) { 269 LOG(WARNING) << "Unexpected response for " << access_point_path.value() 270 << ": " << response->ToString(); 271 continue; 272 } 273 274 ReplaceSubstringsAfterOffset(&mac, 0U, ":", std::string()); 275 std::vector<uint8> mac_bytes; 276 if (!base::HexStringToBytes(mac, &mac_bytes) || mac_bytes.size() != 6) { 277 LOG(WARNING) << "Can't parse mac address (found " << mac_bytes.size() 278 << " bytes) so using raw string: " << mac; 279 access_point_data.mac_address = base::UTF8ToUTF16(mac); 280 } else { 281 access_point_data.mac_address = MacAddressAsString16(&mac_bytes[0]); 282 } 283 } 284 285 { // Read signal strength. 286 scoped_ptr<dbus::Response> response( 287 GetAccessPointProperty(access_point_proxy, "Strength")); 288 if (!response) 289 continue; 290 dbus::MessageReader reader(response.get()); 291 uint8 strength = 0; 292 if (!reader.PopVariantOfByte(&strength)) { 293 LOG(WARNING) << "Unexpected response for " << access_point_path.value() 294 << ": " << response->ToString(); 295 continue; 296 } 297 // Convert strength as a percentage into dBs. 298 access_point_data.radio_signal_strength = -100 + strength / 2; 299 } 300 301 { // Read the channel 302 scoped_ptr<dbus::Response> response( 303 GetAccessPointProperty(access_point_proxy, "Frequency")); 304 if (!response) 305 continue; 306 dbus::MessageReader reader(response.get()); 307 uint32 frequency = 0; 308 if (!reader.PopVariantOfUint32(&frequency)) { 309 LOG(WARNING) << "Unexpected response for " << access_point_path.value() 310 << ": " << response->ToString(); 311 continue; 312 } 313 314 // NetworkManager returns frequency in MHz. 315 access_point_data.channel = 316 frquency_in_khz_to_channel(frequency * 1000); 317 } 318 VLOG(1) << "Access point data of " << access_point_path.value() << ": " 319 << "SSID: " << access_point_data.ssid << ", " 320 << "MAC: " << access_point_data.mac_address << ", " 321 << "Strength: " << access_point_data.radio_signal_strength << ", " 322 << "Channel: " << access_point_data.channel; 323 324 data->insert(access_point_data); 325 } 326 return true; 327 } 328 329 scoped_ptr<dbus::Response> NetworkManagerWlanApi::GetAccessPointProperty( 330 dbus::ObjectProxy* access_point_proxy, 331 const std::string& property_name) { 332 dbus::MethodCall method_call(DBUS_INTERFACE_PROPERTIES, "Get"); 333 dbus::MessageWriter builder(&method_call); 334 builder.AppendString("org.freedesktop.NetworkManager.AccessPoint"); 335 builder.AppendString(property_name); 336 scoped_ptr<dbus::Response> response = access_point_proxy->CallMethodAndBlock( 337 &method_call, 338 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT); 339 if (!response) { 340 LOG(WARNING) << "Failed to get property for " << property_name; 341 } 342 return response.Pass(); 343 } 344 345 } // namespace 346 347 // static 348 WifiDataProvider* WifiDataProviderManager::DefaultFactoryFunction() { 349 return new WifiDataProviderLinux(); 350 } 351 352 WifiDataProviderLinux::WifiDataProviderLinux() { 353 } 354 355 WifiDataProviderLinux::~WifiDataProviderLinux() { 356 } 357 358 WifiDataProviderCommon::WlanApiInterface* 359 WifiDataProviderLinux::NewWlanApi() { 360 scoped_ptr<NetworkManagerWlanApi> wlan_api(new NetworkManagerWlanApi); 361 if (wlan_api->Init()) 362 return wlan_api.release(); 363 return NULL; 364 } 365 366 WifiPollingPolicy* WifiDataProviderLinux::NewPollingPolicy() { 367 return new GenericWifiPollingPolicy<kDefaultPollingIntervalMilliseconds, 368 kNoChangePollingIntervalMilliseconds, 369 kTwoNoChangePollingIntervalMilliseconds, 370 kNoWifiPollingIntervalMilliseconds>; 371 } 372 373 WifiDataProviderCommon::WlanApiInterface* 374 WifiDataProviderLinux::NewWlanApiForTesting(dbus::Bus* bus) { 375 scoped_ptr<NetworkManagerWlanApi> wlan_api(new NetworkManagerWlanApi); 376 if (wlan_api->InitWithBus(bus)) 377 return wlan_api.release(); 378 return NULL; 379 } 380 381 } // namespace content 382