device: export list of LLDP neighbors through D-Bus
authorBeniamino Galvani <bgalvani@redhat.com>
Wed, 7 Oct 2015 09:48:38 +0000 (11:48 +0200)
committerBeniamino Galvani <bgalvani@redhat.com>
Mon, 12 Oct 2015 12:44:30 +0000 (14:44 +0200)
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.

introspection/nm-device.xml
libnm-core/nm-dbus-interface.h
src/Makefile.am
src/devices/nm-device.c
src/devices/nm-device.h
src/devices/nm-lldp-listener.c [new file with mode: 0644]
src/devices/nm-lldp-listener.h [new file with mode: 0644]
src/nm-types.h

index 9092540..fd377a0 100644 (file)
         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>
index 0dda998..68b0592 100644 (file)
@@ -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__ */
index d23cbf8..b845958 100644 (file)
@@ -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 \
index 46bf08b..fcd5dfe 100644 (file)
@@ -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",
index 441ae06..1585819 100644 (file)
@@ -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 */
diff --git a/src/devices/nm-lldp-listener.c b/src/devices/nm-lldp-listener.c
new file mode 100644 (file)
index 0000000..6d29a67
--- /dev/null
@@ -0,0 +1,651 @@
+/* -*- 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);
+
+       if (priv->timer > 0) {
+               priv->num_pending_events++;
+               return;
+       }
+
+       process_lldp_neighbors (self);
+}
+
+gboolean
+nm_lldp_listener_start (NMLldpListener *self, int ifindex, const char *iface,
+                        GError **error)
+{
+       NMLldpListenerPrivate *priv;
+       const struct ether_addr *addr;
+       size_t addr_len;
+       int ret;
+
+       g_return_val_if_fail (NM_IS_LLDP_LISTENER (self), FALSE);
+       g_return_val_if_fail (ifindex > 0, FALSE);
+       g_return_val_if_fail (iface, FALSE);
+       g_return_val_if_fail (!error || !*error, FALSE);
+
+       priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
+
+       if (priv->lldp_handle) {
+               g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+                                    "already running");
+               return FALSE;
+       }
+
+       addr = nm_platform_link_get_address (NM_PLATFORM_GET, ifindex, &addr_len);
+       if (!addr || addr_len != ETH_ALEN) {
+               g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+                                    "unsupported device");
+               return FALSE;
+       }
+
+       ret = sd_lldp_new (ifindex, iface, addr, &priv->lldp_handle);
+       if (ret) {
+               g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+                                    "initialization failed");
+               return FALSE;
+       }
+
+       ret = sd_lldp_attach_event (priv->lldp_handle, NULL, 0);
+       if (ret) {
+               g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+                                    "attach event failed");
+               goto err_free;
+       }
+
+       ret = sd_lldp_set_callback (priv->lldp_handle, lldp_event_handler, self);
+       if (ret) {
+               g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+                                    "set callback failed");
+               goto err;
+       }
+
+       ret = sd_lldp_start (priv->lldp_handle);
+       if (ret) {
+               g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+                                    "start failed");
+               goto err;
+       }
+
+       priv->ifindex = ifindex;
+       priv->iface = strdup (iface);
+       return TRUE;
+
+err:
+       sd_lldp_detach_event (priv->lldp_handle);
+err_free:
+       sd_lldp_free (priv->lldp_handle);
+       priv->lldp_handle = NULL;
+       return FALSE;
+}
+
+void
+nm_lldp_listener_stop (NMLldpListener *self)
+{
+       NMLldpListenerPrivate *priv;
+       guint size;
+
+       g_return_if_fail (NM_IS_LLDP_LISTENER (self));
+       priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
+
+       if (priv->lldp_handle) {
+               sd_lldp_stop (priv->lldp_handle);
+               sd_lldp_detach_event (priv->lldp_handle);
+               sd_lldp_free (priv->lldp_handle);
+               g_clear_pointer (&priv->iface, g_free);
+               priv->lldp_handle = NULL;
+
+               size = g_hash_table_size (priv->lldp_neighbors);
+               g_hash_table_remove_all (priv->lldp_neighbors);
+               if (size) {
+                       nm_clear_g_variant (&priv->variant);
+                       g_object_notify (G_OBJECT (self), NM_LLDP_LISTENER_NEIGHBORS);
+               }
+       }
+
+       nm_clear_g_source (&priv->timer);
+}
+
+gboolean
+nm_lldp_listener_is_running (NMLldpListener *self)
+{
+       NMLldpListenerPrivate *priv;
+
+       g_return_val_if_fail (NM_IS_LLDP_LISTENER (self), FALSE);
+
+       priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
+       return !!priv->lldp_handle;
+}
+
+GVariant *
+nm_lldp_listener_get_neighbors (NMLldpListener *self)
+{
+       GVariantBuilder array_builder, neigh_builder;
+       GHashTableIter iter;
+       NMLldpListenerPrivate *priv;
+       LLDPNeighbor *neigh;
+       char *dest_str = NULL;
+
+       priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
+
+       if (priv->variant)
+               goto out;
+
+       g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("aa{sv}"));
+       g_hash_table_iter_init (&iter, priv->lldp_neighbors);
+
+       while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &neigh)) {
+               GHashTableIter val_iter;
+               gpointer key, val;
+
+               g_variant_builder_init (&neigh_builder, G_VARIANT_TYPE ("a{sv}"));
+
+               g_variant_builder_add (&neigh_builder, "{sv}",
+                                      NM_LLDP_ATTR_CHASSIS_ID_TYPE,
+                                      g_variant_new_uint32 (neigh->chassis_id_type));
+               g_variant_builder_add (&neigh_builder, "{sv}",
+                                      NM_LLDP_ATTR_CHASSIS_ID,
+                                      g_variant_new_string (neigh->chassis_id));
+               g_variant_builder_add (&neigh_builder, "{sv}",
+                                      NM_LLDP_ATTR_PORT_ID_TYPE,
+                                      g_variant_new_uint32 (neigh->port_id_type));
+               g_variant_builder_add (&neigh_builder, "{sv}",
+                                      NM_LLDP_ATTR_PORT_ID,
+                                      g_variant_new_string (neigh->port_id));
+
+               switch (neigh->dest) {
+               case SD_LLDP_DESTINATION_TYPE_NEAREST_BRIDGE:
+                       dest_str = NM_LLDP_DEST_NEAREST_BRIDGE;
+                       break;
+               case SD_LLDP_DESTINATION_TYPE_NEAREST_NON_TPMR_BRIDGE:
+                       dest_str = NM_LLDP_DEST_NEAREST_NON_TPMR_BRIDGE;
+                       break;
+               case SD_LLDP_DESTINATION_TYPE_NEAREST_CUSTOMER_BRIDGE:
+                       dest_str = NM_LLDP_DEST_NEAREST_CUSTOMER_BRIDGE;
+                       break;
+               }
+
+               if (dest_str) {
+                       g_variant_builder_add (&neigh_builder, "{sv}",
+                                              NM_LLDP_ATTR_DESTINATION,
+                                              g_variant_new_string (dest_str));
+               }
+
+               g_hash_table_iter_init (&val_iter, neigh->tlvs);
+               while (g_hash_table_iter_next (&val_iter, &key, &val)) {
+                       GValue *item = val;
+
+                       if (G_VALUE_HOLDS_STRING (item)) {
+                               g_variant_builder_add (&neigh_builder, "{sv}",
+                                                      key,
+                                                      g_variant_new_string (g_value_get_string (item)));
+                       } else if (G_VALUE_HOLDS_UINT (item)) {
+                               g_variant_builder_add (&neigh_builder, "{sv}",
+                                                      key,
+                                                      g_variant_new_uint32 (g_value_get_uint (item)));
+                       }
+               }
+
+               g_variant_builder_add (&array_builder, "a{sv}", &neigh_builder);
+       }
+
+       priv->variant = g_variant_ref_sink (g_variant_builder_end (&array_builder));
+
+out:
+       return priv->variant;
+}
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+       NMLldpListener *self = NM_LLDP_LISTENER (object);
+
+       switch (prop_id) {
+       case PROP_NEIGHBORS:
+               g_value_set_variant (value, nm_lldp_listener_get_neighbors (self));
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+nm_lldp_listener_init (NMLldpListener *self)
+{
+       NMLldpListenerPrivate *priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
+
+       priv->lldp_neighbors = g_hash_table_new_full (lldp_neighbor_id_hash,
+                                                     lldp_neighbor_id_equal,
+                                                     lldp_neighbor_free, NULL);
+}
+
+NMLldpListener *
+nm_lldp_listener_new (void)
+{
+       return (NMLldpListener *) g_object_new (NM_TYPE_LLDP_LISTENER, NULL);
+}
+
+static void
+dispose (GObject *object)
+{
+       nm_lldp_listener_stop (NM_LLDP_LISTENER (object));
+
+       G_OBJECT_CLASS (nm_lldp_listener_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+       NMLldpListener *self = NM_LLDP_LISTENER (object);
+       NMLldpListenerPrivate *priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
+
+       nm_lldp_listener_stop (self);
+       g_hash_table_unref (priv->lldp_neighbors);
+
+       nm_clear_g_variant (&priv->variant);
+
+       G_OBJECT_CLASS (nm_lldp_listener_parent_class)->finalize (object);
+}
+
+static void
+nm_lldp_listener_class_init (NMLldpListenerClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       g_type_class_add_private (klass, sizeof (NMLldpListenerPrivate));
+
+       object_class->dispose = dispose;
+       object_class->finalize = finalize;
+       object_class->get_property = get_property;
+
+       g_object_class_install_property
+               (object_class, PROP_NEIGHBORS,
+                g_param_spec_variant (NM_LLDP_LISTENER_NEIGHBORS, "", "",
+                                      G_VARIANT_TYPE ("aa{sv}"),
+                                      NULL,
+                                      G_PARAM_READABLE |
+                                      G_PARAM_STATIC_STRINGS));
+}
+
diff --git a/src/devices/nm-lldp-listener.h b/src/devices/nm-lldp-listener.h
new file mode 100644 (file)
index 0000000..7e8fa20
--- /dev/null
@@ -0,0 +1,56 @@
+/* -*- 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.
+ */
+
+#ifndef __NM_LLDP_LISTENER__
+#define __NM_LLDP_LISTENER__
+
+#include "nm-glib.h"
+#include "nm-types.h"
+
+G_BEGIN_DECLS
+
+#define NM_TYPE_LLDP_LISTENER            (nm_lldp_listener_get_type ())
+#define NM_LLDP_LISTENER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_LLDP_LISTENER, NMLldpListener))
+#define NM_LLDP_LISTENER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_LLDP_LISTENER, NMLldpListenerClass))
+#define NM_IS_LLDP_LISTENER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_LLDP_LISTENER))
+#define NM_IS_LLDP_LISTENER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_LLDP_LISTENER))
+#define NM_LLDP_LISTENER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_LLDP_LISTENER, NMLldpListenerClass))
+
+#define NM_LLDP_LISTENER_NEIGHBORS "neighbors"
+
+struct _NMLldpListener {
+       GObject parent;
+};
+
+typedef struct {
+       GObjectClass parent;
+} NMLldpListenerClass;
+
+GType nm_lldp_listener_get_type (void);
+NMLldpListener *nm_lldp_listener_new (void);
+gboolean nm_lldp_listener_start (NMLldpListener *self, int ifindex, const char *iface, GError **error);
+void nm_lldp_listener_stop (NMLldpListener *self);
+gboolean nm_lldp_listener_is_running (NMLldpListener *self);
+
+GVariant *nm_lldp_listener_get_neighbors (NMLldpListener *self);
+
+G_END_DECLS
+
+#endif /* __NM_LLDP_LISTENER__ */
index 8fd4c36..2554d47 100644 (file)
@@ -49,6 +49,7 @@ typedef struct _NMRfkillManager      NMRfkillManager;
 typedef struct _NMRouteManager       NMRouteManager;
 typedef struct _NMSessionMonitor     NMSessionMonitor;
 typedef struct _NMSleepMonitor       NMSleepMonitor;
+typedef struct _NMLldpListener       NMLldpListener;
 
 typedef enum {
        /* In priority order; higher number == higher priority */