Home | History | Annotate | Download | only in geolocation
      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