Commit 07a9364d authored by Beniamino Galvani's avatar Beniamino Galvani

device: export list of LLDP neighbors through D-Bus

This adds a LldpNeighbors property to the Device D-Bus interface
carrying information about devices discovered through LLDP. The
property is an array of hashes and each hash describes the values of
LLDP TLVs for a specific neighbor.
parent c364ef0b
......@@ -155,6 +155,12 @@
subject to limitations, for example set by service providers.
</tp:docstring>
</property>
<property name="LldpNeighbors" type="aa{sv}" access="read">
<tp:docstring>
Array of LLDP neighbors; each element is a dictionary mapping
LLDP TLV names to variant boxed values.
</tp:docstring>
</property>
<method name="Disconnect">
<tp:docstring>
......
......@@ -647,4 +647,23 @@ typedef enum /*< flags >*/ {
#undef NM_AVAILABLE_IN_1_2
#endif
#define NM_LLDP_ATTR_DESTINATION "destination"
#define NM_LLDP_ATTR_CHASSIS_ID_TYPE "chassis-id-type"
#define NM_LLDP_ATTR_CHASSIS_ID "chassis-id"
#define NM_LLDP_ATTR_PORT_ID_TYPE "port-id-type"
#define NM_LLDP_ATTR_PORT_ID "port-id"
#define NM_LLDP_ATTR_PORT_DESCRIPTION "port-description"
#define NM_LLDP_ATTR_SYSTEM_NAME "system-name"
#define NM_LLDP_ATTR_SYSTEM_DESCRIPTION "system-description"
#define NM_LLDP_ATTR_SYSTEM_CAPABILITIES "system-capabilities"
#define NM_LLDP_ATTR_IEEE_802_1_PVID "ieee-802-1-pvid"
#define NM_LLDP_ATTR_IEEE_802_1_PPVID "ieee-802-1-ppvid"
#define NM_LLDP_ATTR_IEEE_802_1_PPVID_FLAGS "ieee-802-1-ppvid-flags"
#define NM_LLDP_ATTR_IEEE_802_1_VID "ieee-802-1-pvid"
#define NM_LLDP_ATTR_IEEE_802_1_VLAN_NAME "ieee-802-1-vlan-name"
#define NM_LLDP_DEST_NEAREST_BRIDGE "nearest-bridge"
#define NM_LLDP_DEST_NEAREST_NON_TPMR_BRIDGE "nearest-non-tpmr-bridge"
#define NM_LLDP_DEST_NEAREST_CUSTOMER_BRIDGE "nearest-customer-bridge"
#endif /* __NM_DBUS_INTERFACE_H__ */
......@@ -221,6 +221,8 @@ libNetworkManager_la_SOURCES = \
$(nm_dhcp_client_headers) \
devices/nm-device.c \
devices/nm-device.h \
devices/nm-lldp-listener.c \
devices/nm-lldp-listener.h \
devices/nm-device-ethernet-utils.c \
devices/nm-device-ethernet-utils.h \
devices/nm-device-factory.c \
......
......@@ -60,6 +60,7 @@
#include "nm-core-internal.h"
#include "nm-default-route-manager.h"
#include "nm-route-manager.h"
#include "nm-lldp-listener.h"
#include "sd-ipv4ll.h"
#include "nm-audit-manager.h"
......@@ -126,6 +127,7 @@ enum {
PROP_HW_ADDRESS,
PROP_HAS_PENDING_ACTION,
PROP_METERED,
PROP_LLDP_NEIGHBORS,
LAST_PROP
};
......@@ -351,6 +353,7 @@ typedef struct {
NMMetered metered;
NMConnectionProvider *con_provider;
NMLldpListener *lldp_listener;
} NMDevicePrivate;
static gboolean nm_device_set_ip4_config (NMDevice *self,
......@@ -1182,6 +1185,7 @@ static void
update_dynamic_ip_setup (NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
GError *error;
g_hash_table_remove_all (priv->ip6_saved_properties);
......@@ -1207,6 +1211,16 @@ update_dynamic_ip_setup (NMDevice *self)
if (priv->dnsmasq_manager) {
/* FIXME: todo */
}
if (priv->lldp_listener && nm_lldp_listener_is_running (priv->lldp_listener)) {
nm_lldp_listener_stop (priv->lldp_listener);
if (!nm_lldp_listener_start (priv->lldp_listener, nm_device_get_ifindex (self),
nm_device_get_iface (self), &error)) {
_LOGD (LOGD_DEVICE, "LLDP listener %p could not be restarted: %s",
priv->lldp_listener, error->message);
g_clear_error (&error);
}
}
}
static void
......@@ -2969,6 +2983,45 @@ master_ready_cb (NMActiveConnection *active,
nm_device_activate_schedule_stage2_device_config (self);
}
static void
lldp_neighbors_changed (NMLldpListener *lldp_listener, GParamSpec *pspec,
gpointer user_data)
{
NMDevice *self = NM_DEVICE (user_data);
g_object_notify (G_OBJECT (self), NM_DEVICE_LLDP_NEIGHBORS);
}
static gboolean
lldp_rx_enabled (NMDevice *self)
{
NMConnection *connection;
NMSettingConnection *s_con;
NMSettingConnectionLldp lldp = NM_SETTING_CONNECTION_LLDP_DEFAULT;
connection = nm_device_get_applied_connection (self);
g_return_val_if_fail (connection, FALSE);
s_con = nm_connection_get_setting_connection (connection);
g_return_val_if_fail (s_con, FALSE);
lldp = nm_setting_connection_get_lldp (s_con);
if (lldp == NM_SETTING_CONNECTION_LLDP_DEFAULT) {
gs_free char *value = NULL;
value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
"connection.lldp",
self);
lldp = _nm_utils_ascii_str_to_int64 (value, 10,
NM_SETTING_CONNECTION_LLDP_DEFAULT,
NM_SETTING_CONNECTION_LLDP_ENABLE_RX,
NM_SETTING_CONNECTION_LLDP_DEFAULT);
if (lldp == NM_SETTING_CONNECTION_LLDP_DEFAULT)
lldp = NM_SETTING_CONNECTION_LLDP_DISABLE;
}
return lldp == NM_SETTING_CONNECTION_LLDP_ENABLE_RX;
}
static NMActStageReturn
act_stage1_prepare (NMDevice *self, NMDeviceStateReason *reason)
{
......@@ -3089,6 +3142,28 @@ activate_stage2_device_config (NMDevice *self)
nm_device_queue_recheck_assume (info->slave);
}
if (lldp_rx_enabled (self)) {
gs_free_error GError *error = NULL;
if (priv->lldp_listener)
nm_lldp_listener_stop (priv->lldp_listener);
else {
priv->lldp_listener = nm_lldp_listener_new ();
g_signal_connect (priv->lldp_listener,
"notify::" NM_LLDP_LISTENER_NEIGHBORS,
G_CALLBACK (lldp_neighbors_changed),
self);
}
if (nm_lldp_listener_start (priv->lldp_listener, nm_device_get_ifindex (self),
nm_device_get_iface (self), &error))
_LOGD (LOGD_DEVICE, "LLDP listener %p started", priv->lldp_listener);
else {
_LOGD (LOGD_DEVICE, "LLDP listener %p could not be started: %s",
priv->lldp_listener, error->message);
}
}
nm_device_activate_schedule_stage3_ip_config_start (self);
}
......@@ -8526,6 +8601,9 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
nm_platform_address_flush (NM_PLATFORM_GET, ifindex);
}
if (priv->lldp_listener)
nm_lldp_listener_stop (priv->lldp_listener);
nm_device_update_metered (self);
_cleanup_generic_post (self, cleanup_type);
}
......@@ -9556,6 +9634,14 @@ dispose (GObject *object)
nm_clear_g_source (&priv->device_link_changed_id);
nm_clear_g_source (&priv->device_ip_link_changed_id);
if (priv->lldp_listener) {
g_signal_handlers_disconnect_by_func (priv->lldp_listener,
G_CALLBACK (lldp_neighbors_changed),
self);
nm_lldp_listener_stop (priv->lldp_listener);
g_clear_object (&priv->lldp_listener);
}
G_OBJECT_CLASS (nm_device_parent_class)->dispose (object);
}
......@@ -9707,6 +9793,7 @@ get_property (GObject *object, guint prop_id,
GPtrArray *array;
GHashTableIter iter;
NMConnection *connection;
GVariantBuilder array_builder;
switch (prop_id) {
case PROP_UDI:
......@@ -9814,6 +9901,14 @@ get_property (GObject *object, guint prop_id,
case PROP_METERED:
g_value_set_uint (value, priv->metered);
break;
case PROP_LLDP_NEIGHBORS:
if (priv->lldp_listener)
g_value_set_variant (value, nm_lldp_listener_get_neighbors (priv->lldp_listener));
else {
g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("aa{sv}"));
g_value_take_variant (value, g_variant_builder_end (&array_builder));
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
......@@ -10096,6 +10191,14 @@ nm_device_class_init (NMDeviceClass *klass)
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property
(object_class, PROP_LLDP_NEIGHBORS,
g_param_spec_variant (NM_DEVICE_LLDP_NEIGHBORS, "", "",
G_VARIANT_TYPE ("aa{sv}"),
NULL,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/* Signals */
signals[STATE_CHANGED] =
g_signal_new ("state-changed",
......
......@@ -58,6 +58,7 @@
#define NM_DEVICE_MTU "mtu"
#define NM_DEVICE_HW_ADDRESS "hw-address"
#define NM_DEVICE_METERED "metered"
#define NM_DEVICE_LLDP_NEIGHBORS "lldp-neighbors"
#define NM_DEVICE_TYPE_DESC "type-desc" /* Internal only */
#define NM_DEVICE_RFKILL_TYPE "rfkill-type" /* Internal only */
......
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* NetworkManager -- Network link manager
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright (C) 2015 Red Hat, Inc.
*/
#include "config.h"
#include <net/ethernet.h>
#include "sd-lldp.h"
#include "lldp.h"
#include "nm-lldp-listener.h"
#include "nm-platform.h"
#include "nm-utils.h"
#define MAX_NEIGHBORS 4096
#define MIN_UPDATE_INTERVAL 2
typedef struct {
char *iface;
int ifindex;
sd_lldp *lldp_handle;
GHashTable *lldp_neighbors;
guint timer;
guint num_pending_events;
GVariant *variant;
} NMLldpListenerPrivate;
enum {
PROP_0,
PROP_NEIGHBORS,
LAST_PROP
};
G_DEFINE_TYPE (NMLldpListener, nm_lldp_listener, G_TYPE_OBJECT)
#define NM_LLDP_LISTENER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_LLDP_LISTENER, NMLldpListenerPrivate))
typedef struct {
guint8 chassis_id_type;
guint8 port_id_type;
char *chassis_id;
char *port_id;
int dest;
GHashTable *tlvs;
} LLDPNeighbor;
static void process_lldp_neighbors (NMLldpListener *self);
static void
gvalue_destroy (gpointer data)
{
GValue *value = (GValue *) data;
g_value_unset (value);
g_slice_free (GValue, value);
}
static GValue *
gvalue_new_nstr (const char *str, guint16 len)
{
GValue *value;
value = g_slice_new0 (GValue);
g_value_init (value, G_TYPE_STRING);
g_value_take_string (value, strndup (str, len));
return value;
}
static GValue *
gvalue_new_uint (guint val)
{
GValue *value;
value = g_slice_new0 (GValue);
g_value_init (value, G_TYPE_UINT);
g_value_set_uint (value, val);
return value;
}
static guint
lldp_neighbor_id_hash (gconstpointer ptr)
{
const LLDPNeighbor *neigh = ptr;
return g_str_hash (neigh->chassis_id) ^
g_str_hash (neigh->port_id) ^
neigh->chassis_id_type ^
(neigh->port_id_type * 33);
}
static gboolean
lldp_neighbor_id_equal (gconstpointer a, gconstpointer b)
{
const LLDPNeighbor *x = a, *y = b;
return x->chassis_id_type == y->chassis_id_type &&
x->port_id_type == y->port_id_type &&
!g_strcmp0 (x->chassis_id, y->chassis_id) &&
!g_strcmp0 (x->port_id, y->port_id);
}
static void
lldp_neighbor_free (gpointer data)
{
LLDPNeighbor *neighbor = data;
if (neighbor) {
g_free (neighbor->chassis_id);
g_free (neighbor->port_id);
g_hash_table_unref (neighbor->tlvs);
g_free (neighbor);
}
}
static gboolean
lldp_neighbor_equal (LLDPNeighbor *a, LLDPNeighbor *b)
{
GHashTableIter iter;
gpointer k, v;
g_return_val_if_fail (a && a->tlvs, FALSE);
g_return_val_if_fail (b && b->tlvs, FALSE);
if ( a->chassis_id_type != b->chassis_id_type
|| a->port_id_type != b->port_id_type
|| a->dest != b->dest
|| g_strcmp0 (a->chassis_id, b->chassis_id)
|| g_strcmp0 (a->port_id, b->port_id))
return FALSE;
if (g_hash_table_size (a->tlvs) != g_hash_table_size (b->tlvs))
return FALSE;
g_hash_table_iter_init (&iter, a->tlvs);
while (g_hash_table_iter_next (&iter, &k, &v)) {
GValue *value_a, *value_b;
value_a = v;
value_b = g_hash_table_lookup (b->tlvs, k);
if (!value_b)
return FALSE;
g_return_val_if_fail (G_VALUE_TYPE (value_a) == G_VALUE_TYPE (value_b), FALSE);
if (G_VALUE_HOLDS_STRING (value_a)) {
if (g_strcmp0 (g_value_get_string (value_a), g_value_get_string (value_b)))
return FALSE;
} else if (G_VALUE_HOLDS_UINT (value_a)) {
if (g_value_get_uint (value_a) != g_value_get_uint (value_b))
return FALSE;
} else
g_return_val_if_reached (FALSE);
}
return TRUE;
}
static gboolean
lldp_hash_table_equal (GHashTable *a, GHashTable *b)
{
GHashTableIter iter;
gpointer val;
g_return_val_if_fail (a, FALSE);
g_return_val_if_fail (b, FALSE);
if (g_hash_table_size (a) != g_hash_table_size (b))
return FALSE;
g_hash_table_iter_init (&iter, a);
while (g_hash_table_iter_next (&iter, NULL, &val)) {
LLDPNeighbor *neigh_a, *neigh_b;
neigh_a = val;
neigh_b = g_hash_table_lookup (b, val);
if (!neigh_b)
return FALSE;
if (!lldp_neighbor_equal (neigh_a, neigh_b))
return FALSE;
}
return TRUE;
}
static gboolean
lldp_timeout (gpointer user_data)
{
NMLldpListener *self = NM_LLDP_LISTENER (user_data);
NMLldpListenerPrivate *priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
priv->timer = 0;
if (priv->num_pending_events)
process_lldp_neighbors (self);
return G_SOURCE_REMOVE;
}
static void
process_lldp_neighbors (NMLldpListener *self)
{
NMLldpListenerPrivate *priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
sd_lldp_packet **packets = NULL;
GHashTable *hash;
int num, i;
num = sd_lldp_get_packets (priv->lldp_handle, &packets);
if (num < 0) {
nm_log_dbg (LOGD_DEVICE, "LLDP: error %d retrieving neighbor packets for %s",
num, priv->iface);
return;
}
hash = g_hash_table_new_full (lldp_neighbor_id_hash, lldp_neighbor_id_equal,
lldp_neighbor_free, NULL);
for (i = 0; packets && i < num; i++) {
uint8_t chassis_id_type, port_id_type, *chassis_id, *port_id, data8;
uint16_t chassis_id_len, port_id_len, len, data16;
LLDPNeighbor *neigh;
GValue *value;
char *str;
int r;
if (i >= MAX_NEIGHBORS)
goto next_packet;
r = sd_lldp_packet_read_chassis_id (packets[i], &chassis_id_type,
&chassis_id, &chassis_id_len);
if (r < 0)
goto next_packet;
r = sd_lldp_packet_read_port_id (packets[i], &port_id_type,
&port_id, &port_id_len);
if (r < 0)
goto next_packet;
neigh = g_malloc0 (sizeof (LLDPNeighbor));
neigh->tlvs = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, gvalue_destroy);
neigh->chassis_id_type = chassis_id_type;
neigh->port_id_type = port_id_type;
sd_lldp_packet_get_destination_type (packets[i], &neigh->dest);
if (chassis_id_len < 1) {
lldp_neighbor_free (neigh);
goto next_packet;
}
switch (chassis_id_type) {
case LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS:
case LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME:
case LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED:
case LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT:
neigh->chassis_id = strndup ((char *) chassis_id, chassis_id_len);
break;
case LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS:
neigh->chassis_id = nm_utils_hwaddr_ntoa (chassis_id, chassis_id_len);
break;
default:
nm_log_dbg (LOGD_DEVICE, "LLDP: unsupported chassis ID type %d", chassis_id_type);
lldp_neighbor_free (neigh);
goto next_packet;
}
if (port_id_len < 1) {
lldp_neighbor_free (neigh);
goto next_packet;
}
switch (port_id_type) {
case LLDP_PORT_SUBTYPE_INTERFACE_ALIAS:
case LLDP_PORT_SUBTYPE_INTERFACE_NAME:
case LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED:
case LLDP_PORT_SUBTYPE_PORT_COMPONENT:
neigh->port_id = strndup ((char *) port_id, port_id_len);
break;
case LLDP_PORT_SUBTYPE_MAC_ADDRESS:
neigh->port_id = nm_utils_hwaddr_ntoa (port_id, port_id_len);
break;
default:
nm_log_dbg (LOGD_DEVICE, "LLDP: unsupported port ID type %d", port_id_type);
lldp_neighbor_free (neigh);
goto next_packet;
}
if (sd_lldp_packet_read_port_description (packets[i], &str, &len) == 0) {
value = gvalue_new_nstr (str, len);
g_hash_table_insert (neigh->tlvs, NM_LLDP_ATTR_PORT_DESCRIPTION, value);
}
if (sd_lldp_packet_read_system_name (packets[i], &str, &len) == 0) {
value = gvalue_new_nstr (str, len);
g_hash_table_insert (neigh->tlvs, NM_LLDP_ATTR_SYSTEM_NAME, value);
}
if (sd_lldp_packet_read_system_description (packets[i], &str, &len) == 0) {
value = gvalue_new_nstr (str, len);
g_hash_table_insert (neigh->tlvs, NM_LLDP_ATTR_SYSTEM_DESCRIPTION, value);
}
if (sd_lldp_packet_read_system_capability (packets[i], &data16) == 0) {
value = gvalue_new_uint (data16);
g_hash_table_insert (neigh->tlvs, NM_LLDP_ATTR_SYSTEM_CAPABILITIES, value);
}
if (sd_lldp_packet_read_port_vlan_id (packets[i], &data16) == 0) {
value = gvalue_new_uint (data16);
g_hash_table_insert (neigh->tlvs, NM_LLDP_ATTR_IEEE_802_1_PVID, value);
}
if (sd_lldp_packet_read_port_protocol_vlan_id (packets[i], &data8, &data16) == 0) {
value = gvalue_new_uint (data16);
g_hash_table_insert (neigh->tlvs, NM_LLDP_ATTR_IEEE_802_1_PPVID, value);
value = gvalue_new_uint (data8);
g_hash_table_insert (neigh->tlvs, NM_LLDP_ATTR_IEEE_802_1_PPVID_FLAGS, value);
}
if (sd_lldp_packet_read_vlan_name (packets[i], &data16, &str, &len) == 0) {
value = gvalue_new_uint (data16);
g_hash_table_insert (neigh->tlvs, NM_LLDP_ATTR_IEEE_802_1_VID, value);
value = gvalue_new_nstr (str, len);
g_hash_table_insert (neigh->tlvs, NM_LLDP_ATTR_IEEE_802_1_VLAN_NAME, value);
}
nm_log_dbg (LOGD_DEVICE, "LLDP: new neigh: CHASSIS='%s' PORT='%s'",
neigh->chassis_id, neigh->port_id);
g_hash_table_add (hash, neigh);
next_packet:
sd_lldp_packet_unref (packets[i]);
}
g_free (packets);
if (lldp_hash_table_equal (priv->lldp_neighbors, hash)) {
g_hash_table_destroy (hash);
} else {
g_hash_table_destroy (priv->lldp_neighbors);
priv->lldp_neighbors = hash;
nm_clear_g_variant (&priv->variant);
g_object_notify (G_OBJECT (self), NM_LLDP_LISTENER_NEIGHBORS);
}
/* Since the processing of the neighbor list is potentially
* expensive when there are many neighbors, coalesce multiple
* events arriving in short time.
*/
priv->timer = g_timeout_add_seconds (MIN_UPDATE_INTERVAL, lldp_timeout, self);
priv->num_pending_events = 0;
}
static void
lldp_event_handler (sd_lldp *lldp, int event, void *userdata)
{
NMLldpListener *self = userdata;
NMLldpListenerPrivate *priv;
g_return_if_fail (NM_IS_LLDP_LISTENER (self));
priv = NM_LLDP_LISTENER_GET_PRIVATE (self);