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
20namespace content {
21namespace {
22// The time periods between successive polls of the wifi data.
23const int kDefaultPollingIntervalMilliseconds = 10 * 1000;  // 10s
24const int kNoChangePollingIntervalMilliseconds = 2 * 60 * 1000;  // 2 mins
25const int kTwoNoChangePollingIntervalMilliseconds = 10 * 60 * 1000;  // 10 mins
26const int kNoWifiPollingIntervalMilliseconds = 20 * 1000; // 20s
27
28const char kNetworkManagerServiceName[] = "org.freedesktop.NetworkManager";
29const char kNetworkManagerPath[] = "/org/freedesktop/NetworkManager";
30const char kNetworkManagerInterface[] = "org.freedesktop.NetworkManager";
31
32// From http://projects.gnome.org/NetworkManager/developers/spec.html
33enum { 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).
40class 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).
85int 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
96NetworkManagerWlanApi::NetworkManagerWlanApi()
97    : network_manager_proxy_(NULL) {
98}
99
100NetworkManagerWlanApi::~NetworkManagerWlanApi() {
101  // Close the connection.
102  system_bus_->ShutdownAndBlock();
103}
104
105bool 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
112bool 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
125bool 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
177bool 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
198bool 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
329scoped_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
348WifiDataProvider* WifiDataProviderManager::DefaultFactoryFunction() {
349  return new WifiDataProviderLinux();
350}
351
352WifiDataProviderLinux::WifiDataProviderLinux() {
353}
354
355WifiDataProviderLinux::~WifiDataProviderLinux() {
356}
357
358WifiDataProviderCommon::WlanApiInterface*
359WifiDataProviderLinux::NewWlanApi() {
360  scoped_ptr<NetworkManagerWlanApi> wlan_api(new NetworkManagerWlanApi);
361  if (wlan_api->Init())
362    return wlan_api.release();
363  return NULL;
364}
365
366WifiPollingPolicy* WifiDataProviderLinux::NewPollingPolicy() {
367  return new GenericWifiPollingPolicy<kDefaultPollingIntervalMilliseconds,
368                                      kNoChangePollingIntervalMilliseconds,
369                                      kTwoNoChangePollingIntervalMilliseconds,
370                                      kNoWifiPollingIntervalMilliseconds>;
371}
372
373WifiDataProviderCommon::WlanApiInterface*
374WifiDataProviderLinux::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