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"
15 #include "dbus/message.h"
16 #include "dbus/object_path.h"
17 #include "dbus/object_proxy.h"
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
27 const char kNetworkManagerServiceName
[] = "org.freedesktop.NetworkManager";
28 const char kNetworkManagerPath
[] = "/org/freedesktop/NetworkManager";
29 const char kNetworkManagerInterface
[] = "org.freedesktop.NetworkManager";
31 // From http://projects.gnome.org/NetworkManager/developers/spec.html
32 enum { NM_DEVICE_TYPE_WIFI
= 2 };
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
{
41 NetworkManagerWlanApi();
42 virtual ~NetworkManagerWlanApi();
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.
49 // Similar to Init() but can inject the bus object. Used for testing.
50 bool InitWithBus(dbus::Bus
* bus
);
52 // WifiDataProviderCommon::WlanApiInterface
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
;
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
);
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
);
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
);
76 scoped_refptr
<dbus::Bus
> system_bus_
;
77 dbus::ObjectProxy
* network_manager_proxy_
;
79 DISALLOW_COPY_AND_ASSIGN(NetworkManagerWlanApi
);
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)
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
95 NetworkManagerWlanApi::NetworkManagerWlanApi()
96 : network_manager_proxy_(NULL
) {
99 NetworkManagerWlanApi::~NetworkManagerWlanApi() {
100 // Close the connection.
101 system_bus_
->ShutdownAndBlock();
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
));
111 bool NetworkManagerWlanApi::InitWithBus(dbus::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
;
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";
131 int success_count
= 0;
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();
139 dbus::ObjectProxy
* device_proxy
=
140 system_bus_
->GetObjectProxy(kNetworkManagerServiceName
,
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(
150 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
152 LOG(WARNING
) << "Failed to get the device type for "
153 << device_path
.value();
154 continue; // Check the next device.
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.
163 VLOG(1) << "Device type: " << device_type
;
165 if (device_type
== NM_DEVICE_TYPE_WIFI
) { // Found a wlan adapter
166 if (GetAccessPointsForAdapter(device_path
, data
))
172 // At least one successfull scan overrides any other adapter reporting error.
173 return success_count
|| fail_count
== 0;
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(
182 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
184 LOG(WARNING
) << "Failed to get the device list";
188 dbus::MessageReader
reader(response
.get());
189 if (!reader
.PopArrayOfObjectPaths(device_paths
)) {
190 LOG(WARNING
) << "Unexpected response: " << response
->ToString();
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
,
204 dbus::MethodCall
method_call(
205 "org.freedesktop.NetworkManager.Device.Wireless",
207 scoped_ptr
<dbus::Response
> response(
208 device_proxy
->CallMethodAndBlock(
210 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
212 LOG(WARNING
) << "Failed to get access points data for "
213 << adapter_path
.value();
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();
224 VLOG(1) << "Wireless adapter " << adapter_path
.value() << " found "
225 << access_point_paths
.size() << " access points.";
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();
231 dbus::ObjectProxy
* access_point_proxy
=
232 system_bus_
->GetObjectProxy(kNetworkManagerServiceName
,
235 AccessPointData access_point_data
;
237 scoped_ptr
<dbus::Response
> response(
238 GetAccessPointProperty(access_point_proxy
, "Ssid"));
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();
249 const 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();
256 std::string
ssid(ssid_bytes
, ssid_bytes
+ ssid_length
);
257 access_point_data
.ssid
= base::UTF8ToUTF16(ssid
);
260 { // Read the mac address
261 scoped_ptr
<dbus::Response
> response(
262 GetAccessPointProperty(access_point_proxy
, "HwAddress"));
265 dbus::MessageReader
reader(response
.get());
267 if (!reader
.PopVariantOfString(&mac
)) {
268 LOG(WARNING
) << "Unexpected response for " << access_point_path
.value()
269 << ": " << response
->ToString();
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
= base::UTF8ToUTF16(mac
);
280 access_point_data
.mac_address
= MacAddressAsString16(&mac_bytes
[0]);
284 { // Read signal strength.
285 scoped_ptr
<dbus::Response
> response(
286 GetAccessPointProperty(access_point_proxy
, "Strength"));
289 dbus::MessageReader
reader(response
.get());
291 if (!reader
.PopVariantOfByte(&strength
)) {
292 LOG(WARNING
) << "Unexpected response for " << access_point_path
.value()
293 << ": " << response
->ToString();
296 // Convert strength as a percentage into dBs.
297 access_point_data
.radio_signal_strength
= -100 + strength
/ 2;
300 { // Read the channel
301 scoped_ptr
<dbus::Response
> response(
302 GetAccessPointProperty(access_point_proxy
, "Frequency"));
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();
313 // NetworkManager returns frequency in MHz.
314 access_point_data
.channel
=
315 frquency_in_khz_to_channel(frequency
* 1000);
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
;
323 data
->insert(access_point_data
);
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(
337 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
);
339 LOG(WARNING
) << "Failed to get property for " << property_name
;
341 return response
.Pass();
347 WifiDataProviderImplBase
* WifiDataProvider::DefaultFactoryFunction() {
348 return new WifiDataProviderLinux();
351 WifiDataProviderLinux::WifiDataProviderLinux() {
354 WifiDataProviderLinux::~WifiDataProviderLinux() {
357 WifiDataProviderCommon::WlanApiInterface
*
358 WifiDataProviderLinux::NewWlanApi() {
359 scoped_ptr
<NetworkManagerWlanApi
> wlan_api(new NetworkManagerWlanApi
);
360 if (wlan_api
->Init())
361 return wlan_api
.release();
365 WifiPollingPolicy
* WifiDataProviderLinux::NewPollingPolicy() {
366 return new GenericWifiPollingPolicy
<kDefaultPollingIntervalMilliseconds
,
367 kNoChangePollingIntervalMilliseconds
,
368 kTwoNoChangePollingIntervalMilliseconds
,
369 kNoWifiPollingIntervalMilliseconds
>;
372 WifiDataProviderCommon::WlanApiInterface
*
373 WifiDataProviderLinux::NewWlanApiForTesting(dbus::Bus
* bus
) {
374 scoped_ptr
<NetworkManagerWlanApi
> wlan_api(new NetworkManagerWlanApi
);
375 if (wlan_api
->InitWithBus(bus
))
376 return wlan_api
.release();
380 } // namespace content