diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:49:52 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:49:52 +0000 |
commit | 55944e5e40b1be2afc4855d8d2baf4b73d1876b5 (patch) | |
tree | 33f869f55a1b149e9b7c2b7e201867ca5dd52992 /src/libsystemd-network/sd-lldp-tx.c | |
parent | Initial commit. (diff) | |
download | systemd-55944e5e40b1be2afc4855d8d2baf4b73d1876b5.tar.xz systemd-55944e5e40b1be2afc4855d8d2baf4b73d1876b5.zip |
Adding upstream version 255.4.upstream/255.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/libsystemd-network/sd-lldp-tx.c')
-rw-r--r-- | src/libsystemd-network/sd-lldp-tx.c | 628 |
1 files changed, 628 insertions, 0 deletions
diff --git a/src/libsystemd-network/sd-lldp-tx.c b/src/libsystemd-network/sd-lldp-tx.c new file mode 100644 index 0000000..2b822af --- /dev/null +++ b/src/libsystemd-network/sd-lldp-tx.c @@ -0,0 +1,628 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <arpa/inet.h> +#include <linux/sockios.h> +#include <sys/ioctl.h> + +#include "sd-event.h" +#include "sd-id128.h" +#include "sd-lldp-tx.h" + +#include "alloc-util.h" +#include "ether-addr-util.h" +#include "fd-util.h" +#include "hostname-util.h" +#include "network-common.h" +#include "random-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "time-util.h" +#include "unaligned.h" +#include "web-util.h" + +/* The LLDP spec calls this "txFastInit", see 9.2.5.19 */ +#define LLDP_FAST_TX_INIT 4U + +/* The LLDP spec calls this "msgTxHold", see 9.2.5.6 */ +#define LLDP_TX_HOLD 4U + +/* The jitter range to add, see 9.2.2. */ +#define LLDP_TX_JITTER_USEC (400U * USEC_PER_MSEC) + +/* The LLDP spec calls this msgTxInterval, but we subtract half the jitter off it. */ +#define LLDP_TX_INTERVAL_USEC (30U * USEC_PER_SEC - LLDP_TX_JITTER_USEC / 2) + +/* The LLDP spec calls this msgFastTx, but we subtract half the jitter off it. */ +#define LLDP_FAST_TX_INTERVAL_USEC (1U * USEC_PER_SEC - LLDP_TX_JITTER_USEC / 2) + +#define LLDP_TX_TTL ((uint16_t) DIV_ROUND_UP(LLDP_TX_INTERVAL_USEC * LLDP_TX_HOLD + 1, USEC_PER_SEC)) + +static const struct ether_addr lldp_multicast_addr[_SD_LLDP_MULTICAST_MODE_MAX] = { + [SD_LLDP_MULTICAST_MODE_NEAREST_BRIDGE] = {{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e }}, + [SD_LLDP_MULTICAST_MODE_NON_TPMR_BRIDGE] = {{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 }}, + [SD_LLDP_MULTICAST_MODE_CUSTOMER_BRIDGE] = {{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 }}, +}; + +struct sd_lldp_tx { + unsigned n_ref; + + int ifindex; + char *ifname; + + sd_event *event; + int64_t event_priority; + sd_event_source *timer_event_source; + + unsigned fast_tx; + + sd_lldp_multicast_mode_t mode; + struct ether_addr hwaddr; + + char *port_description; + char *hostname; + char *pretty_hostname; + char *mud_url; + uint16_t supported_capabilities; + uint16_t enabled_capabilities; +}; + +#define log_lldp_tx_errno(lldp_tx, error, fmt, ...) \ + log_interface_prefix_full_errno( \ + "LLDP Tx: ", \ + sd_lldp_tx, lldp_tx, \ + error, fmt, ##__VA_ARGS__) +#define log_lldp_tx(lldp_tx, fmt, ...) \ + log_interface_prefix_full_errno_zerook( \ + "LLDP Tx: ", \ + sd_lldp_tx, lldp_tx, \ + 0, fmt, ##__VA_ARGS__) + +static sd_lldp_tx *lldp_tx_free(sd_lldp_tx *lldp_tx) { + if (!lldp_tx) + return NULL; + + sd_lldp_tx_detach_event(lldp_tx); + + free(lldp_tx->port_description); + free(lldp_tx->hostname); + free(lldp_tx->pretty_hostname); + free(lldp_tx->mud_url); + + free(lldp_tx->ifname); + return mfree(lldp_tx); +} + +DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_lldp_tx, sd_lldp_tx, lldp_tx_free); + +int sd_lldp_tx_new(sd_lldp_tx **ret) { + _cleanup_(sd_lldp_tx_unrefp) sd_lldp_tx *lldp_tx = NULL; + + assert_return(ret, -EINVAL); + + lldp_tx = new(sd_lldp_tx, 1); + if (!lldp_tx) + return -ENOMEM; + + *lldp_tx = (sd_lldp_tx) { + .n_ref = 1, + .mode = _SD_LLDP_MULTICAST_MODE_INVALID, + }; + + *ret = TAKE_PTR(lldp_tx); + return 0; +} + +int sd_lldp_tx_set_ifindex(sd_lldp_tx *lldp_tx, int ifindex) { + assert_return(lldp_tx, -EINVAL); + assert_return(ifindex > 0, -EINVAL); + + lldp_tx->ifindex = ifindex; + return 0; +} + +int sd_lldp_tx_set_ifname(sd_lldp_tx *lldp_tx, const char *ifname) { + assert_return(lldp_tx, -EINVAL); + assert_return(ifname, -EINVAL); + + if (!ifname_valid_full(ifname, IFNAME_VALID_ALTERNATIVE)) + return -EINVAL; + + return free_and_strdup(&lldp_tx->ifname, ifname); +} + +int sd_lldp_tx_get_ifname(sd_lldp_tx *lldp_tx, const char **ret) { + int r; + + assert_return(lldp_tx, -EINVAL); + + r = get_ifname(lldp_tx->ifindex, &lldp_tx->ifname); + if (r < 0) + return r; + + if (ret) + *ret = lldp_tx->ifname; + + return 0; +} + +int sd_lldp_tx_set_multicast_mode(sd_lldp_tx *lldp_tx, sd_lldp_multicast_mode_t mode) { + assert_return(lldp_tx, -EINVAL); + assert_return(mode >= 0 && mode < _SD_LLDP_MULTICAST_MODE_MAX, -EINVAL); + + lldp_tx->mode = mode; + return 0; +} + +int sd_lldp_tx_set_hwaddr(sd_lldp_tx *lldp_tx, const struct ether_addr *hwaddr) { + assert_return(lldp_tx, -EINVAL); + assert_return(!ether_addr_is_null(hwaddr), -EINVAL); + + lldp_tx->hwaddr = *hwaddr; + return 0; +} + +int sd_lldp_tx_set_capabilities(sd_lldp_tx *lldp_tx, uint16_t supported, uint16_t enabled) { + assert_return(lldp_tx, -EINVAL); + assert_return((enabled & ~supported) == 0, -EINVAL); + + lldp_tx->supported_capabilities = supported; + lldp_tx->enabled_capabilities = enabled; + return 0; +} + +int sd_lldp_tx_set_port_description(sd_lldp_tx *lldp_tx, const char *port_description) { + assert_return(lldp_tx, -EINVAL); + + /* An empty string unset the previously set hostname. */ + if (strlen_ptr(port_description) >= 512) + return -EINVAL; + + return free_and_strdup(&lldp_tx->port_description, empty_to_null(port_description)); +} + +int sd_lldp_tx_set_hostname(sd_lldp_tx *lldp_tx, const char *hostname) { + assert_return(lldp_tx, -EINVAL); + + /* An empty string unset the previously set hostname. */ + if (!isempty(hostname)) { + assert_cc(HOST_NAME_MAX < 512); + + if (!hostname_is_valid(hostname, 0)) + return -EINVAL; + } + + return free_and_strdup(&lldp_tx->hostname, empty_to_null(hostname)); +} + +int sd_lldp_tx_set_pretty_hostname(sd_lldp_tx *lldp_tx, const char *pretty_hostname) { + assert_return(lldp_tx, -EINVAL); + + /* An empty string unset the previously set hostname. */ + if (strlen_ptr(pretty_hostname) >= 512) + return -EINVAL; + + return free_and_strdup(&lldp_tx->pretty_hostname, empty_to_null(pretty_hostname)); +} + +int sd_lldp_tx_set_mud_url(sd_lldp_tx *lldp_tx, const char *mud_url) { + assert_return(lldp_tx, -EINVAL); + + /* An empty string unset the previously set hostname. */ + if (!isempty(mud_url)) { + /* Unless the maximum length of each value is 511, the MUD url must be smaller than 256. + * See RFC 8520. */ + if (strlen(mud_url) >= 256) + return -EINVAL; + + if (!http_url_is_valid(mud_url)) + return -EINVAL; + } + + return free_and_strdup(&lldp_tx->mud_url, empty_to_null(mud_url)); +} + +static size_t lldp_tx_calculate_maximum_packet_size(sd_lldp_tx *lldp_tx, const char *hostname, const char *pretty_hostname) { + assert(lldp_tx); + assert(lldp_tx->ifindex > 0); + + return sizeof(struct ether_header) + + /* Chassis ID */ + 2 + 1 + (SD_ID128_STRING_MAX - 1) + + /* Port ID */ + 2 + 1 + strlen_ptr(lldp_tx->ifname) + + /* TTL */ + 2 + 2 + + /* Port description */ + 2 + strlen_ptr(lldp_tx->port_description) + + /* System name */ + 2 + strlen_ptr(hostname) + + /* System description */ + 2 + strlen_ptr(pretty_hostname) + + /* MUD URL */ + 2 + sizeof(SD_LLDP_OUI_IANA_MUD) + strlen_ptr(lldp_tx->mud_url) + + /* System Capabilities */ + 2 + 4 + + /* End */ + 2; +} + +static int packet_append_tlv_header(uint8_t *packet, size_t packet_size, size_t *offset, uint8_t type, size_t data_len) { + assert(packet); + assert(offset); + + /* + * +--------+--------+-------------- + * |TLV Type| len | value + * |(7 bits)|(9 bits)|(0-511 octets) + * +--------+--------+-------------- + * where: + * + * len = indicates the length of value + */ + + /* The type field is 7-bits. */ + if (type >= 128) + return -EINVAL; + + /* The data length field is 9-bits. */ + if (data_len >= 512) + return -EINVAL; + + if (packet_size < 2 + data_len) + return -ENOBUFS; + + if (*offset > packet_size - 2 - data_len) + return -ENOBUFS; + + packet[(*offset)++] = (type << 1) | !!(data_len >> 8); + packet[(*offset)++] = data_len & (size_t) UINT8_MAX; + + return 0; +} + +static int packet_append_prefixed_string( + uint8_t *packet, + size_t packet_size, + size_t *offset, + uint8_t type, + size_t prefix_len, + const void *prefix, + const char *str) { + + size_t len; + int r; + + assert(packet); + assert(offset); + assert(prefix_len == 0 || prefix); + + if (isempty(str)) + return 0; + + len = strlen(str); + + /* Check for overflow */ + if (len > SIZE_MAX - prefix_len) + return -ENOBUFS; + + r = packet_append_tlv_header(packet, packet_size, offset, type, prefix_len + len); + if (r < 0) + return r; + + memcpy_safe(packet + *offset, prefix, prefix_len); + *offset += prefix_len; + + memcpy(packet + *offset, str, len); + *offset += len; + + return 0; +} + +static int packet_append_string( + uint8_t *packet, + size_t packet_size, + size_t *offset, + uint8_t type, + const char *str) { + + return packet_append_prefixed_string(packet, packet_size, offset, type, 0, NULL, str); +} + +static int lldp_tx_create_packet(sd_lldp_tx *lldp_tx, size_t *ret_packet_size, uint8_t **ret_packet) { + _cleanup_free_ char *hostname = NULL, *pretty_hostname = NULL; + _cleanup_free_ uint8_t *packet = NULL; + struct ether_header *header; + size_t packet_size, offset; + sd_id128_t machine_id; + int r; + + assert(lldp_tx); + assert(lldp_tx->ifindex > 0); + assert(ret_packet_size); + assert(ret_packet); + + /* If ifname is not set yet, set ifname from ifindex. */ + r = sd_lldp_tx_get_ifname(lldp_tx, NULL); + if (r < 0) + return r; + + r = sd_id128_get_machine(&machine_id); + if (r < 0) + return r; + + if (!lldp_tx->hostname) + (void) gethostname_strict(&hostname); + if (!lldp_tx->pretty_hostname) + (void) get_pretty_hostname(&pretty_hostname); + + packet_size = lldp_tx_calculate_maximum_packet_size(lldp_tx, + lldp_tx->hostname ?: hostname, + lldp_tx->pretty_hostname ?: pretty_hostname); + + packet = new(uint8_t, packet_size); + if (!packet) + return -ENOMEM; + + header = (struct ether_header*) packet; + header->ether_type = htobe16(ETHERTYPE_LLDP); + memcpy(header->ether_dhost, lldp_multicast_addr + lldp_tx->mode, ETH_ALEN); + memcpy(header->ether_shost, &lldp_tx->hwaddr, ETH_ALEN); + + offset = sizeof(struct ether_header); + + /* The three mandatory TLVs must appear first, in this specific order: + * 1. Chassis ID + * 2. Port ID + * 3. Time To Live + */ + + r = packet_append_prefixed_string(packet, packet_size, &offset, SD_LLDP_TYPE_CHASSIS_ID, + 1, (const uint8_t[]) { SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED }, + SD_ID128_TO_STRING(machine_id)); + if (r < 0) + return r; + + r = packet_append_prefixed_string(packet, packet_size, &offset, SD_LLDP_TYPE_PORT_ID, + 1, (const uint8_t[]) { SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME }, + lldp_tx->ifname); + if (r < 0) + return r; + + r = packet_append_tlv_header(packet, packet_size, &offset, SD_LLDP_TYPE_TTL, 2); + if (r < 0) + return r; + + unaligned_write_be16(packet + offset, LLDP_TX_TTL); + offset += 2; + + /* Optional TLVs follow, in no specific order: */ + + r = packet_append_string(packet, packet_size, &offset, SD_LLDP_TYPE_PORT_DESCRIPTION, + lldp_tx->port_description); + if (r < 0) + return r; + + r = packet_append_string(packet, packet_size, &offset, SD_LLDP_TYPE_SYSTEM_NAME, + lldp_tx->hostname ?: hostname); + if (r < 0) + return r; + + r = packet_append_string(packet, packet_size, &offset, SD_LLDP_TYPE_SYSTEM_DESCRIPTION, + lldp_tx->pretty_hostname ?: pretty_hostname); + if (r < 0) + return r; + + /* See section 12 of RFC 8520. + * +--------+--------+----------+---------+-------------- + * |TLV Type| len | OUI |subtype | MUDString + * | =127 | |= 00 00 5E| = 1 | + * |(7 bits)|(9 bits)|(3 octets)|(1 octet)|(1-255 octets) + * +--------+--------+----------+---------+-------------- + * where: + * + * o TLV Type = 127 indicates a vendor-specific TLV + * o len = indicates the TLV string length + * o OUI = 00 00 5E is the organizationally unique identifier of IANA + * o subtype = 1 (as assigned by IANA for the MUDstring) + * o MUDstring = the length MUST NOT exceed 255 octets + */ + r = packet_append_prefixed_string(packet, packet_size, &offset, SD_LLDP_TYPE_PRIVATE, + sizeof(SD_LLDP_OUI_IANA_MUD), SD_LLDP_OUI_IANA_MUD, + lldp_tx->mud_url); + if (r < 0) + return r; + + r = packet_append_tlv_header(packet, packet_size, &offset, SD_LLDP_TYPE_SYSTEM_CAPABILITIES, 4); + if (r < 0) + return r; + + unaligned_write_be16(packet + offset, lldp_tx->supported_capabilities); + offset += 2; + unaligned_write_be16(packet + offset, lldp_tx->enabled_capabilities); + offset += 2; + + r = packet_append_tlv_header(packet, packet_size, &offset, SD_LLDP_TYPE_END, 0); + if (r < 0) + return r; + + *ret_packet_size = offset; + *ret_packet = TAKE_PTR(packet); + return 0; +} + +static int lldp_tx_send_packet(sd_lldp_tx *lldp_tx, size_t packet_size, const uint8_t *packet) { + _cleanup_close_ int fd = -EBADF; + union sockaddr_union sa; + ssize_t l; + + assert(lldp_tx); + assert(lldp_tx->ifindex > 0); + assert(packet_size > sizeof(struct ether_header)); + assert(packet); + + sa = (union sockaddr_union) { + .ll.sll_family = AF_PACKET, + .ll.sll_protocol = htobe16(ETHERTYPE_LLDP), + .ll.sll_ifindex = lldp_tx->ifindex, + .ll.sll_halen = ETH_ALEN, + }; + memcpy(sa.ll.sll_addr, lldp_multicast_addr + lldp_tx->mode, ETH_ALEN); + + fd = socket(AF_PACKET, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_RAW); + if (fd < 0) + return -errno; + + l = sendto(fd, packet, packet_size, MSG_NOSIGNAL, &sa.sa, sizeof(sa.ll)); + if (l < 0) + return -errno; + + if ((size_t) l != packet_size) + return -EIO; + + return 0; +} + +static int lldp_tx_send(sd_lldp_tx *lldp_tx) { + _cleanup_free_ uint8_t *packet = NULL; + size_t packet_size = 0; /* avoid false maybe-uninitialized warning */ + int r; + + assert(lldp_tx); + + r = lldp_tx_create_packet(lldp_tx, &packet_size, &packet); + if (r < 0) + return r; + + return lldp_tx_send_packet(lldp_tx, packet_size, packet); +} + +int sd_lldp_tx_attach_event(sd_lldp_tx *lldp_tx, sd_event *event, int64_t priority) { + int r; + + assert_return(lldp_tx, -EINVAL); + assert_return(!lldp_tx->event, -EBUSY); + + if (event) + lldp_tx->event = sd_event_ref(event); + else { + r = sd_event_default(&lldp_tx->event); + if (r < 0) + return r; + } + + lldp_tx->event_priority = priority; + + return 0; +} + +int sd_lldp_tx_detach_event(sd_lldp_tx *lldp_tx) { + assert_return(lldp_tx, -EINVAL); + + lldp_tx->timer_event_source = sd_event_source_disable_unref(lldp_tx->timer_event_source); + lldp_tx->event = sd_event_unref(lldp_tx->event); + return 0; +} + +static usec_t lldp_tx_get_delay(sd_lldp_tx *lldp_tx) { + assert(lldp_tx); + + return usec_add(lldp_tx->fast_tx > 0 ? LLDP_FAST_TX_INTERVAL_USEC : LLDP_TX_INTERVAL_USEC, + (usec_t) random_u64() % LLDP_TX_JITTER_USEC); +} + +static int lldp_tx_reset_timer(sd_lldp_tx *lldp_tx) { + usec_t delay; + int r; + + assert(lldp_tx); + assert(lldp_tx->timer_event_source); + + delay = lldp_tx_get_delay(lldp_tx); + + r = sd_event_source_set_time_relative(lldp_tx->timer_event_source, delay); + if (r < 0) + return r; + + return sd_event_source_set_enabled(lldp_tx->timer_event_source, SD_EVENT_ONESHOT); +} + +static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) { + sd_lldp_tx *lldp_tx = ASSERT_PTR(userdata); + int r; + + r = lldp_tx_send(lldp_tx); + if (r < 0) + log_lldp_tx_errno(lldp_tx, r, "Failed to send packet, ignoring: %m"); + + if (lldp_tx->fast_tx > 0) + lldp_tx->fast_tx--; + + r = lldp_tx_reset_timer(lldp_tx); + if (r < 0) + log_lldp_tx_errno(lldp_tx, r, "Failed to reset timer: %m"); + + return 0; +} + +int sd_lldp_tx_is_running(sd_lldp_tx *lldp_tx) { + int enabled; + + if (!lldp_tx) + return 0; + + if (!lldp_tx->timer_event_source) + return 0; + + if (sd_event_source_get_enabled(lldp_tx->timer_event_source, &enabled) < 0) + return 0; + + return enabled == SD_EVENT_ONESHOT; +} + +int sd_lldp_tx_stop(sd_lldp_tx *lldp_tx) { + if (!lldp_tx) + return 0; + + if (!lldp_tx->timer_event_source) + return 0; + + (void) sd_event_source_set_enabled(lldp_tx->timer_event_source, SD_EVENT_OFF); + + return 1; +} +int sd_lldp_tx_start(sd_lldp_tx *lldp_tx) { + usec_t delay; + int r; + + assert_return(lldp_tx, -EINVAL); + assert_return(lldp_tx->event, -EINVAL); + assert_return(lldp_tx->ifindex > 0, -EINVAL); + assert_return(lldp_tx->mode >= 0 && lldp_tx->mode < _SD_LLDP_MULTICAST_MODE_MAX, -EINVAL); + assert_return(!ether_addr_is_null(&lldp_tx->hwaddr), -EINVAL); + + if (sd_lldp_tx_is_running(lldp_tx)) + return 0; + + lldp_tx->fast_tx = LLDP_FAST_TX_INIT; + + if (lldp_tx->timer_event_source) { + r = lldp_tx_reset_timer(lldp_tx); + if (r < 0) + return log_lldp_tx_errno(lldp_tx, r, "Failed to re-enable timer: %m"); + + return 0; + } + + delay = lldp_tx_get_delay(lldp_tx); + + r = sd_event_add_time_relative(lldp_tx->event, &lldp_tx->timer_event_source, + CLOCK_BOOTTIME, delay, 0, + on_timer_event, lldp_tx); + if (r < 0) + return r; + + (void) sd_event_source_set_description(lldp_tx->timer_event_source, "lldp-tx-timer"); + (void) sd_event_source_set_priority(lldp_tx->timer_event_source, lldp_tx->event_priority); + + return 0; +} |