diff --git a/NEWS b/NEWS index 54c7cdb487..dd6f21dc33 100644 --- a/NEWS +++ b/NEWS @@ -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 diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index c2ea147738..87fb67502a 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -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`, diff --git a/src/libsystemd-network/lldp-neighbor.c b/src/libsystemd-network/lldp-neighbor.c index 0955c8cb0b..e35df5e145 100644 --- a/src/libsystemd-network/lldp-neighbor.c +++ b/src/libsystemd-network/lldp-neighbor.c @@ -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))); } diff --git a/src/libsystemd-network/sd-lldp-tx.c b/src/libsystemd-network/sd-lldp-tx.c index 9e93a77cd1..9b86558eba 100644 --- a/src/libsystemd-network/sd-lldp-tx.c +++ b/src/libsystemd-network/sd-lldp-tx.c @@ -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)); +} diff --git a/src/network/networkd-json.c b/src/network/networkd-json.c index d07227d288..b6b4b02c06 100644 --- a/src/network/networkd-json.c +++ b/src/network/networkd-json.c @@ -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; } diff --git a/src/network/networkd-lldp-tx.c b/src/network/networkd-lldp-tx.c index cc22fbf2d6..0c108b875c 100644 --- a/src/network/networkd-lldp-tx.c +++ b/src/network/networkd-lldp-tx.c @@ -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; } diff --git a/src/systemd/sd-lldp-tx.h b/src/systemd/sd-lldp-tx.h index 107292027d..3dfb481b55 100644 --- a/src/systemd/sd-lldp-tx.h +++ b/src/systemd/sd-lldp-tx.h @@ -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); diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 7f20aa4aa5..710d94b463 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -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):