about summary refs log tree commit diff
path: root/src/devices
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices')
-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
16 files changed, 1613 insertions, 999 deletions
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);