/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include #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 = -1; 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 = userdata; int r; assert(lldp_tx); 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; }