| // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // 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). |
| |
| #include "chrome/browser/geolocation/wifi_data_provider_linux.h" |
| |
| #include <dbus/dbus-glib.h> |
| #include <dbus/dbus-glib-lowlevel.h> |
| #include <dbus/dbus.h> |
| #include <glib.h> |
| |
| #include "base/scoped_ptr.h" |
| #include "base/utf_string_conversions.h" |
| |
| 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 |
| const int kNoWifiPollingIntervalMilliseconds = 20 * 1000; // 20s |
| |
| const char kNetworkManagerServiceName[] = "org.freedesktop.NetworkManager"; |
| const char kNetworkManagerPath[] = "/org/freedesktop/NetworkManager"; |
| const char kNetworkManagerInterface[] = "org.freedesktop.NetworkManager"; |
| |
| // 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; |
| |
| 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 |
| } |
| |
| NetworkManagerWlanApi::NetworkManagerWlanApi() |
| : error_(NULL), connection_(NULL) { |
| } |
| |
| NetworkManagerWlanApi::~NetworkManagerWlanApi() { |
| proxy_.reset(); |
| if (connection_) { |
| dbus_g_connection_unref(connection_); |
| } |
| DCHECK(!error_) << "Missing a call to CheckError() to clear |error_|"; |
| } |
| |
| 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()); |
| |
| // Get a connection to the session bus. |
| connection_ = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error_); |
| if (CheckError()) |
| return false; |
| DCHECK(connection_); |
| |
| // dbus-glib queues timers that get fired on the default loop, unfortunately |
| // it isn't thread safe in it's handling of these timers. We can't easily |
| // tell it which loop to queue them on instead, but as we only make |
| // blocking sync calls we don't need timers anyway, so disable them. |
| // See http://crbug.com/40803 TODO(joth): This is not an ideal solution, as |
| // we're reconfiguring the process global system bus connection, so could |
| // impact other users of DBus. |
| dbus_bool_t ok = dbus_connection_set_timeout_functions( |
| dbus_g_connection_get_connection(connection_), |
| NULL, NULL, NULL, NULL, NULL); |
| DCHECK(ok); |
| |
| 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; |
| } |
| } |
| // At least one successfull scan overrides any other adapter reporting error. |
| return success_count || fail_count == 0; |
| } |
| |
| bool NetworkManagerWlanApi::CheckError() { |
| if (error_) { |
| LOG(ERROR) << "Failed to complete NetworkManager call: " << error_->message; |
| g_error_free(error_); |
| error_ = NULL; |
| return true; |
| } |
| return false; |
| } |
| |
| 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; |
| } |
| |
| 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); |
| } |
| |
| { // 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]); |
| } |
| } |
| |
| { // 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; |
| } |
| |
| 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; |
| } |
| |
| } // namespace |
| |
| // static |
| template<> |
| WifiDataProviderImplBase* WifiDataProvider::DefaultFactoryFunction() { |
| return new WifiDataProviderLinux(); |
| } |
| |
| WifiDataProviderLinux::WifiDataProviderLinux() { |
| } |
| |
| WifiDataProviderLinux::~WifiDataProviderLinux() { |
| } |
| |
| WifiDataProviderCommon::WlanApiInterface* |
| WifiDataProviderLinux::NewWlanApi() { |
| scoped_ptr<NetworkManagerWlanApi> wlan_api(new NetworkManagerWlanApi); |
| if (wlan_api->Init()) |
| return wlan_api.release(); |
| return NULL; |
| } |
| |
| PollingPolicyInterface* WifiDataProviderLinux::NewPollingPolicy() { |
| return new GenericPollingPolicy<kDefaultPollingIntervalMilliseconds, |
| kNoChangePollingIntervalMilliseconds, |
| kTwoNoChangePollingIntervalMilliseconds, |
| kNoWifiPollingIntervalMilliseconds>; |
| } |
| |