summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2019-11-06 20:45:33 +0100
committerMichael Biebl <biebl@debian.org>2019-11-06 20:45:33 +0100
commit9c642eac191ef0dce49855d8da0a9e9af18d5113 (patch)
tree3d1c7574a02b28c68dd5f9a7288565c7ea4f0fe0 /src
parentd0425f68f7ee83a1dc5a6ad3688627c1b472a58b (diff)
New upstream version 1.20.6 upstream/1.20.6
Diffstat (limited to 'src')
-rw-r--r--src/devices/bluetooth/nm-device-bt.c3
-rw-r--r--src/devices/nm-acd-manager.c10
-rw-r--r--src/devices/nm-device-ethernet.c126
-rw-r--r--src/devices/nm-device-macvlan.c9
-rw-r--r--src/devices/wifi/nm-iwd-manager.c20
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c43
-rw-r--r--src/dhcp/tests/test-dhcp-dhclient.c19
-rw-r--r--src/initrd/nmi-cmdline-reader.c52
-rw-r--r--src/initrd/tests/test-cmdline-reader.c86
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c5
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c5
-rw-r--r--src/supplicant/nm-supplicant-config.c4
-rw-r--r--src/supplicant/nm-supplicant-interface.c48
-rw-r--r--src/supplicant/nm-supplicant-interface.h11
-rw-r--r--src/supplicant/tests/test-supplicant-config.c1
15 files changed, 373 insertions, 69 deletions
diff --git a/src/devices/bluetooth/nm-device-bt.c b/src/devices/bluetooth/nm-device-bt.c
index 0853c926..f8626011 100644
--- a/src/devices/bluetooth/nm-device-bt.c
+++ b/src/devices/bluetooth/nm-device-bt.c
@@ -550,6 +550,9 @@ ip_ifindex_changed_cb (NMModem *modem, GParamSpec *pspec, gpointer user_data)
 {
 	NMDevice *device = NM_DEVICE (user_data);
 
+	if (!nm_device_is_activating (device))
+		return;
+
 	if (!nm_device_set_ip_ifindex (device,
 	                               nm_modem_get_ip_ifindex (modem))) {
 		nm_device_state_changed (device,
diff --git a/src/devices/nm-acd-manager.c b/src/devices/nm-acd-manager.c
index aca7ffd2..036c4709 100644
--- a/src/devices/nm-acd-manager.c
+++ b/src/devices/nm-acd-manager.c
@@ -344,6 +344,8 @@ nm_acd_manager_start_probe (NMAcdManager *self, guint timeout)
 	if (success)
 		self->state = STATE_PROBING;
 
+	nm_assert (!self->channel);
+	nm_assert (self->event_id == 0);
 	n_acd_get_fd (self->acd, &fd);
 	self->channel = g_io_channel_unix_new (fd);
 	self->event_id = g_io_add_watch (self->channel, G_IO_IN, acd_event, self);
@@ -389,6 +391,7 @@ nm_acd_manager_announce_addresses (NMAcdManager *self)
 	GHashTableIter iter;
 	AddressInfo *info;
 	int r;
+	int fd;
 	gboolean success = TRUE;
 
 	r = acd_init (self);
@@ -428,6 +431,13 @@ nm_acd_manager_announce_addresses (NMAcdManager *self)
 		}
 	}
 
+	if (!self->channel) {
+		nm_assert (self->event_id == 0);
+		n_acd_get_fd (self->acd, &fd);
+		self->channel = g_io_channel_unix_new (fd);
+		self->event_id = g_io_add_watch (self->channel, G_IO_IN, acd_event, self);
+	}
+
 	return success ? 0 : -NME_UNSPEC;
 }
 
diff --git a/src/devices/nm-device-ethernet.c b/src/devices/nm-device-ethernet.c
index 3e84847e..8571e526 100644
--- a/src/devices/nm-device-ethernet.c
+++ b/src/devices/nm-device-ethernet.c
@@ -65,6 +65,7 @@ typedef struct Supplicant {
 
 	/* signal handler ids */
 	gulong iface_state_id;
+	gulong auth_state_id;
 
 	/* Timeouts and idles */
 	guint con_timeout_id;
@@ -415,6 +416,7 @@ supplicant_interface_release (NMDeviceEthernet *self)
 	nm_clear_g_source (&priv->supplicant_timeout_id);
 	nm_clear_g_source (&priv->supplicant.con_timeout_id);
 	nm_clear_g_signal_handler (priv->supplicant.iface, &priv->supplicant.iface_state_id);
+	nm_clear_g_signal_handler (priv->supplicant.iface, &priv->supplicant.auth_state_id);
 
 	if (priv->supplicant.iface) {
 		nm_supplicant_interface_disconnect (priv->supplicant.iface);
@@ -423,6 +425,62 @@ supplicant_interface_release (NMDeviceEthernet *self)
 }
 
 static void
+supplicant_auth_state_changed (NMSupplicantInterface *iface,
+                               GParamSpec *pspec,
+                               NMDeviceEthernet *self)
+{
+	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
+	NMSupplicantAuthState state;
+
+	state = nm_supplicant_interface_get_auth_state (priv->supplicant.iface);
+	_LOGD (LOGD_CORE, "supplicant auth state changed to %u", (unsigned) state);
+
+	if (state == NM_SUPPLICANT_AUTH_STATE_SUCCESS) {
+		nm_clear_g_signal_handler (priv->supplicant.iface, &priv->supplicant.iface_state_id);
+		nm_device_update_dynamic_ip_setup (NM_DEVICE (self));
+	}
+}
+
+static gboolean
+wired_auth_is_optional (NMDeviceEthernet *self)
+{
+	NMSetting8021x *s_8021x;
+
+	s_8021x = nm_device_get_applied_setting (NM_DEVICE (self), NM_TYPE_SETTING_802_1X);
+	g_return_val_if_fail (s_8021x, FALSE);
+	return nm_setting_802_1x_get_optional (s_8021x);
+}
+
+static void
+wired_auth_cond_fail (NMDeviceEthernet *self, NMDeviceStateReason reason)
+{
+	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+
+	if (wired_auth_is_optional (self)) {
+		_LOGI (LOGD_DEVICE | LOGD_ETHER,
+		       "Activation: (ethernet) 802.1X authentication is optional, continuing after a failure");
+		if (NM_IN_SET (nm_device_get_state (device),
+		               NM_DEVICE_STATE_CONFIG,
+		               NM_DEVICE_STATE_NEED_AUTH))
+			nm_device_activate_schedule_stage3_ip_config_start (device);
+
+		if (!priv->supplicant.auth_state_id) {
+			priv->supplicant.auth_state_id = g_signal_connect (priv->supplicant.iface,
+			                                                   "notify::" NM_SUPPLICANT_INTERFACE_AUTH_STATE,
+			                                                   G_CALLBACK (supplicant_auth_state_changed),
+			                                                   self);
+		}
+		return;
+	}
+
+	supplicant_interface_release (self);
+	nm_device_state_changed (NM_DEVICE (self),
+	                         NM_DEVICE_STATE_FAILED,
+	                         reason);
+}
+
+static void
 wired_secrets_cb (NMActRequest *req,
                   NMActRequestGetSecretsCallId *call_id,
                   NMSettingsConnection *connection,
@@ -451,11 +509,12 @@ wired_secrets_cb (NMActRequest *req,
 
 	if (error) {
 		_LOGW (LOGD_ETHER, "%s", error->message);
-		nm_device_state_changed (device,
-		                         NM_DEVICE_STATE_FAILED,
-		                         NM_DEVICE_STATE_REASON_NO_SECRETS);
-	} else
-		nm_device_activate_schedule_stage1_device_prepare (device);
+		wired_auth_cond_fail (self, NM_DEVICE_STATE_REASON_NO_SECRETS);
+		return;
+	}
+
+	supplicant_interface_release (self);
+	nm_device_activate_schedule_stage1_device_prepare (device);
 }
 
 static void
@@ -496,19 +555,17 @@ link_timeout_cb (gpointer user_data)
 {
 	NMDeviceEthernet *self = NM_DEVICE_ETHERNET (user_data);
 	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
-	NMDevice *dev = NM_DEVICE (self);
+	NMDevice *device = NM_DEVICE (self);
 	NMActRequest *req;
 	NMConnection *applied_connection;
 	const char *setting_name;
 
 	priv->supplicant_timeout_id = 0;
 
-	req = nm_device_get_act_request (dev);
+	req = nm_device_get_act_request (device);
 
-	if (nm_device_get_state (dev) == NM_DEVICE_STATE_ACTIVATED) {
-		nm_device_state_changed (dev,
-		                         NM_DEVICE_STATE_FAILED,
-		                         NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT);
+	if (nm_device_get_state (device) == NM_DEVICE_STATE_ACTIVATED) {
+		wired_auth_cond_fail (self, NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT);
 		return FALSE;
 	}
 
@@ -516,7 +573,7 @@ link_timeout_cb (gpointer user_data)
 	 * ARE checked - we are likely to have wrong key.  Ask the user for
 	 * another one.
 	 */
-	if (nm_device_get_state (dev) != NM_DEVICE_STATE_CONFIG)
+	if (nm_device_get_state (device) != NM_DEVICE_STATE_CONFIG)
 		goto time_out;
 
 	nm_active_connection_clear_secrets (NM_ACTIVE_CONNECTION (req));
@@ -528,16 +585,17 @@ link_timeout_cb (gpointer user_data)
 
 	_LOGI (LOGD_DEVICE | LOGD_ETHER,
 	       "Activation: (ethernet) disconnected during authentication, asking for new key.");
-	supplicant_interface_release (self);
+	if (!wired_auth_is_optional (self))
+		supplicant_interface_release (self);
 
-	nm_device_state_changed (dev, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+	nm_device_state_changed (device, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
 	wired_secrets_get_secrets (self, setting_name, NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW);
 
 	return FALSE;
 
 time_out:
 	_LOGW (LOGD_DEVICE | LOGD_ETHER, "link timed out.");
-	nm_device_state_changed (dev, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+	wired_auth_cond_fail (self, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
 
 	return FALSE;
 }
@@ -652,11 +710,8 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 	case NM_SUPPLICANT_INTERFACE_STATE_DOWN:
 		supplicant_interface_release (self);
 
-		if ((devstate == NM_DEVICE_STATE_ACTIVATED) || nm_device_is_activating (device)) {
-			nm_device_state_changed (device,
-			                         NM_DEVICE_STATE_FAILED,
-			                         NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
-		}
+		if ((devstate == NM_DEVICE_STATE_ACTIVATED) || nm_device_is_activating (device))
+			wired_auth_cond_fail (self, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
 		break;
 	default:
 		break;
@@ -685,6 +740,15 @@ handle_auth_or_fail (NMDeviceEthernet *self,
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
+	_LOGI (LOGD_DEVICE | LOGD_ETHER, "Activation: (ethernet) asking for new secrets");
+
+	/* Don't tear down supplicant if the authentication is optional
+	 * because in case of a failure in getting new secrets we want to
+	 * keep the supplicant alive.
+	 */
+	if (!wired_auth_is_optional (self))
+		supplicant_interface_release (self);
+
 	wired_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));
@@ -710,12 +774,8 @@ supplicant_connection_timeout_cb (gpointer user_data)
 	_LOGW (LOGD_DEVICE | LOGD_ETHER,
 	       "Activation: (ethernet) association took too long.");
 
-	supplicant_interface_release (self);
 	req = nm_device_get_act_request (device);
-	g_assert (req);
-
 	connection = nm_act_request_get_settings_connection (req);
-	g_assert (connection);
 
 	/* Ask for new secrets only if we've never activated this connection
 	 * before.  If we've connected before, don't bother the user with dialogs,
@@ -724,10 +784,8 @@ supplicant_connection_timeout_cb (gpointer user_data)
 	if (nm_settings_connection_get_timestamp (connection, &timestamp))
 		new_secrets = !timestamp;
 
-	if (handle_auth_or_fail (self, req, new_secrets) == NM_ACT_STAGE_RETURN_POSTPONE)
-		_LOGW (LOGD_DEVICE | LOGD_ETHER, "Activation: (ethernet) asking for new secrets");
-	else
-		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NO_SECRETS);
+	if (handle_auth_or_fail (self, req, new_secrets) == NM_ACT_STAGE_RETURN_FAILURE)
+		wired_auth_cond_fail (self, NM_DEVICE_STATE_REASON_NO_SECRETS);
 
 	return FALSE;
 }
@@ -850,19 +908,19 @@ pppoe_reconnect_delay (gpointer user_data)
 }
 
 static NMActStageReturn
-act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *out_failure_reason)
+act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-	NMDeviceEthernet *self = NM_DEVICE_ETHERNET (dev);
+	NMDeviceEthernet *self = NM_DEVICE_ETHERNET (device);
 	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
 	NMActStageReturn ret;
 
-	ret = NM_DEVICE_CLASS (nm_device_ethernet_parent_class)->act_stage1_prepare (dev, out_failure_reason);
+	ret = NM_DEVICE_CLASS (nm_device_ethernet_parent_class)->act_stage1_prepare (device, out_failure_reason);
 	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
 		return ret;
 
-	link_negotiation_set (dev);
+	link_negotiation_set (device);
 
-	if (!nm_device_hw_addr_set_cloned (dev, nm_device_get_applied_connection (dev), FALSE))
+	if (!nm_device_hw_addr_set_cloned (device, nm_device_get_applied_connection (device), FALSE))
 		return NM_ACT_STAGE_RETURN_FAILURE;
 
 	/* If we're re-activating a PPPoE connection a short while after
@@ -874,7 +932,7 @@ act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *out_failure_reason)
 		gint32 delay = nm_utils_get_monotonic_timestamp_s () - priv->last_pppoe_time;
 
 		if (   delay < PPPOE_RECONNECT_DELAY
-		    && nm_device_get_applied_setting (dev, NM_TYPE_SETTING_PPPOE)) {
+		    && nm_device_get_applied_setting (device, NM_TYPE_SETTING_PPPOE)) {
 			_LOGI (LOGD_DEVICE, "delaying PPPoE reconnect for %d seconds to ensure peer is ready...",
 			       delay);
 			g_assert (!priv->pppoe_wait_id);
diff --git a/src/devices/nm-device-macvlan.c b/src/devices/nm-device-macvlan.c
index 709f98da..ee8b9974 100644
--- a/src/devices/nm-device-macvlan.c
+++ b/src/devices/nm-device-macvlan.c
@@ -270,7 +270,7 @@ create_and_realize (NMDevice *device,
 /*****************************************************************************/
 
 static NMDeviceCapabilities
-get_generic_capabilities (NMDevice *dev)
+get_generic_capabilities (NMDevice *device)
 {
 	/* We assume MACVLAN interfaces always support carrier detect */
 	return NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_IS_SOFTWARE;
@@ -416,16 +416,17 @@ update_connection (NMDevice *device, NMConnection *connection)
 }
 
 static NMActStageReturn
-act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *out_failure_reason)
+act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
 	NMActStageReturn ret;
 
-	ret = NM_DEVICE_CLASS (nm_device_macvlan_parent_class)->act_stage1_prepare (dev, out_failure_reason);
+	ret = NM_DEVICE_CLASS (nm_device_macvlan_parent_class)->act_stage1_prepare (device, out_failure_reason);
 	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
 		return ret;
 
-	if (!nm_device_hw_addr_set_cloned (dev, nm_device_get_applied_connection (dev), FALSE))
+	if (!nm_device_hw_addr_set_cloned (device, nm_device_get_applied_connection (device), FALSE))
 		return NM_ACT_STAGE_RETURN_FAILURE;
+
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c
index 5b550ea4..c79f6cc6 100644
--- a/src/devices/wifi/nm-iwd-manager.c
+++ b/src/devices/wifi/nm-iwd-manager.c
@@ -150,6 +150,11 @@ agent_dbus_method_cb (GDBusConnection *connection,
 	network = g_dbus_object_manager_get_interface (priv->object_manager,
 	                                               network_path,
 	                                               NM_IWD_NETWORK_INTERFACE);
+	if (!network) {
+		_LOGE ("unable to find the network object");
+		return;
+	}
+
 
 	device_path = get_property_string_or_null (G_DBUS_PROXY (network), "Device");
 	if (!device_path) {
@@ -271,9 +276,22 @@ register_agent (NMIwdManager *self)
 	GDBusInterface *agent_manager;
 
 	agent_manager = g_dbus_object_manager_get_interface (priv->object_manager,
-	                                                     "/",
+	                                                     "/net/connman/iwd",
 	                                                     NM_IWD_AGENT_MANAGER_INTERFACE);
 
+	if (!agent_manager) {
+		/* IWD prior to 1.0 dated 30 October, 2019 has the agent manager on a
+		 * different path. */
+		agent_manager = g_dbus_object_manager_get_interface (priv->object_manager,
+		                                                     "/",
+		                                                     NM_IWD_AGENT_MANAGER_INTERFACE);
+	}
+
+	if (!agent_manager) {
+		_LOGE ("unable to register the IWD Agent: PSK/8021x Wi-Fi networks may not work");
+		return;
+	}
+
 	/* Register our agent */
 	g_dbus_proxy_call (G_DBUS_PROXY (agent_manager),
 	                   "RegisterAgent",
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/dhcp/nm-dhcp-dhclient-utils.c
index bf3df399..98f8c13a 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -304,11 +304,13 @@ nm_dhcp_dhclient_create_config (const char *interface,
 	if (orig_contents) {
 		gs_free const char **lines = NULL;
 		gsize line_i;
-		int nest = 0;
+		nm_auto_free_gstring GString *blocks_stack = NULL;
+		guint blocks_skip = 0;
 		gboolean in_alsoreq = FALSE;
 		gboolean in_req = FALSE;
 		char intf[IFNAMSIZ];
 
+		blocks_stack = g_string_new (NULL);
 		g_string_append_printf (new_contents, _("# Merged from %s\n\n"), orig_path);
 		intf[0] = '\0';
 
@@ -326,19 +328,38 @@ nm_dhcp_dhclient_create_config (const char *interface,
 			if (in_req) {
 				/* pass */
 			} else if (strchr (p, '{')) {
-				nest++;
-				if (   !intf[0]
-				    && NM_STR_HAS_PREFIX (p, "interface"))
-					if (read_interface (p, intf, sizeof (intf)))
-						continue;
+				if (   NM_STR_HAS_PREFIX (p, "lease")
+				    || NM_STR_HAS_PREFIX (p, "alias")
+				    || NM_STR_HAS_PREFIX (p, "interface")
+				    || NM_STR_HAS_PREFIX (p, "pseudo")) {
+					/* skip over these blocks, except 'interface' when it
+					 * matches the current interface */
+					blocks_skip++;
+					g_string_append_c (blocks_stack, 'b');
+					if (   !intf[0]
+					    && NM_STR_HAS_PREFIX (p, "interface")) {
+						if (read_interface (p, intf, sizeof (intf)))
+							continue;
+					}
+				} else {
+					/* allow other blocks (conditionals) */
+					if (!strchr (p, '}')) /* '} else {'  */
+						g_string_append_c (blocks_stack, 'c');
+				}
 			} else if (strchr (p, '}')) {
-				if (nest)
-					nest--;
-				intf[0] = '\0';
-				continue;
+				if (blocks_stack->len > 0) {
+					if (blocks_stack->str[blocks_stack->len - 1] == 'b') {
+						g_string_truncate (blocks_stack, blocks_stack->len - 1);
+						nm_assert(blocks_skip > 0);
+						blocks_skip--;
+						intf[0] = '\0';
+						continue;
+					}
+					g_string_truncate (blocks_stack, blocks_stack->len - 1);
+				}
 			}
 
-			if (nest && !intf[0])
+			if (blocks_skip > 0 && !intf[0])
 				continue;
 
 			if (intf[0] && !nm_streq (intf, interface))
diff --git a/src/dhcp/tests/test-dhcp-dhclient.c b/src/dhcp/tests/test-dhcp-dhclient.c
index 2cc7e5ca..9e51fceb 100644
--- a/src/dhcp/tests/test-dhcp-dhclient.c
+++ b/src/dhcp/tests/test-dhcp-dhclient.c
@@ -996,6 +996,11 @@ test_structured (void)
 		"    request subnet-mask, broadcast-address, time-offset, routers,\n"
 		"        domain-search, domain-name, domain-name-servers, host-name;\n"
 		"    require subnet-mask, domain-name-servers;\n"
+		"    if not option domain-name = \"example.org\" {\n"
+		"        prepend domain-name-servers 127.0.0.1;\n"
+		"    } else {\n"
+		"        prepend domain-name-servers 127.0.0.2;\n"
+		"    }  \n"
 		"    }  \n"
 		"\n"
 		"pseudo \"secondary\" \"eth0\"   {  \n"
@@ -1022,7 +1027,13 @@ test_structured (void)
 		"    interface \"eth0\";\n"
 		"    fixed-address 192.0.2.2;\n"
 		"    option subnet-mask 255.255.255.0;\n"
-		"  }  \n";
+		"  }  \n"
+		"if not option domain-name = \"example.org\" {\n"
+		"  prepend domain-name-servers 127.0.0.1;\n"
+		"  if not option domain-name = \"useless.example.com\" {\n"
+		"    prepend domain-name-servers 127.0.0.2;\n"
+		"  }\n"
+		"}\n";
 
 	static const char *const expected = \
 		"# Created by NetworkManager\n"
@@ -1033,6 +1044,12 @@ test_structured (void)
 		"send dhcp-client-identifier \"sad-and-useless\";\n"
 		"send dhcp-lease-time 8086;\n"
 		"require subnet-mask;\n"
+		"if not option domain-name = \"example.org\" {\n"
+		"prepend domain-name-servers 127.0.0.1;\n"
+		"if not option domain-name = \"useless.example.com\" {\n"
+		"prepend domain-name-servers 127.0.0.2;\n"
+		"}\n"
+		"}\n"
 		"\n"
 		"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
 		"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
diff --git a/src/initrd/nmi-cmdline-reader.c b/src/initrd/nmi-cmdline-reader.c
index 8ae6aa36..b90ee203 100644
--- a/src/initrd/nmi-cmdline-reader.c
+++ b/src/initrd/nmi-cmdline-reader.c
@@ -690,21 +690,57 @@ parse_rd_peerdns (GHashTable *connections, char *argument)
 }
 
 static void
-parse_rd_znet (GHashTable *connections, char *argument)
+parse_rd_znet (GHashTable *connections, char *argument, gboolean net_ifnames)
 {
 	const char *nettype;
 	const char *subchannels[4] = { 0, 0, 0, 0 };
 	const char *tmp;
+	gs_free char *ifname = NULL;
+	const char *prefix;
 	NMConnection *connection;
 	NMSettingWired *s_wired;
+	static int count_ctc = 0;
+	static int count_eth = 0;
+	int index;
 
 	nettype = get_word (&argument, ',');
 	subchannels[0] = get_word (&argument, ',');
 	subchannels[1] = get_word (&argument, ',');
-	if (!nm_streq0 (nettype, "ctc"))
+
+	if (nm_streq0 (nettype, "ctc")) {
+		if (net_ifnames == TRUE) {
+			prefix = "sl";
+		} else {
+			prefix = "ctc";
+			index = count_ctc++;
+		}
+	} else {
 		subchannels[2] = get_word (&argument, ',');
+		if (net_ifnames == TRUE) {
+			prefix = "en";
+		} else {
+			prefix = "eth";
+			index = count_eth++;
+		}
+	}
+
+	if (net_ifnames == TRUE) {
+		const char *bus_id;
+		size_t bus_id_len;
+		size_t bus_id_start;
 
-	connection = get_conn (connections, NULL, NM_SETTING_WIRED_SETTING_NAME);
+		/* The following logic is taken from names_ccw() in systemd/src/udev/udev-builtin-net_id.c */
+		bus_id = subchannels[0];
+		bus_id_len = strlen (bus_id);
+		bus_id_start = strspn (bus_id, ".0");
+		bus_id += bus_id_start < bus_id_len ? bus_id_start : bus_id_len - 1;
+
+		ifname = g_strdup_printf ("%sc%s", prefix, bus_id);
+	} else {
+		ifname = g_strdup_printf ("%s%d", prefix, index);
+	}
+
+	connection = get_conn (connections, ifname, NM_SETTING_WIRED_SETTING_NAME);
 	s_wired = nm_connection_get_setting_wired (connection);
 	g_object_set (s_wired,
 	              NM_SETTING_WIRED_S390_NETTYPE, nettype,
@@ -742,11 +778,19 @@ nmi_cmdline_reader_parse (const char *sysfs_dir, const char *const*argv)
 	gboolean ignore_bootif = FALSE;
 	gboolean neednet = FALSE;
 	gs_free char *bootif_val = NULL;
+	gboolean net_ifnames = TRUE;
 	int i;
 
 	connections = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref);
 
 	for (i = 0; argv[i]; i++) {
+		if (strcmp (argv[i], "net.ifnames=0") == 0)
+			net_ifnames = FALSE;
+		else if (g_str_has_prefix (argv[i], "net.ifnames="))
+			net_ifnames = TRUE;
+	}
+
+	for (i = 0; argv[i]; i++) {
 		gs_free char *argument_clone = NULL;
 		char *argument;
 
@@ -777,7 +821,7 @@ nmi_cmdline_reader_parse (const char *sysfs_dir, const char *const*argv)
 		else if (strcmp (tag, "rd.neednet") == 0)
 			neednet = _nm_utils_ascii_str_to_bool (argument, TRUE);
 		else if (strcmp (tag, "rd.znet") == 0)
-			parse_rd_znet (connections, argument);
+			parse_rd_znet (connections, argument, net_ifnames);
 		else if (strcasecmp (tag, "BOOTIF") == 0) {
 			nm_clear_g_free (&bootif_val);
 			bootif_val = g_strdup (argument);
diff --git a/src/initrd/tests/test-cmdline-reader.c b/src/initrd/tests/test-cmdline-reader.c
index a8a6e825..5ac82f50 100644
--- a/src/initrd/tests/test-cmdline-reader.c
+++ b/src/initrd/tests/test-cmdline-reader.c
@@ -801,9 +801,10 @@ test_rd_znet (void)
 {
 	gs_unref_hashtable GHashTable *connections = NULL;
 	const char *const*const ARGV = NM_MAKE_STRV ("ip=10.11.12.13::10.11.12.1:24:foo.example.com:enc800:none",
-	                                             "rd.znet=ctc,0.0.0800,0.0.0801,layer2=0,portno=1");
-	GHashTableIter h_iter;
+	                                             "rd.znet=qeth,0.0.0800,0.0.0801,0.0.0802,layer2=0,portno=1",
+	                                             "rd.znet=ctc,0.0.0600,0.0.0601,layer2=0,portno=0");
 	NMConnection *connection;
+	NMSettingConnection *s_con;
 	NMSettingWired *s_wired;
 	const char *const*v_subchannels;
 	const NMUtilsNamedValue s390_options[] = {
@@ -814,15 +815,17 @@ test_rd_znet (void)
 
 	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
 	g_assert (connections);
-	g_assert_cmpint (g_hash_table_size (connections), ==, 1);
-
-	g_hash_table_iter_init (&h_iter, connections);
-	if (!g_hash_table_iter_next (&h_iter, NULL, (gpointer *) &connection))
-		g_assert_not_reached ();
-	if (g_hash_table_iter_next (&h_iter, NULL, NULL))
-		g_assert_not_reached ();
+	g_assert_cmpint (g_hash_table_size (connections), ==, 2);
 
+	connection = g_hash_table_lookup (connections, "enc800");
 	g_assert (NM_IS_CONNECTION (connection));
+
+	s_con = nm_connection_get_setting_connection (connection);
+	g_assert (NM_IS_SETTING_CONNECTION (s_con));
+	g_assert_cmpstr (nm_setting_connection_get_connection_type (s_con), ==, NM_SETTING_WIRED_SETTING_NAME);
+	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "enc800");
+	g_assert_cmpstr (nm_setting_connection_get_interface_name (s_con), ==, "enc800");
+
 	s_wired = nm_connection_get_setting_wired (connection);
 	g_assert (NM_IS_SETTING_WIRED (s_wired));
 
@@ -830,7 +833,8 @@ test_rd_znet (void)
 	g_assert (v_subchannels);
 	g_assert_cmpstr (v_subchannels[0], ==, "0.0.0800");
 	g_assert_cmpstr (v_subchannels[1], ==, "0.0.0801");
-	g_assert_cmpstr (v_subchannels[2], ==, NULL);
+	g_assert_cmpstr (v_subchannels[2], ==, "0.0.0802");
+	g_assert_cmpstr (v_subchannels[3], ==, NULL);
 
 	g_assert_cmpint (nm_setting_wired_get_num_s390_options (s_wired), ==, G_N_ELEMENTS (s390_options));
 	for (i_s390_options_keys = 0; i_s390_options_keys < G_N_ELEMENTS (s390_options); i_s390_options_keys++) {
@@ -853,8 +857,67 @@ test_rd_znet (void)
 	}
 
 	nmtst_assert_connection_verifies_without_normalization (connection);
+
+	connection = g_hash_table_lookup (connections, "slc600");
+	g_assert (NM_IS_CONNECTION (connection));
+
+	s_con = nm_connection_get_setting_connection (connection);
+	g_assert (NM_IS_SETTING_CONNECTION (s_con));
+	g_assert_cmpstr (nm_setting_connection_get_connection_type (s_con), ==, NM_SETTING_WIRED_SETTING_NAME);
+	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "slc600");
+	g_assert_cmpstr (nm_setting_connection_get_interface_name (s_con), ==, "slc600");
+
+	s_wired = nm_connection_get_setting_wired (connection);
+	g_assert (NM_IS_SETTING_WIRED (s_wired));
+
+	v_subchannels = nm_setting_wired_get_s390_subchannels (s_wired);
+	g_assert (v_subchannels);
+	g_assert_cmpstr (v_subchannels[0], ==, "0.0.0600");
+	g_assert_cmpstr (v_subchannels[1], ==, "0.0.0601");
+	g_assert_cmpstr (v_subchannels[2], ==, NULL);
+
+	nmtst_assert_connection_verifies_without_normalization (connection);
 }
 
+static void
+test_rd_znet_legacy (void)
+{
+	gs_unref_hashtable GHashTable *connections = NULL;
+	const char *const*const ARGV = NM_MAKE_STRV ("ip=10.11.12.13::10.11.12.1:24:foo.example.com:eth0:none",
+	                                             "rd.znet=qeth,0.0.0800,0.0.0801,0.0.0802,layer2=0,portno=1",
+	                                             "rd.znet=ctc,0.0.0600,0.0.0601,layer2=0,portno=0",
+	                                             "net.ifnames=0");
+	NMConnection *connection;
+	NMSettingConnection *s_con;
+
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	g_assert (connections);
+	g_assert_cmpint (g_hash_table_size (connections), ==, 2);
+
+	connection = g_hash_table_lookup (connections, "eth0");
+	g_assert (NM_IS_CONNECTION (connection));
+
+	s_con = nm_connection_get_setting_connection (connection);
+	g_assert (NM_IS_SETTING_CONNECTION (s_con));
+	g_assert_cmpstr (nm_setting_connection_get_connection_type (s_con), ==, NM_SETTING_WIRED_SETTING_NAME);
+	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "eth0");
+	g_assert_cmpstr (nm_setting_connection_get_interface_name (s_con), ==, "eth0");
+
+	nmtst_assert_connection_verifies_without_normalization (connection);
+
+	connection = g_hash_table_lookup (connections, "ctc0");
+	g_assert (NM_IS_CONNECTION (connection));
+
+	s_con = nm_connection_get_setting_connection (connection);
+	g_assert (NM_IS_SETTING_CONNECTION (s_con));
+	g_assert_cmpstr (nm_setting_connection_get_connection_type (s_con), ==, NM_SETTING_WIRED_SETTING_NAME);
+	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "ctc0");
+	g_assert_cmpstr (nm_setting_connection_get_interface_name (s_con), ==, "ctc0");
+
+	nmtst_assert_connection_verifies_without_normalization (connection);
+}
+
+
 NMTST_DEFINE ();
 
 int main (int argc, char **argv)
@@ -877,7 +940,8 @@ int main (int argc, char **argv)
 	g_test_add_func ("/initrd/cmdline/bridge/default", test_bridge_default);
 	g_test_add_func ("/initrd/cmdline/ibft", test_ibft);
 	g_test_add_func ("/initrd/cmdline/ignore_extra", test_ignore_extra);
-	g_test_add_func ("/initrd/cmdline/rd_zdnet", test_rd_znet);
+	g_test_add_func ("/initrd/cmdline/rd_znet", test_rd_znet);
+	g_test_add_func ("/initrd/cmdline/rd_znet/legacy", test_rd_znet_legacy);
 
 	return g_test_run ();
 }
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
index 9c3ae10a..020766c8 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -3604,6 +3604,11 @@ next:
 	timeout = svGetValueInt64 (ifcfg, "IEEE_8021X_AUTH_TIMEOUT", 10, 0, G_MAXINT32, 0);
 	g_object_set (s_8021x, NM_SETTING_802_1X_AUTH_TIMEOUT, (int) timeout, NULL);
 
+	g_object_set (s_8021x,
+	              NM_SETTING_802_1X_OPTIONAL,
+	              svGetValueBoolean (ifcfg, "IEEE_8021X_OPTIONAL", FALSE),
+	              NULL);
+
 	return g_steal_pointer (&s_8021x);
 }
 
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
index 38dc5c8d..f3cd71bd 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -537,6 +537,11 @@ write_8021x_setting (NMConnection *connection,
 	vint = nm_setting_802_1x_get_auth_timeout (s_8021x);
 	svSetValueInt64_cond (ifcfg, "IEEE_8021X_AUTH_TIMEOUT", vint > 0, vint);
 
+	if (nm_setting_802_1x_get_optional (s_8021x))
+		svSetValueBoolean (ifcfg, "IEEE_8021X_OPTIONAL", TRUE);
+	else
+		svUnsetValue (ifcfg, "IEEE_8021X_OPTIONAL");
+
 	if (!write_8021x_certs (s_8021x, secrets, blobs, FALSE, ifcfg, error))
 		return FALSE;
 
diff --git a/src/supplicant/nm-supplicant-config.c b/src/supplicant/nm-supplicant-config.c
index f6e3c7da..a5a68070 100644
--- a/src/supplicant/nm-supplicant-config.c
+++ b/src/supplicant/nm-supplicant-config.c
@@ -881,8 +881,8 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 		}
 	}
 
-	/* Don't try to enable PMF on non-WPA networks */
-	if (!NM_IN_STRSET (key_mgmt, "wpa-eap", "wpa-psk"))
+	/* Don't try to enable PMF on non-WPA/SAE networks */
+	if (!NM_IN_STRSET (key_mgmt, "wpa-eap", "wpa-psk", "sae"))
 		pmf = NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE;
 
 	/* Check if we actually support PMF */
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c
index a54b770c..be4f65dc 100644
--- a/src/supplicant/nm-supplicant-interface.c
+++ b/src/supplicant/nm-supplicant-interface.c
@@ -122,6 +122,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSupplicantInterface,
 	PROP_WFD_SUPPORT,
 	PROP_FT_SUPPORT,
 	PROP_SHA384_SUPPORT,
+	PROP_AUTH_STATE,
 );
 
 typedef struct {
@@ -175,6 +176,7 @@ typedef struct {
 
 	gint64         last_scan; /* timestamp as returned by nm_utils_get_monotonic_timestamp_ms() */
 
+	NMSupplicantAuthState auth_state;
 } NMSupplicantInterfacePrivate;
 
 struct _NMSupplicantInterface {
@@ -833,6 +835,12 @@ nm_supplicant_interface_get_sha384_support (NMSupplicantInterface *self)
 	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->sha384_support;
 }
 
+NMSupplicantAuthState
+nm_supplicant_interface_get_auth_state (NMSupplicantInterface *self)
+{
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->auth_state;
+}
+
 void
 nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self,
                                         NMSupplicantFeature ap_support)
@@ -1344,6 +1352,34 @@ wpas_iface_network_request (GDBusProxy *proxy,
 }
 
 static void
+eap_changed (GDBusProxy *proxy,
+             const char *status,
+             const char *parameter,
+             gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	NMSupplicantAuthState auth_state = NM_SUPPLICANT_AUTH_STATE_UNKNOWN;
+
+	if (nm_streq0 (status, "started"))
+		auth_state = NM_SUPPLICANT_AUTH_STATE_STARTED;
+	else if (nm_streq0 (status, "completion")) {
+		if (nm_streq0 (parameter, "success"))
+			auth_state = NM_SUPPLICANT_AUTH_STATE_SUCCESS;
+		else if (nm_streq0 (parameter, "failure"))
+			auth_state = NM_SUPPLICANT_AUTH_STATE_FAILURE;
+	}
+
+	/* the state eventually reaches one of started, success or failure
+	 * so ignore any other intermediate (unknown) state change. */
+	if (   auth_state != NM_SUPPLICANT_AUTH_STATE_UNKNOWN
+	    && auth_state != priv->auth_state) {
+		priv->auth_state = auth_state;
+		_notify (self, PROP_AUTH_STATE);
+	}
+}
+
+static void
 props_changed_cb (GDBusProxy *proxy,
                   GVariant *changed_properties,
                   GStrv invalidated_properties,
@@ -1685,6 +1721,8 @@ on_iface_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_
 	                         G_CALLBACK (wpas_iface_bss_removed), self);
 	_nm_dbus_signal_connect (priv->iface_proxy, "NetworkRequest", G_VARIANT_TYPE ("(oss)"),
 	                         G_CALLBACK (wpas_iface_network_request), self);
+	_nm_dbus_signal_connect (priv->iface_proxy, "EAP", G_VARIANT_TYPE ("(ss)"),
+	                         G_CALLBACK (eap_changed), self);
 
 	/* Scan result aging parameters */
 	g_dbus_proxy_call (priv->iface_proxy,
@@ -2761,6 +2799,9 @@ get_property (GObject *object,
 	case PROP_P2P_AVAILABLE:
 		g_value_set_boolean (value, priv->p2p_capable && priv->p2p_proxy_acquired);
 		break;
+	case PROP_AUTH_STATE:
+		g_value_set_uint (value, priv->auth_state);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -3053,6 +3094,13 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 	                      G_PARAM_WRITABLE |
 	                      G_PARAM_CONSTRUCT_ONLY |
 	                      G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_AUTH_STATE] =
+	    g_param_spec_uint (NM_SUPPLICANT_INTERFACE_AUTH_STATE, "", "",
+	                       NM_SUPPLICANT_AUTH_STATE_UNKNOWN,
+	                       _NM_SUPPLICANT_AUTH_STATE_NUM - 1,
+	                       NM_SUPPLICANT_AUTH_STATE_UNKNOWN,
+	                       G_PARAM_READABLE |
+	                       G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
diff --git a/src/supplicant/nm-supplicant-interface.h b/src/supplicant/nm-supplicant-interface.h
index 653830da..e621f1a3 100644
--- a/src/supplicant/nm-supplicant-interface.h
+++ b/src/supplicant/nm-supplicant-interface.h
@@ -45,6 +45,14 @@ typedef enum {
 	NM_SUPPLICANT_INTERFACE_STATE_DOWN,
 } NMSupplicantInterfaceState;
 
+typedef enum {
+	NM_SUPPLICANT_AUTH_STATE_UNKNOWN,
+	NM_SUPPLICANT_AUTH_STATE_STARTED,
+	NM_SUPPLICANT_AUTH_STATE_SUCCESS,
+	NM_SUPPLICANT_AUTH_STATE_FAILURE,
+	_NM_SUPPLICANT_AUTH_STATE_NUM,
+} NMSupplicantAuthState;
+
 #define NM_TYPE_SUPPLICANT_INTERFACE            (nm_supplicant_interface_get_type ())
 #define NM_SUPPLICANT_INTERFACE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SUPPLICANT_INTERFACE, NMSupplicantInterface))
 #define NM_SUPPLICANT_INTERFACE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_SUPPLICANT_INTERFACE, NMSupplicantInterfaceClass))
@@ -71,6 +79,7 @@ typedef enum {
 #define NM_SUPPLICANT_INTERFACE_WFD_SUPPORT      "wfd-support"
 #define NM_SUPPLICANT_INTERFACE_FT_SUPPORT       "ft-support"
 #define NM_SUPPLICANT_INTERFACE_SHA384_SUPPORT   "sha384-support"
+#define NM_SUPPLICANT_INTERFACE_AUTH_STATE       "auth-state"
 
 /* Signals */
 #define NM_SUPPLICANT_INTERFACE_STATE            "state"
@@ -215,4 +224,6 @@ void nm_supplicant_interface_enroll_wps (NMSupplicantInterface *self,
 
 void nm_supplicant_interface_cancel_wps (NMSupplicantInterface *self);
 
+NMSupplicantAuthState nm_supplicant_interface_get_auth_state (NMSupplicantInterface *self);
+
 #endif /* __NM_SUPPLICANT_INTERFACE_H__ */
diff --git a/src/supplicant/tests/test-supplicant-config.c b/src/supplicant/tests/test-supplicant-config.c
index 819256fb..ea9bac1e 100644
--- a/src/supplicant/tests/test-supplicant-config.c
+++ b/src/supplicant/tests/test-supplicant-config.c
@@ -430,7 +430,6 @@ test_wifi_sae_psk (const char *psk)
 	NMTST_EXPECT_NM_INFO ("Config: added 'proto' value 'RSN'");
 	NMTST_EXPECT_NM_INFO ("Config: added 'pairwise' value 'TKIP CCMP'");
 	NMTST_EXPECT_NM_INFO ("Config: added 'group' value 'TKIP CCMP'");
-	NMTST_EXPECT_NM_INFO ("Config: added 'ieee80211w' value '0'");
 	config_dict = build_supplicant_config (connection, 1500, 0, TRUE, TRUE);
 
 	g_test_assert_expected_messages ();