// SPDX-License-Identifier: GPL-2.0+
/*
* 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;
gsize raw_len;
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 (NMLldpListener *) 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);
}