sd-lldp: several improvements (#37845)

This makes
- sd-lldp-tx not send machine ID as chassis ID, but use application
specific machine ID,
- sd-lldp-tx emit vlan ID if it is running on a vlan interface,
- Describe() DBus method also reply LLDP configurations,
- io.systemd.Network.GetLLDPNeighbors varlink method provides vlan ID,
if received.

Closes #37613.
This commit is contained in:
Yu Watanabe
2025-06-18 04:58:04 +09:00
committed by GitHub
8 changed files with 162 additions and 12 deletions
+7
View File
@@ -101,6 +101,13 @@ CHANGES WITH 258 in spe:
IPv4DuplicateAddressDetectionTimeoutSec=. The default timeout value
has been changed from 7 seconds to 200 milliseconds.
* systemd-networkd previously emitted the machine ID as chassis ID
through LLDP protocol, but now emits a deterministic ID,
cryptographically derived from the machine ID as chassis ID. If you
want to use the previous behavior, please set
SYSTEMD_LLDP_SEND_MACHINE_ID=1 environment variable to
systemd-networkd.
* Support for the !! command line prefix on ExecStart= lines (and
related) has been removed, and if specified will be ignored. The
concept was supposed to provide compatibility with kernels that
+3
View File
@@ -688,6 +688,9 @@ SYSTEMD_HOME_DEBUG_SUFFIX=foo \
work, ProtectSystem=strict in systemd-networkd.service needs to be downgraded
or disabled.
* `$SYSTEMD_LLDP_SEND_MACHINE_ID` - takes a boolean, If true, systemd-networkd
sends machine ID as chassis ID through LLDP protocol.
`systemd-storagetm`:
* `$SYSTEMD_NVME_MODEL`, `$SYSTEMD_NVME_FIRMWARE`, `$SYSTEMD_NVME_SERIAL`,
+1
View File
@@ -815,5 +815,6 @@ int lldp_neighbor_build_json(sd_lldp_neighbor *n, sd_json_variant **ret) {
JSON_BUILD_PAIR_STRING_NON_EMPTY("SystemName", system_name),
JSON_BUILD_PAIR_STRING_NON_EMPTY("SystemDescription", system_description),
SD_JSON_BUILD_PAIR_CONDITION(valid_cc, "EnabledCapabilities", SD_JSON_BUILD_UNSIGNED(cc)),
JSON_BUILD_PAIR_STRING_NON_EMPTY("MUDURL", n->mud_url),
SD_JSON_BUILD_PAIR_CONDITION(valid_vlanid, "VlanID", SD_JSON_BUILD_UNSIGNED(vlanid)));
}
+111 -11
View File
@@ -5,10 +5,12 @@
#include "sd-lldp-tx.h"
#include "alloc-util.h"
#include "env-util.h"
#include "ether-addr-util.h"
#include "fd-util.h"
#include "hostname-setup.h"
#include "hostname-util.h"
#include "json-util.h"
#include "network-common.h"
#include "random-util.h"
#include "socket-util.h"
@@ -17,6 +19,8 @@
#include "unaligned.h"
#include "web-util.h"
#define LLDP_APP_ID SD_ID128_MAKE(07,3a,43,bf,54,de,40,8d,8e,c4,96,ed,fd,94,72,dc)
/* The LLDP spec calls this "txFastInit", see 9.2.5.19 */
#define LLDP_FAST_TX_INIT 4U
@@ -61,6 +65,7 @@ struct sd_lldp_tx {
char *mud_url;
uint16_t supported_capabilities;
uint16_t enabled_capabilities;
uint16_t vlan_id;
};
#define log_lldp_tx_errno(lldp_tx, error, fmt, ...) \
@@ -218,6 +223,13 @@ int sd_lldp_tx_set_mud_url(sd_lldp_tx *lldp_tx, const char *mud_url) {
return free_and_strdup(&lldp_tx->mud_url, empty_to_null(mud_url));
}
int sd_lldp_tx_set_vlan_id(sd_lldp_tx *lldp_tx, uint16_t vlan_id) {
assert_return(lldp_tx, -EINVAL);
lldp_tx->vlan_id = vlan_id;
return 0;
}
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);
@@ -235,10 +247,12 @@ static size_t lldp_tx_calculate_maximum_packet_size(sd_lldp_tx *lldp_tx, const c
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 +
/* MUD URL */
2 + sizeof(SD_LLDP_OUI_IANA_MUD) + strlen_ptr(lldp_tx->mud_url) +
/* VLAN ID */
2 + sizeof(SD_LLDP_OUI_802_1_VLAN_ID) + sizeof(uint16_t) +
/* End */
2;
}
@@ -325,6 +339,22 @@ static int packet_append_string(
return packet_append_prefixed_string(packet, packet_size, offset, type, 0, NULL, str);
}
static int lldp_tx_get_machine_id(sd_id128_t *ret) {
int r;
assert(ret);
/* Unfortunately we previously exposed machine ID. If the environment variable is set, then
* use the machine ID as is. Otherwise, use application specific one. */
r = secure_getenv_bool("SYSTEMD_LLDP_SEND_MACHINE_ID");
if (r < 0 && r != -ENXIO)
log_debug_errno(r, "Failed to parse $SYSTEMD_LLDP_SEND_MACHINE_ID, ignoring: %m");
if (r > 0)
return sd_id128_get_machine(ret);
return sd_id128_get_machine_app_specific(LLDP_APP_ID, ret);
}
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;
@@ -339,11 +369,11 @@ static int lldp_tx_create_packet(sd_lldp_tx *lldp_tx, size_t *ret_packet_size, u
assert(ret_packet);
/* If ifname is not set yet, set ifname from ifindex. */
r = sd_lldp_tx_get_ifname(lldp_tx, NULL);
r = sd_lldp_tx_get_ifname(lldp_tx, /* ret = */ NULL);
if (r < 0)
return r;
r = sd_id128_get_machine(&machine_id);
r = lldp_tx_get_machine_id(&machine_id);
if (r < 0)
return r;
@@ -409,6 +439,15 @@ static int lldp_tx_create_packet(sd_lldp_tx *lldp_tx, size_t *ret_packet_size, u
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;
/* See section 12 of RFC 8520.
* +--------+--------+----------+---------+--------------
* |TLV Type| len | OUI |subtype | MUDString
@@ -429,14 +468,18 @@ static int lldp_tx_create_packet(sd_lldp_tx *lldp_tx, size_t *ret_packet_size, u
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;
/* VLAN ID */
if (lldp_tx->vlan_id > 0) {
r = packet_append_tlv_header(packet, packet_size, &offset, SD_LLDP_TYPE_PRIVATE,
sizeof(SD_LLDP_OUI_802_1_VLAN_ID) + sizeof(uint16_t));
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;
memcpy_safe(packet + offset, SD_LLDP_OUI_802_1_VLAN_ID, sizeof(SD_LLDP_OUI_802_1_VLAN_ID));
offset += sizeof(SD_LLDP_OUI_802_1_VLAN_ID);
unaligned_write_be16(packet + offset, lldp_tx->vlan_id);
offset += 2;
}
r = packet_append_tlv_header(packet, packet_size, &offset, SD_LLDP_TYPE_END, 0);
if (r < 0)
@@ -624,3 +667,60 @@ int sd_lldp_tx_start(sd_lldp_tx *lldp_tx) {
return 0;
}
int sd_lldp_tx_describe(sd_lldp_tx *lldp_tx, sd_json_variant **ret) {
int r;
assert(lldp_tx);
assert(ret);
/* If ifname is not set yet, set ifname from ifindex. */
const char *ifname;
r = sd_lldp_tx_get_ifname(lldp_tx, &ifname);
if (r < 0)
return r;
size_t port_id_len = strlen(ifname) + 1; /* +1 for subtype */
_cleanup_free_ uint8_t *port_id = new(uint8_t, port_id_len + 1); /* +1 for unused zero suffix for safety */
if (!port_id)
return -ENOMEM;
port_id[0] = SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME;
memcpy(port_id + 1, ifname, port_id_len); /* also copy the final NUL for safety */
assert(port_id[port_id_len] == 0);
sd_id128_t machine_id;
r = lldp_tx_get_machine_id(&machine_id);
if (r < 0)
return r;
size_t chassis_id_len = (SD_ID128_STRING_MAX - 1) + 1;
_cleanup_free_ uint8_t *chassis_id = new(uint8_t, chassis_id_len + 1);
if (!chassis_id)
return -ENOMEM;
chassis_id[0] = SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED;
memcpy(chassis_id + 1, SD_ID128_TO_STRING(machine_id), chassis_id_len);
assert(chassis_id[chassis_id_len] == 0);
_cleanup_free_ char *hostname = NULL;
if (!lldp_tx->hostname)
(void) gethostname_strict(&hostname);
_cleanup_free_ char *pretty_hostname = NULL;
if (!lldp_tx->pretty_hostname)
(void) get_pretty_hostname(&pretty_hostname);
return sd_json_buildo(
ret,
SD_JSON_BUILD_PAIR_STRING("ChassisID", SD_ID128_TO_STRING(machine_id)),
SD_JSON_BUILD_PAIR_BYTE_ARRAY("RawChassisID", chassis_id, chassis_id_len),
SD_JSON_BUILD_PAIR_STRING("PortID", lldp_tx->ifname),
SD_JSON_BUILD_PAIR_BYTE_ARRAY("RawPortID", port_id, port_id_len),
JSON_BUILD_PAIR_STRING_NON_EMPTY("PortDescription", lldp_tx->port_description),
JSON_BUILD_PAIR_STRING_NON_EMPTY("SystemName", lldp_tx->hostname ?: hostname),
JSON_BUILD_PAIR_STRING_NON_EMPTY("SystemDescription", lldp_tx->pretty_hostname ?: pretty_hostname),
SD_JSON_BUILD_PAIR_UNSIGNED("EnabledCapabilities", lldp_tx->enabled_capabilities),
JSON_BUILD_PAIR_STRING_NON_EMPTY("MUDURL", lldp_tx->mud_url),
JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("VlanID", lldp_tx->vlan_id));
}
+21
View File
@@ -1404,6 +1404,23 @@ static int dhcp_client_append_json(Link *link, sd_json_variant **v) {
return json_variant_set_field_non_null(v, "DHCPv4Client", w);
}
static int lldp_tx_append_json(Link *link, sd_json_variant **v) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL;
int r;
assert(link);
assert(v);
if (!link->lldp_tx)
return 0;
r = sd_lldp_tx_describe(link->lldp_tx, &w);
if (r < 0)
return r;
return json_variant_set_field_non_null(v, "LLDP", w);
}
int link_build_json(Link *link, sd_json_variant **ret) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
_cleanup_free_ char *type = NULL, *flags = NULL;
@@ -1540,6 +1557,10 @@ int link_build_json(Link *link, sd_json_variant **ret) {
if (r < 0)
return r;
r = lldp_tx_append_json(link, &v);
if (r < 0)
return r;
*ret = TAKE_PTR(v);
return 0;
}
+7
View File
@@ -13,6 +13,7 @@
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
#include "vlan.h"
static bool link_lldp_tx_enabled(Link *link) {
assert(link);
@@ -83,6 +84,12 @@ int link_lldp_tx_configure(Link *link) {
if (r < 0)
return r;
if (link->netdev && link->netdev->kind == NETDEV_KIND_VLAN) {
r = sd_lldp_tx_set_vlan_id(link->lldp_tx, VLAN(link->netdev)->id);
if (r < 0)
return r;
}
return 0;
}
+5 -1
View File
@@ -18,6 +18,7 @@
***/
#include "_sd-common.h"
#include "sd-json.h"
#include "sd-lldp.h" /* IWYU pragma: export*/
_SD_BEGIN_DECLARATIONS;
@@ -56,8 +57,11 @@ 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);
int sd_lldp_tx_set_mud_url(sd_lldp_tx *lldp_tx, const char *mud_url);
int sd_lldp_tx_set_vlan_id(sd_lldp_tx *lldp_tx, uint16_t vlan_id);
int sd_lldp_tx_describe(sd_lldp_tx *lldp_tx, sd_json_variant **ret);
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp_tx, sd_lldp_tx_unref);
@@ -6660,6 +6660,13 @@ class NetworkdLLDPTests(unittest.TestCase, Utilities):
print(output)
self.assertRegex(output, r'Connected To: .* on port veth-peer')
# Compare the json output from sender and receiver
sender_json = get_link_description('veth-peer')['LLDP']
receiver_json = json.loads(networkctl('--json=short', 'lldp', 'veth99'))['Neighbors'][0]['Neighbors'][0]
print(sender_json)
print(receiver_json)
self.assertEqual(sender_json, receiver_json)
class NetworkdRATests(unittest.TestCase, Utilities):
def setUp(self):