summary refs log tree commit diff
path: root/src/devices/wifi
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/wifi')
-rw-r--r--src/devices/wifi/nm-device-olpc-mesh.c23
-rw-r--r--src/devices/wifi/nm-device-wifi.c516
-rw-r--r--src/devices/wifi/nm-device-wifi.h2
-rw-r--r--src/devices/wifi/nm-wifi-ap.c368
-rw-r--r--src/devices/wifi/nm-wifi-ap.h3
-rw-r--r--src/devices/wifi/nm-wifi-utils.c4
-rw-r--r--src/devices/wifi/tests/test-general.c32
7 files changed, 749 insertions, 199 deletions
diff --git a/src/devices/wifi/nm-device-olpc-mesh.c b/src/devices/wifi/nm-device-olpc-mesh.c
index 24811931..ac78757d 100644
--- a/src/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/devices/wifi/nm-device-olpc-mesh.c
@@ -312,13 +312,13 @@ companion_state_changed_cb (NMDeviceWifi *companion,
 }
 
 static gboolean
-companion_scan_allowed_cb (NMDeviceWifi *companion, gpointer user_data)
+companion_scan_prohibited_cb (NMDeviceWifi *companion, gpointer user_data)
 {
 	NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (user_data);
 	NMDeviceState state = nm_device_get_state (NM_DEVICE (self));
 
 	/* Don't allow the companion to scan while configuring the mesh interface */
-	return (state < NM_DEVICE_STATE_PREPARE) || (state > NM_DEVICE_STATE_IP_CONFIG);
+	return (state >= NM_DEVICE_STATE_PREPARE) && (state <= NM_DEVICE_STATE_IP_CONFIG);
 }
 
 static gboolean
@@ -358,8 +358,8 @@ check_companion (NMDeviceOlpcMesh *self, NMDevice *other)
 	g_signal_connect (G_OBJECT (other), "notify::" NM_DEVICE_WIFI_SCANNING,
 	                  G_CALLBACK (companion_notify_cb), self);
 
-	g_signal_connect (G_OBJECT (other), NM_DEVICE_WIFI_SCANNING_ALLOWED,
-	                  G_CALLBACK (companion_scan_allowed_cb), self);
+	g_signal_connect (G_OBJECT (other), NM_DEVICE_WIFI_SCANNING_PROHIBITED,
+	                  G_CALLBACK (companion_scan_prohibited_cb), self);
 
 	g_signal_connect (G_OBJECT (other), NM_DEVICE_AUTOCONNECT_ALLOWED,
 	                  G_CALLBACK (companion_autoconnect_allowed_cb), self);
@@ -425,6 +425,13 @@ state_changed (NMDevice *device,
 		find_companion (NM_DEVICE_OLPC_MESH (device));
 }
 
+static guint32
+get_dhcp_timeout (NMDevice *device, int addr_family)
+{
+	/* shorter timeout for mesh connectivity */
+	return 20;
+}
+
 /*****************************************************************************/
 
 static void
@@ -465,11 +472,8 @@ constructed (GObject *object)
 
 	priv->manager = g_object_ref (nm_manager_get ());
 
-	g_signal_connect (priv->manager, "device-added", G_CALLBACK (device_added_cb), self);
-	g_signal_connect (priv->manager, "device-removed", G_CALLBACK (device_removed_cb), self);
-
-	/* shorter timeout for mesh connectivity */
-	nm_device_set_dhcp_timeout (NM_DEVICE (self), 20);
+	g_signal_connect (priv->manager, NM_MANAGER_DEVICE_ADDED, G_CALLBACK (device_added_cb), self);
+	g_signal_connect (priv->manager, NM_MANAGER_DEVICE_REMOVED, G_CALLBACK (device_removed_cb), self);
 }
 
 NMDevice *
@@ -519,6 +523,7 @@ nm_device_olpc_mesh_class_init (NMDeviceOlpcMeshClass *klass)
 	parent_class->act_stage1_prepare = act_stage1_prepare;
 	parent_class->act_stage2_config = act_stage2_config;
 	parent_class->state_changed = state_changed;
+	parent_class->get_dhcp_timeout = get_dhcp_timeout;
 
 	obj_properties[PROP_COMPANION] =
 	     g_param_spec_string (NM_DEVICE_OLPC_MESH_COMPANION, "", "",
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index 20692ed9..8bfddbd9 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2005 - 2012 Red Hat, Inc.
+ * Copyright (C) 2005 - 2017 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
@@ -63,8 +63,6 @@ _LOG_DECLARE_SELF(NMDeviceWifi);
 
 #define SCAN_RAND_MAC_ADDRESS_EXPIRE_MIN 5
 
-static NM_CACHED_QUARK_FCN ("wireless-secrets-tries", wireless_secrets_tries_quark)
-
 /*****************************************************************************/
 
 NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWifi,
@@ -79,7 +77,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWifi,
 enum {
 	ACCESS_POINT_ADDED,
 	ACCESS_POINT_REMOVED,
-	SCANNING_ALLOWED,
+	SCANNING_PROHIBITED,
 
 	LAST_SIGNAL
 };
@@ -119,6 +117,8 @@ typedef struct {
 	NMDeviceWifiCapabilities capabilities;
 
 	gint32 hw_addr_scan_expire;
+
+	guint             wps_timeout_id;
 } NMDeviceWifiPrivate;
 
 struct _NMDeviceWifi
@@ -132,7 +132,7 @@ struct _NMDeviceWifiClass
 	NMDeviceClass parent;
 
 	/* Signals */
-	gboolean (*scanning_allowed) (NMDeviceWifi *device);
+	gboolean (*scanning_prohibited) (NMDeviceWifi *device, gboolean periodic);
 };
 
 /*****************************************************************************/
@@ -143,7 +143,7 @@ G_DEFINE_TYPE (NMDeviceWifi, nm_device_wifi, NM_TYPE_DEVICE)
 
 /*****************************************************************************/
 
-static gboolean check_scanning_allowed (NMDeviceWifi *self);
+static gboolean check_scanning_prohibited (NMDeviceWifi *self, gboolean periodic);
 
 static void schedule_scan (NMDeviceWifi *self, gboolean backoff);
 
@@ -169,6 +169,10 @@ static void supplicant_iface_scan_done_cb (NMSupplicantInterface * iface,
                                            gboolean success,
                                            NMDeviceWifi * self);
 
+static void supplicant_iface_wps_credentials_cb (NMSupplicantInterface *iface,
+                                                 GVariant *credentials,
+                                                 NMDeviceWifi *self);
+
 static void supplicant_iface_notify_scanning_cb (NMSupplicantInterface * iface,
                                                  GParamSpec * pspec,
                                                  NMDeviceWifi * self);
@@ -177,7 +181,10 @@ static void supplicant_iface_notify_current_bss (NMSupplicantInterface *iface,
                                                  GParamSpec *pspec,
                                                  NMDeviceWifi *self);
 
-static void request_wireless_scan (NMDeviceWifi *self, gboolean force_if_scanning, GVariant *scan_options);
+static void request_wireless_scan (NMDeviceWifi *self,
+                                   gboolean periodic,
+                                   gboolean force_if_scanning,
+                                   const GPtrArray *ssids);
 
 static void ap_add_remove (NMDeviceWifi *self,
                            guint signum,
@@ -268,6 +275,10 @@ supplicant_interface_acquire (NMDeviceWifi *self)
 	                  G_CALLBACK (supplicant_iface_scan_done_cb),
 	                  self);
 	g_signal_connect (priv->sup_iface,
+	                  NM_SUPPLICANT_INTERFACE_WPS_CREDENTIALS,
+	                  G_CALLBACK (supplicant_iface_wps_credentials_cb),
+	                  self);
+	g_signal_connect (priv->sup_iface,
 	                  "notify::"NM_SUPPLICANT_INTERFACE_SCANNING,
 	                  G_CALLBACK (supplicant_iface_notify_scanning_cb),
 	                  self);
@@ -559,7 +570,7 @@ deactivate (NMDevice *device)
 
 	/* Ensure we trigger a scan after deactivating a Hotspot */
 	if (old_mode == NM_802_11_MODE_AP)
-		request_wireless_scan (self, FALSE, NULL);
+		request_wireless_scan (self, FALSE, FALSE, NULL);
 }
 
 static void
@@ -781,15 +792,18 @@ complete_connection (NMDevice *device,
 	NMSettingWireless *s_wifi;
 	const char *setting_mac;
 	char *str_ssid = NULL;
-	NMWifiAP *ap = NULL;
+	NMWifiAP *ap;
 	const GByteArray *ssid = NULL;
 	GByteArray *tmp_ssid = NULL;
 	GBytes *setting_ssid = NULL;
 	gboolean hidden = FALSE;
 	const char *perm_hw_addr;
+	const char *mode;
 
 	s_wifi = nm_connection_get_setting_wireless (connection);
 
+	mode = s_wifi ? nm_setting_wireless_get_mode (s_wifi) : NULL;
+
 	if (!specific_object) {
 		/* If not given a specific object, we need at minimum an SSID */
 		if (!s_wifi) {
@@ -809,19 +823,29 @@ complete_connection (NMDevice *device,
 			return FALSE;
 		}
 
-		/* Find a compatible AP in the scan list */
-		ap = find_first_compatible_ap (self, connection, FALSE);
+		if (!nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP)) {
+			/* Find a compatible AP in the scan list */
+			ap = find_first_compatible_ap (self, connection, FALSE);
 
-		/* If we still don't have an AP, then the WiFI settings needs to be
-		 * fully specified by the client.  Might not be able to find an AP
-		 * if the network isn't broadcasting the SSID for example.
-		 */
-		if (!ap) {
+			/* If we still don't have an AP, then the WiFI settings needs to be
+			 * fully specified by the client.  Might not be able to find an AP
+			 * if the network isn't broadcasting the SSID for example.
+			 */
+			if (!ap) {
+				if (!nm_setting_verify (NM_SETTING (s_wifi), connection, error))
+					return FALSE;
+
+				hidden = TRUE;
+			}
+		} else {
 			if (!nm_setting_verify (NM_SETTING (s_wifi), connection, error))
 				return FALSE;
-
-			hidden = TRUE;
+			ap = NULL;
 		}
+	} else if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP)) {
+		if (!nm_setting_verify (NM_SETTING (s_wifi), connection, error))
+			return FALSE;
+		ap = NULL;
 	} else {
 		ap = get_ap_by_path (self, specific_object);
 		if (!ap) {
@@ -1169,23 +1193,65 @@ _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset)
 	}
 }
 
+static GPtrArray *
+ssids_options_to_ptrarray (GVariant *value, GError **error)
+{
+	GPtrArray *ssids = NULL;
+	GByteArray *ssid_array;
+	GVariant *v;
+	const guint8 *bytes;
+	gsize len;
+	int num_ssids, i;
+
+	num_ssids = g_variant_n_children (value);
+	if (num_ssids > 32) {
+		g_set_error_literal (error,
+		                     NM_DEVICE_ERROR,
+		                     NM_DEVICE_ERROR_NOT_ALLOWED,
+		                     "too many SSIDs requested to scan");
+		return NULL;
+	}
+
+	if (num_ssids) {
+		ssids = g_ptr_array_new_full (num_ssids, (GDestroyNotify) g_byte_array_unref);
+		for (i = 0; i < num_ssids; i++) {
+			v = g_variant_get_child_value (value, i);
+			bytes = g_variant_get_fixed_array (v, &len, sizeof (guint8));
+			if (len > 32) {
+				g_set_error (error,
+				             NM_DEVICE_ERROR,
+				             NM_DEVICE_ERROR_NOT_ALLOWED,
+				             "SSID at index %d more than 32 bytes", i);
+				g_ptr_array_unref (ssids);
+				return NULL;
+			}
+
+			ssid_array = g_byte_array_new ();
+			g_byte_array_append (ssid_array, bytes, len);
+			g_ptr_array_add (ssids, ssid_array);
+		}
+	}
+	return ssids;
+}
+
 static void
-request_scan_cb (NMDevice *device,
-                 GDBusMethodInvocation *context,
-                 NMAuthSubject *subject,
-                 GError *error,
-                 gpointer user_data)
+dbus_request_scan_cb (NMDevice *device,
+                      GDBusMethodInvocation *context,
+                      NMAuthSubject *subject,
+                      GError *error,
+                      gpointer user_data)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
 	NMDeviceWifiPrivate *priv;
-	gs_unref_variant GVariant *new_scan_options = user_data;
+	gs_unref_variant GVariant *scan_options = user_data;
+	gs_unref_ptrarray GPtrArray *ssids = NULL;
 
 	if (error) {
 		g_dbus_method_invocation_return_gerror (context, error);
 		return;
 	}
 
-	if (!check_scanning_allowed (self)) {
+	if (check_scanning_prohibited (self, FALSE)) {
 		g_dbus_method_invocation_return_error_literal (context,
 		                                               NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
@@ -1195,7 +1261,29 @@ request_scan_cb (NMDevice *device,
 
 	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
-	request_wireless_scan (self, FALSE, new_scan_options);
+	if (scan_options) {
+		gs_unref_variant GVariant *val = g_variant_lookup_value (scan_options, "ssids", NULL);
+
+		if (val) {
+			gs_free_error GError *ssid_error = NULL;
+
+			if (!g_variant_is_of_type (val, G_VARIANT_TYPE ("aay"))) {
+				g_dbus_method_invocation_return_error_literal (context,
+				                                               NM_DEVICE_ERROR,
+				                                               NM_DEVICE_ERROR_NOT_ALLOWED,
+				                                               "Invalid 'ssid' scan option");
+				return;
+			}
+
+			ssids = ssids_options_to_ptrarray (val, &ssid_error);
+			if (ssid_error) {
+				g_dbus_method_invocation_return_gerror (context, ssid_error);
+				return;
+			}
+		}
+	}
+
+	request_wireless_scan (self, FALSE, FALSE, ssids);
 	g_dbus_method_invocation_return_value (context, NULL);
 }
 
@@ -1243,22 +1331,23 @@ impl_device_wifi_request_scan (NMDeviceWifi *self,
 	                       NULL,
 	                       NM_AUTH_PERMISSION_NETWORK_CONTROL,
 	                       TRUE,
-	                       request_scan_cb,
+	                       dbus_request_scan_cb,
 	                       options ? g_variant_ref (options) : NULL);
 }
 
 static gboolean
-scanning_allowed (NMDeviceWifi *self)
+scanning_prohibited (NMDeviceWifi *self, gboolean periodic)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	NMSupplicantInterfaceState supplicant_state;
-	NMConnection *connection;
 
-	g_return_val_if_fail (priv->sup_iface != NULL, FALSE);
+	g_return_val_if_fail (priv->sup_iface != NULL, TRUE);
 
-	/* Scanning not done in AP mode */
-	if (priv->mode == NM_802_11_MODE_AP)
-		return FALSE;
+	/* Don't scan when a an AP or Ad-Hoc connection is active as it will
+	 * disrupt connected clients or peers.
+	 */
+	if (priv->mode == NM_802_11_MODE_ADHOC || priv->mode == NM_802_11_MODE_AP)
+		return TRUE;
 
 	switch (nm_device_get_state (NM_DEVICE (self))) {
 	case NM_DEVICE_STATE_UNKNOWN:
@@ -1271,78 +1360,42 @@ scanning_allowed (NMDeviceWifi *self)
 	case NM_DEVICE_STATE_IP_CHECK:
 	case NM_DEVICE_STATE_SECONDARIES:
 	case NM_DEVICE_STATE_DEACTIVATING:
-		/* Don't scan when unusable or activating */
-		return FALSE;
+		/* Prohibit scans when unusable or activating */
+		return TRUE;
 	case NM_DEVICE_STATE_DISCONNECTED:
 	case NM_DEVICE_STATE_FAILED:
 		/* Can always scan when disconnected */
-		return TRUE;
+		return FALSE;
 	case NM_DEVICE_STATE_ACTIVATED:
-		/* Need to do further checks when activated */
+		/* Prohibit periodic scans when connected; we ask the supplicant to
+		 * background scan for us, unless the connection is locked to a specifc
+		 * BSSID.
+		 */
+		if (periodic)
+			return TRUE;
 		break;
 	}
 
-	/* Don't scan if the supplicant is busy */
+	/* Prohibit scans if the supplicant is busy */
 	supplicant_state = nm_supplicant_interface_get_state (priv->sup_iface);
 	if (   supplicant_state == NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING
 	    || supplicant_state == NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED
 	    || supplicant_state == NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE
 	    || supplicant_state == NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE
 	    || nm_supplicant_interface_get_scanning (priv->sup_iface))
-		return FALSE;
-
-	connection = nm_device_get_applied_connection (NM_DEVICE (self));
-	if (connection) {
-		NMSettingWireless *s_wifi;
-		const char *ip4_method = NULL;
-
-		/* Don't scan when a shared connection is active; it makes drivers mad */
-		ip4_method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-
-		if (!strcmp (ip4_method, NM_SETTING_IP4_CONFIG_METHOD_SHARED))
-			return FALSE;
-
-		/* Don't scan when the connection is locked to a specifc AP, since
-		 * intra-ESS roaming (which requires periodic scanning) isn't being
-		 * used due to the specific AP lock. (bgo #513820)
-		 */
-		s_wifi = nm_connection_get_setting_wireless (connection);
-		g_assert (s_wifi);
-		if (nm_setting_wireless_get_bssid (s_wifi))
-			return FALSE;
-	}
-
-	return TRUE;
-}
+		return TRUE;
 
-static gboolean
-scanning_allowed_accumulator (GSignalInvocationHint *ihint,
-                              GValue *return_accu,
-                              const GValue *handler_return,
-                              gpointer data)
-{
-	if (!g_value_get_boolean (handler_return))
-		g_value_set_boolean (return_accu, FALSE);
-	return TRUE;
+	/* Allow the scan */
+	return FALSE;
 }
 
 static gboolean
-check_scanning_allowed (NMDeviceWifi *self)
+check_scanning_prohibited (NMDeviceWifi *self, gboolean periodic)
 {
-	GValue instance = G_VALUE_INIT;
-	GValue retval = G_VALUE_INIT;
-
-	g_value_init (&instance, G_TYPE_OBJECT);
-	g_value_take_object (&instance, self);
-
-	g_value_init (&retval, G_TYPE_BOOLEAN);
-	g_value_set_boolean (&retval, TRUE);
+	gboolean prohibited = FALSE;
 
-	/* Use g_signal_emitv() rather than g_signal_emit() to avoid the return
-	 * value being changed if no handlers are connected */
-	g_signal_emitv (&instance, signals[SCANNING_ALLOWED], 0, &retval);
-
-	return g_value_get_boolean (&retval);
+	g_signal_emit (self, signals[SCANNING_PROHIBITED], 0, periodic, &prohibited);
+	return prohibited;
 }
 
 static gboolean
@@ -1354,8 +1407,12 @@ hidden_filter_func (NMSettings *settings,
 
 	if (!nm_connection_is_type (NM_CONNECTION (connection), NM_SETTING_WIRELESS_SETTING_NAME))
 		return FALSE;
-	s_wifi = (NMSettingWireless *) nm_connection_get_setting_wireless (NM_CONNECTION (connection));
-	return s_wifi ? nm_setting_wireless_get_hidden (s_wifi) : FALSE;
+	s_wifi = nm_connection_get_setting_wireless (NM_CONNECTION (connection));
+	if (!s_wifi)
+		return FALSE;
+	if (nm_streq0 (nm_setting_wireless_get_mode (s_wifi), NM_SETTING_WIRELESS_MODE_AP))
+		return FALSE;
+	return nm_setting_wireless_get_hidden (s_wifi);
 }
 
 static GPtrArray *
@@ -1410,32 +1467,11 @@ build_hidden_probe_list (NMDeviceWifi *self)
 	return ssids;
 }
 
-static GPtrArray *
-ssids_options_to_ptrarray (GVariant *value)
-{
-	GPtrArray *ssids = NULL;
-	GByteArray *ssid_array;
-	GVariant *v;
-	const guint8 *bytes;
-	gsize len;
-	int num_ssids, i;
-
-	num_ssids = g_variant_n_children (value);
-	if (num_ssids) {
-		ssids = g_ptr_array_new_full (num_ssids, (GDestroyNotify) g_byte_array_unref);
-		for (i = 0; i < num_ssids; i++) {
-			v = g_variant_get_child_value (value, i);
-			bytes = g_variant_get_fixed_array (v, &len, sizeof (guint8));
-			ssid_array = g_byte_array_new ();
-			g_byte_array_append (ssid_array, bytes, len);
-			g_ptr_array_add (ssids, ssid_array);
-		}
-	}
-	return ssids;
-}
-
 static void
-request_wireless_scan (NMDeviceWifi *self, gboolean force_if_scanning, GVariant *scan_options)
+request_wireless_scan (NMDeviceWifi *self,
+                       gboolean periodic,
+                       gboolean force_if_scanning,
+                       const GPtrArray *ssids)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	gboolean request_started = FALSE;
@@ -1447,24 +1483,14 @@ request_wireless_scan (NMDeviceWifi *self, gboolean force_if_scanning, GVariant
 		return;
 	}
 
-	if (check_scanning_allowed (self)) {
-		gs_unref_ptrarray GPtrArray *ssids = NULL;
+	if (!check_scanning_prohibited (self, periodic)) {
+		gs_unref_ptrarray GPtrArray *hidden_ssids = NULL;
 
 		_LOGD (LOGD_WIFI, "wifi-scan: scanning requested");
 
-		if (scan_options) {
-			GVariant *val = g_variant_lookup_value (scan_options, "ssids", NULL);
-
-			if (val) {
-				if (g_variant_is_of_type (val, G_VARIANT_TYPE ("aay")))
-					ssids = ssids_options_to_ptrarray (val);
-				else
-					_LOGD (LOGD_WIFI, "wifi-scan: ignoring invalid 'ssids' scan option");
-				g_variant_unref (val);
-			}
+		if (!ssids) {
+			ssids = hidden_ssids = build_hidden_probe_list (self);
 		}
-		if (!ssids)
-			ssids = build_hidden_probe_list (self);
 
 		if (_LOGD_ENABLED (LOGD_WIFI)) {
 			if (ssids) {
@@ -1478,7 +1504,7 @@ request_wireless_scan (NMDeviceWifi *self, gboolean force_if_scanning, GVariant
 					      ? nm_utils_ssid_to_utf8 (ssid->data, ssid->len)
 					      : NULL;
 					_LOGD (LOGD_WIFI, "wifi-scan: (%u) probe scanning SSID %s%s%s",
-					       i, NM_PRINT_FMT_QUOTED (foo, "\"", foo, "\"", "<hidden>"));
+					       i, NM_PRINT_FMT_QUOTED (foo, "\"", foo, "\"", "*any*"));
 					g_free (foo);
 				}
 			} else
@@ -1504,7 +1530,7 @@ request_wireless_scan_periodic (gpointer user_data)
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
 	priv->pending_scan_id = 0;
-	request_wireless_scan (self, FALSE, NULL);
+	request_wireless_scan (self, TRUE, FALSE, NULL);
 	return G_SOURCE_REMOVE;
 }
 
@@ -1747,6 +1773,7 @@ cleanup_association_attempt (NMDeviceWifi *self, gboolean disconnect)
 
 	nm_clear_g_source (&priv->sup_timeout_id);
 	nm_clear_g_source (&priv->link_timeout_id);
+	nm_clear_g_source (&priv->wps_timeout_id);
 	if (disconnect && priv->sup_iface)
 		nm_supplicant_interface_disconnect (priv->sup_iface);
 }
@@ -1789,9 +1816,19 @@ wifi_secrets_cb (NMActRequest *req,
 
 	if (error) {
 		_LOGW (LOGD_WIFI, "%s", error->message);
-		nm_device_state_changed (device,
-		                         NM_DEVICE_STATE_FAILED,
-		                         NM_DEVICE_STATE_REASON_NO_SECRETS);
+
+		if (g_error_matches (error, NM_AGENT_MANAGER_ERROR,
+		                     NM_AGENT_MANAGER_ERROR_USER_CANCELED)) {
+			/* Don't wait for WPS timeout on an explicit cancel. */
+			nm_clear_g_source (&priv->wps_timeout_id);
+		}
+
+		if (!priv->wps_timeout_id) {
+			/* Fail the device only if the WPS period is over too. */
+			nm_device_state_changed (device,
+			                         NM_DEVICE_STATE_FAILED,
+			                         NM_DEVICE_STATE_REASON_NO_SECRETS);
+		}
 	} else
 		nm_device_activate_schedule_stage1_device_prepare (device);
 }
@@ -1807,6 +1844,78 @@ wifi_secrets_cancel (NMDeviceWifi *self)
 }
 
 static void
+supplicant_iface_wps_credentials_cb (NMSupplicantInterface *iface,
+                                     GVariant *credentials,
+                                     NMDeviceWifi *self)
+{
+	NMActRequest *req;
+	GVariant *val, *secrets = NULL;
+	const char *array;
+	gsize psk_len = 0;
+	GError *error = NULL;
+
+	if (nm_device_get_state (NM_DEVICE (self)) != NM_DEVICE_STATE_NEED_AUTH) {
+		_LOGI (LOGD_DEVICE | LOGD_WIFI, "WPS: The connection can't be updated with credentials");
+		return;
+	}
+
+	_LOGI (LOGD_DEVICE | LOGD_WIFI, "WPS: Updating the connection with credentials");
+
+	req = nm_device_get_act_request (NM_DEVICE (self));
+	g_return_if_fail (NM_IS_ACT_REQUEST (req));
+
+	val = g_variant_lookup_value (credentials, "Key", G_VARIANT_TYPE_BYTESTRING);
+	if (val) {
+		char psk[64];
+
+		array = g_variant_get_fixed_array (val, &psk_len, 1);
+		if (psk_len >= 8 && psk_len <= 63) {
+			memcpy (psk, array, psk_len);
+			psk[psk_len] = '\0';
+			if (g_utf8_validate (psk, psk_len, NULL)) {
+				secrets = g_variant_new_parsed ("[{%s, [{%s, <%s>}]}]",
+				                                NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+				                                NM_SETTING_WIRELESS_SECURITY_PSK, psk);
+				g_variant_ref_sink (secrets);
+			}
+		}
+		if (!secrets)
+			_LOGW (LOGD_DEVICE | LOGD_WIFI, "WPS: ignore invalid PSK");
+		g_variant_unref (val);
+	}
+	if (secrets) {
+		if (nm_settings_connection_new_secrets (nm_act_request_get_settings_connection (req),
+		                                         nm_act_request_get_applied_connection (req),
+		                                         NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+		                                         secrets, &error)) {
+			wifi_secrets_cancel (self);
+			nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self));
+		} else {
+			_LOGW (LOGD_DEVICE | LOGD_WIFI, "WPS: Could not update the connection with credentials: %s", error->message);
+			g_error_free (error);
+		}
+		g_variant_unref (secrets);
+	}
+}
+
+static gboolean
+wps_timeout_cb (gpointer user_data)
+{
+	NMDeviceWifi *self = NM_DEVICE_WIFI (user_data);
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+
+	priv->wps_timeout_id = 0;
+	if (!priv->wifi_secrets_id) {
+		/* Fail only if the secrets are not being requested. */
+		nm_device_state_changed (NM_DEVICE (self),
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_NO_SECRETS);
+	}
+
+	return G_SOURCE_REMOVE;
+}
+
+static void
 wifi_secrets_get_secrets (NMDeviceWifi *self,
                           const char *setting_name,
                           NMSecretAgentGetSecretsFlags flags)
@@ -2055,6 +2164,7 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 	case NM_SUPPLICANT_INTERFACE_STATE_COMPLETED:
 		nm_clear_g_source (&priv->sup_timeout_id);
 		nm_clear_g_source (&priv->link_timeout_id);
+		nm_clear_g_source (&priv->wps_timeout_id);
 
 		/* If this is the initial association during device activation,
 		 * schedule the next activation stage.
@@ -2131,7 +2241,7 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 		/* we would clear _requested_scan_set() and trigger a new scan.
 		 * However, we don't want to cancel the current pending action, so force
 		 * a new scan request. */
-		request_wireless_scan (self, TRUE, NULL);
+		request_wireless_scan (self, FALSE, TRUE, NULL);
 		break;
 	default:
 		break;
@@ -2222,9 +2332,15 @@ handle_auth_or_fail (NMDeviceWifi *self,
                      NMActRequest *req,
                      gboolean new_secrets)
 {
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	const char *setting_name;
-	guint32 tries;
 	NMConnection *applied_connection;
+	NMSettingWirelessSecurity *s_wsec;
+	const char *bssid = NULL;
+	NM80211ApFlags ap_flags;
+	NMSettingWirelessSecurityWpsMethod wps_method;
+	const char *type;
+	NMSecretAgentGetSecretsFlags get_secret_flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION;
 
 	g_return_val_if_fail (NM_IS_DEVICE_WIFI (self), FALSE);
 
@@ -2233,14 +2349,50 @@ handle_auth_or_fail (NMDeviceWifi *self,
 		g_return_val_if_fail (req, FALSE);
 	}
 
-	applied_connection = nm_act_request_get_applied_connection (req);
-
-	tries = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (applied_connection), wireless_secrets_tries_quark ()));
-	if (tries > 3)
+	if (!nm_device_auth_retries_try_next (NM_DEVICE (self)))
 		return FALSE;
 
 	nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE);
 
+	applied_connection = nm_act_request_get_applied_connection (req);
+	s_wsec = nm_connection_get_setting_wireless_security (applied_connection);
+	wps_method = nm_setting_wireless_security_get_wps_method (s_wsec);
+
+	/* Negotiate the WPS method */
+	if (wps_method == NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DEFAULT)
+		wps_method = NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_AUTO;
+
+	if (   wps_method & NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_AUTO
+	    && priv->current_ap) {
+		/* Determine the method to use from AP capabilities. */
+		ap_flags = nm_wifi_ap_get_flags (priv->current_ap);
+		if (ap_flags & NM_802_11_AP_FLAGS_WPS_PBC)
+			wps_method |= NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PBC;
+		if (ap_flags & NM_802_11_AP_FLAGS_WPS_PIN)
+			wps_method |= NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PIN;
+		if (   ap_flags & NM_802_11_AP_FLAGS_WPS
+		    && wps_method == NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_AUTO) {
+			/* The AP doesn't specify which methods are supported. Allow all. */
+			wps_method |= NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PBC;
+			wps_method |= NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PIN;
+		}
+	}
+
+	if (wps_method & NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PBC) {
+		get_secret_flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_WPS_PBC_ACTIVE;
+		type = "pbc";
+	} else if (wps_method & NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PIN) {
+		type = "pin";
+	} else
+		type = NULL;
+
+	if (type) {
+		priv->wps_timeout_id = g_timeout_add_seconds (30, wps_timeout_cb, self);
+		if (priv->current_ap)
+			bssid = nm_wifi_ap_get_address (priv->current_ap);
+		nm_supplicant_interface_enroll_wps (priv->sup_iface, type, bssid, NULL);
+	}
+
 	nm_act_request_clear_secrets (req);
 	setting_name = nm_connection_need_secrets (applied_connection, NULL);
 	if (!setting_name) {
@@ -2248,10 +2400,9 @@ handle_auth_or_fail (NMDeviceWifi *self,
 		return FALSE;
 	}
 
-	wifi_secrets_get_secrets (self, setting_name,
-	                          NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
-	                          | (new_secrets ? NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW : 0));
-	g_object_set_qdata (G_OBJECT (applied_connection), wireless_secrets_tries_quark (), GUINT_TO_POINTER (++tries));
+	if (new_secrets)
+		get_secret_flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW;
+	wifi_secrets_get_secrets (self, setting_name, get_secret_flags);
 	return TRUE;
 }
 
@@ -2348,6 +2499,8 @@ build_supplicant_config (NMDeviceWifi *self,
 	NMSupplicantConfig *config = NULL;
 	NMSettingWireless *s_wireless;
 	NMSettingWirelessSecurity *s_wireless_sec;
+	NMSettingWirelessSecurityPmf pmf;
+	gs_free char *value = NULL;
 
 	g_return_val_if_fail (priv->sup_iface, NULL);
 
@@ -2370,6 +2523,11 @@ build_supplicant_config (NMDeviceWifi *self,
 		goto error;
 	}
 
+	if (!nm_supplicant_config_add_bgscan (config, connection, error)) {
+		g_prefix_error (error, "bgscan: ");
+		goto error;
+	}
+
 	s_wireless_sec = nm_connection_get_setting_wireless_security (connection);
 	if (s_wireless_sec) {
 		NMSetting8021x *s_8021x;
@@ -2378,12 +2536,46 @@ build_supplicant_config (NMDeviceWifi *self,
 		                                        nm_device_get_ifindex (NM_DEVICE (self)));
 
 		g_assert (con_uuid);
+
+		/* Configure PMF (802.11w) */
+		pmf = nm_setting_wireless_security_get_pmf (s_wireless_sec);
+		if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_DEFAULT) {
+			value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
+			                                               "wifi-sec.pmf",
+			                                               NM_DEVICE (self));
+			pmf = _nm_utils_ascii_str_to_int64 (value, 10,
+			                                    NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE,
+			                                    NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED,
+			                                    NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL);
+		}
+
+		/* Don't try to enable PMF on non-WPA networks */
+		if (!NM_IN_STRSET (nm_setting_wireless_security_get_key_mgmt (s_wireless_sec),
+		                   "wpa-eap",
+		                   "wpa-psk"))
+			pmf = NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE;
+
+		/* Check if we actually support PMF */
+		if (nm_supplicant_interface_get_pmf_support (priv->sup_iface) != NM_SUPPLICANT_FEATURE_YES) {
+			if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED) {
+				g_set_error_literal (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
+				                     "Supplicant does not support PMF");
+				goto error;
+			} else if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL) {
+				/* To be on the safe side, assume no support if we can't determine
+				 * capabilities.
+				 */
+				pmf = NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE;
+			}
+		}
+
 		s_8021x = nm_connection_get_setting_802_1x (connection);
 		if (!nm_supplicant_config_add_setting_wireless_security (config,
 		                                                         s_wireless_sec,
 		                                                         s_8021x,
 		                                                         con_uuid,
 		                                                         mtu,
+		                                                         pmf,
 		                                                         error)) {
 			g_prefix_error (error, "802-11-wireless-security: ");
 			goto error;
@@ -2430,6 +2622,8 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	s_wireless = nm_connection_get_setting_wireless (connection);
 	g_return_val_if_fail (s_wireless, NM_ACT_STAGE_RETURN_FAILURE);
 
+	nm_supplicant_interface_cancel_wps (priv->sup_iface);
+
 	mode = nm_setting_wireless_get_mode (s_wireless);
 	if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_INFRA) == 0)
 		priv->mode = NM_802_11_MODE_INFRA;
@@ -2578,6 +2772,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
 	nm_clear_g_source (&priv->sup_timeout_id);
 	nm_clear_g_source (&priv->link_timeout_id);
+	nm_clear_g_source (&priv->wps_timeout_id);
 
 	req = nm_device_get_act_request (device);
 	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
@@ -2873,9 +3068,6 @@ activation_success_handler (NMDevice *device)
 	/* Clear any critical protocol notification in the wifi stack */
 	nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), ifindex, FALSE);
 
-	/* Clear wireless secrets tries on success */
-	g_object_set_qdata (G_OBJECT (applied_connection), wireless_secrets_tries_quark (), NULL);
-
 	/* There should always be a current AP, either a fake one because we haven't
 	 * seen a scan result for the activated AP yet, or a real one from the
 	 * supplicant's scan list.
@@ -2922,21 +3114,6 @@ activation_success_handler (NMDevice *device)
 }
 
 static void
-activation_failure_handler (NMDevice *device)
-{
-	NMConnection *applied_connection;
-
-	applied_connection = nm_device_get_applied_connection (device);
-	g_assert (applied_connection);
-
-	/* Clear wireless secrets tries on failure */
-	g_object_set_qdata (G_OBJECT (applied_connection), wireless_secrets_tries_quark (), NULL);
-
-	/* Clear any critical protocol notification in the wifi stack */
-	nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ifindex (device), FALSE);
-}
-
-static void
 device_state_changed (NMDevice *device,
                       NMDeviceState new_state,
                       NMDeviceState old_state,
@@ -2990,12 +3167,13 @@ device_state_changed (NMDevice *device,
 		activation_success_handler (device);
 		break;
 	case NM_DEVICE_STATE_FAILED:
-		activation_failure_handler (device);
+		/* Clear any critical protocol notification in the wifi stack */
+		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ifindex (device), FALSE);
 		break;
 	case NM_DEVICE_STATE_DISCONNECTED:
 		/* Kick off a scan to get latest results */
 		priv->scan_interval = SCAN_INTERVAL_MIN;
-		request_wireless_scan (self, FALSE, NULL);
+		request_wireless_scan (self, FALSE, FALSE, NULL);
 		break;
 	default:
 		break;
@@ -3159,7 +3337,7 @@ nm_device_wifi_init (NMDeviceWifi *self)
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
 	priv->mode = NM_802_11_MODE_INFRA;
-	priv->aps = g_hash_table_new (g_str_hash, g_str_equal);
+	priv->aps = g_hash_table_new (nm_str_hash, g_str_equal);
 }
 
 static void
@@ -3261,7 +3439,7 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 
 	parent_class->state_changed = device_state_changed;
 
-	klass->scanning_allowed = scanning_allowed;
+	klass->scanning_prohibited = scanning_prohibited;
 
 	obj_properties[PROP_MODE] =
 	    g_param_spec_uint (NM_DEVICE_WIFI_MODE, "", "",
@@ -3322,13 +3500,13 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	                  G_TYPE_NONE, 1,
 	                  NM_TYPE_WIFI_AP);
 
-	signals[SCANNING_ALLOWED] =
-	    g_signal_new (NM_DEVICE_WIFI_SCANNING_ALLOWED,
+	signals[SCANNING_PROHIBITED] =
+	    g_signal_new (NM_DEVICE_WIFI_SCANNING_PROHIBITED,
 	                  G_OBJECT_CLASS_TYPE (object_class),
 	                  G_SIGNAL_RUN_LAST,
-	                  G_STRUCT_OFFSET (NMDeviceWifiClass, scanning_allowed),
-	                  scanning_allowed_accumulator, NULL, NULL,
-	                  G_TYPE_BOOLEAN, 0);
+	                  G_STRUCT_OFFSET (NMDeviceWifiClass, scanning_prohibited),
+	                  NULL, NULL, NULL,
+	                  G_TYPE_BOOLEAN, 1, G_TYPE_BOOLEAN);
 
 	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
 	                                        NMDBUS_TYPE_DEVICE_WIFI_SKELETON,
diff --git a/src/devices/wifi/nm-device-wifi.h b/src/devices/wifi/nm-device-wifi.h
index 024fe0ef..09707d4f 100644
--- a/src/devices/wifi/nm-device-wifi.h
+++ b/src/devices/wifi/nm-device-wifi.h
@@ -44,7 +44,7 @@
 #define NM_DEVICE_WIFI_ACCESS_POINT_REMOVED "access-point-removed"
 
 /* internal signals */
-#define NM_DEVICE_WIFI_SCANNING_ALLOWED    "scanning-allowed"
+#define NM_DEVICE_WIFI_SCANNING_PROHIBITED    "scanning-prohibited"
 
 typedef struct _NMDeviceWifi NMDeviceWifi;
 typedef struct _NMDeviceWifiClass NMDeviceWifiClass;
diff --git a/src/devices/wifi/nm-wifi-ap.c b/src/devices/wifi/nm-wifi-ap.c
index 7de0838f..bc823af0 100644
--- a/src/devices/wifi/nm-wifi-ap.c
+++ b/src/devices/wifi/nm-wifi-ap.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2004 - 2011 Red Hat, Inc.
+ * Copyright (C) 2004 - 2017 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
@@ -71,7 +71,7 @@ typedef struct {
 	/* Non-scanned attributes */
 	bool                fake:1;       /* Whether or not the AP is from a scan */
 	bool                hotspot:1;    /* Whether the AP is a local device's hotspot network */
-	gint32              last_seen;  /* Timestamp when the AP was seen lastly (obtained via nm_utils_get_monotonic_timestamp_s()) */
+	gint32              last_seen;    /* Timestamp when the AP was seen lastly (obtained via nm_utils_get_monotonic_timestamp_s()) */
 } NMWifiAPPrivate;
 
 struct _NMWifiAP {
@@ -79,7 +79,7 @@ struct _NMWifiAP {
 	NMWifiAPPrivate _priv;
 };
 
-struct _NMWifiAPClass{
+struct _NMWifiAPClass {
 	NMExportedObjectClass parent;
 };
 
@@ -376,6 +376,14 @@ nm_wifi_ap_set_fake (NMWifiAP *ap, gboolean fake)
 	return FALSE;
 }
 
+NM80211ApFlags
+nm_wifi_ap_get_flags (const NMWifiAP *ap)
+{
+	g_return_val_if_fail (NM_IS_WIFI_AP (ap), NM_802_11_AP_FLAGS_NONE);
+
+	return NM_WIFI_AP_GET_PRIVATE (ap)->flags;
+}
+
 static gboolean
 nm_wifi_ap_set_last_seen (NMWifiAP *ap, gint32 last_seen)
 {
@@ -435,6 +443,326 @@ security_from_vardict (GVariant *security)
 	return flags;
 }
 
+/*****************************************************************************/
+
+static guint32
+get_max_rate_ht_20 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 6500000;
+	case 1:
+	case 8:  return 13000000;
+	case 2:
+	case 16: return 19500000;
+	case 3:
+	case 9:
+	case 24: return 26000000;
+	case 4:
+	case 10:
+	case 17: return 39000000;
+	case 5:
+	case 11:
+	case 25: return 52000000;
+	case 6:
+	case 18: return 58500000;
+	case 7:  return 65000000;
+	case 12:
+	case 19:
+	case 26: return 78000000;
+	case 13:
+	case 27: return 104000000;
+	case 14:
+	case 20: return 117000000;
+	case 15: return 130000000;
+	case 21:
+	case 28: return 156000000;
+	case 22: return 175500000;
+	case 23: return 195000000;
+	case 29: return 208000000;
+	case 30: return 234000000;
+	case 31: return 260000000;
+	}
+	return 0;
+}
+
+static guint32
+get_max_rate_ht_40 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 13500000;
+	case 1:
+	case 8:  return 27000000;
+	case 2:  return 40500000;
+	case 3:
+	case 9:
+	case 24: return 54000000;
+	case 4:
+	case 10:
+	case 17: return 81000000;
+	case 5:
+	case 11:
+	case 25: return 108000000;
+	case 6:
+	case 18: return 121500000;
+	case 7:  return 135000000;
+	case 12:
+	case 19:
+	case 26: return 162000000;
+	case 13:
+	case 27: return 216000000;
+	case 14:
+	case 20: return 243000000;
+	case 15: return 270000000;
+	case 16: return 40500000;
+	case 21:
+	case 28: return 324000000;
+	case 22: return 364500000;
+	case 23: return 405000000;
+	case 29: return 432000000;
+	case 30: return 486000000;
+	case 31: return 540000000;
+	}
+	return 0;
+}
+
+static guint32
+get_max_rate_vht_80_ss1 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 29300000;
+	case 1:  return 58500000;
+	case 2:  return 87800000;
+	case 3:  return 117000000;
+	case 4:  return 175500000;
+	case 5:  return 234000000;
+	case 6:  return 263300000;
+	case 7:  return 292500000;
+	case 8:  return 351000000;
+	case 9:  return 390000000;
+	}
+	return 0;
+}
+
+static guint32
+get_max_rate_vht_80_ss2 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 58500000;
+	case 1:  return 117000000;
+	case 2:  return 175500000;
+	case 3:  return 234000000;
+	case 4:  return 351000000;
+	case 5:  return 468000000;
+	case 6:  return 526500000;
+	case 7:  return 585000000;
+	case 8:  return 702000000;
+	case 9:  return 780000000;
+	}
+	return 0;
+}
+
+static guint32
+get_max_rate_vht_80_ss3 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 87800000;
+	case 1:  return 175500000;
+	case 2:  return 263300000;
+	case 3:  return 351000000;
+	case 4:  return 526500000;
+	case 5:  return 702000000;
+	case 6:  return 0;
+	case 7:  return 877500000;
+	case 8:  return 105300000;
+	case 9:  return 117000000;
+	}
+	return 0;
+}
+
+static guint32
+get_max_rate_vht_160_ss1 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 58500000;
+	case 1:  return 117000000;
+	case 2:  return 175500000;
+	case 3:  return 234000000;
+	case 4:  return 351000000;
+	case 5:  return 468000000;
+	case 6:  return 526500000;
+	case 7:  return 585000000;
+	case 8:  return 702000000;
+	case 9:  return 780000000;
+	}
+	return 0;
+}
+
+static guint32
+get_max_rate_vht_160_ss2 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 117000000;
+	case 1:  return 234000000;
+	case 2:  return 351000000;
+	case 3:  return 468000000;
+	case 4:  return 702000000;
+	case 5:  return 936000000;
+	case 6:  return 1053000000;
+	case 7:  return 1170000000;
+	case 8:  return 1404000000;
+	case 9:  return 1560000000;
+	}
+	return 0;
+}
+
+static guint32
+get_max_rate_vht_160_ss3 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 175500000;
+	case 1:  return 351000000;
+	case 2:  return 526500000;
+	case 3:  return 702000000;
+	case 4:  return 1053000000;
+	case 5:  return 1404000000;
+	case 6:  return 1579500000;
+	case 7:  return 1755000000;
+	case 8:  return 2106000000;
+	case 9:  return 0;
+	}
+	return 0;
+}
+
+static gboolean
+get_max_rate_ht (const guint8 *bytes, guint len, guint32 *out_maxrate)
+{
+	guint32 mcs, i;
+	guint8 ht_cap_info;
+	const guint8 *supported_mcs_set;
+	guint32 rate;
+
+	/* http://standards.ieee.org/getieee802/download/802.11-2012.pdf
+	 * https://mrncciew.com/2014/10/19/cwap-ht-capabilities-ie/
+	 */
+
+	if (len != 26)
+		return FALSE;
+
+	ht_cap_info = bytes[0];
+	supported_mcs_set = &bytes[3];
+	*out_maxrate = 0;
+
+	/* Find the maximum supported mcs rate */
+	mcs = -1;
+	for (i = 0; i <= 76; i++) {
+		unsigned int mcs_octet = i / 8;
+		unsigned int MCS_RATE_BIT = 1 << i % 8;
+
+		if (supported_mcs_set[mcs_octet] & MCS_RATE_BIT) {
+			/* Check for 40Mhz wide channel support */
+			if (ht_cap_info & (1 << 1))
+				rate = get_max_rate_ht_40 (i);
+			else
+				rate = get_max_rate_ht_20 (i);
+
+			if (rate > *out_maxrate)
+				*out_maxrate = rate;
+		}
+	}
+
+	return TRUE;
+}
+
+static gboolean
+get_max_rate_vht (const guint8 *bytes, guint len, guint32 *out_maxrate)
+{
+	guint32 mcs, m;
+	guint8 vht_cap, tx_map;
+
+	/* https://tda802dot11.blogspot.it/2014/10/vht-capabilities-element-vht.html
+	 * http://chimera.labs.oreilly.com/books/1234000001739/ch03.html#management_frames */
+
+	if (len != 12)
+		return FALSE;
+
+	vht_cap = bytes[0];
+	tx_map = bytes[8];
+
+	/* Check for mcs rates 8 and 9 support */
+	if (tx_map & 0x2a)
+		mcs = 9;
+	else if (tx_map & 0x15)
+		mcs = 8;
+	else
+		mcs = 7;
+
+	/* Check for 160Mhz wide channel support and
+	 * spatial stream support */
+	if (vht_cap & (1 << 2)) {
+		if (tx_map & 0x30)
+			m = get_max_rate_vht_160_ss3 (mcs);
+		else if (tx_map & 0x0C)
+			m = get_max_rate_vht_160_ss2 (mcs);
+		else
+			m = get_max_rate_vht_160_ss1 (mcs);
+	} else {
+		if (tx_map & 0x30)
+			m = get_max_rate_vht_80_ss3 (mcs);
+		else if (tx_map & 0x0C)
+			m = get_max_rate_vht_80_ss2 (mcs);
+		else
+			m = get_max_rate_vht_80_ss1 (mcs);
+	}
+
+	*out_maxrate = m;
+	return TRUE;
+}
+
+/* Management Frame Information Element IDs, ieee80211_eid */
+#define WLAN_EID_HT_CAPABILITY       45
+#define WLAN_EID_VHT_CAPABILITY     191
+
+static guint32
+get_max_rate (const guint8 *bytes, gsize len)
+{
+	guint8 id, elem_len;
+	guint32 max_rate = 0;
+
+	while (len) {
+		guint32 m;
+
+		if (len < 2)
+			return 0;
+
+		id = *bytes++;
+		elem_len = *bytes++;
+		len -= 2;
+
+		if (elem_len > len)
+			return 0;
+
+		switch (id) {
+		case WLAN_EID_HT_CAPABILITY:
+			if (!get_max_rate_ht (bytes, elem_len, &m))
+				return 0;
+			max_rate = NM_MAX (max_rate, m);
+			break;
+		case WLAN_EID_VHT_CAPABILITY:
+			if (!get_max_rate_vht (bytes, elem_len, &m))
+				return 0;
+			max_rate = NM_MAX (max_rate, m);
+			break;
+		}
+
+		len -= elem_len;
+		bytes += elem_len;
+	}
+
+	return max_rate;
+}
+
+/*****************************************************************************/
+
 gboolean
 nm_wifi_ap_update_from_properties (NMWifiAP *ap,
                                    const char *supplicant_path,
@@ -444,11 +772,13 @@ nm_wifi_ap_update_from_properties (NMWifiAP *ap,
 	const guint8 *bytes;
 	GVariant *v;
 	gsize len;
+	gsize i;
 	gboolean b = FALSE;
 	const char *s;
 	gint16 i16;
 	guint16 u16;
 	gboolean changed = FALSE;
+	guint32 max_rate;
 
 	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);
 	g_return_val_if_fail (properties, FALSE);
@@ -460,6 +790,18 @@ nm_wifi_ap_update_from_properties (NMWifiAP *ap,
 	if (g_variant_lookup (properties, "Privacy", "b", &b) && b)
 		changed |= nm_wifi_ap_set_flags (ap, priv->flags | NM_802_11_AP_FLAGS_PRIVACY);
 
+	v = g_variant_lookup_value (properties, "WPS", G_VARIANT_TYPE_VARDICT);
+	if (v) {
+		if (g_variant_lookup (v, "Type", "&s", &s)) {
+			changed |= nm_wifi_ap_set_flags (ap, priv->flags | NM_802_11_AP_FLAGS_WPS);
+			if (strcmp (s, "pbc") == 0)
+				changed |= nm_wifi_ap_set_flags (ap, priv->flags | NM_802_11_AP_FLAGS_WPS_PBC);
+			else if (strcmp (s, "pin") == 0)
+				changed |= nm_wifi_ap_set_flags (ap, priv->flags | NM_802_11_AP_FLAGS_WPS_PIN);
+		}
+		g_variant_unref (v);
+	}
+
 	if (g_variant_lookup (properties, "Mode", "&s", &s)) {
 		if (!g_strcmp0 (s, "infrastructure"))
 			changed |= nm_wifi_ap_set_mode (ap, NM_802_11_MODE_INFRA);
@@ -497,21 +839,23 @@ nm_wifi_ap_update_from_properties (NMWifiAP *ap,
 		g_variant_unref (v);
 	}
 
+	max_rate = 0;
 	v = g_variant_lookup_value (properties, "Rates", G_VARIANT_TYPE ("au"));
 	if (v) {
 		const guint32 *rates = g_variant_get_fixed_array (v, &len, sizeof (guint32));
-		guint32 maxrate = 0;
-		int i;
 
-		/* Find the max AP rate */
-		for (i = 0; i < len; i++) {
-			if (rates[i] > maxrate)
-				maxrate = rates[i];
-		}
-		if (maxrate)
-			changed |= nm_wifi_ap_set_max_bitrate (ap, maxrate / 1000);
+		for (i = 0; i < len; i++)
+			max_rate = NM_MAX (max_rate, rates[i]);
+		g_variant_unref (v);
+	}
+	v = g_variant_lookup_value (properties, "IEs", G_VARIANT_TYPE_BYTESTRING);
+	if (v) {
+		bytes = g_variant_get_fixed_array (v, &len, 1);
+		max_rate = NM_MAX (max_rate, get_max_rate (bytes, len));
 		g_variant_unref (v);
 	}
+	if (max_rate)
+		changed |= nm_wifi_ap_set_max_bitrate (ap, max_rate / 1000);
 
 	v = g_variant_lookup_value (properties, "WPA", G_VARIANT_TYPE_VARDICT);
 	if (v) {
diff --git a/src/devices/wifi/nm-wifi-ap.h b/src/devices/wifi/nm-wifi-ap.h
index 5e64087c..dd5a4ad1 100644
--- a/src/devices/wifi/nm-wifi-ap.h
+++ b/src/devices/wifi/nm-wifi-ap.h
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2004 - 2011 Red Hat, Inc.
+ * Copyright (C) 2004 - 2017 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
@@ -88,6 +88,7 @@ gboolean          nm_wifi_ap_set_max_bitrate          (NMWifiAP *ap,
 gboolean          nm_wifi_ap_get_fake                 (const NMWifiAP *ap);
 gboolean          nm_wifi_ap_set_fake                 (NMWifiAP *ap,
                                                        gboolean fake);
+NM80211ApFlags    nm_wifi_ap_get_flags                (const NMWifiAP *self);
 
 const char       *nm_wifi_ap_to_string                (const NMWifiAP *self,
                                                        char *str_buf,
diff --git a/src/devices/wifi/nm-wifi-utils.c b/src/devices/wifi/nm-wifi-utils.c
index 06da92ce..3ff82004 100644
--- a/src/devices/wifi/nm-wifi-utils.c
+++ b/src/devices/wifi/nm-wifi-utils.c
@@ -777,10 +777,8 @@ nm_wifi_utils_level_to_quality (gint val)
 		val = 100 - (int) ((100.0 * (double) val) / 60.0);
 	} else {
 		/* Assume signal is a "quality" percentage */
-		val = CLAMP (val, 0, 100);
 	}
-	g_assert (val >= 0);
 
-	return (guint32) val;
+	return CLAMP (val, 0, 100);
 }
 
diff --git a/src/devices/wifi/tests/test-general.c b/src/devices/wifi/tests/test-general.c
index 3e61c5f0..89eebb22 100644
--- a/src/devices/wifi/tests/test-general.c
+++ b/src/devices/wifi/tests/test-general.c
@@ -44,16 +44,16 @@
 \
 		success = nm_connection_compare (src, expected, NM_SETTING_COMPARE_FLAG_EXACT); \
 		if (success == FALSE && DEBUG) { \
-			g_message ("\n- COMPLETED ---------------------------------\n"); \
+			g_print ("\n- COMPLETED ---------------------------------\n"); \
 			nm_connection_dump (src); \
-			g_message ("+ EXPECTED ++++++++++++++++++++++++++++++++++++\n"); \
+			g_print ("+ EXPECTED ++++++++++++++++++++++++++++++++++++\n"); \
 			nm_connection_dump (expected); \
-			g_message ("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); \
+			g_print ("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); \
 		} \
 		g_assert (success == TRUE); \
 	} else { \
 		if (success) { \
-			g_message ("\n- COMPLETED ---------------------------------\n"); \
+			g_print ("\n- COMPLETED ---------------------------------\n"); \
 			nm_connection_dump (src); \
 		} \
 		g_assert (success == FALSE); \
@@ -1334,6 +1334,28 @@ test_strength_wext (void)
 	g_assert_cmpint (nm_wifi_utils_level_to_quality (215), ==, 99);
 }
 
+#define _assert_strength_in_range(x) \
+	({ \
+		guint32 _x = (x); \
+		g_assert_cmpint (_x, >=, 0); \
+		g_assert_cmpint (_x, <=, 100); \
+	})
+
+static void
+test_strength_all (void)
+{
+	int val;
+
+	for (val = -200; val < 300; val++)
+		_assert_strength_in_range (nm_wifi_utils_level_to_quality (val));
+	_assert_strength_in_range (nm_wifi_utils_level_to_quality (G_MININT));
+	_assert_strength_in_range (nm_wifi_utils_level_to_quality (G_MAXINT));
+	_assert_strength_in_range (nm_wifi_utils_level_to_quality (G_MININT32));
+	_assert_strength_in_range (nm_wifi_utils_level_to_quality (G_MAXINT32));
+	_assert_strength_in_range (nm_wifi_utils_level_to_quality (G_MININT16));
+	_assert_strength_in_range (nm_wifi_utils_level_to_quality (G_MAXINT16));
+}
+
 /*****************************************************************************/
 
 NMTST_DEFINE ();
@@ -1497,6 +1519,8 @@ main (int argc, char **argv)
 	                 test_strength_percent);
 	g_test_add_func ("/wifi/strength/wext",
 	                 test_strength_wext);
+	g_test_add_func ("/wifi/strength/all",
+	                 test_strength_all);
 
 	return g_test_run ();
 }