/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2015 Red Hat, Inc.
*/
#include "nm-default.h"
#include "nm-lldp-listener.h"
#include <net/ethernet.h>
#include "nm-std-aux/unaligned.h"
#include "platform/nm-platform.h"
#include "nm-glib-aux/nm-c-list.h"
#include "nm-utils.h"
#include "systemd/nm-sd.h"
#define MAX_NEIGHBORS 128
#define MIN_UPDATE_INTERVAL_NSEC (2 * NM_UTILS_NSEC_PER_SEC)
#define LLDP_MAC_NEAREST_BRIDGE \
(&((struct ether_addr){.ether_addr_octet = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e}}))
#define LLDP_MAC_NEAREST_NON_TPMR_BRIDGE \
(&((struct ether_addr){.ether_addr_octet = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x03}}))
#define LLDP_MAC_NEAREST_CUSTOMER_BRIDGE \
(&((struct ether_addr){.ether_addr_octet = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x00}}))
/*****************************************************************************/
NM_GOBJECT_PROPERTIES_DEFINE(NMLldpListener, PROP_NEIGHBORS, );
typedef struct {
sd_lldp * lldp_handle;
GHashTable *lldp_neighbors;
GVariant * variant;
/* the timestamp in nsec until which we delay updates. */
gint64 ratelimit_next_nsec;
guint ratelimit_id;
int ifindex;
} NMLldpListenerPrivate;
struct _NMLldpListener {
GObject parent;
NMLldpListenerPrivate _priv;
};
struct _NMLldpListenerClass {
GObjectClass parent;
};
G_DEFINE_TYPE(NMLldpListener, nm_lldp_listener, G_TYPE_OBJECT)
#define NM_LLDP_LISTENER_GET_PRIVATE(self) \
_NM_GET_PRIVATE(self, NMLldpListener, NM_IS_LLDP_LISTENER)
/*****************************************************************************/
typedef struct {
GVariant * variant;
sd_lldp_neighbor *neighbor_sd;
char * chassis_id;
char * port_id;
guint8 chassis_id_type;
guint8 port_id_type;
} LldpNeighbor;
/*****************************************************************************/
#define _NMLOG_PREFIX_NAME "lldp"
#define _NMLOG_DOMAIN LOGD_DEVICE
#define _NMLOG(level, ...) \
G_STMT_START \
{ \
const NMLogLevel _level = (level); \
\
if (nm_logging_enabled(_level, _NMLOG_DOMAIN)) { \
char _sbuf[64]; \
int _ifindex = (self) ? NM_LLDP_LISTENER_GET_PRIVATE(self)->ifindex : 0; \
\
_nm_log(_level, \
_NMLOG_DOMAIN, \
0, \
_ifindex > 0 ? nm_platform_link_get_name(NM_PLATFORM_GET, _ifindex) : NULL, \
NULL, \
"%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
_NMLOG_PREFIX_NAME, \
((_ifindex > 0) ? nm_sprintf_buf(_sbuf, "[%p,%d]", (self), _ifindex) \
: ((self) ? nm_sprintf_buf(_sbuf, "[%p]", (self)) : "")) \
_NM_UTILS_MACRO_REST(__VA_ARGS__)); \
} \
} \
G_STMT_END
#define LOG_NEIGH_FMT "CHASSIS=%u/%s PORT=%u/%s"
#define LOG_NEIGH_ARG(neigh) \
(neigh)->chassis_id_type, (neigh)->chassis_id, (neigh)->port_id_type, (neigh)->port_id
/*****************************************************************************/
static void
lldp_neighbor_get_raw(LldpNeighbor *neigh, const guint8 **out_raw_data, gsize *out_raw_len)
{
gconstpointer raw_data = NULL;
gsize raw_len = 0;
int r;
nm_assert(neigh);
r = sd_lldp_neighbor_get_raw(neigh->neighbor_sd, &raw_data, &raw_len);
nm_assert(r >= 0);
nm_assert(raw_data);
nm_assert(raw_len > 0);
*out_raw_data = raw_data;
*out_raw_len = raw_len;
}
static gboolean
lldp_neighbor_id_get(struct sd_lldp_neighbor *neighbor_sd,
guint8 * out_chassis_id_type,
const guint8 ** out_chassis_id,
gsize * out_chassis_id_len,
guint8 * out_port_id_type,
const guint8 ** out_port_id,
gsize * out_port_id_len)
{
int r;
r = sd_lldp_neighbor_get_chassis_id(neighbor_sd,
out_chassis_id_type,
(gconstpointer *) out_chassis_id,
out_chassis_id_len);
if (r < 0)
return FALSE;
r = sd_lldp_neighbor_get_port_id(neighbor_sd,
out_port_id_type,
(gconstpointer *) out_port_id,
out_port_id_len);
if (r < 0)
return FALSE;
return TRUE;
}
static guint
lldp_neighbor_id_hash(gconstpointer ptr)
{
const LldpNeighbor *neigh = ptr;
guint8 chassis_id_type;
guint8 port_id_type;
const guint8 * chassis_id;
const guint8 * port_id;
gsize chassis_id_len;
gsize port_id_len;
NMHashState h;
if (!lldp_neighbor_id_get(neigh->neighbor_sd,
&chassis_id_type,
&chassis_id,
&chassis_id_len,
&port_id_type,
&port_id,
&port_id_len)) {
nm_assert_not_reached();
return 0;
}
nm_hash_init(&h, 23423423u);
nm_hash_update_vals(&h, chassis_id_len, port_id_len, chassis_id_type, port_id_type);
nm_hash_update(&h, chassis_id, chassis_id_len);
nm_hash_update(&h, port_id, port_id_len);
return nm_hash_complete(&h);
}
static int
lldp_neighbor_id_cmp(const LldpNeighbor *a, const LldpNeighbor *b)
{
guint8 a_chassis_id_type;
guint8 b_chassis_id_type;
guint8 a_port_id_type;
guint8 b_port_id_type;
const guint8 *a_chassis_id;
const guint8 *b_chassis_id;
const guint8 *a_port_id;
const guint8 *b_port_id;
gsize a_chassis_id_len;
gsize b_chassis_id_len;
gsize a_port_id_len;
gsize b_port_id_len;
NM_CMP_SELF(a, b);
if (!lldp_neighbor_id_get(a->neighbor_sd,
&a_chassis_id_type,
&a_chassis_id,
&a_chassis_id_len,
&a_port_id_type,
&a_port_id,
&a_port_id_len)) {
nm_assert_not_reached();
return FALSE;
}
if (!lldp_neighbor_id_get(b->neighbor_sd,
&b_chassis_id_type,
&b_chassis_id,
&b_chassis_id_len,
&b_port_id_type,
&b_port_id,
&b_port_id_len)) {
nm_assert_not_reached();
return FALSE;
}
NM_CMP_DIRECT(a_chassis_id_type, b_chassis_id_type);
NM_CMP_DIRECT(a_port_id_type, b_port_id_type);
NM_CMP_DIRECT(a_chassis_id_len, b_chassis_id_len);
NM_CMP_DIRECT(a_port_id_len, b_port_id_len);
NM_CMP_DIRECT_MEMCMP(a_chassis_id, b_chassis_id, a_chassis_id_len);
NM_CMP_DIRECT_MEMCMP(a_port_id, b_port_id, a_port_id_len);
return 0;
}
static int
lldp_neighbor_id_cmp_p(gconstpointer a, gconstpointer b, gpointer user_data)
{
return lldp_neighbor_id_cmp(*((const LldpNeighbor *const *) a),
*((const LldpNeighbor *const *) b));
}
static gboolean
lldp_neighbor_id_equal(gconstpointer a, gconstpointer b)
{
return lldp_neighbor_id_cmp(a, b) == 0;
}
static void
lldp_neighbor_free(LldpNeighbor *neighbor)
{
if (!neighbor)
return;
g_free(neighbor->chassis_id);
g_free(neighbor->port_id);
nm_g_variant_unref(neighbor->variant);
sd_lldp_neighbor_unref(neighbor->neighbor_sd);
nm_g_slice_free(neighbor);
}
static void
lldp_neighbor_freep(LldpNeighbor **ptr)
{
lldp_neighbor_free(*ptr);
}
static gboolean
lldp_neighbor_equal(LldpNeighbor *a, LldpNeighbor *b)
{
const guint8 *raw_data_a;
const guint8 *raw_data_b;
gsize raw_len_a;
gsize raw_len_b;
if (a->neighbor_sd == b->neighbor_sd)
return TRUE;
lldp_neighbor_get_raw(a, &raw_data_a, &raw_len_a);
lldp_neighbor_get_raw(b, &raw_data_b, &raw_len_b);
return raw_len_a == raw_len_b && (memcmp(raw_data_a, raw_data_b, raw_len_a) == 0);
}
static GVariant *
parse_management_address_tlv(const uint8_t *data, gsize len)
{
GVariantBuilder builder;
gsize addr_len;
const guint8 * v_object_id_arr;
gsize v_object_id_len;
const guint8 * v_address_arr;
gsize v_address_len;
guint32 v_interface_number;
guint32 v_interface_number_subtype;
guint32 v_address_subtype;
/* 802.1AB-2009 - Figure 8-11
*
* - TLV type / length (2 bytes)
* - address string length (1 byte)
* - address subtype (1 byte)
* - address (1 to 31 bytes)
* - interface number subtype (1 byte)
* - interface number (4 bytes)
* - OID string length (1 byte)
* - OID (0 to 128 bytes)
*/
if (len < 11)
return NULL;
nm_assert((data[0] >> 1) == SD_LLDP_TYPE_MGMT_ADDRESS);
nm_assert((((data[0] & 1) << 8) + data[1]) + 2 == len);
data += 2;
len -= 2;
addr_len = *data; /* length of (address subtype + address) */
if (addr_len < 2 || addr_len > 32)
return NULL;
if (len < (1 /* address stringth length */
+ addr_len /* address subtype + address */
+ 5 /* interface */
+ 1)) /* oid */
return NULL;
data++;
len--;
v_address_subtype = *data;
v_address_arr = &data[1];
v_address_len = addr_len - 1;
data += addr_len;
len -= addr_len;
v_interface_number_subtype = *data;
data++;
len--;
v_interface_number = unaligned_read_be32(data);
data += 4;
len -= 4;
v_object_id_len = *data;
if (len < (1 + v_object_id_len))
return NULL;
data++;
v_object_id_arr = data;
g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
nm_g_variant_builder_add_sv_uint32(&builder, "address-subtype", v_address_subtype);
nm_g_variant_builder_add_sv_bytearray(&builder, "address", v_address_arr, v_address_len);
nm_g_variant_builder_add_sv_uint32(&builder,
"interface-number-subtype",
v_interface_number_subtype);
nm_g_variant_builder_add_sv_uint32(&builder, "interface-number", v_interface_number);
if (v_object_id_len > 0)
nm_g_variant_builder_add_sv_bytearray(&builder,
"object-id",
v_object_id_arr,
v_object_id_len);
return g_variant_builder_end(&builder);
}
static char *
format_network_address(const guint8 *data, gsize sz)
{
NMIPAddr a;
int family;
if (sz == 5 && data[0] == 1 /* LLDP_MGMT_ADDR_IP4 */) {
memcpy(&a, &data[1], sizeof(a.addr4));
family = AF_INET;
} else if (sz == 17 && data[0] == 2 /* LLDP_MGMT_ADDR_IP6 */) {
memcpy(&a, &data[1], sizeof(a.addr6));
family = AF_INET6;
} else
return NULL;
return nm_utils_inet_ntop_dup(family, &a);
}
static const char *
format_string(const guint8 *data, gsize len, gboolean allow_trim, char **out_to_free)
{
gboolean is_null_terminated = FALSE;
nm_assert(out_to_free && !*out_to_free);
if (allow_trim) {
while (len > 0 && data[len - 1] == '\0') {
is_null_terminated = TRUE;
len--;
}
}
if (len == 0)
return NULL;
if (memchr(data, len, '\0'))
return NULL;
return nm_utils_buf_utf8safe_escape(data,
is_null_terminated ? -1 : (gssize) len,
NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL
| NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII,
out_to_free);
}
static char *
format_string_cp(const guint8 *data, gsize len, gboolean allow_trim)
{
char * s_free = NULL;
const char *s;
s = format_string(data, len, allow_trim, &s_free);
nm_assert(!s_free || s == s_free);
return s ? (s_free ?: g_strdup(s)) : NULL;
}
static LldpNeighbor *
lldp_neighbor_new(sd_lldp_neighbor *neighbor_sd)
{
LldpNeighbor *neigh;
guint8 chassis_id_type;
guint8 port_id_type;
const guint8 *chassis_id;
const guint8 *port_id;
gsize chassis_id_len;
gsize port_id_len;
gs_free char *s_chassis_id = NULL;
gs_free char *s_port_id = NULL;
if (!lldp_neighbor_id_get(neighbor_sd,
&chassis_id_type,
&chassis_id,
&chassis_id_len,
&port_id_type,
&port_id,
&port_id_len))
return NULL;
switch (chassis_id_type) {
case SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT:
case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS:
case SD_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT:
case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME:
case SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED:
s_chassis_id = format_string_cp(chassis_id, chassis_id_len, FALSE);
break;
case SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS:
s_chassis_id = nm_utils_hwaddr_ntoa(chassis_id, chassis_id_len);
break;
case SD_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS:
s_chassis_id = format_network_address(chassis_id, chassis_id_len);
break;
}
if (!s_chassis_id) {
/* Invalid/unsupported chassis_id? Expose as hex string. This format is not stable, and
* in the future we may add a better string representation for these case (thus
* changing the API). */
s_chassis_id = nm_utils_bin2hexstr_full(chassis_id, chassis_id_len, '\0', FALSE, NULL);
}
switch (port_id_type) {
case SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS:
case SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT:
case SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME:
case SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED:
s_port_id = format_string_cp(port_id, port_id_len, FALSE);
break;
case SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS:
s_port_id = nm_utils_hwaddr_ntoa(port_id, port_id_len);
break;
case SD_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS:
s_port_id = format_network_address(port_id, port_id_len);
break;
}
if (!s_port_id) {
/* Invalid/unsupported port_id? Expose as hex string. This format is not stable, and
* in the future we may add a better string representation for these case (thus
* changing the API). */
s_port_id = nm_utils_bin2hexstr_full(port_id, port_id_len, '\0', FALSE, NULL);
}
neigh = g_slice_new(LldpNeighbor);
*neigh = (LldpNeighbor){
.neighbor_sd = sd_lldp_neighbor_ref(neighbor_sd),
.chassis_id_type = chassis_id_type,
.chassis_id = g_steal_pointer(&s_chassis_id),
.port_id_type = port_id_type,
.port_id = g_steal_pointer(&s_port_id),
};
return neigh;
}
static GVariant *
lldp_neighbor_to_variant(LldpNeighbor *neigh)
{
struct ether_addr destination_address;
GVariantBuilder builder;
const char * str;
const guint8 * raw_data;
gsize raw_len;
uint16_t u16;
uint8_t * data8;
gsize len;
int r;
if (neigh->variant)
return neigh->variant;
lldp_neighbor_get_raw(neigh, &raw_data, &raw_len);
g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
nm_g_variant_builder_add_sv_bytearray(&builder, NM_LLDP_ATTR_RAW, raw_data, raw_len);
nm_g_variant_builder_add_sv_uint32(&builder,
NM_LLDP_ATTR_CHASSIS_ID_TYPE,
neigh->chassis_id_type);
nm_g_variant_builder_add_sv_str(&builder, NM_LLDP_ATTR_CHASSIS_ID, neigh->chassis_id);
nm_g_variant_builder_add_sv_uint32(&builder, NM_LLDP_ATTR_PORT_ID_TYPE, neigh->port_id_type);
nm_g_variant_builder_add_sv_str(&builder, NM_LLDP_ATTR_PORT_ID, neigh->port_id);
r = sd_lldp_neighbor_get_destination_address(neigh->neighbor_sd, &destination_address);
if (r < 0)
str = NULL;
else if (nm_utils_ether_addr_equal(&destination_address, LLDP_MAC_NEAREST_BRIDGE))
str = NM_LLDP_DEST_NEAREST_BRIDGE;
else if (nm_utils_ether_addr_equal(&destination_address, LLDP_MAC_NEAREST_NON_TPMR_BRIDGE))
str = NM_LLDP_DEST_NEAREST_NON_TPMR_BRIDGE;
else if (nm_utils_ether_addr_equal(&destination_address, LLDP_MAC_NEAREST_CUSTOMER_BRIDGE))
str = NM_LLDP_DEST_NEAREST_CUSTOMER_BRIDGE;
else
str = NULL;
if (str)
nm_g_variant_builder_add_sv_str(&builder, NM_LLDP_ATTR_DESTINATION, str);
if (sd_lldp_neighbor_get_port_description(neigh->neighbor_sd, &str) == 0)
nm_g_variant_builder_add_sv_str(&builder, NM_LLDP_ATTR_PORT_DESCRIPTION, str);
if (sd_lldp_neighbor_get_system_name(neigh->neighbor_sd, &str) == 0)
nm_g_variant_builder_add_sv_str(&builder, NM_LLDP_ATTR_SYSTEM_NAME, str);
if (sd_lldp_neighbor_get_system_description(neigh->neighbor_sd, &str) == 0)
nm_g_variant_builder_add_sv_str(&builder, NM_LLDP_ATTR_SYSTEM_DESCRIPTION, str);
if (sd_lldp_neighbor_get_system_capabilities(neigh->neighbor_sd, &u16) == 0)
nm_g_variant_builder_add_sv_uint32(&builder, NM_LLDP_ATTR_SYSTEM_CAPABILITIES, u16);
r = sd_lldp_neighbor_tlv_rewind(neigh->neighbor_sd);
if (r < 0)
nm_assert_not_reached();
else {
gboolean v_management_addresses_has = FALSE;
GVariantBuilder v_management_addresses;
GVariant * v_ieee_802_1_pvid = NULL;
GVariant * v_ieee_802_1_ppvid = NULL;
GVariant * v_ieee_802_1_ppvid_flags = NULL;
GVariantBuilder v_ieee_802_1_ppvids;
GVariant * v_ieee_802_1_vid = NULL;
GVariant * v_ieee_802_1_vlan_name = NULL;
GVariantBuilder v_ieee_802_1_vlans;
GVariant * v_ieee_802_3_mac_phy_conf = NULL;
GVariant * v_ieee_802_3_power_via_mdi = NULL;
GVariant * v_ieee_802_3_max_frame_size = NULL;
GVariant * v_mud_url = NULL;
GVariantBuilder tmp_builder;
GVariant * tmp_variant;
do {
guint8 oui[3];
guint8 type;
guint8 subtype;
if (sd_lldp_neighbor_tlv_get_type(neigh->neighbor_sd, &type) < 0)
continue;
if (sd_lldp_neighbor_tlv_get_raw(neigh->neighbor_sd, (void *) &data8, &len) < 0)
continue;
switch (type) {
case SD_LLDP_TYPE_MGMT_ADDRESS:
tmp_variant = parse_management_address_tlv(data8, len);
if (tmp_variant) {
if (!v_management_addresses_has) {
v_management_addresses_has = TRUE;
g_variant_builder_init(&v_management_addresses, G_VARIANT_TYPE("aa{sv}"));
}
g_variant_builder_add_value(&v_management_addresses, tmp_variant);
}
continue;
case SD_LLDP_TYPE_PRIVATE:
break;
default:
continue;
}
r = sd_lldp_neighbor_tlv_get_oui(neigh->neighbor_sd, oui, &subtype);
if (r < 0) {
if (r == -ENXIO)
continue;
/* in other cases, something is seriously wrong. Abort, but
* keep what we parsed so far. */
break;
}
if (len <= 6)
continue;
/* skip over leading TLV, OUI and subtype */
#if NM_MORE_ASSERTS > 5
{
guint8 check_hdr[] = {0xfe | (((len - 2) >> 8) & 0x01),
((len - 2) & 0xFF),
oui[0],
oui[1],
oui[2],
subtype};
nm_assert(len > 2 + 3 + 1);
nm_assert(memcmp(data8, check_hdr, sizeof check_hdr) == 0);
}
#endif
data8 += 6;
len -= 6;
if (memcmp(oui, SD_LLDP_OUI_802_1, sizeof(oui)) == 0) {
switch (subtype) {
case SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID:
if (len != 2)
continue;
if (!v_ieee_802_1_pvid)
v_ieee_802_1_pvid = g_variant_new_uint32(unaligned_read_be16(data8));
break;
case SD_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID:
if (len != 3)
continue;
if (!v_ieee_802_1_ppvid) {
v_ieee_802_1_ppvid_flags = g_variant_new_uint32(data8[0]);
v_ieee_802_1_ppvid = g_variant_new_uint32(unaligned_read_be16(&data8[1]));
g_variant_builder_init(&v_ieee_802_1_ppvids, G_VARIANT_TYPE("aa{sv}"));
}
g_variant_builder_init(&tmp_builder, G_VARIANT_TYPE("a{sv}"));
nm_g_variant_builder_add_sv_uint32(&tmp_builder,
"ppvid",
unaligned_read_be16(&data8[1]));
nm_g_variant_builder_add_sv_uint32(&tmp_builder, "flags", data8[0]);
g_variant_builder_add_value(&v_ieee_802_1_ppvids,
g_variant_builder_end(&tmp_builder));
break;
case SD_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME:
{
gs_free char *name_to_free = NULL;
const char * name;
guint32 vid;
gsize l;
if (len <= 3)
continue;
l = data8[2];
if (len != 3 + l)
continue;
if (l > 32)
continue;
name = format_string(&data8[3], l, TRUE, &name_to_free);
if (!name)
continue;
vid = unaligned_read_be16(&data8[0]);
if (!v_ieee_802_1_vid) {
v_ieee_802_1_vid = g_variant_new_uint32(vid);
v_ieee_802_1_vlan_name = g_variant_new_string(name);
g_variant_builder_init(&v_ieee_802_1_vlans, G_VARIANT_TYPE("aa{sv}"));
}
g_variant_builder_init(&tmp_builder, G_VARIANT_TYPE("a{sv}"));
nm_g_variant_builder_add_sv_uint32(&tmp_builder, "vid", vid);
nm_g_variant_builder_add_sv_str(&tmp_builder, "name", name);
g_variant_builder_add_value(&v_ieee_802_1_vlans,
g_variant_builder_end(&tmp_builder));
break;
}
default:
continue;
}
} else if (memcmp(oui, SD_LLDP_OUI_802_3, sizeof(oui)) == 0) {
switch (subtype) {
case SD_LLDP_OUI_802_3_SUBTYPE_MAC_PHY_CONFIG_STATUS:
if (len != 5)
continue;
if (!v_ieee_802_3_mac_phy_conf) {
g_variant_builder_init(&tmp_builder, G_VARIANT_TYPE("a{sv}"));
nm_g_variant_builder_add_sv_uint32(&tmp_builder, "autoneg", data8[0]);
nm_g_variant_builder_add_sv_uint32(&tmp_builder,
"pmd-autoneg-cap",
unaligned_read_be16(&data8[1]));
nm_g_variant_builder_add_sv_uint32(&tmp_builder,
"operational-mau-type",
unaligned_read_be16(&data8[3]));
v_ieee_802_3_mac_phy_conf = g_variant_builder_end(&tmp_builder);
}
break;
case SD_LLDP_OUI_802_3_SUBTYPE_POWER_VIA_MDI:
if (len != 3)
continue;
if (!v_ieee_802_3_power_via_mdi) {
g_variant_builder_init(&tmp_builder, G_VARIANT_TYPE("a{sv}"));
nm_g_variant_builder_add_sv_uint32(&tmp_builder,
"mdi-power-support",
data8[0]);
nm_g_variant_builder_add_sv_uint32(&tmp_builder,
"pse-power-pair",
data8[1]);
nm_g_variant_builder_add_sv_uint32(&tmp_builder, "power-class", data8[2]);
v_ieee_802_3_power_via_mdi = g_variant_builder_end(&tmp_builder);
}
break;
case SD_LLDP_OUI_802_3_SUBTYPE_MAXIMUM_FRAME_SIZE:
if (len != 2)
continue;
if (!v_ieee_802_3_max_frame_size)
v_ieee_802_3_max_frame_size =
g_variant_new_uint32(unaligned_read_be16(data8));
break;
}
} else if (memcmp(oui, SD_LLDP_OUI_MUD, sizeof(oui)) == 0) {
switch (subtype) {
case SD_LLDP_OUI_SUBTYPE_MUD_USAGE_DESCRIPTION:
if (!v_mud_url) {
gs_free char *s_free = NULL;
const char * s;
s = format_string(data8, len, TRUE, &s_free);
if (s)
v_mud_url = g_variant_new_string(s);
}
break;
}
}
} while (sd_lldp_neighbor_tlv_next(neigh->neighbor_sd) > 0);
if (v_management_addresses_has)
nm_g_variant_builder_add_sv(&builder,
NM_LLDP_ATTR_MANAGEMENT_ADDRESSES,
g_variant_builder_end(&v_management_addresses));
if (v_ieee_802_1_pvid)
nm_g_variant_builder_add_sv(&builder, NM_LLDP_ATTR_IEEE_802_1_PVID, v_ieee_802_1_pvid);
if (v_ieee_802_1_ppvid) {
nm_g_variant_builder_add_sv(&builder,
NM_LLDP_ATTR_IEEE_802_1_PPVID,
v_ieee_802_1_ppvid);
nm_g_variant_builder_add_sv(&builder,
NM_LLDP_ATTR_IEEE_802_1_PPVID_FLAGS,
v_ieee_802_1_ppvid_flags);
nm_g_variant_builder_add_sv(&builder,
NM_LLDP_ATTR_IEEE_802_1_PPVIDS,
g_variant_builder_end(&v_ieee_802_1_ppvids));
}
if (v_ieee_802_1_vid) {
nm_g_variant_builder_add_sv(&builder, NM_LLDP_ATTR_IEEE_802_1_VID, v_ieee_802_1_vid);
nm_g_variant_builder_add_sv(&builder,
NM_LLDP_ATTR_IEEE_802_1_VLAN_NAME,
v_ieee_802_1_vlan_name);
nm_g_variant_builder_add_sv(&builder,
NM_LLDP_ATTR_IEEE_802_1_VLANS,
g_variant_builder_end(&v_ieee_802_1_vlans));
}
if (v_ieee_802_3_mac_phy_conf)
nm_g_variant_builder_add_sv(&builder,
NM_LLDP_ATTR_IEEE_802_3_MAC_PHY_CONF,
v_ieee_802_3_mac_phy_conf);
if (v_ieee_802_3_power_via_mdi)
nm_g_variant_builder_add_sv(&builder,
NM_LLDP_ATTR_IEEE_802_3_POWER_VIA_MDI,
v_ieee_802_3_power_via_mdi);
if (v_ieee_802_3_max_frame_size)
nm_g_variant_builder_add_sv(&builder,
NM_LLDP_ATTR_IEEE_802_3_MAX_FRAME_SIZE,
v_ieee_802_3_max_frame_size);
if (v_mud_url)
nm_g_variant_builder_add_sv(&builder, NM_LLDP_ATTR_MUD_URL, v_mud_url);
}
return (neigh->variant = g_variant_ref_sink(g_variant_builder_end(&builder)));
}
/*****************************************************************************/
GVariant *
nmtst_lldp_parse_from_raw(const guint8 *raw_data, gsize raw_len)
{
nm_auto(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *neighbor_sd = NULL;
nm_auto(lldp_neighbor_freep) LldpNeighbor * neigh = NULL;
GVariant * variant;
int r;
g_assert(raw_data);
g_assert(raw_len > 0);
r = sd_lldp_neighbor_from_raw(&neighbor_sd, raw_data, raw_len);
g_assert(r >= 0);
neigh = lldp_neighbor_new(neighbor_sd);
g_assert(neigh);
variant = lldp_neighbor_to_variant(neigh);
g_assert(variant);
return g_variant_ref(variant);
}
/*****************************************************************************/
static void
data_changed_notify(NMLldpListener *self, NMLldpListenerPrivate *priv)
{
nm_clear_g_variant(&priv->variant);
_notify(self, PROP_NEIGHBORS);
}
static gboolean
data_changed_timeout(gpointer user_data)
{
NMLldpListener * self = user_data;
NMLldpListenerPrivate *priv;
g_return_val_if_fail(NM_IS_LLDP_LISTENER(self), G_SOURCE_REMOVE);
priv = NM_LLDP_LISTENER_GET_PRIVATE(self);
priv->ratelimit_id = 0;
priv->ratelimit_next_nsec = nm_utils_get_monotonic_timestamp_nsec() + MIN_UPDATE_INTERVAL_NSEC;
data_changed_notify(self, priv);
return G_SOURCE_REMOVE;
}
static void
data_changed_schedule(NMLldpListener *self)
{
NMLldpListenerPrivate *priv = NM_LLDP_LISTENER_GET_PRIVATE(self);
gint64 now_nsec;
if (priv->ratelimit_id != 0)
return;
now_nsec = nm_utils_get_monotonic_timestamp_nsec();
if (now_nsec < priv->ratelimit_next_nsec) {
priv->ratelimit_id =
g_timeout_add_full(G_PRIORITY_LOW,
NM_UTILS_NSEC_TO_MSEC_CEIL(priv->ratelimit_next_nsec - now_nsec),
data_changed_timeout,
self,
NULL);
return;
}
priv->ratelimit_id = g_idle_add_full(G_PRIORITY_LOW, data_changed_timeout, self, NULL);
}
static void
process_lldp_neighbor(NMLldpListener *self, sd_lldp_neighbor *neighbor_sd, gboolean remove)
{
NMLldpListenerPrivate * priv;
nm_auto(lldp_neighbor_freep) LldpNeighbor *neigh = NULL;
LldpNeighbor * neigh_old;
g_return_if_fail(NM_IS_LLDP_LISTENER(self));
priv = NM_LLDP_LISTENER_GET_PRIVATE(self);
g_return_if_fail(priv->lldp_handle);
g_return_if_fail(neighbor_sd);
nm_assert(priv->lldp_neighbors);
neigh = lldp_neighbor_new(neighbor_sd);
if (!neigh) {
_LOGT("process: failed to parse neighbor");
return;
}
neigh_old = g_hash_table_lookup(priv->lldp_neighbors, neigh);
if (remove) {
if (neigh_old) {
_LOGT("process: %s neigh: " LOG_NEIGH_FMT, "remove", LOG_NEIGH_ARG(neigh));
g_hash_table_remove(priv->lldp_neighbors, neigh_old);
goto handle_changed;
}
return;
}
if (neigh_old && lldp_neighbor_equal(neigh_old, neigh))
return;
_LOGD("process: %s neigh: " LOG_NEIGH_FMT, neigh_old ? "update" : "new", LOG_NEIGH_ARG(neigh));
g_hash_table_add(priv->lldp_neighbors, g_steal_pointer(&neigh));
handle_changed:
data_changed_schedule(self);
}
static void
lldp_event_handler(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata)
{
process_lldp_neighbor(
userdata,
n,
!NM_IN_SET(event, SD_LLDP_EVENT_ADDED, SD_LLDP_EVENT_UPDATED, SD_LLDP_EVENT_REFRESHED));
}
gboolean
nm_lldp_listener_start(NMLldpListener *self, int ifindex, GError **error)
{
NMLldpListenerPrivate *priv;
int ret;
g_return_val_if_fail(NM_IS_LLDP_LISTENER(self), FALSE);
g_return_val_if_fail(ifindex > 0, FALSE);
g_return_val_if_fail(!error || !*error, FALSE);
priv = NM_LLDP_LISTENER_GET_PRIVATE(self);
if (priv->lldp_handle) {
g_set_error_literal(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "already running");
return FALSE;
}
ret = sd_lldp_new(&priv->lldp_handle);
if (ret < 0) {
g_set_error_literal(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"initialization failed");
return FALSE;
}
ret = sd_lldp_set_ifindex(priv->lldp_handle, ifindex);
if (ret < 0) {
g_set_error_literal(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"failed setting ifindex");
goto err;
}
ret = sd_lldp_set_callback(priv->lldp_handle, lldp_event_handler, self);
if (ret < 0) {
g_set_error_literal(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "set callback failed");
goto err;
}
ret = sd_lldp_set_neighbors_max(priv->lldp_handle, MAX_NEIGHBORS);
nm_assert(ret == 0);
priv->ifindex = ifindex;
ret = sd_lldp_attach_event(priv->lldp_handle, NULL, 0);
if (ret < 0) {
g_set_error_literal(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "attach event failed");
goto err_free;
}
ret = sd_lldp_start(priv->lldp_handle);
if (ret < 0) {
g_set_error_literal(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "start failed");
goto err;
}
priv->lldp_neighbors = g_hash_table_new_full(lldp_neighbor_id_hash,
lldp_neighbor_id_equal,
(GDestroyNotify) lldp_neighbor_free,
NULL);
_LOGD("start");
return TRUE;
err:
sd_lldp_detach_event(priv->lldp_handle);
err_free:
sd_lldp_unref(priv->lldp_handle);
priv->lldp_handle = NULL;
priv->ifindex = 0;
return FALSE;
}
void
nm_lldp_listener_stop(NMLldpListener *self)
{
NMLldpListenerPrivate *priv;
guint size;
gboolean changed = FALSE;
g_return_if_fail(NM_IS_LLDP_LISTENER(self));
priv = NM_LLDP_LISTENER_GET_PRIVATE(self);
if (priv->lldp_handle) {
_LOGD("stop");
sd_lldp_stop(priv->lldp_handle);
sd_lldp_detach_event(priv->lldp_handle);
sd_lldp_unref(priv->lldp_handle);
priv->lldp_handle = NULL;
size = g_hash_table_size(priv->lldp_neighbors);
g_hash_table_remove_all(priv->lldp_neighbors);
nm_clear_pointer(&priv->lldp_neighbors, g_hash_table_unref);
if (size > 0 || priv->ratelimit_id != 0)
changed = TRUE;
}
nm_clear_g_source(&priv->ratelimit_id);
priv->ratelimit_next_nsec = 0;
priv->ifindex = 0;
if (changed)
data_changed_notify(self, priv);
}
gboolean
nm_lldp_listener_is_running(NMLldpListener *self)
{
NMLldpListenerPrivate *priv;
g_return_val_if_fail(NM_IS_LLDP_LISTENER(self), FALSE);
priv = NM_LLDP_LISTENER_GET_PRIVATE(self);
return !!priv->lldp_handle;
}
GVariant *
nm_lldp_listener_get_neighbors(NMLldpListener *self)
{
NMLldpListenerPrivate *priv;
g_return_val_if_fail(NM_IS_LLDP_LISTENER(self), FALSE);
priv = NM_LLDP_LISTENER_GET_PRIVATE(self);
if (G_UNLIKELY(!priv->variant)) {
gs_free LldpNeighbor **neighbors = NULL;
GVariantBuilder array_builder;
guint i, n;
g_variant_builder_init(&array_builder, G_VARIANT_TYPE("aa{sv}"));
neighbors = (LldpNeighbor **)
nm_utils_hash_keys_to_array(priv->lldp_neighbors, lldp_neighbor_id_cmp_p, NULL, &n);
for (i = 0; i < n; i++)
g_variant_builder_add_value(&array_builder, lldp_neighbor_to_variant(neighbors[i]));
priv->variant = g_variant_ref_sink(g_variant_builder_end(&array_builder));
}
return priv->variant;
}
static void
get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
NMLldpListener *self = NM_LLDP_LISTENER(object);
switch (prop_id) {
case PROP_NEIGHBORS:
g_value_set_variant(value, nm_lldp_listener_get_neighbors(self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void
nm_lldp_listener_init(NMLldpListener *self)
{
_LOGT("lldp listener created");
}
NMLldpListener *
nm_lldp_listener_new(void)
{
return g_object_new(NM_TYPE_LLDP_LISTENER, NULL);
}
static void
dispose(GObject *object)
{
nm_lldp_listener_stop(NM_LLDP_LISTENER(object));
G_OBJECT_CLASS(nm_lldp_listener_parent_class)->dispose(object);
}
static void
finalize(GObject *object)
{
NMLldpListener * self = NM_LLDP_LISTENER(object);
NMLldpListenerPrivate *priv = NM_LLDP_LISTENER_GET_PRIVATE(self);
nm_lldp_listener_stop(self);
nm_clear_g_variant(&priv->variant);
_LOGT("lldp listener destroyed");
G_OBJECT_CLASS(nm_lldp_listener_parent_class)->finalize(object);
}
static void
nm_lldp_listener_class_init(NMLldpListenerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->dispose = dispose;
object_class->finalize = finalize;
object_class->get_property = get_property;
obj_properties[PROP_NEIGHBORS] =
g_param_spec_variant(NM_LLDP_LISTENER_NEIGHBORS,
"",
"",
G_VARIANT_TYPE("aa{sv}"),
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
}