summary refs log tree commit diff
path: root/src/devices
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2016-03-30 00:56:30 +0200
committerMichael Biebl <biebl@debian.org>2016-03-30 00:56:30 +0200
commitd9c99a29a0d3384c9c3d2adce430f5cb1134ab6a (patch)
treefa41baf72753961e71dd8d5bdbe2b89c9109e4f1 /src/devices
parentc2de0d98ba39e0a1a970d066fd19be786092f376 (diff)
Imported Upstream version 1.1.92 upstream/1.1.92
Diffstat (limited to 'src/devices')
-rw-r--r--src/devices/adsl/nm-atm-manager.c2
-rw-r--r--src/devices/adsl/nm-device-adsl.c5
-rw-r--r--src/devices/bluetooth/nm-bluez-device.c5
-rw-r--r--src/devices/bluetooth/nm-bluez-manager.c26
-rw-r--r--src/devices/bluetooth/nm-bluez4-adapter.c2
-rw-r--r--src/devices/bluetooth/nm-bluez5-manager.c4
-rw-r--r--src/devices/bluetooth/nm-bluez5-manager.h2
-rw-r--r--src/devices/bluetooth/nm-device-bt.c14
-rw-r--r--src/devices/nm-device-bond.c3
-rw-r--r--src/devices/nm-device-bridge.c3
-rw-r--r--src/devices/nm-device-ethernet.c26
-rw-r--r--src/devices/nm-device-factory.c4
-rw-r--r--src/devices/nm-device-infiniband.c5
-rw-r--r--src/devices/nm-device-ip-tunnel.c10
-rw-r--r--src/devices/nm-device-logging.h4
-rw-r--r--src/devices/nm-device-macvlan.c3
-rw-r--r--src/devices/nm-device-tun.c3
-rw-r--r--src/devices/nm-device-vlan.c3
-rw-r--r--src/devices/nm-device-vxlan.c3
-rw-r--r--src/devices/nm-device.c141
-rw-r--r--src/devices/nm-device.h5
-rw-r--r--src/devices/nm-lldp-listener.c910
-rw-r--r--src/devices/nm-lldp-listener.h3
-rw-r--r--src/devices/team/nm-device-team.c3
-rw-r--r--src/devices/tests/test-arping.c6
-rw-r--r--src/devices/tests/test-lldp.c466
-rw-r--r--src/devices/wifi/nm-device-olpc-mesh.c3
-rw-r--r--src/devices/wifi/nm-device-wifi.c7
-rw-r--r--src/devices/wifi/nm-wifi-ap.c2
-rw-r--r--src/devices/wifi/tests/test-wifi-ap-utils.c2
-rw-r--r--src/devices/wwan/nm-device-modem.c7
-rw-r--r--src/devices/wwan/nm-modem-broadband.c16
-rw-r--r--src/devices/wwan/nm-modem-manager.c2
-rw-r--r--src/devices/wwan/nm-modem.c5
34 files changed, 1108 insertions, 597 deletions
diff --git a/src/devices/adsl/nm-atm-manager.c b/src/devices/adsl/nm-atm-manager.c
index a3fa4901..67085752 100644
--- a/src/devices/adsl/nm-atm-manager.c
+++ b/src/devices/adsl/nm-atm-manager.c
@@ -117,7 +117,7 @@ adsl_add (NMAtmManager *self, GUdevDevice *udev_device)
 	nm_log_dbg (LOGD_HW, "(%s): found ATM device", ifname);
 
 	atm_index_path = g_strdup_printf ("/sys/class/atm/%s/atmindex",
-	                                  ASSERT_VALID_PATH_COMPONENT (ifname));
+	                                  NM_ASSERT_VALID_PATH_COMPONENT (ifname));
 	atm_index = (int) nm_platform_sysctl_get_int_checked (NM_PLATFORM_GET,
 	                                                      atm_index_path,
 	                                                      10, 0, G_MAXINT,
diff --git a/src/devices/adsl/nm-device-adsl.c b/src/devices/adsl/nm-device-adsl.c
index e7a91e0a..ebb7a319 100644
--- a/src/devices/adsl/nm-device-adsl.c
+++ b/src/devices/adsl/nm-device-adsl.c
@@ -125,7 +125,8 @@ complete_connection (NMDevice *device,
 	if (s_adsl && !nm_setting_verify (NM_SETTING (s_adsl), NULL, error))
 		return FALSE;
 
-	nm_utils_complete_generic (connection,
+	nm_utils_complete_generic (NM_PLATFORM_GET,
+	                           connection,
 	                           NM_SETTING_ADSL_SETTING_NAME,
 	                           existing_connections,
 	                           NULL,
@@ -535,7 +536,7 @@ carrier_update_cb (gpointer user_data)
 	char *path;
 
 	path  = g_strdup_printf ("/sys/class/atm/%s/carrier",
-	                         ASSERT_VALID_PATH_COMPONENT (nm_device_get_iface (NM_DEVICE (self))));
+	                         NM_ASSERT_VALID_PATH_COMPONENT (nm_device_get_iface (NM_DEVICE (self))));
 	carrier = (int) nm_platform_sysctl_get_int_checked (NM_PLATFORM_GET, path, 10, 0, 1, -1);
 	g_free (path);
 
diff --git a/src/devices/bluetooth/nm-bluez-device.c b/src/devices/bluetooth/nm-bluez-device.c
index 71226813..a009746d 100644
--- a/src/devices/bluetooth/nm-bluez-device.c
+++ b/src/devices/bluetooth/nm-bluez-device.c
@@ -246,9 +246,8 @@ pan_connection_check_create (NMBluezDevice *self)
 		priv->pan_connection = added;
 		nm_log_dbg (LOGD_BT, "bluez[%s] added new Bluetooth connection for NAP device: '%s' (%s)", priv->path, id, uuid);
 	} else {
-		nm_log_warn (LOGD_BT, "bluez[%s] couldn't add new Bluetooth connection for NAP device: '%s' (%s): %d / %s",
-		             priv->path, id, uuid, error ? error->code : -1,
-		             (error && error->message) ? error->message : "(unknown)");
+		nm_log_warn (LOGD_BT, "bluez[%s] couldn't add new Bluetooth connection for NAP device: '%s' (%s): %s",
+		             priv->path, id, uuid, error->message);
 		g_clear_error (&error);
 
 	}
diff --git a/src/devices/bluetooth/nm-bluez-manager.c b/src/devices/bluetooth/nm-bluez-manager.c
index f66b5abe..766ecc0a 100644
--- a/src/devices/bluetooth/nm-bluez-manager.c
+++ b/src/devices/bluetooth/nm-bluez-manager.c
@@ -38,6 +38,16 @@
 #include "nm-platform.h"
 #include "nm-dbus-compat.h"
 
+#define _NMLOG_DOMAIN        LOGD_BT
+#define _NMLOG_PREFIX_NAME   "bluez"
+#define _NMLOG(level, ...) \
+    G_STMT_START { \
+        nm_log ((level), _NMLOG_DOMAIN, \
+                "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+                _NMLOG_PREFIX_NAME": " \
+                _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+    } G_STMT_END
+
 typedef struct {
 	int bluez_version;
 
@@ -146,12 +156,12 @@ manager_bdaddr_added_cb (NMBluez4Manager *bluez_mgr,
 	if (!device)
 		return;
 
-	nm_log_info (LOGD_BT, "BT device %s (%s) added (%s%s%s)",
-	             name,
-	             bdaddr,
-	             has_dun ? "DUN" : "",
-	             has_dun && has_nap ? " " : "",
-	             has_nap ? "NAP" : "");
+	_LOGI ("BT device %s (%s) added (%s%s%s)",
+	       name,
+	       bdaddr,
+	       has_dun ? "DUN" : "",
+	       has_dun && has_nap ? " " : "",
+	       has_nap ? "NAP" : "");
 	g_signal_emit_by_name (self, NM_DEVICE_FACTORY_DEVICE_ADDED, device);
 	g_object_unref (device);
 }
@@ -163,7 +173,7 @@ setup_version_number (NMBluezManager *self, int bluez_version)
 
 	g_return_if_fail (!priv->bluez_version);
 
-	nm_log_info (LOGD_BT, "use BlueZ version %d", bluez_version);
+	_LOGI ("use BlueZ version %d", bluez_version);
 
 	priv->bluez_version = bluez_version;
 
@@ -235,7 +245,7 @@ check_bluez_and_try_setup_final_step (NMBluezManager *self, int bluez_version, c
 		setup_bluez5 (self);
 		break;
 	default:
-		nm_log_dbg (LOGD_BT, "detecting BlueZ version failed: %s", reason);
+		_LOGD ("detecting BlueZ version failed: %s", reason);
 
 		/* cancel current attempts to detect the version. */
 		cleanup_checking (self, FALSE);
diff --git a/src/devices/bluetooth/nm-bluez4-adapter.c b/src/devices/bluetooth/nm-bluez4-adapter.c
index deeebcb6..9d8fd210 100644
--- a/src/devices/bluetooth/nm-bluez4-adapter.c
+++ b/src/devices/bluetooth/nm-bluez4-adapter.c
@@ -203,7 +203,7 @@ get_properties_cb (GObject *proxy, GAsyncResult *result, gpointer user_data)
 
 	properties = g_variant_get_child_value (ret, 0);
 
-	g_variant_lookup (properties, "Address", "s", &priv->address);
+	(void) g_variant_lookup (properties, "Address", "s", &priv->address);
 	if (g_variant_lookup (properties, "Devices", "^ao", &devices)) {
 		for (i = 0; devices[i]; i++)
 			device_created (priv->proxy, devices[i], self);
diff --git a/src/devices/bluetooth/nm-bluez5-manager.c b/src/devices/bluetooth/nm-bluez5-manager.c
index e44e87d8..66d171bf 100644
--- a/src/devices/bluetooth/nm-bluez5-manager.c
+++ b/src/devices/bluetooth/nm-bluez5-manager.c
@@ -213,6 +213,7 @@ get_managed_objects_cb (GDBusProxy *proxy,
 		                            G_VARIANT_TYPE_DICTIONARY)) {
 			device_added (proxy, path, self);
 		}
+		g_variant_unref (ifaces);
 	}
 
 	g_variant_unref (variant);
@@ -231,8 +232,7 @@ on_proxy_acquired (GObject *object,
 	priv->proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
 
 	if (!priv->proxy) {
-		nm_log_warn (LOGD_BT, "Couldn't acquire object manager proxy: %s",
-		             error && error->message ? error->message : "(unknown)");
+		nm_log_warn (LOGD_BT, "Couldn't acquire object manager proxy: %s", error->message);
 		g_clear_error (&error);
 		return;
 	}
diff --git a/src/devices/bluetooth/nm-bluez5-manager.h b/src/devices/bluetooth/nm-bluez5-manager.h
index ae5512f8..1c531090 100644
--- a/src/devices/bluetooth/nm-bluez5-manager.h
+++ b/src/devices/bluetooth/nm-bluez5-manager.h
@@ -1,4 +1,4 @@
-/* -*- Mode: C; tab-width: 5; indent-tabs-mode: t; c-basic-offset: 5 -*- */
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/devices/bluetooth/nm-device-bt.c b/src/devices/bluetooth/nm-device-bt.c
index e151b795..eef4ed0a 100644
--- a/src/devices/bluetooth/nm-device-bt.c
+++ b/src/devices/bluetooth/nm-device-bt.c
@@ -20,12 +20,13 @@
 
 #include "nm-default.h"
 
+#include "nm-device-bt.h"
+
 #include <stdio.h>
 #include <string.h>
 
 #include "nm-bluez-common.h"
 #include "nm-bluez-device.h"
-#include "nm-device-bt.h"
 #include "nm-device-private.h"
 #include "ppp-manager/nm-ppp-manager.h"
 #include "nm-setting-connection.h"
@@ -39,6 +40,7 @@
 #include "nm-utils.h"
 #include "nm-bt-error.h"
 #include "nm-bt-enum-types.h"
+#include "nm-platform.h"
 
 #include "nmdbus-device-bt.h"
 
@@ -315,7 +317,8 @@ complete_connection (NMDevice *device,
 		return FALSE;
 	}
 
-	nm_utils_complete_generic (connection,
+	nm_utils_complete_generic (NM_PLATFORM_GET,
+	                           connection,
 	                           NM_SETTING_BLUETOOTH_SETTING_NAME,
 	                           existing_connections,
 	                           preferred,
@@ -497,8 +500,8 @@ modem_ip4_config_result (NMModem *modem,
 
 	if (error) {
 		_LOGW (LOGD_MB | LOGD_IP4 | LOGD_BT,
-		       "retrieving IP4 configuration failed: (%d) %s",
-		       error->code, error->message ? error->message : "(unknown)");
+		       "retrieving IP4 configuration failed: %s",
+		       error->message);
 
 		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 	} else
@@ -728,8 +731,7 @@ bluez_connect_cb (GObject *object,
 	                                         res, &error);
 
 	if (!device) {
-		_LOGW (LOGD_BT, "Error connecting with bluez: %s",
-		       error && error->message ? error->message : "(unknown)");
+		_LOGW (LOGD_BT, "Error connecting with bluez: %s", error->message);
 		g_clear_error (&error);
 
 		nm_device_state_changed (NM_DEVICE (self),
diff --git a/src/devices/nm-device-bond.c b/src/devices/nm-device-bond.c
index cef104e6..b88b2511 100644
--- a/src/devices/nm-device-bond.c
+++ b/src/devices/nm-device-bond.c
@@ -97,7 +97,8 @@ complete_connection (NMDevice *device,
 {
 	NMSettingBond *s_bond;
 
-	nm_utils_complete_generic (connection,
+	nm_utils_complete_generic (NM_PLATFORM_GET,
+	                           connection,
 	                           NM_SETTING_BOND_SETTING_NAME,
 	                           existing_connections,
 	                           NULL,
diff --git a/src/devices/nm-device-bridge.c b/src/devices/nm-device-bridge.c
index cc72b003..c35d2863 100644
--- a/src/devices/nm-device-bridge.c
+++ b/src/devices/nm-device-bridge.c
@@ -103,7 +103,8 @@ complete_connection (NMDevice *device,
 {
 	NMSettingBridge *s_bridge;
 
-	nm_utils_complete_generic (connection,
+	nm_utils_complete_generic (NM_PLATFORM_GET,
+	                           connection,
 	                           NM_SETTING_BRIDGE_SETTING_NAME,
 	                           existing_connections,
 	                           NULL,
diff --git a/src/devices/nm-device-ethernet.c b/src/devices/nm-device-ethernet.c
index 2fb84276..6225ae7b 100644
--- a/src/devices/nm-device-ethernet.c
+++ b/src/devices/nm-device-ethernet.c
@@ -158,7 +158,7 @@ _update_s390_subchannels (NMDeviceEthernet *self)
 	dev = (GUdevDevice *) nm_platform_link_get_udev_device (NM_PLATFORM_GET, ifindex);
 	if (!dev) {
 		_LOGW (LOGD_DEVICE | LOGD_HW, "failed to find device %d '%s' with udev",
-		       ifindex, str_if_set (nm_device_get_iface (NM_DEVICE (self)), "(null)"));
+		       ifindex, nm_device_get_iface (NM_DEVICE (self)) ?: "(null)");
 		goto out;
 	}
 	g_object_ref (dev);
@@ -177,7 +177,7 @@ _update_s390_subchannels (NMDeviceEthernet *self)
 	dir = g_dir_open (parent_path, 0, &error);
 	if (!dir) {
 		_LOGW (LOGD_DEVICE | LOGD_HW, "failed to open directory '%s': %s",
-		       parent_path, error && error->message ? error->message : "(unknown)");
+		       parent_path, error->message);
 		g_clear_error (&error);
 		goto out;
 	}
@@ -597,13 +597,13 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 			if (!success) {
 				_LOGE (LOGD_DEVICE | LOGD_ETHER,
 				       "Activation: (ethernet) couldn't send security configuration to the supplicant: %s",
-				       error ? error->message : "<BUG>");
+				       error->message);
 				g_clear_error (&error);
 			}
 		} else {
 			_LOGE (LOGD_DEVICE | LOGD_ETHER,
 			       "Activation: (ethernet) couldn't build security configuration: %s",
-			       error ? error->message : "<BUG>");
+			       error->message);
 			g_clear_error (&error);
 		}
 
@@ -721,7 +721,7 @@ supplicant_connection_timeout_cb (gpointer user_data)
 	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
 	NMDevice *device = NM_DEVICE (self);
 	NMActRequest *req;
-	NMConnection *connection;
+	NMSettingsConnection *connection;
 	guint64 timestamp = 0;
 	gboolean new_secrets = TRUE;
 
@@ -737,14 +737,14 @@ supplicant_connection_timeout_cb (gpointer user_data)
 	req = nm_device_get_act_request (device);
 	g_assert (req);
 
-	connection = nm_act_request_get_applied_connection (req);
+	connection = nm_act_request_get_settings_connection (req);
 	g_assert (connection);
 
 	/* Ask for new secrets only if we've never activated this connection
 	 * before.  If we've connected before, don't bother the user with dialogs,
 	 * just retry or fail, and if we never connect the user can fix the
 	 * password somewhere else. */
-	if (nm_settings_connection_get_timestamp (NM_SETTINGS_CONNECTION (connection), &timestamp))
+	if (nm_settings_connection_get_timestamp (connection, &timestamp))
 		new_secrets = !timestamp;
 
 	if (handle_auth_or_fail (self, req, new_secrets) == NM_ACT_STAGE_RETURN_POSTPONE)
@@ -1187,7 +1187,7 @@ wake_on_lan_enable (NMDevice *device)
 	}
 	wol = NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE;
 found:
-	return nmp_utils_ethtool_set_wake_on_lan (nm_device_get_iface (device), wol, password);
+	return nm_platform_ethtool_set_wake_on_lan (NM_PLATFORM_GET, nm_device_get_iface (device), wol, password);
 }
 
 /****************************************************************/
@@ -1388,7 +1388,8 @@ complete_connection (NMDevice *device,
 	/* Default to an ethernet-only connection, but if a PPPoE setting was given
 	 * then PPPoE should be our connection type.
 	 */
-	nm_utils_complete_generic (connection,
+	nm_utils_complete_generic (NM_PLATFORM_GET,
+	                           connection,
 	                           s_pppoe ? NM_SETTING_PPPOE_SETTING_NAME : NM_SETTING_WIRED_SETTING_NAME,
 	                           existing_connections,
 	                           NULL,
@@ -1498,6 +1499,11 @@ update_connection (NMDevice *device, NMConnection *connection)
 		nm_connection_add_setting (connection, (NMSetting *) s_wired);
 	}
 
+	g_object_set (nm_connection_get_setting_connection (connection),
+	              NM_SETTING_CONNECTION_TYPE, nm_connection_get_setting_pppoe (connection)
+	                                          ? NM_SETTING_PPPOE_SETTING_NAME
+	                                          : NM_SETTING_WIRED_SETTING_NAME, NULL);
+
 	/* If the device reports a permanent address, use that for the MAC address
 	 * and the current MAC, if different, is the cloned MAC.
 	 */
@@ -1533,7 +1539,7 @@ get_link_speed (NMDevice *device)
 	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
 	guint32 speed;
 
-	if (!nmp_utils_ethtool_get_link_speed (nm_device_get_iface (device), &speed))
+	if (!nm_platform_ethtool_get_link_speed (NM_PLATFORM_GET, nm_device_get_iface (device), &speed))
 		return;
 	if (priv->speed == speed)
 		return;
diff --git a/src/devices/nm-device-factory.c b/src/devices/nm-device-factory.c
index bd0ec002..5ce4333f 100644
--- a/src/devices/nm-device-factory.c
+++ b/src/devices/nm-device-factory.c
@@ -355,7 +355,7 @@ read_device_factory_paths (void)
 	if (!dir) {
 		nm_log_warn (LOGD_HW, "device plugin: failed to open directory %s: %s",
 		             NMPLUGINDIR,
-		             (error && error->message) ? error->message : "(unknown)");
+		             error->message);
 		g_clear_error (&error);
 		return NULL;
 	}
@@ -499,7 +499,7 @@ nm_device_factory_manager_load_factories (NMDeviceFactoryManagerFactoryFunc call
 		factory = create_func (&error);
 		if (!factory) {
 			nm_log_warn (LOGD_HW, "(%s): failed to initialize device factory: %s",
-			             item, error ? error->message : "unknown");
+			             item, NM_G_ERROR_MSG (error));
 			g_clear_error (&error);
 			g_module_close (plugin);
 			continue;
diff --git a/src/devices/nm-device-infiniband.c b/src/devices/nm-device-infiniband.c
index 79bcb57c..a27af314 100644
--- a/src/devices/nm-device-infiniband.c
+++ b/src/devices/nm-device-infiniband.c
@@ -85,7 +85,7 @@ act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *reason)
 	transport_mode = nm_setting_infiniband_get_transport_mode (s_infiniband);
 
 	mode_path = g_strdup_printf ("/sys/class/net/%s/mode",
-	                             ASSERT_VALID_PATH_COMPONENT (nm_device_get_iface (dev)));
+	                             NM_ASSERT_VALID_PATH_COMPONENT (nm_device_get_iface (dev)));
 	if (!g_file_test (mode_path, G_FILE_TEST_EXISTS)) {
 		g_free (mode_path);
 
@@ -166,7 +166,8 @@ complete_connection (NMDevice *device,
 	const char *setting_mac;
 	const char *hw_address;
 
-	nm_utils_complete_generic (connection,
+	nm_utils_complete_generic (NM_PLATFORM_GET,
+	                           connection,
 	                           NM_SETTING_INFINIBAND_SETTING_NAME,
 	                           existing_connections,
 	                           NULL,
diff --git a/src/devices/nm-device-ip-tunnel.c b/src/devices/nm-device-ip-tunnel.c
index 0ba813eb..089b1035 100644
--- a/src/devices/nm-device-ip-tunnel.c
+++ b/src/devices/nm-device-ip-tunnel.c
@@ -338,7 +338,8 @@ complete_connection (NMDevice *device,
 {
 	NMSettingIPTunnel *s_ip_tunnel;
 
-	nm_utils_complete_generic (connection,
+	nm_utils_complete_generic (NM_PLATFORM_GET,
+	                           connection,
 	                           NM_SETTING_IP_TUNNEL_SETTING_NAME,
 	                           existing_connections,
 	                           NULL,
@@ -788,6 +789,12 @@ ip4_config_pre_commit (NMDevice *device, NMIP4Config *config)
 		nm_ip4_config_set_mtu (config, mtu, NM_IP_CONFIG_SOURCE_USER);
 }
 
+static NMDeviceCapabilities
+get_generic_capabilities (NMDevice *dev)
+{
+	return NM_DEVICE_CAP_IS_SOFTWARE;
+}
+
 static void
 unrealize_notify (NMDevice *device)
 {
@@ -876,6 +883,7 @@ nm_device_ip_tunnel_class_init (NMDeviceIPTunnelClass *klass)
 	device_class->update_connection = update_connection;
 	device_class->check_connection_compatible = check_connection_compatible;
 	device_class->create_and_realize = create_and_realize;
+	device_class->get_generic_capabilities = get_generic_capabilities;
 	device_class->ip4_config_pre_commit = ip4_config_pre_commit;
 	device_class->realize_start_notify = realize_start_notify;
 	device_class->unrealize_notify = unrealize_notify;
diff --git a/src/devices/nm-device-logging.h b/src/devices/nm-device-logging.h
index 6255d6f3..1be17073 100644
--- a/src/devices/nm-device-logging.h
+++ b/src/devices/nm-device-logging.h
@@ -34,9 +34,9 @@ _nm_device_log_self_to_device (t *self) \
 #undef  _NMLOG_ENABLED
 #define _NMLOG_ENABLED(level, domain) ( nm_logging_enabled ((level), (domain)) )
 #define _NMLOG(level, domain, ...) \
-    nm_log_obj ((level), (domain), (self), \
+    nm_log_obj ((level), (domain), (self), "device", \
                 "(%s): " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
-                (self) ? str_if_set (nm_device_get_iface (_nm_device_log_self_to_device (self)), "(null)") : "(none)" \
+                (self) ? (nm_device_get_iface (_nm_device_log_self_to_device (self)) ?: "(null)") : "(none)" \
                 _NM_UTILS_MACRO_REST(__VA_ARGS__))
 
 #endif /* __NETWORKMANAGER_DEVICE_LOGGING_H__ */
diff --git a/src/devices/nm-device-macvlan.c b/src/devices/nm-device-macvlan.c
index 2dc9d4f6..cd02cb8e 100644
--- a/src/devices/nm-device-macvlan.c
+++ b/src/devices/nm-device-macvlan.c
@@ -420,7 +420,8 @@ complete_connection (NMDevice *device,
 {
 	NMSettingMacvlan *s_macvlan;
 
-	nm_utils_complete_generic (connection,
+	nm_utils_complete_generic (NM_PLATFORM_GET,
+	                           connection,
 	                           NM_SETTING_MACVLAN_SETTING_NAME,
 	                           existing_connections,
 	                           NULL,
diff --git a/src/devices/nm-device-tun.c b/src/devices/nm-device-tun.c
index a8ae3edc..eac2fb82 100644
--- a/src/devices/nm-device-tun.c
+++ b/src/devices/nm-device-tun.c
@@ -121,7 +121,8 @@ complete_connection (NMDevice *device,
 {
 	NMSettingTun *s_tun;
 
-	nm_utils_complete_generic (connection,
+	nm_utils_complete_generic (NM_PLATFORM_GET,
+	                           connection,
 	                           NM_SETTING_TUN_SETTING_NAME,
 	                           existing_connections,
 	                           NULL,
diff --git a/src/devices/nm-device-vlan.c b/src/devices/nm-device-vlan.c
index e6e3708b..4d606e9d 100644
--- a/src/devices/nm-device-vlan.c
+++ b/src/devices/nm-device-vlan.c
@@ -439,7 +439,8 @@ complete_connection (NMDevice *device,
 {
 	NMSettingVlan *s_vlan;
 
-	nm_utils_complete_generic (connection,
+	nm_utils_complete_generic (NM_PLATFORM_GET,
+	                           connection,
 	                           NM_SETTING_VLAN_SETTING_NAME,
 	                           existing_connections,
 	                           NULL,
diff --git a/src/devices/nm-device-vxlan.c b/src/devices/nm-device-vxlan.c
index 79942458..d308d47a 100644
--- a/src/devices/nm-device-vxlan.c
+++ b/src/devices/nm-device-vxlan.c
@@ -365,7 +365,8 @@ complete_connection (NMDevice *device,
 {
 	NMSettingVxlan *s_vxlan;
 
-	nm_utils_complete_generic (connection,
+	nm_utils_complete_generic (NM_PLATFORM_GET,
+	                           connection,
 	                           NM_SETTING_VXLAN_SETTING_NAME,
 	                           existing_connections,
 	                           NULL,
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index cdd3f7cb..529b3cd8 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -70,13 +70,6 @@ _LOG_DECLARE_SELF (NMDevice);
 
 #include "nmdbus-device.h"
 
-static void ip_check_ping_watch_cb (GPid pid, gint status, gpointer user_data);
-static gboolean ip_config_valid (NMDeviceState state);
-static NMActStageReturn dhcp4_start (NMDevice *self, NMConnection *connection, NMDeviceStateReason *reason);
-static gboolean dhcp6_start (NMDevice *self, gboolean wait_for_ll, NMDeviceStateReason *reason);
-static void nm_device_start_ip_check (NMDevice *self);
-static void realize_start_setup (NMDevice *self, const NMPlatformLink *plink);
-
 G_DEFINE_ABSTRACT_TYPE (NMDevice, nm_device, NM_TYPE_EXPORTED_OBJECT)
 
 #define NM_DEVICE_GET_PRIVATE(o) ((o)->priv)
@@ -414,6 +407,12 @@ static void _set_state_full (NMDevice *self,
 
 static gboolean queued_ip4_config_change (gpointer user_data);
 static gboolean queued_ip6_config_change (gpointer user_data);
+static void ip_check_ping_watch_cb (GPid pid, gint status, gpointer user_data);
+static gboolean ip_config_valid (NMDeviceState state);
+static NMActStageReturn dhcp4_start (NMDevice *self, NMConnection *connection, NMDeviceStateReason *reason);
+static gboolean dhcp6_start (NMDevice *self, gboolean wait_for_ll, NMDeviceStateReason *reason);
+static void nm_device_start_ip_check (NMDevice *self);
+static void realize_start_setup (NMDevice *self, const NMPlatformLink *plink);
 
 /***********************************************************/
 
@@ -1236,8 +1235,7 @@ update_dynamic_ip_setup (NMDevice *self)
 		nm_lldp_listener_stop (priv->lldp_listener);
 		addr = nm_platform_link_get_address (NM_PLATFORM_GET, priv->ifindex, &addr_length);
 
-		if (!nm_lldp_listener_start (priv->lldp_listener, nm_device_get_ifindex (self),
-		                             nm_device_get_iface (self), addr, addr_length, &error)) {
+		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",
 			       priv->lldp_listener, error->message);
 			g_clear_error (&error);
@@ -2883,7 +2881,7 @@ nm_device_generate_connection (NMDevice *self, NMDevice *master)
 		                                               &error))
 		{
 			_LOGE (LOGD_DEVICE, "master device '%s' failed to update slave connection: %s",
-			       nm_device_get_iface (master), error ? error->message : "(unknown error)");
+			       nm_device_get_iface (master), error->message);
 			g_error_free (error);
 			g_object_unref (connection);
 			return NULL;
@@ -3164,7 +3162,7 @@ recheck_available (gpointer user_data)
 	}
 
 	if (new_state > NM_DEVICE_STATE_UNKNOWN) {
-		_LOGD (LOGD_DEVICE, "device is %savailable, %s %s",
+		_LOGD (LOGD_DEVICE, "is %savailable, %s %s",
 			   now_available ? "" : "not ",
 			   new_state == NM_DEVICE_STATE_UNAVAILABLE ? "no change required for" : "will transition to",
 			   state_to_string (new_state == NM_DEVICE_STATE_UNAVAILABLE ? state : new_state));
@@ -3565,8 +3563,7 @@ activate_stage2_device_config (NMDevice *self)
 
 		addr = nm_platform_link_get_address (NM_PLATFORM_GET, priv->ifindex, &addr_length);
 
-		if (nm_lldp_listener_start (priv->lldp_listener, nm_device_get_ifindex (self),
-		                            nm_device_get_iface (self), addr, addr_length, &error))
+		if (nm_lldp_listener_start (priv->lldp_listener, nm_device_get_ifindex (self), &error))
 			_LOGD (LOGD_DEVICE, "LLDP listener %p started", priv->lldp_listener);
 		else {
 			_LOGD (LOGD_DEVICE, "LLDP listener %p could not be started: %s",
@@ -4675,10 +4672,7 @@ reserve_shared_ip (NMDevice *self, NMSettingIPConfig *s_ip4, NMPlatformIP4Addres
 			}
 		}
 		nm_platform_ip4_address_set_addr (address, start + count, 24);
-
-		g_hash_table_insert (shared_ips,
-		                     GUINT_TO_POINTER (address->address),
-		                     GUINT_TO_POINTER (TRUE));
+		g_hash_table_add (shared_ips, GUINT_TO_POINTER (address->address));
 	}
 
 	return TRUE;
@@ -5852,7 +5846,8 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
 	s_ip6 = NM_SETTING_IP6_CONFIG (nm_connection_get_setting_ip6_config (connection));
 	g_assert (s_ip6);
 
-	priv->rdisc = nm_lndp_rdisc_new (nm_device_get_ip_ifindex (self),
+	priv->rdisc = nm_lndp_rdisc_new (NM_PLATFORM_GET,
+	                                 nm_device_get_ip_ifindex (self),
 	                                 nm_device_get_ip_iface (self),
 	                                 nm_connection_get_uuid (connection),
 	                                 nm_setting_ip6_config_get_addr_gen_mode (s_ip6),
@@ -6631,7 +6626,7 @@ start_sharing (NMDevice *self, NMIP4Config *config)
 
 	if (!nm_dnsmasq_manager_start (priv->dnsmasq_manager, config, &error)) {
 		_LOGE (LOGD_SHARING, "share: (%s) failed to start dnsmasq: %s",
-		       ip_iface, (error && error->message) ? error->message : "(unknown)");
+		       ip_iface, error->message);
 		g_error_free (error);
 		nm_act_request_set_shared (req, FALSE);
 		return FALSE;
@@ -7145,6 +7140,7 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 
 	if (priv->ip4_state != IP_NONE) {
 		g_clear_object (&priv->con_ip4_config);
+		g_clear_object (&priv->ext_ip4_config);
 		priv->con_ip4_config = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
 		nm_ip4_config_merge_setting (priv->con_ip4_config,
 		                             s_ip4_new,
@@ -7175,6 +7171,7 @@ nm_device_reactivate_ip6_config (NMDevice *self,
 
 	if (priv->ip6_state != IP_NONE) {
 		g_clear_object (&priv->con_ip6_config);
+		g_clear_object (&priv->ext_ip6_config);
 		priv->con_ip6_config = nm_ip6_config_new (nm_device_get_ip_ifindex (self));
 		nm_ip6_config_merge_setting (priv->con_ip6_config,
 		                             s_ip6_new,
@@ -7374,7 +7371,10 @@ impl_device_reapply (NMDevice *self,
 
 	if (settings && g_variant_n_children (settings)) {
 		/* New settings specified inline. */
-		connection = nm_simple_connection_new_from_dbus (settings, &error);
+		connection = _nm_simple_connection_new_from_dbus (settings,
+		                                                    NM_SETTING_PARSE_FLAGS_STRICT
+		                                                  | NM_SETTING_PARSE_FLAGS_NORMALIZE,
+		                                                  &error);
 		if (!connection) {
 			g_prefix_error (&error, "The settings specified are invalid: ");
 			nm_audit_log_device_op (NM_AUDIT_OP_DEVICE_REAPPLY, self, FALSE, context, error->message);
@@ -7740,7 +7740,7 @@ nm_device_queue_activation (NMDevice *self, NMActRequest *req)
 
 	must_queue = _carrier_wait_check_act_request_must_queue (self, req);
 
-	if (!priv->act_request && !must_queue) {
+	if (!priv->act_request && !must_queue && nm_device_is_real (self)) {
 		/* Just activate immediately */
 		if (!_device_activate (self, req))
 			g_assert_not_reached ();
@@ -7926,9 +7926,10 @@ _replace_vpn_config_in_list (GSList **plist, GObject *old, GObject *new)
 {
 	GSList *old_link;
 
-	/* Below, assert that we have an @old instance to replace and that
-	 * @new is not yet tracked. But still, behave correctly in any
-	 * case. */
+	/* Below, assert that @new is not yet tracked, but still behave
+	 * correctly in any case. Don't complain for missing @old since
+	 * it could have been removed when the parent device became
+	 * unmanaged. */
 
 	if (   old
 	    && (old_link = g_slist_find (*plist, old))) {
@@ -7936,7 +7937,7 @@ _replace_vpn_config_in_list (GSList **plist, GObject *old, GObject *new)
 			if (new)
 				old_link->data = g_object_ref (new);
 			else
-				*plist = g_slist_remove_link (*plist, old_link);
+				*plist = g_slist_delete_link (*plist, old_link);
 			g_object_unref (old);
 		}
 		return TRUE;
@@ -7947,12 +7948,9 @@ _replace_vpn_config_in_list (GSList **plist, GObject *old, GObject *new)
 			*plist = g_slist_append (*plist, g_object_ref (new));
 		else
 			g_return_val_if_reached (TRUE);
-		g_return_val_if_fail (!old, TRUE);
 		return TRUE;
 	}
 
-	/* return FALSE if both @old and @new are unset. */
-	g_return_val_if_fail (!old, FALSE);
 	return FALSE;
 }
 
@@ -8434,7 +8432,7 @@ nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware)
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
-	_LOGD (LOGD_HW, "bringing up device.");
+	_LOGD (LOGD_HW, "bringing up device");
 
 	if (NM_DEVICE_GET_CLASS (self)->bring_up) {
 		if (!NM_DEVICE_GET_CLASS (self)->bring_up (self, no_firmware))
@@ -8508,7 +8506,7 @@ nm_device_take_down (NMDevice *self, gboolean block)
 
 	g_return_if_fail (NM_IS_DEVICE (self));
 
-	_LOGD (LOGD_HW, "taking down device.");
+	_LOGD (LOGD_HW, "taking down device");
 
 	if (NM_DEVICE_GET_CLASS (self)->take_down) {
 		if (!NM_DEVICE_GET_CLASS (self)->take_down (self))
@@ -9668,14 +9666,13 @@ nm_device_recheck_available_connections (NMDevice *self)
 			connection = NM_CONNECTION (iter->data);
 
 			if (nm_device_check_connection_available (self,
-				                                  connection,
-				                                  NM_DEVICE_CHECK_CON_AVAILABLE_NONE,
-				                                  NULL)) {
+			                                          connection,
+			                                          NM_DEVICE_CHECK_CON_AVAILABLE_NONE,
+			                                          NULL)) {
 				if (available_connections_add (self, connection))
 					changed = TRUE;
-			} else {
-				if (prune_list && g_hash_table_remove (prune_list, connection))
-					changed = TRUE;
+				if (prune_list)
+					g_hash_table_remove (prune_list, connection);
 			}
 		}
 
@@ -9697,39 +9694,55 @@ nm_device_recheck_available_connections (NMDevice *self)
 }
 
 /**
- * nm_device_get_available_connections:
+ * nm_device_get_best_connection:
  * @self: the #NMDevice
  * @specific_object: a specific object path if any
+ * @error: reason why no connection was returned
  *
- * Returns a list of connections available to activate on the device, taking
- * into account any device-specific details given by @specific_object (like
- * WiFi access point path).
+ * Returns a connection that's most suitable for user-initiated activation
+ * of a device, optionally with a given specific object.
  *
- * Returns: caller-owned #GPtrArray of #NMConnections
+ * Returns: the #NMSettingsConnection or %NULL (setting an @error)
  */
-GPtrArray *
-nm_device_get_available_connections (NMDevice *self, const char *specific_object)
+NMSettingsConnection *
+nm_device_get_best_connection (NMDevice *self,
+                               const char *specific_object,
+                               GError **error)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMSettingsConnection *connection = NULL;
+	NMSettingsConnection *candidate;
+	guint64 best_timestamp = 0;
 	GHashTableIter iter;
-	guint num_available;
-	NMConnection *connection = NULL;
-	GPtrArray *array = NULL;
 
-	num_available = g_hash_table_size (priv->available_connections);
-	if (num_available > 0) {
-		array = g_ptr_array_sized_new (num_available);
-		g_hash_table_iter_init (&iter, priv->available_connections);
-		while (g_hash_table_iter_next (&iter, (gpointer) &connection, NULL)) {
-			/* If a specific object is given, only include connections that are
-			 * compatible with it.
-			 */
-			if (   !specific_object /* << Optimization: we know that the connection is available without @specific_object.  */
-			    || nm_device_check_connection_available (self, connection, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST, specific_object))
-				g_ptr_array_add (array, connection);
+	g_hash_table_iter_init (&iter, priv->available_connections);
+	while (g_hash_table_iter_next (&iter, (gpointer) &candidate, NULL)) {
+		guint64 candidate_timestamp = 0;
+
+		/* If a specific object is given, only include connections that are
+		 * compatible with it.
+		 */
+		if (    specific_object /* << Optimization: we know that the connection is available without @specific_object.  */
+		    && !nm_device_check_connection_available (self,
+		                                              NM_CONNECTION (candidate),
+		                                              _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST,
+		                                              specific_object))
+			continue;
+
+		nm_settings_connection_get_timestamp (candidate, &candidate_timestamp);
+		if (!connection || (candidate_timestamp > best_timestamp)) {
+			connection = candidate;
+			best_timestamp = candidate_timestamp;
 		}
 	}
-	return array;
+
+	if (!connection) {
+		g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_CONNECTION,
+		             "The device '%s' has no connections available for activation.",
+		              nm_device_get_iface (self));
+	}
+
+	return connection;
 }
 
 static void
@@ -9838,12 +9851,15 @@ nm_device_add_pending_action (NMDevice *self, const char *action, gboolean asser
 gboolean
 nm_device_remove_pending_action (NMDevice *self, const char *action, gboolean assert_is_pending)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDevicePrivate *priv;
 	GSList *iter, *next;
 	guint count = 0;
 
+	g_return_val_if_fail (self, FALSE);
 	g_return_val_if_fail (action, FALSE);
 
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
 	for (iter = priv->pending_actions; iter; iter = next) {
 		next = iter->next;
 		if (!strcmp (action, iter->data)) {
@@ -10352,7 +10368,7 @@ _set_state_full (NMDevice *self,
 	if (   (priv->state == state)
 	    && (   state != NM_DEVICE_STATE_UNAVAILABLE
 	        || !priv->firmware_missing)) {
-		_LOGD (LOGD_DEVICE, "device state change: %s -> %s (reason '%s') [%d %d %d]%s",
+		_LOGD (LOGD_DEVICE, "state change: %s -> %s (reason '%s') [%d %d %d]%s",
 		       state_to_string (old_state),
 		       state_to_string (state),
 		       reason_to_string (reason),
@@ -10363,7 +10379,7 @@ _set_state_full (NMDevice *self,
 		return;
 	}
 
-	_LOGI (LOGD_DEVICE, "device state change: %s -> %s (reason '%s') [%d %d %d]",
+	_LOGI (LOGD_DEVICE, "state change: %s -> %s (reason '%s') [%d %d %d]",
 	       state_to_string (old_state),
 	       state_to_string (state),
 	       reason_to_string (reason),
@@ -10389,7 +10405,8 @@ _set_state_full (NMDevice *self,
 	if (state <= NM_DEVICE_STATE_UNAVAILABLE) {
 		if (available_connections_del_all (self))
 			available_connections_notify (self);
-		_clear_queued_act_request (priv);
+		if (old_state > NM_DEVICE_STATE_UNAVAILABLE)
+			_clear_queued_act_request (priv);
 	}
 
 	/* Update the available connections list when a device first becomes available */
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index 9b9edda4..bd657e68 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -529,8 +529,9 @@ gboolean nm_device_add_pending_action    (NMDevice *device, const char *action,
 gboolean nm_device_remove_pending_action (NMDevice *device, const char *action, gboolean assert_is_pending);
 gboolean nm_device_has_pending_action    (NMDevice *device);
 
-GPtrArray *nm_device_get_available_connections (NMDevice *device,
-                                                const char *specific_object);
+NMSettingsConnection *nm_device_get_best_connection (NMDevice *device,
+                                                     const char *specific_object,
+                                                     GError **error);
 
 gboolean   nm_device_check_connection_available (NMDevice *device,
                                                  NMConnection *connection,
diff --git a/src/devices/nm-lldp-listener.c b/src/devices/nm-lldp-listener.c
index 81827817..e678dc67 100644
--- a/src/devices/nm-lldp-listener.c
+++ b/src/devices/nm-lldp-listener.c
@@ -20,33 +20,71 @@
 
 #include "nm-default.h"
 
+#include "nm-lldp-listener.h"
+
 #include <net/ethernet.h>
+#include <errno.h>
 
-#include "sd-lldp.h"
-#include "lldp.h"
-#include "nm-lldp-listener.h"
 #include "nm-platform.h"
 #include "nm-utils.h"
 
+#include "sd-lldp.h"
+
+#include "nm-sd-adapt.h"
+#include "lldp.h"
+
 #define MAX_NEIGHBORS         4096
-#define MIN_UPDATE_INTERVAL   2
+#define MIN_UPDATE_INTERVAL_NS (2 * NM_UTILS_NS_PER_SECOND)
+
+#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,
+} 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_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_VID,
+	LLDP_ATTR_ID_IEEE_802_1_VLAN_NAME,
+	_LLDP_PROP_ID_COUNT,
+} LldpAttrId;
+
+typedef struct {
+	LldpAttrType attr_type;
+	union {
+		guint32 v_uint32;
+		char *v_string;
+	};
+} LldpAttrData;
 
 typedef struct {
 	char         *iface;
 	int           ifindex;
 	sd_lldp      *lldp_handle;
 	GHashTable   *lldp_neighbors;
-	guint         timer;
-	guint         num_pending_events;
+
+	/* the timestamp in nsec until which we delay updates. */
+	gint64        ratelimit_next;
+	guint         ratelimit_id;
+
 	GVariant     *variant;
 } NMLldpListenerPrivate;
 
-enum {
-	PROP_0,
+NM_GOBJECT_PROPERTIES_DEFINE (NMLldpListener,
 	PROP_NEIGHBORS,
-
-	LAST_PROP
-};
+);
 
 G_DEFINE_TYPE (NMLldpListener, nm_lldp_listener, G_TYPE_OBJECT)
 
@@ -58,12 +96,14 @@ typedef struct {
 	char *chassis_id;
 	char *port_id;
 
-	int dest;
+	struct ether_addr destination_address;
+
+	bool valid:1;
 
-	GHashTable *tlvs;
-} LLDPNeighbor;
+	LldpAttrData attrs[_LLDP_PROP_ID_COUNT];
 
-static void process_lldp_neighbors (NMLldpListener *self);
+	GVariant *variant;
+} LldpNeighbor;
 
 /*****************************************************************************/
 
@@ -89,43 +129,139 @@ static void process_lldp_neighbors (NMLldpListener *self);
         } \
     } 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;
+}
+
+static guint32
+_access_uint8 (const void *data)
+{
+	return *((const guint8 *) data);
+}
+
+static guint32
+_access_uint16 (const void *data)
+{
+	guint16 v;
+
+	memcpy (&v, data, sizeof (v));
+	return ntohs (v);
+}
+
+/*****************************************************************************/
+
+NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_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_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_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_ITEM_IGNORE (_LLDP_PROP_ID_COUNT),
+);
+
+_NM_UTILS_LOOKUP_DEFINE (static, _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_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_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_IGNORE (_LLDP_PROP_ID_COUNT),
+);
+
 static void
-gvalue_destroy (gpointer data)
+_lldp_attr_set_str (LldpAttrData *pdata, LldpAttrId attr_id, const char *v_string)
 {
-	GValue *value = (GValue *) data;
+	nm_assert (pdata);
+	nm_assert (_lldp_attr_id_to_type (attr_id) == LLDP_ATTR_TYPE_STRING);
+
+	pdata = &pdata[attr_id];
 
-	g_value_unset (value);
-	g_slice_free (GValue, value);
+	/* 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 GValue *
-gvalue_new_nstr (const char *str, guint16 len)
+static void
+_lldp_attr_set_str_ptr (LldpAttrData *pdata, LldpAttrId attr_id, const void *str, gsize len)
 {
-	GValue *value;
+	const char *s = str;
+	const char *tmp;
+	gsize len0 = len;
+	gs_free char *str_free = NULL;
+
+	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;
 
-	value = g_slice_new0 (GValue);
-	g_value_init (value, G_TYPE_STRING);
-	g_value_take_string (value, strndup (str, len));
-	return value;
+	/* truncate at first NUL, including removing trailing NULs*/
+	tmp = memchr (s, '\0', len);
+	if (tmp)
+		len = tmp - s;
+
+	if (!len) {
+		pdata->v_string = g_strdup ("");
+		return;
+	}
+
+	if (len0 <= len || s[len] != '\0') {
+		/* hmpf, g_strescape needs a trailing NUL. Need to clone */
+		s = str_free = g_strndup (s, len);
+	}
+
+	pdata->v_string = g_strescape (s, NULL);
 }
 
-static GValue *
-gvalue_new_uint (guint val)
+static void
+_lldp_attr_set_uint32 (LldpAttrData *pdata, LldpAttrId attr_id, guint32 v_uint32)
 {
-	GValue *value;
+	nm_assert (pdata);
+	nm_assert (_lldp_attr_id_to_type (attr_id) == LLDP_ATTR_TYPE_UINT32);
+
+	pdata = &pdata[attr_id];
 
-	value = g_slice_new0 (GValue);
-	g_value_init (value, G_TYPE_UINT);
-	g_value_set_uint (value, val);
-	return value;
+	/* 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 guint
 lldp_neighbor_id_hash (gconstpointer ptr)
 {
-	const LLDPNeighbor *neigh = ptr;
+	const LldpNeighbor *neigh = ptr;
 	guint hash;
 
 	hash =   23423423u  + ((guint) (neigh->chassis_id ? g_str_hash (neigh->chassis_id) : 12321u));
@@ -135,303 +271,453 @@ lldp_neighbor_id_hash (gconstpointer ptr)
 	return hash;
 }
 
+static int
+lldp_neighbor_id_cmp (gconstpointer a, gconstpointer b)
+{
+	const LldpNeighbor *x = a, *y = b;
+	int c;
+
+	if (x->chassis_id_type != y->chassis_id_type)
+		return x->chassis_id_type < y->chassis_id_type ? -1 : 1;
+	if (x->port_id_type != y->port_id_type)
+		return x->port_id_type < y->port_id_type ? -1 : 1;
+	c = g_strcmp0 (x->chassis_id, y->chassis_id);
+	if (c == 0)
+		c = g_strcmp0 (x->port_id, y->port_id);
+	return c < 0 ? -1 : (c > 0 ? 1 : 0);
+}
+
 static gboolean
 lldp_neighbor_id_equal (gconstpointer a, gconstpointer b)
 {
-	const LLDPNeighbor *x = a, *y = b;
-
-	return x->chassis_id_type == y->chassis_id_type &&
-	       x->port_id_type == y->port_id_type &&
-	       !g_strcmp0 (x->chassis_id, y->chassis_id) &&
-	       !g_strcmp0 (x->port_id, y->port_id);
+	return lldp_neighbor_id_cmp (a, b) == 0;
 }
 
 static void
-lldp_neighbor_free (LLDPNeighbor *neighbor)
+lldp_neighbor_free (LldpNeighbor *neighbor)
 {
+	LldpAttrId attr_id;
+
 	if (neighbor) {
 		g_free (neighbor->chassis_id);
 		g_free (neighbor->port_id);
-		g_hash_table_unref (neighbor->tlvs);
-		g_slice_free (LLDPNeighbor, neighbor);
+		for (attr_id = 0; attr_id < _LLDP_PROP_ID_COUNT; attr_id++) {
+			if (neighbor->attrs[attr_id].attr_type == LLDP_ATTR_TYPE_STRING)
+				g_free (neighbor->attrs[attr_id].v_string);
+		}
+		g_clear_pointer (&neighbor->variant, g_variant_unref);
+		g_slice_free (LldpNeighbor, neighbor);
 	}
 }
 
 static void
-lldp_neighbor_freep (LLDPNeighbor **ptr)
+lldp_neighbor_freep (LldpNeighbor **ptr)
 {
 	lldp_neighbor_free (*ptr);
 }
 
 static gboolean
-lldp_neighbor_equal (LLDPNeighbor *a, LLDPNeighbor *b)
+lldp_neighbor_equal (LldpNeighbor *a, LldpNeighbor *b)
 {
-	GHashTableIter iter;
-	gpointer k, v;
+	LldpAttrId attr_id;
 
-	g_return_val_if_fail (a && a->tlvs, FALSE);
-	g_return_val_if_fail (b && b->tlvs, FALSE);
+	nm_assert (a);
+	nm_assert (b);
 
 	if (   a->chassis_id_type != b->chassis_id_type
 	    || a->port_id_type != b->port_id_type
-	    || a->dest != b->dest
-	    || g_strcmp0 (a->chassis_id, b->chassis_id)
-	    || g_strcmp0 (a->port_id, b->port_id))
-		return FALSE;
-
-	if (g_hash_table_size (a->tlvs) != g_hash_table_size (b->tlvs))
+	    || 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;
 
-	g_hash_table_iter_init (&iter, a->tlvs);
-	while (g_hash_table_iter_next (&iter, &k, &v)) {
-		GValue *value_a, *value_b;
-
-		value_a = v;
-		value_b = g_hash_table_lookup (b->tlvs, k);
-
-		if (!value_b)
+	for (attr_id = 0; attr_id < _LLDP_PROP_ID_COUNT; attr_id++) {
+		if (a->attrs[attr_id].attr_type != b->attrs[attr_id].attr_type)
 			return FALSE;
-
-		g_return_val_if_fail (G_VALUE_TYPE (value_a) == G_VALUE_TYPE (value_b), FALSE);
-
-		if (G_VALUE_HOLDS_STRING (value_a)) {
-			if (g_strcmp0 (g_value_get_string (value_a), g_value_get_string (value_b)))
+		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;
-		} else if (G_VALUE_HOLDS_UINT (value_a)) {
-			if (g_value_get_uint (value_a) != g_value_get_uint (value_b))
+			break;
+		case LLDP_ATTR_TYPE_STRING:
+			if (!nm_streq (a->attrs[attr_id].v_string, b->attrs[attr_id].v_string))
 				return FALSE;
-		} else
-			g_return_val_if_reached (FALSE);
+			break;
+		default:
+			nm_assert (a->attrs[attr_id].attr_type == LLDP_ATTR_TYPE_NONE);
+			break;
+		}
 	}
 
 	return TRUE;
 }
 
-static gboolean
-lldp_hash_table_equal (GHashTable *a, GHashTable *b)
+static LldpNeighbor *
+lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
 {
-	GHashTableIter iter;
-	gpointer val;
-
-	g_return_val_if_fail (a, FALSE);
-	g_return_val_if_fail (b, FALSE);
+	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;
+
+	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", g_strerror (-r));
+		return NULL;
+	}
+	if (chassis_id_len < 1) {
+		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+		             "empty chassis-id");
+		return NULL;
+	}
 
-	if (g_hash_table_size (a) != g_hash_table_size (b))
-		return FALSE;
+	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", g_strerror (-r));
+		return NULL;
+	}
+	if (port_id_len < 1) {
+		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+		             "empty port-id");
+		return NULL;
+	}
 
-	g_hash_table_iter_init (&iter, a);
-	while (g_hash_table_iter_next (&iter, NULL, &val)) {
-		LLDPNeighbor *neigh_a, *neigh_b;
+	neigh = g_slice_new0 (LldpNeighbor);
+	neigh->chassis_id_type = chassis_id_type;
+	neigh->port_id_type = port_id_type;
 
-		neigh_a = val;
-		neigh_b = g_hash_table_lookup (b, val);
+	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", g_strerror (-r));
+		goto out;
+	}
 
-		if (!neigh_b)
-			return FALSE;
+	switch (chassis_id_type) {
+	case LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS:
+	case LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME:
+	case LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED:
+	case LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT:
+		neigh->chassis_id = g_strndup ((const char *) chassis_id, chassis_id_len);
+		break;
+	case LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS:
+		neigh->chassis_id = nm_utils_hwaddr_ntoa (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 (!lldp_neighbor_equal (neigh_a, neigh_b))
-			return FALSE;
+	switch (port_id_type) {
+	case LLDP_PORT_SUBTYPE_INTERFACE_ALIAS:
+	case LLDP_PORT_SUBTYPE_INTERFACE_NAME:
+	case LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED:
+	case LLDP_PORT_SUBTYPE_PORT_COMPONENT:
+		neigh->port_id = strndup ((char *) port_id, port_id_len);
+		break;
+	case LLDP_PORT_SUBTYPE_MAC_ADDRESS:
+		neigh->port_id = nm_utils_hwaddr_ntoa (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;
 	}
 
-	return TRUE;
-}
+	if (sd_lldp_neighbor_get_port_description (neighbor_sd, &str) == 0)
+		_lldp_attr_set_str (neigh->attrs, LLDP_ATTR_ID_PORT_DESCRIPTION, str);
 
-static gboolean
-lldp_timeout (gpointer user_data)
-{
-	NMLldpListener *self = user_data;
-	NMLldpListenerPrivate *priv;
+	if (sd_lldp_neighbor_get_system_name (neighbor_sd, &str) == 0)
+		_lldp_attr_set_str (neigh->attrs, LLDP_ATTR_ID_SYSTEM_NAME, str);
 
-	g_return_val_if_fail (NM_IS_LLDP_LISTENER (self), G_SOURCE_REMOVE);
+	if (sd_lldp_neighbor_get_system_description (neighbor_sd, &str) == 0)
+		_lldp_attr_set_str (neigh->attrs, LLDP_ATTR_ID_SYSTEM_DESCRIPTION, str);
 
-	priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
+	if (sd_lldp_neighbor_get_system_capabilities (neighbor_sd, &data16) == 0)
+		_lldp_attr_set_uint32 (neigh->attrs, LLDP_ATTR_ID_SYSTEM_CAPABILITIES, data16);
 
-	priv->timer = 0;
+	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", g_strerror (-r));
+		goto out;
+	}
+	do {
+		guint8 oui[3];
+		guint8 subtype;
+
+		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", g_strerror (-r));
+			goto out;
+		}
 
-	if (priv->num_pending_events)
-		process_lldp_neighbors (self);
+		if (!(   memcmp (oui, LLDP_OUI_802_1, sizeof (oui)) == 0
+		      && NM_IN_SET (subtype,
+		                    LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID,
+		                    LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID,
+		                    LLDP_OUI_802_1_SUBTYPE_VLAN_NAME)))
+			continue;
+
+		if (sd_lldp_neighbor_tlv_get_raw (neighbor_sd, (void *) &data8, &len) < 0)
+			continue;
+
+		/* skip over leading TLV, OUI and subtype */
+#ifdef WITH_MORE_ASSERTS
+		{
+			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, LLDP_OUI_802_1, sizeof (oui)) == 0)*/
+		{
+			switch (subtype) {
+			case 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,
+				                       _access_uint16 (data8));
+				break;
+			case 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,
+				                       _access_uint8 (&data8[0]));
+				_lldp_attr_set_uint32 (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_PPVID,
+				                       _access_uint16 (&data8[1]));
+				break;
+			case LLDP_OUI_802_1_SUBTYPE_VLAN_NAME: {
+				int l;
+
+				if (len <= 3)
+					continue;
+
+				l = data8[2];
+				if (len != 3 + l)
+					continue;
+
+				_lldp_attr_set_uint32 (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_VID,
+				                       _access_uint16 (&data8[0]));
+				_lldp_attr_set_str_ptr (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_VLAN_NAME,
+				                        &data8[3], len);
+				break;
+			}
+			default:
+				g_assert_not_reached ();
+			}
+		}
+	} while (sd_lldp_neighbor_tlv_next (neighbor_sd) > 0);
 
-	return G_SOURCE_REMOVE;
+	neigh->valid = TRUE;
+
+out:
+	return nm_unauto (&neigh);
 }
 
-static void
-process_lldp_neighbors (NMLldpListener *self)
+static GVariant *
+lldp_neighbor_to_variant (LldpNeighbor *neigh)
 {
-	NMLldpListenerPrivate *priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
-	nm_auto_free sd_lldp_packet **packets = NULL;
-	GHashTable *hash;
-	int num, i;
-
-	g_return_if_fail (priv->lldp_handle);
-
-	num = sd_lldp_get_packets (priv->lldp_handle, &packets);
-	if (num < 0) {
-		_LOGD ("process: error %d retrieving neighbor packets for %s",
-		        num, priv->iface);
-		return;
+	GVariantBuilder builder;
+	const char *dest_str;
+	LldpAttrId attr_id;
+
+	if (neigh->variant)
+		return neigh->variant;
+
+	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+
+	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));
 	}
 
-	hash = g_hash_table_new_full (lldp_neighbor_id_hash, lldp_neighbor_id_equal,
-	                              (GDestroyNotify) lldp_neighbor_free, NULL);
-
-	for (i = 0; packets && i < num; i++) {
-		nm_auto (lldp_neighbor_freep) LLDPNeighbor *neigh = NULL;
-		uint8_t chassis_id_type, port_id_type, *chassis_id, *port_id, data8;
-		uint16_t chassis_id_len, port_id_len, len, data16;
-		GValue *value;
-		char *str;
-		int r;
-
-		if (i >= MAX_NEIGHBORS)
-			goto next_packet;
-
-		r = sd_lldp_packet_read_chassis_id (packets[i], &chassis_id_type,
-		                                    &chassis_id, &chassis_id_len);
-		if (r < 0)
-			goto next_packet;
-
-		r = sd_lldp_packet_read_port_id (packets[i], &port_id_type,
-		                                 &port_id, &port_id_len);
-		if (r < 0)
-			goto next_packet;
-
-		neigh = g_slice_new0 (LLDPNeighbor);
-		neigh->tlvs = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, gvalue_destroy);
-		neigh->chassis_id_type = chassis_id_type;
-		neigh->port_id_type = port_id_type;
-		sd_lldp_packet_get_destination_type (packets[i], &neigh->dest);
-
-		if (chassis_id_len < 1)
-			goto next_packet;
-
-		switch (chassis_id_type) {
-		case LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS:
-		case LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME:
-		case LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED:
-		case LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT:
-			neigh->chassis_id = g_strndup ((const char *) chassis_id, chassis_id_len);
-			break;
-		case LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS:
-			neigh->chassis_id = nm_utils_hwaddr_ntoa (chassis_id, chassis_id_len);
-			break;
-		default:
-			_LOGD ("process: unsupported chassis ID type %d", chassis_id_type);
-			goto next_packet;
-		}
-
-		if (port_id_len < 1)
-			goto next_packet;
+	for (attr_id = 0; attr_id < _LLDP_PROP_ID_COUNT; attr_id++) {
+		const LldpAttrData *data = &neigh->attrs[attr_id];
 
-		switch (port_id_type) {
-		case LLDP_PORT_SUBTYPE_INTERFACE_ALIAS:
-		case LLDP_PORT_SUBTYPE_INTERFACE_NAME:
-		case LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED:
-		case LLDP_PORT_SUBTYPE_PORT_COMPONENT:
-			neigh->port_id = strndup ((char *) port_id, port_id_len);
+		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_PORT_SUBTYPE_MAC_ADDRESS:
-			neigh->port_id = nm_utils_hwaddr_ntoa (port_id, port_id_len);
+		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;
 		default:
-			_LOGD ("process: unsupported port ID type %d", port_id_type);
-			goto next_packet;
-		}
-
-		if (sd_lldp_packet_read_port_description (packets[i], &str, &len) == 0) {
-			value = gvalue_new_nstr (str, len);
-			g_hash_table_insert (neigh->tlvs, NM_LLDP_ATTR_PORT_DESCRIPTION, value);
-		}
-
-		if (sd_lldp_packet_read_system_name (packets[i], &str, &len) == 0) {
-			value = gvalue_new_nstr (str, len);
-			g_hash_table_insert (neigh->tlvs, NM_LLDP_ATTR_SYSTEM_NAME, value);
-		}
-
-		if (sd_lldp_packet_read_system_description (packets[i], &str, &len) == 0) {
-			value = gvalue_new_nstr (str, len);
-			g_hash_table_insert (neigh->tlvs, NM_LLDP_ATTR_SYSTEM_DESCRIPTION, value);
-		}
-
-		if (sd_lldp_packet_read_system_capability (packets[i], &data16) == 0) {
-			value = gvalue_new_uint (data16);
-			g_hash_table_insert (neigh->tlvs, NM_LLDP_ATTR_SYSTEM_CAPABILITIES, value);
-		}
-
-		if (sd_lldp_packet_read_port_vlan_id (packets[i], &data16) == 0) {
-			value = gvalue_new_uint (data16);
-			g_hash_table_insert (neigh->tlvs, NM_LLDP_ATTR_IEEE_802_1_PVID, value);
+			break;
 		}
+	}
 
-		if (sd_lldp_packet_read_port_protocol_vlan_id (packets[i], &data8, &data16) == 0) {
-			value = gvalue_new_uint (data16);
-			g_hash_table_insert (neigh->tlvs, NM_LLDP_ATTR_IEEE_802_1_PPVID, value);
+	return (neigh->variant = g_variant_ref_sink (g_variant_builder_end (&builder)));
+}
 
-			value = gvalue_new_uint (data8);
-			g_hash_table_insert (neigh->tlvs, NM_LLDP_ATTR_IEEE_802_1_PPVID_FLAGS, value);
-		}
+/*****************************************************************************/
 
-		if (sd_lldp_packet_read_vlan_name (packets[i], &data16, &str, &len) == 0) {
-			value = gvalue_new_uint (data16);
-			g_hash_table_insert (neigh->tlvs, NM_LLDP_ATTR_IEEE_802_1_VID, value);
+static void
+data_changed_notify (NMLldpListener *self, NMLldpListenerPrivate *priv)
+{
+	nm_clear_g_variant (&priv->variant);
+	_notify (self, PROP_NEIGHBORS);
+}
 
-			value = gvalue_new_nstr (str, len);
-			g_hash_table_insert (neigh->tlvs, NM_LLDP_ATTR_IEEE_802_1_VLAN_NAME, value);
-		}
+static gboolean
+data_changed_timeout (gpointer user_data)
+{
+	NMLldpListener *self = user_data;
+	NMLldpListenerPrivate *priv;
 
-		_LOGD ("process: new neigh: CHASSIS='%s' PORT='%s'",
-		        neigh->chassis_id, neigh->port_id);
+	g_return_val_if_fail (NM_IS_LLDP_LISTENER (self), G_SOURCE_REMOVE);
 
-		g_hash_table_add (hash, neigh);
-		neigh = NULL;
-next_packet:
-		sd_lldp_packet_unref (packets[i]);
-	}
+	priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
 
-	if (lldp_hash_table_equal (priv->lldp_neighbors, hash)) {
-		g_hash_table_destroy (hash);
-	} else {
-		g_hash_table_destroy (priv->lldp_neighbors);
-		priv->lldp_neighbors = hash;
-		nm_clear_g_variant (&priv->variant);
-		g_object_notify (G_OBJECT (self), NM_LLDP_LISTENER_NEIGHBORS);
-	}
+	priv->ratelimit_id = 0;
+	priv->ratelimit_next = nm_utils_get_monotonic_timestamp_ns() + MIN_UPDATE_INTERVAL_NS;
+	data_changed_notify (self, priv);
+	return G_SOURCE_REMOVE;
+}
 
-	/* Since the processing of the neighbor list is potentially
-	 * expensive when there are many neighbors, coalesce multiple
-	 * events arriving in short time.
-	 */
-	priv->timer = g_timeout_add_seconds (MIN_UPDATE_INTERVAL, lldp_timeout, self);
-	priv->num_pending_events = 0;
+static void
+data_changed_schedule (NMLldpListener *self)
+{
+	NMLldpListenerPrivate *priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
+	gint64 now;
+
+	now = nm_utils_get_monotonic_timestamp_ns ();
+	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_NS_TO_MSEC_CEIL (priv->ratelimit_next - now), data_changed_timeout, self);
 }
 
 static void
-lldp_event_handler (sd_lldp *lldp, int event, void *userdata)
+process_lldp_neighbor (NMLldpListener *self, sd_lldp_neighbor *neighbor_sd, gboolean neighbor_valid)
 {
-	NMLldpListener *self = userdata;
 	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));
 
 	priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
 
-	if (priv->timer > 0) {
-		priv->num_pending_events++;
+	g_return_if_fail (priv->lldp_handle);
+	g_return_if_fail (neighbor_sd);
+
+	p_parse_error = _LOGT_ENABLED () ? &parse_error : NULL;
+
+	neigh = lldp_neighbor_new (neighbor_sd, p_parse_error);
+	if (!neigh) {
+		_LOGT ("process: failed to parse neighbor: %s", parse_error->message);
+		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, ")", ""));
+
+			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);
+		return;
+	}
+
+	/* ensure that we have at most MAX_NEIGHBORS entires */
+	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);
 		return;
 	}
 
-	process_lldp_neighbors (self);
+	_LOGD ("process: %s neigh: "LOG_NEIGH_FMT,
+	        neigh_old ? "update" : "new",
+	        LOG_NEIGH_ARG (neigh));
+
+	changed = TRUE;
+	g_hash_table_add (priv->lldp_neighbors, nm_unauto (&neigh));
+
+done:
+	if (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);
 }
 
 gboolean
-nm_lldp_listener_start (NMLldpListener *self, int ifindex, const char *iface,
-                        const guint8 *mac, guint mac_len, GError **error)
+nm_lldp_listener_start (NMLldpListener *self, int ifindex, GError **error)
 {
 	NMLldpListenerPrivate *priv;
 	int ret;
 
 	g_return_val_if_fail (NM_IS_LLDP_LISTENER (self), FALSE);
 	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (iface, FALSE);
 	g_return_val_if_fail (!error || !*error, FALSE);
 
 	priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
@@ -442,43 +728,38 @@ nm_lldp_listener_start (NMLldpListener *self, int ifindex, const char *iface,
 		return FALSE;
 	}
 
-	if (!mac || mac_len != ETH_ALEN) {
+	ret = sd_lldp_new (&priv->lldp_handle, ifindex);
+	if (ret < 0) {
 		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
-		                     "unsupported device");
+		                     "initialization failed");
 		return FALSE;
 	}
 
-	ret = sd_lldp_new (ifindex, iface, (struct ether_addr *) mac, &priv->lldp_handle);
-	if (ret) {
+	ret = sd_lldp_set_callback (priv->lldp_handle, lldp_event_handler, self);
+	if (ret < 0) {
 		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
-		                     "initialization failed");
-		return FALSE;
+		                     "set callback failed");
+		goto err;
 	}
 
+	priv->ifindex = ifindex;
+
 	ret = sd_lldp_attach_event (priv->lldp_handle, NULL, 0);
-	if (ret) {
+	if (ret < 0) {
 		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
 		                     "attach event failed");
 		goto err_free;
 	}
 
-	ret = sd_lldp_set_callback (priv->lldp_handle, lldp_event_handler, self);
-	if (ret) {
-		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
-		                     "set callback failed");
-		goto err;
-	}
-
 	ret = sd_lldp_start (priv->lldp_handle);
-	if (ret) {
+	if (ret < 0) {
 		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
 		                     "start failed");
 		goto err;
 	}
 
-	priv->ifindex = ifindex;
-	priv->iface = strdup (iface);
 	_LOGD ("start");
+
 	return TRUE;
 
 err:
@@ -486,6 +767,7 @@ err:
 err_free:
 	sd_lldp_unref (priv->lldp_handle);
 	priv->lldp_handle = NULL;
+	priv->ifindex = 0;
 	return FALSE;
 }
 
@@ -494,6 +776,7 @@ nm_lldp_listener_stop (NMLldpListener *self)
 {
 	NMLldpListenerPrivate *priv;
 	guint size;
+	gboolean changed = FALSE;
 
 	g_return_if_fail (NM_IS_LLDP_LISTENER (self));
 	priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
@@ -503,19 +786,20 @@ nm_lldp_listener_stop (NMLldpListener *self)
 		sd_lldp_stop (priv->lldp_handle);
 		sd_lldp_detach_event (priv->lldp_handle);
 		sd_lldp_unref (priv->lldp_handle);
-		g_clear_pointer (&priv->iface, g_free);
 		priv->lldp_handle = NULL;
 
 		size = g_hash_table_size (priv->lldp_neighbors);
 		g_hash_table_remove_all (priv->lldp_neighbors);
-		if (size) {
-			nm_clear_g_variant (&priv->variant);
-			g_object_notify (G_OBJECT (self), NM_LLDP_LISTENER_NEIGHBORS);
-		}
+		if (size || priv->ratelimit_id)
+			changed = TRUE;
 	}
 
-	nm_clear_g_source (&priv->timer);
+	nm_clear_g_source (&priv->ratelimit_id);
+	priv->ratelimit_next = 0;
 	priv->ifindex = 0;
+
+	if (changed)
+		data_changed_notify (self, priv);
 }
 
 gboolean
@@ -532,78 +816,23 @@ nm_lldp_listener_is_running (NMLldpListener *self)
 GVariant *
 nm_lldp_listener_get_neighbors (NMLldpListener *self)
 {
-	GVariantBuilder array_builder, neigh_builder;
-	GHashTableIter iter;
 	NMLldpListenerPrivate *priv;
-	LLDPNeighbor *neigh;
-	char *dest_str = NULL;
+	GVariantBuilder array_builder;
+	GList *neighbors, *iter;
 
-	priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
-
-	if (priv->variant)
-		goto out;
-
-	g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("aa{sv}"));
-	g_hash_table_iter_init (&iter, priv->lldp_neighbors);
-
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &neigh)) {
-		GHashTableIter val_iter;
-		gpointer key, val;
-
-		g_variant_builder_init (&neigh_builder, G_VARIANT_TYPE ("a{sv}"));
-
-		g_variant_builder_add (&neigh_builder, "{sv}",
-		                       NM_LLDP_ATTR_CHASSIS_ID_TYPE,
-		                       g_variant_new_uint32 (neigh->chassis_id_type));
-		g_variant_builder_add (&neigh_builder, "{sv}",
-		                       NM_LLDP_ATTR_CHASSIS_ID,
-		                       g_variant_new_string (neigh->chassis_id));
-		g_variant_builder_add (&neigh_builder, "{sv}",
-		                       NM_LLDP_ATTR_PORT_ID_TYPE,
-		                       g_variant_new_uint32 (neigh->port_id_type));
-		g_variant_builder_add (&neigh_builder, "{sv}",
-		                       NM_LLDP_ATTR_PORT_ID,
-		                       g_variant_new_string (neigh->port_id));
-
-		switch (neigh->dest) {
-		case SD_LLDP_DESTINATION_TYPE_NEAREST_BRIDGE:
-			dest_str = NM_LLDP_DEST_NEAREST_BRIDGE;
-			break;
-		case SD_LLDP_DESTINATION_TYPE_NEAREST_NON_TPMR_BRIDGE:
-			dest_str = NM_LLDP_DEST_NEAREST_NON_TPMR_BRIDGE;
-			break;
-		case SD_LLDP_DESTINATION_TYPE_NEAREST_CUSTOMER_BRIDGE:
-			dest_str = NM_LLDP_DEST_NEAREST_CUSTOMER_BRIDGE;
-			break;
-		}
-
-		if (dest_str) {
-			g_variant_builder_add (&neigh_builder, "{sv}",
-			                       NM_LLDP_ATTR_DESTINATION,
-			                       g_variant_new_string (dest_str));
-		}
+	g_return_val_if_fail (NM_IS_LLDP_LISTENER (self), FALSE);
 
-		g_hash_table_iter_init (&val_iter, neigh->tlvs);
-		while (g_hash_table_iter_next (&val_iter, &key, &val)) {
-			GValue *item = val;
-
-			if (G_VALUE_HOLDS_STRING (item)) {
-				g_variant_builder_add (&neigh_builder, "{sv}",
-				                       key,
-				                       g_variant_new_string (g_value_get_string (item)));
-			} else if (G_VALUE_HOLDS_UINT (item)) {
-				g_variant_builder_add (&neigh_builder, "{sv}",
-				                       key,
-				                       g_variant_new_uint32 (g_value_get_uint (item)));
-			}
-		}
+	priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
 
-		g_variant_builder_add (&array_builder, "a{sv}", &neigh_builder);
+	if (!priv->variant) {
+		g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("aa{sv}"));
+		neighbors = g_hash_table_get_keys (priv->lldp_neighbors);
+		neighbors = g_list_sort (neighbors, lldp_neighbor_id_cmp);
+		for (iter = neighbors; iter; iter = iter->next)
+			g_variant_builder_add_value (&array_builder, lldp_neighbor_to_variant (iter->data));
+		g_list_free (neighbors);
+		priv->variant = g_variant_ref_sink (g_variant_builder_end (&array_builder));
 	}
-
-	priv->variant = g_variant_ref_sink (g_variant_builder_end (&array_builder));
-
-out:
 	return priv->variant;
 }
 
@@ -676,12 +905,13 @@ nm_lldp_listener_class_init (NMLldpListenerClass *klass)
 	object_class->finalize = finalize;
 	object_class->get_property = get_property;
 
-	g_object_class_install_property
-		(object_class, PROP_NEIGHBORS,
-		 g_param_spec_variant (NM_LLDP_LISTENER_NEIGHBORS, "", "",
-		                       G_VARIANT_TYPE ("aa{sv}"),
-		                       NULL,
-		                       G_PARAM_READABLE |
-		                       G_PARAM_STATIC_STRINGS));
+	obj_properties[PROP_NEIGHBORS] =
+	    g_param_spec_variant (NM_LLDP_LISTENER_NEIGHBORS, "", "",
+	                          G_VARIANT_TYPE ("aa{sv}"),
+	                          NULL,
+	                          G_PARAM_READABLE |
+	                          G_PARAM_STATIC_STRINGS);
+
+	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 10c43e7c..df02fd63 100644
--- a/src/devices/nm-lldp-listener.h
+++ b/src/devices/nm-lldp-listener.h
@@ -42,8 +42,7 @@ typedef struct {
 
 GType nm_lldp_listener_get_type (void);
 NMLldpListener *nm_lldp_listener_new (void);
-gboolean nm_lldp_listener_start (NMLldpListener *self, int ifindex, const char *iface,
-                                 const guint8 *mac, guint mac_len, GError **error);
+gboolean nm_lldp_listener_start (NMLldpListener *self, int ifindex, GError **error);
 void nm_lldp_listener_stop (NMLldpListener *self);
 gboolean nm_lldp_listener_is_running (NMLldpListener *self);
 
diff --git a/src/devices/team/nm-device-team.c b/src/devices/team/nm-device-team.c
index c3d48b41..60842c18 100644
--- a/src/devices/team/nm-device-team.c
+++ b/src/devices/team/nm-device-team.c
@@ -107,7 +107,8 @@ complete_connection (NMDevice *device,
 {
 	NMSettingTeam *s_team;
 
-	nm_utils_complete_generic (connection,
+	nm_utils_complete_generic (NM_PLATFORM_GET,
+	                           connection,
 	                           NM_SETTING_TEAM_SETTING_NAME,
 	                           existing_connections,
 	                           NULL,
diff --git a/src/devices/tests/test-arping.c b/src/devices/tests/test-arping.c
index 36e17631..72d28a7a 100644
--- a/src/devices/tests/test-arping.c
+++ b/src/devices/tests/test-arping.c
@@ -41,8 +41,8 @@ fixture_setup (test_fixture *fixture, gconstpointer user_data)
 {
 	/* create veth pair. */
 	nmtstp_run_command_check ("ip link add dev %s type veth peer name %s", IFACE_VETH0, IFACE_VETH1);
-	fixture->ifindex0 = nmtstp_assert_wait_for_link (IFACE_VETH0, NM_LINK_TYPE_VETH, 100)->ifindex;
-	fixture->ifindex1 = nmtstp_assert_wait_for_link (IFACE_VETH1, NM_LINK_TYPE_VETH, 100)->ifindex;
+	fixture->ifindex0 = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, IFACE_VETH0, NM_LINK_TYPE_VETH, 100)->ifindex;
+	fixture->ifindex1 = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, IFACE_VETH1, NM_LINK_TYPE_VETH, 100)->ifindex;
 
 	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, fixture->ifindex0, NULL));
 	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, fixture->ifindex1, NULL));
@@ -80,7 +80,7 @@ test_arping_common (test_fixture *fixture, TestInfo *info)
 
 	for (i = 0; info->peer_addresses[i]; i++) {
 		nmtstp_ip4_address_add (FALSE, fixture->ifindex1, info->peer_addresses[i],
-		                        24, 0, 3600, 1800, NULL);
+		                        24, 0, 3600, 1800, 0, NULL);
 	}
 
 	loop = g_main_loop_new (NULL, FALSE);
diff --git a/src/devices/tests/test-lldp.c b/src/devices/tests/test-lldp.c
index dfae58b1..85c3b510 100644
--- a/src/devices/tests/test-lldp.c
+++ b/src/devices/tests/test-lldp.c
@@ -27,18 +27,327 @@
 #include <sys/types.h>
 
 #include "nm-lldp-listener.h"
+#include "nm-sd.h"
+
+#include "nm-sd-adapt.h"
+#include "lldp.h"
+
 #include "test-common.h"
 
+#include "nm-test-utils.h"
+
+/*****************************************************************************/
+
+static GVariant *
+get_lldp_neighbor (GVariant *neighbors,
+                   int chassis_id_type,
+                   const char *chassis_id,
+                   int port_id_type,
+                   const char *port_id)
+{
+	GVariantIter iter;
+	GVariant *variant;
+	GVariant *result = NULL;
+
+	nmtst_assert_variant_is_of_type (neighbors, G_VARIANT_TYPE ("aa{sv}"));
+
+	g_assert (chassis_id_type >= -1 && chassis_id_type <= G_MAXUINT8);
+	g_assert (port_id_type >= -1 && port_id_type <= G_MAXUINT8);
+
+	g_variant_iter_init (&iter, neighbors);
+	while (g_variant_iter_next (&iter, "@a{sv}", &variant)) {
+		gs_unref_variant GVariant *v_chassis_id_type = NULL;
+		gs_unref_variant GVariant *v_chassis_id = NULL;
+		gs_unref_variant GVariant *v_port_id_type = NULL;
+		gs_unref_variant GVariant *v_port_id = NULL;
+
+		v_chassis_id_type = g_variant_lookup_value (variant, NM_LLDP_ATTR_CHASSIS_ID_TYPE, G_VARIANT_TYPE_UINT32);
+		g_assert (v_chassis_id_type);
+
+		v_chassis_id = g_variant_lookup_value (variant, NM_LLDP_ATTR_CHASSIS_ID, G_VARIANT_TYPE_STRING);
+		g_assert (v_chassis_id);
+
+		v_port_id_type = g_variant_lookup_value (variant, NM_LLDP_ATTR_PORT_ID_TYPE, G_VARIANT_TYPE_UINT32);
+		g_assert (v_port_id_type);
+
+		v_port_id = g_variant_lookup_value (variant, NM_LLDP_ATTR_PORT_ID, G_VARIANT_TYPE_STRING);
+		g_assert (v_port_id);
+
+		if (   nm_streq (g_variant_get_string (v_chassis_id, NULL), chassis_id)
+		    && nm_streq (g_variant_get_string (v_port_id, NULL), port_id)
+		    && NM_IN_SET (chassis_id_type, -1, g_variant_get_uint32 (v_chassis_id_type))
+		    && NM_IN_SET (port_id_type, -1, g_variant_get_uint32 (v_port_id_type))) {
+			g_assert (!result);
+			result = variant;
+		} else
+			g_variant_unref (variant);
+	}
+
+	return result;
+}
+
 typedef struct {
 	int ifindex;
 	int fd;
 	guint8 mac[ETH_ALEN];
-} test_fixture;
+} TestRecvFixture;
+
+typedef struct {
+	gsize frame_len;
+	const uint8_t *frame;
+} TestRecvFrame;
+#define TEST_RECV_FRAME_DEFINE(name, ...) \
+	static const guint8 _##name##_v[] = { __VA_ARGS__ }; \
+	static const TestRecvFrame name = { \
+		.frame_len = sizeof (_##name##_v), \
+		.frame = _##name##_v, \
+	}
+
+typedef struct {
+	guint expected_num_called;
+	gsize frames_len;
+	const TestRecvFrame *frames[10];
+	void (*check) (GMainLoop *loop, NMLldpListener *listener);
+} TestRecvData;
+#define TEST_RECV_DATA_DEFINE(name, _expected_num_called, _check, ...) \
+	static const TestRecvData name = { \
+		.expected_num_called = _expected_num_called, \
+		.check = _check, \
+		.frames_len = NM_NARG (__VA_ARGS__), \
+		.frames = { __VA_ARGS__ }, \
+	}
 
 #define TEST_IFNAME "nm-tap-test0"
 
+TEST_RECV_FRAME_DEFINE (_test_recv_data0_frame0,
+	/* Ethernet header */
+	0x01, 0x80, 0xc2, 0x00, 0x00, 0x03,     /* Destination MAC */
+	0x01, 0x02, 0x03, 0x04, 0x05, 0x06,     /* Source MAC */
+	0x88, 0xcc,                             /* Ethertype */
+	/* LLDP mandatory TLVs */
+	0x02, 0x07, 0x04, 0x00, 0x01, 0x02,     /* Chassis: MAC, 00:01:02:03:04:05 */
+	0x03, 0x04, 0x05,
+	0x04, 0x04, 0x05, 0x31, 0x2f, 0x33,     /* Port: interface name, "1/3" */
+	0x06, 0x02, 0x00, 0x78,                 /* TTL: 120 seconds */
+	/* LLDP optional TLVs */
+	0x08, 0x04, 0x50, 0x6f, 0x72, 0x74,     /* Port Description: "Port" */
+	0x0a, 0x03, 0x53, 0x59, 0x53,           /* System Name: "SYS" */
+	0x0c, 0x04, 0x66, 0x6f, 0x6f, 0x00,     /* System Description: "foo" (NULL-terminated) */
+	0x00, 0x00                              /* End Of LLDPDU */
+);
+
+static void
+_test_recv_data0_check (GMainLoop *loop, NMLldpListener *listener)
+{
+	GVariant *neighbors, *attr;
+	gs_unref_variant GVariant *neighbor = NULL;
+
+	neighbors = nm_lldp_listener_get_neighbors (listener);
+	nmtst_assert_variant_is_of_type (neighbors, G_VARIANT_TYPE ("aa{sv}"));
+	g_assert_cmpint (g_variant_n_children (neighbors), ==, 1);
+
+	neighbor = get_lldp_neighbor (neighbors,
+	                              LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS, "00:01:02:03:04:05",
+	                              LLDP_PORT_SUBTYPE_INTERFACE_NAME, "1/3");
+	g_assert (neighbor);
+	g_assert_cmpint (g_variant_n_children (neighbor), ==, 4 + 4);
+
+	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_PORT_DESCRIPTION, G_VARIANT_TYPE_STRING);
+	nmtst_assert_variant_string (attr, "Port");
+	nm_clear_g_variant (&attr);
+
+	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_SYSTEM_NAME, G_VARIANT_TYPE_STRING);
+	nmtst_assert_variant_string (attr, "SYS");
+	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_NON_TPMR_BRIDGE);
+	nm_clear_g_variant (&attr);
+
+	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_SYSTEM_DESCRIPTION, G_VARIANT_TYPE_STRING);
+	nmtst_assert_variant_string (attr, "foo");
+	nm_clear_g_variant (&attr);
+}
+
+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 */
+
+	/* ethernet header */
+	0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e, /* destination mac */
+	0x00, 0x01, 0x30, 0xf9, 0xad, 0xa0, /* source mac */
+	0x88, 0xcc,                         /* ethernet type */
+
+	0x02, 0x07, 0x04, 0x00, 0x01, 0x30, /* Chassis Subtype */
+	0xf9, 0xad, 0xa0,
+	0x04, 0x04, 0x05, 0x31, 0x2f, 0x31, /* Port Subtype */
+	0x06, 0x02, 0x00, 0x78,             /* Time To Live */
+	0x08, 0x17, 0x53, 0x75, 0x6d, 0x6d, /* Port Description */
+	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, /* System Name */
+	0x69, 0x74, 0x33, 0x30, 0x30, 0x2d,
+	0x34, 0x38, 0x00,
+	0x0c, 0x4c, 0x53, 0x75, 0x6d, 0x6d, /* System Description */
+	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, /* Capabilities */
+	0x10, 0x0e, 0x07, 0x06, 0x00, 0x01, /* Management Address */
+	0x30, 0xf9, 0xad, 0xa0, 0x02, 0x00,
+	0x00, 0x03, 0xe9, 0x00,
+	0xfe, 0x07, 0x00, 0x12, 0x0f, 0x02, /* IEEE 802.3 - Power Via MDI */
+	0x07, 0x01, 0x00,
+	0xfe, 0x09, 0x00, 0x12, 0x0f, 0x01, /* IEEE 802.3 - MAC/PHY Configuration/Status */
+	0x03, 0x6c, 0x00, 0x00, 0x10,
+	0xfe, 0x09, 0x00, 0x12, 0x0f, 0x03, /* IEEE 802.3 - Link Aggregation */
+	0x01, 0x00, 0x00, 0x00, 0x00,
+	0xfe, 0x06, 0x00, 0x12, 0x0f, 0x04, /* IEEE 802.3 - Maximum Frame Size */
+	0x05, 0xf2,
+	0xfe, 0x06, 0x00, 0x80, 0xc2, 0x01, /* IEEE 802.1 - Port VLAN ID */
+	0x01, 0xe8,
+	0xfe, 0x07, 0x00, 0x80, 0xc2, 0x02, /* IEEE 802.1 - Port and Protocol VLAN ID */
+	0x01, 0x00, 0x00,
+	0xfe, 0x17, 0x00, 0x80, 0xc2, 0x03, /* IEEE 802.1 - VLAN Name */
+	0x01, 0xe8, 0x10, 0x76, 0x32, 0x2d,
+	0x30, 0x34, 0x38, 0x38, 0x2d, 0x30,
+	0x33, 0x2d, 0x30, 0x35, 0x30, 0x35,
+	0x00,
+	0xfe, 0x05, 0x00, 0x80, 0xc2, 0x04, /* IEEE 802.1 - Protocol Identity */
+	0x00,
+	0x00, 0x00                          /* End of LLDPDU */
+);
+
 static void
-fixture_setup (test_fixture *fixture, gconstpointer user_data)
+_test_recv_data1_check (GMainLoop *loop, NMLldpListener *listener)
+{
+	GVariant *neighbors, *attr;
+	gs_unref_variant GVariant *neighbor = NULL;
+
+	neighbors = nm_lldp_listener_get_neighbors (listener);
+	nmtst_assert_variant_is_of_type (neighbors, G_VARIANT_TYPE ("aa{sv}"));
+	g_assert_cmpint (g_variant_n_children (neighbors), ==, 1);
+
+	neighbor = get_lldp_neighbor (neighbors,
+	                              LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS, "00:01:30:F9:AD:A0",
+	                              LLDP_PORT_SUBTYPE_INTERFACE_NAME, "1/1");
+	g_assert (neighbor);
+	g_assert_cmpint (g_variant_n_children (neighbor), ==, 4 + 10);
+
+	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_DESTINATION, G_VARIANT_TYPE_STRING);
+	nmtst_assert_variant_string (attr, NM_LLDP_DEST_NEAREST_BRIDGE);
+	nm_clear_g_variant (&attr);
+
+	/* unsupported: Time To Live */
+
+	/* Port Description */
+	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_PORT_DESCRIPTION, G_VARIANT_TYPE_STRING);
+	nmtst_assert_variant_string (attr, "Summit300-48-Port 1001");
+	nm_clear_g_variant (&attr);
+
+	/* System Name */
+	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_SYSTEM_NAME, G_VARIANT_TYPE_STRING);
+	nmtst_assert_variant_string (attr, "Summit300-48");
+	nm_clear_g_variant (&attr);
+
+	/* System Description */
+	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_SYSTEM_DESCRIPTION, G_VARIANT_TYPE_STRING);
+	nmtst_assert_variant_string (attr, "Summit300-48 - Version 7.4e.1 (Build 5) by Release_Master 05/27/05 04:53:11");
+	nm_clear_g_variant (&attr);
+
+	/* Capabilities */
+	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_SYSTEM_CAPABILITIES, G_VARIANT_TYPE_UINT32);
+	nmtst_assert_variant_uint32 (attr, 20);
+	nm_clear_g_variant (&attr);
+
+	/* unsupported: Management Address */
+	/* unsupported: IEEE 802.3 - Power Via MDI */
+	/* unsupported: IEEE 802.3 - MAC/PHY Configuration/Status */
+	/* unsupported: IEEE 802.3 - Link Aggregation */
+	/* unsupported: IEEE 802.3 - Maximum Frame Size*/
+
+	/* IEEE 802.1 - Port VLAN ID */
+	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_IEEE_802_1_PVID, G_VARIANT_TYPE_UINT32);
+	nmtst_assert_variant_uint32 (attr, 488);
+	nm_clear_g_variant (&attr);
+
+	/* IEEE 802.1 - Port and Protocol VLAN ID */
+	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_IEEE_802_1_PPVID, G_VARIANT_TYPE_UINT32);
+	nmtst_assert_variant_uint32 (attr, 0);
+	nm_clear_g_variant (&attr);
+	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_IEEE_802_1_PPVID_FLAGS, G_VARIANT_TYPE_UINT32);
+	nmtst_assert_variant_uint32 (attr, 1);
+	nm_clear_g_variant (&attr);
+
+	/* IEEE 802.1 - VLAN Name */
+	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_IEEE_802_1_VLAN_NAME, G_VARIANT_TYPE_STRING);
+	nmtst_assert_variant_string (attr, "v2-0488-03-0505");
+	nm_clear_g_variant (&attr);
+	attr = g_variant_lookup_value (neighbor, NM_LLDP_ATTR_IEEE_802_1_VID, G_VARIANT_TYPE_UINT32);
+	nmtst_assert_variant_uint32 (attr, 488);
+	nm_clear_g_variant (&attr);
+
+	/* unsupported: IEEE 802.1 - Protocol Identity */
+}
+
+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,
+	/* Ethernet header */
+	0x01, 0x80, 0xc2, 0x00, 0x00, 0x03,     /* Destination MAC */
+	0x01, 0x02, 0x03, 0x04, 0x05, 0x06,     /* Source MAC */
+	0x88, 0xcc,                             /* Ethertype */
+	/* LLDP mandatory TLVs */
+	0x02, 0x07, 0x04, 0x00, 0x01, 0x02,     /* Chassis: MAC, 00:01:02:03:04:05 */
+	0x03, 0x04, 0x05,
+	0x04, 0x04, 0x05, 0x31, 0x2f, 0x33,     /* Port: interface name, "1/3" */
+	0x06, 0x02, 0x00, 0x01,                 /* TTL: 1 seconds */
+	/* LLDP optional TLVs */
+	0x08, 0x04, 0x50, 0x6f, 0x72, 0x74,     /* Port Description: "Port" */
+	0x0a, 0x03, 0x53, 0x59, 0x53,           /* System Name: "SYS" */
+	0x0c, 0x04, 0x66, 0x6f, 0x6f, 0x00,     /* System Description: "foo" (NULL-terminated) */
+	0x00, 0x00                              /* End Of LLDPDU */
+);
+
+static void
+_test_recv_data2_ttl1_check (GMainLoop *loop, NMLldpListener *listener)
+{
+	gulong notify_id;
+	GVariant *neighbors;
+
+	_test_recv_data0_check (loop, listener);
+
+	/* wait for signal. */
+	notify_id = g_signal_connect (listener, "notify::" NM_LLDP_LISTENER_NEIGHBORS,
+	                              nmtst_main_loop_quit_on_notify, loop);
+	if (!nmtst_main_loop_run (loop, 5000))
+		g_assert_not_reached ();
+	nm_clear_g_signal_handler (listener, &notify_id);
+
+	neighbors = nm_lldp_listener_get_neighbors (listener);
+	nmtst_assert_variant_is_of_type (neighbors, G_VARIANT_TYPE ("aa{sv}"));
+	g_assert_cmpint (g_variant_n_children (neighbors), ==, 0);
+}
+
+TEST_RECV_DATA_DEFINE (_test_recv_data2_ttl1, 1, _test_recv_data2_ttl1_check,  &_test_recv_data2_frame0_ttl1);
+
+static void
+_test_recv_fixture_setup (TestRecvFixture *fixture, gconstpointer user_data)
 {
 	const NMPlatformLink *link;
 	struct ifreq ifr = { };
@@ -48,7 +357,7 @@ fixture_setup (test_fixture *fixture, gconstpointer user_data)
 	g_assert (fd >= 0);
 
 	ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
-	strncpy (ifr.ifr_name, TEST_IFNAME, IFNAMSIZ);
+	nm_utils_ifname_cpy (ifr.ifr_name, TEST_IFNAME);
 	g_assert (ioctl (fd, TUNSETIFF, &ifr) >= 0);
 
 	/* Bring the interface up */
@@ -58,9 +367,7 @@ fixture_setup (test_fixture *fixture, gconstpointer user_data)
 	g_assert (ioctl (s, SIOCSIFFLAGS, &ifr) >= 0);
 	close (s);
 
-	nm_platform_process_events (NM_PLATFORM_GET);
-	link = nm_platform_link_get_by_ifname (NM_PLATFORM_GET, TEST_IFNAME);
-	g_assert (link);
+	link = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, TEST_IFNAME, NM_LINK_TYPE_TAP, 100);
 	fixture->ifindex = link->ifindex;
 	fixture->fd = fd;
 	memcpy (fixture->mac, link->addr.data, ETH_ALEN);
@@ -68,149 +375,66 @@ fixture_setup (test_fixture *fixture, gconstpointer user_data)
 
 typedef struct {
 	int num_called;
-} TestInfo;
-
-static gboolean
-loop_quit (gpointer user_data)
-{
-	g_main_loop_quit ((GMainLoop *) user_data);
-	return G_SOURCE_REMOVE;
-}
+} TestRecvCallbackInfo;
 
 static void
 lldp_neighbors_changed (NMLldpListener *lldp_listener, GParamSpec *pspec,
                         gpointer user_data)
 {
-	TestInfo *info = user_data;
+	TestRecvCallbackInfo *info = user_data;
 
 	info->num_called++;
 }
 
-static GVariant *
-get_lldp_neighbor_attribute (GVariant *neighbors,
-                             const char *chassis, const char *port,
-                             const char *name)
-{
-	GVariantIter iter, attrs_iter;
-	GVariant *variant, *attr_variant;
-	const char *attr_name;
-
-	g_return_val_if_fail (g_variant_is_of_type (neighbors,
-	                                            G_VARIANT_TYPE ("aa{sv}")),
-	                      NULL);
-	g_variant_iter_init (&iter, neighbors);
-
-	while (g_variant_iter_next (&iter, "@a{sv}", &variant)) {
-		gs_unref_variant GVariant *chassis_v = NULL;
-		gs_unref_variant GVariant *port_v = NULL;
-		gs_unref_variant GVariant *attr_v = NULL;
-
-		g_variant_iter_init (&attrs_iter, variant);
-		while (g_variant_iter_next (&attrs_iter, "{&sv}", &attr_name, &attr_variant)) {
-			if (!g_strcmp0 (attr_name, NM_LLDP_ATTR_CHASSIS_ID)) {
-				g_assert (!chassis_v);
-				chassis_v = attr_variant;
-			} else if (!g_strcmp0 (attr_name, NM_LLDP_ATTR_PORT_ID)) {
-				g_assert (!port_v);
-				port_v = attr_variant;
-			} else if (!g_strcmp0 (attr_name, name)) {
-				g_assert (!attr_v);
-				attr_v = attr_variant;
-			} else
-				g_variant_unref (attr_variant);
-		}
-
-		g_variant_unref (variant);
-
-		if (   chassis_v
-		    && port_v
-		    && g_variant_is_of_type (chassis_v, G_VARIANT_TYPE_STRING)
-		    && g_variant_is_of_type (port_v, G_VARIANT_TYPE_STRING)
-		    && !g_strcmp0 (chassis, g_variant_get_string (chassis_v, NULL))
-		    && !g_strcmp0 (port, g_variant_get_string (port_v, NULL)))
-			return g_variant_ref (attr_v);
-	}
-
-	/* neighbor not found */
-	return NULL;
-}
-
 static void
-test_receive_frame (test_fixture *fixture, gconstpointer user_data)
+test_recv (TestRecvFixture *fixture, gconstpointer user_data)
 {
+	const TestRecvData *data = user_data;
 	gs_unref_object NMLldpListener *listener = NULL;
 	GMainLoop *loop;
-	TestInfo info = { };
-	GVariant *neighbors, *attr;
-	uint8_t frame[] = {
-		/* Ethernet header */
-		0x01, 0x80, 0xc2, 0x00, 0x00, 0x03,     /* Destination MAC */
-		0x01, 0x02, 0x03, 0x04, 0x05, 0x06,     /* Source MAC */
-		0x88, 0xcc,                             /* Ethertype */
-		/* LLDP mandatory TLVs */
-		0x02, 0x07, 0x04, 0x00, 0x01, 0x02,     /* Chassis: MAC, 00:01:02:03:04:05 */
-		0x03, 0x04, 0x05,
-		0x04, 0x04, 0x05, 0x31, 0x2f, 0x33,     /* Port: interface name, "1/3" */
-		0x06, 0x02, 0x00, 0x78,                 /* TTL: 120 seconds */
-		/* LLDP optional TLVs */
-		0x08, 0x04, 0x50, 0x6f, 0x72, 0x74,     /* Port Description: "Port" */
-		0x0a, 0x03, 0x53, 0x59, 0x53,           /* System Name: "SYS" */
-		0x0c, 0x04, 0x66, 0x6f, 0x6f, 0x00,     /* System Description: "foo" (NULL-terminated) */
-		0x00, 0x00                              /* End Of LLDPDU */
-	};
+	TestRecvCallbackInfo info = { };
+	gsize i_frames;
+	gulong notify_id;
+	GError *error = NULL;
+	guint sd_id;
 
 	listener = nm_lldp_listener_new ();
 	g_assert (listener != NULL);
-	g_assert (nm_lldp_listener_start (listener, fixture->ifindex, TEST_IFNAME, fixture->mac, ETH_ALEN, NULL));
+	g_assert (nm_lldp_listener_start (listener, fixture->ifindex, &error));
+	g_assert_no_error (error);
 
-	g_signal_connect (listener, "notify::" NM_LLDP_LISTENER_NEIGHBORS,
-	                  (GCallback) lldp_neighbors_changed, &info);
+	notify_id = g_signal_connect (listener, "notify::" NM_LLDP_LISTENER_NEIGHBORS,
+	                              (GCallback) lldp_neighbors_changed, &info);
 	loop = g_main_loop_new (NULL, FALSE);
-	g_timeout_add_seconds (1, loop_quit, loop);
+	sd_id = nm_sd_event_attach_default ();
+
+	for (i_frames = 0; i_frames < data->frames_len; i_frames++) {
+		const TestRecvFrame *f = data->frames[i_frames];
 
-	g_assert (write (fixture->fd, frame, sizeof (frame)) == sizeof (frame));
-	g_assert (write (fixture->fd, frame, sizeof (frame)) == sizeof (frame));
+		g_assert (write (fixture->fd, f->frame, f->frame_len) == f->frame_len);
+	}
 
-	g_main_loop_run (loop);
+	if (nmtst_main_loop_run (loop, 500))
+		g_assert_not_reached ();
 
-	g_assert_cmpint (info.num_called, ==, 1);
-	neighbors = nm_lldp_listener_get_neighbors (listener);
-	g_assert (neighbors != NULL);
-
-	/* Check port description */
-	attr = get_lldp_neighbor_attribute (neighbors, "00:01:02:03:04:05", "1/3",
-	                                    NM_LLDP_ATTR_PORT_DESCRIPTION);
-	g_assert (attr != NULL);
-	g_assert (g_variant_is_of_type (attr, G_VARIANT_TYPE_STRING));
-	g_assert_cmpstr (g_variant_get_string (attr, NULL), ==, "Port");
-	nm_clear_g_variant (&attr);
+	g_assert_cmpint (info.num_called, ==, data->expected_num_called);
 
-	/* Check system name */
-	attr = get_lldp_neighbor_attribute (neighbors, "00:01:02:03:04:05", "1/3",
-	                                    NM_LLDP_ATTR_SYSTEM_NAME);
-	g_assert (attr != NULL);
-	g_assert (g_variant_is_of_type (attr, G_VARIANT_TYPE_STRING));
-	g_assert_cmpstr (g_variant_get_string (attr, NULL), ==, "SYS");
-	nm_clear_g_variant (&attr);
+	nm_clear_g_signal_handler (listener, &notify_id);
 
-	/* Check destination */
-	attr = get_lldp_neighbor_attribute (neighbors, "00:01:02:03:04:05", "1/3",
-	                                    NM_LLDP_ATTR_DESTINATION);
-	g_assert (attr != NULL);
-	g_assert (g_variant_is_of_type (attr, G_VARIANT_TYPE_STRING));
-	g_assert_cmpstr (g_variant_get_string (attr, NULL), ==,
-	                 NM_LLDP_DEST_NEAREST_NON_TPMR_BRIDGE);
-	nm_clear_g_variant (&attr);
+	data->check (loop, listener);
 
+	nm_clear_g_source (&sd_id);
 	g_clear_pointer (&loop, g_main_loop_unref);
 }
 
 static void
-fixture_teardown (test_fixture *fixture, gconstpointer user_data)
+_test_recv_fixture_teardown (TestRecvFixture *fixture, gconstpointer user_data)
 {
 	nm_platform_link_delete (NM_PLATFORM_GET, fixture->ifindex);
 }
 
+/*****************************************************************************/
+
 void
 init_tests (int *argc, char ***argv)
 {
@@ -220,6 +444,10 @@ init_tests (int *argc, char ***argv)
 void
 setup_tests (void)
 {
-	g_test_add ("/lldp/receive_frame", test_fixture, NULL, fixture_setup,
-	            test_receive_frame, fixture_teardown);
+#define _TEST_ADD_RECV(testpath, testdata) \
+	g_test_add (testpath, TestRecvFixture, testdata, _test_recv_fixture_setup, test_recv, _test_recv_fixture_teardown)
+	_TEST_ADD_RECV ("/lldp/recv/0",       &_test_recv_data0);
+	_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);
 }
diff --git a/src/devices/wifi/nm-device-olpc-mesh.c b/src/devices/wifi/nm-device-olpc-mesh.c
index 11da011e..6fbc63a1 100644
--- a/src/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/devices/wifi/nm-device-olpc-mesh.c
@@ -138,7 +138,8 @@ complete_connection (NMDevice *device,
 
 	}
 
-	nm_utils_complete_generic (connection,
+	nm_utils_complete_generic (NM_PLATFORM_GET,
+	                           connection,
 	                           NM_SETTING_OLPC_MESH_SETTING_NAME,
 	                           existing_connections,
 	                           NULL,
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index da2a0f4a..95a89eab 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -845,7 +845,8 @@ complete_connection (NMDevice *device,
 
 	str_ssid = nm_utils_ssid_to_utf8 (ssid->data, ssid->len);
 
-	nm_utils_complete_generic (connection,
+	nm_utils_complete_generic (NM_PLATFORM_GET,
+	                           connection,
 	                           NM_SETTING_WIRELESS_SETTING_NAME,
 	                           existing_connections,
 	                           str_ssid,
@@ -2491,7 +2492,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *reason)
 	if (config == NULL) {
 		_LOGE (LOGD_DEVICE | LOGD_WIFI,
 		       "Activation: (wifi) couldn't build wireless configuration: %s",
-		       error ? error->message : "<BUG>");
+		       error->message);
 		g_clear_error (&error);
 		*reason = NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED;
 		goto out;
@@ -2506,7 +2507,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *reason)
 	if (!nm_supplicant_interface_set_config (priv->sup_iface, config, &error)) {
 		_LOGE (LOGD_DEVICE | LOGD_WIFI,
 		       "Activation: (wifi) couldn't send wireless configuration to the supplicant: %s",
-		       error ? error->message : "<BUG>");
+		       error->message);
 		g_clear_error (&error);
 		*reason = NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED;
 		goto out;
diff --git a/src/devices/wifi/nm-wifi-ap.c b/src/devices/wifi/nm-wifi-ap.c
index 307825c6..a0c25709 100644
--- a/src/devices/wifi/nm-wifi-ap.c
+++ b/src/devices/wifi/nm-wifi-ap.c
@@ -741,7 +741,7 @@ nm_ap_dump (NMAccessPoint *self,
 
 	nm_log_dbg (LOGD_WIFI_SCAN, "%s[%s%c] %-32s[%s%u %3u%% %c W:%04X R:%04X] [%3u] %s%s",
 	            prefix,
-	            str_if_set (priv->address, "(none)"),
+	            priv->address ?: "(none)",
 	            mode_to_char (self),
 	            priv->ssid ? nm_utils_escape_ssid (priv->ssid->data, priv->ssid->len) : "(none)",
 	            chan > 99 ? "" : (chan > 9 ? " " : "  "),
diff --git a/src/devices/wifi/tests/test-wifi-ap-utils.c b/src/devices/wifi/tests/test-wifi-ap-utils.c
index 01990bee..838368c4 100644
--- a/src/devices/wifi/tests/test-wifi-ap-utils.c
+++ b/src/devices/wifi/tests/test-wifi-ap-utils.c
@@ -37,7 +37,7 @@
 	if (expected) { \
 		if (!success) { \
 			g_assert (error != NULL); \
-			g_warning ("Failed to complete connection: (%d) %s", error->code, error->message); \
+			g_warning ("Failed to complete connection: %s", error->message); \
 		} \
 		g_assert (success == TRUE); \
 		g_assert (error == NULL); \
diff --git a/src/devices/wwan/nm-device-modem.c b/src/devices/wwan/nm-device-modem.c
index e9c4f02e..a8361c49 100644
--- a/src/devices/wwan/nm-device-modem.c
+++ b/src/devices/wwan/nm-device-modem.c
@@ -157,8 +157,8 @@ modem_ip4_config_result (NMModem *modem,
 	g_return_if_fail (nm_device_activate_ip4_state_in_conf (device) == TRUE);
 
 	if (error) {
-		_LOGW (LOGD_MB | LOGD_IP4, "retrieving IPv4 configuration failed: (%d) %s",
-		       error->code, error->message ? error->message : "(unknown)");
+		_LOGW (LOGD_MB | LOGD_IP4, "retrieving IPv4 configuration failed: %s",
+		       error->message);
 
 		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 	} else {
@@ -184,8 +184,7 @@ modem_ip6_config_result (NMModem *modem,
 	g_return_if_fail (nm_device_activate_ip6_state_in_conf (device) == TRUE);
 
 	if (error) {
-		_LOGW (LOGD_MB | LOGD_IP6, "retrieving IPv6 configuration failed: (%d) %s",
-		       error->code, error->message ? error->message : "(unknown)");
+		_LOGW (LOGD_MB | LOGD_IP6, "retrieving IPv6 configuration failed: %s", error->message);
 
 		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 		return;
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index 6343416c..769e6a27 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -464,7 +464,7 @@ connect_context_step (NMModemBroadband *self)
 			nm_log_warn (LOGD_MB, "(%s): Failed to connect '%s': %s",
 			             nm_modem_get_uid (NM_MODEM (self)),
 			             nm_connection_get_id (ctx->connection),
-			             error ? error->message : "unknown error");
+			             error->message);
 			g_clear_error (&error);
 
 			g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED);
@@ -647,7 +647,8 @@ complete_connection (NMModem *_self,
 		if (!nm_setting_gsm_get_number (s_gsm))
 			g_object_set (G_OBJECT (s_gsm), NM_SETTING_GSM_NUMBER, "*99#", NULL);
 
-		nm_utils_complete_generic (connection,
+		nm_utils_complete_generic (NM_PLATFORM_GET,
+		                           connection,
 		                           NM_SETTING_GSM_SETTING_NAME,
 		                           existing_connections,
 		                           NULL,
@@ -670,7 +671,8 @@ complete_connection (NMModem *_self,
 		if (!nm_setting_cdma_get_number (s_cdma))
 			g_object_set (G_OBJECT (s_cdma), NM_SETTING_CDMA_NUMBER, "#777", NULL);
 
-		nm_utils_complete_generic (connection,
+		nm_utils_complete_generic (NM_PLATFORM_GET,
+		                           connection,
 		                           NM_SETTING_CDMA_SETTING_NAME,
 		                           existing_connections,
 		                           NULL,
@@ -734,7 +736,7 @@ set_power_state_low_ready (MMModem *modem,
 		/* Log but ignore errors; not all modems support low power state */
 		nm_log_dbg (LOGD_MB, "(%s): failed to set modem low power state: %s",
 		            nm_modem_get_uid (NM_MODEM (self)),
-		            error && error->message ? error->message : "(unknown)");
+		            NM_G_ERROR_MSG (error));
 		g_clear_error (&error);
 	}
 
@@ -759,7 +761,7 @@ modem_disable_ready (MMModem *modem_iface,
 	} else {
 		nm_log_warn (LOGD_MB, "(%s): failed to disable modem: %s",
 		             nm_modem_get_uid (NM_MODEM (self)),
-		             error && error->message ? error->message : "(unknown)");
+		             NM_G_ERROR_MSG (error));
 		nm_modem_set_prev_state (NM_MODEM (self), "disable failed");
 		g_clear_error (&error);
 	}
@@ -778,7 +780,7 @@ modem_enable_ready (MMModem *modem_iface,
 	if (!mm_modem_enable_finish (modem_iface, res, &error)) {
 		nm_log_warn (LOGD_MB, "(%s) failed to enable modem: %s",
 		             nm_modem_get_uid (NM_MODEM (self)),
-		             error && error->message ? error->message : "(unknown)");
+		             NM_G_ERROR_MSG (error));
 		nm_modem_set_prev_state (NM_MODEM (self), "enable failed");
 		g_clear_error (&error);
 	}
@@ -1259,7 +1261,7 @@ get_sim_ready (MMModem *modem,
 	} else {
 		nm_log_warn (LOGD_MB, "(%s): failed to retrieve SIM object: %s",
 		             nm_modem_get_uid (NM_MODEM (self)),
-		             error && error->message ? error->message : "(unknown)");
+		             NM_G_ERROR_MSG (error));
 	}
 	g_clear_error (&error);
 	g_object_unref (self);
diff --git a/src/devices/wwan/nm-modem-manager.c b/src/devices/wwan/nm-modem-manager.c
index 79082c41..e26321b0 100644
--- a/src/devices/wwan/nm-modem-manager.c
+++ b/src/devices/wwan/nm-modem-manager.c
@@ -145,7 +145,7 @@ modem_object_added (MMManager *modem_manager,
 		handle_new_modem (self, modem);
 	else {
 		nm_log_warn (LOGD_MB, "failed to create modem: %s",
-		             error ? error->message : "(unknown)");
+		             error->message);
 	}
 	g_clear_error (&error);
 }
diff --git a/src/devices/wwan/nm-modem.c b/src/devices/wwan/nm-modem.c
index c1736ecf..2a8a9d1e 100644
--- a/src/devices/wwan/nm-modem.c
+++ b/src/devices/wwan/nm-modem.c
@@ -545,10 +545,9 @@ ppp_stage3_ip_config_start (NMModem *self,
 
 		ret = NM_ACT_STAGE_RETURN_POSTPONE;
 	} else {
-		nm_log_err (LOGD_PPP, "(%s): error starting PPP: (%d) %s",
+		nm_log_err (LOGD_PPP, "(%s): error starting PPP: %s",
 		            nm_modem_get_uid (self),
-		            error ? error->code : -1,
-		            error && error->message ? error->message : "(unknown)");
+		            error->message);
 		g_error_free (error);
 
 		nm_exported_object_clear_and_unexport (&priv->ppp_manager);