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