blob: d858e12494d511b04c6c51a24af0ba1137e4c7d2 [file] [log] [blame]
From 1cdab502f703bdca754d7d55b9017d25c492c0c9 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
This makes sd_lldp public in libsystemd. Clients can therefore operate
with LLDP structures and files (e.g. parse LLDP neighbours).
---
meson.build | 1 +
src/libsystemd-network/lldp-internal.h | 39 --
src/libsystemd-network/lldp-neighbor.c | 792 -------------------------
src/libsystemd-network/lldp-neighbor.h | 92 ---
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 | 378 ------------
src/libsystemd/libsystemd.sym | 39 ++
src/libsystemd/meson.build | 6 +
src/libsystemd/sd-lldp/lldp-internal.h | 39 ++
src/libsystemd/sd-lldp/lldp-neighbor.c | 792 +++++++++++++++++++++++++
src/libsystemd/sd-lldp/lldp-neighbor.h | 92 +++
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 | 378 ++++++++++++
src/systemd/meson.build | 2 +-
src/test/meson.build | 10 +-
20 files changed, 1935 insertions(+), 1895 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 a5d1ed3d4d..45cc47f604 100644
--- a/meson.build
+++ b/meson.build
@@ -1552,6 +1552,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/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 02645b2bcd..0000000000
--- a/src/libsystemd-network/lldp-neighbor.c
+++ /dev/null
@@ -1,792 +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_network.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->mud_url);
- 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.");
- else {
- /* RFC 8520: MUD URL */
- if (memcmp(p, SD_LLDP_OUI_MUD, sizeof(SD_LLDP_OUI_MUD)) == 0 &&
- p[sizeof(SD_LLDP_OUI_MUD)] == SD_LLDP_OUI_SUBTYPE_MUD_USAGE_DESCRIPTION) {
- r = parse_string(&n->mud_url, p + sizeof(SD_LLDP_OUI_MUD) + 1,
- length - 1 - sizeof(SD_LLDP_OUI_MUD));
- if (r < 0)
- return r;
- }
- }
- }
-
- 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_mud_url(sd_lldp_neighbor *n, const char **ret) {
- assert_return(n, -EINVAL);
- assert_return(ret, -EINVAL);
-
- if (!n->mud_url)
- return -ENODATA;
-
- *ret = n->mud_url;
- 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 74175edf54..0000000000
--- a/src/libsystemd-network/lldp-neighbor.h
+++ /dev/null
@@ -1,92 +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;
- char *mud_url;
-
- 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 53e329734b..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_network.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 7fa0c67956..517ffd1471 100644
--- a/src/libsystemd-network/meson.build
+++ b/src/libsystemd-network/meson.build
@@ -34,12 +34,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 d3606cf501..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 a2ac65095f..0000000000
--- a/src/libsystemd-network/test-lldp.c
+++ /dev/null
@@ -1,378 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include <arpa/inet.h>
-#include <errno.h>
-#include <net/ethernet.h>
-#include <stdio.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 1e654b49ea..d584ac2faf 100644
--- a/src/libsystemd/libsystemd.sym
+++ b/src/libsystemd/libsystemd.sym
@@ -720,4 +720,43 @@ global:
sd_journal_enumerate_available_data;
sd_journal_enumerate_available_unique;
+
+ sd_lldp_start;
+ sd_lldp_neighbor_ref;
+ sd_lldp_neighbor_unref;
+ sd_lldp_neighbor_get_source_address;
+ sd_lldp_neighbor_get_destination_address;
+ 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_from_raw;
+ 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;
+ sd_lldp_neighbor_get_timestamp;
+ 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_new;
+ sd_lldp_get_neighbors;
+ sd_lldp_set_neighbors_max;
+ sd_lldp_match_capabilities;
+ sd_lldp_set_filter_address;
} LIBSYSTEMD_245;
diff --git a/src/libsystemd/meson.build b/src/libsystemd/meson.build
index aa1ed9b7dd..db28fbcf37 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/generic-netlink.h
sd-netlink/netlink-internal.h
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..02645b2bcd
--- /dev/null
+++ b/src/libsystemd/sd-lldp/lldp-neighbor.c
@@ -0,0 +1,792 @@
+/* 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_network.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->mud_url);
+ 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.");
+ else {
+ /* RFC 8520: MUD URL */
+ if (memcmp(p, SD_LLDP_OUI_MUD, sizeof(SD_LLDP_OUI_MUD)) == 0 &&
+ p[sizeof(SD_LLDP_OUI_MUD)] == SD_LLDP_OUI_SUBTYPE_MUD_USAGE_DESCRIPTION) {
+ r = parse_string(&n->mud_url, p + sizeof(SD_LLDP_OUI_MUD) + 1,
+ length - 1 - sizeof(SD_LLDP_OUI_MUD));
+ if (r < 0)
+ return r;
+ }
+ }
+ }
+
+ 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_mud_url(sd_lldp_neighbor *n, const char **ret) {
+ assert_return(n, -EINVAL);
+ assert_return(ret, -EINVAL);
+
+ if (!n->mud_url)
+ return -ENODATA;
+
+ *ret = n->mud_url;
+ 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..74175edf54
--- /dev/null
+++ b/src/libsystemd/sd-lldp/lldp-neighbor.h
@@ -0,0 +1,92 @@
+/* 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;
+ char *mud_url;
+
+ 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..53e329734b
--- /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_network.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..d3606cf501
--- /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..a2ac65095f
--- /dev/null
+++ b/src/libsystemd/sd-lldp/test-lldp.c
@@ -0,0 +1,378 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <net/ethernet.h>
+#include <stdio.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 62baf7784e..090f06087b 100644
--- a/src/systemd/meson.build
+++ b/src/systemd/meson.build
@@ -13,6 +13,7 @@ _systemd_headers = '''
sd-login.h
sd-messages.h
sd-path.h
+ sd-lldp.h
'''.split()
# https://github.com/mesonbuild/meson/issues/1633
@@ -28,7 +29,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 132989f197..1b7d2d6e6d 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -1037,6 +1037,11 @@ tests += [
[],
[]],
+ [['src/libsystemd/sd-lldp/test-lldp.c'],
+ [libsystemd_static,
+ libshared],
+ []],
+
]
if cxx_cmd != ''
@@ -1122,11 +1127,6 @@ tests += [
[libshared,
libsystemd_network],
[]],
-
- [['src/libsystemd-network/test-lldp.c'],
- [libshared,
- libsystemd_network],
- []],
]
############################################################
--
2.28.0