1
0
mirror of https://github.com/systemd/systemd.git synced 2024-10-30 14:55:37 +03:00
systemd/src/libsystemd-network/sd-lldp-tx.c
Matthijs van Duin b0221bb6a4 Fix placement of TTL TLV in LLDP transmit
The LLDP spec (IEEE 802.1AB) requires the three mandatory TLVs (Chassis
ID, Port ID, and TTL) to be the first three TLVs in the packet, in that
specific order, whereas systemd put the TTL near the end of the packet.

This violation caused the ethernet switch in our office to discard these
packets as malformed, and Wireshark's packet parser also chokes on them.
2022-05-05 01:19:02 +09:00

631 lines
20 KiB
C

/* 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 = -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;
}