summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/NetworkManagerUtils.c4
-rw-r--r--src/NetworkManagerUtils.h1
-rw-r--r--src/devices/bluetooth/nm-device-bt.c2
-rw-r--r--src/devices/nm-device-bridge.c101
-rw-r--r--src/devices/nm-device-ethernet.c5
-rw-r--r--src/devices/nm-device-infiniband.c1
-rw-r--r--src/devices/nm-device-ip-tunnel.c1
-rw-r--r--src/devices/nm-device-macvlan.c1
-rw-r--r--src/devices/nm-device-wpan.c1
-rw-r--r--src/devices/nm-device.c441
-rw-r--r--src/devices/nm-device.h2
-rw-r--r--src/devices/nm-lldp-listener.c4
-rw-r--r--src/devices/ovs/nm-ovsdb.c78
-rw-r--r--src/devices/tests/test-lldp.c5
-rw-r--r--src/devices/wifi/nm-device-olpc-mesh.c19
-rw-r--r--src/devices/wwan/nm-device-modem.c38
-rw-r--r--src/dhcp/nm-dhcp-client.c75
-rw-r--r--src/dhcp/nm-dhcp-client.h13
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c36
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.h5
-rw-r--r--src/dhcp/nm-dhcp-dhclient.c98
-rw-r--r--src/dhcp/nm-dhcp-dhcpcanon.c19
-rw-r--r--src/dhcp/nm-dhcp-dhcpcd.c5
-rw-r--r--src/dhcp/nm-dhcp-manager.c90
-rw-r--r--src/dhcp/nm-dhcp-manager.h2
-rw-r--r--src/dhcp/nm-dhcp-systemd.c302
-rw-r--r--src/dhcp/nm-dhcp-utils.c4
-rw-r--r--src/dhcp/tests/test-dhcp-dhclient.c69
-rw-r--r--src/dns/nm-dns-dnsmasq.c13
-rw-r--r--src/dns/nm-dns-manager.c163
-rw-r--r--src/dns/nm-dns-manager.h6
-rw-r--r--src/dnsmasq/nm-dnsmasq-manager.c2
-rw-r--r--src/initrd/meson.build32
-rw-r--r--src/initrd/tests/meson.build23
-rw-r--r--src/ndisc/nm-ndisc.h1
-rw-r--r--src/nm-config-data.c6
-rw-r--r--src/nm-config-data.h9
-rw-r--r--src/nm-connectivity.h15
-rw-r--r--src/nm-core-utils.c984
-rw-r--r--src/nm-core-utils.h82
-rw-r--r--src/nm-dbus-manager.c26
-rw-r--r--src/nm-ip4-config.c24
-rw-r--r--src/nm-ip6-config.c24
-rw-r--r--src/nm-manager.c14
-rw-r--r--src/nm-policy.c11
-rw-r--r--src/nm-test-utils-core.h4
-rw-r--r--src/platform/nm-fake-platform.c1
-rw-r--r--src/platform/nm-linux-platform.c23
-rw-r--r--src/platform/nm-platform-utils.c1
-rw-r--r--src/platform/nm-platform.c49
-rw-r--r--src/platform/nm-platform.h21
-rw-r--r--src/platform/nmp-object.c1
-rw-r--r--src/platform/tests/test-common.c2
-rw-r--r--src/platform/tests/test-common.h3
-rw-r--r--src/platform/wifi/nm-wifi-utils-nl80211.c1
-rw-r--r--src/platform/wpan/nm-wpan-utils.c2
-rw-r--r--src/settings/nm-settings-connection.c22
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-bad-dns121
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-bad-dns221
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-bad-entry20
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-bad-gateway21
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-bad-ipaddr21
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-bad-record18
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-dhcp33
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-static35
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-vlan19
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c31
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c9
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-plugin.c6
-rw-r--r--src/supplicant/nm-supplicant-config.c46
-rw-r--r--src/supplicant/nm-supplicant-interface.c36
-rw-r--r--src/supplicant/tests/test-supplicant-config.c4
-rw-r--r--src/systemd/meson.build3
-rw-r--r--src/systemd/nm-sd-utils.c61
-rw-r--r--src/systemd/nm-sd-utils.h38
-rw-r--r--src/systemd/src/basic/path-util.c2
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-identifier.c8
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-identifier.h2
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-option.c78
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-client.c31
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-lease.c2
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp6-client.c25
-rw-r--r--src/tests/config/test-config.c19
-rw-r--r--src/tests/test-general.c253
-rw-r--r--src/tests/test-ip6-config.c1
-rw-r--r--src/tests/test-systemd.c46
-rw-r--r--src/vpn/nm-vpn-connection.c40
87 files changed, 2777 insertions, 1160 deletions
diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c
index 1404854d..e3766809 100644
--- a/src/NetworkManagerUtils.c
+++ b/src/NetworkManagerUtils.c
@@ -881,6 +881,7 @@ nm_utils_match_connection (NMConnection *const*connections,
 int
 nm_match_spec_device_by_pllink (const NMPlatformLink *pllink,
                                 const char *match_device_type,
+                                const char *match_dhcp_plugin,
                                 const GSList *specs,
                                 int no_match_value)
 {
@@ -897,7 +898,8 @@ nm_match_spec_device_by_pllink (const NMPlatformLink *pllink,
 	                          pllink ? pllink->driver : NULL,
 	                          NULL,
 	                          NULL,
-	                          NULL);
+	                          NULL,
+	                          match_dhcp_plugin);
 
 	switch (m) {
 	case NM_MATCH_SPEC_MATCH:
diff --git a/src/NetworkManagerUtils.h b/src/NetworkManagerUtils.h
index b26d08bd..efbd9037 100644
--- a/src/NetworkManagerUtils.h
+++ b/src/NetworkManagerUtils.h
@@ -50,6 +50,7 @@ NMConnection *nm_utils_match_connection (NMConnection *const*connections,
 
 int nm_match_spec_device_by_pllink (const NMPlatformLink *pllink,
                                     const char *match_device_type,
+                                    const char *match_dhcp_plugin,
                                     const GSList *specs,
                                     int no_match_value);
 
diff --git a/src/devices/bluetooth/nm-device-bt.c b/src/devices/bluetooth/nm-device-bt.c
index 1f650d33..f4a1b709 100644
--- a/src/devices/bluetooth/nm-device-bt.c
+++ b/src/devices/bluetooth/nm-device-bt.c
@@ -323,7 +323,7 @@ complete_connection (NMDevice *device,
 		} else {
 			fallback_prefix = _("CDMA connection");
 			if (!nm_setting_cdma_get_number (s_cdma))
-				g_object_set (G_OBJECT (s_cdma), NM_SETTING_GSM_NUMBER, "#777", NULL);
+				g_object_set (G_OBJECT (s_cdma), NM_SETTING_CDMA_NUMBER, "#777", NULL);
 		}
 	} else {
 		g_set_error_literal (error,
diff --git a/src/devices/nm-device-bridge.c b/src/devices/nm-device-bridge.c
index e79de95c..68e1ac28 100644
--- a/src/devices/nm-device-bridge.c
+++ b/src/devices/nm-device-bridge.c
@@ -168,26 +168,52 @@ complete_connection (NMDevice *device,
 typedef struct {
 	const char *name;
 	const char *sysname;
-	gboolean default_if_zero;
-	gboolean user_hz_compensate;
+	uint nm_min;
+	uint nm_max;
+	uint nm_default;
+	bool default_if_zero;
+	bool user_hz_compensate;
+	bool only_with_stp;
 } Option;
 
 static const Option master_options[] = {
-	{ NM_SETTING_BRIDGE_STP, "stp_state", FALSE, FALSE },
-	{ NM_SETTING_BRIDGE_PRIORITY, "priority", TRUE, FALSE },
-	{ NM_SETTING_BRIDGE_FORWARD_DELAY, "forward_delay", TRUE, TRUE },
-	{ NM_SETTING_BRIDGE_HELLO_TIME, "hello_time", TRUE, TRUE },
-	{ NM_SETTING_BRIDGE_MAX_AGE, "max_age", TRUE, TRUE },
-	{ NM_SETTING_BRIDGE_AGEING_TIME, "ageing_time", TRUE, TRUE },
-	{ NM_SETTING_BRIDGE_GROUP_FORWARD_MASK, "group_fwd_mask", TRUE, FALSE },
-	{ NM_SETTING_BRIDGE_MULTICAST_SNOOPING, "multicast_snooping", FALSE, FALSE },
+	{ NM_SETTING_BRIDGE_STP,                "stp_state", /* this must stay as the first item */
+	                                        0, 1, 1,
+	                                        FALSE, FALSE, FALSE },
+	{ NM_SETTING_BRIDGE_PRIORITY,           "priority",
+	                                        0, G_MAXUINT16, 0x8000,
+	                                        TRUE, FALSE, TRUE },
+	{ NM_SETTING_BRIDGE_FORWARD_DELAY,      "forward_delay",
+	                                        0, NM_BR_MAX_FORWARD_DELAY, 15,
+	                                        TRUE, TRUE, TRUE},
+	{ NM_SETTING_BRIDGE_HELLO_TIME,         "hello_time",
+	                                        0, NM_BR_MAX_HELLO_TIME, 2,
+	                                        TRUE, TRUE, TRUE },
+	{ NM_SETTING_BRIDGE_MAX_AGE,            "max_age",
+	                                        0, NM_BR_MAX_MAX_AGE, 20,
+	                                        TRUE, TRUE, TRUE },
+	{ NM_SETTING_BRIDGE_AGEING_TIME,        "ageing_time",
+	                                        NM_BR_MIN_AGEING_TIME, NM_BR_MAX_AGEING_TIME, 300,
+	                                        TRUE, TRUE, FALSE },
+	{ NM_SETTING_BRIDGE_GROUP_FORWARD_MASK, "group_fwd_mask",
+	                                        0, 0xFFFF, 0,
+	                                        TRUE, FALSE, FALSE },
+	{ NM_SETTING_BRIDGE_MULTICAST_SNOOPING, "multicast_snooping",
+	                                        0, 1, 1,
+	                                        FALSE, FALSE, FALSE },
 	{ NULL, NULL }
 };
 
 static const Option slave_options[] = {
-	{ NM_SETTING_BRIDGE_PORT_PRIORITY, "priority", TRUE, FALSE },
-	{ NM_SETTING_BRIDGE_PORT_PATH_COST, "path_cost", TRUE, FALSE },
-	{ NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE, "hairpin_mode", FALSE, FALSE },
+	{ NM_SETTING_BRIDGE_PORT_PRIORITY,     "priority",
+	                                       0, NM_BR_PORT_MAX_PRIORITY, NM_BR_PORT_DEF_PRIORITY,
+	                                       TRUE, FALSE },
+	{ NM_SETTING_BRIDGE_PORT_PATH_COST,    "path_cost",
+	                                       0, NM_BR_PORT_MAX_PATH_COST, 100,
+	                                       TRUE, FALSE },
+	{ NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE, "hairpin_mode",
+	                                       0, 1, 0,
+	                                       FALSE, FALSE },
 	{ NULL, NULL }
 };
 
@@ -275,23 +301,43 @@ update_connection (NMDevice *device, NMConnection *connection)
 	NMSettingBridge *s_bridge = nm_connection_get_setting_bridge (connection);
 	int ifindex = nm_device_get_ifindex (device);
 	const Option *option;
+	gs_free char *stp = NULL;
+	int stp_value;
 
 	if (!s_bridge) {
 		s_bridge = (NMSettingBridge *) nm_setting_bridge_new ();
 		nm_connection_add_setting (connection, (NMSetting *) s_bridge);
 	}
 
-	for (option = master_options; option->name; option++) {
+	option = master_options;
+	nm_assert (nm_streq (option->sysname, "stp_state"));
+
+	stp = nm_platform_sysctl_master_get_option (nm_device_get_platform (device), ifindex, option->sysname);
+	stp_value = _nm_utils_ascii_str_to_int64 (stp, 10, option->nm_min, option->nm_max, option->nm_default);
+	g_object_set (s_bridge, option->name, stp_value, NULL);
+	option++;
+
+	for (; option->name; option++) {
 		gs_free char *str = nm_platform_sysctl_master_get_option (nm_device_get_platform (device), ifindex, option->sysname);
-		int value;
+		uint value;
 
-		if (str) {
-			value = strtol (str, NULL, 10);
+		if (!stp_value && option->only_with_stp)
+			continue;
 
+		if (str) {
 			/* See comments in set_sysfs_uint() about centiseconds. */
-			if (option->user_hz_compensate)
+			if (option->user_hz_compensate) {
+				value = _nm_utils_ascii_str_to_int64 (str, 10,
+				                                      option->nm_min * 100,
+				                                      option->nm_max * 100,
+				                                      option->nm_default * 100);
 				value /= 100;
-
+			} else {
+				value = _nm_utils_ascii_str_to_int64 (str, 10,
+				                                      option->nm_min,
+				                                      option->nm_max,
+				                                      option->nm_default);
+			}
 			g_object_set (s_bridge, option->name, value, NULL);
 		} else
 			_LOGW (LOGD_BRIDGE, "failed to read bridge setting '%s'", option->sysname);
@@ -322,15 +368,22 @@ master_update_slave_connection (NMDevice *device,
 
 	for (option = slave_options; option->name; option++) {
 		gs_free char *str = nm_platform_sysctl_slave_get_option (nm_device_get_platform (device), ifindex_slave, option->sysname);
-		int value;
+		uint value;
 
 		if (str) {
-			value = strtol (str, NULL, 10);
-
 			/* See comments in set_sysfs_uint() about centiseconds. */
-			if (option->user_hz_compensate)
+			if (option->user_hz_compensate) {
+				value = _nm_utils_ascii_str_to_int64 (str, 10,
+				                                      option->nm_min * 100,
+				                                      option->nm_max * 100,
+				                                      option->nm_default * 100);
 				value /= 100;
-
+			} else {
+				value = _nm_utils_ascii_str_to_int64 (str, 10,
+				                                      option->nm_min,
+				                                      option->nm_max,
+				                                      option->nm_default);
+			}
 			g_object_set (s_port, option->name, value, NULL);
 		} else
 			_LOGW (LOGD_BRIDGE, "failed to read bridge port setting '%s'", option->sysname);
diff --git a/src/devices/nm-device-ethernet.c b/src/devices/nm-device-ethernet.c
index 0fb8fea4..e7262683 100644
--- a/src/devices/nm-device-ethernet.c
+++ b/src/devices/nm-device-ethernet.c
@@ -1446,7 +1446,6 @@ new_default_connection (NMDevice *self)
 	const char *uprop = "0";
 	gs_free char *defname = NULL;
 	gs_free char *uuid = NULL;
-	gs_free char *machine_id = NULL;
 	guint i, n_connections;
 
 	if (nm_config_get_no_auto_default_for_device (nm_config_get (), self))
@@ -1470,12 +1469,10 @@ new_default_connection (NMDevice *self)
 	if (!defname)
 		return NULL;
 
-	machine_id = nm_utils_machine_id_read ();
-
 	/* Create a stable UUID. The UUID is also the Network_ID for stable-privacy addr-gen-mode,
 	 * thus when it changes we will also generate different IPv6 addresses. */
 	uuid = _nm_utils_uuid_generate_from_strings ("default-wired",
-	                                             machine_id ?: "",
+	                                             nm_utils_machine_id_str (),
 	                                             defname,
 	                                             perm_hw_addr,
 	                                             NULL);
diff --git a/src/devices/nm-device-infiniband.c b/src/devices/nm-device-infiniband.c
index 41fac157..5138b684 100644
--- a/src/devices/nm-device-infiniband.c
+++ b/src/devices/nm-device-infiniband.c
@@ -22,6 +22,7 @@
 
 #include "nm-device-infiniband.h"
 
+#include <linux/if.h>
 #include <linux/if_infiniband.h>
 
 #include "NetworkManagerUtils.h"
diff --git a/src/devices/nm-device-ip-tunnel.c b/src/devices/nm-device-ip-tunnel.c
index 568403c6..1c7e6d51 100644
--- a/src/devices/nm-device-ip-tunnel.c
+++ b/src/devices/nm-device-ip-tunnel.c
@@ -27,6 +27,7 @@
 #include <linux/if.h>
 #include <linux/ip.h>
 #include <linux/if_tunnel.h>
+#include <linux/ip6_tunnel.h>
 
 #include "nm-device-private.h"
 #include "nm-manager.h"
diff --git a/src/devices/nm-device-macvlan.c b/src/devices/nm-device-macvlan.c
index ff386c82..2b212154 100644
--- a/src/devices/nm-device-macvlan.c
+++ b/src/devices/nm-device-macvlan.c
@@ -23,6 +23,7 @@
 #include "nm-device-macvlan.h"
 
 #include <string.h>
+#include <linux/if_link.h>
 
 #include "nm-device-private.h"
 #include "settings/nm-settings.h"
diff --git a/src/devices/nm-device-wpan.c b/src/devices/nm-device-wpan.c
index f910b0b8..05b507d5 100644
--- a/src/devices/nm-device-wpan.c
+++ b/src/devices/nm-device-wpan.c
@@ -25,6 +25,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/types.h>
+#include <linux/if.h>
 
 #include "nm-act-request.h"
 #include "nm-device-private.h"
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index f2dbe659..89e1cc51 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -37,7 +37,6 @@
 #include <linux/if_arp.h>
 #include <linux/rtnetlink.h>
 #include <linux/pkt_sched.h>
-#include <uuid/uuid.h>
 
 #include "nm-utils/nm-dedup-multi.h"
 #include "nm-utils/nm-random-utils.h"
@@ -386,13 +385,14 @@ typedef struct _NMDevicePrivate {
 	bool            v4_route_table_initialized:1;
 	bool            v6_route_table_initialized:1;
 
-	NMDeviceAutoconnectBlockedFlags autoconnect_blocked_flags:4;
+	NMDeviceAutoconnectBlockedFlags autoconnect_blocked_flags:5;
 
 	bool            is_enslaved:1;
 	bool            master_ready_handled:1;
 
 	bool            ipv6ll_handle:1; /* TRUE if NM handles the device's IPv6LL address */
 	bool            ipv6ll_has:1;
+	bool            device_link_changed_down:1;
 
 	/* Generic DHCP stuff */
 	char *          dhcp_anycast_address;
@@ -1264,6 +1264,8 @@ _get_stable_id (NMDevice *self,
 		gs_free char *generated = NULL;
 		NMUtilsStableType stable_type;
 		NMSettingConnection *s_con;
+		gboolean hwaddr_is_fake;
+		const char *hwaddr;
 		const char *stable_id;
 		const char *uuid;
 
@@ -1280,9 +1282,15 @@ _get_stable_id (NMDevice *self,
 
 		uuid = nm_connection_get_uuid (connection);
 
+		/* the cloned-mac-address may be generated based on the stable-id.
+		 * Thus, at this point, we can only use the permanant MAC address
+		 * as seed. */
+		hwaddr = nm_device_get_permanent_hw_address_full (self, TRUE, &hwaddr_is_fake);
+
 		stable_type = nm_utils_stable_id_parse (stable_id,
 		                                        nm_device_get_ip_iface (self),
-		                                        NULL,
+		                                        !hwaddr_is_fake ? hwaddr : NULL,
+		                                        nm_utils_boot_id_str (),
 		                                        uuid,
 		                                        &generated);
 
@@ -3585,8 +3593,10 @@ device_link_changed (NMDevice *self)
 	gboolean was_up;
 	gboolean update_unmanaged_specs = FALSE;
 	gboolean got_hw_addr = FALSE, had_hw_addr;
+	gboolean seen_down = priv->device_link_changed_down;
 
 	priv->device_link_changed_id = 0;
+	priv->device_link_changed_down = FALSE;
 
 	ifindex = nm_device_get_ifindex (self);
 	pllink = nm_platform_link_get (nm_device_get_platform (self), ifindex);
@@ -3696,7 +3706,7 @@ device_link_changed (NMDevice *self)
 
 	device_recheck_slave_status (self, pllink);
 
-	if (priv->up && !was_up) {
+	if (priv->up && (!was_up || seen_down)) {
 		/* the link was down and just came up. That happens for example, while changing MTU.
 		 * We must restore IP configuration. */
 		if (priv->ip4_state == IP_DONE) {
@@ -3772,6 +3782,8 @@ link_changed_cb (NMPlatform *platform,
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	if (ifindex == nm_device_get_ifindex (self)) {
+		if (!(info->n_ifi_flags & IFF_UP))
+			priv->device_link_changed_down = TRUE;
 		if (!priv->device_link_changed_id) {
 			priv->device_link_changed_id = g_idle_add ((GSourceFunc) device_link_changed, self);
 			_LOGD (LOGD_DEVICE, "queued link change for ifindex %d", ifindex);
@@ -4133,7 +4145,7 @@ device_init_static_sriov_num_vfs (NMDevice *self)
 		num_vfs = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXINT32, -1);
 		if (num_vfs >= 0) {
 			nm_platform_link_set_sriov_params (nm_device_get_platform (self),
-			                                   priv->ifindex, num_vfs, -1);
+			                                   priv->ifindex, num_vfs, NM_TERNARY_DEFAULT);
 		}
 	}
 }
@@ -4296,8 +4308,6 @@ realize_start_setup (NMDevice *self,
 
 	nm_device_set_carrier_from_platform (self);
 
-	device_init_static_sriov_num_vfs (self);
-
 	nm_assert (!priv->stats.timeout_id);
 	real_rate = _stats_refresh_rate_real (priv->stats.refresh_rate_ms);
 	if (real_rate)
@@ -6201,7 +6211,7 @@ act_stage1_prepare (NMDevice *self, NMDeviceStateReason *out_failure_reason)
 		nm_auto_freev NMPlatformVF **plat_vfs = NULL;
 		gs_free_error GError *error = NULL;
 		NMSriovVF *vf;
-		int autoprobe;
+		NMTernary autoprobe;
 
 		autoprobe = nm_setting_sriov_get_autoprobe_drivers (s_sriov);
 		if (autoprobe == NM_TERNARY_DEFAULT) {
@@ -7450,18 +7460,6 @@ get_dhcp_timeout (NMDevice *self, int addr_family)
 }
 
 static GBytes *
-dhcp4_get_client_id_mac (const guint8 *hwaddr /* ETH_ALEN bytes */)
-{
-	guint8 *client_id_buf;
-	guint8 hwaddr_type = ARPHRD_ETHER;
-
-	client_id_buf = g_malloc (ETH_ALEN + 1);
-	client_id_buf[0] = hwaddr_type;
-	memcpy (&client_id_buf[1], hwaddr, ETH_ALEN);
-	return g_bytes_new_take (client_id_buf, ETH_ALEN + 1);
-}
-
-static GBytes *
 dhcp4_get_client_id (NMDevice *self,
                      NMConnection *connection,
                      GBytes *hwaddr)
@@ -7473,6 +7471,7 @@ dhcp4_get_client_id (NMDevice *self,
 	const char *fail_reason;
 	guint8 hwaddr_bin_buf[NM_UTILS_HWADDR_LEN_MAX];
 	const guint8 *hwaddr_bin;
+	int arp_type;
 	gsize hwaddr_len;
 	GBytes *result;
 	gs_free char *logstr1 = NULL;
@@ -7497,17 +7496,18 @@ dhcp4_get_client_id (NMDevice *self,
 
 	if (nm_streq (client_id, "mac")) {
 		if (!hwaddr) {
-			fail_reason = "failed to get current MAC address";
+			fail_reason = "missing link-layer address";
 			goto out_fail;
 		}
 
 		hwaddr_bin = g_bytes_get_data (hwaddr, &hwaddr_len);
-		if (hwaddr_len != ETH_ALEN) {
-			fail_reason = "MAC address is not ethernet";
+		arp_type = nm_utils_arp_type_detect_from_hwaddrlen (hwaddr_len);
+		if (arp_type < 0) {
+			fail_reason = "unsupported link-layer address";
 			goto out_fail;
 		}
 
-		result = dhcp4_get_client_id_mac (hwaddr_bin);
+		result = nm_utils_dhcp_client_id_mac (arp_type, hwaddr_bin, hwaddr_len);
 		goto out_good;
 	}
 
@@ -7516,32 +7516,37 @@ dhcp4_get_client_id (NMDevice *self,
 
 		hwaddr_str = nm_device_get_permanent_hw_address (self);
 		if (!hwaddr_str) {
-			fail_reason = "failed to get permanent MAC address";
+			fail_reason = "missing permanent link-layer address";
 			goto out_fail;
 		}
 
 		if (!_nm_utils_hwaddr_aton (hwaddr_str, hwaddr_bin_buf, sizeof (hwaddr_bin_buf), &hwaddr_len))
 			g_return_val_if_reached (NULL);
 
-		if (hwaddr_len != ETH_ALEN) {
-			/* unsupported type. */
-			fail_reason = "MAC address is not ethernet";
+		arp_type = nm_utils_arp_type_detect_from_hwaddrlen (hwaddr_len);
+		if (arp_type < 0) {
+			fail_reason = "unsupported permanent link-layer address";
 			goto out_fail;
 		}
 
-		result = dhcp4_get_client_id_mac (hwaddr_bin_buf);
+		result = nm_utils_dhcp_client_id_mac (arp_type, hwaddr_bin_buf, hwaddr_len);
+		goto out_good;
+	}
+
+	if (nm_streq (client_id, "duid")) {
+		result = nm_utils_dhcp_client_id_systemd_node_specific (TRUE,
+		                                                        nm_device_get_ip_iface (self));
 		goto out_good;
 	}
 
 	if (nm_streq (client_id, "stable")) {
+		nm_auto_free_checksum GChecksum *sum = NULL;
+		guint8 digest[NM_UTILS_CHECKSUM_LENGTH_SHA1];
 		NMUtilsStableType stable_type;
 		const char *stable_id;
-		GChecksum *sum;
-		guint8 buf[20];
-		gsize buf_size;
 		guint32 salted_header;
-		const guint8 *secret_key;
-		gsize secret_key_len;
+		const guint8 *host_id;
+		gsize host_id_len;
 
 		stable_id = _get_stable_id (self, connection, &stable_type);
 		if (!stable_id)
@@ -7549,23 +7554,17 @@ dhcp4_get_client_id (NMDevice *self,
 
 		salted_header = htonl (2011610591 + stable_type);
 
-		nm_utils_secret_key_get (&secret_key, &secret_key_len);
+		nm_utils_host_id_get (&host_id, &host_id_len);
 
 		sum = g_checksum_new (G_CHECKSUM_SHA1);
-
 		g_checksum_update (sum, (const guchar *) &salted_header, sizeof (salted_header));
 		g_checksum_update (sum, (const guchar *) stable_id, strlen (stable_id) + 1);
-		g_checksum_update (sum, (const guchar *) secret_key, secret_key_len);
-
-		buf_size = sizeof (buf);
-		g_checksum_get_digest (sum, buf, &buf_size);
-		nm_assert (buf_size == sizeof (buf));
-
-		g_checksum_free (sum);
+		g_checksum_update (sum, (const guchar *) host_id, host_id_len);
+		nm_utils_checksum_get_digest (sum, digest);
 
 		client_id_buf = g_malloc (1 + 15);
 		client_id_buf[0] = 0;
-		memcpy (&client_id_buf[1], buf, 15);
+		memcpy (&client_id_buf[1], digest, 15);
 		result = g_bytes_new_take (client_id_buf, 1 + 15);
 		goto out_good;
 	}
@@ -8187,53 +8186,63 @@ dhcp6_prefix_delegated (NMDhcpClient *client,
 	g_signal_emit (self, signals[IP6_PREFIX_DELEGATED], 0, prefix);
 }
 
+/*****************************************************************************/
+
 /* RFC 3315 defines the epoch for the DUID-LLT time field on Jan 1st 2000. */
 #define EPOCH_DATETIME_200001010000  946684800
 
 static GBytes *
-generate_duid_llt (const guint8 *hwaddr /* ETH_ALEN bytes */,
+generate_duid_llt (int arp_type,
+                   const guint8 *hwaddr,
+                   gsize hwaddr_len,
                    gint64 time)
 {
 	guint8 *arr;
 	const guint16 duid_type = htons (1);
-	const guint16 hw_type = htons (ARPHRD_ETHER);
+	const guint16 hw_type = htons (arp_type);
 	const guint32 duid_time = htonl (NM_MAX (0, time - EPOCH_DATETIME_200001010000));
 
-	arr = g_new (guint8, 2 + 2 + 4 + ETH_ALEN);
+	if (!nm_utils_arp_type_get_hwaddr_relevant_part (arp_type, &hwaddr, &hwaddr_len))
+		nm_assert_not_reached ();
+
+	arr = g_new (guint8, 2 + 2 + 4 + hwaddr_len);
 
 	memcpy (&arr[0], &duid_type, 2);
 	memcpy (&arr[2], &hw_type, 2);
 	memcpy (&arr[4], &duid_time, 4);
-	memcpy (&arr[8], hwaddr, ETH_ALEN);
+	memcpy (&arr[8], hwaddr, hwaddr_len);
 
-	return g_bytes_new_take (arr, 2 + 2 + 4 + ETH_ALEN);
+	return g_bytes_new_take (arr, 2 + 2 + 4 + hwaddr_len);
 }
 
 static GBytes *
-generate_duid_ll (const guint8 *hwaddr /* ETH_ALEN bytes */)
+generate_duid_ll (int arp_type,
+                  const guint8 *hwaddr,
+                  gsize hwaddr_len)
 {
 	guint8 *arr;
 	const guint16 duid_type = htons (3);
-	const guint16 hw_type = htons (ARPHRD_ETHER);
+	const guint16 hw_type = htons (arp_type);
 
-	arr = g_new (guint8, 2 + 2 + ETH_ALEN);
+	if (!nm_utils_arp_type_get_hwaddr_relevant_part (arp_type, &hwaddr, &hwaddr_len))
+		nm_assert_not_reached ();
+
+	arr = g_new (guint8, 2 + 2 + hwaddr_len);
 
 	memcpy (&arr[0], &duid_type, 2);
 	memcpy (&arr[2], &hw_type, 2);
-	memcpy (&arr[4], hwaddr, ETH_ALEN);
+	memcpy (&arr[4], hwaddr, hwaddr_len);
 
-	return g_bytes_new_take (arr, 2 + 2 + ETH_ALEN);
+	return g_bytes_new_take (arr, 2 + 2 + hwaddr_len);
 }
 
 static GBytes *
-generate_duid_uuid (guint8 *data, gsize data_len)
+generate_duid_uuid (const NMUuid *uuid)
 {
-	const guint16 duid_type = g_htons (4);
-	const int DUID_SIZE = 18;
+	const guint16 duid_type = htons (4);
 	guint8 *duid_buffer;
 
-	nm_assert (data);
-	nm_assert (data_len >= 16);
+	nm_assert (uuid);
 
 	/* Generate a DHCP Unique Identifier for DHCPv6 using the
 	 * DUID-UUID method (see RFC 6355 section 4).  Format is:
@@ -8241,44 +8250,47 @@ generate_duid_uuid (guint8 *data, gsize data_len)
 	 * u16: type (DUID-UUID = 4)
 	 * u8[16]: UUID bytes
 	 */
-	duid_buffer = g_malloc (DUID_SIZE);
-
 	G_STATIC_ASSERT_EXPR (sizeof (duid_type) == 2);
+	G_STATIC_ASSERT_EXPR (sizeof (*uuid) == 16);
+	duid_buffer = g_malloc (18);
 	memcpy (&duid_buffer[0], &duid_type, 2);
-
-	/* UUID is 128 bits, we just take the first 128 bits
-	 * (regardless of data size) as the DUID-UUID.
-	 */
-	memcpy (&duid_buffer[2], data, 16);
-
-	return g_bytes_new_take (duid_buffer, DUID_SIZE);
+	memcpy (&duid_buffer[2], uuid, 16);
+	return g_bytes_new_take (duid_buffer, 18);
 }
 
 static GBytes *
 generate_duid_from_machine_id (void)
 {
-	gs_free const char *machine_id_s = NULL;
-	uuid_t uuid;
-	GChecksum *sum;
-	guint8 sha256_digest[32];
-	gsize len = sizeof (sha256_digest);
-	static GBytes *global_duid = NULL;
+	static GBytes *volatile global_duid = NULL;
+	GBytes *p;
 
-	if (global_duid)
-		return g_bytes_ref (global_duid);
+again:
+	p = g_atomic_pointer_get (&global_duid);
+	if (G_UNLIKELY (!p)) {
+		nm_auto_free_checksum GChecksum *sum = NULL;
+		const NMUuid *machine_id;
+		union {
+			guint8 sha256[NM_UTILS_CHECKSUM_LENGTH_SHA256];
+			NMUuid uuid;
+		} digest;
 
-	machine_id_s = nm_utils_machine_id_read ();
-	if (!nm_utils_machine_id_parse (machine_id_s, uuid))
-		return NULL;
+		machine_id = nm_utils_machine_id_bin ();
+
+		/* Hash the machine ID so it's not leaked to the network */
+		sum = g_checksum_new (G_CHECKSUM_SHA256);
+		g_checksum_update (sum, (const guchar *) machine_id, sizeof (*machine_id));
+		nm_utils_checksum_get_digest (sum, digest.sha256);
 
-	/* Hash the machine ID so it's not leaked to the network */
-	sum = g_checksum_new (G_CHECKSUM_SHA256);
-	g_checksum_update (sum, (const guchar *) &uuid, sizeof (uuid));
-	g_checksum_get_digest (sum, sha256_digest, &len);
-	g_checksum_free (sum);
+		G_STATIC_ASSERT_EXPR (sizeof (digest.sha256) > sizeof (digest.uuid));
+		p = generate_duid_uuid (&digest.uuid);
+
+		if (!g_atomic_pointer_compare_and_exchange (&global_duid, NULL, p)) {
+			g_bytes_unref (p);
+			goto again;
+		}
+	}
 
-	global_duid = generate_duid_uuid (sha256_digest, len);
-	return g_bytes_ref (global_duid);
+	return g_bytes_ref (p);
 }
 
 static GBytes *
@@ -8289,10 +8301,11 @@ dhcp6_get_duid (NMDevice *self, NMConnection *connection, GBytes *hwaddr, gboole
 	gs_free char *duid_default = NULL;
 	const char *duid_error;
 	GBytes *duid_out;
-	guint8 sha256_digest[32];
-	gsize len = sizeof (sha256_digest);
 	gboolean duid_enforce = TRUE;
 	gs_free char *logstr1 = NULL;
+	const guint8 *hwaddr_bin;
+	gsize hwaddr_len;
+	int arp_type;
 
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
 	duid = nm_setting_ip6_config_get_dhcp_duid (NM_SETTING_IP6_CONFIG (s_ip6));
@@ -8308,10 +8321,6 @@ dhcp6_get_duid (NMDevice *self, NMConnection *connection, GBytes *hwaddr, gboole
 	if (nm_streq (duid, "lease")) {
 		duid_enforce = FALSE;
 		duid_out = generate_duid_from_machine_id ();
-		if (!duid_out) {
-			duid_error = "failure to read machine-id";
-			goto out_fail;
-		}
 		goto out_good;
 	}
 
@@ -8328,78 +8337,138 @@ dhcp6_get_duid (NMDevice *self, NMConnection *connection, GBytes *hwaddr, gboole
 			duid_error = "missing link-layer address";
 			goto out_fail;
 		}
-		if (g_bytes_get_size (hwaddr) != ETH_ALEN) {
+
+		hwaddr_bin = g_bytes_get_data (hwaddr, &hwaddr_len);
+		arp_type = nm_utils_arp_type_detect_from_hwaddrlen (hwaddr_len);
+		if (arp_type < 0) {
 			duid_error = "unsupported link-layer address";
 			goto out_fail;
 		}
 
-		if (nm_streq (duid, "ll")) {
-			duid_out = generate_duid_ll (g_bytes_get_data (hwaddr, NULL));
-		} else {
-			gint64 time;
-
-			time = nm_utils_secret_key_get_timestamp ();
-			if (!time) {
-				duid_error = "cannot retrieve the secret key timestamp";
-				goto out_fail;
-			}
-
-			duid_out = generate_duid_llt (g_bytes_get_data (hwaddr, NULL), time);
+		if (nm_streq (duid, "ll"))
+			duid_out = generate_duid_ll (arp_type, hwaddr_bin, hwaddr_len);
+		else {
+			duid_out = generate_duid_llt (arp_type, hwaddr_bin, hwaddr_len,
+			                              nm_utils_host_id_get_timestamp_ns () / NM_UTILS_NS_PER_SECOND);
 		}
 
 		goto out_good;
 	}
 
 	if (NM_IN_STRSET (duid, "stable-ll", "stable-llt", "stable-uuid")) {
+		/* preferably, we would salt the checksum differently for each @duid type. We missed
+		 * to do that initially, so most types use the DEFAULT_SALT.
+		 *
+		 * Implemenations that are added later, should use a distinct salt instead,
+		 * like "stable-ll"/"stable-llt" with ARPHRD_INFINIBAND below. */
+		const guint32 DEFAULT_SALT = 670531087u;
+		nm_auto_free_checksum GChecksum *sum = NULL;
 		NMUtilsStableType stable_type;
 		const char *stable_id = NULL;
 		guint32 salted_header;
-		GChecksum *sum;
-		const guint8 *secret_key;
-		gsize secret_key_len;
+		const guint8 *host_id;
+		gsize host_id_len;
+		union {
+			guint8 sha256[NM_UTILS_CHECKSUM_LENGTH_SHA256];
+			guint8 hwaddr_eth[ETH_ALEN];
+			guint8 hwaddr_infiniband[INFINIBAND_ALEN];
+			NMUuid uuid;
+			struct _nm_packed {
+				guint8 hwaddr[ETH_ALEN];
+				guint32 timestamp;
+			} llt_eth;
+			struct _nm_packed {
+				guint8 hwaddr[INFINIBAND_ALEN];
+				guint32 timestamp;
+			} llt_infiniband;
+		} digest;
 
 		stable_id = _get_stable_id (self, connection, &stable_type);
 		if (!stable_id)
 			g_return_val_if_reached (NULL);
 
-		salted_header = htonl (670531087 + stable_type);
+		if (NM_IN_STRSET (duid, "stable-ll", "stable-llt")) {
+			/* for stable LL/LLT DUIDs, we still need a hardware address to detect
+			 * the arp-type. Alternatively, we would be able to detect it based on
+			 * other means (e.g. NMDevice type), but instead require the hardware
+			 * address to be present. This is at least consistent with the "ll"/"llt"
+			 * modes above. */
+			if (!hwaddr) {
+				duid_error = "missing link-layer address";
+				goto out_fail;
+			}
+			if ((arp_type = nm_utils_arp_type_detect_from_hwaddrlen (g_bytes_get_size (hwaddr))) < 0) {
+				duid_error = "unsupported link-layer address";
+				goto out_fail;
+			}
+
+			if (arp_type == ARPHRD_ETHER)
+				salted_header = DEFAULT_SALT;
+			else {
+				nm_assert (arp_type == ARPHRD_INFINIBAND);
+				salted_header = 0x42492CEFu + ((guint32) arp_type);
+			}
+		} else {
+			salted_header = DEFAULT_SALT;
+			arp_type = -1;
+		}
 
-		nm_utils_secret_key_get (&secret_key, &secret_key_len);
+		salted_header = htonl (salted_header + ((guint32) stable_type));
 
-		sum = g_checksum_new (G_CHECKSUM_SHA256);
+		nm_utils_host_id_get (&host_id, &host_id_len);
 
+		sum = g_checksum_new (G_CHECKSUM_SHA256);
 		g_checksum_update (sum, (const guchar *) &salted_header, sizeof (salted_header));
 		g_checksum_update (sum, (const guchar *) stable_id, -1);
-		g_checksum_update (sum, (const guchar *) secret_key, secret_key_len);
+		g_checksum_update (sum, (const guchar *) host_id, host_id_len);
+		nm_utils_checksum_get_digest (sum, digest.sha256);
 
-		g_checksum_get_digest (sum, sha256_digest, &len);
-		g_checksum_free (sum);
+		G_STATIC_ASSERT_EXPR (sizeof (digest) == sizeof (digest.sha256));
 
 		if (nm_streq (duid, "stable-ll")) {
-			duid_out = generate_duid_ll (sha256_digest);
+			switch (arp_type) {
+			case ARPHRD_ETHER:
+				duid_out = generate_duid_ll (arp_type, digest.hwaddr_eth, sizeof (digest.hwaddr_eth));
+				break;
+			case ARPHRD_INFINIBAND:
+				duid_out = generate_duid_ll (arp_type, digest.hwaddr_infiniband, sizeof (digest.hwaddr_infiniband));
+				break;
+			default:
+				g_return_val_if_reached (NULL);
+			}
 		} else if (nm_streq (duid, "stable-llt")) {
 			gint64 time;
+			guint32 timestamp;
 
 #define EPOCH_DATETIME_THREE_YEARS  (356 * 24 * 3600 * 3)
 
-			/* We want a variable time between the secret_key timestamp and three years
+			/* We want a variable time between the host_id timestamp and three years
 			 * before. Let's compute the time (in seconds) from 0 to 3 years; then we'll
-			 * subtract it from the secret_key timestamp.
+			 * subtract it from the host_id timestamp.
 			 */
-			time = nm_utils_secret_key_get_timestamp ();
-			if (!time) {
-				duid_error = "cannot retrieve the secret key timestamp";
-				goto out_fail;
-			}
+			time = nm_utils_host_id_get_timestamp_ns () / NM_UTILS_NS_PER_SECOND;
+
 			/* don't use too old timestamps. They cannot be expressed in DUID-LLT and
 			 * would all be truncated to zero. */
 			time = NM_MAX (time, EPOCH_DATETIME_200001010000 + EPOCH_DATETIME_THREE_YEARS);
-			time -= (unaligned_read_be32 (&sha256_digest[ETH_ALEN]) % EPOCH_DATETIME_THREE_YEARS);
 
-			duid_out = generate_duid_llt (sha256_digest, time);
+			switch (arp_type) {
+			case ARPHRD_ETHER:
+				timestamp = unaligned_read_be32 (&digest.llt_eth.timestamp);
+				time -= timestamp % EPOCH_DATETIME_THREE_YEARS;
+				duid_out = generate_duid_llt (arp_type, digest.llt_eth.hwaddr, sizeof (digest.llt_eth.hwaddr), time);
+				break;
+			case ARPHRD_INFINIBAND:
+				timestamp = unaligned_read_be32 (&digest.llt_infiniband.timestamp);
+				time -= timestamp % EPOCH_DATETIME_THREE_YEARS;
+				duid_out = generate_duid_llt (arp_type, digest.llt_infiniband.hwaddr, sizeof (digest.llt_infiniband.hwaddr), time);
+				break;
+			default:
+				g_return_val_if_reached (NULL);
+			}
 		} else {
 			nm_assert (nm_streq (duid, "stable-uuid"));
-			duid_out = generate_duid_uuid (sha256_digest, len);
+			duid_out = generate_duid_uuid (&digest.uuid);
 		}
 
 		goto out_good;
@@ -8410,14 +8479,14 @@ dhcp6_get_duid (NMDevice *self, NMConnection *connection, GBytes *hwaddr, gboole
 out_fail:
 	nm_assert (!duid_out && duid_error);
 	{
-		guint8 uuid[16];
+		NMUuid uuid;
 
 		_LOGW (LOGD_IP6 | LOGD_DHCP6,
 		       "ipv6.dhcp-duid: failure to generate %s DUID: %s. Fallback to random DUID-UUID.",
 		       duid, duid_error);
 
-		nm_utils_random_bytes (uuid, sizeof (uuid));
-		duid_out = generate_duid_uuid (uuid, sizeof (uuid));
+		nm_utils_random_bytes (&uuid, sizeof (uuid));
+		duid_out = generate_duid_uuid (&uuid);
 	}
 
 out_good:
@@ -8432,6 +8501,8 @@ out_good:
 	return duid_out;
 }
 
+/*****************************************************************************/
+
 static gboolean
 dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 {
@@ -11961,7 +12032,7 @@ nm_device_set_ip_config (NMDevice *self,
 			priv->needs_ip6_subnet = FALSE;
 	}
 
-	if (IS_IPv4) {
+	if (IS_IPv4 && FALSE /* rp_filter handling is disabled */) {
 		if (!nm_device_sys_iface_state_is_external_or_assume (self))
 			ip4_rp_filter_update (self);
 	}
@@ -12859,7 +12930,7 @@ queued_ip_config_change (NMDevice *self, int addr_family)
 
 	set_unmanaged_external_down (self, TRUE);
 
-	if (IS_IPv4) {
+	if (IS_IPv4 && FALSE /* rp_filter handling is disabled */) {
 		if (!nm_device_sys_iface_state_is_external_or_assume (self)) {
 			priv->v4_has_shadowed_routes = _v4_has_shadowed_routes_detect (self);;
 			ip4_rp_filter_update (self);
@@ -14384,9 +14455,11 @@ nm_device_spawn_iface_helper (NMDevice *self)
 			if (client_id) {
 				g_ptr_array_add (argv, g_strdup ("--dhcp4-clientid"));
 				g_ptr_array_add (argv,
-				                 _nm_utils_bin2str (g_bytes_get_data (client_id, NULL),
-				                                    g_bytes_get_size (client_id),
-				                                    FALSE));
+				                 _nm_utils_bin2hexstr_full (g_bytes_get_data (client_id, NULL),
+				                                            g_bytes_get_size (client_id),
+				                                            ':',
+				                                            FALSE,
+				                                            NULL));
 			}
 
 			hostname = nm_dhcp_client_get_hostname (priv->dhcp4.client);
@@ -14424,9 +14497,11 @@ nm_device_spawn_iface_helper (NMDevice *self)
 		if (nm_device_get_ip_iface_identifier (self, &iid, FALSE)) {
 			g_ptr_array_add (argv, g_strdup ("--iid"));
 			g_ptr_array_add (argv,
-			                 _nm_utils_bin2str (iid.id_u8,
-			                                    sizeof (NMUtilsIPv6IfaceId),
-			                                    FALSE));
+			                 _nm_utils_bin2hexstr_full (iid.id_u8,
+			                                            sizeof (NMUtilsIPv6IfaceId),
+			                                            ':',
+			                                            FALSE,
+			                                            NULL));
 		}
 
 		g_ptr_array_add (argv, g_strdup ("--addr-gen-mode"));
@@ -14561,6 +14636,7 @@ _set_state_full (NMDevice *self,
 	NMActRequest *req;
 	gboolean no_firmware = FALSE;
 	NMSettingsConnection *sett_conn;
+	NMSettingSriov *s_sriov;
 
 	g_return_if_fail (NM_IS_DEVICE (self));
 
@@ -14668,6 +14744,7 @@ _set_state_full (NMDevice *self,
 			save_ip6_properties (self);
 			if (priv->sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_MANAGED)
 				ip6_managed_setup (self);
+			device_init_static_sriov_num_vfs (self);
 		}
 
 		if (priv->sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_MANAGED) {
@@ -14756,6 +14833,12 @@ _set_state_full (NMDevice *self,
 		}
 		break;
 	case NM_DEVICE_STATE_DEACTIVATING:
+		if (   (s_sriov = (NMSettingSriov *) nm_device_get_applied_setting (self, NM_TYPE_SETTING_SRIOV))
+		    && priv->ifindex > 0) {
+			nm_platform_link_set_sriov_params (nm_device_get_platform (self),
+			                                   priv->ifindex, 0, NM_TERNARY_TRUE);
+		}
+
 		_cancel_activation (self);
 
 		/* We cache the ignore_carrier state to not react on config-reloads while the connection
@@ -15273,7 +15356,7 @@ _get_generate_mac_address_mask_setting (NMDevice *self, NMConnection *connection
 	}
 
 	a = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-	                                           is_wifi ? "wifi.generate-mac-address-mask" : "ethernet.generate-mac-mac-address-mask",
+	                                           is_wifi ? "wifi.generate-mac-address-mask" : "ethernet.generate-mac-address-mask",
 	                                           self);
 	if (!a)
 		return NULL;
@@ -15314,7 +15397,8 @@ _hw_addr_set (NMDevice *self,
 	NMPlatformError plerr;
 	guint8 addr_bytes[NM_UTILS_HWADDR_LEN_MAX];
 	gsize addr_len;
-	gboolean was_up;
+	gboolean was_taken_down = FALSE;
+	gboolean retry_down;
 
 	nm_assert (NM_IS_DEVICE (self));
 	nm_assert (addr);
@@ -15337,21 +15421,37 @@ _hw_addr_set (NMDevice *self,
 
 	_LOGT (LOGD_DEVICE, "set-hw-addr: setting MAC address to '%s' (%s, %s)...", addr, operation, detail);
 
-	was_up = nm_device_is_up (self);
-	if (was_up) {
-		/* Can't change MAC address while device is up */
+	if (nm_device_get_device_type (self) == NM_DEVICE_TYPE_WIFI) {
+		/* Always take the device down for Wi-Fi because
+		 * wpa_supplicant needs it to properly detect the MAC
+		 * change. */
+		retry_down = FALSE;
+		was_taken_down = TRUE;
 		nm_device_take_down (self, FALSE);
 	}
 
+again:
 	plerr = nm_platform_link_set_address (nm_device_get_platform (self), nm_device_get_ip_ifindex (self), addr_bytes, addr_len);
 	success = (plerr == NM_PLATFORM_ERROR_SUCCESS);
-	if (success) {
-		/* MAC address succesfully changed; update the current MAC to match */
+	if (!success) {
+		retry_down =    !was_taken_down
+		             && plerr != NM_PLATFORM_ERROR_NOT_FOUND
+		             && nm_platform_link_is_up (nm_device_get_platform (self),
+		                                        nm_device_get_ip_ifindex (self));
+		_NMLOG (     retry_down
+		          || plerr == NM_PLATFORM_ERROR_NOT_FOUND
+		        ? LOGL_DEBUG
+		        : LOGL_WARN,
+		        LOGD_DEVICE,
+		        "set-hw-addr: failed to %s MAC address to %s (%s) (%s)%s",
+		        operation, addr, detail,
+		        nm_platform_error_to_string_a (plerr),
+		        retry_down ? " (retry with taking down)" : "");
+	} else {
+		/* MAC address successfully changed; update the current MAC to match */
 		nm_device_update_hw_address (self);
-		if (_hw_addr_matches (self, addr_bytes, addr_len)) {
-			_LOGI (LOGD_DEVICE, "set-hw-addr: %s MAC address to %s (%s)",
-			       operation, addr, detail);
-		} else {
+
+		if (!_hw_addr_matches (self, addr_bytes, addr_len)) {
 			gint64 poll_end, now;
 
 			_LOGD (LOGD_DEVICE,
@@ -15392,24 +15492,40 @@ handle_fail:
 				success = FALSE;
 				break;
 			}
+		}
 
-			if (success) {
-				_LOGI (LOGD_DEVICE, "set-hw-addr: %s MAC address to %s (%s)",
-				       operation, addr, detail);
-			} else {
-				_LOGW (LOGD_DEVICE,
-				       "set-hw-addr: new MAC address %s not successfully %s (%s)",
-				       addr, operation, detail);
-			}
+		if (success) {
+			retry_down = FALSE;
+			_LOGI (LOGD_DEVICE, "set-hw-addr: %s MAC address to %s (%s)",
+			       operation, addr, detail);
+		} else {
+			retry_down =    !was_taken_down
+			             && nm_platform_link_is_up (nm_device_get_platform (self),
+			                                        nm_device_get_ip_ifindex (self));
+
+			_NMLOG (  retry_down
+			        ? LOGL_DEBUG
+			        : LOGL_WARN,
+			        LOGD_DEVICE,
+			        "set-hw-addr: new MAC address %s not successfully %s (%s)%s",
+			        addr,
+			        operation,
+			        detail,
+			        retry_down ? " (retry with taking down)" : "");
 		}
-	} else {
-		_NMLOG (plerr == NM_PLATFORM_ERROR_NOT_FOUND ? LOGL_DEBUG : LOGL_WARN,
-		        LOGD_DEVICE, "set-hw-addr: failed to %s MAC address to %s (%s) (%s)",
-		        operation, addr, detail,
-		        nm_platform_error_to_string_a (plerr));
 	}
 
-	if (was_up) {
+	if (retry_down) {
+		/* changing the MAC address failed, but also the device was up (and we did not yet try to take
+		 * it down). Optimally, we change the MAC address without taking the device down, but some
+		 * devices don't like that. So, retry with taking the device down. */
+		retry_down = FALSE;
+		was_taken_down = TRUE;
+		nm_device_take_down (self, FALSE);
+		goto again;
+	}
+
+	if (was_taken_down) {
 		if (!nm_device_bring_up (self, TRUE, NULL))
 			return FALSE;
 	}
@@ -15728,7 +15844,8 @@ nm_device_spec_match_list_full (NMDevice *self, const GSList *specs, int no_matc
 	                          nm_device_get_driver (self),
 	                          nm_device_get_driver_version (self),
 	                          nm_device_get_permanent_hw_address (self),
-	                          klass->get_s390_subchannels ? klass->get_s390_subchannels (self) : NULL);
+	                          klass->get_s390_subchannels ? klass->get_s390_subchannels (self) : NULL,
+	                          nm_dhcp_manager_get_config (nm_dhcp_manager_get ()));
 
 	switch (m) {
 	case NM_MATCH_SPEC_MATCH:
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index 8f376b20..3c703564 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -722,6 +722,8 @@ typedef enum {
 
 	NM_DEVICE_AUTOCONNECT_BLOCKED_WRONG_PIN             = (1LL <<  1),
 	NM_DEVICE_AUTOCONNECT_BLOCKED_MANUAL_DISCONNECT     = (1LL <<  2),
+	NM_DEVICE_AUTOCONNECT_BLOCKED_SIM_MISSING           = (1LL <<  3),
+	NM_DEVICE_AUTOCONNECT_BLOCKED_INIT_FAILED           = (1LL <<  4),
 
 	_NM_DEVICE_AUTOCONNECT_BLOCKED_LAST,
 
diff --git a/src/devices/nm-lldp-listener.c b/src/devices/nm-lldp-listener.c
index f637825b..c0484ed3 100644
--- a/src/devices/nm-lldp-listener.c
+++ b/src/devices/nm-lldp-listener.c
@@ -534,11 +534,13 @@ lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 				l = data8[2];
 				if (len != 3 + l)
 					continue;
+				if (l > 32)
+					continue;
 
 				_lldp_attr_set_uint32 (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_VID,
 				                       _access_uint16 (&data8[0]));
 				_lldp_attr_set_str_ptr (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_VLAN_NAME,
-				                        &data8[3], len);
+				                        &data8[3], l);
 				break;
 			}
 			default:
diff --git a/src/devices/ovs/nm-ovsdb.c b/src/devices/ovs/nm-ovsdb.c
index b00397cf..494e2ea0 100644
--- a/src/devices/ovs/nm-ovsdb.c
+++ b/src/devices/ovs/nm-ovsdb.c
@@ -349,14 +349,14 @@ _insert_interface (json_t *params, NMConnection *interface)
 	if (s_ovs_iface)
 		type = nm_setting_ovs_interface_get_interface_type (s_ovs_iface);
 
-	json_array_append (options, json_string ("map"));
+	json_array_append_new (options, json_string ("map"));
 	s_ovs_patch = nm_connection_get_setting_ovs_patch (interface);
 	if (s_ovs_patch) {
-		json_array_append (options, json_pack ("[[s, s]]",
+		json_array_append_new (options, json_pack ("[[s, s]]",
 		                                       "peer",
 		                                        nm_setting_ovs_patch_get_peer (s_ovs_patch)));
 	} else {
-		json_array_append (options, json_array ());
+		json_array_append_new (options, json_array ());
 	}
 
 	json_array_append_new (params,
@@ -504,12 +504,15 @@ _add_interface (NMOvsdb *self, json_t *params,
 	OpenvswitchBridge *ovs_bridge = NULL;
 	OpenvswitchPort *ovs_port = NULL;
 	OpenvswitchInterface *ovs_interface = NULL;
+	nm_auto_decref_json json_t *bridges = NULL;
+	nm_auto_decref_json json_t *new_bridges = NULL;
+	nm_auto_decref_json json_t *ports = NULL;
+	nm_auto_decref_json json_t *new_ports = NULL;
+	nm_auto_decref_json json_t *interfaces = NULL;
+	nm_auto_decref_json json_t *new_interfaces = NULL;
+	gboolean has_interface = FALSE;
 	int pi;
 	int ii;
-	json_t *bridges, *new_bridges;
-	json_t *ports, *new_ports;
-	json_t *interfaces, *new_interfaces;
-	gboolean has_interface = FALSE;
 
 	bridges = json_array ();
 	ports = json_array ();
@@ -585,14 +588,6 @@ _add_interface (NMOvsdb *self, json_t *params,
 		_insert_interface (params, interface);
 		json_array_append_new (new_interfaces, json_pack ("[s, s]", "named-uuid", "rowInterface"));
 	}
-
-	json_decref (interfaces);
-	json_decref (ports);
-	json_decref (bridges);
-
-	json_decref (new_interfaces);
-	json_decref (new_ports);
-	json_decref (new_bridges);
 }
 
 /**
@@ -612,14 +607,13 @@ _delete_interface (NMOvsdb *self, json_t *params, const char *ifname)
 	OpenvswitchBridge *ovs_bridge;
 	OpenvswitchPort *ovs_port;
 	OpenvswitchInterface *ovs_interface;
-	int pi;
-	int ii;
-	json_t *bridges, *new_bridges;
-	json_t *ports, *new_ports;
-	json_t *interfaces, *new_interfaces;
+	nm_auto_decref_json json_t *bridges = NULL;
+	nm_auto_decref_json json_t *new_bridges = NULL;
 	gboolean bridges_changed;
 	gboolean ports_changed;
 	gboolean interfaces_changed;
+	int pi;
+	int ii;
 
 	bridges = json_array ();
 	new_bridges = json_array ();
@@ -627,20 +621,26 @@ _delete_interface (NMOvsdb *self, json_t *params, const char *ifname)
 
 	g_hash_table_iter_init (&iter, priv->bridges);
 	while (g_hash_table_iter_next (&iter, (gpointer) &bridge_uuid, (gpointer) &ovs_bridge)) {
-		json_array_append_new (bridges, json_pack ("[s,s]", "uuid", bridge_uuid));
+		nm_auto_decref_json json_t *ports = NULL;
+		nm_auto_decref_json json_t *new_ports = NULL;
 
 		ports = json_array ();
 		new_ports = json_array ();
 		ports_changed = FALSE;
 
+		json_array_append_new (bridges, json_pack ("[s,s]", "uuid", bridge_uuid));
+
 		for (pi = 0; pi < ovs_bridge->ports->len; pi++) {
+			nm_auto_decref_json json_t *interfaces = NULL;
+			nm_auto_decref_json json_t *new_interfaces = NULL;
+
+			interfaces = json_array ();
+			new_interfaces = json_array ();
 			port_uuid = g_ptr_array_index (ovs_bridge->ports, pi);
 			ovs_port = g_hash_table_lookup (priv->ports, port_uuid);
 
 			json_array_append_new (ports, json_pack ("[s,s]", "uuid", port_uuid));
 
-			interfaces = json_array ();
-			new_interfaces = json_array ();
 			interfaces_changed = FALSE;
 
 			for (ii = 0; ii < ovs_port->interfaces->len; ii++) {
@@ -667,9 +667,6 @@ _delete_interface (NMOvsdb *self, json_t *params, const char *ifname)
 				}
 				json_array_append_new (new_ports, json_pack ("[s,s]", "uuid", port_uuid));
 			}
-
-			json_decref (interfaces);
-			json_decref (new_interfaces);
 		}
 
 		if (json_array_size (new_ports) == 0) {
@@ -681,9 +678,6 @@ _delete_interface (NMOvsdb *self, json_t *params, const char *ifname)
 			}
 			json_array_append_new (new_bridges, json_pack ("[s,s]", "uuid", bridge_uuid));
 		}
-
-		json_decref (ports);
-		json_decref (new_ports);
 	}
 
 	if (bridges_changed) {
@@ -708,7 +702,7 @@ ovsdb_next_command (NMOvsdb *self)
 	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
 	OvsdbMethodCall *call = NULL;
 	char *cmd;
-	json_t *msg = NULL;
+	nm_auto_decref_json json_t *msg = NULL;
 	json_t *params;
 
 	if (!priv->conn)
@@ -764,7 +758,6 @@ ovsdb_next_command (NMOvsdb *self)
 	cmd = json_dumps (msg, 0);
 
 	g_string_append (priv->output, cmd);
-	json_decref (msg);
 	free (cmd);
 
 	ovsdb_write (self);
@@ -1041,7 +1034,7 @@ static void
 ovsdb_got_echo (NMOvsdb *self, json_int_t id, json_t *data)
 {
 	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
-	json_t *msg;
+	nm_auto_decref_json json_t *msg = NULL;
 	char *reply;
 	gboolean output_was_empty;
 
@@ -1050,7 +1043,6 @@ ovsdb_got_echo (NMOvsdb *self, json_int_t id, json_t *data)
 	msg = json_pack ("{s:I, s:O}", "id", id, "result", data);
 	reply = json_dumps (msg, 0);
 	g_string_append (priv->output, reply);
-	json_decref (msg);
 	free (reply);
 
 	if (output_was_empty)
@@ -1300,6 +1292,9 @@ ovsdb_disconnect (NMOvsdb *self, gboolean is_disposing)
 	gpointer user_data;
 	gs_free_error GError *error = NULL;
 
+	if (!priv->client)
+		return;
+
 	_LOGD ("disconnecting from ovsdb");
 	nm_utils_error_set_cancelled (&error, is_disposing, "NMOvsdb");
 
@@ -1317,6 +1312,7 @@ ovsdb_disconnect (NMOvsdb *self, gboolean is_disposing)
 	g_clear_object (&priv->client);
 	g_clear_object (&priv->conn);
 	g_clear_pointer (&priv->db_uuid, g_free);
+	nm_clear_g_cancellable (&priv->cancellable);
 }
 
 static void
@@ -1535,11 +1531,14 @@ dispose (GObject *object)
 
 	ovsdb_disconnect (self, TRUE);
 
-	g_string_free (priv->input, TRUE);
-	priv->input = NULL;
-	g_string_free (priv->output, TRUE);
-	priv->output = NULL;
-
+	if (priv->input) {
+		g_string_free (priv->input, TRUE);
+		priv->input = NULL;
+	}
+	if (priv->output) {
+		g_string_free (priv->output, TRUE);
+		priv->output = NULL;
+	}
 	if (priv->calls) {
 		g_array_free (priv->calls, TRUE);
 		priv->calls = NULL;
@@ -1549,9 +1548,6 @@ dispose (GObject *object)
 	g_clear_pointer (&priv->ports, g_hash_table_destroy);
 	g_clear_pointer (&priv->interfaces, g_hash_table_destroy);
 
-	g_cancellable_cancel (priv->cancellable);
-	g_clear_object (&priv->cancellable);
-
 	G_OBJECT_CLASS (nm_ovsdb_parent_class)->dispose (object);
 }
 
diff --git a/src/devices/tests/test-lldp.c b/src/devices/tests/test-lldp.c
index c2ac8e19..7227d082 100644
--- a/src/devices/tests/test-lldp.c
+++ b/src/devices/tests/test-lldp.c
@@ -219,11 +219,10 @@ TEST_RECV_FRAME_DEFINE (_test_recv_data1_frame0,
 	0x01, 0xe8,
 	0xfe, 0x07, 0x00, 0x80, 0xc2, 0x02, /* IEEE 802.1 - Port and Protocol VLAN ID */
 	0x01, 0x00, 0x00,
-	0xfe, 0x17, 0x00, 0x80, 0xc2, 0x03, /* IEEE 802.1 - VLAN Name */
-	0x01, 0xe8, 0x10, 0x76, 0x32, 0x2d,
+	0xfe, 0x16, 0x00, 0x80, 0xc2, 0x03, /* IEEE 802.1 - VLAN Name */
+	0x01, 0xe8, 0x0f, 0x76, 0x32, 0x2d,
 	0x30, 0x34, 0x38, 0x38, 0x2d, 0x30,
 	0x33, 0x2d, 0x30, 0x35, 0x30, 0x35,
-	0x00,
 	0xfe, 0x05, 0x00, 0x80, 0xc2, 0x04, /* IEEE 802.1 - Protocol Identity */
 	0x00,
 	0x00, 0x00                          /* End of LLDPDU */
diff --git a/src/devices/wifi/nm-device-olpc-mesh.c b/src/devices/wifi/nm-device-olpc-mesh.c
index 9ae81139..4c42e7d3 100644
--- a/src/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/devices/wifi/nm-device-olpc-mesh.c
@@ -82,7 +82,19 @@ G_DEFINE_TYPE (NMDeviceOlpcMesh, nm_device_olpc_mesh, NM_TYPE_DEVICE)
 static gboolean
 get_autoconnect_allowed (NMDevice *device)
 {
-	return FALSE;
+	NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (device);
+	NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE (self);
+
+	/* We shall always have a companion if we're >= DISCONENCTED, and this
+	 * ought not be called until then. */
+	g_return_val_if_fail (priv->companion, FALSE);
+
+	/* We must not attempt to autoconnect when the companion is connected or
+	 * connecting, * because we'd tear down its connection. */
+	if (nm_device_get_state (priv->companion) > NM_DEVICE_STATE_DISCONNECTED)
+		return FALSE;
+
+	return TRUE;
 }
 
 #define DEFAULT_SSID "olpc-mesh"
@@ -265,6 +277,11 @@ companion_state_changed_cb (NMDeviceWifi *companion,
 	NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (user_data);
 	NMDeviceState self_state = nm_device_get_state (NM_DEVICE (self));
 
+	if (   old_state > NM_DEVICE_STATE_DISCONNECTED
+	    && state <= NM_DEVICE_STATE_DISCONNECTED) {
+		nm_device_emit_recheck_auto_activate (NM_DEVICE (self));
+	}
+
 	if (   self_state < NM_DEVICE_STATE_PREPARE
 	    || self_state > NM_DEVICE_STATE_ACTIVATED
 	    || state < NM_DEVICE_STATE_PREPARE
diff --git a/src/devices/wwan/nm-device-modem.c b/src/devices/wwan/nm-device-modem.c
index 4119d598..8ff931aa 100644
--- a/src/devices/wwan/nm-device-modem.c
+++ b/src/devices/wwan/nm-device-modem.c
@@ -125,14 +125,40 @@ modem_prepare_result (NMModem *modem,
 	if (success)
 		nm_device_activate_schedule_stage2_device_config (device);
 	else {
-		if (nm_device_state_reason_check (reason) == NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT) {
-			/* If the connect failed because the SIM PIN was wrong don't allow
-			 * the device to be auto-activated anymore, which would risk locking
-			 * the SIM if the incorrect PIN continues to be used.
-			 */
+		/* There are several reasons to block autoconnection at device level:
+		 *
+		 *  - Wrong SIM-PIN: The device won't autoconnect because it doesn't make sense
+		 *    to retry the connection with the same PIN. This error also makes autoconnection
+		 *    blocked at settings level, so not even a modem unplug and replug will allow
+		 *    autoconnection again. It is somewhat redundant to block autoconnection at
+		 *    both device and setting level really.
+		 *
+		 *  - SIM wrong or not inserted: If the modem is reporting a SIM not inserted error,
+		 *    we can block autoconnection at device level, so that if the same device is
+		 *    unplugged and replugged with a SIM (or if a SIM hotplug event happens in MM,
+		 *    recreating the device completely), we can try the autoconnection again.
+		 *
+		 *  - Modem initialization failed: For some reason unknown to NM, the modem wasn't
+		 *    initialized correctly, which leads to an unusable device. A device unplug and
+		 *    replug may solve the issue, so make it a device-level autoconnection blocking
+		 *    reason.
+		 */
+		switch (nm_device_state_reason_check (reason)) {
+		case NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED:
+		case NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED:
+		case NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT:
 			nm_device_autoconnect_blocked_set (device, NM_DEVICE_AUTOCONNECT_BLOCKED_WRONG_PIN);
+			break;
+		case NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED:
+		case NM_DEVICE_STATE_REASON_GSM_SIM_WRONG:
+			nm_device_autoconnect_blocked_set (device, NM_DEVICE_AUTOCONNECT_BLOCKED_SIM_MISSING);
+			break;
+		case NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED:
+			nm_device_autoconnect_blocked_set (device, NM_DEVICE_AUTOCONNECT_BLOCKED_INIT_FAILED);
+			break;
+		default:
+			break;
 		}
-
 		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason);
 	}
 }
diff --git a/src/dhcp/nm-dhcp-client.c b/src/dhcp/nm-dhcp-client.c
index 16db8306..37fb18c4 100644
--- a/src/dhcp/nm-dhcp-client.c
+++ b/src/dhcp/nm-dhcp-client.c
@@ -28,7 +28,6 @@
 #include <unistd.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <uuid/uuid.h>
 #include <linux/rtnetlink.h>
 
 #include "nm-utils/nm-dedup-multi.h"
@@ -62,6 +61,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDhcpClient,
 	PROP_ROUTE_TABLE,
 	PROP_TIMEOUT,
 	PROP_UUID,
+	PROP_HOSTNAME,
 );
 
 typedef struct _NMDhcpClientPrivate {
@@ -69,7 +69,6 @@ typedef struct _NMDhcpClientPrivate {
 	char *       iface;
 	GBytes *     hwaddr;
 	char *       uuid;
-	GBytes *     duid;
 	GBytes *     client_id;
 	char *       hostname;
 	pid_t        pid;
@@ -140,14 +139,6 @@ nm_dhcp_client_get_uuid (NMDhcpClient *self)
 }
 
 GBytes *
-nm_dhcp_client_get_duid (NMDhcpClient *self)
-{
-	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
-
-	return NM_DHCP_CLIENT_GET_PRIVATE (self)->duid;
-}
-
-GBytes *
 nm_dhcp_client_get_hw_addr (NMDhcpClient *self)
 {
 	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
@@ -230,6 +221,18 @@ _set_client_id (NMDhcpClient *self, GBytes *client_id, gboolean take)
 	priv->client_id = client_id;
 	if (!take && client_id)
 		g_bytes_ref (client_id);
+
+	{
+		gs_free char *s = NULL;
+
+		_LOGT ("%s: set %s",
+		       nm_dhcp_client_get_addr_family (self) == AF_INET6
+		         ? "duid"
+		         : "client-id",
+		       priv->client_id
+		         ? (s = nm_dhcp_utils_duid_to_string (priv->client_id))
+		         : "default");
+	}
 }
 
 void
@@ -362,7 +365,7 @@ nm_dhcp_client_stop_pid (pid_t pid, const char *iface)
 }
 
 static void
-stop (NMDhcpClient *self, gboolean release, GBytes *duid)
+stop (NMDhcpClient *self, gboolean release)
 {
 	NMDhcpClientPrivate *priv;
 
@@ -509,7 +512,6 @@ gboolean
 nm_dhcp_client_start_ip4 (NMDhcpClient *self,
                           GBytes *client_id,
                           const char *dhcp_anycast_addr,
-                          const char *hostname,
                           const char *last_ip4_address,
                           GError **error)
 {
@@ -529,9 +531,6 @@ nm_dhcp_client_start_ip4 (NMDhcpClient *self,
 
 	nm_dhcp_client_set_client_id (self, client_id);
 
-	g_clear_pointer (&priv->hostname, g_free);
-	priv->hostname = g_strdup (hostname);
-
 	return NM_DHCP_CLIENT_GET_CLASS (self)->ip4_start (self,
 	                                                   dhcp_anycast_addr,
 	                                                   last_ip4_address,
@@ -550,34 +549,29 @@ nm_dhcp_client_start_ip6 (NMDhcpClient *self,
                           gboolean enforce_duid,
                           const char *dhcp_anycast_addr,
                           const struct in6_addr *ll_addr,
-                          const char *hostname,
                           NMSettingIP6ConfigPrivacy privacy,
                           guint needed_prefixes,
                           GError **error)
 {
 	NMDhcpClientPrivate *priv;
-	gs_free char *str = NULL;
+	gs_unref_bytes GBytes *own_client_id = NULL;
 
 	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);
+	g_return_val_if_fail (client_id, FALSE);
 
 	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+
 	g_return_val_if_fail (priv->pid == -1, FALSE);
 	g_return_val_if_fail (priv->addr_family == AF_INET6, FALSE);
 	g_return_val_if_fail (priv->uuid != NULL, FALSE);
-
-	nm_assert (!priv->duid);
-	nm_assert (client_id);
+	g_return_val_if_fail (!priv->client_id, FALSE);
 
 	if (!enforce_duid)
-		priv->duid = NM_DHCP_CLIENT_GET_CLASS (self)->get_duid (self);
-
-	if (!priv->duid)
-		priv->duid = g_bytes_ref (client_id);
-
-	_LOGD ("DUID is '%s'", (str = nm_dhcp_utils_duid_to_string (priv->duid)));
+		own_client_id = NM_DHCP_CLIENT_GET_CLASS (self)->get_duid (self);
 
-	g_clear_pointer (&priv->hostname, g_free);
-	priv->hostname = g_strdup (hostname);
+	_set_client_id (self,
+	                own_client_id ?: client_id,
+	                FALSE);
 
 	if (priv->timeout == NM_DHCP_TIMEOUT_INFINITY)
 		_LOGI ("activation: beginning transaction (no timeout)");
@@ -588,7 +582,6 @@ nm_dhcp_client_start_ip6 (NMDhcpClient *self,
 	                                                   dhcp_anycast_addr,
 	                                                   ll_addr,
 	                                                   privacy,
-	                                                   priv->duid,
 	                                                   needed_prefixes,
 	                                                   error);
 }
@@ -655,7 +648,7 @@ nm_dhcp_client_stop (NMDhcpClient *self, gboolean release)
 
 	/* Kill the DHCP client */
 	old_pid = priv->pid;
-	NM_DHCP_CLIENT_GET_CLASS (self)->stop (self, release, priv->duid);
+	NM_DHCP_CLIENT_GET_CLASS (self)->stop (self, release);
 	if (old_pid > 0)
 		_LOGI ("canceled DHCP transaction, DHCP client pid %d", old_pid);
 	else
@@ -859,6 +852,9 @@ get_property (GObject *object, guint prop_id,
 	case PROP_UUID:
 		g_value_set_string (value, priv->uuid);
 		break;
+	case PROP_HOSTNAME:
+		g_value_set_string (value, priv->hostname);
+		break;
 	case PROP_ROUTE_METRIC:
 		g_value_set_uint (value, priv->route_metric);
 		break;
@@ -899,11 +895,13 @@ set_property (GObject *object, guint prop_id,
 	case PROP_IFACE:
 		/* construct-only */
 		priv->iface = g_value_dup_string (value);
+		g_return_if_fail (   priv->iface
+		                  && nm_utils_is_valid_iface_name (priv->iface, NULL));
 		break;
 	case PROP_IFINDEX:
 		/* construct-only */
 		priv->ifindex = g_value_get_int (value);
-		g_warn_if_fail (priv->ifindex > 0);
+		g_return_if_fail (priv->ifindex > 0);
 		break;
 	case PROP_HWADDR:
 		/* construct-only */
@@ -919,6 +917,10 @@ set_property (GObject *object, guint prop_id,
 		/* construct-only */
 		priv->uuid = g_value_dup_string (value);
 		break;
+	case PROP_HOSTNAME:
+		/* construct-only */
+		priv->hostname = g_value_dup_string (value);
+		break;
 	case PROP_ROUTE_TABLE:
 		priv->route_table = g_value_get_uint (value);
 		break;
@@ -971,7 +973,6 @@ dispose (GObject *object)
 	g_clear_pointer (&priv->uuid, g_free);
 	g_clear_pointer (&priv->client_id, g_bytes_unref);
 	g_clear_pointer (&priv->hwaddr, g_bytes_unref);
-	g_clear_pointer (&priv->duid, g_bytes_unref);
 
 	G_OBJECT_CLASS (nm_dhcp_client_parent_class)->dispose (object);
 
@@ -1028,6 +1029,12 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
 	                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                         G_PARAM_STATIC_STRINGS);
 
+	obj_properties[PROP_HOSTNAME] =
+	    g_param_spec_string (NM_DHCP_CLIENT_HOSTNAME, "", "",
+	                         NULL,
+	                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+	                         G_PARAM_STATIC_STRINGS);
+
 	obj_properties[PROP_ROUTE_TABLE] =
 	    g_param_spec_uint (NM_DHCP_CLIENT_ROUTE_TABLE, "", "",
 	                       0, G_MAXUINT32, RT_TABLE_MAIN,
@@ -1058,7 +1065,7 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
 	    g_signal_new (NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED,
 	                  G_OBJECT_CLASS_TYPE (object_class),
 	                  G_SIGNAL_RUN_FIRST,
-	                  G_STRUCT_OFFSET (NMDhcpClientClass, state_changed),
+	                  0,
 	                  NULL, NULL, NULL,
 	                  G_TYPE_NONE, 4, G_TYPE_UINT, G_TYPE_OBJECT, G_TYPE_HASH_TABLE, G_TYPE_STRING);
 
@@ -1066,7 +1073,7 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
 	    g_signal_new (NM_DHCP_CLIENT_SIGNAL_PREFIX_DELEGATED,
 	                  G_OBJECT_CLASS_TYPE (object_class),
 	                  G_SIGNAL_RUN_FIRST,
-	                  G_STRUCT_OFFSET (NMDhcpClientClass, state_changed),
+	                  0,
 	                  NULL, NULL, NULL,
 	                  G_TYPE_NONE, 1, G_TYPE_POINTER);
 }
diff --git a/src/dhcp/nm-dhcp-client.h b/src/dhcp/nm-dhcp-client.h
index 86d60e38..8be50717 100644
--- a/src/dhcp/nm-dhcp-client.h
+++ b/src/dhcp/nm-dhcp-client.h
@@ -41,6 +41,7 @@
 #define NM_DHCP_CLIENT_IFINDEX      "ifindex"
 #define NM_DHCP_CLIENT_INTERFACE    "iface"
 #define NM_DHCP_CLIENT_MULTI_IDX    "multi-idx"
+#define NM_DHCP_CLIENT_HOSTNAME     "hostname"
 #define NM_DHCP_CLIENT_ROUTE_METRIC "route-metric"
 #define NM_DHCP_CLIENT_ROUTE_TABLE  "route-table"
 #define NM_DHCP_CLIENT_TIMEOUT      "timeout"
@@ -85,13 +86,11 @@ typedef struct {
 	                           const char *anycast_addr,
 	                           const struct in6_addr *ll_addr,
 	                           NMSettingIP6ConfigPrivacy privacy,
-	                           GBytes *duid,
 	                           guint needed_prefixes,
 	                           GError **error);
 
 	void (*stop)              (NMDhcpClient *self,
-	                           gboolean release,
-	                           GBytes *duid);
+	                           gboolean release);
 
 	/**
 	 * get_duid:
@@ -103,12 +102,6 @@ typedef struct {
 	 * returned.
 	 */
 	GBytes *(*get_duid) (NMDhcpClient *self);
-
-	/* Signals */
-	void (*state_changed) (NMDhcpClient *self,
-	                       NMDhcpState state,
-	                       GObject *ip_config,
-	                       GHashTable *options);
 } NMDhcpClientClass;
 
 GType nm_dhcp_client_get_type (void);
@@ -150,7 +143,6 @@ gboolean nm_dhcp_client_get_use_fqdn (NMDhcpClient *self);
 gboolean nm_dhcp_client_start_ip4 (NMDhcpClient *self,
                                    GBytes *client_id,
                                    const char *dhcp_anycast_addr,
-                                   const char *hostname,
                                    const char *last_ip4_address,
                                    GError **error);
 
@@ -159,7 +151,6 @@ gboolean nm_dhcp_client_start_ip6 (NMDhcpClient *self,
                                    gboolean enforce_duid,
                                    const char *dhcp_anycast_addr,
                                    const struct in6_addr *ll_addr,
-                                   const char *hostname,
                                    NMSettingIP6ConfigPrivacy privacy,
                                    guint needed_prefixes,
                                    GError **error);
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/dhcp/nm-dhcp-dhclient-utils.c
index a2c3bfb6..be8d06d9 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -24,6 +24,7 @@
 #include <string.h>
 #include <ctype.h>
 #include <arpa/inet.h>
+#include <net/if.h>
 
 #include "nm-utils/nm-dedup-multi.h"
 
@@ -233,29 +234,6 @@ read_client_id (const char *str)
 	return nm_utils_hexstr2bin (s);
 }
 
-GBytes *
-nm_dhcp_dhclient_get_client_id_from_config_file (const char *path)
-{
-	gs_free char *contents = NULL;
-	gs_strfreev char **lines = NULL;
-	char **line;
-
-	g_return_val_if_fail (path != NULL, NULL);
-
-	if (!g_file_test (path, G_FILE_TEST_EXISTS))
-		return NULL;
-
-	if (!g_file_get_contents (path, &contents, NULL, NULL))
-		return NULL;
-
-	lines = g_strsplit_set (contents, "\n\r", 0);
-	for (line = lines; lines && *line; line++) {
-		if (!strncmp (*line, CLIENTID_TAG, NM_STRLEN (CLIENTID_TAG)))
-			return read_client_id (*line);
-	}
-	return NULL;
-}
-
 static gboolean
 read_interface (const char *line, char *interface, guint size)
 {
@@ -570,6 +548,7 @@ error:
 
 #define DUID_PREFIX "default-duid \""
 
+/* Beware: @error may be unset even if the function returns %NULL. */
 GBytes *
 nm_dhcp_dhclient_read_duid (const char *leasefile, GError **error)
 {
@@ -606,9 +585,10 @@ nm_dhcp_dhclient_read_duid (const char *leasefile, GError **error)
 
 gboolean
 nm_dhcp_dhclient_save_duid (const char *leasefile,
-                            const char *escaped_duid,
+                            GBytes *duid,
                             GError **error)
 {
+	gs_free char *escaped_duid = NULL;
 	gs_strfreev char **lines = NULL;
 	char **iter, *l;
 	GString *s;
@@ -616,6 +596,14 @@ nm_dhcp_dhclient_save_duid (const char *leasefile,
 	gsize len = 0;
 
 	g_return_val_if_fail (leasefile != NULL, FALSE);
+
+	if (!duid) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN,
+		                            "missing duid");
+		g_return_val_if_reached (FALSE);
+	}
+
+	escaped_duid = nm_dhcp_dhclient_escape_duid (duid);
 	g_return_val_if_fail (escaped_duid != NULL, FALSE);
 
 	if (g_file_test (leasefile, G_FILE_TEST_EXISTS)) {
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.h b/src/dhcp/nm-dhcp-dhclient-utils.h
index fab9196a..57a711db 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.h
+++ b/src/dhcp/nm-dhcp-dhclient-utils.h
@@ -40,10 +40,7 @@ GBytes *nm_dhcp_dhclient_unescape_duid (const char *duid);
 GBytes *nm_dhcp_dhclient_read_duid (const char *leasefile, GError **error);
 
 gboolean nm_dhcp_dhclient_save_duid (const char *leasefile,
-                                     const char *escaped_duid,
+                                     GBytes *duid,
                                      GError **error);
 
-GBytes *nm_dhcp_dhclient_get_client_id_from_config_file (const char *path);
-
 #endif /* __NETWORKMANAGER_DHCP_DHCLIENT_UTILS_H__ */
-
diff --git a/src/dhcp/nm-dhcp-dhclient.c b/src/dhcp/nm-dhcp-dhclient.c
index 33c26712..0146c8b4 100644
--- a/src/dhcp/nm-dhcp-dhclient.c
+++ b/src/dhcp/nm-dhcp-dhclient.c
@@ -121,8 +121,8 @@ get_dhclient_leasefile (int addr_family,
                         const char *uuid,
                         char **out_preferred_path)
 {
-	char *rundir_path;
-	char *path;
+	gs_free char *rundir_path = NULL;
+	gs_free char *path = NULL;
 
 	/* First, see if the lease file is in /run */
 	rundir_path = g_strdup_printf (NMRUNDIR "/dhclient%s-%s-%s.lease",
@@ -132,7 +132,7 @@ get_dhclient_leasefile (int addr_family,
 
 	if (g_file_test (rundir_path, G_FILE_TEST_EXISTS)) {
 		NM_SET_OUT (out_preferred_path, g_strdup (rundir_path));
-		return rundir_path;
+		return g_steal_pointer (&rundir_path);
 	}
 
 	/* /var/lib/NetworkManager is the preferred leasefile path */
@@ -142,18 +142,14 @@ get_dhclient_leasefile (int addr_family,
 	                        iface);
 
 	if (g_file_test (path, G_FILE_TEST_EXISTS)) {
-		g_free (rundir_path);
 		NM_SET_OUT (out_preferred_path, g_strdup (path));
-		return path;
+		return g_steal_pointer (&path);
 	}
 
-	if (nm_config_get_configure_and_quit (nm_config_get ()) == NM_CONFIG_CONFIGURE_AND_QUIT_INITRD) {
-		g_free (path);
-		path = rundir_path;
-	} else {
-		g_free (rundir_path);
-	}
-	NM_SET_OUT (out_preferred_path, g_steal_pointer (&path));
+	if (nm_config_get_configure_and_quit (nm_config_get ()) == NM_CONFIG_CONFIGURE_AND_QUIT_INITRD)
+		NM_SET_OUT (out_preferred_path, g_steal_pointer (&rundir_path));
+	else
+		NM_SET_OUT (out_preferred_path, g_steal_pointer (&path));
 
 	/* If the leasefile we're looking for doesn't exist yet in the new location
 	 * (eg, /var/lib/NetworkManager) then look in old locations to maintain
@@ -166,17 +162,16 @@ get_dhclient_leasefile (int addr_family,
 	path = g_strdup_printf (LOCALSTATEDIR "/lib/dhcp/dhclient%s-%s-%s.lease",
 	                        _addr_family_to_path_part (addr_family), uuid, iface);
 	if (g_file_test (path, G_FILE_TEST_EXISTS))
-		return path;
+		return g_steal_pointer (&path);
 
 	/* Old Red Hat and Fedora location */
 	g_free (path);
 	path = g_strdup_printf (LOCALSTATEDIR "/lib/dhclient/dhclient%s-%s-%s.lease",
 	                        _addr_family_to_path_part (addr_family), uuid, iface);
 	if (g_file_test (path, G_FILE_TEST_EXISTS))
-		return path;
+		return g_steal_pointer (&path);
 
 	/* Fail */
-	g_free (path);
 	return NULL;
 }
 
@@ -327,8 +322,18 @@ create_dhclient_config (NMDhcpDhclient *self,
 	else
 		_LOGD ("no existing dhclient configuration to merge");
 
-	if (!merge_dhclient_config (self, addr_family, iface, new, client_id, dhcp_anycast_addr,
-	                            hostname, timeout, use_fqdn, orig, out_new_client_id, &error)) {
+	if (!merge_dhclient_config (self,
+	                            addr_family,
+	                            iface,
+	                            new,
+	                            client_id,
+	                            dhcp_anycast_addr,
+	                            hostname,
+	                            timeout,
+	                            use_fqdn,
+	                            orig,
+	                            out_new_client_id,
+	                            &error)) {
 		_LOGW ("error creating dhclient configuration: %s", error->message);
 		g_clear_error (&error);
 	}
@@ -339,7 +344,6 @@ create_dhclient_config (NMDhcpDhclient *self,
 static gboolean
 dhclient_start (NMDhcpClient *client,
                 const char *mode_opt,
-                GBytes *duid,
                 gboolean release,
                 pid_t *out_pid,
                 int prefixes,
@@ -418,10 +422,9 @@ dhclient_start (NMDhcpClient *client,
 
 	/* Save the DUID to the leasefile dhclient will actually use */
 	if (addr_family == AF_INET6) {
-		gs_free char *escaped = NULL;
-
-		escaped = nm_dhcp_dhclient_escape_duid (duid);
-		if (!nm_dhcp_dhclient_save_duid (priv->lease_file, escaped, &local)) {
+		if (!nm_dhcp_dhclient_save_duid (priv->lease_file,
+		                                 nm_dhcp_client_get_client_id (client),
+		                                 &local)) {
 			nm_utils_error_set (error,
 			                    NM_UTILS_ERROR_UNKNOWN,
 			                    "failed to save DUID to '%s': %s",
@@ -541,7 +544,6 @@ ip4_start (NMDhcpClient *client,
 	}
 	return dhclient_start (client,
 	                       NULL,
-	                       NULL,
 	                       FALSE,
 	                       NULL,
 	                       0,
@@ -553,7 +555,6 @@ ip6_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
            const struct in6_addr *ll_addr,
            NMSettingIP6ConfigPrivacy privacy,
-           GBytes *duid,
            guint needed_prefixes,
            GError **error)
 {
@@ -581,7 +582,6 @@ ip6_start (NMDhcpClient *client,
 	                       nm_dhcp_client_get_info_only (NM_DHCP_CLIENT (self))
 	                         ? "-S"
 	                         : "-N",
-	                       duid,
 	                       FALSE,
 	                       NULL,
 	                       needed_prefixes,
@@ -589,12 +589,12 @@ ip6_start (NMDhcpClient *client,
 }
 
 static void
-stop (NMDhcpClient *client, gboolean release, GBytes *duid)
+stop (NMDhcpClient *client, gboolean release)
 {
 	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
 	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
 
-	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhclient_parent_class)->stop (client, release, duid);
+	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhclient_parent_class)->stop (client, release);
 
 	if (priv->conf_file)
 		if (remove (priv->conf_file) == -1)
@@ -613,7 +613,6 @@ stop (NMDhcpClient *client, gboolean release, GBytes *duid)
 
 		if (dhclient_start (client,
 		                    NULL,
-		                    duid,
 		                    TRUE,
 		                    &rpid,
 		                    0,
@@ -624,31 +623,13 @@ stop (NMDhcpClient *client, gboolean release, GBytes *duid)
 	}
 }
 
-static void
-state_changed (NMDhcpClient *client,
-               NMDhcpState state,
-               GObject *ip_config,
-               GHashTable *options)
-{
-	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE ((NMDhcpDhclient *) client);
-	gs_unref_bytes GBytes *client_id = NULL;
-
-	if (nm_dhcp_client_get_client_id (client))
-		return;
-	if (state != NM_DHCP_STATE_BOUND)
-		return;
-
-	client_id = nm_dhcp_dhclient_get_client_id_from_config_file (priv->conf_file);
-	nm_dhcp_client_set_client_id (client, client_id);
-}
-
 static GBytes *
 get_duid (NMDhcpClient *client)
 {
 	NMDhcpDhclient *self = NM_DHCP_DHCLIENT (client);
 	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (self);
 	GBytes *duid = NULL;
-	char *leasefile;
+	gs_free char *leasefile = NULL;
 	GError *error = NULL;
 
 	/* Look in interface-specific leasefile first for backwards compat */
@@ -659,25 +640,23 @@ get_duid (NMDhcpClient *client)
 	if (leasefile) {
 		_LOGD ("looking for DUID in '%s'", leasefile);
 		duid = nm_dhcp_dhclient_read_duid (leasefile, &error);
-
 		if (error) {
 			_LOGW ("failed to read leasefile '%s': %s",
 			       leasefile, error->message);
 			g_clear_error (&error);
 		}
-		g_free (leasefile);
+		if (duid)
+			return duid;
 	}
 
-	if (!duid) {
-		/* Otherwise read the default machine-wide DUID */
-		_LOGD ("looking for default DUID in '%s'", priv->def_leasefile);
-		duid = nm_dhcp_dhclient_read_duid (priv->def_leasefile, &error);
-		if (error) {
-			_LOGW ("failed to read leasefile '%s': %s",
-			        priv->def_leasefile,
-			        error->message);
-			g_clear_error (&error);
-		}
+	/* Otherwise read the default machine-wide DUID */
+	_LOGD ("looking for default DUID in '%s'", priv->def_leasefile);
+	duid = nm_dhcp_dhclient_read_duid (priv->def_leasefile, &error);
+	if (error) {
+		_LOGW ("failed to read leasefile '%s': %s",
+		        priv->def_leasefile,
+		        error->message);
+		g_clear_error (&error);
 	}
 
 	return duid;
@@ -742,7 +721,6 @@ nm_dhcp_dhclient_class_init (NMDhcpDhclientClass *dhclient_class)
 	client_class->ip6_start = ip6_start;
 	client_class->stop = stop;
 	client_class->get_duid = get_duid;
-	client_class->state_changed = state_changed;
 }
 
 const NMDhcpClientFactory _nm_dhcp_client_factory_dhclient = {
diff --git a/src/dhcp/nm-dhcp-dhcpcanon.c b/src/dhcp/nm-dhcp-dhcpcanon.c
index de403020..0f033e22 100644
--- a/src/dhcp/nm-dhcp-dhcpcanon.c
+++ b/src/dhcp/nm-dhcp-dhcpcanon.c
@@ -193,20 +193,20 @@ ip6_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
            const struct in6_addr *ll_addr,
            NMSettingIP6ConfigPrivacy privacy,
-           GBytes *duid,
            guint needed_prefixes,
            GError **error)
 {
 	nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "dhcpcanon plugin does not support IPv6");
 	return FALSE;
 }
+
 static void
-stop (NMDhcpClient *client, gboolean release, GBytes *duid)
+stop (NMDhcpClient *client, gboolean release)
 {
 	NMDhcpDhcpcanon *self = NM_DHCP_DHCPCANON (client);
 	NMDhcpDhcpcanonPrivate *priv = NM_DHCP_DHCPCANON_GET_PRIVATE (self);
 
-	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhcpcanon_parent_class)->stop (client, release, duid);
+	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhcpcanon_parent_class)->stop (client, release);
 
 	if (priv->pid_file) {
 		if (remove (priv->pid_file) == -1)
@@ -216,18 +216,6 @@ stop (NMDhcpClient *client, gboolean release, GBytes *duid)
 	}
 }
 
-static void
-state_changed (NMDhcpClient *client,
-               NMDhcpState state,
-               GObject *ip_config,
-               GHashTable *options)
-{
-	if (nm_dhcp_client_get_client_id (client))
-		return;
-	if (state != NM_DHCP_STATE_BOUND)
-		return;
-}
-
 /*****************************************************************************/
 
 static void
@@ -270,7 +258,6 @@ nm_dhcp_dhcpcanon_class_init (NMDhcpDhcpcanonClass *dhcpcanon_class)
 	client_class->ip4_start = ip4_start;
 	client_class->ip6_start = ip6_start;
 	client_class->stop = stop;
-	client_class->state_changed = state_changed;
 }
 
 const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcanon = {
diff --git a/src/dhcp/nm-dhcp-dhcpcd.c b/src/dhcp/nm-dhcp-dhcpcd.c
index 98ab5342..e2a1354f 100644
--- a/src/dhcp/nm-dhcp-dhcpcd.c
+++ b/src/dhcp/nm-dhcp-dhcpcd.c
@@ -187,7 +187,6 @@ ip6_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
            const struct in6_addr *ll_addr,
            NMSettingIP6ConfigPrivacy privacy,
-           GBytes *duid,
            guint needed_prefixes,
            GError **error)
 {
@@ -196,12 +195,12 @@ ip6_start (NMDhcpClient *client,
 }
 
 static void
-stop (NMDhcpClient *client, gboolean release, GBytes *duid)
+stop (NMDhcpClient *client, gboolean release)
 {
 	NMDhcpDhcpcd *self = NM_DHCP_DHCPCD (client);
 	NMDhcpDhcpcdPrivate *priv = NM_DHCP_DHCPCD_GET_PRIVATE (self);
 
-	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhcpcd_parent_class)->stop (client, release, duid);
+	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhcpcd_parent_class)->stop (client, release);
 
 	if (priv->pid_file) {
 		if (remove (priv->pid_file) == -1)
diff --git a/src/dhcp/nm-dhcp-manager.c b/src/dhcp/nm-dhcp-manager.c
index 5ae16d72..a51c6e38 100644
--- a/src/dhcp/nm-dhcp-manager.c
+++ b/src/dhcp/nm-dhcp-manager.c
@@ -181,6 +181,7 @@ client_start (NMDhcpManager *self,
 	gsize hwaddr_len;
 
 	g_return_val_if_fail (NM_IS_DHCP_MANAGER (self), NULL);
+	g_return_val_if_fail (iface, NULL);
 	g_return_val_if_fail (ifindex > 0, NULL);
 	g_return_val_if_fail (uuid != NULL, NULL);
 	g_return_val_if_fail (!dhcp_client_id || g_bytes_get_size (dhcp_client_id) >= 2, NULL);
@@ -221,6 +222,7 @@ client_start (NMDhcpManager *self,
 	                       NM_DHCP_CLIENT_IFINDEX, ifindex,
 	                       NM_DHCP_CLIENT_HWADDR, hwaddr,
 	                       NM_DHCP_CLIENT_UUID, uuid,
+	                       NM_DHCP_CLIENT_HOSTNAME, hostname,
 	                       NM_DHCP_CLIENT_ROUTE_TABLE, (guint) route_table,
 	                       NM_DHCP_CLIENT_ROUTE_METRIC, (guint) route_metric,
 	                       NM_DHCP_CLIENT_TIMEOUT, (guint) timeout,
@@ -233,11 +235,36 @@ client_start (NMDhcpManager *self,
 	c_list_link_tail (&priv->dhcp_client_lst_head, &client->dhcp_client_lst);
 	g_signal_connect (client, NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED, G_CALLBACK (client_state_changed), self);
 
+	/* unfortunately, our implementations work differently per address-family regarding client-id/DUID.
+	 *
+	 * - for IPv4, the calling code may determine a client-id (from NM's connection profile).
+	 *   If present, it is taken. If not present, the DHCP plugin uses a plugin specific default.
+	 *     - for "internal" plugin, the default is just "duid".
+	 *     - for "dhclient", we try to get the configuration from dhclient's /etc/dhcp or fallback
+	 *       to whatever dhclient uses by default.
+	 *   We do it this way, because for dhclient the user may configure a default
+	 *   outside of NM, and we want to honor that. Worse, dhclient could be a wapper
+	 *   script where the wrapper script overwrites the client-id. We need to distinguish
+	 *   between: force a particular client-id and leave it unspecified to whatever dhclient
+	 *   wants.
+	 *
+	 * - for IPv6, the calling code always determines a client-id. It also specifies @enforce_duid,
+	 *   to determine whether the given client-id must be used.
+	 *     - for "internal" plugin @enforce_duid doesn't matter and the given client-id is
+	 *       always used.
+	 *     - for "dhclient", @enforce_duid FALSE means to first try to load the DUID from the
+	 *       lease file, and only otherwise fallback to the given client-id.
+	 *     - other plugins don't support DHCPv6.
+	 *   It's done this way, so that existing dhclient setups don't change behavior on upgrade.
+	 *
+	 * This difference is cumbersome and only exists because of "dhclient" which supports hacking the
+	 * default outside of NetworkManager API.
+	 */
+
 	if (addr_family == AF_INET) {
 		success = nm_dhcp_client_start_ip4 (client,
 		                                    dhcp_client_id,
 		                                    dhcp_anycast_addr,
-		                                    hostname,
 		                                    last_ip4_address,
 		                                    error);
 	} else {
@@ -246,7 +273,6 @@ client_start (NMDhcpManager *self,
 		                                    enforce_duid,
 		                                    dhcp_anycast_addr,
 		                                    ipv6_ll_addr,
-		                                    hostname,
 		                                    privacy,
 		                                    needed_prefixes,
 		                                    error);
@@ -311,10 +337,27 @@ nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
 		}
 	}
 
-	return client_start (self, AF_INET, multi_idx, iface, ifindex, hwaddr, uuid,
-	                     route_table, route_metric, NULL,
-	                     dhcp_client_id, 0, timeout, dhcp_anycast_addr, hostname,
-	                     use_fqdn, FALSE, 0, last_ip_address, 0, error);
+	return client_start (self,
+	                     AF_INET,
+	                     multi_idx,
+	                     iface,
+	                     ifindex,
+	                     hwaddr,
+	                     uuid,
+	                     route_table,
+	                     route_metric,
+	                     NULL,
+	                     dhcp_client_id,
+	                     FALSE,
+	                     timeout,
+	                     dhcp_anycast_addr,
+	                     hostname,
+	                     use_fqdn,
+	                     FALSE,
+	                     0,
+	                     last_ip_address,
+	                     0,
+	                     error);
 }
 
 /* Caller owns a reference to the NMDhcpClient on return */
@@ -349,10 +392,27 @@ nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
 		/* Always prefer the explicit dhcp-hostname if given */
 		hostname = dhcp_hostname ?: priv->default_hostname;
 	}
-	return client_start (self, AF_INET6, multi_idx, iface, ifindex, hwaddr, uuid,
-	                     route_table, route_metric, ll_addr, duid, enforce_duid,
-	                     timeout, dhcp_anycast_addr, hostname, TRUE, info_only,
-	                     privacy, NULL, needed_prefixes, error);
+	return client_start (self,
+	                     AF_INET6,
+	                     multi_idx,
+	                     iface,
+	                     ifindex,
+	                     hwaddr,
+	                     uuid,
+	                     route_table,
+	                     route_metric,
+	                     ll_addr,
+	                     duid,
+	                     enforce_duid,
+	                     timeout,
+	                     dhcp_anycast_addr,
+	                     hostname,
+	                     TRUE,
+	                     info_only,
+	                     privacy,
+	                     NULL,
+	                     needed_prefixes,
+	                     error);
 }
 
 void
@@ -384,6 +444,12 @@ nm_dhcp_manager_get_config (NMDhcpManager *self)
 
 NM_DEFINE_SINGLETON_GETTER (NMDhcpManager, nm_dhcp_manager_get, NM_TYPE_DHCP_MANAGER);
 
+void
+nmtst_dhcp_manager_unget (gpointer self)
+{
+	_nmtst_nm_dhcp_manager_get_reset (self);
+}
+
 static void
 nm_dhcp_manager_init (NMDhcpManager *self)
 {
@@ -446,6 +512,10 @@ nm_dhcp_manager_init (NMDhcpManager *self)
 
 	nm_log_info (LOGD_DHCP, "dhcp-init: Using DHCP client '%s'", client_factory->name);
 
+	/* NOTE: currently the DHCP plugin is chosen once at start. It's not
+	 * possible to reload that configuration. If that ever becomes possible,
+	 * beware that the "dhcp-plugin" device spec made decisions based on
+	 * the previous plugin and may need reevaluation. */
 	priv->client_factory = client_factory;
 }
 
diff --git a/src/dhcp/nm-dhcp-manager.h b/src/dhcp/nm-dhcp-manager.h
index 1d9e5c21..f8f39e53 100644
--- a/src/dhcp/nm-dhcp-manager.h
+++ b/src/dhcp/nm-dhcp-manager.h
@@ -87,4 +87,6 @@ extern const char* nm_dhcp_helper_path;
 
 extern const NMDhcpClientFactory *const _nm_dhcp_manager_factories[4];
 
+void nmtst_dhcp_manager_unget (gpointer singleton_instance);
+
 #endif /* __NETWORKMANAGER_DHCP_MANAGER_H__ */
diff --git a/src/dhcp/nm-dhcp-systemd.c b/src/dhcp/nm-dhcp-systemd.c
index 5b7b5fbe..5c60af5f 100644
--- a/src/dhcp/nm-dhcp-systemd.c
+++ b/src/dhcp/nm-dhcp-systemd.c
@@ -34,6 +34,7 @@
 #include "nm-utils.h"
 #include "nm-config.h"
 #include "nm-dhcp-utils.h"
+#include "nm-core-utils.h"
 #include "NetworkManagerUtils.h"
 #include "platform/nm-platform.h"
 #include "nm-dhcp-client-logging.h"
@@ -483,22 +484,6 @@ get_leasefile_path (int addr_family, const char *iface, const char *uuid)
 /*****************************************************************************/
 
 static void
-_save_client_id (NMDhcpSystemd *self,
-                 uint8_t type,
-                 const uint8_t *client_id,
-                 size_t len)
-{
-	g_return_if_fail (self != NULL);
-	g_return_if_fail (client_id != NULL);
-	g_return_if_fail (len > 0);
-
-	if (!nm_dhcp_client_get_client_id (NM_DHCP_CLIENT (self))) {
-		nm_dhcp_client_set_client_id_bin (NM_DHCP_CLIENT (self),
-		                                  type, client_id, len);
-	}
-}
-
-static void
 bound4_handle (NMDhcpSystemd *self)
 {
 	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
@@ -529,17 +514,9 @@ bound4_handle (NMDhcpSystemd *self)
 	                                  TRUE,
 	                                  &error);
 	if (ip4_config) {
-		const uint8_t *client_id = NULL;
-		size_t client_id_len = 0;
-		uint8_t type = 0;
-
 		add_requests_to_options (options, dhcp4_requests);
 		dhcp_lease_save (lease, priv->lease_file);
 
-		sd_dhcp_client_get_client_id (priv->client4, &type, &client_id, &client_id_len);
-		if (client_id)
-			_save_client_id (self, type, client_id, client_id_len);
-
 		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self),
 		                          NM_DHCP_STATE_BOUND,
 		                          NM_IP_CONFIG_CAST (ip4_config),
@@ -582,127 +559,122 @@ dhcp_event_cb (sd_dhcp_client *client, int event, gpointer user_data)
 	}
 }
 
-static guint16
-get_arp_type (GBytes *hwaddr)
-{
-	switch (g_bytes_get_size (hwaddr)) {
-	case ETH_ALEN:
-		return ARPHRD_ETHER;
-	case INFINIBAND_ALEN:
-		return ARPHRD_INFINIBAND;
-	default:
-		return ARPHRD_NONE;
-	}
-}
-
 static gboolean
 ip4_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
            const char *last_ip4_address,
            GError **error)
 {
+	nm_auto (sd_dhcp_client_unrefp) sd_dhcp_client *sd_client = NULL;
 	NMDhcpSystemd *self = NM_DHCP_SYSTEMD (client);
 	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
-	const char *iface = nm_dhcp_client_get_iface (client);
+	gs_free char *lease_file = NULL;
 	GBytes *hwaddr;
-	sd_dhcp_lease *lease = NULL;
-	GBytes *override_client_id;
-	const uint8_t *client_id = NULL;
-	size_t client_id_len = 0;
+	const uint8_t *hwaddr_arr;
+	gsize hwaddr_len;
+	int arp_type;
+	GBytes *client_id;
+	gs_unref_bytes GBytes *client_id_new = NULL;
+	const uint8_t *client_id_arr;
+	size_t client_id_len;
 	struct in_addr last_addr = { 0 };
 	const char *hostname;
 	int r, i;
-	gboolean success = FALSE;
 
-	g_assert (priv->client4 == NULL);
-	g_assert (priv->client6 == NULL);
-
-	g_free (priv->lease_file);
-	priv->lease_file = get_leasefile_path (AF_INET, iface, nm_dhcp_client_get_uuid (client));
+	g_return_val_if_fail (!priv->client4, FALSE);
+	g_return_val_if_fail (!priv->client6, FALSE);
 
-	r = sd_dhcp_client_new (&priv->client4, FALSE);
+	r = sd_dhcp_client_new (&sd_client, FALSE);
 	if (r < 0) {
 		nm_utils_error_set_errno (error, r, "failed to create dhcp-client: %s");
 		return FALSE;
 	}
 
-	_LOGT ("dhcp-client4: set %p", priv->client4);
+	_LOGT ("dhcp-client4: set %p", sd_client);
 
-	r = sd_dhcp_client_attach_event (priv->client4, NULL, 0);
+	r = sd_dhcp_client_attach_event (sd_client, NULL, 0);
 	if (r < 0) {
 		nm_utils_error_set_errno (error, r, "failed to attach event: %s");
-		goto errout;
+		return FALSE;
 	}
 
 	hwaddr = nm_dhcp_client_get_hw_addr (client);
-	if (hwaddr) {
-		const uint8_t *data;
-		gsize len;
-
-		data = g_bytes_get_data (hwaddr, &len);
-		r = sd_dhcp_client_set_mac (priv->client4,
-		                            data,
-		                            len,
-		                            get_arp_type (hwaddr));
-		if (r < 0) {
-			nm_utils_error_set_errno (error, r, "failed to set MAC address: %s");
-			goto errout;
-		}
+	if (   !hwaddr
+	    || !(hwaddr_arr = g_bytes_get_data (hwaddr, &hwaddr_len))
+	    || (arp_type = nm_utils_arp_type_detect_from_hwaddrlen (hwaddr_len)) < 0) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "invalid MAC address");
+		return FALSE;
 	}
-
-	r = sd_dhcp_client_set_ifindex (priv->client4, nm_dhcp_client_get_ifindex (client));
+	r = sd_dhcp_client_set_mac (sd_client,
+	                            hwaddr_arr,
+	                            hwaddr_len,
+	                            (guint16) arp_type);
 	if (r < 0) {
-		nm_utils_error_set_errno (error, r, "failed to set ifindex: %s");
-		goto errout;
+		nm_utils_error_set_errno (error, r, "failed to set MAC address: %s");
+		return FALSE;
 	}
 
-	r = sd_dhcp_client_set_callback (priv->client4, dhcp_event_cb, client);
+	r = sd_dhcp_client_set_ifindex (sd_client,
+	                                nm_dhcp_client_get_ifindex (client));
 	if (r < 0) {
-		nm_utils_error_set_errno (error, r, "failed to set callback: %s");
-		goto errout;
+		nm_utils_error_set_errno (error, r, "failed to set ifindex: %s");
+		return FALSE;
 	}
 
-	dhcp_lease_load (&lease, priv->lease_file);
+	lease_file = get_leasefile_path (AF_INET,
+	                                 nm_dhcp_client_get_iface (client),
+	                                 nm_dhcp_client_get_uuid (client));
 
 	if (last_ip4_address)
 		inet_pton (AF_INET, last_ip4_address, &last_addr);
-	else if (lease)
-		sd_dhcp_lease_get_address (lease, &last_addr);
+	else {
+		nm_auto (sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
+
+		dhcp_lease_load (&lease, lease_file);
+		if (lease)
+			sd_dhcp_lease_get_address (lease, &last_addr);
+	}
 
 	if (last_addr.s_addr) {
-		r = sd_dhcp_client_set_request_address (priv->client4, &last_addr);
+		r = sd_dhcp_client_set_request_address (sd_client, &last_addr);
 		if (r < 0) {
 			nm_utils_error_set_errno (error, r, "failed to set last IPv4 address: %s");
-			goto errout;
+			return FALSE;
 		}
 	}
 
-	override_client_id = nm_dhcp_client_get_client_id (client);
-	if (override_client_id) {
-		client_id = g_bytes_get_data (override_client_id, &client_id_len);
-		nm_assert (client_id && client_id_len >= 2);
-		sd_dhcp_client_set_client_id (priv->client4,
-		                              client_id[0],
-		                              client_id + 1,
-		                              NM_MIN (client_id_len - 1, _NM_SD_MAX_CLIENT_ID_LEN));
-	} else if (lease) {
-		r = sd_dhcp_lease_get_client_id (lease, (const void **) &client_id, &client_id_len);
-		if (r == 0 && client_id_len >= 2) {
-			sd_dhcp_client_set_client_id (priv->client4,
-			                              client_id[0],
-			                              client_id + 1,
-			                              client_id_len - 1);
-			_save_client_id (NM_DHCP_SYSTEMD (client),
-			                 client_id[0],
-			                 client_id + 1,
-			                 client_id_len - 1);
-		}
+	client_id = nm_dhcp_client_get_client_id (client);
+	if (!client_id) {
+		client_id_new = nm_utils_dhcp_client_id_systemd_node_specific (TRUE,
+		                                                               nm_dhcp_client_get_iface (client));
+		client_id = client_id_new;
+	}
+
+	if (   !(client_id_arr = g_bytes_get_data (client_id, &client_id_len))
+	    || client_id_len < 2) {
+
+		/* invalid client-ids are not expected. */
+		nm_assert_not_reached ();
+
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "no valid IPv4 client-id");
+		return FALSE;
+	}
+
+	/* Note that we always set a client-id. In particular for infiniband that is necessary,
+	 * see https://tools.ietf.org/html/rfc4390#section-2.1 . */
+	r = sd_dhcp_client_set_client_id (sd_client,
+	                                  client_id_arr[0],
+	                                  client_id_arr + 1,
+	                                  NM_MIN (client_id_len - 1, _NM_SD_MAX_CLIENT_ID_LEN));
+	if (r < 0) {
+		nm_utils_error_set_errno (error, r, "failed to set IPv4 client-id: %s");
+		return FALSE;
 	}
 
 	/* Add requested options */
 	for (i = 0; dhcp4_requests[i].name; i++) {
 		if (dhcp4_requests[i].include)
-			sd_dhcp_client_set_request_option (priv->client4, dhcp4_requests[i].num);
+			sd_dhcp_client_set_request_option (sd_client, dhcp4_requests[i].num);
 	}
 
 	hostname = nm_dhcp_client_get_hostname (client);
@@ -711,28 +683,36 @@ ip4_start (NMDhcpClient *client,
 		 * only based on whether the hostname has a domain part or not. At the
 		 * moment there is no way to force one or another.
 		 */
-		r = sd_dhcp_client_set_hostname (priv->client4, hostname);
+		r = sd_dhcp_client_set_hostname (sd_client, hostname);
 		if (r < 0) {
 			nm_utils_error_set_errno (error, r, "failed to set DHCP hostname: %s");
-			goto errout;
+			return FALSE;
 		}
 	}
 
+	r = sd_dhcp_client_set_callback (sd_client, dhcp_event_cb, client);
+	if (r < 0) {
+		nm_utils_error_set_errno (error, r, "failed to set callback: %s");
+		return FALSE;
+	}
+
+	priv->client4 = g_steal_pointer (&sd_client);
+
+	g_free (priv->lease_file);
+	priv->lease_file = g_steal_pointer (&lease_file);
+
+	nm_dhcp_client_set_client_id (client, client_id);
+
 	r = sd_dhcp_client_start (priv->client4);
 	if (r < 0) {
+		sd_dhcp_client_set_callback (priv->client4, NULL, NULL);
+		nm_clear_pointer (&priv->client4, sd_dhcp_client_unref);
 		nm_utils_error_set_errno (error, r, "failed to start DHCP client: %s");
-		goto errout;
+		return FALSE;
 	}
 
 	nm_dhcp_client_start_timeout (client);
-
-	success = TRUE;
-
-errout:
-	sd_dhcp_lease_unref (lease);
-	if (!success)
-		sd_dhcp_client_unref (g_steal_pointer (&priv->client4));
-	return success;
+	return TRUE;
 }
 
 static NMIP6Config *
@@ -895,31 +875,33 @@ ip6_start (NMDhcpClient *client,
            const char *dhcp_anycast_addr,
            const struct in6_addr *ll_addr,
            NMSettingIP6ConfigPrivacy privacy,
-           GBytes *duid,
            guint needed_prefixes,
            GError **error)
 {
 	NMDhcpSystemd *self = NM_DHCP_SYSTEMD (client);
 	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
-	const char *iface = nm_dhcp_client_get_iface (client);
+	nm_auto (sd_dhcp6_client_unrefp) sd_dhcp6_client *sd_client = NULL;
 	GBytes *hwaddr;
 	const char *hostname;
 	int r, i;
 	const guint8 *duid_arr;
 	gsize duid_len;
-
-	g_assert (priv->client4 == NULL);
-	g_assert (priv->client6 == NULL);
-	g_return_val_if_fail (duid != NULL, FALSE);
-
-	duid_arr = g_bytes_get_data (duid, &duid_len);
-	if (!duid_arr || duid_len < 2)
+	GBytes *duid;
+	const uint8_t *hwaddr_arr;
+	gsize hwaddr_len;
+	int arp_type;
+
+	g_return_val_if_fail (!priv->client4, FALSE);
+	g_return_val_if_fail (!priv->client6, FALSE);
+
+	if (   !(duid = nm_dhcp_client_get_client_id (client))
+	    || !(duid_arr = g_bytes_get_data (duid, &duid_len))
+	    || duid_len < 2) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "missing DUID");
 		g_return_val_if_reached (FALSE);
+	}
 
-	g_free (priv->lease_file);
-	priv->lease_file = get_leasefile_path (AF_INET6, iface, nm_dhcp_client_get_uuid (client));
-
-	r = sd_dhcp6_client_new (&priv->client6);
+	r = sd_dhcp6_client_new (&sd_client);
 	if (r < 0) {
 		nm_utils_error_set_errno (error, r, "failed to create dhcp-client: %s");
 		return FALSE;
@@ -930,12 +912,12 @@ ip6_start (NMDhcpClient *client,
 		       needed_prefixes);
 	}
 
-	_LOGT ("dhcp-client6: set %p", priv->client6);
+	_LOGT ("dhcp-client6: set %p", sd_client);
 
 	if (nm_dhcp_client_get_info_only (client))
-		sd_dhcp6_client_set_information_request (priv->client6, 1);
+		sd_dhcp6_client_set_information_request (sd_client, 1);
 
-	r = sd_dhcp6_client_set_duid (priv->client6,
+	r = sd_dhcp6_client_set_duid (sd_client,
 	                              unaligned_read_be16 (&duid_arr[0]),
 	                              &duid_arr[2],
 	                              duid_len - 2);
@@ -944,82 +926,82 @@ ip6_start (NMDhcpClient *client,
 		return FALSE;
 	}
 
-	r = sd_dhcp6_client_attach_event (priv->client6, NULL, 0);
+	r = sd_dhcp6_client_attach_event (sd_client, NULL, 0);
 	if (r < 0) {
 		nm_utils_error_set_errno (error, r, "failed to attach event: %s");
-		goto errout;
+		return FALSE;
 	}
 
 	hwaddr = nm_dhcp_client_get_hw_addr (client);
-	if (hwaddr) {
-		const uint8_t *data;
-		gsize len;
-
-		data = g_bytes_get_data (hwaddr, &len);
-		r = sd_dhcp6_client_set_mac (priv->client6,
-		                             data,
-		                             len,
-		                             get_arp_type (hwaddr));
-		if (r < 0) {
-			nm_utils_error_set_errno (error, r, "failed to set MAC address: %s");
-			goto errout;
-		}
+	if (   !hwaddr
+	    || !(hwaddr_arr = g_bytes_get_data (hwaddr, &hwaddr_len))
+	    || (arp_type = nm_utils_arp_type_detect_from_hwaddrlen (hwaddr_len)) < 0) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "invalid MAC address");
+		return FALSE;
 	}
-
-	r = sd_dhcp6_client_set_ifindex (priv->client6, nm_dhcp_client_get_ifindex (client));
+	r = sd_dhcp6_client_set_mac (sd_client,
+	                             hwaddr_arr,
+	                             hwaddr_len,
+	                             (guint16) arp_type);
 	if (r < 0) {
-		nm_utils_error_set_errno (error, r, "failed to set ifindex: %s");
-		goto errout;
+		nm_utils_error_set_errno (error, r, "failed to set MAC address: %s");
+		return FALSE;
 	}
 
-	r = sd_dhcp6_client_set_callback (priv->client6, dhcp6_event_cb, client);
+	r = sd_dhcp6_client_set_ifindex (sd_client,
+	                                 nm_dhcp_client_get_ifindex (client));
 	if (r < 0) {
-		nm_utils_error_set_errno (error, r, "failed to set callback: %s");
-		goto errout;
+		nm_utils_error_set_errno (error, r, "failed to set ifindex: %s");
+		return FALSE;
 	}
 
 	/* Add requested options */
 	for (i = 0; dhcp6_requests[i].name; i++) {
 		if (dhcp6_requests[i].include)
-			sd_dhcp6_client_set_request_option (priv->client6, dhcp6_requests[i].num);
+			sd_dhcp6_client_set_request_option (sd_client, dhcp6_requests[i].num);
 	}
 
-	r = sd_dhcp6_client_set_local_address (priv->client6, ll_addr);
+	r = sd_dhcp6_client_set_local_address (sd_client, ll_addr);
 	if (r < 0) {
 		nm_utils_error_set_errno (error, r, "failed to set local address: %s");
-		goto errout;
+		return FALSE;
 	}
 
 	hostname = nm_dhcp_client_get_hostname (client);
-	r = sd_dhcp6_client_set_fqdn (priv->client6, hostname);
+	r = sd_dhcp6_client_set_fqdn (sd_client, hostname);
 	if (r < 0) {
 		nm_utils_error_set_errno (error, r, "failed to set DHCP hostname: %s");
-		goto errout;
+		return FALSE;
 	}
 
+	r = sd_dhcp6_client_set_callback (sd_client, dhcp6_event_cb, client);
+	if (r < 0) {
+		nm_utils_error_set_errno (error, r, "failed to set callback: %s");
+		return FALSE;
+	}
+
+	priv->client6 = g_steal_pointer (&sd_client);
+
 	r = sd_dhcp6_client_start (priv->client6);
 	if (r < 0) {
+		sd_dhcp6_client_set_callback (priv->client6, NULL, NULL);
+		nm_clear_pointer (&priv->client6, sd_dhcp6_client_unref);
 		nm_utils_error_set_errno (error, r, "failed to start client: %s");
-		goto errout;
+		return FALSE;
 	}
 
 	nm_dhcp_client_start_timeout (client);
-
 	return TRUE;
-
-errout:
-	sd_dhcp6_client_unref (g_steal_pointer (&priv->client6));
-	return FALSE;
 }
 
 static void
-stop (NMDhcpClient *client, gboolean release, GBytes *duid)
+stop (NMDhcpClient *client, gboolean release)
 {
 	NMDhcpSystemd *self = NM_DHCP_SYSTEMD (client);
 	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
 	int r = 0;
 
-	NM_DHCP_CLIENT_CLASS (nm_dhcp_systemd_parent_class)->stop (client, release, duid);
+	NM_DHCP_CLIENT_CLASS (nm_dhcp_systemd_parent_class)->stop (client, release);
 
 	_LOGT ("dhcp-client%d: stop %p",
 	       priv->client4 ? '4' : '6',
diff --git a/src/dhcp/nm-dhcp-utils.c b/src/dhcp/nm-dhcp-utils.c
index 6bbc670b..9b1653b8 100644
--- a/src/dhcp/nm-dhcp-utils.c
+++ b/src/dhcp/nm-dhcp-utils.c
@@ -726,10 +726,10 @@ nm_dhcp_utils_duid_to_string (GBytes *duid)
 	gconstpointer data;
 	gsize len;
 
-	g_return_val_if_fail (duid != NULL, NULL);
+	g_return_val_if_fail (duid, NULL);
 
 	data = g_bytes_get_data (duid, &len);
-	return _nm_utils_bin2str (data, len, FALSE);
+	return _nm_utils_bin2hexstr_full (data, len, ':', FALSE, NULL);
 }
 
 /**
diff --git a/src/dhcp/tests/test-dhcp-dhclient.c b/src/dhcp/tests/test-dhcp-dhclient.c
index edac4257..ab1f5551 100644
--- a/src/dhcp/tests/test-dhcp-dhclient.c
+++ b/src/dhcp/tests/test-dhcp-dhclient.c
@@ -760,62 +760,74 @@ test_read_commented_duid_from_leasefile (void)
 	g_assert (duid == NULL);
 }
 
+/*****************************************************************************/
+
+static void
+_save_duid (const char *path,
+            const guint8 *duid_bin,
+            gsize duid_len)
+{
+	gs_unref_bytes GBytes *duid = NULL;
+	GError *error = NULL;
+	gboolean success;
+
+	g_assert (path);
+	g_assert (duid_bin);
+	g_assert (duid_len > 0);
+
+	duid = g_bytes_new (duid_bin, duid_len);
+	success = nm_dhcp_dhclient_save_duid (path, duid, &error);
+	nmtst_assert_success (success, error);
+}
+
 static void
 test_write_duid (void)
 {
-	const char *duid = "\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254";
+	const guint8 duid[] = { 000, 001, 000, 001, 027, 'X', 0350, 'X', 0, '#', 025, 010, '~', 0254 };
 	const char *expected_contents = "default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n";
 	GError *error = NULL;
-	char *contents = NULL;
+	gs_free char *contents = NULL;
 	gboolean success;
 	const char *path = "test-dhclient-write-duid.leases";
 
-	success = nm_dhcp_dhclient_save_duid (path, duid, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	_save_duid (path, duid, G_N_ELEMENTS (duid));
 
 	success = g_file_get_contents (path, &contents, NULL, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	nmtst_assert_success (success, error);
 
 	unlink (path);
-	g_assert_cmpstr (expected_contents, ==, contents);
 
-	g_free (contents);
+	g_assert_cmpstr (expected_contents, ==, contents);
 }
 
 static void
 test_write_existing_duid (void)
 {
-	const char *duid = "\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302";
+	const guint8 duid[] = { 000, 001, 000, 001, 023, 'o', 023, 'n', 000, '\"', 0372, 0214, 0326, 0302 };
 	const char *original_contents = "default-duid \"\\000\\001\\000\\001\\027X\\350X\\000#\\025\\010~\\254\";\n";
 	const char *expected_contents = "default-duid \"\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302\";\n";
 	GError *error = NULL;
-	char *contents = NULL;
+	gs_free char *contents = NULL;
 	gboolean success;
 	const char *path = "test-dhclient-write-existing-duid.leases";
 
 	success = g_file_set_contents (path, original_contents, -1, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	nmtst_assert_success (success, error);
 
 	/* Save other DUID; should be overwritten */
-	success = nm_dhcp_dhclient_save_duid (path, duid, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	_save_duid (path, duid, G_N_ELEMENTS (duid));
 
 	/* reread original contents */
 	success = g_file_get_contents (path, &contents, NULL, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	nmtst_assert_success (success, error);
 
 	unlink (path);
 	g_assert_cmpstr (expected_contents, ==, contents);
-
-	g_free (contents);
 }
 
+static const guint8 DUID_BIN[] = { 000, 001, 000, 001, 023, 'o', 023, 'n', 000, '\"', 0372, 0214, 0326, 0302 };
 #define DUID "\\000\\001\\000\\001\\023o\\023n\\000\\\"\\372\\214\\326\\302"
+
 static void
 test_write_existing_commented_duid (void)
 {
@@ -824,28 +836,22 @@ test_write_existing_commented_duid (void)
 	    "default-duid \"" DUID "\";\n"
 	    ORIG_CONTENTS;
 	GError *error = NULL;
-	char *contents = NULL;
+	gs_free char *contents = NULL;
 	gboolean success;
 	const char *path = "test-dhclient-write-existing-commented-duid.leases";
 
 	success = g_file_set_contents (path, ORIG_CONTENTS, -1, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	nmtst_assert_success (success, error);
 
 	/* Save other DUID; should be saved on top */
-	success = nm_dhcp_dhclient_save_duid (path, DUID, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	_save_duid (path, DUID_BIN, G_N_ELEMENTS (DUID_BIN));
 
 	/* reread original contents */
 	success = g_file_get_contents (path, &contents, NULL, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	nmtst_assert_success (success, error);
 
 	unlink (path);
 	g_assert_cmpstr (expected_contents, ==, contents);
-
-	g_free (contents);
 #undef ORIG_CONTENTS
 }
 
@@ -865,8 +871,7 @@ test_write_existing_multiline_duid (void)
 	success = g_file_set_contents (path, ORIG_CONTENTS, -1, &error);
 	nmtst_assert_success (success, error);
 
-	success = nm_dhcp_dhclient_save_duid (path, DUID, &error);
-	nmtst_assert_success (success, error);
+	_save_duid (path, DUID_BIN, G_N_ELEMENTS (DUID_BIN));
 
 	success = g_file_get_contents (path, &contents, NULL, &error);
 	nmtst_assert_success (success, error);
diff --git a/src/dns/nm-dns-dnsmasq.c b/src/dns/nm-dns-dnsmasq.c
index b48c6b87..b54df730 100644
--- a/src/dns/nm-dns-dnsmasq.c
+++ b/src/dns/nm-dns-dnsmasq.c
@@ -254,9 +254,16 @@ name_owner_changed (GObject    *object,
 		priv->running = TRUE;
 		send_dnsmasq_update (self);
 	} else {
-		_LOGI ("dnsmasq disappeared");
-		priv->running = FALSE;
-		g_signal_emit_by_name (self, NM_DNS_PLUGIN_FAILED);
+		if (priv->running) {
+			_LOGI ("dnsmasq disappeared");
+			priv->running = FALSE;
+			g_signal_emit_by_name (self, NM_DNS_PLUGIN_FAILED);
+		} else {
+			/* The only reason for which (!priv->running) here
+			 * is that the dnsmasq process quit. We don't care
+			 * of that here, the manager handles child restarts
+			 * by itself. */
+		}
 	}
 }
 
diff --git a/src/dns/nm-dns-manager.c b/src/dns/nm-dns-manager.c
index 6a59b41c..aebe3e12 100644
--- a/src/dns/nm-dns-manager.c
+++ b/src/dns/nm-dns-manager.c
@@ -55,7 +55,7 @@
 #include "nm-dns-systemd-resolved.h"
 #include "nm-dns-unbound.h"
 
-#define HASH_LEN 20
+#define HASH_LEN   NM_UTILS_CHECKSUM_LENGTH_SHA1
 
 #ifndef RESOLVCONF_PATH
 #define RESOLVCONF_PATH "/sbin/resolvconf"
@@ -582,53 +582,85 @@ again:
 }
 
 static char *
-create_resolv_conf (char **searches,
-                    char **nameservers,
-                    char **options)
+create_resolv_conf (const char *const*searches,
+                    const char *const*nameservers,
+                    const char *const*options)
 {
-	gs_free char *searches_str = NULL;
-	gs_free char *nameservers_str = NULL;
-	gs_free char *options_str = NULL;
-	char *tmp_str;
 	GString *str;
-	int i;
+	gsize i;
 
-	if (searches) {
-		tmp_str = g_strjoinv (" ", searches);
-		searches_str = g_strconcat ("search ", tmp_str, "\n", NULL);
-		g_free (tmp_str);
-	}
+	str = g_string_new_len (NULL, 245);
 
-	if (options) {
-		tmp_str = g_strjoinv (" ", options);
-		options_str = g_strconcat ("options ", tmp_str, "\n", NULL);
-		g_free (tmp_str);
-	}
+	g_string_append (str, "# Generated by NetworkManager\n");
 
-	if (nameservers) {
-		int num = g_strv_length (nameservers);
+	if (searches && searches[0]) {
+		gsize search_base_idx;
 
-		str = g_string_new ("");
-		for (i = 0; i < num; i++) {
-			if (i == 3) {
-				g_string_append (str, "# ");
-				g_string_append (str, "NOTE: the libc resolver may not support more than 3 nameservers.");
-				g_string_append (str, "\n# ");
-				g_string_append (str, "The nameservers listed below may not be recognized.");
-				g_string_append_c (str, '\n');
+		g_string_append (str, "search");
+		search_base_idx = str->len;
+
+		for (i = 0; searches[i]; i++) {
+			const char *s = searches[i];
+			gsize l = strlen (s);
+
+			if (   l == 0
+			    || NM_STRCHAR_ANY (s, ch, NM_IN_SET (ch, ' ', '\t', '\n'))) {
+				/* there should be no such characters in the search entry. Also,
+				 * because glibc parser would treat them as line/word separator.
+				 *
+				 * Skip the value silently. */
+				continue;
 			}
 
+			if (search_base_idx > 0) {
+				if (str->len - search_base_idx + 1 + l > 254) {
+					/* this entry crosses the 256 character boundery. Older glibc versions
+					 * would truncate the entry at this point.
+					 *
+					 * Fill the line with spaces to cross the 256 char boundary and continue
+					 * afterwards. This way, the truncation happens between two search entries. */
+					while (str->len - search_base_idx < 257)
+						g_string_append_c (str, ' ');
+					search_base_idx = 0;
+				}
+			}
+
+			g_string_append_c (str, ' ');
+			g_string_append_len (str, s, l);
+		}
+		g_string_append_c (str, '\n');
+	}
+
+	if (nameservers && nameservers[0]) {
+		for (i = 0; nameservers[i]; i++) {
+			if (i == 3) {
+				g_string_append (str, "# NOTE: the libc resolver may not support more than 3 nameservers.\n");
+				g_string_append (str, "# The nameservers listed below may not be recognized.\n");
+			}
 			g_string_append (str, "nameserver ");
 			g_string_append (str, nameservers[i]);
 			g_string_append_c (str, '\n');
 		}
-		nameservers_str = g_string_free (str, FALSE);
 	}
 
-	return g_strdup_printf ("# Generated by NetworkManager\n%s%s%s",
-	                        searches_str ?: "",
-	                        nameservers_str ?: "",
-	                        options_str ?: "");
+	if (options && options[0]) {
+		g_string_append (str, "options");
+		for (i = 0; options[i]; i++) {
+			g_string_append_c (str, ' ');
+			g_string_append (str, options[i]);
+		}
+		g_string_append_c (str, '\n');
+	}
+
+	return g_string_free (str, FALSE);
+}
+
+char *
+nmtst_dns_create_resolv_conf (const char *const*searches,
+                              const char *const*nameservers,
+                              const char *const*options)
+{
+	return create_resolv_conf (searches, nameservers, options);
 }
 
 static gboolean
@@ -654,9 +686,9 @@ write_resolv_conf_contents (FILE *f,
 
 static gboolean
 write_resolv_conf (FILE *f,
-                   char **searches,
-                   char **nameservers,
-                   char **options,
+                   const char *const*searches,
+                   const char *const*nameservers,
+                   const char *const*options,
                    GError **error)
 {
 	gs_free char *content = NULL;
@@ -718,7 +750,11 @@ dispatch_resolvconf (NMDnsManager *self,
 		return SR_ERROR;
 	}
 
-	success = write_resolv_conf (f, searches, nameservers, options, error);
+	success = write_resolv_conf (f,
+	                             NM_CAST_STRV_CC (searches),
+	                             NM_CAST_STRV_CC (nameservers),
+	                             NM_CAST_STRV_CC (options),
+	                             error);
 	err = pclose (f);
 	if (err < 0) {
 		errnosv = errno;
@@ -757,9 +793,9 @@ _read_link_cached (const char *path, gboolean *is_cached, char **cached)
 
 static SpawnResult
 update_resolv_conf (NMDnsManager *self,
-                    char **searches,
-                    char **nameservers,
-                    char **options,
+                    const char *const*searches,
+                    const char *const*nameservers,
+                    const char *const*options,
                     GError **error,
                     NMDnsManagerResolvConfManager rc_manager)
 {
@@ -958,12 +994,11 @@ update_resolv_conf (NMDnsManager *self,
 static void
 compute_hash (NMDnsManager *self, const NMGlobalDnsConfig *global, guint8 buffer[HASH_LEN])
 {
-	GChecksum *sum;
-	gsize len = HASH_LEN;
+	nm_auto_free_checksum GChecksum *sum = NULL;
 	NMDnsIPConfigData *ip_data;
 
 	sum = g_checksum_new (G_CHECKSUM_SHA1);
-	nm_assert (len == g_checksum_type_get_length (G_CHECKSUM_SHA1));
+	nm_assert (HASH_LEN == g_checksum_type_get_length (G_CHECKSUM_SHA1));
 
 	if (global)
 		nm_global_dns_config_update_checksum (global, sum);
@@ -977,8 +1012,7 @@ compute_hash (NMDnsManager *self, const NMGlobalDnsConfig *global, guint8 buffer
 			nm_ip_config_hash (ip_data->ip_config, sum, TRUE);
 	}
 
-	g_checksum_get_digest (sum, buffer, &len);
-	g_checksum_free (sum);
+	nm_utils_checksum_get_digest_len (sum, buffer, HASH_LEN);
 }
 
 static gboolean
@@ -1066,7 +1100,6 @@ _collect_resolv_conf_data (NMDnsManager *self,
                            const char **out_nis_domain)
 {
 	NMDnsManagerPrivate *priv;
-	guint i, num, len;
 	NMResolvConfData rc = {
 		.nameservers = g_ptr_array_new (),
 		.searches = g_ptr_array_new (),
@@ -1136,17 +1169,6 @@ _collect_resolv_conf_data (NMDnsManager *self,
 		}
 	}
 
-	/* Per 'man resolv.conf', the search list is limited to 6 domains
-	 * totalling 256 characters.
-	 */
-	num = MIN (rc.searches->len, 6u);
-	for (i = 0, len = 0; i < num; i++) {
-		len += strlen (rc.searches->pdata[i]) + 1; /* +1 for spaces */
-		if (len > 256)
-			break;
-	}
-	g_ptr_array_set_size (rc.searches, i);
-
 	*out_searches = _ptrarray_to_strv (rc.searches);
 	*out_options = _ptrarray_to_strv (rc.options);
 	*out_nameservers = _ptrarray_to_strv (rc.nameservers);
@@ -1449,7 +1471,12 @@ update_dns (NMDnsManager *self,
 		switch (priv->rc_manager) {
 		case NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK:
 		case NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE:
-			result = update_resolv_conf (self, searches, nameservers, options, error, priv->rc_manager);
+			result = update_resolv_conf (self,
+			                             NM_CAST_STRV_CC (searches),
+			                             NM_CAST_STRV_CC (nameservers),
+			                             NM_CAST_STRV_CC (options),
+			                             error,
+			                             priv->rc_manager);
 			resolv_conf_updated = TRUE;
 			/* If we have ended with no nameservers avoid updating again resolv.conf
 			 * on stop, as some external changes may be applied to it in the meanwhile */
@@ -1474,15 +1501,26 @@ update_dns (NMDnsManager *self,
 		if (result == SR_NOTFOUND) {
 			_LOGD ("update-dns: program not available, writing to resolv.conf");
 			g_clear_error (error);
-			result = update_resolv_conf (self, searches, nameservers, options, error, NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK);
+			result = update_resolv_conf (self,
+			                             NM_CAST_STRV_CC (searches),
+			                             NM_CAST_STRV_CC (nameservers),
+			                             NM_CAST_STRV_CC (options),
+			                             error,
+			                             NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK);
 			resolv_conf_updated = TRUE;
 		}
 	}
 
 	/* Unless we've already done it, update private resolv.conf in NMRUNDIR
 	   ignoring any errors */
-	if (!resolv_conf_updated)
-		update_resolv_conf (self, searches, nameservers, options, NULL, NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED);
+	if (!resolv_conf_updated) {
+		update_resolv_conf (self,
+		                    NM_CAST_STRV_CC (searches),
+		                    NM_CAST_STRV_CC (nameservers),
+		                    NM_CAST_STRV_CC (options),
+		                    NULL,
+		                    NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED);
+	}
 
 	/* signal that resolv.conf was changed */
 	if (update && result == SR_SUCCESS)
@@ -1542,6 +1580,7 @@ plugin_child_quit (NMDnsPlugin *plugin, int exit_status, gpointer user_data)
 	} else {
 		priv->plugin_ratelimit.num_restarts++;
 		if (priv->plugin_ratelimit.num_restarts > PLUGIN_RATELIMIT_BURST) {
+			plugin_failed (plugin, self);
 			_LOGW ("plugin %s child respawning too fast, delaying update for %u seconds",
 			        nm_dns_plugin_get_name (plugin), PLUGIN_RATELIMIT_DELAY);
 			priv->plugin_ratelimit.timer = g_timeout_add_seconds (PLUGIN_RATELIMIT_DELAY,
diff --git a/src/dns/nm-dns-manager.h b/src/dns/nm-dns-manager.h
index ed1974a5..a3e9472e 100644
--- a/src/dns/nm-dns-manager.h
+++ b/src/dns/nm-dns-manager.h
@@ -129,4 +129,10 @@ typedef enum {
 
 void nm_dns_manager_stop (NMDnsManager *self);
 
+/*****************************************************************************/
+
+char *nmtst_dns_create_resolv_conf (const char *const*searches,
+                                    const char *const*nameservers,
+                                    const char *const*options);
+
 #endif /* __NETWORKMANAGER_DNS_MANAGER_H__ */
diff --git a/src/dnsmasq/nm-dnsmasq-manager.c b/src/dnsmasq/nm-dnsmasq-manager.c
index 09889441..3fe2f489 100644
--- a/src/dnsmasq/nm-dnsmasq-manager.c
+++ b/src/dnsmasq/nm-dnsmasq-manager.c
@@ -235,6 +235,7 @@ create_dm_cmd_line (const char *iface,
 			g_string_append_c (s, ',');
 			g_string_append (s, nm_utils_inet4_ntop (nm_ip4_config_get_nameserver (ip4_config, i), tmpaddr));
 		}
+		nm_cmd_line_add_string (cmd, s->str);
 		g_string_truncate (s, 0);
 	}
 
@@ -244,6 +245,7 @@ create_dm_cmd_line (const char *iface,
 			g_string_append_c (s, ',');
 			g_string_append (s, nm_ip4_config_get_search (ip4_config, i));
 		}
+		nm_cmd_line_add_string (cmd, s->str);
 		g_string_truncate (s, 0);
 	}
 
diff --git a/src/initrd/meson.build b/src/initrd/meson.build
new file mode 100644
index 00000000..66e825d5
--- /dev/null
+++ b/src/initrd/meson.build
@@ -0,0 +1,32 @@
+sources = files(
+  'nmi-cmdline-reader.c',
+  'nmi-ibft-reader.c'
+)
+
+nm_cflags = ['-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_DAEMON']
+
+libnmi_core = static_library(
+  'nmi-core',
+  c_args: nm_cflags,
+  sources: sources,
+  include_directories: src_inc,
+  dependencies: nm_core_dep,
+)
+
+name = 'nm-initrd-generator'
+executable(
+  name,
+  name + '.c',
+  c_args: nm_cflags,
+  include_directories: src_inc,
+  dependencies: [ nm_core_dep ],
+  link_with: [libnetwork_manager_base, libnmi_core],
+  link_args: ldflags_linker_script_binary,
+  link_depends: linker_script_binary,
+  install: true,
+  install_dir: nm_libexecdir,
+)
+
+if enable_tests
+  subdir('tests')
+endif
diff --git a/src/initrd/tests/meson.build b/src/initrd/tests/meson.build
new file mode 100644
index 00000000..6b316d4e
--- /dev/null
+++ b/src/initrd/tests/meson.build
@@ -0,0 +1,23 @@
+test_units = [
+  'test-ibft-reader',
+  'test-cmdline-reader',
+]
+
+cflags = [
+  '-DTEST_INITRD_DIR="@0@"'.format(meson.current_source_dir()),
+]
+
+foreach test_unit : test_units
+  exe = executable(
+    test_unit,
+    test_unit + '.c',
+    dependencies: test_nm_dep,
+    c_args: cflags,
+    link_with: libnmi_core
+  )
+  test(
+    'initrd/' + test_unit,
+    test_script,
+    args: test_args + [exe.full_path()]
+  )
+endforeach
diff --git a/src/ndisc/nm-ndisc.h b/src/ndisc/nm-ndisc.h
index 73eef368..6c0c0264 100644
--- a/src/ndisc/nm-ndisc.h
+++ b/src/ndisc/nm-ndisc.h
@@ -23,6 +23,7 @@
 
 #include <stdlib.h>
 #include <netinet/in.h>
+#include <linux/if_addr.h>
 
 #include "nm-setting-ip6-config.h"
 #include "NetworkManagerUtils.h"
diff --git a/src/nm-config-data.c b/src/nm-config-data.c
index 8d84e74a..0259f001 100644
--- a/src/nm-config-data.c
+++ b/src/nm-config-data.c
@@ -1241,9 +1241,13 @@ _match_section_infos_lookup (const MatchSectionInfo *match_section_infos,
                              const char *match_device_type,
                              char **out_value)
 {
+	const char *match_dhcp_plugin;
+
 	if (!match_section_infos)
 		return NULL;
 
+	match_dhcp_plugin = nm_dhcp_manager_get_config (nm_dhcp_manager_get ());
+
 	for (; match_section_infos->group_name; match_section_infos++) {
 		char *value = NULL;
 		gboolean match;
@@ -1263,7 +1267,7 @@ _match_section_infos_lookup (const MatchSectionInfo *match_section_infos,
 			if (device)
 				match = nm_device_spec_match_list (device, match_section_infos->match_device.spec);
 			else if (pllink)
-				match = nm_match_spec_device_by_pllink (pllink, match_device_type, match_section_infos->match_device.spec, FALSE);
+				match = nm_match_spec_device_by_pllink (pllink, match_device_type, match_dhcp_plugin, match_section_infos->match_device.spec, FALSE);
 			else
 				match = FALSE;
 		} else
diff --git a/src/nm-config-data.h b/src/nm-config-data.h
index b52ddcc4..545d9a87 100644
--- a/src/nm-config-data.h
+++ b/src/nm-config-data.h
@@ -232,5 +232,14 @@ GKeyFile *_nm_config_data_get_keyfile (const NMConfigData *self);
 GKeyFile *_nm_config_data_get_keyfile_user (const NMConfigData *self);
 GKeyFile *_nm_config_data_get_keyfile_intern (const NMConfigData *self);
 
+/*****************************************************************************/
+
+/* nm-config-data.c requires getting the DHCP manager's configuration. That is a bit
+ * ugly, and optimally, NMConfig* is independent of NMDhcpManager. Instead of
+ * including the header, forward declare the two functions that we need. */
+struct _NMDhcpManager;
+struct _NMDhcpManager *nm_dhcp_manager_get (void);
+const char *nm_dhcp_manager_get_config (struct _NMDhcpManager *self);
+
 #endif /* NM_CONFIG_DATA_H */
 
diff --git a/src/nm-connectivity.h b/src/nm-connectivity.h
index 178f27ad..99333ced 100644
--- a/src/nm-connectivity.h
+++ b/src/nm-connectivity.h
@@ -24,6 +24,21 @@
 
 #include "nm-dbus-interface.h"
 
+/*****************************************************************************/
+
+static inline int
+nm_connectivity_state_cmp (NMConnectivityState a, NMConnectivityState b)
+{
+	if (a == NM_CONNECTIVITY_PORTAL && b == NM_CONNECTIVITY_LIMITED)
+		return 1;
+	if (b == NM_CONNECTIVITY_PORTAL && a == NM_CONNECTIVITY_LIMITED)
+		return -1;
+	NM_CMP_DIRECT (a, b);
+	return 0;
+}
+
+/*****************************************************************************/
+
 #define NM_CONNECTIVITY_ERROR     ((NMConnectivityState) -1)
 #define NM_CONNECTIVITY_FAKE      ((NMConnectivityState) -2)
 #define NM_CONNECTIVITY_CANCELLED ((NMConnectivityState) -3)
diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c
index ca8c9526..a65ac636 100644
--- a/src/nm-core-utils.c
+++ b/src/nm-core-utils.c
@@ -30,15 +30,19 @@
 #include <unistd.h>
 #include <stdlib.h>
 #include <resolv.h>
+#include <byteswap.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <sys/stat.h>
 #include <linux/if.h>
 #include <linux/if_infiniband.h>
+#include <net/if_arp.h>
 #include <net/ethernet.h>
 
 #include "nm-utils/nm-random-utils.h"
 #include "nm-utils/nm-io-utils.h"
+#include "nm-utils/unaligned.h"
+#include "nm-utils/nm-secret-utils.h"
 #include "nm-utils.h"
 #include "nm-core-internal.h"
 #include "nm-setting-connection.h"
@@ -48,6 +52,17 @@
 #include "nm-setting-wireless-security.h"
 
 G_STATIC_ASSERT (sizeof (NMUtilsTestFlags) <= sizeof (int));
+
+/* we read _nm_utils_testing without memory barrier. This is thread-safe,
+ * because the static variable is initialized to zero, and only reset
+ * once to a non-zero value (via g_atomic_int_compare_and_exchange()).
+ *
+ * Since there is only one integer that contains the data, there is no
+ * caching problem reading this (atomic int) variable without
+ * synchronization/memory-barrier. Contrary to a double-checked locking,
+ * where one needs a memory barrier to read the variable and ensure
+ * that also the related data is coherent in cache. Here there is no
+ * related data. */
 static int _nm_utils_testing = 0;
 
 gboolean
@@ -66,6 +81,7 @@ nm_utils_get_testing ()
 {
 	NMUtilsTestFlags flags;
 
+again:
 	flags = (NMUtilsTestFlags) _nm_utils_testing;
 	if (flags != NM_UTILS_TEST_NONE) {
 		/* Flags already initialized. Return them. */
@@ -78,12 +94,11 @@ nm_utils_get_testing ()
 	if (g_test_initialized ())
 		flags |= _NM_UTILS_TEST_GENERAL;
 
-	if (g_atomic_int_compare_and_exchange (&_nm_utils_testing, 0, (int) flags)) {
-		/* Done. We set it. */
-		return flags & NM_UTILS_TEST_ALL;
-	}
-	/* It changed in the meantime (??). Re-read the value. */
-	return ((NMUtilsTestFlags) _nm_utils_testing) & NM_UTILS_TEST_ALL;
+	g_atomic_int_compare_and_exchange (&_nm_utils_testing, 0, (int) flags);
+
+	/* regardless of whether we won the race of initializing _nm_utils_testing,
+	 * go back and read the value again. It must be non-zero by now. */
+	goto again;
 }
 
 void
@@ -1133,6 +1148,7 @@ nm_utils_read_link_absolute (const char *link_file, GError **error)
 #define DEVICE_TYPE_TAG "type:"
 #define DRIVER_TAG "driver:"
 #define SUBCHAN_TAG "s390-subchannels:"
+#define DHCP_PLUGIN_TAG "dhcp-plugin:"
 #define EXCEPT_TAG "except:"
 #define MATCH_TAG_CONFIG_NM_VERSION             "nm-version:"
 #define MATCH_TAG_CONFIG_NM_VERSION_MIN         "nm-version-min:"
@@ -1144,6 +1160,7 @@ typedef struct {
 	const char *device_type;
 	const char *driver;
 	const char *driver_version;
+	const char *dhcp_plugin;
 	struct {
 		const char *value;
 		gboolean is_parsed;
@@ -1280,6 +1297,34 @@ match_device_hwaddr_eval (const char *spec_str,
 		_has; \
 	})
 
+static NMMatchSpecMatchType
+_match_result (gboolean has_except,
+               gboolean has_not_except,
+               gboolean has_match,
+               gboolean has_match_except)
+{
+	if (   has_except
+	    && !has_not_except) {
+		/* a match spec that only consists of a list of except matches is treated specially. */
+		nm_assert (!has_match);
+		if (has_match_except) {
+			/* one of the "except:" matches matched. The result is an explicit
+			 * negative match. */
+			return NM_MATCH_SPEC_NEG_MATCH;
+		} else {
+			/* none of the "except:" matches matched. The result is a positive match,
+			 * despite there being no positive match. */
+			return NM_MATCH_SPEC_MATCH;
+		}
+	}
+
+	if (has_match_except)
+		return NM_MATCH_SPEC_NEG_MATCH;
+	if (has_match)
+		return NM_MATCH_SPEC_MATCH;
+	return NM_MATCH_SPEC_NO_MATCH;
+}
+
 static const char *
 match_except (const char *spec_str, gboolean *out_except)
 {
@@ -1361,6 +1406,9 @@ match_device_eval (const char *spec_str,
 	if (_MATCH_CHECK (spec_str, SUBCHAN_TAG))
 		return match_data_s390_subchannels_eval (spec_str, match_data);
 
+	if (_MATCH_CHECK (spec_str, DHCP_PLUGIN_TAG))
+		return nm_streq0 (spec_str, match_data->dhcp_plugin);
+
 	if (allow_fuzzy) {
 		if (match_device_hwaddr_eval (spec_str, match_data))
 			return TRUE;
@@ -1379,17 +1427,21 @@ nm_match_spec_device (const GSList *specs,
                       const char *driver,
                       const char *driver_version,
                       const char *hwaddr,
-                      const char *s390_subchannels)
+                      const char *s390_subchannels,
+                      const char *dhcp_plugin)
 {
 	const GSList *iter;
-	NMMatchSpecMatchType match;
+	gboolean has_match = FALSE;
+	gboolean has_match_except = FALSE;
+	gboolean has_except = FALSE;
+	gboolean has_not_except = FALSE;
 	const char *spec_str;
-	gboolean except;
 	MatchDeviceData match_data = {
 	    .interface_name = interface_name,
 	    .device_type = nm_str_not_empty (device_type),
 	    .driver = nm_str_not_empty (driver),
 	    .driver_version = nm_str_not_empty (driver_version),
+	    .dhcp_plugin = nm_str_not_empty (dhcp_plugin),
 	    .hwaddr = {
 	        .value = hwaddr,
 	    },
@@ -1403,19 +1455,9 @@ nm_match_spec_device (const GSList *specs,
 	if (!specs)
 		return NM_MATCH_SPEC_NO_MATCH;
 
-	match = NM_MATCH_SPEC_NO_MATCH;
-
-	/* pre-search for "*" */
 	for (iter = specs; iter; iter = iter->next) {
-		spec_str = iter->data;
-
-		if (spec_str && spec_str[0] == '*' && spec_str[1] == '\0') {
-			match = NM_MATCH_SPEC_MATCH;
-			break;
-		}
-	}
+		gboolean except;
 
-	for (iter = specs; iter; iter = iter->next) {
 		spec_str = iter->data;
 
 		if (!spec_str || !*spec_str)
@@ -1423,10 +1465,14 @@ nm_match_spec_device (const GSList *specs,
 
 		spec_str = match_except (spec_str, &except);
 
-		if (   !except
-		    && match == NM_MATCH_SPEC_MATCH) {
-			/* we have no "except-match" but already match. No need to evaluate
-			 * the match, we cannot match stronger. */
+		if (except)
+			has_except = TRUE;
+		else
+			has_not_except = TRUE;
+
+		if (   ( except && has_match_except)
+		    || (!except && has_match)) {
+			/* evaluating the match does not give new information. Skip it. */
 			continue;
 		}
 
@@ -1436,11 +1482,12 @@ nm_match_spec_device (const GSList *specs,
 			continue;
 
 		if (except)
-			return NM_MATCH_SPEC_NEG_MATCH;
-		match = NM_MATCH_SPEC_MATCH;
+			has_match_except = TRUE;
+		else
+			has_match = TRUE;
 	}
 
-	return match;
+	return _match_result (has_except, has_not_except, has_match, has_match_except);
 }
 
 static gboolean
@@ -1510,7 +1557,10 @@ NMMatchSpecMatchType
 nm_match_spec_config (const GSList *specs, guint cur_nm_version, const char *env)
 {
 	const GSList *iter;
-	NMMatchSpecMatchType match = NM_MATCH_SPEC_NO_MATCH;
+	gboolean has_match = FALSE;
+	gboolean has_match_except = FALSE;
+	gboolean has_except = FALSE;
+	gboolean has_not_except = FALSE;
 
 	if (!specs)
 		return NM_MATCH_SPEC_NO_MATCH;
@@ -1525,6 +1575,17 @@ nm_match_spec_config (const GSList *specs, guint cur_nm_version, const char *env
 
 		spec_str = match_except (spec_str, &except);
 
+		if (except)
+			has_except = TRUE;
+		else
+			has_not_except = TRUE;
+
+		if (   ( except && has_match_except)
+		    || (!except && has_match)) {
+			/* evaluating the match does not give new information. Skip it. */
+			continue;
+		}
+
 		if (_MATCH_CHECK (spec_str, MATCH_TAG_CONFIG_NM_VERSION))
 			v_match = match_config_eval (spec_str, MATCH_TAG_CONFIG_NM_VERSION, cur_nm_version);
 		else if (_MATCH_CHECK (spec_str, MATCH_TAG_CONFIG_NM_VERSION_MIN))
@@ -1534,15 +1595,18 @@ nm_match_spec_config (const GSList *specs, guint cur_nm_version, const char *env
 		else if (_MATCH_CHECK (spec_str, MATCH_TAG_CONFIG_ENV))
 			v_match = env && env[0] && !strcmp (spec_str, env);
 		else
+			v_match = FALSE;
+
+		if (!v_match)
 			continue;
 
-		if (v_match) {
-			if (except)
-				return NM_MATCH_SPEC_NEG_MATCH;
-			match = NM_MATCH_SPEC_MATCH;
-		}
+		if (except)
+			has_match_except = TRUE;
+		else
+			has_match = TRUE;
 	}
-	return match;
+
+	return _match_result (has_except, has_not_except, has_match, has_match_except);
 }
 
 #undef _MATCH_CHECK
@@ -2503,209 +2567,549 @@ nm_utils_is_specific_hostname (const char *name)
 
 /*****************************************************************************/
 
-gboolean
-nm_utils_machine_id_parse (const char *id_str, /*uuid_t*/ guchar *out_uuid)
-{
-	int i;
-	guint8 v0, v1;
-
-	if (!id_str)
-		return FALSE;
-
-	for (i = 0; i < 32; i++) {
-		if (!g_ascii_isxdigit (id_str[i]))
-			return FALSE;
-	}
-	if (id_str[i] != '\0')
-		return FALSE;
+typedef struct {
+	NMUuid bin;
+	char _nul_sentinel; /* just for safety, if somebody accidentally uses the binary in a string context. */
 
-	if (out_uuid) {
-		for (i = 0; i < 16; i++) {
-			v0 = g_ascii_xdigit_value (*(id_str++));
-			v1 = g_ascii_xdigit_value (*(id_str++));
-			out_uuid[i] = (v0 << 4) + v1;
-		}
+	/* depending on whether the string is packed or not (with/without hyphens),
+	 * it's 32 or 36 characters long (plus the trailing NUL).
+	 *
+	 * The difference is that boot-id is a valid RFC 4211 UUID and represented
+	 * as a 36 ascii string (with hyphens). The machine-id technically is not
+	 * a UUID, but just a 32 byte sequence of hexchars. */
+	char str[37];
+	bool is_fake;
+} UuidData;
+
+static UuidData *
+_uuid_data_init (UuidData *uuid_data,
+                 gboolean packed,
+                 gboolean is_fake,
+                 const NMUuid *uuid)
+{
+	nm_assert (uuid_data);
+	nm_assert (uuid);
+
+	uuid_data->bin = *uuid;
+	uuid_data->_nul_sentinel = '\0';
+	uuid_data->is_fake = is_fake;
+	if (packed) {
+		G_STATIC_ASSERT_EXPR (sizeof (uuid_data->str) >= (sizeof (*uuid) * 2 + 1));
+		_nm_utils_bin2hexstr_full (uuid,
+		                           sizeof (*uuid),
+		                           '\0',
+		                           FALSE,
+		                           uuid_data->str);
+	} else {
+		G_STATIC_ASSERT_EXPR (sizeof (uuid_data->str) >= 37);
+		_nm_utils_uuid_unparse (uuid, uuid_data->str);
 	}
-	return TRUE;
+	return uuid_data;
 }
 
-char *
-nm_utils_machine_id_read (void)
+/*****************************************************************************/
+
+static const UuidData *
+_machine_id_get (gboolean allow_fake)
 {
-	gs_free char *contents = NULL;
-	int i;
+	static const UuidData *volatile p_uuid_data;
+	const UuidData *d;
+
+again:
+	d = g_atomic_pointer_get (&p_uuid_data);
+	if (G_UNLIKELY (!d)) {
+		static gsize lock;
+		static UuidData uuid_data;
+		gs_free char *content = NULL;
+		gboolean is_fake = TRUE;
+		const char *fake_type = NULL;
+		NMUuid uuid;
+
+		/* Get the machine ID from /etc/machine-id; it's always in /etc no matter
+		 * where our configured SYSCONFDIR is.  Alternatively, it might be in
+		 * LOCALSTATEDIR /lib/dbus/machine-id.
+		 */
+		if (   nm_utils_file_get_contents (-1, "/etc/machine-id", 100*1024, 0, &content, NULL, NULL) >= 0
+		    || nm_utils_file_get_contents (-1, LOCALSTATEDIR"/lib/dbus/machine-id", 100*1024, 0, &content, NULL, NULL) >= 0) {
+			g_strstrip (content);
+			if (_nm_utils_hexstr2bin_full (content,
+			                               FALSE,
+			                               FALSE,
+			                               NULL,
+			                               16,
+			                               (guint8 *) &uuid,
+			                               sizeof (uuid),
+			                               NULL)) {
+				if (!nm_utils_uuid_is_null (&uuid)) {
+					/* an all-zero machine-id is not valid. */
+					is_fake = FALSE;
+				}
+			}
+		}
 
-	/* Get the machine ID from /etc/machine-id; it's always in /etc no matter
-	 * where our configured SYSCONFDIR is.  Alternatively, it might be in
-	 * LOCALSTATEDIR /lib/dbus/machine-id.
-	 */
-	if (   !g_file_get_contents ("/etc/machine-id", &contents, NULL, NULL)
-	    && !g_file_get_contents (LOCALSTATEDIR "/lib/dbus/machine-id", &contents, NULL, NULL))
-		return NULL;
+		if (is_fake) {
+			const guint8 *seed_bin;
+			const char *hash_seed;
+			gsize seed_len;
+
+			if (!allow_fake) {
+				/* we don't allow generating (and memoizing) a fake key.
+				 * Signal that no valid machine-id exists. */
+				return NULL;
+			}
 
-	contents = g_strstrip (contents);
+			if (nm_utils_host_id_get (&seed_bin, &seed_len)) {
+				/* we have no valid machine-id. Generate a fake one by hashing
+				 * the secret-key. This key is commonly persisted, so it should be
+				 * stable accross reboots (despite having a broken system without
+				 * proper machine-id). */
+				fake_type = "secret-key";
+				hash_seed = "ab085f06-b629-46d1-a553-84eeba5683b6";
+			} else {
+				/* the secret-key is not valid/persistent either. That happens when we fail
+				 * to read/write the secret-key to disk. Fallback to boot-id. The boot-id
+				 * itself may be fake and randomly generated ad-hoc, but that is as best
+				 * as it gets.  */
+				seed_bin = (const guint8 *) nm_utils_boot_id_bin ();
+				seed_len = sizeof (NMUuid);
+				fake_type = "boot-id";
+				hash_seed = "7ff0c8f5-5399-4901-ab63-61bf594abe8b";
+			}
 
-	for (i = 0; i < 32; i++) {
-		if (!g_ascii_isxdigit (contents[i]))
-			return NULL;
-		if (contents[i] >= 'A' && contents[i] <= 'F') {
-			/* canonicalize to lower-case */
-			contents[i] = 'a' + (contents[i] - 'A');
+			/* the fake machine-id is based on secret-key/boot-id, but we hash it
+			 * again, so that they are not literally the same. */
+			nm_utils_uuid_generate_from_string_bin (&uuid,
+			                                        (const char *) seed_bin,
+			                                        seed_len,
+			                                        NM_UTILS_UUID_TYPE_VERSION5,
+			                                        (gpointer) hash_seed);
 		}
+
+		if (!g_once_init_enter (&lock))
+			goto again;
+
+		d = _uuid_data_init (&uuid_data, TRUE, is_fake, &uuid);
+		g_atomic_pointer_set (&p_uuid_data, d);
+		g_once_init_leave (&lock, 1);
+
+		if (is_fake) {
+			nm_log_err (LOGD_CORE,
+			            "/etc/machine-id: no valid machine-id. Use fake one based on %s: %s",
+			            fake_type,
+			            d->str);
+		} else
+			nm_log_dbg (LOGD_CORE, "/etc/machine-id: %s", d->str);
 	}
-	if (contents[i] != '\0')
-		return NULL;
 
-	return g_steal_pointer (&contents);
+	return d;
+}
+
+const char *
+nm_utils_machine_id_str (void)
+{
+	return _machine_id_get (TRUE)->str;
+}
+
+const NMUuid *
+nm_utils_machine_id_bin (void)
+{
+	return &_machine_id_get (TRUE)->bin;
+}
+
+gboolean
+nm_utils_machine_id_is_fake (void)
+{
+	return _machine_id_get (TRUE)->is_fake;
 }
 
 /*****************************************************************************/
 
+/* prefix for version2 secret key. The secret key is hashed with /etc/machine-id. */
+#define SECRET_KEY_V2_PREFIX "nm-v2:"
+#define SECRET_KEY_FILE      NMSTATEDIR"/secret_key"
+
 static gboolean
-_secret_key_read (guint8 **out_secret_key,
-                  gsize *out_key_len)
+_host_id_read_timestamp (gboolean use_secret_key_file,
+                         const guint8 *host_id,
+                         gsize host_id_len,
+                         gint64 *out_timestamp_ns)
 {
-	guint8 *secret_key;
-	gboolean success = TRUE;
-	gsize key_len;
-	gs_free_error GError *error = NULL;
+	struct stat st;
+	gint64 now;
+	guint64 v;
+
+	if (   use_secret_key_file
+	    && stat (SECRET_KEY_FILE, &st) == 0) {
+		/* don't check for overflow or timestamps in the future. We get whatever
+		 * (bogus) date is on the file. */
+		*out_timestamp_ns = (st.st_mtim.tv_sec * NM_UTILS_NS_PER_SECOND) + st.st_mtim.tv_nsec;
+		return TRUE;
+	}
 
-	/* Let's try to load a saved secret key first. */
-	if (g_file_get_contents (NMSTATEDIR "/secret_key", (char **) &secret_key, &key_len, &error)) {
-		if (key_len >= 16)
-			goto out;
+	/* generate a fake timestamp based on the host-id.
+	 *
+	 * This really should never happen under normal circumstances. We already
+	 * are in a code path, where the system has a problem (unable to get good randomness
+	 * and/or can't access the secret_key). In such a scenario, a fake timestamp is the
+	 * least of our problems.
+	 *
+	 * At least, generate something sensible so we don't have to worry about the
+	 * timestamp. It is wrong to worry about using a fake timestamp (which is tied to
+	 * the secret_key) if we are unable to access the secret_key file in the first place.
+	 *
+	 * Pick a random timestamp from the past two years. Yes, this timestamp
+	 * is not stable accross restarts, but apparently neither is the host-id
+	 * nor the secret_key itself. */
 
-		/* the secret key is borked. Log a warning, but proceed below to generate
-		 * a new one. */
-		nm_log_warn (LOGD_CORE, "secret-key: too short secret key in \"%s\" (generate new key)", NMSTATEDIR "/secret_key");
-		nm_clear_g_free (&secret_key);
-	} else {
-		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
+#define EPOCH_TWO_YEARS  (G_GINT64_CONSTANT (2 * 365 * 24 * 3600) * NM_UTILS_NS_PER_SECOND)
+
+	v = nm_hash_siphash42 (1156657133u, host_id, host_id_len);
+
+	now = time (NULL);
+	*out_timestamp_ns = NM_MAX ((gint64) 1,
+	                            (now * NM_UTILS_NS_PER_SECOND) - ((gint64) (v % ((guint64) (EPOCH_TWO_YEARS)))));
+	return FALSE;
+}
+
+static const guint8 *
+_host_id_hash_v2 (const guint8 *seed_arr,
+                  gsize seed_len,
+                  guint8 *out_digest /* 32 bytes (NM_UTILS_CHECKSUM_LENGTH_SHA256) */)
+{
+	nm_auto_free_checksum GChecksum *sum = g_checksum_new (G_CHECKSUM_SHA256);
+	const UuidData *machine_id_data;
+	char slen[100];
+
+	/*
+	    (stat -c '%s' /var/lib/NetworkManager/secret_key;
+	     echo -n ' ';
+	     cat /var/lib/NetworkManager/secret_key;
+	     cat /etc/machine-id | tr -d '\n' | sed -n 's/[a-f0-9-]/\0/pg') | sha256sum
+	*/
+
+	nm_sprintf_buf (slen, "%"G_GSIZE_FORMAT" ", seed_len);
+	g_checksum_update (sum, (const guchar *) slen, strlen (slen));
+
+	g_checksum_update (sum, (const guchar *) seed_arr, seed_len);
+
+	machine_id_data = _machine_id_get (FALSE);
+	if (   machine_id_data
+	    && !machine_id_data->is_fake)
+		g_checksum_update (sum, (const guchar *) machine_id_data->str, strlen (machine_id_data->str));
+
+	nm_utils_checksum_get_digest_len (sum, out_digest, NM_UTILS_CHECKSUM_LENGTH_SHA256);
+	return out_digest;
+}
+
+static gboolean
+_host_id_read (guint8 **out_host_id,
+               gsize *out_host_id_len)
+{
+#define SECRET_KEY_LEN 32u
+	guint8 sha256_digest[NM_UTILS_CHECKSUM_LENGTH_SHA256];
+	nm_auto_clear_secret_ptr NMSecretPtr file_content = { 0 };
+	const guint8 *secret_arr;
+	gsize secret_len;
+	GError *error = NULL;
+	gboolean success;
+
+	if (nm_utils_file_get_contents (-1,
+	                                SECRET_KEY_FILE,
+	                                10*1024,
+	                                NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET,
+	                                (char **) &file_content.str,
+	                                &file_content.len,
+	                                &error) < 0) {
+		if (!nm_utils_error_is_notfound (error)) {
 			nm_log_warn (LOGD_CORE, "secret-key: failure reading secret key in \"%s\": %s (generate new key)",
-			             NMSTATEDIR "/secret_key", error->message);
+			             SECRET_KEY_FILE, error->message);
 		}
 		g_clear_error (&error);
-	}
-
-	/* RFC7217 mandates the key SHOULD be at least 128 bits.
-	 * Let's use twice as much. */
-	key_len = 32;
-	secret_key = g_malloc (key_len + 1);
-
-	/* the secret-key is binary. Still, ensure that it's NULL terminated, just like
-	 * g_file_set_contents() does. */
-	secret_key[32] = '\0';
+	} else if (   file_content.len >= NM_STRLEN (SECRET_KEY_V2_PREFIX) + SECRET_KEY_LEN
+	           && memcmp (file_content.bin, SECRET_KEY_V2_PREFIX, NM_STRLEN (SECRET_KEY_V2_PREFIX)) == 0) {
+		/* for this type of secret key, we require a prefix followed at least SECRET_KEY_LEN (32) bytes. We
+		 * (also) do that, because older versions of NetworkManager wrote exactly 32 bytes without
+		 * prefix, so we won't wrongly interpret such legacy keys as v2 (if they accidentally have
+		 * a SECRET_KEY_V2_PREFIX prefix, they'll still have the wrong size).
+		 *
+		 * Note that below we generate the random seed in base64 encoding. But that is only done
+		 * to write an ASCII file. There is no base64 decoding and the ASCII is hashed as-is.
+		 * We would accept any binary data just as well (provided a suitable prefix and at least
+		 * 32 bytes).
+		 *
+		 * Note that when hashing the v2 content, we also hash the prefix. There is no strong reason,
+		 * except that it seems simpler not to distinguish between the v2 prefix and the content.
+		 * It's all just part of the seed. */
 
-	if (!nm_utils_random_bytes (secret_key, key_len)) {
-		nm_log_warn (LOGD_CORE, "secret-key: failure to generate good random data for secret-key (use non-persistent key)");
-		success = FALSE;
+		secret_arr = _host_id_hash_v2 (file_content.bin, file_content.len, sha256_digest);
+		secret_len = NM_UTILS_CHECKSUM_LENGTH_SHA256;
+		success = TRUE;
 		goto out;
+	} else if (file_content.len >= 16) {
+		secret_arr = file_content.bin;
+		secret_len = file_content.len;
+		success = TRUE;
+		goto out;
+	} else {
+		/* the secret key is borked. Log a warning, but proceed below to generate
+		 * a new one. */
+		nm_log_warn (LOGD_CORE, "secret-key: too short secret key in \"%s\" (generate new key)", SECRET_KEY_FILE);
 	}
 
-	if (!nm_utils_file_set_contents (NMSTATEDIR "/secret_key", (char *) secret_key, key_len, 0077, &error)) {
-		nm_log_warn (LOGD_CORE, "secret-key: failure to persist secret key in \"%s\" (%s) (use non-persistent key)",
-		             NMSTATEDIR "/secret_key", error->message);
-		success = FALSE;
-		goto out;
+	/* generate and persist new key */
+	{
+#define SECRET_KEY_LEN_BASE64 ((((SECRET_KEY_LEN / 3) + 1) * 4) + 4)
+		guint8 rnd_buf[SECRET_KEY_LEN];
+		guint8 new_content[NM_STRLEN (SECRET_KEY_V2_PREFIX) + SECRET_KEY_LEN_BASE64];
+		int base64_state = 0;
+		int base64_save = 0;
+		gsize len;
+
+		success = nm_utils_random_bytes (rnd_buf, sizeof (rnd_buf));
+
+		/* Our key is really binary data. But since we anyway generate a random seed
+		 * (with 32 random bytes), don't write it in binary, but instead create
+		 * an pure ASCII (base64) representation. Note that the ASCII will still be taken
+		 * as-is (no base64 decoding is done). The sole purpose is to write a ASCII file
+		 * instead of a binary. The content is gibberish either way. */
+		memcpy (new_content, SECRET_KEY_V2_PREFIX, NM_STRLEN (SECRET_KEY_V2_PREFIX));
+		len = NM_STRLEN (SECRET_KEY_V2_PREFIX);
+		len += g_base64_encode_step (rnd_buf,
+		                             sizeof (rnd_buf),
+		                             FALSE,
+		                             (char *) &new_content[len],
+		                             &base64_state,
+		                             &base64_save);
+		len += g_base64_encode_close (FALSE,
+		                              (char *) &new_content[len],
+		                              &base64_state,
+		                              &base64_save);
+		nm_assert (len <= sizeof (new_content));
+
+		secret_arr = _host_id_hash_v2 (new_content, len, sha256_digest);
+		secret_len = NM_UTILS_CHECKSUM_LENGTH_SHA256;
+
+		if (!success)
+			nm_log_warn (LOGD_CORE, "secret-key: failure to generate good random data for secret-key (use non-persistent key)");
+		else if (nm_utils_get_testing ()) {
+			/* for test code, we don't write the generated secret-key to disk. */
+		} else if (!nm_utils_file_set_contents (SECRET_KEY_FILE,
+		                                        (const char *) new_content,
+		                                        len,
+		                                        0077,
+		                                        &error)) {
+			nm_log_warn (LOGD_CORE, "secret-key: failure to persist secret key in \"%s\" (%s) (use non-persistent key)",
+			             SECRET_KEY_FILE, error->message);
+			g_clear_error (&error);
+			success = FALSE;
+		} else
+			nm_log_dbg (LOGD_CORE, "secret-key: persist new secret key to \"%s\"", SECRET_KEY_FILE);
+
+		nm_explicit_bzero (rnd_buf, sizeof (rnd_buf));
+		nm_explicit_bzero (new_content, sizeof (new_content));
 	}
 
 out:
-	/* regardless of success or failue, we always return a secret-key. The
-	 * caller may choose to ignore the error and proceed. */
-	*out_key_len = key_len;
-	*out_secret_key = secret_key;
+	*out_host_id_len = secret_len;
+	*out_host_id = nm_memdup (secret_arr, secret_len);
 	return success;
 }
 
 typedef struct {
-	const guint8 *secret_key;
-	gsize key_len;
+	guint8 *host_id;
+	gsize host_id_len;
+	gint64 timestamp_ns;
 	bool is_good:1;
-} SecretKeyData;
+	bool timestamp_is_good:1;
+} HostIdData;
 
-gboolean
-nm_utils_secret_key_get (const guint8 **out_secret_key,
-                         gsize *out_key_len)
+static const HostIdData *
+_host_id_get (void)
 {
-	static volatile const SecretKeyData *secret_key_static;
-	const SecretKeyData *secret_key;
+	static const HostIdData *volatile host_id_static;
+	const HostIdData *host_id;
 
-	secret_key = g_atomic_pointer_get (&secret_key_static);
-	if (G_UNLIKELY (!secret_key)) {
+again:
+	host_id = g_atomic_pointer_get (&host_id_static);
+	if (G_UNLIKELY (!host_id)) {
+		static HostIdData host_id_data;
 		static gsize init_value = 0;
-		static SecretKeyData secret_key_data;
-		gboolean tmp_success;
-		gs_free guint8 *tmp_secret_key = NULL;
-		gsize tmp_key_len;
-
-		tmp_success = _secret_key_read (&tmp_secret_key, &tmp_key_len);
-		if (g_once_init_enter (&init_value)) {
-			secret_key_data.secret_key = tmp_secret_key;
-			secret_key_data.key_len = tmp_key_len;
-			secret_key_data.is_good = tmp_success;
-
-			if (g_atomic_pointer_compare_and_exchange (&secret_key_static, NULL, &secret_key_data)) {
-				g_steal_pointer (&tmp_secret_key);
-				secret_key = &secret_key_data;
-			}
 
-			g_once_init_leave (&init_value, 1);
-		}
-		if (!secret_key)
-			secret_key = g_atomic_pointer_get (&secret_key_static);
+		if (!g_once_init_enter (&init_value))
+			goto again;
+
+		host_id_data.is_good = _host_id_read (&host_id_data.host_id,
+		                                      &host_id_data.host_id_len);
+
+		host_id_data.timestamp_is_good = _host_id_read_timestamp (host_id_data.is_good,
+		                                                          host_id_data.host_id,
+		                                                          host_id_data.host_id_len,
+		                                                          &host_id_data.timestamp_ns);
+		if (   !host_id_data.timestamp_is_good
+		    && host_id_data.is_good)
+			nm_log_warn (LOGD_CORE, "secret-key: failure reading host timestamp (use fake one)");
+
+		host_id = &host_id_data;
+		g_atomic_pointer_set (&host_id_static, host_id);
+		g_once_init_leave (&init_value, 1);
 	}
 
-	*out_secret_key = secret_key->secret_key;
-	*out_key_len = secret_key->key_len;
-	return secret_key->is_good;
+	return host_id;
 }
 
-gint64
-nm_utils_secret_key_get_timestamp (void)
+/**
+ * nm_utils_host_id_get:
+ * @out_host_id: (out) (transfer none): the binary host key
+ * @out_host_id_len: the length of the host key.
+ *
+ * This returns a per-host key that depends on /var/lib/NetworkManage/secret_key
+ * and (depending on the version) on /etc/machine-id. If /var/lib/NetworkManage/secret_key
+ * does not exist, it will be generated and persisted for next boot.
+ *
+ * Returns: %TRUE, if the host key is "good". Note that this function
+ *   will always succeed to return a host-key, and that this key
+ *   won't change during the run of the program (no matter what).
+ *   A %FALSE return possibly means, that the secret_key is not persisted
+ *   to disk, and/or that it was generated with bad randomness.
+ */
+gboolean
+nm_utils_host_id_get (const guint8 **out_host_id,
+                      gsize *out_host_id_len)
 {
-	struct stat stat_buf;
-	const guint8 *key;
-	gsize key_len;
+	const HostIdData *host_id;
 
-	if (!nm_utils_secret_key_get (&key, &key_len))
-		return 0;
-
-	if (stat (NMSTATEDIR "/secret_key", &stat_buf) != 0)
-		return 0;
+	host_id = _host_id_get ();
+	*out_host_id = host_id->host_id;
+	*out_host_id_len = host_id->host_id_len;
+	return host_id->is_good;
+}
 
-	return stat_buf.st_mtim.tv_sec;
+gint64
+nm_utils_host_id_get_timestamp_ns (void)
+{
+	return _host_id_get ()->timestamp_ns;
 }
 
 /*****************************************************************************/
 
-const char *
-nm_utils_get_boot_id (void)
+static const UuidData *
+_boot_id_get (void)
 {
-	static const char *boot_id;
-
-	if (G_UNLIKELY (!boot_id)) {
+	static const UuidData *volatile p_boot_id;
+	const UuidData *d;
+
+again:
+	d = g_atomic_pointer_get (&p_boot_id);
+	if (G_UNLIKELY (!d)) {
+		static gsize lock;
+		static UuidData boot_id;
 		gs_free char *contents = NULL;
+		NMUuid uuid;
+		gboolean is_fake = FALSE;
 
 		nm_utils_file_get_contents (-1, "/proc/sys/kernel/random/boot_id", 0,
 		                            NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
 		                            &contents, NULL, NULL);
-		if (contents) {
-			g_strstrip (contents);
-			if (contents[0]) {
-				/* clone @contents because we keep @boot_id until the program
-				 * ends.
-				 * nm_utils_file_get_contents() likely allocated a larger
-				 * buffer chunk initially and (although using realloc to shrink
-				 * the buffer) it might not be best to keep this memory
-				 * around. */
-				boot_id = g_strdup (contents);
-			}
+		if (   !contents
+		    || !_nm_utils_uuid_parse (nm_strstrip (contents), &uuid)) {
+			/* generate a random UUID instead. */
+			is_fake = TRUE;
+			_nm_utils_uuid_generate_random (&uuid);
 		}
-		if (!boot_id)
-			boot_id = nm_utils_uuid_generate ();
+
+		if (!g_once_init_enter (&lock))
+			goto again;
+
+		d = _uuid_data_init (&boot_id, FALSE, is_fake, &uuid);
+		g_atomic_pointer_set (&p_boot_id, d);
+		g_once_init_leave (&lock, 1);
 	}
 
-	return boot_id;
+	return d;
+}
+
+const char *
+nm_utils_boot_id_str (void)
+{
+	return _boot_id_get ()->str;
+}
+
+const NMUuid *
+nm_utils_boot_id_bin (void)
+{
+	return &_boot_id_get ()->bin;
+}
+
+/*****************************************************************************/
+
+/**
+ * nm_utils_arp_type_detect_from_hwaddrlen:
+ * @hwaddr_len: the length of the hardware address in bytes.
+ *
+ * Detects the arp-type based on the length of the MAC address.
+ * On success, this returns a (positive) value in uint16_t range,
+ * like ARPHRD_ETHER or ARPHRD_INFINIBAND.
+ *
+ * On failure, returns a negative error code.
+ *
+ * Returns: the arp-type or negative value on error. */
+int
+nm_utils_arp_type_detect_from_hwaddrlen (gsize hwaddr_len)
+{
+	switch (hwaddr_len) {
+	case ETH_ALEN:
+		return ARPHRD_ETHER;
+	case INFINIBAND_ALEN:
+		return ARPHRD_INFINIBAND;
+	default:
+		/* Note: if you ever support anything but ethernet and infiniband,
+		 * make sure to look at all callers. They assert that it's one of
+		 * these two. */
+		return -EINVAL;
+	}
+}
+
+gboolean
+nm_utils_arp_type_validate_hwaddr (int arp_type,
+                                   const guint8 *hwaddr,
+                                   gsize hwaddr_len)
+{
+
+	if (!hwaddr)
+		return FALSE;
+
+	if (arp_type == ARPHRD_ETHER) {
+		G_STATIC_ASSERT (ARPHRD_ETHER >= 0 && ARPHRD_ETHER <= 0xFF);
+		if (hwaddr_len != ETH_ALEN)
+			return FALSE;
+	} else if (arp_type == ARPHRD_INFINIBAND) {
+		G_STATIC_ASSERT (ARPHRD_INFINIBAND >= 0 && ARPHRD_INFINIBAND <= 0xFF);
+		if (hwaddr_len != INFINIBAND_ALEN)
+			return FALSE;
+	} else
+		return FALSE;
+
+	nm_assert (arp_type == nm_utils_arp_type_detect_from_hwaddrlen (hwaddr_len));
+	return TRUE;
+}
+
+gboolean
+nm_utils_arp_type_get_hwaddr_relevant_part (int arp_type,
+                                            const guint8 **hwaddr,
+                                            gsize *hwaddr_len)
+{
+	g_return_val_if_fail (   hwaddr
+	                      && hwaddr_len
+	                      && nm_utils_arp_type_validate_hwaddr (arp_type, *hwaddr, *hwaddr_len),
+	                      FALSE);
+
+	/* for infiniband, we only consider the last 8 bytes. */
+	if (arp_type == ARPHRD_INFINIBAND) {
+		*hwaddr += (INFINIBAND_ALEN - 8);
+		*hwaddr_len = 8;
+	}
+
+	return TRUE;
 }
 
 /*****************************************************************************/
@@ -2920,9 +3324,8 @@ nm_utils_stable_id_random (void)
 char *
 nm_utils_stable_id_generated_complete (const char *stable_id_generated)
 {
-	guint8 buf[20];
-	GChecksum *sum;
-	gsize buf_size;
+	nm_auto_free_checksum GChecksum *sum = NULL;
+	guint8 buf[NM_UTILS_CHECKSUM_LENGTH_SHA1];
 	char *base64;
 
 	/* for NM_UTILS_STABLE_TYPE_GENERATED we genererate a possibly long string
@@ -2933,15 +3336,8 @@ nm_utils_stable_id_generated_complete (const char *stable_id_generated)
 	g_return_val_if_fail (stable_id_generated, NULL);
 
 	sum = g_checksum_new (G_CHECKSUM_SHA1);
-	nm_assert (sum);
-
 	g_checksum_update (sum, (guchar *) stable_id_generated, strlen (stable_id_generated));
-
-	buf_size = sizeof (buf);
-	g_checksum_get_digest (sum, buf, &buf_size);
-	nm_assert (buf_size == sizeof (buf));
-
-	g_checksum_free (sum);
+	nm_utils_checksum_get_digest (sum, buf);
 
 	/* we don't care to use the sha1 sum in common hex representation.
 	 * Use instead base64, it's 27 chars (stripping the padding) vs.
@@ -2967,6 +3363,7 @@ _stable_id_append (GString *str,
 NMUtilsStableType
 nm_utils_stable_id_parse (const char *stable_id,
                           const char *deviceid,
+                          const char *hwaddr,
                           const char *bootid,
                           const char *uuid,
                           char **out_generated)
@@ -3042,9 +3439,11 @@ nm_utils_stable_id_parse (const char *stable_id,
 		if (CHECK_PREFIX ("${CONNECTION}"))
 			_stable_id_append (str, uuid);
 		else if (CHECK_PREFIX ("${BOOT}"))
-			_stable_id_append (str, bootid ?: nm_utils_get_boot_id ());
+			_stable_id_append (str, bootid);
 		else if (CHECK_PREFIX ("${DEVICE}"))
 			_stable_id_append (str, deviceid);
+		else if (CHECK_PREFIX ("${MAC}"))
+			_stable_id_append (str, hwaddr);
 		else if (g_str_has_prefix (&stable_id[i], "${RANDOM}")) {
 			/* RANDOM makes not so much sense for cloned-mac-address
 			 * as the result is simmilar to specyifing "cloned-mac-address=random".
@@ -3121,28 +3520,20 @@ _set_stable_privacy (NMUtilsStableType stable_type,
                      const char *ifname,
                      const char *network_id,
                      guint32 dad_counter,
-                     const guint8 *secret_key,
-                     gsize key_len,
+                     const guint8 *host_id,
+                     gsize host_id_len,
                      GError **error)
 {
-	GChecksum *sum;
-	guint8 digest[32];
+	nm_auto_free_checksum GChecksum *sum = NULL;
+	guint8 digest[NM_UTILS_CHECKSUM_LENGTH_SHA256];
 	guint32 tmp[2];
-	gsize len = sizeof (digest);
 
-	nm_assert (key_len);
+	nm_assert (host_id_len);
 	nm_assert (network_id);
 
-	/* Documentation suggests that this can fail.
-	 * Maybe in case of a missing algorithm in crypto library? */
 	sum = g_checksum_new (G_CHECKSUM_SHA256);
-	if (!sum) {
-		g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		                     "Can't create a SHA256 hash");
-		return FALSE;
-	}
 
-	key_len = MIN (key_len, G_MAXUINT32);
+	host_id_len = MIN (host_id_len, G_MAXUINT32);
 
 	if (stable_type != NM_UTILS_STABLE_TYPE_UUID) {
 		guint8 stable_type_uint8;
@@ -3155,7 +3546,7 @@ _set_stable_privacy (NMUtilsStableType stable_type,
 		 *
 		 * That is no real problem and it is still impossible to
 		 * force a collision here, because of how the remaining
-		 * fields are hashed. That is, as we also hash @key_len
+		 * fields are hashed. That is, as we also hash @host_id_len
 		 * and the terminating '\0' of @network_id, it is unambigiously
 		 * possible to revert the process and deduce the @stable_type.
 		 */
@@ -3166,27 +3557,20 @@ _set_stable_privacy (NMUtilsStableType stable_type,
 	g_checksum_update (sum, (const guchar *) ifname, strlen (ifname) + 1);
 	g_checksum_update (sum, (const guchar *) network_id, strlen (network_id) + 1);
 	tmp[0] = htonl (dad_counter);
-	tmp[1] = htonl (key_len);
+	tmp[1] = htonl (host_id_len);
 	g_checksum_update (sum, (const guchar *) tmp, sizeof (tmp));
-	g_checksum_update (sum, (const guchar *) secret_key, key_len);
-
-	g_checksum_get_digest (sum, digest, &len);
-
-	nm_assert (len == sizeof (digest));
+	g_checksum_update (sum, (const guchar *) host_id, host_id_len);
+	nm_utils_checksum_get_digest (sum, digest);
 
 	while (_is_reserved_ipv6_iid (digest)) {
 		g_checksum_reset (sum);
 		tmp[0] = htonl (++dad_counter);
-		g_checksum_update (sum, digest, len);
+		g_checksum_update (sum, digest, sizeof (digest));
 		g_checksum_update (sum, (const guchar *) &tmp[0], sizeof (tmp[0]));
-		g_checksum_get_digest (sum, digest, &len);
-		nm_assert (len == sizeof (digest));
+		nm_utils_checksum_get_digest (sum, digest);
 	}
 
-	g_checksum_free (sum);
-
 	memcpy (addr->s6_addr + 8, &digest[0], 8);
-
 	return TRUE;
 }
 
@@ -3196,11 +3580,11 @@ nm_utils_ipv6_addr_set_stable_privacy_impl (NMUtilsStableType stable_type,
                                             const char *ifname,
                                             const char *network_id,
                                             guint32 dad_counter,
-                                            guint8 *secret_key,
-                                            gsize key_len,
+                                            guint8 *host_id,
+                                            gsize host_id_len,
                                             GError **error)
 {
-	return _set_stable_privacy (stable_type, addr, ifname, network_id, dad_counter, secret_key, key_len, error);
+	return _set_stable_privacy (stable_type, addr, ifname, network_id, dad_counter, host_id, host_id_len, error);
 }
 
 #define RFC7217_IDGEN_RETRIES 3
@@ -3220,8 +3604,8 @@ nm_utils_ipv6_addr_set_stable_privacy (NMUtilsStableType stable_type,
                                        guint32 dad_counter,
                                        GError **error)
 {
-	const guint8 *secret_key;
-	gsize key_len;
+	const guint8 *host_id;
+	gsize host_id_len;
 
 	g_return_val_if_fail (network_id, FALSE);
 
@@ -3231,10 +3615,10 @@ nm_utils_ipv6_addr_set_stable_privacy (NMUtilsStableType stable_type,
 		return FALSE;
 	}
 
-	nm_utils_secret_key_get (&secret_key, &key_len);
+	nm_utils_host_id_get (&host_id, &host_id_len);
 
 	return _set_stable_privacy (stable_type, addr, ifname, network_id, dad_counter,
-	                            secret_key, key_len, error);
+	                            host_id, host_id_len, error);
 }
 
 /*****************************************************************************/
@@ -3306,42 +3690,36 @@ nm_utils_hw_addr_gen_random_eth (const char *current_mac_address,
 static char *
 _hw_addr_gen_stable_eth (NMUtilsStableType stable_type,
                          const char *stable_id,
-                         const guint8 *secret_key,
-                         gsize key_len,
+                         const guint8 *host_id,
+                         gsize host_id_len,
                          const char *ifname,
                          const char *current_mac_address,
                          const char *generate_mac_address_mask)
 {
-	GChecksum *sum;
+	nm_auto_free_checksum GChecksum *sum = NULL;
 	guint32 tmp;
-	guint8 digest[32];
-	gsize len = sizeof (digest);
+	guint8 digest[NM_UTILS_CHECKSUM_LENGTH_SHA256];
 	struct ether_addr bin_addr;
 	guint8 stable_type_uint8;
 
 	nm_assert (stable_id);
-	nm_assert (secret_key);
+	nm_assert (host_id);
 
 	sum = g_checksum_new (G_CHECKSUM_SHA256);
-	if (!sum)
-		return NULL;
 
-	key_len = MIN (key_len, G_MAXUINT32);
+	host_id_len = MIN (host_id_len, G_MAXUINT32);
 
 	nm_assert (stable_type < (NMUtilsStableType) 255);
 	stable_type_uint8 = stable_type;
 	g_checksum_update (sum, (const guchar *) &stable_type_uint8, sizeof (stable_type_uint8));
 
-	tmp = htonl ((guint32) key_len);
+	tmp = htonl ((guint32) host_id_len);
 	g_checksum_update (sum, (const guchar *) &tmp, sizeof (tmp));
-	g_checksum_update (sum, (const guchar *) secret_key, key_len);
+	g_checksum_update (sum, (const guchar *) host_id, host_id_len);
 	g_checksum_update (sum, (const guchar *) (ifname ?: ""), ifname ? (strlen (ifname) + 1) : 1);
 	g_checksum_update (sum, (const guchar *) stable_id, strlen (stable_id) + 1);
 
-	g_checksum_get_digest (sum, digest, &len);
-	g_checksum_free (sum);
-
-	g_return_val_if_fail (len == 32, NULL);
+	nm_utils_checksum_get_digest (sum, digest);
 
 	memcpy (&bin_addr, digest, ETH_ALEN);
 	_hw_addr_eth_complete (&bin_addr, current_mac_address, generate_mac_address_mask);
@@ -3351,13 +3729,13 @@ _hw_addr_gen_stable_eth (NMUtilsStableType stable_type,
 char *
 nm_utils_hw_addr_gen_stable_eth_impl (NMUtilsStableType stable_type,
                                       const char *stable_id,
-                                      const guint8 *secret_key,
-                                      gsize key_len,
+                                      const guint8 *host_id,
+                                      gsize host_id_len,
                                       const char *ifname,
                                       const char *current_mac_address,
                                       const char *generate_mac_address_mask)
 {
-	return _hw_addr_gen_stable_eth (stable_type, stable_id, secret_key, key_len, ifname, current_mac_address, generate_mac_address_mask);
+	return _hw_addr_gen_stable_eth (stable_type, stable_id, host_id, host_id_len, ifname, current_mac_address, generate_mac_address_mask);
 }
 
 char *
@@ -3367,17 +3745,17 @@ nm_utils_hw_addr_gen_stable_eth (NMUtilsStableType stable_type,
                                  const char *current_mac_address,
                                  const char *generate_mac_address_mask)
 {
-	const guint8 *secret_key;
-	gsize key_len;
+	const guint8 *host_id;
+	gsize host_id_len;
 
 	g_return_val_if_fail (stable_id, NULL);
 
-	nm_utils_secret_key_get (&secret_key, &key_len);
+	nm_utils_host_id_get (&host_id, &host_id_len);
 
 	return _hw_addr_gen_stable_eth (stable_type,
 	                                stable_id,
-	                                secret_key,
-	                                key_len,
+	                                host_id,
+	                                host_id_len,
 	                                ifname,
 	                                current_mac_address,
 	                                generate_mac_address_mask);
@@ -3385,6 +3763,118 @@ nm_utils_hw_addr_gen_stable_eth (NMUtilsStableType stable_type,
 
 /*****************************************************************************/
 
+GBytes *
+nm_utils_dhcp_client_id_mac (int arp_type,
+                             const guint8 *hwaddr,
+                             gsize hwaddr_len)
+{
+	guint8 *client_id_buf;
+	const guint8 hwaddr_type = arp_type;
+
+	if (!nm_utils_arp_type_get_hwaddr_relevant_part (arp_type, &hwaddr, &hwaddr_len))
+		g_return_val_if_reached (NULL);
+
+	client_id_buf = g_malloc (hwaddr_len + 1);
+	client_id_buf[0] = hwaddr_type;
+	memcpy (&client_id_buf[1], hwaddr, hwaddr_len);
+	return g_bytes_new_take (client_id_buf, hwaddr_len + 1);
+}
+
+/**
+ * nm_utils_dhcp_client_id_systemd_node_specific_full:
+ * @legacy_unstable_byteorder: historically, the code would generate a iaid
+ *   dependent on host endianness. This is undesirable, if backward compatibility
+ *   are not a concern, generate stable endianness.
+ * @interface_id: a binary identifer that is hashed into the DUID.
+ *   Comonly this is the interface-name, but it may be the MAC address.
+ * @interface_id_len: the length of @interface_id.
+ * @machine_id: the binary identifier for the machine. It is hashed
+ *   into the DUID. It commonly is /etc/machine-id (parsed in binary as NMUuid).
+ * @machine_id_len: the length of the @machine_id.
+ *
+ * Systemd's sd_dhcp_client generates a default client ID (type 255, node-specific,
+ * RFC 4361) if no explicit client-id is set. This function duplicates that
+ * implementation and exposes it as (internal) API.
+ *
+ * Returns: a %GBytes of generated client-id. This function cannot fail.
+ */
+GBytes *
+nm_utils_dhcp_client_id_systemd_node_specific_full (gboolean legacy_unstable_byteorder,
+                                                    const guint8 *interface_id,
+                                                    gsize interface_id_len,
+                                                    const guint8 *machine_id,
+                                                    gsize machine_id_len)
+{
+	const guint8 HASH_KEY[16] = { 0x80, 0x11, 0x8c, 0xc2, 0xfe, 0x4a, 0x03, 0xee, 0x3e, 0xd6, 0x0c, 0x6f, 0x36, 0x39, 0x14, 0x09 };
+	const guint16 DUID_TYPE_EN = 2;
+	const guint32 SYSTEMD_PEN = 43793;
+	struct _nm_packed {
+		guint8 type;
+		guint32 iaid;
+		struct _nm_packed {
+			guint16 type;
+			union {
+				struct _nm_packed {
+						/* DUID_TYPE_EN */
+						guint32 pen;
+						uint8_t id[8];
+				} en;
+			};
+		} duid;
+	} *client_id;
+	guint64 u64;
+	guint32 u32;
+
+	g_return_val_if_fail (interface_id, NULL);
+	g_return_val_if_fail (interface_id_len > 0, NULL);
+	g_return_val_if_fail (machine_id, NULL);
+	g_return_val_if_fail (machine_id_len > 0, NULL);
+
+	client_id = g_malloc (sizeof (*client_id));
+
+	client_id->type = 255;
+
+	u64 = c_siphash_hash (HASH_KEY, interface_id, interface_id_len);
+	u32 = (u64 & 0xffffffffu) ^ (u64 >> 32);
+	if (legacy_unstable_byteorder) {
+		/* original systemd code dhcp_identifier_set_iaid() generates the iaid
+		 * in native endianness. Do that too, to preserve compatibility
+		 * (https://github.com/systemd/systemd/pull/10614). */
+		u32 = bswap_32 (u32);
+	} else {
+		/* generate fixed byteorder, in a way that on little endian systems
+		 * the values agree. Meaning: legacy behavior is identical to this
+		 * on little endian. */
+		u32 = be32toh (u32);
+	}
+	unaligned_write_ne32 (&client_id->iaid, u32);
+
+	unaligned_write_be16 (&client_id->duid.type, DUID_TYPE_EN);
+
+	unaligned_write_be32 (&client_id->duid.en.pen, SYSTEMD_PEN);
+
+	u64 = htole64 (c_siphash_hash (HASH_KEY, machine_id, machine_id_len));
+	memcpy(client_id->duid.en.id, &u64, sizeof (client_id->duid.en.id));
+
+	G_STATIC_ASSERT_EXPR (sizeof (*client_id) == 19);
+	return g_bytes_new_take (client_id, 19);
+}
+
+GBytes *
+nm_utils_dhcp_client_id_systemd_node_specific (gboolean legacy_unstable_byteorder,
+                                               const char *ifname)
+{
+	g_return_val_if_fail (ifname && ifname[0], NULL);
+
+	return nm_utils_dhcp_client_id_systemd_node_specific_full (legacy_unstable_byteorder,
+	                                                           (const guint8 *) ifname,
+	                                                           strlen (ifname),
+	                                                           (const guint8 *) nm_utils_machine_id_bin (),
+	                                                           sizeof (NMUuid));
+}
+
+/*****************************************************************************/
+
 /**
  * nm_utils_setpgid:
  * @unused: unused
diff --git a/src/nm-core-utils.h b/src/nm-core-utils.h
index 30d1360a..2e75ea62 100644
--- a/src/nm-core-utils.h
+++ b/src/nm-core-utils.h
@@ -61,20 +61,35 @@ void _nm_singleton_instance_register_destruction (GObject *instance);
 #define NM_DEFINE_SINGLETON_GETTER(TYPE, GETTER, GTYPE, ...) \
 NM_DEFINE_SINGLETON_INSTANCE (TYPE); \
 NM_DEFINE_SINGLETON_REGISTER (TYPE); \
+static char _already_created_##GETTER = FALSE; \
 TYPE * \
 GETTER (void) \
 { \
 	if (G_UNLIKELY (!singleton_instance)) { \
-		static char _already_created = FALSE; \
-\
-		g_assert (!_already_created || (NM_DEFINE_SINGLETON_ALLOW_MULTIPLE)); \
-		_already_created = TRUE;\
+		g_assert (!(_already_created_##GETTER) || (NM_DEFINE_SINGLETON_ALLOW_MULTIPLE)); \
+		(_already_created_##GETTER) = TRUE;\
 		singleton_instance = (g_object_new (GTYPE, ##__VA_ARGS__, NULL)); \
 		g_assert (singleton_instance); \
 		nm_singleton_instance_register (); \
 		nm_log_dbg (LOGD_CORE, "create %s singleton (%p)", G_STRINGIFY (TYPE), singleton_instance); \
 	} \
 	return singleton_instance; \
+} \
+_nm_unused static void \
+_nmtst_##GETTER##_reset (TYPE *instance) \
+{ \
+	/* usually, the singleton can only be created once (and further instantiations
+	 * are guarded by an assert). For testing, we need to reset the singleton to
+	 * allow multiple instantiations. */ \
+	g_assert (G_IS_OBJECT (instance)); \
+	g_assert (instance == singleton_instance); \
+	g_assert (_already_created_##GETTER); \
+	g_object_unref (instance); \
+	\
+	/* require that the last unref also destroyed the singleton. If this fails,
+	 * somebody still keeps a reference. Fix your test! */ \
+	g_assert (!singleton_instance); \
+	_already_created_##GETTER = FALSE; \
 }
 
 /* attach @instance to the data or @owner. @owner owns a reference
@@ -208,7 +223,8 @@ NMMatchSpecMatchType nm_match_spec_device (const GSList *specs,
                                            const char *driver,
                                            const char *driver_version,
                                            const char *hwaddr,
-                                           const char *s390_subchannels);
+                                           const char *s390_subchannels,
+                                           const char *dhcp_plugin);
 NMMatchSpecMatchType nm_match_spec_config (const GSList *specs,
                                            guint nm_version,
                                            const char *env);
@@ -264,14 +280,32 @@ gboolean nm_utils_sysctl_ip_conf_is_path (int addr_family, const char *path, con
 
 gboolean nm_utils_is_specific_hostname (const char *name);
 
-char *nm_utils_machine_id_read (void);
-gboolean nm_utils_machine_id_parse (const char *id_str, /*uuid_t*/ guchar *out_uuid);
+struct _NMUuid;
+
+const char *nm_utils_machine_id_str (void);
+const struct _NMUuid *nm_utils_machine_id_bin (void);
+gboolean nm_utils_machine_id_is_fake (void);
 
-gboolean nm_utils_secret_key_get (const guint8 **out_secret_key,
-                                  gsize *out_key_len);
-gint64 nm_utils_secret_key_get_timestamp (void);
+const char *nm_utils_boot_id_str (void);
+const struct _NMUuid *nm_utils_boot_id_bin (void);
 
-const char *nm_utils_get_boot_id (void);
+gboolean nm_utils_host_id_get (const guint8 **out_host_id,
+                               gsize *out_host_id_len);
+gint64 nm_utils_host_id_get_timestamp_ns (void);
+
+/*****************************************************************************/
+
+int nm_utils_arp_type_detect_from_hwaddrlen (gsize hwaddr_len);
+
+gboolean nm_utils_arp_type_validate_hwaddr (int arp_type,
+                                            const guint8 *hwaddr,
+                                            gsize hwaddr_len);
+
+gboolean nm_utils_arp_type_get_hwaddr_relevant_part (int arp_type,
+                                                     const guint8 **hwaddr,
+                                                     gsize *hwaddr_len);
+
+/*****************************************************************************/
 
 /* IPv6 Interface Identifier helpers */
 
@@ -326,6 +360,7 @@ typedef enum {
 
 NMUtilsStableType nm_utils_stable_id_parse (const char *stable_id,
                                             const char *deviceid,
+                                            const char *hwaddr,
                                             const char *bootid,
                                             const char *uuid,
                                             char **out_generated);
@@ -338,8 +373,8 @@ gboolean nm_utils_ipv6_addr_set_stable_privacy_impl (NMUtilsStableType stable_ty
                                                      const char *ifname,
                                                      const char *network_id,
                                                      guint32 dad_counter,
-                                                     guint8 *secret_key,
-                                                     gsize key_len,
+                                                     guint8 *host_id,
+                                                     gsize host_id_len,
                                                      GError **error);
 
 gboolean nm_utils_ipv6_addr_set_stable_privacy (NMUtilsStableType id_type,
@@ -353,8 +388,8 @@ char *nm_utils_hw_addr_gen_random_eth (const char *current_mac_address,
                                        const char *generate_mac_address_mask);
 char *nm_utils_hw_addr_gen_stable_eth_impl (NMUtilsStableType stable_type,
                                             const char *stable_id,
-                                            const guint8 *secret_key,
-                                            gsize key_len,
+                                            const guint8 *host_id,
+                                            gsize host_id_len,
                                             const char *ifname,
                                             const char *current_mac_address,
                                             const char *generate_mac_address_mask);
@@ -364,6 +399,23 @@ char *nm_utils_hw_addr_gen_stable_eth (NMUtilsStableType stable_type,
                                        const char *current_mac_address,
                                        const char *generate_mac_address_mask);
 
+/*****************************************************************************/
+
+GBytes *nm_utils_dhcp_client_id_mac (int arp_type,
+                                     const guint8 *hwaddr,
+                                     gsize hwaddr_len);
+
+GBytes *nm_utils_dhcp_client_id_systemd_node_specific_full (gboolean legacy_unstable_byteorder,
+                                                            const guint8 *interface_id,
+                                                            gsize interface_id_len,
+                                                            const guint8 *machine_id,
+                                                            gsize machine_id_len);
+
+GBytes *nm_utils_dhcp_client_id_systemd_node_specific (gboolean legacy_unstable_byteorder,
+                                                       const char *ifname);
+
+/*****************************************************************************/
+
 void nm_utils_array_remove_at_indexes (GArray *array, const guint *indexes_to_delete, gsize len);
 
 void nm_utils_setpgid (gpointer unused);
diff --git a/src/nm-dbus-manager.c b/src/nm-dbus-manager.c
index ca7bde57..a5c7c12b 100644
--- a/src/nm-dbus-manager.c
+++ b/src/nm-dbus-manager.c
@@ -1538,6 +1538,18 @@ nm_dbus_manager_acquire_bus (NMDBusManager *self)
 		return FALSE;
 	}
 
+	registration_id = g_dbus_connection_register_object (connection,
+	                                                     OBJECT_MANAGER_SERVER_BASE_PATH,
+	                                                     NM_UNCONST_PTR (GDBusInterfaceInfo, &interface_info_objmgr),
+	                                                     &dbus_vtable_objmgr,
+	                                                     self,
+	                                                     NULL,
+	                                                     &error);
+	if (!registration_id) {
+		_LOGE ("failure to register object manager: %s", error->message);
+		return FALSE;
+	}
+
 	ret = _nm_dbus_proxy_call_sync (proxy,
 	                                "RequestName",
 	                                g_variant_new ("(su)",
@@ -1550,6 +1562,7 @@ nm_dbus_manager_acquire_bus (NMDBusManager *self)
 	if (!ret) {
 		_LOGE ("fatal failure to acquire D-Bus service \"%s"": %s",
 		       NM_DBUS_SERVICE, error->message);
+		g_dbus_connection_unregister_object(connection, registration_id);
 		return FALSE;
 	}
 
@@ -1557,18 +1570,7 @@ nm_dbus_manager_acquire_bus (NMDBusManager *self)
 	if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
 		_LOGE ("fatal failure to acquire D-Bus service \"%s\" (%u). Service already taken",
 		       NM_DBUS_SERVICE, (guint) result);
-		return FALSE;
-	}
-
-	registration_id = g_dbus_connection_register_object (connection,
-	                                                     OBJECT_MANAGER_SERVER_BASE_PATH,
-	                                                     NM_UNCONST_PTR (GDBusInterfaceInfo, &interface_info_objmgr),
-	                                                     &dbus_vtable_objmgr,
-	                                                     self,
-	                                                     NULL,
-	                                                     &error);
-	if (!registration_id) {
-		_LOGE ("failure to register object manager: %s", error->message);
+		g_dbus_connection_unregister_object(connection, registration_id);
 		return FALSE;
 	}
 
diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c
index 6604711c..ce7f7fc4 100644
--- a/src/nm-ip4-config.c
+++ b/src/nm-ip4-config.c
@@ -2996,29 +2996,19 @@ nm_ip4_config_hash (const NMIP4Config *self, GChecksum *sum, gboolean dns_only)
 gboolean
 nm_ip4_config_equal (const NMIP4Config *a, const NMIP4Config *b)
 {
-	GChecksum *a_checksum = g_checksum_new (G_CHECKSUM_SHA1);
-	GChecksum *b_checksum = g_checksum_new (G_CHECKSUM_SHA1);
-	guchar a_data[20], b_data[20];
-	gsize a_len = sizeof (a_data);
-	gsize b_len = sizeof (b_data);
-	gboolean equal;
+	nm_auto_free_checksum GChecksum *a_checksum = g_checksum_new (G_CHECKSUM_SHA1);
+	nm_auto_free_checksum GChecksum *b_checksum = g_checksum_new (G_CHECKSUM_SHA1);
+	guint8 a_data[NM_UTILS_CHECKSUM_LENGTH_SHA1];
+	guint8 b_data[NM_UTILS_CHECKSUM_LENGTH_SHA1];
 
 	if (a)
 		nm_ip4_config_hash (a, a_checksum, FALSE);
 	if (b)
 		nm_ip4_config_hash (b, b_checksum, FALSE);
 
-	g_checksum_get_digest (a_checksum, a_data, &a_len);
-	g_checksum_get_digest (b_checksum, b_data, &b_len);
-
-	nm_assert (a_len == sizeof (a_data));
-	nm_assert (b_len == sizeof (b_data));
-	equal = !memcmp (a_data, b_data, a_len);
-
-	g_checksum_free (a_checksum);
-	g_checksum_free (b_checksum);
-
-	return equal;
+	nm_utils_checksum_get_digest (a_checksum, a_data);
+	nm_utils_checksum_get_digest (b_checksum, b_data);
+	return !memcmp (a_data, b_data, sizeof (a_data));
 }
 
 /*****************************************************************************/
diff --git a/src/nm-ip6-config.c b/src/nm-ip6-config.c
index a8c4aecd..42240e69 100644
--- a/src/nm-ip6-config.c
+++ b/src/nm-ip6-config.c
@@ -2426,29 +2426,19 @@ nm_ip6_config_hash (const NMIP6Config *self, GChecksum *sum, gboolean dns_only)
 gboolean
 nm_ip6_config_equal (const NMIP6Config *a, const NMIP6Config *b)
 {
-	GChecksum *a_checksum = g_checksum_new (G_CHECKSUM_SHA1);
-	GChecksum *b_checksum = g_checksum_new (G_CHECKSUM_SHA1);
-	guchar a_data[20], b_data[20];
-	gsize a_len = sizeof (a_data);
-	gsize b_len = sizeof (b_data);
-	gboolean equal;
+	nm_auto_free_checksum GChecksum *a_checksum = g_checksum_new (G_CHECKSUM_SHA1);
+	nm_auto_free_checksum GChecksum *b_checksum = g_checksum_new (G_CHECKSUM_SHA1);
+	guint8 a_data[NM_UTILS_CHECKSUM_LENGTH_SHA1];
+	guint8 b_data[NM_UTILS_CHECKSUM_LENGTH_SHA1];
 
 	if (a)
 		nm_ip6_config_hash (a, a_checksum, FALSE);
 	if (b)
 		nm_ip6_config_hash (b, b_checksum, FALSE);
 
-	g_checksum_get_digest (a_checksum, a_data, &a_len);
-	g_checksum_get_digest (b_checksum, b_data, &b_len);
-
-	nm_assert (a_len == sizeof (a_data));
-	nm_assert (b_len == sizeof (b_data));
-	equal = !memcmp (a_data, b_data, a_len);
-
-	g_checksum_free (a_checksum);
-	g_checksum_free (b_checksum);
-
-	return equal;
+	nm_utils_checksum_get_digest (a_checksum, a_data);
+	nm_utils_checksum_get_digest (b_checksum, b_data);
+	return !memcmp (a_data, b_data, sizeof (a_data));
 }
 
 /*****************************************************************************/
diff --git a/src/nm-manager.c b/src/nm-manager.c
index 7598995d..3ddc3b92 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -1596,7 +1596,12 @@ again:
 static gboolean
 device_is_wake_on_lan (NMPlatform *platform, NMDevice *device)
 {
-	return nm_platform_link_get_wake_on_lan (platform, nm_device_get_ip_ifindex (device));
+	int ifindex;
+
+	ifindex = nm_device_get_ip_ifindex (device);
+	if (ifindex <= 0)
+		return FALSE;
+	return nm_platform_link_get_wake_on_lan (platform, ifindex);
 }
 
 static void
@@ -2816,18 +2821,21 @@ device_connectivity_changed (NMDevice *device,
 
 	best_state = nm_device_get_connectivity_state (device);
 	if (best_state < NM_CONNECTIVITY_FULL) {
+		/* FIXME: is this really correct, to considere devices that don't have
+		 * (the best) default route for connectivity checking? */
 		c_list_for_each_entry (dev, &priv->devices_lst_head, devices_lst) {
 			state = nm_device_get_connectivity_state (dev);
-			if (state <= best_state)
+			if (nm_connectivity_state_cmp (state, best_state) <= 0)
 				continue;
 			best_state = state;
-			if (best_state >= NM_CONNECTIVITY_FULL) {
+			if (nm_connectivity_state_cmp (best_state, NM_CONNECTIVITY_FULL) >= 0) {
 				/* it doesn't get better than this. */
 				break;
 			}
 		}
 	}
 	nm_assert (best_state <= NM_CONNECTIVITY_FULL);
+	nm_assert (nm_connectivity_state_cmp (best_state, NM_CONNECTIVITY_FULL) <= 0);
 
 	if (best_state != priv->connectivity_state) {
 		priv->connectivity_state = best_state;
diff --git a/src/nm-policy.c b/src/nm-policy.c
index 7f8c665c..a5960105 100644
--- a/src/nm-policy.c
+++ b/src/nm-policy.c
@@ -1751,17 +1751,14 @@ device_state_changed (NMDevice *device,
 	NMSettingConnection *s_con = NULL;
 
 	switch (nm_device_state_reason_check (reason)) {
-	case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED:
-	case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING:
-	case NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED:
 	case NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED:
 	case NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED:
-	case NM_DEVICE_STATE_REASON_GSM_SIM_WRONG:
 	case NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT:
-	case NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED:
 	case NM_DEVICE_STATE_REASON_GSM_APN_FAILED:
-		/* Block autoconnect of the just-failed connection for situations
-		 * where a retry attempt would just fail again.
+		/* Block autoconnection at settings level if there is any settings-specific
+		 * error reported by the modem (e.g. wrong SIM-PIN or wrong APN). Do not block
+		 * autoconnection at settings level for errors in the device domain (e.g.
+		 * a missing SIM or wrong modem initialization).
 		 */
 		if (sett_conn) {
 			nm_settings_connection_autoconnect_blocked_reason_set (sett_conn,
diff --git a/src/nm-test-utils-core.h b/src/nm-test-utils-core.h
index 5e89cb39..dbfe71ad 100644
--- a/src/nm-test-utils-core.h
+++ b/src/nm-test-utils-core.h
@@ -88,8 +88,8 @@ nmtst_platform_ip4_address_full (const char *address, const char *peer_address,
 {
 	NMPlatformIP4Address *addr = nmtst_platform_ip4_address (address, peer_address, plen);
 
-	G_STATIC_ASSERT (IFNAMSIZ == sizeof (addr->label));
-	g_assert (!label || strlen (label) < IFNAMSIZ);
+	G_STATIC_ASSERT (NMP_IFNAMSIZ == sizeof (addr->label));
+	g_assert (!label || strlen (label) < NMP_IFNAMSIZ);
 
 	addr->ifindex = ifindex;
 	addr->addr_source = source;
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index 82f9e2fb..ef69b391 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -26,6 +26,7 @@
 #include <unistd.h>
 #include <netinet/icmp6.h>
 #include <netinet/in.h>
+#include <linux/if.h>
 #include <linux/rtnetlink.h>
 
 #include "nm-utils.h"
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index e73d5d8c..c0224fff 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -2031,7 +2031,7 @@ _wireguard_update_from_peers_nla (CList *peers,
 			}
 		}
 		if (tb[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL])
-			peer_c->data.persistent_keepalive_interval = nla_get_u64 (tb[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]);
+			peer_c->data.persistent_keepalive_interval = nla_get_u16 (tb[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]);
 		if (tb[WGPEER_A_LAST_HANDSHAKE_TIME])
 			nla_memcpy (&peer_c->data.last_handshake_time, tb[WGPEER_A_LAST_HANDSHAKE_TIME], sizeof (peer_c->data.last_handshake_time));
 		if (tb[WGPEER_A_RX_BYTES])
@@ -5737,12 +5737,13 @@ static gboolean
 link_set_sriov_params (NMPlatform *platform,
                        int ifindex,
                        guint num_vfs,
-                       int autoprobe)
+                       NMTernary autoprobe)
 {
 	nm_auto_pop_netns NMPNetns *netns = NULL;
 	nm_auto_close int dirfd = -1;
-	gboolean current_autoprobe;
-	guint total, current_num;
+	int current_autoprobe;
+	guint total;
+	gint64 current_num;
 	char ifname[IFNAMSIZ];
 	char buf[64];
 
@@ -5775,14 +5776,14 @@ link_set_sriov_params (NMPlatform *platform,
 	                                                  NMP_SYSCTL_PATHID_NETDIR (dirfd,
 	                                                                            ifname,
 	                                                                            "device/sriov_numvfs"),
-	                                                  10, 0, G_MAXUINT, 0);
+	                                                  10, 0, G_MAXUINT, -1);
 	current_autoprobe = nm_platform_sysctl_get_int_checked (platform,
 	                                                        NMP_SYSCTL_PATHID_NETDIR (dirfd,
 	                                                                                  ifname,
 	                                                                                  "device/sriov_drivers_autoprobe"),
-	                                                        10, 0, G_MAXUINT, 0);
+	                                                        10, 0, 1, -1);
 	if (   current_num == num_vfs
-	    && (autoprobe == -1 || current_autoprobe == autoprobe))
+	    && (autoprobe == NM_TERNARY_DEFAULT || current_autoprobe == autoprobe))
 		return TRUE;
 
 	if (current_num != 0) {
@@ -5800,14 +5801,14 @@ link_set_sriov_params (NMPlatform *platform,
 	if (num_vfs == 0)
 		return TRUE;
 
-	if (   autoprobe >= 0
+	if (   NM_IN_SET (autoprobe, NM_TERNARY_TRUE, NM_TERNARY_FALSE)
 	    && current_autoprobe != autoprobe
 	    && !nm_platform_sysctl_set (NM_PLATFORM_GET,
 	                                NMP_SYSCTL_PATHID_NETDIR (dirfd,
 	                                                          ifname,
 	                                                          "device/sriov_drivers_autoprobe"),
-	                                nm_sprintf_buf (buf, "%d", autoprobe))) {
-		_LOGW ("link: couldn't set SR-IOV drivers-autoprobe to %d: %s", autoprobe, strerror (errno));
+	                                nm_sprintf_buf (buf, "%d", (int) autoprobe))) {
+		_LOGW ("link: couldn't set SR-IOV drivers-autoprobe to %d: %s", (int) autoprobe, strerror (errno));
 		return FALSE;
 	}
 
@@ -7044,7 +7045,7 @@ ip6_address_add (NMPlatform *platform,
 	                             ifindex,
 	                             &addr,
 	                             plen,
-	                             &peer_addr,
+	                             IN6_IS_ADDR_UNSPECIFIED (&peer_addr) ? NULL : &peer_addr,
 	                             flags,
 	                             RT_SCOPE_UNIVERSE,
 	                             lifetime,
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c
index 216b1547..cc43b27f 100644
--- a/src/platform/nm-platform-utils.c
+++ b/src/platform/nm-platform-utils.c
@@ -29,6 +29,7 @@
 #include <linux/ethtool.h>
 #include <linux/sockios.h>
 #include <linux/mii.h>
+#include <linux/if.h>
 #include <linux/version.h>
 #include <linux/rtnetlink.h>
 #include <fcntl.h>
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index 7ddcf41a..757a0f43 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -31,6 +31,7 @@
 #include <netdb.h>
 #include <string.h>
 #include <linux/ip.h>
+#include <linux/if.h>
 #include <linux/if_tun.h>
 #include <linux/if_tunnel.h>
 #include <linux/rtnetlink.h>
@@ -1192,13 +1193,29 @@ nm_platform_link_refresh (NMPlatform *self, int ifindex)
 	return TRUE;
 }
 
-static guint
-_link_get_flags (NMPlatform *self, int ifindex)
+int
+nm_platform_link_get_ifi_flags (NMPlatform *self,
+                                int ifindex,
+                                guint requested_flags)
 {
 	const NMPlatformLink *pllink;
 
-	pllink = nm_platform_link_get (self, ifindex);
-	return pllink ? pllink->n_ifi_flags : IFF_NOARP;
+	_CHECK_SELF (self, klass, -EINVAL);
+
+	if (ifindex <= 0)
+		return -EINVAL;
+
+	/* include invisible links (only in netlink, not udev). */
+	pllink = NMP_OBJECT_CAST_LINK (nm_platform_link_get_obj (self, ifindex, FALSE));
+	if (!pllink)
+		return -ENODEV;
+
+	/* Errors are signaled as negative values. That means, you cannot request
+	 * the most significant bit (2^31) with this API. Assert against that. */
+	nm_assert ((int) requested_flags >= 0);
+	nm_assert (requested_flags < (guint) G_MAXINT);
+
+	return (int) (pllink->n_ifi_flags & requested_flags);
 }
 
 /**
@@ -1211,9 +1228,7 @@ _link_get_flags (NMPlatform *self, int ifindex)
 gboolean
 nm_platform_link_is_up (NMPlatform *self, int ifindex)
 {
-	_CHECK_SELF (self, klass, FALSE);
-
-	return NM_FLAGS_HAS (_link_get_flags (self, ifindex), IFF_UP);
+	return nm_platform_link_get_ifi_flags (self, ifindex, IFF_UP) == IFF_UP;
 }
 
 /**
@@ -1244,9 +1259,15 @@ nm_platform_link_is_connected (NMPlatform *self, int ifindex)
 gboolean
 nm_platform_link_uses_arp (NMPlatform *self, int ifindex)
 {
-	_CHECK_SELF (self, klass, FALSE);
+	int f;
+
+	f = nm_platform_link_get_ifi_flags (self, ifindex, IFF_NOARP);
 
-	return !NM_FLAGS_HAS (_link_get_flags (self, ifindex), IFF_NOARP);
+	if (f < 0)
+		return FALSE;
+	if (f == IFF_NOARP)
+		return FALSE;
+	return TRUE;
 }
 
 /**
@@ -1468,25 +1489,25 @@ nm_platform_link_supports_sriov (NMPlatform *self, int ifindex)
  * @self: platform instance
  * @ifindex: the index of the interface to change
  * @num_vfs: the number of VFs to create
- * @autoprobe: -1 to keep the current autoprobe-drivers value,
- *   or {0,1} to set a new value
+ * @autoprobe: the new autoprobe-drivers value (pass
+ *     %NM_TERNARY_DEFAULT to keep current value)
  */
 gboolean
 nm_platform_link_set_sriov_params (NMPlatform *self,
                                    int ifindex,
                                    guint num_vfs,
-                                   int autoprobe)
+                                   NMTernary autoprobe)
 {
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (NM_IN_SET (autoprobe, -1, 0, 1), FALSE);
 
 	_LOGD ("link: setting %u total VFs and autoprobe %d for %s (%d)",
 	       num_vfs,
-	       autoprobe,
+	       (int) autoprobe,
 	       nm_strquote_a (25, nm_platform_link_get_name (self, ifindex)),
 	       ifindex);
+
 	return klass->link_set_sriov_params (self, ifindex, num_vfs, autoprobe);
 }
 
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index 11495aff..7e91f1f3 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -21,12 +21,6 @@
 #ifndef __NETWORKMANAGER_PLATFORM_H__
 #define __NETWORKMANAGER_PLATFORM_H__
 
-#include <netinet/in.h>
-#include <linux/if.h>
-#include <linux/if_addr.h>
-#include <linux/if_link.h>
-#include <linux/ip6_tunnel.h>
-
 #include "nm-dbus-interface.h"
 #include "nm-core-types-internal.h"
 
@@ -53,6 +47,12 @@
 
 /*****************************************************************************/
 
+/* IFNAMSIZ is both defined in <linux/if.h> and <net/if.h>. In the past, these
+ * headers conflicted, so we cannot simply include either of them in a header-file.*/
+#define NMP_IFNAMSIZ 16
+
+/*****************************************************************************/
+
 struct udev_device;
 
 typedef gboolean (*NMPObjectPredicateFunc) (const NMPObject *obj,
@@ -208,7 +208,7 @@ typedef enum {
 
 struct _NMPlatformLink {
 	__NMPlatformObject_COMMON;
-	char name[IFNAMSIZ];
+	char name[NMP_IFNAMSIZ];
 	NMLinkType type;
 
 	/* rtnl_link_get_type(), IFLA_INFO_KIND. */
@@ -355,7 +355,7 @@ struct _NMPlatformIP4Address {
 	 * */
 	in_addr_t peer_address;  /* PTP peer address */
 
-	char label[IFNAMSIZ];
+	char label[NMP_IFNAMSIZ];
 };
 
 /**
@@ -1105,12 +1105,12 @@ const char *nm_platform_error_to_string (NMPlatformError error,
 	((const char *) NULL), -1, (path)
 
 #define NMP_SYSCTL_PATHID_NETDIR_unsafe(dirfd, ifname, path) \
-	nm_sprintf_bufa (NM_STRLEN ("net:/sys/class/net//\0") + IFNAMSIZ + strlen (path), \
+	nm_sprintf_bufa (NM_STRLEN ("net:/sys/class/net//\0") + NMP_IFNAMSIZ + strlen (path), \
 	                 "net:/sys/class/net/%s/%s", (ifname), (path)), \
 	(dirfd), (path)
 
 #define NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, path) \
-	nm_sprintf_bufa (NM_STRLEN ("net:/sys/class/net//"path"/\0") + IFNAMSIZ, \
+	nm_sprintf_bufa (NM_STRLEN ("net:/sys/class/net//"path"/\0") + NMP_IFNAMSIZ, \
 	                 "net:/sys/class/net/%s/%s", (ifname), path), \
 	(dirfd), (""path"")
 
@@ -1165,6 +1165,7 @@ int nm_platform_link_get_ifindex (NMPlatform *self, const char *name);
 const char *nm_platform_link_get_name (NMPlatform *self, int ifindex);
 NMLinkType nm_platform_link_get_type (NMPlatform *self, int ifindex);
 gboolean nm_platform_link_is_software (NMPlatform *self, int ifindex);
+int nm_platform_link_get_ifi_flags (NMPlatform *self, int ifindex, guint requested_flags);
 gboolean nm_platform_link_is_up (NMPlatform *self, int ifindex);
 gboolean nm_platform_link_is_connected (NMPlatform *self, int ifindex);
 gboolean nm_platform_link_uses_arp (NMPlatform *self, int ifindex);
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index fdc27440..f7fa6cb3 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -24,6 +24,7 @@
 
 #include <unistd.h>
 #include <linux/rtnetlink.h>
+#include <linux/if.h>
 #include <libudev.h>
 
 #include "nm-utils.h"
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index 3dedbd10..1cb2f516 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -787,8 +787,6 @@ nmtstp_ip_address_assert_lifetime (const NMPlatformIPAddress *addr,
 		if (lft == NM_PLATFORM_LIFETIME_PERMANENT)
 			g_assert_cmpint (adr, ==, NM_PLATFORM_LIFETIME_PERMANENT);
 		else {
-			g_assert_cmpint (adr, <=, lft);
-			g_assert_cmpint (offset, <=, adr);
 			g_assert_cmpint (adr - offset, <=, lft + CHECK_LIFETIME_MAX_DIFF);
 			g_assert_cmpint (adr - offset, >=, lft - CHECK_LIFETIME_MAX_DIFF);
 		}
diff --git a/src/platform/tests/test-common.h b/src/platform/tests/test-common.h
index 1baadfa1..7e81baea 100644
--- a/src/platform/tests/test-common.h
+++ b/src/platform/tests/test-common.h
@@ -21,6 +21,9 @@
 #include <syslog.h>
 #include <string.h>
 #include <arpa/inet.h>
+#include <linux/if.h>
+#include <linux/if_link.h>
+#include <linux/ip6_tunnel.h>
 
 #include "platform/nm-platform.h"
 #include "platform/nmp-object.h"
diff --git a/src/platform/wifi/nm-wifi-utils-nl80211.c b/src/platform/wifi/nm-wifi-utils-nl80211.c
index b3bb2bb6..39e3f971 100644
--- a/src/platform/wifi/nm-wifi-utils-nl80211.c
+++ b/src/platform/wifi/nm-wifi-utils-nl80211.c
@@ -30,6 +30,7 @@
 #include <net/ethernet.h>
 #include <unistd.h>
 #include <linux/nl80211.h>
+#include <linux/if.h>
 
 #include "platform/nm-netlink.h"
 #include "nm-wifi-utils-private.h"
diff --git a/src/platform/wpan/nm-wpan-utils.c b/src/platform/wpan/nm-wpan-utils.c
index 0544539a..882c4eec 100644
--- a/src/platform/wpan/nm-wpan-utils.c
+++ b/src/platform/wpan/nm-wpan-utils.c
@@ -21,6 +21,8 @@
 
 #include "nm-wpan-utils.h"
 
+#include <linux/if.h>
+
 #include "platform/linux/nl802154.h"
 #include "platform/nm-netlink.h"
 
diff --git a/src/settings/nm-settings-connection.c b/src/settings/nm-settings-connection.c
index 4c25d0e9..6f47e14d 100644
--- a/src/settings/nm-settings-connection.c
+++ b/src/settings/nm-settings-connection.c
@@ -640,7 +640,9 @@ nm_settings_connection_update (NMSettingsConnection *self,
 	gboolean replaced = FALSE;
 	gs_free char *logmsg_change = NULL;
 	GError *local = NULL;
+	gs_unref_object NMConnection *simple = NULL;
 	gs_unref_variant GVariant *con_agent_secrets = NULL;
+	gs_unref_variant GVariant *new_agent_secrets = NULL;
 
 	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), FALSE);
 
@@ -681,6 +683,16 @@ nm_settings_connection_update (NMSettingsConnection *self,
 
 	replace_connection = reread_connection ?: new_connection;
 
+	/* Save agent-owned secrets from the new connection for later use */
+	if (new_connection) {
+		simple = nm_simple_connection_new_clone (new_connection);
+		nm_connection_clear_secrets_with_flags (simple,
+		                                        secrets_filter_cb,
+		                                        GUINT_TO_POINTER (NM_SETTING_SECRET_FLAG_AGENT_OWNED));
+		new_agent_secrets = nm_connection_to_dbus (simple, NM_CONNECTION_SERIALIZE_ONLY_SECRETS);
+		g_clear_object (&simple);
+	}
+
 	/* Disconnect the changed signal to ensure we don't set Unsaved when
 	 * it's not required.
 	 */
@@ -691,7 +703,6 @@ nm_settings_connection_update (NMSettingsConnection *self,
 	    && !nm_connection_compare (nm_settings_connection_get_connection (self),
 	                               replace_connection,
 	                               NM_SETTING_COMPARE_FLAG_EXACT)) {
-		gs_unref_object NMConnection *simple = NULL;
 
 		if (log_diff_name) {
 			nm_utils_log_connection_diff (replace_connection, nm_settings_connection_get_connection (self), LOGL_DEBUG, LOGD_CORE, log_diff_name, "++ ",
@@ -738,6 +749,15 @@ nm_settings_connection_update (NMSettingsConnection *self,
 			(void) nm_connection_update_secrets (nm_settings_connection_get_connection (self), NULL, con_agent_secrets, NULL);
 	}
 
+	/* Apply agent-owned secrets from the new connection so that
+	 * they can be sent to agents */
+	if (new_agent_secrets) {
+		(void) nm_connection_update_secrets (nm_settings_connection_get_connection (self),
+		                                     NULL,
+		                                     new_agent_secrets,
+		                                     NULL);
+	}
+
 	nm_settings_connection_recheck_visibility (self);
 
 	if (   replaced
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-dns1 b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-dns1
new file mode 100755
index 00000000..54f02da6
--- /dev/null
+++ b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-dns1
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+cat << EOF
+# BEGIN RECORD
+iface.initiatorname = iqn.pjones6
+iface.hwaddress = 00:33:21:98:b9:f0
+iface.bootproto = STATIC
+iface.ipaddress = 192.168.32.72
+iface.subnet_mask = 255.255.252.0
+iface.gateway = 192.168.35.254
+iface.primary_dns = 10000.500.250.1
+iface.secondary_dns = 10.16.255.3
+iface.vlan_id = 0
+iface.net_ifacename = eth0
+node.name = iqn.0.2008-11.com.blahblah:iscsi0
+node.conn[0].address = 10.16.52.16
+node.conn[0].port = 3260
+node.boot_lun = 00000000
+# END RECORD
+EOF
+
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-dns2 b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-dns2
new file mode 100755
index 00000000..ebd7a9ca
--- /dev/null
+++ b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-dns2
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+cat << EOF
+# BEGIN RECORD
+iface.initiatorname = iqn.pjones6
+iface.hwaddress = 00:33:21:98:b9:f0
+iface.bootproto = STATIC
+iface.ipaddress = 192.168.32.72
+iface.subnet_mask = 255.255.252.0
+iface.gateway = 192.168.35.254
+iface.primary_dns = 10.16.255.2
+iface.secondary_dns = blah.foo.bar.baz
+iface.vlan_id = 0
+iface.net_ifacename = eth0
+node.name = iqn.0.2008-11.com.blahblah:iscsi0
+node.conn[0].address = 10.16.52.16
+node.conn[0].port = 3260
+node.boot_lun = 00000000
+# END RECORD
+EOF
+
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-entry b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-entry
new file mode 100755
index 00000000..4e326048
--- /dev/null
+++ b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-entry
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+cat << EOF
+# BEGIN RECORD
+iface.initiatorname = iqn.pjones6
+iface.hwaddress = 00:33:21:98:b9:f0
+iface.bootproto = STATIC
+iface.ipaddress 192.168.32.72
+iface.subnet_mask = 255.255.252.0
+iface.gateway = 192.168.35.254
+iface.primary_dns = 10.16.255.2
+iface.secondary_dns = 10.16.255.3
+iface.vlan_id = 0
+iface.net_ifacename = eth0
+node.name = iqn.0.2008-11.com.blahblah:iscsi0
+node.conn[0].address = 10.16.52.16
+node.conn[0].port = 3260
+node.boot_lun = 00000000
+# END RECORD
+
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-gateway b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-gateway
new file mode 100755
index 00000000..5390a6c3
--- /dev/null
+++ b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-gateway
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+cat << EOF
+# BEGIN RECORD
+iface.initiatorname = iqn.pjones6
+iface.hwaddress = 00:33:21:98:b9:f0
+iface.bootproto = STATIC
+iface.ipaddress = 192.168.32.72
+iface.subnet_mask = 255.255.252.0
+iface.gateway = bb.cc.dd.ee
+iface.primary_dns = 10.16.255.2
+iface.secondary_dns = 10.16.255.3
+iface.vlan_id = 0
+iface.net_ifacename = eth0
+node.name = iqn.0.2008-11.com.blahblah:iscsi0
+node.conn[0].address = 10.16.52.16
+node.conn[0].port = 3260
+node.boot_lun = 00000000
+# END RECORD
+EOF
+
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-ipaddr b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-ipaddr
new file mode 100755
index 00000000..b41cd1f1
--- /dev/null
+++ b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-ipaddr
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+cat << EOF
+# BEGIN RECORD
+iface.initiatorname = iqn.pjones6
+iface.hwaddress = 00:33:21:98:b9:f0
+iface.bootproto = STATIC
+iface.ipaddress = aa.bb.cc.dd
+iface.subnet_mask = 255.255.252.0
+iface.gateway = 192.168.35.254
+iface.primary_dns = 10.16.255.2
+iface.secondary_dns = 10.16.255.3
+iface.vlan_id = 0
+iface.net_ifacename = eth0
+node.name = iqn.0.2008-11.com.blahblah:iscsi0
+node.conn[0].address = 10.16.52.16
+node.conn[0].port = 3260
+node.boot_lun = 00000000
+# END RECORD
+EOF
+
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-record b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-record
new file mode 100755
index 00000000..22b34e6f
--- /dev/null
+++ b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-record
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+cat << EOF
+# BEGIN RECORD
+iface.initiatorname = iqn.pjones6
+iface.hwaddress = 00:33:21:98:b9:f0
+iface.bootproto = DHCP
+iface.gateway = 10.16.52.254
+iface.primary_dns = 10.16.255.2
+iface.secondary_dns = 10.16.255.3
+iface.vlan_id = 0
+iface.net_ifacename = eth0
+node.name = iqn.0.2008-11.com.blahblah:iscsi0
+node.conn[0].address = 10.16.52.16
+node.conn[0].port = 3260
+node.boot_lun = 00000000
+EOF
+
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-dhcp b/src/settings/plugins/ibft/tests/iscsiadm-test-dhcp
new file mode 100755
index 00000000..556b0586
--- /dev/null
+++ b/src/settings/plugins/ibft/tests/iscsiadm-test-dhcp
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+cat << EOF
+# BEGIN RECORD
+iface.initiatorname = iqn.pjones6
+iface.hwaddress = 00:33:21:98:b9:f0
+iface.bootproto = DHCP
+iface.gateway = 10.16.52.254
+iface.primary_dns = 10.16.255.2
+iface.secondary_dns = 10.16.255.3
+iface.vlan_id = 0
+iface.net_ifacename = eth0
+node.name = iqn.0.2008-11.com.blahblah:iscsi0
+node.conn[0].address = 10.16.52.16
+node.conn[0].port = 3260
+node.boot_lun = 00000000
+# END RECORD
+# BEGIN RECORD
+iface.initiatorname = iqn.pjones6
+iface.hwaddress = 00:33:21:98:b9:f1
+iface.bootproto = DHCP
+iface.gateway = 10.16.52.254
+iface.primary_dns = 10.16.255.2
+iface.secondary_dns = 10.16.255.3
+iface.vlan_id = 0
+iface.net_ifacename = eth1
+node.name = iqn.1.2008-11.com.blahblah:iscsi1
+node.conn[0].address = 10.16.52.16
+node.conn[0].port = 3260
+node.boot_lun = 00000000
+# END RECORD
+EOF
+
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-static b/src/settings/plugins/ibft/tests/iscsiadm-test-static
new file mode 100755
index 00000000..51711480
--- /dev/null
+++ b/src/settings/plugins/ibft/tests/iscsiadm-test-static
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+cat << EOF
+# BEGIN RECORD
+iface.initiatorname = iqn.pjones6
+iface.hwaddress = 00:33:21:98:b9:f0
+iface.bootproto = STATIC
+iface.ipaddress = 192.168.32.72
+iface.subnet_mask = 255.255.252.0
+iface.gateway = 192.168.35.254
+iface.primary_dns = 10.16.255.2
+iface.secondary_dns = 10.16.255.3
+iface.vlan_id = 0
+iface.net_ifacename = eth0
+node.name = iqn.0.2008-11.com.blahblah:iscsi0
+node.conn[0].address = 10.16.52.16
+node.conn[0].port = 3260
+node.boot_lun = 00000000
+# END RECORD
+# BEGIN RECORD
+iface.initiatorname = iqn.pjones6
+iface.hwaddress = 00:33:21:98:b9:f1
+iface.bootproto = DHCP
+iface.gateway = 10.16.52.254
+iface.primary_dns = 10.16.255.2
+iface.secondary_dns = 10.16.255.3
+iface.vlan_id = 0
+iface.net_ifacename = eth1
+node.name = iqn.1.2008-11.com.blahblah:iscsi1
+node.conn[0].address = 10.16.52.16
+node.conn[0].port = 3260
+node.boot_lun = 00000000
+# END RECORD
+EOF
+
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-vlan b/src/settings/plugins/ibft/tests/iscsiadm-test-vlan
new file mode 100755
index 00000000..59b80bd0
--- /dev/null
+++ b/src/settings/plugins/ibft/tests/iscsiadm-test-vlan
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+cat << EOF
+# BEGIN RECORD 6.2.0.873-21
+iface.initiatorname = iqn.2010-04.org.ipxe:d05faa97-c4be-44f6-a723-efde9aa399a0
+iface.transport_name = tcp
+iface.hwaddress = 00:33:21:98:b9:f0
+iface.bootproto = STATIC
+iface.ipaddress = 192.168.6.200
+iface.subnet_mask = 255.255.255.0
+iface.vlan_id = 123
+iface.net_ifacename = eth0
+node.name = iqn.2003-01.org.x:disk1
+node.conn[0].address = 192.168.6.32
+node.conn[0].port = 3260
+node.boot_lun = 01000000
+# END RECORD
+EOF
+
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 09a37991..261dfea2 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -189,7 +189,7 @@ _secret_password_raw_to_bytes (const char *ifcfg_key,
 		password_raw += 2;
 
 	secret = nm_secret_buf_new (strlen (password_raw) / 2 + 3);
-	if (!_nm_utils_str2bin_full (password_raw, FALSE, ":", secret->bin, secret->len, &len)) {
+	if (!_nm_utils_hexstr2bin_full (password_raw, FALSE, FALSE, ":", 0, secret->bin, secret->len, &len)) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "Invalid hex password in %s",
 		             ifcfg_key);
@@ -2226,20 +2226,19 @@ make_sriov_setting (shvarFile *ifcfg)
 {
 	gs_unref_hashtable GHashTable *keys = NULL;
 	gs_unref_ptrarray GPtrArray *vfs = NULL;
-	NMTernary autoprobe_drivers;
+	int autoprobe_drivers;
 	NMSettingSriov *s_sriov;
-	int total_vfs;
+	gint64 total_vfs;
 
-	total_vfs = svGetValueInt64 (ifcfg, "SRIOV_TOTAL_VFS", 10, 0, G_MAXINT32, 0);
-	if (!total_vfs)
-		return NULL;
+
+	total_vfs = svGetValueInt64 (ifcfg, "SRIOV_TOTAL_VFS", 10, 0, G_MAXUINT32, -1);
 
 	autoprobe_drivers = svGetValueInt64 (ifcfg,
 	                                     "SRIOV_AUTOPROBE_DRIVERS",
 	                                     10,
-	                                     NM_TERNARY_FALSE,
+	                                     NM_TERNARY_DEFAULT,
 	                                     NM_TERNARY_TRUE,
-	                                     NM_TERNARY_DEFAULT);
+	                                     -2);
 
 	keys = svGetKeys (ifcfg, SV_KEY_TYPE_SRIOV_VF);
 	if (keys) {
@@ -2261,7 +2260,7 @@ make_sriov_setting (shvarFile *ifcfg)
 
 			key += NM_STRLEN ("SRIOV_VF");
 
-			vf = _nm_utils_sriov_vf_from_strparts (key, value, &error);
+			vf = _nm_utils_sriov_vf_from_strparts (key, value, TRUE, &error);
 			if (!vf) {
 				PARSE_WARNING ("ignoring invalid SR-IOV VF '%s %s': %s",
 				               key, value, error->message);
@@ -2273,11 +2272,21 @@ make_sriov_setting (shvarFile *ifcfg)
 		}
 	}
 
+	/* Create the setting when at least one key is set */
+	if (   total_vfs < 0
+	    && !vfs
+	    && autoprobe_drivers < NM_TERNARY_DEFAULT)
+		return NULL;
+
 	s_sriov = (NMSettingSriov *) nm_setting_sriov_new ();
+
+	autoprobe_drivers = NM_MAX (autoprobe_drivers, NM_TERNARY_DEFAULT);
+	total_vfs = NM_MAX (total_vfs, 0);
+
 	g_object_set (s_sriov,
-	              NM_SETTING_SRIOV_TOTAL_VFS, total_vfs,
+	              NM_SETTING_SRIOV_TOTAL_VFS, (guint) total_vfs,
 	              NM_SETTING_SRIOV_VFS, vfs,
-	              NM_SETTING_SRIOV_AUTOPROBE_DRIVERS, (int) autoprobe_drivers,
+	              NM_SETTING_SRIOV_AUTOPROBE_DRIVERS, autoprobe_drivers,
 	              NULL);
 
 	return (NMSetting *) s_sriov;
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 b70690cc..f5be7520 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -2223,16 +2223,15 @@ write_sriov_setting (NMConnection *connection, shvarFile *ifcfg)
 
 	svUnsetAll (ifcfg, SV_KEY_TYPE_SRIOV_VF);
 
-	s_sriov = NM_SETTING_SRIOV (nm_connection_get_setting (connection, NM_TYPE_SETTING_SRIOV));
-	if (s_sriov)
-		num = nm_setting_sriov_get_total_vfs (s_sriov);
-	if (num == 0) {
+	s_sriov = NM_SETTING_SRIOV (nm_connection_get_setting (connection,
+	                                                       NM_TYPE_SETTING_SRIOV));
+	if (!s_sriov) {
 		svUnsetValue (ifcfg, "SRIOV_TOTAL_VFS");
 		svUnsetValue (ifcfg, "SRIOV_AUTOPROBE_DRIVERS");
 		return;
 	}
 
-	svSetValueInt64 (ifcfg, "SRIOV_TOTAL_VFS", num);
+	svSetValueInt64 (ifcfg, "SRIOV_TOTAL_VFS", nm_setting_sriov_get_total_vfs (s_sriov));
 
 	b = nm_setting_sriov_get_autoprobe_drivers (s_sriov);
 	if (b != NM_TERNARY_DEFAULT)
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
index b66eedc7..f0e64d1d 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
@@ -282,8 +282,10 @@ initialize (NMSettingsPlugin *plugin)
 
 		g_hash_table_iter_init (&iter, priv->eni_ifaces);
 		while (g_hash_table_iter_next (&iter, NULL, (gpointer *) conn)) {
-			_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
-			                                                  NM_SETTINGS_CONNECTION (conn));
+			if (conn) {
+				_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
+				                                                  NM_SETTINGS_CONNECTION (conn));
+			}
 		}
 	}
 }
diff --git a/src/supplicant/nm-supplicant-config.c b/src/supplicant/nm-supplicant-config.c
index 043b5550..cfb33008 100644
--- a/src/supplicant/nm-supplicant-config.c
+++ b/src/supplicant/nm-supplicant-config.c
@@ -648,31 +648,28 @@ add_string_val (NMSupplicantConfig *self,
 
 static void
 wep128_passphrase_hash (const char *input,
-                        size_t input_len,
-                        guint8 *out_digest,
-                        size_t *out_digest_len)
+                        gsize input_len,
+                        guint8 *digest /* 13 bytes */)
 {
-	GChecksum *sum;
+	nm_auto_free_checksum GChecksum *sum = NULL;
+	guint8 md5[NM_UTILS_CHECKSUM_LENGTH_MD5];
 	guint8 data[64];
 	int i;
 
-	g_return_if_fail (out_digest != NULL);
-	g_return_if_fail (out_digest_len != NULL);
-	g_return_if_fail (*out_digest_len >= 16);
+	nm_assert (input);
+	nm_assert (input_len);
+	nm_assert (digest);
 
 	/* Get at least 64 bytes by repeating the passphrase into the buffer */
 	for (i = 0; i < sizeof (data); i++)
 		data[i] = input[i % input_len];
 
 	sum = g_checksum_new (G_CHECKSUM_MD5);
-	g_assert (sum);
 	g_checksum_update (sum, data, sizeof (data));
-	g_checksum_get_digest (sum, out_digest, out_digest_len);
-	g_checksum_free (sum);
+	nm_utils_checksum_get_digest (sum, md5);
 
-	g_assert (*out_digest_len == 16);
 	/* WEP104 keys are 13 bytes in length (26 hex characters) */
-	*out_digest_len = 13;
+	memcpy (digest, md5, 13);
 }
 
 static gboolean
@@ -682,9 +679,10 @@ add_wep_key (NMSupplicantConfig *self,
              NMWepKeyType wep_type,
              GError **error)
 {
-	size_t key_len = key ? strlen (key) : 0;
+	gsize key_len;
 
-	if (!key || !key_len)
+	if (   !key
+	    || (key_len = strlen (key)) == 0)
 		return TRUE;
 
 	if (wep_type == NM_WEP_KEY_TYPE_UNKNOWN) {
@@ -723,11 +721,10 @@ add_wep_key (NMSupplicantConfig *self,
 			return FALSE;
 		}
 	} else if (wep_type == NM_WEP_KEY_TYPE_PASSPHRASE) {
-		guint8 digest[16];
-		size_t digest_len = sizeof (digest);
+		guint8 digest[13];
 
-		wep128_passphrase_hash (key, key_len, digest, &digest_len);
-		if (!nm_supplicant_config_add_option (self, name, (const char *) digest, digest_len, "<hidden>", error))
+		wep128_passphrase_hash (key, key_len, digest);
+		if (!nm_supplicant_config_add_option (self, name, (const char *) digest, sizeof (digest), "<hidden>", error))
 			return FALSE;
 	}
 
@@ -747,6 +744,7 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 	NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE (self);
 	const char *key_mgmt, *key_mgmt_conf, *auth_alg;
 	const char *psk;
+	gboolean set_pmf;
 
 	g_return_val_if_fail (NM_IS_SUPPLICANT_CONFIG (self), FALSE);
 	g_return_val_if_fail (setting != NULL, FALSE);
@@ -834,13 +832,14 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 		pmf = NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE;
 
 	/* Check if we actually support PMF */
+	set_pmf = TRUE;
 	if (!priv->support_pmf) {
 		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");
 			return FALSE;
-		} else if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL)
-			pmf = NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE;
+		}
+		set_pmf = FALSE;
 	}
 
 	/* Only WPA-specific things when using WPA */
@@ -854,13 +853,14 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 		if (!ADD_STRING_LIST_VAL (self, setting, wireless_security, group, groups, "group", ' ', TRUE, NULL, error))
 			return FALSE;
 
-		if (   !nm_streq (key_mgmt, "wpa-none")
+		if (   set_pmf
+		    && !nm_streq (key_mgmt, "wpa-none")
 		    && NM_IN_SET (pmf,
-		                  NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL,
+		                  NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE,
 		                  NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED)) {
 			if (!nm_supplicant_config_add_option (self,
 			                                      "ieee80211w",
-			                                      pmf == NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL ? "1" : "2",
+			                                      pmf == NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE ? "0" : "2",
 			                                      -1,
 			                                      NULL,
 			                                      error))
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c
index 5237acb2..0af9ebdb 100644
--- a/src/supplicant/nm-supplicant-interface.c
+++ b/src/supplicant/nm-supplicant-interface.c
@@ -555,6 +555,26 @@ iface_check_netreply_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_
 	iface_check_ready (self);
 }
 
+static void
+iface_set_pmf_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	gs_unref_variant GVariant *variant = NULL;
+	gs_free_error GError *error = NULL;
+
+	variant = g_dbus_proxy_call_finish (proxy, result, &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+
+	/* This can fail if the supplicant doesn't support PMF */
+	if (error)
+		_LOGD ("failed to set Pmf=1: %s", error->message);
+
+	iface_check_ready (self);
+}
+
 NMSupplicantFeature
 nm_supplicant_interface_get_ap_support (NMSupplicantInterface *self)
 {
@@ -1155,9 +1175,23 @@ on_iface_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_
 	                   NULL,
 	                   NULL);
 
-	/* Check whether NetworkReply and AP mode are supported */
+	/* Initialize global PMF setting to 'optional' */
 	priv->ready_count = 1;
 	g_dbus_proxy_call (priv->iface_proxy,
+	                   DBUS_INTERFACE_PROPERTIES ".Set",
+	                   g_variant_new ("(ssv)",
+	                                  WPAS_DBUS_IFACE_INTERFACE,
+	                                  "Pmf",
+	                                  g_variant_new_string ("1")),
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   priv->init_cancellable,
+	                   (GAsyncReadyCallback) iface_set_pmf_cb,
+	                   self);
+
+	/* Check whether NetworkReply and AP mode are supported */
+	priv->ready_count++;
+	g_dbus_proxy_call (priv->iface_proxy,
 	                   "NetworkReply",
 	                   g_variant_new ("(oss)",
 	                                  "/fff",
diff --git a/src/supplicant/tests/test-supplicant-config.c b/src/supplicant/tests/test-supplicant-config.c
index 36831e67..d7ec1fe2 100644
--- a/src/supplicant/tests/test-supplicant-config.c
+++ b/src/supplicant/tests/test-supplicant-config.c
@@ -359,8 +359,8 @@ test_wifi_wpa_psk (const char *detail,
 	NMTST_EXPECT_NM_INFO ("Config: added 'pairwise' value 'TKIP CCMP'");
 	NMTST_EXPECT_NM_INFO ("Config: added 'group' value 'TKIP CCMP'");
 	switch (pmf) {
-	case NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL:
-		NMTST_EXPECT_NM_INFO ("Config: added 'ieee80211w' value '1'");
+	case NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE:
+		NMTST_EXPECT_NM_INFO ("Config: added 'ieee80211w' value '0'");
 		break;
 	case NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED:
 		NMTST_EXPECT_NM_INFO ("Config: added 'ieee80211w' value '2'");
diff --git a/src/systemd/meson.build b/src/systemd/meson.build
index 870721b0..1bf1ea41 100644
--- a/src/systemd/meson.build
+++ b/src/systemd/meson.build
@@ -49,7 +49,8 @@ sources = files(
   'src/libsystemd/sd-id128/id128-util.c',
   'src/libsystemd/sd-id128/sd-id128.c',
   'src/shared/dns-domain.c',
-  'nm-sd.c'
+  'nm-sd.c',
+  'nm-sd-utils.c',
 )
 
 incs = [
diff --git a/src/systemd/nm-sd-utils.c b/src/systemd/nm-sd-utils.c
new file mode 100644
index 00000000..c6c4c123
--- /dev/null
+++ b/src/systemd/nm-sd-utils.c
@@ -0,0 +1,61 @@
+/* This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-sd-utils.h"
+
+#include "nm-core-internal.h"
+
+#include "nm-sd-adapt.h"
+
+#include "path-util.h"
+#include "sd-id128.h"
+
+/*****************************************************************************/
+
+gboolean
+nm_sd_utils_path_equal (const char *a, const char *b)
+{
+	return path_equal (a, b);
+}
+
+char *
+nm_sd_utils_path_simplify (char *path, gboolean kill_dots)
+{
+	return path_simplify (path, kill_dots);
+}
+
+const char *
+nm_sd_utils_path_startswith (const char *path, const char *prefix)
+{
+	return path_startswith (path, prefix);
+}
+
+/*****************************************************************************/
+
+NMUuid *
+nm_sd_utils_id128_get_machine (NMUuid *out_uuid)
+{
+	g_assert (out_uuid);
+
+	G_STATIC_ASSERT_EXPR (sizeof (*out_uuid) == sizeof (sd_id128_t));
+	if (sd_id128_get_machine ((sd_id128_t *) out_uuid) < 0)
+		return NULL;
+	return out_uuid;
+}
diff --git a/src/systemd/nm-sd-utils.h b/src/systemd/nm-sd-utils.h
new file mode 100644
index 00000000..0af514eb
--- /dev/null
+++ b/src/systemd/nm-sd-utils.h
@@ -0,0 +1,38 @@
+/* This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_SD_UTILS_H__
+#define __NM_SD_UTILS_H__
+
+/*****************************************************************************/
+
+gboolean nm_sd_utils_path_equal (const char *a, const char *b);
+
+char *nm_sd_utils_path_simplify (char *path, gboolean kill_dots);
+
+const char *nm_sd_utils_path_startswith (const char *path, const char *prefix);
+
+/*****************************************************************************/
+
+struct _NMUuid;
+
+struct _NMUuid *nm_sd_utils_id128_get_machine (struct _NMUuid *out_uuid);
+
+/*****************************************************************************/
+
+#endif /* __NM_SD_UTILS_H__ */
diff --git a/src/systemd/src/basic/path-util.c b/src/systemd/src/basic/path-util.c
index 5202eae3..3656a011 100644
--- a/src/systemd/src/basic/path-util.c
+++ b/src/systemd/src/basic/path-util.c
@@ -380,7 +380,6 @@ char *path_simplify(char *path, bool kill_dots) {
         return path;
 }
 
-#if 0 /* NM_IGNORED */
 char* path_startswith(const char *path, const char *prefix) {
         assert(path);
         assert(prefix);
@@ -423,7 +422,6 @@ char* path_startswith(const char *path, const char *prefix) {
                 prefix += b;
         }
 }
-#endif /* NM_IGNORED */
 
 int path_compare(const char *a, const char *b) {
         int d;
diff --git a/src/systemd/src/libsystemd-network/dhcp-identifier.c b/src/systemd/src/libsystemd-network/dhcp-identifier.c
index f18713e8..b1afbf00 100644
--- a/src/systemd/src/libsystemd-network/dhcp-identifier.c
+++ b/src/systemd/src/libsystemd-network/dhcp-identifier.c
@@ -25,13 +25,19 @@
 #define APPLICATION_ID SD_ID128_MAKE(a5,0a,d1,12,bf,60,45,77,a2,fb,74,1a,b1,95,5b,03)
 #define USEC_2000       ((usec_t) 946684800000000) /* 2000-01-01 00:00:00 UTC */
 
-int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len) {
+int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len, bool strict) {
         struct duid d;
 
         assert_cc(sizeof(d.raw) >= MAX_DUID_LEN);
         if (duid_len > MAX_DUID_LEN)
                 return -EINVAL;
 
+        if (!strict) {
+                /* Strict validation is not requested. We only ensure that the
+                 * DUID is not too long. */
+                return 0;
+        }
+
         switch (duid_type) {
         case DUID_TYPE_LLT:
                 if (duid_len <= sizeof(d.llt))
diff --git a/src/systemd/src/libsystemd-network/dhcp-identifier.h b/src/systemd/src/libsystemd-network/dhcp-identifier.h
index 64315d3a..e6834039 100644
--- a/src/systemd/src/libsystemd-network/dhcp-identifier.h
+++ b/src/systemd/src/libsystemd-network/dhcp-identifier.h
@@ -52,7 +52,7 @@ struct duid {
         };
 } _packed_;
 
-int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len);
+int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len, bool strict);
 int dhcp_identifier_set_duid_llt(struct duid *duid, usec_t t, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len);
 int dhcp_identifier_set_duid_ll(struct duid *duid, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len);
 int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len);
diff --git a/src/systemd/src/libsystemd-network/dhcp6-option.c b/src/systemd/src/libsystemd-network/dhcp6-option.c
index ff1cbf13..22970443 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-option.c
+++ b/src/systemd/src/libsystemd-network/dhcp6-option.c
@@ -105,7 +105,7 @@ int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
                 return -EINVAL;
         }
 
-        if (*buflen < len)
+        if (*buflen < offsetof(DHCP6Option, data) + len)
                 return -ENOBUFS;
 
         ia_hdr = *buf;
@@ -465,13 +465,15 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) {
 
                 case SD_DHCP6_OPTION_STATUS_CODE:
 
-                        status = dhcp6_option_parse_status(option, optlen);
-                        if (status) {
+                        status = dhcp6_option_parse_status(option, optlen + sizeof(DHCP6Option));
+                        if (status < 0) {
+                                r = status;
+                                goto error;
+                        }
+                        if (status > 0) {
                                 log_dhcp6_client(client, "IA status %d",
                                                  status);
 
-                                dhcp6_lease_free_ia(ia);
-
                                 r = -EINVAL;
                                 goto error;
                         }
@@ -553,6 +555,7 @@ int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char *
                 bool first = true;
 
                 for (;;) {
+                        const char *label;
                         uint8_t c;
 
                         c = optval[pos++];
@@ -560,47 +563,41 @@ int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char *
                         if (c == 0)
                                 /* End of name */
                                 break;
-                        else if (c <= 63) {
-                                const char *label;
-
-                                /* Literal label */
-                                label = (const char *)&optval[pos];
-                                pos += c;
-                                if (pos >= optlen)
-                                        return -EMSGSIZE;
-
-                                if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) {
-                                        r = -ENOMEM;
-                                        goto fail;
-                                }
-
-                                if (first)
-                                        first = false;
-                                else
-                                        ret[n++] = '.';
-
-                                r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX);
-                                if (r < 0)
-                                        goto fail;
-
-                                n += r;
-                                continue;
-                        } else {
-                                r = -EBADMSG;
-                                goto fail;
-                        }
-                }
+                        if (c > 63)
+                                return -EBADMSG;
+
+                        /* Literal label */
+                        label = (const char *)&optval[pos];
+                        pos += c;
+                        if (pos >= optlen)
+                                return -EMSGSIZE;
+
+                        if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
+                                return -ENOMEM;
+
+                        if (first)
+                                first = false;
+                        else
+                                ret[n++] = '.';
 
-                if (!GREEDY_REALLOC(ret, allocated, n + 1)) {
-                        r = -ENOMEM;
-                        goto fail;
+                        r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX);
+                        if (r < 0)
+                                return r;
+
+                        n += r;
                 }
 
+                if (n == 0)
+                        continue;
+
+                if (!GREEDY_REALLOC(ret, allocated, n + 1))
+                        return -ENOMEM;
+
                 ret[n] = 0;
 
                 r = strv_extend(&names, ret);
                 if (r < 0)
-                        goto fail;
+                        return r;
 
                 idx++;
         }
@@ -608,7 +605,4 @@ int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char *
         *str_arr = TAKE_PTR(names);
 
         return idx;
-
-fail:
-        return r;
 }
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-client.c b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
index 42707f10..0c385a84 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
@@ -301,27 +301,22 @@ int sd_dhcp_client_set_client_id(
         assert_return(data_len > 0 && data_len <= MAX_CLIENT_ID_LEN, -EINVAL);
         G_STATIC_ASSERT_EXPR (_NM_SD_MAX_CLIENT_ID_LEN == MAX_CLIENT_ID_LEN);
 
-        switch (type) {
-
-        case ARPHRD_ETHER:
-                if (data_len != ETH_ALEN)
-                        return -EINVAL;
-                break;
-
-        case ARPHRD_INFINIBAND:
-                if (data_len != INFINIBAND_ALEN)
-                        return -EINVAL;
-                break;
-
-        default:
-                break;
-        }
-
         if (client->client_id_len == data_len + sizeof(client->client_id.type) &&
             client->client_id.type == type &&
             memcmp(&client->client_id.raw.data, data, data_len) == 0)
                 return 0;
 
+        /* For hardware types, log debug message about unexpected data length.
+         *
+         * Note that infiniband's INFINIBAND_ALEN is 20 bytes long, but only
+         * last last 8 bytes of the address are stable and suitable to put into
+         * the client-id. The caller is advised to account for that. */
+        if ((type == ARPHRD_ETHER && data_len != ETH_ALEN) ||
+            (type == ARPHRD_INFINIBAND && data_len != 8))
+                log_dhcp_client(client, "Changing client ID to hardware type %u with "
+                                "unexpected address length %zu",
+                                type, data_len);
+
         if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) {
                 log_dhcp_client(client, "Changing client ID on running DHCP "
                                 "client, restarting");
@@ -362,7 +357,7 @@ static int dhcp_client_set_iaid_duid_internal(
         assert_return(duid_len == 0 || duid != NULL, -EINVAL);
 
         if (duid != NULL) {
-                r = dhcp_validate_duid_len(duid_type, duid_len);
+                r = dhcp_validate_duid_len(duid_type, duid_len, true);
                 if (r < 0)
                         return r;
         }
@@ -1688,6 +1683,8 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i
                         client->timeout_resend =
                                 sd_event_source_unref(client->timeout_resend);
 
+                        client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED);
+
                         r = client_initialize(client);
                         if (r < 0)
                                 goto error;
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
index d2402595..cac07d3e 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
@@ -279,6 +279,8 @@ sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease) {
                 free(option);
         }
 
+        free(lease->root_path);
+        free(lease->timezone);
         free(lease->hostname);
         free(lease->domainname);
         free(lease->dns);
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c b/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
index 8444a750..f0cd2f97 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
@@ -197,9 +197,13 @@ static int dhcp6_client_set_duid_internal(
         assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
 
         if (duid != NULL) {
-                r = dhcp_validate_duid_len(duid_type, duid_len);
-                if (r < 0)
-                        return r;
+                r = dhcp_validate_duid_len(duid_type, duid_len, true);
+                if (r < 0) {
+                        r = dhcp_validate_duid_len(duid_type, duid_len, false);
+                        if (r < 0)
+                                return r;
+                        log_dhcp6_client(client, "Setting DUID of type %u with unexpected content", duid_type);
+                }
 
                 client->duid.type = htobe16(duid_type);
                 memcpy(&client->duid.raw.data, duid, duid_len);
@@ -818,8 +822,8 @@ static int client_parse_message(
                 uint8_t *optval;
                 be32_t iaid_lease;
 
-                if (len < offsetof(DHCP6Option, data) ||
-                    len < offsetof(DHCP6Option, data) + be16toh(option->len))
+                if (len < pos + offsetof(DHCP6Option, data) ||
+                    len < pos + offsetof(DHCP6Option, data) + be16toh(option->len))
                         return -ENOBUFS;
 
                 optcode = be16toh(option->code);
@@ -870,13 +874,14 @@ static int client_parse_message(
                         break;
 
                 case SD_DHCP6_OPTION_STATUS_CODE:
-                        status = dhcp6_option_parse_status(option, optlen);
-                        if (status) {
+                        status = dhcp6_option_parse_status(option, optlen + sizeof(DHCP6Option));
+                        if (status < 0)
+                                return status;
+
+                        if (status > 0) {
                                 log_dhcp6_client(client, "%s Status %s",
                                                  dhcp6_message_type_to_string(message->type),
                                                  dhcp6_message_status_to_string(status));
-                                dhcp6_lease_free_ia(&lease->ia);
-                                dhcp6_lease_free_ia(&lease->pd);
 
                                 return -EINVAL;
                         }
@@ -1274,6 +1279,7 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
                 log_dhcp6_client(client, "T1 expires in %s",
                                  format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC));
 
+                client->lease->ia.timeout_t1 = sd_event_source_unref(client->lease->ia.timeout_t1);
                 r = sd_event_add_time(client->event,
                                       &client->lease->ia.timeout_t1,
                                       clock_boottime_or_monotonic(), time_now + timeout,
@@ -1296,6 +1302,7 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) {
                 log_dhcp6_client(client, "T2 expires in %s",
                                  format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC));
 
+                client->lease->ia.timeout_t2 = sd_event_source_unref(client->lease->ia.timeout_t2);
                 r = sd_event_add_time(client->event,
                                       &client->lease->ia.timeout_t2,
                                       clock_boottime_or_monotonic(), time_now + timeout,
diff --git a/src/tests/config/test-config.c b/src/tests/config/test-config.c
index 75fef4fa..20a05df1 100644
--- a/src/tests/config/test-config.c
+++ b/src/tests/config/test-config.c
@@ -25,6 +25,7 @@
 #include "nm-config.h"
 #include "nm-test-device.h"
 #include "platform/nm-fake-platform.h"
+#include "dhcp/nm-dhcp-manager.h"
 #include "nm-dbus-manager.h"
 #include "nm-connectivity.h"
 
@@ -123,6 +124,24 @@ setup_config (GError **error, const char *config_file, const char *intern_config
 		g_assert_no_error (local_error);
 	}
 	nm_config_cmd_line_options_free (cli);
+
+	if (config) {
+		NMDhcpManager *dhcp_manager;
+		gpointer logging_old_state;
+
+		logging_old_state = nmtst_logging_disable (FALSE);
+
+		dhcp_manager = nm_dhcp_manager_get ();
+		g_test_assert_expected_messages ();
+
+		nmtst_logging_reenable (logging_old_state);
+
+		g_object_set_data_full (G_OBJECT (config),
+		                        "nmtst-config-keep-dhcp-manager-alive",
+		                        dhcp_manager,
+		                        nmtst_dhcp_manager_unget);
+	}
+
 	return config;
 }
 
diff --git a/src/tests/test-general.c b/src/tests/test-general.c
index 1e7329d1..9dce9435 100644
--- a/src/tests/test-general.c
+++ b/src/tests/test-general.c
@@ -22,12 +22,19 @@
 
 #include <string.h>
 #include <errno.h>
+#include <net/if.h>
+#include <byteswap.h>
 
 /* need math.h for isinf() and INFINITY. No need to link with -lm */
 #include <math.h>
 
 #include "NetworkManagerUtils.h"
 #include "nm-core-internal.h"
+#include "nm-core-utils.h"
+#include "systemd/nm-sd-utils.h"
+
+#include "dns/nm-dns-manager.h"
+#include "nm-connectivity.h"
 
 #include "nm-test-utils-core.h"
 
@@ -1093,7 +1100,7 @@ static NMMatchSpecMatchType
 _test_match_spec_device (const GSList *specs, const char *match_str)
 {
 	if (match_str && g_str_has_prefix (match_str, MATCH_S390))
-		return nm_match_spec_device (specs, NULL, NULL, NULL, NULL, NULL, &match_str[NM_STRLEN (MATCH_S390)]);
+		return nm_match_spec_device (specs, NULL, NULL, NULL, NULL, NULL, &match_str[NM_STRLEN (MATCH_S390)], NULL);
 	if (match_str && g_str_has_prefix (match_str, MATCH_DRIVER)) {
 		gs_free char *s = g_strdup (&match_str[NM_STRLEN (MATCH_DRIVER)]);
 		char *t;
@@ -1103,9 +1110,9 @@ _test_match_spec_device (const GSList *specs, const char *match_str)
 			t[0] = '\0';
 			t++;
 		}
-		return nm_match_spec_device (specs, NULL, NULL, s, t, NULL, NULL);
+		return nm_match_spec_device (specs, NULL, NULL, s, t, NULL, NULL, NULL);
 	}
-	return nm_match_spec_device (specs, match_str, NULL, NULL, NULL, NULL, NULL);
+	return nm_match_spec_device (specs, match_str, NULL, NULL, NULL, NULL, NULL, NULL);
 }
 
 static void
@@ -1226,6 +1233,10 @@ test_match_spec_device (void)
 	                            S ("em", "em\\", "em\\*", "em\\1", "em\\11", "em\\2", "em1", "em11", "em2", "em3"),
 	                            NULL,
 	                            S ("em*"));
+	_do_test_match_spec_device ("except:interface-name:em*",
+	                            S ("", "eth", "eth1", "e1"),
+	                            S (NULL),
+	                            S ("em", "em\\", "em\\*", "em\\1", "em\\11", "em\\2", "em1", "em11", "em2", "em3"));
 	_do_test_match_spec_device ("aa,bb,cc\\,dd,e,,",
 	                            S ("aa", "bb", "cc,dd", "e"),
 	                            NULL,
@@ -1298,7 +1309,8 @@ _do_test_match_spec_config (const char *file, int line, const char *spec_str, gu
 	if (expected != match_result)
 		g_error ("%s:%d: faild comparing \"%s\" with %u.%u.%u. Expected %d, but got %d", file, line, spec_str, v_maj, v_min, v_mic, (int) expected, (int) match_result);
 
-	if (g_slist_length (specs) == 1 && match_result != NM_MATCH_SPEC_NEG_MATCH) {
+	if (   g_slist_length (specs) == 1
+	    && !g_str_has_prefix (specs->data, "except:")) {
 		/* there is only one spec in the list... test that we match except: */
 		char *sss = g_strdup_printf ("except:%s", (char *) specs->data);
 		GSList *specs2 = g_slist_append (NULL, sss);
@@ -1306,7 +1318,7 @@ _do_test_match_spec_config (const char *file, int line, const char *spec_str, gu
 
 		match_result2 = nm_match_spec_config (specs2, version, NULL);
 		if (match_result == NM_MATCH_SPEC_NO_MATCH)
-			g_assert_cmpint (match_result2, ==, NM_MATCH_SPEC_NO_MATCH);
+			g_assert_cmpint (match_result2, ==, NM_MATCH_SPEC_MATCH);
 		else
 			g_assert_cmpint (match_result2, ==, NM_MATCH_SPEC_NEG_MATCH);
 
@@ -1390,7 +1402,7 @@ test_match_spec_config (void)
 	do_test_match_spec_config ("nm-version-max:1", 1, 4, 30, NM_MATCH_SPEC_MATCH);
 	do_test_match_spec_config ("nm-version-max:1", 2, 4, 30, NM_MATCH_SPEC_NO_MATCH);
 
-	do_test_match_spec_config ("except:nm-version:1.4.8", 1, 6, 0, NM_MATCH_SPEC_NO_MATCH);
+	do_test_match_spec_config ("except:nm-version:1.4.8", 1, 6, 0, NM_MATCH_SPEC_MATCH);
 	do_test_match_spec_config ("nm-version-min:1.6,except:nm-version:1.4.8", 1, 6, 0, NM_MATCH_SPEC_MATCH);
 
 	do_test_match_spec_config ("nm-version-min:1.6,nm-version-min:1.4.6,nm-version-min:1.2.16,except:nm-version:1.4.8", 1, 2, 0, NM_MATCH_SPEC_NO_MATCH);
@@ -1733,7 +1745,7 @@ do_test_stable_id_parse (const char *stable_id,
 	else
 		g_assert (stable_id);
 
-	stable_type = nm_utils_stable_id_parse (stable_id, "_DEVICE", "_BOOT", "_CONNECTION", &generated);
+	stable_type = nm_utils_stable_id_parse (stable_id, "_DEVICE", "_MAC", "_BOOT", "_CONNECTION", &generated);
 
 	g_assert_cmpint (expected_stable_type, ==, stable_type);
 
@@ -1772,6 +1784,7 @@ test_stable_id_parse (void)
 	_parse_generated ("x${BOOT}", "x${BOOT}=5{_BOOT}");
 	_parse_generated ("x${BOOT}${CONNECTION}", "x${BOOT}=5{_BOOT}${CONNECTION}=11{_CONNECTION}");
 	_parse_generated ("xX${BOOT}yY${CONNECTION}zZ", "xX${BOOT}=5{_BOOT}yY${CONNECTION}=11{_CONNECTION}zZ");
+	_parse_generated ("${MAC}x", "${MAC}=4{_MAC}x");
 	_parse_random ("${RANDOM}");
 	_parse_random (" ${RANDOM}");
 	_parse_random ("${BOOT}${RANDOM}");
@@ -1845,6 +1858,223 @@ test_nm_utils_exp10 (void)
 
 /*****************************************************************************/
 
+#define _TEST_RC(searches, nameservers, options, expected) \
+	G_STMT_START { \
+		const char *const*const _searches = (searches); \
+		const char *const*const _nameservers = (nameservers); \
+		const char *const*const _options = (options); \
+		gs_free char *_content = NULL; \
+		\
+		_content = nmtst_dns_create_resolv_conf (_searches, _nameservers, _options); \
+		g_assert_cmpstr (_content, ==, expected); \
+	} G_STMT_END
+
+static void
+test_dns_create_resolv_conf (void)
+{
+	_TEST_RC (NM_MAKE_STRV ("a"),
+	          NULL,
+	          NULL,
+	          "# Generated by NetworkManager\n"
+	          "search a\n"
+	          "");
+
+	_TEST_RC (NM_MAKE_STRV ("a", "b.com"),
+	          NM_MAKE_STRV ("192.168.55.1", "192.168.56.1"),
+	          NM_MAKE_STRV ("opt1", "opt2"),
+	          "# Generated by NetworkManager\n"
+	          "search a b.com\n"
+	          "nameserver 192.168.55.1\n"
+	          "nameserver 192.168.56.1\n"
+	          "options opt1 opt2\n"
+	          "");
+
+	_TEST_RC (NM_MAKE_STRV ("a2x456789.b2x456789.c2x456789.d2x456789.e2x456789.f2x456789.g2x456789.h2x456789.i2x456789.j2x4567890",
+	                        "a2y456789.b2y456789.c2y456789.d2y456789.e2y456789.f2y456789.g2y456789.h2y456789.i2y456789.j2y4567890",
+	                        "a2z456789.b2z456789.c2z456789.d2z456789.e2z456789.f2z456789.g2z456789.h2z456789.i2z456789.j2z4567890"),
+	          NULL,
+	          NULL,
+	          "# Generated by NetworkManager\n"
+	          "search a2x456789.b2x456789.c2x456789.d2x456789.e2x456789.f2x456789.g2x456789.h2x456789.i2x456789.j2x4567890 a2y456789.b2y456789.c2y456789.d2y456789.e2y456789.f2y456789.g2y456789.h2y456789.i2y456789.j2y4567890                                                        a2z456789.b2z456789.c2z456789.d2z456789.e2z456789.f2z456789.g2z456789.h2z456789.i2z456789.j2z4567890\n"
+	          "");
+
+}
+
+/*****************************************************************************/
+
+static void
+test_machine_id_read (void)
+{
+	NMUuid machine_id_sd;
+	const NMUuid *machine_id;
+	char machine_id_str[33];
+	gpointer logstate;
+
+	logstate = nmtst_logging_disable (FALSE);
+	/* If you run this test as root, without a valid /etc/machine-id,
+	 * the code will try to get the secret-key. That is a bit ugly,
+	 * but no real problem. */
+	machine_id = nm_utils_machine_id_bin ();
+	nmtst_logging_reenable (logstate);
+
+	g_assert (machine_id);
+	g_assert (_nm_utils_bin2hexstr_full (machine_id,
+	                                     sizeof (NMUuid),
+	                                     '\0',
+	                                     FALSE,
+	                                     machine_id_str) == machine_id_str);
+	g_assert (strlen (machine_id_str) == 32);
+	g_assert_cmpstr (machine_id_str, ==, nm_utils_machine_id_str ());
+
+	/* double check with systemd's implementation... */
+	if (!nm_sd_utils_id128_get_machine (&machine_id_sd)) {
+		/* if systemd failed to read /etc/machine-id, the file likely
+		 * is invalid. Our machine-id is fake, and we have nothing to
+		 * compare against. */
+
+		/* NOTE: this test will fail, if you don't have /etc/machine-id,
+		 * but a valid "LOCALSTATEDIR/lib/dbus/machine-id" file.
+		 * Just don't do that. */
+		g_assert (nm_utils_machine_id_is_fake ());
+	} else {
+		g_assert (!nm_utils_machine_id_is_fake ());
+		g_assert_cmpmem (&machine_id_sd, sizeof (NMUuid), machine_id, 16);
+	}
+}
+
+/*****************************************************************************/
+
+static void
+test_nm_utils_dhcp_client_id_systemd_node_specific (gconstpointer test_data)
+{
+	const int TEST_IDX = GPOINTER_TO_INT (test_data);
+	const guint8 HASH_KEY[16] = { 0x80, 0x11, 0x8c, 0xc2, 0xfe, 0x4a, 0x03, 0xee, 0x3e, 0xd6, 0x0c, 0x6f, 0x36, 0x39, 0x14, 0x09 };
+	const guint16 duid_type_en = htons (2);
+	const guint32 systemd_pen = htonl (43793);
+	const struct {
+		NMUuid machine_id;
+		const char *ifname;
+		guint64 ifname_hash_1;
+		guint32 iaid_ifname;
+		guint64 duid_id;
+	} d_array[] = {
+		[0] = {
+			.machine_id = { 0xcb, 0xc2, 0x2e, 0x47, 0x41, 0x8e, 0x40, 0x2a, 0xa7, 0xb3, 0x0d, 0xea, 0x92, 0x83, 0x94, 0xef },
+			.ifname = "lo",
+			.ifname_hash_1 = 0x7297085c2b12c911llu,
+			.iaid_ifname = htobe32 (0x5985c14du),
+			.duid_id = htobe64 (0x3d769bb2c14d29e1u),
+		},
+		[1] = {
+			.machine_id = { 0x11, 0x4e, 0xb4, 0xda, 0xd3, 0x22, 0x4a, 0xff, 0x9f, 0xc3, 0x30, 0x83, 0x38, 0xa0, 0xeb, 0xb7 },
+			.ifname = "eth0",
+			.ifname_hash_1 = 0x9e1cb083b54cd7b6llu,
+			.iaid_ifname = htobe32 (0x2b506735u),
+			.duid_id = htobe64 (0x551572e0f2a2a10fu),
+		},
+	};
+	int i;
+	typeof (d_array[0]) *d = &d_array[TEST_IDX];
+	gint64 u64;
+	gint32 u32;
+
+	/* the test already hard-codes the expected values iaid_ifname and duid_id
+	 * above. Still, redo the steps to derive them from the ifname/machine-id
+	 * and double check. */
+	u64 = c_siphash_hash (HASH_KEY, (const guint8 *) d->ifname, strlen (d->ifname));
+	g_assert_cmpint (u64, ==, d->ifname_hash_1);
+	u32 = be32toh ((u64 & 0xffffffffu) ^ (u64 >> 32));
+	g_assert_cmpint (u32, ==, d->iaid_ifname);
+
+	u64 = htole64 (c_siphash_hash (HASH_KEY, (const guint8 *) &d->machine_id, sizeof (d->machine_id)));
+	g_assert_cmpint (u64, ==, d->duid_id);
+
+	for (i = 0; i < 2; i++) {
+		const gboolean legacy_unstable_byteorder = (i != 0);
+		gs_unref_bytes GBytes *client_id = NULL;
+		const guint8 *cid;
+		guint32 iaid = d->iaid_ifname;
+
+		client_id = nm_utils_dhcp_client_id_systemd_node_specific_full (legacy_unstable_byteorder,
+		                                                                (const guint8 *) d->ifname,
+		                                                                strlen (d->ifname),
+		                                                                (const guint8 *) &d->machine_id,
+		                                                                sizeof (d->machine_id));
+
+		g_assert (client_id);
+		g_assert_cmpint (g_bytes_get_size (client_id), ==, 19);
+		cid = g_bytes_get_data (client_id, NULL);
+		g_assert_cmpint (cid[0], ==, 255);
+#if __BYTE_ORDER == __BIG_ENDIAN
+		if (legacy_unstable_byteorder) {
+			/* on non-little endian, the legacy behavior is to have the bytes
+			 * swapped. */
+			iaid = bswap_32 (iaid);
+		}
+#endif
+		g_assert_cmpmem (&cid[1], 4, &iaid, sizeof (iaid));
+		g_assert_cmpmem (&cid[5], 2, &duid_type_en, sizeof (duid_type_en));
+		g_assert_cmpmem (&cid[7], 4, &systemd_pen, sizeof (systemd_pen));
+		g_assert_cmpmem (&cid[11], 8, &d->duid_id, sizeof (d->duid_id));
+	}
+}
+
+/*****************************************************************************/
+
+static void
+test_connectivity_state_cmp (void)
+{
+	NMConnectivityState a;
+
+#define _cmp(a, b, cmp) \
+	G_STMT_START { \
+		const NMConnectivityState _a = (a); \
+		const NMConnectivityState _b = (b); \
+		const int _cmp = (cmp); \
+		\
+		g_assert (NM_IN_SET (_cmp, -1, 0, 1)); \
+		g_assert_cmpint (nm_connectivity_state_cmp (_a, _b), ==, _cmp); \
+		g_assert_cmpint (nm_connectivity_state_cmp (_b, _a), ==, -_cmp); \
+	} G_STMT_END
+
+	for (a = NM_CONNECTIVITY_UNKNOWN; a <= NM_CONNECTIVITY_FULL; a++)
+		_cmp (a, a, 0);
+
+	_cmp (NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_UNKNOWN,  0);
+	_cmp (NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_NONE,    -1);
+	_cmp (NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_LIMITED, -1);
+	_cmp (NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_PORTAL,  -1);
+	_cmp (NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_FULL,    -1);
+
+	_cmp (NM_CONNECTIVITY_NONE,    NM_CONNECTIVITY_UNKNOWN,  1);
+	_cmp (NM_CONNECTIVITY_NONE,    NM_CONNECTIVITY_NONE,     0);
+	_cmp (NM_CONNECTIVITY_NONE,    NM_CONNECTIVITY_LIMITED, -1);
+	_cmp (NM_CONNECTIVITY_NONE,    NM_CONNECTIVITY_PORTAL,  -1);
+	_cmp (NM_CONNECTIVITY_NONE,    NM_CONNECTIVITY_FULL,    -1);
+
+	_cmp (NM_CONNECTIVITY_LIMITED, NM_CONNECTIVITY_UNKNOWN,  1);
+	_cmp (NM_CONNECTIVITY_LIMITED, NM_CONNECTIVITY_NONE,     1);
+	_cmp (NM_CONNECTIVITY_LIMITED, NM_CONNECTIVITY_LIMITED,  0);
+	_cmp (NM_CONNECTIVITY_LIMITED, NM_CONNECTIVITY_PORTAL,  -1);
+	_cmp (NM_CONNECTIVITY_LIMITED, NM_CONNECTIVITY_FULL,    -1);
+
+	_cmp (NM_CONNECTIVITY_PORTAL,  NM_CONNECTIVITY_UNKNOWN,  1);
+	_cmp (NM_CONNECTIVITY_PORTAL,  NM_CONNECTIVITY_NONE,     1);
+	_cmp (NM_CONNECTIVITY_PORTAL,  NM_CONNECTIVITY_LIMITED,  1);
+	_cmp (NM_CONNECTIVITY_PORTAL,  NM_CONNECTIVITY_PORTAL,   0);
+	_cmp (NM_CONNECTIVITY_PORTAL,  NM_CONNECTIVITY_FULL,    -1);
+
+	_cmp (NM_CONNECTIVITY_FULL,    NM_CONNECTIVITY_UNKNOWN,  1);
+	_cmp (NM_CONNECTIVITY_FULL,    NM_CONNECTIVITY_NONE,     1);
+	_cmp (NM_CONNECTIVITY_FULL,    NM_CONNECTIVITY_LIMITED,  1);
+	_cmp (NM_CONNECTIVITY_FULL,    NM_CONNECTIVITY_PORTAL,   1);
+	_cmp (NM_CONNECTIVITY_FULL,    NM_CONNECTIVITY_FULL,     0);
+
+#undef _cmp
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE ();
 
 int
@@ -1891,6 +2121,15 @@ main (int argc, char **argv)
 	g_test_add_func ("/general/stable-id/parse", test_stable_id_parse);
 	g_test_add_func ("/general/stable-id/generated-complete", test_stable_id_generated_complete);
 
+	g_test_add_func ("/general/machine-id/read", test_machine_id_read);
+
+	g_test_add_func ("/general/test_dns_create_resolv_conf", test_dns_create_resolv_conf);
+
+	g_test_add_data_func ("/general/nm_utils_dhcp_client_id_systemd_node_specific/0", GINT_TO_POINTER (0), test_nm_utils_dhcp_client_id_systemd_node_specific);
+	g_test_add_data_func ("/general/nm_utils_dhcp_client_id_systemd_node_specific/1", GINT_TO_POINTER (1), test_nm_utils_dhcp_client_id_systemd_node_specific);
+
+	g_test_add_func ("/core/general/test_connectivity_state_cmp", test_connectivity_state_cmp);
+
 	return g_test_run ();
 }
 
diff --git a/src/tests/test-ip6-config.c b/src/tests/test-ip6-config.c
index a03d89b0..5b255068 100644
--- a/src/tests/test-ip6-config.c
+++ b/src/tests/test-ip6-config.c
@@ -22,6 +22,7 @@
 
 #include <string.h>
 #include <arpa/inet.h>
+#include <linux/if_addr.h>
 
 #include "nm-ip6-config.h"
 
diff --git a/src/tests/test-systemd.c b/src/tests/test-systemd.c
index ab5fed22..4660bd0d 100644
--- a/src/tests/test-systemd.c
+++ b/src/tests/test-systemd.c
@@ -20,6 +20,7 @@
 #include "nm-default.h"
 
 #include "systemd/nm-sd.h"
+#include "systemd/nm-sd-utils.h"
 
 #include "nm-test-utils-core.h"
 
@@ -173,6 +174,50 @@ test_sd_event (void)
 
 /*****************************************************************************/
 
+static void
+test_path_equal (void)
+{
+#define _path_equal_check1(path, kill_dots, expected) \
+	G_STMT_START { \
+		const gboolean _kill_dots = (kill_dots); \
+		const char *_path0 = (path); \
+		const char *_expected = (expected); \
+		gs_free char *_path = g_strdup (_path0); \
+		const char *_path_result; \
+		\
+		if (   !_kill_dots \
+		    && !nm_sd_utils_path_equal (_path0, _expected)) \
+			g_error ("Paths \"%s\" and \"%s\" don't compare equal", _path0, _expected); \
+		\
+		_path_result = nm_sd_utils_path_simplify (_path, _kill_dots); \
+		g_assert (_path_result == _path); \
+		g_assert_cmpstr (_path, ==, _expected); \
+	} G_STMT_END
+
+#define _path_equal_check(path, expected_no_kill_dots, expected_kill_dots) \
+	G_STMT_START { \
+		_path_equal_check1 (path, FALSE, expected_no_kill_dots); \
+		_path_equal_check1 (path, TRUE,  expected_kill_dots ?: expected_no_kill_dots); \
+	} G_STMT_END
+
+	_path_equal_check ("",                  "",                NULL);
+	_path_equal_check (".",                 ".",               "");
+	_path_equal_check ("..",                "..",              NULL);
+	_path_equal_check ("/..",               "/..",             NULL);
+	_path_equal_check ("//..",              "/..",             NULL);
+	_path_equal_check ("/.",                "/.",              "/");
+	_path_equal_check ("./",                ".",               "");
+	_path_equal_check ("./.",               "./.",             "");
+	_path_equal_check (".///.",             "./.",             "");
+	_path_equal_check (".///./",            "./.",             "");
+	_path_equal_check (".////",             ".",               "");
+	_path_equal_check ("//..//foo/",        "/../foo",         NULL);
+	_path_equal_check ("///foo//./bar/.",   "/foo/./bar/.",    "/foo/bar");
+	_path_equal_check (".//./foo//./bar/.", "././foo/./bar/.", "foo/bar");
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE ();
 
 int
@@ -183,6 +228,7 @@ main (int argc, char **argv)
 	g_test_add_func ("/systemd/dhcp/create", test_dhcp_create);
 	g_test_add_func ("/systemd/lldp/create", test_lldp_create);
 	g_test_add_func ("/systemd/sd-event", test_sd_event);
+	g_test_add_func ("/systemd/test_path_equal", test_path_equal);
 
 	return g_test_run ();
 }
diff --git a/src/vpn/nm-vpn-connection.c b/src/vpn/nm-vpn-connection.c
index bd847d75..3b3a97f4 100644
--- a/src/vpn/nm-vpn-connection.c
+++ b/src/vpn/nm-vpn-connection.c
@@ -729,15 +729,23 @@ add_ip4_vpn_gateway_route (NMIP4Config *config,
 		const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (route_resolved);
 
 		if (r->ifindex == ifindex) {
+			const NMPObject *obj;
+
 			/* `ip route get` always resolves the route, even if the destination is unreachable.
 			 * In which case, it pretends the destination is directly reachable.
 			 *
-			 * So, only accept direct routes, if @vpn_gw is a private network. */
-			if (   nm_platform_route_table_is_main (r->table_coerced)
-			    && (   r->gateway
-			        || nm_utils_ip_is_site_local (AF_INET, &vpn_gw))) {
-				parent_gw = r->gateway;
-				has_parent_gw = TRUE;
+			 * So, only accept direct routes if @vpn_gw is a private network
+			 * or if the parent device also has a direct default route */
+			if (nm_platform_route_table_is_main (r->table_coerced)) {
+				if (r->gateway) {
+					parent_gw = r->gateway;
+					has_parent_gw = TRUE;
+				} else if (nm_utils_ip_is_site_local (AF_INET, &vpn_gw)) {
+					has_parent_gw = TRUE;
+				} else if (   (obj = nm_device_get_best_default_route (parent_device, AF_INET))
+				           && !NMP_OBJECT_CAST_IP4_ROUTE (obj)->gateway) {
+					has_parent_gw = TRUE;
+				}
 			}
 		}
 	}
@@ -803,15 +811,23 @@ add_ip6_vpn_gateway_route (NMIP6Config *config,
 		const NMPlatformIP6Route *r = NMP_OBJECT_CAST_IP6_ROUTE (route_resolved);
 
 		if (r->ifindex == ifindex) {
+			const NMPObject *obj;
+
 			/* `ip route get` always resolves the route, even if the destination is unreachable.
 			 * In which case, it pretends the destination is directly reachable.
 			 *
-			 * So, only accept direct routes, if @vpn_gw is a private network. */
-			if (   nm_platform_route_table_is_main (r->table_coerced)
-			    && (   !IN6_IS_ADDR_UNSPECIFIED (&r->gateway)
-			        || nm_utils_ip_is_site_local (AF_INET6, &vpn_gw))) {
-				parent_gw = &r->gateway;
-				has_parent_gw = TRUE;
+			 * So, only accept direct routes if @vpn_gw is a private network
+			 * or if the parent device also has a direct default route */
+			if (nm_platform_route_table_is_main (r->table_coerced)) {
+				if (!IN6_IS_ADDR_UNSPECIFIED (&r->gateway)) {
+					parent_gw = &r->gateway;
+					has_parent_gw = TRUE;
+				} else if (nm_utils_ip_is_site_local (AF_INET6, &vpn_gw)) {
+					has_parent_gw = TRUE;
+				} else if (   (obj = nm_device_get_best_default_route (parent_device, AF_INET6))
+				           && IN6_IS_ADDR_UNSPECIFIED (&NMP_OBJECT_CAST_IP6_ROUTE (obj)->gateway)) {
+					has_parent_gw = TRUE;
+				}
 			}
 		}
 	}