From b89990ae2cd79217f69d5e691b03a7be0570e969 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Tue, 18 Sep 2012 17:33:57 -0500 Subject: [PATCH] wifi: detect whether supplicant supports AP mode or not And return an error when trying to activate an AP mode connection if the supplicant does not support it. With wpa_supplicant 1.0 and earlier there is no way to positively detect whether AP mode is supported, so we simply try to start AP mode and then fail if it doesn't work. With more recent versions we can check the Introspection data (if the supplicant has been built with introspection enabled) or check the global Capabilities (if the supplicant is recent enough) for positive indication of AP mode support. --- src/nm-device-wifi.c | 37 +++++-- src/nm-device-wifi.h | 1 + .../nm-supplicant-interface.c | 80 ++++++++++++++- .../nm-supplicant-interface.h | 12 +++ .../nm-supplicant-manager.c | 99 ++++++++++++------- 5 files changed, 188 insertions(+), 41 deletions(-) diff --git a/src/nm-device-wifi.c b/src/nm-device-wifi.c index de7943d518..48e509bef8 100644 --- a/src/nm-device-wifi.c +++ b/src/nm-device-wifi.c @@ -1077,13 +1077,30 @@ check_connection_compatible (NMDevice *device, return FALSE; } - if ( (g_strcmp0 (nm_setting_wireless_get_mode (s_wireless), NM_SETTING_WIRELESS_MODE_AP) == 0) - && !(priv->capabilities & NM_WIFI_DEVICE_CAP_AP)) { - g_set_error_literal (error, - NM_WIFI_ERROR, - NM_WIFI_ERROR_CONNECTION_INCOMPATIBLE, - "Access Point (AP) mode is not supported by this device."); - return FALSE; + /* Early exit if supplicant or device don't support AP mode */ + if (g_strcmp0 (nm_setting_wireless_get_mode (s_wireless), NM_SETTING_WIRELESS_MODE_AP) == 0) { + if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_AP)) { + g_set_error_literal (error, + NM_WIFI_ERROR, + NM_WIFI_ERROR_AP_MODE_UNSUPPORTED, + "Access Point (AP) mode is not supported by this device."); + return FALSE; + } + + if (priv->supplicant.iface) { + switch (nm_supplicant_interface_get_ap_support (priv->supplicant.iface)) { + case AP_SUPPORT_NO: + g_set_error_literal (error, + NM_WIFI_ERROR, + NM_WIFI_ERROR_AP_MODE_UNSUPPORTED, + "Access Point (AP) mode is not supported by the supplicant."); + return FALSE; + case AP_SUPPORT_YES: + case AP_SUPPORT_UNKNOWN: + default: + break; + } + } } // FIXME: check channel/freq/band against bands the hardware supports @@ -2810,6 +2827,12 @@ build_supplicant_config (NMDeviceWifi *self, } } + /* Warn if AP mode may not be supported */ + if ( g_strcmp0 (nm_setting_wireless_get_mode (s_wireless), NM_SETTING_WIRELESS_MODE_AP) == 0 + && nm_supplicant_interface_get_ap_support (priv->supplicant.iface) == AP_SUPPORT_UNKNOWN) { + nm_log_warn (LOGD_WIFI, "Supplicant may not support AP mode; connection may time out."); + } + if (!nm_supplicant_config_add_setting_wireless (config, s_wireless, nm_ap_get_broadcast (ap), diff --git a/src/nm-device-wifi.h b/src/nm-device-wifi.h index b22c78b3a3..26d66ffcf8 100644 --- a/src/nm-device-wifi.h +++ b/src/nm-device-wifi.h @@ -47,6 +47,7 @@ typedef enum { NM_WIFI_ERROR_CONNECTION_INCOMPATIBLE, /*< nick=ConnectionIncompatible >*/ NM_WIFI_ERROR_ACCESS_POINT_NOT_FOUND, /*< nick=AccessPointNotFound >*/ NM_WIFI_ERROR_SCAN_NOT_ALLOWED, /*< nick=ScanNotAllowed >*/ + NM_WIFI_ERROR_AP_MODE_UNSUPPORTED, /*< nick=ApModeUnsupported >*/ } NMWifiError; #define NM_DEVICE_WIFI_HW_ADDRESS "hw-address" diff --git a/src/supplicant-manager/nm-supplicant-interface.c b/src/supplicant-manager/nm-supplicant-interface.c index 45fe77afd6..75450a0a14 100644 --- a/src/supplicant-manager/nm-supplicant-interface.c +++ b/src/supplicant-manager/nm-supplicant-interface.c @@ -86,6 +86,7 @@ typedef struct { char * dev; gboolean is_wireless; gboolean has_credreq; /* Whether querying 802.1x credentials is supported */ + ApSupport ap_support; /* Lightweight AP mode support */ gboolean fast_supported; guint32 max_scan_ssids; guint32 ready_count; @@ -757,6 +758,7 @@ wpas_iface_check_network_reply (NMSupplicantInterface *self) NMSupplicantInfo *info; DBusGProxyCall *call; + priv->ready_count++; info = nm_supplicant_info_new (self, priv->props_proxy, priv->other_pcalls); call = dbus_g_proxy_begin_call (priv->iface_proxy, "NetworkReply", iface_check_netreply_cb, @@ -769,6 +771,76 @@ wpas_iface_check_network_reply (NMSupplicantInterface *self) nm_supplicant_info_set_call (info, call); } +ApSupport +nm_supplicant_interface_get_ap_support (NMSupplicantInterface *self) +{ + return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->ap_support; +} + +void +nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self, + ApSupport ap_support) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + + /* Use the best indicator of support between the supplicant global + * Capabilities property and the interface's introspection data. + */ + if (ap_support > priv->ap_support) + priv->ap_support = ap_support; +} + +static void +iface_check_ap_mode_cb (DBusGProxy *proxy, DBusGProxyCall *call_id, gpointer user_data) +{ + NMSupplicantInfo *info = (NMSupplicantInfo *) user_data; + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (info->interface); + char *data; + + /* The ProbeRequest method only exists if AP mode has been enabled */ + if (dbus_g_proxy_end_call (proxy, call_id, NULL, + G_TYPE_STRING, + &data, + G_TYPE_INVALID)) { + if (data && strstr (data, "ProbeRequest")) + priv->ap_support = AP_SUPPORT_YES; + g_free (data); + } + + iface_check_ready (info->interface); +} + +static void +wpas_iface_check_ap_mode (NMSupplicantInterface *self) +{ + NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self); + NMSupplicantInfo *info; + DBusGProxyCall *call; + DBusGProxy *proxy; + + priv->ready_count++; + + proxy = dbus_g_proxy_new_for_name (nm_dbus_manager_get_connection (priv->dbus_mgr), + WPAS_DBUS_SERVICE, + priv->object_path, + DBUS_INTERFACE_INTROSPECTABLE); + + info = nm_supplicant_info_new (self, proxy, priv->other_pcalls); + g_object_unref (proxy); + + /* If the global supplicant capabilities property is not present, we can + * fall back to checking whether the ProbeRequest method is supported. If + * neither of these works we have no way of determining if AP mode is + * supported or not. hostap 1.0 and earlier don't support either of these. + */ + call = dbus_g_proxy_begin_call (proxy, "Introspect", + iface_check_ap_mode_cb, + info, + nm_supplicant_info_destroy, + G_TYPE_INVALID); + nm_supplicant_info_set_call (info, call); +} + static void interface_add_done (NMSupplicantInterface *self, char *path) { @@ -841,9 +913,13 @@ interface_add_done (NMSupplicantInterface *self, char *path) path, DBUS_INTERFACE_PROPERTIES); /* Get initial properties and check whether NetworkReply is supported */ + priv->ready_count = 1; wpas_iface_get_props (self); + + /* These two increment ready_count themselves */ wpas_iface_check_network_reply (self); - priv->ready_count = 2; + if (priv->ap_support == AP_SUPPORT_UNKNOWN) + wpas_iface_check_ap_mode (self); } static void @@ -1406,6 +1482,7 @@ nm_supplicant_interface_new (NMSupplicantManager *smgr, const char *ifname, gboolean is_wireless, gboolean fast_supported, + ApSupport ap_support, gboolean start_now) { NMSupplicantInterface *self; @@ -1429,6 +1506,7 @@ nm_supplicant_interface_new (NMSupplicantManager *smgr, priv->dev = g_strdup (ifname); priv->is_wireless = is_wireless; priv->fast_supported = fast_supported; + priv->ap_support = ap_support; if (start_now) interface_add (self, priv->is_wireless); diff --git a/src/supplicant-manager/nm-supplicant-interface.h b/src/supplicant-manager/nm-supplicant-interface.h index 73cf5b524e..2f0233cf78 100644 --- a/src/supplicant-manager/nm-supplicant-interface.h +++ b/src/supplicant-manager/nm-supplicant-interface.h @@ -64,6 +64,12 @@ enum { #define NM_SUPPLICANT_INTERFACE_CONNECTION_ERROR "connection-error" #define NM_SUPPLICANT_INTERFACE_CREDENTIALS_REQUEST "credentials-request" +typedef enum { + AP_SUPPORT_UNKNOWN = 0, /* Can't detect whether supported or not */ + AP_SUPPORT_NO = 1, /* AP mode definitely not supported */ + AP_SUPPORT_YES = 2, /* AP mode definitely supported */ +} ApSupport; + struct _NMSupplicantInterface { GObject parent; }; @@ -118,6 +124,7 @@ NMSupplicantInterface * nm_supplicant_interface_new (NMSupplicantManager * smgr, const char *ifname, gboolean is_wireless, gboolean fast_supported, + ApSupport ap_support, gboolean start_now); gboolean nm_supplicant_interface_set_config (NMSupplicantInterface * iface, @@ -150,4 +157,9 @@ gboolean nm_supplicant_interface_credentials_reply (NMSupplicantInterface *self, const char *value, GError **error); +ApSupport nm_supplicant_interface_get_ap_support (NMSupplicantInterface *self); + +void nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self, + ApSupport apmode); + #endif /* NM_SUPPLICANT_INTERFACE_H */ diff --git a/src/supplicant-manager/nm-supplicant-manager.c b/src/supplicant-manager/nm-supplicant-manager.c index ae15086516..430a3852c3 100644 --- a/src/supplicant-manager/nm-supplicant-manager.c +++ b/src/supplicant-manager/nm-supplicant-manager.c @@ -27,6 +27,7 @@ #include "nm-supplicant-interface.h" #include "nm-dbus-manager.h" #include "nm-logging.h" +#include "nm-dbus-glib-types.h" #define NM_SUPPLICANT_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \ NM_TYPE_SUPPLICANT_MANAGER, \ @@ -49,6 +50,7 @@ typedef struct { gboolean running; GHashTable * ifaces; gboolean fast_supported; + ApSupport ap_support; guint die_count_reset_id; guint die_count; gboolean disposed; @@ -86,7 +88,12 @@ nm_supplicant_manager_iface_get (NMSupplicantManager * self, start_now = !die_count_exceeded (priv->die_count); nm_log_dbg (LOGD_SUPPLICANT, "(%s): creating new supplicant interface", ifname); - iface = nm_supplicant_interface_new (self, ifname, is_wireless, priv->fast_supported, start_now); + iface = nm_supplicant_interface_new (self, + ifname, + is_wireless, + priv->fast_supported, + priv->ap_support, + start_now); if (iface) g_hash_table_insert (priv->ifaces, g_strdup (ifname), iface); } else { @@ -125,52 +132,78 @@ nm_supplicant_manager_iface_release (NMSupplicantManager *self, } static void -get_eap_methods_reply (DBusGProxy *proxy, - DBusGProxyCall *call, - gpointer user_data) +get_capabilities_cb (DBusGProxy *proxy, DBusGProxyCall *call_id, gpointer user_data) { NMSupplicantManager *self = NM_SUPPLICANT_MANAGER (user_data); NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); + NMSupplicantInterface *iface; + GHashTableIter hash_iter; GError *error = NULL; - GValue value = { 0 }; - const char **iter; + GHashTable *props = NULL; + GValue *value; + char **iter; - if (dbus_g_proxy_end_call (proxy, call, &error, - G_TYPE_VALUE, &value, - G_TYPE_INVALID)) { - if (G_VALUE_HOLDS (&value, G_TYPE_STRV)) { - iter = g_value_get_boxed (&value); - while (iter && *iter) { - if (strcasecmp (*iter++, "FAST") == 0) { - priv->fast_supported = TRUE; - break; - } - } - } else { - nm_log_warn (LOGD_SUPPLICANT, "Unexpected EapMethods property type %s", - G_VALUE_TYPE_NAME (&value)); - } - g_value_unset (&value); - } else { - nm_log_warn (LOGD_SUPPLICANT, "Unexpected error requesting EapMethods: (%d) %s", + if (!dbus_g_proxy_end_call (proxy, call_id, &error, + DBUS_TYPE_G_MAP_OF_VARIANT, &props, + G_TYPE_INVALID)) { + nm_log_warn (LOGD_CORE, "Unexpected error requesting supplicant properties: (%d) %s", error ? error->code : -1, error && error->message ? error->message : "(unknown)"); g_clear_error (&error); + return; } - nm_log_dbg (LOGD_SUPPLICANT, "EAP-FAST is %ssupported", - priv->fast_supported ? "" : "not "); + /* The supplicant only advertises global capabilities if the following + * commit has been applied: + * + * commit 1634ac0654eba8d458640a115efc0a6cde3bac4d + * Author: Dan Williams + * Date: Sat Sep 29 19:06:30 2012 +0300 + * + * dbus: Add global capabilities property + */ + priv->ap_support = AP_SUPPORT_UNKNOWN; + value = g_hash_table_lookup (props, "Capabilities"); + if (value && G_VALUE_HOLDS (value, G_TYPE_STRV)) { + priv->ap_support = AP_SUPPORT_NO; + for (iter = g_value_get_boxed (value); iter && *iter; iter++) { + if (strcasecmp (*iter, "ap")) + priv->ap_support = AP_SUPPORT_YES; + } + } + + /* Tell all interfaces about results of the AP check */ + g_hash_table_iter_init (&hash_iter, priv->ifaces); + while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &iface)) + nm_supplicant_interface_set_ap_support (iface, priv->ap_support); + + nm_log_dbg (LOGD_SUPPLICANT, "AP mode is %ssupported", + (priv->ap_support == AP_SUPPORT_YES) ? "" : + (priv->ap_support == AP_SUPPORT_NO) ? "not " : " possibly"); + + /* EAP-FAST */ + priv->fast_supported = FALSE; + value = g_hash_table_lookup (props, "EapMethods"); + if (value && G_VALUE_HOLDS (value, G_TYPE_STRV)) { + for (iter = g_value_get_boxed (value); iter && *iter; iter++) { + if (strcasecmp (*iter, "fast")) + priv->fast_supported = TRUE; + } + } + + nm_log_dbg (LOGD_SUPPLICANT, "EAP-FAST is %ssupported", priv->fast_supported ? "" : "not "); + + g_hash_table_unref (props); } static void -check_supported_eap_methods (NMSupplicantManager *self) +check_capabilities (NMSupplicantManager *self) { NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); - dbus_g_proxy_begin_call (priv->props_proxy, "Get", - get_eap_methods_reply, self, NULL, + dbus_g_proxy_begin_call (priv->props_proxy, "GetAll", + get_capabilities_cb, self, NULL, G_TYPE_STRING, WPAS_DBUS_INTERFACE, - G_TYPE_STRING, "EapMethods", G_TYPE_INVALID); } @@ -239,7 +272,7 @@ name_owner_changed (NMDBusManager *dbus_mgr, if (!old_owner_good && new_owner_good) { nm_log_info (LOGD_SUPPLICANT, "wpa_supplicant started"); set_running (self, TRUE); - check_supported_eap_methods (self); + check_capabilities (self); } else if (old_owner_good && !new_owner_good) { nm_log_info (LOGD_SUPPLICANT, "wpa_supplicant stopped"); @@ -307,9 +340,9 @@ nm_supplicant_manager_init (NMSupplicantManager *self) priv->ifaces = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); - /* Grab list of supported EAP methods */ + /* Check generic supplicant capabilities */ if (priv->running) - check_supported_eap_methods (self); + check_capabilities (self); } static void