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.
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).
9 #include "content/browser/geolocation/wifi_data_provider_linux.h"
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"
16 #include "dbus/message.h"
17 #include "dbus/object_path.h"
18 #include "dbus/object_proxy.h"
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
28 const char kNetworkManagerServiceName
[] = "org.freedesktop.NetworkManager";
29 const char kNetworkManagerPath
[] = "/org/freedesktop/NetworkManager";
30 const char kNetworkManagerInterface
[] = "org.freedesktop.NetworkManager";
32 // From http://projects.gnome.org/NetworkManager/developers/spec.html
33 enum { NM_DEVICE_TYPE_WIFI
= 2 };
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
{
42 NetworkManagerWlanApi();
43 ~NetworkManagerWlanApi() override
;
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.
50 // Similar to Init() but can inject the bus object. Used for testing.
51 bool InitWithBus(dbus::Bus
* bus
);
53 // WifiDataProviderCommon::WlanApiInterface
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 bool GetAccessPointData(WifiData::AccessPointDataSet
* data
) override
;
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
);
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
);
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
);
77 scoped_refptr
<dbus::Bus
> system_bus_
;
78 dbus::ObjectProxy
* network_manager_proxy_
;
80 DISALLOW_COPY_AND_ASSIGN(NetworkManagerWlanApi
);
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)
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
96 NetworkManagerWlanApi::NetworkManagerWlanApi()
97 : network_manager_proxy_(NULL
) {
100 NetworkManagerWlanApi::~NetworkManagerWlanApi() {
101 // Close the connection.
102 system_bus_
->ShutdownAndBlock();
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
));
112 bool NetworkManagerWlanApi::InitWithBus(dbus::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
;
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";
132 int success_count
= 0;
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();
140 dbus::ObjectProxy
* device_proxy
=
141 system_bus_
->GetObjectProxy(kNetworkManagerServiceName
,
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(
151 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
153 LOG(WARNING
) << "Failed to get the device type for "
154 << device_path
.value();
155 continue; // Check the next device.
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.
164 VLOG(1) << "Device type: " << device_type
;
166 if (device_type
== NM_DEVICE_TYPE_WIFI
) { // Found a wlan adapter
167 if (GetAccessPointsForAdapter(device_path
, data
))
173 // At least one successfull scan overrides any other adapter reporting error.
174 return success_count
|| fail_count
== 0;
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(
183 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
185 LOG(WARNING
) << "Failed to get the device list";
189 dbus::MessageReader
reader(response
.get());
190 if (!reader
.PopArrayOfObjectPaths(device_paths
)) {
191 LOG(WARNING
) << "Unexpected response: " << response
->ToString();
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
,
205 dbus::MethodCall
method_call(
206 "org.freedesktop.NetworkManager.Device.Wireless",
208 scoped_ptr
<dbus::Response
> response(
209 device_proxy
->CallMethodAndBlock(
211 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
213 LOG(WARNING
) << "Failed to get access points data for "
214 << adapter_path
.value();
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();
225 VLOG(1) << "Wireless adapter " << adapter_path
.value() << " found "
226 << access_point_paths
.size() << " access points.";
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();
232 dbus::ObjectProxy
* access_point_proxy
=
233 system_bus_
->GetObjectProxy(kNetworkManagerServiceName
,
236 AccessPointData access_point_data
;
238 scoped_ptr
<dbus::Response
> response(
239 GetAccessPointProperty(access_point_proxy
, "Ssid"));
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();
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();
257 std::string
ssid(ssid_bytes
, ssid_bytes
+ ssid_length
);
258 access_point_data
.ssid
= base::UTF8ToUTF16(ssid
);
261 { // Read the mac address
262 scoped_ptr
<dbus::Response
> response(
263 GetAccessPointProperty(access_point_proxy
, "HwAddress"));
266 dbus::MessageReader
reader(response
.get());
268 if (!reader
.PopVariantOfString(&mac
)) {
269 LOG(WARNING
) << "Unexpected response for " << access_point_path
.value()
270 << ": " << response
->ToString();
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
);
281 access_point_data
.mac_address
= MacAddressAsString16(&mac_bytes
[0]);
285 { // Read signal strength.
286 scoped_ptr
<dbus::Response
> response(
287 GetAccessPointProperty(access_point_proxy
, "Strength"));
290 dbus::MessageReader
reader(response
.get());
292 if (!reader
.PopVariantOfByte(&strength
)) {
293 LOG(WARNING
) << "Unexpected response for " << access_point_path
.value()
294 << ": " << response
->ToString();
297 // Convert strength as a percentage into dBs.
298 access_point_data
.radio_signal_strength
= -100 + strength
/ 2;
301 { // Read the channel
302 scoped_ptr
<dbus::Response
> response(
303 GetAccessPointProperty(access_point_proxy
, "Frequency"));
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();
314 // NetworkManager returns frequency in MHz.
315 access_point_data
.channel
=
316 frquency_in_khz_to_channel(frequency
* 1000);
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
;
324 data
->insert(access_point_data
);
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(
338 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
);
340 LOG(WARNING
) << "Failed to get property for " << property_name
;
342 return response
.Pass();
348 WifiDataProvider
* WifiDataProviderManager::DefaultFactoryFunction() {
349 return new WifiDataProviderLinux();
352 WifiDataProviderLinux::WifiDataProviderLinux() {
355 WifiDataProviderLinux::~WifiDataProviderLinux() {
358 WifiDataProviderCommon::WlanApiInterface
*
359 WifiDataProviderLinux::NewWlanApi() {
360 scoped_ptr
<NetworkManagerWlanApi
> wlan_api(new NetworkManagerWlanApi
);
361 if (wlan_api
->Init())
362 return wlan_api
.release();
366 WifiPollingPolicy
* WifiDataProviderLinux::NewPollingPolicy() {
367 return new GenericWifiPollingPolicy
<kDefaultPollingIntervalMilliseconds
,
368 kNoChangePollingIntervalMilliseconds
,
369 kTwoNoChangePollingIntervalMilliseconds
,
370 kNoWifiPollingIntervalMilliseconds
>;
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();
381 } // namespace content