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
19namespace content {
20namespace {
21// The time periods between successive polls of the wifi data.
22const int kDefaultPollingIntervalMilliseconds = 10 * 1000;  // 10s
23const int kNoChangePollingIntervalMilliseconds = 2 * 60 * 1000;  // 2 mins
24const int kTwoNoChangePollingIntervalMilliseconds = 10 * 60 * 1000;  // 10 mins
25const int kNoWifiPollingIntervalMilliseconds = 20 * 1000; // 20s
26
27const char kNetworkManagerServiceName[] = "org.freedesktop.NetworkManager";
28const char kNetworkManagerPath[] = "/org/freedesktop/NetworkManager";
29const char kNetworkManagerInterface[] = "org.freedesktop.NetworkManager";
30
31// From http://projects.gnome.org/NetworkManager/developers/spec.html
32enum { 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).
39class 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).
84int 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
95NetworkManagerWlanApi::NetworkManagerWlanApi()
96    : network_manager_proxy_(NULL) {
97}
98
99NetworkManagerWlanApi::~NetworkManagerWlanApi() {
100  // Close the connection.
101  system_bus_->ShutdownAndBlock();
102}
103
104bool 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
111bool 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
124bool 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
176bool 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
197bool 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
328scoped_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
347template<>
348WifiDataProviderImplBase* WifiDataProvider::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
366PollingPolicyInterface* WifiDataProviderLinux::NewPollingPolicy() {
367  return new GenericPollingPolicy<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