From: Yu Watanabe Date: Sun, 26 Sep 2021 08:07:34 +0000 (+0900) Subject: sd-lldp-tx: introduce sd-lldp-tx X-Git-Tag: v250-rc1~613^2~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7df9656782866ded45a4f32680eaae6586ca0404;p=thirdparty%2Fsystemd.git sd-lldp-tx: introduce sd-lldp-tx --- diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 1d1c4396b54..54248a1e6e8 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -41,6 +41,7 @@ sources = files(''' sd-ipv4acd.c sd-ipv4ll.c sd-lldp-rx.c + sd-lldp-tx.c sd-ndisc.c sd-radv.c '''.split()) diff --git a/src/libsystemd-network/sd-lldp-tx.c b/src/libsystemd-network/sd-lldp-tx.c new file mode 100644 index 00000000000..24003af7e14 --- /dev/null +++ b/src/libsystemd-network/sd-lldp-tx.c @@ -0,0 +1,615 @@ +/* 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 "log-link.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_get_ifname(lldp_tx), \ + error, fmt, ##__VA_ARGS__) +#define log_lldp_tx(lldp_tx, fmt, ...) \ + log_interface_prefix_full_errno_zerook( \ + "LLDP Tx: ", \ + sd_lldp_tx_get_ifname(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); +} + +const char *sd_lldp_tx_get_ifname(sd_lldp_tx *lldp_tx) { + if (!lldp_tx) + return NULL; + + return get_ifname(lldp_tx->ifindex, &lldp_tx->ifname); +} + +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(sd_lldp_tx_get_ifname(lldp_tx)) + + /* 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) + + /* TTL */ + 2 + 2 + + /* 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); + + 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); + 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 }, + sd_lldp_tx_get_ifname(lldp_tx)); + if (r < 0) + return r; + + 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_TTL, 2); + if (r < 0) + return r; + + unaligned_write_be16(packet + offset, LLDP_TX_TTL); + offset += 2; + + 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; +} + +sd_event* sd_lldp_tx_get_event(sd_lldp_tx *lldp_tx) { + assert_return(lldp_tx, NULL); + + return lldp_tx->event; +} + +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_or_monotonic(), 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; +} diff --git a/src/systemd/meson.build b/src/systemd/meson.build index bc91abeb4e3..4f40b9f57f7 100644 --- a/src/systemd/meson.build +++ b/src/systemd/meson.build @@ -29,6 +29,7 @@ _not_installed_headers = ''' sd-ipv4acd.h sd-ipv4ll.h sd-lldp-rx.h + sd-lldp-tx.h sd-lldp.h sd-ndisc.h sd-netlink.h diff --git a/src/systemd/sd-lldp-tx.h b/src/systemd/sd-lldp-tx.h new file mode 100644 index 00000000000..aef06ed7eb0 --- /dev/null +++ b/src/systemd/sd-lldp-tx.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdlldptxhfoo +#define foosdlldptxhfoo + +/*** + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "sd-event.h" +#include "sd-lldp.h" + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_lldp_tx sd_lldp_tx; + +typedef enum sd_lldp_multicast_mode_t { + SD_LLDP_MULTICAST_MODE_NEAREST_BRIDGE, + SD_LLDP_MULTICAST_MODE_NON_TPMR_BRIDGE, + SD_LLDP_MULTICAST_MODE_CUSTOMER_BRIDGE, + _SD_LLDP_MULTICAST_MODE_MAX, + _SD_LLDP_MULTICAST_MODE_INVALID = -EINVAL, + _SD_ENUM_FORCE_S64(LLDP_TX_MODE), +} sd_lldp_multicast_mode_t; + +int sd_lldp_tx_new(sd_lldp_tx **ret); +sd_lldp_tx *sd_lldp_tx_ref(sd_lldp_tx *lldp_tx); +sd_lldp_tx *sd_lldp_tx_unref(sd_lldp_tx *lldp_tx); + +int sd_lldp_tx_start(sd_lldp_tx *lldp_tx); +int sd_lldp_tx_stop(sd_lldp_tx *lldp_tx); +int sd_lldp_tx_is_running(sd_lldp_tx *lldp_tx); + +int sd_lldp_tx_attach_event(sd_lldp_tx *lldp_tx, sd_event *event, int64_t priority); +int sd_lldp_tx_detach_event(sd_lldp_tx *lldp_tx); +sd_event *sd_lldp_tx_get_event(sd_lldp_tx *lldp_tx); + +int sd_lldp_tx_set_ifindex(sd_lldp_tx *lldp_tx, int ifindex); +int sd_lldp_tx_set_ifname(sd_lldp_tx *lldp_tx, const char *ifname); +const char *sd_lldp_tx_get_ifname(sd_lldp_tx *lldp_tx); + +int sd_lldp_tx_set_multicast_mode(sd_lldp_tx *lldp_tx, sd_lldp_multicast_mode_t mode); +int sd_lldp_tx_set_hwaddr(sd_lldp_tx *lldp_tx, const struct ether_addr *hwaddr); +int sd_lldp_tx_set_port_description(sd_lldp_tx *lldp_tx, const char *port_description); +int sd_lldp_tx_set_hostname(sd_lldp_tx *lldp_tx, const char *hostname); +int sd_lldp_tx_set_pretty_hostname(sd_lldp_tx *lldp_tx, const char *pretty_hostname); +int sd_lldp_tx_set_mud_url(sd_lldp_tx *lldp_tx, const char *mud_url); +int sd_lldp_tx_set_capabilities(sd_lldp_tx *lldp_tx, uint16_t supported, uint16_t enabled); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp_tx, sd_lldp_tx_unref); + +_SD_END_DECLARATIONS; + +#endif