sd-lldp-rx: add VLAN ID parsing

Closes #28354.
This commit is contained in:
Lorenzo Arena
2025-06-04 13:21:21 +02:00
parent 8f8148cb08
commit 496b21ab8e
6 changed files with 96 additions and 3 deletions

View 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)));
}

View 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;
}

View 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 },
{},
};

View 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,

View 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. */

View 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