about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/NetworkManagerUtils.c138
-rw-r--r--src/NetworkManagerUtils.h7
-rw-r--r--src/devices/nm-device-bridge.c256
-rw-r--r--src/devices/nm-device-ethernet.c59
-rw-r--r--src/devices/nm-device-macsec.c5
-rw-r--r--src/devices/nm-device-macvlan.c10
-rw-r--r--src/devices/nm-device.c711
-rw-r--r--src/devices/nm-device.h6
-rw-r--r--src/devices/nm-lldp-listener.c1181
-rw-r--r--src/devices/nm-lldp-listener.h3
-rw-r--r--src/devices/ovs/nm-ovs-factory.c37
-rw-r--r--src/devices/ovs/nm-ovsdb.c21
-rw-r--r--src/devices/team/nm-device-team.c181
-rw-r--r--src/devices/tests/test-lldp.c117
-rw-r--r--src/devices/wifi/nm-device-wifi.c10
-rw-r--r--src/devices/wifi/nm-wifi-ap.c3
-rw-r--r--src/devices/wifi/nm-wifi-utils.c3
-rw-r--r--src/devices/wwan/nm-modem-broadband.c9
-rw-r--r--src/dhcp/nm-dhcp-client.c21
-rw-r--r--src/dhcp/nm-dhcp-client.h2
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c22
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.h1
-rw-r--r--src/dhcp/nm-dhcp-dhclient.c6
-rw-r--r--src/dhcp/nm-dhcp-helper.c47
-rw-r--r--src/dhcp/nm-dhcp-manager.c6
-rw-r--r--src/dhcp/nm-dhcp-manager.h2
-rw-r--r--src/dhcp/nm-dhcp-nettools.c96
-rw-r--r--src/dhcp/nm-dhcp-options.c2
-rw-r--r--src/dhcp/nm-dhcp-options.h2
-rw-r--r--src/dhcp/nm-dhcp-systemd.c23
-rw-r--r--src/dhcp/tests/test-dhcp-dhclient.c59
-rw-r--r--src/initrd/nm-initrd-generator.c42
-rw-r--r--src/initrd/nm-initrd-generator.h2
-rw-r--r--src/initrd/nmi-cmdline-reader.c125
-rw-r--r--src/initrd/nmi-dt-reader.c14
-rw-r--r--src/initrd/nmi-ibft-reader.c9
-rw-r--r--src/initrd/tests/test-cmdline-reader.c120
-rw-r--r--src/nm-active-connection.c24
-rw-r--r--src/nm-auth-manager.c171
-rw-r--r--src/nm-config-data.c19
-rw-r--r--src/nm-config.c153
-rw-r--r--src/nm-config.h4
-rw-r--r--src/nm-core-utils.c35
-rw-r--r--src/nm-dbus-manager.c8
-rw-r--r--src/nm-iface-helper.c2
-rw-r--r--src/nm-manager.c71
-rw-r--r--src/org.freedesktop.NetworkManager.conf252
-rw-r--r--src/platform/nm-fake-platform.c15
-rw-r--r--src/platform/nm-linux-platform.c235
-rw-r--r--src/platform/nm-netlink.h14
-rw-r--r--src/platform/nm-platform-utils.c232
-rw-r--r--src/platform/nm-platform-utils.h23
-rw-r--r--src/platform/nm-platform.c223
-rw-r--r--src/platform/nm-platform.h47
-rw-r--r--src/platform/tests/meson.build2
-rw-r--r--src/platform/tests/test-common.c5
-rw-r--r--src/platform/tests/test-link.c10
-rw-r--r--src/platform/tests/test-tc.c223
-rw-r--r--src/settings/nm-settings-connection.c127
-rw-r--r--src/settings/nm-settings-connection.h10
-rw-r--r--src/settings/nm-settings.c44
-rw-r--r--src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf13
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c301
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c98
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h15
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c307
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c50
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-plugin.c44
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-plugin.h2
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-reader.c49
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-reader.h1
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-storage.c8
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-storage.h2
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-writer.c58
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-writer.h1
-rw-r--r--src/settings/plugins/keyfile/tests/test-keyfile-settings.c1
-rw-r--r--src/supplicant/nm-supplicant-config.c32
-rw-r--r--src/supplicant/nm-supplicant-interface.c30
-rw-r--r--src/supplicant/nm-supplicant-settings-verify.c370
-rw-r--r--src/supplicant/nm-supplicant-settings-verify.h21
-rw-r--r--src/supplicant/tests/test-supplicant-config.c130
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-identifier.c1
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-identifier.h2
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-internal.h6
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-lease-internal.h19
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-internal.h17
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-option.c167
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-protocol.h35
-rw-r--r--src/systemd/src/libsystemd-network/lldp-neighbor.c25
-rw-r--r--src/systemd/src/libsystemd-network/lldp-neighbor.h1
-rw-r--r--src/systemd/src/libsystemd-network/network-internal.c72
-rw-r--r--src/systemd/src/libsystemd-network/network-internal.h22
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-client.c264
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-lease.c169
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp6-client.c279
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp6-lease.c4
-rw-r--r--src/systemd/src/libsystemd-network/sd-ipv4ll.c6
-rw-r--r--src/systemd/src/libsystemd-network/sd-lldp.c2
-rw-r--r--src/systemd/src/systemd/sd-dhcp-client.h6
-rw-r--r--src/systemd/src/systemd/sd-dhcp-lease.h17
-rw-r--r--src/systemd/src/systemd/sd-dhcp6-client.h22
-rw-r--r--src/systemd/src/systemd/sd-dhcp6-lease.h5
-rw-r--r--src/systemd/src/systemd/sd-dhcp6-option.h37
-rw-r--r--src/systemd/src/systemd/sd-lldp.h4
-rw-r--r--src/systemd/src/systemd/sd-ndisc.h2
-rwxr-xr-xsrc/tests/test-secret-agent.py51
107 files changed, 5550 insertions, 2527 deletions
diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c
index 3f2f44d3..bcb12a56 100644
--- a/src/NetworkManagerUtils.c
+++ b/src/NetworkManagerUtils.c
@@ -9,6 +9,7 @@
 #include "NetworkManagerUtils.h"
 
 #include <linux/fib_rules.h>
+#include <linux/pkt_sched.h>
 
 #include "nm-glib-aux/nm-c-list.h"
 
@@ -18,6 +19,7 @@
 #include "nm-setting-ip4-config.h"
 #include "nm-setting-ip6-config.h"
 #include "nm-core-internal.h"
+#include "platform/nmp-object.h"
 
 #include "platform/nm-platform.h"
 #include "nm-auth-utils.h"
@@ -1125,3 +1127,139 @@ nm_utils_file_is_in_path (const char *abs_filename,
 	       ? path
 	       : NULL;
 }
+
+/* The returned qdisc array is valid as long as s_tc is not modified */
+GPtrArray *
+nm_utils_qdiscs_from_tc_setting (NMPlatform *platform,
+                                 NMSettingTCConfig *s_tc,
+                                 int ip_ifindex)
+{
+	GPtrArray *qdiscs;
+	guint nqdiscs;
+	guint i;
+
+	nqdiscs = nm_setting_tc_config_get_num_qdiscs (s_tc);
+	qdiscs = g_ptr_array_new_full (nqdiscs, (GDestroyNotify) nmp_object_unref);
+
+	for (i = 0; i < nqdiscs; i++) {
+		NMTCQdisc *s_qdisc = nm_setting_tc_config_get_qdisc (s_tc, i);
+		NMPObject *q = nmp_object_new (NMP_OBJECT_TYPE_QDISC, NULL);
+		NMPlatformQdisc *qdisc = NMP_OBJECT_CAST_QDISC (q);
+
+		qdisc->ifindex = ip_ifindex;
+		qdisc->kind = nm_tc_qdisc_get_kind (s_qdisc);
+
+		qdisc->addr_family = AF_UNSPEC;
+		qdisc->handle = nm_tc_qdisc_get_handle (s_qdisc);
+		qdisc->parent = nm_tc_qdisc_get_parent (s_qdisc);
+		qdisc->info = 0;
+
+#define GET_ATTR(name, dst, variant_type, type, dflt) G_STMT_START { \
+	GVariant *_variant = nm_tc_qdisc_get_attribute (s_qdisc, ""name""); \
+	\
+	if (   _variant \
+	    && g_variant_is_of_type (_variant, G_VARIANT_TYPE_ ## variant_type)) \
+		(dst) = g_variant_get_ ## type (_variant); \
+	else \
+		(dst) = (dflt); \
+} G_STMT_END
+
+		if (strcmp (qdisc->kind, "fq_codel") == 0) {
+			GET_ATTR ("limit",        qdisc->fq_codel.limit,        UINT32,  uint32,  0);
+			GET_ATTR ("flows",        qdisc->fq_codel.flows,        UINT32,  uint32,  0);
+			GET_ATTR ("target",       qdisc->fq_codel.target,       UINT32,  uint32,  0);
+			GET_ATTR ("interval",     qdisc->fq_codel.interval,     UINT32,  uint32,  0);
+			GET_ATTR ("quantum",      qdisc->fq_codel.quantum,      UINT32,  uint32,  0);
+			GET_ATTR ("ce_threshold", qdisc->fq_codel.ce_threshold, UINT32,  uint32,  NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED);
+			GET_ATTR ("memory_limit", qdisc->fq_codel.memory_limit, UINT32,  uint32,  NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET);
+			GET_ATTR ("ecn",          qdisc->fq_codel.ecn,          BOOLEAN, boolean, FALSE);
+		} else if (nm_streq (qdisc->kind, "sfq")) {
+			GET_ATTR ("limit",        qdisc->sfq.limit,             UINT32,  uint32,  0);
+			GET_ATTR ("flows",        qdisc->sfq.flows,             UINT32,  uint32,  0);
+			GET_ATTR ("divisor",      qdisc->sfq.divisor,           UINT32,  uint32,  0);
+			GET_ATTR ("perturb",      qdisc->sfq.perturb_period,    INT32,   int32,   0);
+			GET_ATTR ("quantum",      qdisc->sfq.quantum,           UINT32,  uint32,  0);
+			GET_ATTR ("depth",        qdisc->sfq.depth,             UINT32,  uint32,  0);
+		} else if (nm_streq (qdisc->kind, "tbf")) {
+			GET_ATTR ("rate",         qdisc->tbf.rate,              UINT64,  uint64,  0);
+			GET_ATTR ("burst",        qdisc->tbf.burst,             UINT32,  uint32,  0);
+			GET_ATTR ("limit",        qdisc->tbf.limit,             UINT32,  uint32,  0);
+			GET_ATTR ("latency",      qdisc->tbf.latency,           UINT32,  uint32,  0);
+		}
+#undef GET_ADDR
+
+		g_ptr_array_add (qdiscs, q);
+	}
+
+	return qdiscs;
+}
+
+/* The returned tfilter array is valid as long as s_tc is not modified */
+GPtrArray *
+nm_utils_tfilters_from_tc_setting (NMPlatform *platform,
+                                   NMSettingTCConfig *s_tc,
+                                   int ip_ifindex)
+{
+	GPtrArray *tfilters;
+	guint ntfilters;
+	guint i;
+
+	ntfilters = nm_setting_tc_config_get_num_tfilters (s_tc);
+	tfilters = g_ptr_array_new_full (ntfilters, (GDestroyNotify) nmp_object_unref);
+
+	for (i = 0; i < ntfilters; i++) {
+		NMTCTfilter *s_tfilter = nm_setting_tc_config_get_tfilter (s_tc, i);
+		NMTCAction *action;
+		NMPObject *t = nmp_object_new (NMP_OBJECT_TYPE_TFILTER, NULL);
+		NMPlatformTfilter *tfilter = NMP_OBJECT_CAST_TFILTER (t);
+
+		tfilter->ifindex = ip_ifindex;
+		tfilter->kind = nm_tc_tfilter_get_kind (s_tfilter);
+		tfilter->addr_family = AF_UNSPEC;
+		tfilter->handle = nm_tc_tfilter_get_handle (s_tfilter);
+		tfilter->parent = nm_tc_tfilter_get_parent (s_tfilter);
+		tfilter->info = TC_H_MAKE (0, htons (ETH_P_ALL));
+
+		action = nm_tc_tfilter_get_action (s_tfilter);
+		if (action) {
+			GVariant *var;
+
+			tfilter->action.kind = nm_tc_action_get_kind (action);
+
+			if (strcmp (tfilter->action.kind, "simple") == 0) {
+				var = nm_tc_action_get_attribute (action, "sdata");
+				if (var && g_variant_is_of_type (var, G_VARIANT_TYPE_BYTESTRING)) {
+					g_strlcpy (tfilter->action.simple.sdata,
+					           g_variant_get_bytestring (var),
+					           sizeof (tfilter->action.simple.sdata));
+				}
+			} else if (strcmp (tfilter->action.kind, "mirred") == 0) {
+				if (nm_tc_action_get_attribute (action, "egress"))
+					tfilter->action.mirred.egress = TRUE;
+
+				if (nm_tc_action_get_attribute (action, "ingress"))
+					tfilter->action.mirred.ingress = TRUE;
+
+				if (nm_tc_action_get_attribute (action, "mirror"))
+					tfilter->action.mirred.mirror = TRUE;
+
+				if (nm_tc_action_get_attribute (action, "redirect"))
+					tfilter->action.mirred.redirect = TRUE;
+
+				var = nm_tc_action_get_attribute (action, "dev");
+				if (var && g_variant_is_of_type (var, G_VARIANT_TYPE_STRING)) {
+					int ifindex;
+
+					ifindex = nm_platform_link_get_ifindex (platform,
+					                                        g_variant_get_string (var, NULL));
+					if (ifindex > 0)
+						tfilter->action.mirred.ifindex = ifindex;
+				}
+			}
+		}
+
+		g_ptr_array_add (tfilters, t);
+	}
+
+	return tfilters;
+}
diff --git a/src/NetworkManagerUtils.h b/src/NetworkManagerUtils.h
index 558a262b..38386c3d 100644
--- a/src/NetworkManagerUtils.h
+++ b/src/NetworkManagerUtils.h
@@ -134,4 +134,11 @@ nm_utils_file_is_in_path (const char *abs_filename,
 
 /*****************************************************************************/
 
+GPtrArray *nm_utils_qdiscs_from_tc_setting (NMPlatform *platform,
+                                            NMSettingTCConfig *s_tc,
+                                            int ip_ifindex);
+GPtrArray *nm_utils_tfilters_from_tc_setting (NMPlatform *platform,
+                                              NMSettingTCConfig *s_tc,
+                                              int ip_ifindex);
+
 #endif /* __NETWORKMANAGER_UTILS_H__ */
diff --git a/src/devices/nm-device-bridge.c b/src/devices/nm-device-bridge.c
index c93c6a54..544e6614 100644
--- a/src/devices/nm-device-bridge.c
+++ b/src/devices/nm-device-bridge.c
@@ -237,88 +237,156 @@ typedef struct {
 	const char *sysname;
 	const char *(*to_sysfs) (GValue *value);
 	void (*from_sysfs) (const char *value, GValue *out);
-	uint nm_min;
-	uint nm_max;
-	uint nm_default;
+	guint64 nm_min;
+	guint64 nm_max;
+	guint64 nm_default;
 	bool default_if_zero;
 	bool user_hz_compensate;
 	bool only_with_stp;
 } Option;
 
+#define OPTION(_name, _sysname, ...) \
+	{ \
+		.name    = ""_name"", \
+		.sysname = ""_sysname"", \
+		__VA_ARGS__ \
+	}
+
+#define OPTION_TYPE_INT(min, max, def) \
+	.nm_min = (min), .nm_max = (max), .nm_default = (def)
+
+#define OPTION_TYPE_BOOL(def) \
+	OPTION_TYPE_INT (FALSE, TRUE, def)
+
+#define OPTION_TYPE_TOFROM(to, fro) \
+	.to_sysfs = (to), .from_sysfs = (fro)
+
 static const Option master_options[] = {
-	{ NM_SETTING_BRIDGE_STP,                          "stp_state", /* this must stay as the first item */
-	                                                  NULL, NULL,
-	                                                  0, 1, 1,
-	                                                  FALSE, FALSE, FALSE },
-	{ NM_SETTING_BRIDGE_PRIORITY,                     "priority",
-	                                                  NULL, NULL,
-	                                                  0, G_MAXUINT16, 0x8000,
-	                                                  TRUE, FALSE, TRUE },
-	{ NM_SETTING_BRIDGE_FORWARD_DELAY,                "forward_delay",
-	                                                  NULL, NULL,
-	                                                  0, NM_BR_MAX_FORWARD_DELAY, 15,
-	                                                  TRUE, TRUE, TRUE},
-	{ NM_SETTING_BRIDGE_HELLO_TIME,                   "hello_time",
-	                                                  NULL, NULL,
-	                                                  0, NM_BR_MAX_HELLO_TIME, 2,
-	                                                  TRUE, TRUE, TRUE },
-	{ NM_SETTING_BRIDGE_MAX_AGE,                      "max_age",
-	                                                  NULL, NULL,
-	                                                  0, NM_BR_MAX_MAX_AGE, 20,
-	                                                  TRUE, TRUE, TRUE },
-	{ NM_SETTING_BRIDGE_AGEING_TIME,                  "ageing_time",
-	                                                  NULL, NULL,
-	                                                  NM_BR_MIN_AGEING_TIME, NM_BR_MAX_AGEING_TIME, 300,
-	                                                  TRUE, TRUE, FALSE },
-	{ NM_SETTING_BRIDGE_GROUP_FORWARD_MASK,           "group_fwd_mask",
-	                                                  NULL, NULL,
-	                                                  0, 0xFFFF, 0,
-	                                                  TRUE, FALSE, FALSE },
-	{ NM_SETTING_BRIDGE_MULTICAST_QUERIER,            "multicast_querier",
-	                                                  NULL, NULL,
-	                                                  0, 1, 0,
-	                                                  FALSE, FALSE, FALSE },
-	{ NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR,   "multicast_query_use_ifaddr",
-	                                                  NULL, NULL,
-	                                                  0, 1, 0,
-	                                                  FALSE, FALSE, FALSE },
-	{ NM_SETTING_BRIDGE_MULTICAST_SNOOPING,           "multicast_snooping",
-	                                                  NULL, NULL,
-	                                                  0, 1, 1,
-	                                                  FALSE, FALSE, FALSE },
-	{ NM_SETTING_BRIDGE_MULTICAST_ROUTER,             "multicast_router",
-	                                                  to_sysfs_multicast_router, from_sysfs_multicast_router,
-	                                                  0, 0, 0,
-	                                                  FALSE, FALSE, FALSE },
-	{ NM_SETTING_BRIDGE_GROUP_ADDRESS,                "group_addr",
-	                                                  to_sysfs_group_address, from_sysfs_group_address,
-	                                                  0, 0, 0,
-	                                                  FALSE, FALSE, FALSE },
-	{ NM_SETTING_BRIDGE_VLAN_PROTOCOL,                "vlan_protocol",
-	                                                  to_sysfs_vlan_protocol, from_sysfs_vlan_protocol,
-	                                                  0, 0, 0,
-	                                                  FALSE, FALSE, FALSE },
-	{ NM_SETTING_BRIDGE_VLAN_STATS_ENABLED,           "vlan_stats_enabled",
-	                                                  NULL, NULL,
-	                                                  0, 1, 0,
-	                                                  FALSE, FALSE, FALSE },
-	{ NULL, NULL }
+	OPTION (NM_SETTING_BRIDGE_STP, /* this must stay as the first item */
+	        "stp_state",
+	        OPTION_TYPE_BOOL (NM_BRIDGE_STP_DEF),
+	),
+	OPTION (NM_SETTING_BRIDGE_PRIORITY,
+	        "priority",
+	        OPTION_TYPE_INT (NM_BRIDGE_PRIORITY_MIN, NM_BRIDGE_PRIORITY_MAX, NM_BRIDGE_PRIORITY_DEF),
+	        .default_if_zero    = TRUE,
+	        .only_with_stp      = TRUE,
+	),
+	OPTION (NM_SETTING_BRIDGE_FORWARD_DELAY,
+	        "forward_delay",
+	        OPTION_TYPE_INT (NM_BRIDGE_FORWARD_DELAY_MIN, NM_BRIDGE_FORWARD_DELAY_MAX, NM_BRIDGE_FORWARD_DELAY_DEF),
+	        .default_if_zero    = TRUE,
+	        .user_hz_compensate = TRUE,
+	        .only_with_stp      = TRUE,
+	),
+	OPTION (NM_SETTING_BRIDGE_HELLO_TIME,
+	        "hello_time",
+	        OPTION_TYPE_INT (NM_BRIDGE_HELLO_TIME_MIN, NM_BRIDGE_HELLO_TIME_MAX, NM_BRIDGE_HELLO_TIME_DEF),
+	        .default_if_zero    = TRUE,
+	        .user_hz_compensate = TRUE,
+	        .only_with_stp      = TRUE,
+	),
+	OPTION (NM_SETTING_BRIDGE_MAX_AGE,
+	        "max_age",
+	        OPTION_TYPE_INT (NM_BRIDGE_MAX_AGE_MIN, NM_BRIDGE_MAX_AGE_MAX, NM_BRIDGE_MAX_AGE_DEF),
+	        .default_if_zero    = TRUE,
+	        .user_hz_compensate = TRUE,
+	        .only_with_stp      = TRUE,
+	),
+	OPTION (NM_SETTING_BRIDGE_AGEING_TIME,
+	        "ageing_time",
+	        OPTION_TYPE_INT (NM_BRIDGE_AGEING_TIME_MIN, NM_BRIDGE_AGEING_TIME_MAX, NM_BRIDGE_AGEING_TIME_DEF),
+	        .default_if_zero    = TRUE,
+	        .user_hz_compensate = TRUE,
+	),
+	OPTION (NM_SETTING_BRIDGE_GROUP_FORWARD_MASK,
+	        "group_fwd_mask",
+	        OPTION_TYPE_INT (0, 0xFFFF, 0),
+	        .default_if_zero    = TRUE,
+	),
+	OPTION (NM_SETTING_BRIDGE_MULTICAST_HASH_MAX,
+	        "hash_max",
+	        OPTION_TYPE_INT (NM_BRIDGE_MULTICAST_HASH_MAX_MIN, NM_BRIDGE_MULTICAST_HASH_MAX_MAX, NM_BRIDGE_MULTICAST_HASH_MAX_DEF),
+	),
+	OPTION (NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_COUNT,
+	        "multicast_last_member_count",
+	        OPTION_TYPE_INT (NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_MIN, NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_MAX, NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_DEF),
+	),
+	OPTION (NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL,
+	        "multicast_last_member_interval",
+	        OPTION_TYPE_INT (NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_MIN, NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_MAX, NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_DEF),
+	),
+	OPTION (NM_SETTING_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL,
+	        "multicast_membership_interval",
+	        OPTION_TYPE_INT (NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_MIN, NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_MAX, NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_DEF),
+	),
+	OPTION (NM_SETTING_BRIDGE_MULTICAST_QUERIER,
+	        "multicast_querier",
+	        OPTION_TYPE_BOOL (NM_BRIDGE_MULTICAST_QUERIER_DEF),
+	),
+	OPTION (NM_SETTING_BRIDGE_MULTICAST_QUERIER_INTERVAL,
+	        "multicast_querier_interval",
+	        OPTION_TYPE_INT (NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_MIN, NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_MAX, NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_DEF),
+	),
+	OPTION (NM_SETTING_BRIDGE_MULTICAST_QUERY_INTERVAL,
+	        "multicast_query_interval",
+	        OPTION_TYPE_INT (NM_BRIDGE_MULTICAST_QUERY_INTERVAL_MIN, NM_BRIDGE_MULTICAST_QUERY_INTERVAL_MAX, NM_BRIDGE_MULTICAST_QUERY_INTERVAL_DEF),
+	),
+	OPTION (NM_SETTING_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL,
+	        "multicast_query_response_interval",
+	        OPTION_TYPE_INT (NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_MIN, NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_MAX, NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_DEF),
+	),
+	OPTION (NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR,
+	        "multicast_query_use_ifaddr",
+	        OPTION_TYPE_BOOL (NM_BRIDGE_MULTICAST_QUERY_USE_IFADDR_DEF),
+	),
+	OPTION (NM_SETTING_BRIDGE_MULTICAST_SNOOPING,
+	        "multicast_snooping",
+	        OPTION_TYPE_BOOL (NM_BRIDGE_MULTICAST_SNOOPING_DEF),
+	),
+	OPTION (NM_SETTING_BRIDGE_MULTICAST_ROUTER,
+	        "multicast_router",
+	        OPTION_TYPE_TOFROM (to_sysfs_multicast_router, from_sysfs_multicast_router),
+	),
+	OPTION (NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT,
+	        "multicast_startup_query_count",
+	        OPTION_TYPE_INT (NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_MIN, NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_MAX, NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_DEF),
+	),
+	OPTION (NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL,
+	        "multicast_startup_query_interval",
+	        OPTION_TYPE_INT (NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_MIN, NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_MAX, NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_DEF),
+	),
+	OPTION (NM_SETTING_BRIDGE_GROUP_ADDRESS,
+	        "group_addr",
+	        OPTION_TYPE_TOFROM (to_sysfs_group_address, from_sysfs_group_address),
+	),
+	OPTION (NM_SETTING_BRIDGE_VLAN_PROTOCOL,
+	        "vlan_protocol",
+	        OPTION_TYPE_TOFROM (to_sysfs_vlan_protocol, from_sysfs_vlan_protocol),
+	),
+	OPTION (NM_SETTING_BRIDGE_VLAN_STATS_ENABLED,
+	        "vlan_stats_enabled",
+	        OPTION_TYPE_BOOL (NM_BRIDGE_VLAN_STATS_ENABLED_DEF)
+	),
+	{ 0, }
 };
 
 static const Option slave_options[] = {
-	{ NM_SETTING_BRIDGE_PORT_PRIORITY,     "priority",
-	                                       NULL, NULL,
-	                                       0, NM_BR_PORT_MAX_PRIORITY, NM_BR_PORT_DEF_PRIORITY,
-	                                       TRUE, FALSE },
-	{ NM_SETTING_BRIDGE_PORT_PATH_COST,    "path_cost",
-	                                       NULL, NULL,
-	                                       0, NM_BR_PORT_MAX_PATH_COST, 100,
-	                                       TRUE, FALSE },
-	{ NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE, "hairpin_mode",
-	                                       NULL, NULL,
-	                                       0, 1, 0,
-	                                       FALSE, FALSE },
-	{ NULL, NULL }
+	OPTION (NM_SETTING_BRIDGE_PORT_PRIORITY,
+	        "priority",
+	        OPTION_TYPE_INT (NM_BRIDGE_PORT_PRIORITY_MIN, NM_BRIDGE_PORT_PRIORITY_MAX, NM_BRIDGE_PORT_PRIORITY_DEF),
+	        .default_if_zero    = TRUE,
+	),
+	OPTION (NM_SETTING_BRIDGE_PORT_PATH_COST,
+	        "path_cost",
+	        OPTION_TYPE_INT (NM_BRIDGE_PORT_PATH_COST_MIN, NM_BRIDGE_PORT_PATH_COST_MAX, NM_BRIDGE_PORT_PATH_COST_DEF),
+	        .default_if_zero    = TRUE,
+	),
+	OPTION (NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE,
+	        "hairpin_mode",
+	        OPTION_TYPE_BOOL (FALSE),
+	),
+	{ 0 }
 };
 
 static void
@@ -350,19 +418,24 @@ commit_option (NMDevice *device, NMSetting *setting, const Option *option, gbool
 	case G_TYPE_BOOLEAN:
 		value = g_value_get_boolean (&val) ? "1" : "0";
 		break;
+	case G_TYPE_UINT64:
 	case G_TYPE_UINT: {
-			guint uval;
+			guint64 uval;
 
-			uval = g_value_get_uint (&val);
+			if (pspec->value_type == G_TYPE_UINT64)
+				uval = g_value_get_uint64 (&val);
+			else
+				uval = (guint) g_value_get_uint (&val);
 
 			/* zero means "unspecified" for some NM properties but isn't in the
 			 * allowed kernel range, so reset the property to the default value.
 			 */
-			if (option->default_if_zero && uval == 0) {
-				g_value_unset (&val);
-				g_value_init (&val, G_PARAM_SPEC_VALUE_TYPE (pspec));
-				g_param_value_set_default (pspec, &val);
-				uval = g_value_get_uint (&val);
+			if (   option->default_if_zero
+			    && uval == 0) {
+				if (pspec->value_type == G_TYPE_UINT64)
+					uval = NM_G_PARAM_SPEC_GET_DEFAULT_UINT64 (pspec);
+				else
+					uval = NM_G_PARAM_SPEC_GET_DEFAULT_UINT (pspec);
 			}
 
 			/* Linux kernel bridge interfaces use 'centiseconds' for time-based values.
@@ -373,7 +446,11 @@ commit_option (NMDevice *device, NMSetting *setting, const Option *option, gbool
 			if (option->user_hz_compensate)
 				uval *= 100;
 
-			nm_sprintf_buf (value_buf, "%u", uval);
+			if (pspec->value_type == G_TYPE_UINT64)
+				nm_sprintf_buf (value_buf, "%"G_GUINT64_FORMAT, uval);
+			else
+				nm_sprintf_buf (value_buf, "%u", (guint) uval);
+
 			value = value_buf;
 		}
 		break;
@@ -499,8 +576,9 @@ update_connection (NMDevice *device, NMConnection *connection)
 		}
 
 		switch (pspec->value_type) {
+		case G_TYPE_UINT64:
 		case G_TYPE_UINT: {
-				guint uvalue;
+				guint64 uvalue;
 
 				/* See comments in set_sysfs_uint() about centiseconds. */
 				if (option->user_hz_compensate) {
@@ -510,12 +588,16 @@ update_connection (NMDevice *device, NMConnection *connection)
 					                                       option->nm_default * 100);
 					uvalue /= 100;
 				} else {
-					uvalue = _nm_utils_ascii_str_to_int64 (str, 10,
-					                                       option->nm_min,
-					                                       option->nm_max,
-					                                       option->nm_default);
+					uvalue = _nm_utils_ascii_str_to_uint64 (str, 10,
+					                                        option->nm_min,
+					                                        option->nm_max,
+					                                        option->nm_default);
 				}
-				g_value_set_uint (&value, uvalue);
+
+				if (pspec->value_type == G_TYPE_UINT64)
+					g_value_set_uint64(&value, uvalue);
+				else
+					g_value_set_uint (&value, (guint) uvalue);
 			}
 			break;
 		case G_TYPE_BOOLEAN: {
diff --git a/src/devices/nm-device-ethernet.c b/src/devices/nm-device-ethernet.c
index 7556bb27..9e7601f0 100644
--- a/src/devices/nm-device-ethernet.c
+++ b/src/devices/nm-device-ethernet.c
@@ -100,7 +100,15 @@ typedef struct _NMDeviceEthernetPrivate {
 	DcbWait       dcb_wait;
 	guint         dcb_timeout_id;
 
+	guint32       ethtool_prev_speed;
+
+	NMPlatformLinkDuplexType ethtool_prev_duplex:3;
+
 	bool          dcb_handle_carrier_changes:1;
+
+	bool          ethtool_prev_set:1;
+	bool          ethtool_prev_autoneg:1;
+
 } NMDeviceEthernetPrivate;
 
 NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceEthernet,
@@ -882,6 +890,7 @@ static void
 link_negotiation_set (NMDevice *device)
 {
 	NMDeviceEthernet *self = NM_DEVICE_ETHERNET (device);
+	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
 	NMSettingWired *s_wired;
 	gboolean autoneg = TRUE;
 	gboolean link_autoneg;
@@ -925,7 +934,7 @@ link_negotiation_set (NMDevice *device)
 	    && !duplex)
 		_LOGD (LOGD_DEVICE, "set-link: configure auto-negotiation");
 	else {
-		_LOGD (LOGD_DEVICE, "set-link: configure %snegotiation (%u Mbit%s - %s duplex%s)",
+		_LOGD (LOGD_DEVICE, "set-link: configure %snegotiation (%u Mbit%s, %s duplex%s)",
 		       autoneg ? "auto-" : "static ",
 		       speed ?: link_speed,
 		       speed ? "" : "*",
@@ -935,6 +944,14 @@ link_negotiation_set (NMDevice *device)
 		       duplex ? "" : "*");
 	}
 
+	if (!priv->ethtool_prev_set) {
+		/* remember the values we had before setting it. */
+		priv->ethtool_prev_autoneg = link_autoneg;
+		priv->ethtool_prev_speed = link_speed;
+		priv->ethtool_prev_duplex = link_duplex;
+		priv->ethtool_prev_set = TRUE;
+	}
+
 	if (!nm_platform_ethtool_set_link_settings (nm_device_get_platform (device),
 	                                            nm_device_get_ifindex (device),
 	                                            autoneg,
@@ -964,6 +981,28 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	NMDeviceEthernet *self = NM_DEVICE_ETHERNET (device);
 	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
 
+	if (nm_device_sys_iface_state_is_external_or_assume (device)) {
+		if (   !priv->ethtool_prev_set
+		    && !nm_device_sys_iface_state_is_external (device)) {
+			NMSettingWired *s_wired;
+
+			/* During restart of NetworkManager service we forget the original auto
+			 * negotiation settings. When taking over a device, remember to reset
+			 * the "default" during deactivate. */
+			s_wired = nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRED);
+			if (   s_wired
+			    && (   nm_setting_wired_get_auto_negotiate (s_wired)
+			        || nm_setting_wired_get_speed (s_wired)
+			        || nm_setting_wired_get_duplex (s_wired))) {
+				priv->ethtool_prev_set = TRUE;
+				priv->ethtool_prev_autoneg = TRUE;
+				priv->ethtool_prev_speed = 0;
+				priv->ethtool_prev_duplex = NM_PLATFORM_LINK_DUPLEX_UNKNOWN;
+			}
+		}
+		return NM_ACT_STAGE_RETURN_SUCCESS;
+	}
+
 	link_negotiation_set (device);
 
 	/* If we're re-activating a PPPoE connection a short while after
@@ -1525,6 +1564,23 @@ deactivate (NMDevice *device)
 	/* Set last PPPoE connection time */
 	if (nm_device_get_applied_setting (device, NM_TYPE_SETTING_PPPOE))
 		priv->last_pppoe_time = nm_utils_get_monotonic_timestamp_sec ();
+
+	if (priv->ethtool_prev_set) {
+		priv->ethtool_prev_set = FALSE;
+
+		_LOGD (LOGD_DEVICE, "set-link: reset %snegotiation (%u Mbit, %s duplex)",
+		       priv->ethtool_prev_autoneg ? "auto-" : "static ",
+		       priv->ethtool_prev_speed,
+		       nm_platform_link_duplex_type_to_string (priv->ethtool_prev_duplex));
+		if (!nm_platform_ethtool_set_link_settings (nm_device_get_platform (device),
+		                                            nm_device_get_ifindex (device),
+		                                            priv->ethtool_prev_autoneg,
+		                                            priv->ethtool_prev_speed,
+		                                            priv->ethtool_prev_duplex)) {
+			_LOGW (LOGD_DEVICE, "set-link: failure to reset link negotiation");
+			return;
+		}
+	}
 }
 
 static gboolean
@@ -1914,6 +1970,7 @@ nm_device_ethernet_class_init (NMDeviceEthernetClass *klass)
 	device_class->complete_connection = complete_connection;
 	device_class->new_default_connection = new_default_connection;
 
+	device_class->act_stage1_prepare_also_for_external_or_assume = TRUE;
 	device_class->act_stage1_prepare = act_stage1_prepare;
 	device_class->act_stage1_prepare_set_hwaddr_ethernet = TRUE;
 	device_class->act_stage2_config = act_stage2_config;
diff --git a/src/devices/nm-device-macsec.c b/src/devices/nm-device-macsec.c
index f1878078..94edc947 100644
--- a/src/devices/nm-device-macsec.c
+++ b/src/devices/nm-device-macsec.c
@@ -863,15 +863,14 @@ static void
 dispose (GObject *object)
 {
 	NMDeviceMacsec *self = NM_DEVICE_MACSEC (object);
-	NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
 
 	macsec_secrets_cancel (self);
 	supplicant_interface_release (self);
 
 	G_OBJECT_CLASS (nm_device_macsec_parent_class)->dispose (object);
 
-	nm_assert (priv->parent_state_id == 0);
-	nm_assert (priv->parent_mtu_id == 0);
+	nm_assert (NM_DEVICE_MACSEC_GET_PRIVATE (self)->parent_state_id == 0);
+	nm_assert (NM_DEVICE_MACSEC_GET_PRIVATE (self)->parent_mtu_id == 0);
 }
 
 static const NMDBusInterfaceInfoExtended interface_info_device_macsec = {
diff --git a/src/devices/nm-device-macvlan.c b/src/devices/nm-device-macvlan.c
index 2f8c4c32..3435df1b 100644
--- a/src/devices/nm-device-macvlan.c
+++ b/src/devices/nm-device-macvlan.c
@@ -462,16 +462,16 @@ nm_device_macvlan_init (NMDeviceMacvlan *self)
 {
 }
 
+#if NM_MORE_ASSERTS
 static void
 dispose (GObject *object)
 {
-	NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE (object);
-
 	G_OBJECT_CLASS (nm_device_macvlan_parent_class)->dispose (object);
 
-	nm_assert (priv->parent_state_id == 0);
-	nm_assert (priv->parent_mtu_id == 0);
+	nm_assert (NM_DEVICE_MACVLAN_GET_PRIVATE (object)->parent_state_id == 0);
+	nm_assert (NM_DEVICE_MACVLAN_GET_PRIVATE (object)->parent_mtu_id == 0);
 }
+#endif
 
 static const NMDBusInterfaceInfoExtended interface_info_device_macvlan = {
 	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
@@ -496,7 +496,9 @@ nm_device_macvlan_class_init (NMDeviceMacvlanClass *klass)
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
+#if NM_MORE_ASSERTS
 	object_class->dispose = dispose;
+#endif
 	object_class->get_property = get_property;
 	object_class->set_property = set_property;
 
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 76c24a38..63fa5a5c 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -19,11 +19,11 @@
 #include <linux/if_addr.h>
 #include <linux/if_arp.h>
 #include <linux/rtnetlink.h>
-#include <linux/pkt_sched.h>
 
 #include "nm-std-aux/unaligned.h"
 #include "nm-glib-aux/nm-dedup-multi.h"
 #include "nm-glib-aux/nm-random-utils.h"
+#include "systemd/nm-sd-utils-shared.h"
 
 #include "nm-libnm-core-intern/nm-ethtool-utils.h"
 #include "nm-libnm-core-intern/nm-common-macros.h"
@@ -31,6 +31,7 @@
 #include "NetworkManagerUtils.h"
 #include "nm-manager.h"
 #include "platform/nm-platform.h"
+#include "platform/nm-platform-utils.h"
 #include "platform/nmp-object.h"
 #include "platform/nmp-rules-manager.h"
 #include "ndisc/nm-ndisc.h"
@@ -184,6 +185,8 @@ typedef struct {
 	int ifindex;
 	NMEthtoolFeatureStates *features;
 	NMTernary requested[_NM_ETHTOOL_ID_FEATURE_NUM];
+	NMEthtoolCoalesceState *coalesce;
+	NMEthtoolRingState *ring;
 } EthtoolState;
 
 /*****************************************************************************/
@@ -204,6 +207,7 @@ static guint signals[LAST_SIGNAL] = { 0 };
 
 NM_GOBJECT_PROPERTIES_DEFINE (NMDevice,
 	PROP_UDI,
+	PROP_PATH,
 	PROP_IFACE,
 	PROP_IP_IFACE,
 	PROP_DRIVER,
@@ -278,6 +282,7 @@ typedef struct _NMDevicePrivate {
 	NMDBusTrackObjPath parent_device;
 
 	char *        udi;
+	char *        path;
 	char *        iface;   /* may change, could be renamed by user */
 	int           ifindex;
 
@@ -811,64 +816,47 @@ NM_UTILS_LOOKUP_STR_DEFINE (mtu_source_to_str, NMDeviceMtuSource,
 /*****************************************************************************/
 
 static void
-_ethtool_state_reset (NMDevice *self)
+_ethtool_features_reset (NMDevice *self,
+                         NMPlatform *platform,
+                         EthtoolState *ethtool_state)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	gs_free NMEthtoolFeatureStates *features;
 
-	if (priv->ethtool_state) {
-		gs_free NMEthtoolFeatureStates *features = priv->ethtool_state->features;
-		gs_free EthtoolState *ethtool_state = g_steal_pointer (&priv->ethtool_state);
+	features = g_steal_pointer (&ethtool_state->features);
 
-		if (!nm_platform_ethtool_set_features (nm_device_get_platform (self),
-		                                       ethtool_state->ifindex,
-		                                       features,
-		                                       ethtool_state->requested,
-		                                       FALSE))
-			_LOGW (LOGD_DEVICE, "ethtool: failure resetting one or more offload features");
-		else
-			_LOGD (LOGD_DEVICE, "ethtool: offload features successfully reset");
-	}
+	if (!nm_platform_ethtool_set_features (platform,
+	                                       ethtool_state->ifindex,
+	                                       features,
+	                                       ethtool_state->requested,
+	                                       FALSE))
+		_LOGW (LOGD_DEVICE, "ethtool: failure resetting one or more offload features");
+	else
+		_LOGD (LOGD_DEVICE, "ethtool: offload features successfully reset");
 }
 
 static void
-_ethtool_state_set (NMDevice *self)
+_ethtool_features_set (NMDevice *self,
+                       NMPlatform *platform,
+                       EthtoolState *ethtool_state,
+                       NMSettingEthtool *s_ethtool)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	int ifindex;
-	NMConnection *connection;
-	NMSettingEthtool *s_ethtool;
-	NMPlatform *platform;
-	gs_free EthtoolState *ethtool_state = NULL;
 	gs_free NMEthtoolFeatureStates *features = NULL;
 
-	_ethtool_state_reset (self);
-
-	connection = nm_device_get_applied_connection (self);
-	if (!connection)
-		return;
-
-	ifindex = nm_device_get_ip_ifindex (self);
-	if (ifindex <= 0)
-		return;
-
-	s_ethtool = NM_SETTING_ETHTOOL (nm_connection_get_setting (connection, NM_TYPE_SETTING_ETHTOOL));
-	if (!s_ethtool)
-		return;
+	if (ethtool_state->features)
+		_ethtool_features_reset (self, platform, ethtool_state);
 
-	ethtool_state = g_new (EthtoolState, 1);
 	if (nm_setting_ethtool_init_features (s_ethtool, ethtool_state->requested) == 0)
 		return;
 
-	platform = nm_device_get_platform (self);
-
-	features = nm_platform_ethtool_get_link_features (platform, ifindex);
+	features = nm_platform_ethtool_get_link_features (platform,
+	                                                  ethtool_state->ifindex);
 	if (!features) {
 		_LOGW (LOGD_DEVICE, "ethtool: failure setting offload features (cannot read features)");
 		return;
 	}
 
 	if (!nm_platform_ethtool_set_features (platform,
-	                                       ifindex,
+	                                       ethtool_state->ifindex,
 	                                       features,
 	                                       ethtool_state->requested,
 	                                       TRUE))
@@ -876,9 +864,259 @@ _ethtool_state_set (NMDevice *self)
 	else
 		_LOGD (LOGD_DEVICE, "ethtool: offload features successfully set");
 
-	ethtool_state->ifindex = ifindex;
 	ethtool_state->features = g_steal_pointer (&features);
-	priv->ethtool_state = g_steal_pointer (&ethtool_state);
+}
+
+static void
+_ethtool_coalesce_reset (NMDevice *self,
+                         NMPlatform *platform,
+                         EthtoolState *ethtool_state)
+{
+	gs_free NMEthtoolCoalesceState *coalesce = NULL;
+
+	nm_assert (NM_IS_DEVICE (self));
+	nm_assert (NM_IS_PLATFORM (platform));
+	nm_assert (ethtool_state);
+
+	coalesce = g_steal_pointer (&ethtool_state->coalesce);
+	if (!coalesce)
+		return;
+
+	if (!nm_platform_ethtool_set_coalesce (platform,
+	                                       ethtool_state->ifindex,
+	                                       coalesce))
+		_LOGW (LOGD_DEVICE, "ethtool: failure resetting one or more coalesce settings");
+	else
+		_LOGD (LOGD_DEVICE, "ethtool: coalesce settings successfully reset");
+}
+
+static void
+_ethtool_coalesce_set (NMDevice *self,
+                       NMPlatform *platform,
+                       EthtoolState *ethtool_state,
+                       NMSettingEthtool *s_ethtool)
+{
+	NMEthtoolCoalesceState coalesce_old;
+	NMEthtoolCoalesceState coalesce_new;
+	gboolean has_old = FALSE;
+	GHashTable *hash;
+	GHashTableIter iter;
+	const char *name;
+	GVariant *variant;
+
+	nm_assert (NM_IS_DEVICE (self));
+	nm_assert (NM_IS_PLATFORM (platform));
+	nm_assert (NM_IS_SETTING_ETHTOOL (s_ethtool));
+	nm_assert (ethtool_state);
+	nm_assert (!ethtool_state->coalesce);
+
+	hash = _nm_setting_option_hash (NM_SETTING (s_ethtool), FALSE);
+	if (!hash)
+		return;
+
+	g_hash_table_iter_init (&iter, hash);
+	while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &variant)) {
+		NMEthtoolID ethtool_id = nm_ethtool_id_get_by_name (name);
+
+		if (!nm_ethtool_id_is_coalesce (ethtool_id))
+			continue;
+
+		if (!has_old) {
+			if (!nm_platform_ethtool_get_link_coalesce (platform,
+			                                            ethtool_state->ifindex,
+			                                            &coalesce_old)) {
+				_LOGW (LOGD_DEVICE, "ethtool: failure getting coalesce settings (cannot read)");
+				return;
+			}
+			has_old = TRUE;
+			coalesce_new = coalesce_old;
+		}
+
+		nm_assert (g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT32));
+		coalesce_new.s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (ethtool_id)] = g_variant_get_uint32 (variant);
+	}
+
+	if (!has_old)
+		return;
+
+	ethtool_state->coalesce = nm_memdup (&coalesce_old, sizeof (coalesce_old));
+
+	if (!nm_platform_ethtool_set_coalesce (platform,
+	                                       ethtool_state->ifindex,
+	                                       &coalesce_new)) {
+		_LOGW (LOGD_DEVICE, "ethtool: failure setting coalesce settings");
+		return;
+	}
+
+	_LOGD (LOGD_DEVICE, "ethtool: coalesce settings successfully set");
+}
+
+static void
+_ethtool_ring_reset (NMDevice *self,
+                     NMPlatform *platform,
+                     EthtoolState *ethtool_state)
+{
+	gs_free NMEthtoolRingState *ring = NULL;
+
+	nm_assert (NM_IS_DEVICE (self));
+	nm_assert (NM_IS_PLATFORM (platform));
+	nm_assert (ethtool_state);
+
+	ring = g_steal_pointer (&ethtool_state->ring);
+	if (!ring)
+		return;
+
+	if (!nm_platform_ethtool_set_ring (platform,
+	                                   ethtool_state->ifindex,
+	                                   ring))
+		_LOGW (LOGD_DEVICE, "ethtool: failure resetting one or more ring settings");
+	else
+		_LOGD (LOGD_DEVICE, "ethtool: ring settings successfully reset");
+}
+
+static void
+_ethtool_ring_set (NMDevice *self,
+                   NMPlatform *platform,
+                   EthtoolState *ethtool_state,
+                   NMSettingEthtool *s_ethtool)
+{
+	NMEthtoolRingState ring_old;
+	NMEthtoolRingState ring_new;
+	GHashTable *hash;
+	GHashTableIter iter;
+	const char *name;
+	GVariant *variant;
+	gboolean has_old = FALSE;
+
+	nm_assert (NM_IS_DEVICE (self));
+	nm_assert (NM_IS_PLATFORM (platform));
+	nm_assert (NM_IS_SETTING_ETHTOOL (s_ethtool));
+	nm_assert (ethtool_state);
+	nm_assert (!ethtool_state->ring);
+
+	hash = _nm_setting_option_hash (NM_SETTING (s_ethtool), FALSE);
+	if (!hash)
+		return;
+
+	g_hash_table_iter_init (&iter, hash);
+	while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &variant)) {
+		NMEthtoolID ethtool_id = nm_ethtool_id_get_by_name (name);
+		guint32 u32;
+
+		if (!nm_ethtool_id_is_ring (ethtool_id))
+			continue;
+
+		nm_assert (g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT32));
+
+		if (!has_old) {
+			if (!nm_platform_ethtool_get_link_ring (platform,
+			                                        ethtool_state->ifindex,
+			                                        &ring_old)) {
+				_LOGW (LOGD_DEVICE, "ethtool: failure setting ring options (cannot read existing setting)");
+				return;
+			}
+			has_old = TRUE;
+			ring_new = ring_old;
+		}
+
+		u32 = g_variant_get_uint32 (variant);
+
+		switch (ethtool_id) {
+		case NM_ETHTOOL_ID_RING_RX:
+			ring_new.rx_pending = u32;
+			break;
+		case NM_ETHTOOL_ID_RING_RX_JUMBO:
+			ring_new.rx_jumbo_pending = u32;
+			break;
+		case NM_ETHTOOL_ID_RING_RX_MINI:
+			ring_new.rx_mini_pending = u32;
+			break;
+		case NM_ETHTOOL_ID_RING_TX:
+			ring_new.tx_pending = u32;
+			break;
+		default:
+			nm_assert_not_reached ();
+		}
+	}
+
+	if (!has_old)
+		return;
+
+	ethtool_state->ring = nm_memdup (&ring_old, sizeof (ring_old));
+
+	if (!nm_platform_ethtool_set_ring (platform,
+	                                   ethtool_state->ifindex,
+	                                   &ring_new)) {
+		_LOGW (LOGD_DEVICE, "ethtool: failure setting ring settings");
+		return;
+	}
+
+	_LOGD (LOGD_DEVICE, "ethtool: ring settings successfully set");
+}
+
+static void
+_ethtool_state_reset (NMDevice *self)
+{
+	NMPlatform *platform = nm_device_get_platform (self);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	gs_free EthtoolState *ethtool_state = g_steal_pointer (&priv->ethtool_state);
+
+	if (!ethtool_state)
+		return;
+
+	if (ethtool_state->features)
+		_ethtool_features_reset (self, platform, ethtool_state);
+	if (ethtool_state->coalesce)
+		_ethtool_coalesce_reset (self, platform, ethtool_state);
+	if (ethtool_state->ring)
+		_ethtool_ring_reset (self, platform, ethtool_state);
+}
+
+static void
+_ethtool_state_set (NMDevice *self)
+{
+	int ifindex;
+	NMPlatform *platform;
+	NMConnection *connection;
+	NMSettingEthtool *s_ethtool;
+	gs_free EthtoolState *ethtool_state = NULL;
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	ifindex = nm_device_get_ip_ifindex (self);
+	if (ifindex <= 0)
+		return;
+
+	platform = nm_device_get_platform (self);
+	nm_assert (platform);
+
+	connection = nm_device_get_applied_connection (self);
+	if (!connection)
+		return;
+
+	s_ethtool = NM_SETTING_ETHTOOL (nm_connection_get_setting (connection, NM_TYPE_SETTING_ETHTOOL));
+	if (!s_ethtool)
+		return;
+
+	ethtool_state = g_new0 (EthtoolState, 1);
+	ethtool_state->ifindex = ifindex;
+
+	_ethtool_features_set (self,
+	                       platform,
+	                       ethtool_state,
+	                       s_ethtool);
+	_ethtool_coalesce_set (self,
+	                       platform,
+	                       ethtool_state,
+	                       s_ethtool);
+	_ethtool_ring_set (self,
+	                   platform,
+	                   ethtool_state,
+	                   s_ethtool);
+
+	if (   ethtool_state->features
+	    || ethtool_state->coalesce
+	    || ethtool_state->ring)
+		priv->ethtool_state = g_steal_pointer (&ethtool_state);
 }
 
 /*****************************************************************************/
@@ -1265,6 +1503,50 @@ nm_device_sysctl_ip_conf_get_int_checked (NMDevice *self,
 	                                                   fallback);
 }
 
+static void
+set_ipv6_token (NMDevice *self, NMUtilsIPv6IfaceId iid, const char *token_str)
+{
+	NMPlatform *platform;
+	int ifindex;
+	const NMPlatformLink *link;
+	char buf[32];
+	gint64 val;
+
+	/* Setting the kernel token is not strictly necessary as the
+	 * IPv6 address is generated in userspace. However it is
+	 * convenient so that users can see the token with iproute
+	 * ('ip token'). */
+	platform = nm_device_get_platform (self);
+	ifindex = nm_device_get_ip_ifindex (self);
+	link = nm_platform_link_get (platform, ifindex);
+
+	if (link && link->inet6_token.id == iid.id) {
+		_LOGT (LOGD_DEVICE | LOGD_IP6, "token %s already set", token_str);
+		return;
+	}
+
+	/* The kernel allows setting a token only when 'accept_ra'
+	 * is 1: temporarily flip it if necessary; unfortunately
+	 * this will also generate an additional Router Solicitation
+	 * from kernel. */
+	val = nm_device_sysctl_ip_conf_get_int_checked (self,
+	                                                AF_INET6,
+	                                                "accept_ra",
+	                                                10,
+	                                                G_MININT32,
+	                                                G_MAXINT32,
+	                                                1);
+	if (val != 1)
+		nm_device_sysctl_ip_conf_set (self, AF_INET6, "accept_ra", "1");
+
+	nm_platform_link_set_ipv6_token (platform, ifindex, iid);
+
+	if (val != 1) {
+		nm_sprintf_buf (buf, "%d", (int) val);
+		nm_device_sysctl_ip_conf_set (self, AF_INET6, "accept_ra", buf);
+	}
+}
+
 gboolean
 nm_device_sysctl_ip_conf_set (NMDevice *self,
                               int addr_family,
@@ -3565,7 +3847,8 @@ nm_device_update_dynamic_ip_setup (NMDevice *self)
 		/* FIXME: todo */
 	}
 
-	if (priv->lldp_listener && nm_lldp_listener_is_running (priv->lldp_listener)) {
+	if (   priv->lldp_listener
+	    && nm_lldp_listener_is_running (priv->lldp_listener)) {
 		nm_lldp_listener_stop (priv->lldp_listener);
 		if (!nm_lldp_listener_start (priv->lldp_listener, nm_device_get_ifindex (self), &error)) {
 			_LOGD (LOGD_DEVICE, "LLDP listener %p could not be restarted: %s",
@@ -3957,6 +4240,13 @@ device_link_changed (NMDevice *self)
 		_notify (self, PROP_UDI);
 	}
 
+	str = nm_platform_link_get_path (nm_device_get_platform (self), pllink->ifindex);
+	if (!nm_streq0 (str, priv->path)) {
+		g_free (priv->path);
+		priv->path = g_strdup (str);
+		_notify (self, PROP_PATH);
+	}
+
 	if (!nm_streq0 (pllink->driver, priv->driver)) {
 		g_free (priv->driver);
 		priv->driver = g_strdup (pllink->driver);
@@ -4342,6 +4632,13 @@ nm_device_update_from_platform_link (NMDevice *self, const NMPlatformLink *plink
 		_notify (self, PROP_UDI);
 	}
 
+	str = plink ? nm_platform_link_get_path (nm_device_get_platform (self), plink->ifindex) : NULL;
+	if (g_strcmp0 (str, priv->path)) {
+		g_free (priv->path);
+		priv->path = g_strdup (str);
+		_notify (self, PROP_PATH);
+	}
+
 	str = plink ? plink->name : NULL;
 	if (str && g_strcmp0 (str, priv->iface)) {
 		g_free (priv->iface);
@@ -4872,6 +5169,10 @@ nm_device_unrealize (NMDevice *self, gboolean remove_resources, GError **error)
 		nm_clear_g_free (&priv->udi);
 		_notify (self, PROP_UDI);
 	}
+	if (priv->path) {
+		nm_clear_g_free (&priv->path);
+		_notify (self, PROP_PATH);
+	}
 	if (priv->physical_port_id) {
 		nm_clear_g_free (&priv->physical_port_id);
 		_notify (self, PROP_PHYSICAL_PORT_ID);
@@ -5994,11 +6295,11 @@ nm_device_match_parent_hwaddr (NMDevice *device,
 static gboolean
 check_connection_compatible (NMDevice *self, NMConnection *connection, GError **error)
 {
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	const char *device_iface = nm_device_get_iface (self);
 	gs_free_error GError *local = NULL;
 	gs_free char *conn_iface = NULL;
 	NMDeviceClass *klass;
-	const char *const *patterns;
 	NMSettingMatch *s_match;
 
 	klass = NM_DEVICE_GET_CLASS (self);
@@ -6041,6 +6342,8 @@ check_connection_compatible (NMDevice *self, NMConnection *connection, GError **
 	s_match = (NMSettingMatch *) nm_connection_get_setting (connection,
 	                                                        NM_TYPE_SETTING_MATCH);
 	if (s_match) {
+		const char *const *patterns;
+		const char *device_driver;
 		guint num_patterns = 0;
 
 		patterns = nm_setting_match_get_interface_names (s_match, &num_patterns);
@@ -6049,6 +6352,101 @@ check_connection_compatible (NMDevice *self, NMConnection *connection, GError **
 			                            "device does not satisfy match.interface-name property");
 			return FALSE;
 		}
+
+		{
+			const char *const*proc_cmdline;
+			gboolean pos_patterns = FALSE;
+			guint i;
+
+			patterns = nm_setting_match_get_kernel_command_lines (s_match, &num_patterns);
+			proc_cmdline = nm_utils_proc_cmdline_split ();
+
+			for (i = 0; i < num_patterns; i++) {
+				const char *patterns_i = patterns[i];
+				const char *const*proc_cmdline_i;
+				gboolean negative = FALSE;
+				gboolean found = FALSE;
+				const char *equal;
+
+				if (patterns_i[0] == '!') {
+					++patterns_i;
+					negative = TRUE;
+				} else
+					pos_patterns = TRUE;
+
+				equal = strchr (patterns_i, '=');
+
+				proc_cmdline_i = proc_cmdline;
+				while (*proc_cmdline_i) {
+					if (equal) {
+						/* if pattern contains = compare full key=value */
+						found = nm_streq (*proc_cmdline_i, patterns_i);
+					} else {
+						gsize l = strlen (patterns_i);
+
+						/* otherwise consider pattern as key only */
+						if (   strncmp (*proc_cmdline_i, patterns_i, l) == 0
+						    && NM_IN_SET ((*proc_cmdline_i)[l], '\0', '='))
+							found = TRUE;
+					}
+					if (   found
+					    && negative) {
+						/* first negative match */
+						nm_utils_error_set (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+						                    "device does not satisfy match.kernel-command-line property %s",
+						                    patterns[i]);
+						return FALSE;
+					}
+					proc_cmdline_i++;
+				}
+
+				/* FIXME(release-blocker): match.interface-name and match.driver have the meaning,
+				 * that any of the matches may yield success. For match.kernel-command-line, we
+				 * do here that all must match. This inconsistency is undesired.
+				 *
+				 * 1) improve gtk-doc documentation explaining how these options match.
+				 *
+				 * 2) possibly unify the behavior so that kernel-command-line behaves like other
+				 *    matches (and ANY may match). Note that this would be contrary to systemd's
+				 *    Conditions, which by default requires that ALL conditions match (AND). We
+				 *    should be consistent within our match options, and not with systemd here.
+				 *
+				 * 2b) Note that systemd supports special token like "=|", to indicate that
+				 *    ANY behavior. If we want, we could also introduce two special prefixes
+				 *    "&..." and "|...", to support either. It's slightly complicated how
+				 *    these work in combinations with "!".
+				 *    Unless we fully decide what we do about this, NMSettingMatch.verify() should
+				 *    reject matches that start with '&' or '|', because these will be reserved for
+				 *    future use.
+				 *
+				 * 3) while fixing this, this code should move to a separate function so we
+				 *    can unit test the match of kernel command lines.
+				 */
+				if (   pos_patterns
+				    && !found) {
+					/* positive patterns configured but no match */
+					nm_utils_error_set (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+					                    "device does not satisfy any match.kernel-command-line property %s...",
+					                    patterns[0]);
+					return FALSE;
+				}
+			}
+		}
+
+		device_driver = nm_device_get_driver (self);
+		patterns = nm_setting_match_get_drivers (s_match, &num_patterns);
+		if (!nm_wildcard_match_check (device_driver, patterns, num_patterns)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "device does not satisfy match.driver property");
+			return FALSE;
+		}
+
+		patterns = nm_setting_match_get_paths (s_match, &num_patterns);
+		if (!nm_wildcard_match_check (priv->path, patterns, num_patterns)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
+			                            "device does not satisfy match.path property");
+			return FALSE;
+		}
 	}
 
 	return TRUE;
@@ -6627,6 +7025,7 @@ activate_stage1_device_prepare (NMDevice *self)
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS;
 	NMActiveConnection *active;
 	NMActiveConnection *master;
+	NMDeviceClass *klass;
 
 	priv->v4_route_table_initialized = FALSE;
 	priv->v6_route_table_initialized = FALSE;
@@ -6700,18 +7099,21 @@ activate_stage1_device_prepare (NMDevice *self)
 	}
 
 	/* Assumed connections were already set up outside NetworkManager */
-	if (!nm_device_sys_iface_state_is_external_or_assume (self)) {
-		NMDeviceClass *klass = NM_DEVICE_GET_CLASS (self);
+	klass = NM_DEVICE_GET_CLASS (self);
 
-		if (klass->act_stage1_prepare_set_hwaddr_ethernet) {
-			if (!nm_device_hw_addr_set_cloned (self,
-			                                   nm_device_get_applied_connection (self),
-			                                   FALSE)) {
-				nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
-				return;
-			}
+	if (   klass->act_stage1_prepare_set_hwaddr_ethernet
+	    && !nm_device_sys_iface_state_is_external_or_assume (self)) {
+		if (!nm_device_hw_addr_set_cloned (self,
+		                                   nm_device_get_applied_connection (self),
+		                                   FALSE)) {
+			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+			return;
 		}
+	}
 
+	if (   klass->act_stage1_prepare_also_for_external_or_assume
+	    || !nm_device_sys_iface_state_is_external_or_assume (self)) {
+		nm_assert (!klass->act_stage1_prepare_also_for_external_or_assume || klass->act_stage1_prepare);
 		if (klass->act_stage1_prepare) {
 			NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
 
@@ -6784,7 +7186,8 @@ lldp_init (NMDevice *self, gboolean restart)
 		gs_free_error GError *error = NULL;
 
 		if (priv->lldp_listener) {
-			if (restart && nm_lldp_listener_is_running (priv->lldp_listener))
+			if (   restart
+			    && nm_lldp_listener_is_running (priv->lldp_listener))
 				nm_lldp_listener_stop (priv->lldp_listener);
 		} else {
 			priv->lldp_listener = nm_lldp_listener_new ();
@@ -6921,10 +7324,10 @@ tc_commit (NMDevice *self)
 	gs_unref_ptrarray GPtrArray *qdiscs = NULL;
 	gs_unref_ptrarray GPtrArray *tfilters = NULL;
 	NMSettingTCConfig *s_tc = NULL;
+	NMPlatform *platform;
 	int ip_ifindex;
-	guint nqdiscs, ntfilters;
-	guint i;
 
+	platform = nm_device_get_platform (self);
 	connection = nm_device_get_applied_connection (self);
 	if (connection)
 		s_tc = nm_connection_get_setting_tc_config (connection);
@@ -6934,122 +7337,14 @@ tc_commit (NMDevice *self)
 		return s_tc == NULL;
 
 	if (s_tc) {
-		nqdiscs = nm_setting_tc_config_get_num_qdiscs (s_tc);
-		qdiscs = g_ptr_array_new_full (nqdiscs, (GDestroyNotify) nmp_object_unref);
-
-		for (i = 0; i < nqdiscs; i++) {
-			NMTCQdisc *s_qdisc = nm_setting_tc_config_get_qdisc (s_tc, i);
-			NMPObject *q = nmp_object_new (NMP_OBJECT_TYPE_QDISC, NULL);
-			NMPlatformQdisc *qdisc = NMP_OBJECT_CAST_QDISC (q);
-
-			qdisc->ifindex = ip_ifindex;
-
-			/* Note: kind string is still owned by NMTCTfilter.
-			 * This qdisc instance must not be kept alive beyond this function.
-			 * nm_platform_qdisc_sync() promises to do that. */
-			qdisc->kind = nm_tc_qdisc_get_kind (s_qdisc);
-
-			qdisc->addr_family = AF_UNSPEC;
-			qdisc->handle = nm_tc_qdisc_get_handle (s_qdisc);
-			qdisc->parent = nm_tc_qdisc_get_parent (s_qdisc);
-			qdisc->info = 0;
-
-#define GET_ATTR(name, dst, variant_type, type, dflt) G_STMT_START { \
-	GVariant *_variant = nm_tc_qdisc_get_attribute (s_qdisc, ""name""); \
-	\
-	if (   _variant \
-	    && g_variant_is_of_type (_variant, G_VARIANT_TYPE_ ## variant_type)) \
-		(dst) = g_variant_get_ ## type (_variant); \
-	else \
-		(dst) = (dflt); \
-} G_STMT_END
-
-			if (strcmp (qdisc->kind, "fq_codel") == 0) {
-				GET_ATTR ("limit",        qdisc->fq_codel.limit,        UINT32,  uint32,  0);
-				GET_ATTR ("flows",        qdisc->fq_codel.flows,        UINT32,  uint32,  0);
-				GET_ATTR ("target",       qdisc->fq_codel.target,       UINT32,  uint32,  0);
-				GET_ATTR ("interval",     qdisc->fq_codel.interval,     UINT32,  uint32,  0);
-				GET_ATTR ("quantum",      qdisc->fq_codel.quantum,      UINT32,  uint32,  0);
-				GET_ATTR ("ce_threshold", qdisc->fq_codel.ce_threshold, UINT32,  uint32,  NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED);
-				GET_ATTR ("memory_limit", qdisc->fq_codel.memory_limit, UINT32,  uint32,  NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET);
-				GET_ATTR ("ecn",          qdisc->fq_codel.ecn,          BOOLEAN, boolean, FALSE);
-			}
-
-#undef GET_ADDR
-
-			g_ptr_array_add (qdiscs, q);
-		}
-
-		ntfilters = nm_setting_tc_config_get_num_tfilters (s_tc);
-		tfilters = g_ptr_array_new_full (ntfilters, (GDestroyNotify) nmp_object_unref);
-
-		for (i = 0; i < ntfilters; i++) {
-			NMTCTfilter *s_tfilter = nm_setting_tc_config_get_tfilter (s_tc, i);
-			NMTCAction *action;
-			NMPObject *q = nmp_object_new (NMP_OBJECT_TYPE_TFILTER, NULL);
-			NMPlatformTfilter *tfilter = NMP_OBJECT_CAST_TFILTER (q);
-
-			tfilter->ifindex = ip_ifindex;
-
-			/* Note: kind string is still owned by NMTCTfilter.
-			 * This tfilter instance must not be kept alive beyond this function.
-			 * nm_platform_tfilter_sync() promises to do that. */
-			tfilter->kind = nm_tc_tfilter_get_kind (s_tfilter);
-
-			tfilter->addr_family = AF_UNSPEC;
-			tfilter->handle = nm_tc_tfilter_get_handle (s_tfilter);
-			tfilter->parent = nm_tc_tfilter_get_parent (s_tfilter);
-			tfilter->info = TC_H_MAKE (0, htons (ETH_P_ALL));
-
-			action = nm_tc_tfilter_get_action (s_tfilter);
-			if (action) {
-				GVariant *var;
-
-				/* Note: kind string is still owned by NMTCAction.
-				 * This tfilter instance must not be kept alive beyond this function.
-				 * nm_platform_tfilter_sync() promises to do that. */
-				tfilter->action.kind = nm_tc_action_get_kind (action);
-
-				if (strcmp (tfilter->action.kind, "simple") == 0) {
-					var = nm_tc_action_get_attribute (action, "sdata");
-					if (var && g_variant_is_of_type (var, G_VARIANT_TYPE_BYTESTRING)) {
-						g_strlcpy (tfilter->action.simple.sdata,
-						           g_variant_get_bytestring (var),
-						           sizeof (tfilter->action.simple.sdata));
-					}
-				} else if (strcmp (tfilter->action.kind, "mirred") == 0) {
-					if (nm_tc_action_get_attribute (action, "egress"))
-						tfilter->action.mirred.egress = TRUE;
-
-					if (nm_tc_action_get_attribute (action, "ingress"))
-						tfilter->action.mirred.ingress = TRUE;
-
-					if (nm_tc_action_get_attribute (action, "mirror"))
-						tfilter->action.mirred.mirror = TRUE;
-
-					if (nm_tc_action_get_attribute (action, "redirect"))
-						tfilter->action.mirred.redirect = TRUE;
-
-					var = nm_tc_action_get_attribute (action, "dev");
-					if (var && g_variant_is_of_type (var, G_VARIANT_TYPE_STRING)) {
-						int ifindex;
-
-						ifindex = nm_platform_link_get_ifindex (nm_device_get_platform (self),
-						                                        g_variant_get_string (var, NULL));
-						if (ifindex > 0)
-							tfilter->action.mirred.ifindex = ifindex;
-					}
-				}
-			}
-
-			g_ptr_array_add (tfilters, q);
-		}
+		qdiscs = nm_utils_qdiscs_from_tc_setting (platform, s_tc, ip_ifindex);
+		tfilters = nm_utils_tfilters_from_tc_setting (platform, s_tc, ip_ifindex);
 	}
 
-	if (!nm_platform_qdisc_sync (nm_device_get_platform (self), ip_ifindex, qdiscs))
+	if (!nm_platform_qdisc_sync (platform, ip_ifindex, qdiscs))
 		return FALSE;
 
-	if (!nm_platform_tfilter_sync (nm_device_get_platform (self), ip_ifindex, tfilters))
+	if (!nm_platform_tfilter_sync (platform, ip_ifindex, tfilters))
 		return FALSE;
 
 	return TRUE;
@@ -7841,23 +8136,13 @@ ip_config_merge_and_apply (NMDevice *self,
 	}
 
 	if (!IS_IPv4) {
-		const NMPlatformLink *link;
 		NMUtilsIPv6IfaceId iid;
-		NMPlatform *platform;
-		int ifindex;
 
 		if (   commit
 		    && priv->ndisc_started
 		    && ip6_addr_gen_token
 		    && nm_utils_ipv6_interface_identifier_get_from_token (&iid, ip6_addr_gen_token)) {
-			platform = nm_device_get_platform (self);
-			ifindex = nm_device_get_ip_ifindex (self);
-			link = nm_platform_link_get (platform, ifindex);
-
-			if (link && link->inet6_token.id == iid.id)
-				_LOGT (LOGD_DEVICE | LOGD_IP6, "token %s already set", ip6_addr_gen_token);
-			else
-				nm_platform_link_set_ipv6_token (platform, ifindex, iid);
+			set_ipv6_token (self, iid, ip6_addr_gen_token);
 		}
 	}
 
@@ -8315,6 +8600,37 @@ get_dhcp_hostname_flags (NMDevice *self, int addr_family)
 		return NM_DHCP_HOSTNAME_FLAGS_FQDN_DEFAULT_IP6;
 }
 
+static const char *
+connection_get_mud_url (NMDevice *self,
+                        NMSettingConnection *s_con,
+                        char **out_mud_url)
+{
+	const char *mud_url;
+	gs_free char *s = NULL;
+
+	nm_assert (out_mud_url && !*out_mud_url);
+
+	mud_url = nm_setting_connection_get_mud_url (s_con);
+
+	if (mud_url) {
+		if (nm_streq (mud_url, NM_CONNECTION_MUD_URL_NONE))
+			return NULL;
+		return mud_url;
+	}
+
+	s = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
+	                                           NM_CON_DEFAULT ("connection.mud-url"),
+	                                           self);
+	if (s) {
+		if (nm_streq (s, NM_CONNECTION_MUD_URL_NONE))
+			return NULL;
+		if (nm_sd_http_url_is_valid_https (s))
+			return (*out_mud_url = g_steal_pointer (&s));
+	}
+
+	return NULL;
+}
+
 static GBytes *
 dhcp4_get_client_id (NMDevice *self,
                      NMConnection *connection,
@@ -8453,7 +8769,9 @@ dhcp4_start (NMDevice *self)
 	gs_unref_bytes GBytes *hwaddr = NULL;
 	gs_unref_bytes GBytes *bcast_hwaddr = NULL;
 	gs_unref_bytes GBytes *client_id = NULL;
+	gs_free char *mud_url_free = NULL;
 	NMConnection *connection;
+	NMSettingConnection *s_con;
 	GError *error = NULL;
 	const NMPlatformLink *pllink;
 
@@ -8462,6 +8780,9 @@ dhcp4_start (NMDevice *self)
 
 	s_ip4 = nm_connection_get_setting_ip4_config (connection);
 
+	s_con = nm_connection_get_setting_connection (connection);
+	nm_assert (s_con);
+
 	/* Clear old exported DHCP options */
 	nm_dbus_object_clear_and_unexport (&priv->dhcp_data_4.config);
 	priv->dhcp_data_4.config = nm_dhcp_config_new (AF_INET);
@@ -8488,6 +8809,7 @@ dhcp4_start (NMDevice *self)
 	                                                      nm_setting_ip_config_get_dhcp_hostname (s_ip4),
 	                                                      nm_setting_ip4_config_get_dhcp_fqdn (NM_SETTING_IP4_CONFIG (s_ip4)),
 	                                                      get_dhcp_hostname_flags (self, AF_INET),
+	                                                      connection_get_mud_url (self, s_con, &mud_url_free),
 	                                                      client_id,
 	                                                      get_dhcp_timeout (self, AF_INET),
 	                                                      priv->dhcp_anycast_address,
@@ -9235,15 +9557,19 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 	gs_unref_bytes GBytes *duid = NULL;
 	gboolean enforce_duid = FALSE;
 	const NMPlatformLink *pllink;
+	gs_free char *mud_url_free = NULL;
 	GError *error = NULL;
 	guint32 iaid;
 	gboolean iaid_explicit;
-
+	NMSettingConnection *s_con;
 	const NMPlatformIP6Address *ll_addr = NULL;
 
-	g_assert (connection);
+	g_return_val_if_fail (connection, FALSE);
+
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
-	g_assert (s_ip6);
+	nm_assert (s_ip6);
+	s_con = nm_connection_get_setting_connection (connection);
+	nm_assert (s_con);
 
 	if (priv->ext_ip6_config_captured) {
 		ll_addr = nm_ip6_config_find_first_address (priv->ext_ip6_config_captured,
@@ -9278,6 +9604,7 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 	                                                      nm_setting_ip_config_get_dhcp_send_hostname (s_ip6),
 	                                                      nm_setting_ip_config_get_dhcp_hostname (s_ip6),
 	                                                      get_dhcp_hostname_flags (self, AF_INET6),
+	                                                      connection_get_mud_url (self, s_con, &mud_url_free),
 	                                                      duid,
 	                                                      enforce_duid,
 	                                                      iaid,
@@ -13023,10 +13350,8 @@ nm_device_set_ip_config (NMDevice *self,
 
 		if (   nm_device_sys_iface_state_is_external (self)
 		    && (settings_connection = nm_device_get_settings_connection (self))
-		    && NM_FLAGS_ALL (nm_settings_connection_get_flags (settings_connection),
-		                       NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED
-		                     | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
-		                     | NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)
+		    && NM_FLAGS_HAS (nm_settings_connection_get_flags (settings_connection),
+		                     NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)
 		    && nm_active_connection_get_activation_type (NM_ACTIVE_CONNECTION (priv->act_request.obj)) == NM_ACTIVATION_TYPE_EXTERNAL) {
 			gs_unref_object NMConnection *new_connection = NULL;
 
@@ -15317,11 +15642,13 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 		/* Take out any entries in the routing table and any IP address the device had. */
 		if (ifindex > 0) {
 			NMPlatform *platform = nm_device_get_platform (self);
+			NMUtilsIPv6IfaceId iid = { };
 
 			nm_platform_ip_route_flush (platform, AF_UNSPEC, ifindex);
 			nm_platform_ip_address_flush (platform, AF_UNSPEC, ifindex);
 			nm_platform_tfilter_sync (platform, ifindex, NULL);
 			nm_platform_qdisc_sync (platform, ifindex, NULL);
+			set_ipv6_token (self, iid, "::");
 		}
 	}
 
@@ -17073,6 +17400,11 @@ get_property (GObject *object, guint prop_id,
 		                     nm_utils_str_utf8safe_escape_cp (priv->udi,
 		                                                      NM_UTILS_STR_UTF8_SAFE_FLAG_NONE));
 		break;
+	case PROP_PATH:
+		g_value_take_string (value,
+		                     nm_utils_str_utf8safe_escape_cp (priv->path,
+		                                                      NM_UTILS_STR_UTF8_SAFE_FLAG_NONE));
+		break;
 	case PROP_IFACE:
 		g_value_take_string (value,
 		                     nm_utils_str_utf8safe_escape_cp (priv->iface,
@@ -17590,6 +17922,7 @@ finalize (GObject *object)
 	g_slist_free_full (priv->dad6_failed_addrs, (GDestroyNotify) nmp_object_unref);
 	nm_clear_g_free (&priv->physical_port_id);
 	g_free (priv->udi);
+	g_free (priv->path);
 	g_free (priv->iface);
 	g_free (priv->ip_iface);
 	g_free (priv->driver);
@@ -17674,6 +18007,7 @@ static const NMDBusInterfaceInfoExtended interface_info_device = {
 		),
 		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Udi",                  "s",      NM_DEVICE_UDI),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Path",                 "s",      NM_DEVICE_PATH),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Interface",            "s",      NM_DEVICE_IFACE),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("IpInterface",          "s",      NM_DEVICE_IP_IFACE),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Driver",               "s",      NM_DEVICE_DRIVER),
@@ -17769,6 +18103,11 @@ nm_device_class_init (NMDeviceClass *klass)
 	                         NULL,
 	                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                         G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_PATH] =
+	    g_param_spec_string (NM_DEVICE_PATH, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE |
+	                         G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_IFACE] =
 	    g_param_spec_string (NM_DEVICE_IFACE, "", "",
 	                         NULL,
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index 8d51c707..36cae7a3 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -66,6 +66,7 @@ nm_device_state_reason_check (NMDeviceStateReason reason)
 
 /* Properties */
 #define NM_DEVICE_UDI              "udi"
+#define NM_DEVICE_PATH             "path"
 #define NM_DEVICE_IFACE            "interface"
 #define NM_DEVICE_IP_IFACE         "ip-interface"
 #define NM_DEVICE_DRIVER           "driver"
@@ -445,9 +446,10 @@ typedef struct _NMDeviceClass {
 
 	gboolean        (* set_platform_mtu) (NMDevice *self, guint32 mtu);
 
-	/* Controls, whether to call act_stage2_config() callback also for assuming
-	 * a device or for external activations. In this case, act_stage2_config() must
+	/* Control whether to call stage1 and stage2 callbacks also for assuming
+	 * a device or for external activations. In this case, the callback must
 	 * take care not to touch the device's configuration. */
+	bool act_stage1_prepare_also_for_external_or_assume:1;
 	bool act_stage2_config_also_for_external_or_assume:1;
 
 	bool act_stage1_prepare_set_hwaddr_ethernet:1;
diff --git a/src/devices/nm-lldp-listener.c b/src/devices/nm-lldp-listener.c
index b9caed50..9d37758d 100644
--- a/src/devices/nm-lldp-listener.c
+++ b/src/devices/nm-lldp-listener.c
@@ -16,51 +16,12 @@
 
 #include "systemd/nm-sd.h"
 
-#define MAX_NEIGHBORS         4096
-#define MIN_UPDATE_INTERVAL_NS (2 * NM_UTILS_NSEC_PER_SEC)
-
-#define LLDP_MAC_NEAREST_BRIDGE          ((const struct ether_addr *) ((uint8_t[ETH_ALEN]) { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e }))
-#define LLDP_MAC_NEAREST_NON_TPMR_BRIDGE ((const struct ether_addr *) ((uint8_t[ETH_ALEN]) { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 }))
-#define LLDP_MAC_NEAREST_CUSTOMER_BRIDGE ((const struct ether_addr *) ((uint8_t[ETH_ALEN]) { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 }))
-
-typedef enum {
-	LLDP_ATTR_TYPE_NONE,
-	LLDP_ATTR_TYPE_UINT32,
-	LLDP_ATTR_TYPE_STRING,
-	LLDP_ATTR_TYPE_VARDICT,
-	LLDP_ATTR_TYPE_ARRAY_OF_VARDICTS,
-} LldpAttrType;
-
-typedef enum {
-	/* the order of the enum values determines the order of the fields in
-	 * the variant. */
-	LLDP_ATTR_ID_PORT_DESCRIPTION,
-	LLDP_ATTR_ID_SYSTEM_NAME,
-	LLDP_ATTR_ID_SYSTEM_DESCRIPTION,
-	LLDP_ATTR_ID_SYSTEM_CAPABILITIES,
-	LLDP_ATTR_ID_MANAGEMENT_ADDRESSES,
-	LLDP_ATTR_ID_IEEE_802_1_PVID,
-	LLDP_ATTR_ID_IEEE_802_1_PPVID,
-	LLDP_ATTR_ID_IEEE_802_1_PPVID_FLAGS,
-	LLDP_ATTR_ID_IEEE_802_1_PPVIDS,
-	LLDP_ATTR_ID_IEEE_802_1_VID,
-	LLDP_ATTR_ID_IEEE_802_1_VLAN_NAME,
-	LLDP_ATTR_ID_IEEE_802_1_VLANS,
-	LLDP_ATTR_ID_IEEE_802_3_MAC_PHY_CONF,
-	LLDP_ATTR_ID_IEEE_802_3_POWER_VIA_MDI,
-	LLDP_ATTR_ID_IEEE_802_3_MAX_FRAME_SIZE,
-	_LLDP_ATTR_ID_COUNT,
-} LldpAttrId;
+#define MAX_NEIGHBORS            128
+#define MIN_UPDATE_INTERVAL_NSEC (2 * NM_UTILS_NSEC_PER_SEC)
 
-typedef struct {
-	LldpAttrType attr_type;
-	union {
-		guint32 v_uint32;
-		char *v_string;
-		GVariant *v_variant;
-		CList v_variant_list;
-	};
-} LldpAttrData;
+#define LLDP_MAC_NEAREST_BRIDGE          (&((struct ether_addr) { .ether_addr_octet = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e } }))
+#define LLDP_MAC_NEAREST_NON_TPMR_BRIDGE (&((struct ether_addr) { .ether_addr_octet = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 } }))
+#define LLDP_MAC_NEAREST_CUSTOMER_BRIDGE (&((struct ether_addr) { .ether_addr_octet = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 } }))
 
 /*****************************************************************************/
 
@@ -69,16 +30,15 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMLldpListener,
 );
 
 typedef struct {
-	char         *iface;
-	int           ifindex;
 	sd_lldp      *lldp_handle;
 	GHashTable   *lldp_neighbors;
+	GVariant     *variant;
 
 	/* the timestamp in nsec until which we delay updates. */
-	gint64        ratelimit_next;
+	gint64        ratelimit_next_nsec;
 	guint         ratelimit_id;
 
-	GVariant     *variant;
+	int           ifindex;
 } NMLldpListenerPrivate;
 
 struct _NMLldpListener {
@@ -97,18 +57,12 @@ G_DEFINE_TYPE (NMLldpListener, nm_lldp_listener, G_TYPE_OBJECT)
 /*****************************************************************************/
 
 typedef struct {
-	guint8 chassis_id_type;
-	guint8 port_id_type;
+	GVariant *variant;
+	sd_lldp_neighbor *neighbor_sd;
 	char *chassis_id;
 	char *port_id;
-
-	struct ether_addr destination_address;
-
-	bool valid:1;
-
-	LldpAttrData attrs[_LLDP_ATTR_ID_COUNT];
-
-	GVariant *variant;
+	guint8 chassis_id_type;
+	guint8 port_id_type;
 } LldpNeighbor;
 
 /*****************************************************************************/
@@ -137,175 +91,122 @@ typedef struct {
         } \
     } G_STMT_END \
 
-#define LOG_NEIGH_FMT        "CHASSIS=%s%s%s PORT=%s%s%s"
-#define LOG_NEIGH_ARG(neigh) NM_PRINT_FMT_QUOTE_STRING ((neigh)->chassis_id), NM_PRINT_FMT_QUOTE_STRING ((neigh)->port_id)
-
-/*****************************************************************************/
-
-static gboolean
-ether_addr_equal (const struct ether_addr *a1, const struct ether_addr *a2)
-{
-	nm_assert (a1);
-	nm_assert (a2);
-
-	G_STATIC_ASSERT_EXPR (sizeof (*a1) == ETH_ALEN);
-	return memcmp (a1, a2, ETH_ALEN) == 0;
-}
+#define LOG_NEIGH_FMT        "CHASSIS=%u/%s PORT=%u/%s"
+#define LOG_NEIGH_ARG(neigh) (neigh)->chassis_id_type, (neigh)->chassis_id, (neigh)->port_id_type, (neigh)->port_id
 
 /*****************************************************************************/
 
-static
-NM_UTILS_LOOKUP_STR_DEFINE (_lldp_attr_id_to_name, LldpAttrId,
-	NM_UTILS_LOOKUP_DEFAULT_WARN (NULL),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_PORT_DESCRIPTION,         NM_LLDP_ATTR_PORT_DESCRIPTION),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_SYSTEM_NAME,              NM_LLDP_ATTR_SYSTEM_NAME),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_SYSTEM_DESCRIPTION,       NM_LLDP_ATTR_SYSTEM_DESCRIPTION),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_SYSTEM_CAPABILITIES,      NM_LLDP_ATTR_SYSTEM_CAPABILITIES),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_MANAGEMENT_ADDRESSES,     NM_LLDP_ATTR_MANAGEMENT_ADDRESSES),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_PVID,          NM_LLDP_ATTR_IEEE_802_1_PVID),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_PPVID,         NM_LLDP_ATTR_IEEE_802_1_PPVID),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_PPVID_FLAGS,   NM_LLDP_ATTR_IEEE_802_1_PPVID_FLAGS),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_PPVIDS,        NM_LLDP_ATTR_IEEE_802_1_PPVIDS),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_VID,           NM_LLDP_ATTR_IEEE_802_1_VID),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_VLAN_NAME,     NM_LLDP_ATTR_IEEE_802_1_VLAN_NAME),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_VLANS,         NM_LLDP_ATTR_IEEE_802_1_VLANS),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_3_MAC_PHY_CONF,  NM_LLDP_ATTR_IEEE_802_3_MAC_PHY_CONF),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_3_POWER_VIA_MDI, NM_LLDP_ATTR_IEEE_802_3_POWER_VIA_MDI),
-	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_3_MAX_FRAME_SIZE,NM_LLDP_ATTR_IEEE_802_3_MAX_FRAME_SIZE),
-	NM_UTILS_LOOKUP_ITEM_IGNORE (_LLDP_ATTR_ID_COUNT),
-);
-
-static
-NM_UTILS_LOOKUP_DEFINE (_lldp_attr_id_to_type, LldpAttrId, LldpAttrType,
-	NM_UTILS_LOOKUP_DEFAULT_WARN (LLDP_ATTR_TYPE_NONE),
-	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_PORT_DESCRIPTION,            LLDP_ATTR_TYPE_STRING),
-	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_SYSTEM_NAME,                 LLDP_ATTR_TYPE_STRING),
-	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_SYSTEM_DESCRIPTION,          LLDP_ATTR_TYPE_STRING),
-	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_SYSTEM_CAPABILITIES,         LLDP_ATTR_TYPE_UINT32),
-	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_MANAGEMENT_ADDRESSES,        LLDP_ATTR_TYPE_ARRAY_OF_VARDICTS),
-	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_1_PVID,             LLDP_ATTR_TYPE_UINT32),
-	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_1_PPVID,            LLDP_ATTR_TYPE_UINT32),
-	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_1_PPVID_FLAGS,      LLDP_ATTR_TYPE_UINT32),
-	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_1_PPVIDS,           LLDP_ATTR_TYPE_ARRAY_OF_VARDICTS),
-	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_1_VID,              LLDP_ATTR_TYPE_UINT32),
-	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_1_VLAN_NAME,        LLDP_ATTR_TYPE_STRING),
-	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_1_VLANS,            LLDP_ATTR_TYPE_ARRAY_OF_VARDICTS),
-	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_3_MAC_PHY_CONF,     LLDP_ATTR_TYPE_VARDICT),
-	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_3_POWER_VIA_MDI,    LLDP_ATTR_TYPE_VARDICT),
-	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_3_MAX_FRAME_SIZE,   LLDP_ATTR_TYPE_UINT32),
-	NM_UTILS_LOOKUP_ITEM_IGNORE (_LLDP_ATTR_ID_COUNT),
-);
-
 static void
-_lldp_attr_set_str (LldpAttrData *pdata, LldpAttrId attr_id, const char *v_string)
-{
-	nm_assert (pdata);
-	nm_assert (_lldp_attr_id_to_type (attr_id) == LLDP_ATTR_TYPE_STRING);
-
-	pdata = &pdata[attr_id];
-
-	/* we ignore duplicate fields silently. */
-	if (pdata->attr_type != LLDP_ATTR_TYPE_NONE)
-		return;
-	pdata->attr_type = LLDP_ATTR_TYPE_STRING;
-	pdata->v_string = g_strdup (v_string ?: "");
-}
-
-static void
-_lldp_attr_set_str_take (LldpAttrData *pdata, LldpAttrId attr_id, char *str)
-{
-	nm_assert (pdata);
-	nm_assert (_lldp_attr_id_to_type (attr_id) == LLDP_ATTR_TYPE_STRING);
-
-	pdata = &pdata[attr_id];
-
-	/* we ignore duplicate fields silently. */
-	if (pdata->attr_type != LLDP_ATTR_TYPE_NONE) {
-		g_free (str);
-		return;
-	}
-
-	pdata->attr_type = LLDP_ATTR_TYPE_STRING;
-	pdata->v_string = str;
-}
-
-static void
-_lldp_attr_set_uint32 (LldpAttrData *pdata, LldpAttrId attr_id, guint32 v_uint32)
-{
-	nm_assert (pdata);
-	nm_assert (_lldp_attr_id_to_type (attr_id) == LLDP_ATTR_TYPE_UINT32);
-
-	pdata = &pdata[attr_id];
-
-	/* we ignore duplicate fields silently. */
-	if (pdata->attr_type != LLDP_ATTR_TYPE_NONE)
-		return;
-	pdata->attr_type = LLDP_ATTR_TYPE_UINT32;
-	pdata->v_uint32 = v_uint32;
-}
-
-static void
-_lldp_attr_set_vardict (LldpAttrData *pdata, LldpAttrId attr_id, GVariant *variant)
+lldp_neighbor_get_raw (LldpNeighbor *neigh,
+                       const guint8 **out_raw_data,
+                       gsize *out_raw_len)
 {
+	gconstpointer raw_data;
+	gsize raw_len;
+	int r;
 
-	nm_assert (pdata);
-	nm_assert (_lldp_attr_id_to_type (attr_id) == LLDP_ATTR_TYPE_VARDICT);
+	nm_assert (neigh);
 
-	pdata = &pdata[attr_id];
+	r = sd_lldp_neighbor_get_raw (neigh->neighbor_sd, &raw_data, &raw_len);
 
-	/* we ignore duplicate fields silently */
-	if (pdata->attr_type != LLDP_ATTR_TYPE_NONE) {
-		nm_g_variant_unref_floating (variant);
-		return;
-	}
+	nm_assert (r >= 0);
+	nm_assert (raw_data);
+	nm_assert (raw_len > 0);
 
-	pdata->attr_type = LLDP_ATTR_TYPE_VARDICT;
-	pdata->v_variant = g_variant_ref_sink (variant);
+	*out_raw_data = raw_data;
+	*out_raw_len = raw_len;
 }
 
-static void
-_lldp_attr_add_vardict (LldpAttrData *pdata, LldpAttrId attr_id, GVariant *variant)
+static gboolean
+lldp_neighbor_id_get (struct sd_lldp_neighbor *neighbor_sd,
+                      guint8 *out_chassis_id_type,
+                      const guint8 **out_chassis_id,
+                      gsize *out_chassis_id_len,
+                      guint8 *out_port_id_type,
+                      const guint8 **out_port_id,
+                      gsize *out_port_id_len)
 {
-	nm_assert (pdata);
-	nm_assert (_lldp_attr_id_to_type (attr_id) == LLDP_ATTR_TYPE_ARRAY_OF_VARDICTS);
+	int r;
 
-	g_variant_ref_sink (variant);
-	pdata = &pdata[attr_id];
+	r = sd_lldp_neighbor_get_chassis_id (neighbor_sd,
+	                                     out_chassis_id_type,
+	                                     (gconstpointer *) out_chassis_id,
+	                                     out_chassis_id_len);
+	if (r < 0)
+		return FALSE;
 
-	if (pdata->attr_type == LLDP_ATTR_TYPE_NONE) {
-		c_list_init (&pdata->v_variant_list);
-		pdata->attr_type = LLDP_ATTR_TYPE_ARRAY_OF_VARDICTS;
-	} else
-		nm_assert (pdata->attr_type == LLDP_ATTR_TYPE_ARRAY_OF_VARDICTS);
+	r = sd_lldp_neighbor_get_port_id (neighbor_sd,
+	                                  out_port_id_type,
+	                                  (gconstpointer *) out_port_id,
+	                                  out_port_id_len);
+	if (r < 0)
+		return FALSE;
 
-	c_list_link_tail (&pdata->v_variant_list, &nm_c_list_elem_new_stale (variant)->lst);
+	return TRUE;
 }
 
-/*****************************************************************************/
-
 static guint
 lldp_neighbor_id_hash (gconstpointer ptr)
 {
 	const LldpNeighbor *neigh = ptr;
+	guint8 chassis_id_type;
+	guint8 port_id_type;
+	const guint8 *chassis_id;
+	const guint8 *port_id;
+	gsize chassis_id_len;
+	gsize port_id_len;
 	NMHashState h;
 
+	if (!lldp_neighbor_id_get (neigh->neighbor_sd, &chassis_id_type, &chassis_id, &chassis_id_len, &port_id_type, &port_id, &port_id_len)) {
+		nm_assert_not_reached ();
+		return 0;
+	}
+
 	nm_hash_init (&h, 23423423u);
-	nm_hash_update_str0 (&h, neigh->chassis_id);
-	nm_hash_update_str0 (&h, neigh->port_id);
 	nm_hash_update_vals (&h,
-	                     neigh->chassis_id_type,
-	                     neigh->port_id_type);
+	                     chassis_id_len,
+	                     port_id_len,
+	                     chassis_id_type,
+	                     port_id_type);
+	nm_hash_update (&h, chassis_id, chassis_id_len);
+	nm_hash_update (&h, port_id, port_id_len);
 	return nm_hash_complete (&h);
 }
 
 static int
-lldp_neighbor_id_cmp (const LldpNeighbor *x, const LldpNeighbor *y)
+lldp_neighbor_id_cmp (const LldpNeighbor *a, const LldpNeighbor *b)
 {
-	NM_CMP_SELF (x, y);
-	NM_CMP_FIELD (x, y, chassis_id_type);
-	NM_CMP_FIELD (x, y, port_id_type);
-	NM_CMP_FIELD_STR0 (x, y, chassis_id);
-	NM_CMP_FIELD_STR0 (x, y, port_id);
+	guint8 a_chassis_id_type;
+	guint8 b_chassis_id_type;
+	guint8 a_port_id_type;
+	guint8 b_port_id_type;
+	const guint8 *a_chassis_id;
+	const guint8 *b_chassis_id;
+	const guint8 *a_port_id;
+	const guint8 *b_port_id;
+	gsize a_chassis_id_len;
+	gsize b_chassis_id_len;
+	gsize a_port_id_len;
+	gsize b_port_id_len;
+
+	NM_CMP_SELF (a, b);
+
+	if (!lldp_neighbor_id_get (a->neighbor_sd, &a_chassis_id_type, &a_chassis_id, &a_chassis_id_len, &a_port_id_type, &a_port_id, &a_port_id_len)) {
+		nm_assert_not_reached ();
+		return FALSE;
+	}
+
+	if (!lldp_neighbor_id_get (b->neighbor_sd, &b_chassis_id_type, &b_chassis_id, &b_chassis_id_len, &b_port_id_type, &b_port_id, &b_port_id_len)) {
+		nm_assert_not_reached ();
+		return FALSE;
+	}
+
+	NM_CMP_DIRECT (a_chassis_id_type, b_chassis_id_type);
+	NM_CMP_DIRECT (a_port_id_type, b_port_id_type);
+	NM_CMP_DIRECT (a_chassis_id_len, b_chassis_id_len);
+	NM_CMP_DIRECT (a_port_id_len, b_port_id_len);
+	NM_CMP_DIRECT_MEMCMP (a_chassis_id, b_chassis_id, a_chassis_id_len);
+	NM_CMP_DIRECT_MEMCMP (a_port_id, b_port_id, a_port_id_len);
 	return 0;
 }
 
@@ -325,33 +226,14 @@ lldp_neighbor_id_equal (gconstpointer a, gconstpointer b)
 static void
 lldp_neighbor_free (LldpNeighbor *neighbor)
 {
-	LldpAttrId attr_id;
-	LldpAttrType attr_type;
-
-	if (neighbor) {
-		g_free (neighbor->chassis_id);
-		g_free (neighbor->port_id);
-		for (attr_id = 0; attr_id < _LLDP_ATTR_ID_COUNT; attr_id++) {
-			attr_type = neighbor->attrs[attr_id].attr_type;
-
-			switch (attr_type) {
-			case LLDP_ATTR_TYPE_STRING:
-				g_free (neighbor->attrs[attr_id].v_string);
-				break;
-			case LLDP_ATTR_TYPE_VARDICT:
-				g_variant_unref (neighbor->attrs[attr_id].v_variant);
-				break;
-			case LLDP_ATTR_TYPE_ARRAY_OF_VARDICTS:
-				nm_c_list_elem_free_all (&neighbor->attrs[attr_id].v_variant_list,
-				                         (GDestroyNotify) g_variant_unref);
-				break;
-			default:
-				;
-			}
-		}
-		nm_clear_pointer (&neighbor->variant, g_variant_unref);
-		g_slice_free (LldpNeighbor, neighbor);
-	}
+	if (!neighbor)
+		return;
+
+	g_free (neighbor->chassis_id);
+	g_free (neighbor->port_id);
+	nm_g_variant_unref (neighbor->variant);
+	sd_lldp_neighbor_unref (neighbor->neighbor_sd);
+	nm_g_slice_free (neighbor);
 }
 
 static void
@@ -363,45 +245,32 @@ lldp_neighbor_freep (LldpNeighbor **ptr)
 static gboolean
 lldp_neighbor_equal (LldpNeighbor *a, LldpNeighbor *b)
 {
-	LldpAttrId attr_id;
-
-	nm_assert (a);
-	nm_assert (b);
-
-	if (   a->chassis_id_type != b->chassis_id_type
-	    || a->port_id_type != b->port_id_type
-	    || ether_addr_equal (&a->destination_address, &b->destination_address)
-	    || !nm_streq0 (a->chassis_id, b->chassis_id)
-	    || !nm_streq0 (a->port_id, b->port_id))
-		return FALSE;
-
-	for (attr_id = 0; attr_id < _LLDP_ATTR_ID_COUNT; attr_id++) {
-		if (a->attrs[attr_id].attr_type != b->attrs[attr_id].attr_type)
-			return FALSE;
-		switch (a->attrs[attr_id].attr_type) {
-		case LLDP_ATTR_TYPE_UINT32:
-			if (a->attrs[attr_id].v_uint32 != b->attrs[attr_id].v_uint32)
-				return FALSE;
-			break;
-		case LLDP_ATTR_TYPE_STRING:
-			if (!nm_streq (a->attrs[attr_id].v_string, b->attrs[attr_id].v_string))
-				return FALSE;
-			break;
-		default:
-			nm_assert (a->attrs[attr_id].attr_type == LLDP_ATTR_TYPE_NONE);
-			break;
-		}
-	}
-
-	return TRUE;
+	const guint8 *raw_data_a;
+	const guint8 *raw_data_b;
+	gsize raw_len_a;
+	gsize raw_len_b;
+
+	if (a->neighbor_sd == b->neighbor_sd)
+		return TRUE;
+
+	lldp_neighbor_get_raw (a, &raw_data_a, &raw_len_a);
+	lldp_neighbor_get_raw (b, &raw_data_b, &raw_len_b);
+	return    raw_len_a == raw_len_b
+	       && (memcmp (raw_data_a, raw_data_b, raw_len_a) == 0);
 }
 
 static GVariant *
-parse_management_address_tlv (uint8_t *data, gsize len)
+parse_management_address_tlv (const uint8_t *data, gsize len)
 {
-	GVariantDict dict;
-	GVariant *variant;
-	gsize addr_len, oid_len;
+	GVariantBuilder builder;
+	gsize addr_len;
+	const guint8 *v_object_id_arr;
+	gsize v_object_id_len;
+	const guint8 *v_address_arr;
+	gsize v_address_len;
+	guint32 v_interface_number;
+	guint32 v_interface_number_subtype;
+	guint32 v_address_subtype;
 
 	/* 802.1AB-2009 - Figure 8-11
 	 *
@@ -416,7 +285,7 @@ parse_management_address_tlv (uint8_t *data, gsize len)
 	 */
 
 	if (len < 11)
-		goto err;
+		return NULL;
 
 	nm_assert ((data[0] >> 1) == SD_LLDP_TYPE_MGMT_ADDRESS);
 	nm_assert ((((data[0] & 1) << 8) + data[1]) + 2 == len);
@@ -426,388 +295,467 @@ parse_management_address_tlv (uint8_t *data, gsize len)
 	addr_len = *data; /* length of (address subtype + address) */
 
 	if (addr_len < 2 || addr_len > 32)
-		goto err;
+		return NULL;
 	if (len < (  1         /* address stringth length */
 	           + addr_len  /* address subtype + address */
 	           + 5         /* interface */
 	           + 1))       /* oid */
-		goto err;
-
-	g_variant_dict_init (&dict, NULL);
+		return NULL;
 
 	data++;
 	len--;
-	g_variant_dict_insert (&dict, "address-subtype", "u", (guint32) *data);
-	variant = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, data + 1, addr_len - 1, 1);
-	g_variant_dict_insert_value (&dict, "address", variant);
+	v_address_subtype = *data;
+	v_address_arr = &data[1];
+	v_address_len = addr_len - 1;
 
 	data += addr_len;
 	len -= addr_len;
-	g_variant_dict_insert (&dict, "interface-number-subtype", "u", (guint32) *data);
+	v_interface_number_subtype = *data;
 
 	data++;
 	len--;
-	g_variant_dict_insert (&dict, "interface-number", "u", unaligned_read_be32 (data));
+	v_interface_number = unaligned_read_be32 (data);
 
 	data += 4;
 	len -= 4;
-	oid_len = *data;
+	v_object_id_len = *data;
+	if (len < (1 + v_object_id_len))
+		return NULL;
+	data++;
+	v_object_id_arr = data;
 
-	if (len < (1 + oid_len))
-		goto err;
+	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+	nm_g_variant_builder_add_sv_uint32 (&builder, "address-subtype", v_address_subtype);
+	nm_g_variant_builder_add_sv_bytearray (&builder, "address", v_address_arr, v_address_len);
+	nm_g_variant_builder_add_sv_uint32 (&builder, "interface-number-subtype", v_interface_number_subtype);
+	nm_g_variant_builder_add_sv_uint32 (&builder, "interface-number", v_interface_number);
+	if (v_object_id_len > 0)
+		nm_g_variant_builder_add_sv_bytearray (&builder, "object-id", v_object_id_arr, v_object_id_len);
+	return g_variant_builder_end (&builder);
+}
 
-	data++;
-	variant = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, data, oid_len, 1);
-	g_variant_dict_insert_value (&dict, "object-id", variant);
-	return g_variant_dict_end (&dict);
-err:
-	g_variant_dict_clear (&dict);
-	return NULL;
+static char *
+format_network_address (const guint8 *data, gsize sz)
+{
+	NMIPAddr a;
+	int family;
+
+	if (   sz == 5
+	    && data[0] == 1 /* LLDP_MGMT_ADDR_IP4 */) {
+		memcpy (&a, &data[1], sizeof (a.addr4));
+		family = AF_INET;
+	} else if (   sz == 17
+	           && data[0] == 2 /* LLDP_MGMT_ADDR_IP6 */) {
+		memcpy (&a, &data[1], sizeof (a.addr6));
+		family = AF_INET6;
+	} else
+		return NULL;
+
+	return nm_utils_inet_ntop_dup (family, &a);
 }
 
-static LldpNeighbor *
-lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
+static const char *
+format_string (const guint8 *data, gsize len, gboolean allow_trim, char **out_to_free)
 {
-	nm_auto (lldp_neighbor_freep) LldpNeighbor *neigh = NULL;
-	uint8_t chassis_id_type, port_id_type;
-	uint16_t data16;
-	uint8_t *data8;
-	const void *chassis_id, *port_id;
-	gsize chassis_id_len, port_id_len, len;
-	const char *str;
-	int r;
+	gboolean is_null_terminated = FALSE;
 
-	r = sd_lldp_neighbor_get_chassis_id (neighbor_sd, &chassis_id_type,
-	                                     &chassis_id, &chassis_id_len);
-	if (r < 0) {
-		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "failed reading chassis-id: %s", nm_strerror_native (-r));
-		return NULL;
-	}
-	if (chassis_id_len < 1) {
-		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "empty chassis-id");
-		return NULL;
+	nm_assert (out_to_free && !*out_to_free);
+
+	if (allow_trim) {
+		while (   len > 0
+		       && data[len - 1] == '\0') {
+			is_null_terminated = TRUE;
+			len--;
+		}
 	}
 
-	r = sd_lldp_neighbor_get_port_id (neighbor_sd, &port_id_type,
-	                                  &port_id, &port_id_len);
-	if (r < 0) {
-		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "failed reading port-id: %s", nm_strerror_native (-r));
+	if (len == 0)
 		return NULL;
-	}
-	if (port_id_len < 1) {
-		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "empty port-id");
+
+	if (memchr (data, len, '\0'))
 		return NULL;
-	}
 
-	neigh = g_slice_new0 (LldpNeighbor);
-	neigh->chassis_id_type = chassis_id_type;
-	neigh->port_id_type = port_id_type;
+	return nm_utils_buf_utf8safe_escape (data,
+	                                     is_null_terminated ? -1 : (gssize) len,
+	                                       NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL
+	                                     | NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII,
+	                                     out_to_free);
+}
 
-	r = sd_lldp_neighbor_get_destination_address (neighbor_sd, &neigh->destination_address);
-	if (r < 0) {
-		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "failed getting destination address: %s", nm_strerror_native (-r));
-		goto out;
-	}
+static char *
+format_string_cp (const guint8 *data, gsize len, gboolean allow_trim)
+{
+	char *s_free = NULL;
+	const char *s;
+
+	s = format_string (data, len, allow_trim, &s_free);
+	nm_assert (!s_free || s == s_free);
+	return s ? (s_free ?: g_strdup (s)) : NULL;
+}
+
+static LldpNeighbor *
+lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd)
+{
+	LldpNeighbor *neigh;
+	guint8 chassis_id_type;
+	guint8 port_id_type;
+	const guint8 *chassis_id;
+	const guint8 *port_id;
+	gsize chassis_id_len;
+	gsize port_id_len;
+	gs_free char *s_chassis_id = NULL;
+	gs_free char *s_port_id = NULL;
+
+	if (!lldp_neighbor_id_get (neighbor_sd,
+	                           &chassis_id_type,
+	                           &chassis_id,
+	                           &chassis_id_len,
+	                           &port_id_type,
+	                           &port_id,
+	                           &port_id_len))
+		return NULL;
 
 	switch (chassis_id_type) {
+	case SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT:
 	case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS:
+	case SD_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT:
 	case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME:
 	case SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED:
-	case SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT:
-		neigh->chassis_id = g_strndup ((const char *) chassis_id, chassis_id_len);
+		s_chassis_id = format_string_cp (chassis_id, chassis_id_len, FALSE);
 		break;
 	case SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS:
-		neigh->chassis_id = nm_utils_hwaddr_ntoa (chassis_id, chassis_id_len);
+		s_chassis_id = nm_utils_hwaddr_ntoa (chassis_id, chassis_id_len);
+		break;
+	case SD_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS:
+		s_chassis_id = format_network_address (chassis_id, chassis_id_len);
 		break;
-	default:
-		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "unsupported chassis-id type %d", chassis_id_type);
-		goto out;
+	}
+	if (!s_chassis_id) {
+		/* Invalid/unsupported chassis_id? Expose as hex string. This format is not stable, and
+		 * in the future we may add a better string representation for these case (thus
+		 * changing the API). */
+		s_chassis_id = nm_utils_bin2hexstr_full (chassis_id, chassis_id_len, '\0', FALSE, NULL);
 	}
 
 	switch (port_id_type) {
 	case SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS:
+	case SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT:
 	case SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME:
 	case SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED:
-	case SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT:
-		neigh->port_id = strndup ((char *) port_id, port_id_len);
+		s_port_id = format_string_cp (port_id, port_id_len, FALSE);
 		break;
 	case SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS:
-		neigh->port_id = nm_utils_hwaddr_ntoa (port_id, port_id_len);
+		s_port_id = nm_utils_hwaddr_ntoa (port_id, port_id_len);
+		break;
+	case SD_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS:
+		s_port_id = format_network_address (port_id, port_id_len);
 		break;
-	default:
-		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "unsupported port-id type %d", port_id_type);
-		goto out;
 	}
-
-	if (sd_lldp_neighbor_get_port_description (neighbor_sd, &str) == 0)
-		_lldp_attr_set_str (neigh->attrs, LLDP_ATTR_ID_PORT_DESCRIPTION, str);
-
-	if (sd_lldp_neighbor_get_system_name (neighbor_sd, &str) == 0)
-		_lldp_attr_set_str (neigh->attrs, LLDP_ATTR_ID_SYSTEM_NAME, str);
-
-	if (sd_lldp_neighbor_get_system_description (neighbor_sd, &str) == 0)
-		_lldp_attr_set_str (neigh->attrs, LLDP_ATTR_ID_SYSTEM_DESCRIPTION, str);
-
-	if (sd_lldp_neighbor_get_system_capabilities (neighbor_sd, &data16) == 0)
-		_lldp_attr_set_uint32 (neigh->attrs, LLDP_ATTR_ID_SYSTEM_CAPABILITIES, data16);
-
-	r = sd_lldp_neighbor_tlv_rewind (neighbor_sd);
-	if (r < 0) {
-		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-		             "failed reading tlv (rewind): %s", nm_strerror_native (-r));
-		goto out;
+	if (!s_port_id) {
+		/* Invalid/unsupported port_id? Expose as hex string. This format is not stable, and
+		 * in the future we may add a better string representation for these case (thus
+		 * changing the API). */
+		s_port_id = nm_utils_bin2hexstr_full (port_id, port_id_len, '\0', FALSE, NULL);
 	}
-	do {
-		guint8 oui[3];
-		guint8 type, subtype;
-		GVariant *variant;
-
-		if (sd_lldp_neighbor_tlv_get_type (neighbor_sd, &type) < 0)
-			continue;
-
-		if (sd_lldp_neighbor_tlv_get_raw (neighbor_sd, (void *) &data8, &len) < 0)
-			continue;
-
-		switch (type) {
-		case SD_LLDP_TYPE_MGMT_ADDRESS:
-			variant = parse_management_address_tlv (data8, len);
-			if (variant) {
-				_lldp_attr_add_vardict (neigh->attrs,
-				                        LLDP_ATTR_ID_MANAGEMENT_ADDRESSES,
-				                        variant);
-			}
-			continue;
-		case SD_LLDP_TYPE_PRIVATE:
-			break;
-		default:
-			continue;
-		}
-
-		r = sd_lldp_neighbor_tlv_get_oui (neighbor_sd, oui, &subtype);
-		if (r < 0) {
-			if (r == -ENXIO)
-				continue;
-			g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-			             "failed reading tlv: %s", nm_strerror_native (-r));
-			goto out;
-		}
-
-		if (   memcmp (oui, SD_LLDP_OUI_802_1, sizeof (oui)) != 0
-		    && memcmp (oui, SD_LLDP_OUI_802_3, sizeof (oui)) != 0)
-			continue;
-
-		/* skip over leading TLV, OUI and subtype */
-#if NM_MORE_ASSERTS > 5
-		{
-			guint8 check_hdr[] = {
-				0xfe | (((len - 2) >> 8) & 0x01), ((len - 2) & 0xFF),
-				oui[0], oui[1], oui[2],
-				subtype
-			};
-
-			nm_assert (len > 2 + 3 +1);
-			nm_assert (memcmp (data8, check_hdr, sizeof check_hdr) == 0);
-		}
-#endif
-		if (len <= 6)
-			continue;
-		data8 += 6;
-		len -= 6;
 
-		if (memcmp (oui, SD_LLDP_OUI_802_1, sizeof (oui)) == 0) {
-			GVariantDict dict;
+	neigh = g_slice_new (LldpNeighbor);
+	*neigh = (LldpNeighbor) {
+		.neighbor_sd     = sd_lldp_neighbor_ref (neighbor_sd),
+		.chassis_id_type = chassis_id_type,
+		.chassis_id      = g_steal_pointer (&s_chassis_id),
+		.port_id_type    = port_id_type,
+		.port_id         = g_steal_pointer (&s_port_id),
+	};
+	return neigh;
+}
 
-			switch (subtype) {
-			case SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID:
-				if (len != 2)
-					continue;
-				_lldp_attr_set_uint32 (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_PVID,
-				                       unaligned_read_be16 (data8));
-				break;
-			case SD_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID:
-				if (len != 3)
-					continue;
-				_lldp_attr_set_uint32 (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_PPVID_FLAGS,
-				                       data8[0]);
-				_lldp_attr_set_uint32 (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_PPVID,
-				                       unaligned_read_be16 (&data8[1]));
-
-				g_variant_dict_init (&dict, NULL);
-				g_variant_dict_insert (&dict, "ppvid", "u", (guint32) unaligned_read_be16 (&data8[1]));
-				g_variant_dict_insert (&dict, "flags", "u", (guint32) data8[0]);
-
-				_lldp_attr_add_vardict (neigh->attrs,
-				                        LLDP_ATTR_ID_IEEE_802_1_PPVIDS,
-				                        g_variant_dict_end (&dict));
-				break;
-			case SD_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME: {
-				int l;
-				guint32 vid;
-				const char *name;
-				char *name_to_free;
+static GVariant *
+lldp_neighbor_to_variant (LldpNeighbor *neigh)
+{
+	struct ether_addr destination_address;
+	GVariantBuilder builder;
+	const char *str;
+	const guint8 *raw_data;
+	gsize raw_len;
+	uint16_t u16;
+	uint8_t *data8;
+	gsize len;
+	int r;
 
-				if (len <= 3)
-					continue;
+	if (neigh->variant)
+		return neigh->variant;
 
-				l = data8[2];
-				if (len != 3 + l)
-					continue;
-				if (l > 32)
-					continue;
+	lldp_neighbor_get_raw (neigh, &raw_data, &raw_len);
 
-				name = nm_utils_buf_utf8safe_escape (&data8[3], l, 0, &name_to_free);
-				vid = unaligned_read_be16 (&data8[0]);
+	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
 
-				g_variant_dict_init (&dict, NULL);
-				g_variant_dict_insert (&dict, "vid", "u", vid);
-				g_variant_dict_insert (&dict, "name", "s", name);
+	nm_g_variant_builder_add_sv_bytearray (&builder,
+	                                       NM_LLDP_ATTR_RAW,
+	                                       raw_data,
+	                                       raw_len);
+	nm_g_variant_builder_add_sv_uint32 (&builder, NM_LLDP_ATTR_CHASSIS_ID_TYPE, neigh->chassis_id_type);
+	nm_g_variant_builder_add_sv_str (&builder, NM_LLDP_ATTR_CHASSIS_ID, neigh->chassis_id);
+	nm_g_variant_builder_add_sv_uint32 (&builder, NM_LLDP_ATTR_PORT_ID_TYPE, neigh->port_id_type);
+	nm_g_variant_builder_add_sv_str (&builder, NM_LLDP_ATTR_PORT_ID, neigh->port_id);
+
+	r = sd_lldp_neighbor_get_destination_address (neigh->neighbor_sd, &destination_address);
+	if (r < 0)
+		str = NULL;
+	else if (nm_utils_ether_addr_equal (&destination_address, LLDP_MAC_NEAREST_BRIDGE))
+		str = NM_LLDP_DEST_NEAREST_BRIDGE;
+	else if (nm_utils_ether_addr_equal (&destination_address, LLDP_MAC_NEAREST_NON_TPMR_BRIDGE))
+		str = NM_LLDP_DEST_NEAREST_NON_TPMR_BRIDGE;
+	else if (nm_utils_ether_addr_equal (&destination_address, LLDP_MAC_NEAREST_CUSTOMER_BRIDGE))
+		str = NM_LLDP_DEST_NEAREST_CUSTOMER_BRIDGE;
+	else
+		str = NULL;
+	if (str)
+		nm_g_variant_builder_add_sv_str (&builder, NM_LLDP_ATTR_DESTINATION, str);
+
+	if (sd_lldp_neighbor_get_port_description (neigh->neighbor_sd, &str) == 0)
+		nm_g_variant_builder_add_sv_str (&builder, NM_LLDP_ATTR_PORT_DESCRIPTION, str);
+
+	if (sd_lldp_neighbor_get_system_name (neigh->neighbor_sd, &str) == 0)
+		nm_g_variant_builder_add_sv_str (&builder, NM_LLDP_ATTR_SYSTEM_NAME, str);
+
+	if (sd_lldp_neighbor_get_system_description (neigh->neighbor_sd, &str) == 0)
+		nm_g_variant_builder_add_sv_str (&builder, NM_LLDP_ATTR_SYSTEM_DESCRIPTION, str);
+
+	if (sd_lldp_neighbor_get_system_capabilities (neigh->neighbor_sd, &u16) == 0)
+		nm_g_variant_builder_add_sv_uint32 (&builder, NM_LLDP_ATTR_SYSTEM_CAPABILITIES, u16);
+
+	r = sd_lldp_neighbor_tlv_rewind (neigh->neighbor_sd);
+	if (r < 0)
+		nm_assert_not_reached ();
+	else {
+		gboolean v_management_addresses_has = FALSE;
+		GVariantBuilder v_management_addresses;
+		GVariant *v_ieee_802_1_pvid = NULL;
+		GVariant *v_ieee_802_1_ppvid = NULL;
+		GVariant *v_ieee_802_1_ppvid_flags = NULL;
+		GVariantBuilder v_ieee_802_1_ppvids;
+		GVariant *v_ieee_802_1_vid = NULL;
+		GVariant *v_ieee_802_1_vlan_name = NULL;
+		GVariantBuilder v_ieee_802_1_vlans;
+		GVariant *v_ieee_802_3_mac_phy_conf = NULL;
+		GVariant *v_ieee_802_3_power_via_mdi = NULL;
+		GVariant *v_ieee_802_3_max_frame_size = NULL;
+		GVariant *v_mud_url = NULL;
+		GVariantBuilder tmp_builder;
+		GVariant *tmp_variant;
+
+		do {
+			guint8 oui[3];
+			guint8 type;
+			guint8 subtype;
+
+			if (sd_lldp_neighbor_tlv_get_type (neigh->neighbor_sd, &type) < 0)
+				continue;
 
-				_lldp_attr_add_vardict (neigh->attrs,
-				                        LLDP_ATTR_ID_IEEE_802_1_VLANS,
-				                        g_variant_dict_end (&dict));
+			if (sd_lldp_neighbor_tlv_get_raw (neigh->neighbor_sd, (void *) &data8, &len) < 0)
+				continue;
 
-				_lldp_attr_set_uint32 (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_VID, vid);
-				if (name_to_free)
-					_lldp_attr_set_str_take (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_VLAN_NAME, name_to_free);
-				else
-					_lldp_attr_set_str (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_VLAN_NAME, name);
+			switch (type) {
+			case SD_LLDP_TYPE_MGMT_ADDRESS:
+				tmp_variant = parse_management_address_tlv (data8, len);
+				if (tmp_variant) {
+					if (!v_management_addresses_has) {
+						v_management_addresses_has = TRUE;
+						g_variant_builder_init (&v_management_addresses, G_VARIANT_TYPE ("aa{sv}"));
+					}
+					g_variant_builder_add_value (&v_management_addresses, tmp_variant);
+				}
+				continue;
+			case SD_LLDP_TYPE_PRIVATE:
 				break;
-			}
 			default:
 				continue;
 			}
-		} else if (memcmp (oui, SD_LLDP_OUI_802_3, sizeof (oui)) == 0) {
-			GVariantDict dict;
 
-			switch (subtype) {
-			case SD_LLDP_OUI_802_3_SUBTYPE_MAC_PHY_CONFIG_STATUS:
-				if (len != 5)
+			r = sd_lldp_neighbor_tlv_get_oui (neigh->neighbor_sd, oui, &subtype);
+			if (r < 0) {
+				if (r == -ENXIO)
 					continue;
 
-				g_variant_dict_init (&dict, NULL);
-				g_variant_dict_insert (&dict, "autoneg", "u", (guint32) data8[0]);
-				g_variant_dict_insert (&dict, "pmd-autoneg-cap", "u", (guint32) unaligned_read_be16 (&data8[1]));
-				g_variant_dict_insert (&dict, "operational-mau-type", "u", (guint32) unaligned_read_be16 (&data8[3]));
-
-				_lldp_attr_set_vardict (neigh->attrs,
-				                        LLDP_ATTR_ID_IEEE_802_3_MAC_PHY_CONF,
-				                        g_variant_dict_end (&dict));
+				/* in other cases, something is seriously wrong. Abort, but
+				 * keep what we parsed so far. */
 				break;
-			case SD_LLDP_OUI_802_3_SUBTYPE_POWER_VIA_MDI:
-				if (len != 3)
-					continue;
+			}
 
-				g_variant_dict_init (&dict, NULL);
-				g_variant_dict_insert (&dict, "mdi-power-support", "u", (guint32) data8[0]);
-				g_variant_dict_insert (&dict, "pse-power-pair", "u", (guint32) data8[1]);
-				g_variant_dict_insert (&dict, "power-class", "u", (guint32) data8[2]);
+			if (len <= 6)
+				continue;
 
-				_lldp_attr_set_vardict (neigh->attrs,
-				                        LLDP_ATTR_ID_IEEE_802_3_POWER_VIA_MDI,
-				                        g_variant_dict_end (&dict));
-				break;
-			case SD_LLDP_OUI_802_3_SUBTYPE_MAXIMUM_FRAME_SIZE:
-				if (len != 2)
+			/* skip over leading TLV, OUI and subtype */
+#if NM_MORE_ASSERTS > 5
+			{
+				guint8 check_hdr[] = {
+					0xfe | (((len - 2) >> 8) & 0x01), ((len - 2) & 0xFF),
+					oui[0], oui[1], oui[2],
+					subtype
+				};
+
+				nm_assert (len > 2 + 3 +1);
+				nm_assert (memcmp (data8, check_hdr, sizeof check_hdr) == 0);
+			}
+#endif
+			data8 += 6;
+			len -= 6;
+
+			if (memcmp (oui, SD_LLDP_OUI_802_1, sizeof (oui)) == 0) {
+				switch (subtype) {
+				case SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID:
+					if (len != 2)
+						continue;
+					if (!v_ieee_802_1_pvid)
+						v_ieee_802_1_pvid = g_variant_new_uint32 (unaligned_read_be16 (data8));
+					break;
+				case SD_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID:
+					if (len != 3)
+						continue;
+					if (!v_ieee_802_1_ppvid) {
+						v_ieee_802_1_ppvid_flags = g_variant_new_uint32 (data8[0]);
+						v_ieee_802_1_ppvid = g_variant_new_uint32 (unaligned_read_be16 (&data8[1]));
+						g_variant_builder_init (&v_ieee_802_1_ppvids, G_VARIANT_TYPE ("aa{sv}"));
+					}
+					g_variant_builder_init (&tmp_builder, G_VARIANT_TYPE ("a{sv}"));
+					nm_g_variant_builder_add_sv_uint32 (&tmp_builder, "ppvid", unaligned_read_be16 (&data8[1]));
+					nm_g_variant_builder_add_sv_uint32 (&tmp_builder, "flags", data8[0]);
+					g_variant_builder_add_value (&v_ieee_802_1_ppvids, g_variant_builder_end (&tmp_builder));
+					break;
+				case SD_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME: {
+					gs_free char *name_to_free = NULL;
+					const char *name;
+					guint32 vid;
+					gsize l;
+
+					if (len <= 3)
+						continue;
+
+					l = data8[2];
+					if (len != 3 + l)
+						continue;
+					if (l > 32)
+						continue;
+
+					name = format_string (&data8[3], l, TRUE, &name_to_free);
+					if (!name)
+						continue;
+
+					vid = unaligned_read_be16 (&data8[0]);
+					if (!v_ieee_802_1_vid) {
+						v_ieee_802_1_vid = g_variant_new_uint32 (vid);
+						v_ieee_802_1_vlan_name = g_variant_new_string (name);
+						g_variant_builder_init (&v_ieee_802_1_vlans, G_VARIANT_TYPE ("aa{sv}"));
+					}
+					g_variant_builder_init (&tmp_builder, G_VARIANT_TYPE ("a{sv}"));
+					nm_g_variant_builder_add_sv_uint32 (&tmp_builder, "vid", vid);
+					nm_g_variant_builder_add_sv_str (&tmp_builder, "name", name);
+					g_variant_builder_add_value (&v_ieee_802_1_vlans, g_variant_builder_end (&tmp_builder));
+					break;
+				}
+				default:
 					continue;
-				_lldp_attr_set_uint32 (neigh->attrs, LLDP_ATTR_ID_IEEE_802_3_MAX_FRAME_SIZE,
-				                       unaligned_read_be16 (data8));
-				break;
+				}
+			} else if (memcmp (oui, SD_LLDP_OUI_802_3, sizeof (oui)) == 0) {
+				switch (subtype) {
+				case SD_LLDP_OUI_802_3_SUBTYPE_MAC_PHY_CONFIG_STATUS:
+					if (len != 5)
+						continue;
+
+					if (!v_ieee_802_3_mac_phy_conf) {
+						g_variant_builder_init (&tmp_builder, G_VARIANT_TYPE ("a{sv}"));
+						nm_g_variant_builder_add_sv_uint32 (&tmp_builder, "autoneg", data8[0]);
+						nm_g_variant_builder_add_sv_uint32 (&tmp_builder, "pmd-autoneg-cap", unaligned_read_be16 (&data8[1]));
+						nm_g_variant_builder_add_sv_uint32 (&tmp_builder, "operational-mau-type", unaligned_read_be16 (&data8[3]));
+						v_ieee_802_3_mac_phy_conf = g_variant_builder_end (&tmp_builder);
+					}
+					break;
+				case SD_LLDP_OUI_802_3_SUBTYPE_POWER_VIA_MDI:
+					if (len != 3)
+						continue;
+
+					if (!v_ieee_802_3_power_via_mdi) {
+						g_variant_builder_init (&tmp_builder, G_VARIANT_TYPE ("a{sv}"));
+						nm_g_variant_builder_add_sv_uint32 (&tmp_builder, "mdi-power-support", data8[0]);
+						nm_g_variant_builder_add_sv_uint32 (&tmp_builder, "pse-power-pair", data8[1]);
+						nm_g_variant_builder_add_sv_uint32 (&tmp_builder, "power-class", data8[2]);
+						v_ieee_802_3_power_via_mdi = g_variant_builder_end (&tmp_builder);
+					}
+					break;
+				case SD_LLDP_OUI_802_3_SUBTYPE_MAXIMUM_FRAME_SIZE:
+					if (len != 2)
+						continue;
+					if (!v_ieee_802_3_max_frame_size)
+						v_ieee_802_3_max_frame_size = g_variant_new_uint32 (unaligned_read_be16 (data8));
+					break;
+				}
+			} else if (memcmp (oui, SD_LLDP_OUI_MUD, sizeof (oui)) == 0) {
+				switch (subtype) {
+				case SD_LLDP_OUI_SUBTYPE_MUD_USAGE_DESCRIPTION:
+					if (!v_mud_url) {
+						gs_free char *s_free = NULL;
+						const char *s;
+
+						s = format_string (data8, len, TRUE, &s_free);
+						if (s)
+							v_mud_url = g_variant_new_string (s);
+					}
+					break;
+				}
 			}
+		} while (sd_lldp_neighbor_tlv_next (neigh->neighbor_sd) > 0);
+
+		if (v_management_addresses_has)
+			nm_g_variant_builder_add_sv (&builder, NM_LLDP_ATTR_MANAGEMENT_ADDRESSES, g_variant_builder_end (&v_management_addresses));
+		if (v_ieee_802_1_pvid)
+			nm_g_variant_builder_add_sv (&builder, NM_LLDP_ATTR_IEEE_802_1_PVID, v_ieee_802_1_pvid);
+		if (v_ieee_802_1_ppvid) {
+			nm_g_variant_builder_add_sv (&builder, NM_LLDP_ATTR_IEEE_802_1_PPVID, v_ieee_802_1_ppvid);
+			nm_g_variant_builder_add_sv (&builder, NM_LLDP_ATTR_IEEE_802_1_PPVID_FLAGS, v_ieee_802_1_ppvid_flags);
+			nm_g_variant_builder_add_sv (&builder, NM_LLDP_ATTR_IEEE_802_1_PPVIDS, g_variant_builder_end (&v_ieee_802_1_ppvids));
 		}
-	} while (sd_lldp_neighbor_tlv_next (neighbor_sd) > 0);
-
-	neigh->valid = TRUE;
+		if (v_ieee_802_1_vid) {
+			nm_g_variant_builder_add_sv (&builder, NM_LLDP_ATTR_IEEE_802_1_VID, v_ieee_802_1_vid);
+			nm_g_variant_builder_add_sv (&builder, NM_LLDP_ATTR_IEEE_802_1_VLAN_NAME, v_ieee_802_1_vlan_name);
+			nm_g_variant_builder_add_sv (&builder, NM_LLDP_ATTR_IEEE_802_1_VLANS, g_variant_builder_end (&v_ieee_802_1_vlans));
+		}
+		if (v_ieee_802_3_mac_phy_conf)
+			nm_g_variant_builder_add_sv (&builder, NM_LLDP_ATTR_IEEE_802_3_MAC_PHY_CONF, v_ieee_802_3_mac_phy_conf);
+		if (v_ieee_802_3_power_via_mdi)
+			nm_g_variant_builder_add_sv (&builder, NM_LLDP_ATTR_IEEE_802_3_POWER_VIA_MDI, v_ieee_802_3_power_via_mdi);
+		if (v_ieee_802_3_max_frame_size)
+			nm_g_variant_builder_add_sv (&builder, NM_LLDP_ATTR_IEEE_802_3_MAX_FRAME_SIZE, v_ieee_802_3_max_frame_size);
+		if (v_mud_url)
+			nm_g_variant_builder_add_sv (&builder, NM_LLDP_ATTR_MUD_URL, v_mud_url);
+	}
 
-out:
-	return g_steal_pointer (&neigh);
+	return (neigh->variant = g_variant_ref_sink (g_variant_builder_end (&builder)));
 }
 
-static GVariant *
-lldp_neighbor_to_variant (LldpNeighbor *neigh)
+/*****************************************************************************/
+
+GVariant *
+nmtst_lldp_parse_from_raw (const guint8 *raw_data,
+                           gsize raw_len)
 {
-	GVariantBuilder builder;
-	const char *dest_str;
-	LldpAttrId attr_id;
+	nm_auto (sd_lldp_neighbor_unrefp) sd_lldp_neighbor *neighbor_sd = NULL;
+	nm_auto (lldp_neighbor_freep) LldpNeighbor *neigh = NULL;
+	GVariant *variant;
+	int r;
 
-	if (neigh->variant)
-		return neigh->variant;
+	g_assert (raw_data);
+	g_assert (raw_len > 0);
 
-	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+	r = sd_lldp_neighbor_from_raw (&neighbor_sd, raw_data, raw_len);
+	g_assert (r >= 0);
 
-	g_variant_builder_add (&builder, "{sv}",
-	                       NM_LLDP_ATTR_CHASSIS_ID_TYPE,
-	                       g_variant_new_uint32 (neigh->chassis_id_type));
-	g_variant_builder_add (&builder, "{sv}",
-	                       NM_LLDP_ATTR_CHASSIS_ID,
-	                       g_variant_new_string (neigh->chassis_id));
-	g_variant_builder_add (&builder, "{sv}",
-	                       NM_LLDP_ATTR_PORT_ID_TYPE,
-	                       g_variant_new_uint32 (neigh->port_id_type));
-	g_variant_builder_add (&builder, "{sv}",
-	                       NM_LLDP_ATTR_PORT_ID,
-	                       g_variant_new_string (neigh->port_id));
-
-	if (ether_addr_equal (&neigh->destination_address, LLDP_MAC_NEAREST_BRIDGE))
-		dest_str = NM_LLDP_DEST_NEAREST_BRIDGE;
-	else if (ether_addr_equal (&neigh->destination_address, LLDP_MAC_NEAREST_NON_TPMR_BRIDGE))
-		dest_str = NM_LLDP_DEST_NEAREST_NON_TPMR_BRIDGE;
-	else if (ether_addr_equal (&neigh->destination_address, LLDP_MAC_NEAREST_CUSTOMER_BRIDGE))
-		dest_str = NM_LLDP_DEST_NEAREST_CUSTOMER_BRIDGE;
-	else
-		dest_str = NULL;
-	if (dest_str) {
-		g_variant_builder_add (&builder, "{sv}",
-		                       NM_LLDP_ATTR_DESTINATION,
-		                       g_variant_new_string (dest_str));
-	}
+	neigh = lldp_neighbor_new (neighbor_sd);
+	g_assert (neigh);
 
-	for (attr_id = 0; attr_id < _LLDP_ATTR_ID_COUNT; attr_id++) {
-		const LldpAttrData *data = &neigh->attrs[attr_id];
-
-		nm_assert (NM_IN_SET (data->attr_type, _lldp_attr_id_to_type (attr_id), LLDP_ATTR_TYPE_NONE));
-		switch (data->attr_type) {
-		case LLDP_ATTR_TYPE_UINT32:
-			g_variant_builder_add (&builder, "{sv}",
-			                       _lldp_attr_id_to_name (attr_id),
-			                       g_variant_new_uint32 (data->v_uint32));
-			break;
-		case LLDP_ATTR_TYPE_STRING:
-			g_variant_builder_add (&builder, "{sv}",
-			                       _lldp_attr_id_to_name (attr_id),
-			                       g_variant_new_string (data->v_string));
-			break;
-		case LLDP_ATTR_TYPE_VARDICT:
-			g_variant_builder_add (&builder, "{sv}",
-			                       _lldp_attr_id_to_name (attr_id),
-			                       data->v_variant);
-			break;
-		case LLDP_ATTR_TYPE_ARRAY_OF_VARDICTS: {
-			NMCListElem *elem;
-			GVariantBuilder builder2;
-
-			g_variant_builder_init (&builder2, G_VARIANT_TYPE ("aa{sv}"));
-
-			c_list_for_each_entry (elem, &data->v_variant_list, lst)
-				g_variant_builder_add_value (&builder2, elem->data);
-
-			g_variant_builder_add (&builder, "{sv}",
-			                       _lldp_attr_id_to_name (attr_id),
-			                       g_variant_builder_end (&builder2));
-			break;
-		}
-		case LLDP_ATTR_TYPE_NONE:
-			break;
-		}
-	}
+	variant = lldp_neighbor_to_variant (neigh);
+	g_assert (variant);
 
-	return (neigh->variant = g_variant_ref_sink (g_variant_builder_end (&builder)));
+	return g_variant_ref (variant);
 }
 
 /*****************************************************************************/
@@ -830,7 +778,7 @@ data_changed_timeout (gpointer user_data)
 	priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
 
 	priv->ratelimit_id = 0;
-	priv->ratelimit_next = nm_utils_get_monotonic_timestamp_nsec() + MIN_UPDATE_INTERVAL_NS;
+	priv->ratelimit_next_nsec = nm_utils_get_monotonic_timestamp_nsec() + MIN_UPDATE_INTERVAL_NSEC;
 	data_changed_notify (self, priv);
 	return G_SOURCE_REMOVE;
 }
@@ -839,26 +787,33 @@ static void
 data_changed_schedule (NMLldpListener *self)
 {
 	NMLldpListenerPrivate *priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
-	gint64 now;
+	gint64 now_nsec;
 
-	now = nm_utils_get_monotonic_timestamp_nsec ();
-	if (now >= priv->ratelimit_next) {
-		nm_clear_g_source (&priv->ratelimit_id);
-		priv->ratelimit_next = now + MIN_UPDATE_INTERVAL_NS;
-		data_changed_notify (self, priv);
-	} else if (!priv->ratelimit_id)
-		priv->ratelimit_id = g_timeout_add (NM_UTILS_NSEC_TO_MSEC_CEIL (priv->ratelimit_next - now), data_changed_timeout, self);
+	if (priv->ratelimit_id != 0)
+		return;
+
+	now_nsec = nm_utils_get_monotonic_timestamp_nsec ();
+	if (now_nsec < priv->ratelimit_next_nsec) {
+		priv->ratelimit_id = g_timeout_add_full (G_PRIORITY_LOW,
+		                                         NM_UTILS_NSEC_TO_MSEC_CEIL (priv->ratelimit_next_nsec - now_nsec),
+		                                         data_changed_timeout,
+		                                         self,
+		                                         NULL);
+		return;
+	}
+
+	priv->ratelimit_id = g_idle_add_full (G_PRIORITY_LOW,
+	                                      data_changed_timeout,
+	                                      self,
+	                                      NULL);
 }
 
 static void
-process_lldp_neighbor (NMLldpListener *self, sd_lldp_neighbor *neighbor_sd, gboolean neighbor_valid)
+process_lldp_neighbor (NMLldpListener *self, sd_lldp_neighbor *neighbor_sd, gboolean remove)
 {
 	NMLldpListenerPrivate *priv;
 	nm_auto (lldp_neighbor_freep) LldpNeighbor *neigh = NULL;
 	LldpNeighbor *neigh_old;
-	gs_free_error GError *parse_error = NULL;
-	GError **p_parse_error;
-	gboolean changed = FALSE;
 
 	g_return_if_fail (NM_IS_LLDP_LISTENER (self));
 
@@ -867,58 +822,49 @@ process_lldp_neighbor (NMLldpListener *self, sd_lldp_neighbor *neighbor_sd, gboo
 	g_return_if_fail (priv->lldp_handle);
 	g_return_if_fail (neighbor_sd);
 
-	p_parse_error = _LOGT_ENABLED () ? &parse_error : NULL;
+	nm_assert (priv->lldp_neighbors);
 
-	neigh = lldp_neighbor_new (neighbor_sd, p_parse_error);
+	neigh = lldp_neighbor_new (neighbor_sd);
 	if (!neigh) {
-		_LOGT ("process: failed to parse neighbor: %s", parse_error->message);
+		_LOGT ("process: failed to parse neighbor");
 		return;
 	}
 
-	if (!neigh->valid)
-		neighbor_valid = FALSE;
-
 	neigh_old = g_hash_table_lookup (priv->lldp_neighbors, neigh);
-	if (neigh_old) {
-		if (!neighbor_valid) {
-			_LOGT ("process: %s neigh: "LOG_NEIGH_FMT"%s%s%s",
-			       "remove", LOG_NEIGH_ARG (neigh),
-			       NM_PRINT_FMT_QUOTED (parse_error, " (failed to parse: ", parse_error->message, ")", ""));
+
+	if (remove) {
+		if (neigh_old) {
+			_LOGT ("process: %s neigh: "LOG_NEIGH_FMT,
+			       "remove", LOG_NEIGH_ARG (neigh));
 
 			g_hash_table_remove (priv->lldp_neighbors, neigh_old);
-			changed = TRUE;
-			goto done;
-		} else if (lldp_neighbor_equal (neigh_old, neigh))
-			return;
-	} else if (!neighbor_valid) {
-		if (parse_error)
-			_LOGT ("process: failed to parse neighbor: %s", parse_error->message);
+			goto handle_changed;
+		}
 		return;
 	}
 
-	/* ensure that we have at most MAX_NEIGHBORS entries */
-	if (   !neigh_old /* only matters in the "add" case. */
-	    && (g_hash_table_size (priv->lldp_neighbors) + 1 > MAX_NEIGHBORS)) {
-		_LOGT ("process: ignore neighbor due to overall limit of %d", MAX_NEIGHBORS);
+	if (   neigh_old
+	    && lldp_neighbor_equal (neigh_old, neigh))
 		return;
-	}
 
 	_LOGD ("process: %s neigh: "LOG_NEIGH_FMT,
 	        neigh_old ? "update" : "new",
 	        LOG_NEIGH_ARG (neigh));
 
-	changed = TRUE;
 	g_hash_table_add (priv->lldp_neighbors, g_steal_pointer (&neigh));
 
-done:
-	if (changed)
-		data_changed_schedule (self);
+handle_changed:
+	data_changed_schedule (self);
 }
 
 static void
 lldp_event_handler (sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata)
 {
-	process_lldp_neighbor (userdata, n, event != SD_LLDP_EVENT_REMOVED);
+	process_lldp_neighbor (userdata,
+	                       n,
+	                       !NM_IN_SET (event, SD_LLDP_EVENT_ADDED,
+	                                          SD_LLDP_EVENT_UPDATED,
+	                                          SD_LLDP_EVENT_REFRESHED));
 }
 
 gboolean
@@ -960,6 +906,9 @@ nm_lldp_listener_start (NMLldpListener *self, int ifindex, GError **error)
 		goto err;
 	}
 
+	ret = sd_lldp_set_neighbors_max (priv->lldp_handle, MAX_NEIGHBORS);
+	nm_assert (ret == 0);
+
 	priv->ifindex = ifindex;
 
 	ret = sd_lldp_attach_event (priv->lldp_handle, NULL, 0);
@@ -976,6 +925,10 @@ nm_lldp_listener_start (NMLldpListener *self, int ifindex, GError **error)
 		goto err;
 	}
 
+	priv->lldp_neighbors = g_hash_table_new_full (lldp_neighbor_id_hash,
+	                                              lldp_neighbor_id_equal,
+	                                              (GDestroyNotify) lldp_neighbor_free, NULL);
+
 	_LOGD ("start");
 
 	return TRUE;
@@ -1008,12 +961,14 @@ nm_lldp_listener_stop (NMLldpListener *self)
 
 		size = g_hash_table_size (priv->lldp_neighbors);
 		g_hash_table_remove_all (priv->lldp_neighbors);
-		if (size || priv->ratelimit_id)
+		nm_clear_pointer (&priv->lldp_neighbors, g_hash_table_unref);
+		if (   size > 0
+		    || priv->ratelimit_id != 0)
 			changed = TRUE;
 	}
 
 	nm_clear_g_source (&priv->ratelimit_id);
-	priv->ratelimit_next = 0;
+	priv->ratelimit_next_nsec = 0;
 	priv->ifindex = 0;
 
 	if (changed)
@@ -1041,8 +996,8 @@ nm_lldp_listener_get_neighbors (NMLldpListener *self)
 	priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
 
 	if (G_UNLIKELY (!priv->variant)) {
-		GVariantBuilder array_builder;
 		gs_free LldpNeighbor **neighbors = NULL;
+		GVariantBuilder array_builder;
 		guint i, n;
 
 		g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("aa{sv}"));
@@ -1076,12 +1031,6 @@ get_property (GObject *object, guint prop_id,
 static void
 nm_lldp_listener_init (NMLldpListener *self)
 {
-	NMLldpListenerPrivate *priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
-
-	priv->lldp_neighbors = g_hash_table_new_full (lldp_neighbor_id_hash,
-	                                              lldp_neighbor_id_equal,
-	                                              (GDestroyNotify) lldp_neighbor_free, NULL);
-
 	_LOGT ("lldp listener created");
 }
 
@@ -1106,7 +1055,6 @@ finalize (GObject *object)
 	NMLldpListenerPrivate *priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
 
 	nm_lldp_listener_stop (self);
-	g_hash_table_unref (priv->lldp_neighbors);
 
 	nm_clear_g_variant (&priv->variant);
 
@@ -1133,4 +1081,3 @@ nm_lldp_listener_class_init (NMLldpListenerClass *klass)
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 }
-
diff --git a/src/devices/nm-lldp-listener.h b/src/devices/nm-lldp-listener.h
index 92e1d00f..adea91b8 100644
--- a/src/devices/nm-lldp-listener.h
+++ b/src/devices/nm-lldp-listener.h
@@ -25,4 +25,7 @@ gboolean nm_lldp_listener_is_running (NMLldpListener *self);
 
 GVariant *nm_lldp_listener_get_neighbors (NMLldpListener *self);
 
+GVariant *nmtst_lldp_parse_from_raw (const guint8 *raw_data,
+                                     gsize raw_len);
+
 #endif /* __NM_LLDP_LISTENER__ */
diff --git a/src/devices/ovs/nm-ovs-factory.c b/src/devices/ovs/nm-ovs-factory.c
index d1d79a1c..d7bd0a09 100644
--- a/src/devices/ovs/nm-ovs-factory.c
+++ b/src/devices/ovs/nm-ovs-factory.c
@@ -142,15 +142,40 @@ ovsdb_interface_failed (NMOvsdb *ovsdb,
 {
 	NMDevice *device = NULL;
 	NMSettingsConnection *connection = NULL;
-
-	_LOGI (name, connection_uuid, "ovs interface \"%s\" (%s) failed: %s", name, connection_uuid, error);
+	NMConnection *c;
+	const char *type;
+	NMSettingOvsInterface *s_ovs_int;
+	gboolean is_patch = FALSE;
+	gboolean ignore;
 
 	device = nm_manager_get_device (NM_MANAGER_GET, name, NM_DEVICE_TYPE_OVS_INTERFACE);
-	if (!device)
-		return;
+	if (device && connection_uuid) {
+		connection = nm_settings_get_connection_by_uuid (nm_device_get_settings (device),
+		                                                 connection_uuid);
+	}
 
-	if (connection_uuid)
-		connection = nm_settings_get_connection_by_uuid (nm_device_get_settings (device), connection_uuid);
+	/* The patch interface which gets created first is expected to
+	 * fail because the second patch doesn't exist yet. Ignore all
+	 * failures of patch interfaces. */
+	if (   connection
+	    && (c = nm_settings_connection_get_connection (connection))
+	    && (type = nm_connection_get_connection_type (c))
+	    && nm_streq0 (type, NM_SETTING_OVS_INTERFACE_SETTING_NAME)
+	    && (s_ovs_int = nm_connection_get_setting_ovs_interface (c))
+	    && nm_streq0 (nm_setting_ovs_interface_get_interface_type (s_ovs_int), "patch"))
+		is_patch = TRUE;
+
+	ignore = !device || is_patch;
+
+	_NMLOG (ignore ? LOGL_DEBUG : LOGL_INFO,
+	        name, connection_uuid,
+	        "ovs interface \"%s\" (%s) failed%s: %s",
+	        name, connection_uuid,
+	        ignore ? " (ignored)" : "",
+	        error);
+
+	if (ignore)
+		return;
 
 	if (connection) {
 		nm_settings_connection_autoconnect_blocked_reason_set (connection,
diff --git a/src/devices/ovs/nm-ovsdb.c b/src/devices/ovs/nm-ovsdb.c
index b6550b25..e1865f9d 100644
--- a/src/devices/ovs/nm-ovsdb.c
+++ b/src/devices/ovs/nm-ovsdb.c
@@ -131,10 +131,9 @@ typedef struct {
 #define OVSDB_MAX_FAILURES    3
 
 static void
-_call_trace (const char *comment, OvsdbMethodCall *call, json_t *msg)
+_LOGT_call_do (const char *comment, OvsdbMethodCall *call, json_t *msg)
 {
-#if NM_MORE_LOGGING
-	char *str = NULL;
+	gs_free char *str = NULL;
 
 	if (msg)
 		str = json_dumps (msg, 0);
@@ -169,12 +168,14 @@ _call_trace (const char *comment, OvsdbMethodCall *call, json_t *msg)
 		       call->mtu);
 		break;
 	}
-
-	if (msg)
-		g_free (str);
-#endif
 }
 
+#define _LOGT_call(comment, call, message) \
+	G_STMT_START { \
+		if (_LOGT_ENABLED ()) \
+			_LOGT_call_do ((comment), (call), (message)); \
+	} G_STMT_END
+
 /**
  * ovsdb_call_method:
  *
@@ -226,7 +227,7 @@ ovsdb_call_method (NMOvsdb *self, OvsdbCommand command,
 		break;
 	}
 
-	_call_trace ("enqueue", call, NULL);
+	_LOGT_call ("enqueue", call, NULL);
 
 	ovsdb_next_command (self);
 }
@@ -875,7 +876,7 @@ ovsdb_next_command (NMOvsdb *self)
 	}
 
 	g_return_if_fail (msg);
-	_call_trace ("send", call, msg);
+	_LOGT_call ("send", call, msg);
 	cmd = json_dumps (msg, 0);
 
 	g_string_append (priv->output, cmd);
@@ -1247,7 +1248,7 @@ ovsdb_got_msg (NMOvsdb *self, json_t *msg)
 		}
 		/* Cool, we found a corresponding call. Finish it. */
 
-		_call_trace ("response", call, msg);
+		_LOGT_call ("response", call, msg);
 
 		if (!json_is_null (error)) {
 			/* The response contains an error. */
diff --git a/src/devices/team/nm-device-team.c b/src/devices/team/nm-device-team.c
index 06f8840d..217c4bdd 100644
--- a/src/devices/team/nm-device-team.c
+++ b/src/devices/team/nm-device-team.c
@@ -21,6 +21,7 @@
 #include "platform/nm-platform.h"
 #include "nm-config.h"
 #include "nm-core-internal.h"
+#include "nm-dbus-manager.h"
 #include "nm-ip4-config.h"
 #include "nm-std-aux/nm-dbus-compat.h"
 
@@ -42,6 +43,7 @@ typedef struct {
 	guint teamd_read_timeout;
 	guint teamd_dbus_watch;
 	bool kill_in_progress:1;
+	GFileMonitor *usock_monitor;
 	NMDeviceStageState stage1_state:3;
 } NMDeviceTeamPrivate;
 
@@ -341,6 +343,63 @@ teamd_timeout_cb (gpointer user_data)
 }
 
 static void
+teamd_ready (NMDeviceTeam *self)
+{
+	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+	gboolean success;
+
+	if (priv->kill_in_progress) {
+		/* If we are currently killing teamd, we are not
+		 * interested in knowing when it becomes ready. */
+		return;
+	}
+
+	nm_device_queue_recheck_assume (device);
+
+	/* Grab a teamd control handle even if we aren't going to use it
+	 * immediately.  But if we are, and grabbing it failed, fail the
+	 * device activation.
+	 */
+	success = ensure_teamd_connection (device);
+
+	if (   nm_device_get_state (device) != NM_DEVICE_STATE_PREPARE
+	    || priv->stage1_state != NM_DEVICE_STAGE_STATE_PENDING)
+		return;
+
+	if (success)
+		success = teamd_read_config (self);
+
+	if (!success) {
+		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
+		return;
+	}
+
+	priv->stage1_state = NM_DEVICE_STAGE_STATE_COMPLETED;
+	nm_device_activate_schedule_stage1_device_prepare (device, FALSE);
+}
+
+static void
+teamd_gone (NMDeviceTeam *self)
+{
+	NMDevice *device = NM_DEVICE (self);
+	NMDeviceState state;
+
+	teamd_cleanup (self, TRUE);
+	state = nm_device_get_state (device);
+
+	/* Attempt to respawn teamd */
+	if (   state >= NM_DEVICE_STATE_PREPARE
+	    && state <= NM_DEVICE_STATE_ACTIVATED) {
+		if (!teamd_start (self)) {
+			nm_device_state_changed (device,
+			                         NM_DEVICE_STATE_FAILED,
+			                         NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
+		}
+	}
+}
+
+static void
 teamd_dbus_appeared (GDBusConnection *connection,
                      const char *name,
                      const char *name_owner,
@@ -348,19 +407,10 @@ teamd_dbus_appeared (GDBusConnection *connection,
 {
 	NMDeviceTeam *self = NM_DEVICE_TEAM (user_data);
 	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
-	NMDevice *device = NM_DEVICE (self);
-	gboolean success;
 
 	g_return_if_fail (priv->teamd_dbus_watch);
 
 	_LOGI (LOGD_TEAM, "teamd appeared on D-Bus");
-	nm_device_queue_recheck_assume (device);
-
-	if (priv->kill_in_progress) {
-		/* If we are currently killing teamd, we are not
-		 * interested in knowing when it becomes ready. */
-		return;
-	}
 
 	/* If another teamd grabbed the bus name while our teamd was starting,
 	 * just ignore the death of our teamd and run with the existing one.
@@ -397,26 +447,7 @@ teamd_dbus_appeared (GDBusConnection *connection,
 		}
 	}
 
-	/* Grab a teamd control handle even if we aren't going to use it
-	 * immediately.  But if we are, and grabbing it failed, fail the
-	 * device activation.
-	 */
-	success = ensure_teamd_connection (device);
-
-	if (   nm_device_get_state (device) != NM_DEVICE_STATE_PREPARE
-	    || priv->stage1_state != NM_DEVICE_STAGE_STATE_PENDING)
-		return;
-
-	if (success)
-		success = teamd_read_config (self);
-
-	if (!success) {
-		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
-		return;
-	}
-
-	priv->stage1_state = NM_DEVICE_STAGE_STATE_COMPLETED;
-	nm_device_activate_schedule_stage1_device_prepare (device, FALSE);
+	teamd_ready (self);
 }
 
 static void
@@ -426,8 +457,6 @@ teamd_dbus_vanished (GDBusConnection *dbus_connection,
 {
 	NMDeviceTeam *self = NM_DEVICE_TEAM (user_data);
 	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
-	NMDevice *device = NM_DEVICE (self);
-	NMDeviceState state = nm_device_get_state (device);
 
 	g_return_if_fail (priv->teamd_dbus_watch);
 
@@ -441,16 +470,31 @@ teamd_dbus_vanished (GDBusConnection *dbus_connection,
 	}
 
 	_LOGI (LOGD_TEAM, "teamd vanished from D-Bus");
-	teamd_cleanup (self, TRUE);
 
-	/* Attempt to respawn teamd */
-	if (   state >= NM_DEVICE_STATE_PREPARE
-	    && state <= NM_DEVICE_STATE_ACTIVATED) {
-		if (!teamd_start (self)) {
-			nm_device_state_changed (device,
-			                         NM_DEVICE_STATE_FAILED,
-			                         NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
-		}
+	teamd_gone (self);
+}
+
+
+static void
+monitor_changed_cb (GFileMonitor *monitor,
+                    GFile *file,
+                    GFile *other_file,
+                    GFileMonitorEvent event_type,
+                    gpointer user_data)
+{
+	NMDeviceTeam *self = NM_DEVICE_TEAM (user_data);
+
+	switch (event_type) {
+	case G_FILE_MONITOR_EVENT_CREATED:
+		_LOGI (LOGD_TEAM, "file %s was created", g_file_get_path (file));
+		teamd_ready (self);
+		break;
+	case G_FILE_MONITOR_EVENT_DELETED:
+		_LOGI (LOGD_TEAM, "file %s was deleted", g_file_get_path (file));
+		teamd_gone (self);
+		break;
+	default:
+		;
 	}
 }
 
@@ -572,7 +616,8 @@ teamd_start (NMDeviceTeam *self)
 	g_ptr_array_add (argv, (gpointer) "-o");
 	g_ptr_array_add (argv, (gpointer) "-n");
 	g_ptr_array_add (argv, (gpointer) "-U");
-	g_ptr_array_add (argv, (gpointer) "-D");
+	if (priv->teamd_dbus_watch)
+		g_ptr_array_add (argv, (gpointer) "-D");
 	g_ptr_array_add (argv, (gpointer) "-N");
 	g_ptr_array_add (argv, (gpointer) "-t");
 	g_ptr_array_add (argv, (gpointer) iface);
@@ -651,6 +696,14 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	NMSettingTeam *s_team;
 	const char *cfg;
 
+	if (nm_device_sys_iface_state_is_external (device))
+		return NM_ACT_STAGE_RETURN_SUCCESS;
+
+	if (nm_device_sys_iface_state_is_external_or_assume (device)) {
+		if (ensure_teamd_connection (device))
+			return NM_ACT_STAGE_RETURN_SUCCESS;
+	}
+
 	s_team = nm_device_get_applied_setting (device, NM_TYPE_SETTING_TEAM);
 	if (!s_team)
 		g_return_val_if_reached (NM_ACT_STAGE_RETURN_FAILURE);
@@ -892,20 +945,38 @@ constructed (GObject *object)
 {
 	NMDevice *device = NM_DEVICE (object);
 	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (device);
-	char *tmp_str = NULL;
+	gs_free char *tmp_str = NULL;
+	gs_unref_object GFile *file = NULL;
+	GError *error;
 
 	G_OBJECT_CLASS (nm_device_team_parent_class)->constructed (object);
 
-	/* Register D-Bus name watcher */
-	tmp_str = g_strdup_printf ("org.libteam.teamd.%s", nm_device_get_ip_iface (device));
-	priv->teamd_dbus_watch = g_bus_watch_name (G_BUS_TYPE_SYSTEM,
-	                                           tmp_str,
-	                                           G_BUS_NAME_WATCHER_FLAGS_NONE,
-	                                           teamd_dbus_appeared,
-	                                           teamd_dbus_vanished,
-	                                           NM_DEVICE (device),
-	                                           NULL);
-	g_free (tmp_str);
+	if (nm_dbus_manager_get_dbus_connection (nm_dbus_manager_get ())) {
+		/* Register D-Bus name watcher */
+		tmp_str = g_strdup_printf ("org.libteam.teamd.%s", nm_device_get_ip_iface (device));
+		priv->teamd_dbus_watch = g_bus_watch_name (G_BUS_TYPE_SYSTEM,
+		                                           tmp_str,
+		                                           G_BUS_NAME_WATCHER_FLAGS_NONE,
+		                                           teamd_dbus_appeared,
+		                                           teamd_dbus_vanished,
+		                                           NM_DEVICE (device),
+		                                           NULL);
+		return;
+	}
+
+	/* No D-Bus, watch unix socket */
+	tmp_str = g_strdup_printf ("/run/teamd/%s.sock",
+	                           nm_device_get_ip_iface (device));
+	file = g_file_new_for_path (tmp_str);
+	priv->usock_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error);
+	if (!priv->usock_monitor) {
+		nm_log_warn (LOGD_TEAM, "error monitoring %s: %s", tmp_str, error->message);
+	} else {
+		g_signal_connect (priv->usock_monitor,
+		                  "changed",
+		                  G_CALLBACK (monitor_changed_cb),
+		                  object);
+	}
 }
 
 NMDevice *
@@ -931,6 +1002,11 @@ dispose (GObject *object)
 		priv->teamd_dbus_watch = 0;
 	}
 
+	if (priv->usock_monitor) {
+		g_signal_handlers_disconnect_by_data (priv->usock_monitor, object);
+		g_clear_object (&priv->usock_monitor);
+	}
+
 	teamd_cleanup (self, TRUE);
 	nm_clear_g_free (&priv->config);
 
@@ -977,6 +1053,7 @@ nm_device_team_class_init (NMDeviceTeamClass *klass)
 	device_class->update_connection = update_connection;
 	device_class->master_update_slave_connection = master_update_slave_connection;
 
+	device_class->act_stage1_prepare_also_for_external_or_assume = TRUE;
 	device_class->act_stage1_prepare = act_stage1_prepare;
 	device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
 	device_class->deactivate = deactivate;
diff --git a/src/devices/tests/test-lldp.c b/src/devices/tests/test-lldp.c
index 8fc4ad67..d710327a 100644
--- a/src/devices/tests/test-lldp.c
+++ b/src/devices/tests/test-lldp.c
@@ -77,10 +77,12 @@ typedef struct {
 typedef struct {
 	gsize frame_len;
 	const uint8_t *frame;
+	const char *as_variant;
 } TestRecvFrame;
-#define TEST_RECV_FRAME_DEFINE(name, ...) \
+#define TEST_RECV_FRAME_DEFINE(name, _as_variant, ...) \
 	static const guint8 _##name##_v[] = { __VA_ARGS__ }; \
 	static const TestRecvFrame name = { \
+		.as_variant = _as_variant, \
 		.frame_len = sizeof (_##name##_v), \
 		.frame = _##name##_v, \
 	}
@@ -102,6 +104,7 @@ typedef struct {
 #define TEST_IFNAME "nm-tap-test0"
 
 TEST_RECV_FRAME_DEFINE (_test_recv_data0_frame0,
+	"{'raw': <[byte 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x88, 0xcc, 0x02, 0x07, 0x04, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x04, 0x04, 0x05, 0x31, 0x2f, 0x33, 0x06, 0x02, 0x00, 0x78, 0x08, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x0a, 0x03, 0x53, 0x59, 0x53, 0x0c, 0x04, 0x66, 0x6f, 0x6f, 0x00, 0x00, 0x00]>, 'chassis-id-type': <uint32 4>, 'chassis-id': <'00:01:02:03:04:05'>, 'port-id-type': <uint32 5>, 'port-id': <'1/3'>, 'destination': <'nearest-non-tpmr-bridge'>, 'port-description': <'Port'>, 'system-name': <'SYS'>, 'system-description': <'foo'>}",
 	/* Ethernet header */
 	0x01, 0x80, 0xc2, 0x00, 0x00, 0x03,     /* Destination MAC */
 	0x01, 0x02, 0x03, 0x04, 0x05, 0x06,     /* Source MAC */
@@ -119,7 +122,7 @@ TEST_RECV_FRAME_DEFINE (_test_recv_data0_frame0,
 );
 
 static void
-_test_recv_data0_check (GMainLoop *loop, NMLldpListener *listener)
+_test_recv_data0_check_do (GMainLoop *loop, NMLldpListener *listener, const TestRecvFrame *frame)
 {
 	GVariant *neighbors, *attr;
 	gs_unref_variant GVariant *neighbor = NULL;
@@ -132,7 +135,11 @@ _test_recv_data0_check (GMainLoop *loop, NMLldpListener *listener)
 	                              SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS, "00:01:02:03:04:05",
 	                              SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME, "1/3");
 	g_assert (neighbor);
-	g_assert_cmpint (g_variant_n_children (neighbor), ==, 4 + 4);
+	g_assert_cmpint (g_variant_n_children (neighbor), ==, 1 + 4 + 4);
+
+	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_RAW, G_VARIANT_TYPE_BYTESTRING);
+	nmtst_assert_variant_bytestring (attr, frame->frame, frame->frame_len);
+	nm_clear_g_variant (&attr);
 
 	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_PORT_DESCRIPTION, G_VARIANT_TYPE_STRING);
 	nmtst_assert_variant_string (attr, "Port");
@@ -151,13 +158,19 @@ _test_recv_data0_check (GMainLoop *loop, NMLldpListener *listener)
 	nm_clear_g_variant (&attr);
 }
 
+static void
+_test_recv_data0_check (GMainLoop *loop, NMLldpListener *listener)
+{
+	_test_recv_data0_check_do (loop, listener, &_test_recv_data0_frame0);
+}
+
 TEST_RECV_DATA_DEFINE (_test_recv_data0,       1, _test_recv_data0_check,  &_test_recv_data0_frame0);
 TEST_RECV_DATA_DEFINE (_test_recv_data0_twice, 1, _test_recv_data0_check,  &_test_recv_data0_frame0, &_test_recv_data0_frame0);
 
 TEST_RECV_FRAME_DEFINE (_test_recv_data1_frame0,
 	/* lldp.detailed.pcap from
 	 * https://wiki.wireshark.org/SampleCaptures#Link_Layer_Discovery_Protocol_.28LLDP.29 */
-
+	"{'raw': <[byte 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e, 0x00, 0x01, 0x30, 0xf9, 0xad, 0xa0, 0x88, 0xcc, 0x02, 0x07, 0x04, 0x00, 0x01, 0x30, 0xf9, 0xad, 0xa0, 0x04, 0x04, 0x05, 0x31, 0x2f, 0x31, 0x06, 0x02, 0x00, 0x78, 0x08, 0x17, 0x53, 0x75, 0x6d, 0x6d, 0x69, 0x74, 0x33, 0x30, 0x30, 0x2d, 0x34, 0x38, 0x2d, 0x50, 0x6f, 0x72, 0x74, 0x20, 0x31, 0x30, 0x30, 0x31, 0x00, 0x0a, 0x0d, 0x53, 0x75, 0x6d, 0x6d, 0x69, 0x74, 0x33, 0x30, 0x30, 0x2d, 0x34, 0x38, 0x00, 0x0c, 0x4c, 0x53, 0x75, 0x6d, 0x6d, 0x69, 0x74, 0x33, 0x30, 0x30, 0x2d, 0x34, 0x38, 0x20, 0x2d, 0x20, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x37, 0x2e, 0x34, 0x65, 0x2e, 0x31, 0x20, 0x28, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x20, 0x35, 0x29, 0x20, 0x62, 0x79, 0x20, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x30, 0x35, 0x2f, 0x32, 0x37, 0x2f, 0x30, 0x35, 0x20, 0x30, 0x34, 0x3a, 0x35, 0x33, 0x3a, 0x31, 0x31, 0x00, 0x0e, 0x04, 0x00, 0x14, 0x00, 0x14, 0x10, 0x0e, 0x07, 0x06, 0x00, 0x01, 0x30, 0xf9, 0xad, 0xa0, 0x02, 0x00, 0x00, 0x03, 0xe9, 0x00, 0xfe, 0x07, 0x00, 0x12, 0x0f, 0x02, 0x07, 0x01, 0x00, 0xfe, 0x09, 0x00, 0x12, 0x0f, 0x01, 0x03, 0x6c, 0x00, 0x00, 0x10, 0xfe, 0x09, 0x00, 0x12, 0x0f, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x06, 0x00, 0x12, 0x0f, 0x04, 0x05, 0xf2, 0xfe, 0x06, 0x00, 0x80, 0xc2, 0x01, 0x01, 0xe8, 0xfe, 0x07, 0x00, 0x80, 0xc2, 0x02, 0x01, 0x00, 0x00, 0xfe, 0x16, 0x00, 0x80, 0xc2, 0x03, 0x01, 0xe8, 0x0f, 0x76, 0x32, 0x2d, 0x30, 0x34, 0x38, 0x38, 0x2d, 0x30, 0x33, 0x2d, 0x30, 0x35, 0x30, 0x35, 0xfe, 0x05, 0x00, 0x80, 0xc2, 0x04, 0x00, 0x00, 0x00]>, 'chassis-id-type': <uint32 4>, 'chassis-id': <'00:01:30:F9:AD:A0'>, 'port-id-type': <uint32 5>, 'port-id': <'1/1'>, 'destination': <'nearest-bridge'>, 'port-description': <'Summit300-48-Port 1001'>, 'system-name': <'Summit300-48'>, 'system-description': <'Summit300-48 - Version 7.4e.1 (Build 5) by Release_Master 05/27/05 04:53:11'>, 'system-capabilities': <uint32 20>, 'management-addresses': <[{'address-subtype': <uint32 6>, 'address': <[byte 0x00, 0x01, 0x30, 0xf9, 0xad, 0xa0]>, 'interface-number-subtype': <uint32 2>, 'interface-number': <uint32 1001>}]>, 'ieee-802-1-pvid': <uint32 488>, 'ieee-802-1-ppvid': <uint32 0>, 'ieee-802-1-ppvid-flags': <uint32 1>, 'ieee-802-1-ppvids': <[{'ppvid': <uint32 0>, 'flags': <uint32 1>}]>, 'ieee-802-1-vid': <uint32 488>, 'ieee-802-1-vlan-name': <'v2-0488-03-0505'>, 'ieee-802-1-vlans': <[{'vid': <uint32 488>, 'name': <'v2-0488-03-0505'>}]>, 'ieee-802-3-mac-phy-conf': <{'autoneg': <uint32 3>, 'pmd-autoneg-cap': <uint32 27648>, 'operational-mau-type': <uint32 16>}>, 'ieee-802-3-power-via-mdi': <{'mdi-power-support': <uint32 7>, 'pse-power-pair': <uint32 1>, 'power-class': <uint32 0>}>, 'ieee-802-3-max-frame-size': <uint32 1522>}",
 	/* ethernet header */
 	0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e, /* destination mac */
 	0x00, 0x01, 0x30, 0xf9, 0xad, 0xa0, /* source mac */
@@ -229,7 +242,11 @@ _test_recv_data1_check (GMainLoop *loop, NMLldpListener *listener)
 	                              SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS, "00:01:30:F9:AD:A0",
 	                              SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME, "1/1");
 	g_assert (neighbor);
-	g_assert_cmpint (g_variant_n_children (neighbor), ==, 4 + 16);
+	g_assert_cmpint (g_variant_n_children (neighbor), ==, 1 + 4 + 16);
+
+	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_RAW, G_VARIANT_TYPE_BYTESTRING);
+	nmtst_assert_variant_bytestring (attr, _test_recv_data1_frame0.frame, _test_recv_data1_frame0.frame_len);
+	nm_clear_g_variant (&attr);
 
 	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_DESTINATION, G_VARIANT_TYPE_STRING);
 	nmtst_assert_variant_string (attr, NM_LLDP_DEST_NEAREST_BRIDGE);
@@ -263,11 +280,11 @@ _test_recv_data1_check (GMainLoop *loop, NMLldpListener *listener)
 	g_assert_cmpuint (g_variant_n_children (attr), ==, 1);
 	child = g_variant_get_child_value (attr, 0);
 	g_assert (child);
-	g_variant_lookup (child, "interface-number", "u", &v_uint);
+	g_assert (g_variant_lookup (child, "interface-number", "u", &v_uint));
 	g_assert_cmpint (v_uint, ==, 1001);
-	g_variant_lookup (child, "interface-number-subtype", "u", &v_uint);
+	g_assert (g_variant_lookup (child, "interface-number-subtype", "u", &v_uint));
 	g_assert_cmpint (v_uint, ==, 2);
-	g_variant_lookup (child, "address-subtype", "u", &v_uint);
+	g_assert (g_variant_lookup (child, "address-subtype", "u", &v_uint));
 	g_assert_cmpint (v_uint, ==, 6);
 	nm_clear_g_variant (&child);
 	nm_clear_g_variant (&attr);
@@ -275,22 +292,22 @@ _test_recv_data1_check (GMainLoop *loop, NMLldpListener *listener)
 	/* IEEE 802.3 - Power Via MDI */
 	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_IEEE_802_3_POWER_VIA_MDI, G_VARIANT_TYPE_VARDICT);
 	g_assert (attr);
-	g_variant_lookup (attr, "mdi-power-support", "u", &v_uint);
+	g_assert (g_variant_lookup (attr, "mdi-power-support", "u", &v_uint));
 	g_assert_cmpint (v_uint, ==, 7);
-	g_variant_lookup (attr, "pse-power-pair", "u", &v_uint);
+	g_assert (g_variant_lookup (attr, "pse-power-pair", "u", &v_uint));
 	g_assert_cmpint (v_uint, ==, 1);
-	g_variant_lookup (attr, "power-class", "u", &v_uint);
+	g_assert (g_variant_lookup (attr, "power-class", "u", &v_uint));
 	g_assert_cmpint (v_uint, ==, 0);
 	nm_clear_g_variant (&attr);
 
 	/* IEEE 802.3 - MAC/PHY Configuration/Status */
 	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_IEEE_802_3_MAC_PHY_CONF, G_VARIANT_TYPE_VARDICT);
 	g_assert (attr);
-	g_variant_lookup (attr, "autoneg", "u", &v_uint);
+	g_assert (g_variant_lookup (attr, "autoneg", "u", &v_uint));
 	g_assert_cmpint (v_uint, ==, 3);
-	g_variant_lookup (attr, "pmd-autoneg-cap", "u", &v_uint);
+	g_assert (g_variant_lookup (attr, "pmd-autoneg-cap", "u", &v_uint));
 	g_assert_cmpint (v_uint, ==, 0x6c00);
-	g_variant_lookup (attr, "operational-mau-type", "u", &v_uint);
+	g_assert (g_variant_lookup (attr, "operational-mau-type", "u", &v_uint));
 	g_assert_cmpint (v_uint, ==, 16);
 	nm_clear_g_variant (&attr);
 
@@ -319,9 +336,9 @@ _test_recv_data1_check (GMainLoop *loop, NMLldpListener *listener)
 	g_assert_cmpuint (g_variant_n_children (attr), ==, 1);
 	child = g_variant_get_child_value (attr, 0);
 	g_assert (child);
-	g_variant_lookup (child, "ppvid", "u", &v_uint);
+	g_assert (g_variant_lookup (child, "ppvid", "u", &v_uint));
 	g_assert_cmpint (v_uint, ==, 0);
-	g_variant_lookup (child, "flags", "u", &v_uint);
+	g_assert (g_variant_lookup (child, "flags", "u", &v_uint));
 	g_assert_cmpint (v_uint, ==, 1);
 	nm_clear_g_variant (&child);
 	nm_clear_g_variant (&attr);
@@ -339,9 +356,9 @@ _test_recv_data1_check (GMainLoop *loop, NMLldpListener *listener)
 	g_assert_cmpuint (g_variant_n_children (attr), ==, 1);
 	child = g_variant_get_child_value (attr, 0);
 	g_assert (child);
-	g_variant_lookup (child, "vid", "u", &v_uint);
+	g_assert (g_variant_lookup (child, "vid", "u", &v_uint));
 	g_assert_cmpint (v_uint, ==, 488);
-	g_variant_lookup (child, "name", "&s", &v_str);
+	g_assert (g_variant_lookup (child, "name", "&s", &v_str));
 	g_assert_cmpstr (v_str, ==, "v2-0488-03-0505");
 	nm_clear_g_variant (&child);
 	nm_clear_g_variant (&attr);
@@ -352,6 +369,7 @@ _test_recv_data1_check (GMainLoop *loop, NMLldpListener *listener)
 TEST_RECV_DATA_DEFINE (_test_recv_data1,       1, _test_recv_data1_check,  &_test_recv_data1_frame0);
 
 TEST_RECV_FRAME_DEFINE (_test_recv_data2_frame0_ttl1,
+	"{'raw': <[byte 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x88, 0xcc, 0x02, 0x07, 0x04, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x04, 0x04, 0x05, 0x31, 0x2f, 0x33, 0x06, 0x02, 0x00, 0x01, 0x08, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x0a, 0x03, 0x53, 0x59, 0x53, 0x0c, 0x04, 0x66, 0x6f, 0x6f, 0x00, 0x00, 0x00]>, 'chassis-id-type': <uint32 4>, 'chassis-id': <'00:01:02:03:04:05'>, 'port-id-type': <uint32 5>, 'port-id': <'1/3'>, 'destination': <'nearest-non-tpmr-bridge'>, 'port-description': <'Port'>, 'system-name': <'SYS'>, 'system-description': <'foo'>}",
 	/* Ethernet header */
 	0x01, 0x80, 0xc2, 0x00, 0x00, 0x03,     /* Destination MAC */
 	0x01, 0x02, 0x03, 0x04, 0x05, 0x06,     /* Source MAC */
@@ -374,7 +392,7 @@ _test_recv_data2_ttl1_check (GMainLoop *loop, NMLldpListener *listener)
 	gulong notify_id;
 	GVariant *neighbors;
 
-	_test_recv_data0_check (loop, listener);
+	_test_recv_data0_check_do (loop, listener, &_test_recv_data2_frame0_ttl1);
 
 	/* wait for signal. */
 	notify_id = g_signal_connect (listener, "notify::" NM_LLDP_LISTENER_NEIGHBORS,
@@ -513,6 +531,62 @@ _test_recv_fixture_teardown (TestRecvFixture *fixture, gconstpointer user_data)
 
 /*****************************************************************************/
 
+static void
+test_parse_frames (gconstpointer test_data)
+{
+	const TestRecvFrame *frame = test_data;
+	gs_unref_variant GVariant *v_neighbor = NULL;
+	gs_unref_variant GVariant *attr = NULL;
+	gs_free char *as_variant = NULL;
+
+	v_neighbor = nmtst_lldp_parse_from_raw (frame->frame, frame->frame_len);
+	g_assert (v_neighbor);
+
+	attr = g_variant_lookup_value (v_neighbor, NM_LLDP_ATTR_RAW, G_VARIANT_TYPE_BYTESTRING);
+	nmtst_assert_variant_bytestring (attr, frame->frame, frame->frame_len);
+	nm_clear_g_variant (&attr);
+
+	as_variant = g_variant_print (v_neighbor, TRUE);
+	g_assert (as_variant);
+	g_assert_cmpstr (frame->as_variant, ==, as_variant);
+}
+
+/*****************************************************************************/
+
+TEST_RECV_FRAME_DEFINE (_test_parse_frames_3,
+	/* https://github.com/the-tcpdump-group/tcpdump/blob/c4f8796bf8bec740621a360eded236d8991ea00f/tests/lldp_mudurl.pcap */
+	"{'raw': <[byte 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e, 0x00, 0x23, 0x54, 0xc2, 0x57, 0x02, 0x88, 0xcc, 0x02, 0x07, 0x04, 0x00, 0x23, 0x54, 0xc2, 0x57, 0x02, 0x04, 0x07, 0x03, 0x00, 0x23, 0x54, 0xc2, 0x57, 0x02, 0x06, 0x02, 0x00, 0x78, 0x0a, 0x1c, 0x75, 0x70, 0x73, 0x74, 0x61, 0x69, 0x72, 0x73, 0x2e, 0x6f, 0x66, 0x63, 0x6f, 0x75, 0x72, 0x73, 0x65, 0x69, 0x6d, 0x72, 0x69, 0x67, 0x68, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x0c, 0x5c, 0x55, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x20, 0x31, 0x34, 0x2e, 0x30, 0x34, 0x2e, 0x35, 0x20, 0x4c, 0x54, 0x53, 0x20, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x20, 0x33, 0x2e, 0x31, 0x33, 0x2e, 0x30, 0x2d, 0x31, 0x30, 0x36, 0x2d, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x20, 0x23, 0x31, 0x35, 0x33, 0x2d, 0x55, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x20, 0x53, 0x4d, 0x50, 0x20, 0x54, 0x75, 0x65, 0x20, 0x44, 0x65, 0x63, 0x20, 0x36, 0x20, 0x31, 0x35, 0x3a, 0x34, 0x35, 0x3a, 0x31, 0x33, 0x20, 0x55, 0x54, 0x43, 0x20, 0x32, 0x30, 0x31, 0x36, 0x20, 0x69, 0x36, 0x38, 0x36, 0x0e, 0x04, 0x00, 0x9c, 0x00, 0x08, 0x10, 0x0c, 0x05, 0x01, 0x3e, 0x0c, 0xad, 0x72, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x10, 0x18, 0x11, 0x02, 0x20, 0x01, 0x08, 0xa8, 0x10, 0x06, 0x00, 0x04, 0x02, 0x23, 0x54, 0xff, 0xfe, 0xc2, 0x57, 0x02, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x08, 0x04, 0x65, 0x74, 0x68, 0x30, 0xfe, 0x09, 0x00, 0x12, 0x0f, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x09, 0x00, 0x12, 0x0f, 0x01, 0x03, 0xec, 0xc3, 0x00, 0x10, 0xfe, 0x40, 0x00, 0x00, 0x5e, 0x01, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x69, 0x6d, 0x72, 0x69, 0x67, 0x68, 0x74, 0x2e, 0x6d, 0x75, 0x64, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x2e, 0x77, 0x65, 0x6c, 0x6c, 0x2d, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x2f, 0x6d, 0x75, 0x64, 0x2f, 0x76, 0x31, 0x2f, 0x76, 0x6f, 0x6d, 0x69, 0x74, 0x76, 0x32, 0x2e, 0x30, 0x00, 0x00]>, 'chassis-id-type': <uint32 4>, 'chassis-id': <'00:23:54:C2:57:02'>, 'port-id-type': <uint32 3>, 'port-id': <'00:23:54:C2:57:02'>, 'destination': <'nearest-bridge'>, 'port-description': <'eth0'>, 'system-name': <'upstairs.ofcourseimright.com'>, 'system-description': <'Ubuntu 14.04.5 LTS Linux 3.13.0-106-generic #153-Ubuntu SMP Tue Dec 6 15:45:13 UTC 2016 i686'>, 'system-capabilities': <uint32 156>, 'management-addresses': <[{'address-subtype': <uint32 1>, 'address': <[byte 0x3e, 0x0c, 0xad, 0x72]>, 'interface-number-subtype': <uint32 2>, 'interface-number': <uint32 2>}, {'address-subtype': <uint32 2>, 'address': <[byte 0x20, 0x01, 0x08, 0xa8, 0x10, 0x06, 0x00, 0x04, 0x02, 0x23, 0x54, 0xff, 0xfe, 0xc2, 0x57, 0x02]>, 'interface-number-subtype': <uint32 2>, 'interface-number': <uint32 2>}]>, 'ieee-802-3-mac-phy-conf': <{'autoneg': <uint32 3>, 'pmd-autoneg-cap': <uint32 60611>, 'operational-mau-type': <uint32 16>}>, 'mud-url': <'https://imright.mud.example.com/.well-known/mud/v1/vomitv2.0'>}",
+	0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e, /* ethernet destination */
+	0x00, 0x23, 0x54, 0xc2, 0x57, 0x02, /* ethernet source */
+	0x88, 0xcc, /* ethernet type */
+
+	0x02, 0x07, 0x04, 0x00, 0x23, 0x54, 0xc2, 0x57, 0x02, 0x04, 0x07, 0x03, 0x00, 0x23,
+	0x54, 0xc2, 0x57, 0x02, 0x06, 0x02, 0x00, 0x78, 0x0a, 0x1c, 0x75, 0x70, 0x73, 0x74,
+	0x61, 0x69, 0x72, 0x73, 0x2e, 0x6f, 0x66, 0x63, 0x6f, 0x75, 0x72, 0x73, 0x65, 0x69,
+	0x6d, 0x72, 0x69, 0x67, 0x68, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x0c, 0x5c, 0x55, 0x62,
+	0x75, 0x6e, 0x74, 0x75, 0x20, 0x31, 0x34, 0x2e, 0x30, 0x34, 0x2e, 0x35, 0x20, 0x4c,
+	0x54, 0x53, 0x20, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x20, 0x33, 0x2e, 0x31, 0x33, 0x2e,
+	0x30, 0x2d, 0x31, 0x30, 0x36, 0x2d, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x20,
+	0x23, 0x31, 0x35, 0x33, 0x2d, 0x55, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x20, 0x53, 0x4d,
+	0x50, 0x20, 0x54, 0x75, 0x65, 0x20, 0x44, 0x65, 0x63, 0x20, 0x36, 0x20, 0x31, 0x35,
+	0x3a, 0x34, 0x35, 0x3a, 0x31, 0x33, 0x20, 0x55, 0x54, 0x43, 0x20, 0x32, 0x30, 0x31,
+	0x36, 0x20, 0x69, 0x36, 0x38, 0x36, 0x0e, 0x04, 0x00, 0x9c, 0x00, 0x08, 0x10, 0x0c,
+	0x05, 0x01, 0x3e, 0x0c, 0xad, 0x72, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x10, 0x18,
+	0x11, 0x02, 0x20, 0x01, 0x08, 0xa8, 0x10, 0x06, 0x00, 0x04, 0x02, 0x23, 0x54, 0xff,
+	0xfe, 0xc2, 0x57, 0x02, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x08, 0x04, 0x65, 0x74,
+	0x68, 0x30, 0xfe, 0x09, 0x00, 0x12, 0x0f, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0xfe,
+	0x09, 0x00, 0x12, 0x0f, 0x01, 0x03, 0xec, 0xc3, 0x00, 0x10, 0xfe, 0x40, 0x00, 0x00,
+	0x5e, 0x01, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x69, 0x6d, 0x72, 0x69,
+	0x67, 0x68, 0x74, 0x2e, 0x6d, 0x75, 0x64, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c,
+	0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x2e, 0x77, 0x65, 0x6c, 0x6c, 0x2d, 0x6b, 0x6e,
+	0x6f, 0x77, 0x6e, 0x2f, 0x6d, 0x75, 0x64, 0x2f, 0x76, 0x31, 0x2f, 0x76, 0x6f, 0x6d,
+	0x69, 0x74, 0x76, 0x32, 0x2e,
+
+	0x30, 0x00, 0x00, /* ethernet trailer */
+);
+
+/*****************************************************************************/
+
 NMTstpSetupFunc const _nmtstp_setup_platform_func = nm_linux_platform_setup;
 
 void
@@ -530,4 +604,9 @@ _nmtstp_setup_tests (void)
 	_TEST_ADD_RECV ("/lldp/recv/0_twice", &_test_recv_data0_twice);
 	_TEST_ADD_RECV ("/lldp/recv/1",       &_test_recv_data1);
 	_TEST_ADD_RECV ("/lldp/recv/2_ttl1",  &_test_recv_data2_ttl1);
+
+	g_test_add_data_func ("/lldp/parse-frames/0", &_test_recv_data0_frame0, test_parse_frames);
+	g_test_add_data_func ("/lldp/parse-frames/1", &_test_recv_data1_frame0, test_parse_frames);
+	g_test_add_data_func ("/lldp/parse-frames/2", &_test_recv_data2_frame0_ttl1, test_parse_frames);
+	g_test_add_data_func ("/lldp/parse-frames/3", &_test_parse_frames_3, test_parse_frames);
 }
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index f5b62989..2d97f9b1 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -1287,7 +1287,6 @@ can_auto_connect (NMDevice *device,
 	NMWifiAP *ap;
 	const char *method6, *mode;
 	gboolean auto4, auto6;
-	guint64 timestamp = 0;
 
 	nm_assert (!specific_object || !*specific_object);
 
@@ -1315,15 +1314,6 @@ can_auto_connect (NMDevice *device,
 	else if (!auto4 && !auto6 && nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_MESH))
 		return TRUE;
 
-	/* Don't autoconnect to networks that have been tried at least once
-	 * but haven't been successful, since these are often accidental choices
-	 * from the menu and the user may not know the password.
-	 */
-	if (nm_settings_connection_get_timestamp (sett_conn, &timestamp)) {
-		if (timestamp == 0)
-			return FALSE;
-	}
-
 	ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
 	if (ap) {
 		/* All good; connection is usable */
diff --git a/src/devices/wifi/nm-wifi-ap.c b/src/devices/wifi/nm-wifi-ap.c
index e427c86f..b0aaf3e9 100644
--- a/src/devices/wifi/nm-wifi-ap.c
+++ b/src/devices/wifi/nm-wifi-ap.c
@@ -913,7 +913,8 @@ nm_wifi_ap_class_init (NMWifiAPClass *ap_class)
 	| NM_802_11_AP_SEC_KEY_MGMT_PSK \
 	| NM_802_11_AP_SEC_KEY_MGMT_802_1X \
 	| NM_802_11_AP_SEC_KEY_MGMT_SAE \
-	| NM_802_11_AP_SEC_KEY_MGMT_OWE )
+	| NM_802_11_AP_SEC_KEY_MGMT_OWE \
+	| NM_802_11_AP_SEC_KEY_MGMT_OWE_TM)
 
 	GObjectClass *object_class = G_OBJECT_CLASS (ap_class);
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (ap_class);
diff --git a/src/devices/wifi/nm-wifi-utils.c b/src/devices/wifi/nm-wifi-utils.c
index 0903dc8b..68538525 100644
--- a/src/devices/wifi/nm-wifi-utils.c
+++ b/src/devices/wifi/nm-wifi-utils.c
@@ -760,7 +760,8 @@ nm_wifi_utils_complete_connection (GBytes *ap_ssid,
 		              NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open",
 		              NULL);
 	} else if (   (key_mgmt && !strcmp (key_mgmt, "owe"))
-	           || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_OWE)) {
+	           || NM_FLAGS_ANY (ap_rsn_flags, NM_802_11_AP_SEC_KEY_MGMT_OWE |
+	                                          NM_802_11_AP_SEC_KEY_MGMT_OWE_TM)) {
 		g_object_set (s_wsec,
 		              NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "owe",
 		              NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open",
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index 3fdc5ac5..f172e57f 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -954,6 +954,7 @@ static_stage3_ip4_done (NMModemBroadband *self)
 	guint i;
 	guint32 ip4_route_table, ip4_route_metric;
 	NMPlatformIP4Route *r;
+	guint32 mtu_n;
 
 	g_return_val_if_fail (self->_priv.ipv4_config, FALSE);
 	g_return_val_if_fail (self->_priv.bearer, FALSE);
@@ -1025,6 +1026,14 @@ static_stage3_ip4_done (NMModemBroadband *self)
 		}
 	}
 
+#if MM_CHECK_VERSION(1, 4, 0)
+	mtu_n = mm_bearer_ip_config_get_mtu (self->_priv.ipv4_config);
+	if (mtu_n) {
+		nm_ip4_config_set_mtu (config, mtu_n, NM_IP_CONFIG_SOURCE_WWAN);
+		_LOGI ("  MTU %u", mtu_n);
+	}
+#endif
+
 out:
 	g_signal_emit_by_name (self, NM_MODEM_IP4_CONFIG_RESULT, config, error);
 	g_clear_error (&error);
diff --git a/src/dhcp/nm-dhcp-client.c b/src/dhcp/nm-dhcp-client.c
index 90a64aca..f7324baf 100644
--- a/src/dhcp/nm-dhcp-client.c
+++ b/src/dhcp/nm-dhcp-client.c
@@ -50,6 +50,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDhcpClient,
 	PROP_IAID_EXPLICIT,
 	PROP_HOSTNAME,
 	PROP_HOSTNAME_FLAGS,
+	PROP_MUD_URL,
 );
 
 typedef struct _NMDhcpClientPrivate {
@@ -60,6 +61,7 @@ typedef struct _NMDhcpClientPrivate {
 	char *       uuid;
 	GBytes *     client_id;
 	char *       hostname;
+	char *       mud_url;
 	pid_t        pid;
 	guint        timeout_id;
 	guint        watch_id;
@@ -312,6 +314,14 @@ nm_dhcp_client_get_use_fqdn (NMDhcpClient *self)
 	return NM_DHCP_CLIENT_GET_PRIVATE (self)->use_fqdn;
 }
 
+const char *
+nm_dhcp_client_get_mud_url (NMDhcpClient *self)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
+
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->mud_url;
+}
+
 /*****************************************************************************/
 
 static const char *state_table[NM_DHCP_STATE_MAX + 1] = {
@@ -1052,6 +1062,10 @@ set_property (GObject *object, guint prop_id,
 		/* construct-only */
 		priv->hostname_flags = g_value_get_uint (value);
 		break;
+	case PROP_MUD_URL:
+		/* construct-only */
+		priv->mud_url = g_value_dup_string (value);
+		break;
 	case PROP_ROUTE_TABLE:
 		priv->route_table = g_value_get_uint (value);
 		break;
@@ -1102,6 +1116,7 @@ dispose (GObject *object)
 	nm_clear_g_free (&priv->iface);
 	nm_clear_g_free (&priv->hostname);
 	nm_clear_g_free (&priv->uuid);
+	nm_clear_g_free (&priv->mud_url);
 	nm_clear_pointer (&priv->client_id, g_bytes_unref);
 	nm_clear_pointer (&priv->hwaddr, g_bytes_unref);
 	nm_clear_pointer (&priv->bcast_hwaddr, g_bytes_unref);
@@ -1191,6 +1206,12 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
 	                       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
 	                       G_PARAM_STATIC_STRINGS);
 
+	obj_properties[PROP_MUD_URL] =
+	    g_param_spec_string (NM_DHCP_CLIENT_MUD_URL, "", "",
+	                         NULL,
+	                         G_PARAM_WRITABLE | 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,
diff --git a/src/dhcp/nm-dhcp-client.h b/src/dhcp/nm-dhcp-client.h
index 1ff09067..479ea000 100644
--- a/src/dhcp/nm-dhcp-client.h
+++ b/src/dhcp/nm-dhcp-client.h
@@ -30,6 +30,7 @@
 #define NM_DHCP_CLIENT_INTERFACE        "iface"
 #define NM_DHCP_CLIENT_MULTI_IDX        "multi-idx"
 #define NM_DHCP_CLIENT_HOSTNAME         "hostname"
+#define NM_DHCP_CLIENT_MUD_URL          "mud-url"
 #define NM_DHCP_CLIENT_ROUTE_METRIC     "route-metric"
 #define NM_DHCP_CLIENT_ROUTE_TABLE      "route-table"
 #define NM_DHCP_CLIENT_TIMEOUT          "timeout"
@@ -141,6 +142,7 @@ gboolean nm_dhcp_client_get_iaid_explicit (NMDhcpClient *self);
 GBytes *nm_dhcp_client_get_client_id (NMDhcpClient *self);
 
 const char *nm_dhcp_client_get_hostname (NMDhcpClient *self);
+const char *nm_dhcp_client_get_mud_url (NMDhcpClient *self);
 
 NMDhcpHostnameFlags nm_dhcp_client_get_hostname_flags (NMDhcpClient *self);
 
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/dhcp/nm-dhcp-dhclient-utils.c
index da28abad..76fc1a89 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -33,6 +33,13 @@
 #define ALSOREQ_TAG "also request "
 #define REQ_TAG "request "
 
+#define MUDURLv4_DEF    "option mudurl code 161 = text;\n"
+#define MUDURLv4_FMT    "send mudurl \"%s\";\n"
+
+#define MUDURLv6_DEF    "option dhcp6.mudurl code 112 = text;\n"
+#define MUDURLv6_FMT    "send dhcp6.mudurl \"%s\";\n"
+
+
 static void
 add_request (GPtrArray *array, const char *item)
 {
@@ -183,6 +190,19 @@ add_hostname6 (GString *str,
 	}
 }
 
+static void add_mud_url_config(GString *str, const char *mud_url, int addr_family)
+{
+	if (mud_url) {
+		if (addr_family == AF_INET) {
+			g_string_append (str, MUDURLv4_DEF);
+			g_string_append_printf (str, MUDURLv4_FMT, mud_url);
+		} else {
+			g_string_append (str, MUDURLv6_DEF);
+			g_string_append_printf (str, MUDURLv6_FMT, mud_url);
+		}
+	}
+}
+
 static GBytes *
 read_client_id (const char *str)
 {
@@ -286,6 +306,7 @@ nm_dhcp_dhclient_create_config (const char *interface,
                                 guint32 timeout,
                                 gboolean use_fqdn,
                                 NMDhcpHostnameFlags hostname_flags,
+                                const char *mud_url,
                                 const char *orig_path,
                                 const char *orig_contents,
                                 GBytes **out_new_client_id)
@@ -451,6 +472,7 @@ nm_dhcp_dhclient_create_config (const char *interface,
 		g_string_append_printf (new_contents, "timeout %u;\n", timeout);
 	}
 
+	add_mud_url_config (new_contents, mud_url, addr_family);
 	if (addr_family == AF_INET) {
 		add_ip4_config (new_contents, client_id, hostname, use_fqdn, hostname_flags);
 		add_request (reqs, "rfc3442-classless-static-routes");
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.h b/src/dhcp/nm-dhcp-dhclient-utils.h
index 0cf53887..5094d614 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.h
+++ b/src/dhcp/nm-dhcp-dhclient-utils.h
@@ -17,6 +17,7 @@ char *nm_dhcp_dhclient_create_config (const char *interface,
                                       guint32 timeout,
                                       gboolean use_fqdn,
                                       NMDhcpHostnameFlags hostname_flags,
+                                      const char *mud_url,
                                       const char *orig_path,
                                       const char *orig_contents,
                                       GBytes **out_new_client_id);
diff --git a/src/dhcp/nm-dhcp-dhclient.c b/src/dhcp/nm-dhcp-dhclient.c
index 617ce236..4ad056e5 100644
--- a/src/dhcp/nm-dhcp-dhclient.c
+++ b/src/dhcp/nm-dhcp-dhclient.c
@@ -147,6 +147,7 @@ merge_dhclient_config (NMDhcpDhclient *self,
                        guint32 timeout,
                        gboolean use_fqdn,
                        NMDhcpHostnameFlags hostname_flags,
+                       const char *mud_url,
                        const char *orig_path,
                        GBytes **out_new_client_id,
                        GError **error)
@@ -176,6 +177,7 @@ merge_dhclient_config (NMDhcpDhclient *self,
 	                                      timeout,
 	                                      use_fqdn,
 	                                      hostname_flags,
+	                                      mud_url,
 	                                      orig_path,
 	                                      orig,
 	                                      out_new_client_id);
@@ -268,6 +270,7 @@ create_dhclient_config (NMDhcpDhclient *self,
                         guint32 timeout,
                         gboolean use_fqdn,
                         NMDhcpHostnameFlags hostname_flags,
+                        const char *mud_url,
                         GBytes **out_new_client_id)
 {
 	gs_free char *orig = NULL;
@@ -296,6 +299,7 @@ create_dhclient_config (NMDhcpDhclient *self,
 	                            timeout,
 	                            use_fqdn,
 	                            hostname_flags,
+	                            mud_url,
 	                            orig,
 	                            out_new_client_id,
 	                            &error)) {
@@ -496,6 +500,7 @@ ip4_start (NMDhcpClient *client,
 	                                          nm_dhcp_client_get_timeout (client),
 	                                          nm_dhcp_client_get_use_fqdn (client),
 	                                          nm_dhcp_client_get_hostname_flags (client),
+	                                          nm_dhcp_client_get_mud_url (client),
 	                                          &new_client_id);
 	if (!priv->conf_file) {
 		nm_utils_error_set_literal (error,
@@ -540,6 +545,7 @@ ip6_start (NMDhcpClient *client,
 	                                          nm_dhcp_client_get_timeout (client),
 	                                          TRUE,
 	                                          nm_dhcp_client_get_hostname_flags (client),
+	                                          nm_dhcp_client_get_mud_url (client),
 	                                          NULL);
 	if (!priv->conf_file) {
 		nm_utils_error_set_literal (error,
diff --git a/src/dhcp/nm-dhcp-helper.c b/src/dhcp/nm-dhcp-helper.c
index 365e32e3..071d331b 100644
--- a/src/dhcp/nm-dhcp-helper.c
+++ b/src/dhcp/nm-dhcp-helper.c
@@ -46,33 +46,46 @@
 
 /*****************************************************************************/
 
-static const char * ignore[] = {"PATH", "SHLVL", "_", "PWD", "dhc_dbus", NULL};
-
 static GVariant *
 build_signal_parameters (void)
 {
-	char **item;
+	const char *const*environ_iter;
 	GVariantBuilder builder;
 
 	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
 
 	/* List environment and format for dbus dict */
-	for (item = environ; *item; item++) {
-		char *name, *val, **p;
-
-		/* Split on the = */
-		name = g_strdup (*item);
-		val = strchr (name, '=');
-		if (!val || val == name)
-			goto next;
-		*val++ = '\0';
-
-		/* Ignore non-DCHP-related environment variables */
-		for (p = (char **) ignore; *p; p++) {
+	for (environ_iter = (const char *const*) environ; *environ_iter; environ_iter++) {
+		static const char *const ignore_with_prefix_list[] = {
+			"PATH",
+			"SHLVL",
+			"_",
+			"PWD",
+			"dhc_dbus",
+			NULL
+		};
+		const char *item = *environ_iter;
+		gs_free char *name = NULL;
+		const char *val;
+		const char *const*p;
+
+		val = strchr (item, '=');
+		if (   !val
+		    || item == val)
+			continue;
+
+		name = g_strndup (item, val - item);
+		val += 1;
+
+		/* Ignore non-DHCP-related environment variables */
+		for (p = ignore_with_prefix_list; *p; p++) {
 			if (strncmp (name, *p, strlen (*p)) == 0)
 				goto next;
 		}
 
+		if (!g_utf8_validate (name, -1, NULL))
+			continue;
+
 		/* Value passed as a byte array rather than a string, because there are
 		 * no character encoding guarantees with DHCP, and D-Bus requires
 		 * strings to be UTF-8.
@@ -85,8 +98,8 @@ build_signal_parameters (void)
 		                       g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
 		                                                  val, strlen (val), 1));
 
-	next:
-		g_free (name);
+next:
+		;
 	}
 
 	return g_variant_ref_sink (g_variant_new ("(a{sv})", &builder));
diff --git a/src/dhcp/nm-dhcp-manager.c b/src/dhcp/nm-dhcp-manager.c
index 333744dd..7565bfc8 100644
--- a/src/dhcp/nm-dhcp-manager.c
+++ b/src/dhcp/nm-dhcp-manager.c
@@ -220,6 +220,7 @@ client_start (NMDhcpManager *self,
               const char *hostname,
               gboolean hostname_use_fqdn,
               NMDhcpHostnameFlags hostname_flags,
+              const char *mud_url,
               gboolean info_only,
               NMSettingIP6ConfigPrivacy privacy,
               const char *last_ip4_address,
@@ -303,6 +304,7 @@ client_start (NMDhcpManager *self,
 	                       NM_DHCP_CLIENT_IAID, (guint) iaid,
 	                       NM_DHCP_CLIENT_IAID_EXPLICIT, iaid_explicit,
 	                       NM_DHCP_CLIENT_HOSTNAME, hostname,
+	                       NM_DHCP_CLIENT_MUD_URL, mud_url,
 	                       NM_DHCP_CLIENT_ROUTE_TABLE, (guint) route_table,
 	                       NM_DHCP_CLIENT_ROUTE_METRIC, (guint) route_metric,
 	                       NM_DHCP_CLIENT_TIMEOUT, (guint) timeout,
@@ -382,6 +384,7 @@ nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
                            const char *dhcp_hostname,
                            const char *dhcp_fqdn,
                            NMDhcpHostnameFlags hostname_flags,
+                           const char *mud_url,
                            GBytes *dhcp_client_id,
                            guint32 timeout,
                            const char *dhcp_anycast_addr,
@@ -440,6 +443,7 @@ nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
 	                     hostname,
 	                     use_fqdn,
 	                     hostname_flags,
+	                     mud_url,
 	                     FALSE,
 	                     0,
 	                     last_ip_address,
@@ -462,6 +466,7 @@ nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
                            gboolean send_hostname,
                            const char *dhcp_hostname,
                            NMDhcpHostnameFlags hostname_flags,
+                           const char *mud_url,
                            GBytes *duid,
                            gboolean enforce_duid,
                            guint32 iaid,
@@ -503,6 +508,7 @@ nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
 	                     hostname,
 	                     TRUE,
 	                     hostname_flags,
+	                     mud_url,
 	                     info_only,
 	                     privacy,
 	                     NULL,
diff --git a/src/dhcp/nm-dhcp-manager.h b/src/dhcp/nm-dhcp-manager.h
index 1b793c22..88435282 100644
--- a/src/dhcp/nm-dhcp-manager.h
+++ b/src/dhcp/nm-dhcp-manager.h
@@ -43,6 +43,7 @@ NMDhcpClient * nm_dhcp_manager_start_ip4     (NMDhcpManager *manager,
                                               const char *dhcp_hostname,
                                               const char *dhcp_fqdn,
                                               NMDhcpHostnameFlags hostname_flags,
+                                              const char *mud_url,
                                               GBytes *dhcp_client_id,
                                               guint32 timeout,
                                               const char *dhcp_anycast_addr,
@@ -62,6 +63,7 @@ NMDhcpClient * nm_dhcp_manager_start_ip6     (NMDhcpManager *manager,
                                               gboolean send_hostname,
                                               const char *dhcp_hostname,
                                               NMDhcpHostnameFlags hostname_flags,
+                                              const char *mud_url,
                                               GBytes *duid,
                                               gboolean enforce_duid,
                                               guint32 iaid,
diff --git a/src/dhcp/nm-dhcp-nettools.c b/src/dhcp/nm-dhcp-nettools.c
index 00b416da..fe6a4582 100644
--- a/src/dhcp/nm-dhcp-nettools.c
+++ b/src/dhcp/nm-dhcp-nettools.c
@@ -68,6 +68,20 @@ G_DEFINE_TYPE (NMDhcpNettools, nm_dhcp_nettools, NM_TYPE_DHCP_CLIENT)
 
 /*****************************************************************************/
 
+static void
+set_error_nettools (GError **error, int r, const char *message)
+{
+	/* the error code returned from n_dhcp4_* API is either a negative
+	 * errno, or a positive internal error code. Generate different messages
+	 * for these. */
+	if (r < 0)
+		nm_utils_error_set_errno (error, r, "%s: %s", message);
+	else
+		nm_utils_error_set (error, r, "%s (code %d)", message, r);
+}
+
+/*****************************************************************************/
+
 #define DHCP_MAX_FQDN_LENGTH 255
 
 enum {
@@ -681,11 +695,10 @@ lease_parse_metered (NDhcp4ClientLease *lease,
 	int r;
 
 	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_VENDOR_SPECIFIC, &data, &n_data);
-	if (r) {
+	if (r)
 		metered = FALSE;
-	} else {
+	else
 		metered = !!memmem (data, n_data, "ANDROID_METERED", NM_STRLEN ("ANDROID_METERED"));
-	}
 
 	/* TODO: expose the vendor specific option when present */
 	nm_ip4_config_set_metered (ip4_config, metered);
@@ -1023,9 +1036,8 @@ dhcp4_event_handle (NMDhcpNettools *self,
 	case N_DHCP4_CLIENT_EVENT_OFFER:
 		/* always accept the first lease */
 		r = n_dhcp4_client_lease_select (event->offer.lease);
-		if (r) {
+		if (r)
 			_LOGW ("selecting lease failed: %d", r);
-		}
 		break;
 	case N_DHCP4_CLIENT_EVENT_RETRACTED:
 	case N_DHCP4_CLIENT_EVENT_EXPIRED:
@@ -1044,6 +1056,18 @@ dhcp4_event_handle (NMDhcpNettools *self,
 	case N_DHCP4_CLIENT_EVENT_DOWN:
 		/* ignore down events, they are purely informational */
 		break;
+	case N_DHCP4_CLIENT_EVENT_LOG: {
+			NMLogLevel nm_level;
+
+			nm_level = nm_log_level_from_syslog (event->log.level);
+			if (nm_logging_enabled (nm_level, LOGD_DHCP4)) {
+				nm_log (nm_level, LOGD_DHCP4, NULL , NULL,
+				        "dhcp4 (%s): %s",
+				        nm_dhcp_client_get_iface (NM_DHCP_CLIENT (self)),
+				        event->log.message);
+			}
+		}
+		break;
 	default:
 		_LOGW ("unhandled DHCP event %d", event->event);
 		break;
@@ -1084,27 +1108,6 @@ dhcp4_event_cb (int fd,
 	return G_SOURCE_CONTINUE;
 }
 
-G_GNUC_PRINTF (3, 4)
-static void
-nettools_log (int level, void *data, const char *fmt, ...)
-{
-	NMDhcpNettools *self = data;
-	NMLogLevel nm_level;
-	gs_free char *msg = NULL;
-	va_list ap;
-
-	nm_level = nm_log_level_from_syslog (level);
-	if (nm_logging_enabled (nm_level, LOGD_DHCP4)) {
-		va_start (ap, fmt);
-		msg = g_strdup_vprintf (fmt, ap);
-		va_end (ap);
-		nm_log (nm_level, LOGD_DHCP4, NULL , NULL,
-		        "dhcp4 (%s): %s",
-		        nm_dhcp_client_get_iface (NM_DHCP_CLIENT (self)),
-		        msg);
-	}
-}
-
 static gboolean
 nettools_create (NMDhcpNettools *self,
                  const char *dhcp_anycast_addr,
@@ -1170,12 +1173,10 @@ nettools_create (NMDhcpNettools *self,
 
 	r = n_dhcp4_client_config_new (&config);
 	if (r) {
-		nm_utils_error_set_errno (error, r, "failed to create client-config: %s");
+		set_error_nettools (error, r, "failed to create client-config");
 		return FALSE;
 	}
 
-	n_dhcp4_client_config_set_log_level (config, nm_log_level_to_syslog (nm_logging_get_level (LOGD_DHCP4)));
-	n_dhcp4_client_config_set_log_func (config, nettools_log, self);
 	n_dhcp4_client_config_set_ifindex (config, nm_dhcp_client_get_ifindex (NM_DHCP_CLIENT (self)));
 	n_dhcp4_client_config_set_transport (config, transport);
 	n_dhcp4_client_config_set_mac (config, hwaddr_arr, hwaddr_len);
@@ -1184,19 +1185,21 @@ nettools_create (NMDhcpNettools *self,
 	                                         client_id_arr,
 	                                         NM_MIN (client_id_len, 1 + _NM_SD_MAX_CLIENT_ID_LEN));
 	if (r) {
-		nm_utils_error_set_errno (error, r, "failed to set client-id: %s");
+		set_error_nettools (error, r, "failed to set client-id");
 		return FALSE;
 	}
 
 	r = n_dhcp4_client_new (&client, config);
 	if (r) {
-		nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, "failed to create client: error %d", r);
+		set_error_nettools (error, r, "failed to create client");
 		return FALSE;
 	}
 
 	priv->client = client;
 	client = NULL;
 
+	n_dhcp4_client_set_log_level (priv->client, nm_log_level_to_syslog (nm_logging_get_level (LOGD_DHCP4)));
+
 	n_dhcp4_client_get_fd (priv->client, &fd);
 
 	priv->event_source = nm_g_unix_fd_source_new (fd,
@@ -1224,7 +1227,7 @@ _accept (NMDhcpClient *client,
 
 	r = n_dhcp4_client_lease_accept (priv->lease);
 	if (r) {
-		nm_utils_error_set_errno (error, r, "failed to accept lease: %s");
+		set_error_nettools (error, r, "failed to accept lease");
 		return FALSE;
 	}
 
@@ -1248,7 +1251,7 @@ decline (NMDhcpClient *client,
 
 	r = n_dhcp4_client_lease_decline (priv->lease, error_message);
 	if (r) {
-		nm_utils_error_set_errno (error, r, "failed to decline lease: %s");
+		set_error_nettools (error, r, "failed to decline lease");
 		return FALSE;
 	}
 
@@ -1285,6 +1288,7 @@ ip4_start (NMDhcpClient *client,
 	gs_free char *lease_file = NULL;
 	struct in_addr last_addr = { 0 };
 	const char *hostname;
+	const char *mud_url;
 	int r, i;
 
 	g_return_val_if_fail (!priv->probe, FALSE);
@@ -1294,7 +1298,7 @@ ip4_start (NMDhcpClient *client,
 
 	r = n_dhcp4_client_probe_config_new (&config);
 	if (r) {
-		nm_utils_error_set_errno (error, r, "failed to create dhcp-client-probe-config: %s");
+		set_error_nettools (error, r, "failed to create dhcp-client-probe-config");
 		return FALSE;
 	}
 
@@ -1341,6 +1345,17 @@ ip4_start (NMDhcpClient *client,
 		}
 	}
 
+	mud_url = nm_dhcp_client_get_mud_url (client);
+	if (mud_url) {
+		r = n_dhcp4_client_probe_config_append_option (config,
+		                                               NM_DHCP_OPTION_DHCP4_MUD_URL,
+		                                               mud_url,
+		                                               strlen (mud_url));
+		if (r) {
+			set_error_nettools (error, r, "failed to set MUD URL");
+			return FALSE;
+		}
+	}
 	hostname = nm_dhcp_client_get_hostname (client);
 	if (hostname) {
 		if (nm_dhcp_client_get_use_fqdn (client)) {
@@ -1359,7 +1374,10 @@ ip4_start (NMDhcpClient *client,
 				                                   sizeof (buffer) - 3,
 				                                   FALSE);
 				if (r <= 0) {
-					nm_utils_error_set_errno (error, r, "failed to convert DHCP FQDN: %s");
+					if (r < 0)
+						nm_utils_error_set_errno (error, r, "failed to convert DHCP FQDN: %s");
+					else
+						nm_utils_error_set (error, r, "failed to convert DHCP FQDN");
 					return FALSE;
 				}
 				fqdn_len = r;
@@ -1377,7 +1395,7 @@ ip4_start (NMDhcpClient *client,
 			                                               buffer,
 			                                               3 + fqdn_len);
 			if (r) {
-				nm_utils_error_set_errno (error, r, "failed to set DHCP FQDN: %s");
+				set_error_nettools (error, r, "failed to set DHCP FQDN");
 				return FALSE;
 			}
 		} else {
@@ -1386,7 +1404,7 @@ ip4_start (NMDhcpClient *client,
 			                                               hostname,
 			                                               strlen (hostname));
 			if (r) {
-				nm_utils_error_set_errno (error, r, "failed to set DHCP hostname: %s");
+				set_error_nettools (error, r, "failed to set DHCP hostname");
 				return FALSE;
 			}
 		}
@@ -1397,7 +1415,7 @@ ip4_start (NMDhcpClient *client,
 
 	r = n_dhcp4_client_probe (priv->client, &priv->probe, config);
 	if (r) {
-		nm_utils_error_set_errno (error, r, "failed to start DHCP client: %s");
+		set_error_nettools (error, r, "failed to start DHCP client");
 		return FALSE;
 	}
 
@@ -1434,7 +1452,7 @@ dispose (GObject *object)
 {
 	NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE (object);
 
-	nm_clear_pointer (&priv->lease_file, g_free);
+	nm_clear_g_free (&priv->lease_file);
 	nm_clear_g_source_inst (&priv->event_source);
 	nm_clear_pointer (&priv->lease, n_dhcp4_client_lease_unref);
 	nm_clear_pointer (&priv->probe, n_dhcp4_client_probe_free);
diff --git a/src/dhcp/nm-dhcp-options.c b/src/dhcp/nm-dhcp-options.c
index 1d391f3e..b10635fc 100644
--- a/src/dhcp/nm-dhcp-options.c
+++ b/src/dhcp/nm-dhcp-options.c
@@ -126,6 +126,7 @@ const NMDhcpOption _nm_dhcp_option_dhcp4_options[] = {
 	REQ (NM_DHCP_OPTION_DHCP4_TFTP_SERVER_ADDRESS,               "tftp_server_address",             FALSE ),
 	REQ (NM_DHCP_OPTION_DHCP4_V4_PORTPARAMS,                     "v4_portparams",                   FALSE ),
 	REQ (NM_DHCP_OPTION_DHCP4_V4_CAPTIVE_PORTAL,                 "v4_captive_portal",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_MUD_URL,                           "mud_url",                         FALSE ),
 	REQ (NM_DHCP_OPTION_DHCP4_LOADER_CONFIGFILE,                 "loader_configfile",               FALSE ),
 	REQ (NM_DHCP_OPTION_DHCP4_LOADER_PATHPREFIX,                 "loader_pathprefix",               FALSE ),
 	REQ (NM_DHCP_OPTION_DHCP4_LOADER_REBOOTTIME,                 "loader_reboottime",               FALSE ),
@@ -182,6 +183,7 @@ const NMDhcpOption _nm_dhcp_option_dhcp6_options[] = {
 	REQ (NM_DHCP_OPTION_DHCP6_DNS_SERVERS,                      "dhcp6_name_servers",  TRUE ),
 	REQ (NM_DHCP_OPTION_DHCP6_DOMAIN_LIST,                      "dhcp6_domain_search", TRUE ),
 	REQ (NM_DHCP_OPTION_DHCP6_SNTP_SERVERS,                     "dhcp6_sntp_servers",  TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP6_MUD_URL,                          "dhcp6_mud_url",       FALSE ),
 
 	/* Internal values */
 	REQ (NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS,                    "ip6_address",         FALSE ),
diff --git a/src/dhcp/nm-dhcp-options.h b/src/dhcp/nm-dhcp-options.h
index bf9ccd57..7c012170 100644
--- a/src/dhcp/nm-dhcp-options.h
+++ b/src/dhcp/nm-dhcp-options.h
@@ -110,6 +110,7 @@ typedef enum {
 	NM_DHCP_OPTION_DHCP4_TFTP_SERVER_ADDRESS            = 150,
 	NM_DHCP_OPTION_DHCP4_V4_PORTPARAMS                  = 159,
 	NM_DHCP_OPTION_DHCP4_V4_CAPTIVE_PORTAL              = 160,
+	NM_DHCP_OPTION_DHCP4_MUD_URL                        = 161,
 	NM_DHCP_OPTION_DHCP4_LOADER_CONFIGFILE              = 209,
 	NM_DHCP_OPTION_DHCP4_LOADER_PATHPREFIX              = 210,
 	NM_DHCP_OPTION_DHCP4_LOADER_REBOOTTIME              = 211,
@@ -159,6 +160,7 @@ typedef enum {
 	NM_DHCP_OPTION_DHCP6_DNS_SERVERS       = 23,
 	NM_DHCP_OPTION_DHCP6_DOMAIN_LIST       = 24,
 	NM_DHCP_OPTION_DHCP6_SNTP_SERVERS      = 31,
+	NM_DHCP_OPTION_DHCP6_MUD_URL           = 112,
 	/* Internal values */
 	NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS     = 1026,
 	NM_DHCP_OPTION_DHCP6_NM_PREFIXLEN      = 1027,
diff --git a/src/dhcp/nm-dhcp-systemd.c b/src/dhcp/nm-dhcp-systemd.c
index 23862e9e..f65937d8 100644
--- a/src/dhcp/nm-dhcp-systemd.c
+++ b/src/dhcp/nm-dhcp-systemd.c
@@ -573,6 +573,7 @@ ip4_start (NMDhcpClient *client,
 	size_t client_id_len;
 	struct in_addr last_addr = { 0 };
 	const char *hostname;
+	const char *mud_url;
 	int r, i;
 
 	g_return_val_if_fail (!priv->client4, FALSE);
@@ -688,6 +689,15 @@ ip4_start (NMDhcpClient *client,
 		}
 	}
 
+	mud_url = nm_dhcp_client_get_mud_url (client);
+	if (mud_url) {
+		r = sd_dhcp_client_set_mud_url (sd_client, mud_url);
+		if (r < 0) {
+			nm_utils_error_set_errno (error, r, "failed to set DHCP MUDURL: %s");
+			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");
@@ -725,7 +735,8 @@ lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
 {
 	gs_unref_object NMIP6Config *ip6_config = NULL;
 	gs_unref_hashtable GHashTable *options = NULL;
-	struct in6_addr tmp_addr, *dns;
+	struct in6_addr tmp_addr;
+	const struct in6_addr *dns;
 	uint32_t lft_pref, lft_valid;
 	char addr_str[NM_UTILS_INET_ADDRSTRLEN];
 	char **domains;
@@ -894,6 +905,7 @@ ip6_start (NMDhcpClient *client,
 	nm_auto (sd_dhcp6_client_unrefp) sd_dhcp6_client *sd_client = NULL;
 	GBytes *hwaddr;
 	const char *hostname;
+	const char *mud_url;
 	int r, i;
 	const guint8 *duid_arr;
 	gsize duid_len;
@@ -976,6 +988,15 @@ ip6_start (NMDhcpClient *client,
 		}
 	}
 
+	mud_url = nm_dhcp_client_get_mud_url (client);
+	if (mud_url) {
+		r = sd_dhcp6_client_set_request_mud_url (sd_client, mud_url);
+		if (r < 0) {
+			nm_utils_error_set_errno (error, r, "failed to set mud-url: %s");
+			return FALSE;
+		}
+	}
+
 	if (needed_prefixes > 0) {
 		if (needed_prefixes > 1)
 			_LOGW ("dhcp-client6: only one prefix request is supported");
diff --git a/src/dhcp/tests/test-dhcp-dhclient.c b/src/dhcp/tests/test-dhcp-dhclient.c
index 761f9cdb..d51bcae5 100644
--- a/src/dhcp/tests/test-dhcp-dhclient.c
+++ b/src/dhcp/tests/test-dhcp-dhclient.c
@@ -21,6 +21,7 @@
 #include "nm-test-utils-core.h"
 
 #define TEST_DIR             NM_BUILD_SRCDIR"/src/dhcp/tests"
+#define TEST_MUDURL          "https://example.com/mud.json"
 
 static void
 test_config (const char *orig,
@@ -33,7 +34,8 @@ test_config (const char *orig,
              const char *dhcp_client_id,
              GBytes *expected_new_client_id,
              const char *iface,
-             const char *anycast_addr)
+             const char *anycast_addr,
+             const char *mud_url)
 {
 	gs_free char *new = NULL;
 	gs_unref_bytes GBytes *client_id = NULL;
@@ -52,6 +54,7 @@ test_config (const char *orig,
 	                                      timeout,
 	                                      use_fqdn,
 	                                      hostname_flags,
+	                                      mud_url,
 	                                      "/path/to/dhclient.conf",
 	                                      orig,
 	                                      &new_client_id);
@@ -100,7 +103,37 @@ test_orig_missing (void)
 	             orig_missing_expected,
 	             AF_INET, NULL, 0, FALSE,
 	             NM_DHCP_HOSTNAME_FLAG_NONE,
-	             NULL, NULL, "eth0", NULL);
+	             NULL, NULL, "eth0", NULL, NULL);
+}
+
+/*****************************************************************************/
+
+
+static const char *orig_missing_add_mud_url_expected = \
+	"# Created by NetworkManager\n"
+	"\n"
+	"option mudurl code 161 = text;\n"
+	"send mudurl \"https://example.com/mud.json\";\n\n"
+	"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
+	"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
+	"option wpad code 252 = string;\n"
+	"\n"
+	"also request rfc3442-classless-static-routes;\n"
+	"also request ms-classless-static-routes;\n"
+	"also request static-routes;\n"
+	"also request wpad;\n"
+	"also request ntp-servers;\n"
+	"also request root-path;\n"
+	"\n";
+
+static void
+test_orig_missing_add_mud_url (void)
+{
+	test_config (NULL,
+	             orig_missing_add_mud_url_expected,
+	             AF_INET, NULL, 0, FALSE,
+	             NM_DHCP_HOSTNAME_FLAG_NONE,
+	             NULL, NULL, "eth0", NULL, TEST_MUDURL);
 }
 
 /*****************************************************************************/
@@ -135,6 +168,7 @@ test_override_client_id (void)
 	             "11:22:33:44:55:66",
 	             NULL,
 	             "eth0",
+	             NULL,
 	             NULL);
 }
 
@@ -166,6 +200,7 @@ test_quote_client_id (void)
 	             "abcd",
 	             NULL,
 	             "eth0",
+	             NULL,
 	             NULL);
 }
 
@@ -197,6 +232,7 @@ test_quote_client_id_2 (void)
 	             "a\\bc",
 	             NULL,
 	             "eth0",
+	             NULL,
 	             NULL);
 }
 
@@ -228,6 +264,7 @@ test_hex_zero_client_id (void)
 	             "00:11:22:33",
 	             NULL,
 	             "eth0",
+	             NULL,
 	             NULL);
 }
 
@@ -259,6 +296,7 @@ test_ascii_client_id (void)
 	             "qb:cd:ef:12:34:56",
 	             NULL,
 	             "eth0",
+	             NULL,
 	             NULL);
 }
 
@@ -290,6 +328,7 @@ test_hex_single_client_id (void)
 	             "ab:cd:e:12:34:56",
 	             NULL,
 	             "eth0",
+	             NULL,
 	             NULL);
 }
 
@@ -329,6 +368,7 @@ test_existing_hex_client_id (void)
 	             NULL,
 	             new_client_id,
 	             "eth0",
+	             NULL,
 	             NULL);
 }
 
@@ -367,6 +407,7 @@ test_existing_escaped_client_id (void)
 	             NULL,
 	             new_client_id,
 	             "eth0",
+	             NULL,
 	             NULL);
 }
 
@@ -409,6 +450,7 @@ test_existing_ascii_client_id (void)
 	             NULL,
 	             new_client_id,
 	             "eth0",
+	             NULL,
 	             NULL);
 }
 /*****************************************************************************/
@@ -443,6 +485,7 @@ test_fqdn (void)
 	             NULL,
 	             NULL,
 	             "eth0",
+	             NULL,
 	             NULL);
 }
 
@@ -486,6 +529,7 @@ test_fqdn_options_override (void)
 	             TRUE, NULL,
 	             NULL,
 	             "eth0",
+	             NULL,
 	             NULL);
 }
 
@@ -521,6 +565,7 @@ test_override_hostname (void)
 	             NULL,
 	             NULL,
 	             "eth0",
+	             NULL,
 	             NULL);
 }
 
@@ -550,6 +595,7 @@ test_override_hostname6 (void)
 	             NULL,
 	             NULL,
 	             "eth0",
+	             NULL,
 	             NULL);
 }
 
@@ -576,6 +622,7 @@ test_nonfqdn_hostname6 (void)
 	             NULL,
 	             NULL,
 	             "eth0",
+	             NULL,
 	             NULL);
 }
 
@@ -613,6 +660,7 @@ test_existing_alsoreq (void)
 	             NULL,
 	             NULL,
 	             "eth0",
+	             NULL,
 	             NULL);
 }
 
@@ -653,6 +701,7 @@ test_existing_req (void)
 	             NULL,
 	             NULL,
 	             "eth0",
+	             NULL,
 	             NULL);
 }
 
@@ -694,6 +743,7 @@ test_existing_multiline_alsoreq (void)
 	             NULL,
 	             NULL,
 	             "eth0",
+	             NULL,
 	             NULL);
 }
 
@@ -934,6 +984,7 @@ test_interface1 (void)
 	             NULL,
 	             NULL,
 	             "eth0",
+	             NULL,
 	             NULL);
 }
 
@@ -981,6 +1032,7 @@ test_interface2 (void)
 	             NULL,
 	             NULL,
 	             "eth1",
+	             NULL,
 	             NULL);
 }
 
@@ -1093,6 +1145,7 @@ test_structured (void)
 	             NULL,
 	             new_client_id,
 	             "eth0",
+	             NULL,
 	             NULL);
 }
 
@@ -1149,6 +1202,7 @@ test_config_req_intf (void)
 	             NULL,
 	             NULL,
 	             "eth0",
+	             NULL,
 	             NULL);
 }
 
@@ -1162,6 +1216,7 @@ main (int argc, char **argv)
 	nmtst_init_with_logging (&argc, &argv, NULL, "DEFAULT");
 
 	g_test_add_func ("/dhcp/dhclient/orig_missing", test_orig_missing);
+	g_test_add_func ("/dhcp/dhclient/orig_missing_add_mud_url", test_orig_missing_add_mud_url);
 	g_test_add_func ("/dhcp/dhclient/override_client_id", test_override_client_id);
 	g_test_add_func ("/dhcp/dhclient/quote_client_id/1", test_quote_client_id);
 	g_test_add_func ("/dhcp/dhclient/quote_client_id/2", test_quote_client_id_2);
diff --git a/src/initrd/nm-initrd-generator.c b/src/initrd/nm-initrd-generator.c
index dccb21f8..ac81dde0 100644
--- a/src/initrd/nm-initrd-generator.c
+++ b/src/initrd/nm-initrd-generator.c
@@ -33,7 +33,7 @@ output_conn (gpointer key, gpointer value, gpointer user_data)
 	if (!nm_connection_normalize (connection, NULL, NULL, &error))
 		goto err_out;
 
-	file = nm_keyfile_write (connection, NULL, NULL, &error);
+	file = nm_keyfile_write (connection, NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, &error);
 	if (file == NULL)
 		goto err_out;
 
@@ -64,24 +64,28 @@ err_out:
 }
 
 #define DEFAULT_SYSFS_DIR        "/sys"
+#define DEFAULT_INITRD_DATA_DIR  NMRUNDIR "/initrd"
 
 int
 main (int argc, char *argv[])
 {
 	GHashTable *connections;
 	gs_free char *connections_dir = NULL;
+	gs_free char *initrd_dir = NULL;
 	gs_free char *sysfs_dir = NULL;
 	gboolean dump_to_stdout = FALSE;
 	gs_strfreev char **remaining = NULL;
 	GOptionEntry option_entries[] = {
-		{ "connections-dir", 'c', 0, G_OPTION_ARG_FILENAME, &connections_dir, "Output connection directory", NM_KEYFILE_PATH_NAME_RUN },
-		{ "sysfs-dir", 'd', 0, G_OPTION_ARG_FILENAME, &sysfs_dir, "The sysfs mount point", DEFAULT_SYSFS_DIR },
-		{ "stdout", 's', 0, G_OPTION_ARG_NONE, &dump_to_stdout, "Dump connections to standard output", NULL },
-		{ G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_STRING_ARRAY, &remaining, NULL, NULL },
+		{ "connections-dir",  'c',  0, G_OPTION_ARG_FILENAME,     &connections_dir, "Output connection directory",         NM_KEYFILE_PATH_NAME_RUN },
+		{ "initrd-data-dir",  'i',  0, G_OPTION_ARG_FILENAME,     &initrd_dir,      "Output initrd data directory",        DEFAULT_INITRD_DATA_DIR },
+		{ "sysfs-dir",        'd',  0, G_OPTION_ARG_FILENAME,     &sysfs_dir,       "The sysfs mount point",               DEFAULT_SYSFS_DIR },
+		{ "stdout",           's',  0, G_OPTION_ARG_NONE,         &dump_to_stdout,  "Dump connections to standard output", NULL },
+		{ G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_STRING_ARRAY, &remaining,       NULL,                                  NULL },
 		{ NULL }
 	};
 	GOptionContext *option_context;
-	GError *error = NULL;
+	gs_free_error GError *error = NULL;
+	gs_free char *hostname = NULL;
 	int errsv;
 
 	option_context = g_option_context_new ("-- [ip=...] [rd.route=...] [bridge=...] [bond=...] [team=...] [vlan=...] "
@@ -108,6 +112,8 @@ main (int argc, char *argv[])
 		connections_dir = g_strdup (NM_KEYFILE_PATH_NAME_RUN);
 	if (!sysfs_dir)
 		sysfs_dir = g_strdup (DEFAULT_SYSFS_DIR);
+	if (!initrd_dir)
+		initrd_dir = g_strdup (DEFAULT_INITRD_DATA_DIR);
 	if (dump_to_stdout)
 		nm_clear_g_free (&connections_dir);
 
@@ -117,9 +123,31 @@ main (int argc, char *argv[])
 		return 1;
 	}
 
-	connections = nmi_cmdline_reader_parse (sysfs_dir, (const char *const*) remaining);
+	connections = nmi_cmdline_reader_parse (sysfs_dir,
+	                                        (const char *const*) remaining,
+	                                        &hostname);
+
 	g_hash_table_foreach (connections, output_conn, connections_dir);
 	g_hash_table_destroy (connections);
 
+	if (g_mkdir_with_parents (initrd_dir, 0755) != 0) {
+		errsv = errno;
+		_LOGW (LOGD_CORE, "%s: %s", initrd_dir, nm_strerror_native (errsv));
+		return 1;
+	}
+
+	if (hostname) {
+		gs_free char *hostname_file = NULL;
+		gs_free char *data = NULL;
+
+		hostname_file = g_strdup_printf ("%s/hostname", initrd_dir);
+		data = g_strdup_printf ("%s\n", hostname);
+
+		if (!g_file_set_contents (hostname_file, data, strlen (data), &error)) {
+			_LOGW (LOGD_CORE, "%s: %s", hostname_file, error->message);
+			return 1;
+		}
+	}
+
 	return 0;
 }
diff --git a/src/initrd/nm-initrd-generator.h b/src/initrd/nm-initrd-generator.h
index cba383d1..97199921 100644
--- a/src/initrd/nm-initrd-generator.h
+++ b/src/initrd/nm-initrd-generator.h
@@ -28,6 +28,6 @@ gboolean nmi_ibft_update_connection_from_nic (NMConnection *connection, GHashTab
 
 NMConnection *nmi_dt_reader_parse (const char *sysfs_dir);
 
-GHashTable *nmi_cmdline_reader_parse (const char *sysfs_dir, const char *const*argv);
+GHashTable *nmi_cmdline_reader_parse (const char *sysfs_dir, const char *const*argv, char **hostname);
 
 #endif  /* __NM_INITRD_GENERATOR_H__ */
diff --git a/src/initrd/nmi-cmdline-reader.c b/src/initrd/nmi-cmdline-reader.c
index 2eb6d7e5..7513b4c9 100644
--- a/src/initrd/nmi-cmdline-reader.c
+++ b/src/initrd/nmi-cmdline-reader.c
@@ -7,6 +7,7 @@
 
 #include "nm-core-internal.h"
 #include "nm-initrd-generator.h"
+#include "systemd/nm-sd-utils-shared.h"
 
 /*****************************************************************************/
 
@@ -22,6 +23,11 @@ typedef struct {
 	GPtrArray *array;
 	NMConnection *bootdev_connection;   /* connection for bootdev=$ifname */
 	NMConnection *default_connection;   /* connection not bound to any ifname */
+	char *hostname;
+
+	/* Parameters to be set for all connections */
+	gboolean ignore_auto_dns;
+	int dhcp_timeout;
 } Reader;
 
 static Reader *
@@ -45,6 +51,7 @@ reader_destroy (Reader *reader, gboolean free_hash)
 
 	g_ptr_array_unref (reader->array);
 	hash = g_steal_pointer (&reader->hash);
+	nm_clear_g_free (&reader->hostname);
 	nm_g_slice_free (reader);
 	if (!free_hash)
 		return g_steal_pointer (&hash);
@@ -86,6 +93,8 @@ reader_create_connection (Reader *reader,
 	g_object_set (setting,
 	              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO,
 	              NM_SETTING_IP_CONFIG_MAY_FAIL, TRUE,
+	              NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, reader->ignore_auto_dns,
+	              NM_SETTING_IP_CONFIG_DHCP_TIMEOUT, reader->dhcp_timeout,
 	              NULL);
 
 	setting = nm_setting_ip6_config_new ();
@@ -94,6 +103,8 @@ reader_create_connection (Reader *reader,
 	              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO,
 	              NM_SETTING_IP_CONFIG_MAY_FAIL, TRUE,
 	              NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE, (int) NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64,
+	              NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, reader->ignore_auto_dns,
+	              NM_SETTING_IP_CONFIG_DHCP_TIMEOUT, reader->dhcp_timeout,
 	              NULL);
 
 	setting = nm_setting_connection_new ();
@@ -345,6 +356,14 @@ reader_parse_ip (Reader *reader, const char *sysfs_dir, char *argument)
 			ifname = tmp;
 		}
 
+		if (client_hostname && !nm_sd_hostname_is_valid (client_hostname, FALSE))
+			client_hostname = NULL;
+
+		if (client_hostname) {
+			g_free (reader->hostname);
+			reader->hostname = g_strdup (client_hostname);
+		}
+
 		/* <ifname>:{none|off|dhcp|on|any|dhcp6|auto6|ibft} */
 
 		kind = get_word (&argument, ':');
@@ -363,8 +382,8 @@ reader_parse_ip (Reader *reader, const char *sysfs_dir, char *argument)
 		}
 	}
 
-	if (ifname == NULL && (   g_strcmp0 (kind, "fw") == 0
-	                       || g_strcmp0 (kind, "ibft") == 0)) {
+	if (   ifname == NULL
+	    && NM_IN_STRSET (kind, "fw", "ibft")) {
 		reader_read_all_connections_from_fw (reader, sysfs_dir);
 		return;
 	}
@@ -442,7 +461,7 @@ reader_parse_ip (Reader *reader, const char *sysfs_dir, char *argument)
 	}
 
 	/* Dynamic IP configuration configured explicitly. */
-	if (g_strcmp0 (kind, "none") == 0 || (g_strcmp0 (kind, "off") == 0)) {
+	if (NM_IN_STRSET (kind, "none", "off")) {
 		if (nm_setting_ip_config_get_num_addresses (s_ip6) == 0) {
 			g_object_set (s_ip6,
 			              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_DISABLED,
@@ -453,7 +472,7 @@ reader_parse_ip (Reader *reader, const char *sysfs_dir, char *argument)
 			              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
 			              NULL);
 		}
-	} else if (g_strcmp0 (kind, "dhcp") == 0) {
+	} else if (nm_streq0 (kind, "dhcp")) {
 		g_object_set (s_ip4,
 		              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO,
 		              NM_SETTING_IP_CONFIG_MAY_FAIL, FALSE,
@@ -463,7 +482,7 @@ reader_parse_ip (Reader *reader, const char *sysfs_dir, char *argument)
 			              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
 			              NULL);
 		}
-	} else if (g_strcmp0 (kind, "dhcp6") == 0) {
+	} else if (nm_streq0 (kind, "dhcp6")) {
 		g_object_set (s_ip6,
 		              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_DHCP,
 		              NM_SETTING_IP_CONFIG_MAY_FAIL, FALSE,
@@ -473,7 +492,7 @@ reader_parse_ip (Reader *reader, const char *sysfs_dir, char *argument)
 			              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
 			              NULL);
 		}
-	} else if (g_strcmp0 (kind, "auto6") == 0) {
+	} else if (nm_streq0 (kind, "auto6")) {
 		g_object_set (s_ip4,
 		              NM_SETTING_IP_CONFIG_MAY_FAIL, FALSE,
 		              NULL);
@@ -482,7 +501,7 @@ reader_parse_ip (Reader *reader, const char *sysfs_dir, char *argument)
 			              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
 			              NULL);
 		}
-	} else if (g_strcmp0 (kind, "ibft") == 0) {
+	} else if (nm_streq0 (kind, "ibft")) {
 		gs_free char *address_path = g_build_filename (sysfs_dir, "class", "net", ifname, "address", NULL);
 		gs_free char *mac, *mac_up = NULL;
 		GHashTable *nic = NULL;
@@ -591,7 +610,7 @@ reader_parse_master (Reader *reader,
 	s_con = nm_connection_get_setting_connection (connection);
 	master = nm_setting_connection_get_uuid (s_con);
 
-	if (strcmp (type_name, NM_SETTING_BOND_SETTING_NAME) == 0) {
+	if (nm_streq (type_name, NM_SETTING_BOND_SETTING_NAME)) {
 		s_bond = (NMSettingBond *)nm_connection_get_setting_by_name (connection, type_name);
 
 		opts = get_word (&argument, ':');
@@ -777,7 +796,9 @@ reader_parse_rd_znet (Reader *reader, char *argument, gboolean net_ifnames)
 		ifname = g_strdup_printf ("%s%d", prefix, index);
 	}
 
-	connection = reader_get_connection (reader, ifname, NM_SETTING_WIRED_SETTING_NAME, TRUE);
+	connection = reader_get_connection (reader, ifname, NM_SETTING_WIRED_SETTING_NAME, FALSE);
+	if (!connection)
+		return;
 	s_wired = nm_connection_get_setting_wired (connection);
 	g_object_set (s_wired,
 	              NM_SETTING_WIRED_S390_NETTYPE, nettype,
@@ -808,27 +829,6 @@ _normalize_conn (gpointer key, gpointer value, gpointer user_data)
 }
 
 static void
-reader_set_ignore_auto_dns (Reader *reader)
-{
-	GHashTableIter iter;
-	NMConnection *connection;
-	NMSettingIPConfig *s_ip = NULL;
-
-	g_hash_table_iter_init (&iter, reader->hash);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &connection)) {
-		s_ip = nm_connection_get_setting_ip4_config (connection);
-		g_object_set (s_ip,
-		              NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, TRUE,
-		              NULL);
-
-		s_ip = nm_connection_get_setting_ip6_config (connection);
-		g_object_set (s_ip,
-		              NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, TRUE,
-		              NULL);
-	}
-}
-
-static void
 reader_add_nameservers (Reader *reader, GPtrArray *nameservers)
 {
 	NMConnection *connection;
@@ -875,7 +875,7 @@ reader_add_nameservers (Reader *reader, GPtrArray *nameservers)
 }
 
 GHashTable *
-nmi_cmdline_reader_parse (const char *sysfs_dir, const char *const*argv)
+nmi_cmdline_reader_parse (const char *sysfs_dir, const char *const*argv, char **hostname)
 {
 	Reader *reader;
 	const char *tag;
@@ -886,16 +886,28 @@ nmi_cmdline_reader_parse (const char *sysfs_dir, const char *const*argv)
 	gboolean net_ifnames = TRUE;
 	gs_unref_ptrarray GPtrArray *nameservers = NULL;
 	gs_unref_ptrarray GPtrArray *routes = NULL;
-	gboolean ignore_auto_dns = FALSE;
+	gs_unref_ptrarray GPtrArray *znets = NULL;
 	int i;
 
 	reader = reader_new ();
 
 	for (i = 0; argv[i]; i++) {
-		if (strcmp (argv[i], "net.ifnames=0") == 0)
-			net_ifnames = FALSE;
-		else if (g_str_has_prefix (argv[i], "net.ifnames="))
-			net_ifnames = TRUE;
+		gs_free char *argument_clone = NULL;
+		char *argument;
+
+		argument_clone = g_strdup (argv[i]);
+		argument = argument_clone;
+
+		tag = get_word (&argument, '=');
+
+		if (nm_streq (tag, "net.ifnames"))
+			net_ifnames = !nm_streq (argument, "0");
+		else if (nm_streq (tag, "rd.peerdns"))
+			reader->ignore_auto_dns = !_nm_utils_ascii_str_to_bool (argument, TRUE);
+		else if (nm_streq (tag, "rd.net.timeout.dhcp")) {
+			reader->dhcp_timeout = _nm_utils_ascii_str_to_int64 (argument,
+			                                                     10, 0, G_MAXINT32, 0);
+		}
 	}
 
 	for (i = 0; argv[i]; i++) {
@@ -907,24 +919,24 @@ nmi_cmdline_reader_parse (const char *sysfs_dir, const char *const*argv)
 		argument = argument_clone;
 
 		tag = get_word (&argument, '=');
-		if (strcmp (tag, "ip") == 0)
+		if (nm_streq (tag, "ip"))
 			reader_parse_ip (reader, sysfs_dir, argument);
-		else if (strcmp (tag, "rd.route") == 0) {
+		else if (nm_streq (tag, "rd.route")) {
 			if (!routes)
 				routes = g_ptr_array_new_with_free_func (g_free);
 			g_ptr_array_add (routes, g_strdup (argument));
-		} else if (strcmp (tag, "bridge") == 0)
+		} else if (nm_streq (tag, "bridge"))
 			reader_parse_master (reader, argument, NM_SETTING_BRIDGE_SETTING_NAME, "br");
-		else if (strcmp (tag, "bond") == 0)
+		else if (nm_streq (tag, "bond"))
 			reader_parse_master (reader, argument, NM_SETTING_BOND_SETTING_NAME, NULL);
-		else if (strcmp (tag, "team") == 0)
+		else if (nm_streq (tag, "team"))
 			reader_parse_master (reader, argument, NM_SETTING_TEAM_SETTING_NAME, NULL);
-		else if (strcmp (tag, "vlan") == 0)
+		else if (nm_streq (tag, "vlan"))
 			reader_parse_vlan (reader, argument);
-		else if (strcmp (tag, "bootdev") == 0) {
+		else if (nm_streq (tag, "bootdev")) {
 			g_free (bootdev);
 			bootdev = g_strdup (argument);
-		} else if (strcmp (tag, "nameserver") == 0) {
+		} else if (nm_streq (tag, "nameserver")) {
 			word = get_word (&argument, '\0');
 			if (word) {
 				if (!nameservers)
@@ -933,17 +945,17 @@ nmi_cmdline_reader_parse (const char *sysfs_dir, const char *const*argv)
 			}
 			if (argument && *argument)
 				_LOGW (LOGD_CORE, "Ignoring extra: '%s'.", argument);
-		} else if (strcmp (tag, "rd.peerdns") == 0)
-			ignore_auto_dns = !_nm_utils_ascii_str_to_bool (argument, TRUE);
-		else if (strcmp (tag, "rd.iscsi.ibft") == 0 && _nm_utils_ascii_str_to_bool (argument, TRUE))
+		} else if (nm_streq (tag, "rd.iscsi.ibft") && _nm_utils_ascii_str_to_bool (argument, TRUE))
 			reader_read_all_connections_from_fw (reader, sysfs_dir);
-		else if (strcmp (tag, "rd.bootif") == 0)
+		else if (nm_streq (tag, "rd.bootif"))
 			ignore_bootif = !_nm_utils_ascii_str_to_bool (argument, TRUE);
-		else if (strcmp (tag, "rd.neednet") == 0)
+		else if (nm_streq (tag, "rd.neednet"))
 			neednet = _nm_utils_ascii_str_to_bool (argument, TRUE);
-		else if (strcmp (tag, "rd.znet") == 0)
-			reader_parse_rd_znet (reader, argument, net_ifnames);
-		else if (g_ascii_strcasecmp (tag, "BOOTIF") == 0) {
+		else if (nm_streq (tag, "rd.znet")) {
+			if (!znets)
+				znets = g_ptr_array_new_with_free_func (g_free);
+			g_ptr_array_add (znets, g_strdup (argument));
+		} else if (g_ascii_strcasecmp (tag, "BOOTIF") == 0) {
 			nm_clear_g_free (&bootif_val);
 			bootif_val = g_strdup (argument);
 		}
@@ -1009,9 +1021,14 @@ nmi_cmdline_reader_parse (const char *sysfs_dir, const char *const*argv)
 	if (nameservers)
 		reader_add_nameservers (reader, nameservers);
 
-	if (ignore_auto_dns)
-		reader_set_ignore_auto_dns (reader);
+	if (znets) {
+		for (i = 0; i < znets->len; i++)
+			reader_parse_rd_znet (reader, znets->pdata[i], net_ifnames);
+	}
 
 	g_hash_table_foreach (reader->hash, _normalize_conn, NULL);
+
+	NM_SET_OUT (hostname, g_steal_pointer (&reader->hostname));
+
 	return reader_destroy (reader, FALSE);
 }
diff --git a/src/initrd/nmi-dt-reader.c b/src/initrd/nmi-dt-reader.c
index f118adc3..205cc0b5 100644
--- a/src/initrd/nmi-dt-reader.c
+++ b/src/initrd/nmi-dt-reader.c
@@ -156,7 +156,7 @@ nmi_dt_reader_parse (const char *sysfs_dir)
 
 	local_hwaddr = dt_get_hwaddr_property (base, bootpath, "local-mac-address");
 	hwaddr = dt_get_hwaddr_property (base, bootpath, "mac-address");
-	if (g_strcmp0 (local_hwaddr, hwaddr) == 0)
+	if (nm_streq0 (local_hwaddr, hwaddr))
 		nm_clear_g_free (&local_hwaddr);
 
 	tokens = g_strsplit (path, ",", 0);
@@ -169,10 +169,10 @@ nmi_dt_reader_parse (const char *sysfs_dir)
 
 	for (i = 0; tokens[i]; i++) {
 		/* Skip these. They have magical meaning for OpenFirmware. */
-		if (   strcmp (tokens[i], "nfs") == 0
-		    || strcmp (tokens[i], "last") == 0)
+		if (NM_IN_STRSET (tokens[i], "nfs",
+		                             "last"))
 			continue;
-		if (strcmp (tokens[i], "promiscuous") == 0) {
+		if (nm_streq (tokens[i], "promiscuous")) {
 			/* Ignore. */
 			continue;
 		}
@@ -201,9 +201,9 @@ nmi_dt_reader_parse (const char *sysfs_dir)
 	 */
 
 	for (; tokens[i]; i++) {
-		if (   strcmp (tokens[i], "bootp") == 0
-		    || strcmp (tokens[i], "dhcp") == 0
-		    || strcmp (tokens[i], "rarp") == 0) {
+		if (NM_IN_STRSET (tokens[i], "bootp",
+		                             "dhcp",
+		                             "rarp")) {
 			bootp = TRUE;
 			continue;
 		}
diff --git a/src/initrd/nmi-ibft-reader.c b/src/initrd/nmi-ibft-reader.c
index 477bc0bf..fe6f6432 100644
--- a/src/initrd/nmi-ibft-reader.c
+++ b/src/initrd/nmi-ibft-reader.c
@@ -17,7 +17,6 @@
 #include <unistd.h>
 
 #include "nm-core-internal.h"
-#include "platform/nm-platform.h"
 #include "NetworkManagerUtils.h"
 
 /*****************************************************************************/
@@ -185,12 +184,12 @@ ip_setting_add_from_block (GHashTable *nic,
 		return FALSE;
 	}
 
-	if (   (g_strcmp0 (s_origin, "3") == 0 && family == AF_INET)
-	    || (g_strcmp0 (s_origin, "4") == 0 && family == AF_INET)) {
+	if (   (nm_streq0 (s_origin, "3") && family == AF_INET)
+	    || (nm_streq0 (s_origin, "4") && family == AF_INET)) {
 		method = NM_SETTING_IP4_CONFIG_METHOD_AUTO;
-	} else if (g_strcmp0 (s_origin, "3") == 0 && family == AF_INET6) {
+	} else if (nm_streq0 (s_origin, "3") && family == AF_INET6) {
 		method = NM_SETTING_IP6_CONFIG_METHOD_DHCP;
-	} else if (g_strcmp0 (s_origin, "4") == 0 && family == AF_INET6) {
+	} else if (nm_streq0 (s_origin, "4") && family == AF_INET6) {
 		method = NM_SETTING_IP6_CONFIG_METHOD_AUTO;
 	} else if (family == AF_INET) {
 		method = NM_SETTING_IP4_CONFIG_METHOD_MANUAL;
diff --git a/src/initrd/tests/test-cmdline-reader.c b/src/initrd/tests/test-cmdline-reader.c
index b2b06e27..5379c3a3 100644
--- a/src/initrd/tests/test-cmdline-reader.c
+++ b/src/initrd/tests/test-cmdline-reader.c
@@ -30,10 +30,12 @@ test_auto (void)
 	NMSettingWired *s_wired;
 	NMSettingIPConfig *s_ip4;
 	NMSettingIPConfig *s_ip6;
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 1);
+	g_assert_cmpstr (hostname, ==, NULL);
 
 	connection = g_hash_table_lookup (connections, "default_connection");
 	g_assert (connection);
@@ -81,10 +83,12 @@ test_if_auto_with_mtu (void)
 	NMSettingWired *s_wired;
 	NMSettingIPConfig *s_ip4;
 	NMSettingIPConfig *s_ip6;
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 1);
+	g_assert_cmpstr (hostname, ==, NULL);
 
 	connection = g_hash_table_lookup (connections, "eth0");
 	g_assert (connection);
@@ -115,10 +119,13 @@ test_if_dhcp6 (void)
 	NMConnection *connection;
 	NMSettingIPConfig *s_ip4;
 	NMSettingIPConfig *s_ip6;
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 1);
+	g_assert_cmpstr (hostname, ==, NULL);
+
 	connection = g_hash_table_lookup (connections, "eth1");
 	g_assert (connection);
 	nmtst_assert_connection_verifies_without_normalization (connection);
@@ -145,10 +152,12 @@ test_if_auto_with_mtu_and_mac (void)
 	NMSettingWired *s_wired;
 	NMSettingIPConfig *s_ip4;
 	NMSettingIPConfig *s_ip6;
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 1);
+	g_assert_cmpstr (hostname, ==, NULL);
 
 	connection = g_hash_table_lookup (connections, "eth2");
 	g_assert (connection);
@@ -183,10 +192,12 @@ test_if_ip4_manual (void)
 	NMConnection *connection;
 	NMSettingIPConfig *s_ip4;
 	NMIPAddress *ip_addr;
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 2);
+	g_assert_cmpstr (hostname, ==, "hostname1.example.com");
 
 	connection = g_hash_table_lookup (connections, "eth3");
 	g_assert (connection);
@@ -237,10 +248,12 @@ test_if_ip6_manual (void)
 	NMConnection *connection;
 	NMSettingIPConfig *s_ip6;
 	NMIPAddress *ip_addr;
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 1);
+	g_assert_cmpstr (hostname, ==, "hostname0.example.com");
 
 	connection = g_hash_table_lookup (connections, "eth4");
 	g_assert (connection);
@@ -274,10 +287,12 @@ test_multiple_merge (void)
 	NMSettingIPConfig *s_ip4;
 	NMSettingIPConfig *s_ip6;
 	NMIPAddress *ip_addr;
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 1);
+	g_assert_cmpstr (hostname, ==, NULL);
 
 	connection = g_hash_table_lookup (connections, "eth0");
 	g_assert (connection);
@@ -317,10 +332,12 @@ test_multiple_bootdev (void)
 	NMConnection *connection;
 	NMSettingIPConfig *s_ip4;
 	NMSettingIPConfig *s_ip6;
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 2);
+	g_assert_cmpstr (hostname, ==, NULL);
 
 	connection = g_hash_table_lookup (connections, "eth3");
 	g_assert (connection);
@@ -344,10 +361,12 @@ test_bootdev (void)
 	const char *const*ARGV = NM_MAKE_STRV ("vlan=vlan2:ens5", "bootdev=ens3");
 	NMConnection *connection;
 	NMSettingConnection *s_con;
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 2);
+	g_assert_cmpstr (hostname, ==, NULL);
 
 	connection = g_hash_table_lookup (connections, "ens3");
 	g_assert (connection);
@@ -384,10 +403,12 @@ test_some_more (void)
 	NMSettingIPConfig *s_ip4;
 	NMSettingIPConfig *s_ip6;
 	NMIPRoute *ip_route;
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 2);
+	g_assert_cmpstr (hostname, ==, NULL);
 
 	connection = g_hash_table_lookup (connections, "eth1");
 	g_assert (connection);
@@ -466,10 +487,12 @@ test_bond (void)
 	NMSettingBond *s_bond;
 	NMIPRoute *ip_route;
 	const char *master_uuid;
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 3);
+	g_assert_cmpstr (hostname, ==, NULL);
 
 	connection = g_hash_table_lookup (connections, "bong0");
 	g_assert (connection);
@@ -545,10 +568,12 @@ test_bond_default (void)
 	NMSettingIPConfig *s_ip6;
 	NMSettingBond *s_bond;
 	const char *master_uuid;
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 2);
+	g_assert_cmpstr (hostname, ==, NULL);
 
 	connection = g_hash_table_lookup (connections, "bond0");
 
@@ -598,7 +623,9 @@ static void
 test_bridge (void)
 {
 	gs_unref_hashtable GHashTable *connections = NULL;
-	const char *const*ARGV = NM_MAKE_STRV ("bridge=bridge0:eth0,eth1", "rd.route=192.0.2.53::bridge0");
+	const char *const*ARGV = NM_MAKE_STRV ("bridge=bridge0:eth0,eth1",
+	                                       "rd.route=192.0.2.53::bridge0",
+	                                       "rd.net.timeout.dhcp=10");
 	NMConnection *connection;
 	NMSettingConnection *s_con;
 	NMSettingIPConfig *s_ip4;
@@ -606,10 +633,12 @@ test_bridge (void)
 	NMSettingBridge *s_bridge;
 	NMIPRoute *ip_route;
 	const char *master_uuid;
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 3);
+	g_assert_cmpstr (hostname, ==, NULL);
 
 	connection = g_hash_table_lookup (connections, "bridge0");
 	g_assert (connection);
@@ -626,6 +655,7 @@ test_bridge (void)
 	g_assert_cmpint (nm_setting_ip_config_get_num_dns (s_ip4), ==, 0);
 	g_assert (!nm_setting_ip_config_get_gateway (s_ip4));
 	g_assert_cmpint (nm_setting_ip_config_get_num_routes (s_ip4), ==, 1);
+	g_assert_cmpint (nm_setting_ip_config_get_dhcp_timeout(s_ip4), ==, 10);
 	ip_route = nm_setting_ip_config_get_route (s_ip4, 0);
 	g_assert_cmpstr (nm_ip_route_get_dest (ip_route), ==, "192.0.2.53");
 	g_assert_cmpint (nm_ip_route_get_family (ip_route), ==, AF_INET);
@@ -640,6 +670,8 @@ test_bridge (void)
 	g_assert_cmpint (nm_setting_ip_config_get_num_dns (s_ip6), ==, 0);
 	g_assert (!nm_setting_ip_config_get_gateway (s_ip6));
 	g_assert_cmpint (nm_setting_ip_config_get_num_routes (s_ip6), ==, 0);
+	g_assert_cmpint (nm_setting_ip_config_get_dhcp_timeout(s_ip6), ==, 10);
+
 
 	s_bridge = nm_connection_get_setting_bridge (connection);
 	g_assert (s_bridge);
@@ -682,10 +714,12 @@ test_bridge_default (void)
 	NMSettingIPConfig *s_ip6;
 	NMSettingBridge *s_bridge;
 	const char *master_uuid;
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 2);
+	g_assert_cmpstr (hostname, ==, NULL);
 
 	connection = g_hash_table_lookup (connections, "br0");
 
@@ -740,10 +774,12 @@ test_team (void)
 	NMSettingIPConfig *s_ip6;
 	NMSettingTeam *s_team;
 	const char *master_uuid;
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 3);
+	g_assert_cmpstr (hostname, ==, NULL);
 
 	connection = g_hash_table_lookup (connections, "team0");
 	g_assert (connection);
@@ -806,10 +842,12 @@ test_ibft_ip_dev (void)
 	gs_unref_hashtable GHashTable *connections = NULL;
 	NMSettingConnection *s_con;
 	NMConnection *connection;
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 1);
+	g_assert_cmpstr (hostname, ==, NULL);
 
 	connection = g_hash_table_lookup (connections, "eth0");
 	g_assert (connection);
@@ -825,10 +863,12 @@ _test_ibft_ip (const char *const*ARGV)
 {
 	gs_unref_hashtable GHashTable *connections = NULL;
 	NMConnection *connection;
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 2);
+	g_assert_cmpstr (hostname, ==, NULL);
 
 	connection = g_hash_table_lookup (connections, "ibft0");
 	g_assert (connection);
@@ -864,10 +904,12 @@ test_ignore_extra (void)
 {
 	gs_unref_hashtable GHashTable *connections = NULL;
 	const char *const*ARGV = NM_MAKE_STRV ("blabla", "extra", "lalala");
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 0);
+	g_assert_cmpstr (hostname, ==, NULL);
 }
 
 static void
@@ -875,6 +917,7 @@ test_rd_znet (void)
 {
 	gs_unref_hashtable GHashTable *connections = NULL;
 	const char *const*const ARGV = NM_MAKE_STRV ("ip=10.11.12.13::10.11.12.1:24:foo.example.com:enc800:none",
+	                                             "ip=slc600:dhcp",
 	                                             "rd.znet=qeth,0.0.0800,0.0.0801,0.0.0802,layer2=0,portno=1",
 	                                             "rd.znet=ctc,0.0.0600,0.0.0601,layer2=0,portno=0");
 	NMConnection *connection;
@@ -886,10 +929,12 @@ test_rd_znet (void)
 		{ .name = "portno", .value_str = "1" },
 	};
 	int i_s390_options_keys;
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 2);
+	g_assert_cmpstr (hostname, ==, "foo.example.com");
 
 	connection = g_hash_table_lookup (connections, "enc800");
 	g_assert (NM_IS_CONNECTION (connection));
@@ -960,13 +1005,16 @@ test_rd_znet_legacy (void)
 	const char *const*const ARGV = NM_MAKE_STRV ("ip=10.11.12.13::10.11.12.1:24:foo.example.com:eth0:none",
 	                                             "rd.znet=qeth,0.0.0800,0.0.0801,0.0.0802,layer2=0,portno=1",
 	                                             "rd.znet=ctc,0.0.0600,0.0.0601,layer2=0,portno=0",
+	                                             "ip=ctc0:dhcp",
 	                                             "net.ifnames=0");
 	NMConnection *connection;
 	NMSettingConnection *s_con;
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 2);
+	g_assert_cmpstr (hostname, ==, "foo.example.com");
 
 	connection = g_hash_table_lookup (connections, "eth0");
 	g_assert (NM_IS_CONNECTION (connection));
@@ -992,6 +1040,19 @@ test_rd_znet_legacy (void)
 }
 
 static void
+test_rd_znet_no_ip (void)
+{
+	gs_unref_hashtable GHashTable *connections = NULL;
+	const char *const*const ARGV = NM_MAKE_STRV ("rd.znet=qeth,0.0.0800,0.0.0801,0.0.0802,layer2=0,portno=1");
+	gs_free char *hostname = NULL;
+
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
+	g_assert (connections);
+	g_assert_cmpint (g_hash_table_size (connections), ==, 0);
+	g_assert_cmpstr (hostname, ==, NULL);
+}
+
+static void
 test_bootif (void)
 {
 	gs_unref_hashtable GHashTable *connections = NULL;
@@ -1001,10 +1062,12 @@ test_bootif (void)
 	NMSettingWired *s_wired;
 	NMSettingIPConfig *s_ip4;
 	NMSettingIPConfig *s_ip6;
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 1);
+	g_assert_cmpstr (hostname, ==, NULL);
 
 	connection = g_hash_table_lookup (connections, "default_connection");
 	g_assert (connection);
@@ -1037,10 +1100,12 @@ test_bootif_hwtype (void)
 	NMSettingWired *s_wired;
 	NMSettingIPConfig *s_ip4;
 	NMSettingIPConfig *s_ip6;
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 2);
+	g_assert_cmpstr (hostname, ==, NULL);
 
 	connection = g_hash_table_lookup (connections, "eth0");
 	g_assert (connection);
@@ -1100,10 +1165,12 @@ test_nameserver (void)
 	                                       "nameserver=[2606:4700:4700::1111]");
 	NMConnection *connection;
 	NMSettingIPConfig *s_ip;
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 3);
+	g_assert_cmpstr (hostname, ==, "foo.example.com");
 
 	connection = g_hash_table_lookup (connections, "eth0");
 	g_assert (connection);
@@ -1140,10 +1207,12 @@ test_bootif_off (void)
 {
 	gs_unref_hashtable GHashTable *connections = NULL;
 	const char *const*ARGV = NM_MAKE_STRV ("BOOTIF=01-00-53-AB-cd-02-03", "rd.bootif=0");
+	gs_free char *hostname = NULL;
 
-	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 0);
+	g_assert_cmpstr (hostname, ==, NULL);
 }
 
 NMTST_DEFINE ();
@@ -1174,6 +1243,7 @@ int main (int argc, char **argv)
 	g_test_add_func ("/initrd/cmdline/ignore_extra", test_ignore_extra);
 	g_test_add_func ("/initrd/cmdline/rd_znet", test_rd_znet);
 	g_test_add_func ("/initrd/cmdline/rd_znet/legacy", test_rd_znet_legacy);
+	g_test_add_func ("/initrd/cmdline/rd_znet/no_ip", test_rd_znet_no_ip);
 	g_test_add_func ("/initrd/cmdline/bootif", test_bootif);
 	g_test_add_func ("/initrd/cmdline/bootif/hwtype", test_bootif_hwtype);
 	g_test_add_func ("/initrd/cmdline/bootif/off", test_bootif_off);
diff --git a/src/nm-active-connection.c b/src/nm-active-connection.c
index f1bb1ede..08037ebe 100644
--- a/src/nm-active-connection.c
+++ b/src/nm-active-connection.c
@@ -163,6 +163,7 @@ NM_UTILS_FLAGS2STR_DEFINE (_state_flags_to_string, NMActivationStateFlags,
 	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_IP6_READY,                            "ip6-ready"),
 	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_MASTER_HAS_SLAVES,                    "master-has-slaves"),
 	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY, "lifetime-bound-to-profile-visibility"),
+	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_EXTERNAL,                             "external"),
 );
 
 /*****************************************************************************/
@@ -340,7 +341,12 @@ nm_active_connection_set_state_fail (NMActiveConnection *self,
 NMActivationStateFlags
 nm_active_connection_get_state_flags (NMActiveConnection *self)
 {
-	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->state_flags;
+	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
+
+	return   priv->state_flags
+	       | (  priv->activation_type == NM_ACTIVATION_TYPE_EXTERNAL
+	          ? NM_ACTIVATION_STATE_FLAG_EXTERNAL
+	          : NM_ACTIVATION_STATE_FLAG_NONE);
 }
 
 void
@@ -351,6 +357,8 @@ nm_active_connection_set_state_flags_full (NMActiveConnection *self,
 	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
 	NMActivationStateFlags f;
 
+	nm_assert (!NM_FLAGS_HAS (mask, NM_ACTIVATION_STATE_FLAG_EXTERNAL));
+
 	f = (priv->state_flags & ~mask) | (state_flags & mask);
 	if (f != priv->state_flags) {
 		char buf1[_NM_ACTIVATION_STATE_FLAG_TO_STRING_BUFSIZE];
@@ -869,10 +877,14 @@ _set_activation_type (NMActiveConnection *self,
                       NMActivationType activation_type)
 {
 	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
+	gboolean state_flags_changed;
 
 	if (priv->activation_type == activation_type)
 		return;
 
+	state_flags_changed =    (priv->activation_type == NM_ACTIVATION_TYPE_EXTERNAL)
+	                      != (activation_type == NM_ACTIVATION_TYPE_EXTERNAL);
+
 	priv->activation_type = activation_type;
 
 	if (priv->settings_connection.obj) {
@@ -881,6 +893,9 @@ _set_activation_type (NMActiveConnection *self,
 		else
 			g_signal_handlers_disconnect_by_func (priv->settings_connection.obj, _settings_connection_flags_changed, self);
 	}
+
+	if (state_flags_changed)
+		_notify (self, PROP_STATE_FLAGS);
 }
 
 static void
@@ -953,7 +968,7 @@ _settings_connection_flags_changed (NMSettingsConnection *settings_connection,
 	nm_assert (NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->settings_connection.obj == settings_connection);
 
 	if (NM_FLAGS_HAS (nm_settings_connection_get_flags (settings_connection),
-	                  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED))
+	                  NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL))
 		return;
 
 	_set_activation_type_managed (self);
@@ -1253,7 +1268,8 @@ static void
 get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
-	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (object);
+	NMActiveConnection *self = NM_ACTIVE_CONNECTION (object);
+	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
 	char **strv;
 	NMDevice *master_device = NULL;
 
@@ -1300,7 +1316,7 @@ get_property (GObject *object, guint prop_id,
 		}
 		break;
 	case PROP_STATE_FLAGS:
-		g_value_set_uint (value, priv->state_flags);
+		g_value_set_uint (value, nm_active_connection_get_state_flags (self));
 		break;
 	case PROP_DEFAULT:
 		g_value_set_boolean (value, priv->is_default);
diff --git a/src/nm-auth-manager.c b/src/nm-auth-manager.c
index 88e6cb94..5b3702ec 100644
--- a/src/nm-auth-manager.c
+++ b/src/nm-auth-manager.c
@@ -37,11 +37,14 @@ static guint signals[LAST_SIGNAL] = {0};
 typedef struct {
 	CList calls_lst_head;
 	GDBusConnection *dbus_connection;
-	GCancellable *shutdown_cancellable;
+	GCancellable *main_cancellable;
+	char *name_owner;
 	guint64 call_numid_counter;
-	guint changed_signal_id;
+	guint changed_id;
+	guint name_owner_changed_id;
 	bool disposing:1;
 	bool shutting_down:1;
+	bool got_name_owner:1;
 	NMAuthPolkitMode auth_polkit_mode:3;
 } NMAuthManagerPrivate;
 
@@ -105,6 +108,12 @@ nm_auth_manager_get_polkit_enabled (NMAuthManager *self)
 
 /*****************************************************************************/
 
+static void
+_emit_changed_signal (NMAuthManager *self)
+{
+	g_signal_emit (self, signals[CHANGED_SIGNAL], 0);
+}
+
 typedef enum {
 	POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE                   = 0,
 	POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION = (1<<0),
@@ -204,17 +213,18 @@ _call_check_authorize_cb (GObject *proxy,
 	priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
 
 	value = g_dbus_connection_call_finish (G_DBUS_CONNECTION (proxy), res, &error);
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+
+	if (nm_utils_error_is_cancelled (error)) {
 		/* call_id was cancelled externally, but _call_id_free() kept call_id
 		 * alive (and it has still the reference on @self. */
 
-		if (!priv->shutdown_cancellable) {
+		if (!priv->main_cancellable) {
 			/* we do a forced shutdown. There is no more time for cancelling... */
 			_call_id_free (call_id);
 
 			/* this shouldn't really happen, because:
-			 * _call_check_authorize() only scheduled the D-Bus request at a time when
-			 * shutdown_cancellable was still set. It means, somebody called force-shutdown
+			 * nm_auth_manager_check_authorization() only scheduled the D-Bus request at a time when
+			 * main_cancellable was still set. It means, somebody called force-shutdown
 			 * after call-id was schedule.
 			 * force-shutdown should only be called after:
 			 *   - cancel all pending requests
@@ -233,7 +243,7 @@ _call_check_authorize_cb (GObject *proxy,
 		                        G_VARIANT_TYPE ("()"),
 		                        G_DBUS_CALL_FLAGS_NONE,
 		                        CANCELLATION_TIMEOUT_MS,
-		                        priv->shutdown_cancellable,
+		                        priv->main_cancellable,
 		                        cancel_check_authorization_cb,
 		                        call_id);
 		return;
@@ -357,7 +367,7 @@ nm_auth_manager_check_authorization (NMAuthManager *self,
 
 		call_id->dbus_cancellable = g_cancellable_new ();
 
-		nm_assert (priv->shutdown_cancellable);
+		nm_assert (priv->main_cancellable);
 
 		g_dbus_connection_call (priv->dbus_connection,
 		                        POLKIT_SERVICE,
@@ -411,9 +421,95 @@ changed_signal_cb (GDBusConnection *connection,
                    gpointer user_data)
 {
 	NMAuthManager *self = user_data;
+	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
+	gboolean valid_sender;
 
-	_LOGD ("dbus signal: \"Changed\"");
-	g_signal_emit (self, signals[CHANGED_SIGNAL], 0);
+	nm_assert (nm_streq0 (signal_name, "Changed"));
+
+	valid_sender = nm_streq0 (priv->name_owner, sender_name);
+
+	_LOGD ("dbus-signal: \"Changed\" notification%s", valid_sender ? "" : " (ignore)");
+
+	if (valid_sender)
+		_emit_changed_signal (self);
+}
+
+static void
+_name_owner_changed (NMAuthManager *self,
+                     const char *name_owner,
+                     gboolean is_initial)
+{
+	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
+	gboolean is_changed;
+	gs_free char *old_name_owner = NULL;
+
+	if (is_initial)
+		priv->got_name_owner = TRUE;
+	else {
+		if (!priv->got_name_owner)
+			return;
+	}
+
+	name_owner = nm_str_not_empty (name_owner);
+
+	is_changed = !nm_streq0 (priv->name_owner, name_owner);
+	if (is_changed) {
+		old_name_owner = g_steal_pointer (&priv->name_owner);
+		priv->name_owner = g_strdup (name_owner);
+	} else {
+		if (!is_initial)
+			return;
+	}
+
+	if (!priv->name_owner) {
+		if (is_initial)
+			_LOGT ("name-owner: polkit not running");
+		else
+			_LOGT ("name-owner: polkit stopped (was %s)", old_name_owner);
+	} else {
+		if (is_initial)
+			_LOGT ("name-owner: polkit is running (now %s)", priv->name_owner);
+		else if (old_name_owner)
+			_LOGT ("name-owner: polkit restarted (now %s, was %s)", priv->name_owner, old_name_owner);
+		else
+			_LOGT ("name-owner: polkit started (now %s)", priv->name_owner);
+	}
+
+	if (priv->name_owner)
+		_emit_changed_signal (self);
+}
+
+static void
+_name_owner_changed_cb (GDBusConnection *connection,
+                        const char *sender_name,
+                        const char *object_path,
+                        const char *interface_name,
+                        const char *signal_name,
+                        GVariant *parameters,
+                        gpointer user_data)
+{
+	NMAuthManager *self = user_data;
+	const char *new_owner;
+
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sss)")))
+		return;
+
+	g_variant_get (parameters,
+	               "(&s&s&s)",
+	               NULL,
+	               NULL,
+	               &new_owner);
+
+	_name_owner_changed (self, new_owner, FALSE);
+}
+
+static void
+_name_owner_get_cb (const char *name_owner,
+                    GError *error,
+                    gpointer user_data)
+{
+	if (!nm_utils_error_is_cancelled (error))
+		_name_owner_changed (user_data, name_owner, TRUE);
 }
 
 /*****************************************************************************/
@@ -435,14 +531,17 @@ nm_auth_manager_force_shutdown (NMAuthManager *self)
 
 	priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
 
+	/* FIXME(shutdown): ensure we properly call this API during shutdown as
+	 * described next. */
+
 	/* while we have pending requests (NMAuthManagerCallId), the instance
 	 * is kept alive.
 	 *
-	 * Even if the caller cancells all pending call-ids, we still need to keep
+	 * Even if the caller cancels all pending call-ids, we still need to keep
 	 * a reference to self, in order to handle pending CancelCheckAuthorization
 	 * requests.
 	 *
-	 * To do a corrdinated shutdown, do the following:
+	 * To do a coordinated shutdown, do the following:
 	 * - cancel all pending NMAuthManagerCallId requests.
 	 * - ensure everybody unrefs the NMAuthManager instance. If by that, the instance
 	 *   gets destroyed, the shutdown already completed successfully.
@@ -457,7 +556,7 @@ nm_auth_manager_force_shutdown (NMAuthManager *self)
 	 */
 
 	priv->shutting_down = TRUE;
-	nm_clear_g_cancellable (&priv->shutdown_cancellable);
+	nm_clear_g_cancellable (&priv->main_cancellable);
 }
 
 /*****************************************************************************/
@@ -523,18 +622,31 @@ constructed (GObject *object)
 		goto out;
 	}
 
-	priv->shutdown_cancellable = g_cancellable_new ();
-
-	priv->changed_signal_id = g_dbus_connection_signal_subscribe (priv->dbus_connection,
-	                                                              POLKIT_SERVICE,
-	                                                              POLKIT_INTERFACE,
-	                                                              "Changed",
-	                                                              POLKIT_OBJECT_PATH,
-	                                                              NULL,
-	                                                              G_DBUS_SIGNAL_FLAGS_NONE,
-	                                                              changed_signal_cb,
-	                                                              self,
-	                                                              NULL);
+	priv->main_cancellable = g_cancellable_new ();
+
+	priv->name_owner_changed_id = nm_dbus_connection_signal_subscribe_name_owner_changed (priv->dbus_connection,
+	                                                                                      POLKIT_SERVICE,
+	                                                                                      _name_owner_changed_cb,
+	                                                                                      self,
+	                                                                                      NULL);
+
+	priv->changed_id = g_dbus_connection_signal_subscribe (priv->dbus_connection,
+	                                                       POLKIT_SERVICE,
+	                                                       POLKIT_INTERFACE,
+	                                                       "Changed",
+	                                                       POLKIT_OBJECT_PATH,
+	                                                       NULL,
+	                                                       G_DBUS_SIGNAL_FLAGS_NONE,
+	                                                       changed_signal_cb,
+	                                                       self,
+	                                                       NULL);
+
+	nm_dbus_connection_call_get_name_owner (priv->dbus_connection,
+	                                        POLKIT_SERVICE,
+	                                        -1,
+	                                        priv->main_cancellable,
+	                                        _name_owner_get_cb,
+	                                        self);
 
 	create_message = "polkit enabled";
 
@@ -578,14 +690,19 @@ dispose (GObject *object)
 
 	priv->disposing = TRUE;
 
-	nm_clear_g_cancellable (&priv->shutdown_cancellable);
+	nm_clear_g_cancellable (&priv->main_cancellable);
 
 	nm_clear_g_dbus_connection_signal (priv->dbus_connection,
-	                                   &priv->changed_signal_id);
+	                                   &priv->name_owner_changed_id);
+
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection,
+	                                   &priv->changed_id);
 
 	G_OBJECT_CLASS (nm_auth_manager_parent_class)->dispose (object);
 
 	g_clear_object (&priv->dbus_connection);
+
+	nm_clear_g_free (&priv->name_owner);
 }
 
 static void
diff --git a/src/nm-config-data.c b/src/nm-config-data.c
index 29dafa14..4169a36e 100644
--- a/src/nm-config-data.c
+++ b/src/nm-config-data.c
@@ -12,6 +12,7 @@
 #include "devices/nm-device.h"
 #include "nm-core-internal.h"
 #include "nm-keyfile/nm-keyfile-internal.h"
+#include "nm-keyfile/nm-keyfile-utils.h"
 
 /*****************************************************************************/
 
@@ -116,14 +117,6 @@ G_DEFINE_TYPE (NMConfigData, nm_config_data, G_TYPE_OBJECT)
 
 /*****************************************************************************/
 
-#define _HAS_PREFIX(str, prefix) \
-	({ \
-		const char *_str = (str); \
-		g_str_has_prefix ( _str, ""prefix"") && _str[NM_STRLEN(prefix)] != '\0'; \
-	})
-
-/*****************************************************************************/
-
 const char *
 nm_config_data_get_config_main_file (const NMConfigData *self)
 {
@@ -215,6 +208,7 @@ nm_config_data_get_value_int64 (const NMConfigData *self, const char *group, con
 char **
 nm_config_data_get_plugins (const NMConfigData *self, gboolean allow_default)
 {
+	gs_free_error GError *error = NULL;
 	const NMConfigDataPrivate *priv;
 	char **list;
 
@@ -222,8 +216,9 @@ nm_config_data_get_plugins (const NMConfigData *self, gboolean allow_default)
 
 	priv = NM_CONFIG_DATA_GET_PRIVATE (self);
 
-	list = g_key_file_get_string_list (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins", NULL, NULL);
-	if (!list && allow_default) {
+	list = g_key_file_get_string_list (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins", NULL, &error);
+	if (   nm_keyfile_error_is_not_found (error)
+	    && allow_default) {
 		gs_unref_keyfile GKeyFile *kf = nm_config_create_keyfile ();
 
 		/* let keyfile split the default string according to its own escaping rules. */
@@ -550,14 +545,14 @@ _merge_keyfiles (GKeyFile *keyfile_user, GKeyFile *keyfile_intern)
 				continue;
 
 			if (   !is_intern && !is_atomic
-			    && _HAS_PREFIX (key, NM_CONFIG_KEYFILE_KEYPREFIX_WAS)) {
+			    && NM_STR_HAS_PREFIX_WITH_MORE (key, NM_CONFIG_KEYFILE_KEYPREFIX_WAS)) {
 				const char *key_base = &key[NM_STRLEN (NM_CONFIG_KEYFILE_KEYPREFIX_WAS)];
 
 				if (!g_key_file_has_key (keyfile_intern, group, key_base, NULL))
 					g_key_file_remove_key (keyfile, group, key_base, NULL);
 				continue;
 			}
-			if (!is_intern && !is_atomic && _HAS_PREFIX (key, NM_CONFIG_KEYFILE_KEYPREFIX_SET))
+			if (!is_intern && !is_atomic && NM_STR_HAS_PREFIX_WITH_MORE (key, NM_CONFIG_KEYFILE_KEYPREFIX_SET))
 				continue;
 
 			value = g_key_file_get_value (keyfile_intern, group, key, NULL);
diff --git a/src/nm-config.c b/src/nm-config.c
index 63347db7..4fa85319 100644
--- a/src/nm-config.c
+++ b/src/nm-config.c
@@ -15,6 +15,7 @@
 #include "NetworkManagerUtils.h"
 #include "nm-core-internal.h"
 #include "nm-keyfile/nm-keyfile-internal.h"
+#include "nm-keyfile/nm-keyfile-utils.h"
 
 #define DEFAULT_CONFIG_MAIN_FILE        NMCONFDIR "/NetworkManager.conf"
 #define DEFAULT_CONFIG_DIR              NMCONFDIR "/conf.d"
@@ -144,14 +145,6 @@ static void _set_config_data (NMConfig *self, NMConfigData *new_data, NMConfigCh
 
 /*****************************************************************************/
 
-#define _HAS_PREFIX(str, prefix) \
-	({ \
-		const char *_str = (str); \
-		g_str_has_prefix ( _str, ""prefix"") && _str[NM_STRLEN(prefix)] != '\0'; \
-	})
-
-/*****************************************************************************/
-
 int
 nm_config_parse_boolean (const char *str,
                          int default_value)
@@ -351,7 +344,7 @@ no_auto_default_from_file (const char *no_auto_default_file)
 
 	if (list) {
 		for (i = 0; list[i]; i++)
-			list[i] = nm_utils_str_utf8safe_unescape_cp (list[i]);
+			list[i] = nm_utils_str_utf8safe_unescape_cp (list[i], NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
 	}
 
 	/* The returned buffer here is not at all compact. That means, it has additional
@@ -548,7 +541,7 @@ string_to_configure_and_quit (const char *value, GError **error)
 	if (value == NULL)
 		return NM_CONFIG_CONFIGURE_AND_QUIT_DISABLED;
 
-	if (strcmp (value, "initrd") == 0)
+	if (nm_streq (value, "initrd"))
 		return NM_CONFIG_CONFIGURE_AND_QUIT_INITRD;
 
 	ret = nm_config_parse_boolean (value, NM_CONFIG_CONFIGURE_AND_QUIT_INVALID);
@@ -664,15 +657,13 @@ ignore_config_snippet (GKeyFile *keyfile, gboolean is_base_config)
 static int
 _sort_groups_cmp (const char **pa, const char **pb, gpointer dummy)
 {
-	const char *a, *b;
+	const char *a = *pa;
+	const char *b = *pb;
 	gboolean a_is_connection, b_is_connection;
 	gboolean a_is_device, b_is_device;
 
-	a = *pa;
-	b = *pb;
-
-	a_is_connection = g_str_has_prefix (a, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION);
-	b_is_connection = g_str_has_prefix (b, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION);
+	a_is_connection = NM_STR_HAS_PREFIX (a, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION);
+	b_is_connection = NM_STR_HAS_PREFIX (b, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION);
 
 	if (a_is_connection != b_is_connection) {
 		/* one is a [connection*] entry, the other not. We sort [connection*] entries
@@ -689,8 +680,8 @@ _sort_groups_cmp (const char **pa, const char **pb, gpointer dummy)
 		return pa > pb ? -1 : 1;
 	}
 
-	a_is_device = g_str_has_prefix (a, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE);
-	b_is_device = g_str_has_prefix (b, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE);
+	a_is_device = NM_STR_HAS_PREFIX (a, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE);
+	b_is_device = NM_STR_HAS_PREFIX (b, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE);
 
 	if (a_is_device != b_is_device) {
 		/* one is a [device*] entry, the other not. We sort [device*] entries
@@ -726,13 +717,13 @@ _nm_config_sort_groups (char **groups, gsize ngroups)
 static gboolean
 _setting_is_device_spec (const char *group, const char *key)
 {
-#define _IS(group_v, key_v) (strcmp (group, (""group_v)) == 0 && strcmp (key, (""key_v)) == 0)
+#define _IS(group_v, key_v) (nm_streq (group, ""group_v"") && nm_streq (key, ""key_v""))
 	return    _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_NO_AUTO_DEFAULT)
 	       || _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_IGNORE_CARRIER)
 	       || _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_ASSUME_IPV6LL_ONLY)
 	       || _IS (NM_CONFIG_KEYFILE_GROUP_KEYFILE, NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES)
-	       || (g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION) && !strcmp (key, NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE))
-	       || (g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE    ) && !strcmp (key, NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE));
+	       || (NM_STR_HAS_PREFIX (group, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION) && nm_streq (key, NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE))
+	       || (NM_STR_HAS_PREFIX (group, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE    ) && nm_streq (key, NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE));
 }
 
 static gboolean
@@ -741,7 +732,7 @@ _setting_is_string_list (const char *group, const char *key)
 	return    _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_PLUGINS)
 	       || _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_DEBUG)
 	       || _IS (NM_CONFIG_KEYFILE_GROUP_LOGGING, NM_CONFIG_KEYFILE_KEY_LOGGING_DOMAINS)
-	       || g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST);
+	       || NM_STR_HAS_PREFIX (group, NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST);
 #undef _IS
 }
 
@@ -902,15 +893,20 @@ check_config_key (const char *group, const char *key)
 }
 
 static gboolean
-read_config (GKeyFile *keyfile, gboolean is_base_config,
-             const char *dirname, const char *path,
-             GPtrArray *warnings, GError **error)
-{
-	GKeyFile *kf;
-	char **groups, **keys;
-	gsize ngroups, nkeys;
-	int g, k;
+read_config (GKeyFile *keyfile,
+             gboolean is_base_config,
+             const char *dirname,
+             const char *path,
+             GPtrArray *warnings,
+             GError **error)
+{
+	gs_unref_keyfile GKeyFile *kf = NULL;
+	gs_strfreev char **groups = NULL;
 	gs_free char *path_free = NULL;
+	gsize ngroups;
+	gsize nkeys;
+	int g;
+	int k;
 
 	g_return_val_if_fail (keyfile, FALSE);
 	g_return_val_if_fail (path, FALSE);
@@ -931,14 +927,11 @@ read_config (GKeyFile *keyfile, gboolean is_base_config,
 	kf = nm_config_create_keyfile ();
 	if (!g_key_file_load_from_file (kf, path, G_KEY_FILE_NONE, error)) {
 		g_prefix_error (error, "%s: ", path);
-		g_key_file_free (kf);
 		return FALSE;
 	}
 
-	if (ignore_config_snippet (kf, is_base_config)) {
-		g_key_file_free (kf);
+	if (ignore_config_snippet (kf, is_base_config))
 		return TRUE;
-	}
 
 	/* the config-group is internal to every configuration snippets. It doesn't make sense
 	 * to merge it into the global configuration, and it doesn't make sense to preserve the
@@ -962,8 +955,9 @@ read_config (GKeyFile *keyfile, gboolean is_base_config,
 
 	for (g = 0; groups && groups[g]; g++) {
 		const char *group = groups[g];
+		gs_strfreev char **keys = NULL;
 
-		if (g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN)) {
+		if (NM_STR_HAS_PREFIX (group, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN)) {
 			/* internal groups cannot be set by user configuration. */
 			continue;
 		}
@@ -971,21 +965,21 @@ read_config (GKeyFile *keyfile, gboolean is_base_config,
 		if (!keys)
 			continue;
 		for (k = 0; keys[k]; k++) {
+			gs_free char *new_value = NULL;
 			const char *key;
-			char *new_value;
 			char last_char;
 			gsize key_len;
 
 			key = keys[k];
-			g_assert (key && *key);
+			nm_assert (key && *key);
 
-			if (   _HAS_PREFIX (key, NM_CONFIG_KEYFILE_KEYPREFIX_WAS)
-			    || _HAS_PREFIX (key, NM_CONFIG_KEYFILE_KEYPREFIX_SET)) {
+			if (   NM_STR_HAS_PREFIX_WITH_MORE (key, NM_CONFIG_KEYFILE_KEYPREFIX_WAS)
+			    || NM_STR_HAS_PREFIX_WITH_MORE (key, NM_CONFIG_KEYFILE_KEYPREFIX_SET)) {
 				/* these keys are protected. We ignore them if the user sets them. */
 				continue;
 			}
 
-			if (!strcmp (key, NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS)) {
+			if (nm_streq (key, NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS)) {
 				/* the "was" key is protected and it cannot be set by user configuration. */
 				continue;
 			}
@@ -996,6 +990,7 @@ read_config (GKeyFile *keyfile, gboolean is_base_config,
 			    && (last_char == '+' || last_char == '-')) {
 				gs_free char *base_key = g_strndup (key, key_len - 1);
 				gboolean is_string_list;
+				gboolean old_val_was_set = FALSE;
 
 				is_string_list = _setting_is_string_list (group, base_key);
 
@@ -1007,13 +1002,19 @@ read_config (GKeyFile *keyfile, gboolean is_base_config,
 					gs_free char **new_val = NULL;
 
 					if (is_string_list) {
-						old_val = g_key_file_get_string_list (keyfile, group, base_key, NULL, NULL);
+						gs_free_error GError *old_error = NULL;
+
+						old_val = g_key_file_get_string_list (keyfile, group, base_key, NULL, &old_error);
 						new_val = g_key_file_get_string_list (kf, group, key, NULL, NULL);
-						if (!old_val && !g_key_file_has_key (keyfile, group, base_key, NULL)) {
-							/* we must fill the unspecified value with the compile-time default. */
-							if (nm_streq (group, NM_CONFIG_KEYFILE_GROUP_MAIN) && nm_streq (base_key, "plugins")) {
+						if (   nm_streq (group, NM_CONFIG_KEYFILE_GROUP_MAIN)
+						    && nm_streq (base_key, "plugins")) {
+							old_val_was_set = !nm_keyfile_error_is_not_found (old_error);
+							if (   !old_val
+							    && !old_val_was_set) {
+								/* we must fill the unspecified value with the compile-time default. */
 								g_key_file_set_value (keyfile, group, base_key, NM_CONFIG_DEFAULT_MAIN_PLUGINS);
 								old_val = g_key_file_get_string_list (keyfile, group, base_key, NULL, NULL);
+								old_val_was_set = TRUE;
 							}
 						}
 					} else {
@@ -1059,7 +1060,7 @@ read_config (GKeyFile *keyfile, gboolean is_base_config,
 							g_key_file_set_value (keyfile, group, base_key, specs_joined);
 						}
 					} else {
-						if (is_string_list)
+						if (is_string_list && !old_val_was_set)
 							g_key_file_remove_key (keyfile, group, base_key, NULL);
 						else
 							g_key_file_set_value (keyfile, group, base_key, "");
@@ -1079,12 +1080,8 @@ read_config (GKeyFile *keyfile, gboolean is_base_config,
 				                 g_strdup_printf ("unknown key '%s' in section [%s] of file '%s'",
 				                                  key, group, path));
 			}
-			g_free (new_value);
 		}
-		g_strfreev (keys);
 	}
-	g_strfreev (groups);
-	g_key_file_free (kf);
 
 	return TRUE;
 }
@@ -1174,7 +1171,7 @@ _get_config_dir_files (const char *config_dir)
 	if (direnum) {
 		while ((info = g_file_enumerator_next_file (direnum, NULL, NULL))) {
 			name = g_file_info_get_name (info);
-			if (g_str_has_suffix (name, ".conf"))
+			if (NM_STR_HAS_SUFFIX (name, ".conf"))
 				g_ptr_array_add (confs, g_strdup (name));
 			g_object_unref (info);
 		}
@@ -1472,14 +1469,14 @@ intern_config_read (const char *filename,
 		const char *group = groups[g];
 		gboolean is_intern, is_atomic;
 
-		if (!strcmp (group, NM_CONFIG_KEYFILE_GROUP_CONFIG))
+		if (nm_streq (group, NM_CONFIG_KEYFILE_GROUP_CONFIG))
 			continue;
 
 		keys = g_key_file_get_keys (keyfile, group, NULL, NULL);
 		if (!keys)
 			continue;
 
-		is_intern = g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN);
+		is_intern = NM_STR_HAS_PREFIX (group, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN);
 		is_atomic = !is_intern && _is_atomic_section (atomic_section_prefixes, group);
 
 		if (is_atomic) {
@@ -1489,7 +1486,7 @@ intern_config_read (const char *filename,
 			conf_section_is = _keyfile_serialize_section (keyfile_conf, group);
 			conf_section_was = g_key_file_get_string (keyfile, group, NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS, NULL);
 
-			if (g_strcmp0 (conf_section_was, conf_section_is) != 0) {
+			if (!nm_streq0 (conf_section_was, conf_section_is)) {
 				/* the section no longer matches. Skip it entirely. */
 				needs_rewrite = TRUE;
 				continue;
@@ -1510,10 +1507,10 @@ intern_config_read (const char *filename,
 				has_intern = TRUE;
 				g_key_file_set_value (keyfile_intern, group, key, value_set);
 			} else if (is_atomic) {
-				if (strcmp (key, NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS) == 0)
+				if (nm_streq (key, NM_CONFIG_KEYFILE_KEY_ATOMIC_SECTION_WAS))
 					continue;
 				g_key_file_set_value (keyfile_intern, group, key, value_set);
-			} else if (_HAS_PREFIX (key, NM_CONFIG_KEYFILE_KEYPREFIX_SET)) {
+			} else if (NM_STR_HAS_PREFIX_WITH_MORE (key, NM_CONFIG_KEYFILE_KEYPREFIX_SET)) {
 				const char *key_base = &key[NM_STRLEN (NM_CONFIG_KEYFILE_KEYPREFIX_SET)];
 				gs_free char *value_was = NULL;
 				gs_free char *value_conf = NULL;
@@ -1523,7 +1520,7 @@ intern_config_read (const char *filename,
 					value_conf = g_key_file_get_value (keyfile_conf, group, key_base, NULL);
 				value_was = g_key_file_get_value (keyfile, group, key_was, NULL);
 
-				if (g_strcmp0 (value_conf, value_was) != 0) {
+				if (!nm_streq0 (value_conf, value_was)) {
 					/* if value_was is no longer the same as @value_conf, it means the user
 					 * changed the configuration since the last write. In this case, we
 					 * drop the value. It also means our file is out-of-date, and we should
@@ -1533,7 +1530,7 @@ intern_config_read (const char *filename,
 				}
 				has_intern = TRUE;
 				g_key_file_set_value (keyfile_intern, group, key_base, value_set);
-			} else if (_HAS_PREFIX (key, NM_CONFIG_KEYFILE_KEYPREFIX_WAS)) {
+			} else if (NM_STR_HAS_PREFIX_WITH_MORE (key, NM_CONFIG_KEYFILE_KEYPREFIX_WAS)) {
 				const char *key_base = &key[NM_STRLEN (NM_CONFIG_KEYFILE_KEYPREFIX_WAS)];
 				gs_free char *key_set = g_strdup_printf (NM_CONFIG_KEYFILE_KEYPREFIX_SET"%s", key_base);
 				gs_free char *value_was = NULL;
@@ -1548,7 +1545,7 @@ intern_config_read (const char *filename,
 					value_conf = g_key_file_get_value (keyfile_conf, group, key_base, NULL);
 				value_was = g_key_file_get_value (keyfile, group, key, NULL);
 
-				if (g_strcmp0 (value_conf, value_was) != 0) {
+				if (!nm_streq0 (value_conf, value_was)) {
 					/* if value_was is no longer the same as @value_conf, it means the user
 					 * changed the configuration since the last write. In this case, we
 					 * don't overwrite the user-provided value. It also means our file is
@@ -1577,7 +1574,7 @@ out:
 		if (g_key_file_remove_group (keyfile_intern, NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS, NULL))
 			needs_rewrite = TRUE;
 		for (g = 0; groups && groups[g]; g++) {
-			if (   g_str_has_prefix (groups[g], NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN)
+			if (   NM_STR_HAS_PREFIX (groups[g], NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN)
 			    && groups[g][NM_STRLEN (NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN)]) {
 				g_key_file_remove_group (keyfile_intern, groups[g], NULL);
 				needs_rewrite = TRUE;
@@ -1602,12 +1599,12 @@ out:
 static int
 _intern_config_write_sort_fcn (const char **a, const char **b, const char *const*atomic_section_prefixes)
 {
-	const char *g_a = (a ? *a : NULL);
-	const char *g_b = (b ? *b : NULL);
+	const char *g_a = *a;
+	const char *g_b = *b;
 	gboolean a_is, b_is;
 
-	a_is = g_str_has_prefix (g_a, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN);
-	b_is = g_str_has_prefix (g_b, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN);
+	a_is = NM_STR_HAS_PREFIX (g_a, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN);
+	b_is = NM_STR_HAS_PREFIX (g_b, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN);
 
 	if (a_is != b_is) {
 		if (a_is)
@@ -1668,7 +1665,7 @@ intern_config_write (const char *filename,
 		if (!keys)
 			continue;
 
-		is_intern = g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN);
+		is_intern = NM_STR_HAS_PREFIX (group, NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN);
 		is_atomic = !is_intern && _is_atomic_section (atomic_section_prefixes, group);
 
 		if (is_atomic) {
@@ -1707,16 +1704,16 @@ intern_config_write (const char *filename,
 			else {
 				gs_free char *value_was = NULL;
 
-				if (_HAS_PREFIX (key, NM_CONFIG_KEYFILE_KEYPREFIX_SET)) {
+				if (NM_STR_HAS_PREFIX_WITH_MORE (key, NM_CONFIG_KEYFILE_KEYPREFIX_SET)) {
 					/* Setting a key with .set prefix has no meaning, as these keys
 					 * are protected. Just set the value you want to set instead.
 					 * Why did this happen?? */
 					g_warn_if_reached ();
-				} else if (_HAS_PREFIX (key, NM_CONFIG_KEYFILE_KEYPREFIX_WAS)) {
+				} else if (NM_STR_HAS_PREFIX_WITH_MORE (key, NM_CONFIG_KEYFILE_KEYPREFIX_WAS)) {
 					const char *key_base = &key[NM_STRLEN (NM_CONFIG_KEYFILE_KEYPREFIX_WAS)];
 
-					if (   _HAS_PREFIX (key_base, NM_CONFIG_KEYFILE_KEYPREFIX_SET)
-					    || _HAS_PREFIX (key_base, NM_CONFIG_KEYFILE_KEYPREFIX_WAS)) {
+					if (   NM_STR_HAS_PREFIX_WITH_MORE (key_base, NM_CONFIG_KEYFILE_KEYPREFIX_SET)
+					    || NM_STR_HAS_PREFIX_WITH_MORE (key_base, NM_CONFIG_KEYFILE_KEYPREFIX_WAS)) {
 						g_warn_if_reached ();
 						continue;
 					}
@@ -1735,7 +1732,7 @@ intern_config_write (const char *filename,
 				} else {
 					if (keyfile_conf) {
 						value_was = g_key_file_get_value (keyfile_conf, group, key, NULL);
-						if (g_strcmp0 (value_set, value_was) == 0) {
+						if (nm_streq0 (value_set, value_was)) {
 							/* there is no point in storing the identical value as we have via
 							 * user configuration. Skip it. */
 							continue;
@@ -1837,7 +1834,7 @@ nm_config_set_global_dns (NMConfig *self, NMGlobalDnsConfig *global_dns, GError
 	g_key_file_remove_group (keyfile, NM_CONFIG_KEYFILE_GROUP_INTERN_GLOBAL_DNS, NULL);
 	groups = g_key_file_get_groups (keyfile, NULL);
 	for (i = 0; groups[i]; i++) {
-		if (g_str_has_prefix (groups[i], NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN))
+		if (NM_STR_HAS_PREFIX (groups[i], NM_CONFIG_KEYFILE_GROUPPREFIX_INTERN_GLOBAL_DNS_DOMAIN))
 			g_key_file_remove_group (keyfile, groups[i], NULL);
 	}
 	g_strfreev (groups);
@@ -2210,7 +2207,7 @@ _config_device_state_data_new (int ifindex, GKeyFile *kf)
 	gs_free char *perm_hw_addr_fake = NULL;
 	gsize connection_uuid_len;
 	gsize perm_hw_addr_fake_len;
-	int nm_owned = -1;
+	NMTernary nm_owned;
 	char *p;
 	guint32 route_metric_default_effective;
 	guint32 route_metric_default_aspired;
@@ -2252,7 +2249,7 @@ _config_device_state_data_new (int ifindex, GKeyFile *kf)
 	nm_owned = nm_config_keyfile_get_boolean (kf,
 	                                          DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
 	                                          DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NM_OWNED,
-	                                          -1);
+	                                          NM_TERNARY_DEFAULT);
 
 	/* metric zero is not a valid metric. While zero valid for IPv4, for IPv6 it is an alias
 	 * for 1024. Since we handle here IPv4 and IPv6 the same, we cannot allow zero. */
@@ -2325,9 +2322,11 @@ nm_config_device_state_load (int ifindex)
 		return NULL;
 
 	device_state = _config_device_state_data_new (ifindex, kf);
-	nm_owned_str = device_state->nm_owned == TRUE ?
-	               ", nm-owned=1" :
-	               (device_state->nm_owned == FALSE ? ", nm-owned=0" : "");
+	nm_owned_str =   device_state->nm_owned == NM_TERNARY_TRUE
+	               ? ", nm-owned=1"
+	               : (  device_state->nm_owned == NM_TERNARY_FALSE
+	                  ? ", nm-owned=0"
+	                  : "");
 
 	_LOGT ("device-state: %s #%d (%s); managed=%s%s%s%s%s%s%s%s, route-metric-default=%"G_GUINT32_FORMAT"-%"G_GUINT32_FORMAT"",
 	       kf ? "read" : "miss",
@@ -2390,7 +2389,7 @@ nm_config_device_state_write (int ifindex,
                               NMConfigDeviceStateManagedType managed,
                               const char *perm_hw_addr_fake,
                               const char *connection_uuid,
-                              int nm_owned,
+                              NMTernary nm_owned,
                               guint32 route_metric_default_aspired,
                               guint32 route_metric_default_effective,
                               const char *next_server,
@@ -2429,7 +2428,7 @@ nm_config_device_state_write (int ifindex,
 		                       DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_CONNECTION_UUID,
 		                       connection_uuid);
 	}
-	if (nm_owned >= 0) {
+	if (nm_owned != NM_TERNARY_DEFAULT) {
 		g_key_file_set_boolean (kf,
 		                        DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
 		                        DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NM_OWNED,
diff --git a/src/nm-config.h b/src/nm-config.h
index b4478ceb..0fce7ec0 100644
--- a/src/nm-config.h
+++ b/src/nm-config.h
@@ -243,7 +243,7 @@ struct _NMConfigDeviceStateData {
 
 	/* whether the device was nm-owned (0/1) or -1 for
 	 * non-software devices. */
-	int nm_owned:3;
+	NMTernary nm_owned:3;
 };
 
 NMConfigDeviceStateData *nm_config_device_state_load (int ifindex);
@@ -252,7 +252,7 @@ gboolean nm_config_device_state_write (int ifindex,
                                        NMConfigDeviceStateManagedType managed,
                                        const char *perm_hw_addr_fake,
                                        const char *connection_uuid,
-                                       int nm_owned,
+                                       NMTernary nm_owned,
                                        guint32 route_metric_default_aspired,
                                        guint32 route_metric_default_effective,
                                        const char *next_server,
diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c
index a9f50a0f..019d1e60 100644
--- a/src/nm-core-utils.c
+++ b/src/nm-core-utils.c
@@ -1703,11 +1703,13 @@ nm_wildcard_match_check (const char *str,
                          const char *const *patterns,
                          guint num_patterns)
 {
-	guint i, neg = 0;
+	gsize i, neg = 0;
 
 	for (i = 0; i < num_patterns; i++) {
 		if (patterns[i][0] == '!') {
 			neg++;
+			if (!str)
+				continue;
 			if (!fnmatch (patterns[i] + 1, str, 0))
 				return FALSE;
 		}
@@ -1716,10 +1718,12 @@ nm_wildcard_match_check (const char *str,
 	if (neg == num_patterns)
 		return TRUE;
 
-	for (i = 0; i < num_patterns; i++) {
-		if (   patterns[i][0] != '!'
-		    && !fnmatch (patterns[i], str, 0))
-			return TRUE;
+	if (str) {
+		for (i = 0; i < num_patterns; i++) {
+			if (   patterns[i][0] != '!'
+			    && !fnmatch (patterns[i], str, 0))
+				return TRUE;
+		}
 	}
 
 	return FALSE;
@@ -2776,7 +2780,7 @@ again:
 	if (G_UNLIKELY (!proc_cmdline)) {
 		gs_free const char **split = NULL;
 
-		/* TODO: support quotation, like systemd's proc_cmdline_extract_first().
+		/* FIXME(release-blocker): support quotation, like systemd's proc_cmdline_extract_first().
 		 * For that, add a new NMUtilsStrsplitSetFlags flag. */
 		split = nm_utils_strsplit_set_full (nm_utils_proc_cmdline (),
 		                                    NM_ASCII_WHITESPACES,
@@ -4442,21 +4446,18 @@ nm_wifi_utils_parse_ies (const guint8 *bytes,
 			}
 			break;
 		case WLAN_EID_VENDOR_SPECIFIC:
-			if (out_metered) {
-				if (   len == 8
-				    && bytes[0] == 0x00            /* OUI: Microsoft */
-				    && bytes[1] == 0x50
-				    && bytes[2] == 0xf2
-				    && bytes[3] == 0x11)           /* OUI type: Network cost */
-					*out_metered = (bytes[7] > 1); /* Cost level > 1 */
-			}
-			if (   out_owe_transition_mode
-			    && elem_len >= 10
+			if (   len == 8
+			    && bytes[0] == 0x00            /* OUI: Microsoft */
+			    && bytes[1] == 0x50
+			    && bytes[2] == 0xf2
+			    && bytes[3] == 0x11)           /* OUI type: Network cost */
+				NM_SET_OUT (out_metered, (bytes[7] > 1)); /* Cost level > 1 */
+			if (   elem_len >= 10
 			    && bytes[0] == 0x50            /* OUI: WiFi Alliance */
 			    && bytes[1] == 0x6f
 			    && bytes[2] == 0x9a
 			    && bytes[3] == 0x1c)           /* OUI type: OWE Transition Mode */
-				*out_owe_transition_mode = TRUE;
+				NM_SET_OUT (out_owe_transition_mode, TRUE);
 			break;
 		}
 
diff --git a/src/nm-dbus-manager.c b/src/nm-dbus-manager.c
index 239e06e1..9849656d 100644
--- a/src/nm-dbus-manager.c
+++ b/src/nm-dbus-manager.c
@@ -247,7 +247,9 @@ private_server_closed_connection (GDBusConnection *conn,
 	CloseConnectionInfo *info;
 
 	/* Clean up after the connection */
-	_LOGD ("(%s) closed connection %p on private socket", s->tag, conn);
+	_LOGD ("(%s) closed connection "NM_HASH_OBFUSCATE_PTR_FMT" on private socket",
+	       s->tag,
+	       NM_HASH_OBFUSCATE_PTR (conn));
 
 	info = g_slice_new0 (CloseConnectionInfo);
 	info->connection = conn;
@@ -285,7 +287,9 @@ private_server_new_connection (GDBusServer *server,
 	obj_mgr_data->fake_sender = sender;
 	c_list_link_tail (&s->object_mgr_lst_head, &obj_mgr_data->object_mgr_lst);
 
-	_LOGD ("(%s) accepted connection %p on private socket", s->tag, conn);
+	_LOGD ("(%s) accepted connection "NM_HASH_OBFUSCATE_PTR_FMT" on private socket",
+	       s->tag,
+	       NM_HASH_OBFUSCATE_PTR (conn));
 
 	/* Emit this for the manager.
 	 *
diff --git a/src/nm-iface-helper.c b/src/nm-iface-helper.c
index fdc0d4a4..6320e722 100644
--- a/src/nm-iface-helper.c
+++ b/src/nm-iface-helper.c
@@ -64,6 +64,7 @@ static struct {
 	char *dhcp4_clientid;
 	char *dhcp4_hostname;
 	char *dhcp4_fqdn;
+	char *mud_url;
 	char *iid_str;
 	NMSettingIP6ConfigAddrGenMode addr_gen_mode;
 	char *logging_backend;
@@ -523,6 +524,7 @@ main (int argc, char *argv[])
 		                                          global_opt.dhcp4_hostname,
 		                                          global_opt.dhcp4_fqdn,
 		                                          NM_DHCP_HOSTNAME_FLAGS_FQDN_DEFAULT_IP4,
+		                                          global_opt.mud_url,
 		                                          client_id,
 		                                          NM_DHCP_TIMEOUT_DEFAULT,
 		                                          NULL,
diff --git a/src/nm-manager.c b/src/nm-manager.c
index bbe50125..ae78b7c9 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -815,8 +815,9 @@ _delete_volatile_connection_do (NMManager *self,
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 
-	if (!NM_FLAGS_HAS (nm_settings_connection_get_flags (connection),
-	                   NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
+	if (!NM_FLAGS_ANY (nm_settings_connection_get_flags (connection),
+	                     NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+	                   | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL))
 		return;
 	if (!nm_settings_has_connection (priv->settings, connection))
 		return;
@@ -1058,8 +1059,9 @@ _get_activatable_connections_filter (NMSettings *settings,
 	const GetActivatableConnectionsFilterData *d = user_data;
 	NMConnectionMultiConnect multi_connect;
 
-	if (NM_FLAGS_HAS (nm_settings_connection_get_flags (sett_conn),
-	                  NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
+	if (NM_FLAGS_ANY (nm_settings_connection_get_flags (sett_conn),
+	                    NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+	                  | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL))
 		return FALSE;
 
 	multi_connect = _nm_connection_get_multi_connect (nm_settings_connection_get_connection (sett_conn));
@@ -2117,8 +2119,9 @@ connection_changed (NMManager *self,
 	NMConnection *connection;
 	NMDevice *device;
 
-	if (NM_FLAGS_HAS (nm_settings_connection_get_flags (sett_conn),
-	                  NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
+	if (NM_FLAGS_ANY (nm_settings_connection_get_flags (sett_conn),
+	                    NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+	                  | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL))
 		return;
 
 	connection = nm_settings_connection_get_connection (sett_conn);
@@ -2189,8 +2192,9 @@ connection_flags_changed (NMSettings *settings,
 	NMManager *self = user_data;
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 
-	if (!NM_FLAGS_HAS (nm_settings_connection_get_flags (connection),
-	                   NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
+	if (!NM_FLAGS_ANY (nm_settings_connection_get_flags (connection),
+	                     NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+	                   | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL))
 		return;
 
 	if (active_connection_find (self, connection, NULL, NM_ACTIVE_CONNECTION_STATE_DEACTIVATED, NULL)) {
@@ -2695,11 +2699,19 @@ get_existing_connection (NMManager *self,
 	}
 
 	if (matched) {
-		_LOG2I (LOGD_DEVICE, device, "assume: will attempt to assume matching connection '%s' (%s)%s",
-		        nm_settings_connection_get_id (matched),
-		        nm_settings_connection_get_uuid (matched),
-		        assume_state_connection_uuid && nm_streq (assume_state_connection_uuid, nm_settings_connection_get_uuid (matched))
-		            ? " (indicated)" : " (guessed)");
+
+		if (NM_FLAGS_HAS (nm_settings_connection_get_flags (matched), NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) {
+			_LOG2D (LOGD_DEVICE, device, "assume: take over previous connection '%s' (%s)",
+			        nm_settings_connection_get_id (matched),
+			        nm_settings_connection_get_uuid (matched));
+			NM_SET_OUT (out_generated, TRUE);
+		} else {
+			_LOG2I (LOGD_DEVICE, device, "assume: will attempt to assume matching connection '%s' (%s)%s",
+			        nm_settings_connection_get_id (matched),
+			        nm_settings_connection_get_uuid (matched),
+			        assume_state_connection_uuid && nm_streq (assume_state_connection_uuid, nm_settings_connection_get_uuid (matched))
+			            ? " (indicated)" : " (guessed)");
+		}
 		nm_device_assume_state_reset (device);
 		return matched;
 	}
@@ -2714,8 +2726,9 @@ get_existing_connection (NMManager *self,
 	                                 connection,
 	                                 NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY,
 	                                 NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
-	                                   NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
-	                                 | NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED,
+	                                   NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+	                                 | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+	                                 | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL,
 	                                 &added,
 	                                 &error)) {
 		_LOG2W (LOGD_SETTINGS, device, "assume: failure to save generated connection '%s': %s",
@@ -2819,8 +2832,9 @@ recheck_assume_connection (NMManager *self,
 				nm_settings_connection_update (sett_conn,
 				                               NULL,
 				                               NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
-				                               0,
-				                               NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE,
+				                               NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+				                                 NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+				                               | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL,
 				                               NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE,
 				                               "assume-initrd",
 				                               NULL);
@@ -4717,8 +4731,8 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 			}
 
 			if (   nm_active_connection_get_activation_reason (active) == NM_ACTIVATION_REASON_AUTOCONNECT
-			    && nm_settings_connection_autoconnect_blocked_reason_get (parent_con,
-			                                                              NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST)) {
+			    && NM_FLAGS_HAS (nm_settings_connection_autoconnect_blocked_reason_get (parent_con),
+			                                                                            NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST)) {
 				g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED,
 				             "the parent connection of %s cannot autoactivate because it is blocked due to user request",
 				             nm_device_get_iface (device));
@@ -6552,7 +6566,7 @@ nm_manager_write_device_state (NMManager *self, NMDevice *device, int *out_ifind
 	gboolean perm_hw_addr_is_fake;
 	guint32 route_metric_default_aspired;
 	guint32 route_metric_default_effective;
-	int nm_owned;
+	NMTernary nm_owned;
 	NMDhcpConfig *dhcp_config;
 	const char *next_server = NULL;
 	const char *root_path = NULL;
@@ -6588,7 +6602,9 @@ nm_manager_write_device_state (NMManager *self, NMDevice *device, int *out_ifind
 	if (perm_hw_addr_fake && !perm_hw_addr_is_fake)
 		perm_hw_addr_fake = NULL;
 
-	nm_owned = nm_device_is_software (device) ? nm_device_is_nm_owned (device) : -1;
+	nm_owned =   nm_device_is_software (device)
+	           ? nm_device_is_nm_owned (device)
+	           : NM_TERNARY_DEFAULT;
 
 	route_metric_default_effective = _device_route_metric_get (self, ifindex, NM_DEVICE_TYPE_UNKNOWN,
 	                                                           TRUE, &route_metric_default_aspired);
@@ -7342,12 +7358,19 @@ periodic_update_active_connection_timestamps (gpointer user_data)
 	NMManager *manager = NM_MANAGER (user_data);
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
 	NMActiveConnection *ac;
+	gboolean has_time = FALSE;
+	guint64 t = 0;
 
 	c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) {
-		if (nm_active_connection_get_state (ac) == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) {
-			nm_settings_connection_update_timestamp (nm_active_connection_get_settings_connection (ac),
-			                                         (guint64) time (NULL));
+		if (nm_active_connection_get_state (ac) != NM_ACTIVE_CONNECTION_STATE_ACTIVATED)
+			continue;
+
+		if (!has_time) {
+			t = time (NULL);
+			has_time = TRUE;
 		}
+		nm_settings_connection_update_timestamp (nm_active_connection_get_settings_connection (ac),
+		                                         t);
 	}
 	return G_SOURCE_CONTINUE;
 }
diff --git a/src/org.freedesktop.NetworkManager.conf b/src/org.freedesktop.NetworkManager.conf
index 720c090e..5c2af2e0 100644
--- a/src/org.freedesktop.NetworkManager.conf
+++ b/src/org.freedesktop.NetworkManager.conf
@@ -2,150 +2,110 @@
  "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
  "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
 <busconfig>
-        <policy user="root">
-                <allow own="org.freedesktop.NetworkManager"/>
-                <allow send_destination="org.freedesktop.NetworkManager"/>
-
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.PPP"/>
-
-                <allow send_interface="org.freedesktop.NetworkManager.SecretAgent"/>
-                <!-- These are there because some broken policies do
-                     <deny send_interface="..." /> (see dbus-daemon(8) for details).
-                     This seems to override that for the known VPN plugins.
-                  -->
-                <allow send_destination="org.freedesktop.NetworkManager.openconnect"/>
-                <allow send_destination="org.freedesktop.NetworkManager.openswan"/>
-                <allow send_destination="org.freedesktop.NetworkManager.openvpn"/>
-                <allow send_destination="org.freedesktop.NetworkManager.pptp"/>
-                <allow send_destination="org.freedesktop.NetworkManager.vpnc"/>
-                <allow send_destination="org.freedesktop.NetworkManager.ssh"/>
-                <allow send_destination="org.freedesktop.NetworkManager.iodine"/>
-                <allow send_destination="org.freedesktop.NetworkManager.l2tp"/>
-                <allow send_destination="org.freedesktop.NetworkManager.libreswan"/>
-                <allow send_destination="org.freedesktop.NetworkManager.fortisslvpn"/>
-                <allow send_destination="org.freedesktop.NetworkManager.strongswan"/>
-                <allow send_interface="org.freedesktop.NetworkManager.VPN.Plugin"/>
-
-                <allow send_destination="org.fedoraproject.FirewallD1"/>
-
-                <!-- Allow the custom name for the dnsmasq instance spawned by NM
-                     from the dns dnsmasq plugin to own its dbus name, and for
-                     messages to be sent to it.
-                 -->
-                <allow own="org.freedesktop.NetworkManager.dnsmasq"/>
-                <allow send_destination="org.freedesktop.NetworkManager.dnsmasq"/>
-        </policy>
-        <policy context="default">
-                <deny own="org.freedesktop.NetworkManager"/>
-
-                <deny send_destination="org.freedesktop.NetworkManager"/>
-
-                <!-- Basic D-Bus API stuff -->
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.DBus.Introspectable"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.DBus.Properties"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.DBus.ObjectManager"/>
-
-                <!-- Devices (read-only properties, no methods) -->
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.Device.Adsl"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.Device.Bond"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.Device.Bridge"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.Device.Bluetooth"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.Device.Wired"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.Device.Generic"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.Device.Gre"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.Device.Infiniband"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.Device.Macvlan"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.Device.Modem"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.Device.OlpcMesh"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.Device.Team"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.Device.Tun"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.Device.Veth"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.Device.Vlan"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.WiMax.Nsp"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.AccessPoint"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.WifiP2PPeer"/>
-
-                <!-- Devices (read-only, no security required) -->
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.Device.WiMax"/>
-
-                <!-- Devices (read/write, secured with PolicyKit) -->
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.Device.Wireless"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.Device.WifiP2P"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.Device"/>
-
-                <!-- Core stuff (read-only properties, no methods) -->
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.Connection.Active"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.DHCP4Config"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.DHCP6Config"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.IP4Config"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.IP6Config"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.VPN.Connection"/>
-
-                <!-- Core stuff (read/write, secured with PolicyKit) -->
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.Settings"/>
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.Settings.Connection"/>
-
-                <!-- Agents; secured with PolicyKit.  Any process can talk to
-                     the AgentManager API, but only NetworkManager can talk
-                     to the agents themselves. -->
-                <allow send_destination="org.freedesktop.NetworkManager"
-                       send_interface="org.freedesktop.NetworkManager.AgentManager"/>
-
-                <!-- Root-only functions -->
-                <deny send_destination="org.freedesktop.NetworkManager"
-                      send_interface="org.freedesktop.NetworkManager"
-                      send_member="SetLogging"/>
-                <deny send_destination="org.freedesktop.NetworkManager"
-                      send_interface="org.freedesktop.NetworkManager"
-                      send_member="Sleep"/>
-                <deny send_destination="org.freedesktop.NetworkManager"
-                      send_interface="org.freedesktop.NetworkManager.Settings"
-                      send_member="LoadConnections"/>
-                <deny send_destination="org.freedesktop.NetworkManager"
-                      send_interface="org.freedesktop.NetworkManager.Settings"
-                      send_member="ReloadConnections"/>
-
-                <deny own="org.freedesktop.NetworkManager.dnsmasq"/>
-                <deny send_destination="org.freedesktop.NetworkManager.dnsmasq"/>
-        </policy>
-
-        <limit name="max_replies_per_connection">1024</limit>
-        <limit name="max_match_rules_per_connection">2048</limit>
+    <policy user="root">
+        <allow own="org.freedesktop.NetworkManager"/>
+        <allow send_destination="org.freedesktop.NetworkManager"/>
+
+        <allow send_destination="org.freedesktop.NetworkManager"
+               send_interface="org.freedesktop.NetworkManager.PPP"/>
+
+        <allow send_interface="org.freedesktop.NetworkManager.SecretAgent"/>
+
+        <!-- These are there because some broken policies do
+             <deny send_interface="..." /> (see dbus-daemon(8) for details).
+             This seems to override that for the known VPN plugins.
+          -->
+        <allow send_destination="org.freedesktop.NetworkManager.openconnect"/>
+        <allow send_destination="org.freedesktop.NetworkManager.openswan"/>
+        <allow send_destination="org.freedesktop.NetworkManager.openvpn"/>
+        <allow send_destination="org.freedesktop.NetworkManager.pptp"/>
+        <allow send_destination="org.freedesktop.NetworkManager.vpnc"/>
+        <allow send_destination="org.freedesktop.NetworkManager.ssh"/>
+        <allow send_destination="org.freedesktop.NetworkManager.iodine"/>
+        <allow send_destination="org.freedesktop.NetworkManager.l2tp"/>
+        <allow send_destination="org.freedesktop.NetworkManager.libreswan"/>
+        <allow send_destination="org.freedesktop.NetworkManager.fortisslvpn"/>
+        <allow send_destination="org.freedesktop.NetworkManager.strongswan"/>
+        <allow send_interface="org.freedesktop.NetworkManager.VPN.Plugin"/>
+
+        <allow send_destination="org.fedoraproject.FirewallD1"/>
+
+        <!-- Allow the custom name for the dnsmasq instance spawned by NM
+             from the dns dnsmasq plugin to own its dbus name, and for
+             messages to be sent to it.
+         -->
+        <allow own="org.freedesktop.NetworkManager.dnsmasq"/>
+        <allow send_destination="org.freedesktop.NetworkManager.dnsmasq"/>
+    </policy>
+    <policy context="default">
+        <deny own="org.freedesktop.NetworkManager"/>
+
+        <deny send_destination="org.freedesktop.NetworkManager"/>
+
+        <!-- Basic D-Bus API stuff -->
+        <allow send_destination="org.freedesktop.NetworkManager"
+               send_interface="org.freedesktop.DBus.Introspectable"/>
+        <allow send_destination="org.freedesktop.NetworkManager"
+               send_interface="org.freedesktop.DBus.Properties"/>
+        <allow send_destination="org.freedesktop.NetworkManager"
+               send_interface="org.freedesktop.DBus.ObjectManager"/>
+
+        <!-- Devices (read-only properties, no methods) -->
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.AccessPoint"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Adsl"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Bluetooth"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Bond"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Bridge"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Generic"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Gre"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Infiniband"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Macvlan"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Modem"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.OlpcMesh"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Team"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Tun"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Veth"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Vlan"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Wired"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.WiMax.Nsp"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.WifiP2PPeer"/>
+
+        <!-- Devices (read-only, no security required) -->
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.WiMax"/>
+
+        <!-- Devices (read/write, secured with PolicyKit) -->
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.Wireless"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device.WifiP2P"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Device"/>
+
+        <!-- Core stuff (read-only properties, no methods) -->
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Connection.Active"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.DHCP4Config"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.DHCP6Config"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.IP4Config"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.IP6Config"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.VPN.Connection"/>
+
+        <!-- Core stuff (read/write, secured with PolicyKit) -->
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Settings"/>
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Settings.Connection"/>
+
+        <!-- Agents; secured with PolicyKit.  Any process can talk to
+             the AgentManager API, but only NetworkManager can talk
+             to the agents themselves. -->
+        <allow send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.AgentManager"/>
+
+        <!-- Root-only functions -->
+        <deny send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager"          send_member="SetLogging"/>
+        <deny send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager"          send_member="Sleep"/>
+        <deny send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Settings" send_member="LoadConnections"/>
+        <deny send_destination="org.freedesktop.NetworkManager" send_interface="org.freedesktop.NetworkManager.Settings" send_member="ReloadConnections"/>
+
+        <deny own="org.freedesktop.NetworkManager.dnsmasq"/>
+        <deny send_destination="org.freedesktop.NetworkManager.dnsmasq"/>
+    </policy>
+
+    <limit name="max_replies_per_connection">1024</limit>
+    <limit name="max_match_rules_per_connection">2048</limit>
 </busconfig>
-
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index 4a81dc49..d9cf2de5 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -268,7 +268,6 @@ link_add_pre (NMPlatform *platform,
 		g_assert (address_len == 0);
 
 	device->obj = o;
-	device->udi = g_strdup_printf ("fake:%d", ifindex);
 	device->ip6_lladdr = *nmtst_inet6_from_string (ip6_lladdr);
 
 	return device;
@@ -420,7 +419,6 @@ link_delete (NMPlatform *platform, int ifindex)
 		return FALSE;
 
 	obj_old = g_steal_pointer (&device->obj);
-	nm_clear_g_free (&device->udi);
 
 	cache_op = nmp_cache_remove (nm_platform_get_cache (platform),
 	                             obj_old,
@@ -633,16 +631,6 @@ link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 	return 0;
 }
 
-static const char *
-link_get_udi (NMPlatform *platform, int ifindex)
-{
-	NMFakePlatformLink *device = link_get (platform, ifindex);
-
-	if (!device)
-		return NULL;
-	return device->udi;
-}
-
 static gboolean
 link_get_driver_info (NMPlatform *platform,
                       int ifindex,
@@ -1345,7 +1333,6 @@ finalize (GObject *object)
 	for (i = 0; i < priv->links->len; i++) {
 		NMFakePlatformLink *device = &g_array_index (priv->links, NMFakePlatformLink, i);
 
-		g_free (device->udi);
 		nm_clear_pointer (&device->obj, nmp_object_unref);
 	}
 	g_array_unref (priv->links);
@@ -1371,8 +1358,6 @@ nm_fake_platform_class_init (NMFakePlatformClass *klass)
 	platform_class->link_add = link_add;
 	platform_class->link_delete = link_delete;
 
-	platform_class->link_get_udi = link_get_udi;
-
 	platform_class->link_set_up = link_set_up;
 	platform_class->link_set_down = link_set_down;
 	platform_class->link_set_arp = link_set_arp;
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index a721fc7c..e2c45c88 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -279,6 +279,10 @@ struct _ifla_vf_vlan_info {
 
 /*****************************************************************************/
 
+#define PSCHED_TIME_UNITS_PER_SEC 1000000
+
+/*****************************************************************************/
+
 typedef enum {
 	INFINIBAND_ACTION_CREATE_CHILD,
 	INFINIBAND_ACTION_DELETE_CHILD,
@@ -3660,8 +3664,39 @@ _new_from_nl_routing_rule (struct nlmsghdr *nlh, gboolean id_only)
 	return g_steal_pointer (&obj);
 }
 
+static guint32
+psched_tick_to_time (NMPlatform *platform, guint32 tick)
+{
+	static gboolean initialized;
+	static double tick_in_usec = 1;
+
+	if (!initialized) {
+		gs_free char *params = NULL;
+		double clock_factor = 1;
+		guint32 clock_res;
+		guint32 t2us;
+		guint32 us2t;
+
+		initialized = TRUE;
+		params = nm_platform_sysctl_get (platform, NMP_SYSCTL_PATHID_ABSOLUTE ("/proc/net/psched"));
+		if (   !params
+		    || sscanf (params, "%08x%08x%08x", &t2us, &us2t, &clock_res) != 3) {
+			_LOGW ("packet scheduler parameters not available");
+		} else {
+			/* See tc_core_init() in iproute2 */
+			if (clock_res == 1000000000)
+				t2us = us2t;
+
+			clock_factor  = (double) clock_res / PSCHED_TIME_UNITS_PER_SEC;
+			tick_in_usec = (double) t2us / us2t * clock_factor;
+		}
+	}
+
+	return tick / tick_in_usec;
+}
+
 static NMPObject *
-_new_from_nl_qdisc (struct nlmsghdr *nlh, gboolean id_only)
+_new_from_nl_qdisc (NMPlatform *platform, struct nlmsghdr *nlh, gboolean id_only)
 {
 	static const struct nla_policy policy[] = {
 		[TCA_KIND] = { .type = NLA_STRING },
@@ -3669,7 +3704,7 @@ _new_from_nl_qdisc (struct nlmsghdr *nlh, gboolean id_only)
 	};
 	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	const struct tcmsg *tcm;
-	NMPObject *obj;
+	nm_auto_nmpobj NMPObject *obj = NULL;
 
 	if (nlmsg_parse_arr (nlh,
 	                     sizeof (*tcm),
@@ -3700,42 +3735,77 @@ _new_from_nl_qdisc (struct nlmsghdr *nlh, gboolean id_only)
 		struct nlattr *options_attr;
 		int remaining;
 
-		nla_for_each_nested (options_attr, tb[TCA_OPTIONS], remaining) {
-			if (nla_len (options_attr) < sizeof (uint32_t))
-				continue;
+		if (nm_streq0 (obj->qdisc.kind, "sfq")) {
+			struct tc_sfq_qopt_v1 opt;
+
+			if (tb[TCA_OPTIONS]->nla_len >= nla_attr_size (sizeof (opt))) {
+				memcpy (&opt, nla_data (tb[TCA_OPTIONS]), sizeof (opt));
+				obj->qdisc.sfq.quantum = opt.v0.quantum;
+				obj->qdisc.sfq.perturb_period = opt.v0.perturb_period;
+				obj->qdisc.sfq.limit = opt.v0.limit;
+				obj->qdisc.sfq.divisor = opt.v0.divisor;
+				obj->qdisc.sfq.flows = opt.v0.flows;
+				obj->qdisc.sfq.depth = opt.depth;
+			}
+		} else if (nm_streq0 (obj->qdisc.kind, "tbf")) {
+			static const struct nla_policy tbf_policy[] = {
+				[TCA_TBF_PARMS]     = { .minlen = sizeof (struct tc_tbf_qopt) },
+				[TCA_TBF_RATE64]    = { .type = NLA_U64 },
+			};
+			struct nlattr *tbf_tb[G_N_ELEMENTS (tbf_policy)];
+			struct tc_tbf_qopt opt;
 
-			if (nm_streq0 (obj->qdisc.kind, "fq_codel")) {
-				switch (nla_type (options_attr)) {
-				case TCA_FQ_CODEL_LIMIT:
-					obj->qdisc.fq_codel.limit = nla_get_u32 (options_attr);
-					break;
-				case TCA_FQ_CODEL_FLOWS:
-					obj->qdisc.fq_codel.flows = nla_get_u32 (options_attr);
-					break;
-				case TCA_FQ_CODEL_TARGET:
-					obj->qdisc.fq_codel.target = nla_get_u32 (options_attr);
-					break;
-				case TCA_FQ_CODEL_INTERVAL:
-					obj->qdisc.fq_codel.interval = nla_get_u32 (options_attr);
-					break;
-				case TCA_FQ_CODEL_QUANTUM:
-					obj->qdisc.fq_codel.quantum = nla_get_u32 (options_attr);
-					break;
-				case TCA_FQ_CODEL_CE_THRESHOLD:
-					obj->qdisc.fq_codel.ce_threshold = nla_get_u32 (options_attr);
-					break;
-				case TCA_FQ_CODEL_MEMORY_LIMIT:
-					obj->qdisc.fq_codel.memory_limit = nla_get_u32 (options_attr);
-					break;
-				case TCA_FQ_CODEL_ECN:
-					obj->qdisc.fq_codel.ecn = !!nla_get_u32 (options_attr);
-					break;
+			if (nla_parse_nested_arr (tbf_tb, tb[TCA_OPTIONS], tbf_policy) < 0)
+				return NULL;
+			if (!tbf_tb[TCA_TBF_PARMS])
+				return NULL;
+
+			nla_memcpy_checked_size (&opt, tbf_tb[TCA_TBF_PARMS], sizeof (opt));
+			obj->qdisc.tbf.rate = opt.rate.rate;
+			if (tbf_tb[TCA_TBF_RATE64])
+				obj->qdisc.tbf.rate = nla_get_u64 (tbf_tb[TCA_TBF_RATE64]);
+			obj->qdisc.tbf.burst = ((double) obj->qdisc.tbf.rate *
+			                       psched_tick_to_time (platform, opt.buffer)) /
+			                       PSCHED_TIME_UNITS_PER_SEC;
+			obj->qdisc.tbf.limit = opt.limit;
+		} else {
+			nla_for_each_nested (options_attr, tb[TCA_OPTIONS], remaining) {
+				if (nla_len (options_attr) < sizeof (uint32_t))
+					continue;
+
+				if (nm_streq0 (obj->qdisc.kind, "fq_codel")) {
+					switch (nla_type (options_attr)) {
+					case TCA_FQ_CODEL_LIMIT:
+						obj->qdisc.fq_codel.limit = nla_get_u32 (options_attr);
+						break;
+					case TCA_FQ_CODEL_FLOWS:
+						obj->qdisc.fq_codel.flows = nla_get_u32 (options_attr);
+						break;
+					case TCA_FQ_CODEL_TARGET:
+						obj->qdisc.fq_codel.target = nla_get_u32 (options_attr);
+						break;
+					case TCA_FQ_CODEL_INTERVAL:
+						obj->qdisc.fq_codel.interval = nla_get_u32 (options_attr);
+						break;
+					case TCA_FQ_CODEL_QUANTUM:
+						obj->qdisc.fq_codel.quantum = nla_get_u32 (options_attr);
+						break;
+					case TCA_FQ_CODEL_CE_THRESHOLD:
+						obj->qdisc.fq_codel.ce_threshold = nla_get_u32 (options_attr);
+						break;
+					case TCA_FQ_CODEL_MEMORY_LIMIT:
+						obj->qdisc.fq_codel.memory_limit = nla_get_u32 (options_attr);
+						break;
+					case TCA_FQ_CODEL_ECN:
+						obj->qdisc.fq_codel.ecn = !!nla_get_u32 (options_attr);
+						break;
+					}
 				}
 			}
 		}
 	}
 
-	return obj;
+	return g_steal_pointer (&obj);
 }
 
 static NMPObject *
@@ -3811,7 +3881,7 @@ nmp_object_new_from_nl (NMPlatform *platform, const NMPCache *cache, struct nl_m
 	case RTM_NEWQDISC:
 	case RTM_DELQDISC:
 	case RTM_GETQDISC:
-		return _new_from_nl_qdisc (msghdr, id_only);
+		return _new_from_nl_qdisc (platform, msghdr, id_only);
 	case RTM_NEWTFILTER:
 	case RTM_DELTFILTER:
 	case RTM_GETTFILTER:
@@ -4644,36 +4714,72 @@ _nl_msg_new_qdisc (int nlmsg_type,
 		.tcm_info = qdisc->info,
 	};
 
-	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
+	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags | NMP_NLM_FLAG_F_ECHO);
 
 	if (nlmsg_append_struct (msg, &tcm) < 0)
 		goto nla_put_failure;
 
 	NLA_PUT_STRING (msg, TCA_KIND, qdisc->kind);
 
-	if (!(tc_options = nla_nest_start (msg, TCA_OPTIONS)))
-		goto nla_put_failure;
+	if (nm_streq (qdisc->kind, "sfq")) {
+		struct tc_sfq_qopt_v1 opt = { };
 
-	if (nm_streq (qdisc->kind, "fq_codel")) {
-		if (qdisc->fq_codel.limit)
-			NLA_PUT_U32 (msg, TCA_FQ_CODEL_LIMIT, qdisc->fq_codel.limit);
-		if (qdisc->fq_codel.flows)
-			NLA_PUT_U32 (msg, TCA_FQ_CODEL_FLOWS, qdisc->fq_codel.flows);
-		if (qdisc->fq_codel.target)
-			NLA_PUT_U32 (msg, TCA_FQ_CODEL_TARGET, qdisc->fq_codel.target);
-		if (qdisc->fq_codel.interval)
-			NLA_PUT_U32 (msg, TCA_FQ_CODEL_INTERVAL, qdisc->fq_codel.interval);
-		if (qdisc->fq_codel.quantum)
-			NLA_PUT_U32 (msg, TCA_FQ_CODEL_QUANTUM, qdisc->fq_codel.quantum);
-		if (qdisc->fq_codel.ce_threshold != NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED)
-			NLA_PUT_U32 (msg, TCA_FQ_CODEL_CE_THRESHOLD, qdisc->fq_codel.ce_threshold);
-		if (qdisc->fq_codel.memory_limit != NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET)
-			NLA_PUT_U32 (msg, TCA_FQ_CODEL_MEMORY_LIMIT, qdisc->fq_codel.memory_limit);
-		if (qdisc->fq_codel.ecn)
-			NLA_PUT_U32 (msg, TCA_FQ_CODEL_ECN, qdisc->fq_codel.ecn);
-	}
+		opt.v0.quantum = qdisc->sfq.quantum;
+		opt.v0.limit = qdisc->sfq.limit;
+		opt.v0.perturb_period = qdisc->sfq.perturb_period;
+		opt.v0.flows = qdisc->sfq.flows;
+		opt.v0.divisor = qdisc->sfq.divisor;
+		opt.depth = qdisc->sfq.depth;
 
-	nla_nest_end (msg, tc_options);
+		NLA_PUT (msg, TCA_OPTIONS, sizeof (opt), &opt);
+	} else if (nm_streq (qdisc->kind, "tbf")) {
+		struct tc_tbf_qopt opt = { };
+
+		if (!(tc_options = nla_nest_start (msg, TCA_OPTIONS)))
+			goto nla_put_failure;
+
+		opt.rate.rate =   (qdisc->tbf.rate >= (1ULL << 32))
+		                ? ~0U
+		                : (guint32) qdisc->tbf.rate;
+		if (qdisc->tbf.limit)
+			opt.limit = qdisc->tbf.limit;
+		else if (qdisc->tbf.latency) {
+			opt.limit = qdisc->tbf.rate * (double) qdisc->tbf.latency
+			            / PSCHED_TIME_UNITS_PER_SEC
+			            + qdisc->tbf.burst;
+		}
+
+		NLA_PUT (msg, TCA_TBF_PARMS, sizeof (opt), &opt);
+		if (qdisc->tbf.rate >= (1ULL << 32))
+			NLA_PUT_U64 (msg, TCA_TBF_RATE64, qdisc->tbf.rate);
+		NLA_PUT_U32 (msg, TCA_TBF_BURST, qdisc->tbf.burst);
+
+		nla_nest_end (msg, tc_options);
+	} else {
+		if (!(tc_options = nla_nest_start (msg, TCA_OPTIONS)))
+			goto nla_put_failure;
+
+		if (nm_streq (qdisc->kind, "fq_codel")) {
+			if (qdisc->fq_codel.limit)
+				NLA_PUT_U32 (msg, TCA_FQ_CODEL_LIMIT, qdisc->fq_codel.limit);
+			if (qdisc->fq_codel.flows)
+				NLA_PUT_U32 (msg, TCA_FQ_CODEL_FLOWS, qdisc->fq_codel.flows);
+			if (qdisc->fq_codel.target)
+				NLA_PUT_U32 (msg, TCA_FQ_CODEL_TARGET, qdisc->fq_codel.target);
+			if (qdisc->fq_codel.interval)
+				NLA_PUT_U32 (msg, TCA_FQ_CODEL_INTERVAL, qdisc->fq_codel.interval);
+			if (qdisc->fq_codel.quantum)
+				NLA_PUT_U32 (msg, TCA_FQ_CODEL_QUANTUM, qdisc->fq_codel.quantum);
+			if (qdisc->fq_codel.ce_threshold != NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED)
+				NLA_PUT_U32 (msg, TCA_FQ_CODEL_CE_THRESHOLD, qdisc->fq_codel.ce_threshold);
+			if (qdisc->fq_codel.memory_limit != NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET)
+				NLA_PUT_U32 (msg, TCA_FQ_CODEL_MEMORY_LIMIT, qdisc->fq_codel.memory_limit);
+			if (qdisc->fq_codel.ecn)
+				NLA_PUT_U32 (msg, TCA_FQ_CODEL_ECN, qdisc->fq_codel.ecn);
+		}
+
+		nla_nest_end (msg, tc_options);
+	}
 
 	return g_steal_pointer (&msg);
 
@@ -4697,7 +4803,7 @@ _nl_msg_new_tfilter (int nlmsg_type,
 		.tcm_info = tfilter->info,
 	};
 
-	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
+	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags | NMP_NLM_FLAG_F_ECHO);
 
 	if (nlmsg_append_struct (msg, &tcm) < 0)
 		goto nla_put_failure;
@@ -4789,7 +4895,8 @@ _genl_sock (NMLinuxPlatform *platform)
 			nm_assert (!_pathid); \
 			nm_assert (_path[0] == '/'); \
 			nm_assert (   g_str_has_prefix (_path, "/proc/sys/") \
-			           || g_str_has_prefix (_path, "/sys/")); \
+			           || g_str_has_prefix (_path, "/sys/") \
+			           || g_str_has_prefix (_path, "/proc/net")); \
 		} else { \
 			nm_assert (_pathid && _pathid[0] && _pathid[0] != '/'); \
 			nm_assert (_path[0] != '/'); \
@@ -7058,18 +7165,6 @@ link_set_noarp (NMPlatform *platform, int ifindex)
 	return (link_change_flags (platform, ifindex, IFF_NOARP, IFF_NOARP) >= 0);
 }
 
-static const char *
-link_get_udi (NMPlatform *platform, int ifindex)
-{
-	const NMPObject *obj = nm_platform_link_get_obj (platform, ifindex, TRUE);
-
-	if (   !obj
-	    || !obj->_link.netlink.is_in_netlink
-	    || !obj->_link.udev.device)
-		return NULL;
-	return udev_device_get_syspath (obj->_link.udev.device);
-}
-
 static int
 link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enabled)
 {
@@ -9211,8 +9306,6 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_set_arp = link_set_arp;
 	platform_class->link_set_noarp = link_set_noarp;
 
-	platform_class->link_get_udi = link_get_udi;
-
 	platform_class->link_set_user_ipv6ll_enabled = link_set_user_ipv6ll_enabled;
 	platform_class->link_set_token = link_set_token;
 
diff --git a/src/platform/nm-netlink.h b/src/platform/nm-netlink.h
index 0ac5b3b7..a5124f52 100644
--- a/src/platform/nm-netlink.h
+++ b/src/platform/nm-netlink.h
@@ -77,17 +77,13 @@ struct nla_policy {
 /* static asserts that @tb and @policy are suitable arguments to nla_parse(). */
 #define _nl_static_assert_tb(tb, policy) \
 	G_STMT_START { \
-		\
 		G_STATIC_ASSERT_EXPR (G_N_ELEMENTS (tb) > 0); \
 		\
-		/* we allow @policy to be either NULL or a C array. */ \
-		G_STATIC_ASSERT_EXPR (   sizeof (policy) == sizeof (NULL) \
-		                      || G_N_ELEMENTS (tb) == (sizeof (policy) / sizeof (struct nla_policy))); \
-		\
-		/* For above check to work, we don't support policy being an array with same size as
-		 * sizeof(NULL), otherwise, the compile time check breaks down. */ \
-		G_STATIC_ASSERT_EXPR (sizeof (NULL) != G_N_ELEMENTS (tb) * sizeof (struct nla_policy)); \
-		\
+		/* We allow @policy to be either a C array or NULL. The sizeof()
+		 * must either match the expected array size or the sizeof(NULL),
+		 * but not both. */ \
+		G_STATIC_ASSERT_EXPR (  (sizeof (policy) == G_N_ELEMENTS (tb) * sizeof (struct nla_policy)) \
+		                      ^ (sizeof (policy) == sizeof (NULL))); \
 	} G_STMT_END
 
 /*****************************************************************************/
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c
index 98884cd7..6c14bc5c 100644
--- a/src/platform/nm-platform-utils.c
+++ b/src/platform/nm-platform-utils.c
@@ -264,16 +264,20 @@ out:
 
 static
 NM_UTILS_ENUM2STR_DEFINE (_ethtool_cmd_to_string, guint32,
+	NM_UTILS_ENUM2STR (ETHTOOL_GCOALESCE,  "ETHTOOL_GCOALESCE"),
 	NM_UTILS_ENUM2STR (ETHTOOL_GDRVINFO,   "ETHTOOL_GDRVINFO"),
 	NM_UTILS_ENUM2STR (ETHTOOL_GFEATURES,  "ETHTOOL_GFEATURES"),
 	NM_UTILS_ENUM2STR (ETHTOOL_GLINK,      "ETHTOOL_GLINK"),
 	NM_UTILS_ENUM2STR (ETHTOOL_GPERMADDR,  "ETHTOOL_GPERMADDR"),
+	NM_UTILS_ENUM2STR (ETHTOOL_GRINGPARAM, "ETHTOOL_GRINGPARAM"),
 	NM_UTILS_ENUM2STR (ETHTOOL_GSET,       "ETHTOOL_GSET"),
 	NM_UTILS_ENUM2STR (ETHTOOL_GSSET_INFO, "ETHTOOL_GSSET_INFO"),
 	NM_UTILS_ENUM2STR (ETHTOOL_GSTATS,     "ETHTOOL_GSTATS"),
 	NM_UTILS_ENUM2STR (ETHTOOL_GSTRINGS,   "ETHTOOL_GSTRINGS"),
 	NM_UTILS_ENUM2STR (ETHTOOL_GWOL,       "ETHTOOL_GWOL"),
+	NM_UTILS_ENUM2STR (ETHTOOL_SCOALESCE,  "ETHTOOL_SCOALESCE"),
 	NM_UTILS_ENUM2STR (ETHTOOL_SFEATURES,  "ETHTOOL_SFEATURES"),
+	NM_UTILS_ENUM2STR (ETHTOOL_SRINGPARAM, "ETHTOOL_SRINGPARAM"),
 	NM_UTILS_ENUM2STR (ETHTOOL_SSET,       "ETHTOOL_SSET"),
 	NM_UTILS_ENUM2STR (ETHTOOL_SWOL,       "ETHTOOL_SWOL"),
 );
@@ -516,8 +520,8 @@ _ASSERT_ethtool_feature_infos (void)
 		for (k = 0; k < i; k++)
 			g_assert (inf->ethtool_id != _ethtool_feature_infos[k].ethtool_id);
 
-		g_assert (!found[inf->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST]);
-		found[inf->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST] = TRUE;
+		g_assert (!found[_NM_ETHTOOL_ID_FEATURE_AS_IDX (inf->ethtool_id)]);
+		found[_NM_ETHTOOL_ID_FEATURE_AS_IDX (inf->ethtool_id)] = TRUE;
 
 		kstate.idx_kernel_name = inf->n_kernel_names - 1;
 		g_assert ((guint) kstate.idx_kernel_name == (guint) (inf->n_kernel_names - 1));
@@ -607,13 +611,13 @@ ethtool_get_features (SocketHandle *shandle)
 
 				nm_assert (states_plist_n < N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS (_ethtool_feature_infos));
 
-				if (!states->states_indexed[info->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST])
-					states->states_indexed[info->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST] = &states_plist0[states_plist_n];
+				if (!states->states_indexed[_NM_ETHTOOL_ID_FEATURE_AS_IDX (info->ethtool_id)])
+					states->states_indexed[_NM_ETHTOOL_ID_FEATURE_AS_IDX (info->ethtool_id)] = &states_plist0[states_plist_n];
 				((const NMEthtoolFeatureState **) states_plist0)[states_plist_n] = kstate;
 				states_plist_n++;
 			}
 
-			if (states && states->states_indexed[info->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST]) {
+			if (states && states->states_indexed[_NM_ETHTOOL_ID_FEATURE_AS_IDX (info->ethtool_id)]) {
 				nm_assert (states_plist_n < N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS (_ethtool_feature_infos));
 				nm_assert (!states_plist0[states_plist_n]);
 				states_plist_n++;
@@ -804,6 +808,224 @@ nmp_utils_ethtool_set_features (int ifindex,
 	return success;
 }
 
+static gboolean
+ethtool_get_coalesce (SocketHandle *shandle,
+                      NMEthtoolCoalesceState *coalesce)
+{
+	struct ethtool_coalesce eth_data;
+
+	eth_data.cmd = ETHTOOL_GCOALESCE;
+
+	if (_ethtool_call_handle (shandle,
+	                          &eth_data,
+	                          sizeof (struct ethtool_coalesce)) != 0)
+		return FALSE;
+
+	*coalesce = (NMEthtoolCoalesceState) {
+	    .s = {
+	        [_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_RX_USECS)]          = eth_data.rx_coalesce_usecs,
+	        [_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_RX_FRAMES)]         = eth_data.rx_max_coalesced_frames,
+	        [_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_RX_USECS_IRQ)]      = eth_data.rx_coalesce_usecs_irq,
+	        [_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_RX_FRAMES_IRQ)]     = eth_data.rx_max_coalesced_frames_irq,
+	        [_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_TX_USECS)]          = eth_data.tx_coalesce_usecs,
+	        [_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_TX_FRAMES)]         = eth_data.tx_max_coalesced_frames,
+	        [_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_TX_USECS_IRQ)]      = eth_data.tx_coalesce_usecs_irq,
+	        [_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_TX_FRAMES_IRQ)]     = eth_data.tx_max_coalesced_frames_irq,
+	        [_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_STATS_BLOCK_USECS)] = eth_data.stats_block_coalesce_usecs,
+	        [_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX)]       = eth_data.use_adaptive_rx_coalesce,
+	        [_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX)]       = eth_data.use_adaptive_tx_coalesce,
+	        [_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_PKT_RATE_LOW)]      = eth_data.pkt_rate_low,
+	        [_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_RX_USECS_LOW)]      = eth_data.rx_coalesce_usecs_low,
+	        [_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_RX_FRAMES_LOW)]     = eth_data.rx_max_coalesced_frames_low,
+	        [_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW)]      = eth_data.tx_coalesce_usecs_low,
+	        [_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_TX_FRAMES_LOW)]     = eth_data.tx_max_coalesced_frames_low,
+	        [_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_PKT_RATE_HIGH)]     = eth_data.pkt_rate_high,
+	        [_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_RX_USECS_HIGH)]     = eth_data.rx_coalesce_usecs_high,
+	        [_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_RX_FRAMES_HIGH)]    = eth_data.rx_max_coalesced_frames_high,
+	        [_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_TX_USECS_HIGH)]     = eth_data.tx_coalesce_usecs_high,
+	        [_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_TX_FRAMES_HIGH)]    = eth_data.tx_max_coalesced_frames_high,
+	        [_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_SAMPLE_INTERVAL)]   = eth_data.rate_sample_interval,
+	    }
+	};
+	return TRUE;
+}
+
+
+gboolean
+nmp_utils_ethtool_get_coalesce (int ifindex,
+                                NMEthtoolCoalesceState *coalesce)
+{
+	nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT (ifindex);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (coalesce, FALSE);
+
+	if (!ethtool_get_coalesce (&shandle, coalesce)) {
+		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failure getting coalesce settings",
+		              ifindex,
+		              "get-coalesce");
+		return FALSE;
+	}
+
+	nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: retrieved kernel coalesce settings",
+	              ifindex,
+	              "get-coalesce");
+	return TRUE;
+}
+
+static gboolean
+ethtool_set_coalesce (SocketHandle *shandle,
+                      const NMEthtoolCoalesceState *coalesce)
+{
+	struct ethtool_coalesce eth_data;
+	gboolean success;
+
+	nm_assert (shandle);
+	nm_assert (coalesce);
+
+	eth_data = (struct ethtool_coalesce) {
+		.cmd = ETHTOOL_SCOALESCE,
+		.rx_coalesce_usecs            = coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_RX_USECS)],
+		.rx_max_coalesced_frames      = coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_RX_FRAMES)],
+		.rx_coalesce_usecs_irq        = coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_RX_USECS_IRQ)],
+		.rx_max_coalesced_frames_irq  = coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_RX_FRAMES_IRQ)],
+		.tx_coalesce_usecs            = coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_TX_USECS)],
+		.tx_max_coalesced_frames      = coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_TX_FRAMES)],
+		.tx_coalesce_usecs_irq        = coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_TX_USECS_IRQ)],
+		.tx_max_coalesced_frames_irq  = coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_TX_FRAMES_IRQ)],
+		.stats_block_coalesce_usecs   = coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_STATS_BLOCK_USECS)],
+		.use_adaptive_rx_coalesce     = coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX)],
+		.use_adaptive_tx_coalesce     = coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX)],
+		.pkt_rate_low                 = coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_PKT_RATE_LOW)],
+		.rx_coalesce_usecs_low        = coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_RX_USECS_LOW)],
+		.rx_max_coalesced_frames_low  = coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_RX_FRAMES_LOW)],
+		.tx_coalesce_usecs_low        = coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW)],
+		.tx_max_coalesced_frames_low  = coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_TX_FRAMES_LOW)],
+		.pkt_rate_high                = coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_PKT_RATE_HIGH)],
+		.rx_coalesce_usecs_high       = coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_RX_USECS_HIGH)],
+		.rx_max_coalesced_frames_high = coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_RX_FRAMES_HIGH)],
+		.tx_coalesce_usecs_high       = coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_TX_USECS_HIGH)],
+		.tx_max_coalesced_frames_high = coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_TX_FRAMES_HIGH)],
+		.rate_sample_interval         = coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX (NM_ETHTOOL_ID_COALESCE_SAMPLE_INTERVAL)],
+	};
+
+	success = (_ethtool_call_handle (shandle,
+	                                 &eth_data,
+	                                 sizeof (struct ethtool_coalesce)) == 0);
+	return success;
+}
+
+gboolean
+nmp_utils_ethtool_set_coalesce (int ifindex,
+                                const NMEthtoolCoalesceState *coalesce)
+{
+	nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT (ifindex);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (coalesce, FALSE);
+
+	if (!ethtool_set_coalesce (&shandle, coalesce)) {
+		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failure setting coalesce settings",
+		              ifindex,
+		              "set-coalesce");
+		return FALSE;
+	}
+
+	nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: set kernel coalesce settings",
+	              ifindex,
+	              "set-coalesce");
+	return TRUE;
+}
+
+static gboolean
+ethtool_get_ring (SocketHandle *shandle,
+                  NMEthtoolRingState *ring)
+{
+	struct ethtool_ringparam eth_data;
+
+	eth_data.cmd = ETHTOOL_GRINGPARAM;
+
+	if (_ethtool_call_handle (shandle,
+	                          &eth_data,
+	                          sizeof (struct ethtool_ringparam)) != 0)
+		return FALSE;
+
+	ring->rx_pending       = eth_data.rx_pending;
+	ring->rx_jumbo_pending = eth_data.rx_jumbo_pending;
+	ring->rx_mini_pending  = eth_data.rx_mini_pending;
+	ring->tx_pending       = eth_data.tx_pending;
+
+	return TRUE;
+}
+
+gboolean
+nmp_utils_ethtool_get_ring (int ifindex,
+                            NMEthtoolRingState *ring)
+{
+	nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT (ifindex);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (ring, FALSE);
+
+	if (!ethtool_get_ring (&shandle, ring)) {
+		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failure getting ring settings",
+		              ifindex,
+		              "get-ring");
+		return FALSE;
+	}
+
+	nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: retrieved kernel ring settings",
+	              ifindex,
+	              "get-ring");
+	return TRUE;
+}
+
+static gboolean
+ethtool_set_ring (SocketHandle *shandle,
+                  const NMEthtoolRingState *ring)
+{
+	gboolean success;
+	struct ethtool_ringparam eth_data;
+
+	g_return_val_if_fail (shandle, FALSE);
+	g_return_val_if_fail (ring, FALSE);
+
+	eth_data = (struct ethtool_ringparam) {
+		.cmd = ETHTOOL_SRINGPARAM,
+		.rx_pending       = ring->rx_pending,
+		.rx_jumbo_pending = ring->rx_jumbo_pending,
+		.rx_mini_pending  = ring->rx_mini_pending,
+		.tx_pending       = ring->tx_pending,
+	};
+
+	success = (_ethtool_call_handle (shandle,
+	                                 &eth_data,
+	                                 sizeof (struct ethtool_ringparam)) == 0);
+	return success;
+}
+
+gboolean
+nmp_utils_ethtool_set_ring (int ifindex,
+                            const NMEthtoolRingState *ring)
+{
+	nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT (ifindex);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (ring, FALSE);
+
+	if (!ethtool_set_ring (&shandle, ring)) {
+		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failure setting ring settings",
+		              ifindex,
+		              "set-ring");
+		return FALSE;
+	}
+
+	nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: set kernel ring settings",
+	              ifindex,
+	              "set-ring");
+	return TRUE;
+}
+
 /*****************************************************************************/
 
 gboolean
diff --git a/src/platform/nm-platform-utils.h b/src/platform/nm-platform-utils.h
index a62d828c..062c8597 100644
--- a/src/platform/nm-platform-utils.h
+++ b/src/platform/nm-platform-utils.h
@@ -92,6 +92,29 @@ gboolean nmp_utils_ethtool_set_features (int ifindex,
                                          const NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
                                          gboolean do_set /* or reset */);
 
+struct _NMEthtoolCoalesceState {
+	guint32 s[_NM_ETHTOOL_ID_COALESCE_NUM /* indexed by (NMEthtoolID - _NM_ETHTOOL_ID_COALESCE_FIRST) */];
+};
+
+gboolean nmp_utils_ethtool_get_coalesce (int ifindex,
+                                         NMEthtoolCoalesceState *coalesce);
+
+gboolean nmp_utils_ethtool_set_coalesce (int ifindex,
+                                         const NMEthtoolCoalesceState *coalesce);
+
+struct _NMEthtoolRingState {
+	guint32 rx_pending;
+	guint32 rx_mini_pending;
+	guint32 rx_jumbo_pending;
+	guint32 tx_pending;
+};
+
+gboolean nmp_utils_ethtool_get_ring (int ifindex,
+                                     NMEthtoolRingState *ring);
+
+gboolean nmp_utils_ethtool_set_ring (int ifindex,
+                                     const NMEthtoolRingState *ring);
+
 /*****************************************************************************/
 
 gboolean nmp_utils_mii_supports_carrier_detect (int ifindex);
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index 9bbc3745..c023f435 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -41,6 +41,14 @@ G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPAddress, address_ptr) == G_STRUCT_
 G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OFFSET (NMPlatformIP4Route, network));
 G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OFFSET (NMPlatformIP6Route, network));
 
+G_STATIC_ASSERT (_nm_alignof (NMPlatformIPRoute) == _nm_alignof (NMPlatformIP4Route));
+G_STATIC_ASSERT (_nm_alignof (NMPlatformIPRoute) == _nm_alignof (NMPlatformIP6Route));
+G_STATIC_ASSERT (_nm_alignof (NMPlatformIPRoute) == _nm_alignof (NMPlatformIPXRoute));
+
+G_STATIC_ASSERT (_nm_alignof (NMPlatformIPAddress) == _nm_alignof (NMPlatformIP4Address));
+G_STATIC_ASSERT (_nm_alignof (NMPlatformIPAddress) == _nm_alignof (NMPlatformIP6Address));
+G_STATIC_ASSERT (_nm_alignof (NMPlatformIPAddress) == _nm_alignof (NMPlatformIPXAddress));
+
 /*****************************************************************************/
 
 G_STATIC_ASSERT (sizeof ( ((NMPLinkAddress *) NULL)->data ) == NM_UTILS_HWADDR_LEN_MAX);
@@ -310,6 +318,7 @@ NM_UTILS_LOOKUP_STR_DEFINE (_nmp_nlm_flag_to_string_lookup, NMPNlmFlags,
 	NM_UTILS_LOOKUP_ITEM_IGNORE (NMP_NLM_FLAG_F_APPEND),
 	NM_UTILS_LOOKUP_ITEM_IGNORE (NMP_NLM_FLAG_FMASK),
 	NM_UTILS_LOOKUP_ITEM_IGNORE (NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE),
+	NM_UTILS_LOOKUP_ITEM_IGNORE (NMP_NLM_FLAG_F_ECHO),
 );
 
 #define _nmp_nlm_flag_to_string(flags) \
@@ -1403,6 +1412,27 @@ nm_platform_link_get_type_name (NMPlatform *self, int ifindex)
 	return obj->link.kind ?: "unknown";
 }
 
+static gboolean
+link_get_udev_property (NMPlatform *self,
+                        int ifindex,
+                        const char *name,
+                        const char **out_value)
+{
+	struct udev_device *udevice = NULL;
+	const char *uproperty;
+
+	udevice = nm_platform_link_get_udev_device (self, ifindex);
+	if (!udevice)
+		return FALSE;
+
+	uproperty = udev_device_get_property_value (udevice, name);
+	if (!uproperty)
+		return FALSE;
+
+	NM_SET_OUT (out_value, uproperty);
+	return TRUE;
+}
+
 /**
  * nm_platform_link_get_unmanaged:
  * @self: platform instance
@@ -1415,26 +1445,14 @@ nm_platform_link_get_type_name (NMPlatform *self, int ifindex)
 gboolean
 nm_platform_link_get_unmanaged (NMPlatform *self, int ifindex, gboolean *unmanaged)
 {
-	const NMPObject *link;
-	struct udev_device *udevice = NULL;
-	const char *uproperty;
+	const char *value;
 
-	_CHECK_SELF (self, klass, FALSE);
-
-	link = nmp_cache_lookup_link (nm_platform_get_cache (self), ifindex);
-	if (!link)
-		return FALSE;
-
-	udevice = link->_link.udev.device;
-	if (!udevice)
-		return FALSE;
-
-	uproperty = udev_device_get_property_value (udevice, "NM_UNMANAGED");
-	if (!uproperty)
-		return FALSE;
+	if (link_get_udev_property (self, ifindex, "NM_UNMANAGED", &value)) {
+		NM_SET_OUT (unmanaged, nm_udev_utils_property_as_boolean (value));
+		return TRUE;
+	}
 
-	*unmanaged = nm_udev_utils_property_as_boolean (uproperty);
-	return TRUE;
+	return FALSE;
 }
 
 /**
@@ -1571,7 +1589,6 @@ nm_platform_link_set_ipv6_token (NMPlatform *self, int ifindex, NMUtilsIPv6Iface
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex >= 0, FALSE);
-	g_return_val_if_fail (iid.id, FALSE);
 
 	if (klass->link_set_token)
 		return klass->link_set_token (self, ifindex, iid);
@@ -1581,13 +1598,20 @@ nm_platform_link_set_ipv6_token (NMPlatform *self, int ifindex, NMUtilsIPv6Iface
 const char *
 nm_platform_link_get_udi (NMPlatform *self, int ifindex)
 {
-	_CHECK_SELF (self, klass, FALSE);
+	struct udev_device *device;
 
-	g_return_val_if_fail (ifindex >= 0, NULL);
+	device = nm_platform_link_get_udev_device (self, ifindex);
+	return device ? udev_device_get_syspath (device) : NULL;
+}
 
-	if (klass->link_get_udi)
-		return klass->link_get_udi (self, ifindex);
-	return NULL;
+const char *
+nm_platform_link_get_path (NMPlatform *self, int ifindex)
+{
+	const char *value = NULL;
+
+	link_get_udev_property (self, ifindex, "ID_PATH", &value);
+
+	return value;
 }
 
 struct udev_device *
@@ -3214,6 +3238,56 @@ nm_platform_ethtool_set_features (NMPlatform *self,
 	return nmp_utils_ethtool_set_features (ifindex, features, requested, do_set);
 }
 
+gboolean
+nm_platform_ethtool_get_link_coalesce (NMPlatform *self,
+                                       int ifindex,
+                                       NMEthtoolCoalesceState *coalesce)
+{
+	_CHECK_SELF_NETNS (self, klass, netns, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (coalesce, FALSE);
+
+	return nmp_utils_ethtool_get_coalesce (ifindex, coalesce);
+}
+
+gboolean
+nm_platform_ethtool_set_coalesce (NMPlatform *self,
+                                  int ifindex,
+                                  const NMEthtoolCoalesceState *coalesce)
+{
+	_CHECK_SELF_NETNS (self, klass, netns, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	return nmp_utils_ethtool_set_coalesce (ifindex, coalesce);
+}
+
+gboolean
+nm_platform_ethtool_get_link_ring (NMPlatform *self,
+                                   int ifindex,
+                                   NMEthtoolRingState *ring)
+{
+	_CHECK_SELF_NETNS (self, klass, netns, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (ring, FALSE);
+
+	return nmp_utils_ethtool_get_ring (ifindex, ring);
+}
+
+gboolean
+nm_platform_ethtool_set_ring (NMPlatform *self,
+                              int ifindex,
+                              const NMEthtoolRingState *ring)
+{
+	_CHECK_SELF_NETNS (self, klass, netns, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	return nmp_utils_ethtool_set_ring (ifindex, ring);
+}
+
 /*****************************************************************************/
 
 const NMDedupMultiHeadEntry *
@@ -4978,12 +5052,15 @@ nm_platform_qdisc_sync (NMPlatform *self,
 
 	known_qdiscs_idx = g_hash_table_new ((GHashFunc) nmp_object_id_hash,
 	                                     (GEqualFunc) nmp_object_id_equal);
-
 	if (known_qdiscs) {
 		for (i = 0; i < known_qdiscs->len; i++) {
 			const NMPObject *q = g_ptr_array_index (known_qdiscs, i);
 
-			g_hash_table_insert (known_qdiscs_idx, (gpointer) q, (gpointer) q);
+			if (!g_hash_table_insert (known_qdiscs_idx, (gpointer) q, (gpointer) q)) {
+				_LOGW ("duplicate qdisc %s", nm_platform_qdisc_to_string (&q->qdisc, NULL, 0));
+				return FALSE;
+			}
+
 		}
 	}
 
@@ -4992,13 +5069,34 @@ nm_platform_qdisc_sync (NMPlatform *self,
 	                                                                NMP_OBJECT_TYPE_QDISC,
 	                                                                ifindex),
 	                                        NULL, NULL);
-
 	if (plat_qdiscs) {
 		for (i = 0; i < plat_qdiscs->len; i++) {
-			const NMPObject *q = g_ptr_array_index (plat_qdiscs, i);
+			const NMPObject *p = g_ptr_array_index (plat_qdiscs, i);
+			const NMPObject *k;
 
-			if (!g_hash_table_lookup (known_qdiscs_idx, q))
-				success &= nm_platform_object_delete (self, q);
+			/* look up known qdisc with same parent */
+			k = g_hash_table_lookup (known_qdiscs_idx, p);
+
+			if (k) {
+				const NMPlatformQdisc *qdisc_k = NMP_OBJECT_CAST_QDISC (k);
+				const NMPlatformQdisc *qdisc_p = NMP_OBJECT_CAST_QDISC (p);
+
+				/* check other fields */
+				if (   nm_platform_qdisc_cmp_full (qdisc_k, qdisc_p, FALSE) != 0
+				    || (   qdisc_k->handle != qdisc_p->handle
+				        && qdisc_k != 0)) {
+					k = NULL;
+				}
+			}
+
+			if (k) {
+				g_hash_table_remove (known_qdiscs_idx, k);
+			} else {
+				/* can't delete qdisc with zero handle */
+				if (TC_H_MAJ (p->qdisc.handle) != 0) {
+					success &= nm_platform_object_delete (self, p);
+				}
+			}
 		}
 	}
 
@@ -5006,8 +5104,10 @@ nm_platform_qdisc_sync (NMPlatform *self,
 		for (i = 0; i < known_qdiscs->len; i++) {
 			const NMPObject *q = g_ptr_array_index (known_qdiscs, i);
 
-			success &= (nm_platform_qdisc_add (self, NMP_NLM_FLAG_ADD,
-			                                   NMP_OBJECT_CAST_QDISC (q)) >= 0);
+			if (g_hash_table_contains (known_qdiscs_idx, q)) {
+				success &= (nm_platform_qdisc_add (self, NMP_NLM_FLAG_ADD,
+				                                   NMP_OBJECT_CAST_QDISC (q)) >= 0);
+			}
 		}
 	}
 
@@ -6402,6 +6502,26 @@ nm_platform_qdisc_to_string (const NMPlatformQdisc *qdisc, char *buf, gsize len)
 			nm_utils_strbuf_append (&buf, &len, " memory_limit %u", qdisc->fq_codel.memory_limit);
 		if (qdisc->fq_codel.ecn)
 			nm_utils_strbuf_append (&buf, &len, " ecn");
+	} else if (nm_streq0 (qdisc->kind, "sfq")) {
+		if (qdisc->sfq.quantum)
+			nm_utils_strbuf_append (&buf, &len, " quantum %u", qdisc->sfq.quantum);
+		if (qdisc->sfq.perturb_period)
+			nm_utils_strbuf_append (&buf, &len, " perturb %d", qdisc->sfq.perturb_period);
+		if (qdisc->sfq.limit)
+			nm_utils_strbuf_append (&buf, &len, " limit %u", (guint) qdisc->sfq.limit);
+		if (qdisc->sfq.divisor)
+			nm_utils_strbuf_append (&buf, &len, " divisor %u", qdisc->sfq.divisor);
+		if (qdisc->sfq.flows)
+			nm_utils_strbuf_append (&buf, &len, " flows %u", qdisc->sfq.flows);
+		if (qdisc->sfq.depth)
+			nm_utils_strbuf_append (&buf, &len, " depth %u", qdisc->sfq.depth);
+	} else if (nm_streq0 (qdisc->kind, "tbf")) {
+		nm_utils_strbuf_append (&buf, &len, " rate %"G_GUINT64_FORMAT, qdisc->tbf.rate);
+		nm_utils_strbuf_append (&buf, &len, " burst %u", qdisc->tbf.burst);
+		if (qdisc->tbf.limit)
+			nm_utils_strbuf_append (&buf, &len, " limit %u", qdisc->tbf.limit);
+		if (qdisc->tbf.latency)
+			nm_utils_strbuf_append (&buf, &len, " latency %uns", qdisc->tbf.latency);
 	}
 
 	return buf0;
@@ -6428,18 +6548,35 @@ nm_platform_qdisc_hash_update (const NMPlatformQdisc *obj, NMHashState *h)
 		                     obj->fq_codel.memory_limit,
 		                     NM_HASH_COMBINE_BOOLS (guint8,
 		                                            obj->fq_codel.ecn));
+	} else if (nm_streq0 (obj->kind, "sfq")) {
+		nm_hash_update_vals (h,
+		                     obj->sfq.quantum,
+		                     obj->sfq.perturb_period,
+		                     obj->sfq.limit,
+		                     obj->sfq.divisor,
+		                     obj->sfq.flows,
+		                     obj->sfq.depth);
+	} else if (nm_streq0 (obj->kind, "tbf")) {
+		nm_hash_update_vals (h,
+		                     obj->tbf.rate,
+		                     obj->tbf.burst,
+		                     obj->tbf.limit,
+		                     obj->tbf.latency);
 	}
 }
 
 int
-nm_platform_qdisc_cmp (const NMPlatformQdisc *a, const NMPlatformQdisc *b)
+nm_platform_qdisc_cmp_full (const NMPlatformQdisc *a,
+                            const NMPlatformQdisc *b,
+                            gboolean compare_handle)
 {
 	NM_CMP_SELF (a, b);
 	NM_CMP_FIELD (a, b, ifindex);
 	NM_CMP_FIELD (a, b, parent);
 	NM_CMP_FIELD_STR_INTERNED (a, b, kind);
 	NM_CMP_FIELD (a, b, addr_family);
-	NM_CMP_FIELD (a, b, handle);
+	if (compare_handle)
+		NM_CMP_FIELD (a, b, handle);
 	NM_CMP_FIELD (a, b, info);
 
 	if (nm_streq0 (a->kind, "fq_codel")) {
@@ -6451,11 +6588,29 @@ nm_platform_qdisc_cmp (const NMPlatformQdisc *a, const NMPlatformQdisc *b)
 		NM_CMP_FIELD (a, b, fq_codel.ce_threshold);
 		NM_CMP_FIELD (a, b, fq_codel.memory_limit);
 		NM_CMP_FIELD_UNSAFE (a, b, fq_codel.ecn);
+	} else if (nm_streq0 (a->kind, "sfq")) {
+		NM_CMP_FIELD (a, b, sfq.quantum);
+		NM_CMP_FIELD (a, b, sfq.perturb_period);
+		NM_CMP_FIELD (a, b, sfq.limit);
+		NM_CMP_FIELD (a, b, sfq.flows);
+		NM_CMP_FIELD (a, b, sfq.divisor);
+		NM_CMP_FIELD (a, b, sfq.depth);
+	} else if (nm_streq0 (a->kind, "tbf")) {
+		NM_CMP_FIELD (a, b, tbf.rate);
+		NM_CMP_FIELD (a, b, tbf.burst);
+		NM_CMP_FIELD (a, b, tbf.limit);
+		NM_CMP_FIELD (a, b, tbf.latency);
 	}
 
 	return 0;
 }
 
+int
+nm_platform_qdisc_cmp (const NMPlatformQdisc *a, const NMPlatformQdisc *b)
+{
+	return nm_platform_qdisc_cmp_full (a, b, TRUE);
+}
+
 const char *
 nm_platform_tfilter_to_string (const NMPlatformTfilter *tfilter, char *buf, gsize len)
 {
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index 7d10d909..aa3551a6 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -69,6 +69,8 @@ typedef gboolean (*NMPObjectPredicateFunc) (const NMPObject *obj,
 #define NM_GRE_KEY      0x2000
 
 typedef enum {
+	NMP_NLM_FLAG_F_ECHO         = 0x08, /* NLM_F_ECHO, Echo this request */
+
 	/* use our own platform enum for the nlmsg-flags. Otherwise, we'd have
 	 * to include <linux/netlink.h> */
 	NMP_NLM_FLAG_F_REPLACE      = 0x100, /* NLM_F_REPLACE, Override existing */
@@ -628,6 +630,22 @@ typedef struct {
 } NMPlatformQdiscFqCodel;
 
 typedef struct {
+	unsigned quantum;
+	int perturb_period;
+	guint32 limit;
+	unsigned divisor;
+	unsigned flows;
+	unsigned depth;
+} NMPlatformQdiscSfq;
+
+typedef struct {
+	guint64 rate;
+	guint32 burst;
+	guint32 limit;
+	guint32 latency;
+} NMPlatformQdiscTbf;
+
+typedef struct {
 	__NMPlatformObjWithIfindex_COMMON;
 
 	/* beware, kind is embedded in an NMPObject, hence you must
@@ -640,6 +658,8 @@ typedef struct {
 	guint32 info;
 	union {
 		NMPlatformQdiscFqCodel fq_codel;
+		NMPlatformQdiscSfq sfq;
+		NMPlatformQdiscTbf tbf;
 	};
 } NMPlatformQdisc;
 
@@ -990,9 +1010,6 @@ typedef struct {
 	gboolean (*link_set_arp) (NMPlatform *self, int ifindex);
 	gboolean (*link_set_noarp) (NMPlatform *self, int ifindex);
 
-	const char *(*link_get_udi) (NMPlatform *self, int ifindex);
-	struct udev_device *(*link_get_udev_device) (NMPlatform *self, int ifindex);
-
 	int (*link_set_user_ipv6ll_enabled) (NMPlatform *self, int ifindex, gboolean enabled);
 	gboolean (*link_set_token) (NMPlatform *self, int ifindex, NMUtilsIPv6IfaceId iid);
 
@@ -1613,6 +1630,7 @@ gboolean nm_platform_link_set_arp (NMPlatform *self, int ifindex);
 gboolean nm_platform_link_set_noarp (NMPlatform *self, int ifindex);
 
 const char *nm_platform_link_get_udi (NMPlatform *self, int ifindex);
+const char *nm_platform_link_get_path (NMPlatform *self, int ifindex);
 
 struct udev_device *nm_platform_link_get_udev_device (NMPlatform *self, int ifindex);
 
@@ -1907,6 +1925,9 @@ nm_platform_routing_rule_cmp_full (const NMPlatformRoutingRule *a, const NMPlatf
 }
 
 int nm_platform_qdisc_cmp (const NMPlatformQdisc *a, const NMPlatformQdisc *b);
+int nm_platform_qdisc_cmp_full (const NMPlatformQdisc *a,
+                                const NMPlatformQdisc *b,
+                                gboolean compare_handle);
 int nm_platform_tfilter_cmp (const NMPlatformTfilter *a, const NMPlatformTfilter *b);
 
 void nm_platform_link_hash_update (const NMPlatformLink *obj, NMHashState *h);
@@ -1954,6 +1975,26 @@ gboolean nm_platform_ethtool_set_features (NMPlatform *self,
                                            const NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
                                            gboolean do_set /* or reset */);
 
+typedef struct _NMEthtoolCoalesceState NMEthtoolCoalesceState;
+
+gboolean nm_platform_ethtool_get_link_coalesce (NMPlatform *self,
+                                                int ifindex,
+                                                NMEthtoolCoalesceState *coalesce);
+
+gboolean nm_platform_ethtool_set_coalesce (NMPlatform *self,
+                                           int ifindex,
+                                           const NMEthtoolCoalesceState *coalesce);
+
+typedef struct _NMEthtoolRingState NMEthtoolRingState;
+
+gboolean nm_platform_ethtool_get_link_ring (NMPlatform *self,
+                                            int ifindex,
+                                            NMEthtoolRingState *ring);
+
+gboolean nm_platform_ethtool_set_ring (NMPlatform *self,
+                                       int ifindex,
+                                       const NMEthtoolRingState *ring);
+
 const char * nm_platform_link_duplex_type_to_string (NMPlatformLinkDuplexType duplex);
 
 void nm_platform_ip4_dev_route_blacklist_set (NMPlatform *self,
diff --git a/src/platform/tests/meson.build b/src/platform/tests/meson.build
index 4a50bca9..f96850cc 100644
--- a/src/platform/tests/meson.build
+++ b/src/platform/tests/meson.build
@@ -14,6 +14,8 @@ test_units = [
   ['test-platform-general', 'test-platform-general.c', test_c_flags, default_test_timeout],
   ['test-route-fake', 'test-route.c', test_fake_c_flags, default_test_timeout],
   ['test-route-linux', 'test-route.c', test_linux_c_flags, default_test_timeout],
+  ['test-tc-fake', 'test-tc.c', test_fake_c_flags, default_test_timeout],
+  ['test-tc-linux', 'test-tc.c', test_linux_c_flags, default_test_timeout],
 ]
 
 foreach test_unit: test_units
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index f28dfa3a..9f6a29bc 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -557,6 +557,7 @@ nmtstp_wait_for_signal (NMPlatform *platform, gint64 timeout_msec)
 {
 	WaitForSignalData data = { 0 };
 	gulong id_link, id_ip4_address, id_ip6_address, id_ip4_route, id_ip6_route;
+	gulong id_qdisc, id_tfilter;
 
 	_init_platform (&platform, FALSE);
 
@@ -567,6 +568,8 @@ nmtstp_wait_for_signal (NMPlatform *platform, gint64 timeout_msec)
 	id_ip6_address = g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
 	id_ip4_route   = g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
 	id_ip6_route   = g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
+	id_qdisc       = g_signal_connect (platform, NM_PLATFORM_SIGNAL_QDISC_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
+	id_tfilter     = g_signal_connect (platform, NM_PLATFORM_SIGNAL_TFILTER_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
 
 	/* if timeout_msec is negative, it means the wait-time already expired.
 	 * Maybe, we should do nothing and return right away, without even
@@ -589,6 +592,8 @@ nmtstp_wait_for_signal (NMPlatform *platform, gint64 timeout_msec)
 	g_assert (nm_clear_g_signal_handler (platform, &id_ip6_address));
 	g_assert (nm_clear_g_signal_handler (platform, &id_ip4_route));
 	g_assert (nm_clear_g_signal_handler (platform, &id_ip6_route));
+	g_assert (nm_clear_g_signal_handler (platform, &id_tfilter));
+	g_assert (nm_clear_g_signal_handler (platform, &id_qdisc));
 
 	nm_clear_pointer (&data.loop, g_main_loop_unref);
 
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index ea4a2c5a..d7285c83 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -3290,14 +3290,14 @@ test_ethtool_features_get (void)
 		g_ptr_array_add (gfree_keeper, requested);
 
 		if (i_run == 0) {
-			requested[NM_ETHTOOL_ID_FEATURE_RX]                    = NM_TERNARY_FALSE;
-			requested[NM_ETHTOOL_ID_FEATURE_TSO]                   = NM_TERNARY_FALSE;
-			requested[NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION]  = NM_TERNARY_FALSE;
+			requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX (NM_ETHTOOL_ID_FEATURE_RX)] = NM_TERNARY_FALSE;
+			requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX (NM_ETHTOOL_ID_FEATURE_TSO)] = NM_TERNARY_FALSE;
+			requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX (NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION)] = NM_TERNARY_FALSE;
 		} else if (i_run == 1)
 			do_set = FALSE;
 		else if (i_run == 2) {
-			requested[NM_ETHTOOL_ID_FEATURE_TSO]                   = NM_TERNARY_FALSE;
-			requested[NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION]  = NM_TERNARY_TRUE;
+			requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX (NM_ETHTOOL_ID_FEATURE_TSO)] = NM_TERNARY_FALSE;
+			requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX (NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION)] = NM_TERNARY_TRUE;
 		} else if (i_run == 3)
 			do_set = FALSE;
 
diff --git a/src/platform/tests/test-tc.c b/src/platform/tests/test-tc.c
new file mode 100644
index 00000000..0c90fcfa
--- /dev/null
+++ b/src/platform/tests/test-tc.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: LGPL-2.1+
+
+#include "nm-default.h"
+
+#include <linux/pkt_sched.h>
+
+#include "nm-test-utils-core.h"
+#include "platform/nmp-object.h"
+#include "platform/nmp-netns.h"
+#include "platform/nm-platform-utils.h"
+#include "test-common.h"
+
+static NMPObject *
+qdisc_new (int ifindex, const char *kind, guint32 parent)
+{
+	NMPObject *obj;
+
+	obj = nmp_object_new (NMP_OBJECT_TYPE_QDISC, NULL);
+	obj->qdisc = (NMPlatformQdisc) {
+		.ifindex = ifindex,
+		.kind = kind,
+		.parent = parent,
+	};
+
+	return obj;
+}
+
+static GPtrArray *
+qdiscs_lookup (int ifindex)
+{
+	NMPLookup lookup;
+
+	return nm_platform_lookup_clone (NM_PLATFORM_GET,
+	                                 nmp_lookup_init_object (&lookup,
+	                                                         NMP_OBJECT_TYPE_QDISC,
+	                                                         ifindex),
+	                                 NULL, NULL);
+}
+
+static void
+test_qdisc1 (void)
+{
+	int ifindex;
+	gs_unref_ptrarray GPtrArray *known = NULL;
+	gs_unref_ptrarray GPtrArray *plat = NULL;
+	NMPObject *obj;
+	NMPlatformQdisc *qdisc;
+
+	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
+	g_assert_cmpint (ifindex, >, 0);
+
+	nmtstp_run_command       ("tc qdisc del dev %s root", DEVICE_NAME);
+	nmtstp_run_command_check ("tc qdisc add dev %s root sfq", DEVICE_NAME);
+
+	nmtstp_wait_for_signal (NM_PLATFORM_GET, 0);
+
+	known = g_ptr_array_new_with_free_func ((GDestroyNotify) nmp_object_unref);
+	g_ptr_array_add (known, qdisc_new (ifindex, "fq_codel", TC_H_ROOT));
+	g_ptr_array_add (known, qdisc_new (ifindex, "ingress", TC_H_INGRESS));
+
+	g_assert (nm_platform_qdisc_sync (NM_PLATFORM_GET, ifindex, known));
+	plat = qdiscs_lookup (ifindex);
+	g_assert (plat);
+	g_assert_cmpint (plat->len, ==, 2);
+
+	obj = plat->pdata[0];
+	qdisc = NMP_OBJECT_CAST_QDISC (obj);
+	g_assert_cmpint (qdisc->parent, ==, TC_H_ROOT);
+	g_assert_cmpstr (qdisc->kind, ==, "fq_codel");
+
+	obj = plat->pdata[1];
+	qdisc = NMP_OBJECT_CAST_QDISC (obj);
+	g_assert_cmpint (qdisc->parent, ==, TC_H_INGRESS);
+	g_assert_cmpstr (qdisc->kind, ==, "ingress");
+}
+
+static void
+test_qdisc_fq_codel (void)
+{
+	int ifindex;
+	gs_unref_ptrarray GPtrArray *known = NULL;
+	gs_unref_ptrarray GPtrArray *plat = NULL;
+	NMPObject *obj;
+	NMPlatformQdisc *qdisc;
+
+	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
+	g_assert_cmpint (ifindex, >, 0);
+
+	nmtstp_run_command ("tc qdisc del dev %s root", DEVICE_NAME);
+
+	nmtstp_wait_for_signal (NM_PLATFORM_GET, 0);
+
+	known = g_ptr_array_new_with_free_func ((GDestroyNotify) nmp_object_unref);
+	obj = qdisc_new (ifindex, "fq_codel", TC_H_ROOT);
+	obj->qdisc.handle = TC_H_MAKE (0x8142 << 16, 0);
+	obj->qdisc.fq_codel.limit = 2048;
+	obj->qdisc.fq_codel.flows = 64;
+	obj->qdisc.fq_codel.quantum = 1000;
+	g_ptr_array_add (known, obj);
+
+	g_assert (nm_platform_qdisc_sync (NM_PLATFORM_GET, ifindex, known));
+	plat = qdiscs_lookup (ifindex);
+	g_assert (plat);
+	g_assert_cmpint (plat->len, ==, 1);
+
+	obj = plat->pdata[0];
+	qdisc = NMP_OBJECT_CAST_QDISC (obj);
+	g_assert_cmpstr (qdisc->kind, ==, "fq_codel");
+	g_assert_cmpint (qdisc->handle, ==, TC_H_MAKE (0x8142 << 16, 0));
+	g_assert_cmpint (qdisc->parent, ==, TC_H_ROOT);
+	g_assert_cmpint (qdisc->fq_codel.limit, ==, 2048);
+	g_assert_cmpint (qdisc->fq_codel.flows, ==, 64);
+	g_assert_cmpint (qdisc->fq_codel.quantum, ==, 1000);
+}
+
+static void
+test_qdisc_sfq (void)
+{
+	int ifindex;
+	gs_unref_ptrarray GPtrArray *known = NULL;
+	gs_unref_ptrarray GPtrArray *plat = NULL;
+	NMPObject *obj;
+	NMPlatformQdisc *qdisc;
+
+	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
+	g_assert_cmpint (ifindex, >, 0);
+
+	nmtstp_run_command ("tc qdisc del dev %s root", DEVICE_NAME);
+
+	nmtstp_wait_for_signal (NM_PLATFORM_GET, 0);
+
+	known = g_ptr_array_new_with_free_func ((GDestroyNotify) nmp_object_unref);
+	obj = qdisc_new (ifindex, "sfq", TC_H_ROOT);
+	obj->qdisc.handle = TC_H_MAKE (0x8143 << 16, 0);
+	obj->qdisc.sfq.perturb_period = 10;
+	obj->qdisc.sfq.quantum = 1540;
+	obj->qdisc.sfq.flows = 256;
+	g_ptr_array_add (known, obj);
+
+	g_assert (nm_platform_qdisc_sync (NM_PLATFORM_GET, ifindex, known));
+	plat = qdiscs_lookup (ifindex);
+	g_assert (plat);
+	g_assert_cmpint (plat->len, ==, 1);
+
+	obj = plat->pdata[0];
+	qdisc = NMP_OBJECT_CAST_QDISC (obj);
+	g_assert_cmpstr (qdisc->kind, ==, "sfq");
+	g_assert_cmpint (qdisc->handle, ==, TC_H_MAKE (0x8143 << 16, 0));
+	g_assert_cmpint (qdisc->parent, ==, TC_H_ROOT);
+	g_assert_cmpint (qdisc->sfq.perturb_period, ==, 10);
+	g_assert_cmpint (qdisc->sfq.quantum, ==, 1540);
+	g_assert_cmpint (qdisc->sfq.flows, ==, 256);
+}
+
+static void
+test_qdisc_tbf (void)
+{
+	int ifindex;
+	gs_unref_ptrarray GPtrArray *known = NULL;
+	gs_unref_ptrarray GPtrArray *plat = NULL;
+	NMPObject *obj;
+	NMPlatformQdisc *qdisc;
+
+	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
+	g_assert_cmpint (ifindex, >, 0);
+
+	nmtstp_run_command ("tc qdisc del dev %s root", DEVICE_NAME);
+
+	nmtstp_wait_for_signal (NM_PLATFORM_GET, 0);
+
+	known = g_ptr_array_new_with_free_func ((GDestroyNotify) nmp_object_unref);
+	obj = qdisc_new (ifindex, "tbf", TC_H_ROOT);
+	obj->qdisc.handle = TC_H_MAKE (0x8143 << 16, 0);
+	obj->qdisc.tbf.rate = 1000000;
+	obj->qdisc.tbf.burst = 2000;
+	obj->qdisc.tbf.limit = 3000;
+	g_ptr_array_add (known, obj);
+
+	obj = qdisc_new (ifindex, "sfq", TC_H_MAKE (0x8143 << 16, 0));
+	obj->qdisc.handle = TC_H_MAKE (0x8005 << 16, 0);
+	g_ptr_array_add (known, obj);
+
+	g_assert (nm_platform_qdisc_sync (NM_PLATFORM_GET, ifindex, known));
+	plat = qdiscs_lookup (ifindex);
+	g_assert (plat);
+	g_assert_cmpint (plat->len, ==, 2);
+
+	obj = plat->pdata[0];
+	qdisc = NMP_OBJECT_CAST_QDISC (obj);
+	g_assert_cmpstr (qdisc->kind, ==, "tbf");
+	g_assert_cmpint (qdisc->handle, ==, TC_H_MAKE (0x8143 << 16, 0));
+	g_assert_cmpint (qdisc->parent, ==, TC_H_ROOT);
+	g_assert_cmpint (qdisc->tbf.rate, ==, 1000000);
+	g_assert_cmpint (qdisc->tbf.burst, ==, 2000);
+	g_assert_cmpint (qdisc->tbf.limit, ==, 3000);
+
+	obj = plat->pdata[1];
+	qdisc = NMP_OBJECT_CAST_QDISC (obj);
+	g_assert_cmpstr (qdisc->kind, ==, "sfq");
+	g_assert_cmpint (qdisc->parent, ==, TC_H_MAKE (0x8143 << 16, 0));
+	g_assert_cmpint (qdisc->handle, ==, TC_H_MAKE (0x8005 << 16, 0));
+}
+
+/*****************************************************************************/
+
+NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP;
+
+void
+_nmtstp_init_tests (int *argc, char ***argv)
+{
+	nmtst_init_with_logging (argc, argv, NULL, "ALL");
+}
+
+void
+_nmtstp_setup_tests (void)
+{
+	if (nmtstp_is_root_test ()) {
+		nmtstp_env1_add_test_func ("/link/qdisc/1", test_qdisc1, TRUE);
+		nmtstp_env1_add_test_func ("/link/qdisc/fq_codel", test_qdisc_fq_codel, TRUE);
+		nmtstp_env1_add_test_func ("/link/qdisc/sfq", test_qdisc_sfq, TRUE);
+		nmtstp_env1_add_test_func ("/link/qdisc/tbf", test_qdisc_tbf, TRUE);
+	}
+}
diff --git a/src/settings/nm-settings-connection.c b/src/settings/nm-settings-connection.c
index 48e3dce2..ca46de68 100644
--- a/src/settings/nm-settings-connection.c
+++ b/src/settings/nm-settings-connection.c
@@ -1409,6 +1409,54 @@ update_complete (NMSettingsConnection *self,
 	g_slice_free (UpdateInfo, info);
 }
 
+static int
+_autoconnect_retries_initial (NMSettingsConnection *self)
+{
+	NMSettingConnection *s_con;
+	int retries = -1;
+
+	s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (self));
+	if (s_con)
+		retries = nm_setting_connection_get_autoconnect_retries (s_con);
+
+	/* -1 means 'default' */
+	if (retries == -1)
+		retries = nm_config_data_get_autoconnect_retries_default (NM_CONFIG_GET_DATA);
+
+	/* 0 means 'forever', which is translated to a retry count of -1 */
+	if (retries == 0)
+		retries = AUTOCONNECT_RETRIES_FOREVER;
+
+	nm_assert (retries == AUTOCONNECT_RETRIES_FOREVER || retries >= 0);
+	return retries;
+}
+
+static void
+_autoconnect_retries_set (NMSettingsConnection *self,
+                          int retries,
+                          gboolean is_reset)
+{
+	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+
+	g_return_if_fail (retries == AUTOCONNECT_RETRIES_FOREVER || retries >= 0);
+
+	if (priv->autoconnect_retries != retries) {
+		_LOGT ("autoconnect: retries set %d%s", retries,
+		       is_reset ? " (reset)" : "");
+		priv->autoconnect_retries = retries;
+	}
+
+	if (retries)
+		priv->autoconnect_retries_blocked_until = 0;
+	else {
+		/* NOTE: the blocked time must be identical for all connections, otherwise
+		 * the tracking of resetting the retry count in NMPolicy needs adjustment
+		 * in _connection_autoconnect_retries_set() (as it would need to re-evaluate
+		 * the next-timeout every time a connection gets blocked). */
+		priv->autoconnect_retries_blocked_until = nm_utils_get_monotonic_timestamp_sec () + AUTOCONNECT_RESET_RETRIES_TIMER;
+	}
+}
+
 static void
 update_auth_cb (NMSettingsConnection *self,
                 GDBusMethodInvocation *context,
@@ -1444,6 +1492,11 @@ update_auth_cb (NMSettingsConnection *self,
 			 * they're in the main connection.
 			 */
 			update_agent_secrets_cache (self, info->new_settings);
+
+			/* New secrets, allow autoconnection again */
+			if (   nm_settings_connection_autoconnect_blocked_reason_set (self, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS, FALSE)
+			    && !nm_settings_connection_autoconnect_blocked_reason_get (self))
+				nm_settings_connection_autoconnect_retries_reset (self);
 		}
 	}
 
@@ -1482,7 +1535,8 @@ update_auth_cb (NMSettingsConnection *self,
 	                                ? NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
 	                                : NM_SETTINGS_CONNECTION_INT_FLAGS_NONE),
 	                                 NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
-	                               | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE,
+	                               | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+	                               | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL,
 	                                 NM_SETTINGS_CONNECTION_UPDATE_REASON_FORCE_RENAME
 	                               | (  NM_FLAGS_HAS (info->flags, NM_SETTINGS_UPDATE2_FLAG_NO_REAPPLY)
 	                                  ? NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE
@@ -1975,6 +2029,7 @@ NM_UTILS_FLAGS2STR_DEFINE (_settings_connection_flags_to_string, NMSettingsConne
 	NM_UTILS_FLAGS2STR (NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED,  "nm-generated"),
 	NM_UTILS_FLAGS2STR (NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE,      "volatile"),
 	NM_UTILS_FLAGS2STR (NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE,       "visible"),
+	NM_UTILS_FLAGS2STR (NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL,       "external"),
 );
 
 NMSettingsConnectionIntFlags
@@ -2136,15 +2191,24 @@ void
 nm_settings_connection_update_timestamp (NMSettingsConnection *self,
                                          guint64 timestamp)
 {
-	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+	NMSettingsConnectionPrivate *priv;
 	const char *connection_uuid;
 	char sbuf[60];
 
 	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (self));
 
+	priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+
+	if (   priv->timestamp == timestamp
+	    && priv->timestamp_set)
+		return;
+
 	priv->timestamp = timestamp;
 	priv->timestamp_set = TRUE;
 
+	_LOGT ("timestamp: set timestamp %"G_GUINT64_FORMAT,
+	       timestamp);
+
 	if (!priv->kf_db_timestamps)
 		return;
 
@@ -2185,10 +2249,10 @@ _nm_settings_connection_register_kf_dbs (NMSettingsConnection *self,
 		if (timestamp != G_MAXUINT64) {
 			priv->timestamp = timestamp;
 			priv->timestamp_set = TRUE;
-			_LOGT ("read timestamp %"G_GUINT64_FORMAT" from keyfile database \"%s\"",
+			_LOGT ("timestamp: read timestamp %"G_GUINT64_FORMAT" from keyfile database \"%s\"",
 			       timestamp, nm_key_file_db_get_filename (priv->kf_db_timestamps));
 		} else
-			_LOGT ("no timestamp from keyfile database \"%s\"",
+			_LOGT ("timestamp: no timestamp from keyfile database \"%s\"",
 			       nm_key_file_db_get_filename (priv->kf_db_timestamps));
 	}
 
@@ -2319,54 +2383,6 @@ nm_settings_connection_add_seen_bssid (NMSettingsConnection *self,
 
 /*****************************************************************************/
 
-static int
-_autoconnect_retries_initial (NMSettingsConnection *self)
-{
-	NMSettingConnection *s_con;
-	int retries = -1;
-
-	s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (self));
-	if (s_con)
-		retries = nm_setting_connection_get_autoconnect_retries (s_con);
-
-	/* -1 means 'default' */
-	if (retries == -1)
-		retries = nm_config_data_get_autoconnect_retries_default (NM_CONFIG_GET_DATA);
-
-	/* 0 means 'forever', which is translated to a retry count of -1 */
-	if (retries == 0)
-		retries = AUTOCONNECT_RETRIES_FOREVER;
-
-	nm_assert (retries == AUTOCONNECT_RETRIES_FOREVER || retries >= 0);
-	return retries;
-}
-
-static void
-_autoconnect_retries_set (NMSettingsConnection *self,
-                          int retries,
-                          gboolean is_reset)
-{
-	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
-
-	g_return_if_fail (retries == AUTOCONNECT_RETRIES_FOREVER || retries >= 0);
-
-	if (priv->autoconnect_retries != retries) {
-		_LOGT ("autoconnect: retries set %d%s", retries,
-		       is_reset ? " (reset)" : "");
-		priv->autoconnect_retries = retries;
-	}
-
-	if (retries)
-		priv->autoconnect_retries_blocked_until = 0;
-	else {
-		/* NOTE: the blocked time must be identical for all connections, otherwise
-		 * the tracking of resetting the retry count in NMPolicy needs adjustment
-		 * in _connection_autoconnect_retries_set() (as it would need to re-evaluate
-		 * the next-timeout every time a connection gets blocked). */
-		priv->autoconnect_retries_blocked_until = nm_utils_get_monotonic_timestamp_sec () + AUTOCONNECT_RESET_RETRIES_TIMER;
-	}
-}
-
 /**
  * nm_settings_connection_autoconnect_retries_get:
  * @self: the settings connection
@@ -2423,9 +2439,9 @@ NM_UTILS_FLAGS2STR_DEFINE (_autoconnect_blocked_reason_to_string, NMSettingsAuto
 );
 
 NMSettingsAutoconnectBlockedReason
-nm_settings_connection_autoconnect_blocked_reason_get (NMSettingsConnection *self, NMSettingsAutoconnectBlockedReason mask)
+nm_settings_connection_autoconnect_blocked_reason_get (NMSettingsConnection *self)
 {
-	return NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->autoconnect_blocked_reason & (mask ?: NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_ALL);
+	return NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->autoconnect_blocked_reason;
 }
 
 gboolean
@@ -2467,7 +2483,8 @@ nm_settings_connection_autoconnect_is_blocked (NMSettingsConnection *self)
 		return TRUE;
 
 	flags = priv->flags;
-	if (NM_FLAGS_HAS (flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
+	if (NM_FLAGS_ANY (flags,   NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+	                         | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL))
 		return TRUE;
 	if (!NM_FLAGS_HAS (flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE))
 		return TRUE;
diff --git a/src/settings/nm-settings-connection.h b/src/settings/nm-settings-connection.h
index dfc3786c..17573bbe 100644
--- a/src/settings/nm-settings-connection.h
+++ b/src/settings/nm-settings-connection.h
@@ -147,6 +147,8 @@ typedef enum {
  *  when it disconnects. That is for in-memory connections (unsaved), which are
  *  currently active but cleanup on disconnect.
  *  See also #NM_SETTINGS_CONNECTION_FLAG_VOLATILE.
+ * @NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL: the profile was generated to
+ *  represent the external activation of a device. See also #NM_SETTINGS_CONNECTION_FLAG_EXTERNAL.
  * @NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE: The connection is visible
  * @_NM_SETTINGS_CONNECTION_INT_FLAGS_EXPORTED_MASK: the entire enum is
  *   internal, however, parts of it is public API as #NMSettingsConnectionFlags.
@@ -161,8 +163,9 @@ typedef enum _NMSettingsConnectionIntFlags {
 	NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED                = NM_SETTINGS_CONNECTION_FLAG_UNSAVED,
 	NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED           = NM_SETTINGS_CONNECTION_FLAG_NM_GENERATED,
 	NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE               = NM_SETTINGS_CONNECTION_FLAG_VOLATILE,
+	NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL               = NM_SETTINGS_CONNECTION_FLAG_EXTERNAL,
 
-	NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE                = 0x08,
+	NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE                = 0x10,
 
 	_NM_SETTINGS_CONNECTION_INT_FLAGS_LAST,
 
@@ -170,11 +173,13 @@ typedef enum _NMSettingsConnectionIntFlags {
 	                                                          | NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED
 	                                                          | NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
 	                                                          | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+	                                                          | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL
 	                                                          | 0,
 
 	_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK       = 0
 	                                                          | NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
 	                                                          | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+	                                                          | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL
 	                                                          | 0,
 
 	_NM_SETTINGS_CONNECTION_INT_FLAGS_ALL = ((_NM_SETTINGS_CONNECTION_INT_FLAGS_LAST - 1) << 1) - 1,
@@ -337,8 +342,7 @@ void nm_settings_connection_autoconnect_retries_reset (NMSettingsConnection *sel
 
 gint32 nm_settings_connection_autoconnect_retries_blocked_until (NMSettingsConnection *self);
 
-NMSettingsAutoconnectBlockedReason nm_settings_connection_autoconnect_blocked_reason_get (NMSettingsConnection *self,
-                                                                                          NMSettingsAutoconnectBlockedReason mask);
+NMSettingsAutoconnectBlockedReason nm_settings_connection_autoconnect_blocked_reason_get (NMSettingsConnection *self);
 gboolean nm_settings_connection_autoconnect_blocked_reason_set_full (NMSettingsConnection *self,
                                                                      NMSettingsAutoconnectBlockedReason mask,
                                                                      NMSettingsAutoconnectBlockedReason value);
diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c
index 874efbfc..5899b079 100644
--- a/src/settings/nm-settings.c
+++ b/src/settings/nm-settings.c
@@ -1091,12 +1091,14 @@ _connection_changed_update (NMSettings *self,
 	else {
 		nm_assert (!NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED));
 
-		/* Profiles that don't reside in /run, are never nm-generated
-		 * and never volatile. */
+		/* Profiles that don't reside in /run, are never nm-generated,
+		 * volatile, and external. */
 		sett_mask |= (  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
-		              | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE);
+		              | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+		              | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL);
 		sett_flags &= ~(  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
-		                | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE);
+		                | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+		                | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL);
 	}
 
 	nm_settings_connection_set_flags_full (sett_conn,
@@ -1200,7 +1202,8 @@ _connection_changed_delete (NMSettings *self,
 
 	nm_settings_connection_set_flags (sett_conn,
 	                                    NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE
-	                                  | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE,
+	                                  | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+	                                  | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL,
 	                                  FALSE);
 
 	_emit_connection_removed (self, sett_conn);
@@ -1491,6 +1494,7 @@ _add_connection_to_first_plugin (NMSettings *self,
 			                                             in_memory,
 			                                             NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED),
 			                                             NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE),
+			                                             NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL),
 			                                             shadowed_storage,
 			                                             shadowed_owned,
 			                                             &storage,
@@ -1501,6 +1505,7 @@ _add_connection_to_first_plugin (NMSettings *self,
 				continue;
 			nm_assert (!NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED));
 			nm_assert (!NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE));
+			nm_assert (!NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL));
 			success = nm_settings_plugin_add_connection (plugin,
 			                                             new_connection,
 			                                             &storage,
@@ -1578,6 +1583,7 @@ _update_connection_to_plugin (NMSettings *self,
 		                                                connection,
 		                                                NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED),
 		                                                NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE),
+		                                                NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL),
 		                                                shadowed_storage,
 		                                                shadowed_owned,
 		                                                force_rename,
@@ -1693,8 +1699,9 @@ nm_settings_add_connection (NMSettings *self,
 
 	nm_assert (!NM_FLAGS_ANY (sett_flags, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK));
 
-	if (NM_FLAGS_ANY (sett_flags,   NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
-	                              | NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) {
+	if (NM_FLAGS_ANY (sett_flags,   NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+	                              | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+	                              | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) {
 		nm_assert (new_in_memory);
 		new_in_memory = TRUE;
 	}
@@ -1992,7 +1999,8 @@ nm_settings_update_connection (NMSettings *self,
 			nm_assert (cur_in_memory);
 			nm_assert (!NM_FLAGS_ANY (nm_settings_connection_get_flags (sett_conn),
 			                            NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
-			                          | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE));
+			                          | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+			                          | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL));
 
 			default_wired_clear_tag (self, device, sett_conn, FALSE);
 
@@ -2011,11 +2019,13 @@ nm_settings_update_connection (NMSettings *self,
 
 	if (   persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST
 	    && NM_FLAGS_ANY (sett_mask,   NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
-	                                | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE)
+	                                | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+	                                | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)
 	    && NM_FLAGS_ANY ((sett_flags ^ nm_settings_connection_get_flags (sett_conn)) & sett_mask,
 	                       NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
-	                     | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE)) {
-		/* we update the nm-generated/volatile setting of a profile (which is inherrently
+	                     | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+	                     | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) {
+		/* we update the nm-generated/volatile setting of a profile (which is inherently
 		 * in-memory. The caller did not request to persist this to disk, however we need
 		 * to store the flags in run. */
 		nm_assert (cur_in_memory);
@@ -2043,11 +2053,14 @@ nm_settings_update_connection (NMSettings *self,
 		 * the nm-generated flag after restart/reload, and that cannot be right. If a profile
 		 * ends up on disk, the information who created it gets lost. */
 		nm_assert (!NM_FLAGS_ANY (sett_flags,   NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
-		                                      | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE));
+		                                      | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+		                                      | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL));
 		sett_mask |=   NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
-		             | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE;
+		             | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+		             | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL;
 		sett_flags &= ~(  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
-		                | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE);
+		                | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+		                | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL);
 	}
 
 	if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST) {
@@ -3374,7 +3387,8 @@ have_connection_for_device (NMSettings *self, NMDevice *device)
 			continue;
 
 		if (NM_FLAGS_ANY (nm_settings_connection_get_flags (sett_conn),
-		                  NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
+		                    NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+		                  | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL))
 			continue;
 
 		return TRUE;
diff --git a/src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf b/src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf
index cc6ccb5c..c7a1730a 100644
--- a/src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf
+++ b/src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf
@@ -2,11 +2,10 @@
  "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
  "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
 <busconfig>
-        <policy context="default">
-                <allow send_destination="com.redhat.ifcfgrh1"/>
-        </policy>
-        <policy user="root">
-                <allow own="com.redhat.ifcfgrh1"/>
-        </policy>
+    <policy context="default">
+        <allow send_destination="com.redhat.ifcfgrh1"/>
+    </policy>
+    <policy user="root">
+        <allow own="com.redhat.ifcfgrh1"/>
+    </policy>
 </busconfig>
-
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 338c22ed..d65ae555 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -582,6 +582,11 @@ make_connection_setting (const char *file,
 			g_object_set (s_con, NM_SETTING_CONNECTION_WAIT_DEVICE_TIMEOUT, (int) vint64, NULL);
 	}
 
+	nm_clear_g_free (&value);
+	v = svGetValue (ifcfg, "MUD_URL", &value);
+	if (v)
+		g_object_set (s_con, NM_SETTING_CONNECTION_MUD_URL, v, NULL);
+
 	i_val = NM_SETTING_CONNECTION_MDNS_DEFAULT;
 	if (!svGetValueEnum (ifcfg, "MDNS",
 	                     nm_setting_connection_mdns_get_type (),
@@ -1532,29 +1537,45 @@ make_user_setting (shvarFile *ifcfg)
 	       : NULL;
 }
 
-static NMSetting *
-make_match_setting (shvarFile *ifcfg)
+
+static void
+make_match_setting_prop (const char *v,
+                         NMSettingMatch **s_match,
+                         void (*add_fcn) (NMSettingMatch *s_match, const char *value))
 {
-	NMSettingMatch *s_match = NULL;
 	gs_free const char **strv = NULL;
-	gs_free char *value = NULL;
-	const char *v;
 	gsize i;
 
-	v = svGetValueStr (ifcfg, "MATCH_INTERFACE_NAME", &value);
-	if (!v)
-		return NULL;
-
 	strv = nm_utils_escaped_tokens_split (v, NM_ASCII_SPACES);
 	if (strv) {
 		for (i = 0; strv[i]; i++) {
-			if (!s_match)
-				s_match = (NMSettingMatch *) nm_setting_match_new ();
-			nm_setting_match_add_interface_name (s_match, strv[i]);
+			if (!(*s_match))
+				*s_match = NM_SETTING_MATCH (nm_setting_match_new ());
+			add_fcn (*s_match, strv[i]);
 		}
 	}
+}
+
+static NMSetting *
+make_match_setting (shvarFile *ifcfg)
+{
+	NMSettingMatch *s_match = NULL;
+	gs_free char *value_ifn = NULL;
+	gs_free char *value_kcl = NULL;
+	gs_free char *value_d = NULL;
+	gs_free char *value_p = NULL;
+	const char *v;
 
-	return (NMSetting *) s_match;
+	v = svGetValueStr (ifcfg, "MATCH_INTERFACE_NAME", &value_ifn);
+	make_match_setting_prop (v, &s_match, nm_setting_match_add_interface_name);
+	v = svGetValueStr (ifcfg, "MATCH_KERNEL_COMMAND_LINE", &value_kcl);
+	make_match_setting_prop (v, &s_match, nm_setting_match_add_kernel_command_line);
+	v = svGetValueStr (ifcfg, "MATCH_DRIVER", &value_d);
+	make_match_setting_prop (v, &s_match, nm_setting_match_add_driver);
+	v = svGetValueStr (ifcfg, "MATCH_PATH", &value_p);
+	make_match_setting_prop (v, &s_match, nm_setting_match_add_path);
+
+	return NM_SETTING (s_match);
 }
 
 static NMSetting *
@@ -4244,6 +4265,130 @@ wireless_connection_from_ifcfg (const char *file,
 	return connection;
 }
 
+typedef struct {
+	const char *optname;
+	union {
+		guint32 u32;
+		NMTernary nmternary;
+	} v;
+	gboolean has_value;
+} NMEthtoolIfcfgOption;
+
+/* returns an 'iterator' to words
+ * pointing to the next unprocessed option or NULL
+ * in case of failure */
+static const char **
+_next_ethtool_options_nmternary (const char **words,
+                                 NMEthtoolType ethtool_type,
+                                 NMEthtoolIfcfgOption *out_value)
+{
+	const char *opt;
+	const char *opt_val;
+	const NMEthtoolData *d = NULL;
+	NMTernary onoff = NM_TERNARY_DEFAULT;
+
+	nm_assert (out_value);
+
+	out_value->has_value = FALSE;
+	out_value->optname = NULL;
+
+	if (   !words
+	    || !words[0]
+	    || !words[1])
+		return NULL;
+
+	opt = *words;
+	opt_val = *(++words);
+
+	if (nm_streq0 (opt_val, "on"))
+		onoff = NM_TERNARY_TRUE;
+	else if (nm_streq0 (opt_val, "off"))
+		onoff = NM_TERNARY_FALSE;
+
+	d = nms_ifcfg_rh_utils_get_ethtool_by_name (opt, ethtool_type);
+	if (!d) {
+		if (onoff != NM_TERNARY_DEFAULT) {
+			/* the next value is just the on/off argument. Skip it too. */
+			++words;
+		}
+
+		/* silently ignore unsupported offloading features. */
+		return words;
+	}
+
+	if (onoff == NM_TERNARY_DEFAULT) {
+		PARSE_WARNING ("Expects on/off argument for feature '%s'", opt);
+		return words;
+	}
+
+	out_value->has_value = TRUE;
+	out_value->optname = d->optname;
+	out_value->v.nmternary = onoff;
+
+	return ++words;
+}
+
+/* returns an 'iterator' to words
+ * pointing to the next unprocessed option or NULL
+ * in case of failure */
+static const char **
+_next_ethtool_options_uint32 (const char **words,
+                              NMEthtoolType ethtool_type,
+                              NMEthtoolIfcfgOption *out_value)
+{
+	gint64 i64;
+	const char *opt;
+	const char *opt_val;
+	const NMEthtoolData *d = NULL;
+
+	nm_assert (out_value);
+
+	out_value->has_value = FALSE;
+	out_value->optname = NULL;
+
+	if (   !words
+	    || !words[0]
+	    || !words[1])
+		return NULL;
+
+	opt = *words;
+	opt_val = *(++words);
+
+	i64 = _nm_utils_ascii_str_to_int64 (opt_val, 10, 0, G_MAXUINT32, -1);
+
+	d = nms_ifcfg_rh_utils_get_ethtool_by_name (opt, ethtool_type);
+	if (!d) {
+		if (i64 != -1) {
+			/* the next value is just the on/off argument. Skip it too. */
+			++words;
+		}
+
+		/* silently ignore unsupported offloading features. */
+		return words;
+	}
+
+	out_value->has_value = TRUE;
+	out_value->optname = d->optname;
+	out_value->v.u32 = (guint32) i64;
+
+	return ++words;
+}
+
+static
+NM_UTILS_STRING_TABLE_LOOKUP_DEFINE (
+	_get_ethtool_type_by_name,
+	NMEthtoolType,
+	{ nm_assert (name); },
+	{ return NM_ETHTOOL_TYPE_UNKNOWN; },
+	{ "--coalesce", NM_ETHTOOL_TYPE_COALESCE    },
+	{ "--features", NM_ETHTOOL_TYPE_FEATURE     },
+	{ "--offload",  NM_ETHTOOL_TYPE_FEATURE     },
+	{ "--set-ring", NM_ETHTOOL_TYPE_RING        },
+	{ "-C",         NM_ETHTOOL_TYPE_COALESCE    },
+	{ "-G",         NM_ETHTOOL_TYPE_RING        },
+	{ "-K",         NM_ETHTOOL_TYPE_FEATURE     },
+);
+
 static void
 parse_ethtool_option (const char *value,
                       NMSettingWiredWakeOnLan *out_flags,
@@ -4253,59 +4398,58 @@ parse_ethtool_option (const char *value,
                       const char **out_duplex,
                       NMSettingEthtool **out_s_ethtool)
 {
-	gs_free const char **words = NULL;
 	guint i;
+	const char **w_iter;
+	NMEthtoolIfcfgOption ifcfg_option;
+	gs_free const char **words = NULL;
+	NMEthtoolType ethtool_type = NM_ETHTOOL_TYPE_UNKNOWN;
 
 	words = nm_utils_strsplit_set (value, " \t\n");
 	if (!words)
 		return;
 
-	if (words[0] && words[0][0] == '-') {
-		/* /sbin/ethtool $opts */
-		if (NM_IN_STRSET (words[0], "-K", "--features", "--offload")) {
-			if (!words[1]) {
-				/* first argument must be the interface name. This is invalid. */
-				return;
-			}
-
-			if (!*out_s_ethtool)
-				*out_s_ethtool = NM_SETTING_ETHTOOL (nm_setting_ethtool_new ());
+	if (words[0])
+		ethtool_type = _get_ethtool_type_by_name (words[0]);
 
-			for (i = 2; words[i]; ) {
-				const char *opt = words[i];
-				const char *opt_val = words[++i];
-				const NMEthtoolData *d = NULL;
-				NMTernary onoff = NM_TERNARY_DEFAULT;
+	if (ethtool_type != NM_ETHTOOL_TYPE_UNKNOWN) {
+		if (!words[1]) {
+			/* first argument must be the interface name. This is invalid. */
+			return;
+		}
 
-				if (nm_streq0 (opt_val, "on"))
-					onoff = NM_TERNARY_TRUE;
-				else if (nm_streq0 (opt_val, "off"))
-					onoff = NM_TERNARY_FALSE;
+		if (!*out_s_ethtool)
+			*out_s_ethtool = NM_SETTING_ETHTOOL (nm_setting_ethtool_new ());
 
-				d = nms_ifcfg_rh_utils_get_ethtool_by_name (opt);
+		/* skip ethtool type && interface name */
+		w_iter = &words[2];
 
-				if (!d) {
-					if (onoff != NM_TERNARY_DEFAULT) {
-						/* the next value is just the on/off argument. Skip it too. */
-						i++;
-					}
+		while (w_iter && *w_iter) {
+			if (ethtool_type == NM_ETHTOOL_TYPE_FEATURE) {
+				w_iter = _next_ethtool_options_nmternary (w_iter,
+				                                          ethtool_type,
+				                                          &ifcfg_option);
 
-					/* silently ignore unsupported offloading features. */
-					continue;
+				if (ifcfg_option.has_value) {
+					nm_setting_option_set_boolean (NM_SETTING (*out_s_ethtool),
+					                               ifcfg_option.optname,
+					                               ifcfg_option.v.nmternary != NM_TERNARY_FALSE);
 				}
-
-				i++;
-
-				if (onoff == NM_TERNARY_DEFAULT) {
-					PARSE_WARNING ("Expects on/off argument for feature '%s'", opt);
-					continue;
+			}
+			if (NM_IN_SET (ethtool_type,
+			               NM_ETHTOOL_TYPE_COALESCE,
+			               NM_ETHTOOL_TYPE_RING)) {
+				w_iter = _next_ethtool_options_uint32 (w_iter,
+				                                       ethtool_type,
+				                                       &ifcfg_option);
+
+				if (ifcfg_option.has_value) {
+					nm_setting_option_set_uint32 (NM_SETTING (*out_s_ethtool),
+					                              ifcfg_option.optname,
+					                              ifcfg_option.v.u32);
 				}
-
-				nm_setting_ethtool_set_feature (*out_s_ethtool,
-				                                d->optname,
-				                                onoff);
 			}
 		}
+
 		return;
 	}
 
@@ -5166,24 +5310,33 @@ handle_bridge_option (NMSetting *setting,
 		gboolean only_with_stp;
 		gboolean extended_bool;
 	} m/*etadata*/[] = {
-		{ "DELAY",                      NM_SETTING_BRIDGE_FORWARD_DELAY,              BRIDGE_OPT_TYPE_MAIN,        .only_with_stp = TRUE },
-		{ "priority",                   NM_SETTING_BRIDGE_PRIORITY,                   BRIDGE_OPT_TYPE_OPTION,      .only_with_stp = TRUE },
-		{ "hello_time",                 NM_SETTING_BRIDGE_HELLO_TIME,                 BRIDGE_OPT_TYPE_OPTION,      .only_with_stp = TRUE },
-		{ "max_age",                    NM_SETTING_BRIDGE_MAX_AGE,                    BRIDGE_OPT_TYPE_OPTION,      .only_with_stp = TRUE },
-		{ "ageing_time",                NM_SETTING_BRIDGE_AGEING_TIME,                BRIDGE_OPT_TYPE_OPTION },
-		{ "multicast_querier",          NM_SETTING_BRIDGE_MULTICAST_QUERIER,          BRIDGE_OPT_TYPE_OPTION },
-		{ "multicast_query_use_ifaddr", NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR, BRIDGE_OPT_TYPE_OPTION },
-		{ "multicast_snooping",         NM_SETTING_BRIDGE_MULTICAST_SNOOPING,         BRIDGE_OPT_TYPE_OPTION },
-		{ "multicast_router",           NM_SETTING_BRIDGE_MULTICAST_ROUTER,           BRIDGE_OPT_TYPE_OPTION },
-		{ "vlan_filtering",             NM_SETTING_BRIDGE_VLAN_FILTERING,             BRIDGE_OPT_TYPE_OPTION },
-		{ "default_pvid",               NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID,          BRIDGE_OPT_TYPE_OPTION },
-		{ "group_address",              NM_SETTING_BRIDGE_GROUP_ADDRESS,              BRIDGE_OPT_TYPE_OPTION },
-		{ "group_fwd_mask",             NM_SETTING_BRIDGE_GROUP_FORWARD_MASK,         BRIDGE_OPT_TYPE_OPTION },
-		{ "vlan_protocol",              NM_SETTING_BRIDGE_VLAN_PROTOCOL,              BRIDGE_OPT_TYPE_OPTION },
-		{ "vlan_stats_enabled",         NM_SETTING_BRIDGE_VLAN_STATS_ENABLED,         BRIDGE_OPT_TYPE_OPTION },
-		{ "priority",                   NM_SETTING_BRIDGE_PORT_PRIORITY,              BRIDGE_OPT_TYPE_PORT_OPTION },
-		{ "path_cost",                  NM_SETTING_BRIDGE_PORT_PATH_COST,             BRIDGE_OPT_TYPE_PORT_OPTION },
-		{ "hairpin_mode",               NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE,          BRIDGE_OPT_TYPE_PORT_OPTION, .extended_bool = TRUE, },
+		{ "DELAY",                             NM_SETTING_BRIDGE_FORWARD_DELAY,                     BRIDGE_OPT_TYPE_MAIN,        .only_with_stp = TRUE },
+		{ "priority",                          NM_SETTING_BRIDGE_PRIORITY,                          BRIDGE_OPT_TYPE_OPTION,      .only_with_stp = TRUE },
+		{ "hello_time",                        NM_SETTING_BRIDGE_HELLO_TIME,                        BRIDGE_OPT_TYPE_OPTION,      .only_with_stp = TRUE },
+		{ "max_age",                           NM_SETTING_BRIDGE_MAX_AGE,                           BRIDGE_OPT_TYPE_OPTION,      .only_with_stp = TRUE },
+		{ "ageing_time",                       NM_SETTING_BRIDGE_AGEING_TIME,                       BRIDGE_OPT_TYPE_OPTION },
+		{ "multicast_last_member_count",       NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_COUNT,       BRIDGE_OPT_TYPE_OPTION },
+		{ "multicast_last_member_interval",    NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL,    BRIDGE_OPT_TYPE_OPTION },
+		{ "multicast_membership_interval",     NM_SETTING_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL,     BRIDGE_OPT_TYPE_OPTION },
+		{ "multicast_hash_max",                NM_SETTING_BRIDGE_MULTICAST_HASH_MAX,                BRIDGE_OPT_TYPE_OPTION },
+		{ "multicast_querier",                 NM_SETTING_BRIDGE_MULTICAST_QUERIER,                 BRIDGE_OPT_TYPE_OPTION },
+		{ "multicast_querier_interval",        NM_SETTING_BRIDGE_MULTICAST_QUERIER_INTERVAL,        BRIDGE_OPT_TYPE_OPTION },
+		{ "multicast_query_interval",          NM_SETTING_BRIDGE_MULTICAST_QUERY_INTERVAL,          BRIDGE_OPT_TYPE_OPTION },
+		{ "multicast_query_response_interval", NM_SETTING_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL, BRIDGE_OPT_TYPE_OPTION },
+		{ "multicast_query_use_ifaddr",        NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR,        BRIDGE_OPT_TYPE_OPTION },
+		{ "multicast_snooping",                NM_SETTING_BRIDGE_MULTICAST_SNOOPING,                BRIDGE_OPT_TYPE_OPTION },
+		{ "multicast_router",                  NM_SETTING_BRIDGE_MULTICAST_ROUTER,                  BRIDGE_OPT_TYPE_OPTION },
+		{ "multicast_startup_query_count",     NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT,     BRIDGE_OPT_TYPE_OPTION },
+		{ "multicast_startup_query_interval",  NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL,  BRIDGE_OPT_TYPE_OPTION },
+		{ "vlan_filtering",                    NM_SETTING_BRIDGE_VLAN_FILTERING,                    BRIDGE_OPT_TYPE_OPTION },
+		{ "default_pvid",                      NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID,                 BRIDGE_OPT_TYPE_OPTION },
+		{ "group_address",                     NM_SETTING_BRIDGE_GROUP_ADDRESS,                     BRIDGE_OPT_TYPE_OPTION },
+		{ "group_fwd_mask",                    NM_SETTING_BRIDGE_GROUP_FORWARD_MASK,                BRIDGE_OPT_TYPE_OPTION },
+		{ "vlan_protocol",                     NM_SETTING_BRIDGE_VLAN_PROTOCOL,                     BRIDGE_OPT_TYPE_OPTION },
+		{ "vlan_stats_enabled",                NM_SETTING_BRIDGE_VLAN_STATS_ENABLED,                BRIDGE_OPT_TYPE_OPTION },
+		{ "priority",                          NM_SETTING_BRIDGE_PORT_PRIORITY,                     BRIDGE_OPT_TYPE_PORT_OPTION },
+		{ "path_cost",                         NM_SETTING_BRIDGE_PORT_PATH_COST,                    BRIDGE_OPT_TYPE_PORT_OPTION },
+		{ "hairpin_mode",                      NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE,                 BRIDGE_OPT_TYPE_PORT_OPTION, .extended_bool = TRUE, },
 	};
 	const char *error_message = NULL;
 	int i;
@@ -5236,6 +5389,16 @@ handle_bridge_option (NMSetting *setting,
 				goto warn;
 			}
 			return;
+		case G_TYPE_UINT64: {
+				guint64 vu64;
+
+				vu64 = _nm_utils_ascii_str_to_uint64 (value, 10, 0, G_MAXUINT64, 0);
+				if (!nm_g_object_set_property_uint64 (G_OBJECT (setting), m[i].property_name, vu64, NULL)) {
+					error_message = "number is out of range";
+					goto warn;
+				}
+			}
+			return;
 		case G_TYPE_STRING:
 			nm_g_object_set_property_string (G_OBJECT (setting), m[i].property_name, value, NULL);
 			return;
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
index 15a772bc..d1500dbd 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
@@ -522,9 +522,30 @@ nms_ifcfg_rh_utils_user_key_decode (const char *name, GString *str_buffer)
 /*****************************************************************************/
 
 const char *const _nm_ethtool_ifcfg_names[] = {
-#define ETHT_NAME(eid, ename) \
-[eid - _NM_ETHTOOL_ID_FEATURE_FIRST] = ""ename""
-	/* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */
+#define ETHT_NAME(eid, ename) [eid] = ""ename""
+	/* indexed by NMEthtoolID */
+	ETHT_NAME (NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX,                 "adaptive-rx"),
+	ETHT_NAME (NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX,                 "adaptive-tx"),
+	ETHT_NAME (NM_ETHTOOL_ID_COALESCE_RX_FRAMES,                   "rx-frames"),
+	ETHT_NAME (NM_ETHTOOL_ID_COALESCE_RX_FRAMES_HIGH,              "rx-frames-high"),
+	ETHT_NAME (NM_ETHTOOL_ID_COALESCE_RX_FRAMES_IRQ,               "rx-frames-irq"),
+	ETHT_NAME (NM_ETHTOOL_ID_COALESCE_RX_FRAMES_LOW,               "rx-frames-low"),
+	ETHT_NAME (NM_ETHTOOL_ID_COALESCE_PKT_RATE_HIGH,               "pkt-rate-high"),
+	ETHT_NAME (NM_ETHTOOL_ID_COALESCE_PKT_RATE_LOW,                "pkt-rate-low"),
+	ETHT_NAME (NM_ETHTOOL_ID_COALESCE_RX_USECS,                    "rx-usecs"),
+	ETHT_NAME (NM_ETHTOOL_ID_COALESCE_RX_USECS_HIGH,               "rx-usecs-high"),
+	ETHT_NAME (NM_ETHTOOL_ID_COALESCE_RX_USECS_IRQ,                "rx-usecs-irq"),
+	ETHT_NAME (NM_ETHTOOL_ID_COALESCE_RX_USECS_LOW,                "rx-usecs-low"),
+	ETHT_NAME (NM_ETHTOOL_ID_COALESCE_SAMPLE_INTERVAL,             "sample-interval"),
+	ETHT_NAME (NM_ETHTOOL_ID_COALESCE_STATS_BLOCK_USECS,           "stats-block-usecs"),
+	ETHT_NAME (NM_ETHTOOL_ID_COALESCE_TX_FRAMES,                   "tx-frames"),
+	ETHT_NAME (NM_ETHTOOL_ID_COALESCE_TX_FRAMES_HIGH,              "tx-frames-high"),
+	ETHT_NAME (NM_ETHTOOL_ID_COALESCE_TX_FRAMES_IRQ,               "tx-frames-irq"),
+	ETHT_NAME (NM_ETHTOOL_ID_COALESCE_TX_FRAMES_LOW,               "tx-frames-low"),
+	ETHT_NAME (NM_ETHTOOL_ID_COALESCE_TX_USECS,                    "tx-usecs"),
+	ETHT_NAME (NM_ETHTOOL_ID_COALESCE_TX_USECS_HIGH,               "tx-usecs-high"),
+	ETHT_NAME (NM_ETHTOOL_ID_COALESCE_TX_USECS_IRQ,                "tx-usecs-irq"),
+	ETHT_NAME (NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW,                "tx-usecs-low"),
 	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_ESP_HW_OFFLOAD,               "esp-hw-offload"),
 	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_ESP_TX_CSUM_HW_OFFLOAD,       "esp-tx-csum-hw-offload"),
 	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_FCOE_MTU,                     "fcoe-mtu"),
@@ -577,11 +598,45 @@ const char *const _nm_ethtool_ifcfg_names[] = {
 	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION, "tx-udp_tnl-csum-segmentation"),
 	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION,      "tx-udp_tnl-segmentation"),
 	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT,       "tx-vlan-stag-hw-insert"),
+	ETHT_NAME (NM_ETHTOOL_ID_RING_RX,                              "rx"),
+	ETHT_NAME (NM_ETHTOOL_ID_RING_RX_JUMBO,                        "rx-jumbo"),
+	ETHT_NAME (NM_ETHTOOL_ID_RING_RX_MINI,                         "rx-mini"),
+	ETHT_NAME (NM_ETHTOOL_ID_RING_TX,                              "tx"),
 };
 
 static
 NM_UTILS_STRING_TABLE_LOOKUP_DEFINE (
-	_get_ethtoolid_by_name,
+	_get_ethtoolid_coalesce_by_name,
+	NMEthtoolID,
+	{ nm_assert (name); },
+	{ return NM_ETHTOOL_ID_UNKNOWN; },
+	{ "adaptive-rx",                  NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX                 },
+	{ "adaptive-tx",                  NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX                 },
+	{ "pkt-rate-high",                NM_ETHTOOL_ID_COALESCE_PKT_RATE_HIGH               },
+	{ "pkt-rate-low",                 NM_ETHTOOL_ID_COALESCE_PKT_RATE_LOW                },
+	{ "rx-frames",                    NM_ETHTOOL_ID_COALESCE_RX_FRAMES                   },
+	{ "rx-frames-high",               NM_ETHTOOL_ID_COALESCE_RX_FRAMES_HIGH              },
+	{ "rx-frames-irq",                NM_ETHTOOL_ID_COALESCE_RX_FRAMES_IRQ               },
+	{ "rx-frames-low",                NM_ETHTOOL_ID_COALESCE_RX_FRAMES_LOW               },
+	{ "rx-usecs",                     NM_ETHTOOL_ID_COALESCE_RX_USECS                    },
+	{ "rx-usecs-high",                NM_ETHTOOL_ID_COALESCE_RX_USECS_HIGH               },
+	{ "rx-usecs-irq",                 NM_ETHTOOL_ID_COALESCE_RX_USECS_IRQ                },
+	{ "rx-usecs-low",                 NM_ETHTOOL_ID_COALESCE_RX_USECS_LOW                },
+	{ "sample-interval",              NM_ETHTOOL_ID_COALESCE_SAMPLE_INTERVAL             },
+	{ "stats-block-usecs",            NM_ETHTOOL_ID_COALESCE_STATS_BLOCK_USECS           },
+	{ "tx-frames",                    NM_ETHTOOL_ID_COALESCE_TX_FRAMES                   },
+	{ "tx-frames-high",               NM_ETHTOOL_ID_COALESCE_TX_FRAMES_HIGH              },
+	{ "tx-frames-irq",                NM_ETHTOOL_ID_COALESCE_TX_FRAMES_IRQ               },
+	{ "tx-frames-low",                NM_ETHTOOL_ID_COALESCE_TX_FRAMES_LOW               },
+	{ "tx-usecs",                     NM_ETHTOOL_ID_COALESCE_TX_USECS                    },
+	{ "tx-usecs-high",                NM_ETHTOOL_ID_COALESCE_TX_USECS_HIGH               },
+	{ "tx-usecs-irq",                 NM_ETHTOOL_ID_COALESCE_TX_USECS_IRQ                },
+	{ "tx-usecs-low",                 NM_ETHTOOL_ID_COALESCE_TX_USECS_LOW                },
+);
+
+static
+NM_UTILS_STRING_TABLE_LOOKUP_DEFINE (
+	_get_ethtoolid_feature_by_name,
 	NMEthtoolID,
 	{ nm_assert (name); },
 	{ return NM_ETHTOOL_ID_UNKNOWN; },
@@ -660,12 +715,39 @@ NM_UTILS_STRING_TABLE_LOOKUP_DEFINE (
 	{ "txvlan",                       NM_ETHTOOL_ID_FEATURE_TXVLAN                       },
 );
 
+static
+NM_UTILS_STRING_TABLE_LOOKUP_DEFINE (
+	_get_ethtoolid_ring_by_name,
+	NMEthtoolID,
+	{ nm_assert (name); },
+	{ return NM_ETHTOOL_ID_UNKNOWN; },
+	{ "rx",                           NM_ETHTOOL_ID_RING_RX                              },
+	{ "rx-jumbo",                     NM_ETHTOOL_ID_RING_RX_JUMBO                        },
+	{ "rx-mini",                      NM_ETHTOOL_ID_RING_RX_MINI                         },
+	{ "tx",                           NM_ETHTOOL_ID_RING_TX                              },
+);
+
 const NMEthtoolData *
-nms_ifcfg_rh_utils_get_ethtool_by_name (const char *name)
+nms_ifcfg_rh_utils_get_ethtool_by_name (const char *name,
+                                        NMEthtoolType ethtool_type)
 {
 	NMEthtoolID id;
 
-	id = _get_ethtoolid_by_name (name);
+	switch (ethtool_type) {
+	case NM_ETHTOOL_TYPE_COALESCE:
+		id = _get_ethtoolid_coalesce_by_name (name);
+		break;
+	case NM_ETHTOOL_TYPE_FEATURE:
+		id = _get_ethtoolid_feature_by_name (name);
+		break;
+	case NM_ETHTOOL_TYPE_RING:
+		id = _get_ethtoolid_ring_by_name (name);
+		break;
+	default:
+		nm_assert_not_reached();
+		return NULL;
+	}
+
 	if (id == NM_ETHTOOL_ID_UNKNOWN)
 		return NULL;
 
@@ -892,11 +974,15 @@ const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[] = {
 	_KEY_TYPE ("MAC_ADDRESS_RANDOMIZATION",                   NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
 	_KEY_TYPE ("MASTER",                                      NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
 	_KEY_TYPE ("MASTER_UUID",                                 NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("MATCH_DRIVER",                                NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
 	_KEY_TYPE ("MATCH_INTERFACE_NAME",                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("MATCH_KERNEL_COMMAND_LINE",                   NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("MATCH_PATH",                                  NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
 	_KEY_TYPE ("MDNS",                                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
 	_KEY_TYPE ("METRIC",                                      NMS_IFCFG_KEY_TYPE_IS_NUMBERED ),
 	_KEY_TYPE ("MODE",                                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
 	_KEY_TYPE ("MTU",                                         NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("MUD_URL",                                     NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
 	_KEY_TYPE ("MULTI_CONNECT",                               NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
 	_KEY_TYPE ("MVRP",                                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
 	_KEY_TYPE ("NAME",                                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
index 6c31b269..459c5ae9 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
@@ -33,7 +33,7 @@ typedef struct {
 	NMSIfcfgKeyTypeFlags key_flags;
 } NMSIfcfgKeyTypeInfo;
 
-extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[236];
+extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[240];
 
 const NMSIfcfgKeyTypeInfo *nms_ifcfg_well_known_key_find_info (const char *key, gssize *out_idx);
 
@@ -141,18 +141,19 @@ const NMSIfcfgKeyTypeInfo *nms_ifcfg_rh_utils_is_well_known_key (const char *key
 
 /*****************************************************************************/
 
-extern const char *const _nm_ethtool_ifcfg_names[_NM_ETHTOOL_ID_FEATURE_NUM];
+extern const char *const _nm_ethtool_ifcfg_names[_NM_ETHTOOL_ID_NUM];
 
 static inline const char *
 nms_ifcfg_rh_utils_get_ethtool_name (NMEthtoolID ethtool_id)
 {
-	nm_assert (ethtool_id >= _NM_ETHTOOL_ID_FEATURE_FIRST && ethtool_id <= _NM_ETHTOOL_ID_FEATURE_LAST);
-	nm_assert ((ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST) < G_N_ELEMENTS (_nm_ethtool_ifcfg_names));
-	nm_assert (_nm_ethtool_ifcfg_names[ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST]);
+	nm_assert (ethtool_id >= _NM_ETHTOOL_ID_FIRST && ethtool_id <= _NM_ETHTOOL_ID_LAST);
+	nm_assert (ethtool_id < G_N_ELEMENTS (_nm_ethtool_ifcfg_names));
+	nm_assert (_nm_ethtool_ifcfg_names[ethtool_id]);
 
-	return _nm_ethtool_ifcfg_names[ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST];
+	return _nm_ethtool_ifcfg_names[ethtool_id];
 }
 
-const NMEthtoolData *nms_ifcfg_rh_utils_get_ethtool_by_name (const char *name);
+const NMEthtoolData *nms_ifcfg_rh_utils_get_ethtool_by_name (const char *name,
+                                                             NMEthtoolType ethtool_type);
 
 #endif  /* _UTILS_H_ */
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 e126c77f..199e8e4e 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -1091,6 +1091,27 @@ write_wired_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 	return TRUE;
 }
 
+static void
+_ethtool_gstring_prepare (GString **str,
+                          gboolean *is_first,
+                          char cmdline_flag,
+                          const char *iface)
+{
+	if (!*is_first) {
+		nm_assert (*str && (*str)->len > 0);
+		return;
+	}
+
+	if (!*str)
+		*str = g_string_sized_new (30);
+	else {
+		nm_assert ((*str)->len > 0);
+		g_string_append (*str, " ; ");
+	}
+	g_string_append_printf (*str, "-%c %s", cmdline_flag, iface);
+	*is_first = FALSE;
+}
+
 static gboolean
 write_ethtool_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 {
@@ -1172,7 +1193,10 @@ write_ethtool_setting (NMConnection *connection, shvarFile *ifcfg, GError **erro
 	if (s_ethtool) {
 		NMEthtoolID ethtool_id;
 		NMSettingConnection *s_con;
-		const char *iface = NULL;
+		const char *iface;
+		gboolean is_first;
+		guint32 u32;
+		gboolean b;
 
 		s_con = nm_connection_get_setting_connection (connection);
 		if (s_con) {
@@ -1184,28 +1208,45 @@ write_ethtool_setting (NMConnection *connection, shvarFile *ifcfg, GError **erro
 			                                       || (ch >= '0' && ch <= '9')
 			                                       || NM_IN_SET (ch, '_'))))
 				iface = NULL;
-		}
-
-		if (!str)
-			str = g_string_sized_new (30);
-		else
-			g_string_append (str, " ; ");
-		g_string_append (str, "-K ");
-		g_string_append (str, iface ?: "net0");
+		} else
+			iface = NULL;
+		if (!iface)
+			iface = "net0";
 
+		is_first = TRUE;
 		for (ethtool_id = _NM_ETHTOOL_ID_FEATURE_FIRST; ethtool_id <= _NM_ETHTOOL_ID_FEATURE_LAST; ethtool_id++) {
-			const NMEthtoolData *ed = nm_ethtool_data[ethtool_id];
-			NMTernary val;
+			nm_assert (nms_ifcfg_rh_utils_get_ethtool_name (ethtool_id));
+			if (!nm_setting_option_get_boolean (NM_SETTING (s_ethtool), nm_ethtool_data[ethtool_id]->optname, &b))
+				continue;
+
+			_ethtool_gstring_prepare (&str, &is_first, 'K', iface);
+			g_string_append_c (str, ' ');
+			g_string_append (str, nms_ifcfg_rh_utils_get_ethtool_name (ethtool_id));
+			g_string_append (str, b ? " on" : " off");
+		}
 
+		is_first = TRUE;
+		for (ethtool_id = _NM_ETHTOOL_ID_COALESCE_FIRST; ethtool_id <= _NM_ETHTOOL_ID_COALESCE_LAST; ethtool_id++) {
 			nm_assert (nms_ifcfg_rh_utils_get_ethtool_name (ethtool_id));
+			if (!nm_setting_option_get_uint32 (NM_SETTING (s_ethtool), nm_ethtool_data[ethtool_id]->optname, &u32))
+				continue;
+
+			_ethtool_gstring_prepare (&str, &is_first, 'C', iface);
+			g_string_append_c (str, ' ');
+			g_string_append (str, nms_ifcfg_rh_utils_get_ethtool_name (ethtool_id));
+			g_string_append_printf (str, " %"G_GUINT32_FORMAT, u32);
+		}
 
-			val = nm_setting_ethtool_get_feature (s_ethtool, ed->optname);
-			if (val == NM_TERNARY_DEFAULT)
+		is_first = TRUE;
+		for (ethtool_id = _NM_ETHTOOL_ID_RING_FIRST; ethtool_id <= _NM_ETHTOOL_ID_RING_LAST; ethtool_id++) {
+			nm_assert (nms_ifcfg_rh_utils_get_ethtool_name (ethtool_id));
+			if (!nm_setting_option_get_uint32 (NM_SETTING (s_ethtool), nm_ethtool_data[ethtool_id]->optname, &u32))
 				continue;
 
+			_ethtool_gstring_prepare (&str, &is_first, 'G', iface);
 			g_string_append_c (str, ' ');
 			g_string_append (str, nms_ifcfg_rh_utils_get_ethtool_name (ethtool_id));
-			g_string_append (str, val == NM_TERNARY_TRUE ? " on" : " off");
+			g_string_append_printf (str, " %"G_GUINT32_FORMAT, u32);
 		}
 	}
 
@@ -1374,38 +1415,22 @@ write_team_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wired,
 	return TRUE;
 }
 
-static guint32
-get_setting_default_uint (NMSetting *setting, const char *prop)
+static gboolean
+get_setting_default_boolean (gpointer setting, const char *prop)
 {
-	GParamSpec *pspec;
-	GValue val = G_VALUE_INIT;
-	guint32 ret = 0;
-
-	pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (setting), prop);
-	g_assert (pspec);
-	g_value_init (&val, pspec->value_type);
-	g_param_value_set_default (pspec, &val);
-	g_assert (G_VALUE_HOLDS_UINT (&val));
-	ret = g_value_get_uint (&val);
-	g_value_unset (&val);
-	return ret;
+	return NM_G_PARAM_SPEC_GET_DEFAULT_BOOLEAN (g_object_class_find_property (G_OBJECT_GET_CLASS (setting), prop));
 }
 
-static gboolean
-get_setting_default_boolean (NMSetting *setting, const char *prop)
+static guint
+get_setting_default_uint (gpointer setting, const char *prop)
 {
-	GParamSpec *pspec;
-	GValue val = G_VALUE_INIT;
-	gboolean ret = 0;
-
-	pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (setting), prop);
-	g_assert (pspec);
-	g_value_init (&val, pspec->value_type);
-	g_param_value_set_default (pspec, &val);
-	g_assert (G_VALUE_HOLDS_BOOLEAN (&val));
-	ret = g_value_get_boolean (&val);
-	g_value_unset (&val);
-	return ret;
+	return NM_G_PARAM_SPEC_GET_DEFAULT_UINT (g_object_class_find_property (G_OBJECT_GET_CLASS (setting), prop));
+}
+
+static guint64
+get_setting_default_uint64 (gpointer setting, const char *prop)
+{
+	return NM_G_PARAM_SPEC_GET_DEFAULT_UINT64 (g_object_class_find_property (G_OBJECT_GET_CLASS (setting), prop));
 }
 
 static gboolean
@@ -1448,7 +1473,8 @@ static gboolean
 write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wired, GError **error)
 {
 	NMSettingBridge *s_bridge;
-	guint32 i;
+	guint32 u32;
+	guint64 u64;
 	gboolean b;
 	const char *s;
 	GString *opts;
@@ -1471,32 +1497,32 @@ write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wire
 	if (nm_setting_bridge_get_stp (s_bridge)) {
 		svSetValueStr (ifcfg, "STP", "yes");
 
-		i = nm_setting_bridge_get_forward_delay (s_bridge);
-		if (i != get_setting_default_uint (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_FORWARD_DELAY))
-			svSetValueInt64 (ifcfg, "DELAY", i);
+		u32 = nm_setting_bridge_get_forward_delay (s_bridge);
+		if (u32 != get_setting_default_uint (s_bridge, NM_SETTING_BRIDGE_FORWARD_DELAY))
+			svSetValueInt64 (ifcfg, "DELAY", u32);
 
 		g_string_append_printf (opts, "priority=%u", nm_setting_bridge_get_priority (s_bridge));
 
-		i = nm_setting_bridge_get_hello_time (s_bridge);
-		if (i != get_setting_default_uint (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_HELLO_TIME)) {
+		u32 = nm_setting_bridge_get_hello_time (s_bridge);
+		if (u32 != get_setting_default_uint (s_bridge, NM_SETTING_BRIDGE_HELLO_TIME)) {
 			if (opts->len)
 				g_string_append_c (opts, ' ');
-			g_string_append_printf (opts, "hello_time=%u", i);
+			g_string_append_printf (opts, "hello_time=%u", u32);
 		}
 
-		i = nm_setting_bridge_get_max_age (s_bridge);
-		if (i != get_setting_default_uint (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_MAX_AGE)) {
+		u32 = nm_setting_bridge_get_max_age (s_bridge);
+		if (u32 != get_setting_default_uint (s_bridge, NM_SETTING_BRIDGE_MAX_AGE)) {
 			if (opts->len)
 				g_string_append_c (opts, ' ');
-			g_string_append_printf (opts, "max_age=%u", i);
+			g_string_append_printf (opts, "max_age=%u", u32);
 		}
 	}
 
-	i = nm_setting_bridge_get_ageing_time (s_bridge);
-	if (i != get_setting_default_uint (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_AGEING_TIME)) {
+	u32 = nm_setting_bridge_get_ageing_time (s_bridge);
+	if (u32 != get_setting_default_uint (s_bridge, NM_SETTING_BRIDGE_AGEING_TIME)) {
 		if (opts->len)
 			g_string_append_c (opts, ' ');
-		g_string_append_printf (opts, "ageing_time=%u", i);
+		g_string_append_printf (opts, "ageing_time=%u", u32);
 	}
 
 	s = nm_setting_bridge_get_group_address (s_bridge);
@@ -1506,34 +1532,98 @@ write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wire
 		g_string_append_printf (opts, "group_address=%s", s);
 	}
 
-	i = nm_setting_bridge_get_group_forward_mask (s_bridge);
-	if (i != get_setting_default_uint (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_GROUP_FORWARD_MASK)) {
+	u32 = nm_setting_bridge_get_group_forward_mask (s_bridge);
+	if (u32 != get_setting_default_uint (s_bridge, NM_SETTING_BRIDGE_GROUP_FORWARD_MASK)) {
 		if (opts->len)
 			g_string_append_c (opts, ' ');
-		g_string_append_printf (opts, "group_fwd_mask=%u", i);
+		g_string_append_printf (opts, "group_fwd_mask=%u", u32);
+	}
+
+
+	u32 = nm_setting_bridge_get_multicast_hash_max (s_bridge);
+	if (u32 != get_setting_default_uint (s_bridge, NM_SETTING_BRIDGE_MULTICAST_HASH_MAX)) {
+		if (opts->len)
+			g_string_append_c (opts, ' ');
+		g_string_append_printf (opts, "multicast_hash_max=%u", u32);
+	}
+
+	u32 = nm_setting_bridge_get_multicast_last_member_count (s_bridge);
+	if (u32 != get_setting_default_uint (s_bridge, NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_COUNT)) {
+		if (opts->len)
+			g_string_append_c (opts, ' ');
+		g_string_append_printf (opts, "multicast_last_member_count=%u", u32);
+	}
+
+	u64 = nm_setting_bridge_get_multicast_last_member_interval (s_bridge);
+	if (u64 != get_setting_default_uint64 (s_bridge, NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL)) {
+		if (opts->len)
+			g_string_append_c (opts, ' ');
+		g_string_append_printf (opts, "multicast_last_member_interval=%"G_GUINT64_FORMAT, u64);
+	}
+
+	u64 = nm_setting_bridge_get_multicast_membership_interval (s_bridge);
+	if (u64 != get_setting_default_uint64 (s_bridge, NM_SETTING_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL)) {
+		if (opts->len)
+			g_string_append_c (opts, ' ');
+		g_string_append_printf (opts, "multicast_membership_interval=%"G_GUINT64_FORMAT, u64);
 	}
 
 	b = nm_setting_bridge_get_multicast_querier (s_bridge);
-	if (b != get_setting_default_boolean (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_MULTICAST_QUERIER)) {
+	if (b != get_setting_default_boolean (s_bridge, NM_SETTING_BRIDGE_MULTICAST_QUERIER)) {
 		if (opts->len)
 			g_string_append_c (opts, ' ');
 		g_string_append_printf (opts, "multicast_querier=%u", (guint) b);
 	}
 
+	u64 = nm_setting_bridge_get_multicast_querier_interval (s_bridge);
+	if (u64 != get_setting_default_uint64 (s_bridge, NM_SETTING_BRIDGE_MULTICAST_QUERIER_INTERVAL)) {
+		if (opts->len)
+			g_string_append_c (opts, ' ');
+		g_string_append_printf (opts, "multicast_querier_interval=%"G_GUINT64_FORMAT, u64);
+	}
+
+	u64 = nm_setting_bridge_get_multicast_query_interval (s_bridge);
+	if (u64 != get_setting_default_uint64 (s_bridge, NM_SETTING_BRIDGE_MULTICAST_QUERY_INTERVAL)) {
+		if (opts->len)
+			g_string_append_c (opts, ' ');
+		g_string_append_printf (opts, "multicast_query_interval=%"G_GUINT64_FORMAT, u64);
+	}
+
+	u64 = nm_setting_bridge_get_multicast_query_response_interval (s_bridge);
+	if (u64 != get_setting_default_uint64 (s_bridge, NM_SETTING_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL)) {
+		if (opts->len)
+			g_string_append_c (opts, ' ');
+		g_string_append_printf (opts, "multicast_query_response_interval=%"G_GUINT64_FORMAT, u64);
+	}
+
 	b = nm_setting_bridge_get_multicast_query_use_ifaddr (s_bridge);
-	if (b != get_setting_default_boolean (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR)) {
+	if (b != get_setting_default_boolean (s_bridge, NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR)) {
 		if (opts->len)
 			g_string_append_c (opts, ' ');
 		g_string_append_printf (opts, "multicast_query_use_ifaddr=%u", (guint) b);
 	}
 
 	b = nm_setting_bridge_get_multicast_snooping (s_bridge);
-	if (b != get_setting_default_boolean (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_MULTICAST_SNOOPING)) {
+	if (b != get_setting_default_boolean (s_bridge, NM_SETTING_BRIDGE_MULTICAST_SNOOPING)) {
 		if (opts->len)
 			g_string_append_c (opts, ' ');
 		g_string_append_printf (opts, "multicast_snooping=%u", (guint32) b);
 	}
 
+	u32 = nm_setting_bridge_get_multicast_startup_query_count (s_bridge);
+	if (u32 != get_setting_default_uint (s_bridge, NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT)) {
+		if (opts->len)
+			g_string_append_c (opts, ' ');
+		g_string_append_printf (opts, "multicast_startup_query_count=%u", u32);
+	}
+
+	u64 = nm_setting_bridge_get_multicast_startup_query_interval (s_bridge);
+	if (u64 != get_setting_default_uint64 (s_bridge, NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL)) {
+		if (opts->len)
+			g_string_append_c (opts, ' ');
+		g_string_append_printf (opts, "multicast_startup_query_interval=%"G_GUINT64_FORMAT, u64);
+	}
+
 	s = nm_setting_bridge_get_multicast_router (s_bridge);
 	if (s) {
 		if (opts->len)
@@ -1542,17 +1632,17 @@ write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wire
 	}
 
 	b = nm_setting_bridge_get_vlan_filtering (s_bridge);
-	if (b != get_setting_default_boolean (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_VLAN_FILTERING)) {
+	if (b != get_setting_default_boolean (s_bridge, NM_SETTING_BRIDGE_VLAN_FILTERING)) {
 		if (opts->len)
 			g_string_append_c (opts, ' ');
 		g_string_append_printf (opts, "vlan_filtering=%u", (guint32) b);
 	}
 
-	i = nm_setting_bridge_get_vlan_default_pvid (s_bridge);
-	if (i != get_setting_default_uint (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID)) {
+	u32 = nm_setting_bridge_get_vlan_default_pvid (s_bridge);
+	if (u32 != get_setting_default_uint (s_bridge, NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID)) {
 		if (opts->len)
 			g_string_append_c (opts, ' ');
-		g_string_append_printf (opts, "default_pvid=%u", i);
+		g_string_append_printf (opts, "default_pvid=%u", u32);
 	}
 
 	s = nm_setting_bridge_get_vlan_protocol (s_bridge);
@@ -1563,7 +1653,7 @@ write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wire
 	}
 
 	b = nm_setting_bridge_get_vlan_stats_enabled (s_bridge);
-	if (b != get_setting_default_boolean (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_VLAN_STATS_ENABLED)) {
+	if (b != get_setting_default_boolean (s_bridge, NM_SETTING_BRIDGE_VLAN_STATS_ENABLED)) {
 		if (opts->len)
 			g_string_append_c (opts, ' ');
 		g_string_append_printf (opts, "vlan_stats_enabled=%u", (guint) b);
@@ -1591,7 +1681,7 @@ static gboolean
 write_bridge_port_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 {
 	NMSettingBridgePort *s_port;
-	guint32 i;
+	guint32 u32;
 	GString *string;
 
 	s_port = nm_connection_get_setting_bridge_port (connection);
@@ -1601,15 +1691,15 @@ write_bridge_port_setting (NMConnection *connection, shvarFile *ifcfg, GError **
 	/* Bridge options */
 	string = g_string_sized_new (32);
 
-	i = nm_setting_bridge_port_get_priority (s_port);
-	if (i != get_setting_default_uint (NM_SETTING (s_port), NM_SETTING_BRIDGE_PORT_PRIORITY))
-		g_string_append_printf (string, "priority=%u", i);
+	u32 = nm_setting_bridge_port_get_priority (s_port);
+	if (u32 != get_setting_default_uint (NM_SETTING (s_port), NM_SETTING_BRIDGE_PORT_PRIORITY))
+		g_string_append_printf (string, "priority=%u", u32);
 
-	i = nm_setting_bridge_port_get_path_cost (s_port);
-	if (i != get_setting_default_uint (NM_SETTING (s_port), NM_SETTING_BRIDGE_PORT_PATH_COST)) {
+	u32 = nm_setting_bridge_port_get_path_cost (s_port);
+	if (u32 != get_setting_default_uint (NM_SETTING (s_port), NM_SETTING_BRIDGE_PORT_PATH_COST)) {
 		if (string->len)
 			g_string_append_c (string, ' ');
-		g_string_append_printf (string, "path_cost=%u", i);
+		g_string_append_printf (string, "path_cost=%u", u32);
 	}
 
 	if (nm_setting_bridge_port_get_hairpin_mode (s_port)) {
@@ -1804,7 +1894,7 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 	NMSettingConnectionMdns mdns;
 	NMSettingConnectionLlmnr llmnr;
 	guint32 vuint32;
-	const char *tmp;
+	const char *tmp, *mud_url;
 
 	svSetValueStr (ifcfg, "NAME", nm_setting_connection_get_id (s_con));
 	svSetValueStr (ifcfg, "UUID", nm_setting_connection_get_uuid (s_con));
@@ -1879,6 +1969,9 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 	svSetValueStr (ifcfg, "TEAM_MASTER_UUID", NULL);
 	svSetValueStr (ifcfg, "TEAM_MASTER", NULL);
 
+	mud_url = nm_setting_connection_get_mud_url (s_con);
+	svSetValue (ifcfg, "MUD_URL", mud_url);
+
 	master = nm_setting_connection_get_master (s_con);
 	if (master) {
 		/* The reader prefers the *_UUID variants, however we still try to resolve
@@ -2301,36 +2394,61 @@ write_tc_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 	return TRUE;
 }
 
-static gboolean
-write_match_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
+static void
+write_match_setting (NMConnection *connection, shvarFile *ifcfg)
 {
-	NMSettingMatch *s_match;
 	nm_auto_free_gstring GString *str = NULL;
+	NMSettingMatch *s_match;
 	guint i, num;
+	const char *name;
 
 	s_match = (NMSettingMatch *) nm_connection_get_setting (connection, NM_TYPE_SETTING_MATCH);
 	if (!s_match)
-		return TRUE;
-
-	num = nm_setting_match_get_num_interface_names (s_match);
-	for (i = 0; i < num; i++) {
-		const char *name;
-
-		name = nm_setting_match_get_interface_name (s_match, i);
-		if (!name || !name[0])
-			continue;
+		return;
 
-		if (!str)
-			str = g_string_new ("");
-		else
-			g_string_append_c (str, ' ');
-		nm_utils_escaped_tokens_escape_gstr (name, NM_ASCII_SPACES, str);
+	num = nm_setting_match_get_num_drivers (s_match);
+	if (num > 0) {
+		nm_gstring_prepare (&str);
+		for (i = 0; i < num; i++) {
+			name = nm_setting_match_get_driver (s_match, i);
+			nm_gstring_add_space_delimiter (str);
+			nm_utils_escaped_tokens_escape_gstr (name, NM_ASCII_SPACES, str);
+		}
+		svSetValueStr (ifcfg, "MATCH_DRIVER", str->str);
 	}
 
-	if (str)
+	num = nm_setting_match_get_num_interface_names (s_match);
+	if (num > 0) {
+		nm_gstring_prepare (&str);
+		for (i = 0; i < num; i++) {
+			name = nm_setting_match_get_interface_name (s_match, i);
+			nm_gstring_add_space_delimiter (str);
+			nm_utils_escaped_tokens_escape_gstr (name, NM_ASCII_SPACES, str);
+		}
 		svSetValueStr (ifcfg, "MATCH_INTERFACE_NAME", str->str);
+	}
 
-	return TRUE;
+	num = nm_setting_match_get_num_kernel_command_lines (s_match);
+	if (num > 0) {
+		nm_gstring_prepare (&str);
+		for (i = 0; i < num; i++) {
+			name = nm_setting_match_get_kernel_command_line (s_match, i);
+			nm_gstring_add_space_delimiter (str);
+			nm_utils_escaped_tokens_escape_gstr (name, NM_ASCII_SPACES, str);
+		}
+		svSetValueStr (ifcfg, "MATCH_KERNEL_COMMAND_LINE", str->str);
+	}
+
+	num = nm_setting_match_get_num_paths (s_match);
+	if (num > 0) {
+		nm_gstring_prepare (&str);
+		for (i = 0; i < num; i++) {
+			name = nm_setting_match_get_path (s_match, i);
+			nm_gstring_add_space_delimiter (str);
+			nm_utils_escaped_tokens_escape_gstr (name, NM_ASCII_SPACES, str);
+		}
+		svSetValueStr (ifcfg, "MATCH_PATH", str->str);
+	}
 }
 
 static void
@@ -3090,8 +3208,7 @@ do_write_construct (NMConnection *connection,
 	if (!write_user_setting (connection, ifcfg, error))
 		return FALSE;
 
-	if (!write_match_setting (connection, ifcfg, error))
-		return FALSE;
+	write_match_setting (connection, ifcfg);
 
 	write_sriov_setting (connection, ifcfg);
 
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected
index f0cd36cb..9499278e 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected
@@ -1,7 +1,9 @@
 TYPE=Ethernet
 PROXY_METHOD=none
 BROWSER_ONLY=no
+MATCH_DRIVER="!virtio e1000e"
 MATCH_INTERFACE_NAME="ens* eth\\ 1? !veth*"
+MATCH_KERNEL_COMMAND_LINE=!ip=
 BOOTPROTO=dhcp
 DEFROUTE=yes
 IPV4_FAILURE_FATAL=no
diff --git a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
index ffedff01..a5025f3b 100644
--- a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
+++ b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
@@ -4690,6 +4690,9 @@ test_write_wired_match (void)
 	nm_setting_match_add_interface_name (s_match, "ens*");
 	nm_setting_match_add_interface_name (s_match, "eth 1?");
 	nm_setting_match_add_interface_name (s_match, "!veth*");
+	nm_setting_match_add_driver (s_match, "!virtio");
+	nm_setting_match_add_driver (s_match, "e1000e");
+	nm_setting_match_add_kernel_command_line (s_match, "!ip=");
 	nm_connection_add_setting (connection, NM_SETTING (s_match));
 
 	nmtst_assert_connection_verifies (connection);
@@ -10364,37 +10367,48 @@ test_ethtool_names (void)
 		{ NM_ETHTOOL_ID_FEATURE_RXVLAN, "rx-vlan-hw-parse" },
 		{ NM_ETHTOOL_ID_FEATURE_TXVLAN, "tx-vlan-hw-insert" },
 	};
+	const struct {
+		guint nm_ethtool_id_first;
+		guint nm_ethtool_id_last;
+	} s_idxs[] = {
+		{ _NM_ETHTOOL_ID_FEATURE_FIRST,  _NM_ETHTOOL_ID_FEATURE_LAST },
+		{ _NM_ETHTOOL_ID_COALESCE_FIRST, _NM_ETHTOOL_ID_COALESCE_LAST },
+		{ _NM_ETHTOOL_ID_RING_FIRST,     _NM_ETHTOOL_ID_RING_LAST },
+	};
 	const NMEthtoolData *data;
 	NMEthtoolID id;
-	int i;
+	guint i, k;
 
-	for (id = _NM_ETHTOOL_ID_FEATURE_FIRST; id <= _NM_ETHTOOL_ID_FEATURE_LAST; id++) {
-		const char *ifcfg_rh_name;
-		int idx;
+	for (k = 0; k < sizeof(s_idxs) / sizeof(*s_idxs); ++k) {
+		for (id = s_idxs[k].nm_ethtool_id_first; id <= s_idxs[k].nm_ethtool_id_last; id++) {
+			const char *ifcfg_rh_name;
 
-		idx = id - _NM_ETHTOOL_ID_FEATURE_FIRST;
-		g_assert (idx >= 0);
-		g_assert (idx < G_N_ELEMENTS (_nm_ethtool_ifcfg_names));
-		ifcfg_rh_name = _nm_ethtool_ifcfg_names[idx];
-		g_assert (ifcfg_rh_name && ifcfg_rh_name[0]);
+			g_assert (id >= 0);
+			g_assert (id < G_N_ELEMENTS (_nm_ethtool_ifcfg_names));
+			ifcfg_rh_name = _nm_ethtool_ifcfg_names[id];
+			g_assert (ifcfg_rh_name && ifcfg_rh_name[0]);
 
-		for (i = 0; i < G_N_ELEMENTS (_nm_ethtool_ifcfg_names); i++) {
-			if (i != idx)
-				g_assert_cmpstr (ifcfg_rh_name, !=, _nm_ethtool_ifcfg_names[i]);
-		}
+			for (i = s_idxs[k].nm_ethtool_id_first; i < s_idxs[k].nm_ethtool_id_last; i++) {
+				if (i != id)
+					g_assert_cmpstr (ifcfg_rh_name, !=, _nm_ethtool_ifcfg_names[i]);
+			}
 
-		g_assert_cmpstr (nms_ifcfg_rh_utils_get_ethtool_name (id), ==, ifcfg_rh_name);
+			g_assert_cmpstr (nms_ifcfg_rh_utils_get_ethtool_name (id), ==, ifcfg_rh_name);
 
-		data = nms_ifcfg_rh_utils_get_ethtool_by_name (ifcfg_rh_name);
-		g_assert (data);
-		g_assert (data->id == id);
+			data = nms_ifcfg_rh_utils_get_ethtool_by_name (ifcfg_rh_name, nm_ethtool_id_to_type (id));
+
+			g_assert (data);
+			g_assert (data->id == id);
+		}
 	}
 
 	for (i = 0; i < G_N_ELEMENTS (kernel_names); i++) {
 		const char *name = kernel_names[i].kernel_name;
 
 		id = kernel_names[i].ethtool_id;
-		data = nms_ifcfg_rh_utils_get_ethtool_by_name (name);
+
+		data = nms_ifcfg_rh_utils_get_ethtool_by_name (name, nm_ethtool_id_to_type (id));
+
 		g_assert (data);
 		g_assert (data->id == id);
 		g_assert_cmpstr (nms_ifcfg_rh_utils_get_ethtool_name (id), !=, name);
diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
index 2906b399..c84bd78d 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-plugin.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
@@ -89,22 +89,33 @@ G_DEFINE_TYPE (NMSKeyfilePlugin, nms_keyfile_plugin, NM_TYPE_SETTINGS_PLUGIN)
 /*****************************************************************************/
 
 static const char *
-_extra_flags_to_string (char *str, gsize str_len, gboolean is_nm_generated, gboolean is_volatile)
+_extra_flags_to_string (char *str, gsize str_len, gboolean is_nm_generated, gboolean is_volatile, gboolean is_external)
 {
 	const char *str0 = str;
 
 	if (   !is_nm_generated
-	    && !is_volatile)
+	    && !is_volatile
+	    && !is_external)
 		nm_utils_strbuf_append_str (&str, &str_len, "");
 	else {
-		nm_utils_strbuf_append_str (&str, &str_len, " (");
+		char ch = '(';
+
+		nm_utils_strbuf_append_c (&str, &str_len, ' ');
 		if (is_nm_generated) {
+			nm_utils_strbuf_append_c (&str, &str_len, ch);
 			nm_utils_strbuf_append_str (&str, &str_len, "nm-generated");
-			if (is_volatile)
-				nm_utils_strbuf_append_c (&str, &str_len, ',');
+			ch = ',';
 		}
-		if (is_volatile)
+		if (is_volatile) {
+			nm_utils_strbuf_append_c (&str, &str_len, ch);
 			nm_utils_strbuf_append_str (&str, &str_len, "volatile");
+			ch = ',';
+		}
+		if (is_external) {
+			nm_utils_strbuf_append_c (&str, &str_len, ch);
+			nm_utils_strbuf_append_str (&str, &str_len, "external");
+			ch = ',';
+		}
 		nm_utils_strbuf_append_c (&str, &str_len, ')');
 	}
 
@@ -202,6 +213,7 @@ _read_from_file (const char *full_filename,
                  struct stat *out_stat,
                  NMTernary *out_is_nm_generated,
                  NMTernary *out_is_volatile,
+                 NMTernary *out_is_external,
                  char **out_shadowed_storage,
                  NMTernary *out_shadowed_owned,
                  GError **error)
@@ -215,6 +227,7 @@ _read_from_file (const char *full_filename,
 	                                           out_stat,
 	                                           out_is_nm_generated,
 	                                           out_is_volatile,
+	                                           out_is_external,
 	                                           out_shadowed_storage,
 	                                           out_shadowed_owned,
 	                                           error);
@@ -284,8 +297,9 @@ _load_file (NMSKeyfilePlugin *self,
 {
 	NMSKeyfilePluginPrivate *priv;
 	gs_unref_object NMConnection *connection = NULL;
-	NMTernary is_volatile_opt;
 	NMTernary is_nm_generated_opt;
+	NMTernary is_volatile_opt;
+	NMTernary is_external_opt;
 	NMTernary shadowed_owned_opt;
 	gs_free char *shadowed_storage = NULL;
 	gs_free_error GError *local = NULL;
@@ -350,6 +364,7 @@ _load_file (NMSKeyfilePlugin *self,
 	                              &st,
 	                              &is_nm_generated_opt,
 	                              &is_volatile_opt,
+	                              &is_external_opt,
 	                              &shadowed_storage,
 	                              &shadowed_owned_opt,
 	                              &local);
@@ -367,6 +382,7 @@ _load_file (NMSKeyfilePlugin *self,
 	                                           storage_type,
 	                                           is_nm_generated_opt,
 	                                           is_volatile_opt,
+	                                           is_external_opt,
 	                                           shadowed_storage,
 	                                           shadowed_owned_opt,
 	                                           &st.st_mtim);
@@ -732,6 +748,7 @@ nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self,
                                    gboolean in_memory,
                                    gboolean is_nm_generated,
                                    gboolean is_volatile,
+                                   gboolean is_external,
                                    const char *shadowed_storage,
                                    gboolean shadowed_owned,
                                    NMSettingsStorage **out_storage,
@@ -756,6 +773,7 @@ nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self,
 	nm_assert (   in_memory
 	           || (   !is_nm_generated
 	               && !is_volatile
+	               && !is_external
 	               && !shadowed_storage
 	               && !shadowed_owned));
 
@@ -770,6 +788,7 @@ nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self,
 	if (!nms_keyfile_writer_connection (connection,
 	                                    is_nm_generated,
 	                                    is_volatile,
+	                                    is_external,
 	                                    shadowed_storage,
 	                                    shadowed_owned,
 	                                      storage_type == NMS_KEYFILE_STORAGE_TYPE_ETC
@@ -807,7 +826,7 @@ nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self,
 	       uuid,
 	       nm_connection_get_id (connection),
 	       full_filename,
-	       _extra_flags_to_string (strbuf, sizeof (strbuf), is_nm_generated, is_volatile),
+	       _extra_flags_to_string (strbuf, sizeof (strbuf), is_nm_generated, is_volatile, is_external),
 	       NM_PRINT_FMT_QUOTED (shadowed_storage, " (shadows \"", shadowed_storage, shadowed_owned ? "\", owned)" : "\")", ""));
 
 	storage = nms_keyfile_storage_new_connection (self,
@@ -816,6 +835,7 @@ nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self,
 	                                              storage_type,
 	                                              is_nm_generated ? NM_TERNARY_TRUE : NM_TERNARY_FALSE,
 	                                              is_volatile ? NM_TERNARY_TRUE : NM_TERNARY_FALSE,
+	                                              is_external ? NM_TERNARY_TRUE : NM_TERNARY_FALSE,
 	                                              shadowed_storage,
 	                                              shadowed_owned ? NM_TERNARY_TRUE : NM_TERNARY_FALSE,
 	                                              nm_sett_util_stat_mtime (full_filename, FALSE, &mtime));
@@ -840,6 +860,7 @@ add_connection (NMSettingsPlugin *plugin,
 	                                          FALSE,
 	                                          FALSE,
 	                                          FALSE,
+	                                          FALSE,
 	                                          NULL,
 	                                          FALSE,
 	                                          out_storage,
@@ -853,6 +874,7 @@ nms_keyfile_plugin_update_connection (NMSKeyfilePlugin *self,
                                       NMConnection *connection,
                                       gboolean is_nm_generated,
                                       gboolean is_volatile,
+                                      gboolean is_external,
                                       const char *shadowed_storage,
                                       gboolean shadowed_owned,
                                       gboolean force_rename,
@@ -883,6 +905,7 @@ nms_keyfile_plugin_update_connection (NMSKeyfilePlugin *self,
 	nm_assert (   storage->storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN
 	           || (   !is_nm_generated
 	               && !is_volatile
+	               && !is_external
 	               && !shadowed_storage
 	               && !shadowed_owned));
 	nm_assert (!shadowed_owned || shadowed_storage);
@@ -895,6 +918,7 @@ nms_keyfile_plugin_update_connection (NMSKeyfilePlugin *self,
 	if (!nms_keyfile_writer_connection (connection,
 	                                    is_nm_generated,
 	                                    is_volatile,
+	                                    is_external,
 	                                    shadowed_storage,
 	                                    shadowed_owned,
 	                                      storage->storage_type == NMS_KEYFILE_STORAGE_TYPE_ETC
@@ -933,11 +957,12 @@ nms_keyfile_plugin_update_connection (NMSKeyfilePlugin *self,
 	       full_filename,
 	       uuid,
 	       nm_connection_get_id (connection),
-	       _extra_flags_to_string (strbuf, sizeof (strbuf), is_nm_generated, is_volatile),
+	       _extra_flags_to_string (strbuf, sizeof (strbuf), is_nm_generated, is_volatile, is_external),
 	       NM_PRINT_FMT_QUOTED (shadowed_storage, shadowed_owned ? " (owns \"" : " (shadows \"", shadowed_storage, "\")", ""));
 
 	storage->u.conn_data.is_nm_generated = is_nm_generated;
 	storage->u.conn_data.is_volatile     = is_volatile;
+	storage->u.conn_data.is_external     = is_external;
 	storage->u.conn_data.stat_mtime      = *nm_sett_util_stat_mtime (full_filename, FALSE, &mtime);
 	storage->u.conn_data.shadowed_owned  = shadowed_owned;
 
@@ -959,6 +984,7 @@ update_connection (NMSettingsPlugin *plugin,
 	                                             connection,
 	                                             FALSE,
 	                                             FALSE,
+	                                             FALSE,
 	                                             NULL,
 	                                             FALSE,
 	                                             FALSE,
diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.h b/src/settings/plugins/keyfile/nms-keyfile-plugin.h
index e885f16c..603f0c26 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-plugin.h
+++ b/src/settings/plugins/keyfile/nms-keyfile-plugin.h
@@ -31,6 +31,7 @@ gboolean nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self,
                                             gboolean in_memory,
                                             gboolean is_nm_generated,
                                             gboolean is_volatile,
+                                            gboolean is_external,
                                             const char *shadowed_storage,
                                             gboolean shadowed_owned,
                                             NMSettingsStorage **out_storage,
@@ -42,6 +43,7 @@ gboolean nms_keyfile_plugin_update_connection (NMSKeyfilePlugin *self,
                                                NMConnection *connection,
                                                gboolean is_nm_generated,
                                                gboolean is_volatile,
+                                               gboolean is_external,
                                                const char *shadowed_storage,
                                                gboolean shadowed_owned,
                                                gboolean force_rename,
diff --git a/src/settings/plugins/keyfile/nms-keyfile-reader.c b/src/settings/plugins/keyfile/nms-keyfile-reader.c
index e8c9f9fe..577709d8 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-reader.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-reader.c
@@ -17,11 +17,15 @@
 /*****************************************************************************/
 
 static const char *
-_fmt_warn (const char *group, NMSetting *setting, const char *property_name, const char *message, char **out_message)
+_fmt_warn (const NMKeyfileHandlerData *handler_data, char **out_message)
 {
-	const char *setting_name = setting ? nm_setting_get_name (setting) : NULL;
+	const char *group = handler_data->kf_group_name;
+	const char *message = _nm_keyfile_handler_data_warn_get_message (handler_data);
 
 	if (group) {
+		NMSetting *setting = handler_data->cur_setting;
+		const char *property_name = handler_data->cur_property;
+		const char *setting_name = setting ? nm_setting_get_name (setting) : NULL;
 		char *res;
 
 		if (setting_name) {
@@ -37,30 +41,30 @@ _fmt_warn (const char *group, NMSetting *setting, const char *property_name, con
 			res = g_strdup_printf ("%s: %s", group, message);
 		*out_message = res;
 		return res;
-	} else
-		return message;
+	}
+
+	return message;
 }
 
 typedef struct {
 	bool verbose;
-} HandlerReadData;
+} ReadInfo;
 
 static gboolean
 _handler_read (GKeyFile *keyfile,
                NMConnection *connection,
-               NMKeyfileReadType type,
-               void *type_data,
-               void *user_data,
-               GError **error)
+               NMKeyfileHandlerType handler_type,
+               NMKeyfileHandlerData *handler_data,
+               void *user_data)
 {
-	const HandlerReadData *handler_data = user_data;
+	const ReadInfo *read_info = user_data;
 
-	if (type == NM_KEYFILE_READ_TYPE_WARN) {
-		NMKeyfileReadTypeDataWarn *warn_data = type_data;
+	if (handler_type == NM_KEYFILE_HANDLER_TYPE_WARN) {
+		const NMKeyfileHandlerDataWarn *warn_data = &handler_data->warn;
 		NMLogLevel level;
 		char *message_free = NULL;
 
-		if (!handler_data->verbose)
+		if (!read_info->verbose)
 			return TRUE;
 
 		if (warn_data->severity > NM_KEYFILE_WARN_SEVERITY_WARN)
@@ -72,15 +76,17 @@ _handler_read (GKeyFile *keyfile,
 		else
 			level = LOGL_INFO;
 
-		nm_log (level, LOGD_SETTINGS, NULL,
+		nm_log (level,
+		        LOGD_SETTINGS,
+		        NULL,
 		        nm_connection_get_uuid (connection),
 		        "keyfile: %s",
-		        _fmt_warn (warn_data->group, warn_data->setting,
-		                   warn_data->property_name, warn_data->message,
+		        _fmt_warn (handler_data,
 		                   &message_free));
 		g_free (message_free);
 		return TRUE;
 	}
+
 	return FALSE;
 }
 
@@ -93,7 +99,7 @@ nms_keyfile_reader_from_keyfile (GKeyFile *key_file,
                                  GError **error)
 {
 	NMConnection *connection;
-	HandlerReadData data = {
+	ReadInfo read_info = {
 		.verbose = verbose,
 	};
 	gs_free char *base_dir_free = NULL;
@@ -122,7 +128,7 @@ nms_keyfile_reader_from_keyfile (GKeyFile *key_file,
 		filename = &s[1];
 	}
 
-	connection = nm_keyfile_read (key_file, base_dir, _handler_read, &data, error);
+	connection = nm_keyfile_read (key_file, base_dir, NM_KEYFILE_HANDLER_FLAGS_NONE, _handler_read, &read_info, error);
 	if (!connection)
 		return NULL;
 
@@ -150,6 +156,7 @@ nms_keyfile_reader_from_file (const char *full_filename,
                               struct stat *out_stat,
                               NMTernary *out_is_nm_generated,
                               NMTernary *out_is_volatile,
+                              NMTernary *out_is_external,
                               char **out_shadowed_storage,
                               NMTernary *out_shadowed_owned,
                               GError **error)
@@ -163,6 +170,7 @@ nms_keyfile_reader_from_file (const char *full_filename,
 
 	NM_SET_OUT (out_is_nm_generated, NM_TERNARY_DEFAULT);
 	NM_SET_OUT (out_is_volatile, NM_TERNARY_DEFAULT);
+	NM_SET_OUT (out_is_external, NM_TERNARY_DEFAULT);
 
 	if (!nms_keyfile_utils_check_file_permissions (NMS_KEYFILE_FILETYPE_KEYFILE,
 	                                               full_filename,
@@ -198,6 +206,11 @@ nms_keyfile_reader_from_file (const char *full_filename,
 	                                                      NM_KEYFILE_KEY_NMMETA_VOLATILE,
 	                                                      NM_TERNARY_DEFAULT));
 
+	NM_SET_OUT (out_is_external, nm_key_file_get_boolean (key_file,
+	                                                      NM_KEYFILE_GROUP_NMMETA,
+	                                                      NM_KEYFILE_KEY_NMMETA_EXTERNAL,
+	                                                      NM_TERNARY_DEFAULT));
+
 	NM_SET_OUT (out_shadowed_storage, g_key_file_get_string (key_file,
 	                                                         NM_KEYFILE_GROUP_NMMETA,
 	                                                         NM_KEYFILE_KEY_NMMETA_SHADOWED_STORAGE,
diff --git a/src/settings/plugins/keyfile/nms-keyfile-reader.h b/src/settings/plugins/keyfile/nms-keyfile-reader.h
index 307d6ffe..828715fa 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-reader.h
+++ b/src/settings/plugins/keyfile/nms-keyfile-reader.h
@@ -23,6 +23,7 @@ NMConnection *nms_keyfile_reader_from_file (const char *full_filename,
                                             struct stat *out_stat,
                                             NMTernary *out_is_nm_generated,
                                             NMTernary *out_is_volatile,
+                                            NMTernary *out_is_external,
                                             char **out_shadowed_storage,
                                             NMTernary *out_shadowed_owned,
                                             GError **error);
diff --git a/src/settings/plugins/keyfile/nms-keyfile-storage.c b/src/settings/plugins/keyfile/nms-keyfile-storage.c
index bcc06795..15f20295 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-storage.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-storage.c
@@ -155,6 +155,7 @@ nms_keyfile_storage_new_connection (NMSKeyfilePlugin *plugin,
                                     NMSKeyfileStorageType storage_type,
                                     NMTernary is_nm_generated_opt,
                                     NMTernary is_volatile_opt,
+                                    NMTernary is_external_opt,
                                     const char *shadowed_storage,
                                     NMTernary shadowed_owned_opt,
                                     const struct timespec *stat_mtime)
@@ -181,6 +182,7 @@ nms_keyfile_storage_new_connection (NMSKeyfilePlugin *plugin,
 	if (storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN) {
 		self->u.conn_data.is_nm_generated = (is_nm_generated_opt == NM_TERNARY_TRUE);
 		self->u.conn_data.is_volatile     = (is_volatile_opt == NM_TERNARY_TRUE);
+		self->u.conn_data.is_external     = (is_external_opt == NM_TERNARY_TRUE);
 		self->u.conn_data.shadowed_owned  =    shadowed_storage
 		                                    && (shadowed_owned_opt == NM_TERNARY_TRUE);
 	}
@@ -243,7 +245,8 @@ nm_settings_storage_load_sett_flags (NMSettingsStorage *self,
 
 	*sett_flags = NM_SETTINGS_CONNECTION_INT_FLAGS_NONE;
 	*sett_mask =   NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
-	             | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE;
+	             | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+	             | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL;
 
 	if (!NMS_IS_KEYFILE_STORAGE (self))
 		return;
@@ -260,4 +263,7 @@ nm_settings_storage_load_sett_flags (NMSettingsStorage *self,
 
 	if (s->u.conn_data.is_volatile)
 		*sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE;
+
+	if (s->u.conn_data.is_external)
+		*sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL;
 }
diff --git a/src/settings/plugins/keyfile/nms-keyfile-storage.h b/src/settings/plugins/keyfile/nms-keyfile-storage.h
index 00a034d4..bd8a4367 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-storage.h
+++ b/src/settings/plugins/keyfile/nms-keyfile-storage.h
@@ -65,6 +65,7 @@ typedef struct {
 			 * That is, it must be tied to the actual keyfile, and not to the UUID. */
 			bool is_nm_generated:1;
 			bool is_volatile:1;
+			bool is_external:1;
 
 			/* if shadowed_storage is set, then this flag indicates whether the file
 			 * is owned. The difference comes into play when deleting the in-memory,
@@ -112,6 +113,7 @@ NMSKeyfileStorage *nms_keyfile_storage_new_connection (struct _NMSKeyfilePlugin
                                                        NMSKeyfileStorageType storage_type,
                                                        NMTernary is_nm_generated_opt,
                                                        NMTernary is_volatile_opt,
+                                                       NMTernary is_external_opt,
                                                        const char *shadowed_storage,
                                                        NMTernary shadowed_owned_opt,
                                                        const struct timespec *stat_mtime);
diff --git a/src/settings/plugins/keyfile/nms-keyfile-writer.c b/src/settings/plugins/keyfile/nms-keyfile-writer.c
index 7554becc..feea6065 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-writer.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-writer.c
@@ -28,21 +28,22 @@ typedef struct {
 static void
 cert_writer (NMConnection *connection,
              GKeyFile *file,
-             NMKeyfileWriteTypeDataCert *cert_data,
+             NMSetting8021x *setting,
+             const NMSetting8021xSchemeVtable *vtable,
              WriteInfo *info,
              GError **error)
 {
-	const char *setting_name = nm_setting_get_name (NM_SETTING (cert_data->setting));
+	const char *setting_name = nm_setting_get_name (NM_SETTING (setting));
 	NMSetting8021xCKScheme scheme;
 	NMSetting8021xCKFormat format;
 	const char *path = NULL, *ext = "pem";
 
-	scheme = cert_data->vtable->scheme_func (cert_data->setting);
+	scheme = vtable->scheme_func (setting);
 	if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) {
 		char *tmp = NULL;
 		const char *accepted_path = NULL;
 
-		path = cert_data->vtable->path_func (cert_data->setting);
+		path = vtable->path_func (setting);
 		g_assert (path);
 
 		if (g_str_has_prefix (path, info->keyfile_dir)) {
@@ -78,11 +79,11 @@ cert_writer (NMConnection *connection,
 
 		if (!accepted_path)
 			accepted_path = tmp = g_strconcat (NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, path, NULL);
-		nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->vtable->setting_key, accepted_path);
+		nm_keyfile_plugin_kf_set_string (file, setting_name, vtable->setting_key, accepted_path);
 		g_free (tmp);
 	} else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11) {
-		nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->vtable->setting_key,
-		                                 cert_data->vtable->uri_func (cert_data->setting));
+		nm_keyfile_plugin_kf_set_string (file, setting_name, vtable->setting_key,
+		                                 vtable->uri_func (setting));
 	} else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) {
 		GBytes *blob;
 		const guint8 *blob_data;
@@ -91,13 +92,13 @@ cert_writer (NMConnection *connection,
 		GError *local = NULL;
 		char *new_path;
 
-		blob = cert_data->vtable->blob_func (cert_data->setting);
+		blob = vtable->blob_func (setting);
 		g_assert (blob);
 		blob_data = g_bytes_get_data (blob, &blob_len);
 
-		if (cert_data->vtable->format_func) {
+		if (vtable->format_func) {
 			/* Get the extension for a private key */
-			format = cert_data->vtable->format_func (cert_data->setting);
+			format = vtable->format_func (setting);
 			if (format == NM_SETTING_802_1X_CK_FORMAT_PKCS12)
 				ext = "p12";
 		} else {
@@ -110,7 +111,7 @@ cert_writer (NMConnection *connection,
 		 * from now on instead of pushing around the certificate data.
 		 */
 		new_path = g_strdup_printf ("%s/%s-%s.%s", info->keyfile_dir, nm_connection_get_uuid (connection),
-		                            cert_data->vtable->file_suffix, ext);
+		                            vtable->file_suffix, ext);
 
 		/* FIXME(keyfile-parse-in-memory): writer must not access/write to the file system before
 		 * being sure that the entire profile can be written and all circumstances are good to
@@ -125,10 +126,10 @@ cert_writer (NMConnection *connection,
 		if (success) {
 			/* Write the path value to the keyfile.
 			 * We know, that basename(new_path) starts with a UUID, hence no conflict with "data:;base64,"  */
-			nm_keyfile_plugin_kf_set_string (file, setting_name, cert_data->vtable->setting_key, strrchr (new_path, '/') + 1);
+			nm_keyfile_plugin_kf_set_string (file, setting_name, vtable->setting_key, strrchr (new_path, '/') + 1);
 		} else {
 			nm_log_warn (LOGD_SETTINGS, "keyfile: %s.%s: failed to write certificate to file %s: %s",
-			             setting_name, cert_data->vtable->setting_key, new_path, local->message);
+			             setting_name, vtable->setting_key, new_path, local->message);
 			g_error_free (local);
 		}
 		g_free (new_path);
@@ -146,15 +147,17 @@ cert_writer (NMConnection *connection,
 static gboolean
 _handler_write (NMConnection *connection,
                 GKeyFile *keyfile,
-                NMKeyfileWriteType type,
-                void *type_data,
-                void *user_data,
-                GError **error)
+                NMKeyfileHandlerType type,
+                NMKeyfileHandlerData *type_data,
+                void *user_data)
 {
-	if (type == NM_KEYFILE_WRITE_TYPE_CERT) {
-		cert_writer (connection, keyfile,
-		             (NMKeyfileWriteTypeDataCert *) type_data,
-		             (WriteInfo *) user_data, error);
+	if (type == NM_KEYFILE_HANDLER_TYPE_WRITE_CERT) {
+		cert_writer (connection,
+		             keyfile,
+		             NM_SETTING_802_1X (type_data->cur_setting),
+		             type_data->write_cert.vtable,
+		             user_data,
+		             type_data->p_error);
 		return TRUE;
 	}
 	return FALSE;
@@ -164,6 +167,7 @@ static gboolean
 _internal_write_connection (NMConnection *connection,
                             gboolean is_nm_generated,
                             gboolean is_volatile,
+                            gboolean is_external,
                             const char *shadowed_storage,
                             gboolean shadowed_owned,
                             const char *keyfile_dir,
@@ -211,7 +215,7 @@ _internal_write_connection (NMConnection *connection,
 
 	info.keyfile_dir = keyfile_dir;
 
-	kf_file = nm_keyfile_write (connection, _handler_write, &info, error);
+	kf_file = nm_keyfile_write (connection, NM_KEYFILE_HANDLER_FLAGS_NONE, _handler_write, &info, error);
 	if (!kf_file)
 		return FALSE;
 
@@ -229,6 +233,13 @@ _internal_write_connection (NMConnection *connection,
 		                        TRUE);
 	}
 
+	if (is_external) {
+		g_key_file_set_boolean (kf_file,
+		                        NM_KEYFILE_GROUP_NMMETA,
+		                        NM_KEYFILE_KEY_NMMETA_EXTERNAL,
+		                        TRUE);
+	}
+
 	if (shadowed_storage) {
 		g_key_file_set_string (kf_file,
 		                       NM_KEYFILE_GROUP_NMMETA,
@@ -375,6 +386,7 @@ gboolean
 nms_keyfile_writer_connection (NMConnection *connection,
                                gboolean is_nm_generated,
                                gboolean is_volatile,
+                               gboolean is_external,
                                const char *shadowed_storage,
                                gboolean shadowed_owned,
                                const char *keyfile_dir,
@@ -392,6 +404,7 @@ nms_keyfile_writer_connection (NMConnection *connection,
 	return _internal_write_connection (connection,
 	                                   is_nm_generated,
 	                                   is_volatile,
+	                                   is_external,
 	                                   shadowed_storage,
 	                                   shadowed_owned,
 	                                   keyfile_dir,
@@ -423,6 +436,7 @@ nms_keyfile_writer_test_connection (NMConnection *connection,
 	return _internal_write_connection (connection,
 	                                   FALSE,
 	                                   FALSE,
+	                                   FALSE,
 	                                   NULL,
 	                                   FALSE,
 	                                   keyfile_dir,
diff --git a/src/settings/plugins/keyfile/nms-keyfile-writer.h b/src/settings/plugins/keyfile/nms-keyfile-writer.h
index 98ec8a6b..73a2e333 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-writer.h
+++ b/src/settings/plugins/keyfile/nms-keyfile-writer.h
@@ -15,6 +15,7 @@ typedef gboolean (*NMSKeyfileWriterAllowFilenameCb) (const char *check_filename,
 gboolean nms_keyfile_writer_connection (NMConnection *connection,
                                         gboolean is_nm_generated,
                                         gboolean is_volatile,
+                                        gboolean is_external,
                                         const char *shadowed_storage,
                                         gboolean shadowed_owned,
                                         const char *keyfile_dir,
diff --git a/src/settings/plugins/keyfile/tests/test-keyfile-settings.c b/src/settings/plugins/keyfile/tests/test-keyfile-settings.c
index d2da09da..1a9482e5 100644
--- a/src/settings/plugins/keyfile/tests/test-keyfile-settings.c
+++ b/src/settings/plugins/keyfile/tests/test-keyfile-settings.c
@@ -63,6 +63,7 @@ check_ip_route (NMSettingIPConfig *config, int idx, const char *destination, int
 	                                            NULL, \
 	                                            NULL, \
 	                                            NULL, \
+	                                            NULL, \
 	                                            (nmtst_get_rand_uint32 () % 2) ? &_error : NULL); \
 	nmtst_assert_success (_connection, _error); \
 	nmtst_assert_connection_verifies_without_normalization (_connection); \
diff --git a/src/supplicant/nm-supplicant-config.c b/src/supplicant/nm-supplicant-config.c
index 53b6d360..058792d0 100644
--- a/src/supplicant/nm-supplicant-config.c
+++ b/src/supplicant/nm-supplicant-config.c
@@ -22,7 +22,7 @@
 typedef struct {
 	char *value;
 	guint32 len;
-	OptType type;
+	NMSupplOptType type;
 } ConfigOption;
 
 /*****************************************************************************/
@@ -97,14 +97,14 @@ nm_supplicant_config_add_option_with_type (NMSupplicantConfig *self,
                                            const char *key,
                                            const char *value,
                                            gint32 len,
-                                           OptType opt_type,
+                                           NMSupplOptType opt_type,
                                            const char *hidden,
                                            GError **error)
 {
 	NMSupplicantConfigPrivate *priv;
 	ConfigOption *old_opt;
 	ConfigOption *opt;
-	OptType type;
+	NMSupplOptType type;
 
 	g_return_val_if_fail (NM_IS_SUPPLICANT_CONFIG (self), FALSE);
 	g_return_val_if_fail (key != NULL, FALSE);
@@ -116,11 +116,11 @@ nm_supplicant_config_add_option_with_type (NMSupplicantConfig *self,
 	if (len < 0)
 		len = strlen (value);
 
-	if (opt_type != TYPE_INVALID)
+	if (opt_type != NM_SUPPL_OPT_TYPE_INVALID)
 		type = opt_type;
 	else {
 		type = nm_supplicant_settings_verify_setting (key, value, len);
-		if (type == TYPE_INVALID) {
+		if (type == NM_SUPPL_OPT_TYPE_INVALID) {
 			gs_free char *str_free = NULL;
 			const char *str;
 
@@ -171,7 +171,7 @@ nm_supplicant_config_add_option (NMSupplicantConfig *self,
                                  const char *hidden,
                                  GError **error)
 {
-	return nm_supplicant_config_add_option_with_type (self, key, value, len, TYPE_INVALID, hidden, error);
+	return nm_supplicant_config_add_option_with_type (self, key, value, len, NM_SUPPL_OPT_TYPE_INVALID, hidden, error);
 }
 
 static gboolean
@@ -184,7 +184,7 @@ nm_supplicant_config_add_blob (NMSupplicantConfig *self,
 	NMSupplicantConfigPrivate *priv;
 	ConfigOption *old_opt;
 	ConfigOption *opt;
-	OptType type;
+	NMSupplOptType type;
 	const guint8 *data;
 	gsize data_len;
 
@@ -199,7 +199,7 @@ nm_supplicant_config_add_blob (NMSupplicantConfig *self,
 	priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE (self);
 
 	type = nm_supplicant_settings_verify_setting (key, (const char *) data, data_len);
-	if (type == TYPE_INVALID) {
+	if (type == NM_SUPPL_OPT_TYPE_INVALID) {
 		g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
 		             "key '%s' and/or its contained value is invalid", key);
 		return FALSE;
@@ -307,18 +307,18 @@ nm_supplicant_config_to_variant (NMSupplicantConfig *self)
 	g_hash_table_iter_init (&iter, priv->config);
 	while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &option)) {
 		switch (option->type) {
-		case TYPE_INT:
+		case NM_SUPPL_OPT_TYPE_INT:
 			g_variant_builder_add (&builder, "{sv}", key, g_variant_new_int32 (atoi (option->value)));
 			break;
-		case TYPE_BYTES:
-		case TYPE_UTF8:
+		case NM_SUPPL_OPT_TYPE_BYTES:
+		case NM_SUPPL_OPT_TYPE_UTF8:
 			g_variant_builder_add (&builder, "{sv}",
 			                       key,
 			                       g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
 			                                                  option->value, option->len, 1));
 			break;
-		case TYPE_KEYWORD:
-		case TYPE_STRING:
+		case NM_SUPPL_OPT_TYPE_KEYWORD:
+		case NM_SUPPL_OPT_TYPE_STRING:
 			g_variant_builder_add (&builder, "{sv}", key, g_variant_new_string (option->value));
 			break;
 		default:
@@ -831,18 +831,18 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 
 
 		if (psk_len >= 8 && psk_len <= 63) {
-			/* Use TYPE_STRING here so that it gets pushed to the
+			/* Use NM_SUPPL_OPT_TYPE_STRING here so that it gets pushed to the
 			 * supplicant as a string, and therefore gets quoted,
 			 * and therefore the supplicant will interpret it as a
 			 * passphrase and not a hex key.
 			 */
-			if (!nm_supplicant_config_add_option_with_type (self, "psk", psk, -1, TYPE_STRING, "<hidden>", error))
+			if (!nm_supplicant_config_add_option_with_type (self, "psk", psk, -1, NM_SUPPL_OPT_TYPE_STRING, "<hidden>", error))
 				return FALSE;
 		} else if (nm_streq (key_mgmt, "sae")) {
 			/* If the SAE password doesn't comply with WPA-PSK limitation,
 			 * we need to call it "sae_password" instead of "psk".
 			 */
-			if (!nm_supplicant_config_add_option_with_type (self, "sae_password", psk, -1, TYPE_STRING, "<hidden>", error))
+			if (!nm_supplicant_config_add_option_with_type (self, "sae_password", psk, -1, NM_SUPPL_OPT_TYPE_STRING, "<hidden>", error))
 				return FALSE;
 		} else if (psk_len == 64) {
 			guint8 buffer[32];
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c
index 1c7b9a42..6bbeb1eb 100644
--- a/src/supplicant/nm-supplicant-interface.c
+++ b/src/supplicant/nm-supplicant-interface.c
@@ -702,6 +702,19 @@ _bss_info_properties_changed (NMSupplicantInterface *self,
 		p_max_rate_has = TRUE;
 		g_variant_unref (v_v);
 	}
+
+	v_v = nm_g_variant_lookup_value (properties, "WPA", G_VARIANT_TYPE_VARDICT);
+	if (v_v) {
+		bss_info->wpa_flags = security_from_vardict (v_v);
+		g_variant_unref (v_v);
+	}
+
+	v_v = nm_g_variant_lookup_value (properties, "RSN", G_VARIANT_TYPE_VARDICT);
+	if (v_v) {
+		bss_info->rsn_flags = security_from_vardict (v_v);
+		g_variant_unref (v_v);
+	}
+
 	v_v = nm_g_variant_lookup_value (properties, "IEs", G_VARIANT_TYPE_BYTESTRING);
 	if (v_v) {
 		gboolean p_owe_transition_mode;
@@ -715,27 +728,16 @@ _bss_info_properties_changed (NMSupplicantInterface *self,
 		g_variant_unref (v_v);
 
 		if (p_owe_transition_mode)
-			bss_info->rsn_flags |= NM_802_11_AP_SEC_KEY_MGMT_OWE;
+			bss_info->rsn_flags |= NM_802_11_AP_SEC_KEY_MGMT_OWE_TM;
 		else
-			bss_info->rsn_flags &= ~NM_802_11_AP_SEC_KEY_MGMT_OWE;
+			bss_info->rsn_flags &= ~NM_802_11_AP_SEC_KEY_MGMT_OWE_TM;
 
 		bss_info->metered = p_metered;
 	}
+
 	if (p_max_rate_has)
 		bss_info->max_rate = p_max_rate / 1000u;
 
-	v_v = nm_g_variant_lookup_value (properties, "WPA", G_VARIANT_TYPE_VARDICT);
-	if (v_v) {
-		bss_info->wpa_flags = security_from_vardict (v_v);
-		g_variant_unref (v_v);
-	}
-
-	v_v = nm_g_variant_lookup_value (properties, "RSN", G_VARIANT_TYPE_VARDICT);
-	if (v_v) {
-		bss_info->rsn_flags = security_from_vardict (v_v);
-		g_variant_unref (v_v);
-	}
-
 	_bss_info_changed_emit (self, bss_info, TRUE);
 }
 
diff --git a/src/supplicant/nm-supplicant-settings-verify.c b/src/supplicant/nm-supplicant-settings-verify.c
index 58dbd73a..954a4649 100644
--- a/src/supplicant/nm-supplicant-settings-verify.c
+++ b/src/supplicant/nm-supplicant-settings-verify.c
@@ -11,131 +11,154 @@
 #include <stdlib.h>
 
 struct Opt {
-	const char *     key;
-	const OptType    type;
-	const gint32     int_low;  /* Inclusive */
-	const gint32     int_high; /* Inclusive; max length for strings */
-	const gboolean   str_allowed_multiple;
+	const char *key;
 	const char *const*str_allowed;
+	const NMSupplOptType type;
+	const guint32 int_low;  /* Inclusive */
+	const guint32 int_high; /* Inclusive; max length for strings */
 };
 
-static gboolean validate_type_int     (const struct Opt * opt,
-                                       const char * value,
-                                       const guint32 len);
+typedef gboolean (*validate_func) (const struct Opt *, const char *, const guint32);
 
-static gboolean validate_type_bytes   (const struct Opt * opt,
-                                       const char * value,
-                                       const guint32 len);
-
-static gboolean validate_type_utf8    (const struct Opt *opt,
-                                       const char * value,
-                                       const guint32 len);
-
-static gboolean validate_type_keyword (const struct Opt * opt,
-                                       const char * value,
-                                       const guint32 len);
-
-typedef gboolean (*validate_func)(const struct Opt *, const char *, const guint32);
-
-struct validate_entry {
-	const OptType  type;
-	const validate_func func;
-};
-
-static const struct validate_entry validate_table[] = {
-	{ TYPE_INT,     validate_type_int     },
-	{ TYPE_BYTES,   validate_type_bytes   },
-	{ TYPE_UTF8,    validate_type_utf8    },
-	{ TYPE_KEYWORD, validate_type_keyword },
-};
-
-static const char *const pairwise_allowed[] = { "CCMP", "TKIP", "NONE", NULL };
-static const char *const group_allowed[] =    { "CCMP", "TKIP", "WEP104", "WEP40", NULL };
-static const char *const proto_allowed[] =    { "WPA", "RSN", NULL };
-static const char *const key_mgmt_allowed[] = { "WPA-PSK", "WPA-PSK-SHA256", "FT-PSK",
-                                                "WPA-EAP", "WPA-EAP-SHA256", "FT-EAP", "FT-EAP-SHA384",
-                                                "FILS-SHA256", "FILS-SHA384",
-                                                "IEEE8021X", "SAE", "FT-SAE",
-                                                "OWE", "NONE", NULL };
-static const char *const auth_alg_allowed[] = { "OPEN", "SHARED", "LEAP", NULL };
-static const char *const eap_allowed[] =      { "LEAP", "MD5", "TLS", "PEAP", "TTLS", "SIM",
-                                                "PSK", "FAST", "PWD", NULL };
-
-static const char *const phase1_allowed[] =   { "peapver=0", "peapver=1", "peaplabel=1",
-                                                "peap_outer_success=0", "include_tls_length=1",
-                                                "sim_min_num_chal=3", "fast_provisioning=0",
-                                                "fast_provisioning=1", "fast_provisioning=2",
-                                                "fast_provisioning=3", "tls_disable_tlsv1_0=0",
-                                                "tls_disable_tlsv1_0=1", "tls_disable_tlsv1_1=0",
-                                                "tls_disable_tlsv1_1=1", "tls_disable_tlsv1_2=0",
-                                                "tls_disable_tlsv1_2=1", NULL };
-static const char *const phase2_allowed[] =   { "auth=PAP", "auth=CHAP", "auth=MSCHAP",
-                                                "auth=MSCHAPV2", "auth=GTC", "auth=OTP",
-                                                "auth=MD5", "auth=TLS", "autheap=MD5",
-                                                "autheap=MSCHAPV2", "autheap=OTP",
-                                                "autheap=GTC", "autheap=TLS", NULL };
+#define OPT_INT(    _key, _int_low, _int_high) { .key = _key, .type = NM_SUPPL_OPT_TYPE_INT,     .int_high = _int_high, .int_low = _int_low, }
+#define OPT_BYTES(  _key,           _int_high) { .key = _key, .type = NM_SUPPL_OPT_TYPE_BYTES,   .int_high = _int_high,                      }
+#define OPT_UTF8(   _key,           _int_high) { .key = _key, .type = NM_SUPPL_OPT_TYPE_UTF8,    .int_high = _int_high,                      }
+#define OPT_KEYWORD(_key, _str_allowed)        { .key = _key, .type = NM_SUPPL_OPT_TYPE_KEYWORD, .str_allowed = _str_allowed,                }
 
 static const struct Opt opt_table[] = {
-	{ "ssid",               TYPE_BYTES,   0, 32,FALSE,  NULL },
-	{ "bssid",              TYPE_KEYWORD, 0, 0, FALSE,  NULL },
-	{ "scan_ssid",          TYPE_INT,     0, 1, FALSE,  NULL },
-	{ "frequency",          TYPE_INT,     2412, 5825, FALSE,  NULL },
-	{ "auth_alg",           TYPE_KEYWORD, 0, 0, FALSE,  auth_alg_allowed },
-	{ "psk",                TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "pairwise",           TYPE_KEYWORD, 0, 0, FALSE,  pairwise_allowed },
-	{ "group",              TYPE_KEYWORD, 0, 0, FALSE,  group_allowed },
-	{ "proto",              TYPE_KEYWORD, 0, 0, FALSE,  proto_allowed },
-	{ "key_mgmt",           TYPE_KEYWORD, 0, 0, FALSE,  key_mgmt_allowed },
-	{ "wep_key0",           TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "wep_key1",           TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "wep_key2",           TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "wep_key3",           TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "wep_tx_keyidx",      TYPE_INT,     0, 3, FALSE,  NULL },
-	{ "eapol_flags",        TYPE_INT,     0, 3, FALSE,  NULL },
-	{ "eap",                TYPE_KEYWORD, 0, 0, FALSE,  eap_allowed },
-	{ "identity",           TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "password",           TYPE_UTF8,    0, 0, FALSE,  NULL },
-	{ "ca_path",            TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "subject_match",      TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "altsubject_match",   TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "domain_suffix_match",TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "domain_match",       TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "ca_cert",            TYPE_BYTES,   0, 65536, FALSE,  NULL },
-	{ "client_cert",        TYPE_BYTES,   0, 65536, FALSE,  NULL },
-	{ "private_key",        TYPE_BYTES,   0, 65536, FALSE,  NULL },
-	{ "private_key_passwd", TYPE_BYTES,   0, 1024, FALSE,  NULL },
-	{ "phase1",             TYPE_KEYWORD, 0, 0, TRUE, phase1_allowed },
-	{ "phase2",             TYPE_KEYWORD, 0, 0, TRUE, phase2_allowed },
-	{ "anonymous_identity", TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "ca_path2",           TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "subject_match2",     TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "altsubject_match2",  TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "domain_suffix_match2", TYPE_BYTES, 0, 0, FALSE,  NULL },
-	{ "domain_match2",      TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "ca_cert2",           TYPE_BYTES,   0, 65536, FALSE,  NULL },
-	{ "client_cert2",       TYPE_BYTES,   0, 65536, FALSE,  NULL },
-	{ "private_key2",       TYPE_BYTES,   0, 65536, FALSE,  NULL },
-	{ "private_key2_passwd",TYPE_BYTES,   0, 1024, FALSE,  NULL },
-	{ "pin",                TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "pcsc",               TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "nai",                TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "eappsk",             TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "pac_file",           TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "engine",             TYPE_INT,     0, 1, FALSE,  NULL },
-	{ "engine_id",          TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "key_id",             TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "fragment_size",      TYPE_INT,     1, 2000, FALSE,  NULL },
-	{ "proactive_key_caching", TYPE_INT,  0, 1, FALSE,  NULL },
-	{ "bgscan",             TYPE_BYTES,   0, 0, FALSE,  NULL },
-	{ "pac_file",           TYPE_BYTES,   0, 1024, FALSE,  NULL },
-	{ "freq_list",          TYPE_KEYWORD, 0, 0, FALSE,  NULL },
-	{ "macsec_policy",      TYPE_INT,     0, 1, FALSE, NULL },
-	{ "macsec_integ_only",  TYPE_INT,     0, 1, FALSE, NULL },
-	{ "mka_cak",            TYPE_BYTES,   0, 65536, FALSE, NULL },
-	{ "mka_ckn",            TYPE_BYTES,   0, 65536, FALSE, NULL },
-	{ "macsec_port",        TYPE_INT,     1, 65534, FALSE, NULL },
-	{ "ieee80211w",         TYPE_INT,     0, 2, FALSE, NULL },
-	{ "ignore_broadcast_ssid", TYPE_INT,  0, 2, FALSE, NULL },
+	OPT_BYTES   ("altsubject_match",      0),
+	OPT_BYTES   ("altsubject_match2",     0),
+	OPT_BYTES   ("anonymous_identity",    0),
+	OPT_KEYWORD ("auth_alg",              NM_MAKE_STRV (
+	                                        "OPEN",
+	                                        "SHARED",
+	                                        "LEAP",
+	                                      )),
+	OPT_BYTES   ("bgscan",                0),
+	OPT_KEYWORD ("bssid",                 NULL),
+	OPT_BYTES   ("ca_cert",               65536),
+	OPT_BYTES   ("ca_cert2",              65536),
+	OPT_BYTES   ("ca_path",               0),
+	OPT_BYTES   ("ca_path2",              0),
+	OPT_BYTES   ("client_cert",           65536),
+	OPT_BYTES   ("client_cert2",          65536),
+	OPT_BYTES   ("domain_match",          0),
+	OPT_BYTES   ("domain_match2",         0),
+	OPT_BYTES   ("domain_suffix_match",   0),
+	OPT_BYTES   ("domain_suffix_match2",  0),
+	OPT_KEYWORD ("eap",                   NM_MAKE_STRV (
+	                                        "LEAP",
+	                                        "MD5",
+	                                        "TLS",
+	                                        "PEAP",
+	                                        "TTLS",
+	                                        "SIM",
+	                                        "PSK",
+	                                        "FAST",
+	                                        "PWD",
+	                                      )),
+	OPT_INT     ("eapol_flags",           0, 3),
+	OPT_BYTES   ("eappsk",                0),
+	OPT_INT     ("engine",                0, 1),
+	OPT_BYTES   ("engine_id",             0),
+	OPT_INT     ("fragment_size",         1, 2000),
+	OPT_KEYWORD ("freq_list",             NULL),
+	OPT_INT     ("frequency",             2412, 5825),
+	OPT_KEYWORD ("group",                 NM_MAKE_STRV (
+	                                        "CCMP",
+	                                        "TKIP",
+	                                        "WEP104",
+	                                        "WEP40",
+	                                      )),
+	OPT_BYTES   ("identity",              0),
+	OPT_INT     ("ieee80211w",            0, 2),
+	OPT_INT     ("ignore_broadcast_ssid", 0, 2),
+	OPT_BYTES   ("key_id",                0),
+	OPT_KEYWORD ("key_mgmt",              NM_MAKE_STRV (
+	                                        "WPA-PSK",
+	                                        "WPA-PSK-SHA256",
+	                                        "FT-PSK",
+	                                        "WPA-EAP",
+	                                        "WPA-EAP-SHA256",
+	                                        "FT-EAP",
+	                                        "FT-EAP-SHA384",
+	                                        "FILS-SHA256",
+	                                        "FILS-SHA384",
+	                                        "FT-FILS-SHA256",
+	                                        "FT-FILS-SHA384",
+	                                        "IEEE8021X",
+	                                        "SAE",
+	                                        "FT-SAE",
+	                                        "OWE",
+	                                        "NONE",
+	                                      )),
+	OPT_INT     ("macsec_integ_only",     0, 1),
+	OPT_INT     ("macsec_policy",         0, 1),
+	OPT_INT     ("macsec_port",           1, 65534),
+	OPT_BYTES   ("mka_cak",               65536),
+	OPT_BYTES   ("mka_ckn",               65536),
+	OPT_BYTES   ("nai",                   0),
+	OPT_BYTES   ("pac_file",              0),
+	OPT_KEYWORD ("pairwise",              NM_MAKE_STRV (
+	                                        "CCMP",
+	                                        "TKIP",
+	                                        "NONE",
+	                                      )),
+	OPT_UTF8    ("password",              0),
+	OPT_BYTES   ("pcsc",                  0),
+	OPT_KEYWORD ("phase1",                NM_MAKE_STRV (
+	                                        "peapver=0",
+	                                        "peapver=1",
+	                                        "peaplabel=1",
+	                                        "peap_outer_success=0",
+	                                        "include_tls_length=1",
+	                                        "sim_min_num_chal=3",
+	                                        "fast_provisioning=0",
+	                                        "fast_provisioning=1",
+	                                        "fast_provisioning=2",
+	                                        "fast_provisioning=3",
+	                                        "tls_disable_tlsv1_0=0",
+	                                        "tls_disable_tlsv1_0=1",
+	                                        "tls_disable_tlsv1_1=0",
+	                                        "tls_disable_tlsv1_1=1",
+	                                        "tls_disable_tlsv1_2=0",
+	                                        "tls_disable_tlsv1_2=1",
+	                                      )),
+	OPT_KEYWORD ("phase2",                NM_MAKE_STRV (
+	                                        "auth=PAP",
+	                                        "auth=CHAP",
+	                                        "auth=MSCHAP",
+	                                        "auth=MSCHAPV2",
+	                                        "auth=GTC",
+	                                        "auth=OTP",
+	                                        "auth=MD5",
+	                                        "auth=TLS",
+	                                        "autheap=MD5",
+	                                        "autheap=MSCHAPV2",
+	                                        "autheap=OTP",
+	                                        "autheap=GTC",
+	                                        "autheap=TLS",
+	                                      )),
+	OPT_BYTES   ("pin",                   0),
+	OPT_BYTES   ("private_key",           65536),
+	OPT_BYTES   ("private_key2",          65536),
+	OPT_BYTES   ("private_key2_passwd",   1024),
+	OPT_BYTES   ("private_key_passwd",    1024),
+	OPT_INT     ("proactive_key_caching", 0, 1),
+	OPT_KEYWORD ("proto",                 NM_MAKE_STRV (
+	                                        "WPA",
+	                                        "RSN",
+	                                      )),
+	OPT_BYTES   ("psk",                   0),
+	OPT_INT     ("scan_ssid",             0, 1),
+	OPT_BYTES   ("ssid",                  32),
+	OPT_BYTES   ("subject_match",         0),
+	OPT_BYTES   ("subject_match2",        0),
+	OPT_BYTES   ("wep_key0",              0),
+	OPT_BYTES   ("wep_key1",              0),
+	OPT_BYTES   ("wep_key2",              0),
+	OPT_BYTES   ("wep_key3",              0),
+	OPT_INT     ("wep_tx_keyidx",         0, 3),
 };
 
 static gboolean
@@ -145,8 +168,8 @@ validate_type_int (const struct Opt * opt,
 {
 	gint64 v;
 
-	g_return_val_if_fail (opt != NULL, FALSE);
-	g_return_val_if_fail (value != NULL, FALSE);
+	nm_assert (opt);
+	nm_assert (value);
 
 	v = _nm_utils_ascii_str_to_int64 (value, 10, opt->int_low, opt->int_high, G_MININT64);
 	return v != G_MININT64 || errno == 0;
@@ -159,8 +182,8 @@ validate_type_bytes (const struct Opt * opt,
 {
 	guint32 check_len;
 
-	g_return_val_if_fail (opt != NULL, FALSE);
-	g_return_val_if_fail (value != NULL, FALSE);
+	nm_assert (opt);
+	nm_assert (value);
 
 	check_len = opt->int_high ?: 255;
 	if (len > check_len)
@@ -176,8 +199,8 @@ validate_type_utf8 (const struct Opt *opt,
 {
 	guint32 check_len;
 
-	g_return_val_if_fail (opt != NULL, FALSE);
-	g_return_val_if_fail (value != NULL, FALSE);
+	nm_assert (opt);
+	nm_assert (value);
 
 	check_len = opt->int_high ?: 255;
 	/* Note that we deliberately don't validate the UTF-8, because
@@ -196,8 +219,8 @@ validate_type_keyword (const struct Opt * opt,
 {
 	gs_free char *value_free = NULL;
 
-	g_return_val_if_fail (opt != NULL, FALSE);
-	g_return_val_if_fail (value != NULL, FALSE);
+	nm_assert (opt);
+	nm_assert (value);
 
 	/* Allow everything */
 	if (!opt->str_allowed)
@@ -232,41 +255,74 @@ validate_type_keyword (const struct Opt * opt,
 	}
 }
 
-OptType
-nm_supplicant_settings_verify_setting (const char * key,
-                                       const char * value,
+NMSupplOptType
+nm_supplicant_settings_verify_setting (const char *key,
+                                       const char *value,
                                        const guint32 len)
 {
-	OptType type = TYPE_INVALID;
-	int opt_count = sizeof (opt_table) / sizeof (opt_table[0]);
-	int val_count = sizeof (validate_table) / sizeof (validate_table[0]);
-	int i, j;
-
-	g_return_val_if_fail (key != NULL, FALSE);
-	g_return_val_if_fail (value != NULL, FALSE);
-
-	if (strcmp (key, "mode") == 0) {
-		if (len != 1)
-			return TYPE_INVALID;
-		if (!NM_IN_SET (value[0], '1', '2', '5'))
-			return TYPE_INVALID;
-		return TYPE_INT;
+	static const validate_func validate_table[_NM_SUPPL_OPT_TYPE_NUM - 1] = {
+		[NM_SUPPL_OPT_TYPE_INT     - 1] = validate_type_int,
+		[NM_SUPPL_OPT_TYPE_BYTES   - 1] = validate_type_bytes,
+		[NM_SUPPL_OPT_TYPE_UTF8    - 1] = validate_type_utf8,
+		[NM_SUPPL_OPT_TYPE_KEYWORD - 1] = validate_type_keyword,
+	};
+	const struct Opt *opt;
+	gssize opt_idx;
+
+	g_return_val_if_fail (key, FALSE);
+	g_return_val_if_fail (value, FALSE);
+
+	if (NM_MORE_ASSERT_ONCE (5)) {
+		gsize i;
+
+		for (i = 0; i < G_N_ELEMENTS (opt_table); i++) {
+			opt = &opt_table[i];
+
+			nm_assert (opt->key);
+			nm_assert (opt->type > NM_SUPPL_OPT_TYPE_INVALID);
+			nm_assert (opt->type < _NM_SUPPL_OPT_TYPE_NUM);
+			if (i > 0)
+				nm_assert (strcmp (opt[-1].key, opt->key) < 0);
+			nm_assert (validate_table[opt->type - 1]);
+
+			nm_assert (   !opt->str_allowed
+			           || (opt->type == NM_SUPPL_OPT_TYPE_KEYWORD));
+			nm_assert (   !opt->str_allowed
+			           || NM_PTRARRAY_LEN (opt->str_allowed) > 0);
+
+			nm_assert (   opt->int_low == 0
+			           || opt->type == NM_SUPPL_OPT_TYPE_INT);
+
+			nm_assert (   opt->int_high == 0
+			           || NM_IN_SET (opt->type, NM_SUPPL_OPT_TYPE_INT,
+			                                    NM_SUPPL_OPT_TYPE_UTF8,
+			                                    NM_SUPPL_OPT_TYPE_BYTES));
+
+			nm_assert (   opt->type != NM_SUPPL_OPT_TYPE_INT
+			           || opt->int_low < opt->int_high);
+		}
 	}
 
-	for (i = 0; i < opt_count; i++) {
-		if (strcmp (opt_table[i].key, key) != 0)
-			continue;
-
-		for (j = 0; j < val_count; j++) {
-			if (validate_table[j].type == opt_table[i].type) {
-				if ((*(validate_table[j].func))(&opt_table[i], value, len)) {
-					type = opt_table[i].type;
-					break;
-				}
-			}
+	opt_idx = nm_utils_array_find_binary_search (opt_table,
+	                                             sizeof (opt_table[0]),
+	                                             G_N_ELEMENTS (opt_table),
+	                                             &key,
+	                                             nm_strcmp_p_with_data,
+	                                             NULL);
+	if (opt_idx < 0) {
+		if (nm_streq (key, "mode")) {
+			if (len != 1)
+				return NM_SUPPL_OPT_TYPE_INVALID;
+			if (!NM_IN_SET (value[0], '1', '2', '5'))
+				return NM_SUPPL_OPT_TYPE_INVALID;
+			return NM_SUPPL_OPT_TYPE_INT;
 		}
+		return NM_SUPPL_OPT_TYPE_INVALID;
 	}
 
-	return type;
-}
+	opt = &opt_table[opt_idx];
+	if (!((validate_table[opt->type - 1]) (opt, value, len)))
+		return NM_SUPPL_OPT_TYPE_INVALID;
 
+	return opt->type;
+}
diff --git a/src/supplicant/nm-supplicant-settings-verify.h b/src/supplicant/nm-supplicant-settings-verify.h
index fe1480cc..d804a97e 100644
--- a/src/supplicant/nm-supplicant-settings-verify.h
+++ b/src/supplicant/nm-supplicant-settings-verify.h
@@ -7,16 +7,17 @@
 #define __NETWORKMANAGER_SUPPLICANT_SETTINGS_VERIFY_H__
 
 typedef enum {
-	TYPE_INVALID = 0,
-	TYPE_INT,
-	TYPE_BYTES,
-	TYPE_UTF8,
-	TYPE_KEYWORD,
-	TYPE_STRING
-} OptType;
+	NM_SUPPL_OPT_TYPE_INVALID = 0,
+	NM_SUPPL_OPT_TYPE_INT,
+	NM_SUPPL_OPT_TYPE_BYTES,
+	NM_SUPPL_OPT_TYPE_UTF8,
+	NM_SUPPL_OPT_TYPE_KEYWORD,
+	NM_SUPPL_OPT_TYPE_STRING,
+	_NM_SUPPL_OPT_TYPE_NUM,
+} NMSupplOptType;
 
-OptType nm_supplicant_settings_verify_setting (const char * key,
-                                               const char * value,
-                                               const guint32 len);
+NMSupplOptType nm_supplicant_settings_verify_setting (const char *key,
+                                                      const char *value,
+                                                      const guint32 len);
 
 #endif /* __NETWORKMANAGER_SUPPLICANT_SETTINGS_VERIFY_H__ */
diff --git a/src/supplicant/tests/test-supplicant-config.c b/src/supplicant/tests/test-supplicant-config.c
index f9c71f12..099af949 100644
--- a/src/supplicant/tests/test-supplicant-config.c
+++ b/src/supplicant/tests/test-supplicant-config.c
@@ -29,7 +29,7 @@ static gboolean
 validate_opt (const char *detail,
               GVariant *config,
               const char *key,
-              OptType val_type,
+              NMSupplOptType val_type,
               gconstpointer expected)
 {
 	char *config_key;
@@ -44,12 +44,12 @@ validate_opt (const char *detail,
 		if (!strcmp (key, config_key)) {
 			found = TRUE;
 			switch (val_type) {
-			case TYPE_INT: {
+			case NM_SUPPL_OPT_TYPE_INT: {
 				g_assert (g_variant_is_of_type (config_value, G_VARIANT_TYPE_INT32));
 				g_assert_cmpint (g_variant_get_int32 (config_value), ==, GPOINTER_TO_INT (expected));
 				break;
 			}
-			case TYPE_BYTES: {
+			case NM_SUPPL_OPT_TYPE_BYTES: {
 				const guint8 *expected_bytes;
 				gsize expected_len = 0;
 				const guint8 *config_bytes;
@@ -61,8 +61,8 @@ validate_opt (const char *detail,
 				g_assert_cmpmem (config_bytes, config_len, expected_bytes, expected_len);
 				break;
 			}
-			case TYPE_KEYWORD:
-			case TYPE_STRING: {
+			case NM_SUPPL_OPT_TYPE_KEYWORD:
+			case NM_SUPPL_OPT_TYPE_STRING: {
 				const char *expected_str = expected;
 				const char *config_str;
 
@@ -198,10 +198,10 @@ test_wifi_open (void)
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
 
-	validate_opt ("wifi-open", config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1));
-	validate_opt ("wifi-open", config_dict, "ssid", TYPE_BYTES, ssid);
-	validate_opt ("wifi-open", config_dict, "bssid", TYPE_KEYWORD, bssid_str);
-	validate_opt ("wifi-open", config_dict, "key_mgmt", TYPE_KEYWORD, "NONE");
+	validate_opt ("wifi-open", config_dict, "scan_ssid", NM_SUPPL_OPT_TYPE_INT, GINT_TO_POINTER (1));
+	validate_opt ("wifi-open", config_dict, "ssid", NM_SUPPL_OPT_TYPE_BYTES, ssid);
+	validate_opt ("wifi-open", config_dict, "bssid", NM_SUPPL_OPT_TYPE_KEYWORD, bssid_str);
+	validate_opt ("wifi-open", config_dict, "key_mgmt", NM_SUPPL_OPT_TYPE_KEYWORD, "NONE");
 }
 
 static void
@@ -255,16 +255,16 @@ test_wifi_wep_key (const char *detail,
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
 
-	validate_opt (detail, config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1));
-	validate_opt (detail, config_dict, "ssid", TYPE_BYTES, ssid);
+	validate_opt (detail, config_dict, "scan_ssid", NM_SUPPL_OPT_TYPE_INT, GINT_TO_POINTER (1));
+	validate_opt (detail, config_dict, "ssid", NM_SUPPL_OPT_TYPE_BYTES, ssid);
 	if (test_bssid)
-		validate_opt (detail, config_dict, "bssid", TYPE_KEYWORD, bssid_str);
+		validate_opt (detail, config_dict, "bssid", NM_SUPPL_OPT_TYPE_KEYWORD, bssid_str);
 	else
-		validate_opt (detail, config_dict, "bgscan", TYPE_BYTES, bgscan);
+		validate_opt (detail, config_dict, "bgscan", NM_SUPPL_OPT_TYPE_BYTES, bgscan);
 
-	validate_opt (detail, config_dict, "key_mgmt", TYPE_KEYWORD, "NONE");
-	validate_opt (detail, config_dict, "wep_tx_keyidx", TYPE_INT, GINT_TO_POINTER (0));
-	validate_opt (detail, config_dict, "wep_key0", TYPE_BYTES, wep_key_bytes);
+	validate_opt (detail, config_dict, "key_mgmt", NM_SUPPL_OPT_TYPE_KEYWORD, "NONE");
+	validate_opt (detail, config_dict, "wep_tx_keyidx", NM_SUPPL_OPT_TYPE_INT, GINT_TO_POINTER (0));
+	validate_opt (detail, config_dict, "wep_key0", NM_SUPPL_OPT_TYPE_BYTES, wep_key_bytes);
 }
 
 static void
@@ -295,7 +295,7 @@ test_wifi_wep (void)
 
 static void
 test_wifi_wpa_psk (const char *detail,
-                   OptType key_type,
+                   NMSupplOptType key_type,
                    const char *key_data,
                    const unsigned char *expected,
                    size_t expected_size,
@@ -356,16 +356,16 @@ test_wifi_wpa_psk (const char *detail,
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
 
-	validate_opt (detail, config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1));
-	validate_opt (detail, config_dict, "ssid", TYPE_BYTES, ssid);
-	validate_opt (detail, config_dict, "bssid", TYPE_KEYWORD, bssid_str);
-	validate_opt (detail, config_dict, "key_mgmt", TYPE_KEYWORD, "WPA-PSK WPA-PSK-SHA256");
-	validate_opt (detail, config_dict, "proto", TYPE_KEYWORD, "WPA RSN");
-	validate_opt (detail, config_dict, "pairwise", TYPE_KEYWORD, "TKIP CCMP");
-	validate_opt (detail, config_dict, "group", TYPE_KEYWORD, "TKIP CCMP");
-	if (key_type == TYPE_BYTES)
+	validate_opt (detail, config_dict, "scan_ssid", NM_SUPPL_OPT_TYPE_INT, GINT_TO_POINTER (1));
+	validate_opt (detail, config_dict, "ssid", NM_SUPPL_OPT_TYPE_BYTES, ssid);
+	validate_opt (detail, config_dict, "bssid", NM_SUPPL_OPT_TYPE_KEYWORD, bssid_str);
+	validate_opt (detail, config_dict, "key_mgmt", NM_SUPPL_OPT_TYPE_KEYWORD, "WPA-PSK WPA-PSK-SHA256");
+	validate_opt (detail, config_dict, "proto", NM_SUPPL_OPT_TYPE_KEYWORD, "WPA RSN");
+	validate_opt (detail, config_dict, "pairwise", NM_SUPPL_OPT_TYPE_KEYWORD, "TKIP CCMP");
+	validate_opt (detail, config_dict, "group", NM_SUPPL_OPT_TYPE_KEYWORD, "TKIP CCMP");
+	if (key_type == NM_SUPPL_OPT_TYPE_BYTES)
 		validate_opt (detail, config_dict, "psk", key_type, wpa_psk_bytes);
-	else if (key_type == TYPE_STRING)
+	else if (key_type == NM_SUPPL_OPT_TYPE_STRING)
 		validate_opt (detail, config_dict, "psk", key_type, expected);
 	else
 		g_assert_not_reached ();
@@ -420,17 +420,17 @@ test_wifi_sae_psk (const char *psk)
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
 
-	validate_opt ("wifi-sae", config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1));
-	validate_opt ("wifi-sae", config_dict, "ssid", TYPE_BYTES, ssid);
-	validate_opt ("wifi-sae", config_dict, "bssid", TYPE_KEYWORD, bssid_str);
-	validate_opt ("wifi-sae", config_dict, "key_mgmt", TYPE_KEYWORD, "SAE");
-	validate_opt ("wifi-sae", config_dict, "proto", TYPE_KEYWORD, "RSN");
-	validate_opt ("wifi-sae", config_dict, "pairwise", TYPE_KEYWORD, "TKIP CCMP");
-	validate_opt ("wifi-sae", config_dict, "group", TYPE_KEYWORD, "TKIP CCMP");
+	validate_opt ("wifi-sae", config_dict, "scan_ssid", NM_SUPPL_OPT_TYPE_INT, GINT_TO_POINTER (1));
+	validate_opt ("wifi-sae", config_dict, "ssid", NM_SUPPL_OPT_TYPE_BYTES, ssid);
+	validate_opt ("wifi-sae", config_dict, "bssid", NM_SUPPL_OPT_TYPE_KEYWORD, bssid_str);
+	validate_opt ("wifi-sae", config_dict, "key_mgmt", NM_SUPPL_OPT_TYPE_KEYWORD, "SAE");
+	validate_opt ("wifi-sae", config_dict, "proto", NM_SUPPL_OPT_TYPE_KEYWORD, "RSN");
+	validate_opt ("wifi-sae", config_dict, "pairwise", NM_SUPPL_OPT_TYPE_KEYWORD, "TKIP CCMP");
+	validate_opt ("wifi-sae", config_dict, "group", NM_SUPPL_OPT_TYPE_KEYWORD, "TKIP CCMP");
 	if (short_psk)
-		validate_opt ("wifi-sae", config_dict, "sae_password", TYPE_KEYWORD, psk);
+		validate_opt ("wifi-sae", config_dict, "sae_password", NM_SUPPL_OPT_TYPE_KEYWORD, psk);
 	else
-		validate_opt ("wifi-sae", config_dict, "psk", TYPE_KEYWORD, psk);
+		validate_opt ("wifi-sae", config_dict, "psk", NM_SUPPL_OPT_TYPE_KEYWORD, psk);
 }
 
 static void
@@ -450,11 +450,11 @@ test_wifi_wpa_psk_types (void)
 	                                        0x6c, 0x2f, 0x11, 0x60, 0x5a, 0x16, 0x08, 0x93 };
 	const char *key2 = "r34lly l33t wp4 p4ssphr4s3 for t3st1ng";
 
-	test_wifi_wpa_psk ("wifi-wpa-psk-hex", TYPE_BYTES, key1, key1_expected,
+	test_wifi_wpa_psk ("wifi-wpa-psk-hex", NM_SUPPL_OPT_TYPE_BYTES, key1, key1_expected,
 	                   sizeof (key1_expected), NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL);
-	test_wifi_wpa_psk ("wifi-wep-psk-passphrase", TYPE_STRING, key2,
+	test_wifi_wpa_psk ("wifi-wep-psk-passphrase", NM_SUPPL_OPT_TYPE_STRING, key2,
 	                   (gconstpointer) key2, strlen (key2), NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED);
-	test_wifi_wpa_psk ("pmf-disabled", TYPE_STRING, key2,
+	test_wifi_wpa_psk ("pmf-disabled", NM_SUPPL_OPT_TYPE_STRING, key2,
 	                   (gconstpointer) key2, strlen (key2), NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE);
 }
 
@@ -527,15 +527,15 @@ test_wifi_eap_locked_bssid (void)
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
 
-	validate_opt ("wifi-eap", config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1));
-	validate_opt ("wifi-eap", config_dict, "ssid", TYPE_BYTES, ssid);
-	validate_opt ("wifi-eap", config_dict, "bssid", TYPE_KEYWORD, bssid_str);
-	validate_opt ("wifi-eap", config_dict, "key_mgmt", TYPE_KEYWORD, "WPA-EAP");
-	validate_opt ("wifi-eap", config_dict, "eap", TYPE_KEYWORD, "TLS");
-	validate_opt ("wifi-eap", config_dict, "proto", TYPE_KEYWORD, "WPA RSN");
-	validate_opt ("wifi-eap", config_dict, "pairwise", TYPE_KEYWORD, "TKIP CCMP");
-	validate_opt ("wifi-eap", config_dict, "group", TYPE_KEYWORD, "TKIP CCMP");
-	validate_opt ("wifi-eap", config_dict, "fragment_size", TYPE_INT, GINT_TO_POINTER(mtu-14));
+	validate_opt ("wifi-eap", config_dict, "scan_ssid", NM_SUPPL_OPT_TYPE_INT, GINT_TO_POINTER (1));
+	validate_opt ("wifi-eap", config_dict, "ssid", NM_SUPPL_OPT_TYPE_BYTES, ssid);
+	validate_opt ("wifi-eap", config_dict, "bssid", NM_SUPPL_OPT_TYPE_KEYWORD, bssid_str);
+	validate_opt ("wifi-eap", config_dict, "key_mgmt", NM_SUPPL_OPT_TYPE_KEYWORD, "WPA-EAP");
+	validate_opt ("wifi-eap", config_dict, "eap", NM_SUPPL_OPT_TYPE_KEYWORD, "TLS");
+	validate_opt ("wifi-eap", config_dict, "proto", NM_SUPPL_OPT_TYPE_KEYWORD, "WPA RSN");
+	validate_opt ("wifi-eap", config_dict, "pairwise", NM_SUPPL_OPT_TYPE_KEYWORD, "TKIP CCMP");
+	validate_opt ("wifi-eap", config_dict, "group", NM_SUPPL_OPT_TYPE_KEYWORD, "TKIP CCMP");
+	validate_opt ("wifi-eap", config_dict, "fragment_size", NM_SUPPL_OPT_TYPE_INT, GINT_TO_POINTER(mtu-14));
 }
 
 static void
@@ -568,15 +568,15 @@ test_wifi_eap_unlocked_bssid (void)
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
 
-	validate_opt ("wifi-eap", config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1));
-	validate_opt ("wifi-eap", config_dict, "ssid", TYPE_BYTES, ssid);
-	validate_opt ("wifi-eap", config_dict, "key_mgmt", TYPE_KEYWORD, "FILS-SHA256 FILS-SHA384");
-	validate_opt ("wifi-eap", config_dict, "eap", TYPE_KEYWORD, "TLS");
-	validate_opt ("wifi-eap", config_dict, "proto", TYPE_KEYWORD, "WPA RSN");
-	validate_opt ("wifi-eap", config_dict, "pairwise", TYPE_KEYWORD, "TKIP CCMP");
-	validate_opt ("wifi-eap", config_dict, "group", TYPE_KEYWORD, "TKIP CCMP");
-	validate_opt ("wifi-eap", config_dict, "fragment_size", TYPE_INT, GINT_TO_POINTER(mtu-14));
-	validate_opt ("wifi-eap", config_dict, "bgscan", TYPE_BYTES, bgscan);
+	validate_opt ("wifi-eap", config_dict, "scan_ssid", NM_SUPPL_OPT_TYPE_INT, GINT_TO_POINTER (1));
+	validate_opt ("wifi-eap", config_dict, "ssid", NM_SUPPL_OPT_TYPE_BYTES, ssid);
+	validate_opt ("wifi-eap", config_dict, "key_mgmt", NM_SUPPL_OPT_TYPE_KEYWORD, "FILS-SHA256 FILS-SHA384");
+	validate_opt ("wifi-eap", config_dict, "eap", NM_SUPPL_OPT_TYPE_KEYWORD, "TLS");
+	validate_opt ("wifi-eap", config_dict, "proto", NM_SUPPL_OPT_TYPE_KEYWORD, "WPA RSN");
+	validate_opt ("wifi-eap", config_dict, "pairwise", NM_SUPPL_OPT_TYPE_KEYWORD, "TKIP CCMP");
+	validate_opt ("wifi-eap", config_dict, "group", NM_SUPPL_OPT_TYPE_KEYWORD, "TKIP CCMP");
+	validate_opt ("wifi-eap", config_dict, "fragment_size", NM_SUPPL_OPT_TYPE_INT, GINT_TO_POINTER(mtu-14));
+	validate_opt ("wifi-eap", config_dict, "bgscan", NM_SUPPL_OPT_TYPE_BYTES, bgscan);
 }
 
 static void
@@ -609,15 +609,15 @@ test_wifi_eap_fils_disabled (void)
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
 
-	validate_opt ("wifi-eap", config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1));
-	validate_opt ("wifi-eap", config_dict, "ssid", TYPE_BYTES, ssid);
-	validate_opt ("wifi-eap", config_dict, "key_mgmt", TYPE_KEYWORD, "WPA-EAP WPA-EAP-SHA256");
-	validate_opt ("wifi-eap", config_dict, "eap", TYPE_KEYWORD, "TLS");
-	validate_opt ("wifi-eap", config_dict, "proto", TYPE_KEYWORD, "WPA RSN");
-	validate_opt ("wifi-eap", config_dict, "pairwise", TYPE_KEYWORD, "TKIP CCMP");
-	validate_opt ("wifi-eap", config_dict, "group", TYPE_KEYWORD, "TKIP CCMP");
-	validate_opt ("wifi-eap", config_dict, "fragment_size", TYPE_INT, GINT_TO_POINTER(mtu-14));
-	validate_opt ("wifi-eap", config_dict, "bgscan", TYPE_BYTES, bgscan);
+	validate_opt ("wifi-eap", config_dict, "scan_ssid", NM_SUPPL_OPT_TYPE_INT, GINT_TO_POINTER (1));
+	validate_opt ("wifi-eap", config_dict, "ssid", NM_SUPPL_OPT_TYPE_BYTES, ssid);
+	validate_opt ("wifi-eap", config_dict, "key_mgmt", NM_SUPPL_OPT_TYPE_KEYWORD, "WPA-EAP WPA-EAP-SHA256");
+	validate_opt ("wifi-eap", config_dict, "eap", NM_SUPPL_OPT_TYPE_KEYWORD, "TLS");
+	validate_opt ("wifi-eap", config_dict, "proto", NM_SUPPL_OPT_TYPE_KEYWORD, "WPA RSN");
+	validate_opt ("wifi-eap", config_dict, "pairwise", NM_SUPPL_OPT_TYPE_KEYWORD, "TKIP CCMP");
+	validate_opt ("wifi-eap", config_dict, "group", NM_SUPPL_OPT_TYPE_KEYWORD, "TKIP CCMP");
+	validate_opt ("wifi-eap", config_dict, "fragment_size", NM_SUPPL_OPT_TYPE_INT, GINT_TO_POINTER(mtu-14));
+	validate_opt ("wifi-eap", config_dict, "bgscan", NM_SUPPL_OPT_TYPE_BYTES, bgscan);
 }
 
 /*****************************************************************************/
diff --git a/src/systemd/src/libsystemd-network/dhcp-identifier.c b/src/systemd/src/libsystemd-network/dhcp-identifier.c
index b28e0ba0..026015a7 100644
--- a/src/systemd/src/libsystemd-network/dhcp-identifier.c
+++ b/src/systemd/src/libsystemd-network/dhcp-identifier.c
@@ -17,7 +17,6 @@
 #include "udev-util.h"
 #include "virt.h"
 
-#define SYSTEMD_PEN    43793
 #define HASH_KEY       SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09)
 #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 */
diff --git a/src/systemd/src/libsystemd-network/dhcp-identifier.h b/src/systemd/src/libsystemd-network/dhcp-identifier.h
index b3115125..76abd658 100644
--- a/src/systemd/src/libsystemd-network/dhcp-identifier.h
+++ b/src/systemd/src/libsystemd-network/dhcp-identifier.h
@@ -8,6 +8,8 @@
 #include "time-util.h"
 #include "unaligned.h"
 
+#define SYSTEMD_PEN    43793
+
 typedef enum DUIDType {
         DUID_TYPE_LLT       = 1,
         DUID_TYPE_EN        = 2,
diff --git a/src/systemd/src/libsystemd-network/dhcp-internal.h b/src/systemd/src/libsystemd-network/dhcp-internal.h
index 6a803d7b..7e814948 100644
--- a/src/systemd/src/libsystemd-network/dhcp-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp-internal.h
@@ -7,7 +7,6 @@
 
 #include <linux/if_packet.h>
 #include <net/ethernet.h>
-#include <net/if_arp.h>
 #include <stdint.h>
 
 #include "sd-dhcp-client.h"
@@ -23,6 +22,11 @@ typedef struct sd_dhcp_option {
         size_t length;
 } sd_dhcp_option;
 
+typedef struct DHCPServerData {
+        struct in_addr *addr;
+        size_t size;
+} DHCPServerData;
+
 extern const struct hash_ops dhcp_option_hash_ops;
 
 int dhcp_network_bind_raw_socket(int ifindex, union sockaddr_union *link,
diff --git a/src/systemd/src/libsystemd-network/dhcp-lease-internal.h b/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
index 5cbebb4a..aed30d61 100644
--- a/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
@@ -5,11 +5,9 @@
   Copyright © 2013 Intel Corporation. All rights reserved.
 ***/
 
-#include <stdint.h>
-#include <linux/if_packet.h>
-
 #include "sd-dhcp-client.h"
 
+#include "dhcp-internal.h"
 #include "dhcp-protocol.h"
 #include "list.h"
 #include "util.h"
@@ -52,20 +50,7 @@ struct sd_dhcp_lease {
         struct in_addr *router;
         size_t router_size;
 
-        struct in_addr *dns;
-        size_t dns_size;
-
-        struct in_addr *ntp;
-        size_t ntp_size;
-
-        struct in_addr *sip;
-        size_t sip_size;
-
-        struct in_addr *pop3_server;
-        size_t pop3_server_size;
-
-        struct in_addr *smtp_server;
-        size_t smtp_server_size;
+        DHCPServerData servers[_SD_DHCP_LEASE_SERVER_TYPE_MAX];
 
         struct sd_dhcp_route *static_route;
         size_t static_route_size, static_route_allocated;
diff --git a/src/systemd/src/libsystemd-network/dhcp6-internal.h b/src/systemd/src/libsystemd-network/dhcp6-internal.h
index 517e357d..b0d1216e 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp6-internal.h
@@ -11,9 +11,21 @@
 #include "sd-event.h"
 
 #include "list.h"
+#include "hashmap.h"
 #include "macro.h"
 #include "sparse-endian.h"
 
+typedef struct sd_dhcp6_option {
+        unsigned n_ref;
+
+        uint32_t enterprise_identifier;
+        uint16_t option;
+        void *data;
+        size_t length;
+} sd_dhcp6_option;
+
+extern const struct hash_ops dhcp6_option_hash_ops;
+
 /* Common option header */
 typedef struct DHCP6Option {
         be16_t code;
@@ -87,10 +99,13 @@ int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
 int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia);
 int dhcp6_option_append_pd(uint8_t *buf, size_t len, const DHCP6IA *pd, DHCP6Address *hint_pd_prefix);
 int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn);
+int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char **user_class);
+int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char **user_class);
+int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHashmap *vendor_options);
 int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
                        size_t *optlen, uint8_t **optvalue);
 int dhcp6_option_parse_status(DHCP6Option *option, size_t len);
-int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia);
+int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia, uint16_t *ret_status_code);
 int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
                                 struct in6_addr **addrs, size_t count,
                                 size_t *allocated);
diff --git a/src/systemd/src/libsystemd-network/dhcp6-option.c b/src/systemd/src/libsystemd-network/dhcp6-option.c
index bb4c4d91..d596752b 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-option.c
+++ b/src/systemd/src/libsystemd-network/dhcp6-option.c
@@ -11,6 +11,7 @@
 #include "sd-dhcp6-client.h"
 
 #include "alloc-util.h"
+#include "dhcp-identifier.h"
 #include "dhcp6-internal.h"
 #include "dhcp6-lease-internal.h"
 #include "dhcp6-protocol.h"
@@ -80,6 +81,39 @@ int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
         return 0;
 }
 
+int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHashmap *vendor_options) {
+        sd_dhcp6_option *options;
+        Iterator i;
+        int r;
+
+        assert(buf);
+        assert(*buf);
+        assert(buflen);
+        assert(vendor_options);
+
+        ORDERED_HASHMAP_FOREACH(options, vendor_options, i) {
+                _cleanup_free_ uint8_t *p = NULL;
+                size_t total;
+
+                total = 4 + 2 + 2 + options->length;
+
+                p = malloc(total);
+                if (!p)
+                        return -ENOMEM;
+
+                unaligned_write_be32(p, options->enterprise_identifier);
+                unaligned_write_be16(p + 4, options->option);
+                unaligned_write_be16(p + 6, options->length);
+                memcpy(p + 8, options->data, options->length);
+
+                r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_VENDOR_OPTS, total, p);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
 int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia) {
         uint16_t len;
         uint8_t *ia_hdr;
@@ -169,6 +203,75 @@ int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) {
         return r;
 }
 
+int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char **user_class) {
+        _cleanup_free_ uint8_t *p = NULL;
+        size_t total = 0, offset = 0;
+        char **s;
+
+        assert_return(buf && *buf && buflen && user_class, -EINVAL);
+
+        STRV_FOREACH(s, user_class) {
+                size_t len = strlen(*s);
+                uint8_t *q;
+
+                if (len > 0xffff)
+                        return -ENAMETOOLONG;
+                q = realloc(p, total + len + 2);
+                if (!q)
+                        return -ENOMEM;
+
+                p = q;
+
+                unaligned_write_be16(&p[offset], len);
+                memcpy(&p[offset + 2], *s, len);
+
+                offset += 2 + len;
+                total += 2 + len;
+        }
+
+        return dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_USER_CLASS, total, p);
+}
+
+int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char **vendor_class) {
+        _cleanup_free_ uint8_t *p = NULL;
+        uint32_t enterprise_identifier;
+        size_t total, offset;
+        char **s;
+
+        assert(buf);
+        assert(*buf);
+        assert(buflen);
+        assert(vendor_class);
+
+        enterprise_identifier = htobe32(SYSTEMD_PEN);
+
+        p = memdup(&enterprise_identifier, sizeof(enterprise_identifier));
+        if (!p)
+                return -ENOMEM;
+
+        total = sizeof(enterprise_identifier);
+        offset = total;
+
+        STRV_FOREACH(s, vendor_class) {
+                size_t len = strlen(*s);
+                uint8_t *q;
+
+                q = realloc(p, total + len + 2);
+                if (!q)
+                        return -ENOMEM;
+
+                p = q;
+
+                unaligned_write_be16(&p[offset], len);
+                memcpy(&p[offset + 2], *s, len);
+
+                offset += 2 + len;
+                total += 2 + len;
+        }
+
+        return dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_VENDOR_CLASS, total, p);
+}
+
 int dhcp6_option_append_pd(uint8_t *buf, size_t len, const DHCP6IA *pd, DHCP6Address *hint_pd_prefix) {
         DHCP6Option *option = (DHCP6Option *)buf;
         size_t i = sizeof(*option) + sizeof(pd->ia_pd);
@@ -349,13 +452,13 @@ static int dhcp6_option_parse_pdprefix(DHCP6Option *option, DHCP6IA *ia,
         return 0;
 }
 
-int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) {
+int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia, uint16_t *ret_status_code) {
+        uint32_t lt_t1, lt_t2, lt_valid = 0, lt_min = UINT32_MAX;
         uint16_t iatype, optlen;
-        size_t i, len;
+        size_t iaaddr_offset;
         int r = 0, status;
+        size_t i, len;
         uint16_t opt;
-        size_t iaaddr_offset;
-        uint32_t lt_t1, lt_t2, lt_valid = 0, lt_min = UINT32_MAX;
 
         assert_return(ia, -EINVAL);
         assert_return(!ia->addresses, -EINVAL);
@@ -465,11 +568,15 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) {
                         status = dhcp6_option_parse_status(option, optlen + offsetof(DHCP6Option, data));
                         if (status < 0)
                                 return status;
+
                         if (status > 0) {
-                                log_dhcp6_client(client, "IA status %d",
-                                                 status);
+                                if (ret_status_code)
+                                        *ret_status_code = status;
 
-                                return -EINVAL;
+                                log_dhcp6_client(client, "IA status %s",
+                                                 dhcp6_message_status_to_string(status));
+
+                                return 0;
                         }
 
                         break;
@@ -513,7 +620,10 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) {
                 break;
         }
 
-        return 0;
+        if (ret_status_code)
+                *ret_status_code = 0;
+
+        return 1;
 }
 
 int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen,
@@ -599,3 +709,44 @@ int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char *
 
         return idx;
 }
+
+static sd_dhcp6_option* dhcp6_option_free(sd_dhcp6_option *i) {
+        if (!i)
+                return NULL;
+
+        free(i->data);
+        return mfree(i);
+}
+
+int sd_dhcp6_option_new(uint16_t option, const void *data, size_t length, uint32_t enterprise_identifier, sd_dhcp6_option **ret) {
+        assert_return(ret, -EINVAL);
+        assert_return(length == 0 || data, -EINVAL);
+
+        _cleanup_free_ void *q = memdup(data, length);
+        if (!q)
+                return -ENOMEM;
+
+        sd_dhcp6_option *p = new(sd_dhcp6_option, 1);
+        if (!p)
+                return -ENOMEM;
+
+        *p = (sd_dhcp6_option) {
+                .n_ref = 1,
+                .option = option,
+                .enterprise_identifier = enterprise_identifier,
+                .length = length,
+                .data = TAKE_PTR(q),
+        };
+
+        *ret = p;
+        return 0;
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_option, sd_dhcp6_option, dhcp6_option_free);
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+                dhcp6_option_hash_ops,
+                void,
+                trivial_hash_func,
+                trivial_compare_func,
+                sd_dhcp6_option,
+                sd_dhcp6_option_unref);
diff --git a/src/systemd/src/libsystemd-network/dhcp6-protocol.h b/src/systemd/src/libsystemd-network/dhcp6-protocol.h
index ffae4453..f7a27028 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-protocol.h
+++ b/src/systemd/src/libsystemd-network/dhcp6-protocol.h
@@ -82,14 +82,35 @@ enum {
         DHCP6_NTP_SUBOPTION_SRV_FQDN            = 3,
 };
 
+/*
+ * RFC 8415, RFC 5007 and RFC 7653 status codes:
+ * https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#dhcpv6-parameters-5
+ */
 enum {
-        DHCP6_STATUS_SUCCESS                    = 0,
-        DHCP6_STATUS_UNSPEC_FAIL                = 1,
-        DHCP6_STATUS_NO_ADDRS_AVAIL             = 2,
-        DHCP6_STATUS_NO_BINDING                 = 3,
-        DHCP6_STATUS_NOT_ON_LINK                = 4,
-        DHCP6_STATUS_USE_MULTICAST              = 5,
-        _DHCP6_STATUS_MAX                       = 6,
+        DHCP6_STATUS_SUCCESS                      = 0,
+        DHCP6_STATUS_UNSPEC_FAIL                  = 1,
+        DHCP6_STATUS_NO_ADDRS_AVAIL               = 2,
+        DHCP6_STATUS_NO_BINDING                   = 3,
+        DHCP6_STATUS_NOT_ON_LINK                  = 4,
+        DHCP6_STATUS_USE_MULTICAST                = 5,
+        DHCP6_STATUS_NO_PREFIX_AVAIL              = 6,
+        DHCP6_STATUS_UNKNOWN_QUERY_TYPE           = 7,
+        DHCP6_STATUS_MALFORMED_QUERY              = 8,
+        DHCP6_STATUS_NOT_CONFIGURED               = 9,
+        DHCP6_STATUS_NOT_ALLOWED                  = 10,
+        DHCP6_STATUS_QUERY_TERMINATED             = 11,
+        DHCP6_STATUS_DATA_MISSING                 = 12,
+        DHCP6_STATUS_CATCHUP_COMPLETE             = 13,
+        DHCP6_STATUS_NOT_SUPPORTED                = 14,
+        DHCP6_STATUS_TLS_CONNECTION_REFUSED       = 15,
+        DHCP6_STATUS_ADDRESS_IN_USE               = 16,
+        DHCP6_STATUS_CONFIGURATION_CONFLICT       = 17,
+        DHCP6_STATUS_MISSING_BINDING_INFORMATION  = 18,
+        DHCP6_STATUS_OUTDATED_BINDING_INFORMATION = 19,
+        DHCP6_STATUS_SERVER_SHUTTING_DOWN         = 20,
+        DHCP6_STATUS_DNS_UPDATE_NOT_SUPPORTED     = 21,
+        DHCP6_STATUS_EXCESSIVE_TIME_SKEW          = 22,
+        _DHCP6_STATUS_MAX                         = 23,
 };
 
 enum {
diff --git a/src/systemd/src/libsystemd-network/lldp-neighbor.c b/src/systemd/src/libsystemd-network/lldp-neighbor.c
index 238d718b..9baa8e8c 100644
--- a/src/systemd/src/libsystemd-network/lldp-neighbor.c
+++ b/src/systemd/src/libsystemd-network/lldp-neighbor.c
@@ -52,6 +52,7 @@ static void lldp_neighbor_free(sd_lldp_neighbor *n) {
         free(n->port_description);
         free(n->system_name);
         free(n->system_description);
+        free(n->mud_url);
         free(n->chassis_id_as_string);
         free(n->port_id_as_string);
         free(n);
@@ -294,9 +295,20 @@ int lldp_neighbor_parse(sd_lldp_neighbor *n) {
 
                         break;
 
-                case SD_LLDP_TYPE_PRIVATE:
+                case SD_LLDP_TYPE_PRIVATE: {
                         if (length < 4)
                                 log_lldp("Found private TLV that is too short, ignoring.");
+                        else {
+                                /* RFC 8520: MUD URL */
+                                if (memcmp(p, SD_LLDP_OUI_MUD, sizeof(SD_LLDP_OUI_MUD)) == 0 &&
+                                    p[sizeof(SD_LLDP_OUI_MUD)] == SD_LLDP_OUI_SUBTYPE_MUD_USAGE_DESCRIPTION) {
+                                        r = parse_string(&n->mud_url, p + sizeof(SD_LLDP_OUI_MUD) + 1,
+                                                         length - 1 - sizeof(SD_LLDP_OUI_MUD));
+                                        if (r < 0)
+                                                return r;
+                                }
+                        }
+                }
 
                         break;
                 }
@@ -595,6 +607,17 @@ _public_ int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const ch
         return 0;
 }
 
+_public_ int sd_lldp_neighbor_get_mud_url(sd_lldp_neighbor *n, const char **ret) {
+        assert_return(n, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        if (!n->mud_url)
+                return -ENODATA;
+
+        *ret = n->mud_url;
+        return 0;
+}
+
 _public_ int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret) {
         assert_return(n, -EINVAL);
         assert_return(ret, -EINVAL);
diff --git a/src/systemd/src/libsystemd-network/lldp-neighbor.h b/src/systemd/src/libsystemd-network/lldp-neighbor.h
index 62dbff42..74175edf 100644
--- a/src/systemd/src/libsystemd-network/lldp-neighbor.h
+++ b/src/systemd/src/libsystemd-network/lldp-neighbor.h
@@ -54,6 +54,7 @@ struct sd_lldp_neighbor {
         char *port_description;
         char *system_name;
         char *system_description;
+        char *mud_url;
 
         uint16_t port_vlan_id;
 
diff --git a/src/systemd/src/libsystemd-network/network-internal.c b/src/systemd/src/libsystemd-network/network-internal.c
index 94a6236a..6a7382ac 100644
--- a/src/systemd/src/libsystemd-network/network-internal.c
+++ b/src/systemd/src/libsystemd-network/network-internal.c
@@ -195,30 +195,34 @@ bool net_match_config(Set *match_mac,
                       Set *match_permanent_mac,
                       char * const *match_paths,
                       char * const *match_drivers,
-                      char * const *match_types,
+                      char * const *match_iftypes,
                       char * const *match_names,
                       char * const *match_property,
                       char * const *match_wifi_iftype,
                       char * const *match_ssid,
                       Set *match_bssid,
-                      unsigned short iftype,
                       sd_device *device,
                       const struct ether_addr *dev_mac,
                       const struct ether_addr *dev_permanent_mac,
+                      const char *dev_driver,
+                      unsigned short dev_iftype,
                       const char *dev_name,
                       char * const *alternative_names,
-                      enum nl80211_iftype wifi_iftype,
-                      const char *ssid,
-                      const struct ether_addr *bssid) {
+                      enum nl80211_iftype dev_wifi_iftype,
+                      const char *dev_ssid,
+                      const struct ether_addr *dev_bssid) {
 
-        const char *dev_path = NULL, *dev_driver = NULL, *mac_str;
-        _cleanup_free_ char *dev_type;
+        _cleanup_free_ char *dev_iftype_str;
+        const char *dev_path = NULL;
 
-        dev_type = link_get_type_string(iftype, device);
+        dev_iftype_str = link_get_type_string(dev_iftype, device);
 
         if (device) {
+                const char *mac_str;
+
                 (void) sd_device_get_property_value(device, "ID_PATH", &dev_path);
-                (void) sd_device_get_property_value(device, "ID_NET_DRIVER", &dev_driver);
+                if (!dev_driver)
+                        (void) sd_device_get_property_value(device, "ID_NET_DRIVER", &dev_driver);
                 if (!dev_name)
                         (void) sd_device_get_sysname(device, &dev_name);
                 if (!dev_mac &&
@@ -241,7 +245,7 @@ bool net_match_config(Set *match_mac,
         if (!net_condition_test_strv(match_drivers, dev_driver))
                 return false;
 
-        if (!net_condition_test_strv(match_types, dev_type))
+        if (!net_condition_test_strv(match_iftypes, dev_iftype_str))
                 return false;
 
         if (!net_condition_test_ifname(match_names, dev_name, alternative_names))
@@ -250,13 +254,13 @@ bool net_match_config(Set *match_mac,
         if (!net_condition_test_property(match_property, device))
                 return false;
 
-        if (!net_condition_test_strv(match_wifi_iftype, wifi_iftype_to_string(wifi_iftype)))
+        if (!net_condition_test_strv(match_wifi_iftype, wifi_iftype_to_string(dev_wifi_iftype)))
                 return false;
 
-        if (!net_condition_test_strv(match_ssid, ssid))
+        if (!net_condition_test_strv(match_ssid, dev_ssid))
                 return false;
 
-        if (match_bssid && (!bssid || !set_contains(match_bssid, bssid)))
+        if (match_bssid && (!dev_bssid || !set_contains(match_bssid, dev_bssid)))
                 return false;
 
         return true;
@@ -660,27 +664,27 @@ int config_parse_bridge_port_priority(
 size_t serialize_in_addrs(FILE *f,
                           const struct in_addr *addresses,
                           size_t size,
-                          bool with_leading_space,
+                          bool *with_leading_space,
                           bool (*predicate)(const struct in_addr *addr)) {
-        size_t count;
-        size_t i;
-
         assert(f);
         assert(addresses);
 
-        count = 0;
+        size_t count = 0;
+        bool _space = false;
+        if (!with_leading_space)
+                with_leading_space = &_space;
 
-        for (i = 0; i < size; i++) {
+        for (size_t i = 0; i < size; i++) {
                 char sbuf[INET_ADDRSTRLEN];
 
                 if (predicate && !predicate(&addresses[i]))
                         continue;
-                if (with_leading_space)
+
+                if (*with_leading_space)
                         fputc(' ', f);
-                else
-                        with_leading_space = true;
                 fputs(inet_ntop(AF_INET, &addresses[i], sbuf, sizeof(sbuf)), f);
                 count++;
+                *with_leading_space = true;
         }
 
         return count;
@@ -722,20 +726,22 @@ int deserialize_in_addrs(struct in_addr **ret, const char *string) {
         return size;
 }
 
-void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses, size_t size) {
-        unsigned i;
-
+void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses, size_t size, bool *with_leading_space) {
         assert(f);
         assert(addresses);
         assert(size);
 
-        for (i = 0; i < size; i++) {
-                char buffer[INET6_ADDRSTRLEN];
+        bool _space = false;
+        if (!with_leading_space)
+                with_leading_space = &_space;
 
-                fputs(inet_ntop(AF_INET6, addresses+i, buffer, sizeof(buffer)), f);
+        for (size_t i = 0; i < size; i++) {
+                char buffer[INET6_ADDRSTRLEN];
 
-                if (i < size - 1)
+                if (*with_leading_space)
                         fputc(' ', f);
+                fputs(inet_ntop(AF_INET6, addresses+i, buffer, sizeof(buffer)), f);
+                *with_leading_space = true;
         }
 }
 
@@ -776,8 +782,6 @@ int deserialize_in6_addrs(struct in6_addr **ret, const char *string) {
 }
 
 void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size) {
-        unsigned i;
-
         assert(f);
         assert(key);
         assert(routes);
@@ -785,7 +789,7 @@ void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, siz
 
         fprintf(f, "%s=", key);
 
-        for (i = 0; i < size; i++) {
+        for (size_t i = 0; i < size; i++) {
                 char sbuf[INET_ADDRSTRLEN];
                 struct in_addr dest, gw;
                 uint8_t length;
@@ -794,8 +798,8 @@ void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, siz
                 assert_se(sd_dhcp_route_get_gateway(routes[i], &gw) >= 0);
                 assert_se(sd_dhcp_route_get_destination_prefix_length(routes[i], &length) >= 0);
 
-                fprintf(f, "%s/%" PRIu8, inet_ntop(AF_INET, &dest, sbuf, sizeof(sbuf)), length);
-                fprintf(f, ",%s%s", inet_ntop(AF_INET, &gw, sbuf, sizeof(sbuf)), (i < (size - 1)) ? " ": "");
+                fprintf(f, "%s/%" PRIu8, inet_ntop(AF_INET, &dest, sbuf, sizeof sbuf), length);
+                fprintf(f, ",%s%s", inet_ntop(AF_INET, &gw, sbuf, sizeof sbuf), i < size - 1 ? " ": "");
         }
 
         fputs("\n", f);
diff --git a/src/systemd/src/libsystemd-network/network-internal.h b/src/systemd/src/libsystemd-network/network-internal.h
index 6724cbbf..8e6377e0 100644
--- a/src/systemd/src/libsystemd-network/network-internal.h
+++ b/src/systemd/src/libsystemd-network/network-internal.h
@@ -17,23 +17,24 @@
 #if 0 /* NM_IGNORED */
 bool net_match_config(Set *match_mac,
                       Set *match_permanent_mac,
-                      char * const *match_path,
-                      char * const *match_driver,
-                      char * const *match_type,
-                      char * const *match_name,
+                      char * const *match_paths,
+                      char * const *match_drivers,
+                      char * const *match_iftypes,
+                      char * const *match_names,
                       char * const *match_property,
                       char * const *match_wifi_iftype,
                       char * const *match_ssid,
                       Set *match_bssid,
-                      unsigned short iftype,
                       sd_device *device,
                       const struct ether_addr *dev_mac,
                       const struct ether_addr *dev_permanent_mac,
+                      const char *dev_driver,
+                      unsigned short dev_iftype,
                       const char *dev_name,
                       char * const *alternative_names,
-                      enum nl80211_iftype wifi_iftype,
-                      const char *ssid,
-                      const struct ether_addr *bssid);
+                      enum nl80211_iftype dev_wifi_iftype,
+                      const char *dev_ssid,
+                      const struct ether_addr *dev_bssid);
 
 CONFIG_PARSER_PROTOTYPE(config_parse_net_condition);
 CONFIG_PARSER_PROTOTYPE(config_parse_hwaddr);
@@ -51,11 +52,12 @@ const char *net_get_name_persistent(sd_device *device);
 size_t serialize_in_addrs(FILE *f,
                           const struct in_addr *addresses,
                           size_t size,
-                          bool with_leading_space,
+                          bool *with_leading_space,
                           bool (*predicate)(const struct in_addr *addr));
 int deserialize_in_addrs(struct in_addr **addresses, const char *string);
 void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses,
-                         size_t size);
+                         size_t size,
+                         bool *with_leading_space);
 int deserialize_in6_addrs(struct in6_addr **addresses, const char *string);
 
 /* don't include "dhcp-lease-internal.h" as it causes conflicts between netinet/ip.h and linux/ip.h */
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-client.c b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
index 63321cca..70821ac2 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
@@ -29,6 +29,7 @@
 #include "random-util.h"
 #include "string-util.h"
 #include "strv.h"
+#include "utf8.h"
 #include "web-util.h"
 
 #define MAX_CLIENT_ID_LEN (sizeof(uint32_t) + MAX_DUID_LEN)  /* Arbitrary limit */
@@ -37,6 +38,32 @@
 #define RESTART_AFTER_NAK_MIN_USEC (1 * USEC_PER_SEC)
 #define RESTART_AFTER_NAK_MAX_USEC (30 * USEC_PER_MINUTE)
 
+typedef struct sd_dhcp_client_id {
+        uint8_t type;
+        union {
+                struct {
+                        /* 0: Generic (non-LL) (RFC 2132) */
+                        uint8_t data[MAX_CLIENT_ID_LEN];
+                } _packed_ gen;
+                struct {
+                        /* 1: Ethernet Link-Layer (RFC 2132) */
+                        uint8_t haddr[ETH_ALEN];
+                } _packed_ eth;
+                struct {
+                        /* 2 - 254: ARP/Link-Layer (RFC 2132) */
+                        uint8_t haddr[0];
+                } _packed_ ll;
+                struct {
+                        /* 255: Node-specific (RFC 4361) */
+                        be32_t iaid;
+                        struct duid duid;
+                } _packed_ ns;
+                struct {
+                        uint8_t data[MAX_CLIENT_ID_LEN];
+                } _packed_ raw;
+        };
+} _packed_ sd_dhcp_client_id;
+
 struct sd_dhcp_client {
         unsigned n_ref;
 
@@ -58,37 +85,14 @@ struct sd_dhcp_client {
         uint8_t mac_addr[MAX_MAC_ADDR_LEN];
         size_t mac_addr_len;
         uint16_t arp_type;
-        struct {
-                uint8_t type;
-                union {
-                        struct {
-                                /* 0: Generic (non-LL) (RFC 2132) */
-                                uint8_t data[MAX_CLIENT_ID_LEN];
-                        } _packed_ gen;
-                        struct {
-                                /* 1: Ethernet Link-Layer (RFC 2132) */
-                                uint8_t haddr[ETH_ALEN];
-                        } _packed_ eth;
-                        struct {
-                                /* 2 - 254: ARP/Link-Layer (RFC 2132) */
-                                uint8_t haddr[0];
-                        } _packed_ ll;
-                        struct {
-                                /* 255: Node-specific (RFC 4361) */
-                                be32_t iaid;
-                                struct duid duid;
-                        } _packed_ ns;
-                        struct {
-                                uint8_t data[MAX_CLIENT_ID_LEN];
-                        } _packed_ raw;
-                };
-        } _packed_ client_id;
+        sd_dhcp_client_id client_id;
         size_t client_id_len;
         char *hostname;
         char *vendor_class_identifier;
         char *mudurl;
         char **user_class;
         uint32_t mtu;
+        uint32_t fallback_lease_lifetime;
         uint32_t xid;
         usec_t start_time;
         uint64_t attempt;
@@ -152,6 +156,60 @@ static int client_receive_message_udp(
                 void *userdata);
 static void client_stop(sd_dhcp_client *client, int error);
 
+int sd_dhcp_client_id_to_string(const void *data, size_t len, char **ret) {
+        const sd_dhcp_client_id *client_id = data;
+        _cleanup_free_ char *t = NULL;
+        int r = 0;
+
+        assert_return(data, -EINVAL);
+        assert_return(len >= 1, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        len -= 1;
+        if (len > MAX_CLIENT_ID_LEN)
+                return -EINVAL;
+
+        switch (client_id->type) {
+        case 0:
+                if (utf8_is_printable((char *) client_id->gen.data, len))
+                        r = asprintf(&t, "%.*s", (int) len, client_id->gen.data);
+                else
+                        r = asprintf(&t, "DATA");
+                break;
+        case 1:
+                if (len != sizeof_field(sd_dhcp_client_id, eth))
+                        return -EINVAL;
+
+                r = asprintf(&t, "%x:%x:%x:%x:%x:%x",
+                             client_id->eth.haddr[0],
+                             client_id->eth.haddr[1],
+                             client_id->eth.haddr[2],
+                             client_id->eth.haddr[3],
+                             client_id->eth.haddr[4],
+                             client_id->eth.haddr[5]);
+                break;
+        case 2 ... 254:
+                r = asprintf(&t, "ARP/LL");
+                break;
+        case 255:
+                if (len < 6)
+                        return -EINVAL;
+
+                uint32_t iaid = be32toh(client_id->ns.iaid);
+                uint16_t duid_type = be16toh(client_id->ns.duid.type);
+                if (dhcp_validate_duid_len(duid_type, len - 6, true) < 0)
+                        return -EINVAL;
+
+                r = asprintf(&t, "IAID:0x%x/DUID", iaid);
+                break;
+        }
+
+        if (r < 0)
+                return -ENOMEM;
+        *ret = TAKE_PTR(t);
+        return 0;
+}
+
 int sd_dhcp_client_set_callback(
                 sd_dhcp_client *client,
                 sd_dhcp_client_callback_t cb,
@@ -617,6 +675,15 @@ int sd_dhcp_client_set_service_type(sd_dhcp_client *client, int type) {
         return 0;
 }
 
+int sd_dhcp_client_set_fallback_lease_lifetime(sd_dhcp_client *client, uint32_t fallback_lease_lifetime) {
+        assert_return(client, -EINVAL);
+        assert_return(fallback_lease_lifetime > 0, -EINVAL);
+
+        client->fallback_lease_lifetime = fallback_lease_lifetime;
+
+        return 0;
+}
+
 static int client_notify(sd_dhcp_client *client, int event) {
         assert(client);
 
@@ -855,36 +922,12 @@ static int dhcp_client_send_raw(
                                             packet, len);
 }
 
-static int client_send_discover(sd_dhcp_client *client) {
-        _cleanup_free_ DHCPPacket *discover = NULL;
-        size_t optoffset, optlen;
+static int client_append_common_discover_request_options(sd_dhcp_client *client, DHCPPacket *packet, size_t *optoffset, size_t optlen) {
         sd_dhcp_option *j;
         Iterator i;
         int r;
 
         assert(client);
-        assert(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_SELECTING));
-
-        r = client_message_init(client, &discover, DHCP_DISCOVER,
-                                &optlen, &optoffset);
-        if (r < 0)
-                return r;
-
-        /* the client may suggest values for the network address
-           and lease time in the DHCPDISCOVER message. The client may include
-           the ’requested IP address’ option to suggest that a particular IP
-           address be assigned, and may include the ’IP address lease time’
-           option to suggest the lease time it would like.
-         */
-        /* RFC7844 section 3:
-           SHOULD NOT contain any other option. */
-        if (!client->anonymize && client->last_addr != INADDR_ANY) {
-                r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
-                                       SD_DHCP_OPTION_REQUESTED_IP_ADDRESS,
-                                       4, &client->last_addr);
-                if (r < 0)
-                        return r;
-        }
 
         if (client->hostname) {
                 /* According to RFC 4702 "clients that send the Client FQDN option in
@@ -895,18 +938,18 @@ static int client_send_discover(sd_dhcp_client *client) {
                         /* it is unclear from RFC 2131 if client should send hostname in
                            DHCPDISCOVER but dhclient does and so we do as well
                         */
-                        r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
+                        r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
                                                SD_DHCP_OPTION_HOST_NAME,
                                                strlen(client->hostname), client->hostname);
                 } else
-                        r = client_append_fqdn_option(&discover->dhcp, optlen, &optoffset,
+                        r = client_append_fqdn_option(&packet->dhcp, optlen, optoffset,
                                                       client->hostname);
                 if (r < 0)
                         return r;
         }
 
         if (client->vendor_class_identifier) {
-                r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
+                r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
                                        SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER,
                                        strlen(client->vendor_class_identifier),
                                        client->vendor_class_identifier);
@@ -915,7 +958,7 @@ static int client_send_discover(sd_dhcp_client *client) {
         }
 
         if (client->mudurl) {
-                r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
+                r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
                                        SD_DHCP_OPTION_MUD_URL,
                                        strlen(client->mudurl),
                                        client->mudurl);
@@ -924,7 +967,7 @@ static int client_send_discover(sd_dhcp_client *client) {
         }
 
         if (client->user_class) {
-                r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
+                r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
                                        SD_DHCP_OPTION_USER_CLASS,
                                        strv_length(client->user_class),
                                        client->user_class);
@@ -933,7 +976,7 @@ static int client_send_discover(sd_dhcp_client *client) {
         }
 
         ORDERED_HASHMAP_FOREACH(j, client->extra_options, i) {
-                r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
+                r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0,
                                        j->option, j->length, j->data);
                 if (r < 0)
                         return r;
@@ -941,13 +984,50 @@ static int client_send_discover(sd_dhcp_client *client) {
 
         if (!ordered_hashmap_isempty(client->vendor_options)) {
                 r = dhcp_option_append(
-                                &discover->dhcp, optlen, &optoffset, 0,
+                                &packet->dhcp, optlen, optoffset, 0,
                                 SD_DHCP_OPTION_VENDOR_SPECIFIC,
                                 ordered_hashmap_size(client->vendor_options), client->vendor_options);
                 if (r < 0)
                         return r;
         }
 
+
+        return 0;
+}
+
+static int client_send_discover(sd_dhcp_client *client) {
+        _cleanup_free_ DHCPPacket *discover = NULL;
+        size_t optoffset, optlen;
+        int r;
+
+        assert(client);
+        assert(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_SELECTING));
+
+        r = client_message_init(client, &discover, DHCP_DISCOVER,
+                                &optlen, &optoffset);
+        if (r < 0)
+                return r;
+
+        /* the client may suggest values for the network address
+           and lease time in the DHCPDISCOVER message. The client may include
+           the ’requested IP address’ option to suggest that a particular IP
+           address be assigned, and may include the ’IP address lease time’
+           option to suggest the lease time it would like.
+         */
+        /* RFC7844 section 3:
+           SHOULD NOT contain any other option. */
+        if (!client->anonymize && client->last_addr != INADDR_ANY) {
+                r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
+                                       SD_DHCP_OPTION_REQUESTED_IP_ADDRESS,
+                                       4, &client->last_addr);
+                if (r < 0)
+                        return r;
+        }
+
+        r = client_append_common_discover_request_options(client, discover, &optoffset, optlen);
+        if (r < 0)
+                return r;
+
         r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
                                SD_DHCP_OPTION_END, 0, NULL);
         if (r < 0)
@@ -1039,36 +1119,9 @@ static int client_send_request(sd_dhcp_client *client) {
                 return -EINVAL;
         }
 
-        if (client->hostname) {
-                if (dns_name_is_single_label(client->hostname))
-                        r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
-                                               SD_DHCP_OPTION_HOST_NAME,
-                                               strlen(client->hostname), client->hostname);
-                else
-                        r = client_append_fqdn_option(&request->dhcp, optlen, &optoffset,
-                                                      client->hostname);
-                if (r < 0)
-                        return r;
-        }
-
-        if (client->vendor_class_identifier) {
-                r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
-                                       SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER,
-                                       strlen(client->vendor_class_identifier),
-                                       client->vendor_class_identifier);
-                if (r < 0)
-                        return r;
-        }
-
-        if (client->mudurl) {
-                r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
-                                       SD_DHCP_OPTION_MUD_URL,
-                                       strlen(client->mudurl),
-                                       client->mudurl);
-                if (r < 0)
-                        return r;
-        }
-
+        r = client_append_common_discover_request_options(client, request, &optoffset, optlen);
+        if (r < 0)
+                return r;
 
         r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
                                SD_DHCP_OPTION_END, 0, NULL);
@@ -1424,6 +1477,9 @@ static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, size_
         lease->next_server = offer->siaddr;
         lease->address = offer->yiaddr;
 
+        if (lease->lifetime == 0 && client->fallback_lease_lifetime > 0)
+                lease->lifetime = client->fallback_lease_lifetime;
+
         if (lease->address == 0 ||
             lease->server_address == 0 ||
             lease->lifetime == 0) {
@@ -1904,13 +1960,13 @@ static int client_receive_message_raw(
 
         sd_dhcp_client *client = userdata;
         _cleanup_free_ DHCPPacket *packet = NULL;
-        uint8_t cmsgbuf[CMSG_LEN(sizeof(struct tpacket_auxdata))];
+        CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct tpacket_auxdata))) control;
         struct iovec iov = {};
         struct msghdr msg = {
                 .msg_iov = &iov,
                 .msg_iovlen = 1,
-                .msg_control = cmsgbuf,
-                .msg_controllen = sizeof(cmsgbuf),
+                .msg_control = &control,
+                .msg_controllen = sizeof(control),
         };
         struct cmsghdr *cmsg;
         bool checksum = true;
@@ -1932,25 +1988,21 @@ static int client_receive_message_raw(
 
         iov = IOVEC_MAKE(packet, buflen);
 
-        len = recvmsg(fd, &msg, 0);
-        if (len < 0) {
-                if (IN_SET(errno, EAGAIN, EINTR, ENETDOWN))
-                        return 0;
-
-                return log_dhcp_client_errno(client, errno,
-                                             "Could not receive message from raw socket: %m");
-        } else if ((size_t)len < sizeof(DHCPPacket))
+        len = recvmsg_safe(fd, &msg, 0);
+        if (IN_SET(len, -EAGAIN, -EINTR, -ENETDOWN))
                 return 0;
+        if (len < 0)
+                return log_dhcp_client_errno(client, len,
+                                             "Could not receive message from raw socket: %m");
 
-        CMSG_FOREACH(cmsg, &msg)
-                if (cmsg->cmsg_level == SOL_PACKET &&
-                    cmsg->cmsg_type == PACKET_AUXDATA &&
-                    cmsg->cmsg_len == CMSG_LEN(sizeof(struct tpacket_auxdata))) {
-                        struct tpacket_auxdata *aux = (struct tpacket_auxdata*)CMSG_DATA(cmsg);
+        if ((size_t) len < sizeof(DHCPPacket))
+                return 0;
 
-                        checksum = !(aux->tp_status & TP_STATUS_CSUMNOTREADY);
-                        break;
-                }
+        cmsg = cmsg_find(&msg, SOL_PACKET, PACKET_AUXDATA, CMSG_LEN(sizeof(struct tpacket_auxdata)));
+        if (cmsg) {
+                struct tpacket_auxdata *aux = (struct tpacket_auxdata*) CMSG_DATA(cmsg);
+                checksum = !(aux->tp_status & TP_STATUS_CSUMNOTREADY);
+        }
 
         r = dhcp_packet_verify_headers(packet, len, checksum, client->port);
         if (r < 0)
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
index 86f21290..ae581f91 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
@@ -98,59 +98,40 @@ int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu) {
         return 0;
 }
 
-int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr) {
+int sd_dhcp_lease_get_servers(
+                sd_dhcp_lease *lease,
+                sd_dhcp_lease_server_type what,
+                const struct in_addr **addr) {
+
         assert_return(lease, -EINVAL);
+        assert_return(what >= 0, -EINVAL);
+        assert_return(what < _SD_DHCP_LEASE_SERVER_TYPE_MAX, -EINVAL);
         assert_return(addr, -EINVAL);
 
-        if (lease->dns_size <= 0)
+        if (lease->servers[what].size <= 0)
                 return -ENODATA;
 
-        *addr = lease->dns;
-        return (int) lease->dns_size;
+        *addr = lease->servers[what].addr;
+        return (int) lease->servers[what].size;
 }
 
+int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr) {
+        return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_DNS, addr);
+}
 int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr) {
-        assert_return(lease, -EINVAL);
-        assert_return(addr, -EINVAL);
-
-        if (lease->ntp_size <= 0)
-                return -ENODATA;
-
-        *addr = lease->ntp;
-        return (int) lease->ntp_size;
+        return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_NTP, addr);
 }
-
 int sd_dhcp_lease_get_sip(sd_dhcp_lease *lease, const struct in_addr **addr) {
-        assert_return(lease, -EINVAL);
-        assert_return(addr, -EINVAL);
-
-        if (lease->sip_size <= 0)
-                return -ENODATA;
-
-        *addr = lease->sip;
-        return (int) lease->sip_size;
+        return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_SIP, addr);
 }
-
-int sd_dhcp_lease_get_pop3_server(sd_dhcp_lease *lease, const struct in_addr **addr) {
-        assert_return(lease, -EINVAL);
-        assert_return(addr, -EINVAL);
-
-        if (lease->pop3_server_size <= 0)
-                return -ENODATA;
-
-        *addr = lease->pop3_server;
-        return (int) lease->pop3_server_size;
+int sd_dhcp_lease_get_pop3(sd_dhcp_lease *lease, const struct in_addr **addr) {
+        return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_POP3, addr);
 }
-
-int sd_dhcp_lease_get_smtp_server(sd_dhcp_lease *lease, const struct in_addr **addr) {
-        assert_return(lease, -EINVAL);
-        assert_return(addr, -EINVAL);
-
-        if (lease->smtp_server_size <= 0)
-                return -ENODATA;
-
-        *addr = lease->smtp_server;
-        return (int) lease->smtp_server_size;
+int sd_dhcp_lease_get_smtp(sd_dhcp_lease *lease, const struct in_addr **addr) {
+        return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_SMTP, addr);
+}
+int sd_dhcp_lease_get_lpr(sd_dhcp_lease *lease, const struct in_addr **addr) {
+        return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_LPR, addr);
 }
 
 int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname) {
@@ -300,11 +281,10 @@ static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) {
         free(lease->timezone);
         free(lease->hostname);
         free(lease->domainname);
-        free(lease->dns);
-        free(lease->ntp);
-        free(lease->sip);
-        free(lease->pop3_server);
-        free(lease->smtp_server);
+
+        for (sd_dhcp_lease_server_type i = 0; i < _SD_DHCP_LEASE_SERVER_TYPE_MAX; i++)
+                free(lease->servers[i].addr);
+
         free(lease->static_route);
         free(lease->client_id);
         free(lease->vendor_specific);
@@ -410,7 +390,7 @@ static int lease_parse_domain(const uint8_t *option, size_t len, char **ret) {
 }
 
 static int lease_parse_in_addrs(const uint8_t *option, size_t len, struct in_addr **ret, size_t *n_ret) {
-        assert(option);
+        assert(option || len == 0);
         assert(ret);
         assert(n_ret);
 
@@ -439,33 +419,24 @@ static int lease_parse_in_addrs(const uint8_t *option, size_t len, struct in_add
 }
 
 static int lease_parse_sip_server(const uint8_t *option, size_t len, struct in_addr **ret, size_t *n_ret) {
-        assert(option);
+        assert(option || len == 0);
         assert(ret);
         assert(n_ret);
 
-        if (len <= 0) {
-                *ret = mfree(*ret);
-                *n_ret = 0;
-        } else {
-                size_t n_addresses;
-                struct in_addr *addresses;
-                int l = len - 1;
-
-                if (l % 4 != 0)
-                        return -EINVAL;
-
-                n_addresses = l / 4;
+        if (len <= 0)
+                return -EINVAL;
 
-                addresses = newdup(struct in_addr, option + 1, n_addresses);
-                if (!addresses)
-                        return -ENOMEM;
+        /* The SIP record is like the other, regular server records, but prefixed with a single "encoding"
+         * byte that is either 0 or 1. We only support it to be 1 for now. Let's drop it and parse it like
+         * the other fields */
 
-                free(*ret);
-                *ret = addresses;
-                *n_ret = n_addresses;
+        if (option[0] != 1) { /* We only support IP address encoding for now */
+                *ret = mfree(*ret);
+                *n_ret = 0;
+                return 0;
         }
 
-        return 0;
+        return lease_parse_in_addrs(option + 1, len - 1, ret, n_ret);
 }
 
 static int lease_parse_routes(
@@ -610,35 +581,41 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
                 break;
 
         case SD_DHCP_OPTION_DOMAIN_NAME_SERVER:
-                r = lease_parse_in_addrs(option, len, &lease->dns, &lease->dns_size);
+                r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_DNS].addr, &lease->servers[SD_DHCP_LEASE_DNS].size);
                 if (r < 0)
                         log_debug_errno(r, "Failed to parse DNS server, ignoring: %m");
                 break;
 
         case SD_DHCP_OPTION_NTP_SERVER:
-                r = lease_parse_in_addrs(option, len, &lease->ntp, &lease->ntp_size);
+                r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_NTP].addr, &lease->servers[SD_DHCP_LEASE_NTP].size);
                 if (r < 0)
                         log_debug_errno(r, "Failed to parse NTP server, ignoring: %m");
                 break;
 
         case SD_DHCP_OPTION_SIP_SERVER:
-                r = lease_parse_sip_server(option, len, &lease->sip, &lease->sip_size);
+                r = lease_parse_sip_server(option, len, &lease->servers[SD_DHCP_LEASE_SIP].addr, &lease->servers[SD_DHCP_LEASE_SIP].size);
                 if (r < 0)
                         log_debug_errno(r, "Failed to parse SIP server, ignoring: %m");
                 break;
 
         case SD_DHCP_OPTION_POP3_SERVER:
-                r = lease_parse_in_addrs(option, len, &lease->pop3_server, &lease->pop3_server_size);
+                r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_POP3].addr, &lease->servers[SD_DHCP_LEASE_POP3].size);
                 if (r < 0)
                         log_debug_errno(r, "Failed to parse POP3 server, ignoring: %m");
                 break;
 
         case SD_DHCP_OPTION_SMTP_SERVER:
-                r = lease_parse_in_addrs(option, len, &lease->smtp_server, &lease->smtp_server_size);
+                r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_SMTP].addr, &lease->servers[SD_DHCP_LEASE_SMTP].size);
                 if (r < 0)
                         log_debug_errno(r, "Failed to parse SMTP server, ignoring: %m");
                 break;
 
+        case SD_DHCP_OPTION_LPR_SERVER:
+                r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_LPR].addr, &lease->servers[SD_DHCP_LEASE_LPR].size);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse LPR server, ignoring: %m");
+                break;
+
         case SD_DHCP_OPTION_STATIC_ROUTE:
                 r = lease_parse_routes(option, len, &lease->static_route, &lease->static_route_size, &lease->static_route_allocated);
                 if (r < 0)
@@ -674,7 +651,7 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
         case SD_DHCP_OPTION_HOST_NAME:
                 r = lease_parse_domain(option, len, &lease->hostname);
                 if (r < 0) {
-                        log_debug_errno(r, "Failed to parse host name, ignoring: %m");
+                        log_debug_errno(r, "Failed to parse hostname, ignoring: %m");
                         return 0;
                 }
 
@@ -1075,8 +1052,9 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
                 *dns = NULL,
                 *ntp = NULL,
                 *sip = NULL,
-                *pop3_server = NULL,
-                *smtp_server = NULL,
+                *pop3 = NULL,
+                *smtp = NULL,
+                *lpr = NULL,
                 *mtu = NULL,
                 *routes = NULL,
                 *domains = NULL,
@@ -1106,8 +1084,9 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
                            "DNS", &dns,
                            "NTP", &ntp,
                            "SIP", &sip,
-                           "POP3_SERVERS", &pop3_server,
-                           "SMTP_SERVERS", &smtp_server,
+                           "POP3", &pop3,
+                           "SMTP", &smtp,
+                           "LPR", &lpr,
                            "MTU", &mtu,
                            "DOMAINNAME", &lease->domainname,
                            "HOSTNAME", &lease->hostname,
@@ -1197,43 +1176,51 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
         }
 
         if (dns) {
-                r = deserialize_in_addrs(&lease->dns, dns);
+                r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_DNS].addr, dns);
                 if (r < 0)
                         log_debug_errno(r, "Failed to deserialize DNS servers %s, ignoring: %m", dns);
                 else
-                        lease->dns_size = r;
+                        lease->servers[SD_DHCP_LEASE_DNS].size = r;
         }
 
         if (ntp) {
-                r = deserialize_in_addrs(&lease->ntp, ntp);
+                r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_NTP].addr, ntp);
                 if (r < 0)
                         log_debug_errno(r, "Failed to deserialize NTP servers %s, ignoring: %m", ntp);
                 else
-                        lease->ntp_size = r;
+                        lease->servers[SD_DHCP_LEASE_NTP].size = r;
         }
 
         if (sip) {
-                r = deserialize_in_addrs(&lease->sip, sip);
+                r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_SIP].addr, sip);
                 if (r < 0)
                         log_debug_errno(r, "Failed to deserialize SIP servers %s, ignoring: %m", sip);
                 else
-                        lease->sip_size = r;
+                        lease->servers[SD_DHCP_LEASE_SIP].size = r;
+        }
+
+        if (pop3) {
+                r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_POP3].addr, pop3);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to deserialize POP3 server %s, ignoring: %m", pop3);
+                else
+                        lease->servers[SD_DHCP_LEASE_POP3].size = r;
         }
 
-        if (pop3_server) {
-                r = deserialize_in_addrs(&lease->pop3_server, pop3_server);
+        if (smtp) {
+                r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_SMTP].addr, smtp);
                 if (r < 0)
-                        log_debug_errno(r, "Failed to deserialize POP3 server %s, ignoring: %m", pop3_server);
+                        log_debug_errno(r, "Failed to deserialize SMTP server %s, ignoring: %m", smtp);
                 else
-                        lease->pop3_server_size = r;
+                        lease->servers[SD_DHCP_LEASE_SMTP].size = r;
         }
 
-        if (smtp_server) {
-                r = deserialize_in_addrs(&lease->smtp_server, smtp_server);
+        if (lpr) {
+                r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_LPR].addr, lpr);
                 if (r < 0)
-                        log_debug_errno(r, "Failed to deserialize SMTP server %s, ignoring: %m", smtp_server);
+                        log_debug_errno(r, "Failed to deserialize LPR server %s, ignoring: %m", lpr);
                 else
-                        lease->smtp_server_size = r;
+                        lease->servers[SD_DHCP_LEASE_LPR].size = r;
         }
 
         if (mtu) {
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c b/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
index 826ad56a..d653b257 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
@@ -20,6 +20,7 @@
 #include "dns-domain.h"
 #include "event-util.h"
 #include "fd-util.h"
+#include "hexdecoct.h"
 #include "hostname-util.h"
 #include "in-addr-util.h"
 #include "network-internal.h"
@@ -69,6 +70,8 @@ struct sd_dhcp6_client {
         size_t req_opts_len;
         char *fqdn;
         char *mudurl;
+        char **user_class;
+        char **vendor_class;
         sd_event_source *receive_message;
         usec_t retransmit_time;
         uint8_t retransmit_count;
@@ -80,6 +83,8 @@ struct sd_dhcp6_client {
         size_t duid_len;
         usec_t information_request_time_usec;
         usec_t information_refresh_time_usec;
+        OrderedHashmap *extra_options;
+        OrderedHashmap *vendor_options;
 };
 
 static const uint16_t default_req_opts[] = {
@@ -108,12 +113,29 @@ const char * dhcp6_message_type_table[_DHCP6_MESSAGE_MAX] = {
 DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, int);
 
 const char * dhcp6_message_status_table[_DHCP6_STATUS_MAX] = {
-        [DHCP6_STATUS_SUCCESS] = "Success",
-        [DHCP6_STATUS_UNSPEC_FAIL] = "Unspecified failure",
-        [DHCP6_STATUS_NO_ADDRS_AVAIL] = "No addresses available",
-        [DHCP6_STATUS_NO_BINDING] = "Binding unavailable",
-        [DHCP6_STATUS_NOT_ON_LINK] = "Not on link",
-        [DHCP6_STATUS_USE_MULTICAST] = "Use multicast",
+        [DHCP6_STATUS_SUCCESS]                      = "Success",
+        [DHCP6_STATUS_UNSPEC_FAIL]                  = "Unspecified failure",
+        [DHCP6_STATUS_NO_ADDRS_AVAIL]               = "No addresses available",
+        [DHCP6_STATUS_NO_BINDING]                   = "Binding unavailable",
+        [DHCP6_STATUS_NOT_ON_LINK]                  = "Not on link",
+        [DHCP6_STATUS_USE_MULTICAST]                = "Use multicast",
+        [DHCP6_STATUS_NO_PREFIX_AVAIL]              = "No prefix available",
+        [DHCP6_STATUS_UNKNOWN_QUERY_TYPE]           = "Unknown query type",
+        [DHCP6_STATUS_MALFORMED_QUERY]              = "Malformed query",
+        [DHCP6_STATUS_NOT_CONFIGURED]               = "Not configured",
+        [DHCP6_STATUS_NOT_ALLOWED]                  = "Not allowed",
+        [DHCP6_STATUS_QUERY_TERMINATED]             = "Query terminated",
+        [DHCP6_STATUS_DATA_MISSING]                 = "Data missing",
+        [DHCP6_STATUS_CATCHUP_COMPLETE]             = "Catch up complete",
+        [DHCP6_STATUS_NOT_SUPPORTED]                = "Not supported",
+        [DHCP6_STATUS_TLS_CONNECTION_REFUSED]       = "TLS connection refused",
+        [DHCP6_STATUS_ADDRESS_IN_USE]               = "Address in use",
+        [DHCP6_STATUS_CONFIGURATION_CONFLICT]       = "Configuration conflict",
+        [DHCP6_STATUS_MISSING_BINDING_INFORMATION]  = "Missing binding information",
+        [DHCP6_STATUS_OUTDATED_BINDING_INFORMATION] = "Outdated binding information",
+        [DHCP6_STATUS_SERVER_SHUTTING_DOWN]         = "Server shutting down",
+        [DHCP6_STATUS_DNS_UPDATE_NOT_SUPPORTED]     = "DNS update not supported",
+        [DHCP6_STATUS_EXCESSIVE_TIME_SKEW]          = "Excessive time skew",
 };
 
 DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, int);
@@ -207,6 +229,25 @@ int sd_dhcp6_client_set_prefix_delegation_hint(
         return 0;
 }
 
+int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client, sd_dhcp6_option *v) {
+        int r;
+
+        assert_return(client, -EINVAL);
+        assert_return(v, -EINVAL);
+
+        r = ordered_hashmap_ensure_allocated(&client->vendor_options, &dhcp6_option_hash_ops);
+        if (r < 0)
+                return r;
+
+        r = ordered_hashmap_put(client->vendor_options, v, v);
+        if (r < 0)
+                return r;
+
+        sd_dhcp6_option_ref(v);
+
+        return 1;
+}
+
 static int client_ensure_duid(sd_dhcp6_client *client) {
         if (client->duid_len != 0)
                 return 0;
@@ -237,7 +278,8 @@ static int dhcp6_client_set_duid_internal(
                         r = dhcp_validate_duid_len(duid_type, duid_len, false);
                         if (r < 0)
                                 return log_dhcp6_client_errno(client, r, "Failed to validate length of DUID: %m");
-                        log_dhcp6_client(client, "Setting DUID of type %u with unexpected content", duid_type);
+
+                        log_dhcp6_client(client, "Using DUID of type %u of incorrect length, proceeding.", duid_type);
                 }
 
                 client->duid.type = htobe16(duid_type);
@@ -296,6 +338,48 @@ int sd_dhcp6_client_set_duid_llt(
         return dhcp6_client_set_duid_internal(client, DUID_TYPE_LLT, NULL, 0, llt_time);
 }
 
+static const char* const dhcp6_duid_type_table[_DUID_TYPE_MAX] = {
+        [DUID_TYPE_LLT]  = "DUID-LLT",
+        [DUID_TYPE_EN]   = "DUID-EN/Vendor",
+        [DUID_TYPE_LL]   = "DUID-LL",
+        [DUID_TYPE_UUID] = "UUID",
+};
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(dhcp6_duid_type, DUIDType);
+
+int sd_dhcp6_client_duid_as_string(
+                sd_dhcp6_client *client,
+                char **duid) {
+        _cleanup_free_ char *p = NULL, *s = NULL, *t = NULL;
+        const char *v;
+        int r;
+
+        assert_return(client, -EINVAL);
+        assert_return(client->duid_len > 0, -ENODATA);
+
+        v = dhcp6_duid_type_to_string(be16toh(client->duid.type));
+        if (v) {
+                s = strdup(v);
+                if (!s)
+                        return -ENOMEM;
+        } else {
+                r = asprintf(&s, "%0x", client->duid.type);
+                if (r < 0)
+                        return -ENOMEM;
+        }
+
+        t = hexmem(&client->duid.raw.data, client->duid_len);
+        if (!t)
+                return -ENOMEM;
+
+        p = strjoin(s, ":", t);
+        if (!p)
+                return -ENOMEM;
+
+        *duid = TAKE_PTR(p);
+
+        return 0;
+}
+
 int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) {
         assert_return(client, -EINVAL);
         assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
@@ -307,6 +391,18 @@ int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) {
         return 0;
 }
 
+int sd_dhcp6_client_get_iaid(sd_dhcp6_client *client, uint32_t *iaid) {
+        assert_return(client, -EINVAL);
+        assert_return(iaid, -EINVAL);
+
+        if (!client->iaid_set)
+                return -ENODATA;
+
+        *iaid = be32toh(client->ia_na.ia_na.id);
+
+        return 0;
+}
+
 int sd_dhcp6_client_set_fqdn(
                 sd_dhcp6_client *client,
                 const char *fqdn) {
@@ -345,18 +441,8 @@ int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, uint16_t option)
         assert_return(client, -EINVAL);
         assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
 
-        switch(option) {
-
-        case SD_DHCP6_OPTION_DNS_SERVERS:
-        case SD_DHCP6_OPTION_DOMAIN_LIST:
-        case SD_DHCP6_OPTION_SNTP_SERVERS:
-        case SD_DHCP6_OPTION_NTP_SERVER:
-        case SD_DHCP6_OPTION_RAPID_COMMIT:
-                break;
-
-        default:
+        if (option <= 0 || option >= UINT8_MAX)
                 return -EINVAL;
-        }
 
         for (t = 0; t < client->req_opts_len; t++)
                 if (client->req_opts[t] == htobe16(option))
@@ -371,17 +457,60 @@ int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, uint16_t option)
         return 0;
 }
 
-int sd_dhcp6_client_set_request_mud_url(sd_dhcp6_client *client, char *mudurl) {
+int sd_dhcp6_client_set_request_mud_url(sd_dhcp6_client *client, const char *mudurl) {
 
         assert_return(client, -EINVAL);
         assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
         assert_return(mudurl, -EINVAL);
-        assert_return(strlen(mudurl) <= 255, -EINVAL);
+        assert_return(strlen(mudurl) <= UINT8_MAX, -EINVAL);
         assert_return(http_url_is_valid(mudurl), -EINVAL);
 
         return free_and_strdup(&client->mudurl, mudurl);
 }
 
+int sd_dhcp6_client_set_request_user_class(sd_dhcp6_client *client, char **user_class) {
+        _cleanup_strv_free_ char **s = NULL;
+        char **p;
+
+        assert_return(client, -EINVAL);
+        assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
+
+        assert_return(user_class, -EINVAL);
+
+        STRV_FOREACH(p, user_class)
+                if (strlen(*p) > UINT16_MAX)
+                        return -ENAMETOOLONG;
+
+        s = strv_copy(user_class);
+        if (!s)
+                return -ENOMEM;
+
+        client->user_class = TAKE_PTR(s);
+
+        return 0;
+}
+
+int sd_dhcp6_client_set_request_vendor_class(sd_dhcp6_client *client, char **vendor_class) {
+        _cleanup_strv_free_ char **s = NULL;
+        char **p;
+
+        assert_return(client, -EINVAL);
+        assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
+        assert_return(vendor_class, -EINVAL);
+
+        STRV_FOREACH(p, vendor_class)
+                if (strlen(*p) > UINT8_MAX)
+                        return -ENAMETOOLONG;
+
+        s = strv_copy(vendor_class);
+        if (!s)
+                return -ENOMEM;
+
+        client->vendor_class = TAKE_PTR(s);
+
+        return 0;
+}
+
 int sd_dhcp6_client_get_prefix_delegation(sd_dhcp6_client *client, int *delegation) {
         assert_return(client, -EINVAL);
         assert_return(delegation, -EINVAL);
@@ -436,6 +565,24 @@ int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) {
         return 0;
 }
 
+int sd_dhcp6_client_add_option(sd_dhcp6_client *client, sd_dhcp6_option *v) {
+        int r;
+
+        assert_return(client, -EINVAL);
+        assert_return(v, -EINVAL);
+
+        r = ordered_hashmap_ensure_allocated(&client->extra_options, &dhcp6_option_hash_ops);
+        if (r < 0)
+                return r;
+
+        r = ordered_hashmap_put(client->extra_options, UINT_TO_PTR(v->option), v);
+        if (r < 0)
+                return r;
+
+        sd_dhcp6_option_ref(v);
+        return 0;
+}
+
 static void client_notify(sd_dhcp6_client *client, int event) {
         assert(client);
 
@@ -481,7 +628,9 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
         _cleanup_free_ DHCP6Message *message = NULL;
         struct in6_addr all_servers =
                 IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
+        struct sd_dhcp6_option *j;
         size_t len, optlen = 512;
+        Iterator i;
         uint8_t *opt;
         int r;
         usec_t elapsed_usec;
@@ -542,6 +691,25 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
                                 return r;
                 }
 
+                if (client->user_class) {
+                        r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (client->vendor_class) {
+                        r = dhcp6_option_append_vendor_class(&opt, &optlen, client->vendor_class);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (!ordered_hashmap_isempty(client->vendor_options)) {
+                        r = dhcp6_option_append_vendor_option(&opt, &optlen,
+                                                       client->vendor_options);
+                        if (r < 0)
+                                return r;
+                }
+
                 if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) {
                         r = dhcp6_option_append_pd(opt, optlen, &client->ia_pd, &client->hint_pd_prefix);
                         if (r < 0)
@@ -588,6 +756,24 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
                                 return r;
                 }
 
+                if (client->user_class) {
+                        r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (client->vendor_class) {
+                        r = dhcp6_option_append_vendor_class(&opt, &optlen, client->vendor_class);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (!ordered_hashmap_isempty(client->vendor_options)) {
+                        r = dhcp6_option_append_vendor_option(&opt, &optlen, client->vendor_options);
+                        if (r < 0)
+                                return r;
+                }
+
                 if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) {
                         r = dhcp6_option_append_pd(opt, optlen, &client->lease->pd, NULL);
                         if (r < 0)
@@ -622,6 +808,24 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
                                 return r;
                 }
 
+                if (client->user_class) {
+                        r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (client->vendor_class) {
+                        r = dhcp6_option_append_vendor_class(&opt, &optlen, client->vendor_class);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (!ordered_hashmap_isempty(client->vendor_options)) {
+                        r = dhcp6_option_append_vendor_option(&opt, &optlen, client->vendor_options);
+                        if (r < 0)
+                                return r;
+                }
+
                 if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) {
                         r = dhcp6_option_append_pd(opt, optlen, &client->lease->pd, NULL);
                         if (r < 0)
@@ -661,6 +865,12 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
         if (r < 0)
                 return r;
 
+        ORDERED_HASHMAP_FOREACH(j, client->extra_options, i) {
+                r = dhcp6_option_append(&opt, &optlen, j->option, j->length, j->data);
+                if (r < 0)
+                        return r;
+        }
+
         r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message,
                                           len - optlen);
         if (r < 0)
@@ -888,10 +1098,11 @@ static int client_parse_message(
                 size_t len,
                 sd_dhcp6_lease *lease) {
 
+        uint16_t ia_na_status = 0, ia_pd_status = 0;
         uint32_t lt_t1 = ~0, lt_t2 = ~0;
+        usec_t irt = IRT_DEFAULT;
         bool clientid = false;
         size_t pos = 0;
-        usec_t irt = IRT_DEFAULT;
         int r;
 
         assert(client);
@@ -905,8 +1116,8 @@ static int client_parse_message(
                 DHCP6Option *option = (DHCP6Option *) &message->options[pos];
                 uint16_t optcode, optlen;
                 be32_t iaid_lease;
+                int  status;
                 uint8_t *optval;
-                int status;
 
                 if (len < pos + offsetof(DHCP6Option, data))
                         return -ENOBUFS;
@@ -983,10 +1194,15 @@ static int client_parse_message(
                                 break;
                         }
 
-                        r = dhcp6_option_parse_ia(option, &lease->ia);
+                        r = dhcp6_option_parse_ia(option, &lease->ia, &ia_na_status);
                         if (r < 0 && r != -ENOMSG)
                                 return r;
 
+                        if (ia_na_status == DHCP6_STATUS_NO_ADDRS_AVAIL) {
+                                pos += offsetof(DHCP6Option, data) + optlen;
+                                continue;
+                        }
+
                         r = dhcp6_lease_get_iaid(lease, &iaid_lease);
                         if (r < 0)
                                 return r;
@@ -1011,10 +1227,15 @@ static int client_parse_message(
                                 break;
                         }
 
-                        r = dhcp6_option_parse_ia(option, &lease->pd);
+                        r = dhcp6_option_parse_ia(option, &lease->pd, &ia_pd_status);
                         if (r < 0 && r != -ENOMSG)
                                 return r;
 
+                        if (ia_pd_status == DHCP6_STATUS_NO_PREFIX_AVAIL) {
+                                pos += offsetof(DHCP6Option, data) + optlen;
+                                continue;
+                        }
+
                         r = dhcp6_lease_get_pd_iaid(lease, &iaid_lease);
                         if (r < 0)
                                 return r;
@@ -1078,6 +1299,11 @@ static int client_parse_message(
                 pos += offsetof(DHCP6Option, data) + optlen;
         }
 
+        if (ia_na_status > 0 && ia_pd_status > 0) {
+                log_dhcp6_client(client, "No IA_PD prefix or IA_NA address received. Ignoring.");
+                return -EINVAL;
+        }
+
         if (!clientid) {
                 log_dhcp6_client(client, "%s has incomplete options",
                                  dhcp6_message_type_to_string(message->type));
@@ -1573,6 +1799,11 @@ static sd_dhcp6_client *dhcp6_client_free(sd_dhcp6_client *client) {
         free(client->req_opts);
         free(client->fqdn);
         free(client->mudurl);
+
+        ordered_hashmap_free(client->extra_options);
+        strv_free(client->user_class);
+        strv_free(client->vendor_class);
+
         return mfree(client);
 }
 
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c b/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c
index 35caec7a..b6dc0279 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c
@@ -215,7 +215,7 @@ int dhcp6_lease_set_dns(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) {
         return 0;
 }
 
-int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, struct in6_addr **addrs) {
+int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **addrs) {
         assert_return(lease, -EINVAL);
         assert_return(addrs, -EINVAL);
 
@@ -343,7 +343,7 @@ int dhcp6_lease_set_sntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen)
 }
 
 int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease,
-                                 struct in6_addr **addrs) {
+                                 const struct in6_addr **addrs) {
         assert_return(lease, -EINVAL);
         assert_return(addrs, -EINVAL);
 
diff --git a/src/systemd/src/libsystemd-network/sd-ipv4ll.c b/src/systemd/src/libsystemd-network/sd-ipv4ll.c
index a610546c..0bfaf265 100644
--- a/src/systemd/src/libsystemd-network/sd-ipv4ll.c
+++ b/src/systemd/src/libsystemd-network/sd-ipv4ll.c
@@ -254,12 +254,14 @@ static int ipv4ll_start_internal(sd_ipv4ll *ll, bool reset_generation) {
                 return r;
         }
 
-        return 0;
+        return 1;
 }
 
 int sd_ipv4ll_start(sd_ipv4ll *ll) {
         assert_return(ll, -EINVAL);
-        assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
+
+        if (sd_ipv4ll_is_running(ll))
+                return 0;
 
         return ipv4ll_start_internal(ll, true);
 }
diff --git a/src/systemd/src/libsystemd-network/sd-lldp.c b/src/systemd/src/libsystemd-network/sd-lldp.c
index bdf58dac..eb083f5e 100644
--- a/src/systemd/src/libsystemd-network/sd-lldp.c
+++ b/src/systemd/src/libsystemd-network/sd-lldp.c
@@ -468,7 +468,7 @@ _public_ int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***ret) {
 
 _public_ int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t m) {
         assert_return(lldp, -EINVAL);
-        assert_return(m <= 0, -EINVAL);
+        assert_return(m > 0, -EINVAL);
 
         lldp->neighbors_max = m;
         lldp_make_space(lldp, 0);
diff --git a/src/systemd/src/systemd/sd-dhcp-client.h b/src/systemd/src/systemd/sd-dhcp-client.h
index da2aa6c7..ac3b5b36 100644
--- a/src/systemd/src/systemd/sd-dhcp-client.h
+++ b/src/systemd/src/systemd/sd-dhcp-client.h
@@ -48,6 +48,7 @@ enum {
         SD_DHCP_OPTION_TIME_OFFSET                 = 2,
         SD_DHCP_OPTION_ROUTER                      = 3,
         SD_DHCP_OPTION_DOMAIN_NAME_SERVER          = 6,
+        SD_DHCP_OPTION_LPR_SERVER                  = 9,
         SD_DHCP_OPTION_HOST_NAME                   = 12,
         SD_DHCP_OPTION_BOOT_FILE_SIZE              = 13,
         SD_DHCP_OPTION_DOMAIN_NAME                 = 15,
@@ -184,6 +185,9 @@ int sd_dhcp_client_get_lease(
 int sd_dhcp_client_set_service_type(
                 sd_dhcp_client *client,
                 int type);
+int sd_dhcp_client_set_fallback_lease_lifetime(
+                sd_dhcp_client *client,
+                uint32_t fallback_lease_lifetime);
 
 int sd_dhcp_client_add_option(sd_dhcp_client *client, sd_dhcp_option *v);
 int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v);
@@ -201,6 +205,8 @@ sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client);
  * options when using RFC7844 Anonymity Profiles */
 int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize);
 
+int sd_dhcp_client_id_to_string(const void *data, size_t len, char **ret);
+
 int sd_dhcp_client_attach_event(
                 sd_dhcp_client *client,
                 sd_event *event,
diff --git a/src/systemd/src/systemd/sd-dhcp-lease.h b/src/systemd/src/systemd/sd-dhcp-lease.h
index 1ed5bf27..17bd4918 100644
--- a/src/systemd/src/systemd/sd-dhcp-lease.h
+++ b/src/systemd/src/systemd/sd-dhcp-lease.h
@@ -33,6 +33,17 @@ typedef struct sd_dhcp_route sd_dhcp_route;
 sd_dhcp_lease *sd_dhcp_lease_ref(sd_dhcp_lease *lease);
 sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease);
 
+typedef enum sd_dhcp_lease_server_type {
+        SD_DHCP_LEASE_DNS,
+        SD_DHCP_LEASE_NTP,
+        SD_DHCP_LEASE_SIP,
+        SD_DHCP_LEASE_POP3,
+        SD_DHCP_LEASE_SMTP,
+        SD_DHCP_LEASE_LPR,
+        _SD_DHCP_LEASE_SERVER_TYPE_MAX,
+        _SD_DHCP_LEASE_SERVER_TYPE_INVALID = -1,
+} sd_dhcp_lease_server_type;
+
 int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr);
 int sd_dhcp_lease_get_lifetime(sd_dhcp_lease *lease, uint32_t *lifetime);
 int sd_dhcp_lease_get_t1(sd_dhcp_lease *lease, uint32_t *t1);
@@ -42,11 +53,13 @@ int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr);
 int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **addr);
 int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr);
 int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *addr);
+int sd_dhcp_lease_get_servers(sd_dhcp_lease *lease, sd_dhcp_lease_server_type what, const struct in_addr **addr);
 int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr);
 int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr);
 int sd_dhcp_lease_get_sip(sd_dhcp_lease *lease, const struct in_addr **addr);
-int sd_dhcp_lease_get_pop3_server(sd_dhcp_lease *lease, const struct in_addr **addr);
-int sd_dhcp_lease_get_smtp_server(sd_dhcp_lease *lease, const struct in_addr **addr);
+int sd_dhcp_lease_get_pop3(sd_dhcp_lease *lease, const struct in_addr **addr);
+int sd_dhcp_lease_get_smtp(sd_dhcp_lease *lease, const struct in_addr **addr);
+int sd_dhcp_lease_get_lpr(sd_dhcp_lease *lease, const struct in_addr **addr);
 int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu);
 int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname);
 int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains);
diff --git a/src/systemd/src/systemd/sd-dhcp6-client.h b/src/systemd/src/systemd/sd-dhcp6-client.h
index 42d4ec75..2b0d63a5 100644
--- a/src/systemd/src/systemd/sd-dhcp6-client.h
+++ b/src/systemd/src/systemd/sd-dhcp6-client.h
@@ -24,6 +24,7 @@
 #include <sys/types.h>
 
 #include "sd-dhcp6-lease.h"
+#include "sd-dhcp6-option.h"
 #include "sd-event.h"
 
 #include "_sd-common.h"
@@ -109,6 +110,12 @@ int sd_dhcp6_client_set_duid_llt(
 int sd_dhcp6_client_set_iaid(
                 sd_dhcp6_client *client,
                 uint32_t iaid);
+int sd_dhcp6_client_get_iaid(
+                sd_dhcp6_client *client,
+                uint32_t *iaid);
+int sd_dhcp6_client_duid_as_string(
+                sd_dhcp6_client *client,
+                char **duid);
 int sd_dhcp6_client_set_fqdn(
                 sd_dhcp6_client *client,
                 const char *fqdn);
@@ -123,7 +130,13 @@ int sd_dhcp6_client_set_request_option(
                 uint16_t option);
 int sd_dhcp6_client_set_request_mud_url(
                 sd_dhcp6_client *client,
-                char *mudurl);
+                const char *mudurl);
+int sd_dhcp6_client_set_request_user_class(
+                sd_dhcp6_client *client,
+                char** user_class);
+int sd_dhcp6_client_set_request_vendor_class(
+                sd_dhcp6_client *client,
+                char** vendor_class);
 int sd_dhcp6_client_set_prefix_delegation_hint(
                 sd_dhcp6_client *client,
                 uint8_t prefixlen,
@@ -136,12 +149,17 @@ int sd_dhcp6_client_get_address_request(sd_dhcp6_client *client,
                                         int *request);
 int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client,
                                         int request);
-int sd_dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id);
+int sd_dhcp6_client_set_transaction_id(sd_dhcp6_client *client,
+                                       uint32_t transaction_id);
+int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client,
+                                      sd_dhcp6_option *v);
 
 int sd_dhcp6_client_get_lease(
                 sd_dhcp6_client *client,
                 sd_dhcp6_lease **ret);
 
+int sd_dhcp6_client_add_option(sd_dhcp6_client *client, sd_dhcp6_option *v);
+
 int sd_dhcp6_client_stop(sd_dhcp6_client *client);
 int sd_dhcp6_client_start(sd_dhcp6_client *client);
 int sd_dhcp6_client_is_running(sd_dhcp6_client *client);
diff --git a/src/systemd/src/systemd/sd-dhcp6-lease.h b/src/systemd/src/systemd/sd-dhcp6-lease.h
index 33a32a6d..4301c6db 100644
--- a/src/systemd/src/systemd/sd-dhcp6-lease.h
+++ b/src/systemd/src/systemd/sd-dhcp6-lease.h
@@ -39,10 +39,9 @@ int sd_dhcp6_lease_get_pd(sd_dhcp6_lease *lease, struct in6_addr *prefix,
                           uint32_t *lifetime_preferred,
                           uint32_t *lifetime_valid);
 
-int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, struct in6_addr **addrs);
+int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **addrs);
 int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***domains);
-int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease,
-                                 struct in6_addr **addrs);
+int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, const struct in6_addr **addrs);
 int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ntp_fqdn);
 
 sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease);
diff --git a/src/systemd/src/systemd/sd-dhcp6-option.h b/src/systemd/src/systemd/sd-dhcp6-option.h
new file mode 100644
index 00000000..88a49863
--- /dev/null
+++ b/src/systemd/src/systemd/sd-dhcp6-option.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#ifndef foosddhcp6optionhfoo
+#define foosddhcp6optionhfoo
+
+/***
+  systemd 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.1 of the License, or
+  (at your option) any later version.
+
+  systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <sys/types.h>
+
+#include "_sd-common.h"
+
+_SD_BEGIN_DECLARATIONS;
+
+typedef struct sd_dhcp6_option sd_dhcp6_option;
+
+int sd_dhcp6_option_new(uint16_t option, const void *data, size_t length, uint32_t enterprise_identifier, sd_dhcp6_option **ret);
+sd_dhcp6_option *sd_dhcp6_option_ref(sd_dhcp6_option *ra);
+sd_dhcp6_option *sd_dhcp6_option_unref(sd_dhcp6_option *ra);
+
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp6_option, sd_dhcp6_option_unref);
+
+_SD_END_DECLARATIONS;
+
+#endif
diff --git a/src/systemd/src/systemd/sd-lldp.h b/src/systemd/src/systemd/sd-lldp.h
index bf3afadc..2dc9f632 100644
--- a/src/systemd/src/systemd/sd-lldp.h
+++ b/src/systemd/src/systemd/sd-lldp.h
@@ -96,6 +96,9 @@ enum {
 #define SD_LLDP_OUI_802_1 (uint8_t[]) { 0x00, 0x80, 0xc2 }
 #define SD_LLDP_OUI_802_3 (uint8_t[]) { 0x00, 0x12, 0x0f }
 
+#define SD_LLDP_OUI_MUD   (uint8_t[]) { 0x00, 0x00, 0x5E }
+#define SD_LLDP_OUI_SUBTYPE_MUD_USAGE_DESCRIPTION  0x01
+
 /* IEEE 802.1AB-2009 Annex E */
 enum {
         SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID          = 1,
@@ -169,6 +172,7 @@ int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec);
 int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret);
 int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret);
 int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret);
+int sd_lldp_neighbor_get_mud_url(sd_lldp_neighbor *n, const char **ret);
 int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret);
 int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret);
 
diff --git a/src/systemd/src/systemd/sd-ndisc.h b/src/systemd/src/systemd/sd-ndisc.h
index d1bee343..3ddfc8cb 100644
--- a/src/systemd/src/systemd/sd-ndisc.h
+++ b/src/systemd/src/systemd/sd-ndisc.h
@@ -30,7 +30,7 @@
 
 _SD_BEGIN_DECLARATIONS;
 
-/* Neightbor Discovery Options, RFC 4861, Section 4.6 and
+/* Neighbor Discovery Options, RFC 4861, Section 4.6 and
  * https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml#icmpv6-parameters-5 */
 enum {
         SD_NDISC_OPTION_SOURCE_LL_ADDRESS  = 1,
diff --git a/src/tests/test-secret-agent.py b/src/tests/test-secret-agent.py
index d8cabc65..46f82b34 100755
--- a/src/tests/test-secret-agent.py
+++ b/src/tests/test-secret-agent.py
@@ -6,11 +6,13 @@ import dbus
 import dbus.service
 import dbus.mainloop.glib
 
-IFACE_SECRET_AGENT = 'org.freedesktop.NetworkManager.SecretAgent'
-IFACE_AGENT_MANAGER = 'org.freedesktop.NetworkManager.AgentManager'
+IFACE_SECRET_AGENT = "org.freedesktop.NetworkManager.SecretAgent"
+IFACE_AGENT_MANAGER = "org.freedesktop.NetworkManager.AgentManager"
+
 
 class NotAuthorizedException(dbus.DBusException):
-    _dbus_error_name = IFACE_SECRET_AGENT + '.NotAuthorized'
+    _dbus_error_name = IFACE_SECRET_AGENT + ".NotAuthorized"
+
 
 class Agent(dbus.service.Object):
     def __init__(self, bus, object_path):
@@ -18,41 +20,58 @@ class Agent(dbus.service.Object):
         self.bus = bus
         dbus.service.Object.__init__(self, bus, object_path)
 
-    @dbus.service.method(IFACE_SECRET_AGENT,
-                         in_signature='a{sa{sv}}osasb',
-                         out_signature='a{sa{sv}}',
-                         sender_keyword='sender')
-    def GetSecrets(self, connection_hash, connection_path, setting_name, hints, request_new, sender=None):
+    @dbus.service.method(
+        IFACE_SECRET_AGENT,
+        in_signature="a{sa{sv}}osasb",
+        out_signature="a{sa{sv}}",
+        sender_keyword="sender",
+    )
+    def GetSecrets(
+        self,
+        connection_hash,
+        connection_path,
+        setting_name,
+        hints,
+        request_new,
+        sender=None,
+    ):
         if not sender:
             raise NotAuthorizedException("Internal error: couldn't get sender")
         uid = self.bus.get_unix_user(sender)
         if uid != 0:
             raise NotAuthorizedException("UID %d not authorized" % uid)
 
-        print("Secrets requested path '%s' setting '%s' hints '%s' new %d" % (connection_path, setting_name, str(hints), request_new))
+        print(
+            "Secrets requested path '%s' setting '%s' hints '%s' new %d"
+            % (connection_path, setting_name, str(hints), request_new)
+        )
 
         # return some random GSM secrets
-        s_gsm = dbus.Dictionary({'password': 'asdfadfasdfaf'})
-        con = dbus.Dictionary({'gsm': s_gsm})
+        s_gsm = dbus.Dictionary({"password": "asdfadfasdfaf"})
+        con = dbus.Dictionary({"gsm": s_gsm})
         return con
 
+
 def register(proxy):
     proxy.Register("test.agent.id", dbus_interface=IFACE_AGENT_MANAGER)
     print("Registered!")
     return False
 
+
 def unregister(proxy, loop):
     proxy.Unregister(dbus_interface=IFACE_AGENT_MANAGER)
     loop.quit()
     return False
 
+
 def main():
     dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
 
     bus = dbus.SystemBus()
     obj = Agent(bus, "/org/freedesktop/NetworkManager/SecretAgent")
-    proxy = bus.get_object("org.freedesktop.NetworkManager",
-                           "/org/freedesktop/NetworkManager/AgentManager")
+    proxy = bus.get_object(
+        "org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager/AgentManager"
+    )
 
     mainloop = GLib.MainLoop()
 
@@ -65,8 +84,8 @@ def main():
         pass
 
     print("Unregistering...")
-    unregister(proxy, mainloop);
+    unregister(proxy, mainloop)
 
-if __name__ == '__main__':
-    main()
 
+if __name__ == "__main__":
+    main()