summary refs log tree commit diff
path: root/src/core/devices/tests
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/devices/tests')
-rw-r--r--src/core/devices/tests/meson.build22
-rw-r--r--src/core/devices/tests/test-acd.c259
-rw-r--r--src/core/devices/tests/test-lldp.c1316
3 files changed, 1597 insertions, 0 deletions
diff --git a/src/core/devices/tests/meson.build b/src/core/devices/tests/meson.build
new file mode 100644
index 00000000..1bc88370
--- /dev/null
+++ b/src/core/devices/tests/meson.build
@@ -0,0 +1,22 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+test_units = [
+  'test-acd',
+  'test-lldp',
+]
+
+foreach test_unit: test_units
+  exe = executable(
+    test_unit,
+    test_unit + '.c',
+    dependencies: libNetworkManagerTest_dep,
+    c_args: test_c_flags,
+  )
+
+  test(
+    'devices/' + test_unit,
+    test_script,
+    args: test_args + [exe.full_path()],
+    timeout: default_test_timeout,
+  )
+endforeach
diff --git a/src/core/devices/tests/test-acd.c b/src/core/devices/tests/test-acd.c
new file mode 100644
index 00000000..b0af59db
--- /dev/null
+++ b/src/core/devices/tests/test-acd.c
@@ -0,0 +1,259 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "n-acd/src/n-acd.h"
+
+#include <linux/if_ether.h>
+
+#include "devices/nm-acd-manager.h"
+#include "platform/tests/test-common.h"
+
+#define IFACE_VETH0 "nm-test-veth0"
+#define IFACE_VETH1 "nm-test-veth1"
+
+#define ADDR1 0x01010101
+#define ADDR2 0x02020202
+#define ADDR3 0x03030303
+#define ADDR4 0x04040404
+
+/*****************************************************************************/
+
+static gboolean
+_skip_acd_test_check(void)
+{
+    NAcd *       acd;
+    NAcdConfig * config;
+    const guint8 hwaddr[ETH_ALEN] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
+    int          r;
+    static int   skip = -1;
+
+    if (skip == -1) {
+        r = n_acd_config_new(&config);
+        g_assert(r == 0);
+
+        n_acd_config_set_ifindex(config, 1);
+        n_acd_config_set_transport(config, N_ACD_TRANSPORT_ETHERNET);
+        n_acd_config_set_mac(config, hwaddr, sizeof(hwaddr));
+
+        r = n_acd_new(&acd, config);
+        n_acd_config_free(config);
+        if (r == 0)
+            n_acd_unref(acd);
+
+        skip = (r != 0);
+    }
+    return skip;
+}
+
+#define _skip_acd_test()                                               \
+    ({                                                                 \
+        gboolean _skip = _skip_acd_test_check();                       \
+                                                                       \
+        if (_skip)                                                     \
+            g_test_skip("Cannot create NAcd. Running under valgind?"); \
+        _skip;                                                         \
+    })
+
+/*****************************************************************************/
+
+typedef struct {
+    int           ifindex0;
+    int           ifindex1;
+    const guint8 *hwaddr0;
+    const guint8 *hwaddr1;
+    size_t        hwaddr0_len;
+    size_t        hwaddr1_len;
+} test_fixture;
+
+static void
+fixture_setup(test_fixture *fixture, gconstpointer user_data)
+{
+    /* create veth pair. */
+    fixture->ifindex0 =
+        nmtstp_link_veth_add(NM_PLATFORM_GET, -1, IFACE_VETH0, IFACE_VETH1)->ifindex;
+    fixture->ifindex1 =
+        nmtstp_link_get_typed(NM_PLATFORM_GET, -1, IFACE_VETH1, NM_LINK_TYPE_VETH)->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));
+
+    fixture->hwaddr0 =
+        nm_platform_link_get_address(NM_PLATFORM_GET, fixture->ifindex0, &fixture->hwaddr0_len);
+    fixture->hwaddr1 =
+        nm_platform_link_get_address(NM_PLATFORM_GET, fixture->ifindex1, &fixture->hwaddr1_len);
+}
+
+typedef struct {
+    in_addr_t addresses[8];
+    in_addr_t peer_addresses[8];
+    gboolean  expected_result[8];
+} TestInfo;
+
+static void
+acd_manager_probe_terminated(NMAcdManager *acd_manager, gpointer user_data)
+{
+    g_main_loop_quit(user_data);
+}
+
+static void
+test_acd_common(test_fixture *fixture, TestInfo *info)
+{
+    nm_auto_free_acdmgr NMAcdManager *manager = NULL;
+    nm_auto_unref_gmainloop GMainLoop *loop   = NULL;
+    int                                i;
+    const guint                        WAIT_TIME_OPTIMISTIC = 50;
+    guint                              wait_time;
+    static const NMAcdCallbacks        callbacks = {
+        .probe_terminated_callback = acd_manager_probe_terminated,
+        .user_data_destroy         = (GDestroyNotify) g_main_loop_unref,
+    };
+    int r;
+
+    if (_skip_acd_test())
+        return;
+
+    /* first, try with a short waittime. We hope that this is long enough
+     * to successfully complete the test. Only if that's not the case, we
+     * assume the computer is currently busy (high load) and we retry with
+     * a longer timeout. */
+    wait_time = WAIT_TIME_OPTIMISTIC;
+again:
+
+    nm_clear_pointer(&loop, g_main_loop_unref);
+    loop = g_main_loop_new(NULL, FALSE);
+
+    nm_clear_pointer(&manager, nm_acd_manager_free);
+    manager = nm_acd_manager_new(fixture->ifindex0,
+                                 fixture->hwaddr0,
+                                 fixture->hwaddr0_len,
+                                 &callbacks,
+                                 g_main_loop_ref(loop));
+    g_assert(manager != NULL);
+
+    for (i = 0; info->addresses[i]; i++)
+        g_assert(nm_acd_manager_add_address(manager, info->addresses[i]));
+
+    for (i = 0; info->peer_addresses[i]; i++) {
+        nmtstp_ip4_address_add(NULL,
+                               FALSE,
+                               fixture->ifindex1,
+                               info->peer_addresses[i],
+                               24,
+                               0,
+                               3600,
+                               1800,
+                               0,
+                               NULL);
+    }
+
+    r = nm_acd_manager_start_probe(manager, wait_time);
+    g_assert_cmpint(r, ==, 0);
+
+    g_assert(nmtst_main_loop_run(loop, 2000));
+
+    for (i = 0; info->addresses[i]; i++) {
+        gboolean val;
+        char     sbuf[NM_UTILS_INET_ADDRSTRLEN];
+
+        val = nm_acd_manager_check_address(manager, info->addresses[i]);
+        if (val == info->expected_result[i])
+            continue;
+
+        if (wait_time == WAIT_TIME_OPTIMISTIC) {
+            /* probably we just had a glitch and the system took longer than
+             * expected. Re-verify with a large timeout this time. */
+            wait_time = 1000;
+            goto again;
+        }
+
+        g_error("expected check for address #%d (%s) to %s, but it didn't",
+                i,
+                _nm_utils_inet4_ntop(info->addresses[i], sbuf),
+                info->expected_result[i] ? "detect no duplicated" : "detect a duplicate");
+    }
+}
+
+static void
+test_acd_probe_1(test_fixture *fixture, gconstpointer user_data)
+{
+    TestInfo info = {.addresses       = {ADDR1, ADDR2, ADDR3},
+                     .peer_addresses  = {ADDR4},
+                     .expected_result = {TRUE, TRUE, TRUE}};
+
+    test_acd_common(fixture, &info);
+}
+
+static void
+test_acd_probe_2(test_fixture *fixture, gconstpointer user_data)
+{
+    TestInfo info = {.addresses       = {ADDR1, ADDR2, ADDR3, ADDR4},
+                     .peer_addresses  = {ADDR3, ADDR2},
+                     .expected_result = {TRUE, FALSE, FALSE, TRUE}};
+
+    test_acd_common(fixture, &info);
+}
+
+static void
+test_acd_announce(test_fixture *fixture, gconstpointer user_data)
+{
+    nm_auto_free_acdmgr NMAcdManager *manager = NULL;
+    nm_auto_unref_gmainloop GMainLoop *loop   = NULL;
+    int                                r;
+
+    if (_skip_acd_test())
+        return;
+
+    manager =
+        nm_acd_manager_new(fixture->ifindex0, fixture->hwaddr0, fixture->hwaddr0_len, NULL, NULL);
+    g_assert(manager != NULL);
+
+    g_assert(nm_acd_manager_add_address(manager, ADDR1));
+    g_assert(nm_acd_manager_add_address(manager, ADDR2));
+
+    loop = g_main_loop_new(NULL, FALSE);
+    r    = nm_acd_manager_announce_addresses(manager);
+    g_assert_cmpint(r, ==, 0);
+    g_assert(!nmtst_main_loop_run(loop, 200));
+}
+
+static void
+fixture_teardown(test_fixture *fixture, gconstpointer user_data)
+{
+    nm_platform_link_delete(NM_PLATFORM_GET, fixture->ifindex0);
+    nm_platform_link_delete(NM_PLATFORM_GET, fixture->ifindex1);
+}
+
+NMTstpSetupFunc const _nmtstp_setup_platform_func = nm_linux_platform_setup;
+
+void
+_nmtstp_init_tests(int *argc, char ***argv)
+{
+    nmtst_init_with_logging(argc, argv, NULL, "ALL");
+}
+
+void
+_nmtstp_setup_tests(void)
+{
+    g_test_add("/acd/probe/1",
+               test_fixture,
+               NULL,
+               fixture_setup,
+               test_acd_probe_1,
+               fixture_teardown);
+    g_test_add("/acd/probe/2",
+               test_fixture,
+               NULL,
+               fixture_setup,
+               test_acd_probe_2,
+               fixture_teardown);
+    g_test_add("/acd/announce",
+               test_fixture,
+               NULL,
+               fixture_setup,
+               test_acd_announce,
+               fixture_teardown);
+}
diff --git a/src/core/devices/tests/test-lldp.c b/src/core/devices/tests/test-lldp.c
new file mode 100644
index 00000000..ef0b1549
--- /dev/null
+++ b/src/core/devices/tests/test-lldp.c
@@ -0,0 +1,1316 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include <fcntl.h>
+#include <netinet/if_ether.h>
+#include <linux/if_tun.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "devices/nm-lldp-listener.h"
+#include "systemd/nm-sd.h"
+
+#include "platform/tests/test-common.h"
+
+#include "nm-test-utils-core.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];
+} TestRecvFixture;
+
+typedef struct {
+    gsize          frame_len;
+    const uint8_t *frame;
+    const char *   as_variant;
+} TestRecvFrame;
+#define TEST_RECV_FRAME_DEFINE(name, _as_variant, ...)        \
+    static const guint8        _##name##_v[] = {__VA_ARGS__}; \
+    static const TestRecvFrame name          = {              \
+        .as_variant = _as_variant,                   \
+        .frame_len  = sizeof(_##name##_v),           \
+        .frame      = _##name##_v,                   \
+    }
+
+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,
+    "{'raw': <[byte 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x88, "
+    "0xcc, 0x02, 0x07, 0x04, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x04, 0x04, 0x05, 0x31, 0x2f, "
+    "0x33, 0x06, 0x02, 0x00, 0x78, 0x08, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x0a, 0x03, 0x53, 0x59, "
+    "0x53, 0x0c, 0x04, 0x66, 0x6f, 0x6f, 0x00, 0x00, 0x00]>, 'chassis-id-type': <uint32 4>, "
+    "'chassis-id': <'00:01:02:03:04:05'>, 'port-id-type': <uint32 5>, 'port-id': <'1/3'>, "
+    "'destination': <'nearest-non-tpmr-bridge'>, 'port-description': <'Port'>, 'system-name': "
+    "<'SYS'>, 'system-description': <'foo'>}",
+    /* Ethernet header */
+    0x01,
+    0x80,
+    0xc2,
+    0x00,
+    0x00,
+    0x03, /* Destination MAC */
+    0x01,
+    0x02,
+    0x03,
+    0x04,
+    0x05,
+    0x06, /* Source MAC */
+    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_do(GMainLoop *loop, NMLldpListener *listener, const TestRecvFrame *frame)
+{
+    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,
+                                 SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS,
+                                 "00:01:02:03:04:05",
+                                 SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME,
+                                 "1/3");
+    g_assert(neighbor);
+    g_assert_cmpint(g_variant_n_children(neighbor), ==, 1 + 4 + 4);
+
+    attr = g_variant_lookup_value(neighbor, NM_LLDP_ATTR_RAW, G_VARIANT_TYPE_BYTESTRING);
+    nmtst_assert_variant_bytestring(attr, frame->frame, frame->frame_len);
+    nm_clear_g_variant(&attr);
+
+    attr = g_variant_lookup_value(neighbor, NM_LLDP_ATTR_PORT_DESCRIPTION, G_VARIANT_TYPE_STRING);
+    nmtst_assert_variant_string(attr, "Port");
+    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);
+}
+
+static void
+_test_recv_data0_check(GMainLoop *loop, NMLldpListener *listener)
+{
+    _test_recv_data0_check_do(loop, listener, &_test_recv_data0_frame0);
+}
+
+TEST_RECV_DATA_DEFINE(_test_recv_data0, 1, _test_recv_data0_check, &_test_recv_data0_frame0);
+TEST_RECV_DATA_DEFINE(_test_recv_data0_twice,
+                      1,
+                      _test_recv_data0_check,
+                      &_test_recv_data0_frame0,
+                      &_test_recv_data0_frame0);
+
+TEST_RECV_FRAME_DEFINE(
+    _test_recv_data1_frame0,
+    /* lldp.detailed.pcap from
+     * https://wiki.wireshark.org/SampleCaptures#Link_Layer_Discovery_Protocol_.28LLDP.29 */
+    "{'raw': <[byte 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e, 0x00, 0x01, 0x30, 0xf9, 0xad, 0xa0, 0x88, "
+    "0xcc, 0x02, 0x07, 0x04, 0x00, 0x01, 0x30, 0xf9, 0xad, 0xa0, 0x04, 0x04, 0x05, 0x31, 0x2f, "
+    "0x31, 0x06, 0x02, 0x00, 0x78, 0x08, 0x17, 0x53, 0x75, 0x6d, 0x6d, 0x69, 0x74, 0x33, 0x30, "
+    "0x30, 0x2d, 0x34, 0x38, 0x2d, 0x50, 0x6f, 0x72, 0x74, 0x20, 0x31, 0x30, 0x30, 0x31, 0x00, "
+    "0x0a, 0x0d, 0x53, 0x75, 0x6d, 0x6d, 0x69, 0x74, 0x33, 0x30, 0x30, 0x2d, 0x34, 0x38, 0x00, "
+    "0x0c, 0x4c, 0x53, 0x75, 0x6d, 0x6d, 0x69, 0x74, 0x33, 0x30, 0x30, 0x2d, 0x34, 0x38, 0x20, "
+    "0x2d, 0x20, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x37, 0x2e, 0x34, 0x65, 0x2e, "
+    "0x31, 0x20, 0x28, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x20, 0x35, 0x29, 0x20, 0x62, 0x79, 0x20, "
+    "0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, "
+    "0x30, 0x35, 0x2f, 0x32, 0x37, 0x2f, 0x30, 0x35, 0x20, 0x30, 0x34, 0x3a, 0x35, 0x33, 0x3a, "
+    "0x31, 0x31, 0x00, 0x0e, 0x04, 0x00, 0x14, 0x00, 0x14, 0x10, 0x0e, 0x07, 0x06, 0x00, 0x01, "
+    "0x30, 0xf9, 0xad, 0xa0, 0x02, 0x00, 0x00, 0x03, 0xe9, 0x00, 0xfe, 0x07, 0x00, 0x12, 0x0f, "
+    "0x02, 0x07, 0x01, 0x00, 0xfe, 0x09, 0x00, 0x12, 0x0f, 0x01, 0x03, 0x6c, 0x00, 0x00, 0x10, "
+    "0xfe, 0x09, 0x00, 0x12, 0x0f, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x06, 0x00, 0x12, "
+    "0x0f, 0x04, 0x05, 0xf2, 0xfe, 0x06, 0x00, 0x80, 0xc2, 0x01, 0x01, 0xe8, 0xfe, 0x07, 0x00, "
+    "0x80, 0xc2, 0x02, 0x01, 0x00, 0x00, 0xfe, 0x16, 0x00, 0x80, 0xc2, 0x03, 0x01, 0xe8, 0x0f, "
+    "0x76, 0x32, 0x2d, 0x30, 0x34, 0x38, 0x38, 0x2d, 0x30, 0x33, 0x2d, 0x30, 0x35, 0x30, 0x35, "
+    "0xfe, 0x05, 0x00, 0x80, 0xc2, 0x04, 0x00, 0x00, 0x00]>, 'chassis-id-type': <uint32 4>, "
+    "'chassis-id': <'00:01:30:F9:AD:A0'>, 'port-id-type': <uint32 5>, 'port-id': <'1/1'>, "
+    "'destination': <'nearest-bridge'>, 'port-description': <'Summit300-48-Port 1001'>, "
+    "'system-name': <'Summit300-48'>, 'system-description': <'Summit300-48 - Version 7.4e.1 (Build "
+    "5) by Release_Master 05/27/05 04:53:11'>, 'system-capabilities': <uint32 20>, "
+    "'management-addresses': <[{'address-subtype': <uint32 6>, 'address': <[byte 0x00, 0x01, 0x30, "
+    "0xf9, 0xad, 0xa0]>, 'interface-number-subtype': <uint32 2>, 'interface-number': <uint32 "
+    "1001>}]>, 'ieee-802-1-pvid': <uint32 488>, 'ieee-802-1-ppvid': <uint32 0>, "
+    "'ieee-802-1-ppvid-flags': <uint32 1>, 'ieee-802-1-ppvids': <[{'ppvid': <uint32 0>, 'flags': "
+    "<uint32 1>}]>, 'ieee-802-1-vid': <uint32 488>, 'ieee-802-1-vlan-name': <'v2-0488-03-0505'>, "
+    "'ieee-802-1-vlans': <[{'vid': <uint32 488>, 'name': <'v2-0488-03-0505'>}]>, "
+    "'ieee-802-3-mac-phy-conf': <{'autoneg': <uint32 3>, 'pmd-autoneg-cap': <uint32 27648>, "
+    "'operational-mau-type': <uint32 16>}>, 'ieee-802-3-power-via-mdi': <{'mdi-power-support': "
+    "<uint32 7>, 'pse-power-pair': <uint32 1>, 'power-class': <uint32 0>}>, "
+    "'ieee-802-3-max-frame-size': <uint32 1522>}",
+    /* ethernet header */
+    0x01,
+    0x80,
+    0xc2,
+    0x00,
+    0x00,
+    0x0e, /* destination mac */
+    0x00,
+    0x01,
+    0x30,
+    0xf9,
+    0xad,
+    0xa0, /* source mac */
+    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,
+    0x16,
+    0x00,
+    0x80,
+    0xc2,
+    0x03, /* IEEE 802.1 - VLAN Name */
+    0x01,
+    0xe8,
+    0x0f,
+    0x76,
+    0x32,
+    0x2d,
+    0x30,
+    0x34,
+    0x38,
+    0x38,
+    0x2d,
+    0x30,
+    0x33,
+    0x2d,
+    0x30,
+    0x35,
+    0x30,
+    0x35,
+    0xfe,
+    0x05,
+    0x00,
+    0x80,
+    0xc2,
+    0x04, /* IEEE 802.1 - Protocol Identity */
+    0x00,
+    0x00,
+    0x00 /* End of LLDPDU */
+);
+
+static void
+_test_recv_data1_check(GMainLoop *loop, NMLldpListener *listener)
+{
+    GVariant *       neighbors, *attr, *child;
+    gs_unref_variant GVariant *neighbor = NULL;
+    guint                      v_uint   = 0;
+    const char *               v_str    = 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,
+                                 SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS,
+                                 "00:01:30:F9:AD:A0",
+                                 SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME,
+                                 "1/1");
+    g_assert(neighbor);
+    g_assert_cmpint(g_variant_n_children(neighbor), ==, 1 + 4 + 16);
+
+    attr = g_variant_lookup_value(neighbor, NM_LLDP_ATTR_RAW, G_VARIANT_TYPE_BYTESTRING);
+    nmtst_assert_variant_bytestring(attr,
+                                    _test_recv_data1_frame0.frame,
+                                    _test_recv_data1_frame0.frame_len);
+    nm_clear_g_variant(&attr);
+
+    attr = g_variant_lookup_value(neighbor, NM_LLDP_ATTR_DESTINATION, G_VARIANT_TYPE_STRING);
+    nmtst_assert_variant_string(attr, NM_LLDP_DEST_NEAREST_BRIDGE);
+    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);
+
+    /* Management Address */
+    attr = g_variant_lookup_value(neighbor,
+                                  NM_LLDP_ATTR_MANAGEMENT_ADDRESSES,
+                                  G_VARIANT_TYPE("aa{sv}"));
+    g_assert(attr);
+    g_assert_cmpuint(g_variant_n_children(attr), ==, 1);
+    child = g_variant_get_child_value(attr, 0);
+    g_assert(child);
+    g_assert(g_variant_lookup(child, "interface-number", "u", &v_uint));
+    g_assert_cmpint(v_uint, ==, 1001);
+    g_assert(g_variant_lookup(child, "interface-number-subtype", "u", &v_uint));
+    g_assert_cmpint(v_uint, ==, 2);
+    g_assert(g_variant_lookup(child, "address-subtype", "u", &v_uint));
+    g_assert_cmpint(v_uint, ==, 6);
+    nm_clear_g_variant(&child);
+    nm_clear_g_variant(&attr);
+
+    /* IEEE 802.3 - Power Via MDI */
+    attr = g_variant_lookup_value(neighbor,
+                                  NM_LLDP_ATTR_IEEE_802_3_POWER_VIA_MDI,
+                                  G_VARIANT_TYPE_VARDICT);
+    g_assert(attr);
+    g_assert(g_variant_lookup(attr, "mdi-power-support", "u", &v_uint));
+    g_assert_cmpint(v_uint, ==, 7);
+    g_assert(g_variant_lookup(attr, "pse-power-pair", "u", &v_uint));
+    g_assert_cmpint(v_uint, ==, 1);
+    g_assert(g_variant_lookup(attr, "power-class", "u", &v_uint));
+    g_assert_cmpint(v_uint, ==, 0);
+    nm_clear_g_variant(&attr);
+
+    /* IEEE 802.3 - MAC/PHY Configuration/Status */
+    attr = g_variant_lookup_value(neighbor,
+                                  NM_LLDP_ATTR_IEEE_802_3_MAC_PHY_CONF,
+                                  G_VARIANT_TYPE_VARDICT);
+    g_assert(attr);
+    g_assert(g_variant_lookup(attr, "autoneg", "u", &v_uint));
+    g_assert_cmpint(v_uint, ==, 3);
+    g_assert(g_variant_lookup(attr, "pmd-autoneg-cap", "u", &v_uint));
+    g_assert_cmpint(v_uint, ==, 0x6c00);
+    g_assert(g_variant_lookup(attr, "operational-mau-type", "u", &v_uint));
+    g_assert_cmpint(v_uint, ==, 16);
+    nm_clear_g_variant(&attr);
+
+    /* unsupported: IEEE 802.3 - Link Aggregation */
+
+    /* Maximum Frame Size */
+    attr = g_variant_lookup_value(neighbor,
+                                  NM_LLDP_ATTR_IEEE_802_3_MAX_FRAME_SIZE,
+                                  G_VARIANT_TYPE_UINT32);
+    nmtst_assert_variant_uint32(attr, 1522);
+    nm_clear_g_variant(&attr);
+
+    /* 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);
+
+    /* new PPVID attributes */
+    attr =
+        g_variant_lookup_value(neighbor, NM_LLDP_ATTR_IEEE_802_1_PPVIDS, G_VARIANT_TYPE("aa{sv}"));
+    g_assert_cmpuint(g_variant_n_children(attr), ==, 1);
+    child = g_variant_get_child_value(attr, 0);
+    g_assert(child);
+    g_assert(g_variant_lookup(child, "ppvid", "u", &v_uint));
+    g_assert_cmpint(v_uint, ==, 0);
+    g_assert(g_variant_lookup(child, "flags", "u", &v_uint));
+    g_assert_cmpint(v_uint, ==, 1);
+    nm_clear_g_variant(&child);
+    nm_clear_g_variant(&attr);
+
+    /* 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);
+
+    /* new VLAN attributes */
+    attr =
+        g_variant_lookup_value(neighbor, NM_LLDP_ATTR_IEEE_802_1_VLANS, G_VARIANT_TYPE("aa{sv}"));
+    g_assert_cmpuint(g_variant_n_children(attr), ==, 1);
+    child = g_variant_get_child_value(attr, 0);
+    g_assert(child);
+    g_assert(g_variant_lookup(child, "vid", "u", &v_uint));
+    g_assert_cmpint(v_uint, ==, 488);
+    g_assert(g_variant_lookup(child, "name", "&s", &v_str));
+    g_assert_cmpstr(v_str, ==, "v2-0488-03-0505");
+    nm_clear_g_variant(&child);
+    nm_clear_g_variant(&attr);
+
+    /* 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,
+    "{'raw': <[byte 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x88, "
+    "0xcc, 0x02, 0x07, 0x04, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x04, 0x04, 0x05, 0x31, 0x2f, "
+    "0x33, 0x06, 0x02, 0x00, 0x01, 0x08, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x0a, 0x03, 0x53, 0x59, "
+    "0x53, 0x0c, 0x04, 0x66, 0x6f, 0x6f, 0x00, 0x00, 0x00]>, 'chassis-id-type': <uint32 4>, "
+    "'chassis-id': <'00:01:02:03:04:05'>, 'port-id-type': <uint32 5>, 'port-id': <'1/3'>, "
+    "'destination': <'nearest-non-tpmr-bridge'>, 'port-description': <'Port'>, 'system-name': "
+    "<'SYS'>, 'system-description': <'foo'>}",
+    /* Ethernet header */
+    0x01,
+    0x80,
+    0xc2,
+    0x00,
+    0x00,
+    0x03, /* Destination MAC */
+    0x01,
+    0x02,
+    0x03,
+    0x04,
+    0x05,
+    0x06, /* Source MAC */
+    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_do(loop, listener, &_test_recv_data2_frame0_ttl1);
+
+    /* 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;
+    nm_auto_close int     fd = -1;
+
+    fd = open("/dev/net/tun", O_RDWR | O_CLOEXEC);
+    if (fd == -1) {
+        g_test_skip("Unable to open /dev/net/tun");
+        fixture->ifindex = 0;
+        return;
+    }
+
+    if (nmtst_get_rand_bool()) {
+        const NMPlatformLnkTun lnk = {
+            .type        = IFF_TAP,
+            .pi          = FALSE,
+            .vnet_hdr    = FALSE,
+            .multi_queue = FALSE,
+            .persist     = FALSE,
+        };
+
+        nm_close(nm_steal_fd(&fd));
+
+        link = nmtstp_link_tun_add(NM_PLATFORM_GET, FALSE, TEST_IFNAME, &lnk, &fd);
+        g_assert(link);
+        nmtstp_link_set_updown(NM_PLATFORM_GET, -1, link->ifindex, TRUE);
+        link = nmtstp_assert_wait_for_link(NM_PLATFORM_GET, TEST_IFNAME, NM_LINK_TYPE_TUN, 0);
+    } else {
+        int          s;
+        struct ifreq ifr;
+        int          r;
+        int          try_cnt = 0;
+
+again:
+        memset(&ifr, 0, sizeof(ifr));
+        ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
+        nm_utils_ifname_cpy(ifr.ifr_name, TEST_IFNAME);
+
+        r = ioctl(fd, TUNSETIFF, &ifr);
+        if (r != 0) {
+            if (errno == EPERM && try_cnt++ < 10) {
+                g_usleep(2);
+                goto again;
+            }
+            g_assert_cmpint(errno, ==, 0);
+            g_assert_cmpint(r, ==, 0);
+        }
+
+        /* Bring the interface up */
+        s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+        g_assert(s >= 0);
+
+        ifr.ifr_flags |= IFF_UP;
+        r = ioctl(s, SIOCSIFFLAGS, &ifr);
+        if (r != 0) {
+            g_assert_cmpint(errno, ==, 0);
+            g_assert_cmpint(r, ==, 0);
+        }
+
+        nm_close(s);
+
+        link = nmtstp_assert_wait_for_link(NM_PLATFORM_GET, TEST_IFNAME, NM_LINK_TYPE_TUN, 100);
+    }
+
+    fixture->ifindex = link->ifindex;
+    fixture->fd      = nm_steal_fd(&fd);
+    memcpy(fixture->mac, link->l_address.data, ETH_ALEN);
+}
+
+typedef struct {
+    int num_called;
+} TestRecvCallbackInfo;
+
+static void
+lldp_neighbors_changed(NMLldpListener *lldp_listener, GParamSpec *pspec, gpointer user_data)
+{
+    TestRecvCallbackInfo *info = user_data;
+
+    info->num_called++;
+}
+
+static void
+test_recv(TestRecvFixture *fixture, gconstpointer user_data)
+{
+    const TestRecvData *data                 = user_data;
+    gs_unref_object NMLldpListener *listener = NULL;
+    GMainLoop *                     loop;
+    TestRecvCallbackInfo            info = {};
+    gsize                           i_frames;
+    gulong                          notify_id;
+    GError *                        error = NULL;
+    guint                           sd_id;
+
+    if (fixture->ifindex == 0) {
+        g_test_skip("Tun device not available");
+        return;
+    }
+
+    listener = nm_lldp_listener_new();
+    g_assert(listener != NULL);
+    g_assert(nm_lldp_listener_start(listener, fixture->ifindex, &error));
+    g_assert_no_error(error);
+
+    notify_id = g_signal_connect(listener,
+                                 "notify::" NM_LLDP_LISTENER_NEIGHBORS,
+                                 (GCallback) lldp_neighbors_changed,
+                                 &info);
+    loop      = g_main_loop_new(NULL, FALSE);
+    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, f->frame, f->frame_len) == f->frame_len);
+    }
+
+    if (nmtst_main_loop_run(loop, 500))
+        g_assert_not_reached();
+
+    g_assert_cmpint(info.num_called, ==, data->expected_num_called);
+
+    nm_clear_g_signal_handler(listener, &notify_id);
+
+    data->check(loop, listener);
+
+    nm_clear_g_source(&sd_id);
+    nm_clear_pointer(&loop, g_main_loop_unref);
+}
+
+static void
+_test_recv_fixture_teardown(TestRecvFixture *fixture, gconstpointer user_data)
+{
+    if (fixture->ifindex)
+        nm_platform_link_delete(NM_PLATFORM_GET, fixture->ifindex);
+}
+
+/*****************************************************************************/
+
+static void
+test_parse_frames(gconstpointer test_data)
+{
+    const TestRecvFrame *frame            = test_data;
+    gs_unref_variant GVariant *v_neighbor = NULL;
+    gs_unref_variant GVariant *attr       = NULL;
+    gs_free char *             as_variant = NULL;
+
+    v_neighbor = nmtst_lldp_parse_from_raw(frame->frame, frame->frame_len);
+    g_assert(v_neighbor);
+
+    attr = g_variant_lookup_value(v_neighbor, NM_LLDP_ATTR_RAW, G_VARIANT_TYPE_BYTESTRING);
+    nmtst_assert_variant_bytestring(attr, frame->frame, frame->frame_len);
+    nm_clear_g_variant(&attr);
+
+    as_variant = g_variant_print(v_neighbor, TRUE);
+    g_assert(as_variant);
+    g_assert_cmpstr(frame->as_variant, ==, as_variant);
+}
+
+/*****************************************************************************/
+
+TEST_RECV_FRAME_DEFINE(
+    _test_parse_frames_3,
+    /* https://github.com/the-tcpdump-group/tcpdump/blob/c4f8796bf8bec740621a360eded236d8991ea00f/tests/lldp_mudurl.pcap */
+    "{'raw': <[byte 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e, 0x00, 0x23, 0x54, 0xc2, 0x57, 0x02, 0x88, "
+    "0xcc, 0x02, 0x07, 0x04, 0x00, 0x23, 0x54, 0xc2, 0x57, 0x02, 0x04, 0x07, 0x03, 0x00, 0x23, "
+    "0x54, 0xc2, 0x57, 0x02, 0x06, 0x02, 0x00, 0x78, 0x0a, 0x1c, 0x75, 0x70, 0x73, 0x74, 0x61, "
+    "0x69, 0x72, 0x73, 0x2e, 0x6f, 0x66, 0x63, 0x6f, 0x75, 0x72, 0x73, 0x65, 0x69, 0x6d, 0x72, "
+    "0x69, 0x67, 0x68, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x0c, 0x5c, 0x55, 0x62, 0x75, 0x6e, 0x74, "
+    "0x75, 0x20, 0x31, 0x34, 0x2e, 0x30, 0x34, 0x2e, 0x35, 0x20, 0x4c, 0x54, 0x53, 0x20, 0x4c, "
+    "0x69, 0x6e, 0x75, 0x78, 0x20, 0x33, 0x2e, 0x31, 0x33, 0x2e, 0x30, 0x2d, 0x31, 0x30, 0x36, "
+    "0x2d, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x20, 0x23, 0x31, 0x35, 0x33, 0x2d, 0x55, "
+    "0x62, 0x75, 0x6e, 0x74, 0x75, 0x20, 0x53, 0x4d, 0x50, 0x20, 0x54, 0x75, 0x65, 0x20, 0x44, "
+    "0x65, 0x63, 0x20, 0x36, 0x20, 0x31, 0x35, 0x3a, 0x34, 0x35, 0x3a, 0x31, 0x33, 0x20, 0x55, "
+    "0x54, 0x43, 0x20, 0x32, 0x30, 0x31, 0x36, 0x20, 0x69, 0x36, 0x38, 0x36, 0x0e, 0x04, 0x00, "
+    "0x9c, 0x00, 0x08, 0x10, 0x0c, 0x05, 0x01, 0x3e, 0x0c, 0xad, 0x72, 0x02, 0x00, 0x00, 0x00, "
+    "0x02, 0x00, 0x10, 0x18, 0x11, 0x02, 0x20, 0x01, 0x08, 0xa8, 0x10, 0x06, 0x00, 0x04, 0x02, "
+    "0x23, 0x54, 0xff, 0xfe, 0xc2, 0x57, 0x02, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x08, 0x04, "
+    "0x65, 0x74, 0x68, 0x30, 0xfe, 0x09, 0x00, 0x12, 0x0f, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, "
+    "0xfe, 0x09, 0x00, 0x12, 0x0f, 0x01, 0x03, 0xec, 0xc3, 0x00, 0x10, 0xfe, 0x40, 0x00, 0x00, "
+    "0x5e, 0x01, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x69, 0x6d, 0x72, 0x69, 0x67, "
+    "0x68, 0x74, 0x2e, 0x6d, 0x75, 0x64, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, "
+    "0x63, 0x6f, 0x6d, 0x2f, 0x2e, 0x77, 0x65, 0x6c, 0x6c, 0x2d, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, "
+    "0x2f, 0x6d, 0x75, 0x64, 0x2f, 0x76, 0x31, 0x2f, 0x76, 0x6f, 0x6d, 0x69, 0x74, 0x76, 0x32, "
+    "0x2e, 0x30, 0x00, 0x00]>, 'chassis-id-type': <uint32 4>, 'chassis-id': <'00:23:54:C2:57:02'>, "
+    "'port-id-type': <uint32 3>, 'port-id': <'00:23:54:C2:57:02'>, 'destination': "
+    "<'nearest-bridge'>, 'port-description': <'eth0'>, 'system-name': "
+    "<'upstairs.ofcourseimright.com'>, 'system-description': <'Ubuntu 14.04.5 LTS Linux "
+    "3.13.0-106-generic #153-Ubuntu SMP Tue Dec 6 15:45:13 UTC 2016 i686'>, 'system-capabilities': "
+    "<uint32 156>, 'management-addresses': <[{'address-subtype': <uint32 1>, 'address': <[byte "
+    "0x3e, 0x0c, 0xad, 0x72]>, 'interface-number-subtype': <uint32 2>, 'interface-number': <uint32 "
+    "2>}, {'address-subtype': <uint32 2>, 'address': <[byte 0x20, 0x01, 0x08, 0xa8, 0x10, 0x06, "
+    "0x00, 0x04, 0x02, 0x23, 0x54, 0xff, 0xfe, 0xc2, 0x57, 0x02]>, 'interface-number-subtype': "
+    "<uint32 2>, 'interface-number': <uint32 2>}]>, 'ieee-802-3-mac-phy-conf': <{'autoneg': "
+    "<uint32 3>, 'pmd-autoneg-cap': <uint32 60611>, 'operational-mau-type': <uint32 16>}>, "
+    "'mud-url': <'https://imright.mud.example.com/.well-known/mud/v1/vomitv2.0'>}",
+    0x01,
+    0x80,
+    0xc2,
+    0x00,
+    0x00,
+    0x0e, /* ethernet destination */
+    0x00,
+    0x23,
+    0x54,
+    0xc2,
+    0x57,
+    0x02, /* ethernet source */
+    0x88,
+    0xcc, /* ethernet type */
+
+    0x02,
+    0x07,
+    0x04,
+    0x00,
+    0x23,
+    0x54,
+    0xc2,
+    0x57,
+    0x02,
+    0x04,
+    0x07,
+    0x03,
+    0x00,
+    0x23,
+    0x54,
+    0xc2,
+    0x57,
+    0x02,
+    0x06,
+    0x02,
+    0x00,
+    0x78,
+    0x0a,
+    0x1c,
+    0x75,
+    0x70,
+    0x73,
+    0x74,
+    0x61,
+    0x69,
+    0x72,
+    0x73,
+    0x2e,
+    0x6f,
+    0x66,
+    0x63,
+    0x6f,
+    0x75,
+    0x72,
+    0x73,
+    0x65,
+    0x69,
+    0x6d,
+    0x72,
+    0x69,
+    0x67,
+    0x68,
+    0x74,
+    0x2e,
+    0x63,
+    0x6f,
+    0x6d,
+    0x0c,
+    0x5c,
+    0x55,
+    0x62,
+    0x75,
+    0x6e,
+    0x74,
+    0x75,
+    0x20,
+    0x31,
+    0x34,
+    0x2e,
+    0x30,
+    0x34,
+    0x2e,
+    0x35,
+    0x20,
+    0x4c,
+    0x54,
+    0x53,
+    0x20,
+    0x4c,
+    0x69,
+    0x6e,
+    0x75,
+    0x78,
+    0x20,
+    0x33,
+    0x2e,
+    0x31,
+    0x33,
+    0x2e,
+    0x30,
+    0x2d,
+    0x31,
+    0x30,
+    0x36,
+    0x2d,
+    0x67,
+    0x65,
+    0x6e,
+    0x65,
+    0x72,
+    0x69,
+    0x63,
+    0x20,
+    0x23,
+    0x31,
+    0x35,
+    0x33,
+    0x2d,
+    0x55,
+    0x62,
+    0x75,
+    0x6e,
+    0x74,
+    0x75,
+    0x20,
+    0x53,
+    0x4d,
+    0x50,
+    0x20,
+    0x54,
+    0x75,
+    0x65,
+    0x20,
+    0x44,
+    0x65,
+    0x63,
+    0x20,
+    0x36,
+    0x20,
+    0x31,
+    0x35,
+    0x3a,
+    0x34,
+    0x35,
+    0x3a,
+    0x31,
+    0x33,
+    0x20,
+    0x55,
+    0x54,
+    0x43,
+    0x20,
+    0x32,
+    0x30,
+    0x31,
+    0x36,
+    0x20,
+    0x69,
+    0x36,
+    0x38,
+    0x36,
+    0x0e,
+    0x04,
+    0x00,
+    0x9c,
+    0x00,
+    0x08,
+    0x10,
+    0x0c,
+    0x05,
+    0x01,
+    0x3e,
+    0x0c,
+    0xad,
+    0x72,
+    0x02,
+    0x00,
+    0x00,
+    0x00,
+    0x02,
+    0x00,
+    0x10,
+    0x18,
+    0x11,
+    0x02,
+    0x20,
+    0x01,
+    0x08,
+    0xa8,
+    0x10,
+    0x06,
+    0x00,
+    0x04,
+    0x02,
+    0x23,
+    0x54,
+    0xff,
+    0xfe,
+    0xc2,
+    0x57,
+    0x02,
+    0x02,
+    0x00,
+    0x00,
+    0x00,
+    0x02,
+    0x00,
+    0x08,
+    0x04,
+    0x65,
+    0x74,
+    0x68,
+    0x30,
+    0xfe,
+    0x09,
+    0x00,
+    0x12,
+    0x0f,
+    0x03,
+    0x01,
+    0x00,
+    0x00,
+    0x00,
+    0x00,
+    0xfe,
+    0x09,
+    0x00,
+    0x12,
+    0x0f,
+    0x01,
+    0x03,
+    0xec,
+    0xc3,
+    0x00,
+    0x10,
+    0xfe,
+    0x40,
+    0x00,
+    0x00,
+    0x5e,
+    0x01,
+    0x68,
+    0x74,
+    0x74,
+    0x70,
+    0x73,
+    0x3a,
+    0x2f,
+    0x2f,
+    0x69,
+    0x6d,
+    0x72,
+    0x69,
+    0x67,
+    0x68,
+    0x74,
+    0x2e,
+    0x6d,
+    0x75,
+    0x64,
+    0x2e,
+    0x65,
+    0x78,
+    0x61,
+    0x6d,
+    0x70,
+    0x6c,
+    0x65,
+    0x2e,
+    0x63,
+    0x6f,
+    0x6d,
+    0x2f,
+    0x2e,
+    0x77,
+    0x65,
+    0x6c,
+    0x6c,
+    0x2d,
+    0x6b,
+    0x6e,
+    0x6f,
+    0x77,
+    0x6e,
+    0x2f,
+    0x6d,
+    0x75,
+    0x64,
+    0x2f,
+    0x76,
+    0x31,
+    0x2f,
+    0x76,
+    0x6f,
+    0x6d,
+    0x69,
+    0x74,
+    0x76,
+    0x32,
+    0x2e,
+
+    0x30,
+    0x00,
+    0x00, /* ethernet trailer */
+);
+
+/*****************************************************************************/
+
+NMTstpSetupFunc const _nmtstp_setup_platform_func = nm_linux_platform_setup;
+
+void
+_nmtstp_init_tests(int *argc, char ***argv)
+{
+    nmtst_init_assert_logging(argc, argv, "WARN", "ALL");
+}
+
+void
+_nmtstp_setup_tests(void)
+{
+#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);
+
+    g_test_add_data_func("/lldp/parse-frames/0", &_test_recv_data0_frame0, test_parse_frames);
+    g_test_add_data_func("/lldp/parse-frames/1", &_test_recv_data1_frame0, test_parse_frames);
+    g_test_add_data_func("/lldp/parse-frames/2", &_test_recv_data2_frame0_ttl1, test_parse_frames);
+    g_test_add_data_func("/lldp/parse-frames/3", &_test_parse_frames_3, test_parse_frames);
+}