]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-lldp-rx: add VLAN ID parsing
authorLorenzo Arena <arena.lor@gmail.com>
Wed, 4 Jun 2025 11:21:21 +0000 (13:21 +0200)
committerLorenzo Arena <arena.lor@gmail.com>
Fri, 6 Jun 2025 07:35:11 +0000 (09:35 +0200)
Closes #28354.

src/libsystemd-network/lldp-neighbor.c
src/libsystemd-network/test-lldp-rx.c
src/network/networkctl-lldp.c
src/shared/varlink-io.systemd.Network.c
src/systemd/sd-lldp-rx.h
src/systemd/sd-lldp.h

index e93cf566fa7a2e92720d215208d7e7062786b54f..0955c8cb0b0220d1b0e2ab754264ea4bf087195a 100644 (file)
@@ -315,6 +315,16 @@ int lldp_neighbor_parse(sd_lldp_neighbor *n) {
                                 if (r < 0)
                                         return r;
                         }
+
+                        /* IEEE 802.1: VLAN ID */
+                        if (memcmp(p, SD_LLDP_OUI_802_1_VLAN_ID, sizeof(SD_LLDP_OUI_802_1_VLAN_ID)) == 0) {
+                                if (length != (sizeof(SD_LLDP_OUI_802_1_VLAN_ID) + sizeof(uint16_t)))
+                                        return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
+                                                                 "Found 802.1 VLAN ID TLV with wrong length, ignoring.");
+
+                                n->has_port_vlan_id = true;
+                                n->port_vlan_id = unaligned_read_be16(p + sizeof(SD_LLDP_OUI_802_1_VLAN_ID));
+                        }
                         break;
                 }
 
@@ -632,6 +642,17 @@ int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret
         return 0;
 }
 
+int sd_lldp_neighbor_get_port_vlan_id(sd_lldp_neighbor *n, uint16_t *ret) {
+        assert_return(n, -EINVAL);
+        assert_return(ret, -EINVAL);
+
+        if (!n->has_port_vlan_id)
+                return -ENODATA;
+
+        *ret = n->port_vlan_id;
+        return 0;
+}
+
 int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n) {
         assert_return(n, -EINVAL);
 
@@ -769,6 +790,8 @@ int lldp_neighbor_build_json(sd_lldp_neighbor *n, sd_json_variant **ret) {
                 *system_name = NULL, *system_description = NULL;
         uint16_t cc = 0;
         bool valid_cc;
+        uint16_t vlanid = 0;
+        bool valid_vlanid;
 
         assert(n);
         assert(ret);
@@ -780,6 +803,7 @@ int lldp_neighbor_build_json(sd_lldp_neighbor *n, sd_json_variant **ret) {
         (void) sd_lldp_neighbor_get_system_description(n, &system_description);
 
         valid_cc = sd_lldp_neighbor_get_enabled_capabilities(n, &cc) >= 0;
+        valid_vlanid = sd_lldp_neighbor_get_port_vlan_id(n, &vlanid) >= 0;
 
         return sd_json_buildo(
                         ret,
@@ -790,5 +814,6 @@ int lldp_neighbor_build_json(sd_lldp_neighbor *n, sd_json_variant **ret) {
                         JSON_BUILD_PAIR_STRING_NON_EMPTY("PortDescription", port_description),
                         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)));
+                        SD_JSON_BUILD_PAIR_CONDITION(valid_cc, "EnabledCapabilities", SD_JSON_BUILD_UNSIGNED(cc)),
+                        SD_JSON_BUILD_PAIR_CONDITION(valid_vlanid, "VlanID", SD_JSON_BUILD_UNSIGNED(vlanid)));
 }
index 7f497a4de43c22893252b8a4f57634c5996cd733..aa4bfd9b03e781e35067d167d8ba3068b54a3bf6 100644 (file)
@@ -9,6 +9,8 @@
 #include "sd-lldp-rx.h"
 
 #include "fd-util.h"
+#include "json-util.h"
+#include "lldp-neighbor.h"
 #include "lldp-network.h"
 #include "tests.h"
 
@@ -358,6 +360,61 @@ static void test_multiple_neighbors_sorted(sd_event *e) {
         assert_se(stop_lldp_rx(lldp_rx) == 0);
 }
 
+static void test_receive_oui_vlanid_packet(sd_event *e) {
+        sd_lldp_rx *lldp_rx;
+        sd_lldp_neighbor **neighbors;
+        sd_json_variant *v;
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *neighbor_json = NULL;
+        static const uint8_t frame[] = {
+                /* Ethernet header */
+                0x01, 0x80, 0xc2, 0x00, 0x00, 0x03,     /* Destination MAC */
+                0x01, 0x02, 0x03, 0x04, 0x05, 0x06,     /* Source MAC */
+                0x88, 0xcc,                             /* Ethertype */
+                /* LLDP mandatory TLVs */
+                0x02, 0x07, 0x04, 0x00, 0x01, 0x02,     /* Chassis: MAC, 00:01:02:03:04:05 */
+                0x03, 0x04, 0x05,
+                0x04, 0x04, 0x05, 0x31, 0x2f, 0x33,     /* Port TLV: interface name, "1/3" */
+                0x06, 0x02, 0x00, 0x78,                 /* TTL: 120 seconds */
+                /* LLDP optional TLVs */
+                0xfe, 0x06, 0x00, 0x80, 0xc2, 0x01,     /* Port VLAN ID: 0x1234 */
+                0x12, 0x34,
+                0xfe, 0x07, 0x00, 0x80, 0xc2, 0x02,     /* Port and protocol: flag 1, PPVID 0x7788 */
+                0x01, 0x77, 0x88,
+                0xfe, 0x0d, 0x00, 0x80, 0xc2, 0x03,     /* VLAN Name: ID 0x1234, name "Vlan51" */
+                0x12, 0x34, 0x06, 0x56, 0x6c, 0x61,
+                0x6e, 0x35, 0x31,
+                0xfe, 0x06, 0x00, 0x80, 0xc2, 0x06,     /* Management VID: 0x0102 */
+                0x01, 0x02,
+                0xfe, 0x09, 0x00, 0x80, 0xc2, 0x07,     /* Link aggregation: status 1, ID 0x00140012 */
+                0x01, 0x00, 0x14, 0x00, 0x12,
+                0xfe, 0x07, 0x00, 0x12, 0x0f, 0x02,     /* 802.3 Power via MDI: PSE, MDI enabled */
+                0x07, 0x01, 0x00,
+                0x00, 0x00                              /* End of LLDPDU */
+        };
+        uint16_t vlanid;
+
+        lldp_rx_handler_calls = 0;
+        ASSERT_OK(start_lldp_rx(&lldp_rx, e, lldp_rx_handler, NULL));
+
+        ASSERT_OK_EQ_ERRNO(write(test_fd[1], frame, sizeof(frame)), (ssize_t)sizeof(frame));
+        ASSERT_OK(sd_event_run(e, 0));
+        ASSERT_EQ(lldp_rx_handler_calls, 1);
+        ASSERT_OK_EQ(sd_lldp_rx_get_neighbors(lldp_rx, &neighbors), 1);
+
+        ASSERT_OK(sd_lldp_neighbor_get_port_vlan_id(neighbors[0], &vlanid));
+        ASSERT_EQ(vlanid, 0x1234);
+
+        ASSERT_OK(lldp_neighbor_build_json(neighbors[0], &neighbor_json));
+        ASSERT_NOT_NULL(v = sd_json_variant_by_key(neighbor_json, "VlanID"));
+        ASSERT_EQ(sd_json_variant_type(v), SD_JSON_VARIANT_UNSIGNED);
+        ASSERT_EQ(sd_json_variant_unsigned(v), UINT64_C(0x1234));
+
+        sd_lldp_neighbor_unref(neighbors[0]);
+        free(neighbors);
+
+        ASSERT_OK(stop_lldp_rx(lldp_rx));
+}
+
 int main(int argc, char *argv[]) {
         _cleanup_(sd_event_unrefp) sd_event *e = NULL;
 
@@ -369,6 +426,7 @@ int main(int argc, char *argv[]) {
         test_receive_incomplete_packet(e);
         test_receive_oui_packet(e);
         test_multiple_neighbors_sorted(e);
+        test_receive_oui_vlanid_packet(e);
 
         return 0;
 }
index 43727a1b82fcdff399ce97e2d4c56b1e8c759ba2..300f7b26df9751f6eac38d11bd4a3123d8fcac93 100644 (file)
@@ -44,6 +44,7 @@ typedef struct LLDPNeighborInfo {
         const char *system_name;
         const char *system_description;
         uint16_t capabilities;
+        uint16_t vlan_id;
 } LLDPNeighborInfo;
 
 static const sd_json_dispatch_field lldp_neighbor_dispatch_table[] = {
@@ -53,6 +54,7 @@ static const sd_json_dispatch_field lldp_neighbor_dispatch_table[] = {
         { "SystemName",          SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(LLDPNeighborInfo, system_name),        0 },
         { "SystemDescription",   SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(LLDPNeighborInfo, system_description), 0 },
         { "EnabledCapabilities", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint16,       offsetof(LLDPNeighborInfo, capabilities),       0 },
+        { "VlanID",              _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint16,       offsetof(LLDPNeighborInfo, vlan_id),            0 },
         {},
 };
 
index fe0aecf1f14f66d9156323a94022f7ed444b09d3..fd8ba3c085f153e65f0e06b8e817e164dd107c1c 100644 (file)
@@ -25,7 +25,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 SD_VARLINK_DEFINE_FIELD(PortDescription, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
                 SD_VARLINK_DEFINE_FIELD(SystemName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
                 SD_VARLINK_DEFINE_FIELD(SystemDescription, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
-                SD_VARLINK_DEFINE_FIELD(EnabledCapabilities, SD_VARLINK_INT, SD_VARLINK_NULLABLE));
+                SD_VARLINK_DEFINE_FIELD(EnabledCapabilities, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
+                SD_VARLINK_DEFINE_FIELD(VlanID, SD_VARLINK_INT, SD_VARLINK_NULLABLE));
 
 static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 LLDPNeighborsByInterface,
index 552ab813293d273fbf30caa1723c0a003e961cd7..cb76781e1e67f695621629092811e86c7813ceb8 100644 (file)
@@ -84,6 +84,7 @@ int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret)
 int sd_lldp_neighbor_get_mud_url(sd_lldp_neighbor *n, const char **ret);
 int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret);
 int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret);
+int sd_lldp_neighbor_get_port_vlan_id(sd_lldp_neighbor *n, uint16_t *ret);
 
 /* Low-level, iterative TLV access. This is for everything else, it iteratively goes through all available TLVs
  * (including the ones covered with the calls above), and allows multiple TLVs for the same fields. */
index 7c68dc6e33121f39d41449ae9c0e81530e1e210c..938e2d6402fe5ff36bc6b0b72052c846df74b406 100644 (file)
@@ -87,7 +87,13 @@ enum {
           SD_LLDP_SYSTEM_CAPABILITIES_SVLAN |                           \
           SD_LLDP_SYSTEM_CAPABILITIES_TPMR))
 
-#define SD_LLDP_OUI_802_1 (const uint8_t[]) { 0x00, 0x80, 0xc2 }
+#define _SD_LLDP_OUI_802_1 0x00, 0x80, 0xc2
+#define SD_LLDP_OUI_802_1 (const uint8_t[]) { _SD_LLDP_OUI_802_1 }
+
+#define SD_LLDP_OUI_802_1_SUBTYPE_VLAN_ID  0x01
+#define SD_LLDP_OUI_802_1_VLAN_ID                                       \
+        (const uint8_t[]) { _SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_VLAN_ID }
+
 #define SD_LLDP_OUI_802_3 (const uint8_t[]) { 0x00, 0x12, 0x0f }
 
 #define _SD_LLDP_OUI_IANA 0x00, 0x00, 0x5E