OLD | NEW |
| (Empty) |
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 <stddef.h> | |
12 #include <stdint.h> | |
13 | |
14 #include <memory> | |
15 | |
16 #include "base/macros.h" | |
17 #include "base/strings/string_number_conversions.h" | |
18 #include "base/strings/utf_string_conversions.h" | |
19 #include "content/browser/geolocation/wifi_data_provider_manager.h" | |
20 #include "dbus/bus.h" | |
21 #include "dbus/message.h" | |
22 #include "dbus/object_path.h" | |
23 #include "dbus/object_proxy.h" | |
24 | |
25 namespace content { | |
26 namespace { | |
27 // The time periods between successive polls of the wifi data. | |
28 const int kDefaultPollingIntervalMilliseconds = 10 * 1000; // 10s | |
29 const int kNoChangePollingIntervalMilliseconds = 2 * 60 * 1000; // 2 mins | |
30 const int kTwoNoChangePollingIntervalMilliseconds = 10 * 60 * 1000; // 10 mins | |
31 const int kNoWifiPollingIntervalMilliseconds = 20 * 1000; // 20s | |
32 | |
33 const char kNetworkManagerServiceName[] = "org.freedesktop.NetworkManager"; | |
34 const char kNetworkManagerPath[] = "/org/freedesktop/NetworkManager"; | |
35 const char kNetworkManagerInterface[] = "org.freedesktop.NetworkManager"; | |
36 | |
37 // From http://projects.gnome.org/NetworkManager/developers/spec.html | |
38 enum { NM_DEVICE_TYPE_WIFI = 2 }; | |
39 | |
40 // Wifi API binding to NetworkManager, to allow reuse of the polling behavior | |
41 // defined in WifiDataProviderCommon. | |
42 // TODO(joth): NetworkManager also allows for notification based handling, | |
43 // however this will require reworking of the threading code to run a GLib | |
44 // event loop (GMainLoop). | |
45 class NetworkManagerWlanApi : public WifiDataProviderCommon::WlanApiInterface { | |
46 public: | |
47 NetworkManagerWlanApi(); | |
48 ~NetworkManagerWlanApi() override; | |
49 | |
50 // Must be called before any other interface method. Will return false if the | |
51 // NetworkManager session cannot be created (e.g. not present on this distro), | |
52 // in which case no other method may be called. | |
53 bool Init(); | |
54 | |
55 // Similar to Init() but can inject the bus object. Used for testing. | |
56 bool InitWithBus(dbus::Bus* bus); | |
57 | |
58 // WifiDataProviderCommon::WlanApiInterface | |
59 // | |
60 // This function makes blocking D-Bus calls, but it's totally fine as | |
61 // the code runs in "Geolocation" thread, not the browser's UI thread. | |
62 bool GetAccessPointData(WifiData::AccessPointDataSet* data) override; | |
63 | |
64 private: | |
65 // Enumerates the list of available network adapter devices known to | |
66 // NetworkManager. Return true on success. | |
67 bool GetAdapterDeviceList(std::vector<dbus::ObjectPath>* device_paths); | |
68 | |
69 // Given the NetworkManager path to a wireless adapater, dumps the wifi scan | |
70 // results and appends them to |data|. Returns false if a fatal error is | |
71 // encountered such that the data set could not be populated. | |
72 bool GetAccessPointsForAdapter(const dbus::ObjectPath& adapter_path, | |
73 WifiData::AccessPointDataSet* data); | |
74 | |
75 // Internal method used by |GetAccessPointsForAdapter|, given a wifi access | |
76 // point proxy retrieves the named property and returns it. Returns NULL in | |
77 // a scoped_ptr if the property could not be read. | |
78 std::unique_ptr<dbus::Response> GetAccessPointProperty( | |
79 dbus::ObjectProxy* proxy, | |
80 const std::string& property_name); | |
81 | |
82 scoped_refptr<dbus::Bus> system_bus_; | |
83 dbus::ObjectProxy* network_manager_proxy_; | |
84 | |
85 DISALLOW_COPY_AND_ASSIGN(NetworkManagerWlanApi); | |
86 }; | |
87 | |
88 // Convert a wifi frequency to the corresponding channel. Adapted from | |
89 // geolocaiton/wifilib.cc in googleclient (internal to google). | |
90 int frquency_in_khz_to_channel(int frequency_khz) { | |
91 if (frequency_khz >= 2412000 && frequency_khz <= 2472000) // Channels 1-13. | |
92 return (frequency_khz - 2407000) / 5000; | |
93 if (frequency_khz == 2484000) | |
94 return 14; | |
95 if (frequency_khz > 5000000 && frequency_khz < 6000000) // .11a bands. | |
96 return (frequency_khz - 5000000) / 5000; | |
97 // Ignore everything else. | |
98 return AccessPointData().channel; // invalid channel | |
99 } | |
100 | |
101 NetworkManagerWlanApi::NetworkManagerWlanApi() | |
102 : network_manager_proxy_(NULL) { | |
103 } | |
104 | |
105 NetworkManagerWlanApi::~NetworkManagerWlanApi() { | |
106 // Close the connection. | |
107 system_bus_->ShutdownAndBlock(); | |
108 } | |
109 | |
110 bool NetworkManagerWlanApi::Init() { | |
111 dbus::Bus::Options options; | |
112 options.bus_type = dbus::Bus::SYSTEM; | |
113 options.connection_type = dbus::Bus::PRIVATE; | |
114 return InitWithBus(new dbus::Bus(options)); | |
115 } | |
116 | |
117 bool NetworkManagerWlanApi::InitWithBus(dbus::Bus* bus) { | |
118 system_bus_ = bus; | |
119 // system_bus_ will own all object proxies created from the bus. | |
120 network_manager_proxy_ = | |
121 system_bus_->GetObjectProxy(kNetworkManagerServiceName, | |
122 dbus::ObjectPath(kNetworkManagerPath)); | |
123 // Validate the proxy object by checking we can enumerate devices. | |
124 std::vector<dbus::ObjectPath> adapter_paths; | |
125 const bool success = GetAdapterDeviceList(&adapter_paths); | |
126 VLOG(1) << "Init() result: " << success; | |
127 return success; | |
128 } | |
129 | |
130 bool NetworkManagerWlanApi::GetAccessPointData( | |
131 WifiData::AccessPointDataSet* data) { | |
132 std::vector<dbus::ObjectPath> device_paths; | |
133 if (!GetAdapterDeviceList(&device_paths)) { | |
134 LOG(WARNING) << "Could not enumerate access points"; | |
135 return false; | |
136 } | |
137 int success_count = 0; | |
138 int fail_count = 0; | |
139 | |
140 // Iterate the devices, getting APs for each wireless adapter found | |
141 for (size_t i = 0; i < device_paths.size(); ++i) { | |
142 const dbus::ObjectPath& device_path = device_paths[i]; | |
143 VLOG(1) << "Checking device: " << device_path.value(); | |
144 | |
145 dbus::ObjectProxy* device_proxy = | |
146 system_bus_->GetObjectProxy(kNetworkManagerServiceName, | |
147 device_path); | |
148 | |
149 dbus::MethodCall method_call(DBUS_INTERFACE_PROPERTIES, "Get"); | |
150 dbus::MessageWriter builder(&method_call); | |
151 builder.AppendString("org.freedesktop.NetworkManager.Device"); | |
152 builder.AppendString("DeviceType"); | |
153 std::unique_ptr<dbus::Response> response(device_proxy->CallMethodAndBlock( | |
154 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); | |
155 if (!response) { | |
156 LOG(WARNING) << "Failed to get the device type for " | |
157 << device_path.value(); | |
158 continue; // Check the next device. | |
159 } | |
160 dbus::MessageReader reader(response.get()); | |
161 uint32_t device_type = 0; | |
162 if (!reader.PopVariantOfUint32(&device_type)) { | |
163 LOG(WARNING) << "Unexpected response for " << device_type << ": " | |
164 << response->ToString(); | |
165 continue; // Check the next device. | |
166 } | |
167 VLOG(1) << "Device type: " << device_type; | |
168 | |
169 if (device_type == NM_DEVICE_TYPE_WIFI) { // Found a wlan adapter | |
170 if (GetAccessPointsForAdapter(device_path, data)) | |
171 ++success_count; | |
172 else | |
173 ++fail_count; | |
174 } | |
175 } | |
176 // At least one successful scan overrides any other adapter reporting error. | |
177 return success_count || fail_count == 0; | |
178 } | |
179 | |
180 bool NetworkManagerWlanApi::GetAdapterDeviceList( | |
181 std::vector<dbus::ObjectPath>* device_paths) { | |
182 dbus::MethodCall method_call(kNetworkManagerInterface, "GetDevices"); | |
183 std::unique_ptr<dbus::Response> response( | |
184 network_manager_proxy_->CallMethodAndBlock( | |
185 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); | |
186 if (!response) { | |
187 LOG(WARNING) << "Failed to get the device list"; | |
188 return false; | |
189 } | |
190 | |
191 dbus::MessageReader reader(response.get()); | |
192 if (!reader.PopArrayOfObjectPaths(device_paths)) { | |
193 LOG(WARNING) << "Unexpected response: " << response->ToString(); | |
194 return false; | |
195 } | |
196 return true; | |
197 } | |
198 | |
199 | |
200 bool NetworkManagerWlanApi::GetAccessPointsForAdapter( | |
201 const dbus::ObjectPath& adapter_path, WifiData::AccessPointDataSet* data) { | |
202 // Create a proxy object for this wifi adapter, and ask it to do a scan | |
203 // (or at least, dump its scan results). | |
204 dbus::ObjectProxy* device_proxy = | |
205 system_bus_->GetObjectProxy(kNetworkManagerServiceName, | |
206 adapter_path); | |
207 dbus::MethodCall method_call( | |
208 "org.freedesktop.NetworkManager.Device.Wireless", | |
209 "GetAccessPoints"); | |
210 std::unique_ptr<dbus::Response> response(device_proxy->CallMethodAndBlock( | |
211 &method_call, 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 std::unique_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_t* 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 std::unique_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 base::ReplaceSubstringsAfterOffset(&mac, 0U, ":", base::StringPiece()); | |
275 std::vector<uint8_t> 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 std::unique_ptr<dbus::Response> response( | |
287 GetAccessPointProperty(access_point_proxy, "Strength")); | |
288 if (!response) | |
289 continue; | |
290 dbus::MessageReader reader(response.get()); | |
291 uint8_t 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 std::unique_ptr<dbus::Response> response( | |
303 GetAccessPointProperty(access_point_proxy, "Frequency")); | |
304 if (!response) | |
305 continue; | |
306 dbus::MessageReader reader(response.get()); | |
307 uint32_t 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 | |
329 std::unique_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 std::unique_ptr<dbus::Response> response = | |
337 access_point_proxy->CallMethodAndBlock( | |
338 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT); | |
339 if (!response) { | |
340 LOG(WARNING) << "Failed to get property for " << property_name; | |
341 } | |
342 return response; | |
343 } | |
344 | |
345 } // namespace | |
346 | |
347 // static | |
348 WifiDataProvider* WifiDataProviderManager::DefaultFactoryFunction() { | |
349 return new WifiDataProviderLinux(); | |
350 } | |
351 | |
352 WifiDataProviderLinux::WifiDataProviderLinux() { | |
353 } | |
354 | |
355 WifiDataProviderLinux::~WifiDataProviderLinux() { | |
356 } | |
357 | |
358 WifiDataProviderCommon::WlanApiInterface* | |
359 WifiDataProviderLinux::NewWlanApi() { | |
360 std::unique_ptr<NetworkManagerWlanApi> wlan_api(new NetworkManagerWlanApi); | |
361 if (wlan_api->Init()) | |
362 return wlan_api.release(); | |
363 return NULL; | |
364 } | |
365 | |
366 WifiPollingPolicy* WifiDataProviderLinux::NewPollingPolicy() { | |
367 return new GenericWifiPollingPolicy<kDefaultPollingIntervalMilliseconds, | |
368 kNoChangePollingIntervalMilliseconds, | |
369 kTwoNoChangePollingIntervalMilliseconds, | |
370 kNoWifiPollingIntervalMilliseconds>; | |
371 } | |
372 | |
373 WifiDataProviderCommon::WlanApiInterface* | |
374 WifiDataProviderLinux::NewWlanApiForTesting(dbus::Bus* bus) { | |
375 std::unique_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 | |
OLD | NEW |