Build lldp-systemd-networkd-sysrepod

Change-Id: I2c1efc2729d4a84fad4d38dd7048801e39423cde
Depends-on: https://cesnet-gerrit-czechlight/c/CzechLight/lldp-systemd-networkd-sysrepo/+/3117
Depends-on: https://cesnet-gerrit-public/c/CzechLight/lldp-systemd-networkd-sysrepo/+/3117
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/lldp-systemd-networkd-sysrepo/+/3117
diff --git a/.gitmodules b/.gitmodules
index b249b40..669c57f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -13,3 +13,6 @@
 [submodule "submodules/velia"]
 	path = submodules/velia
 	url = ../velia/
+[submodule "submodules/lldp-systemd-networkd-sysrepo"]
+	path = submodules/lldp-systemd-networkd-sysrepo
+	url = ../lldp-systemd-networkd-sysrepo
diff --git a/.zuul.yaml b/.zuul.yaml
index d5c7540..a127ae3 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -13,6 +13,7 @@
       - CzechLight/gammarus
       - CzechLight/netconf-cli
       - CzechLight/velia
+      - CzechLight/lldp-systemd-networkd-sysrepo
       - github/CESNET/libyang
       - github/sysrepo/libredblack
       - github/sysrepo/sysrepo
diff --git a/board/czechlight/common/patches/systemd/sd_lldp.patch b/board/czechlight/common/patches/systemd/sd_lldp.patch
new file mode 100644
index 0000000..ca32358
--- /dev/null
+++ b/board/czechlight/common/patches/systemd/sd_lldp.patch
@@ -0,0 +1,4023 @@
+From 9b8b7ba40accbd303a3378d75022d6d92a2b7771 Mon Sep 17 00:00:00 2001
+From: Tomas Pecka <peckato1@fit.cvut.cz>
+Date: Mon, 7 Sep 2020 18:26:47 +0200
+Subject: [PATCH] Move sd_lldp to libsystemd
+
+Make sd_lldp public in libsystemd. Clients can therefore operate
+with LLDP structures and files (e.g. parse LLDP neighbours).
+---
+ meson.build                            |   1 +
+ src/fuzz/meson.build                   |   9 +-
+ src/libsystemd-network/lldp-internal.h |  39 --
+ src/libsystemd-network/lldp-neighbor.c | 769 -------------------------
+ src/libsystemd-network/lldp-neighbor.h |  91 ---
+ src/libsystemd-network/lldp-network.c  |  78 ---
+ src/libsystemd-network/lldp-network.h  |   6 -
+ src/libsystemd-network/meson.build     |   6 -
+ src/libsystemd-network/sd-lldp.c       | 498 ----------------
+ src/libsystemd-network/test-lldp.c     | 379 ------------
+ src/libsystemd/libsystemd.sym          |  41 ++
+ src/libsystemd/meson.build             |   6 +
+ src/libsystemd/sd-lldp/lldp-internal.h |  39 ++
+ src/libsystemd/sd-lldp/lldp-neighbor.c | 769 +++++++++++++++++++++++++
+ src/libsystemd/sd-lldp/lldp-neighbor.h |  91 +++
+ src/libsystemd/sd-lldp/lldp-network.c  |  78 +++
+ src/libsystemd/sd-lldp/lldp-network.h  |   6 +
+ src/libsystemd/sd-lldp/sd-lldp.c       | 498 ++++++++++++++++
+ src/libsystemd/sd-lldp/test-lldp.c     | 379 ++++++++++++
+ src/systemd/meson.build                |   2 +-
+ src/test/meson.build                   |  10 +-
+ 21 files changed, 1919 insertions(+), 1876 deletions(-)
+ delete mode 100644 src/libsystemd-network/lldp-internal.h
+ delete mode 100644 src/libsystemd-network/lldp-neighbor.c
+ delete mode 100644 src/libsystemd-network/lldp-neighbor.h
+ delete mode 100644 src/libsystemd-network/lldp-network.c
+ delete mode 100644 src/libsystemd-network/lldp-network.h
+ delete mode 100644 src/libsystemd-network/sd-lldp.c
+ delete mode 100644 src/libsystemd-network/test-lldp.c
+ create mode 100644 src/libsystemd/sd-lldp/lldp-internal.h
+ create mode 100644 src/libsystemd/sd-lldp/lldp-neighbor.c
+ create mode 100644 src/libsystemd/sd-lldp/lldp-neighbor.h
+ create mode 100644 src/libsystemd/sd-lldp/lldp-network.c
+ create mode 100644 src/libsystemd/sd-lldp/lldp-network.h
+ create mode 100644 src/libsystemd/sd-lldp/sd-lldp.c
+ create mode 100644 src/libsystemd/sd-lldp/test-lldp.c
+
+diff --git a/meson.build b/meson.build
+index 8ccc947e37..1c2099d093 100644
+--- a/meson.build
++++ b/meson.build
+@@ -1398,6 +1398,7 @@ includes = include_directories('src/basic',
+                                'src/libsystemd/sd-event',
+                                'src/libsystemd/sd-hwdb',
+                                'src/libsystemd/sd-id128',
++                               'src/libsystemd/sd-lldp',
+                                'src/libsystemd/sd-netlink',
+                                'src/libsystemd/sd-network',
+                                'src/libsystemd/sd-resolve',
+diff --git a/src/fuzz/meson.build b/src/fuzz/meson.build
+index c88812d1de..8171bf58c1 100644
+--- a/src/fuzz/meson.build
++++ b/src/fuzz/meson.build
+@@ -33,8 +33,8 @@ fuzzers += [
+          []],
+ 
+         [['src/fuzz/fuzz-lldp.c'],
+-         [libshared,
+-          libsystemd_network],
++         [libbasic,libshared_static,libsystemd_static
++          ],
+          []],
+ 
+         [['src/fuzz/fuzz-ndisc-rs.c',
+@@ -43,8 +43,9 @@ fuzzers += [
+           'src/libsystemd-network/icmp6-util.h',
+           'src/systemd/sd-dhcp6-client.h',
+           'src/systemd/sd-ndisc.h'],
+-         [libshared,
+-          libsystemd_network],
++         [libsystemd_network,
++          libshared
++          ],
+          []],
+ 
+         [['src/fuzz/fuzz-json.c'],
+diff --git a/src/libsystemd-network/lldp-internal.h b/src/libsystemd-network/lldp-internal.h
+deleted file mode 100644
+index 9598438dba..0000000000
+--- a/src/libsystemd-network/lldp-internal.h
++++ /dev/null
+@@ -1,39 +0,0 @@
+-/* SPDX-License-Identifier: LGPL-2.1+ */
+-#pragma once
+-
+-#include "sd-event.h"
+-#include "sd-lldp.h"
+-
+-#include "hashmap.h"
+-#include "log.h"
+-#include "prioq.h"
+-
+-struct sd_lldp {
+-        unsigned n_ref;
+-
+-        int ifindex;
+-        int fd;
+-
+-        sd_event *event;
+-        int64_t event_priority;
+-        sd_event_source *io_event_source;
+-        sd_event_source *timer_event_source;
+-
+-        Prioq *neighbor_by_expiry;
+-        Hashmap *neighbor_by_id;
+-
+-        uint64_t neighbors_max;
+-
+-        sd_lldp_callback_t callback;
+-        void *userdata;
+-
+-        uint16_t capability_mask;
+-
+-        struct ether_addr filter_address;
+-};
+-
+-#define log_lldp_errno(error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "LLDP: " fmt, ##__VA_ARGS__)
+-#define log_lldp(fmt, ...) log_lldp_errno(0, fmt, ##__VA_ARGS__)
+-
+-const char* lldp_event_to_string(sd_lldp_event e) _const_;
+-sd_lldp_event lldp_event_from_string(const char *s) _pure_;
+diff --git a/src/libsystemd-network/lldp-neighbor.c b/src/libsystemd-network/lldp-neighbor.c
+deleted file mode 100644
+index 9bae4a3c6e..0000000000
+--- a/src/libsystemd-network/lldp-neighbor.c
++++ /dev/null
+@@ -1,769 +0,0 @@
+-/* SPDX-License-Identifier: LGPL-2.1+ */
+-
+-#include "alloc-util.h"
+-#include "escape.h"
+-#include "ether-addr-util.h"
+-#include "hexdecoct.h"
+-#include "in-addr-util.h"
+-#include "lldp-internal.h"
+-#include "lldp-neighbor.h"
+-#include "memory-util.h"
+-#include "missing.h"
+-#include "unaligned.h"
+-
+-static void lldp_neighbor_id_hash_func(const LLDPNeighborID *id, struct siphash *state) {
+-        siphash24_compress(id->chassis_id, id->chassis_id_size, state);
+-        siphash24_compress(&id->chassis_id_size, sizeof(id->chassis_id_size), state);
+-        siphash24_compress(id->port_id, id->port_id_size, state);
+-        siphash24_compress(&id->port_id_size, sizeof(id->port_id_size), state);
+-}
+-
+-int lldp_neighbor_id_compare_func(const LLDPNeighborID *x, const LLDPNeighborID *y) {
+-        return memcmp_nn(x->chassis_id, x->chassis_id_size, y->chassis_id, y->chassis_id_size)
+-            ?: memcmp_nn(x->port_id, x->port_id_size, y->port_id, y->port_id_size);
+-}
+-
+-DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(lldp_neighbor_hash_ops, LLDPNeighborID, lldp_neighbor_id_hash_func, lldp_neighbor_id_compare_func,
+-                                      sd_lldp_neighbor, lldp_neighbor_unlink);
+-
+-int lldp_neighbor_prioq_compare_func(const void *a, const void *b) {
+-        const sd_lldp_neighbor *x = a, *y = b;
+-
+-        return CMP(x->until, y->until);
+-}
+-
+-_public_ sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n) {
+-        if (!n)
+-                return NULL;
+-
+-        assert(n->n_ref > 0 || n->lldp);
+-        n->n_ref++;
+-
+-        return n;
+-}
+-
+-static void lldp_neighbor_free(sd_lldp_neighbor *n) {
+-        assert(n);
+-
+-        free(n->id.port_id);
+-        free(n->id.chassis_id);
+-        free(n->port_description);
+-        free(n->system_name);
+-        free(n->system_description);
+-        free(n->chassis_id_as_string);
+-        free(n->port_id_as_string);
+-        free(n);
+-}
+-
+-_public_ sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n) {
+-
+-        /* Drops one reference from the neighbor. Note that the object is not freed unless it is already unlinked from
+-         * the sd_lldp object. */
+-
+-        if (!n)
+-                return NULL;
+-
+-        assert(n->n_ref > 0);
+-        n->n_ref--;
+-
+-        if (n->n_ref <= 0 && !n->lldp)
+-                lldp_neighbor_free(n);
+-
+-        return NULL;
+-}
+-
+-sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n) {
+-
+-        /* Removes the neighbor object from the LLDP object, and frees it if it also has no other reference. */
+-
+-        if (!n)
+-                return NULL;
+-
+-        if (!n->lldp)
+-                return NULL;
+-
+-        /* Only remove the neighbor object from the hash table if it's in there, don't complain if it isn't. This is
+-         * because we are used as destructor call for hashmap_clear() and thus sometimes are called to de-register
+-         * ourselves from the hashtable and sometimes are called after we already are de-registered. */
+-
+-        (void) hashmap_remove_value(n->lldp->neighbor_by_id, &n->id, n);
+-
+-        assert_se(prioq_remove(n->lldp->neighbor_by_expiry, n, &n->prioq_idx) >= 0);
+-
+-        n->lldp = NULL;
+-
+-        if (n->n_ref <= 0)
+-                lldp_neighbor_free(n);
+-
+-        return NULL;
+-}
+-
+-sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size) {
+-        sd_lldp_neighbor *n;
+-
+-        n = malloc0(ALIGN(sizeof(sd_lldp_neighbor)) + raw_size);
+-        if (!n)
+-                return NULL;
+-
+-        n->raw_size = raw_size;
+-        n->n_ref = 1;
+-
+-        return n;
+-}
+-
+-static int parse_string(char **s, const void *q, size_t n) {
+-        const char *p = q;
+-        char *k;
+-
+-        assert(s);
+-        assert(p || n == 0);
+-
+-        if (*s) {
+-                log_lldp("Found duplicate string, ignoring field.");
+-                return 0;
+-        }
+-
+-        /* Strip trailing NULs, just to be nice */
+-        while (n > 0 && p[n-1] == 0)
+-                n--;
+-
+-        if (n <= 0) /* Ignore empty strings */
+-                return 0;
+-
+-        /* Look for inner NULs */
+-        if (memchr(p, 0, n)) {
+-                log_lldp("Found inner NUL in string, ignoring field.");
+-                return 0;
+-        }
+-
+-        /* Let's escape weird chars, for security reasons */
+-        k = cescape_length(p, n);
+-        if (!k)
+-                return -ENOMEM;
+-
+-        free(*s);
+-        *s = k;
+-
+-        return 1;
+-}
+-
+-int lldp_neighbor_parse(sd_lldp_neighbor *n) {
+-        struct ether_header h;
+-        const uint8_t *p;
+-        size_t left;
+-        int r;
+-
+-        assert(n);
+-
+-        if (n->raw_size < sizeof(struct ether_header)) {
+-                log_lldp("Received truncated packet, ignoring.");
+-                return -EBADMSG;
+-        }
+-
+-        memcpy(&h, LLDP_NEIGHBOR_RAW(n), sizeof(h));
+-
+-        if (h.ether_type != htobe16(ETHERTYPE_LLDP)) {
+-                log_lldp("Received packet with wrong type, ignoring.");
+-                return -EBADMSG;
+-        }
+-
+-        if (h.ether_dhost[0] != 0x01 ||
+-            h.ether_dhost[1] != 0x80 ||
+-            h.ether_dhost[2] != 0xc2 ||
+-            h.ether_dhost[3] != 0x00 ||
+-            h.ether_dhost[4] != 0x00 ||
+-            !IN_SET(h.ether_dhost[5], 0x00, 0x03, 0x0e)) {
+-                log_lldp("Received packet with wrong destination address, ignoring.");
+-                return -EBADMSG;
+-        }
+-
+-        memcpy(&n->source_address, h.ether_shost, sizeof(struct ether_addr));
+-        memcpy(&n->destination_address, h.ether_dhost, sizeof(struct ether_addr));
+-
+-        p = (const uint8_t*) LLDP_NEIGHBOR_RAW(n) + sizeof(struct ether_header);
+-        left = n->raw_size - sizeof(struct ether_header);
+-
+-        for (;;) {
+-                uint8_t type;
+-                uint16_t length;
+-
+-                if (left < 2) {
+-                        log_lldp("TLV lacks header, ignoring.");
+-                        return -EBADMSG;
+-                }
+-
+-                type = p[0] >> 1;
+-                length = p[1] + (((uint16_t) (p[0] & 1)) << 8);
+-                p += 2, left -= 2;
+-
+-                if (left < length) {
+-                        log_lldp("TLV truncated, ignoring datagram.");
+-                        return -EBADMSG;
+-                }
+-
+-                switch (type) {
+-
+-                case SD_LLDP_TYPE_END:
+-                        if (length != 0) {
+-                                log_lldp("End marker TLV not zero-sized, ignoring datagram.");
+-                                return -EBADMSG;
+-                        }
+-
+-                        /* Note that after processing the SD_LLDP_TYPE_END left could still be > 0
+-                         * as the message may contain padding (see IEEE 802.1AB-2016, sec. 8.5.12) */
+-
+-                        goto end_marker;
+-
+-                case SD_LLDP_TYPE_CHASSIS_ID:
+-                        if (length < 2 || length > 256) { /* includes the chassis subtype, hence one extra byte */
+-                                log_lldp("Chassis ID field size out of range, ignoring datagram.");
+-                                return -EBADMSG;
+-                        }
+-                        if (n->id.chassis_id) {
+-                                log_lldp("Duplicate chassis ID field, ignoring datagram.");
+-                                return -EBADMSG;
+-                        }
+-
+-                        n->id.chassis_id = memdup(p, length);
+-                        if (!n->id.chassis_id)
+-                                return -ENOMEM;
+-
+-                        n->id.chassis_id_size = length;
+-                        break;
+-
+-                case SD_LLDP_TYPE_PORT_ID:
+-                        if (length < 2 || length > 256) { /* includes the port subtype, hence one extra byte */
+-                                log_lldp("Port ID field size out of range, ignoring datagram.");
+-                                return -EBADMSG;
+-                        }
+-                        if (n->id.port_id) {
+-                                log_lldp("Duplicate port ID field, ignoring datagram.");
+-                                return -EBADMSG;
+-                        }
+-
+-                        n->id.port_id = memdup(p, length);
+-                        if (!n->id.port_id)
+-                                return -ENOMEM;
+-
+-                        n->id.port_id_size = length;
+-                        break;
+-
+-                case SD_LLDP_TYPE_TTL:
+-                        if (length != 2) {
+-                                log_lldp("TTL field has wrong size, ignoring datagram.");
+-                                return -EBADMSG;
+-                        }
+-
+-                        if (n->has_ttl) {
+-                                log_lldp("Duplicate TTL field, ignoring datagram.");
+-                                return -EBADMSG;
+-                        }
+-
+-                        n->ttl = unaligned_read_be16(p);
+-                        n->has_ttl = true;
+-                        break;
+-
+-                case SD_LLDP_TYPE_PORT_DESCRIPTION:
+-                        r = parse_string(&n->port_description, p, length);
+-                        if (r < 0)
+-                                return r;
+-                        break;
+-
+-                case SD_LLDP_TYPE_SYSTEM_NAME:
+-                        r = parse_string(&n->system_name, p, length);
+-                        if (r < 0)
+-                                return r;
+-                        break;
+-
+-                case SD_LLDP_TYPE_SYSTEM_DESCRIPTION:
+-                        r = parse_string(&n->system_description, p, length);
+-                        if (r < 0)
+-                                return r;
+-                        break;
+-
+-                case SD_LLDP_TYPE_SYSTEM_CAPABILITIES:
+-                        if (length != 4)
+-                                log_lldp("System capabilities field has wrong size, ignoring.");
+-                        else {
+-                                n->system_capabilities = unaligned_read_be16(p);
+-                                n->enabled_capabilities = unaligned_read_be16(p + 2);
+-                                n->has_capabilities = true;
+-                        }
+-
+-                        break;
+-
+-                case SD_LLDP_TYPE_PRIVATE:
+-                        if (length < 4)
+-                                log_lldp("Found private TLV that is too short, ignoring.");
+-
+-                        break;
+-                }
+-
+-                p += length, left -= length;
+-        }
+-
+-end_marker:
+-        if (!n->id.chassis_id || !n->id.port_id || !n->has_ttl) {
+-                log_lldp("One or more mandatory TLV missing in datagram. Ignoring.");
+-                return -EBADMSG;
+-
+-        }
+-
+-        n->rindex = sizeof(struct ether_header);
+-
+-        return 0;
+-}
+-
+-void lldp_neighbor_start_ttl(sd_lldp_neighbor *n) {
+-        assert(n);
+-
+-        if (n->ttl > 0) {
+-                usec_t base;
+-
+-                /* Use the packet's timestamp if there is one known */
+-                base = triple_timestamp_by_clock(&n->timestamp, clock_boottime_or_monotonic());
+-                if (base <= 0 || base == USEC_INFINITY)
+-                        base = now(clock_boottime_or_monotonic()); /* Otherwise, take the current time */
+-
+-                n->until = usec_add(base, n->ttl * USEC_PER_SEC);
+-        } else
+-                n->until = 0;
+-
+-        if (n->lldp)
+-                prioq_reshuffle(n->lldp->neighbor_by_expiry, n, &n->prioq_idx);
+-}
+-
+-bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b) {
+-        if (a == b)
+-                return true;
+-
+-        if (!a || !b)
+-                return false;
+-
+-        if (a->raw_size != b->raw_size)
+-                return false;
+-
+-        return memcmp(LLDP_NEIGHBOR_RAW(a), LLDP_NEIGHBOR_RAW(b), a->raw_size) == 0;
+-}
+-
+-_public_ int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address) {
+-        assert_return(n, -EINVAL);
+-        assert_return(address, -EINVAL);
+-
+-        *address = n->source_address;
+-        return 0;
+-}
+-
+-_public_ int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address) {
+-        assert_return(n, -EINVAL);
+-        assert_return(address, -EINVAL);
+-
+-        *address = n->destination_address;
+-        return 0;
+-}
+-
+-_public_ int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) {
+-        assert_return(n, -EINVAL);
+-        assert_return(ret, -EINVAL);
+-        assert_return(size, -EINVAL);
+-
+-        *ret = LLDP_NEIGHBOR_RAW(n);
+-        *size = n->raw_size;
+-
+-        return 0;
+-}
+-
+-_public_ int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) {
+-        assert_return(n, -EINVAL);
+-        assert_return(type, -EINVAL);
+-        assert_return(ret, -EINVAL);
+-        assert_return(size, -EINVAL);
+-
+-        assert(n->id.chassis_id_size > 0);
+-
+-        *type = *(uint8_t*) n->id.chassis_id;
+-        *ret = (uint8_t*) n->id.chassis_id + 1;
+-        *size = n->id.chassis_id_size - 1;
+-
+-        return 0;
+-}
+-
+-static int format_mac_address(const void *data, size_t sz, char **ret) {
+-        struct ether_addr a;
+-        char *k;
+-
+-        assert(data || sz <= 0);
+-
+-        if (sz != 7)
+-                return 0;
+-
+-        memcpy(&a, (uint8_t*) data + 1, sizeof(a));
+-
+-        k = new(char, ETHER_ADDR_TO_STRING_MAX);
+-        if (!k)
+-                return -ENOMEM;
+-
+-        *ret = ether_addr_to_string(&a, k);
+-        return 1;
+-}
+-
+-static int format_network_address(const void *data, size_t sz, char **ret) {
+-        union in_addr_union a;
+-        int family, r;
+-
+-        if (sz == 6 && ((uint8_t*) data)[1] == 1) {
+-                memcpy(&a.in, (uint8_t*) data + 2, sizeof(a.in));
+-                family = AF_INET;
+-        } else if (sz == 18 && ((uint8_t*) data)[1] == 2) {
+-                memcpy(&a.in6, (uint8_t*) data + 2, sizeof(a.in6));
+-                family = AF_INET6;
+-        } else
+-                return 0;
+-
+-        r = in_addr_to_string(family, &a, ret);
+-        if (r < 0)
+-                return r;
+-        return 1;
+-}
+-
+-_public_ int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret) {
+-        char *k;
+-        int r;
+-
+-        assert_return(n, -EINVAL);
+-        assert_return(ret, -EINVAL);
+-
+-        if (n->chassis_id_as_string) {
+-                *ret = n->chassis_id_as_string;
+-                return 0;
+-        }
+-
+-        assert(n->id.chassis_id_size > 0);
+-
+-        switch (*(uint8_t*) n->id.chassis_id) {
+-
+-        case SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT:
+-        case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS:
+-        case SD_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT:
+-        case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME:
+-        case SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED:
+-                k = cescape_length((char*) n->id.chassis_id + 1, n->id.chassis_id_size - 1);
+-                if (!k)
+-                        return -ENOMEM;
+-
+-                goto done;
+-
+-        case SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS:
+-                r = format_mac_address(n->id.chassis_id, n->id.chassis_id_size, &k);
+-                if (r < 0)
+-                        return r;
+-                if (r > 0)
+-                        goto done;
+-
+-                break;
+-
+-        case SD_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS:
+-                r = format_network_address(n->id.chassis_id, n->id.chassis_id_size, &k);
+-                if (r < 0)
+-                        return r;
+-                if (r > 0)
+-                        goto done;
+-
+-                break;
+-        }
+-
+-        /* Generic fallback */
+-        k = hexmem(n->id.chassis_id, n->id.chassis_id_size);
+-        if (!k)
+-                return -ENOMEM;
+-
+-done:
+-        *ret = n->chassis_id_as_string = k;
+-        return 0;
+-}
+-
+-_public_ int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) {
+-        assert_return(n, -EINVAL);
+-        assert_return(type, -EINVAL);
+-        assert_return(ret, -EINVAL);
+-        assert_return(size, -EINVAL);
+-
+-        assert(n->id.port_id_size > 0);
+-
+-        *type = *(uint8_t*) n->id.port_id;
+-        *ret = (uint8_t*) n->id.port_id + 1;
+-        *size = n->id.port_id_size - 1;
+-
+-        return 0;
+-}
+-
+-_public_ int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret) {
+-        char *k;
+-        int r;
+-
+-        assert_return(n, -EINVAL);
+-        assert_return(ret, -EINVAL);
+-
+-        if (n->port_id_as_string) {
+-                *ret = n->port_id_as_string;
+-                return 0;
+-        }
+-
+-        assert(n->id.port_id_size > 0);
+-
+-        switch (*(uint8_t*) n->id.port_id) {
+-
+-        case SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS:
+-        case SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT:
+-        case SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME:
+-        case SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED:
+-                k = cescape_length((char*) n->id.port_id + 1, n->id.port_id_size - 1);
+-                if (!k)
+-                        return -ENOMEM;
+-
+-                goto done;
+-
+-        case SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS:
+-                r = format_mac_address(n->id.port_id, n->id.port_id_size, &k);
+-                if (r < 0)
+-                        return r;
+-                if (r > 0)
+-                        goto done;
+-
+-                break;
+-
+-        case SD_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS:
+-                r = format_network_address(n->id.port_id, n->id.port_id_size, &k);
+-                if (r < 0)
+-                        return r;
+-                if (r > 0)
+-                        goto done;
+-
+-                break;
+-        }
+-
+-        /* Generic fallback */
+-        k = hexmem(n->id.port_id, n->id.port_id_size);
+-        if (!k)
+-                return -ENOMEM;
+-
+-done:
+-        *ret = n->port_id_as_string = k;
+-        return 0;
+-}
+-
+-_public_ int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec) {
+-        assert_return(n, -EINVAL);
+-        assert_return(ret_sec, -EINVAL);
+-
+-        *ret_sec = n->ttl;
+-        return 0;
+-}
+-
+-_public_ int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret) {
+-        assert_return(n, -EINVAL);
+-        assert_return(ret, -EINVAL);
+-
+-        if (!n->system_name)
+-                return -ENODATA;
+-
+-        *ret = n->system_name;
+-        return 0;
+-}
+-
+-_public_ int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret) {
+-        assert_return(n, -EINVAL);
+-        assert_return(ret, -EINVAL);
+-
+-        if (!n->system_description)
+-                return -ENODATA;
+-
+-        *ret = n->system_description;
+-        return 0;
+-}
+-
+-_public_ int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret) {
+-        assert_return(n, -EINVAL);
+-        assert_return(ret, -EINVAL);
+-
+-        if (!n->port_description)
+-                return -ENODATA;
+-
+-        *ret = n->port_description;
+-        return 0;
+-}
+-
+-_public_ int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret) {
+-        assert_return(n, -EINVAL);
+-        assert_return(ret, -EINVAL);
+-
+-        if (!n->has_capabilities)
+-                return -ENODATA;
+-
+-        *ret = n->system_capabilities;
+-        return 0;
+-}
+-
+-_public_ int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret) {
+-        assert_return(n, -EINVAL);
+-        assert_return(ret, -EINVAL);
+-
+-        if (!n->has_capabilities)
+-                return -ENODATA;
+-
+-        *ret = n->enabled_capabilities;
+-        return 0;
+-}
+-
+-_public_ int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size) {
+-        _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
+-        int r;
+-
+-        assert_return(ret, -EINVAL);
+-        assert_return(raw || raw_size <= 0, -EINVAL);
+-
+-        n = lldp_neighbor_new(raw_size);
+-        if (!n)
+-                return -ENOMEM;
+-
+-        memcpy(LLDP_NEIGHBOR_RAW(n), raw, raw_size);
+-        r = lldp_neighbor_parse(n);
+-        if (r < 0)
+-                return r;
+-
+-        *ret = TAKE_PTR(n);
+-
+-        return r;
+-}
+-
+-_public_ int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n) {
+-        assert_return(n, -EINVAL);
+-
+-        assert(n->raw_size >= sizeof(struct ether_header));
+-        n->rindex = sizeof(struct ether_header);
+-
+-        return n->rindex < n->raw_size;
+-}
+-
+-_public_ int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n) {
+-        size_t length;
+-
+-        assert_return(n, -EINVAL);
+-
+-        if (n->rindex == n->raw_size) /* EOF */
+-                return -ESPIPE;
+-
+-        if (n->rindex + 2 > n->raw_size) /* Truncated message */
+-                return -EBADMSG;
+-
+-        length = LLDP_NEIGHBOR_TLV_LENGTH(n);
+-        if (n->rindex + 2 + length > n->raw_size)
+-                return -EBADMSG;
+-
+-        n->rindex += 2 + length;
+-        return n->rindex < n->raw_size;
+-}
+-
+-_public_ int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type) {
+-        assert_return(n, -EINVAL);
+-        assert_return(type, -EINVAL);
+-
+-        if (n->rindex == n->raw_size) /* EOF */
+-                return -ESPIPE;
+-
+-        if (n->rindex + 2 > n->raw_size)
+-                return -EBADMSG;
+-
+-        *type = LLDP_NEIGHBOR_TLV_TYPE(n);
+-        return 0;
+-}
+-
+-_public_ int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type) {
+-        uint8_t k;
+-        int r;
+-
+-        assert_return(n, -EINVAL);
+-
+-        r = sd_lldp_neighbor_tlv_get_type(n, &k);
+-        if (r < 0)
+-                return r;
+-
+-        return type == k;
+-}
+-
+-_public_ int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t *subtype) {
+-        const uint8_t *d;
+-        size_t length;
+-        int r;
+-
+-        assert_return(n, -EINVAL);
+-        assert_return(oui, -EINVAL);
+-        assert_return(subtype, -EINVAL);
+-
+-        r = sd_lldp_neighbor_tlv_is_type(n, SD_LLDP_TYPE_PRIVATE);
+-        if (r < 0)
+-                return r;
+-        if (r == 0)
+-                return -ENXIO;
+-
+-        length = LLDP_NEIGHBOR_TLV_LENGTH(n);
+-        if (length < 4)
+-                return -EBADMSG;
+-
+-        if (n->rindex + 2 + length > n->raw_size)
+-                return -EBADMSG;
+-
+-        d = LLDP_NEIGHBOR_TLV_DATA(n);
+-        memcpy(oui, d, 3);
+-        *subtype = d[3];
+-
+-        return 0;
+-}
+-
+-_public_ int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t subtype) {
+-        uint8_t k[3], st;
+-        int r;
+-
+-        r = sd_lldp_neighbor_tlv_get_oui(n, k, &st);
+-        if (r == -ENXIO)
+-                return 0;
+-        if (r < 0)
+-                return r;
+-
+-        return memcmp(k, oui, 3) == 0 && st == subtype;
+-}
+-
+-_public_ int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) {
+-        size_t length;
+-
+-        assert_return(n, -EINVAL);
+-        assert_return(ret, -EINVAL);
+-        assert_return(size, -EINVAL);
+-
+-        /* Note that this returns the full TLV, including the TLV header */
+-
+-        if (n->rindex + 2 > n->raw_size)
+-                return -EBADMSG;
+-
+-        length = LLDP_NEIGHBOR_TLV_LENGTH(n);
+-        if (n->rindex + 2 + length > n->raw_size)
+-                return -EBADMSG;
+-
+-        *ret = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex;
+-        *size = length + 2;
+-
+-        return 0;
+-}
+-
+-_public_ int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret) {
+-        assert_return(n, -EINVAL);
+-        assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP);
+-        assert_return(clock_supported(clock), -EOPNOTSUPP);
+-        assert_return(ret, -EINVAL);
+-
+-        if (!triple_timestamp_is_set(&n->timestamp))
+-                return -ENODATA;
+-
+-        *ret = triple_timestamp_by_clock(&n->timestamp, clock);
+-        return 0;
+-}
+diff --git a/src/libsystemd-network/lldp-neighbor.h b/src/libsystemd-network/lldp-neighbor.h
+deleted file mode 100644
+index 62dbff42ca..0000000000
+--- a/src/libsystemd-network/lldp-neighbor.h
++++ /dev/null
+@@ -1,91 +0,0 @@
+-/* SPDX-License-Identifier: LGPL-2.1+ */
+-#pragma once
+-
+-#include <inttypes.h>
+-#include <stdbool.h>
+-#include <sys/types.h>
+-
+-#include "sd-lldp.h"
+-
+-#include "hash-funcs.h"
+-#include "lldp-internal.h"
+-#include "time-util.h"
+-
+-typedef struct LLDPNeighborID {
+-        /* The spec calls this an "MSAP identifier" */
+-        void *chassis_id;
+-        size_t chassis_id_size;
+-
+-        void *port_id;
+-        size_t port_id_size;
+-} LLDPNeighborID;
+-
+-struct sd_lldp_neighbor {
+-        /* Neighbor objects stay around as long as they are linked into an "sd_lldp" object or n_ref > 0. */
+-        sd_lldp *lldp;
+-        unsigned n_ref;
+-
+-        triple_timestamp timestamp;
+-
+-        usec_t until;
+-        unsigned prioq_idx;
+-
+-        struct ether_addr source_address;
+-        struct ether_addr destination_address;
+-
+-        LLDPNeighborID id;
+-
+-        /* The raw packet size. The data is appended to the object, accessible via LLDP_NEIGHBOR_RAW() */
+-        size_t raw_size;
+-
+-        /* The current read index for the iterative TLV interface */
+-        size_t rindex;
+-
+-        /* And a couple of fields parsed out. */
+-        bool has_ttl:1;
+-        bool has_capabilities:1;
+-        bool has_port_vlan_id:1;
+-
+-        uint16_t ttl;
+-
+-        uint16_t system_capabilities;
+-        uint16_t enabled_capabilities;
+-
+-        char *port_description;
+-        char *system_name;
+-        char *system_description;
+-
+-        uint16_t port_vlan_id;
+-
+-        char *chassis_id_as_string;
+-        char *port_id_as_string;
+-};
+-
+-static inline void *LLDP_NEIGHBOR_RAW(const sd_lldp_neighbor *n) {
+-        return (uint8_t*) n + ALIGN(sizeof(sd_lldp_neighbor));
+-}
+-
+-static inline uint8_t LLDP_NEIGHBOR_TLV_TYPE(const sd_lldp_neighbor *n) {
+-        return ((uint8_t*) LLDP_NEIGHBOR_RAW(n))[n->rindex] >> 1;
+-}
+-
+-static inline size_t LLDP_NEIGHBOR_TLV_LENGTH(const sd_lldp_neighbor *n) {
+-        uint8_t *p;
+-
+-        p = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex;
+-        return p[1] + (((size_t) (p[0] & 1)) << 8);
+-}
+-
+-static inline void* LLDP_NEIGHBOR_TLV_DATA(const sd_lldp_neighbor *n) {
+-        return ((uint8_t*) LLDP_NEIGHBOR_RAW(n)) + n->rindex + 2;
+-}
+-
+-extern const struct hash_ops lldp_neighbor_hash_ops;
+-int lldp_neighbor_id_compare_func(const LLDPNeighborID *x, const LLDPNeighborID *y);
+-int lldp_neighbor_prioq_compare_func(const void *a, const void *b);
+-
+-sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n);
+-sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size);
+-int lldp_neighbor_parse(sd_lldp_neighbor *n);
+-void lldp_neighbor_start_ttl(sd_lldp_neighbor *n);
+-bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b);
+diff --git a/src/libsystemd-network/lldp-network.c b/src/libsystemd-network/lldp-network.c
+deleted file mode 100644
+index 870584c0db..0000000000
+--- a/src/libsystemd-network/lldp-network.c
++++ /dev/null
+@@ -1,78 +0,0 @@
+-/* SPDX-License-Identifier: LGPL-2.1+ */
+-
+-#include <linux/filter.h>
+-#include <netinet/if_ether.h>
+-
+-#include "fd-util.h"
+-#include "lldp-network.h"
+-#include "missing.h"
+-#include "socket-util.h"
+-
+-int lldp_network_bind_raw_socket(int ifindex) {
+-
+-        static const struct sock_filter filter[] = {
+-                BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ethhdr, h_dest)),      /* A <- 4 bytes of destination MAC */
+-                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0180c200, 1, 0),                    /* A != 01:80:c2:00 */
+-                BPF_STMT(BPF_RET + BPF_K, 0),                                             /* drop packet */
+-                BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ethhdr, h_dest) + 4),  /* A <- remaining 2 bytes of destination MAC */
+-                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0000, 3, 0),                        /* A != 00:00 */
+-                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0003, 2, 0),                        /* A != 00:03 */
+-                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x000e, 1, 0),                        /* A != 00:0e */
+-                BPF_STMT(BPF_RET + BPF_K, 0),                                             /* drop packet */
+-                BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ethhdr, h_proto)),     /* A <- protocol */
+-                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_LLDP, 1, 0),                /* A != ETHERTYPE_LLDP */
+-                BPF_STMT(BPF_RET + BPF_K, 0),                                             /* drop packet */
+-                BPF_STMT(BPF_RET + BPF_K, (uint32_t) -1),                                 /* accept packet */
+-        };
+-
+-        static const struct sock_fprog fprog = {
+-                .len = ELEMENTSOF(filter),
+-                .filter = (struct sock_filter*) filter,
+-        };
+-
+-        struct packet_mreq mreq = {
+-                .mr_ifindex = ifindex,
+-                .mr_type = PACKET_MR_MULTICAST,
+-                .mr_alen = ETH_ALEN,
+-                .mr_address = { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x00 }
+-        };
+-
+-        union sockaddr_union saddrll = {
+-                .ll.sll_family = AF_PACKET,
+-                .ll.sll_ifindex = ifindex,
+-        };
+-
+-        _cleanup_close_ int fd = -1;
+-        int r;
+-
+-        assert(ifindex > 0);
+-
+-        fd = socket(PF_PACKET, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK,
+-                    htobe16(ETHERTYPE_LLDP));
+-        if (fd < 0)
+-                return -errno;
+-
+-        r = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
+-        if (r < 0)
+-                return -errno;
+-
+-        r = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
+-        if (r < 0)
+-                return -errno;
+-
+-        mreq.mr_address[ETH_ALEN - 1] = 0x03;
+-        r = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
+-        if (r < 0)
+-                return -errno;
+-
+-        mreq.mr_address[ETH_ALEN - 1] = 0x0E;
+-        r = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
+-        if (r < 0)
+-                return -errno;
+-
+-        r = bind(fd, &saddrll.sa, sizeof(saddrll.ll));
+-        if (r < 0)
+-                return -errno;
+-
+-        return TAKE_FD(fd);
+-}
+diff --git a/src/libsystemd-network/lldp-network.h b/src/libsystemd-network/lldp-network.h
+deleted file mode 100644
+index e4ed2898a5..0000000000
+--- a/src/libsystemd-network/lldp-network.h
++++ /dev/null
+@@ -1,6 +0,0 @@
+-/* SPDX-License-Identifier: LGPL-2.1+ */
+-#pragma once
+-
+-#include "sd-event.h"
+-
+-int lldp_network_bind_raw_socket(int ifindex);
+diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build
+index 56d470ff68..88a72aa900 100644
+--- a/src/libsystemd-network/meson.build
++++ b/src/libsystemd-network/meson.build
+@@ -33,12 +33,6 @@ sources = files('''
+         sd-dhcp6-lease.c
+         dhcp-identifier.h
+         dhcp-identifier.c
+-        lldp-internal.h
+-        lldp-network.h
+-        lldp-network.c
+-        lldp-neighbor.h
+-        lldp-neighbor.c
+-        sd-lldp.c
+ '''.split())
+ 
+ network_internal_h = files('network-internal.h')
+diff --git a/src/libsystemd-network/sd-lldp.c b/src/libsystemd-network/sd-lldp.c
+deleted file mode 100644
+index 1f28c5731f..0000000000
+--- a/src/libsystemd-network/sd-lldp.c
++++ /dev/null
+@@ -1,498 +0,0 @@
+-/* SPDX-License-Identifier: LGPL-2.1+ */
+-
+-#include <arpa/inet.h>
+-#include <linux/sockios.h>
+-#include <sys/ioctl.h>
+-
+-#include "sd-lldp.h"
+-
+-#include "alloc-util.h"
+-#include "ether-addr-util.h"
+-#include "event-util.h"
+-#include "fd-util.h"
+-#include "lldp-internal.h"
+-#include "lldp-neighbor.h"
+-#include "lldp-network.h"
+-#include "memory-util.h"
+-#include "socket-util.h"
+-#include "sort-util.h"
+-#include "string-table.h"
+-
+-#define LLDP_DEFAULT_NEIGHBORS_MAX 128U
+-
+-static const char * const lldp_event_table[_SD_LLDP_EVENT_MAX] = {
+-        [SD_LLDP_EVENT_ADDED]   = "added",
+-        [SD_LLDP_EVENT_REMOVED] = "removed",
+-        [SD_LLDP_EVENT_UPDATED]   = "updated",
+-        [SD_LLDP_EVENT_REFRESHED] = "refreshed",
+-};
+-
+-DEFINE_STRING_TABLE_LOOKUP(lldp_event, sd_lldp_event);
+-
+-static void lldp_flush_neighbors(sd_lldp *lldp) {
+-        assert(lldp);
+-
+-        hashmap_clear(lldp->neighbor_by_id);
+-}
+-
+-static void lldp_callback(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n) {
+-        assert(lldp);
+-        assert(event >= 0 && event < _SD_LLDP_EVENT_MAX);
+-
+-        if (!lldp->callback) {
+-                log_lldp("Received '%s' event.", lldp_event_to_string(event));
+-                return;
+-        }
+-
+-        log_lldp("Invoking callback for '%s' event.", lldp_event_to_string(event));
+-        lldp->callback(lldp, event, n, lldp->userdata);
+-}
+-
+-static int lldp_make_space(sd_lldp *lldp, size_t extra) {
+-        usec_t t = USEC_INFINITY;
+-        bool changed = false;
+-
+-        assert(lldp);
+-
+-        /* Remove all entries that are past their TTL, and more until at least the specified number of extra entries
+-         * are free. */
+-
+-        for (;;) {
+-                _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
+-
+-                n = prioq_peek(lldp->neighbor_by_expiry);
+-                if (!n)
+-                        break;
+-
+-                sd_lldp_neighbor_ref(n);
+-
+-                if (hashmap_size(lldp->neighbor_by_id) > LESS_BY(lldp->neighbors_max, extra))
+-                        goto remove_one;
+-
+-                if (t == USEC_INFINITY)
+-                        t = now(clock_boottime_or_monotonic());
+-
+-                if (n->until > t)
+-                        break;
+-
+-        remove_one:
+-                lldp_neighbor_unlink(n);
+-                lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, n);
+-                changed = true;
+-        }
+-
+-        return changed;
+-}
+-
+-static bool lldp_keep_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
+-        assert(lldp);
+-        assert(n);
+-
+-        /* Don't keep data with a zero TTL */
+-        if (n->ttl <= 0)
+-                return false;
+-
+-        /* Filter out data from the filter address */
+-        if (!ether_addr_is_null(&lldp->filter_address) &&
+-            ether_addr_equal(&lldp->filter_address, &n->source_address))
+-                return false;
+-
+-        /* Only add if the neighbor has a capability we are interested in. Note that we also store all neighbors with
+-         * no caps field set. */
+-        if (n->has_capabilities &&
+-            (n->enabled_capabilities & lldp->capability_mask) == 0)
+-                return false;
+-
+-        /* Keep everything else */
+-        return true;
+-}
+-
+-static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor);
+-
+-static int lldp_add_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
+-        _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *old = NULL;
+-        bool keep;
+-        int r;
+-
+-        assert(lldp);
+-        assert(n);
+-        assert(!n->lldp);
+-
+-        keep = lldp_keep_neighbor(lldp, n);
+-
+-        /* First retrieve the old entry for this MSAP */
+-        old = hashmap_get(lldp->neighbor_by_id, &n->id);
+-        if (old) {
+-                sd_lldp_neighbor_ref(old);
+-
+-                if (!keep) {
+-                        lldp_neighbor_unlink(old);
+-                        lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old);
+-                        return 0;
+-                }
+-
+-                if (lldp_neighbor_equal(n, old)) {
+-                        /* Is this equal, then restart the TTL counter, but don't do anything else. */
+-                        old->timestamp = n->timestamp;
+-                        lldp_start_timer(lldp, old);
+-                        lldp_callback(lldp, SD_LLDP_EVENT_REFRESHED, old);
+-                        return 0;
+-                }
+-
+-                /* Data changed, remove the old entry, and add a new one */
+-                lldp_neighbor_unlink(old);
+-
+-        } else if (!keep)
+-                return 0;
+-
+-        /* Then, make room for at least one new neighbor */
+-        lldp_make_space(lldp, 1);
+-
+-        r = hashmap_put(lldp->neighbor_by_id, &n->id, n);
+-        if (r < 0)
+-                goto finish;
+-
+-        r = prioq_put(lldp->neighbor_by_expiry, n, &n->prioq_idx);
+-        if (r < 0) {
+-                assert_se(hashmap_remove(lldp->neighbor_by_id, &n->id) == n);
+-                goto finish;
+-        }
+-
+-        n->lldp = lldp;
+-
+-        lldp_start_timer(lldp, n);
+-        lldp_callback(lldp, old ? SD_LLDP_EVENT_UPDATED : SD_LLDP_EVENT_ADDED, n);
+-
+-        return 1;
+-
+-finish:
+-        if (old)
+-                lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old);
+-
+-        return r;
+-}
+-
+-static int lldp_handle_datagram(sd_lldp *lldp, sd_lldp_neighbor *n) {
+-        int r;
+-
+-        assert(lldp);
+-        assert(n);
+-
+-        r = lldp_neighbor_parse(n);
+-        if (r == -EBADMSG) /* Ignore bad messages */
+-                return 0;
+-        if (r < 0)
+-                return r;
+-
+-        r = lldp_add_neighbor(lldp, n);
+-        if (r < 0) {
+-                log_lldp_errno(r, "Failed to add datagram. Ignoring.");
+-                return 0;
+-        }
+-
+-        log_lldp("Successfully processed LLDP datagram.");
+-        return 0;
+-}
+-
+-static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+-        _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
+-        ssize_t space, length;
+-        sd_lldp *lldp = userdata;
+-        struct timespec ts;
+-
+-        assert(fd >= 0);
+-        assert(lldp);
+-
+-        space = next_datagram_size_fd(fd);
+-        if (space < 0)
+-                return log_lldp_errno(space, "Failed to determine datagram size to read: %m");
+-
+-        n = lldp_neighbor_new(space);
+-        if (!n)
+-                return -ENOMEM;
+-
+-        length = recv(fd, LLDP_NEIGHBOR_RAW(n), n->raw_size, MSG_DONTWAIT);
+-        if (length < 0) {
+-                if (IN_SET(errno, EAGAIN, EINTR))
+-                        return 0;
+-
+-                return log_lldp_errno(errno, "Failed to read LLDP datagram: %m");
+-        }
+-
+-        if ((size_t) length != n->raw_size) {
+-                log_lldp("Packet size mismatch.");
+-                return -EINVAL;
+-        }
+-
+-        /* Try to get the timestamp of this packet if it is known */
+-        if (ioctl(fd, SIOCGSTAMPNS, &ts) >= 0)
+-                triple_timestamp_from_realtime(&n->timestamp, timespec_load(&ts));
+-        else
+-                triple_timestamp_get(&n->timestamp);
+-
+-        return lldp_handle_datagram(lldp, n);
+-}
+-
+-static void lldp_reset(sd_lldp *lldp) {
+-        assert(lldp);
+-
+-        (void) event_source_disable(lldp->timer_event_source);
+-        lldp->io_event_source = sd_event_source_unref(lldp->io_event_source);
+-        lldp->fd = safe_close(lldp->fd);
+-}
+-
+-_public_ int sd_lldp_start(sd_lldp *lldp) {
+-        int r;
+-
+-        assert_return(lldp, -EINVAL);
+-        assert_return(lldp->event, -EINVAL);
+-        assert_return(lldp->ifindex > 0, -EINVAL);
+-
+-        if (lldp->fd >= 0)
+-                return 0;
+-
+-        assert(!lldp->io_event_source);
+-
+-        lldp->fd = lldp_network_bind_raw_socket(lldp->ifindex);
+-        if (lldp->fd < 0)
+-                return lldp->fd;
+-
+-        r = sd_event_add_io(lldp->event, &lldp->io_event_source, lldp->fd, EPOLLIN, lldp_receive_datagram, lldp);
+-        if (r < 0)
+-                goto fail;
+-
+-        r = sd_event_source_set_priority(lldp->io_event_source, lldp->event_priority);
+-        if (r < 0)
+-                goto fail;
+-
+-        (void) sd_event_source_set_description(lldp->io_event_source, "lldp-io");
+-
+-        log_lldp("Started LLDP client");
+-        return 1;
+-
+-fail:
+-        lldp_reset(lldp);
+-        return r;
+-}
+-
+-_public_ int sd_lldp_stop(sd_lldp *lldp) {
+-        assert_return(lldp, -EINVAL);
+-
+-        if (lldp->fd < 0)
+-                return 0;
+-
+-        log_lldp("Stopping LLDP client");
+-
+-        lldp_reset(lldp);
+-        lldp_flush_neighbors(lldp);
+-
+-        return 1;
+-}
+-
+-_public_ int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority) {
+-        int r;
+-
+-        assert_return(lldp, -EINVAL);
+-        assert_return(lldp->fd < 0, -EBUSY);
+-        assert_return(!lldp->event, -EBUSY);
+-
+-        if (event)
+-                lldp->event = sd_event_ref(event);
+-        else {
+-                r = sd_event_default(&lldp->event);
+-                if (r < 0)
+-                        return r;
+-        }
+-
+-        lldp->event_priority = priority;
+-
+-        return 0;
+-}
+-
+-_public_ int sd_lldp_detach_event(sd_lldp *lldp) {
+-
+-        assert_return(lldp, -EINVAL);
+-        assert_return(lldp->fd < 0, -EBUSY);
+-
+-        lldp->event = sd_event_unref(lldp->event);
+-        return 0;
+-}
+-
+-_public_ sd_event* sd_lldp_get_event(sd_lldp *lldp) {
+-        assert_return(lldp, NULL);
+-
+-        return lldp->event;
+-}
+-
+-_public_ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata) {
+-        assert_return(lldp, -EINVAL);
+-
+-        lldp->callback = cb;
+-        lldp->userdata = userdata;
+-
+-        return 0;
+-}
+-
+-_public_ int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex) {
+-        assert_return(lldp, -EINVAL);
+-        assert_return(ifindex > 0, -EINVAL);
+-        assert_return(lldp->fd < 0, -EBUSY);
+-
+-        lldp->ifindex = ifindex;
+-        return 0;
+-}
+-
+-static sd_lldp* lldp_free(sd_lldp *lldp) {
+-        assert(lldp);
+-
+-        lldp->timer_event_source = sd_event_source_unref(lldp->timer_event_source);
+-
+-        lldp_reset(lldp);
+-        sd_lldp_detach_event(lldp);
+-        lldp_flush_neighbors(lldp);
+-
+-        hashmap_free(lldp->neighbor_by_id);
+-        prioq_free(lldp->neighbor_by_expiry);
+-        return mfree(lldp);
+-}
+-
+-DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_lldp, sd_lldp, lldp_free);
+-
+-_public_ int sd_lldp_new(sd_lldp **ret) {
+-        _cleanup_(sd_lldp_unrefp) sd_lldp *lldp = NULL;
+-        int r;
+-
+-        assert_return(ret, -EINVAL);
+-
+-        lldp = new(sd_lldp, 1);
+-        if (!lldp)
+-                return -ENOMEM;
+-
+-        *lldp = (sd_lldp) {
+-                .n_ref = 1,
+-                .fd = -1,
+-                .neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX,
+-                .capability_mask = (uint16_t) -1,
+-        };
+-
+-        lldp->neighbor_by_id = hashmap_new(&lldp_neighbor_hash_ops);
+-        if (!lldp->neighbor_by_id)
+-                return -ENOMEM;
+-
+-        r = prioq_ensure_allocated(&lldp->neighbor_by_expiry, lldp_neighbor_prioq_compare_func);
+-        if (r < 0)
+-                return r;
+-
+-        *ret = TAKE_PTR(lldp);
+-
+-        return 0;
+-}
+-
+-static int neighbor_compare_func(sd_lldp_neighbor * const *a, sd_lldp_neighbor * const *b) {
+-        return lldp_neighbor_id_compare_func(&(*a)->id, &(*b)->id);
+-}
+-
+-static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) {
+-        sd_lldp *lldp = userdata;
+-        int r;
+-
+-        r = lldp_make_space(lldp, 0);
+-        if (r < 0)
+-                return log_lldp_errno(r, "Failed to make space: %m");
+-
+-        r = lldp_start_timer(lldp, NULL);
+-        if (r < 0)
+-                return log_lldp_errno(r, "Failed to restart timer: %m");
+-
+-        return 0;
+-}
+-
+-static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor) {
+-        sd_lldp_neighbor *n;
+-
+-        assert(lldp);
+-
+-        if (neighbor)
+-                lldp_neighbor_start_ttl(neighbor);
+-
+-        n = prioq_peek(lldp->neighbor_by_expiry);
+-        if (!n)
+-                return event_source_disable(lldp->timer_event_source);
+-
+-        if (!lldp->event)
+-                return 0;
+-
+-        return event_reset_time(lldp->event, &lldp->timer_event_source,
+-                                clock_boottime_or_monotonic(),
+-                                n->until, 0,
+-                                on_timer_event, lldp,
+-                                lldp->event_priority, "lldp-timer", true);
+-}
+-
+-_public_ int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***ret) {
+-        sd_lldp_neighbor **l = NULL, *n;
+-        Iterator i;
+-        int k = 0, r;
+-
+-        assert_return(lldp, -EINVAL);
+-        assert_return(ret, -EINVAL);
+-
+-        if (hashmap_isempty(lldp->neighbor_by_id)) { /* Special shortcut */
+-                *ret = NULL;
+-                return 0;
+-        }
+-
+-        l = new0(sd_lldp_neighbor*, hashmap_size(lldp->neighbor_by_id));
+-        if (!l)
+-                return -ENOMEM;
+-
+-        r = lldp_start_timer(lldp, NULL);
+-        if (r < 0) {
+-                free(l);
+-                return r;
+-        }
+-
+-        HASHMAP_FOREACH(n, lldp->neighbor_by_id, i)
+-                l[k++] = sd_lldp_neighbor_ref(n);
+-
+-        assert((size_t) k == hashmap_size(lldp->neighbor_by_id));
+-
+-        /* Return things in a stable order */
+-        typesafe_qsort(l, k, neighbor_compare_func);
+-        *ret = l;
+-
+-        return k;
+-}
+-
+-_public_ int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t m) {
+-        assert_return(lldp, -EINVAL);
+-        assert_return(m <= 0, -EINVAL);
+-
+-        lldp->neighbors_max = m;
+-        lldp_make_space(lldp, 0);
+-
+-        return 0;
+-}
+-
+-_public_ int sd_lldp_match_capabilities(sd_lldp *lldp, uint16_t mask) {
+-        assert_return(lldp, -EINVAL);
+-        assert_return(mask != 0, -EINVAL);
+-
+-        lldp->capability_mask = mask;
+-
+-        return 0;
+-}
+-
+-_public_ int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *addr) {
+-        assert_return(lldp, -EINVAL);
+-
+-        /* In order to deal nicely with bridges that send back our own packets, allow one address to be filtered, so
+-         * that our own can be filtered out here. */
+-
+-        if (addr)
+-                lldp->filter_address = *addr;
+-        else
+-                zero(lldp->filter_address);
+-
+-        return 0;
+-}
+diff --git a/src/libsystemd-network/test-lldp.c b/src/libsystemd-network/test-lldp.c
+deleted file mode 100644
+index 7406f94ce0..0000000000
+--- a/src/libsystemd-network/test-lldp.c
++++ /dev/null
+@@ -1,379 +0,0 @@
+-/* SPDX-License-Identifier: LGPL-2.1+ */
+-
+-#include <arpa/inet.h>
+-#include <errno.h>
+-#include <net/ethernet.h>
+-#include <stdio.h>
+-#include <string.h>
+-#include <unistd.h>
+-
+-#include "sd-event.h"
+-#include "sd-lldp.h"
+-
+-#include "alloc-util.h"
+-#include "fd-util.h"
+-#include "lldp-network.h"
+-#include "macro.h"
+-#include "string-util.h"
+-#include "tests.h"
+-
+-#define TEST_LLDP_PORT "em1"
+-#define TEST_LLDP_TYPE_SYSTEM_NAME "systemd-lldp"
+-#define TEST_LLDP_TYPE_SYSTEM_DESC "systemd-lldp-desc"
+-
+-static int test_fd[2] = { -1, -1 };
+-static int lldp_handler_calls;
+-
+-int lldp_network_bind_raw_socket(int ifindex) {
+-        if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) < 0)
+-                return -errno;
+-
+-        return test_fd[0];
+-}
+-
+-static void lldp_handler(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata) {
+-        lldp_handler_calls++;
+-}
+-
+-static int start_lldp(sd_lldp **lldp, sd_event *e, sd_lldp_callback_t cb, void *cb_data) {
+-        int r;
+-
+-        r = sd_lldp_new(lldp);
+-        if (r < 0)
+-                return r;
+-
+-        r = sd_lldp_set_ifindex(*lldp, 42);
+-        if (r < 0)
+-                return r;
+-
+-        r = sd_lldp_set_callback(*lldp, cb, cb_data);
+-        if (r < 0)
+-                return r;
+-
+-        r = sd_lldp_attach_event(*lldp, e, 0);
+-        if (r < 0)
+-                return r;
+-
+-        r = sd_lldp_start(*lldp);
+-        if (r < 0)
+-                return r;
+-
+-        return 0;
+-}
+-
+-static int stop_lldp(sd_lldp *lldp) {
+-        int r;
+-
+-        r = sd_lldp_stop(lldp);
+-        if (r < 0)
+-                return r;
+-
+-        r = sd_lldp_detach_event(lldp);
+-        if (r < 0)
+-                return r;
+-
+-        sd_lldp_unref(lldp);
+-        safe_close(test_fd[1]);
+-
+-        return 0;
+-}
+-
+-static void test_receive_basic_packet(sd_event *e) {
+-
+-        static const 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 */
+-        };
+-
+-        sd_lldp *lldp;
+-        sd_lldp_neighbor **neighbors;
+-        uint8_t type;
+-        const void *data;
+-        uint16_t ttl;
+-        size_t length;
+-        const char *str;
+-
+-        lldp_handler_calls = 0;
+-        assert_se(start_lldp(&lldp, e, lldp_handler, NULL) == 0);
+-
+-        assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame));
+-        sd_event_run(e, 0);
+-        assert_se(lldp_handler_calls == 1);
+-        assert_se(sd_lldp_get_neighbors(lldp, &neighbors) == 1);
+-
+-        assert_se(sd_lldp_neighbor_get_chassis_id(neighbors[0], &type, &data, &length) == 0);
+-        assert_se(type == SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS);
+-        assert_se(length == ETH_ALEN);
+-        assert_se(!memcmp(data, "\x00\x01\x02\x03\x04\x05", ETH_ALEN));
+-
+-        assert_se(sd_lldp_neighbor_get_port_id(neighbors[0], &type, &data, &length) == 0);
+-        assert_se(type == SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME);
+-        assert_se(length == 3);
+-        assert_se(!memcmp(data, "1/3", 3));
+-
+-        assert_se(sd_lldp_neighbor_get_port_description(neighbors[0], &str) == 0);
+-        assert_se(streq(str, "Port"));
+-
+-        assert_se(sd_lldp_neighbor_get_system_name(neighbors[0], &str) == 0);
+-        assert_se(streq(str, "SYS"));
+-
+-        assert_se(sd_lldp_neighbor_get_system_description(neighbors[0], &str) == 0);
+-        assert_se(streq(str, "foo"));
+-
+-        assert_se(sd_lldp_neighbor_get_ttl(neighbors[0], &ttl) == 0);
+-        assert_se(ttl == 120);
+-
+-        sd_lldp_neighbor_unref(neighbors[0]);
+-        free(neighbors);
+-
+-        assert_se(stop_lldp(lldp) == 0);
+-}
+-
+-static void test_receive_incomplete_packet(sd_event *e) {
+-        sd_lldp *lldp;
+-        sd_lldp_neighbor **neighbors;
+-        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" */
+-                                                        /* Missing TTL */
+-                0x00, 0x00                              /* End Of LLDPDU */
+-        };
+-
+-        lldp_handler_calls = 0;
+-        assert_se(start_lldp(&lldp, e, lldp_handler, NULL) == 0);
+-
+-        assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame));
+-        sd_event_run(e, 0);
+-        assert_se(lldp_handler_calls == 0);
+-        assert_se(sd_lldp_get_neighbors(lldp, &neighbors) == 0);
+-
+-        assert_se(stop_lldp(lldp) == 0);
+-}
+-
+-static void test_receive_oui_packet(sd_event *e) {
+-        sd_lldp *lldp;
+-        sd_lldp_neighbor **neighbors;
+-        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 TLV: interface name, "1/3" */
+-                0x06, 0x02, 0x00, 0x78,                 /* TTL: 120 seconds */
+-                /* LLDP optional TLVs */
+-                0xfe, 0x06, 0x00, 0x80, 0xc2, 0x01,     /* Port VLAN ID: 0x1234 */
+-                0x12, 0x34,
+-                0xfe, 0x07, 0x00, 0x80, 0xc2, 0x02,     /* Port and protocol: flag 1, PPVID 0x7788 */
+-                0x01, 0x77, 0x88,
+-                0xfe, 0x0d, 0x00, 0x80, 0xc2, 0x03,     /* VLAN Name: ID 0x1234, name "Vlan51" */
+-                0x12, 0x34, 0x06, 0x56, 0x6c, 0x61,
+-                0x6e, 0x35, 0x31,
+-                0xfe, 0x06, 0x00, 0x80, 0xc2, 0x06,     /* Management VID: 0x0102 */
+-                0x01, 0x02,
+-                0xfe, 0x09, 0x00, 0x80, 0xc2, 0x07,     /* Link aggregation: status 1, ID 0x00140012 */
+-                0x01, 0x00, 0x14, 0x00, 0x12,
+-                0xfe, 0x07, 0x00, 0x12, 0x0f, 0x02,     /* 802.3 Power via MDI: PSE, MDI enabled */
+-                0x07, 0x01, 0x00,
+-                0x00, 0x00                              /* End of LLDPDU */
+-        };
+-
+-        lldp_handler_calls = 0;
+-        assert_se(start_lldp(&lldp, e, lldp_handler, NULL) == 0);
+-
+-        assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame));
+-        sd_event_run(e, 0);
+-        assert_se(lldp_handler_calls == 1);
+-        assert_se(sd_lldp_get_neighbors(lldp, &neighbors) == 1);
+-
+-        assert_se(sd_lldp_neighbor_tlv_rewind(neighbors[0]) >= 0);
+-        assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_CHASSIS_ID) > 0);
+-        assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
+-        assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_PORT_ID) > 0);
+-        assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
+-        assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_TTL) > 0);
+-        assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
+-        assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID) > 0);
+-        assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
+-        assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID) > 0);
+-        assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
+-        assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME) > 0);
+-        assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
+-        assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_MANAGEMENT_VID) > 0);
+-        assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
+-        assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_LINK_AGGREGATION) > 0);
+-        assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
+-        assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_3, SD_LLDP_OUI_802_3_SUBTYPE_POWER_VIA_MDI) > 0);
+-        assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
+-        assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_END) > 0);
+-        assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) == 0);
+-
+-        sd_lldp_neighbor_unref(neighbors[0]);
+-        free(neighbors);
+-
+-        assert_se(stop_lldp(lldp) == 0);
+-}
+-
+-static void test_multiple_neighbors_sorted(sd_event *e) {
+-
+-        static const uint8_t frame1[] = {
+-                /* 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, 0x04, 0x01, '1', '/', '2',        /* Chassis component: "1/2" */
+-                0x04, 0x04, 0x02, '2', '/', '3',        /* Port component: "2/3" */
+-                0x06, 0x02, 0x00, 0x78,                 /* TTL: 120 seconds */
+-                0x00, 0x00                              /* End Of LLDPDU */
+-        };
+-        static const uint8_t frame2[] = {
+-                /* 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, 0x04, 0x01, '2', '/', '1',        /* Chassis component: "2/1" */
+-                0x04, 0x04, 0x02, '1', '/', '3',        /* Port component: "1/3" */
+-                0x06, 0x02, 0x00, 0x78,                 /* TTL: 120 seconds */
+-                0x00, 0x00                              /* End Of LLDPDU */
+-        };
+-        static const uint8_t frame3[] = {
+-                /* 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, 0x05, 0x01, '2', '/', '1', '0',   /* Chassis component: "2/10" */
+-                0x04, 0x04, 0x02, '1', '/', '0',        /* Port component: "1/0" */
+-                0x06, 0x02, 0x00, 0x78,                 /* TTL: 120 seconds */
+-                0x00, 0x00                              /* End Of LLDPDU */
+-        };
+-        static const uint8_t frame4[] = {
+-                /* 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, 0x05, 0x01, '2', '/', '1', '9',   /* Chassis component: "2/19" */
+-                0x04, 0x04, 0x02, '1', '/', '0',        /* Port component: "1/0" */
+-                0x06, 0x02, 0x00, 0x78,                 /* TTL: 120 seconds */
+-                0x00, 0x00                              /* End Of LLDPDU */
+-        };
+-        static const uint8_t frame5[] = {
+-                /* 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, 0x04, 0x01, '1', '/', '2',        /* Chassis component: "1/2" */
+-                0x04, 0x05, 0x02, '2', '/', '1', '0',   /* Port component: "2/10" */
+-                0x06, 0x02, 0x00, 0x78,                 /* TTL: 120 seconds */
+-                0x00, 0x00                              /* End Of LLDPDU */
+-        };
+-        static const uint8_t frame6[] = {
+-                /* 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, 0x04, 0x01, '1', '/', '2',        /* Chassis component: "1/2" */
+-                0x04, 0x05, 0x02, '2', '/', '3', '9',   /* Port component: "2/10" */
+-                0x06, 0x02, 0x00, 0x78,                 /* TTL: 120 seconds */
+-                0x00, 0x00                              /* End Of LLDPDU */
+-        };
+-        static const char* expected[] = {
+-                /* ordered pairs of Chassis+Port */
+-                "1/2", "2/10",
+-                "1/2", "2/3",
+-                "1/2", "2/39",
+-                "2/1", "1/3",
+-                "2/10", "1/0",
+-                "2/19", "1/0",
+-        };
+-
+-        sd_lldp *lldp;
+-        sd_lldp_neighbor **neighbors;
+-        int i;
+-        uint8_t type;
+-        const void *data;
+-        size_t length, expected_length;
+-        uint16_t ttl;
+-
+-        lldp_handler_calls = 0;
+-        assert_se(start_lldp(&lldp, e, lldp_handler, NULL) == 0);
+-
+-        assert_se(write(test_fd[1], frame1, sizeof(frame1)) == sizeof(frame1));
+-        sd_event_run(e, 0);
+-        assert_se(write(test_fd[1], frame2, sizeof(frame2)) == sizeof(frame2));
+-        sd_event_run(e, 0);
+-        assert_se(write(test_fd[1], frame3, sizeof(frame3)) == sizeof(frame3));
+-        sd_event_run(e, 0);
+-        assert_se(write(test_fd[1], frame4, sizeof(frame4)) == sizeof(frame4));
+-        sd_event_run(e, 0);
+-        assert_se(write(test_fd[1], frame5, sizeof(frame5)) == sizeof(frame5));
+-        sd_event_run(e, 0);
+-        assert_se(write(test_fd[1], frame6, sizeof(frame6)) == sizeof(frame6));
+-        sd_event_run(e, 0);
+-        assert_se(lldp_handler_calls == 6);
+-
+-        assert_se(sd_lldp_get_neighbors(lldp, &neighbors) == 6);
+-
+-        for (i = 0; i < 6; i++) {
+-                assert_se(sd_lldp_neighbor_get_chassis_id(neighbors[i], &type, &data, &length) == 0);
+-                assert_se(type == SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT);
+-                expected_length = strlen(expected[2 * i]);
+-                assert_se(length == expected_length);
+-                assert_se(memcmp(data, expected[2 * i], expected_length) == 0);
+-
+-                assert_se(sd_lldp_neighbor_get_port_id(neighbors[i], &type, &data, &length) == 0);
+-                assert_se(type == SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT);
+-                expected_length = strlen(expected[2 * i + 1]);
+-                assert_se(length == expected_length);
+-                assert_se(memcmp(data, expected[2 * i + 1], expected_length) == 0);
+-
+-                assert_se(sd_lldp_neighbor_get_ttl(neighbors[i], &ttl) == 0);
+-                assert_se(ttl == 120);
+-        }
+-
+-        for (i = 0; i < 6; i++)
+-                sd_lldp_neighbor_unref(neighbors[i]);
+-        free(neighbors);
+-
+-        assert_se(stop_lldp(lldp) == 0);
+-}
+-
+-int main(int argc, char *argv[]) {
+-        _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+-
+-        test_setup_logging(LOG_DEBUG);
+-
+-        /* LLDP reception tests */
+-        assert_se(sd_event_new(&e) == 0);
+-        test_receive_basic_packet(e);
+-        test_receive_incomplete_packet(e);
+-        test_receive_oui_packet(e);
+-        test_multiple_neighbors_sorted(e);
+-
+-        return 0;
+-}
+diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym
+index 5ec42e0f1f..48021b86d3 100644
+--- a/src/libsystemd/libsystemd.sym
++++ b/src/libsystemd/libsystemd.sym
+@@ -681,4 +681,45 @@ LIBSYSTEMD_243 {
+ global:
+         sd_bus_object_vtable_format;
+         sd_event_source_disable_unref;
++
++        sd_lldp_new;
++        sd_lldp_ref;
++        sd_lldp_unref;
++        sd_lldp_start;
++        sd_lldp_stop;
++        sd_lldp_attach_event;
++        sd_lldp_detach_event;
++        sd_lldp_get_event;
++        sd_lldp_set_callback;
++        sd_lldp_set_ifindex;
++        sd_lldp_set_neighbors_max;
++        sd_lldp_match_capabilities;
++        sd_lldp_set_filter_address;
++        sd_lldp_get_neighbors;
++        sd_lldp_neighbor_from_raw;
++        sd_lldp_neighbor_ref;
++        sd_lldp_neighbor_unref;
++        sd_lldp_neighbor_get_source_address;
++        sd_lldp_neighbor_get_destination_address;
++        sd_lldp_neighbor_get_timestamp;
++        sd_lldp_neighbor_get_raw;
++        sd_lldp_neighbor_get_chassis_id;
++        sd_lldp_neighbor_get_chassis_id_as_string;
++        sd_lldp_neighbor_get_port_id;
++        sd_lldp_neighbor_get_port_id_as_string;
++        sd_lldp_neighbor_get_ttl;
++        sd_lldp_neighbor_get_system_name;
++        sd_lldp_neighbor_get_system_description;
++        sd_lldp_neighbor_get_port_description;
++        sd_lldp_neighbor_get_mud_url;
++        sd_lldp_neighbor_get_system_capabilities;
++        sd_lldp_neighbor_get_enabled_capabilities;
++        sd_lldp_neighbor_tlv_rewind;
++        sd_lldp_neighbor_tlv_next;
++        sd_lldp_neighbor_tlv_get_type;
++        sd_lldp_neighbor_tlv_is_type;
++        sd_lldp_neighbor_tlv_get_oui;
++        sd_lldp_neighbor_tlv_is_oui;
++        sd_lldp_neighbor_tlv_get_raw;
++
+ } LIBSYSTEMD_241;
+diff --git a/src/libsystemd/meson.build b/src/libsystemd/meson.build
+index 77fe6e780f..e60853959f 100644
+--- a/src/libsystemd/meson.build
++++ b/src/libsystemd/meson.build
+@@ -70,6 +70,12 @@ libsystemd_sources = files('''
+         sd-hwdb/hwdb-util.c
+         sd-hwdb/hwdb-util.h
+         sd-hwdb/sd-hwdb.c
++        sd-lldp/sd-lldp.c
++        sd-lldp/lldp-internal.h
++        sd-lldp/lldp-neighbor.c
++        sd-lldp/lldp-neighbor.h
++        sd-lldp/lldp-network.c
++        sd-lldp/lldp-network.h
+         sd-netlink/generic-netlink.c
+         sd-netlink/netlink-internal.h
+         sd-netlink/netlink-message.c
+diff --git a/src/libsystemd/sd-lldp/lldp-internal.h b/src/libsystemd/sd-lldp/lldp-internal.h
+new file mode 100644
+index 0000000000..9598438dba
+--- /dev/null
++++ b/src/libsystemd/sd-lldp/lldp-internal.h
+@@ -0,0 +1,39 @@
++/* SPDX-License-Identifier: LGPL-2.1+ */
++#pragma once
++
++#include "sd-event.h"
++#include "sd-lldp.h"
++
++#include "hashmap.h"
++#include "log.h"
++#include "prioq.h"
++
++struct sd_lldp {
++        unsigned n_ref;
++
++        int ifindex;
++        int fd;
++
++        sd_event *event;
++        int64_t event_priority;
++        sd_event_source *io_event_source;
++        sd_event_source *timer_event_source;
++
++        Prioq *neighbor_by_expiry;
++        Hashmap *neighbor_by_id;
++
++        uint64_t neighbors_max;
++
++        sd_lldp_callback_t callback;
++        void *userdata;
++
++        uint16_t capability_mask;
++
++        struct ether_addr filter_address;
++};
++
++#define log_lldp_errno(error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "LLDP: " fmt, ##__VA_ARGS__)
++#define log_lldp(fmt, ...) log_lldp_errno(0, fmt, ##__VA_ARGS__)
++
++const char* lldp_event_to_string(sd_lldp_event e) _const_;
++sd_lldp_event lldp_event_from_string(const char *s) _pure_;
+diff --git a/src/libsystemd/sd-lldp/lldp-neighbor.c b/src/libsystemd/sd-lldp/lldp-neighbor.c
+new file mode 100644
+index 0000000000..9bae4a3c6e
+--- /dev/null
++++ b/src/libsystemd/sd-lldp/lldp-neighbor.c
+@@ -0,0 +1,769 @@
++/* SPDX-License-Identifier: LGPL-2.1+ */
++
++#include "alloc-util.h"
++#include "escape.h"
++#include "ether-addr-util.h"
++#include "hexdecoct.h"
++#include "in-addr-util.h"
++#include "lldp-internal.h"
++#include "lldp-neighbor.h"
++#include "memory-util.h"
++#include "missing.h"
++#include "unaligned.h"
++
++static void lldp_neighbor_id_hash_func(const LLDPNeighborID *id, struct siphash *state) {
++        siphash24_compress(id->chassis_id, id->chassis_id_size, state);
++        siphash24_compress(&id->chassis_id_size, sizeof(id->chassis_id_size), state);
++        siphash24_compress(id->port_id, id->port_id_size, state);
++        siphash24_compress(&id->port_id_size, sizeof(id->port_id_size), state);
++}
++
++int lldp_neighbor_id_compare_func(const LLDPNeighborID *x, const LLDPNeighborID *y) {
++        return memcmp_nn(x->chassis_id, x->chassis_id_size, y->chassis_id, y->chassis_id_size)
++            ?: memcmp_nn(x->port_id, x->port_id_size, y->port_id, y->port_id_size);
++}
++
++DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(lldp_neighbor_hash_ops, LLDPNeighborID, lldp_neighbor_id_hash_func, lldp_neighbor_id_compare_func,
++                                      sd_lldp_neighbor, lldp_neighbor_unlink);
++
++int lldp_neighbor_prioq_compare_func(const void *a, const void *b) {
++        const sd_lldp_neighbor *x = a, *y = b;
++
++        return CMP(x->until, y->until);
++}
++
++_public_ sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n) {
++        if (!n)
++                return NULL;
++
++        assert(n->n_ref > 0 || n->lldp);
++        n->n_ref++;
++
++        return n;
++}
++
++static void lldp_neighbor_free(sd_lldp_neighbor *n) {
++        assert(n);
++
++        free(n->id.port_id);
++        free(n->id.chassis_id);
++        free(n->port_description);
++        free(n->system_name);
++        free(n->system_description);
++        free(n->chassis_id_as_string);
++        free(n->port_id_as_string);
++        free(n);
++}
++
++_public_ sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n) {
++
++        /* Drops one reference from the neighbor. Note that the object is not freed unless it is already unlinked from
++         * the sd_lldp object. */
++
++        if (!n)
++                return NULL;
++
++        assert(n->n_ref > 0);
++        n->n_ref--;
++
++        if (n->n_ref <= 0 && !n->lldp)
++                lldp_neighbor_free(n);
++
++        return NULL;
++}
++
++sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n) {
++
++        /* Removes the neighbor object from the LLDP object, and frees it if it also has no other reference. */
++
++        if (!n)
++                return NULL;
++
++        if (!n->lldp)
++                return NULL;
++
++        /* Only remove the neighbor object from the hash table if it's in there, don't complain if it isn't. This is
++         * because we are used as destructor call for hashmap_clear() and thus sometimes are called to de-register
++         * ourselves from the hashtable and sometimes are called after we already are de-registered. */
++
++        (void) hashmap_remove_value(n->lldp->neighbor_by_id, &n->id, n);
++
++        assert_se(prioq_remove(n->lldp->neighbor_by_expiry, n, &n->prioq_idx) >= 0);
++
++        n->lldp = NULL;
++
++        if (n->n_ref <= 0)
++                lldp_neighbor_free(n);
++
++        return NULL;
++}
++
++sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size) {
++        sd_lldp_neighbor *n;
++
++        n = malloc0(ALIGN(sizeof(sd_lldp_neighbor)) + raw_size);
++        if (!n)
++                return NULL;
++
++        n->raw_size = raw_size;
++        n->n_ref = 1;
++
++        return n;
++}
++
++static int parse_string(char **s, const void *q, size_t n) {
++        const char *p = q;
++        char *k;
++
++        assert(s);
++        assert(p || n == 0);
++
++        if (*s) {
++                log_lldp("Found duplicate string, ignoring field.");
++                return 0;
++        }
++
++        /* Strip trailing NULs, just to be nice */
++        while (n > 0 && p[n-1] == 0)
++                n--;
++
++        if (n <= 0) /* Ignore empty strings */
++                return 0;
++
++        /* Look for inner NULs */
++        if (memchr(p, 0, n)) {
++                log_lldp("Found inner NUL in string, ignoring field.");
++                return 0;
++        }
++
++        /* Let's escape weird chars, for security reasons */
++        k = cescape_length(p, n);
++        if (!k)
++                return -ENOMEM;
++
++        free(*s);
++        *s = k;
++
++        return 1;
++}
++
++int lldp_neighbor_parse(sd_lldp_neighbor *n) {
++        struct ether_header h;
++        const uint8_t *p;
++        size_t left;
++        int r;
++
++        assert(n);
++
++        if (n->raw_size < sizeof(struct ether_header)) {
++                log_lldp("Received truncated packet, ignoring.");
++                return -EBADMSG;
++        }
++
++        memcpy(&h, LLDP_NEIGHBOR_RAW(n), sizeof(h));
++
++        if (h.ether_type != htobe16(ETHERTYPE_LLDP)) {
++                log_lldp("Received packet with wrong type, ignoring.");
++                return -EBADMSG;
++        }
++
++        if (h.ether_dhost[0] != 0x01 ||
++            h.ether_dhost[1] != 0x80 ||
++            h.ether_dhost[2] != 0xc2 ||
++            h.ether_dhost[3] != 0x00 ||
++            h.ether_dhost[4] != 0x00 ||
++            !IN_SET(h.ether_dhost[5], 0x00, 0x03, 0x0e)) {
++                log_lldp("Received packet with wrong destination address, ignoring.");
++                return -EBADMSG;
++        }
++
++        memcpy(&n->source_address, h.ether_shost, sizeof(struct ether_addr));
++        memcpy(&n->destination_address, h.ether_dhost, sizeof(struct ether_addr));
++
++        p = (const uint8_t*) LLDP_NEIGHBOR_RAW(n) + sizeof(struct ether_header);
++        left = n->raw_size - sizeof(struct ether_header);
++
++        for (;;) {
++                uint8_t type;
++                uint16_t length;
++
++                if (left < 2) {
++                        log_lldp("TLV lacks header, ignoring.");
++                        return -EBADMSG;
++                }
++
++                type = p[0] >> 1;
++                length = p[1] + (((uint16_t) (p[0] & 1)) << 8);
++                p += 2, left -= 2;
++
++                if (left < length) {
++                        log_lldp("TLV truncated, ignoring datagram.");
++                        return -EBADMSG;
++                }
++
++                switch (type) {
++
++                case SD_LLDP_TYPE_END:
++                        if (length != 0) {
++                                log_lldp("End marker TLV not zero-sized, ignoring datagram.");
++                                return -EBADMSG;
++                        }
++
++                        /* Note that after processing the SD_LLDP_TYPE_END left could still be > 0
++                         * as the message may contain padding (see IEEE 802.1AB-2016, sec. 8.5.12) */
++
++                        goto end_marker;
++
++                case SD_LLDP_TYPE_CHASSIS_ID:
++                        if (length < 2 || length > 256) { /* includes the chassis subtype, hence one extra byte */
++                                log_lldp("Chassis ID field size out of range, ignoring datagram.");
++                                return -EBADMSG;
++                        }
++                        if (n->id.chassis_id) {
++                                log_lldp("Duplicate chassis ID field, ignoring datagram.");
++                                return -EBADMSG;
++                        }
++
++                        n->id.chassis_id = memdup(p, length);
++                        if (!n->id.chassis_id)
++                                return -ENOMEM;
++
++                        n->id.chassis_id_size = length;
++                        break;
++
++                case SD_LLDP_TYPE_PORT_ID:
++                        if (length < 2 || length > 256) { /* includes the port subtype, hence one extra byte */
++                                log_lldp("Port ID field size out of range, ignoring datagram.");
++                                return -EBADMSG;
++                        }
++                        if (n->id.port_id) {
++                                log_lldp("Duplicate port ID field, ignoring datagram.");
++                                return -EBADMSG;
++                        }
++
++                        n->id.port_id = memdup(p, length);
++                        if (!n->id.port_id)
++                                return -ENOMEM;
++
++                        n->id.port_id_size = length;
++                        break;
++
++                case SD_LLDP_TYPE_TTL:
++                        if (length != 2) {
++                                log_lldp("TTL field has wrong size, ignoring datagram.");
++                                return -EBADMSG;
++                        }
++
++                        if (n->has_ttl) {
++                                log_lldp("Duplicate TTL field, ignoring datagram.");
++                                return -EBADMSG;
++                        }
++
++                        n->ttl = unaligned_read_be16(p);
++                        n->has_ttl = true;
++                        break;
++
++                case SD_LLDP_TYPE_PORT_DESCRIPTION:
++                        r = parse_string(&n->port_description, p, length);
++                        if (r < 0)
++                                return r;
++                        break;
++
++                case SD_LLDP_TYPE_SYSTEM_NAME:
++                        r = parse_string(&n->system_name, p, length);
++                        if (r < 0)
++                                return r;
++                        break;
++
++                case SD_LLDP_TYPE_SYSTEM_DESCRIPTION:
++                        r = parse_string(&n->system_description, p, length);
++                        if (r < 0)
++                                return r;
++                        break;
++
++                case SD_LLDP_TYPE_SYSTEM_CAPABILITIES:
++                        if (length != 4)
++                                log_lldp("System capabilities field has wrong size, ignoring.");
++                        else {
++                                n->system_capabilities = unaligned_read_be16(p);
++                                n->enabled_capabilities = unaligned_read_be16(p + 2);
++                                n->has_capabilities = true;
++                        }
++
++                        break;
++
++                case SD_LLDP_TYPE_PRIVATE:
++                        if (length < 4)
++                                log_lldp("Found private TLV that is too short, ignoring.");
++
++                        break;
++                }
++
++                p += length, left -= length;
++        }
++
++end_marker:
++        if (!n->id.chassis_id || !n->id.port_id || !n->has_ttl) {
++                log_lldp("One or more mandatory TLV missing in datagram. Ignoring.");
++                return -EBADMSG;
++
++        }
++
++        n->rindex = sizeof(struct ether_header);
++
++        return 0;
++}
++
++void lldp_neighbor_start_ttl(sd_lldp_neighbor *n) {
++        assert(n);
++
++        if (n->ttl > 0) {
++                usec_t base;
++
++                /* Use the packet's timestamp if there is one known */
++                base = triple_timestamp_by_clock(&n->timestamp, clock_boottime_or_monotonic());
++                if (base <= 0 || base == USEC_INFINITY)
++                        base = now(clock_boottime_or_monotonic()); /* Otherwise, take the current time */
++
++                n->until = usec_add(base, n->ttl * USEC_PER_SEC);
++        } else
++                n->until = 0;
++
++        if (n->lldp)
++                prioq_reshuffle(n->lldp->neighbor_by_expiry, n, &n->prioq_idx);
++}
++
++bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b) {
++        if (a == b)
++                return true;
++
++        if (!a || !b)
++                return false;
++
++        if (a->raw_size != b->raw_size)
++                return false;
++
++        return memcmp(LLDP_NEIGHBOR_RAW(a), LLDP_NEIGHBOR_RAW(b), a->raw_size) == 0;
++}
++
++_public_ int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address) {
++        assert_return(n, -EINVAL);
++        assert_return(address, -EINVAL);
++
++        *address = n->source_address;
++        return 0;
++}
++
++_public_ int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address) {
++        assert_return(n, -EINVAL);
++        assert_return(address, -EINVAL);
++
++        *address = n->destination_address;
++        return 0;
++}
++
++_public_ int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) {
++        assert_return(n, -EINVAL);
++        assert_return(ret, -EINVAL);
++        assert_return(size, -EINVAL);
++
++        *ret = LLDP_NEIGHBOR_RAW(n);
++        *size = n->raw_size;
++
++        return 0;
++}
++
++_public_ int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) {
++        assert_return(n, -EINVAL);
++        assert_return(type, -EINVAL);
++        assert_return(ret, -EINVAL);
++        assert_return(size, -EINVAL);
++
++        assert(n->id.chassis_id_size > 0);
++
++        *type = *(uint8_t*) n->id.chassis_id;
++        *ret = (uint8_t*) n->id.chassis_id + 1;
++        *size = n->id.chassis_id_size - 1;
++
++        return 0;
++}
++
++static int format_mac_address(const void *data, size_t sz, char **ret) {
++        struct ether_addr a;
++        char *k;
++
++        assert(data || sz <= 0);
++
++        if (sz != 7)
++                return 0;
++
++        memcpy(&a, (uint8_t*) data + 1, sizeof(a));
++
++        k = new(char, ETHER_ADDR_TO_STRING_MAX);
++        if (!k)
++                return -ENOMEM;
++
++        *ret = ether_addr_to_string(&a, k);
++        return 1;
++}
++
++static int format_network_address(const void *data, size_t sz, char **ret) {
++        union in_addr_union a;
++        int family, r;
++
++        if (sz == 6 && ((uint8_t*) data)[1] == 1) {
++                memcpy(&a.in, (uint8_t*) data + 2, sizeof(a.in));
++                family = AF_INET;
++        } else if (sz == 18 && ((uint8_t*) data)[1] == 2) {
++                memcpy(&a.in6, (uint8_t*) data + 2, sizeof(a.in6));
++                family = AF_INET6;
++        } else
++                return 0;
++
++        r = in_addr_to_string(family, &a, ret);
++        if (r < 0)
++                return r;
++        return 1;
++}
++
++_public_ int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret) {
++        char *k;
++        int r;
++
++        assert_return(n, -EINVAL);
++        assert_return(ret, -EINVAL);
++
++        if (n->chassis_id_as_string) {
++                *ret = n->chassis_id_as_string;
++                return 0;
++        }
++
++        assert(n->id.chassis_id_size > 0);
++
++        switch (*(uint8_t*) n->id.chassis_id) {
++
++        case SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT:
++        case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS:
++        case SD_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT:
++        case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME:
++        case SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED:
++                k = cescape_length((char*) n->id.chassis_id + 1, n->id.chassis_id_size - 1);
++                if (!k)
++                        return -ENOMEM;
++
++                goto done;
++
++        case SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS:
++                r = format_mac_address(n->id.chassis_id, n->id.chassis_id_size, &k);
++                if (r < 0)
++                        return r;
++                if (r > 0)
++                        goto done;
++
++                break;
++
++        case SD_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS:
++                r = format_network_address(n->id.chassis_id, n->id.chassis_id_size, &k);
++                if (r < 0)
++                        return r;
++                if (r > 0)
++                        goto done;
++
++                break;
++        }
++
++        /* Generic fallback */
++        k = hexmem(n->id.chassis_id, n->id.chassis_id_size);
++        if (!k)
++                return -ENOMEM;
++
++done:
++        *ret = n->chassis_id_as_string = k;
++        return 0;
++}
++
++_public_ int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) {
++        assert_return(n, -EINVAL);
++        assert_return(type, -EINVAL);
++        assert_return(ret, -EINVAL);
++        assert_return(size, -EINVAL);
++
++        assert(n->id.port_id_size > 0);
++
++        *type = *(uint8_t*) n->id.port_id;
++        *ret = (uint8_t*) n->id.port_id + 1;
++        *size = n->id.port_id_size - 1;
++
++        return 0;
++}
++
++_public_ int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret) {
++        char *k;
++        int r;
++
++        assert_return(n, -EINVAL);
++        assert_return(ret, -EINVAL);
++
++        if (n->port_id_as_string) {
++                *ret = n->port_id_as_string;
++                return 0;
++        }
++
++        assert(n->id.port_id_size > 0);
++
++        switch (*(uint8_t*) n->id.port_id) {
++
++        case SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS:
++        case SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT:
++        case SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME:
++        case SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED:
++                k = cescape_length((char*) n->id.port_id + 1, n->id.port_id_size - 1);
++                if (!k)
++                        return -ENOMEM;
++
++                goto done;
++
++        case SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS:
++                r = format_mac_address(n->id.port_id, n->id.port_id_size, &k);
++                if (r < 0)
++                        return r;
++                if (r > 0)
++                        goto done;
++
++                break;
++
++        case SD_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS:
++                r = format_network_address(n->id.port_id, n->id.port_id_size, &k);
++                if (r < 0)
++                        return r;
++                if (r > 0)
++                        goto done;
++
++                break;
++        }
++
++        /* Generic fallback */
++        k = hexmem(n->id.port_id, n->id.port_id_size);
++        if (!k)
++                return -ENOMEM;
++
++done:
++        *ret = n->port_id_as_string = k;
++        return 0;
++}
++
++_public_ int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec) {
++        assert_return(n, -EINVAL);
++        assert_return(ret_sec, -EINVAL);
++
++        *ret_sec = n->ttl;
++        return 0;
++}
++
++_public_ int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret) {
++        assert_return(n, -EINVAL);
++        assert_return(ret, -EINVAL);
++
++        if (!n->system_name)
++                return -ENODATA;
++
++        *ret = n->system_name;
++        return 0;
++}
++
++_public_ int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret) {
++        assert_return(n, -EINVAL);
++        assert_return(ret, -EINVAL);
++
++        if (!n->system_description)
++                return -ENODATA;
++
++        *ret = n->system_description;
++        return 0;
++}
++
++_public_ int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret) {
++        assert_return(n, -EINVAL);
++        assert_return(ret, -EINVAL);
++
++        if (!n->port_description)
++                return -ENODATA;
++
++        *ret = n->port_description;
++        return 0;
++}
++
++_public_ int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret) {
++        assert_return(n, -EINVAL);
++        assert_return(ret, -EINVAL);
++
++        if (!n->has_capabilities)
++                return -ENODATA;
++
++        *ret = n->system_capabilities;
++        return 0;
++}
++
++_public_ int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret) {
++        assert_return(n, -EINVAL);
++        assert_return(ret, -EINVAL);
++
++        if (!n->has_capabilities)
++                return -ENODATA;
++
++        *ret = n->enabled_capabilities;
++        return 0;
++}
++
++_public_ int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size) {
++        _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
++        int r;
++
++        assert_return(ret, -EINVAL);
++        assert_return(raw || raw_size <= 0, -EINVAL);
++
++        n = lldp_neighbor_new(raw_size);
++        if (!n)
++                return -ENOMEM;
++
++        memcpy(LLDP_NEIGHBOR_RAW(n), raw, raw_size);
++        r = lldp_neighbor_parse(n);
++        if (r < 0)
++                return r;
++
++        *ret = TAKE_PTR(n);
++
++        return r;
++}
++
++_public_ int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n) {
++        assert_return(n, -EINVAL);
++
++        assert(n->raw_size >= sizeof(struct ether_header));
++        n->rindex = sizeof(struct ether_header);
++
++        return n->rindex < n->raw_size;
++}
++
++_public_ int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n) {
++        size_t length;
++
++        assert_return(n, -EINVAL);
++
++        if (n->rindex == n->raw_size) /* EOF */
++                return -ESPIPE;
++
++        if (n->rindex + 2 > n->raw_size) /* Truncated message */
++                return -EBADMSG;
++
++        length = LLDP_NEIGHBOR_TLV_LENGTH(n);
++        if (n->rindex + 2 + length > n->raw_size)
++                return -EBADMSG;
++
++        n->rindex += 2 + length;
++        return n->rindex < n->raw_size;
++}
++
++_public_ int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type) {
++        assert_return(n, -EINVAL);
++        assert_return(type, -EINVAL);
++
++        if (n->rindex == n->raw_size) /* EOF */
++                return -ESPIPE;
++
++        if (n->rindex + 2 > n->raw_size)
++                return -EBADMSG;
++
++        *type = LLDP_NEIGHBOR_TLV_TYPE(n);
++        return 0;
++}
++
++_public_ int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type) {
++        uint8_t k;
++        int r;
++
++        assert_return(n, -EINVAL);
++
++        r = sd_lldp_neighbor_tlv_get_type(n, &k);
++        if (r < 0)
++                return r;
++
++        return type == k;
++}
++
++_public_ int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t *subtype) {
++        const uint8_t *d;
++        size_t length;
++        int r;
++
++        assert_return(n, -EINVAL);
++        assert_return(oui, -EINVAL);
++        assert_return(subtype, -EINVAL);
++
++        r = sd_lldp_neighbor_tlv_is_type(n, SD_LLDP_TYPE_PRIVATE);
++        if (r < 0)
++                return r;
++        if (r == 0)
++                return -ENXIO;
++
++        length = LLDP_NEIGHBOR_TLV_LENGTH(n);
++        if (length < 4)
++                return -EBADMSG;
++
++        if (n->rindex + 2 + length > n->raw_size)
++                return -EBADMSG;
++
++        d = LLDP_NEIGHBOR_TLV_DATA(n);
++        memcpy(oui, d, 3);
++        *subtype = d[3];
++
++        return 0;
++}
++
++_public_ int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t subtype) {
++        uint8_t k[3], st;
++        int r;
++
++        r = sd_lldp_neighbor_tlv_get_oui(n, k, &st);
++        if (r == -ENXIO)
++                return 0;
++        if (r < 0)
++                return r;
++
++        return memcmp(k, oui, 3) == 0 && st == subtype;
++}
++
++_public_ int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) {
++        size_t length;
++
++        assert_return(n, -EINVAL);
++        assert_return(ret, -EINVAL);
++        assert_return(size, -EINVAL);
++
++        /* Note that this returns the full TLV, including the TLV header */
++
++        if (n->rindex + 2 > n->raw_size)
++                return -EBADMSG;
++
++        length = LLDP_NEIGHBOR_TLV_LENGTH(n);
++        if (n->rindex + 2 + length > n->raw_size)
++                return -EBADMSG;
++
++        *ret = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex;
++        *size = length + 2;
++
++        return 0;
++}
++
++_public_ int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret) {
++        assert_return(n, -EINVAL);
++        assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP);
++        assert_return(clock_supported(clock), -EOPNOTSUPP);
++        assert_return(ret, -EINVAL);
++
++        if (!triple_timestamp_is_set(&n->timestamp))
++                return -ENODATA;
++
++        *ret = triple_timestamp_by_clock(&n->timestamp, clock);
++        return 0;
++}
+diff --git a/src/libsystemd/sd-lldp/lldp-neighbor.h b/src/libsystemd/sd-lldp/lldp-neighbor.h
+new file mode 100644
+index 0000000000..62dbff42ca
+--- /dev/null
++++ b/src/libsystemd/sd-lldp/lldp-neighbor.h
+@@ -0,0 +1,91 @@
++/* SPDX-License-Identifier: LGPL-2.1+ */
++#pragma once
++
++#include <inttypes.h>
++#include <stdbool.h>
++#include <sys/types.h>
++
++#include "sd-lldp.h"
++
++#include "hash-funcs.h"
++#include "lldp-internal.h"
++#include "time-util.h"
++
++typedef struct LLDPNeighborID {
++        /* The spec calls this an "MSAP identifier" */
++        void *chassis_id;
++        size_t chassis_id_size;
++
++        void *port_id;
++        size_t port_id_size;
++} LLDPNeighborID;
++
++struct sd_lldp_neighbor {
++        /* Neighbor objects stay around as long as they are linked into an "sd_lldp" object or n_ref > 0. */
++        sd_lldp *lldp;
++        unsigned n_ref;
++
++        triple_timestamp timestamp;
++
++        usec_t until;
++        unsigned prioq_idx;
++
++        struct ether_addr source_address;
++        struct ether_addr destination_address;
++
++        LLDPNeighborID id;
++
++        /* The raw packet size. The data is appended to the object, accessible via LLDP_NEIGHBOR_RAW() */
++        size_t raw_size;
++
++        /* The current read index for the iterative TLV interface */
++        size_t rindex;
++
++        /* And a couple of fields parsed out. */
++        bool has_ttl:1;
++        bool has_capabilities:1;
++        bool has_port_vlan_id:1;
++
++        uint16_t ttl;
++
++        uint16_t system_capabilities;
++        uint16_t enabled_capabilities;
++
++        char *port_description;
++        char *system_name;
++        char *system_description;
++
++        uint16_t port_vlan_id;
++
++        char *chassis_id_as_string;
++        char *port_id_as_string;
++};
++
++static inline void *LLDP_NEIGHBOR_RAW(const sd_lldp_neighbor *n) {
++        return (uint8_t*) n + ALIGN(sizeof(sd_lldp_neighbor));
++}
++
++static inline uint8_t LLDP_NEIGHBOR_TLV_TYPE(const sd_lldp_neighbor *n) {
++        return ((uint8_t*) LLDP_NEIGHBOR_RAW(n))[n->rindex] >> 1;
++}
++
++static inline size_t LLDP_NEIGHBOR_TLV_LENGTH(const sd_lldp_neighbor *n) {
++        uint8_t *p;
++
++        p = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex;
++        return p[1] + (((size_t) (p[0] & 1)) << 8);
++}
++
++static inline void* LLDP_NEIGHBOR_TLV_DATA(const sd_lldp_neighbor *n) {
++        return ((uint8_t*) LLDP_NEIGHBOR_RAW(n)) + n->rindex + 2;
++}
++
++extern const struct hash_ops lldp_neighbor_hash_ops;
++int lldp_neighbor_id_compare_func(const LLDPNeighborID *x, const LLDPNeighborID *y);
++int lldp_neighbor_prioq_compare_func(const void *a, const void *b);
++
++sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n);
++sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size);
++int lldp_neighbor_parse(sd_lldp_neighbor *n);
++void lldp_neighbor_start_ttl(sd_lldp_neighbor *n);
++bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b);
+diff --git a/src/libsystemd/sd-lldp/lldp-network.c b/src/libsystemd/sd-lldp/lldp-network.c
+new file mode 100644
+index 0000000000..870584c0db
+--- /dev/null
++++ b/src/libsystemd/sd-lldp/lldp-network.c
+@@ -0,0 +1,78 @@
++/* SPDX-License-Identifier: LGPL-2.1+ */
++
++#include <linux/filter.h>
++#include <netinet/if_ether.h>
++
++#include "fd-util.h"
++#include "lldp-network.h"
++#include "missing.h"
++#include "socket-util.h"
++
++int lldp_network_bind_raw_socket(int ifindex) {
++
++        static const struct sock_filter filter[] = {
++                BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ethhdr, h_dest)),      /* A <- 4 bytes of destination MAC */
++                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0180c200, 1, 0),                    /* A != 01:80:c2:00 */
++                BPF_STMT(BPF_RET + BPF_K, 0),                                             /* drop packet */
++                BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ethhdr, h_dest) + 4),  /* A <- remaining 2 bytes of destination MAC */
++                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0000, 3, 0),                        /* A != 00:00 */
++                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0003, 2, 0),                        /* A != 00:03 */
++                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x000e, 1, 0),                        /* A != 00:0e */
++                BPF_STMT(BPF_RET + BPF_K, 0),                                             /* drop packet */
++                BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ethhdr, h_proto)),     /* A <- protocol */
++                BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_LLDP, 1, 0),                /* A != ETHERTYPE_LLDP */
++                BPF_STMT(BPF_RET + BPF_K, 0),                                             /* drop packet */
++                BPF_STMT(BPF_RET + BPF_K, (uint32_t) -1),                                 /* accept packet */
++        };
++
++        static const struct sock_fprog fprog = {
++                .len = ELEMENTSOF(filter),
++                .filter = (struct sock_filter*) filter,
++        };
++
++        struct packet_mreq mreq = {
++                .mr_ifindex = ifindex,
++                .mr_type = PACKET_MR_MULTICAST,
++                .mr_alen = ETH_ALEN,
++                .mr_address = { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x00 }
++        };
++
++        union sockaddr_union saddrll = {
++                .ll.sll_family = AF_PACKET,
++                .ll.sll_ifindex = ifindex,
++        };
++
++        _cleanup_close_ int fd = -1;
++        int r;
++
++        assert(ifindex > 0);
++
++        fd = socket(PF_PACKET, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK,
++                    htobe16(ETHERTYPE_LLDP));
++        if (fd < 0)
++                return -errno;
++
++        r = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
++        if (r < 0)
++                return -errno;
++
++        r = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
++        if (r < 0)
++                return -errno;
++
++        mreq.mr_address[ETH_ALEN - 1] = 0x03;
++        r = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
++        if (r < 0)
++                return -errno;
++
++        mreq.mr_address[ETH_ALEN - 1] = 0x0E;
++        r = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
++        if (r < 0)
++                return -errno;
++
++        r = bind(fd, &saddrll.sa, sizeof(saddrll.ll));
++        if (r < 0)
++                return -errno;
++
++        return TAKE_FD(fd);
++}
+diff --git a/src/libsystemd/sd-lldp/lldp-network.h b/src/libsystemd/sd-lldp/lldp-network.h
+new file mode 100644
+index 0000000000..e4ed2898a5
+--- /dev/null
++++ b/src/libsystemd/sd-lldp/lldp-network.h
+@@ -0,0 +1,6 @@
++/* SPDX-License-Identifier: LGPL-2.1+ */
++#pragma once
++
++#include "sd-event.h"
++
++int lldp_network_bind_raw_socket(int ifindex);
+diff --git a/src/libsystemd/sd-lldp/sd-lldp.c b/src/libsystemd/sd-lldp/sd-lldp.c
+new file mode 100644
+index 0000000000..1f28c5731f
+--- /dev/null
++++ b/src/libsystemd/sd-lldp/sd-lldp.c
+@@ -0,0 +1,498 @@
++/* SPDX-License-Identifier: LGPL-2.1+ */
++
++#include <arpa/inet.h>
++#include <linux/sockios.h>
++#include <sys/ioctl.h>
++
++#include "sd-lldp.h"
++
++#include "alloc-util.h"
++#include "ether-addr-util.h"
++#include "event-util.h"
++#include "fd-util.h"
++#include "lldp-internal.h"
++#include "lldp-neighbor.h"
++#include "lldp-network.h"
++#include "memory-util.h"
++#include "socket-util.h"
++#include "sort-util.h"
++#include "string-table.h"
++
++#define LLDP_DEFAULT_NEIGHBORS_MAX 128U
++
++static const char * const lldp_event_table[_SD_LLDP_EVENT_MAX] = {
++        [SD_LLDP_EVENT_ADDED]   = "added",
++        [SD_LLDP_EVENT_REMOVED] = "removed",
++        [SD_LLDP_EVENT_UPDATED]   = "updated",
++        [SD_LLDP_EVENT_REFRESHED] = "refreshed",
++};
++
++DEFINE_STRING_TABLE_LOOKUP(lldp_event, sd_lldp_event);
++
++static void lldp_flush_neighbors(sd_lldp *lldp) {
++        assert(lldp);
++
++        hashmap_clear(lldp->neighbor_by_id);
++}
++
++static void lldp_callback(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n) {
++        assert(lldp);
++        assert(event >= 0 && event < _SD_LLDP_EVENT_MAX);
++
++        if (!lldp->callback) {
++                log_lldp("Received '%s' event.", lldp_event_to_string(event));
++                return;
++        }
++
++        log_lldp("Invoking callback for '%s' event.", lldp_event_to_string(event));
++        lldp->callback(lldp, event, n, lldp->userdata);
++}
++
++static int lldp_make_space(sd_lldp *lldp, size_t extra) {
++        usec_t t = USEC_INFINITY;
++        bool changed = false;
++
++        assert(lldp);
++
++        /* Remove all entries that are past their TTL, and more until at least the specified number of extra entries
++         * are free. */
++
++        for (;;) {
++                _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
++
++                n = prioq_peek(lldp->neighbor_by_expiry);
++                if (!n)
++                        break;
++
++                sd_lldp_neighbor_ref(n);
++
++                if (hashmap_size(lldp->neighbor_by_id) > LESS_BY(lldp->neighbors_max, extra))
++                        goto remove_one;
++
++                if (t == USEC_INFINITY)
++                        t = now(clock_boottime_or_monotonic());
++
++                if (n->until > t)
++                        break;
++
++        remove_one:
++                lldp_neighbor_unlink(n);
++                lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, n);
++                changed = true;
++        }
++
++        return changed;
++}
++
++static bool lldp_keep_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
++        assert(lldp);
++        assert(n);
++
++        /* Don't keep data with a zero TTL */
++        if (n->ttl <= 0)
++                return false;
++
++        /* Filter out data from the filter address */
++        if (!ether_addr_is_null(&lldp->filter_address) &&
++            ether_addr_equal(&lldp->filter_address, &n->source_address))
++                return false;
++
++        /* Only add if the neighbor has a capability we are interested in. Note that we also store all neighbors with
++         * no caps field set. */
++        if (n->has_capabilities &&
++            (n->enabled_capabilities & lldp->capability_mask) == 0)
++                return false;
++
++        /* Keep everything else */
++        return true;
++}
++
++static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor);
++
++static int lldp_add_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
++        _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *old = NULL;
++        bool keep;
++        int r;
++
++        assert(lldp);
++        assert(n);
++        assert(!n->lldp);
++
++        keep = lldp_keep_neighbor(lldp, n);
++
++        /* First retrieve the old entry for this MSAP */
++        old = hashmap_get(lldp->neighbor_by_id, &n->id);
++        if (old) {
++                sd_lldp_neighbor_ref(old);
++
++                if (!keep) {
++                        lldp_neighbor_unlink(old);
++                        lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old);
++                        return 0;
++                }
++
++                if (lldp_neighbor_equal(n, old)) {
++                        /* Is this equal, then restart the TTL counter, but don't do anything else. */
++                        old->timestamp = n->timestamp;
++                        lldp_start_timer(lldp, old);
++                        lldp_callback(lldp, SD_LLDP_EVENT_REFRESHED, old);
++                        return 0;
++                }
++
++                /* Data changed, remove the old entry, and add a new one */
++                lldp_neighbor_unlink(old);
++
++        } else if (!keep)
++                return 0;
++
++        /* Then, make room for at least one new neighbor */
++        lldp_make_space(lldp, 1);
++
++        r = hashmap_put(lldp->neighbor_by_id, &n->id, n);
++        if (r < 0)
++                goto finish;
++
++        r = prioq_put(lldp->neighbor_by_expiry, n, &n->prioq_idx);
++        if (r < 0) {
++                assert_se(hashmap_remove(lldp->neighbor_by_id, &n->id) == n);
++                goto finish;
++        }
++
++        n->lldp = lldp;
++
++        lldp_start_timer(lldp, n);
++        lldp_callback(lldp, old ? SD_LLDP_EVENT_UPDATED : SD_LLDP_EVENT_ADDED, n);
++
++        return 1;
++
++finish:
++        if (old)
++                lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old);
++
++        return r;
++}
++
++static int lldp_handle_datagram(sd_lldp *lldp, sd_lldp_neighbor *n) {
++        int r;
++
++        assert(lldp);
++        assert(n);
++
++        r = lldp_neighbor_parse(n);
++        if (r == -EBADMSG) /* Ignore bad messages */
++                return 0;
++        if (r < 0)
++                return r;
++
++        r = lldp_add_neighbor(lldp, n);
++        if (r < 0) {
++                log_lldp_errno(r, "Failed to add datagram. Ignoring.");
++                return 0;
++        }
++
++        log_lldp("Successfully processed LLDP datagram.");
++        return 0;
++}
++
++static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
++        _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
++        ssize_t space, length;
++        sd_lldp *lldp = userdata;
++        struct timespec ts;
++
++        assert(fd >= 0);
++        assert(lldp);
++
++        space = next_datagram_size_fd(fd);
++        if (space < 0)
++                return log_lldp_errno(space, "Failed to determine datagram size to read: %m");
++
++        n = lldp_neighbor_new(space);
++        if (!n)
++                return -ENOMEM;
++
++        length = recv(fd, LLDP_NEIGHBOR_RAW(n), n->raw_size, MSG_DONTWAIT);
++        if (length < 0) {
++                if (IN_SET(errno, EAGAIN, EINTR))
++                        return 0;
++
++                return log_lldp_errno(errno, "Failed to read LLDP datagram: %m");
++        }
++
++        if ((size_t) length != n->raw_size) {
++                log_lldp("Packet size mismatch.");
++                return -EINVAL;
++        }
++
++        /* Try to get the timestamp of this packet if it is known */
++        if (ioctl(fd, SIOCGSTAMPNS, &ts) >= 0)
++                triple_timestamp_from_realtime(&n->timestamp, timespec_load(&ts));
++        else
++                triple_timestamp_get(&n->timestamp);
++
++        return lldp_handle_datagram(lldp, n);
++}
++
++static void lldp_reset(sd_lldp *lldp) {
++        assert(lldp);
++
++        (void) event_source_disable(lldp->timer_event_source);
++        lldp->io_event_source = sd_event_source_unref(lldp->io_event_source);
++        lldp->fd = safe_close(lldp->fd);
++}
++
++_public_ int sd_lldp_start(sd_lldp *lldp) {
++        int r;
++
++        assert_return(lldp, -EINVAL);
++        assert_return(lldp->event, -EINVAL);
++        assert_return(lldp->ifindex > 0, -EINVAL);
++
++        if (lldp->fd >= 0)
++                return 0;
++
++        assert(!lldp->io_event_source);
++
++        lldp->fd = lldp_network_bind_raw_socket(lldp->ifindex);
++        if (lldp->fd < 0)
++                return lldp->fd;
++
++        r = sd_event_add_io(lldp->event, &lldp->io_event_source, lldp->fd, EPOLLIN, lldp_receive_datagram, lldp);
++        if (r < 0)
++                goto fail;
++
++        r = sd_event_source_set_priority(lldp->io_event_source, lldp->event_priority);
++        if (r < 0)
++                goto fail;
++
++        (void) sd_event_source_set_description(lldp->io_event_source, "lldp-io");
++
++        log_lldp("Started LLDP client");
++        return 1;
++
++fail:
++        lldp_reset(lldp);
++        return r;
++}
++
++_public_ int sd_lldp_stop(sd_lldp *lldp) {
++        assert_return(lldp, -EINVAL);
++
++        if (lldp->fd < 0)
++                return 0;
++
++        log_lldp("Stopping LLDP client");
++
++        lldp_reset(lldp);
++        lldp_flush_neighbors(lldp);
++
++        return 1;
++}
++
++_public_ int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority) {
++        int r;
++
++        assert_return(lldp, -EINVAL);
++        assert_return(lldp->fd < 0, -EBUSY);
++        assert_return(!lldp->event, -EBUSY);
++
++        if (event)
++                lldp->event = sd_event_ref(event);
++        else {
++                r = sd_event_default(&lldp->event);
++                if (r < 0)
++                        return r;
++        }
++
++        lldp->event_priority = priority;
++
++        return 0;
++}
++
++_public_ int sd_lldp_detach_event(sd_lldp *lldp) {
++
++        assert_return(lldp, -EINVAL);
++        assert_return(lldp->fd < 0, -EBUSY);
++
++        lldp->event = sd_event_unref(lldp->event);
++        return 0;
++}
++
++_public_ sd_event* sd_lldp_get_event(sd_lldp *lldp) {
++        assert_return(lldp, NULL);
++
++        return lldp->event;
++}
++
++_public_ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata) {
++        assert_return(lldp, -EINVAL);
++
++        lldp->callback = cb;
++        lldp->userdata = userdata;
++
++        return 0;
++}
++
++_public_ int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex) {
++        assert_return(lldp, -EINVAL);
++        assert_return(ifindex > 0, -EINVAL);
++        assert_return(lldp->fd < 0, -EBUSY);
++
++        lldp->ifindex = ifindex;
++        return 0;
++}
++
++static sd_lldp* lldp_free(sd_lldp *lldp) {
++        assert(lldp);
++
++        lldp->timer_event_source = sd_event_source_unref(lldp->timer_event_source);
++
++        lldp_reset(lldp);
++        sd_lldp_detach_event(lldp);
++        lldp_flush_neighbors(lldp);
++
++        hashmap_free(lldp->neighbor_by_id);
++        prioq_free(lldp->neighbor_by_expiry);
++        return mfree(lldp);
++}
++
++DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_lldp, sd_lldp, lldp_free);
++
++_public_ int sd_lldp_new(sd_lldp **ret) {
++        _cleanup_(sd_lldp_unrefp) sd_lldp *lldp = NULL;
++        int r;
++
++        assert_return(ret, -EINVAL);
++
++        lldp = new(sd_lldp, 1);
++        if (!lldp)
++                return -ENOMEM;
++
++        *lldp = (sd_lldp) {
++                .n_ref = 1,
++                .fd = -1,
++                .neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX,
++                .capability_mask = (uint16_t) -1,
++        };
++
++        lldp->neighbor_by_id = hashmap_new(&lldp_neighbor_hash_ops);
++        if (!lldp->neighbor_by_id)
++                return -ENOMEM;
++
++        r = prioq_ensure_allocated(&lldp->neighbor_by_expiry, lldp_neighbor_prioq_compare_func);
++        if (r < 0)
++                return r;
++
++        *ret = TAKE_PTR(lldp);
++
++        return 0;
++}
++
++static int neighbor_compare_func(sd_lldp_neighbor * const *a, sd_lldp_neighbor * const *b) {
++        return lldp_neighbor_id_compare_func(&(*a)->id, &(*b)->id);
++}
++
++static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) {
++        sd_lldp *lldp = userdata;
++        int r;
++
++        r = lldp_make_space(lldp, 0);
++        if (r < 0)
++                return log_lldp_errno(r, "Failed to make space: %m");
++
++        r = lldp_start_timer(lldp, NULL);
++        if (r < 0)
++                return log_lldp_errno(r, "Failed to restart timer: %m");
++
++        return 0;
++}
++
++static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor) {
++        sd_lldp_neighbor *n;
++
++        assert(lldp);
++
++        if (neighbor)
++                lldp_neighbor_start_ttl(neighbor);
++
++        n = prioq_peek(lldp->neighbor_by_expiry);
++        if (!n)
++                return event_source_disable(lldp->timer_event_source);
++
++        if (!lldp->event)
++                return 0;
++
++        return event_reset_time(lldp->event, &lldp->timer_event_source,
++                                clock_boottime_or_monotonic(),
++                                n->until, 0,
++                                on_timer_event, lldp,
++                                lldp->event_priority, "lldp-timer", true);
++}
++
++_public_ int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***ret) {
++        sd_lldp_neighbor **l = NULL, *n;
++        Iterator i;
++        int k = 0, r;
++
++        assert_return(lldp, -EINVAL);
++        assert_return(ret, -EINVAL);
++
++        if (hashmap_isempty(lldp->neighbor_by_id)) { /* Special shortcut */
++                *ret = NULL;
++                return 0;
++        }
++
++        l = new0(sd_lldp_neighbor*, hashmap_size(lldp->neighbor_by_id));
++        if (!l)
++                return -ENOMEM;
++
++        r = lldp_start_timer(lldp, NULL);
++        if (r < 0) {
++                free(l);
++                return r;
++        }
++
++        HASHMAP_FOREACH(n, lldp->neighbor_by_id, i)
++                l[k++] = sd_lldp_neighbor_ref(n);
++
++        assert((size_t) k == hashmap_size(lldp->neighbor_by_id));
++
++        /* Return things in a stable order */
++        typesafe_qsort(l, k, neighbor_compare_func);
++        *ret = l;
++
++        return k;
++}
++
++_public_ int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t m) {
++        assert_return(lldp, -EINVAL);
++        assert_return(m <= 0, -EINVAL);
++
++        lldp->neighbors_max = m;
++        lldp_make_space(lldp, 0);
++
++        return 0;
++}
++
++_public_ int sd_lldp_match_capabilities(sd_lldp *lldp, uint16_t mask) {
++        assert_return(lldp, -EINVAL);
++        assert_return(mask != 0, -EINVAL);
++
++        lldp->capability_mask = mask;
++
++        return 0;
++}
++
++_public_ int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *addr) {
++        assert_return(lldp, -EINVAL);
++
++        /* In order to deal nicely with bridges that send back our own packets, allow one address to be filtered, so
++         * that our own can be filtered out here. */
++
++        if (addr)
++                lldp->filter_address = *addr;
++        else
++                zero(lldp->filter_address);
++
++        return 0;
++}
+diff --git a/src/libsystemd/sd-lldp/test-lldp.c b/src/libsystemd/sd-lldp/test-lldp.c
+new file mode 100644
+index 0000000000..7406f94ce0
+--- /dev/null
++++ b/src/libsystemd/sd-lldp/test-lldp.c
+@@ -0,0 +1,379 @@
++/* SPDX-License-Identifier: LGPL-2.1+ */
++
++#include <arpa/inet.h>
++#include <errno.h>
++#include <net/ethernet.h>
++#include <stdio.h>
++#include <string.h>
++#include <unistd.h>
++
++#include "sd-event.h"
++#include "sd-lldp.h"
++
++#include "alloc-util.h"
++#include "fd-util.h"
++#include "lldp-network.h"
++#include "macro.h"
++#include "string-util.h"
++#include "tests.h"
++
++#define TEST_LLDP_PORT "em1"
++#define TEST_LLDP_TYPE_SYSTEM_NAME "systemd-lldp"
++#define TEST_LLDP_TYPE_SYSTEM_DESC "systemd-lldp-desc"
++
++static int test_fd[2] = { -1, -1 };
++static int lldp_handler_calls;
++
++int lldp_network_bind_raw_socket(int ifindex) {
++        if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) < 0)
++                return -errno;
++
++        return test_fd[0];
++}
++
++static void lldp_handler(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata) {
++        lldp_handler_calls++;
++}
++
++static int start_lldp(sd_lldp **lldp, sd_event *e, sd_lldp_callback_t cb, void *cb_data) {
++        int r;
++
++        r = sd_lldp_new(lldp);
++        if (r < 0)
++                return r;
++
++        r = sd_lldp_set_ifindex(*lldp, 42);
++        if (r < 0)
++                return r;
++
++        r = sd_lldp_set_callback(*lldp, cb, cb_data);
++        if (r < 0)
++                return r;
++
++        r = sd_lldp_attach_event(*lldp, e, 0);
++        if (r < 0)
++                return r;
++
++        r = sd_lldp_start(*lldp);
++        if (r < 0)
++                return r;
++
++        return 0;
++}
++
++static int stop_lldp(sd_lldp *lldp) {
++        int r;
++
++        r = sd_lldp_stop(lldp);
++        if (r < 0)
++                return r;
++
++        r = sd_lldp_detach_event(lldp);
++        if (r < 0)
++                return r;
++
++        sd_lldp_unref(lldp);
++        safe_close(test_fd[1]);
++
++        return 0;
++}
++
++static void test_receive_basic_packet(sd_event *e) {
++
++        static const 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 */
++        };
++
++        sd_lldp *lldp;
++        sd_lldp_neighbor **neighbors;
++        uint8_t type;
++        const void *data;
++        uint16_t ttl;
++        size_t length;
++        const char *str;
++
++        lldp_handler_calls = 0;
++        assert_se(start_lldp(&lldp, e, lldp_handler, NULL) == 0);
++
++        assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame));
++        sd_event_run(e, 0);
++        assert_se(lldp_handler_calls == 1);
++        assert_se(sd_lldp_get_neighbors(lldp, &neighbors) == 1);
++
++        assert_se(sd_lldp_neighbor_get_chassis_id(neighbors[0], &type, &data, &length) == 0);
++        assert_se(type == SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS);
++        assert_se(length == ETH_ALEN);
++        assert_se(!memcmp(data, "\x00\x01\x02\x03\x04\x05", ETH_ALEN));
++
++        assert_se(sd_lldp_neighbor_get_port_id(neighbors[0], &type, &data, &length) == 0);
++        assert_se(type == SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME);
++        assert_se(length == 3);
++        assert_se(!memcmp(data, "1/3", 3));
++
++        assert_se(sd_lldp_neighbor_get_port_description(neighbors[0], &str) == 0);
++        assert_se(streq(str, "Port"));
++
++        assert_se(sd_lldp_neighbor_get_system_name(neighbors[0], &str) == 0);
++        assert_se(streq(str, "SYS"));
++
++        assert_se(sd_lldp_neighbor_get_system_description(neighbors[0], &str) == 0);
++        assert_se(streq(str, "foo"));
++
++        assert_se(sd_lldp_neighbor_get_ttl(neighbors[0], &ttl) == 0);
++        assert_se(ttl == 120);
++
++        sd_lldp_neighbor_unref(neighbors[0]);
++        free(neighbors);
++
++        assert_se(stop_lldp(lldp) == 0);
++}
++
++static void test_receive_incomplete_packet(sd_event *e) {
++        sd_lldp *lldp;
++        sd_lldp_neighbor **neighbors;
++        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" */
++                                                        /* Missing TTL */
++                0x00, 0x00                              /* End Of LLDPDU */
++        };
++
++        lldp_handler_calls = 0;
++        assert_se(start_lldp(&lldp, e, lldp_handler, NULL) == 0);
++
++        assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame));
++        sd_event_run(e, 0);
++        assert_se(lldp_handler_calls == 0);
++        assert_se(sd_lldp_get_neighbors(lldp, &neighbors) == 0);
++
++        assert_se(stop_lldp(lldp) == 0);
++}
++
++static void test_receive_oui_packet(sd_event *e) {
++        sd_lldp *lldp;
++        sd_lldp_neighbor **neighbors;
++        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 TLV: interface name, "1/3" */
++                0x06, 0x02, 0x00, 0x78,                 /* TTL: 120 seconds */
++                /* LLDP optional TLVs */
++                0xfe, 0x06, 0x00, 0x80, 0xc2, 0x01,     /* Port VLAN ID: 0x1234 */
++                0x12, 0x34,
++                0xfe, 0x07, 0x00, 0x80, 0xc2, 0x02,     /* Port and protocol: flag 1, PPVID 0x7788 */
++                0x01, 0x77, 0x88,
++                0xfe, 0x0d, 0x00, 0x80, 0xc2, 0x03,     /* VLAN Name: ID 0x1234, name "Vlan51" */
++                0x12, 0x34, 0x06, 0x56, 0x6c, 0x61,
++                0x6e, 0x35, 0x31,
++                0xfe, 0x06, 0x00, 0x80, 0xc2, 0x06,     /* Management VID: 0x0102 */
++                0x01, 0x02,
++                0xfe, 0x09, 0x00, 0x80, 0xc2, 0x07,     /* Link aggregation: status 1, ID 0x00140012 */
++                0x01, 0x00, 0x14, 0x00, 0x12,
++                0xfe, 0x07, 0x00, 0x12, 0x0f, 0x02,     /* 802.3 Power via MDI: PSE, MDI enabled */
++                0x07, 0x01, 0x00,
++                0x00, 0x00                              /* End of LLDPDU */
++        };
++
++        lldp_handler_calls = 0;
++        assert_se(start_lldp(&lldp, e, lldp_handler, NULL) == 0);
++
++        assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame));
++        sd_event_run(e, 0);
++        assert_se(lldp_handler_calls == 1);
++        assert_se(sd_lldp_get_neighbors(lldp, &neighbors) == 1);
++
++        assert_se(sd_lldp_neighbor_tlv_rewind(neighbors[0]) >= 0);
++        assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_CHASSIS_ID) > 0);
++        assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
++        assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_PORT_ID) > 0);
++        assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
++        assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_TTL) > 0);
++        assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
++        assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID) > 0);
++        assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
++        assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID) > 0);
++        assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
++        assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME) > 0);
++        assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
++        assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_MANAGEMENT_VID) > 0);
++        assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
++        assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_LINK_AGGREGATION) > 0);
++        assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
++        assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_3, SD_LLDP_OUI_802_3_SUBTYPE_POWER_VIA_MDI) > 0);
++        assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
++        assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_END) > 0);
++        assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) == 0);
++
++        sd_lldp_neighbor_unref(neighbors[0]);
++        free(neighbors);
++
++        assert_se(stop_lldp(lldp) == 0);
++}
++
++static void test_multiple_neighbors_sorted(sd_event *e) {
++
++        static const uint8_t frame1[] = {
++                /* 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, 0x04, 0x01, '1', '/', '2',        /* Chassis component: "1/2" */
++                0x04, 0x04, 0x02, '2', '/', '3',        /* Port component: "2/3" */
++                0x06, 0x02, 0x00, 0x78,                 /* TTL: 120 seconds */
++                0x00, 0x00                              /* End Of LLDPDU */
++        };
++        static const uint8_t frame2[] = {
++                /* 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, 0x04, 0x01, '2', '/', '1',        /* Chassis component: "2/1" */
++                0x04, 0x04, 0x02, '1', '/', '3',        /* Port component: "1/3" */
++                0x06, 0x02, 0x00, 0x78,                 /* TTL: 120 seconds */
++                0x00, 0x00                              /* End Of LLDPDU */
++        };
++        static const uint8_t frame3[] = {
++                /* 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, 0x05, 0x01, '2', '/', '1', '0',   /* Chassis component: "2/10" */
++                0x04, 0x04, 0x02, '1', '/', '0',        /* Port component: "1/0" */
++                0x06, 0x02, 0x00, 0x78,                 /* TTL: 120 seconds */
++                0x00, 0x00                              /* End Of LLDPDU */
++        };
++        static const uint8_t frame4[] = {
++                /* 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, 0x05, 0x01, '2', '/', '1', '9',   /* Chassis component: "2/19" */
++                0x04, 0x04, 0x02, '1', '/', '0',        /* Port component: "1/0" */
++                0x06, 0x02, 0x00, 0x78,                 /* TTL: 120 seconds */
++                0x00, 0x00                              /* End Of LLDPDU */
++        };
++        static const uint8_t frame5[] = {
++                /* 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, 0x04, 0x01, '1', '/', '2',        /* Chassis component: "1/2" */
++                0x04, 0x05, 0x02, '2', '/', '1', '0',   /* Port component: "2/10" */
++                0x06, 0x02, 0x00, 0x78,                 /* TTL: 120 seconds */
++                0x00, 0x00                              /* End Of LLDPDU */
++        };
++        static const uint8_t frame6[] = {
++                /* 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, 0x04, 0x01, '1', '/', '2',        /* Chassis component: "1/2" */
++                0x04, 0x05, 0x02, '2', '/', '3', '9',   /* Port component: "2/10" */
++                0x06, 0x02, 0x00, 0x78,                 /* TTL: 120 seconds */
++                0x00, 0x00                              /* End Of LLDPDU */
++        };
++        static const char* expected[] = {
++                /* ordered pairs of Chassis+Port */
++                "1/2", "2/10",
++                "1/2", "2/3",
++                "1/2", "2/39",
++                "2/1", "1/3",
++                "2/10", "1/0",
++                "2/19", "1/0",
++        };
++
++        sd_lldp *lldp;
++        sd_lldp_neighbor **neighbors;
++        int i;
++        uint8_t type;
++        const void *data;
++        size_t length, expected_length;
++        uint16_t ttl;
++
++        lldp_handler_calls = 0;
++        assert_se(start_lldp(&lldp, e, lldp_handler, NULL) == 0);
++
++        assert_se(write(test_fd[1], frame1, sizeof(frame1)) == sizeof(frame1));
++        sd_event_run(e, 0);
++        assert_se(write(test_fd[1], frame2, sizeof(frame2)) == sizeof(frame2));
++        sd_event_run(e, 0);
++        assert_se(write(test_fd[1], frame3, sizeof(frame3)) == sizeof(frame3));
++        sd_event_run(e, 0);
++        assert_se(write(test_fd[1], frame4, sizeof(frame4)) == sizeof(frame4));
++        sd_event_run(e, 0);
++        assert_se(write(test_fd[1], frame5, sizeof(frame5)) == sizeof(frame5));
++        sd_event_run(e, 0);
++        assert_se(write(test_fd[1], frame6, sizeof(frame6)) == sizeof(frame6));
++        sd_event_run(e, 0);
++        assert_se(lldp_handler_calls == 6);
++
++        assert_se(sd_lldp_get_neighbors(lldp, &neighbors) == 6);
++
++        for (i = 0; i < 6; i++) {
++                assert_se(sd_lldp_neighbor_get_chassis_id(neighbors[i], &type, &data, &length) == 0);
++                assert_se(type == SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT);
++                expected_length = strlen(expected[2 * i]);
++                assert_se(length == expected_length);
++                assert_se(memcmp(data, expected[2 * i], expected_length) == 0);
++
++                assert_se(sd_lldp_neighbor_get_port_id(neighbors[i], &type, &data, &length) == 0);
++                assert_se(type == SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT);
++                expected_length = strlen(expected[2 * i + 1]);
++                assert_se(length == expected_length);
++                assert_se(memcmp(data, expected[2 * i + 1], expected_length) == 0);
++
++                assert_se(sd_lldp_neighbor_get_ttl(neighbors[i], &ttl) == 0);
++                assert_se(ttl == 120);
++        }
++
++        for (i = 0; i < 6; i++)
++                sd_lldp_neighbor_unref(neighbors[i]);
++        free(neighbors);
++
++        assert_se(stop_lldp(lldp) == 0);
++}
++
++int main(int argc, char *argv[]) {
++        _cleanup_(sd_event_unrefp) sd_event *e = NULL;
++
++        test_setup_logging(LOG_DEBUG);
++
++        /* LLDP reception tests */
++        assert_se(sd_event_new(&e) == 0);
++        test_receive_basic_packet(e);
++        test_receive_incomplete_packet(e);
++        test_receive_oui_packet(e);
++        test_multiple_neighbors_sorted(e);
++
++        return 0;
++}
+diff --git a/src/systemd/meson.build b/src/systemd/meson.build
+index 75c48b07a5..887421bee0 100644
+--- a/src/systemd/meson.build
++++ b/src/systemd/meson.build
+@@ -12,6 +12,7 @@ _systemd_headers = '''
+         sd-journal.h
+         sd-login.h
+         sd-messages.h
++        sd-lldp.h
+ '''.split()
+ 
+ # https://github.com/mesonbuild/meson/issues/1633
+@@ -25,7 +26,6 @@ _not_installed_headers = '''
+         sd-dhcp-server.h
+         sd-ipv4acd.h
+         sd-ipv4ll.h
+-        sd-lldp.h
+         sd-ndisc.h
+         sd-netlink.h
+         sd-network.h
+diff --git a/src/test/meson.build b/src/test/meson.build
+index de31e977bc..680d1dec6b 100644
+--- a/src/test/meson.build
++++ b/src/test/meson.build
+@@ -1010,6 +1010,11 @@ tests += [
+          [],
+          []],
+ 
++        [['src/libsystemd/sd-lldp/test-lldp.c'],
++         [libbasic,libshared_static,libsystemd_static
++          ],
++         []],
++
+ ]
+ 
+ # test-bus-vtable-cc.cc is a symlink and symlinks get lost in containers on FuzzBuzz.
+@@ -1098,11 +1103,6 @@ tests += [
+          [libshared,
+           libsystemd_network],
+          []],
+-
+-        [['src/libsystemd-network/test-lldp.c'],
+-         [libshared,
+-          libsystemd_network],
+-         []],
+ ]
+ 
+ ############################################################
+-- 
+2.28.0
+
diff --git a/ci/build.sh b/ci/build.sh
index 1158727..4fced8e 100755
--- a/ci/build.sh
+++ b/ci/build.sh
@@ -61,7 +61,7 @@
     # We don't use gating, so there's a risk that there's no prebuilt artifact, so don't die if we cannot download that file
     curl ${ARTIFACT_URL} | unzstd --stdout | tar -xf - || echo "No Buildroot prebuilt tarball found, will build from scratch"
 
-    for PROJECT in cla-sysrepo netconf-cli gammarus velia; do
+    for PROJECT in cla-sysrepo netconf-cli gammarus velia lldp-systemd-networkd-sysrepo; do
         # If there's a change for ${PROJECT} queued ahead, ensure it gets used.
         # This means that if our submodules still pin, say, `cla-sysrepo` to some ancient version and we're testing a `netconf-cli` change,
         # then we will keep using that ancient `cla-sysrepo`. Hopefully this reduces the number of false alerts.
diff --git a/configs/czechlight_clearfog_defconfig b/configs/czechlight_clearfog_defconfig
index 97f6117..69acd06 100644
--- a/configs/czechlight_clearfog_defconfig
+++ b/configs/czechlight_clearfog_defconfig
@@ -4,7 +4,7 @@
 BR2_ARM_ENABLE_VFP=y
 BR2_ARM_FPU_NEON=y
 BR2_ENABLE_DEBUG=y
-BR2_STRIP_EXCLUDE_FILES="cla-* netopeer2* sysrepo* libsysrepo* libSysrepo* libnetconf2* libyang* sysrepo-cli veliad"
+BR2_STRIP_EXCLUDE_FILES="cla-* netopeer2* sysrepo* libsysrepo* libSysrepo* libnetconf2* libyang* sysrepo-cli veliad lldp-systemd-networkd-sysrepod"
 BR2_STRIP_EXCLUDE_DIRS="/usr/lib/libyang /usr/lib/sysrepo"
 BR2_GLOBAL_PATCH_DIR="$(BR2_EXTERNAL_CZECHLIGHT_PATH)/board/czechlight/common/patches"
 BR2_PER_PACKAGE_DIRECTORIES=y
@@ -105,4 +105,5 @@
 CZECHLIGHT_RAUC_SLOT_B_CFG_DEV="/dev/mmcblk0p4"
 BR2_PACKAGE_GAMMARUS=y
 BR2_PACKAGE_VELIA=y
+BR2_PACKAGE_LLDP_SYSTEMD_NETWORKD_SYSREPO=y
 CZECHLIGHT_NETCONF=y
diff --git a/dev-setup-git.sh b/dev-setup-git.sh
index 447d48b..1f9aedc 100755
--- a/dev-setup-git.sh
+++ b/dev-setup-git.sh
@@ -24,6 +24,7 @@
 PYBIND11_OVERRIDE_SRCDIR = ${CZECHLIGHT_BR2_EXT_LOC}/submodules/pybind11
 SDBUS_CPP_OVERRIDE_SRCDIR = ${CZECHLIGHT_BR2_EXT_LOC}/submodules/cla-sysrepo/submodules/dependencies/sdbus-cpp
 VELIA_OVERRIDE_SRCDIR = ${CZECHLIGHT_BR2_EXT_LOC}/submodules/velia
+LLDP_SYSTEMD_NETWORKD_SYSREPO_OVERRIDE_SRCDIR = ${CZECHLIGHT_BR2_EXT_LOC}/submodules/lldp-systemd-networkd-sysrepo
 
 define CZECHLIGHT_GIT_FIX_GITDIR
 	echo "gitdir: \$\$(git rev-parse --resolve-git-dir \$(SRCDIR)/.git)" > \$(@D)/.git
@@ -32,6 +33,7 @@
 NETCONF_CLI_POST_RSYNC_HOOKS += CZECHLIGHT_GIT_FIX_GITDIR
 GAMMARUS_POST_RSYNC_HOOKS += CZECHLIGHT_GIT_FIX_GITDIR
 VELIA_POST_RSYNC_HOOKS += CZECHLIGHT_GIT_FIX_GITDIR
+LLDP_SYSTEMD_NETWORKD_SYSREPO_POST_RSYNC_HOOKS += CZECHLIGHT_GIT_FIX_GITDIR
 
 EOF
 
diff --git a/package/Config.in b/package/Config.in
index 13a2666..d09116f 100644
--- a/package/Config.in
+++ b/package/Config.in
@@ -4,5 +4,6 @@
 source "$BR2_EXTERNAL_CZECHLIGHT_PATH/package/czechlight-rauc/Config.in"
 source "$BR2_EXTERNAL_CZECHLIGHT_PATH/package/czechlight-separate-boot/Config.in"
 source "$BR2_EXTERNAL_CZECHLIGHT_PATH/package/gammarus/Config.in"
+source "$BR2_EXTERNAL_CZECHLIGHT_PATH/package/lldp-systemd-networkd-sysrepo/Config.in"
 source "$BR2_EXTERNAL_CZECHLIGHT_PATH/package/velia/Config.in"
 source "$BR2_EXTERNAL_CZECHLIGHT_PATH/package/grub2-tools/Config.in"
diff --git a/package/lldp-systemd-networkd-sysrepo/Config.in b/package/lldp-systemd-networkd-sysrepo/Config.in
new file mode 100644
index 0000000..4b9e418
--- /dev/null
+++ b/package/lldp-systemd-networkd-sysrepo/Config.in
@@ -0,0 +1,12 @@
+config BR2_PACKAGE_LLDP_SYSTEMD_NETWORKD_SYSREPO
+	bool "lldp-systemd-networkd-sysrepo"
+	select BR2_PACKAGE_SPDLOG
+	select BR2_PACKAGE_SDBUS_CPP
+	select BR2_PACKAGE_SYSTEMD
+	select BR2_PACKAGE_DOCOPT_CPP
+	select BR2_PACKAGE_SYSREPO
+	select BR2_PACKAGE_SYSREPO_CPP
+	help
+	  lldp-systemd-networkd-sysrepo is a sysrepo application announcing LLDP neighbours from systemd-networkd.
+
+	  https://gerrit.cesnet.cz/q/project:CzechLight/lldp-systemd-networkd-sysrepo
diff --git a/package/lldp-systemd-networkd-sysrepo/lldp-systemd-networkd-sysrepo-install-yang.service b/package/lldp-systemd-networkd-sysrepo/lldp-systemd-networkd-sysrepo-install-yang.service
new file mode 100644
index 0000000..003f7e2
--- /dev/null
+++ b/package/lldp-systemd-networkd-sysrepo/lldp-systemd-networkd-sysrepo-install-yang.service
@@ -0,0 +1,14 @@
+[Unit]
+Description=Install YANG and initial data for lldp-systemd-networkd-sysrepo
+Requires=sysrepod.service
+Before=sysrepod.service
+
+[Service]
+Type=oneshot
+ExecStart=/usr/bin/sysrepoctl --install --yang /usr/share/lldp-systemd-networkd-sysrepo/yang/czechlight-lldp.yang
+ExecStart=/usr/bin/sysrepoctl --install --yang /usr/share/lldp-systemd-networkd-sysrepo/yang/iana-afn-safi@2013-07-04.yang
+ExecStart=/usr/bin/sysrepoctl --install --yang /usr/share/lldp-systemd-networkd-sysrepo/yang/ietf-inet-types@2013-07-15.yang
+ExecStart=/bin/cp -a /etc/sysrepo /cfg/etc/
+
+[Install]
+WantedBy=multi-user.target
diff --git a/package/lldp-systemd-networkd-sysrepo/lldp-systemd-networkd-sysrepo.hash b/package/lldp-systemd-networkd-sysrepo/lldp-systemd-networkd-sysrepo.hash
new file mode 100644
index 0000000..075e53a
--- /dev/null
+++ b/package/lldp-systemd-networkd-sysrepo/lldp-systemd-networkd-sysrepo.hash
@@ -0,0 +1 @@
+none xxx
diff --git a/package/lldp-systemd-networkd-sysrepo/lldp-systemd-networkd-sysrepo.mk b/package/lldp-systemd-networkd-sysrepo/lldp-systemd-networkd-sysrepo.mk
new file mode 100644
index 0000000..84f6252
--- /dev/null
+++ b/package/lldp-systemd-networkd-sysrepo/lldp-systemd-networkd-sysrepo.mk
@@ -0,0 +1,20 @@
+LLDP_SYSTEMD_NETWORKD_SYSREPO_VERSION = master
+LLDP_SYSTEMD_NETWORKD_SYSREPO_SITE = https://gerrit.cesnet.cz/CzechLight/lldp-systemd-networkd-sysrepo
+LLDP_SYSTEMD_NETWORKD_SYSREPO_SITE_METHOD = git
+LLDP_SYSTEMD_NETWORKD_SYSREPO_INSTALL_STAGING = NO
+LLDP_SYSTEMD_NETWORKD_SYSREPO_DEPENDENCIES = spdlog sdbus-cpp systemd docopt-cpp sysrepo
+LLDP_SYSTEMD_NETWORKD_SYSREPO_CONF_OPTS = -DTHREADS_PTHREAD_ARG:STRING=-pthread
+LLDP_SYSTEMD_NETWORKD_SYSREPO_LICENSE = Apache-2.0
+LLDP_SYSTEMD_NETWORKD_SYSREPO_LICENSE_FILES = LICENSE.md
+
+define LLDP_SYSTEMD_NETWORKD_SYSREPO_INSTALL_INIT_SYSTEMD
+        mkdir -p $(TARGET_DIR)/usr/lib/systemd/system/multi-user.target.wants/
+        $(INSTALL) -D -m 0644 \
+                $(BR2_EXTERNAL_CZECHLIGHT_PATH)/package/lldp-systemd-networkd-sysrepo/lldp-systemd-networkd-sysrepo.service \
+                $(BR2_EXTERNAL_CZECHLIGHT_PATH)/package/lldp-systemd-networkd-sysrepo/lldp-systemd-networkd-sysrepo-install-yang.service \
+                $(TARGET_DIR)/usr/lib/systemd/system/
+        ln -sf ../lldp-systemd-networkd-sysrepo.service $(TARGET_DIR)/usr/lib/systemd/system/multi-user.target.wants/
+        ln -sf ../lldp-systemd-networkd-sysrepo-install-yang.service $(TARGET_DIR)/usr/lib/systemd/system/multi-user.target.wants/
+endef
+
+$(eval $(cmake-package))
diff --git a/package/lldp-systemd-networkd-sysrepo/lldp-systemd-networkd-sysrepo.service b/package/lldp-systemd-networkd-sysrepo/lldp-systemd-networkd-sysrepo.service
new file mode 100644
index 0000000..94c7f62
--- /dev/null
+++ b/package/lldp-systemd-networkd-sysrepo/lldp-systemd-networkd-sysrepo.service
@@ -0,0 +1,19 @@
+[Unit]
+Description=lldp-systemd-networkd-sysrepo is a sysrepo application announcing LLDP neighbours from systemd-networkd.
+After=syslog.target network.target sysrepod.service
+Requires=sysrepod.service
+
+[Service]
+Type=simple
+ExecStart=/usr/bin/lldp-systemd-networkd-sysrepod
+PrivateTmp=yes
+PrivateDevices=no
+ProtectSystem=yes
+ProtectHome=yes
+Restart=always
+LogRateLimitIntervalSec=10
+LogRateLimitBurst=30000
+SyslogLevel=alert
+
+[Install]
+WantedBy=multi-user.target
diff --git a/submodules/lldp-systemd-networkd-sysrepo b/submodules/lldp-systemd-networkd-sysrepo
new file mode 160000
index 0000000..3b669e6
--- /dev/null
+++ b/submodules/lldp-systemd-networkd-sysrepo
@@ -0,0 +1 @@
+Subproject commit 3b669e6108501de411039b4972f8a6a34b882e6d