Thomas Haller
2022-10-25 10:59:16 +02:00
36 changed files with 2651 additions and 2033 deletions

View File

@@ -454,6 +454,8 @@ src_libnm_glib_aux_libnm_glib_aux_la_SOURCES = \
src/libnm-glib-aux/nm-logging-syslog.h \
src/libnm-glib-aux/nm-macros-internal.h \
src/libnm-glib-aux/nm-obj.h \
src/libnm-glib-aux/nm-prioq.c \
src/libnm-glib-aux/nm-prioq.h \
src/libnm-glib-aux/nm-random-utils.c \
src/libnm-glib-aux/nm-random-utils.h \
src/libnm-glib-aux/nm-ref-string.c \
@@ -510,6 +512,36 @@ EXTRA_DIST += src/libnm-udev-aux/meson.build
###############################################################################
noinst_LTLIBRARIES += src/libnm-lldp/libnm-lldp.la
src_libnm_lldp_libnm_lldp_la_CFLAGS = \
$(src_libnm_glib_aux_cppflags) \
-I$(srcdir)/src \
-I$(builddir)/src \
$(NULL)
src_libnm_lldp_libnm_lldp_la_CPPFLAGS = \
$(CODE_COVERAGE_CFLAGS) \
$(SANITIZER_LIB_CFLAGS) \
$(NULL)
src_libnm_lldp_libnm_lldp_la_LDFLAGS = \
$(SANITIZER_LIB_LDFLAGS) \
$(NULL)
src_libnm_lldp_libnm_lldp_la_SOURCES = \
src/libnm-lldp/nm-lldp-neighbor.c \
src/libnm-lldp/nm-lldp-neighbor.h \
src/libnm-lldp/nm-lldp-network.c \
src/libnm-lldp/nm-lldp-network.h \
src/libnm-lldp/nm-lldp-rx-internal.h \
src/libnm-lldp/nm-lldp-rx.c \
src/libnm-lldp/nm-lldp-rx.h \
src/libnm-lldp/nm-lldp.h \
$(NULL)
###############################################################################
noinst_LTLIBRARIES += src/libnm-base/libnm-base.la
src_libnm_base_libnm_base_la_CPPFLAGS = \
@@ -2389,16 +2421,10 @@ src_libnm_systemd_core_libnm_systemd_core_la_SOURCES = \
src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.h \
src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.c \
src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.h \
src/libnm-systemd-core/src/libsystemd-network/lldp-neighbor.c \
src/libnm-systemd-core/src/libsystemd-network/lldp-neighbor.h \
src/libnm-systemd-core/src/libsystemd-network/lldp-network.c \
src/libnm-systemd-core/src/libsystemd-network/lldp-network.h \
src/libnm-systemd-core/src/libsystemd-network/lldp-rx-internal.h \
src/libnm-systemd-core/src/libsystemd-network/network-common.c \
src/libnm-systemd-core/src/libsystemd-network/network-common.h \
src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c \
src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-lease.c \
src/libnm-systemd-core/src/libsystemd-network/sd-lldp-rx.c \
src/libnm-systemd-core/src/libsystemd/sd-event/event-source.h \
src/libnm-systemd-core/src/libsystemd/sd-event/event-util.c \
src/libnm-systemd-core/src/libsystemd/sd-event/event-util.h \
@@ -2412,8 +2438,6 @@ src_libnm_systemd_core_libnm_systemd_core_la_SOURCES = \
src/libnm-systemd-core/src/systemd/sd-dhcp6-option.h \
src/libnm-systemd-core/src/systemd/sd-event.h \
src/libnm-systemd-core/src/systemd/sd-id128.h \
src/libnm-systemd-core/src/systemd/sd-lldp-rx.h \
src/libnm-systemd-core/src/systemd/sd-lldp.h \
src/libnm-systemd-core/src/systemd/sd-ndisc.h \
$(NULL)
@@ -2684,6 +2708,7 @@ src_core_libNetworkManager_la_LIBADD = \
src/libnm-platform/libnm-platform.la \
src/libnm-base/libnm-base.la \
src/libnm-log-core/libnm-log-core.la \
src/libnm-lldp/libnm-lldp.la \
src/libnm-udev-aux/libnm-udev-aux.la \
src/libnm-glib-aux/libnm-glib-aux.la \
src/libnm-std-aux/libnm-std-aux.la \

View File

@@ -15,24 +15,20 @@
#include "libnm-std-aux/unaligned.h"
#include "libnm-platform/nm-platform.h"
#include "libnm-glib-aux/nm-c-list.h"
#include "libnm-lldp/nm-lldp-rx.h"
#include "nm-utils.h"
#include "libnm-systemd-core/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}}))
#define LLDP_MAC_NEAREST_BRIDGE (&NM_ETHER_ADDR_INIT(0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e))
#define LLDP_MAC_NEAREST_NON_TPMR_BRIDGE (&NM_ETHER_ADDR_INIT(0x01, 0x80, 0xc2, 0x00, 0x00, 0x03))
#define LLDP_MAC_NEAREST_CUSTOMER_BRIDGE (&NM_ETHER_ADDR_INIT(0x01, 0x80, 0xc2, 0x00, 0x00, 0x00))
/*****************************************************************************/
struct _NMLldpListener {
sd_lldp_rx *lldp_handle;
NMLldpRX *lldp_rx;
GHashTable *lldp_neighbors;
GVariant *variant;
@@ -49,12 +45,12 @@ struct _NMLldpListener {
/*****************************************************************************/
typedef struct {
GVariant *variant;
sd_lldp_neighbor *neighbor_sd;
char *chassis_id;
char *port_id;
guint8 chassis_id_type;
guint8 port_id_type;
GVariant *variant;
NMLldpNeighbor *neighbor_nm;
char *chassis_id;
char *port_id;
guint8 chassis_id_type;
guint8 port_id_type;
} LldpNeighbor;
/*****************************************************************************/
@@ -100,7 +96,7 @@ lldp_neighbor_get_raw(LldpNeighbor *neigh, const guint8 **out_raw_data, gsize *o
nm_assert(neigh);
r = sd_lldp_neighbor_get_raw(neigh->neighbor_sd, &raw_data, &raw_len);
r = nm_lldp_neighbor_get_raw(neigh->neighbor_nm, &raw_data, &raw_len);
nm_assert(r >= 0);
nm_assert(raw_data);
@@ -111,24 +107,24 @@ lldp_neighbor_get_raw(LldpNeighbor *neigh, const guint8 **out_raw_data, gsize *o
}
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)
lldp_neighbor_id_get(NMLldpNeighbor *neighbor_nm,
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,
r = nm_lldp_neighbor_get_chassis_id(neighbor_nm,
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,
r = nm_lldp_neighbor_get_port_id(neighbor_nm,
out_port_id_type,
(gconstpointer *) out_port_id,
out_port_id_len);
@@ -139,82 +135,16 @@ lldp_neighbor_id_get(struct sd_lldp_neighbor *neighbor_sd,
}
static guint
lldp_neighbor_id_hash(gconstpointer ptr)
lldp_neighbor_id_hash(const LldpNeighbor *neigh)
{
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);
return nm_lldp_neighbor_id_hash(nm_lldp_neighbor_get_id(neigh->neighbor_nm));
}
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;
return nm_lldp_neighbor_id_cmp(nm_lldp_neighbor_get_id(a->neighbor_nm),
nm_lldp_neighbor_get_id(b->neighbor_nm));
}
static int
@@ -225,7 +155,7 @@ lldp_neighbor_id_cmp_p(gconstpointer a, gconstpointer b, gpointer user_data)
}
static gboolean
lldp_neighbor_id_equal(gconstpointer a, gconstpointer b)
lldp_neighbor_id_equal(const LldpNeighbor *a, const LldpNeighbor *b)
{
return lldp_neighbor_id_cmp(a, b) == 0;
}
@@ -239,7 +169,7 @@ lldp_neighbor_free(LldpNeighbor *neighbor)
g_free(neighbor->chassis_id);
g_free(neighbor->port_id);
nm_g_variant_unref(neighbor->variant);
sd_lldp_neighbor_unref(neighbor->neighbor_sd);
nm_lldp_neighbor_unref(neighbor->neighbor_nm);
nm_g_slice_free(neighbor);
}
@@ -257,7 +187,7 @@ lldp_neighbor_equal(LldpNeighbor *a, LldpNeighbor *b)
gsize raw_len_a;
gsize raw_len_b;
if (a->neighbor_sd == b->neighbor_sd)
if (a->neighbor_nm == b->neighbor_nm)
return TRUE;
lldp_neighbor_get_raw(a, &raw_data_a, &raw_len_a);
@@ -293,7 +223,7 @@ parse_management_address_tlv(const uint8_t *data, gsize len)
if (len < 11)
return NULL;
nm_assert((data[0] >> 1) == SD_LLDP_TYPE_MGMT_ADDRESS);
nm_assert((data[0] >> 1) == NM_LLDP_TYPE_MGMT_ADDRESS);
nm_assert((((data[0] & 1) << 8) + data[1]) + 2 == len);
data += 2;
@@ -402,7 +332,7 @@ format_string_cp(const guint8 *data, gsize len, gboolean allow_trim)
}
static LldpNeighbor *
lldp_neighbor_new(sd_lldp_neighbor *neighbor_sd)
lldp_neighbor_new(NMLldpNeighbor *neighbor_nm)
{
LldpNeighbor *neigh;
guint8 chassis_id_type;
@@ -414,7 +344,7 @@ lldp_neighbor_new(sd_lldp_neighbor *neighbor_sd)
gs_free char *s_chassis_id = NULL;
gs_free char *s_port_id = NULL;
if (!lldp_neighbor_id_get(neighbor_sd,
if (!lldp_neighbor_id_get(neighbor_nm,
&chassis_id_type,
&chassis_id,
&chassis_id_len,
@@ -424,17 +354,17 @@ lldp_neighbor_new(sd_lldp_neighbor *neighbor_sd)
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:
case NM_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT:
case NM_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS:
case NM_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT:
case NM_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME:
case NM_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:
case NM_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:
case NM_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS:
s_chassis_id = format_network_address(chassis_id, chassis_id_len);
break;
}
@@ -446,16 +376,16 @@ lldp_neighbor_new(sd_lldp_neighbor *neighbor_sd)
}
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:
case NM_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS:
case NM_LLDP_PORT_SUBTYPE_PORT_COMPONENT:
case NM_LLDP_PORT_SUBTYPE_INTERFACE_NAME:
case NM_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:
case NM_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:
case NM_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS:
s_port_id = format_network_address(port_id, port_id_len);
break;
}
@@ -468,7 +398,7 @@ lldp_neighbor_new(sd_lldp_neighbor *neighbor_sd)
neigh = g_slice_new(LldpNeighbor);
*neigh = (LldpNeighbor){
.neighbor_sd = sd_lldp_neighbor_ref(neighbor_sd),
.neighbor_nm = nm_lldp_neighbor_ref(neighbor_nm),
.chassis_id_type = chassis_id_type,
.chassis_id = g_steal_pointer(&s_chassis_id),
.port_id_type = port_id_type,
@@ -480,15 +410,15 @@ lldp_neighbor_new(sd_lldp_neighbor *neighbor_sd)
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;
NMEtherAddr 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;
@@ -505,33 +435,33 @@ lldp_neighbor_to_variant(LldpNeighbor *neigh)
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);
r = nm_lldp_neighbor_get_destination_address(neigh->neighbor_nm, &destination_address);
if (r < 0)
str = NULL;
else if (nm_utils_ether_addr_equal(&destination_address, LLDP_MAC_NEAREST_BRIDGE))
else if (nm_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))
else if (nm_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))
else if (nm_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)
if (nm_lldp_neighbor_get_port_description(neigh->neighbor_nm, &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)
if (nm_lldp_neighbor_get_system_name(neigh->neighbor_nm, &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)
if (nm_lldp_neighbor_get_system_description(neigh->neighbor_nm, &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)
if (nm_lldp_neighbor_get_system_capabilities(neigh->neighbor_nm, &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);
r = nm_lldp_neighbor_tlv_rewind(neigh->neighbor_nm);
if (r < 0)
nm_assert_not_reached();
else {
@@ -556,14 +486,14 @@ lldp_neighbor_to_variant(LldpNeighbor *neigh)
guint8 type;
guint8 subtype;
if (sd_lldp_neighbor_tlv_get_type(neigh->neighbor_sd, &type) < 0)
if (nm_lldp_neighbor_tlv_get_type(neigh->neighbor_nm, &type) < 0)
continue;
if (sd_lldp_neighbor_tlv_get_raw(neigh->neighbor_sd, (void *) &data8, &len) < 0)
if (nm_lldp_neighbor_tlv_get_raw(neigh->neighbor_nm, (void *) &data8, &len) < 0)
continue;
switch (type) {
case SD_LLDP_TYPE_MGMT_ADDRESS:
case NM_LLDP_TYPE_MGMT_ADDRESS:
tmp_variant = parse_management_address_tlv(data8, len);
if (tmp_variant) {
if (!v_management_addresses_has) {
@@ -573,13 +503,13 @@ lldp_neighbor_to_variant(LldpNeighbor *neigh)
g_variant_builder_add_value(&v_management_addresses, tmp_variant);
}
continue;
case SD_LLDP_TYPE_PRIVATE:
case NM_LLDP_TYPE_PRIVATE:
break;
default:
continue;
}
r = sd_lldp_neighbor_tlv_get_oui(neigh->neighbor_sd, oui, &subtype);
r = nm_lldp_neighbor_tlv_get_oui(neigh->neighbor_nm, oui, &subtype);
if (r < 0) {
if (r == -ENXIO)
continue;
@@ -609,15 +539,15 @@ lldp_neighbor_to_variant(LldpNeighbor *neigh)
data8 += 6;
len -= 6;
if (memcmp(oui, SD_LLDP_OUI_802_1, sizeof(oui)) == 0) {
if (memcmp(oui, NM_LLDP_OUI_802_1, sizeof(oui)) == 0) {
switch (subtype) {
case SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID:
case NM_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:
case NM_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID:
if (len != 3)
continue;
if (!v_ieee_802_1_ppvid) {
@@ -633,7 +563,7 @@ lldp_neighbor_to_variant(LldpNeighbor *neigh)
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:
case NM_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME:
{
gs_free char *name_to_free = NULL;
const char *name;
@@ -669,9 +599,9 @@ lldp_neighbor_to_variant(LldpNeighbor *neigh)
default:
continue;
}
} else if (memcmp(oui, SD_LLDP_OUI_802_3, sizeof(oui)) == 0) {
} else if (memcmp(oui, NM_LLDP_OUI_802_3, sizeof(oui)) == 0) {
switch (subtype) {
case SD_LLDP_OUI_802_3_SUBTYPE_MAC_PHY_CONFIG_STATUS:
case NM_LLDP_OUI_802_3_SUBTYPE_MAC_PHY_CONFIG_STATUS:
if (len != 5)
continue;
@@ -687,7 +617,7 @@ lldp_neighbor_to_variant(LldpNeighbor *neigh)
v_ieee_802_3_mac_phy_conf = g_variant_builder_end(&tmp_builder);
}
break;
case SD_LLDP_OUI_802_3_SUBTYPE_POWER_VIA_MDI:
case NM_LLDP_OUI_802_3_SUBTYPE_POWER_VIA_MDI:
if (len != 3)
continue;
@@ -703,7 +633,7 @@ lldp_neighbor_to_variant(LldpNeighbor *neigh)
v_ieee_802_3_power_via_mdi = g_variant_builder_end(&tmp_builder);
}
break;
case SD_LLDP_OUI_802_3_SUBTYPE_MAXIMUM_FRAME_SIZE:
case NM_LLDP_OUI_802_3_SUBTYPE_MAXIMUM_FRAME_SIZE:
if (len != 2)
continue;
if (!v_ieee_802_3_max_frame_size)
@@ -711,9 +641,9 @@ lldp_neighbor_to_variant(LldpNeighbor *neigh)
g_variant_new_uint32(unaligned_read_be16(data8));
break;
}
} else if (memcmp(oui, SD_LLDP_OUI_IANA, sizeof(oui)) == 0) {
} else if (memcmp(oui, NM_LLDP_OUI_IANA, sizeof(oui)) == 0) {
switch (subtype) {
case SD_LLDP_OUI_IANA_SUBTYPE_MUD:
case NM_LLDP_OUI_IANA_SUBTYPE_MUD:
if (!v_mud_url) {
gs_free char *s_free = NULL;
const char *s;
@@ -725,7 +655,7 @@ lldp_neighbor_to_variant(LldpNeighbor *neigh)
break;
}
}
} while (sd_lldp_neighbor_tlv_next(neigh->neighbor_sd) > 0);
} while (nm_lldp_neighbor_tlv_next(neigh->neighbor_nm) > 0);
if (v_management_addresses_has)
nm_g_variant_builder_add_sv(&builder,
@@ -777,18 +707,17 @@ lldp_neighbor_to_variant(LldpNeighbor *neigh)
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;
nm_auto(nm_lldp_neighbor_unrefp) NMLldpNeighbor *neighbor_nm = NULL;
nm_auto(lldp_neighbor_freep) LldpNeighbor *neigh = NULL;
GVariant *variant;
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);
neighbor_nm = nm_lldp_neighbor_new_from_raw(raw_data, raw_len);
g_assert(neighbor_nm);
neigh = lldp_neighbor_new(neighbor_sd);
neigh = lldp_neighbor_new(neighbor_nm);
g_assert(neigh);
variant = lldp_neighbor_to_variant(neigh);
@@ -843,18 +772,18 @@ data_changed_schedule(NMLldpListener *self)
}
static void
process_lldp_neighbor(NMLldpListener *self, sd_lldp_neighbor *neighbor_sd, gboolean remove)
process_lldp_neighbor(NMLldpListener *self, NMLldpNeighbor *neighbor_nm, gboolean remove)
{
nm_auto(lldp_neighbor_freep) LldpNeighbor *neigh = NULL;
LldpNeighbor *neigh_old;
nm_assert(self);
nm_assert(self->lldp_handle);
nm_assert(self->lldp_rx);
nm_assert(self->lldp_neighbors);
g_return_if_fail(neighbor_sd);
g_return_if_fail(neighbor_nm);
neigh = lldp_neighbor_new(neighbor_sd);
neigh = lldp_neighbor_new(neighbor_nm);
if (!neigh) {
_LOGT("process: failed to parse neighbor");
return;
@@ -884,14 +813,17 @@ handle_changed:
}
static void
lldp_event_handler(sd_lldp_rx *lldp, sd_lldp_rx_event_t event, sd_lldp_neighbor *n, void *userdata)
lldp_event_handler(NMLldpRX *lldp, NMLldpRXEvent event, NMLldpNeighbor *n, void *user_data)
{
process_lldp_neighbor(userdata,
NMLldpListener *self = user_data;
_LOGD("event: %s", nm_lldp_rx_event_to_string(event));
process_lldp_neighbor(self,
n,
!NM_IN_SET(event,
SD_LLDP_RX_EVENT_ADDED,
SD_LLDP_RX_EVENT_UPDATED,
SD_LLDP_RX_EVENT_REFRESHED));
NM_LLDP_RX_EVENT_ADDED,
NM_LLDP_RX_EVENT_UPDATED,
NM_LLDP_RX_EVENT_REFRESHED));
}
/*****************************************************************************/
@@ -935,35 +867,14 @@ nm_lldp_listener_new(int ifindex,
gpointer notify_user_data,
GError **error)
{
NMLldpListener *self = NULL;
sd_lldp_rx *lldp_handle;
int r;
NMLldpListener *self = NULL;
nm_auto(nm_lldp_rx_unrefp) NMLldpRX *lldp_rx = NULL;
int r;
g_return_val_if_fail(ifindex > 0, FALSE);
g_return_val_if_fail(!error || !*error, FALSE);
g_return_val_if_fail(notify_callback, FALSE);
r = sd_lldp_rx_new(&lldp_handle);
if (r < 0) {
g_set_error_literal(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"initialization failed");
return FALSE;
}
r = sd_lldp_rx_set_ifindex(lldp_handle, ifindex);
if (r < 0) {
g_set_error_literal(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"failed setting ifindex");
goto fail_handle;
}
r = sd_lldp_rx_set_neighbors_max(lldp_handle, MAX_NEIGHBORS);
nm_assert(r == 0);
self = g_slice_new(NMLldpListener);
*self = (NMLldpListener){
.ifindex = ifindex,
@@ -971,39 +882,34 @@ nm_lldp_listener_new(int ifindex,
.notify_user_data = notify_user_data,
};
r = sd_lldp_rx_set_callback(lldp_handle, lldp_event_handler, self);
if (r < 0) {
g_set_error_literal(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "set callback failed");
goto fail_handle;
}
nm_assert(nm_g_main_context_is_thread_default(g_main_context_default()));
r = sd_lldp_rx_attach_event(lldp_handle, NULL, 0);
if (r < 0) {
g_set_error_literal(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "attach event failed");
goto fail_attached;
}
lldp_rx = nm_lldp_rx_new(&((NMLldpRXConfig){
.ifindex = ifindex,
.neighbors_max = MAX_NEIGHBORS,
.callback = lldp_event_handler,
.userdata = self,
}));
r = sd_lldp_rx_start(lldp_handle);
r = nm_lldp_rx_start(lldp_rx);
if (r < 0) {
g_set_error_literal(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "start failed");
goto fail_attached;
goto fail;
}
self->lldp_neighbors = g_hash_table_new_full(lldp_neighbor_id_hash,
lldp_neighbor_id_equal,
self->lldp_neighbors = g_hash_table_new_full((GHashFunc) lldp_neighbor_id_hash,
(GEqualFunc) lldp_neighbor_id_equal,
(GDestroyNotify) lldp_neighbor_free,
NULL);
self->lldp_handle = lldp_handle;
self->lldp_rx = g_steal_pointer(&lldp_rx);
_LOGD("start lldp listener");
return self;
fail_attached:
sd_lldp_rx_detach_event(lldp_handle);
fail_handle:
fail:
if (self)
nm_g_slice_free(self);
sd_lldp_rx_unref(lldp_handle);
return NULL;
}
@@ -1012,9 +918,8 @@ nm_lldp_listener_destroy(NMLldpListener *self)
{
g_return_if_fail(self);
sd_lldp_rx_stop(self->lldp_handle);
sd_lldp_rx_detach_event(self->lldp_handle);
sd_lldp_rx_unref(self->lldp_handle);
nm_lldp_rx_stop(self->lldp_rx);
nm_lldp_rx_unref(self->lldp_rx);
nm_clear_g_source_inst(&self->ratelimit_source);

View File

@@ -12,9 +12,8 @@
#include <sys/stat.h>
#include <sys/types.h>
#include "libnm-lldp/nm-lldp.h"
#include "devices/nm-lldp-listener.h"
#include "libnm-systemd-core/nm-sd.h"
#include "platform/tests/test-common.h"
#include "nm-test-utils-core.h"
@@ -191,9 +190,9 @@ _test_recv_data0_check_do(GMainLoop *loop, NMLldpListener *listener, const TestR
g_assert_cmpint(g_variant_n_children(neighbors), ==, 1);
neighbor = get_lldp_neighbor(neighbors,
SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS,
NM_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS,
"00:01:02:03:04:05",
SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME,
NM_LLDP_PORT_SUBTYPE_INTERFACE_NAME,
"1/3");
g_assert(neighbor);
g_assert_cmpint(g_variant_n_children(neighbor), ==, 1 + 4 + 4);
@@ -547,9 +546,9 @@ _test_recv_data1_check(GMainLoop *loop, NMLldpListener *listener, TestRecvCallba
g_assert_cmpint(g_variant_n_children(neighbors), ==, 1);
neighbor = get_lldp_neighbor(neighbors,
SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS,
NM_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS,
"00:01:30:F9:AD:A0",
SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME,
NM_LLDP_PORT_SUBTYPE_INTERFACE_NAME,
"1/1");
g_assert(neighbor);
g_assert_cmpint(g_variant_n_children(neighbor), ==, 1 + 4 + 16);
@@ -874,7 +873,6 @@ test_recv(TestRecvFixture *fixture, gconstpointer user_data)
TestRecvCallbackInfo info = {};
gsize i_frames;
GError *error = NULL;
guint sd_id;
if (fixture->ifindex == 0) {
g_test_skip("Tun device not available");
@@ -884,8 +882,7 @@ test_recv(TestRecvFixture *fixture, gconstpointer user_data)
listener = nm_lldp_listener_new(fixture->ifindex, lldp_neighbors_changed, &info, &error);
nmtst_assert_success(listener, error);
loop = g_main_loop_new(NULL, FALSE);
sd_id = nm_sd_event_attach_default();
loop = g_main_loop_new(NULL, FALSE);
for (i_frames = 0; i_frames < data->frames_len; i_frames++) {
const TestRecvFrame *f = data->frames[i_frames];
@@ -902,7 +899,6 @@ test_recv(TestRecvFixture *fixture, gconstpointer user_data)
nm_clear_pointer(&listener, nm_lldp_listener_destroy);
nm_clear_g_source(&sd_id);
nm_clear_pointer(&loop, g_main_loop_unref);
}

View File

@@ -205,6 +205,7 @@ if enable_tests
libnm_core_aux_intern,
libnm_core_impl,
libnm_crypto,
libnm_lldp,
libnm_platform,
libnm_base,
libnm_systemd_shared,
@@ -256,6 +257,7 @@ NetworkManager_all_sym = executable(
libnm_core_aux_intern,
libnm_core_impl,
libnm_crypto,
libnm_lldp,
libnm_platform,
libnm_base,
libnm_log_core,
@@ -296,6 +298,7 @@ NetworkManager = executable(
libnm_core_aux_intern,
libnm_core_impl,
libnm_crypto,
libnm_lldp,
libnm_platform,
libnm_base,
libnm_udev_aux,

View File

@@ -12,21 +12,6 @@
/*****************************************************************************/
static void
test_lldp_create(void)
{
sd_lldp_rx *lldp = NULL;
int r;
r = sd_lldp_rx_new(&lldp);
g_assert(r == 0);
g_assert(lldp);
sd_lldp_rx_unref(lldp);
}
/*****************************************************************************/
typedef struct {
GMainLoop *mainloop;
sd_event_source *event_source;
@@ -105,7 +90,6 @@ main(int argc, char **argv)
{
nmtst_init(&argc, &argv, TRUE);
g_test_add_func("/systemd/lldp/create", test_lldp_create);
g_test_add_func("/systemd/sd-event", test_sd_event);
return g_test_run();

View File

@@ -13,6 +13,7 @@ libnm_glib_aux = static_library(
'nm-json-aux.c',
'nm-keyfile-aux.c',
'nm-logging-base.c',
'nm-prioq.c',
'nm-random-utils.c',
'nm-ref-string.c',
'nm-secret-utils.c',

View File

@@ -51,10 +51,6 @@ typedef struct _NMIPAddr {
extern const NMIPAddr nm_ip_addr_zero;
/* This doesn't really belong here, but since it's convenient to re-use nm_ip_addr_zero.ether_addr
* for NMEtherAddr, it is. */
#define nm_ether_addr_zero (nm_ip_addr_zero.ether_addr)
static inline int
nm_ip_addr_cmp(int addr_family, gconstpointer a, gconstpointer b)
{

View File

@@ -12,6 +12,7 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/ioctl.h>
#include "nm-str-buf.h"
#include "nm-shared-utils.h"
@@ -496,6 +497,45 @@ nm_utils_fd_read(int fd, NMStrBuf *out_string)
/*****************************************************************************/
/* Taken from systemd's next_datagram_size_fd(). */
gssize
nm_fd_next_datagram_size(int fd)
{
gssize l;
int k;
/* This is a bit like FIONREAD/SIOCINQ, however a bit more powerful. The difference being: recv(MSG_PEEK) will
* actually cause the next datagram in the queue to be validated regarding checksums, which FIONREAD doesn't
* do. This difference is actually of major importance as we need to be sure that the size returned here
* actually matches what we will read with recvmsg() next, as otherwise we might end up allocating a buffer of
* the wrong size. */
l = recv(fd, NULL, 0, MSG_PEEK | MSG_TRUNC);
if (l < 0) {
if (NM_IN_SET(errno, EOPNOTSUPP, EFAULT))
goto fallback;
return -errno;
}
if (l == 0)
goto fallback;
return l;
fallback:
k = 0;
/* Some sockets (AF_PACKET) do not support null-sized recv() with MSG_TRUNC set, let's fall back to FIONREAD
* for them. Checksums don't matter for raw sockets anyway, hence this should be fine. */
if (ioctl(fd, FIONREAD, &k) < 0)
return -errno;
return (gssize) k;
}
/*****************************************************************************/
typedef struct {
GSubprocess *subprocess;
GSource *timeout_source;

View File

@@ -52,10 +52,55 @@ struct _NMStrBuf;
gssize nm_utils_fd_read(int fd, struct _NMStrBuf *out_string);
gssize nm_fd_next_datagram_size(int fd);
struct stat;
int nm_utils_file_stat(const char *filename, struct stat *out_st);
/*****************************************************************************/
/* From systemd's ERRNO_IS_TRANSIENT().
*
* For send()/recv() or read()/write(). */
static inline gboolean
NM_ERRNO_IS_TRANSIENT(int r)
{
return NM_IN_SET((r < 0 ? -r : r), EAGAIN, EINTR);
}
/* From systemd's ERRNO_IS_DISCONNECT().
*
* Hint #1: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5.
*
* Hint #2: The kernel sends e.g., EHOSTUNREACH or ENONET to userspace in some ICMP error cases. See the
* icmp_err_convert[] in net/ipv4/icmp.c in the kernel sources.
*
* Hint #3: When asynchronous connect() on TCP fails because the host never acknowledges a single packet,
* kernel tells us that with ETIMEDOUT, see tcp(7). */
static inline gboolean
NM_ERRNO_IS_DISCONNECT(int r)
{
return NM_IN_SET((r < 0 ? -r : r),
ECONNABORTED,
ECONNREFUSED,
ECONNRESET,
EHOSTDOWN,
EHOSTUNREACH,
ENETDOWN,
ENETRESET,
ENETUNREACH,
ENONET,
ENOPROTOOPT,
ENOTCONN,
EPIPE,
EPROTO,
ESHUTDOWN,
ETIMEDOUT);
}
/*****************************************************************************/
void nm_g_subprocess_terminate_in_background(GSubprocess *subprocess, int timeout_msec_before_kill);
char **nm_utils_find_mkstemp_files(const char *dirname, const char *filename);

View File

@@ -0,0 +1,323 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Taken from systemd's Prioq.
*
* Priority Queue
* The prioq object implements a priority queue. That is, it orders objects by
* their priority and allows O(1) access to the object with the highest
* priority. Insertion and removal are Θ(log n). Optionally, the caller can
* provide a pointer to an index which will be kept up-to-date by the prioq.
*
* The underlying algorithm used in this implementation is a Heap.
*/
#include "libnm-glib-aux/nm-default-glib-i18n-lib.h"
#include "nm-prioq.h"
#include <errno.h>
#include <stdlib.h>
/*****************************************************************************/
struct _NMPrioqItem {
void *data;
unsigned *idx;
};
/*****************************************************************************/
void
nm_prioq_init(NMPrioq *q, GCompareFunc compare_func)
{
nm_assert(q);
nm_assert(compare_func);
*q = (NMPrioq){
._priv =
{
.compare_func = compare_func,
.compare_data = NULL,
.compare_with_data = FALSE,
.items = NULL,
.n_items = 0,
.n_allocated = 0,
},
};
}
void
nm_prioq_init_with_data(NMPrioq *q, GCompareDataFunc compare_func, gpointer compare_data)
{
nm_assert(q);
nm_assert(compare_func);
*q = (NMPrioq){
._priv =
{
.compare_data_func = compare_func,
.compare_data = compare_data,
.compare_with_data = TRUE,
.items = NULL,
.n_items = 0,
.n_allocated = 0,
},
};
}
void
nm_prioq_destroy(NMPrioq *q)
{
if (!q || !q->_priv.compare_func)
return;
free(q->_priv.items);
q->_priv.compare_func = NULL;
}
/*****************************************************************************/
static int
compare(NMPrioq *q, unsigned a, unsigned b)
{
nm_assert(q);
nm_assert(q->_priv.compare_func);
nm_assert(a != b);
nm_assert(a < q->_priv.n_items);
nm_assert(b < q->_priv.n_items);
if (q->_priv.compare_with_data) {
return q->_priv.compare_data_func(q->_priv.items[a].data,
q->_priv.items[b].data,
q->_priv.compare_data);
}
return q->_priv.compare_func(q->_priv.items[a].data, q->_priv.items[b].data);
}
static void
swap(NMPrioq *q, unsigned j, unsigned k)
{
nm_assert(q);
nm_assert(j < q->_priv.n_items);
nm_assert(k < q->_priv.n_items);
nm_assert(!q->_priv.items[j].idx || *(q->_priv.items[j].idx) == j);
nm_assert(!q->_priv.items[k].idx || *(q->_priv.items[k].idx) == k);
NM_SWAP(&q->_priv.items[j].data, &q->_priv.items[k].data);
NM_SWAP(&q->_priv.items[j].idx, &q->_priv.items[k].idx);
if (q->_priv.items[j].idx)
*q->_priv.items[j].idx = j;
if (q->_priv.items[k].idx)
*q->_priv.items[k].idx = k;
}
static unsigned
shuffle_up(NMPrioq *q, unsigned idx)
{
nm_assert(q);
nm_assert(idx < q->_priv.n_items);
while (idx > 0) {
unsigned k;
k = (idx - 1) / 2;
if (compare(q, k, idx) <= 0)
break;
swap(q, idx, k);
idx = k;
}
return idx;
}
static unsigned
shuffle_down(NMPrioq *q, unsigned idx)
{
nm_assert(q);
for (;;) {
unsigned j;
unsigned k;
unsigned s;
k = (idx + 1) * 2; /* right child */
j = k - 1; /* left child */
if (j >= q->_priv.n_items)
break;
if (compare(q, j, idx) < 0) {
/* So our left child is smaller than we are, let's
* remember this fact */
s = j;
} else
s = idx;
if ((k < q->_priv.n_items) && compare(q, k, s) < 0) {
/* So our right child is smaller than we are, let's
* remember this fact */
s = k;
}
/* s now points to the smallest of the three items */
if (s == idx)
/* No swap necessary, we're done */
break;
swap(q, idx, s);
idx = s;
}
return idx;
}
void
nm_prioq_put(NMPrioq *q, void *data, unsigned *idx)
{
unsigned k;
nm_assert(q);
if (q->_priv.n_items >= q->_priv.n_allocated) {
q->_priv.n_allocated = NM_MAX((q->_priv.n_items + 1u) * 2u, 16u);
q->_priv.items = g_renew(struct _NMPrioqItem, q->_priv.items, q->_priv.n_allocated);
}
k = q->_priv.n_items++;
q->_priv.items[k] = (struct _NMPrioqItem){
.data = data,
.idx = idx,
};
if (idx)
*idx = k;
shuffle_up(q, k);
}
static void
remove_item(NMPrioq *q, struct _NMPrioqItem *i)
{
struct _NMPrioqItem *l;
unsigned k;
nm_assert(q);
nm_assert(i);
nm_assert(q->_priv.n_items > 0);
nm_assert(i >= q->_priv.items);
nm_assert(i < &q->_priv.items[q->_priv.n_items]);
l = &q->_priv.items[q->_priv.n_items - 1u];
if (i == l) {
/* Last entry, let's just remove it */
q->_priv.n_items--;
return;
}
/* Not last entry, let's replace the last entry with
* this one, and reshuffle */
k = i - q->_priv.items;
*i = *l;
if (i->idx)
*i->idx = k;
q->_priv.n_items--;
k = shuffle_down(q, k);
shuffle_up(q, k);
}
_nm_pure static struct _NMPrioqItem *
find_item(NMPrioq *q, void *data, unsigned *idx)
{
struct _NMPrioqItem *i;
nm_assert(q);
if (q->_priv.n_items <= 0)
return NULL;
if (idx) {
if (*idx == NM_PRIOQ_IDX_NULL || *idx >= q->_priv.n_items)
return NULL;
i = &q->_priv.items[*idx];
if (i->data == data)
return i;
} else {
for (i = q->_priv.items; i < &q->_priv.items[q->_priv.n_items]; i++) {
if (i->data == data)
return i;
}
}
return NULL;
}
gboolean
nm_prioq_remove(NMPrioq *q, void *data, unsigned *idx)
{
struct _NMPrioqItem *i;
nm_assert(q);
i = find_item(q, data, idx);
if (!i)
return FALSE;
remove_item(q, i);
return TRUE;
}
gboolean
nm_prioq_reshuffle(NMPrioq *q, void *data, unsigned *idx)
{
struct _NMPrioqItem *i;
unsigned k;
nm_assert(q);
i = find_item(q, data, idx);
if (!i)
return FALSE;
k = i - q->_priv.items;
k = shuffle_down(q, k);
shuffle_up(q, k);
return TRUE;
}
void *
nm_prioq_peek_by_index(NMPrioq *q, unsigned idx)
{
nm_assert(q);
if (idx >= q->_priv.n_items)
return NULL;
return q->_priv.items[idx].data;
}
void *
nm_prioq_pop(NMPrioq *q)
{
void *data;
nm_assert(q);
if (q->_priv.n_items <= 0)
return NULL;
data = q->_priv.items[0].data;
remove_item(q, &q->_priv.items[0]);
return data;
}

View File

@@ -0,0 +1,71 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef __NM_PRIOQ_H__
#define __NM_PRIOQ_H__
#define NM_PRIOQ_IDX_NULL G_MAXUINT
struct _NMPrioqItem;
typedef struct {
struct {
union {
GCompareDataFunc compare_data_func;
GCompareFunc compare_func;
};
gpointer compare_data;
struct _NMPrioqItem *items;
unsigned n_items;
unsigned n_allocated;
bool compare_with_data;
} _priv;
} NMPrioq;
#define NM_PRIOQ_ZERO \
{ \
._priv = { \
.compare_func = NULL, \
}, \
}
void nm_prioq_init(NMPrioq *q, GCompareFunc compare_func);
void nm_prioq_init_with_data(NMPrioq *q, GCompareDataFunc compare_func, gpointer compare_data);
void nm_prioq_destroy(NMPrioq *q);
#define nm_auto_prioq nm_auto(nm_prioq_destroy)
void nm_prioq_put(NMPrioq *q, void *data, unsigned *idx);
gboolean nm_prioq_remove(NMPrioq *q, void *data, unsigned *idx);
gboolean nm_prioq_reshuffle(NMPrioq *q, void *data, unsigned *idx);
void *nm_prioq_peek_by_index(NMPrioq *q, unsigned idx) _nm_pure;
static inline void *
nm_prioq_peek(NMPrioq *q)
{
return nm_prioq_peek_by_index(q, 0);
}
void *nm_prioq_pop(NMPrioq *q);
#define NM_PRIOQ_FOREACH_ITEM(q, p) for (unsigned _i = 0; (p = nm_prioq_peek_by_index(q, _i)); _i++)
_nm_pure static inline unsigned
nm_prioq_size(NMPrioq *q)
{
nm_assert(q);
return q->_priv.n_items;
}
_nm_pure static inline gboolean
nm_prioq_isempty(NMPrioq *q)
{
return nm_prioq_size(q) <= 0;
}
#endif /* __NM_PRIOQ_H__ */

View File

@@ -198,6 +198,13 @@ typedef struct {
#define NM_ETHER_ADDR_INIT(...) ((NMEtherAddr) _NM_ETHER_ADDR_INIT(__VA_ARGS__))
struct _NMIPAddr;
extern const struct _NMIPAddr nm_ip_addr_zero;
/* Let's reuse nm_ip_addr_zero also for nm_ether_addr_zero. It's a union that
* also contains a NMEtherAddr field. */
#define nm_ether_addr_zero (*((const NMEtherAddr *) ((gconstpointer) &nm_ip_addr_zero)))
static inline int
nm_ether_addr_cmp(const NMEtherAddr *a, const NMEtherAddr *b)
{
@@ -212,6 +219,12 @@ nm_ether_addr_equal(const NMEtherAddr *a, const NMEtherAddr *b)
return nm_ether_addr_cmp(a, b) == 0;
}
static inline gboolean
nm_ether_addr_is_zero(const NMEtherAddr *a)
{
return nm_memeq(a, &nm_ether_addr_zero, sizeof(NMEtherAddr));
}
/*****************************************************************************/
struct ether_addr;
@@ -221,6 +234,7 @@ nm_utils_ether_addr_cmp(const struct ether_addr *a1, const struct ether_addr *a2
{
nm_assert(a1);
nm_assert(a2);
return memcmp(a1, a2, 6 /*ETH_ALEN*/);
}
@@ -2599,6 +2613,9 @@ nm_ether_addr_to_string(const NMEtherAddr *ether_addr, char sbuf[static(sizeof(N
#define nm_ether_addr_to_string_a(ether_addr) \
nm_ether_addr_to_string((ether_addr), g_alloca(sizeof(NMEtherAddr) * 3))
#define nm_ether_addr_to_string_dup(ether_addr) \
((char *) nm_ether_addr_to_string((ether_addr), g_malloc(sizeof(NMEtherAddr) * 3)))
NMEtherAddr *nm_ether_addr_from_string(NMEtherAddr *addr, const char *str);
guint8 *nm_utils_hexstr2bin_full(const char *hexstr,

View File

@@ -301,8 +301,13 @@ nm_utils_monotonic_timestamp_from_boottime(guint64 boottime, gint64 timestamp_ns
nm_assert(offset <= 0 && offset > G_MININT64);
/* check for overflow (note that offset is non-positive). */
g_return_val_if_fail(boottime < G_MAXINT64, G_MAXINT64);
if (boottime >= (guint64) G_MAXINT64) {
/* This indicates infinity. We keep it at such. */
return G_MAXINT64;
}
/* Note that overflow cannot happen, because bootime is non-negative, and
* offset is non-positive. */
return (gint64) boottime + offset;
}
@@ -317,6 +322,16 @@ nm_utils_clock_gettime_nsec(clockid_t clockid)
return nm_utils_timespec_to_nsec(&tp);
}
gint64
nm_utils_clock_gettime_usec(clockid_t clockid)
{
struct timespec tp;
if (clock_gettime(clockid, &tp) != 0)
return -NM_ERRNO_NATIVE(errno);
return nm_utils_timespec_to_usec(&tp);
}
gint64
nm_utils_clock_gettime_msec(clockid_t clockid)
{
@@ -326,3 +341,36 @@ nm_utils_clock_gettime_msec(clockid_t clockid)
return -NM_ERRNO_NATIVE(errno);
return nm_utils_timespec_to_msec(&tp);
}
/*****************************************************************************/
/* Taken from systemd's map_clock_usec_internal(). */
gint64
nm_time_map_clock(gint64 from, gint64 from_base, gint64 to_base)
{
/* Maps the time 'from' between two clocks, based on a common reference point where the first clock
* is at 'from_base' and the second clock at 'to_base'. Basically calculates:
*
* from - from_base + to_base
*
* But takes care of overflows/underflows and avoids signed operations. */
if (from >= from_base) {
gint64 delta = from - from_base;
/* In the future */
if (to_base >= G_MAXINT64 - delta)
return G_MAXINT64;
return to_base + delta;
} else {
gint64 delta = from_base - from;
/* In the past */
if (to_base <= G_MININT64 + delta)
return G_MININT64;
return to_base - delta;
}
}

View File

@@ -8,17 +8,38 @@
#include <time.h>
_nm_always_inline static inline gint64
_nm_utils_timespec_to_xsec(const struct timespec *ts, gint64 xsec_per_sec)
{
nm_assert(ts);
if (ts->tv_sec < 0 || ts->tv_nsec < 0)
return G_MAXINT64;
if (ts->tv_sec > ((guint64) G_MAXINT64) || ts->tv_nsec > ((guint64) G_MAXINT64)
|| ts->tv_sec >= (G_MAXINT64 - ((gint64) ts->tv_nsec)) / xsec_per_sec)
return G_MAXINT64;
return (((gint64) ts->tv_sec) * xsec_per_sec)
+ (((gint64) ts->tv_nsec) / (NM_UTILS_NSEC_PER_SEC / xsec_per_sec));
}
static inline gint64
nm_utils_timespec_to_nsec(const struct timespec *ts)
{
return (((gint64) ts->tv_sec) * ((gint64) NM_UTILS_NSEC_PER_SEC)) + ((gint64) ts->tv_nsec);
return _nm_utils_timespec_to_xsec(ts, NM_UTILS_NSEC_PER_SEC);
}
static inline gint64
nm_utils_timespec_to_usec(const struct timespec *ts)
{
return _nm_utils_timespec_to_xsec(ts, NM_UTILS_USEC_PER_SEC);
}
static inline gint64
nm_utils_timespec_to_msec(const struct timespec *ts)
{
return (((gint64) ts->tv_sec) * ((gint64) 1000))
+ (((gint64) ts->tv_nsec) / ((gint64) NM_UTILS_NSEC_PER_SEC / 1000));
return _nm_utils_timespec_to_xsec(ts, NM_UTILS_MSEC_PER_SEC);
}
gint64 nm_utils_get_monotonic_timestamp_nsec(void);
@@ -35,6 +56,12 @@ nm_utils_get_monotonic_timestamp_nsec_cached(gint64 *cache_now)
return (*cache_now) ?: (*cache_now = nm_utils_get_monotonic_timestamp_nsec());
}
static inline gint64
nm_utils_get_monotonic_timestamp_usec_cached(gint64 *cache_now)
{
return (*cache_now) ?: (*cache_now = nm_utils_get_monotonic_timestamp_usec());
}
static inline gint64
nm_utils_get_monotonic_timestamp_msec_cached(gint64 *cache_now)
{
@@ -48,6 +75,9 @@ nm_utils_get_monotonic_timestamp_sec_cached(gint32 *cache_now)
}
gint64 nm_utils_clock_gettime_nsec(clockid_t clockid);
gint64 nm_utils_clock_gettime_usec(clockid_t clockid);
gint64 nm_utils_clock_gettime_msec(clockid_t clockid);
gint64 nm_time_map_clock(gint64 from, gint64 from_base, gint64 to_base);
#endif /* __NM_TIME_UTILS_H__ */

View File

@@ -11,6 +11,7 @@
#include "libnm-glib-aux/nm-time-utils.h"
#include "libnm-glib-aux/nm-ref-string.h"
#include "libnm-glib-aux/nm-io-utils.h"
#include "libnm-glib-aux/nm-prioq.h"
#include "libnm-glib-aux/nm-test-utils.h"
@@ -89,6 +90,38 @@ test_monotonic_timestamp(void)
/*****************************************************************************/
static void
test_timespect_to(void)
{
struct timespec ts;
int i;
for (i = 0; i < 1000; i++) {
gint64 t_msec;
gint64 t_usec;
gint64 t_nsec;
nmtst_rand_buf(NULL, &ts, sizeof(ts));
ts.tv_sec = llabs(ts.tv_sec % 100000);
ts.tv_nsec = llabs(ts.tv_nsec % NM_UTILS_NSEC_PER_SEC);
t_msec = nm_utils_timespec_to_msec(&ts);
t_usec = nm_utils_timespec_to_usec(&ts);
t_nsec = nm_utils_timespec_to_nsec(&ts);
g_assert_cmpint(t_msec, <=, t_usec / 1000);
g_assert_cmpint(t_msec + 1, >=, t_usec / 1000);
g_assert_cmpint(t_msec, <=, t_nsec / 1000000);
g_assert_cmpint(t_msec + 1, >=, t_nsec / 1000000);
g_assert_cmpint(t_usec, <=, t_nsec / 1000);
g_assert_cmpint(t_usec + 1, >=, t_nsec / 1000);
}
}
/*****************************************************************************/
static void
test_nmhash(void)
{
@@ -2260,6 +2293,79 @@ test_garray(void)
/*****************************************************************************/
static int
_prioq_cmp(gconstpointer a, gconstpointer b)
{
NM_CMP_DIRECT(GPOINTER_TO_UINT(a), GPOINTER_TO_UINT(b));
return 0;
}
static int
_prioq_cmp_with_data(gconstpointer a, gconstpointer b, gpointer user_data)
{
return _prioq_cmp(a, b);
}
static void
test_nm_prioq(void)
{
nm_auto_prioq NMPrioq q = NM_PRIOQ_ZERO;
gpointer data[200];
gpointer data_pop[200];
guint data_idx[G_N_ELEMENTS(data)];
guint i;
guint n;
gpointer p;
if (nmtst_get_rand_one_case_in(10))
return;
if (nmtst_get_rand_bool())
nm_prioq_init(&q, _prioq_cmp);
else
nm_prioq_init_with_data(&q, _prioq_cmp_with_data, NULL);
g_assert(nm_prioq_size(&q) == 0);
if (nmtst_get_rand_one_case_in(10))
return;
for (i = 0; i < G_N_ELEMENTS(data); i++) {
data[i] = GUINT_TO_POINTER((nmtst_get_rand_uint32() % G_N_ELEMENTS(data)) + 1u);
data_idx[i] = NM_PRIOQ_IDX_NULL;
}
nm_prioq_put(&q, data[0], NULL);
g_assert(nm_prioq_size(&q) == 1);
p = nm_prioq_pop(&q);
g_assert(p == data[0]);
g_assert(nm_prioq_size(&q) == 0);
g_assert(!nm_prioq_pop(&q));
n = nmtst_get_rand_uint32() % G_N_ELEMENTS(data);
for (i = 0; i < n; i++)
nm_prioq_put(&q, data[i], &data_idx[i]);
g_assert_cmpint(nm_prioq_size(&q), ==, n);
if (nmtst_get_rand_one_case_in(10))
return;
for (i = 0; i < n; i++) {
data_pop[i] = nm_prioq_pop(&q);
g_assert(data_pop[i]);
if (i > 0)
g_assert(_prioq_cmp(data_pop[i - 1], data_pop[i]) <= 0);
}
g_assert(!nm_prioq_pop(&q));
g_assert(nm_prioq_size(&q) == 0);
}
/*****************************************************************************/
NMTST_DEFINE();
int
@@ -2270,6 +2376,7 @@ main(int argc, char **argv)
g_test_add_func("/general/test_nm_static_assert", test_nm_static_assert);
g_test_add_func("/general/test_gpid", test_gpid);
g_test_add_func("/general/test_monotonic_timestamp", test_monotonic_timestamp);
g_test_add_func("/general/test_timespect_to", test_timespect_to);
g_test_add_func("/general/test_nmhash", test_nmhash);
g_test_add_func("/general/test_nm_make_strv", test_make_strv);
g_test_add_func("/general/test_nm_strdup_int", test_nm_strdup_int);
@@ -2306,6 +2413,7 @@ main(int argc, char **argv)
g_test_add_func("/general/test_hostname_is_valid", test_hostname_is_valid);
g_test_add_func("/general/test_inet_utils", test_inet_utils);
g_test_add_func("/general/test_garray", test_garray);
g_test_add_func("/general/test_nm_prioq", test_nm_prioq);
return g_test_run();
}

View File

@@ -0,0 +1,18 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
libnm_lldp = static_library(
'nm-lldp',
sources: [
'nm-lldp-neighbor.c',
'nm-lldp-network.c',
'nm-lldp-rx.c',
],
include_directories: [
src_inc,
top_inc,
],
dependencies: [
glib_dep,
libudev_dep,
],
)

View File

@@ -0,0 +1,842 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "libnm-glib-aux/nm-default-glib-i18n-lib.h"
#include "nm-lldp-neighbor.h"
#include <net/ethernet.h>
#include "libnm-std-aux/unaligned.h"
#include "libnm-glib-aux/nm-time-utils.h"
#include "nm-lldp-network.h"
#include "nm-lldp.h"
#include "nm-lldp-rx-internal.h"
/*****************************************************************************/
guint
nm_lldp_neighbor_id_hash(const NMLldpNeighborID *id)
{
NMHashState h;
nm_assert(id);
nm_hash_init(&h, 1925469911u);
nm_hash_update_mem(&h, id->chassis_id, id->chassis_id_size);
nm_hash_update_mem(&h, id->port_id, id->port_id_size);
return nm_hash_complete(&h);
}
int
nm_lldp_neighbor_id_cmp(const NMLldpNeighborID *x, const NMLldpNeighborID *y)
{
nm_assert(x);
nm_assert(y);
NM_CMP_SELF(x, y);
NM_CMP_RETURN_DIRECT(
nm_memcmp_n(x->chassis_id, x->chassis_id_size, y->chassis_id, y->chassis_id_size, 1));
NM_CMP_RETURN_DIRECT(nm_memcmp_n(x->port_id, x->port_id_size, y->port_id, y->port_id_size, 1));
return 0;
}
gboolean
nm_lldp_neighbor_id_equal(const NMLldpNeighborID *x, const NMLldpNeighborID *y)
{
return nm_lldp_neighbor_id_cmp(x, y) == 0;
}
int
nm_lldp_neighbor_prioq_compare_func(const void *a, const void *b)
{
const NMLldpNeighbor *x = a;
const NMLldpNeighbor *y = b;
nm_assert(x);
nm_assert(y);
NM_CMP_FIELD(x, y, until_usec);
return 0;
}
static int
parse_string(NMLldpRX *lldp_rx, char **s, const void *q, size_t n)
{
const char *p = q;
char *k;
nm_assert(s);
nm_assert(p || n == 0);
if (*s) {
_LOG2D(lldp_rx, "Found duplicate string, ignoring field.");
return 0;
}
/* Strip trailing NULs, just to be nice */
while (n > 0 && p[n - 1] == 0)
n--;
if (n <= 0) /* Ignore empty strings */
return 0;
/* Look for inner NULs */
if (memchr(p, 0, n)) {
_LOG2D(lldp_rx, "Found inner NUL in string, ignoring field.");
return 0;
}
/* Let's escape weird chars, for security reasons */
k = nm_utils_buf_utf8safe_escape_cp(p,
n,
NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL
| NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII);
g_free(*s);
*s = k;
return 1;
}
int
nm_lldp_neighbor_parse(NMLldpNeighbor *n)
{
struct ether_header h;
const uint8_t *p;
size_t left;
int r;
nm_assert(n);
if (n->raw_size < sizeof(struct ether_header)) {
_LOG2D(n->lldp_rx, "Received truncated packet, ignoring.");
return -NME_UNSPEC;
}
memcpy(&h, NM_LLDP_NEIGHBOR_RAW(n), sizeof(h));
if (h.ether_type != htobe16(NM_ETHERTYPE_LLDP)) {
_LOG2D(n->lldp_rx, "Received packet with wrong type, ignoring.");
return -NME_UNSPEC;
}
if (h.ether_dhost[0] != 0x01 || h.ether_dhost[1] != 0x80 || h.ether_dhost[2] != 0xc2
|| h.ether_dhost[3] != 0x00 || h.ether_dhost[4] != 0x00
|| !NM_IN_SET(h.ether_dhost[5], 0x00, 0x03, 0x0e)) {
_LOG2D(n->lldp_rx, "Received packet with wrong destination address, ignoring.");
return -NME_UNSPEC;
}
memcpy(&n->source_address, h.ether_shost, sizeof(NMEtherAddr));
memcpy(&n->destination_address, h.ether_dhost, sizeof(NMEtherAddr));
p = (const uint8_t *) NM_LLDP_NEIGHBOR_RAW(n) + sizeof(struct ether_header);
left = n->raw_size - sizeof(struct ether_header);
for (;;) {
uint8_t type;
uint16_t length;
if (left < 2) {
_LOG2D(n->lldp_rx, "TLV lacks header, ignoring.");
return -NME_UNSPEC;
}
type = p[0] >> 1;
length = p[1] + (((uint16_t) (p[0] & 1)) << 8);
p += 2, left -= 2;
if (left < length) {
_LOG2D(n->lldp_rx, "TLV truncated, ignoring datagram.");
return -NME_UNSPEC;
}
switch (type) {
case NM_LLDP_TYPE_END:
if (length != 0) {
_LOG2D(n->lldp_rx, "End marker TLV not zero-sized, ignoring datagram.");
return -NME_UNSPEC;
}
/* Note that after processing the NM_LLDP_TYPE_END left could still be > 0
* as the message may contain padding (see IEEE 802.1AB-2016, sec. 8.5.12) */
goto end_marker;
case NM_LLDP_TYPE_CHASSIS_ID:
if (length < 2 || length > 256) {
/* includes the chassis subtype, hence one extra byte */
_LOG2D(n->lldp_rx, "Chassis ID field size out of range, ignoring datagram.");
return -NME_UNSPEC;
}
if (n->id.chassis_id) {
_LOG2D(n->lldp_rx, "Duplicate chassis ID field, ignoring datagram.");
return -NME_UNSPEC;
}
n->id.chassis_id = nm_memdup(p, length);
n->id.chassis_id_size = length;
break;
case NM_LLDP_TYPE_PORT_ID:
if (length < 2 || length > 256) {
/* includes the port subtype, hence one extra byte */
_LOG2D(n->lldp_rx, "Port ID field size out of range, ignoring datagram.");
return -NME_UNSPEC;
}
if (n->id.port_id) {
_LOG2D(n->lldp_rx, "Duplicate port ID field, ignoring datagram.");
return -NME_UNSPEC;
}
n->id.port_id = nm_memdup(p, length);
n->id.port_id_size = length;
break;
case NM_LLDP_TYPE_TTL:
if (length != 2) {
_LOG2D(n->lldp_rx, "TTL field has wrong size, ignoring datagram.");
return -NME_UNSPEC;
}
if (n->has_ttl) {
_LOG2D(n->lldp_rx, "Duplicate TTL field, ignoring datagram.");
return -NME_UNSPEC;
}
n->ttl = unaligned_read_be16(p);
n->has_ttl = true;
break;
case NM_LLDP_TYPE_PORT_DESCRIPTION:
r = parse_string(n->lldp_rx, &n->port_description, p, length);
if (r < 0)
return r;
break;
case NM_LLDP_TYPE_SYSTEM_NAME:
r = parse_string(n->lldp_rx, &n->system_name, p, length);
if (r < 0)
return r;
break;
case NM_LLDP_TYPE_SYSTEM_DESCRIPTION:
r = parse_string(n->lldp_rx, &n->system_description, p, length);
if (r < 0)
return r;
break;
case NM_LLDP_TYPE_SYSTEM_CAPABILITIES:
if (length != 4) {
_LOG2D(n->lldp_rx, "System capabilities field has wrong size.");
return -NME_UNSPEC;
}
n->system_capabilities = unaligned_read_be16(p);
n->enabled_capabilities = unaligned_read_be16(p + 2);
n->has_capabilities = true;
break;
case NM_LLDP_TYPE_PRIVATE:
if (length < 4) {
_LOG2D(n->lldp_rx, "Found private TLV that is too short, ignoring.");
return -NME_UNSPEC;
}
/* RFC 8520: MUD URL */
if (memcmp(p, NM_LLDP_OUI_IANA_MUD, sizeof(NM_LLDP_OUI_IANA_MUD)) == 0) {
r = parse_string(n->lldp_rx,
&n->mud_url,
p + sizeof(NM_LLDP_OUI_IANA_MUD),
length - sizeof(NM_LLDP_OUI_IANA_MUD));
if (r < 0)
return r;
}
break;
}
p += length, left -= length;
}
end_marker:
if (!n->id.chassis_id || !n->id.port_id || !n->has_ttl) {
_LOG2D(n->lldp_rx, "One or more mandatory TLV missing in datagram. Ignoring.");
return -NME_UNSPEC;
}
n->rindex = sizeof(struct ether_header);
return 0;
}
void
nm_lldp_neighbor_start_ttl(NMLldpNeighbor *n)
{
nm_assert(n);
if (n->ttl > 0) {
/* Use the packet's timestamp if there is one known */
if (n->timestamp_usec <= 0) {
/* Otherwise, take the current time */
n->timestamp_usec = nm_utils_get_monotonic_timestamp_usec();
}
n->until_usec = n->timestamp_usec + (n->ttl * NM_UTILS_USEC_PER_SEC);
} else
n->until_usec = 0;
if (n->lldp_rx)
nm_prioq_reshuffle(&n->lldp_rx->neighbor_by_expiry, n, &n->prioq_idx);
}
int
nm_lldp_neighbor_cmp(const NMLldpNeighbor *a, const NMLldpNeighbor *b)
{
NM_CMP_SELF(a, b);
NM_CMP_FIELD(a, b, raw_size);
NM_CMP_DIRECT_MEMCMP(NM_LLDP_NEIGHBOR_RAW(a), NM_LLDP_NEIGHBOR_RAW(b), a->raw_size);
return 0;
}
int
nm_lldp_neighbor_get_source_address(NMLldpNeighbor *n, NMEtherAddr *address)
{
g_return_val_if_fail(n, -EINVAL);
g_return_val_if_fail(address, -EINVAL);
*address = n->source_address;
return 0;
}
int
nm_lldp_neighbor_get_destination_address(NMLldpNeighbor *n, NMEtherAddr *address)
{
g_return_val_if_fail(n, -EINVAL);
g_return_val_if_fail(address, -EINVAL);
*address = n->destination_address;
return 0;
}
int
nm_lldp_neighbor_get_raw(NMLldpNeighbor *n, const void **ret, size_t *size)
{
g_return_val_if_fail(n, -EINVAL);
g_return_val_if_fail(ret, -EINVAL);
g_return_val_if_fail(size, -EINVAL);
*ret = NM_LLDP_NEIGHBOR_RAW(n);
*size = n->raw_size;
return 0;
}
int
nm_lldp_neighbor_get_chassis_id(NMLldpNeighbor *n, uint8_t *type, const void **ret, size_t *size)
{
g_return_val_if_fail(n, -EINVAL);
g_return_val_if_fail(type, -EINVAL);
g_return_val_if_fail(ret, -EINVAL);
g_return_val_if_fail(size, -EINVAL);
nm_assert(n->id.chassis_id_size > 0);
*type = *(uint8_t *) n->id.chassis_id;
*ret = (uint8_t *) n->id.chassis_id + 1;
*size = n->id.chassis_id_size - 1;
return 0;
}
static char *
format_mac_address(const void *data, size_t sz)
{
NMEtherAddr a;
nm_assert(data || sz <= 0);
if (sz != 7)
return NULL;
memcpy(&a, (uint8_t *) data + 1, sizeof(a));
return nm_ether_addr_to_string_dup(&a);
}
static char *
format_network_address(const void *data, size_t sz)
{
int addr_family;
NMIPAddr a;
if (sz == 6 && ((uint8_t *) data)[1] == 1) {
memcpy(&a.addr4, (uint8_t *) data + 2, sizeof(a.addr4));
addr_family = AF_INET;
} else if (sz == 18 && ((uint8_t *) data)[1] == 2) {
memcpy(&a.addr6, (uint8_t *) data + 2, sizeof(a.addr6));
addr_family = AF_INET6;
} else
return NULL;
return nm_inet_ntop_dup(addr_family, &a);
}
const char *
nm_lldp_neighbor_get_chassis_id_as_string(NMLldpNeighbor *n)
{
char *k;
g_return_val_if_fail(n, NULL);
if (n->chassis_id_as_string)
return n->chassis_id_as_string;
nm_assert(n->id.chassis_id_size > 0);
switch (*(uint8_t *) n->id.chassis_id) {
case NM_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT:
case NM_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS:
case NM_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT:
case NM_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME:
case NM_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED:
k = nm_utils_buf_utf8safe_escape_cp((char *) n->id.chassis_id + 1,
n->id.chassis_id_size - 1,
NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL
| NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII);
goto done;
case NM_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS:
k = format_mac_address(n->id.chassis_id, n->id.chassis_id_size);
if (k)
goto done;
break;
case NM_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS:
k = format_network_address(n->id.chassis_id, n->id.chassis_id_size);
if (k)
goto done;
break;
}
/* Generic fallback */
k = nm_utils_bin2hexstr_full(n->id.chassis_id, n->id.chassis_id_size, '\0', FALSE, NULL);
done:
nm_assert(k);
return (n->chassis_id_as_string = k);
}
int
nm_lldp_neighbor_get_port_id(NMLldpNeighbor *n, uint8_t *type, const void **ret, size_t *size)
{
g_return_val_if_fail(n, -EINVAL);
g_return_val_if_fail(type, -EINVAL);
g_return_val_if_fail(ret, -EINVAL);
g_return_val_if_fail(size, -EINVAL);
nm_assert(n->id.port_id_size > 0);
*type = *(uint8_t *) n->id.port_id;
*ret = (uint8_t *) n->id.port_id + 1;
*size = n->id.port_id_size - 1;
return 0;
}
const char *
nm_lldp_neighbor_get_port_id_as_string(NMLldpNeighbor *n)
{
char *k;
g_return_val_if_fail(n, NULL);
if (n->port_id_as_string)
return n->port_id_as_string;
nm_assert(n->id.port_id_size > 0);
switch (*(uint8_t *) n->id.port_id) {
case NM_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS:
case NM_LLDP_PORT_SUBTYPE_PORT_COMPONENT:
case NM_LLDP_PORT_SUBTYPE_INTERFACE_NAME:
case NM_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED:
k = nm_utils_buf_utf8safe_escape_cp((char *) n->id.port_id + 1,
n->id.port_id_size - 1,
NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL
| NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII);
goto done;
case NM_LLDP_PORT_SUBTYPE_MAC_ADDRESS:
k = format_mac_address(n->id.port_id, n->id.port_id_size);
if (k)
goto done;
break;
case NM_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS:
k = format_network_address(n->id.port_id, n->id.port_id_size);
if (k)
goto done;
break;
}
/* Generic fallback */
k = nm_utils_bin2hexstr_full(n->id.port_id, n->id.port_id_size, '\0', FALSE, NULL);
done:
nm_assert(k);
return (n->port_id_as_string = k);
}
int
nm_lldp_neighbor_get_ttl(NMLldpNeighbor *n, uint16_t *ret_sec)
{
g_return_val_if_fail(n, -EINVAL);
g_return_val_if_fail(ret_sec, -EINVAL);
*ret_sec = n->ttl;
return 0;
}
int
nm_lldp_neighbor_get_system_name(NMLldpNeighbor *n, const char **ret)
{
g_return_val_if_fail(n, -EINVAL);
g_return_val_if_fail(ret, -EINVAL);
if (!n->system_name)
return -ENODATA;
*ret = n->system_name;
return 0;
}
int
nm_lldp_neighbor_get_system_description(NMLldpNeighbor *n, const char **ret)
{
g_return_val_if_fail(n, -EINVAL);
g_return_val_if_fail(ret, -EINVAL);
if (!n->system_description)
return -ENODATA;
*ret = n->system_description;
return 0;
}
int
nm_lldp_neighbor_get_port_description(NMLldpNeighbor *n, const char **ret)
{
g_return_val_if_fail(n, -EINVAL);
g_return_val_if_fail(ret, -EINVAL);
if (!n->port_description)
return -ENODATA;
*ret = n->port_description;
return 0;
}
int
nm_lldp_neighbor_get_mud_url(NMLldpNeighbor *n, const char **ret)
{
g_return_val_if_fail(n, -EINVAL);
g_return_val_if_fail(ret, -EINVAL);
if (!n->mud_url)
return -ENODATA;
*ret = n->mud_url;
return 0;
}
int
nm_lldp_neighbor_get_system_capabilities(NMLldpNeighbor *n, uint16_t *ret)
{
g_return_val_if_fail(n, -EINVAL);
g_return_val_if_fail(ret, -EINVAL);
if (!n->has_capabilities)
return -ENODATA;
*ret = n->system_capabilities;
return 0;
}
int
nm_lldp_neighbor_get_enabled_capabilities(NMLldpNeighbor *n, uint16_t *ret)
{
g_return_val_if_fail(n, -EINVAL);
g_return_val_if_fail(ret, -EINVAL);
if (!n->has_capabilities)
return -ENODATA;
*ret = n->enabled_capabilities;
return 0;
}
int
nm_lldp_neighbor_tlv_rewind(NMLldpNeighbor *n)
{
g_return_val_if_fail(n, -EINVAL);
nm_assert(n->raw_size >= sizeof(struct ether_header));
n->rindex = sizeof(struct ether_header);
return n->rindex < n->raw_size;
}
int
nm_lldp_neighbor_tlv_next(NMLldpNeighbor *n)
{
size_t length;
g_return_val_if_fail(n, -EINVAL);
if (n->rindex == n->raw_size) /* EOF */
return -ESPIPE;
if (n->rindex + 2 > n->raw_size) /* Truncated message */
return -EBADMSG;
length = NM_LLDP_NEIGHBOR_TLV_LENGTH(n);
if (n->rindex + 2 + length > n->raw_size)
return -EBADMSG;
n->rindex += 2 + length;
return n->rindex < n->raw_size;
}
int
nm_lldp_neighbor_tlv_get_type(NMLldpNeighbor *n, uint8_t *type)
{
g_return_val_if_fail(n, -EINVAL);
g_return_val_if_fail(type, -EINVAL);
if (n->rindex == n->raw_size) /* EOF */
return -ESPIPE;
if (n->rindex + 2 > n->raw_size)
return -EBADMSG;
*type = NM_LLDP_NEIGHBOR_TLV_TYPE(n);
return 0;
}
int
nm_lldp_neighbor_tlv_is_type(NMLldpNeighbor *n, uint8_t type)
{
uint8_t k;
int r;
g_return_val_if_fail(n, -EINVAL);
r = nm_lldp_neighbor_tlv_get_type(n, &k);
if (r < 0)
return r;
return type == k;
}
int
nm_lldp_neighbor_tlv_get_oui(NMLldpNeighbor *n, uint8_t oui[static 3], uint8_t *subtype)
{
const uint8_t *d;
size_t length;
int r;
g_return_val_if_fail(n, -EINVAL);
g_return_val_if_fail(oui, -EINVAL);
g_return_val_if_fail(subtype, -EINVAL);
r = nm_lldp_neighbor_tlv_is_type(n, NM_LLDP_TYPE_PRIVATE);
if (r < 0)
return r;
if (r == 0)
return -ENXIO;
length = NM_LLDP_NEIGHBOR_TLV_LENGTH(n);
if (length < 4)
return -EBADMSG;
if (n->rindex + 2 + length > n->raw_size)
return -EBADMSG;
d = NM_LLDP_NEIGHBOR_TLV_DATA(n);
memcpy(oui, d, 3);
*subtype = d[3];
return 0;
}
int
nm_lldp_neighbor_tlv_is_oui(NMLldpNeighbor *n, const uint8_t oui[static 3], uint8_t subtype)
{
uint8_t k[3], st;
int r;
r = nm_lldp_neighbor_tlv_get_oui(n, k, &st);
if (r == -ENXIO)
return 0;
if (r < 0)
return r;
return memcmp(k, oui, 3) == 0 && st == subtype;
}
int
nm_lldp_neighbor_tlv_get_raw(NMLldpNeighbor *n, const void **ret, size_t *size)
{
size_t length;
g_return_val_if_fail(n, -EINVAL);
g_return_val_if_fail(ret, -EINVAL);
g_return_val_if_fail(size, -EINVAL);
/* Note that this returns the full TLV, including the TLV header */
if (n->rindex + 2 > n->raw_size)
return -EBADMSG;
length = NM_LLDP_NEIGHBOR_TLV_LENGTH(n);
if (n->rindex + 2 + length > n->raw_size)
return -EBADMSG;
*ret = (uint8_t *) NM_LLDP_NEIGHBOR_RAW(n) + n->rindex;
*size = length + 2;
return 0;
}
int
nm_lldp_neighbor_get_timestamp_usec(NMLldpNeighbor *n, gint64 *out_usec)
{
g_return_val_if_fail(n, -EINVAL);
if (n->timestamp_usec == 0)
return -ENODATA;
NM_SET_OUT(out_usec, n->timestamp_usec);
return 0;
}
/*****************************************************************************/
NMLldpNeighbor *
nm_lldp_neighbor_new(size_t raw_size)
{
NMLldpNeighbor *n;
nm_assert(raw_size < SIZE_MAX - NM_ALIGN(sizeof(NMLldpNeighbor)));
n = g_malloc0(NM_ALIGN(sizeof(NMLldpNeighbor)) + raw_size);
n->raw_size = raw_size;
n->ref_count = 1;
return n;
}
NMLldpNeighbor *
nm_lldp_neighbor_new_from_raw(const void *raw, size_t raw_size)
{
nm_auto(nm_lldp_neighbor_unrefp) NMLldpNeighbor *n = NULL;
int r;
g_return_val_if_fail(raw || raw_size <= 0, NULL);
n = nm_lldp_neighbor_new(raw_size);
nm_memcpy(NM_LLDP_NEIGHBOR_RAW(n), raw, raw_size);
r = nm_lldp_neighbor_parse(n);
if (r < 0)
return NULL;
return g_steal_pointer(&n);
}
NMLldpNeighbor *
nm_lldp_neighbor_ref(NMLldpNeighbor *n)
{
if (!n)
return NULL;
nm_assert(n->ref_count > 0 || n->lldp_rx);
n->ref_count++;
return n;
}
static void
_lldp_neighbor_free(NMLldpNeighbor *n)
{
if (!n)
return;
g_free((gpointer) n->id.port_id);
g_free((gpointer) n->id.chassis_id);
g_free(n->port_description);
g_free(n->system_name);
g_free(n->system_description);
g_free(n->mud_url);
g_free(n->chassis_id_as_string);
g_free(n->port_id_as_string);
g_free(n);
return;
}
NMLldpNeighbor *
nm_lldp_neighbor_unref(NMLldpNeighbor *n)
{
/* Drops one reference from the neighbor. Note that the object is not freed unless it is already unlinked from
* the sd_lldp object. */
if (!n)
return NULL;
nm_assert(n->ref_count > 0);
n->ref_count--;
if (n->ref_count <= 0 && !n->lldp_rx)
_lldp_neighbor_free(n);
return NULL;
}
void
nm_lldp_neighbor_unlink(NMLldpNeighbor *n)
{
gpointer old_key;
gpointer old_val;
/* Removes the neighbor object from the LLDP object, and frees it if it also has no other reference. */
if (!n)
return;
if (!n->lldp_rx)
return;
/* Only remove the neighbor object from the hash table if it's in there, don't complain if it isn't. This is
* because we are used as destructor call for hashmap_clear() and thus sometimes are called to de-register
* ourselves from the hashtable and sometimes are called after we already are de-registered. */
if (g_hash_table_steal_extended(n->lldp_rx->neighbor_by_id, n, &old_key, &old_val)) {
nm_assert(NM_IN_SET(old_val, NULL, old_key));
if (old_key != n) {
/* it wasn't the right key. Add it again. */
g_hash_table_add(n->lldp_rx->neighbor_by_id, old_key);
}
}
nm_prioq_remove(&n->lldp_rx->neighbor_by_expiry, n, &n->prioq_idx);
n->lldp_rx = NULL;
if (n->ref_count <= 0)
_lldp_neighbor_free(n);
return;
}

View File

@@ -0,0 +1,85 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef __NM_LLDP_NEIGHBOR_H__
#define __NM_LLDP_NEIGHBOR_H__
#include "nm-lldp-rx.h"
struct _NMLldpNeighbor {
NMLldpNeighborID id;
/* Neighbor objects stay around as long as they are linked into an "NMLldpRX" object or n_ref > 0. */
struct _NMLldpRX *lldp_rx;
gint64 timestamp_usec;
gint64 until_usec;
int ref_count;
unsigned prioq_idx;
NMEtherAddr source_address;
NMEtherAddr destination_address;
/* The raw packet size. The data is appended to the object, accessible via LLDP_NEIGHBOR_RAW() */
size_t raw_size;
/* The current read index for the iterative TLV interface */
size_t rindex;
/* And a couple of fields parsed out. */
bool has_ttl : 1;
bool has_capabilities : 1;
bool has_port_vlan_id : 1;
uint16_t ttl;
uint16_t system_capabilities;
uint16_t enabled_capabilities;
char *port_description;
char *system_name;
char *system_description;
char *mud_url;
uint16_t port_vlan_id;
char *chassis_id_as_string;
char *port_id_as_string;
};
static inline void *
NM_LLDP_NEIGHBOR_RAW(const NMLldpNeighbor *n)
{
return (uint8_t *) n + NM_ALIGN(sizeof(NMLldpNeighbor));
}
static inline uint8_t
NM_LLDP_NEIGHBOR_TLV_TYPE(const NMLldpNeighbor *n)
{
return ((uint8_t *) NM_LLDP_NEIGHBOR_RAW(n))[n->rindex] >> 1;
}
static inline size_t
NM_LLDP_NEIGHBOR_TLV_LENGTH(const NMLldpNeighbor *n)
{
uint8_t *p;
p = (uint8_t *) NM_LLDP_NEIGHBOR_RAW(n) + n->rindex;
return p[1] + (((size_t) (p[0] & 1)) << 8);
}
static inline void *
NM_LLDP_NEIGHBOR_TLV_DATA(const NMLldpNeighbor *n)
{
return ((uint8_t *) NM_LLDP_NEIGHBOR_RAW(n)) + n->rindex + 2;
}
int nm_lldp_neighbor_prioq_compare_func(const void *a, const void *b);
void nm_lldp_neighbor_unlink(NMLldpNeighbor *n);
NMLldpNeighbor *nm_lldp_neighbor_new(size_t raw_size);
int nm_lldp_neighbor_parse(NMLldpNeighbor *n);
void nm_lldp_neighbor_start_ttl(NMLldpNeighbor *n);
#endif /* __NM_LLDP_NEIGHBOR_H__ */

View File

@@ -0,0 +1,74 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "libnm-glib-aux/nm-default-glib-i18n-lib.h"
#include "nm-lldp-network.h"
#include <linux/filter.h>
#include <linux/if_packet.h>
#include <netinet/if_ether.h>
int
nm_lldp_network_bind_raw_socket(int ifindex)
{
static const struct sock_filter filter[] = {
BPF_STMT(BPF_LD + BPF_W + BPF_ABS,
offsetof(struct ethhdr, h_dest)), /* A <- 4 bytes of destination MAC */
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0180c200, 1, 0), /* A != 01:80:c2:00 */
BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */
BPF_STMT(BPF_LD + BPF_H + BPF_ABS,
offsetof(struct ethhdr, h_dest)
+ 4), /* A <- remaining 2 bytes of destination MAC */
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0000, 3, 0), /* A != 00:00 */
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0003, 2, 0), /* A != 00:03 */
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x000e, 1, 0), /* A != 00:0e */
BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */
BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ethhdr, h_proto)), /* A <- protocol */
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, NM_ETHERTYPE_LLDP, 1, 0), /* A != NM_ETHERTYPE_LLDP */
BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */
BPF_STMT(BPF_RET + BPF_K, UINT32_MAX), /* accept packet */
};
static const struct sock_fprog fprog = {
.len = G_N_ELEMENTS(filter),
.filter = (struct sock_filter *) filter,
};
struct packet_mreq mreq = {
.mr_ifindex = ifindex,
.mr_type = PACKET_MR_MULTICAST,
.mr_alen = ETH_ALEN,
.mr_address = {0x01, 0x80, 0xC2, 0x00, 0x00, 0x00},
};
struct sockaddr_ll saddrll = {
.sll_family = AF_PACKET,
.sll_ifindex = ifindex,
};
nm_auto_close int fd = -1;
assert(ifindex > 0);
fd = socket(AF_PACKET, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, htobe16(NM_ETHERTYPE_LLDP));
if (fd < 0)
return -errno;
if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) < 0)
return -errno;
/* customer bridge */
if (setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
return -errno;
/* non TPMR bridge */
mreq.mr_address[ETH_ALEN - 1] = 0x03;
if (setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
return -errno;
/* nearest bridge */
mreq.mr_address[ETH_ALEN - 1] = 0x0E;
if (setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
return -errno;
if (bind(fd, (const struct sockaddr *) &saddrll, sizeof(saddrll)) < 0)
return -errno;
return nm_steal_fd(&fd);
}

View File

@@ -0,0 +1,9 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef __NM_LLDP_NETWORK_H__
#define __NM_LLDP_NETWORK_H__
#define NM_ETHERTYPE_LLDP 0x88cc
int nm_lldp_network_bind_raw_socket(int ifindex);
#endif /* __NM_LLDP_NETWORK_H__ */

View File

@@ -0,0 +1,55 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef __NM_LLDP_RX_INTERNAL_H__
#define __NM_LLDP_RX_INTERNAL_H__
#include "libnm-glib-aux/nm-prioq.h"
#include "libnm-log-core/nm-logging.h"
#include "nm-lldp-rx.h"
struct _NMLldpRX {
int ref_count;
int fd;
NMLldpRXConfig config;
GMainContext *main_context;
GSource *io_event_source;
GSource *timer_event_source;
GHashTable *neighbor_by_id;
NMPrioq neighbor_by_expiry;
};
/*****************************************************************************/
#define _NMLOG2_DOMAIN LOGD_PLATFORM
#define _NMLOG2(level, lldp_rx, ...) \
G_STMT_START \
{ \
const NMLogLevel _level = (level); \
NMLldpRX *_lldp_rx = (lldp_rx); \
\
if (_NMLOG2_ENABLED(_level)) { \
_nm_log(level, \
_NMLOG2_DOMAIN, \
0, \
_lldp_rx->config.log_ifname, \
_lldp_rx->config.log_uuid, \
"lldp-rx[" NM_HASH_OBFUSCATE_PTR_FMT \
"%s%s]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
NM_HASH_OBFUSCATE_PTR(_lldp_rx), \
NM_PRINT_FMT_QUOTED2(_lldp_rx->config.log_ifname, \
", ", \
_lldp_rx->config.log_ifname, \
"") _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
} \
} \
G_STMT_END
/*****************************************************************************/
#endif /* __NM_LLDP_RX_INTERNAL_H__ */

469
src/libnm-lldp/nm-lldp-rx.c Normal file
View File

@@ -0,0 +1,469 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "libnm-glib-aux/nm-default-glib-i18n-lib.h"
#include "nm-lldp-rx.h"
#include <arpa/inet.h>
#include <linux/sockios.h>
#include <sys/ioctl.h>
#include "libnm-glib-aux/nm-io-utils.h"
#include "libnm-glib-aux/nm-time-utils.h"
#include "nm-lldp-network.h"
#include "nm-lldp-neighbor.h"
#include "nm-lldp-rx-internal.h"
#define LLDP_DEFAULT_NEIGHBORS_MAX 128U
/*****************************************************************************/
static void lldp_rx_start_timer(NMLldpRX *lldp_rx, NMLldpNeighbor *neighbor);
/*****************************************************************************/
NM_UTILS_LOOKUP_STR_DEFINE(nm_lldp_rx_event_to_string,
NMLldpRXEvent,
NM_UTILS_LOOKUP_DEFAULT_WARN("<unknown>"),
NM_UTILS_LOOKUP_STR_ITEM(NM_LLDP_RX_EVENT_ADDED, "added"),
NM_UTILS_LOOKUP_STR_ITEM(NM_LLDP_RX_EVENT_REMOVED, "removed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_LLDP_RX_EVENT_UPDATED, "updated"),
NM_UTILS_LOOKUP_STR_ITEM(NM_LLDP_RX_EVENT_REFRESHED, "refreshed"),
NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER());
/*****************************************************************************/
#define nm_assert_is_lldp_rx(lldp_rx) \
G_STMT_START \
{ \
NMLldpRX *_lldp_rx = (lldp_rx); \
\
nm_assert(_lldp_rx); \
nm_assert(_lldp_rx->ref_count > 0); \
} \
G_STMT_END
/*****************************************************************************/
/* This needs to be first. Check nm_lldp_rx_get_id(). */
G_STATIC_ASSERT(G_STRUCT_OFFSET(NMLldpNeighbor, id) == 0);
/*****************************************************************************/
static void
lldp_rx_callback(NMLldpRX *lldp_rx, NMLldpRXEvent event, NMLldpNeighbor *n)
{
nm_assert_is_lldp_rx(lldp_rx);
nm_assert(event >= 0 && event < _NM_LLDP_RX_EVENT_MAX);
_LOG2D(lldp_rx, "invoking callback for '%s' event", nm_lldp_rx_event_to_string(event));
lldp_rx->config.callback(lldp_rx, event, n, lldp_rx->config.userdata);
}
static gboolean
lldp_rx_make_space(NMLldpRX *lldp_rx, gboolean flush, size_t extra)
{
nm_auto(nm_lldp_rx_unrefp) NMLldpRX *lldp_rx_alive = NULL;
gint64 now_usec = 0;
gboolean changed = FALSE;
size_t max;
/* Remove all entries that are past their TTL, and more until at least the specified number of extra entries
* are free. */
max = (!flush && lldp_rx->config.neighbors_max > extra)
? (lldp_rx->config.neighbors_max - extra)
: 0u;
for (;;) {
NMLldpNeighbor *n;
nm_assert(g_hash_table_size(lldp_rx->neighbor_by_id)
== nm_prioq_size(&lldp_rx->neighbor_by_expiry));
n = nm_prioq_peek(&lldp_rx->neighbor_by_expiry);
if (!n)
break;
if (nm_prioq_size(&lldp_rx->neighbor_by_expiry) > max) {
/* drop it. */
} else {
if (n->until_usec > nm_utils_get_monotonic_timestamp_usec_cached(&now_usec))
break;
}
if (flush) {
changed = TRUE;
nm_lldp_neighbor_unlink(n);
} else {
nm_auto(nm_lldp_neighbor_unrefp) NMLldpNeighbor *n_alive = NULL;
if (!changed) {
lldp_rx_alive = nm_lldp_rx_ref(lldp_rx);
changed = TRUE;
}
n_alive = nm_lldp_neighbor_ref(n);
nm_lldp_neighbor_unlink(n);
lldp_rx_callback(lldp_rx, NM_LLDP_RX_EVENT_REMOVED, n);
}
}
return changed;
}
static bool
lldp_rx_keep_neighbor(NMLldpRX *lldp_rx, NMLldpNeighbor *n)
{
nm_assert_is_lldp_rx(lldp_rx);
nm_assert(n);
/* Don't keep data with a zero TTL */
if (n->ttl <= 0)
return FALSE;
/* Filter out data from the filter address */
if (!nm_ether_addr_is_zero(&lldp_rx->config.filter_address)
&& nm_ether_addr_equal(&lldp_rx->config.filter_address, &n->source_address))
return FALSE;
/* Only add if the neighbor has a capability we are interested in. Note that we also store all neighbors with
* no caps field set. */
if (n->has_capabilities && (n->enabled_capabilities & lldp_rx->config.capability_mask) == 0)
return FALSE;
/* Keep everything else */
return TRUE;
}
static void
lldp_rx_add_neighbor(NMLldpRX *lldp_rx, NMLldpNeighbor *n)
{
nm_auto(nm_lldp_neighbor_unrefp) NMLldpNeighbor *old_alive = NULL;
NMLldpNeighbor *old;
gboolean keep;
nm_assert_is_lldp_rx(lldp_rx);
nm_assert(n);
nm_assert(!n->lldp_rx);
keep = lldp_rx_keep_neighbor(lldp_rx, n);
/* First retrieve the old entry for this MSAP */
old = g_hash_table_lookup(lldp_rx->neighbor_by_id, n);
if (old) {
old_alive = nm_lldp_neighbor_ref(old);
if (!keep) {
nm_lldp_neighbor_unlink(old);
lldp_rx_callback(lldp_rx, NM_LLDP_RX_EVENT_REMOVED, old);
return;
}
if (nm_lldp_neighbor_equal(n, old)) {
/* Is this equal, then restart the TTL counter, but don't do anything else. */
old->timestamp_usec = n->timestamp_usec;
lldp_rx_start_timer(lldp_rx, old);
lldp_rx_callback(lldp_rx, NM_LLDP_RX_EVENT_REFRESHED, old);
return;
}
/* Data changed, remove the old entry, and add a new one */
nm_lldp_neighbor_unlink(old);
} else if (!keep)
return;
/* Then, make room for at least one new neighbor */
lldp_rx_make_space(lldp_rx, FALSE, 1);
if (!g_hash_table_add(lldp_rx->neighbor_by_id, n))
nm_assert_not_reached();
nm_prioq_put(&lldp_rx->neighbor_by_expiry, n, &n->prioq_idx);
n->lldp_rx = lldp_rx;
lldp_rx_start_timer(lldp_rx, n);
lldp_rx_callback(lldp_rx, old ? NM_LLDP_RX_EVENT_UPDATED : NM_LLDP_RX_EVENT_ADDED, n);
}
static gboolean
lldp_rx_receive_datagram(int fd, GIOCondition condition, gpointer user_data)
{
NMLldpRX *lldp_rx = user_data;
nm_auto(nm_lldp_neighbor_unrefp) NMLldpNeighbor *n = NULL;
ssize_t space;
ssize_t length;
struct timespec ts;
gint64 ts_usec;
gint64 now_usec;
gint64 now_usec_rt;
gint64 now_usec_bt;
int r;
nm_assert_is_lldp_rx(lldp_rx);
nm_assert(lldp_rx->fd == fd);
_LOG2T(lldp_rx, "fd ready");
space = nm_fd_next_datagram_size(lldp_rx->fd);
if (space < 0) {
if (!NM_ERRNO_IS_TRANSIENT(space) && !NM_ERRNO_IS_DISCONNECT(space)) {
_LOG2D(lldp_rx,
"Failed to determine datagram size to read, ignoring: %s",
nm_strerror_native(-space));
}
return G_SOURCE_CONTINUE;
}
n = nm_lldp_neighbor_new(space);
length = recv(lldp_rx->fd, NM_LLDP_NEIGHBOR_RAW(n), n->raw_size, MSG_DONTWAIT);
if (length < 0) {
if (!NM_ERRNO_IS_TRANSIENT(errno) && !NM_ERRNO_IS_DISCONNECT(errno)) {
_LOG2D(lldp_rx,
"Failed to read LLDP datagram, ignoring: %s",
nm_strerror_native(errno));
}
return G_SOURCE_CONTINUE;
}
if ((size_t) length != n->raw_size) {
_LOG2D(lldp_rx, "Packet size mismatch, ignoring");
return G_SOURCE_CONTINUE;
}
/* Try to get the timestamp of this packet if it is known */
if (ioctl(lldp_rx->fd, SIOCGSTAMPNS, &ts) >= 0
&& (ts_usec = nm_utils_timespec_to_usec(&ts)) < G_MAXINT64
&& (now_usec_bt = nm_utils_clock_gettime_usec(CLOCK_BOOTTIME)) >= 0
&& (now_usec_rt = nm_utils_clock_gettime_usec(CLOCK_REALTIME)) >= 0) {
gint64 t;
now_usec = nm_utils_monotonic_timestamp_from_boottime(now_usec_bt, 1000);
ts_usec = nm_time_map_clock(ts_usec, now_usec_rt, now_usec_bt);
t = now_usec;
if (ts_usec >= 0) {
ts_usec = nm_utils_monotonic_timestamp_from_boottime(ts_usec, 1000);
if (ts_usec > NM_UTILS_USEC_PER_SEC && ts_usec < now_usec)
t = ts_usec;
}
n->timestamp_usec = t;
} else
n->timestamp_usec = nm_utils_get_monotonic_timestamp_usec();
r = nm_lldp_neighbor_parse(n);
if (r < 0) {
_LOG2D(lldp_rx, "Failure parsing invalid LLDP datagram.");
return G_SOURCE_CONTINUE;
}
_LOG2D(lldp_rx, "Successfully processed LLDP datagram.");
lldp_rx_add_neighbor(lldp_rx, n);
return G_SOURCE_CONTINUE;
}
static void
lldp_rx_reset(NMLldpRX *lldp_rx)
{
nm_clear_g_source_inst(&lldp_rx->timer_event_source);
nm_clear_g_source_inst(&lldp_rx->io_event_source);
nm_clear_fd(&lldp_rx->fd);
lldp_rx_make_space(lldp_rx, TRUE, 0);
nm_assert(g_hash_table_size(lldp_rx->neighbor_by_id) == 0);
nm_assert(nm_prioq_size(&lldp_rx->neighbor_by_expiry) == 0);
}
gboolean
nm_lldp_rx_is_running(NMLldpRX *lldp_rx)
{
if (!lldp_rx)
return FALSE;
return lldp_rx->fd >= 0;
}
int
nm_lldp_rx_start(NMLldpRX *lldp_rx)
{
int r;
g_return_val_if_fail(lldp_rx, -EINVAL);
nm_assert(lldp_rx->main_context);
nm_assert(lldp_rx->config.ifindex > 0);
if (nm_lldp_rx_is_running(lldp_rx))
return 0;
nm_assert(!lldp_rx->io_event_source);
r = nm_lldp_network_bind_raw_socket(lldp_rx->config.ifindex);
if (r < 0) {
_LOG2D(lldp_rx, "start failed to bind socket (%s)", nm_strerror_native(-r));
return r;
}
lldp_rx->fd = r;
lldp_rx->io_event_source = nm_g_source_attach(nm_g_unix_fd_source_new(lldp_rx->fd,
G_IO_IN,
G_PRIORITY_DEFAULT,
lldp_rx_receive_datagram,
lldp_rx,
NULL),
lldp_rx->main_context);
_LOG2D(lldp_rx, "started (fd %d)", lldp_rx->fd);
return 1;
}
int
nm_lldp_rx_stop(NMLldpRX *lldp_rx)
{
if (!nm_lldp_rx_is_running(lldp_rx))
return 0;
_LOG2D(lldp_rx, "stopping");
lldp_rx_reset(lldp_rx);
return 1;
}
static gboolean
on_timer_event(gpointer user_data)
{
NMLldpRX *lldp_rx = user_data;
lldp_rx_make_space(lldp_rx, FALSE, 0);
lldp_rx_start_timer(lldp_rx, NULL);
return G_SOURCE_CONTINUE;
}
static void
lldp_rx_start_timer(NMLldpRX *lldp_rx, NMLldpNeighbor *neighbor)
{
NMLldpNeighbor *n;
gint64 timeout_msec;
nm_assert_is_lldp_rx(lldp_rx);
nm_clear_g_source_inst(&lldp_rx->timer_event_source);
if (neighbor)
nm_lldp_neighbor_start_ttl(neighbor);
n = nm_prioq_peek(&lldp_rx->neighbor_by_expiry);
if (!n)
return;
timeout_msec = (n->until_usec / 1000) - nm_utils_get_monotonic_timestamp_msec();
lldp_rx->timer_event_source =
nm_g_source_attach(nm_g_timeout_source_new(NM_CLAMP(timeout_msec, 0, G_MAXUINT),
G_PRIORITY_DEFAULT,
on_timer_event,
lldp_rx,
NULL),
lldp_rx->main_context);
}
static inline int
neighbor_compare_func(gconstpointer p_a, gconstpointer p_b, gpointer user_data)
{
NMLldpNeighbor *const *a = p_a;
NMLldpNeighbor *const *b = p_b;
nm_assert(a);
nm_assert(b);
nm_assert(*a);
nm_assert(*b);
return nm_lldp_neighbor_id_cmp(&(*a)->id, &(*b)->id);
}
NMLldpNeighbor **
nm_lldp_rx_get_neighbors(NMLldpRX *lldp_rx, guint *out_len)
{
g_return_val_if_fail(lldp_rx, NULL);
return (NMLldpNeighbor **)
nm_utils_hash_keys_to_array(lldp_rx->neighbor_by_id, neighbor_compare_func, NULL, out_len);
}
/*****************************************************************************/
NMLldpRX *
nm_lldp_rx_new(const NMLldpRXConfig *config)
{
NMLldpRX *lldp_rx;
nm_assert(config);
nm_assert(config->ifindex > 0);
nm_assert(config->callback);
/* This needs to be first, see neighbor_by_id hash. */
G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMLldpNeighbor, id) == 0);
lldp_rx = g_slice_new(NMLldpRX);
*lldp_rx = (NMLldpRX){
.ref_count = 1,
.fd = -1,
.main_context = g_main_context_ref_thread_default(),
.config = *config,
.neighbor_by_id = g_hash_table_new((GHashFunc) nm_lldp_neighbor_id_hash,
(GEqualFunc) nm_lldp_neighbor_id_equal),
};
lldp_rx->config.log_ifname = g_strdup(lldp_rx->config.log_ifname);
lldp_rx->config.log_uuid = g_strdup(lldp_rx->config.log_uuid);
if (lldp_rx->config.neighbors_max == 0)
lldp_rx->config.neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX;
if (!lldp_rx->config.has_capability_mask && lldp_rx->config.capability_mask == 0)
lldp_rx->config.capability_mask = UINT16_MAX;
nm_prioq_init(&lldp_rx->neighbor_by_expiry, (GCompareFunc) nm_lldp_neighbor_prioq_compare_func);
return lldp_rx;
}
NMLldpRX *
nm_lldp_rx_ref(NMLldpRX *lldp_rx)
{
if (!lldp_rx)
return NULL;
nm_assert_is_lldp_rx(lldp_rx);
nm_assert(lldp_rx->ref_count < G_MAXINT);
lldp_rx->ref_count++;
return lldp_rx;
}
void
nm_lldp_rx_unref(NMLldpRX *lldp_rx)
{
if (!lldp_rx)
return;
nm_assert_is_lldp_rx(lldp_rx);
if (--lldp_rx->ref_count > 0)
return;
lldp_rx_reset(lldp_rx);
g_hash_table_unref(lldp_rx->neighbor_by_id);
nm_prioq_destroy(&lldp_rx->neighbor_by_expiry);
free((char *) lldp_rx->config.log_ifname);
free((char *) lldp_rx->config.log_uuid);
g_main_context_unref(lldp_rx->main_context);
nm_g_slice_free(lldp_rx);
}

124
src/libnm-lldp/nm-lldp-rx.h Normal file
View File

@@ -0,0 +1,124 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef __NM_LLDP_RX_H__
#define __NM_LLDP_RX_H__
#include "nm-lldp.h"
typedef struct _NMLldpRX NMLldpRX;
typedef struct _NMLldpNeighbor NMLldpNeighbor;
typedef enum {
NM_LLDP_RX_EVENT_ADDED,
NM_LLDP_RX_EVENT_REMOVED,
NM_LLDP_RX_EVENT_UPDATED,
NM_LLDP_RX_EVENT_REFRESHED,
_NM_LLDP_RX_EVENT_MAX,
_NM_LLDP_RX_EVENT_INVALID = -EINVAL,
} NMLldpRXEvent;
const char *nm_lldp_rx_event_to_string(NMLldpRXEvent e) _nm_pure;
typedef struct {
/* The spec calls this an "MSAP identifier" */
const void *chassis_id;
size_t chassis_id_size;
const void *port_id;
size_t port_id_size;
} NMLldpNeighborID;
guint nm_lldp_neighbor_id_hash(const NMLldpNeighborID *id);
int nm_lldp_neighbor_id_cmp(const NMLldpNeighborID *x, const NMLldpNeighborID *y);
gboolean nm_lldp_neighbor_id_equal(const NMLldpNeighborID *x, const NMLldpNeighborID *y);
typedef void (*NMLldpRXCallback)(NMLldpRX *lldp_rx,
NMLldpRXEvent event,
NMLldpNeighbor *n,
void *userdata);
typedef struct {
int ifindex;
guint neighbors_max;
const char *log_ifname;
const char *log_uuid;
NMLldpRXCallback callback;
void *userdata;
/* In order to deal nicely with bridges that send back our own packets, allow one address to be filtered, so
* that our own can be filtered out here. */
NMEtherAddr filter_address;
uint16_t capability_mask;
bool has_capability_mask : 1;
} NMLldpRXConfig;
NMLldpRX *nm_lldp_rx_new(const NMLldpRXConfig *config);
NMLldpRX *nm_lldp_rx_ref(NMLldpRX *lldp_rx);
void nm_lldp_rx_unref(NMLldpRX *lldp_rx);
NM_AUTO_DEFINE_FCN(NMLldpRX *, nm_lldp_rx_unrefp, nm_lldp_rx_unref);
int nm_lldp_rx_start(NMLldpRX *lldp_rx);
int nm_lldp_rx_stop(NMLldpRX *lldp_rx);
gboolean nm_lldp_rx_is_running(NMLldpRX *lldp_rx);
/* Controls how much and what to store in the neighbors database */
NMLldpNeighbor **nm_lldp_rx_get_neighbors(NMLldpRX *lldp_rx, guint *out_len);
/*****************************************************************************/
NMLldpNeighbor *nm_lldp_neighbor_new_from_raw(const void *raw, size_t raw_size);
NMLldpNeighbor *nm_lldp_neighbor_ref(NMLldpNeighbor *n);
NMLldpNeighbor *nm_lldp_neighbor_unref(NMLldpNeighbor *n);
NM_AUTO_DEFINE_FCN(NMLldpNeighbor *, nm_lldp_neighbor_unrefp, nm_lldp_neighbor_unref);
int nm_lldp_neighbor_cmp(const NMLldpNeighbor *a, const NMLldpNeighbor *b);
static inline gboolean
nm_lldp_neighbor_equal(const NMLldpNeighbor *a, const NMLldpNeighbor *b)
{
return nm_lldp_neighbor_cmp(a, b) == 0;
}
/*****************************************************************************/
static inline const NMLldpNeighborID *
nm_lldp_neighbor_get_id(NMLldpNeighbor *lldp_neigbor)
{
return (const NMLldpNeighborID *) ((gconstpointer) lldp_neigbor);
}
/* Access to LLDP frame metadata */
int nm_lldp_neighbor_get_source_address(NMLldpNeighbor *n, NMEtherAddr *address);
int nm_lldp_neighbor_get_destination_address(NMLldpNeighbor *n, NMEtherAddr *address);
int nm_lldp_neighbor_get_timestamp_usec(NMLldpNeighbor *n, gint64 *out_usec);
int nm_lldp_neighbor_get_raw(NMLldpNeighbor *n, const void **ret, size_t *size);
/* High-level, direct, parsed out field access. These fields exist at most once, hence may be queried directly. */
int
nm_lldp_neighbor_get_chassis_id(NMLldpNeighbor *n, uint8_t *type, const void **ret, size_t *size);
const char *nm_lldp_neighbor_get_chassis_id_as_string(NMLldpNeighbor *n);
int nm_lldp_neighbor_get_port_id(NMLldpNeighbor *n, uint8_t *type, const void **ret, size_t *size);
const char *nm_lldp_neighbor_get_port_id_as_string(NMLldpNeighbor *n);
int nm_lldp_neighbor_get_ttl(NMLldpNeighbor *n, uint16_t *ret_sec);
int nm_lldp_neighbor_get_system_name(NMLldpNeighbor *n, const char **ret);
int nm_lldp_neighbor_get_system_description(NMLldpNeighbor *n, const char **ret);
int nm_lldp_neighbor_get_port_description(NMLldpNeighbor *n, const char **ret);
int nm_lldp_neighbor_get_mud_url(NMLldpNeighbor *n, const char **ret);
int nm_lldp_neighbor_get_system_capabilities(NMLldpNeighbor *n, uint16_t *ret);
int nm_lldp_neighbor_get_enabled_capabilities(NMLldpNeighbor *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. */
int nm_lldp_neighbor_tlv_rewind(NMLldpNeighbor *n);
int nm_lldp_neighbor_tlv_next(NMLldpNeighbor *n);
int nm_lldp_neighbor_tlv_get_type(NMLldpNeighbor *n, uint8_t *type);
int nm_lldp_neighbor_tlv_is_type(NMLldpNeighbor *n, uint8_t type);
int nm_lldp_neighbor_tlv_get_oui(NMLldpNeighbor *n, uint8_t oui[static 3], uint8_t *subtype);
int nm_lldp_neighbor_tlv_is_oui(NMLldpNeighbor *n, const uint8_t oui[static 3], uint8_t subtype);
int nm_lldp_neighbor_tlv_get_raw(NMLldpNeighbor *n, const void **ret, size_t *size);
#endif /* __NM_LLDP_RX_H__ */

110
src/libnm-lldp/nm-lldp.h Normal file
View File

@@ -0,0 +1,110 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef __NM_LLDP_H__
#define __NM_LLDP_H__
/* IEEE 802.1AB-2009 Clause 8: TLV Types */
enum {
NM_LLDP_TYPE_END = 0,
NM_LLDP_TYPE_CHASSIS_ID = 1,
NM_LLDP_TYPE_PORT_ID = 2,
NM_LLDP_TYPE_TTL = 3,
NM_LLDP_TYPE_PORT_DESCRIPTION = 4,
NM_LLDP_TYPE_SYSTEM_NAME = 5,
NM_LLDP_TYPE_SYSTEM_DESCRIPTION = 6,
NM_LLDP_TYPE_SYSTEM_CAPABILITIES = 7,
NM_LLDP_TYPE_MGMT_ADDRESS = 8,
NM_LLDP_TYPE_PRIVATE = 127
};
/* IEEE 802.1AB-2009 Clause 8.5.2: Chassis subtypes */
enum {
NM_LLDP_CHASSIS_SUBTYPE_RESERVED = 0,
NM_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT = 1,
NM_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS = 2,
NM_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT = 3,
NM_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS = 4,
NM_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS = 5,
NM_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME = 6,
NM_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED = 7
};
/* IEEE 802.1AB-2009 Clause 8.5.3: Port subtype */
enum {
NM_LLDP_PORT_SUBTYPE_RESERVED = 0,
NM_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS = 1,
NM_LLDP_PORT_SUBTYPE_PORT_COMPONENT = 2,
NM_LLDP_PORT_SUBTYPE_MAC_ADDRESS = 3,
NM_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS = 4,
NM_LLDP_PORT_SUBTYPE_INTERFACE_NAME = 5,
NM_LLDP_PORT_SUBTYPE_AGENT_CIRCUIT_ID = 6,
NM_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED = 7
};
/* IEEE 802.1AB-2009 Clause 8.5.8: System capabilities */
enum {
NM_LLDP_SYSTEM_CAPABILITIES_OTHER = 1 << 0,
NM_LLDP_SYSTEM_CAPABILITIES_REPEATER = 1 << 1,
NM_LLDP_SYSTEM_CAPABILITIES_BRIDGE = 1 << 2,
NM_LLDP_SYSTEM_CAPABILITIES_WLAN_AP = 1 << 3,
NM_LLDP_SYSTEM_CAPABILITIES_ROUTER = 1 << 4,
NM_LLDP_SYSTEM_CAPABILITIES_PHONE = 1 << 5,
NM_LLDP_SYSTEM_CAPABILITIES_DOCSIS = 1 << 6,
NM_LLDP_SYSTEM_CAPABILITIES_STATION = 1 << 7,
NM_LLDP_SYSTEM_CAPABILITIES_CVLAN = 1 << 8,
NM_LLDP_SYSTEM_CAPABILITIES_SVLAN = 1 << 9,
NM_LLDP_SYSTEM_CAPABILITIES_TPMR = 1 << 10
};
#define NM_LLDP_SYSTEM_CAPABILITIES_ALL UINT16_MAX
#define NM_LLDP_SYSTEM_CAPABILITIES_ALL_ROUTERS \
((uint16_t) (NM_LLDP_SYSTEM_CAPABILITIES_REPEATER | NM_LLDP_SYSTEM_CAPABILITIES_BRIDGE \
| NM_LLDP_SYSTEM_CAPABILITIES_WLAN_AP | NM_LLDP_SYSTEM_CAPABILITIES_ROUTER \
| NM_LLDP_SYSTEM_CAPABILITIES_DOCSIS | NM_LLDP_SYSTEM_CAPABILITIES_CVLAN \
| NM_LLDP_SYSTEM_CAPABILITIES_SVLAN | NM_LLDP_SYSTEM_CAPABILITIES_TPMR))
#define NM_LLDP_OUI_802_1 \
(const uint8_t[]) \
{ \
0x00, 0x80, 0xc2 \
}
#define NM_LLDP_OUI_802_3 \
(const uint8_t[]) \
{ \
0x00, 0x12, 0x0f \
}
#define _SD_LLDP_OUI_IANA 0x00, 0x00, 0x5E
#define NM_LLDP_OUI_IANA \
(const uint8_t[]) \
{ \
_SD_LLDP_OUI_IANA \
}
#define NM_LLDP_OUI_IANA_SUBTYPE_MUD 0x01
#define NM_LLDP_OUI_IANA_MUD \
(const uint8_t[]) \
{ \
_SD_LLDP_OUI_IANA, NM_LLDP_OUI_IANA_SUBTYPE_MUD \
}
/* IEEE 802.1AB-2009 Annex E */
enum {
NM_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID = 1,
NM_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID = 2,
NM_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME = 3,
NM_LLDP_OUI_802_1_SUBTYPE_PROTOCOL_IDENTITY = 4,
NM_LLDP_OUI_802_1_SUBTYPE_VID_USAGE_DIGEST = 5,
NM_LLDP_OUI_802_1_SUBTYPE_MANAGEMENT_VID = 6,
NM_LLDP_OUI_802_1_SUBTYPE_LINK_AGGREGATION = 7
};
/* IEEE 802.1AB-2009 Annex F */
enum {
NM_LLDP_OUI_802_3_SUBTYPE_MAC_PHY_CONFIG_STATUS = 1,
NM_LLDP_OUI_802_3_SUBTYPE_POWER_VIA_MDI = 2,
NM_LLDP_OUI_802_3_SUBTYPE_LINK_AGGREGATION = 3,
NM_LLDP_OUI_802_3_SUBTYPE_MAXIMUM_FRAME_SIZE = 4
};
#endif /* __NM_LLDP_H__ */

View File

@@ -410,6 +410,24 @@ nm_mult_clamped_u(unsigned a, unsigned b)
/*****************************************************************************/
static inline size_t
NM_ALIGN_TO(size_t l, size_t ali)
{
nm_assert(nm_utils_is_power_of_two(ali));
if (l > SIZE_MAX - (ali - 1))
return SIZE_MAX; /* indicate overflow */
return ((l + ali - 1) & ~(ali - 1));
}
#define NM_ALIGN4(l) NM_ALIGN_TO(l, 4)
#define NM_ALIGN8(l) NM_ALIGN_TO(l, 8)
#define NM_ALIGN(l) NM_ALIGN_TO(l, sizeof(void *))
#define NM_ALIGN_PTR(p) ((void *) NM_ALIGN((uintptr_t) (p)))
/*****************************************************************************/
#define NM_SWAP(p_a, p_b) \
do { \
typeof(*(p_a)) *const _p_a = (p_a); \

View File

@@ -7,12 +7,9 @@ libnm_systemd_core = static_library(
'src/libsystemd-network/dhcp6-network.c',
'src/libsystemd-network/dhcp6-option.c',
'src/libsystemd-network/dhcp6-protocol.c',
'src/libsystemd-network/lldp-neighbor.c',
'src/libsystemd-network/lldp-network.c',
'src/libsystemd-network/network-common.c',
'src/libsystemd-network/sd-dhcp6-client.c',
'src/libsystemd-network/sd-dhcp6-lease.c',
'src/libsystemd-network/sd-lldp-rx.c',
'src/libsystemd/sd-event/event-util.c',
'src/libsystemd/sd-event/sd-event.c',
'src/libsystemd/sd-id128/id128-util.c',

View File

@@ -7,7 +7,6 @@
#define __NM_SD_H__
#include "src/systemd/sd-dhcp6-client.h"
#include "src/systemd/sd-lldp-rx.h"
/*****************************************************************************/

View File

@@ -1,798 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "nm-sd-adapt-core.h"
#include "alloc-util.h"
#include "escape.h"
#include "ether-addr-util.h"
#include "hexdecoct.h"
#include "in-addr-util.h"
#include "lldp-neighbor.h"
#include "memory-util.h"
#include "missing_network.h"
#include "unaligned.h"
static void lldp_neighbor_id_hash_func(const LLDPNeighborID *id, struct siphash *state) {
assert(id);
assert(state);
siphash24_compress(id->chassis_id, id->chassis_id_size, state);
siphash24_compress(&id->chassis_id_size, sizeof(id->chassis_id_size), state);
siphash24_compress(id->port_id, id->port_id_size, state);
siphash24_compress(&id->port_id_size, sizeof(id->port_id_size), state);
}
int lldp_neighbor_id_compare_func(const LLDPNeighborID *x, const LLDPNeighborID *y) {
assert(x);
assert(y);
return memcmp_nn(x->chassis_id, x->chassis_id_size, y->chassis_id, y->chassis_id_size)
?: memcmp_nn(x->port_id, x->port_id_size, y->port_id, y->port_id_size);
}
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
lldp_neighbor_hash_ops,
LLDPNeighborID,
lldp_neighbor_id_hash_func,
lldp_neighbor_id_compare_func,
sd_lldp_neighbor,
lldp_neighbor_unlink);
int lldp_neighbor_prioq_compare_func(const void *a, const void *b) {
const sd_lldp_neighbor *x = a, *y = b;
assert(x);
assert(y);
return CMP(x->until, y->until);
}
sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n) {
if (!n)
return NULL;
assert(n->n_ref > 0 || n->lldp_rx);
n->n_ref++;
return n;
}
static sd_lldp_neighbor *lldp_neighbor_free(sd_lldp_neighbor *n) {
if (!n)
return NULL;
free(n->id.port_id);
free(n->id.chassis_id);
free(n->port_description);
free(n->system_name);
free(n->system_description);
free(n->mud_url);
free(n->chassis_id_as_string);
free(n->port_id_as_string);
return mfree(n);
}
sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n) {
/* Drops one reference from the neighbor. Note that the object is not freed unless it is already unlinked from
* the sd_lldp object. */
if (!n)
return NULL;
assert(n->n_ref > 0);
n->n_ref--;
if (n->n_ref <= 0 && !n->lldp_rx)
lldp_neighbor_free(n);
return NULL;
}
sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n) {
/* Removes the neighbor object from the LLDP object, and frees it if it also has no other reference. */
if (!n)
return NULL;
if (!n->lldp_rx)
return NULL;
/* Only remove the neighbor object from the hash table if it's in there, don't complain if it isn't. This is
* because we are used as destructor call for hashmap_clear() and thus sometimes are called to de-register
* ourselves from the hashtable and sometimes are called after we already are de-registered. */
(void) hashmap_remove_value(n->lldp_rx->neighbor_by_id, &n->id, n);
assert_se(prioq_remove(n->lldp_rx->neighbor_by_expiry, n, &n->prioq_idx) >= 0);
n->lldp_rx = NULL;
if (n->n_ref <= 0)
lldp_neighbor_free(n);
return NULL;
}
sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size) {
sd_lldp_neighbor *n;
if (raw_size > SIZE_MAX - ALIGN(sizeof(sd_lldp_neighbor)))
return NULL;
n = malloc0(ALIGN(sizeof(sd_lldp_neighbor)) + raw_size);
if (!n)
return NULL;
n->raw_size = raw_size;
n->n_ref = 1;
return n;
}
static int parse_string(sd_lldp_rx *lldp_rx, char **s, const void *q, size_t n) {
const char *p = q;
char *k;
assert(s);
assert(p || n == 0);
if (*s) {
log_lldp_rx(lldp_rx, "Found duplicate string, ignoring field.");
return 0;
}
/* Strip trailing NULs, just to be nice */
while (n > 0 && p[n-1] == 0)
n--;
if (n <= 0) /* Ignore empty strings */
return 0;
/* Look for inner NULs */
if (memchr(p, 0, n)) {
log_lldp_rx(lldp_rx, "Found inner NUL in string, ignoring field.");
return 0;
}
/* Let's escape weird chars, for security reasons */
k = cescape_length(p, n);
if (!k)
return log_oom_debug();
free(*s);
*s = k;
return 1;
}
int lldp_neighbor_parse(sd_lldp_neighbor *n) {
struct ether_header h;
const uint8_t *p;
size_t left;
int r;
assert(n);
if (n->raw_size < sizeof(struct ether_header))
return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
"Received truncated packet, ignoring.");
memcpy(&h, LLDP_NEIGHBOR_RAW(n), sizeof(h));
if (h.ether_type != htobe16(ETHERTYPE_LLDP))
return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
"Received packet with wrong type, ignoring.");
if (h.ether_dhost[0] != 0x01 ||
h.ether_dhost[1] != 0x80 ||
h.ether_dhost[2] != 0xc2 ||
h.ether_dhost[3] != 0x00 ||
h.ether_dhost[4] != 0x00 ||
!IN_SET(h.ether_dhost[5], 0x00, 0x03, 0x0e))
return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
"Received packet with wrong destination address, ignoring.");
memcpy(&n->source_address, h.ether_shost, sizeof(struct ether_addr));
memcpy(&n->destination_address, h.ether_dhost, sizeof(struct ether_addr));
p = (const uint8_t*) LLDP_NEIGHBOR_RAW(n) + sizeof(struct ether_header);
left = n->raw_size - sizeof(struct ether_header);
for (;;) {
uint8_t type;
uint16_t length;
if (left < 2)
return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
"TLV lacks header, ignoring.");
type = p[0] >> 1;
length = p[1] + (((uint16_t) (p[0] & 1)) << 8);
p += 2, left -= 2;
if (left < length)
return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
"TLV truncated, ignoring datagram.");
switch (type) {
case SD_LLDP_TYPE_END:
if (length != 0)
return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
"End marker TLV not zero-sized, ignoring datagram.");
/* Note that after processing the SD_LLDP_TYPE_END left could still be > 0
* as the message may contain padding (see IEEE 802.1AB-2016, sec. 8.5.12) */
goto end_marker;
case SD_LLDP_TYPE_CHASSIS_ID:
if (length < 2 || length > 256)
/* includes the chassis subtype, hence one extra byte */
return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
"Chassis ID field size out of range, ignoring datagram.");
if (n->id.chassis_id)
return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
"Duplicate chassis ID field, ignoring datagram.");
n->id.chassis_id = memdup(p, length);
if (!n->id.chassis_id)
return log_oom_debug();
n->id.chassis_id_size = length;
break;
case SD_LLDP_TYPE_PORT_ID:
if (length < 2 || length > 256)
/* includes the port subtype, hence one extra byte */
return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
"Port ID field size out of range, ignoring datagram.");
if (n->id.port_id)
return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
"Duplicate port ID field, ignoring datagram.");
n->id.port_id = memdup(p, length);
if (!n->id.port_id)
return log_oom_debug();
n->id.port_id_size = length;
break;
case SD_LLDP_TYPE_TTL:
if (length != 2)
return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
"TTL field has wrong size, ignoring datagram.");
if (n->has_ttl)
return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
"Duplicate TTL field, ignoring datagram.");
n->ttl = unaligned_read_be16(p);
n->has_ttl = true;
break;
case SD_LLDP_TYPE_PORT_DESCRIPTION:
r = parse_string(n->lldp_rx, &n->port_description, p, length);
if (r < 0)
return r;
break;
case SD_LLDP_TYPE_SYSTEM_NAME:
r = parse_string(n->lldp_rx, &n->system_name, p, length);
if (r < 0)
return r;
break;
case SD_LLDP_TYPE_SYSTEM_DESCRIPTION:
r = parse_string(n->lldp_rx, &n->system_description, p, length);
if (r < 0)
return r;
break;
case SD_LLDP_TYPE_SYSTEM_CAPABILITIES:
if (length != 4)
return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
"System capabilities field has wrong size.");
n->system_capabilities = unaligned_read_be16(p);
n->enabled_capabilities = unaligned_read_be16(p + 2);
n->has_capabilities = true;
break;
case SD_LLDP_TYPE_PRIVATE:
if (length < 4)
return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
"Found private TLV that is too short, ignoring.");
/* RFC 8520: MUD URL */
if (memcmp(p, SD_LLDP_OUI_IANA_MUD, sizeof(SD_LLDP_OUI_IANA_MUD)) == 0) {
r = parse_string(n->lldp_rx, &n->mud_url, p + sizeof(SD_LLDP_OUI_IANA_MUD),
length - sizeof(SD_LLDP_OUI_IANA_MUD));
if (r < 0)
return r;
}
break;
}
p += length, left -= length;
}
end_marker:
if (!n->id.chassis_id || !n->id.port_id || !n->has_ttl)
return log_lldp_rx_errno(n->lldp_rx, SYNTHETIC_ERRNO(EBADMSG),
"One or more mandatory TLV missing in datagram. Ignoring.");
n->rindex = sizeof(struct ether_header);
return 0;
}
void lldp_neighbor_start_ttl(sd_lldp_neighbor *n) {
assert(n);
if (n->ttl > 0) {
usec_t base;
/* Use the packet's timestamp if there is one known */
base = triple_timestamp_by_clock(&n->timestamp, CLOCK_BOOTTIME);
if (!timestamp_is_set(base))
base = now(CLOCK_BOOTTIME); /* Otherwise, take the current time */
n->until = usec_add(base, n->ttl * USEC_PER_SEC);
} else
n->until = 0;
if (n->lldp_rx)
prioq_reshuffle(n->lldp_rx->neighbor_by_expiry, n, &n->prioq_idx);
}
bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b) {
if (a == b)
return true;
if (!a || !b)
return false;
if (a->raw_size != b->raw_size)
return false;
return memcmp(LLDP_NEIGHBOR_RAW(a), LLDP_NEIGHBOR_RAW(b), a->raw_size) == 0;
}
int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address) {
assert_return(n, -EINVAL);
assert_return(address, -EINVAL);
*address = n->source_address;
return 0;
}
int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address) {
assert_return(n, -EINVAL);
assert_return(address, -EINVAL);
*address = n->destination_address;
return 0;
}
int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) {
assert_return(n, -EINVAL);
assert_return(ret, -EINVAL);
assert_return(size, -EINVAL);
*ret = LLDP_NEIGHBOR_RAW(n);
*size = n->raw_size;
return 0;
}
int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) {
assert_return(n, -EINVAL);
assert_return(type, -EINVAL);
assert_return(ret, -EINVAL);
assert_return(size, -EINVAL);
assert(n->id.chassis_id_size > 0);
*type = *(uint8_t*) n->id.chassis_id;
*ret = (uint8_t*) n->id.chassis_id + 1;
*size = n->id.chassis_id_size - 1;
return 0;
}
static int format_mac_address(const void *data, size_t sz, char **ret) {
struct ether_addr a;
char *k;
assert(data || sz <= 0);
if (sz != 7)
return 0;
memcpy(&a, (uint8_t*) data + 1, sizeof(a));
k = new(char, ETHER_ADDR_TO_STRING_MAX);
if (!k)
return -ENOMEM;
*ret = ether_addr_to_string(&a, k);
return 1;
}
static int format_network_address(const void *data, size_t sz, char **ret) {
union in_addr_union a;
int family, r;
if (sz == 6 && ((uint8_t*) data)[1] == 1) {
memcpy(&a.in, (uint8_t*) data + 2, sizeof(a.in));
family = AF_INET;
} else if (sz == 18 && ((uint8_t*) data)[1] == 2) {
memcpy(&a.in6, (uint8_t*) data + 2, sizeof(a.in6));
family = AF_INET6;
} else
return 0;
r = in_addr_to_string(family, &a, ret);
if (r < 0)
return r;
return 1;
}
int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret) {
char *k;
int r;
assert_return(n, -EINVAL);
assert_return(ret, -EINVAL);
if (n->chassis_id_as_string) {
*ret = n->chassis_id_as_string;
return 0;
}
assert(n->id.chassis_id_size > 0);
switch (*(uint8_t*) n->id.chassis_id) {
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:
k = cescape_length((char*) n->id.chassis_id + 1, n->id.chassis_id_size - 1);
if (!k)
return -ENOMEM;
goto done;
case SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS:
r = format_mac_address(n->id.chassis_id, n->id.chassis_id_size, &k);
if (r < 0)
return r;
if (r > 0)
goto done;
break;
case SD_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS:
r = format_network_address(n->id.chassis_id, n->id.chassis_id_size, &k);
if (r < 0)
return r;
if (r > 0)
goto done;
break;
}
/* Generic fallback */
k = hexmem(n->id.chassis_id, n->id.chassis_id_size);
if (!k)
return -ENOMEM;
done:
*ret = n->chassis_id_as_string = k;
return 0;
}
int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) {
assert_return(n, -EINVAL);
assert_return(type, -EINVAL);
assert_return(ret, -EINVAL);
assert_return(size, -EINVAL);
assert(n->id.port_id_size > 0);
*type = *(uint8_t*) n->id.port_id;
*ret = (uint8_t*) n->id.port_id + 1;
*size = n->id.port_id_size - 1;
return 0;
}
int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret) {
char *k;
int r;
assert_return(n, -EINVAL);
assert_return(ret, -EINVAL);
if (n->port_id_as_string) {
*ret = n->port_id_as_string;
return 0;
}
assert(n->id.port_id_size > 0);
switch (*(uint8_t*) n->id.port_id) {
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:
k = cescape_length((char*) n->id.port_id + 1, n->id.port_id_size - 1);
if (!k)
return -ENOMEM;
goto done;
case SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS:
r = format_mac_address(n->id.port_id, n->id.port_id_size, &k);
if (r < 0)
return r;
if (r > 0)
goto done;
break;
case SD_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS:
r = format_network_address(n->id.port_id, n->id.port_id_size, &k);
if (r < 0)
return r;
if (r > 0)
goto done;
break;
}
/* Generic fallback */
k = hexmem(n->id.port_id, n->id.port_id_size);
if (!k)
return -ENOMEM;
done:
*ret = n->port_id_as_string = k;
return 0;
}
int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec) {
assert_return(n, -EINVAL);
assert_return(ret_sec, -EINVAL);
*ret_sec = n->ttl;
return 0;
}
int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret) {
assert_return(n, -EINVAL);
assert_return(ret, -EINVAL);
if (!n->system_name)
return -ENODATA;
*ret = n->system_name;
return 0;
}
int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret) {
assert_return(n, -EINVAL);
assert_return(ret, -EINVAL);
if (!n->system_description)
return -ENODATA;
*ret = n->system_description;
return 0;
}
int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret) {
assert_return(n, -EINVAL);
assert_return(ret, -EINVAL);
if (!n->port_description)
return -ENODATA;
*ret = n->port_description;
return 0;
}
int sd_lldp_neighbor_get_mud_url(sd_lldp_neighbor *n, const char **ret) {
assert_return(n, -EINVAL);
assert_return(ret, -EINVAL);
if (!n->mud_url)
return -ENODATA;
*ret = n->mud_url;
return 0;
}
int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret) {
assert_return(n, -EINVAL);
assert_return(ret, -EINVAL);
if (!n->has_capabilities)
return -ENODATA;
*ret = n->system_capabilities;
return 0;
}
int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret) {
assert_return(n, -EINVAL);
assert_return(ret, -EINVAL);
if (!n->has_capabilities)
return -ENODATA;
*ret = n->enabled_capabilities;
return 0;
}
int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size) {
_cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
int r;
assert_return(ret, -EINVAL);
assert_return(raw || raw_size <= 0, -EINVAL);
n = lldp_neighbor_new(raw_size);
if (!n)
return -ENOMEM;
memcpy_safe(LLDP_NEIGHBOR_RAW(n), raw, raw_size);
r = lldp_neighbor_parse(n);
if (r < 0)
return r;
*ret = TAKE_PTR(n);
return r;
}
int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n) {
assert_return(n, -EINVAL);
assert(n->raw_size >= sizeof(struct ether_header));
n->rindex = sizeof(struct ether_header);
return n->rindex < n->raw_size;
}
int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n) {
size_t length;
assert_return(n, -EINVAL);
if (n->rindex == n->raw_size) /* EOF */
return -ESPIPE;
if (n->rindex + 2 > n->raw_size) /* Truncated message */
return -EBADMSG;
length = LLDP_NEIGHBOR_TLV_LENGTH(n);
if (n->rindex + 2 + length > n->raw_size)
return -EBADMSG;
n->rindex += 2 + length;
return n->rindex < n->raw_size;
}
int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type) {
assert_return(n, -EINVAL);
assert_return(type, -EINVAL);
if (n->rindex == n->raw_size) /* EOF */
return -ESPIPE;
if (n->rindex + 2 > n->raw_size)
return -EBADMSG;
*type = LLDP_NEIGHBOR_TLV_TYPE(n);
return 0;
}
int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type) {
uint8_t k;
int r;
assert_return(n, -EINVAL);
r = sd_lldp_neighbor_tlv_get_type(n, &k);
if (r < 0)
return r;
return type == k;
}
int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t *subtype) {
const uint8_t *d;
size_t length;
int r;
assert_return(n, -EINVAL);
assert_return(oui, -EINVAL);
assert_return(subtype, -EINVAL);
r = sd_lldp_neighbor_tlv_is_type(n, SD_LLDP_TYPE_PRIVATE);
if (r < 0)
return r;
if (r == 0)
return -ENXIO;
length = LLDP_NEIGHBOR_TLV_LENGTH(n);
if (length < 4)
return -EBADMSG;
if (n->rindex + 2 + length > n->raw_size)
return -EBADMSG;
d = LLDP_NEIGHBOR_TLV_DATA(n);
memcpy(oui, d, 3);
*subtype = d[3];
return 0;
}
int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t subtype) {
uint8_t k[3], st;
int r;
r = sd_lldp_neighbor_tlv_get_oui(n, k, &st);
if (r == -ENXIO)
return 0;
if (r < 0)
return r;
return memcmp(k, oui, 3) == 0 && st == subtype;
}
int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) {
size_t length;
assert_return(n, -EINVAL);
assert_return(ret, -EINVAL);
assert_return(size, -EINVAL);
/* Note that this returns the full TLV, including the TLV header */
if (n->rindex + 2 > n->raw_size)
return -EBADMSG;
length = LLDP_NEIGHBOR_TLV_LENGTH(n);
if (n->rindex + 2 + length > n->raw_size)
return -EBADMSG;
*ret = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex;
*size = length + 2;
return 0;
}
int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret) {
assert_return(n, -EINVAL);
assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP);
assert_return(clock_supported(clock), -EOPNOTSUPP);
assert_return(ret, -EINVAL);
if (!triple_timestamp_is_set(&n->timestamp))
return -ENODATA;
*ret = triple_timestamp_by_clock(&n->timestamp, clock);
return 0;
}

View File

@@ -1,92 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <inttypes.h>
#include <stdbool.h>
#include <sys/types.h>
#include "sd-lldp-rx.h"
#include "hash-funcs.h"
#include "lldp-rx-internal.h"
#include "time-util.h"
typedef struct LLDPNeighborID {
/* The spec calls this an "MSAP identifier" */
void *chassis_id;
size_t chassis_id_size;
void *port_id;
size_t port_id_size;
} LLDPNeighborID;
struct sd_lldp_neighbor {
/* Neighbor objects stay around as long as they are linked into an "sd_lldp_rx" object or n_ref > 0. */
sd_lldp_rx *lldp_rx;
unsigned n_ref;
triple_timestamp timestamp;
usec_t until;
unsigned prioq_idx;
struct ether_addr source_address;
struct ether_addr destination_address;
LLDPNeighborID id;
/* The raw packet size. The data is appended to the object, accessible via LLDP_NEIGHBOR_RAW() */
size_t raw_size;
/* The current read index for the iterative TLV interface */
size_t rindex;
/* And a couple of fields parsed out. */
bool has_ttl:1;
bool has_capabilities:1;
bool has_port_vlan_id:1;
uint16_t ttl;
uint16_t system_capabilities;
uint16_t enabled_capabilities;
char *port_description;
char *system_name;
char *system_description;
char *mud_url;
uint16_t port_vlan_id;
char *chassis_id_as_string;
char *port_id_as_string;
};
static inline void *LLDP_NEIGHBOR_RAW(const sd_lldp_neighbor *n) {
return (uint8_t*) n + ALIGN(sizeof(sd_lldp_neighbor));
}
static inline uint8_t LLDP_NEIGHBOR_TLV_TYPE(const sd_lldp_neighbor *n) {
return ((uint8_t*) LLDP_NEIGHBOR_RAW(n))[n->rindex] >> 1;
}
static inline size_t LLDP_NEIGHBOR_TLV_LENGTH(const sd_lldp_neighbor *n) {
uint8_t *p;
p = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex;
return p[1] + (((size_t) (p[0] & 1)) << 8);
}
static inline void* LLDP_NEIGHBOR_TLV_DATA(const sd_lldp_neighbor *n) {
return ((uint8_t*) LLDP_NEIGHBOR_RAW(n)) + n->rindex + 2;
}
extern const struct hash_ops lldp_neighbor_hash_ops;
int lldp_neighbor_id_compare_func(const LLDPNeighborID *x, const LLDPNeighborID *y);
int lldp_neighbor_prioq_compare_func(const void *a, const void *b);
sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n);
sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size);
int lldp_neighbor_parse(sd_lldp_neighbor *n);
void lldp_neighbor_start_ttl(sd_lldp_neighbor *n);
bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b);

View File

@@ -1,72 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "nm-sd-adapt-core.h"
#include <linux/filter.h>
#include <netinet/if_ether.h>
#include "fd-util.h"
#include "lldp-network.h"
#include "missing_network.h"
#include "socket-util.h"
int lldp_network_bind_raw_socket(int ifindex) {
static const struct sock_filter filter[] = {
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ethhdr, h_dest)), /* A <- 4 bytes of destination MAC */
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0180c200, 1, 0), /* A != 01:80:c2:00 */
BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */
BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ethhdr, h_dest) + 4), /* A <- remaining 2 bytes of destination MAC */
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0000, 3, 0), /* A != 00:00 */
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0003, 2, 0), /* A != 00:03 */
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x000e, 1, 0), /* A != 00:0e */
BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */
BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ethhdr, h_proto)), /* A <- protocol */
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_LLDP, 1, 0), /* A != ETHERTYPE_LLDP */
BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */
BPF_STMT(BPF_RET + BPF_K, UINT32_MAX), /* accept packet */
};
static const struct sock_fprog fprog = {
.len = ELEMENTSOF(filter),
.filter = (struct sock_filter*) filter,
};
struct packet_mreq mreq = {
.mr_ifindex = ifindex,
.mr_type = PACKET_MR_MULTICAST,
.mr_alen = ETH_ALEN,
.mr_address = { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x00 }
};
union sockaddr_union saddrll = {
.ll.sll_family = AF_PACKET,
.ll.sll_ifindex = ifindex,
};
_cleanup_close_ int fd = -1;
assert(ifindex > 0);
fd = socket(AF_PACKET, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK,
htobe16(ETHERTYPE_LLDP));
if (fd < 0)
return -errno;
if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) < 0)
return -errno;
/* customer bridge */
if (setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
return -errno;
/* non TPMR bridge */
mreq.mr_address[ETH_ALEN - 1] = 0x03;
if (setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
return -errno;
/* nearest bridge */
mreq.mr_address[ETH_ALEN - 1] = 0x0E;
if (setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
return -errno;
if (bind(fd, &saddrll.sa, sizeof(saddrll.ll)) < 0)
return -errno;
return TAKE_FD(fd);
}

View File

@@ -1,6 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "sd-event.h"
int lldp_network_bind_raw_socket(int ifindex);

View File

@@ -1,48 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "sd-event.h"
#include "sd-lldp-rx.h"
#include "hashmap.h"
#include "network-common.h"
#include "prioq.h"
struct sd_lldp_rx {
unsigned n_ref;
int ifindex;
char *ifname;
int fd;
sd_event *event;
int64_t event_priority;
sd_event_source *io_event_source;
sd_event_source *timer_event_source;
Prioq *neighbor_by_expiry;
Hashmap *neighbor_by_id;
uint64_t neighbors_max;
sd_lldp_rx_callback_t callback;
void *userdata;
uint16_t capability_mask;
struct ether_addr filter_address;
};
const char* lldp_rx_event_to_string(sd_lldp_rx_event_t e) _const_;
sd_lldp_rx_event_t lldp_rx_event_from_string(const char *s) _pure_;
#define log_lldp_rx_errno(lldp_rx, error, fmt, ...) \
log_interface_prefix_full_errno( \
"LLDP Rx: ", \
sd_lldp_rx, lldp_rx, \
error, fmt, ##__VA_ARGS__)
#define log_lldp_rx(lldp_rx, fmt, ...) \
log_interface_prefix_full_errno_zerook( \
"LLDP Rx: ", \
sd_lldp_rx, lldp_rx, \
0, fmt, ##__VA_ARGS__)

View File

@@ -1,527 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "nm-sd-adapt-core.h"
#include <arpa/inet.h>
#include <linux/sockios.h>
#include <sys/ioctl.h>
#include "sd-lldp-rx.h"
#include "alloc-util.h"
#include "ether-addr-util.h"
#include "event-util.h"
#include "fd-util.h"
#include "lldp-neighbor.h"
#include "lldp-network.h"
#include "lldp-rx-internal.h"
#include "memory-util.h"
#include "network-common.h"
#include "socket-util.h"
#include "sort-util.h"
#include "string-table.h"
#define LLDP_DEFAULT_NEIGHBORS_MAX 128U
static const char * const lldp_rx_event_table[_SD_LLDP_RX_EVENT_MAX] = {
[SD_LLDP_RX_EVENT_ADDED] = "added",
[SD_LLDP_RX_EVENT_REMOVED] = "removed",
[SD_LLDP_RX_EVENT_UPDATED] = "updated",
[SD_LLDP_RX_EVENT_REFRESHED] = "refreshed",
};
DEFINE_STRING_TABLE_LOOKUP(lldp_rx_event, sd_lldp_rx_event_t);
static void lldp_rx_flush_neighbors(sd_lldp_rx *lldp_rx) {
assert(lldp_rx);
hashmap_clear(lldp_rx->neighbor_by_id);
}
static void lldp_rx_callback(sd_lldp_rx *lldp_rx, sd_lldp_rx_event_t event, sd_lldp_neighbor *n) {
assert(lldp_rx);
assert(event >= 0 && event < _SD_LLDP_RX_EVENT_MAX);
if (!lldp_rx->callback)
return (void) log_lldp_rx(lldp_rx, "Received '%s' event.", lldp_rx_event_to_string(event));
log_lldp_rx(lldp_rx, "Invoking callback for '%s' event.", lldp_rx_event_to_string(event));
lldp_rx->callback(lldp_rx, event, n, lldp_rx->userdata);
}
static int lldp_rx_make_space(sd_lldp_rx *lldp_rx, size_t extra) {
usec_t t = USEC_INFINITY;
bool changed = false;
assert(lldp_rx);
/* Remove all entries that are past their TTL, and more until at least the specified number of extra entries
* are free. */
for (;;) {
_cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
n = prioq_peek(lldp_rx->neighbor_by_expiry);
if (!n)
break;
sd_lldp_neighbor_ref(n);
if (hashmap_size(lldp_rx->neighbor_by_id) > LESS_BY(lldp_rx->neighbors_max, extra))
goto remove_one;
if (t == USEC_INFINITY)
t = now(CLOCK_BOOTTIME);
if (n->until > t)
break;
remove_one:
lldp_neighbor_unlink(n);
lldp_rx_callback(lldp_rx, SD_LLDP_RX_EVENT_REMOVED, n);
changed = true;
}
return changed;
}
static bool lldp_rx_keep_neighbor(sd_lldp_rx *lldp_rx, sd_lldp_neighbor *n) {
assert(lldp_rx);
assert(n);
/* Don't keep data with a zero TTL */
if (n->ttl <= 0)
return false;
/* Filter out data from the filter address */
if (!ether_addr_is_null(&lldp_rx->filter_address) &&
ether_addr_equal(&lldp_rx->filter_address, &n->source_address))
return false;
/* Only add if the neighbor has a capability we are interested in. Note that we also store all neighbors with
* no caps field set. */
if (n->has_capabilities &&
(n->enabled_capabilities & lldp_rx->capability_mask) == 0)
return false;
/* Keep everything else */
return true;
}
static int lldp_rx_start_timer(sd_lldp_rx *lldp_rx, sd_lldp_neighbor *neighbor);
static int lldp_rx_add_neighbor(sd_lldp_rx *lldp_rx, sd_lldp_neighbor *n) {
_cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *old = NULL;
bool keep;
int r;
assert(lldp_rx);
assert(n);
assert(!n->lldp_rx);
keep = lldp_rx_keep_neighbor(lldp_rx, n);
/* First retrieve the old entry for this MSAP */
old = hashmap_get(lldp_rx->neighbor_by_id, &n->id);
if (old) {
sd_lldp_neighbor_ref(old);
if (!keep) {
lldp_neighbor_unlink(old);
lldp_rx_callback(lldp_rx, SD_LLDP_RX_EVENT_REMOVED, old);
return 0;
}
if (lldp_neighbor_equal(n, old)) {
/* Is this equal, then restart the TTL counter, but don't do anything else. */
old->timestamp = n->timestamp;
lldp_rx_start_timer(lldp_rx, old);
lldp_rx_callback(lldp_rx, SD_LLDP_RX_EVENT_REFRESHED, old);
return 0;
}
/* Data changed, remove the old entry, and add a new one */
lldp_neighbor_unlink(old);
} else if (!keep)
return 0;
/* Then, make room for at least one new neighbor */
lldp_rx_make_space(lldp_rx, 1);
r = hashmap_ensure_put(&lldp_rx->neighbor_by_id, &lldp_neighbor_hash_ops, &n->id, n);
if (r < 0)
goto finish;
r = prioq_ensure_put(&lldp_rx->neighbor_by_expiry, lldp_neighbor_prioq_compare_func, n, &n->prioq_idx);
if (r < 0) {
assert_se(hashmap_remove(lldp_rx->neighbor_by_id, &n->id) == n);
goto finish;
}
n->lldp_rx = lldp_rx;
lldp_rx_start_timer(lldp_rx, n);
lldp_rx_callback(lldp_rx, old ? SD_LLDP_RX_EVENT_UPDATED : SD_LLDP_RX_EVENT_ADDED, n);
return 1;
finish:
if (old)
lldp_rx_callback(lldp_rx, SD_LLDP_RX_EVENT_REMOVED, old);
return r;
}
static int lldp_rx_handle_datagram(sd_lldp_rx *lldp_rx, sd_lldp_neighbor *n) {
int r;
assert(lldp_rx);
assert(n);
r = lldp_neighbor_parse(n);
if (r < 0)
return r;
r = lldp_rx_add_neighbor(lldp_rx, n);
if (r < 0)
return log_lldp_rx_errno(lldp_rx, r, "Failed to add datagram. Ignoring.");
log_lldp_rx(lldp_rx, "Successfully processed LLDP datagram.");
return 0;
}
static int lldp_rx_receive_datagram(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
_cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
ssize_t space, length;
sd_lldp_rx *lldp_rx = ASSERT_PTR(userdata);
struct timespec ts;
assert(fd >= 0);
space = next_datagram_size_fd(fd);
if (space < 0) {
if (ERRNO_IS_TRANSIENT(space) || ERRNO_IS_DISCONNECT(space))
return 0;
log_lldp_rx_errno(lldp_rx, space, "Failed to determine datagram size to read, ignoring: %m");
return 0;
}
n = lldp_neighbor_new(space);
if (!n) {
log_oom_debug();
return 0;
}
length = recv(fd, LLDP_NEIGHBOR_RAW(n), n->raw_size, MSG_DONTWAIT);
if (length < 0) {
if (ERRNO_IS_TRANSIENT(errno) || ERRNO_IS_DISCONNECT(errno))
return 0;
log_lldp_rx_errno(lldp_rx, errno, "Failed to read LLDP datagram, ignoring: %m");
return 0;
}
if ((size_t) length != n->raw_size) {
log_lldp_rx(lldp_rx, "Packet size mismatch, ignoring");
return 0;
}
/* Try to get the timestamp of this packet if it is known */
if (ioctl(fd, SIOCGSTAMPNS, &ts) >= 0)
triple_timestamp_from_realtime(&n->timestamp, timespec_load(&ts));
else
triple_timestamp_get(&n->timestamp);
(void) lldp_rx_handle_datagram(lldp_rx, n);
return 0;
}
static void lldp_rx_reset(sd_lldp_rx *lldp_rx) {
assert(lldp_rx);
(void) event_source_disable(lldp_rx->timer_event_source);
lldp_rx->io_event_source = sd_event_source_disable_unref(lldp_rx->io_event_source);
lldp_rx->fd = safe_close(lldp_rx->fd);
}
int sd_lldp_rx_is_running(sd_lldp_rx *lldp_rx) {
if (!lldp_rx)
return false;
return lldp_rx->fd >= 0;
}
int sd_lldp_rx_start(sd_lldp_rx *lldp_rx) {
int r;
assert_return(lldp_rx, -EINVAL);
assert_return(lldp_rx->event, -EINVAL);
assert_return(lldp_rx->ifindex > 0, -EINVAL);
if (sd_lldp_rx_is_running(lldp_rx))
return 0;
assert(!lldp_rx->io_event_source);
lldp_rx->fd = lldp_network_bind_raw_socket(lldp_rx->ifindex);
if (lldp_rx->fd < 0)
return lldp_rx->fd;
r = sd_event_add_io(lldp_rx->event, &lldp_rx->io_event_source, lldp_rx->fd, EPOLLIN, lldp_rx_receive_datagram, lldp_rx);
if (r < 0)
goto fail;
r = sd_event_source_set_priority(lldp_rx->io_event_source, lldp_rx->event_priority);
if (r < 0)
goto fail;
(void) sd_event_source_set_description(lldp_rx->io_event_source, "lldp-rx-io");
log_lldp_rx(lldp_rx, "Started LLDP client");
return 1;
fail:
lldp_rx_reset(lldp_rx);
return r;
}
int sd_lldp_rx_stop(sd_lldp_rx *lldp_rx) {
if (!sd_lldp_rx_is_running(lldp_rx))
return 0;
log_lldp_rx(lldp_rx, "Stopping LLDP client");
lldp_rx_reset(lldp_rx);
lldp_rx_flush_neighbors(lldp_rx);
return 1;
}
int sd_lldp_rx_attach_event(sd_lldp_rx *lldp_rx, sd_event *event, int64_t priority) {
int r;
assert_return(lldp_rx, -EINVAL);
assert_return(!sd_lldp_rx_is_running(lldp_rx), -EBUSY);
assert_return(!lldp_rx->event, -EBUSY);
if (event)
lldp_rx->event = sd_event_ref(event);
else {
r = sd_event_default(&lldp_rx->event);
if (r < 0)
return r;
}
lldp_rx->event_priority = priority;
return 0;
}
int sd_lldp_rx_detach_event(sd_lldp_rx *lldp_rx) {
assert_return(lldp_rx, -EINVAL);
assert_return(!sd_lldp_rx_is_running(lldp_rx), -EBUSY);
lldp_rx->io_event_source = sd_event_source_disable_unref(lldp_rx->io_event_source);
lldp_rx->timer_event_source = sd_event_source_disable_unref(lldp_rx->timer_event_source);
lldp_rx->event = sd_event_unref(lldp_rx->event);
return 0;
}
sd_event* sd_lldp_rx_get_event(sd_lldp_rx *lldp_rx) {
assert_return(lldp_rx, NULL);
return lldp_rx->event;
}
int sd_lldp_rx_set_callback(sd_lldp_rx *lldp_rx, sd_lldp_rx_callback_t cb, void *userdata) {
assert_return(lldp_rx, -EINVAL);
lldp_rx->callback = cb;
lldp_rx->userdata = userdata;
return 0;
}
int sd_lldp_rx_set_ifindex(sd_lldp_rx *lldp_rx, int ifindex) {
assert_return(lldp_rx, -EINVAL);
assert_return(ifindex > 0, -EINVAL);
assert_return(!sd_lldp_rx_is_running(lldp_rx), -EBUSY);
lldp_rx->ifindex = ifindex;
return 0;
}
int sd_lldp_rx_set_ifname(sd_lldp_rx *lldp_rx, const char *ifname) {
assert_return(lldp_rx, -EINVAL);
assert_return(ifname, -EINVAL);
if (!ifname_valid_full(ifname, IFNAME_VALID_ALTERNATIVE))
return -EINVAL;
return free_and_strdup(&lldp_rx->ifname, ifname);
}
int sd_lldp_rx_get_ifname(sd_lldp_rx *lldp_rx, const char **ret) {
int r;
assert_return(lldp_rx, -EINVAL);
r = get_ifname(lldp_rx->ifindex, &lldp_rx->ifname);
if (r < 0)
return r;
if (ret)
*ret = lldp_rx->ifname;
return 0;
}
static sd_lldp_rx *lldp_rx_free(sd_lldp_rx *lldp_rx) {
if (!lldp_rx)
return NULL;
lldp_rx_reset(lldp_rx);
sd_lldp_rx_detach_event(lldp_rx);
lldp_rx_flush_neighbors(lldp_rx);
hashmap_free(lldp_rx->neighbor_by_id);
prioq_free(lldp_rx->neighbor_by_expiry);
free(lldp_rx->ifname);
return mfree(lldp_rx);
}
DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_lldp_rx, sd_lldp_rx, lldp_rx_free);
int sd_lldp_rx_new(sd_lldp_rx **ret) {
_cleanup_(sd_lldp_rx_unrefp) sd_lldp_rx *lldp_rx = NULL;
assert_return(ret, -EINVAL);
lldp_rx = new(sd_lldp_rx, 1);
if (!lldp_rx)
return -ENOMEM;
*lldp_rx = (sd_lldp_rx) {
.n_ref = 1,
.fd = -1,
.neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX,
.capability_mask = UINT16_MAX,
};
*ret = TAKE_PTR(lldp_rx);
return 0;
}
static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) {
sd_lldp_rx *lldp_rx = userdata;
int r;
r = lldp_rx_make_space(lldp_rx, 0);
if (r < 0) {
log_lldp_rx_errno(lldp_rx, r, "Failed to make space, ignoring: %m");
return 0;
}
r = lldp_rx_start_timer(lldp_rx, NULL);
if (r < 0) {
log_lldp_rx_errno(lldp_rx, r, "Failed to restart timer, ignoring: %m");
return 0;
}
return 0;
}
static int lldp_rx_start_timer(sd_lldp_rx *lldp_rx, sd_lldp_neighbor *neighbor) {
sd_lldp_neighbor *n;
assert(lldp_rx);
assert(lldp_rx->event);
if (neighbor)
lldp_neighbor_start_ttl(neighbor);
n = prioq_peek(lldp_rx->neighbor_by_expiry);
if (!n)
return event_source_disable(lldp_rx->timer_event_source);
return event_reset_time(lldp_rx->event, &lldp_rx->timer_event_source,
CLOCK_BOOTTIME,
n->until, 0,
on_timer_event, lldp_rx,
lldp_rx->event_priority, "lldp-rx-timer", true);
}
static inline int neighbor_compare_func(sd_lldp_neighbor * const *a, sd_lldp_neighbor * const *b) {
assert(a);
assert(b);
assert(*a);
assert(*b);
return lldp_neighbor_id_compare_func(&(*a)->id, &(*b)->id);
}
int sd_lldp_rx_get_neighbors(sd_lldp_rx *lldp_rx, sd_lldp_neighbor ***ret) {
_cleanup_free_ sd_lldp_neighbor **l = NULL;
sd_lldp_neighbor *n;
int k = 0;
assert_return(lldp_rx, -EINVAL);
assert_return(ret, -EINVAL);
if (hashmap_isempty(lldp_rx->neighbor_by_id)) { /* Special shortcut */
*ret = NULL;
return 0;
}
l = new0(sd_lldp_neighbor*, hashmap_size(lldp_rx->neighbor_by_id));
if (!l)
return -ENOMEM;
HASHMAP_FOREACH(n, lldp_rx->neighbor_by_id)
l[k++] = sd_lldp_neighbor_ref(n);
assert((size_t) k == hashmap_size(lldp_rx->neighbor_by_id));
/* Return things in a stable order */
typesafe_qsort(l, k, neighbor_compare_func);
*ret = TAKE_PTR(l);
return k;
}
int sd_lldp_rx_set_neighbors_max(sd_lldp_rx *lldp_rx, uint64_t m) {
assert_return(lldp_rx, -EINVAL);
assert_return(m > 0, -EINVAL);
lldp_rx->neighbors_max = m;
lldp_rx_make_space(lldp_rx, 0);
return 0;
}
int sd_lldp_rx_match_capabilities(sd_lldp_rx *lldp_rx, uint16_t mask) {
assert_return(lldp_rx, -EINVAL);
assert_return(mask != 0, -EINVAL);
lldp_rx->capability_mask = mask;
return 0;
}
int sd_lldp_rx_set_filter_address(sd_lldp_rx *lldp_rx, const struct ether_addr *addr) {
assert_return(lldp_rx, -EINVAL);
/* In order to deal nicely with bridges that send back our own packets, allow one address to be filtered, so
* that our own can be filtered out here. */
if (addr)
lldp_rx->filter_address = *addr;
else
zero(lldp_rx->filter_address);
return 0;
}

View File

@@ -1,109 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef foosdlldprxhfoo
#define foosdlldprxhfoo
/***
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <https://www.gnu.org/licenses/>.
***/
#include <errno.h>
#include <inttypes.h>
#include <net/ethernet.h>
#include <sys/types.h>
#include "sd-event.h"
#include "sd-lldp.h"
#include "_sd-common.h"
_SD_BEGIN_DECLARATIONS;
typedef struct sd_lldp_rx sd_lldp_rx;
typedef struct sd_lldp_neighbor sd_lldp_neighbor;
__extension__ typedef enum sd_lldp_rx_event_t {
SD_LLDP_RX_EVENT_ADDED,
SD_LLDP_RX_EVENT_REMOVED,
SD_LLDP_RX_EVENT_UPDATED,
SD_LLDP_RX_EVENT_REFRESHED,
_SD_LLDP_RX_EVENT_MAX,
_SD_LLDP_RX_EVENT_INVALID = -EINVAL,
_SD_ENUM_FORCE_S64(LLDP_RX_EVENT)
} sd_lldp_rx_event_t;
typedef void (*sd_lldp_rx_callback_t)(sd_lldp_rx *lldp_rx, sd_lldp_rx_event_t event, sd_lldp_neighbor *n, void *userdata);
int sd_lldp_rx_new(sd_lldp_rx **ret);
sd_lldp_rx *sd_lldp_rx_ref(sd_lldp_rx *lldp_rx);
sd_lldp_rx *sd_lldp_rx_unref(sd_lldp_rx *lldp_rx);
int sd_lldp_rx_start(sd_lldp_rx *lldp_rx);
int sd_lldp_rx_stop(sd_lldp_rx *lldp_rx);
int sd_lldp_rx_is_running(sd_lldp_rx *lldp_rx);
int sd_lldp_rx_attach_event(sd_lldp_rx *lldp_rx, sd_event *event, int64_t priority);
int sd_lldp_rx_detach_event(sd_lldp_rx *lldp_rx);
sd_event *sd_lldp_rx_get_event(sd_lldp_rx *lldp_rx);
int sd_lldp_rx_set_callback(sd_lldp_rx *lldp_rx, sd_lldp_rx_callback_t cb, void *userdata);
int sd_lldp_rx_set_ifindex(sd_lldp_rx *lldp_rx, int ifindex);
int sd_lldp_rx_set_ifname(sd_lldp_rx *lldp_rx, const char *ifname);
int sd_lldp_rx_get_ifname(sd_lldp_rx *lldp_rx, const char **ret);
/* Controls how much and what to store in the neighbors database */
int sd_lldp_rx_set_neighbors_max(sd_lldp_rx *lldp_rx, uint64_t n);
int sd_lldp_rx_match_capabilities(sd_lldp_rx *lldp_rx, uint16_t mask);
int sd_lldp_rx_set_filter_address(sd_lldp_rx *lldp_rx, const struct ether_addr *address);
int sd_lldp_rx_get_neighbors(sd_lldp_rx *lldp_rx, sd_lldp_neighbor ***neighbors);
int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size);
sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n);
sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n);
/* Access to LLDP frame metadata */
int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address);
int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address);
int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret);
int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size);
/* High-level, direct, parsed out field access. These fields exist at most once, hence may be queried directly. */
int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size);
int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret);
int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size);
int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret);
int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec);
int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret);
int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret);
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);
/* 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. */
int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n);
int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n);
int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type);
int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type);
int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t *subtype);
int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[_SD_ARRAY_STATIC 3], uint8_t subtype);
int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size);
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp_rx, sd_lldp_rx_unref);
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp_neighbor, sd_lldp_neighbor_unref);
_SD_END_DECLARATIONS;
#endif

View File

@@ -1,123 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef foosdlldphfoo
#define foosdlldphfoo
/***
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <https://www.gnu.org/licenses/>.
***/
#include <inttypes.h>
#include "_sd-common.h"
_SD_BEGIN_DECLARATIONS;
/* IEEE 802.1AB-2009 Clause 8: TLV Types */
enum {
SD_LLDP_TYPE_END = 0,
SD_LLDP_TYPE_CHASSIS_ID = 1,
SD_LLDP_TYPE_PORT_ID = 2,
SD_LLDP_TYPE_TTL = 3,
SD_LLDP_TYPE_PORT_DESCRIPTION = 4,
SD_LLDP_TYPE_SYSTEM_NAME = 5,
SD_LLDP_TYPE_SYSTEM_DESCRIPTION = 6,
SD_LLDP_TYPE_SYSTEM_CAPABILITIES = 7,
SD_LLDP_TYPE_MGMT_ADDRESS = 8,
SD_LLDP_TYPE_PRIVATE = 127
};
/* IEEE 802.1AB-2009 Clause 8.5.2: Chassis subtypes */
enum {
SD_LLDP_CHASSIS_SUBTYPE_RESERVED = 0,
SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT = 1,
SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS = 2,
SD_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT = 3,
SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS = 4,
SD_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS = 5,
SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME = 6,
SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED = 7
};
/* IEEE 802.1AB-2009 Clause 8.5.3: Port subtype */
enum {
SD_LLDP_PORT_SUBTYPE_RESERVED = 0,
SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS = 1,
SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT = 2,
SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS = 3,
SD_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS = 4,
SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME = 5,
SD_LLDP_PORT_SUBTYPE_AGENT_CIRCUIT_ID = 6,
SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED = 7
};
/* IEEE 802.1AB-2009 Clause 8.5.8: System capabilities */
enum {
SD_LLDP_SYSTEM_CAPABILITIES_OTHER = 1 << 0,
SD_LLDP_SYSTEM_CAPABILITIES_REPEATER = 1 << 1,
SD_LLDP_SYSTEM_CAPABILITIES_BRIDGE = 1 << 2,
SD_LLDP_SYSTEM_CAPABILITIES_WLAN_AP = 1 << 3,
SD_LLDP_SYSTEM_CAPABILITIES_ROUTER = 1 << 4,
SD_LLDP_SYSTEM_CAPABILITIES_PHONE = 1 << 5,
SD_LLDP_SYSTEM_CAPABILITIES_DOCSIS = 1 << 6,
SD_LLDP_SYSTEM_CAPABILITIES_STATION = 1 << 7,
SD_LLDP_SYSTEM_CAPABILITIES_CVLAN = 1 << 8,
SD_LLDP_SYSTEM_CAPABILITIES_SVLAN = 1 << 9,
SD_LLDP_SYSTEM_CAPABILITIES_TPMR = 1 << 10
};
#define SD_LLDP_SYSTEM_CAPABILITIES_ALL UINT16_MAX
#define SD_LLDP_SYSTEM_CAPABILITIES_ALL_ROUTERS \
((uint16_t) \
(SD_LLDP_SYSTEM_CAPABILITIES_REPEATER | \
SD_LLDP_SYSTEM_CAPABILITIES_BRIDGE | \
SD_LLDP_SYSTEM_CAPABILITIES_WLAN_AP | \
SD_LLDP_SYSTEM_CAPABILITIES_ROUTER | \
SD_LLDP_SYSTEM_CAPABILITIES_DOCSIS | \
SD_LLDP_SYSTEM_CAPABILITIES_CVLAN | \
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_3 (const uint8_t[]) { 0x00, 0x12, 0x0f }
#define _SD_LLDP_OUI_IANA 0x00, 0x00, 0x5E
#define SD_LLDP_OUI_IANA (const uint8_t[]) { _SD_LLDP_OUI_IANA }
#define SD_LLDP_OUI_IANA_SUBTYPE_MUD 0x01
#define SD_LLDP_OUI_IANA_MUD \
(const uint8_t[]) { _SD_LLDP_OUI_IANA, SD_LLDP_OUI_IANA_SUBTYPE_MUD }
/* IEEE 802.1AB-2009 Annex E */
enum {
SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID = 1,
SD_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID = 2,
SD_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME = 3,
SD_LLDP_OUI_802_1_SUBTYPE_PROTOCOL_IDENTITY = 4,
SD_LLDP_OUI_802_1_SUBTYPE_VID_USAGE_DIGEST = 5,
SD_LLDP_OUI_802_1_SUBTYPE_MANAGEMENT_VID = 6,
SD_LLDP_OUI_802_1_SUBTYPE_LINK_AGGREGATION = 7
};
/* IEEE 802.1AB-2009 Annex F */
enum {
SD_LLDP_OUI_802_3_SUBTYPE_MAC_PHY_CONFIG_STATUS = 1,
SD_LLDP_OUI_802_3_SUBTYPE_POWER_VIA_MDI = 2,
SD_LLDP_OUI_802_3_SUBTYPE_LINK_AGGREGATION = 3,
SD_LLDP_OUI_802_3_SUBTYPE_MAXIMUM_FRAME_SIZE = 4
};
_SD_END_DECLARATIONS;
#endif

View File

@@ -78,6 +78,7 @@ subdir('libnm-systemd-core')
subdir('libnm-udev-aux')
subdir('libnm-base')
subdir('libnm-platform')
subdir('libnm-lldp')
subdir('libnm-crypto')
subdir('libnm-core-public')
subdir('libnm-core-intern')