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/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
+