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 "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 template<>
    348 WifiDataProviderImplBase* WifiDataProvider::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 PollingPolicyInterface* WifiDataProviderLinux::NewPollingPolicy() {
    367   return new GenericPollingPolicy<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