Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1363)

Unified Diff: chrome/browser/geolocation/wifi_data_provider_linux.cc

Issue 787003: First cut at implementing wifi bindigns for linux, using glib-dbus to Network... (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 10 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/geolocation/wifi_data_provider_linux.cc
===================================================================
--- chrome/browser/geolocation/wifi_data_provider_linux.cc (revision 40894)
+++ chrome/browser/geolocation/wifi_data_provider_linux.cc (working copy)
@@ -2,256 +2,365 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// WiFi card drivers for Linux implement the Wireless Extensions interface.
-// This interface is part of the Linux kernel.
-//
-// Various sets of tools are available to manipulate the Wireless Extensions,
-// of which Wireless Tools is the default implementation. Wireless Tools
-// provides a C++ library (libiw) as well as a set of command line tools
-// (iwconfig, iwlist etc). See
-// http://www.hpl.hp.com/personal/Jean_Tourrilhes/Linux/Tools.html for details.
-//
-// Ideally, we would use libiw to obtain WiFi data. However, Wireless Tools is
-// released under GPL, which is not compatible with Gears. Furthermore, little
-// documentation is available for Wireless Extensions, so replicating libiw
-// without copying it directly would be difficult.
-//
-// We therefore simply invoke iwlist (one of the Wireless Tools command line
-// tools) and parse the output. Sample output is shown below.
-//
-// lo Interface doesn't support scanning.
-//
-// ath0 Scan completed :
-// Cell 01 - Address: 00:24:86:11:4C:42
-// ESSID:"Test SSID"
-// Mode:Master
-// Frequency:2.427 GHz (Channel 4)
-// Quality=5/94 Signal level=-90 dBm Noise level=-95 dBm
-// Encryption key:off
-// Bit Rates:1 Mb/s; 2 Mb/s; 5 Mb/s; 6 Mb/s; 9 Mb/s
-// 11 Mb/s; 12 Mb/s; 18 Mb/s
-// Extra:bcn_int=100
-// Cell 02 - Address: 00:24:86:11:6F:E2
-// ESSID:"Test SSID"
-// Mode:Master
-// Frequency:2.447 GHz (Channel 8)
-// Quality=4/94 Signal level=-91 dBm Noise level=-95 dBm
-// Encryption key:off
-// Bit Rates:1 Mb/s; 2 Mb/s; 5 Mb/s; 6 Mb/s; 9 Mb/s
-// 11 Mb/s; 12 Mb/s; 18 Mb/s
-// Extra:bcn_int=100
-//
-// TODO(steveblock): Investigate the possibility of the author of Wireless Tools
-// releasing libiw under a Gears-compatible license.
+// Provides wifi scan API binding for suitable for typical linux distributions.
+// Currently, only the NetworkManager API is used, accessed via D-Bus (in turn
+// accessed via the GLib wrapper).
-// TODO(joth): port to chromium
-#if 0
+#include "chrome/browser/geolocation/wifi_data_provider_linux.h"
-#include "gears/geolocation/wifi_data_provider_linux.h"
+#include <dbus/dbus-glib.h>
+#include <glib.h>
-#include <ctype.h> // For isxdigit()
-#include <stdio.h>
-#include "gears/base/common/string_utils.h"
-#include "gears/geolocation/wifi_data_provider_common.h"
+#include "base/scoped_ptr.h"
+#include "base/utf_string_conversions.h"
-// The time periods, in milliseconds, between successive polls of the wifi data.
-extern const int kDefaultPollingInterval = 10000; // 10s
-extern const int kNoChangePollingInterval = 120000; // 2 mins
-extern const int kTwoNoChangePollingInterval = 600000; // 10 mins
+namespace {
+// The time periods between successive polls of the wifi data.
+const int kDefaultPollingIntervalMilliseconds = 10 * 1000; // 10s
+const int kNoChangePollingIntervalMilliseconds = 2 * 60 * 1000; // 2 mins
+const int kTwoNoChangePollingIntervalMilliseconds = 10 * 60 * 1000; // 10 mins
-// Local function
-static bool GetAccessPointData(WifiData::AccessPointDataSet *access_points);
+const char kNetworkManagerServiceName[] = "org.freedesktop.NetworkManager";
+const char kNetworkManagerPath[] = "/org/freedesktop/NetworkManager";
+const char kNetworkManagerInterface[] = "org.freedesktop.NetworkManager";
-// static
-template<>
-WifiDataProviderImplBase *WifiDataProvider::DefaultFactoryFunction() {
- return new LinuxWifiDataProvider();
-}
+// From http://projects.gnome.org/NetworkManager/developers/spec.html
+enum { NM_DEVICE_TYPE_WIFI = 2 };
+// Utility wrappers to make various GLib & DBus structs into scoped objects.
+class ScopedGPtrArrayFree {
+ public:
+ void operator()(GPtrArray* x) const {
+ if (x)
+ g_ptr_array_free(x, TRUE);
+ }
+};
+// Use ScopedGPtrArrayPtr as if it were scoped_ptr<GPtrArray>
+typedef scoped_ptr_malloc<GPtrArray, ScopedGPtrArrayFree> ScopedGPtrArrayPtr;
-LinuxWifiDataProvider::LinuxWifiDataProvider()
- : is_first_scan_complete_(false) {
- Start();
+class ScopedGObjectFree {
+ public:
+ void operator()(void* x) const {
+ if (x)
+ g_object_unref(x);
+ }
+};
+// Use ScopedDBusGProxyPtr as if it were scoped_ptr<DBusGProxy>
+typedef scoped_ptr_malloc<DBusGProxy, ScopedGObjectFree> ScopedDBusGProxyPtr;
+
+// Use ScopedGValue::v as an instance of GValue with automatic cleanup.
+class ScopedGValue {
+ public:
+ ScopedGValue()
+ : v(empty_gvalue()) {
+ }
+ ~ScopedGValue() {
+ g_value_unset(&v);
+ }
+ static GValue empty_gvalue() {
+ GValue value = {0};
+ return value;
+ }
+
+ GValue v;
+};
+
+// Wifi API binding to NetworkManager, to allow reuse of the polling behavior
+// defined in WifiDataProviderCommon.
+// TODO(joth): NetworkManager also allows for notification based handling,
+// however this will require reworking of the threading code to run a GLib
+// event loop (GMainLoop).
+class NetworkManagerWlanApi : public WifiDataProviderCommon::WlanApiInterface {
+ public:
+ NetworkManagerWlanApi();
+ ~NetworkManagerWlanApi();
+
+ // Must be called before any other interface method. Will return false if the
+ // NetworkManager session cannot be created (e.g. not present on this distro),
+ // in which case no other method may be called.
+ bool Init();
+
+ // WifiDataProviderCommon::WlanApiInterface
+ bool GetAccessPointData(WifiData::AccessPointDataSet* data);
+
+ private:
+ // Checks if the last dbus call returned an error. If it did, logs the error
+ // message, frees it and returns true.
+ // This must be called after every dbus call that accepts |&error_|
+ bool CheckError();
+
+ // Enumerates the list of available network adapter devices known to
+ // NetworkManager. Ownership of the array (and contained objects) is returned
+ // to the caller.
+ GPtrArray* GetAdapterDeviceList();
+
+ // Given the NetworkManager path to a wireless adapater, dumps the wifi scan
+ // results and appends them to |data|. Returns false if a fatal error is
+ // encountered such that the data set could not be populated.
+ bool GetAccessPointsForAdapter(const gchar* adapter_path,
+ WifiData::AccessPointDataSet* data);
+
+ // Internal method used by |GetAccessPointsForAdapter|, given a wifi access
+ // point proxy retrieves the named property into |value_out|. Returns false if
+ // the property could not be read, or is not of type |expected_gvalue_type|.
+ bool GetAccessPointProperty(DBusGProxy* proxy, const char* property_name,
+ int expected_gvalue_type, GValue* value_out);
+
+ // Error from the last dbus call. NULL when there's no error. Freed and
+ // cleared by CheckError().
+ GError* error_;
+ // Connection to the dbus system bus.
+ DBusGConnection* connection_;
+ // Proxy to the network maanger dbus service.
+ ScopedDBusGProxyPtr proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkManagerWlanApi);
+};
+
+// Convert a wifi frequency to the corresponding channel. Adapted from
+// geolocaiton/wifilib.cc in googleclient (internal to google).
+int frquency_in_khz_to_channel(int frequency_khz) {
+ if (frequency_khz >= 2412000 && frequency_khz <= 2472000) // Channels 1-13.
+ return (frequency_khz - 2407000) / 5000;
+ if (frequency_khz == 2484000)
+ return 14;
+ if (frequency_khz > 5000000 && frequency_khz < 6000000) // .11a bands.
+ return (frequency_khz - 5000000) / 5000;
+ // Ignore everything else.
+ return AccessPointData().channel; // invalid channel
}
-LinuxWifiDataProvider::~LinuxWifiDataProvider() {
- stop_event_.Signal();
- Join();
+NetworkManagerWlanApi::NetworkManagerWlanApi()
+ : error_(NULL), connection_(NULL) {
}
-bool LinuxWifiDataProvider::GetData(WifiData *data) {
- DCHECK(data);
- MutexLock lock(&data_mutex_);
- *data = wifi_data_;
- // If we've successfully completed a scan, indicate that we have all of the
- // data we can get.
- return is_first_scan_complete_;
+NetworkManagerWlanApi::~NetworkManagerWlanApi() {
+ proxy_.reset();
+ if (connection_) {
+ dbus_g_connection_unref(connection_);
+ }
+ DCHECK(!error_) << "Missing a call to CheckError() to clear |error_|";
}
-// Thread implementation
-void LinuxWifiDataProvider::Run() {
- // Regularly get the access point data.
- int polling_interval = kDefaultPollingInterval;
- do {
- WifiData new_data;
- if (GetAccessPointData(&new_data.access_point_data)) {
- bool update_available;
- data_mutex_.Lock();
- update_available = wifi_data_.DiffersSignificantly(new_data);
- wifi_data_ = new_data;
- data_mutex_.Unlock();
- polling_interval =
- UpdatePollingInterval(polling_interval, update_available);
- if (update_available) {
- is_first_scan_complete_ = true;
- NotifyListeners();
- }
+bool NetworkManagerWlanApi::Init() {
+ // Chrome DLL init code handles initializing the thread system, so rather than
+ // get caught up with that nonsense here, lets just assert our requirement.
+ CHECK(g_thread_supported());
+
+ // We should likely do this higher up too, the docs say it must only be done
+ // once but there's no way to know if it already was or not.
+ dbus_g_thread_init();
+
+ // Get a connection to the session bus.
+ connection_ = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error_);
+ if (CheckError())
+ return false;
+ DCHECK(connection_);
+
+ proxy_.reset(dbus_g_proxy_new_for_name(connection_,
+ kNetworkManagerServiceName,
+ kNetworkManagerPath,
+ kNetworkManagerInterface));
+ DCHECK(proxy_.get());
+
+ // Validate the proxy object by checking we can enumerate devices.
+ ScopedGPtrArrayPtr device_list(GetAdapterDeviceList());
+ return !!device_list.get();
+}
+
+bool NetworkManagerWlanApi::GetAccessPointData(
+ WifiData::AccessPointDataSet* data) {
+ ScopedGPtrArrayPtr device_list(GetAdapterDeviceList());
+ if (device_list == NULL) {
+ DLOG(WARNING) << "Could not enumerate access points";
+ return false;
+ }
+ int success_count = 0;
+ int fail_count = 0;
+
+ // Iterate the devices, getting APs for each wireless adapter found
+ for (guint i = 0; i < device_list->len; i++) {
+ const gchar* device_path =
+ reinterpret_cast<const gchar*>(g_ptr_array_index(device_list, i));
+
+ ScopedDBusGProxyPtr device_properties_proxy(dbus_g_proxy_new_from_proxy(
+ proxy_.get(), DBUS_INTERFACE_PROPERTIES, device_path));
+ ScopedGValue device_type_g_value;
+ dbus_g_proxy_call(device_properties_proxy.get(), "Get", &error_,
+ G_TYPE_STRING, "org.freedesktop.NetworkManager.Device",
+ G_TYPE_STRING, "DeviceType",
+ G_TYPE_INVALID,
+ G_TYPE_VALUE, &device_type_g_value.v,
+ G_TYPE_INVALID);
+ if (CheckError())
+ continue;
+
+ const guint device_type = g_value_get_uint(&device_type_g_value.v);
+
+ if (device_type == NM_DEVICE_TYPE_WIFI) { // Found a wlan adapter
+ if (GetAccessPointsForAdapter(device_path, data))
+ ++success_count;
+ else
+ ++fail_count;
}
- } while (!stop_event_.WaitWithTimeout(polling_interval));
+ }
+ // At least one successfull scan overrides any other adapter reporting error.
+ return success_count || fail_count == 0;
}
-// Local functions
+bool NetworkManagerWlanApi::CheckError() {
+ if (error_) {
+ LOG(ERROR) << "Failed to complete NetworkManager call: " << error_->message;
+ g_error_free(error_);
+ error_ = NULL;
+ return true;
+ }
+ return false;
+}
-static bool IsValidMacAddress(const char *mac_address) {
- return isxdigit(mac_address[0]) &&
- isxdigit(mac_address[1]) &&
- mac_address[2] == ':' &&
- isxdigit(mac_address[3]) &&
- isxdigit(mac_address[4]) &&
- mac_address[5] == ':' &&
- isxdigit(mac_address[6]) &&
- isxdigit(mac_address[7]) &&
- mac_address[8] == ':' &&
- isxdigit(mac_address[9]) &&
- isxdigit(mac_address[10]) &&
- mac_address[11] == ':' &&
- isxdigit(mac_address[12]) &&
- isxdigit(mac_address[13]) &&
- mac_address[14] == ':' &&
- isxdigit(mac_address[15]) &&
- isxdigit(mac_address[16]);
+GPtrArray* NetworkManagerWlanApi::GetAdapterDeviceList() {
+ GPtrArray* device_list = NULL;
+ dbus_g_proxy_call(proxy_.get(), "GetDevices", &error_,
+ G_TYPE_INVALID,
+ dbus_g_type_get_collection("GPtrArray",
+ DBUS_TYPE_G_OBJECT_PATH),
+ &device_list,
+ G_TYPE_INVALID);
+ if (CheckError())
+ return NULL;
+ return device_list;
}
-static void ParseLine(const std::string &line,
- const std::string &mac_address_string,
- const std::string &ssid_string,
- const std::string &signal_strength_string,
- AccessPointData *access_point_data) {
- // Currently we get only MAC address, SSID and signal strength.
- // TODO(steveblock): Work out how to get age, channel and signal-to-noise.
- std::string::size_type index;
- if ((index = line.find(mac_address_string)) != std::string::npos) {
- // MAC address
- if (IsValidMacAddress(&line.at(index + mac_address_string.size()))) {
- UTF8ToString16(&line.at(index + mac_address_string.size()),
- 17, // XX:XX:XX:XX:XX:XX
- &access_point_data->mac_address);
+bool NetworkManagerWlanApi::GetAccessPointsForAdapter(
+ const gchar* adapter_path, WifiData::AccessPointDataSet* data) {
+ DCHECK(proxy_.get());
+
+ // Create a proxy object for this wifi adapter, and ask it to do a scan
+ // (or at least, dump its scan results).
+ ScopedDBusGProxyPtr wifi_adapter_proxy(dbus_g_proxy_new_from_proxy(
+ proxy_.get(), "org.freedesktop.NetworkManager.Device.Wireless",
+ adapter_path));
+
+ GPtrArray* ap_list_raw = NULL;
+ // Enumerate the access points for this adapter.
+ dbus_g_proxy_call(wifi_adapter_proxy.get(), "GetAccessPoints", &error_,
+ G_TYPE_INVALID,
+ dbus_g_type_get_collection("GPtrArray",
+ DBUS_TYPE_G_OBJECT_PATH),
+ &ap_list_raw,
+ G_TYPE_INVALID);
+ ScopedGPtrArrayPtr ap_list(ap_list_raw); // Takes ownership.
+ ap_list_raw = NULL;
+
+ if (CheckError())
+ return false;
+
+ DLOG(INFO) << "Wireless adapter " << adapter_path << " found "
+ << ap_list->len << " access points.";
+
+ for (guint i = 0; i < ap_list->len; i++) {
+ const gchar* ap_path =
+ reinterpret_cast<const gchar*>(g_ptr_array_index(ap_list, i));
+ ScopedDBusGProxyPtr access_point_proxy(dbus_g_proxy_new_from_proxy(
+ proxy_.get(), DBUS_INTERFACE_PROPERTIES, ap_path));
+
+ AccessPointData access_point_data;
+ { // Read SSID.
+ ScopedGValue ssid_g_value;
+ if (!GetAccessPointProperty(access_point_proxy.get(), "Ssid",
+ G_TYPE_BOXED, &ssid_g_value.v))
+ continue;
+ const GArray* ssid =
+ reinterpret_cast<const GArray*>(g_value_get_boxed(&ssid_g_value.v));
+ UTF8ToUTF16(ssid->data, ssid->len, &access_point_data.ssid);
}
- } else if ((index = line.find(ssid_string)) != std::string::npos) {
- // SSID
- // The string should be quoted.
- std::string::size_type start = index + ssid_string.size() + 1;
- std::string::size_type end = line.find('\"', start);
- // If we can't find a trailing quote, something has gone wrong.
- if (end != std::string::npos) {
- UTF8ToString16(&line.at(start), end - start, &access_point_data->ssid);
+
+ { // Read the mac address
+ ScopedGValue mac_g_value;
+ if (!GetAccessPointProperty(access_point_proxy.get(), "HwAddress",
+ G_TYPE_STRING, &mac_g_value.v))
+ continue;
+ std::string mac = g_value_get_string(&mac_g_value.v);
+ ReplaceSubstringsAfterOffset(&mac, 0U, ":", "");
+ std::vector<uint8> mac_bytes;
+ if (!HexStringToBytes(mac, &mac_bytes) || mac_bytes.size() != 6) {
+ DLOG(WARNING) << "Can't parse mac address (found " << mac_bytes.size()
+ << " bytes) so using raw string: " << mac;
+ access_point_data.mac_address = UTF8ToUTF16(mac);
+ } else {
+ access_point_data.mac_address = MacAddressAsString16(&mac_bytes[0]);
+ }
}
- } else if ((index = line.find(signal_strength_string)) != std::string::npos) {
- // Signal strength
- // iwlist will convert to dBm if it can. If it has failed to do so, we can't
- // make use of the data.
- if (line.find("dBm") != std::string::npos) {
- // atoi will ignore trailing non-numeric characters
- access_point_data->radio_signal_strength =
- atoi(&line.at(index + signal_strength_string.size()));
+
+ { // Read signal strength.
+ ScopedGValue signal_g_value;
+ if (!GetAccessPointProperty(access_point_proxy.get(), "Strength",
+ G_TYPE_UCHAR, &signal_g_value.v))
+ continue;
+ // Convert strength as a percentage into dBs.
+ access_point_data.radio_signal_strength =
+ -100 + g_value_get_uchar(&signal_g_value.v) / 2;
}
+
+ { // Read the channel
+ ScopedGValue freq_g_value;
+ if (!GetAccessPointProperty(access_point_proxy.get(), "Frequency",
+ G_TYPE_UINT, &freq_g_value.v))
+ continue;
+ // NetworkManager returns frequency in MHz.
+ access_point_data.channel =
+ frquency_in_khz_to_channel(g_value_get_uint(&freq_g_value.v) * 1000);
+ }
+ data->insert(access_point_data);
}
+ return true;
}
-static void ParseAccessPoint(const std::string &text,
- const std::string &mac_address_string,
- const std::string &ssid_string,
- const std::string &signal_strength_string,
- AccessPointData *access_point_data) {
- // Split response into lines to aid parsing.
- std::string::size_type start = 0;
- std::string::size_type end;
- do {
- end = text.find('\n', start);
- std::string::size_type length = (end == std::string::npos) ?
- std::string::npos : end - start;
- ParseLine(text.substr(start, length),
- mac_address_string,
- ssid_string,
- signal_strength_string,
- access_point_data);
- start = end + 1;
- } while (end != std::string::npos);
-}
-
-// Issues the specified command, and parses the response. Data for each access
-// point is separated by the given delimiter. Within each block of data, the
-// repsonse is split into lines and data is extracted by searching for the MAC
-// address, SSID and signal strength strings.
-bool IssueCommandAndParseResult(const char *command,
- const char *delimiter,
- const std::string &mac_address_string,
- const std::string &ssid_string,
- const std::string &signal_strength_string,
- WifiData::AccessPointDataSet *access_points) {
- // Open pipe in read mode.
- FILE *result_pipe = popen(command, "r");
- if (result_pipe == NULL) {
- LOG(("IssueCommand(): Failed to open pipe.\n"));
+bool NetworkManagerWlanApi::GetAccessPointProperty(DBusGProxy* proxy,
+ const char* property_name,
+ int expected_gvalue_type,
+ GValue* value_out) {
+ dbus_g_proxy_call(proxy, "Get", &error_,
+ G_TYPE_STRING, "org.freedesktop.NetworkManager.AccessPoint",
+ G_TYPE_STRING, property_name,
+ G_TYPE_INVALID,
+ G_TYPE_VALUE, value_out,
+ G_TYPE_INVALID);
+ if (CheckError())
return false;
+ if (!G_VALUE_HOLDS(value_out, expected_gvalue_type)) {
+ DLOG(WARNING) << "Property " << property_name << " unexptected type "
+ << G_VALUE_TYPE(value_out);
+ return false;
}
+ return true;
+}
- // Read results of command.
- static const int kBufferSize = 1024;
- char buffer[kBufferSize];
- size_t bytes_read;
- std::string result;
- do {
- bytes_read = fread(buffer, 1, kBufferSize, result_pipe);
- result.append(buffer, bytes_read);
- } while (static_cast<int>(bytes_read) == kBufferSize);
- pclose(result_pipe);
+} // namespace
+// static
+template<>
+WifiDataProviderImplBase* WifiDataProvider::DefaultFactoryFunction() {
+ return new WifiDataProviderLinux();
+}
- // Parse results.
- DCHECK(access_points);
- access_points->clear();
- std::string::size_type start = result.find(delimiter);
- while (start != std::string::npos) {
- std::string::size_type end = result.find(delimiter, start + 1);
- std::string::size_type length = (end == std::string::npos) ?
- std::string::npos : end - start;
- AccessPointData access_point_data;
- ParseAccessPoint(result.substr(start, length),
- mac_address_string,
- ssid_string,
- signal_strength_string,
- &access_point_data);
- access_points->insert(access_point_data);
- start = end;
- }
+WifiDataProviderLinux::WifiDataProviderLinux() {
+}
- return !access_points->empty();
+WifiDataProviderLinux::~WifiDataProviderLinux() {
}
-static bool GetAccessPointData(WifiData::AccessPointDataSet *access_points) {
- return IssueCommandAndParseResult("iwlist scan 2> /dev/null",
- "Cell ",
- "Address: ",
- "ESSID:",
- "Signal level=",
- access_points) ||
- IssueCommandAndParseResult("iwconfig 2> /dev/null",
- "ESSID:\"",
- "Access Point: ",
- "ESSID:",
- "Signal level=",
- access_points);
+WifiDataProviderCommon::WlanApiInterface*
+WifiDataProviderLinux::NewWlanApi() {
+ scoped_ptr<NetworkManagerWlanApi> wlan_api(new NetworkManagerWlanApi);
+ if (wlan_api->Init())
+ return wlan_api.release();
+ return NULL;
}
-#endif // 0
+PollingPolicyInterface* WifiDataProviderLinux::NewPollingPolicy() {
+ return new GenericPollingPolicy<kDefaultPollingIntervalMilliseconds,
+ kNoChangePollingIntervalMilliseconds,
+ kTwoNoChangePollingIntervalMilliseconds>;
+}
+
« no previous file with comments | « chrome/browser/geolocation/wifi_data_provider_linux.h ('k') | chrome/browser/geolocation/wifi_data_provider_mac.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698