summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBeniamino Galvani <bgalvani@redhat.com>2015-10-12 14:52:43 +0200
committerBeniamino Galvani <bgalvani@redhat.com>2015-10-12 14:52:43 +0200
commitb1ab958def25f3045139ef13a1a0ac07a5a41da6 (patch)
tree237ac913ddb89ec254dfba3842d852d67fa33358
parent237030ce2bc23f39ccb4f72f94b3c98e2232ded4 (diff)
parentbd4df767c6faf423b84f54d3489df32a535da348 (diff)
downloadNetworkManager-b1ab958def25f3045139ef13a1a0ac07a5a41da6.tar.gz
merge: LLDP support
Add support for receiving LLDP frames and exporting through D-Bus the list of LLDP neighbors. https://bugzilla.redhat.com/show_bug.cgi?id=1142898
-rw-r--r--clients/cli/settings.c62
-rwxr-xr-xexamples/python/gi/get-lldp-neighbors.py52
-rw-r--r--include/nm-macros-internal.h11
-rw-r--r--introspection/nm-device.xml6
-rw-r--r--libnm-core/nm-dbus-interface.h19
-rw-r--r--libnm-core/nm-setting-connection.c51
-rw-r--r--libnm-core/nm-setting-connection.h16
-rw-r--r--libnm-core/tests/test-general.c1
-rw-r--r--libnm/libnm.ver10
-rw-r--r--libnm/nm-device.c257
-rw-r--r--libnm/nm-device.h23
-rw-r--r--libnm/nm-ip-config.c7
-rw-r--r--man/NetworkManager.conf.xml.in3
-rw-r--r--src/Makefile.am2
-rw-r--r--src/devices/nm-device.c103
-rw-r--r--src/devices/nm-device.h1
-rw-r--r--src/devices/nm-lldp-listener.c651
-rw-r--r--src/devices/nm-lldp-listener.h56
-rw-r--r--src/nm-types.h1
-rw-r--r--src/settings/plugins/ifcfg-rh/reader.c78
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.c68
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.h4
-rw-r--r--src/settings/plugins/ifcfg-rh/writer.c12
23 files changed, 1431 insertions, 63 deletions
diff --git a/clients/cli/settings.c b/clients/cli/settings.c
index 974fa97f61..0c10cf533e 100644
--- a/clients/cli/settings.c
+++ b/clients/cli/settings.c
@@ -59,6 +59,7 @@ NmcOutputField nmc_fields_setting_connection[] = {
SETTING_FIELD (NM_SETTING_CONNECTION_SECONDARIES), /* 14 */
SETTING_FIELD (NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT), /* 15 */
SETTING_FIELD (NM_SETTING_CONNECTION_METERED), /* 16 */
+ SETTING_FIELD (NM_SETTING_CONNECTION_LLDP), /* 17 */
{NULL, NULL, 0, NULL, FALSE, FALSE, 0}
};
#define NMC_FIELDS_SETTING_CONNECTION_ALL "name"","\
@@ -77,7 +78,8 @@ NmcOutputField nmc_fields_setting_connection[] = {
NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES","\
NM_SETTING_CONNECTION_SECONDARIES","\
NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT","\
- NM_SETTING_CONNECTION_METERED
+ NM_SETTING_CONNECTION_METERED","\
+ NM_SETTING_CONNECTION_LLDP
#define NMC_FIELDS_SETTING_CONNECTION_COMMON NMC_FIELDS_SETTING_CONNECTION_ALL
/* Available fields for NM_SETTING_WIRED_SETTING_NAME */
@@ -2929,6 +2931,56 @@ nmc_property_connection_describe_metered (NMSetting *setting, const char *prop)
"'unknown' to let NetworkManager choose a value using some heuristics\n");
}
+/* 'lldp' */
+static char *
+nmc_property_connection_get_lldp (NMSetting *setting, NmcPropertyGetType get_type)
+{
+ NMSettingConnection *s_conn = NM_SETTING_CONNECTION (setting);
+ NMSettingConnectionLldp lldp;
+ char *tmp, *str;
+
+ lldp = nm_setting_connection_get_lldp (s_conn);
+ tmp = nm_utils_enum_to_str (nm_setting_connection_lldp_get_type (), lldp);
+ if (get_type == NMC_PROPERTY_GET_PARSABLE)
+ str = g_strdup_printf ("%s", tmp && *tmp ? tmp : "default");
+ else
+ str = g_strdup_printf ("%d (%s)", lldp, tmp && *tmp ? tmp : "default");
+ g_free (tmp);
+ return str;
+}
+
+static gboolean
+nmc_property_connection_set_lldp (NMSetting *setting, const char *prop,
+ const char *val, GError **error)
+{
+ NMSettingConnectionLldp lldp;
+ gboolean ret;
+ long int t;
+
+ if (nmc_string_to_int_base (val, 0, TRUE,
+ NM_SETTING_CONNECTION_LLDP_DEFAULT,
+ NM_SETTING_CONNECTION_LLDP_ENABLE_RX,
+ &t))
+ lldp = t;
+ else {
+ ret = nm_utils_enum_from_str (nm_setting_connection_lldp_get_type (), val,
+ (int *) &lldp, NULL);
+
+ if (!ret) {
+ if (g_ascii_strcasecmp (val, "enable") == 0)
+ lldp = NM_SETTING_CONNECTION_LLDP_ENABLE_RX;
+ else {
+ g_set_error (error, 1, 0, _("invalid option '%s', use one of [%s]"),
+ val, "default,disable,enable-rx,enable");
+ return FALSE;
+ }
+ }
+ }
+
+ g_object_set (setting, prop, lldp, NULL);
+ return TRUE;
+}
+
/* --- NM_SETTING_802_1X_SETTING_NAME property setter functions --- */
#define DEFINE_SETTER_STR_LIST(def_func, set_func) \
static gboolean \
@@ -5624,6 +5676,13 @@ nmc_properties_init (void)
nmc_property_connection_describe_metered,
NULL,
NULL);
+ nmc_add_prop_funcs (GLUE (CONNECTION, LLDP),
+ nmc_property_connection_get_lldp,
+ nmc_property_connection_set_lldp,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
/* Add editable properties for NM_SETTING_DCB_SETTING_NAME */
nmc_add_prop_funcs (GLUE (DCB, APP_FCOE_FLAGS),
@@ -7035,6 +7094,7 @@ setting_connection_details (NMSetting *setting, NmCli *nmc, const char *one_pro
set_val_str (arr, 14, nmc_property_connection_get_secondaries (setting, NMC_PROPERTY_GET_PRETTY));
set_val_str (arr, 15, nmc_property_connection_get_gateway_ping_timeout (setting, NMC_PROPERTY_GET_PRETTY));
set_val_str (arr, 16, nmc_property_connection_get_metered (setting, NMC_PROPERTY_GET_PRETTY));
+ set_val_str (arr, 17, nmc_property_connection_get_lldp (setting, NMC_PROPERTY_GET_PRETTY));
g_ptr_array_add (nmc->output_data, arr);
print_data (nmc); /* Print all data */
diff --git a/examples/python/gi/get-lldp-neighbors.py b/examples/python/gi/get-lldp-neighbors.py
new file mode 100755
index 0000000000..03690d5a78
--- /dev/null
+++ b/examples/python/gi/get-lldp-neighbors.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+#
+# vim: ft=python ts=4 sts=4 sw=4 et ai
+# -*- Mode: Python; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+#
+# 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.
+#
+
+import sys
+from gi.repository import GLib, NM
+
+# This example shows how to get a list of LLDP neighbors for a given interface.
+
+main_loop = None
+
+if __name__ == "__main__":
+ if len(sys.argv) != 2:
+ sys.exit('Usage: %s <interface>' % sys.argv[0])
+ dev_iface = sys.argv[1]
+
+ c = NM.Client.new(None)
+ dev = c.get_device_by_iface(dev_iface)
+ if dev is None:
+ sys.exit('Device \'%s\' not found' % dev_iface)
+
+ neighbors = dev.get_lldp_neighbors()
+ for neighbor in neighbors:
+ ret, chassis = neighbor.get_attr_string_value('chassis-id')
+ ret, port = neighbor.get_attr_string_value('port-id')
+ print "Neighbor: %s - %s" % (chassis, port)
+ for attr in neighbor.get_attr_names():
+ attr_type = neighbor.get_attr_type(attr)
+ if attr_type.equal(GLib.VariantType.new('s')):
+ ret, value = neighbor.get_attr_string_value(attr)
+ print " %-32s: %s" % (attr, value)
+ elif attr_type.equal(GLib.VariantType.new('u')):
+ ret, value = neighbor.get_attr_uint_value(attr)
+ print " %-32s: %u" % (attr, value)
diff --git a/include/nm-macros-internal.h b/include/nm-macros-internal.h
index 80e9abf01b..5c1d61dbaf 100644
--- a/include/nm-macros-internal.h
+++ b/include/nm-macros-internal.h
@@ -218,6 +218,17 @@ nm_clear_g_signal_handler (gpointer self, guint *id)
return FALSE;
}
+static inline gboolean
+nm_clear_g_variant (GVariant **variant)
+{
+ if (variant && *variant) {
+ g_variant_unref (*variant);
+ *variant = NULL;
+ return TRUE;
+ }
+ return FALSE;
+}
+
/*****************************************************************************/
/* Determine whether @x is a power of two (@x being an integer type).
diff --git a/introspection/nm-device.xml b/introspection/nm-device.xml
index 909254002f..fd377a07eb 100644
--- a/introspection/nm-device.xml
+++ b/introspection/nm-device.xml
@@ -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>
diff --git a/libnm-core/nm-dbus-interface.h b/libnm-core/nm-dbus-interface.h
index 0dda998f79..68b0592e72 100644
--- a/libnm-core/nm-dbus-interface.h
+++ b/libnm-core/nm-dbus-interface.h
@@ -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__ */
diff --git a/libnm-core/nm-setting-connection.c b/libnm-core/nm-setting-connection.c
index 0217c358c1..f341a3987d 100644
--- a/libnm-core/nm-setting-connection.c
+++ b/libnm-core/nm-setting-connection.c
@@ -77,6 +77,7 @@ typedef struct {
GSList *secondaries; /* secondary connections to activate with the base connection */
guint gateway_ping_timeout;
NMMetered metered;
+ NMSettingConnectionLldp lldp;
} NMSettingConnectionPrivate;
enum {
@@ -97,6 +98,7 @@ enum {
PROP_SECONDARIES,
PROP_GATEWAY_PING_TIMEOUT,
PROP_METERED,
+ PROP_LLDP,
LAST_PROP
};
@@ -786,6 +788,24 @@ NM_BACKPORT_SYMBOL (libnm_1_0_6, NMMetered, nm_setting_connection_get_metered, (
NM_BACKPORT_SYMBOL (libnm_1_0_6, GType, nm_metered_get_type, (void), ());
+/**
+ * nm_setting_connection_get_lldp:
+ * @setting: the #NMSettingConnection
+ *
+ * Returns the #NMSettingConnection:lldp property of the connection.
+ *
+ * Returns: a %NMSettingConnectionLldp which indicates whether LLDP must be
+ * enabled for the connection.
+ *
+ * Since: 1.2
+ **/
+NMSettingConnectionLldp
+nm_setting_connection_get_lldp (NMSettingConnection *setting)
+{
+ g_return_val_if_fail (NM_IS_SETTING_CONNECTION (setting), NM_SETTING_CONNECTION_LLDP_DEFAULT);
+
+ return NM_SETTING_CONNECTION_GET_PRIVATE (setting)->lldp;
+}
static void
_set_error_missing_base_setting (GError **error, const char *type)
@@ -1196,6 +1216,9 @@ set_property (GObject *object, guint prop_id,
case PROP_METERED:
priv->metered = g_value_get_enum (value);
break;
+ case PROP_LLDP:
+ priv->lldp = g_value_get_int (value);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -1272,6 +1295,9 @@ get_property (GObject *object, guint prop_id,
case PROP_METERED:
g_value_set_enum (value, priv->metered);
break;
+ case PROP_LLDP:
+ g_value_set_int (value, priv->lldp);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -1702,4 +1728,29 @@ nm_setting_connection_class_init (NMSettingConnectionClass *setting_class)
G_PARAM_READWRITE |
NM_SETTING_PARAM_REAPPLY_IMMEDIATELY |
G_PARAM_STATIC_STRINGS));
+
+ /**
+ * NMSettingConnection:lldp:
+ *
+ * Whether LLDP is enabled for the connection.
+ *
+ * Since: 1.2
+ **/
+ /* ---ifcfg-rh---
+ * property: lldp
+ * variable: LLDP
+ * values: boolean value or 'rx'
+ * default: missing variable means global default
+ * description: whether LLDP is enabled for the connection
+ * example: LLDP=no
+ * ---end---
+ */
+ g_object_class_install_property
+ (object_class, PROP_LLDP,
+ g_param_spec_int (NM_SETTING_CONNECTION_LLDP, "", "",
+ G_MININT32, G_MAXINT32, NM_SETTING_CONNECTION_LLDP_DEFAULT,
+ NM_SETTING_PARAM_FUZZY_IGNORE |
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
}
diff --git a/libnm-core/nm-setting-connection.h b/libnm-core/nm-setting-connection.h
index 0f502c955e..0d4966f05a 100644
--- a/libnm-core/nm-setting-connection.h
+++ b/libnm-core/nm-setting-connection.h
@@ -60,6 +60,7 @@ G_BEGIN_DECLS
#define NM_SETTING_CONNECTION_SECONDARIES "secondaries"
#define NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT "gateway-ping-timeout"
#define NM_SETTING_CONNECTION_METERED "metered"
+#define NM_SETTING_CONNECTION_LLDP "lldp"
/* Types for property values */
/**
@@ -79,6 +80,19 @@ typedef enum {
NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_YES = 1,
} NMSettingConnectionAutoconnectSlaves;
+/**
+ * NMSettingConnectionLldp:
+ * @NM_SETTING_CONNECTION_LLDP_DEFAULT: default value
+ * @NM_SETTING_CONNECTION_LLDP_DISABLE: disable LLDP
+ * @NM_SETTING_CONNECTION_LLDP_ENABLE_RX: enable reception of LLDP frames
+ *
+ * #NMSettingConnectionLldp values indicate whether LLDP should be enabled.
+ */
+typedef enum {
+ NM_SETTING_CONNECTION_LLDP_DEFAULT = -1,
+ NM_SETTING_CONNECTION_LLDP_DISABLE = 0,
+ NM_SETTING_CONNECTION_LLDP_ENABLE_RX = 1,
+} NMSettingConnectionLldp;
/**
* NMSettingConnection:
@@ -144,6 +158,8 @@ gboolean nm_setting_connection_remove_secondary_by_value (NMSettingConnection
guint32 nm_setting_connection_get_gateway_ping_timeout (NMSettingConnection *setting);
NM_AVAILABLE_IN_1_2
NMMetered nm_setting_connection_get_metered (NMSettingConnection *setting);
+NM_AVAILABLE_IN_1_2
+NMSettingConnectionLldp nm_setting_connection_get_lldp (NMSettingConnection *setting);
G_END_DECLS
diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c
index bb2d6cb66d..7bd488b4e8 100644
--- a/libnm-core/tests/test-general.c
+++ b/libnm-core/tests/test-general.c
@@ -1972,6 +1972,7 @@ test_connection_diff_a_only (void)
{ NM_SETTING_CONNECTION_SECONDARIES, NM_SETTING_DIFF_RESULT_IN_A },
{ NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT, NM_SETTING_DIFF_RESULT_IN_A },
{ NM_SETTING_CONNECTION_METERED, NM_SETTING_DIFF_RESULT_IN_A },
+ { NM_SETTING_CONNECTION_LLDP, NM_SETTING_DIFF_RESULT_IN_A },
{ NULL, NM_SETTING_DIFF_RESULT_UNKNOWN }
} },
{ NM_SETTING_WIRED_SETTING_NAME, {
diff --git a/libnm/libnm.ver b/libnm/libnm.ver
index 2e58887d06..a3f9282822 100644
--- a/libnm/libnm.ver
+++ b/libnm/libnm.ver
@@ -859,17 +859,27 @@ libnm_1_2_0 {
global:
nm_access_point_get_last_seen;
nm_device_ethernet_get_s390_subchannels;
+ nm_device_get_lldp_neighbors;
nm_device_get_metered;
nm_device_get_nm_plugin_missing;
nm_device_set_managed;
nm_device_wifi_request_scan_options;
nm_device_wifi_request_scan_options_async;
+ nm_lldp_neighbor_get_attr_names;
+ nm_lldp_neighbor_get_attr_string_value;
+ nm_lldp_neighbor_get_attr_type;
+ nm_lldp_neighbor_get_attr_uint_value;
+ nm_lldp_neighbor_new;
+ nm_lldp_neighbor_ref;
+ nm_lldp_neighbor_unref;
nm_metered_get_type;
nm_setting_802_1x_check_cert_scheme;
nm_setting_bridge_get_multicast_snooping;
nm_setting_connection_autoconnect_slaves_get_type;
nm_setting_connection_get_autoconnect_slaves;
+ nm_setting_connection_get_lldp;
nm_setting_connection_get_metered;
+ nm_setting_connection_lldp_get_type;
nm_setting_ip4_config_get_dhcp_timeout;
nm_setting_ip_config_add_dns_option;
nm_setting_ip_config_clear_dns_options;
diff --git a/libnm/nm-device.c b/libnm/nm-device.c
index 725762de61..bb4231ec52 100644
--- a/libnm/nm-device.c
+++ b/libnm/nm-device.c
@@ -105,6 +105,7 @@ typedef struct {
char *physical_port_id;
guint32 mtu;
+ GPtrArray *lldp_neighbors;
} NMDevicePrivate;
enum {
@@ -134,6 +135,7 @@ enum {
PROP_PHYSICAL_PORT_ID,
PROP_MTU,
PROP_METERED,
+ PROP_LLDP_NEIGHBORS,
LAST_PROP
};
@@ -146,6 +148,11 @@ enum {
static guint signals[LAST_SIGNAL] = { 0 };
+struct _NMLldpNeighbor {
+ guint refcount;
+ GHashTable *attrs;
+};
+
static void
nm_device_init (NMDevice *device)
{
@@ -153,6 +160,7 @@ nm_device_init (NMDevice *device)
priv->state = NM_DEVICE_STATE_UNKNOWN;
priv->reason = NM_DEVICE_STATE_REASON_NONE;
+ priv->lldp_neighbors = g_ptr_array_new ();
}
static gboolean
@@ -165,6 +173,38 @@ demarshal_state_reason (NMObject *object, GParamSpec *pspec, GVariant *value, gp
return TRUE;
}
+static gboolean
+demarshal_lldp_neighbors (NMObject *object, GParamSpec *pspec, GVariant *value, gpointer field)
+{
+ NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (object);
+ GVariantIter iter, attrs_iter;
+ GVariant *variant, *attr_variant;
+ const char *attr_name;
+
+ g_return_val_if_fail (g_variant_is_of_type (value, G_VARIANT_TYPE ("aa{sv}")), FALSE);
+
+ g_ptr_array_unref (priv->lldp_neighbors);
+ priv->lldp_neighbors = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_lldp_neighbor_unref);
+ g_variant_iter_init (&iter, value);
+
+ while (g_variant_iter_next (&iter, "@a{sv}", &variant)) {
+ NMLldpNeighbor *neigh;
+
+ neigh = nm_lldp_neighbor_new ();
+ g_variant_iter_init (&attrs_iter, variant);
+
+ while (g_variant_iter_next (&attrs_iter, "{&sv}", &attr_name, &attr_variant))
+ g_hash_table_insert (neigh->attrs, g_strdup (attr_name), attr_variant);
+
+ g_variant_unref (variant);
+ g_ptr_array_add (priv->lldp_neighbors, neigh);
+ }
+
+ _nm_object_queue_notify (object, NM_DEVICE_LLDP_NEIGHBORS);
+
+ return TRUE;
+}
+
static void
device_state_changed (NMDBusDevice *proxy,
guint new_state,
@@ -199,6 +239,7 @@ init_dbus (NMObject *object)
{ NM_DEVICE_PHYSICAL_PORT_ID, &priv->physical_port_id },
{ NM_DEVICE_MTU, &priv->mtu },
{ NM_DEVICE_METERED, &priv->metered },
+ { NM_DEVICE_LLDP_NEIGHBORS, &priv->lldp_neighbors, demarshal_lldp_neighbors },
/* Properties that exist in D-Bus but that we don't track */
{ "ip4-address", NULL },
@@ -350,6 +391,7 @@ dispose (GObject *object)
g_clear_object (&priv->active_connection);
g_clear_pointer (&priv->available_connections, g_ptr_array_unref);
+ g_clear_pointer (&priv->lldp_neighbors, g_ptr_array_unref);
G_OBJECT_CLASS (nm_device_parent_class)->dispose (object);
}
@@ -461,6 +503,9 @@ get_property (GObject *object,
case PROP_METERED:
g_value_set_uint (value, nm_device_get_metered (device));
break;
+ case PROP_LLDP_NEIGHBORS:
+ g_value_set_boxed (value, nm_device_get_lldp_neighbors (device));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -839,6 +884,18 @@ nm_device_class_init (NMDeviceClass *device_class)
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
+ /**
+ * NMDevice:lldp-neighbors:
+ *
+ * The LLDP neighbors.
+ **/
+ g_object_class_install_property
+ (object_class, PROP_LLDP_NEIGHBORS,
+ g_param_spec_boxed (NM_DEVICE_LLDP_NEIGHBORS, "", "",
+ G_TYPE_PTR_ARRAY,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
/* signals */
/**
@@ -2011,6 +2068,27 @@ nm_device_get_metered (NMDevice *device)
NM_BACKPORT_SYMBOL (libnm_1_0_6, NMMetered, nm_device_get_metered, (NMDevice *device), (device));
/**
+ * nm_device_get_lldp_neighbors:
+ * @device: a #NMDevice
+ *
+ * Gets the list of neighbors discovered through LLDP.
+ *
+ * Returns: (element-type NMLldpNeighbor) (transfer none): the #GPtrArray
+ * containing #NMLldpNeighbor<!-- -->s. This is the internal copy used by the
+ * device and must not be modified. The library never modifies the returned
+ * array and thus it is safe for callers to reference and keep using it.
+ *
+ * Since: 1.2
+ **/
+GPtrArray *
+nm_device_get_lldp_neighbors (NMDevice *device)
+{
+ g_return_val_if_fail (NM_IS_DEVICE (device), NULL);
+
+ return NM_DEVICE_GET_PRIVATE (device)->lldp_neighbors;
+}
+
+/**
* nm_device_is_software:
* @device: a #NMDevice
*
@@ -2360,3 +2438,182 @@ nm_device_get_setting_type (NMDevice *device)
return NM_DEVICE_GET_CLASS (device)->get_setting_type (device);
}
+
+/**
+ * nm_lldp_neighbor_new
+ *
+ * Creates a new #NMLldpNeighbor object.
+ *
+ * Returns: (transfer full): the new #NMLldpNeighbor object.
+ *
+ * Since: 1.2
+ **/
+NMLldpNeighbor *
+nm_lldp_neighbor_new (void)
+{
+ NMLldpNeighbor *neigh;
+
+ neigh = g_new0 (NMLldpNeighbor, 1);
+ neigh->refcount = 1;
+ neigh->attrs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+ (GDestroyNotify) g_variant_unref);
+
+ return neigh;
+}
+
+/**
+ * nm_lldp_neighbor_ref
+ * @neighbor: the #NMLldpNeighbor
+ *
+ * Increases the reference count of the object.
+ *
+ * Since: 1.2
+ **/
+void
+nm_lldp_neighbor_ref (NMLldpNeighbor *neighbor)
+{
+ g_return_if_fail (neighbor);
+ g_return_if_fail (neighbor->refcount > 0);
+
+ neighbor->refcount++;
+}
+
+/**
+ * nm_lldp_neighbor_unref:
+ * @neighbor: the #NMLldpNeighbor
+ *
+ * Decreases the reference count of the object. If the reference count
+ * reaches zero, the object will be destroyed.
+ *
+ * Since: 1.2
+ **/
+void
+nm_lldp_neighbor_unref (NMLldpNeighbor *neighbor)
+{
+ g_return_if_fail (neighbor);
+ g_return_if_fail (neighbor->refcount > 0);
+
+ if (--neighbor->refcount == 0) {
+ g_return_if_fail (neighbor->attrs);
+ g_hash_table_unref (neighbor->attrs);
+ g_free (neighbor);
+ }
+}
+
+/**
+ * nm_lldp_neighbor_get_attr_names:
+ * @neighbor: the #NMLldpNeighbor
+ *
+ * Gets an array of attribute names available for @neighbor.
+ *
+ * Returns: (transfer full): a %NULL-terminated array of attribute names.
+ *
+ * Since: 1.2
+ **/
+char **
+nm_lldp_neighbor_get_attr_names (NMLldpNeighbor *neighbor)
+{
+ GHashTableIter iter;
+ const char *key;
+ GPtrArray *names;
+
+ g_return_val_if_fail (neighbor, NULL);
+ g_return_val_if_fail (neighbor->attrs, NULL);
+
+ names = g_ptr_array_new ();
+
+ g_hash_table_iter_init (&iter, neighbor->attrs);
+ while (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL))
+ g_ptr_array_add (names, g_strdup (key));
+
+ g_ptr_array_add (names, NULL);
+
+ return (char **) g_ptr_array_free (names, FALSE);
+}
+
+/**
+ * nm_lldp_neighbor_get_attr_string_value:
+ * @neighbor: the #NMLldpNeighbor
+ * @name: the attribute name
+ * @out_value: (out) (allow-none) (transfer none): on return, the attribute value
+ *
+ * Gets the string value of attribute with name @name on @neighbor
+ *
+ * Returns: %TRUE if a string attribute with name @name was found, %FALSE otherwise
+ *
+ * Since: 1.2
+ **/
+gboolean
+nm_lldp_neighbor_get_attr_string_value (NMLldpNeighbor *neighbor, char *name,
+ const char **out_value)
+{
+ GVariant *variant;
+
+ g_return_val_if_fail (neighbor, FALSE);
+ g_return_val_if_fail (name && name[0], FALSE);
+
+ variant = g_hash_table_lookup (neighbor->attrs, name);
+ if (variant && g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING)) {
+ if (out_value)
+ *out_value = g_variant_get_string (variant, NULL);
+ return TRUE;
+ } else
+ return FALSE;
+}
+
+/**
+ * nm_lldp_neighbor_get_attr_uint_value:
+ * @neighbor: the #NMLldpNeighbor
+ * @name: the attribute name
+ * @out_value: (out) (allow-none) on return, the attribute value
+ *
+ * Gets the uint value of attribute with name @name on @neighbor
+ *
+ * Returns: %TRUE if a uint attribute with name @name was found, %FALSE otherwise
+ *
+ * Since: 1.2
+ **/
+gboolean
+nm_lldp_neighbor_get_attr_uint_value (NMLldpNeighbor *neighbor, char *name,
+ guint *out_value)
+{
+ GVariant *variant;
+
+ g_return_val_if_fail (neighbor, FALSE);
+ g_return_val_if_fail (name && name[0], FALSE);
+
+ variant = g_hash_table_lookup (neighbor->attrs, name);
+ if (variant && g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT32)) {
+ if (out_value)
+ *out_value = g_variant_get_uint32 (variant);
+ return TRUE;
+ } else
+ return FALSE;
+}
+
+/**
+ * nm_lldp_neighbor_get_attr_type:
+ * @neighbor: the #NMLldpNeighbor
+ * @name: the attribute name
+ *
+ * Get the type of an attribute.
+ *
+ * Returns: the #GVariantType of the attribute with name @name
+ *
+ * Since: 1.2
+ **/
+const GVariantType *
+nm_lldp_neighbor_get_attr_type (NMLldpNeighbor *neighbor, char *name)
+{
+ GVariant *variant;
+
+ g_return_val_if_fail (neighbor, NULL);
+ g_return_val_if_fail (name && name[0], NULL);
+
+ variant = g_hash_table_lookup (neighbor->attrs, name);
+ if (variant)
+ return g_variant_get_type (variant);
+ else
+ return NULL;
+
+}
diff --git a/libnm/nm-device.h b/libnm/nm-device.h
index 4453bfeb2c..7b76cf2d0b 100644
--- a/libnm/nm-device.h
+++ b/libnm/nm-device.h
@@ -62,6 +62,7 @@ G_BEGIN_DECLS
#define NM_DEVICE_PHYSICAL_PORT_ID "physical-port-id"
#define NM_DEVICE_MTU "mtu"
#define NM_DEVICE_METERED "metered"
+#define NM_DEVICE_LLDP_NEIGHBORS "lldp-neighbors"
struct _NMDevice {
NMObject parent;
@@ -90,6 +91,8 @@ typedef struct {
gpointer padding[8];
} NMDeviceClass;
+typedef struct _NMLldpNeighbor NMLldpNeighbor;
+
GType nm_device_get_type (void);
const char * nm_device_get_iface (NMDevice *device);
@@ -127,6 +130,8 @@ const char * nm_device_get_vendor (NMDevice *device);
const char * nm_device_get_description (NMDevice *device);
NM_AVAILABLE_IN_1_2
NMMetered nm_device_get_metered (NMDevice *device);
+NM_AVAILABLE_IN_1_2
+GPtrArray * nm_device_get_lldp_neighbors (NMDevice *device);
char ** nm_device_disambiguate_names (NMDevice **devices,
int num_devices);
@@ -164,6 +169,24 @@ gboolean nm_device_connection_compatible (NMDevice *device,
GType nm_device_get_setting_type (NMDevice *device);
+NM_AVAILABLE_IN_1_2
+NMLldpNeighbor *nm_lldp_neighbor_new (void);
+NM_AVAILABLE_IN_1_2
+void nm_lldp_neighbor_ref (NMLldpNeighbor *neighbor);
+NM_AVAILABLE_IN_1_2
+void nm_lldp_neighbor_unref (NMLldpNeighbor *neighbor);
+NM_AVAILABLE_IN_1_2
+char **nm_lldp_neighbor_get_attr_names (NMLldpNeighbor *neighbor);
+NM_AVAILABLE_IN_1_2
+gboolean nm_lldp_neighbor_get_attr_string_value (NMLldpNeighbor *neighbor, char *name,
+ const char **out_value);
+NM_AVAILABLE_IN_1_2
+gboolean nm_lldp_neighbor_get_attr_uint_value (NMLldpNeighbor *neighbor, char *name,
+ guint *out_value);
+
+NM_AVAILABLE_IN_1_2
+const GVariantType *nm_lldp_neighbor_get_attr_type (NMLldpNeighbor *neighbor, char *name);
+
G_END_DECLS
#endif /* __NM_DEVICE_H__ */
diff --git a/libnm/nm-ip-config.c b/libnm/nm-ip-config.c
index d057903e27..fc4567c420 100644
--- a/libnm/nm-ip-config.c
+++ b/libnm/nm-ip-config.c
@@ -406,7 +406,8 @@ nm_ip_config_get_gateway (NMIPConfig *config)
*
* Returns: (element-type NMIPAddress) (transfer none): the #GPtrArray
* containing #NMIPAddress<!-- -->es. This is the internal copy used by the
- * configuration and must not be modified.
+ * configuration and must not be modified. The library never modifies the
+ * returned array and thus it is safe for callers to reference and keep using it.
**/
GPtrArray *
nm_ip_config_get_addresses (NMIPConfig *config)
@@ -491,7 +492,9 @@ nm_ip_config_get_wins_servers (NMIPConfig *config)
*
* Returns: (element-type NMIPRoute) (transfer none): the #GPtrArray containing
* #NMIPRoutes. This is the internal copy used by the configuration, and must
- * not be modified.
+ * not be modified. The library never modifies the returned array and thus it is
+ * safe for callers to reference and keep using it.
+ *
**/
GPtrArray *
nm_ip_config_get_routes (NMIPConfig *config)
diff --git a/man/NetworkManager.conf.xml.in b/man/NetworkManager.conf.xml.in
index f7bcd1a359..9bf1388bea 100644
--- a/man/NetworkManager.conf.xml.in
+++ b/man/NetworkManager.conf.xml.in
@@ -575,6 +575,9 @@ ipv6.ip6-privacy=1
<term><varname>connection.autoconnect-slaves</varname></term>
</varlistentry>
<varlistentry>
+ <term><varname>connection.lldp</varname></term>
+ </varlistentry>
+ <varlistentry>
<term><varname>ethernet.wake-on-lan</varname></term>
</varlistentry>
<varlistentry>
diff --git a/src/Makefile.am b/src/Makefile.am
index d23cbf81a7..b845958a6e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -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 \
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 46bf08b514..fcd5dfe576 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.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",
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index 441ae06b73..15858194a7 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -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
index 0000000000..6d29a67cac
--- /dev/null
+++ b/src/devices/nm-lldp-listener.c
@@ -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
index 0000000000..7e8fa2051b
--- /dev/null
+++ b/src/devices/nm-lldp-listener.h
@@ -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__ */
diff --git a/src/nm-types.h b/src/nm-types.h
index 8fd4c36aef..2554d47e98 100644
--- a/src/nm-types.h
+++ b/src/nm-types.h
@@ -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 */
diff --git a/src/settings/plugins/ifcfg-rh/reader.c b/src/settings/plugins/ifcfg-rh/reader.c
index 564ed1248a..5a29e924ea 100644
--- a/src/settings/plugins/ifcfg-rh/reader.c
+++ b/src/settings/plugins/ifcfg-rh/reader.c
@@ -128,6 +128,7 @@ make_connection_setting (const char *file,
const char *prefix)
{
NMSettingConnection *s_con;
+ NMSettingConnectionLldp lldp;
const char *ifcfg_name = NULL;
char *new_id, *uuid = NULL, *zone = NULL, *value;
@@ -165,17 +166,24 @@ make_connection_setting (const char *file,
g_free (value);
}
+ value = svGetValue (ifcfg, "LLDP", FALSE);
+ if (!g_strcmp0 (value, "rx"))
+ lldp = NM_SETTING_CONNECTION_LLDP_ENABLE_RX;
+ else
+ lldp = svParseBoolean (value, NM_SETTING_CONNECTION_LLDP_DEFAULT);
+
/* Missing ONBOOT is treated as "ONBOOT=true" by the old network service */
g_object_set (s_con,
NM_SETTING_CONNECTION_AUTOCONNECT,
- svTrueValue (ifcfg, "ONBOOT", TRUE),
+ svGetValueBoolean (ifcfg, "ONBOOT", TRUE),
NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY,
(gint) svGetValueInt64 (ifcfg, "AUTOCONNECT_PRIORITY", 10,
NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_MIN,
NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_MAX,
NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_DEFAULT),
NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES,
- svTrueValue (ifcfg, "AUTOCONNECT_SLAVES", NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT),
+ svGetValueBoolean (ifcfg, "AUTOCONNECT_SLAVES", NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT),
+ NM_SETTING_CONNECTION_LLDP, lldp,
NULL);
value = svGetValue (ifcfg, "USERS", FALSE);
@@ -247,7 +255,7 @@ make_connection_setting (const char *file,
g_free (value);
}
- switch (svTrueValue (ifcfg, "CONNECTION_METERED", -1)) {
+ switch (svGetValueBoolean (ifcfg, "CONNECTION_METERED", -1)) {
case TRUE:
g_object_set (s_con, NM_SETTING_CONNECTION_METERED, NM_METERED_YES, NULL);
break;
@@ -923,7 +931,7 @@ make_ip4_setting (shvarFile *ifcfg,
* specified is DEFROUTE=yes which means that this connection can be used
* as a default route
*/
- never_default = !svTrueValue (ifcfg, "DEFROUTE", TRUE);
+ never_default = !svGetValueBoolean (ifcfg, "DEFROUTE", TRUE);
/* Then check if GATEWAYDEV; it's global and overrides DEFROUTE */
network_ifcfg = svOpenFile (network_file, NULL);
@@ -998,10 +1006,10 @@ make_ip4_setting (shvarFile *ifcfg,
g_object_set (s_ip4,
NM_SETTING_IP_CONFIG_METHOD, method,
- NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, !svTrueValue (ifcfg, "PEERDNS", TRUE),
- NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES, !svTrueValue (ifcfg, "PEERROUTES", TRUE),
+ NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, !svGetValueBoolean (ifcfg, "PEERDNS", TRUE),
+ NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES, !svGetValueBoolean (ifcfg, "PEERROUTES", TRUE),
NM_SETTING_IP_CONFIG_NEVER_DEFAULT, never_default,
- NM_SETTING_IP_CONFIG_MAY_FAIL, !svTrueValue (ifcfg, "IPV4_FAILURE_FATAL", FALSE),
+ NM_SETTING_IP_CONFIG_MAY_FAIL, !svGetValueBoolean (ifcfg, "IPV4_FAILURE_FATAL", FALSE),
NM_SETTING_IP_CONFIG_ROUTE_METRIC, svGetValueInt64 (ifcfg, "IPV4_ROUTE_METRIC", 10,
-1, G_MAXUINT32, -1),
NULL);
@@ -1017,7 +1025,7 @@ make_ip4_setting (shvarFile *ifcfg,
g_free (value);
g_object_set (s_ip4,
- NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, svTrueValue (ifcfg, "DHCP_SEND_HOSTNAME", TRUE),
+ NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, svGetValueBoolean (ifcfg, "DHCP_SEND_HOSTNAME", TRUE),
NM_SETTING_IP4_CONFIG_DHCP_TIMEOUT, svGetValueInt64 (ifcfg, "IPV4_DHCP_TIMEOUT", 10, 0, G_MAXUINT32, 0),
NULL);
@@ -1318,7 +1326,7 @@ make_ip6_setting (shvarFile *ifcfg,
* specified is IPV6_DEFROUTE=yes which means that this connection can be used
* as a default route
*/
- never_default = !svTrueValue (ifcfg, "IPV6_DEFROUTE", TRUE);
+ never_default = !svGetValueBoolean (ifcfg, "IPV6_DEFROUTE", TRUE);
/* Then check if IPV6_DEFAULTGW or IPV6_DEFAULTDEV is specified;
* they are global and override IPV6_DEFROUTE
@@ -1358,11 +1366,11 @@ make_ip6_setting (shvarFile *ifcfg,
/* Find out method property */
/* Is IPV6 enabled? Set method to "ignored", when not enabled */
str_value = svGetValue (ifcfg, "IPV6INIT", FALSE);
- ipv6init = svTrueValue (ifcfg, "IPV6INIT", FALSE);
+ ipv6init = svGetValueBoolean (ifcfg, "IPV6INIT", FALSE);
if (!str_value) {
network_ifcfg = svOpenFile (network_file, NULL);
if (network_ifcfg) {
- ipv6init = svTrueValue (network_ifcfg, "IPV6INIT", FALSE);
+ ipv6init = svGetValueBoolean (network_ifcfg, "IPV6INIT", FALSE);
svCloseFile (network_ifcfg);
}
}
@@ -1371,9 +1379,9 @@ make_ip6_setting (shvarFile *ifcfg,
if (!ipv6init)
method = NM_SETTING_IP6_CONFIG_METHOD_IGNORE; /* IPv6 is disabled */
else {
- ipv6forwarding = svTrueValue (ifcfg, "IPV6FORWARDING", FALSE);
- ipv6_autoconf = svTrueValue (ifcfg, "IPV6_AUTOCONF", !ipv6forwarding);
- dhcp6 = svTrueValue (ifcfg, "DHCPV6C", FALSE);
+ ipv6forwarding = svGetValueBoolean (ifcfg, "IPV6FORWARDING", FALSE);
+ ipv6_autoconf = svGetValueBoolean (ifcfg, "IPV6_AUTOCONF", !ipv6forwarding);
+ dhcp6 = svGetValueBoolean (ifcfg, "DHCPV6C", FALSE);
if (ipv6_autoconf)
method = NM_SETTING_IP6_CONFIG_METHOD_AUTO;
@@ -1395,12 +1403,12 @@ make_ip6_setting (shvarFile *ifcfg,
/* Read IPv6 Privacy Extensions configuration */
ip6_privacy_str = svGetValue (ifcfg, "IPV6_PRIVACY", FALSE);
if (ip6_privacy_str) {
- ip6_privacy = svTrueValue (ifcfg, "IPV6_PRIVACY", FALSE);
+ ip6_privacy = svGetValueBoolean (ifcfg, "IPV6_PRIVACY", FALSE);
if (!ip6_privacy)
ip6_privacy = g_strcmp0 (ip6_privacy_str, "rfc4941") == 0 ||
g_strcmp0 (ip6_privacy_str, "rfc3041") == 0;
}
- ip6_privacy_prefer_public_ip = svTrueValue (ifcfg, "IPV6_PRIVACY_PREFER_PUBLIC_IP", FALSE);
+ ip6_privacy_prefer_public_ip = svGetValueBoolean (ifcfg, "IPV6_PRIVACY_PREFER_PUBLIC_IP", FALSE);
ip6_privacy_val = ip6_privacy_str ?
(ip6_privacy ?
(ip6_privacy_prefer_public_ip ? NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR : NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR) :
@@ -1410,10 +1418,10 @@ make_ip6_setting (shvarFile *ifcfg,
g_object_set (s_ip6,
NM_SETTING_IP_CONFIG_METHOD, method,
- NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, !svTrueValue (ifcfg, "IPV6_PEERDNS", TRUE),
- NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES, !svTrueValue (ifcfg, "IPV6_PEERROUTES", TRUE),
+ NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, !svGetValueBoolean (ifcfg, "IPV6_PEERDNS", TRUE),
+ NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES, !svGetValueBoolean (ifcfg, "IPV6_PEERROUTES", TRUE),
NM_SETTING_IP_CONFIG_NEVER_DEFAULT, never_default,
- NM_SETTING_IP_CONFIG_MAY_FAIL, !svTrueValue (ifcfg, "IPV6_FAILURE_FATAL", FALSE),
+ NM_SETTING_IP_CONFIG_MAY_FAIL, !svGetValueBoolean (ifcfg, "IPV6_FAILURE_FATAL", FALSE),
NM_SETTING_IP_CONFIG_ROUTE_METRIC, svGetValueInt64 (ifcfg, "IPV6_ROUTE_METRIC", 10,
-1, G_MAXUINT32, -1),
NM_SETTING_IP6_CONFIG_IP6_PRIVACY, ip6_privacy_val,
@@ -1617,11 +1625,11 @@ read_dcb_flags (shvarFile *ifcfg, DcbFlagsProperty *property)
{
NMSettingDcbFlags flags = NM_SETTING_DCB_FLAG_NONE;
- if (svTrueValue (ifcfg, property->enable_key, FALSE))
+ if (svGetValueBoolean (ifcfg, property->enable_key, FALSE))
flags |= NM_SETTING_DCB_FLAG_ENABLE;
- if (svTrueValue (ifcfg, property->advertise_key, FALSE))
+ if (svGetValueBoolean (ifcfg, property->advertise_key, FALSE))
flags |= NM_SETTING_DCB_FLAG_ADVERTISE;
- if (svTrueValue (ifcfg, property->willing_key, FALSE))
+ if (svGetValueBoolean (ifcfg, property->willing_key, FALSE))
flags |= NM_SETTING_DCB_FLAG_WILLING;
return flags;
@@ -1851,7 +1859,7 @@ make_dcb_setting (shvarFile *ifcfg,
g_return_val_if_fail (out_setting != NULL, FALSE);
- dcb_on = !!svTrueValue (ifcfg, "DCB", FALSE);
+ dcb_on = !!svGetValueBoolean (ifcfg, "DCB", FALSE);
if (!dcb_on)
return TRUE;
@@ -2649,7 +2657,7 @@ eap_peap_reader (const char *eap_method,
}
}
- if (svTrueValue (ifcfg, "IEEE_8021X_PEAP_FORCE_NEW_LABEL", FALSE))
+ if (svGetValueBoolean (ifcfg, "IEEE_8021X_PEAP_FORCE_NEW_LABEL", FALSE))
g_object_set (s_8021x, NM_SETTING_802_1X_PHASE1_PEAPLABEL, "1", NULL);
anon_ident = svGetValue (ifcfg, "IEEE_8021X_ANON_IDENTITY", FALSE);
@@ -3090,9 +3098,9 @@ make_wpa_setting (shvarFile *ifcfg,
allow_wpa = svGetValue (ifcfg, "WPA_ALLOW_WPA", FALSE);
allow_rsn = svGetValue (ifcfg, "WPA_ALLOW_WPA2", FALSE);
- if (allow_wpa && svTrueValue (ifcfg, "WPA_ALLOW_WPA", TRUE))
+ if (allow_wpa && svGetValueBoolean (ifcfg, "WPA_ALLOW_WPA", TRUE))
nm_setting_wireless_security_add_proto (wsec, "wpa");
- if (allow_rsn && svTrueValue (ifcfg, "WPA_ALLOW_WPA2", TRUE))
+ if (allow_rsn && svGetValueBoolean (ifcfg, "WPA_ALLOW_WPA2", TRUE))
nm_setting_wireless_security_add_proto (wsec, "rsn");
/* If neither WPA_ALLOW_WPA or WPA_ALLOW_WPA2 were present, default
@@ -3462,12 +3470,12 @@ make_wireless_setting (shvarFile *ifcfg,
g_object_set (s_wireless,
NM_SETTING_WIRELESS_HIDDEN,
- svTrueValue (ifcfg, "SSID_HIDDEN", FALSE),
+ svGetValueBoolean (ifcfg, "SSID_HIDDEN", FALSE),
NULL);
g_object_set (s_wireless,
NM_SETTING_WIRELESS_POWERSAVE,
- svTrueValue (ifcfg, "POWERSAVE", FALSE) ? 1 : 0,
+ svGetValueBoolean (ifcfg, "POWERSAVE", FALSE) ? 1 : 0,
NULL);
return NM_SETTING (s_wireless);
@@ -3905,12 +3913,12 @@ make_infiniband_setting (shvarFile *ifcfg,
g_free (value);
}
- if (svTrueValue (ifcfg, "CONNECTED_MODE", FALSE))
+ if (svGetValueBoolean (ifcfg, "CONNECTED_MODE", FALSE))
g_object_set (s_infiniband, NM_SETTING_INFINIBAND_TRANSPORT_MODE, "connected", NULL);
else
g_object_set (s_infiniband, NM_SETTING_INFINIBAND_TRANSPORT_MODE, "datagram", NULL);
- if (svTrueValue (ifcfg, "PKEY", FALSE)) {
+ if (svGetValueBoolean (ifcfg, "PKEY", FALSE)) {
int p_key;
char *parent;
@@ -4447,7 +4455,7 @@ is_bond_device (const char *name, shvarFile *parsed)
g_return_val_if_fail (name != NULL, FALSE);
g_return_val_if_fail (parsed != NULL, FALSE);
- if (svTrueValue (parsed, "BONDING_MASTER", FALSE))
+ if (svGetValueBoolean (parsed, "BONDING_MASTER", FALSE))
return TRUE;
/* XXX: Check for "bond[\d]+"? */
@@ -4461,7 +4469,7 @@ is_vlan_device (const char *name, shvarFile *parsed)
g_return_val_if_fail (name != NULL, FALSE);
g_return_val_if_fail (parsed != NULL, FALSE);
- if (svTrueValue (parsed, "VLAN", FALSE))
+ if (svGetValueBoolean (parsed, "VLAN", FALSE))
return TRUE;
return FALSE;
@@ -4595,7 +4603,7 @@ make_vlan_setting (shvarFile *ifcfg,
g_object_set (s_vlan, NM_SETTING_VLAN_PARENT, parent, NULL);
g_clear_pointer (&parent, g_free);
- if (svTrueValue (ifcfg, "REORDER_HDR", FALSE))
+ if (svGetValueBoolean (ifcfg, "REORDER_HDR", FALSE))
vlan_flags |= NM_VLAN_FLAG_REORDER_HEADERS;
value = svGetValue (ifcfg, "VLAN_FLAGS", FALSE);
@@ -4811,7 +4819,7 @@ connection_from_file_full (const char *filename,
if (!parsed)
return NULL;
- if (!svTrueValue (parsed, "NM_CONTROLLED", TRUE)) {
+ if (!svGetValueBoolean (parsed, "NM_CONTROLLED", TRUE)) {
g_assert (out_unhandled != NULL);
connection = create_unhandled_connection (filename, parsed, "unmanaged", out_unhandled);
@@ -4890,7 +4898,7 @@ connection_from_file_full (const char *filename,
}
}
- if (svTrueValue (parsed, "BONDING_MASTER", FALSE) &&
+ if (svGetValueBoolean (parsed, "BONDING_MASTER", FALSE) &&
strcasecmp (type, TYPE_BOND)) {
g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
"BONDING_MASTER=yes key only allowed in TYPE=bond connections");
diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/settings/plugins/ifcfg-rh/shvar.c
index 225e3b995b..ffb0f8f7da 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.c
+++ b/src/settings/plugins/ifcfg-rh/shvar.c
@@ -298,35 +298,55 @@ svGetValueFull (shvarFile *s, const char *key, gboolean verbatim)
return value;
}
-/* return TRUE if <key> resolves to any truth value (e.g. "yes", "true", "y", "t", "1")
- * return FALSE if <key> resolves to any non-truth value (e.g. "no", "false", "n", "f", "0")
- * return <default> otherwise
+/**
+ * svParseBoolean:
+ * @value: the input string
+ * @fallback: the fallback value
+ *
+ * Parses a string and returns the boolean value it contains or,
+ * in case no valid value is found, the fallback value. Valid values
+ * are: "yes", "true", "t", "y", "1" and "no", "false", "f", "n", "0".
+ *
+ * Returns: the parsed boolean value or @fallback.
*/
gint
-svTrueValue (shvarFile *s, const char *key, gint def)
+svParseBoolean (const char *value, gint fallback)
{
- char *tmp;
- gint returnValue = def;
+ if (!value)
+ return fallback;
- tmp = svGetValue (s, key, FALSE);
- if (!tmp)
- return returnValue;
-
- if ( !g_ascii_strcasecmp ("yes", tmp)
- || !g_ascii_strcasecmp ("true", tmp)
- || !g_ascii_strcasecmp ("t", tmp)
- || !g_ascii_strcasecmp ("y", tmp)
- || !g_ascii_strcasecmp ("1", tmp))
- returnValue = TRUE;
- else if ( !g_ascii_strcasecmp ("no", tmp)
- || !g_ascii_strcasecmp ("false", tmp)
- || !g_ascii_strcasecmp ("f", tmp)
- || !g_ascii_strcasecmp ("n", tmp)
- || !g_ascii_strcasecmp ("0", tmp))
- returnValue = FALSE;
+ if ( !g_ascii_strcasecmp ("yes", value)
+ || !g_ascii_strcasecmp ("true", value)
+ || !g_ascii_strcasecmp ("t", value)
+ || !g_ascii_strcasecmp ("y", value)
+ || !g_ascii_strcasecmp ("1", value))
+ return TRUE;
+ else if ( !g_ascii_strcasecmp ("no", value)
+ || !g_ascii_strcasecmp ("false", value)
+ || !g_ascii_strcasecmp ("f", value)
+ || !g_ascii_strcasecmp ("n", value)
+ || !g_ascii_strcasecmp ("0", value))
+ return FALSE;
+
+ return fallback;
+}
- g_free (tmp);
- return returnValue;
+/* svGetValueBoolean:
+ * @s: fhe file
+ * @key: the name of the key to read
+ * @fallback: the fallback value in any error case
+ *
+ * Reads a value @key and converts it to a boolean using svParseBoolean().
+ *
+ * Returns: the parsed boolean value or @fallback.
+ */
+gint
+svGetValueBoolean (shvarFile *s, const char *key, gint fallback)
+{
+ gs_free char *tmp = NULL;
+
+ tmp = svGetValue (s, key, FALSE);
+ return svParseBoolean (tmp, fallback);
}
/* svGetValueInt64:
diff --git a/src/settings/plugins/ifcfg-rh/shvar.h b/src/settings/plugins/ifcfg-rh/shvar.h
index 876be3ade0..d51e07685f 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.h
+++ b/src/settings/plugins/ifcfg-rh/shvar.h
@@ -58,11 +58,13 @@ shvarFile *svOpenFile (const char *name, GError **error);
char *svGetValue (shvarFile *s, const char *key, gboolean verbatim);
char *svGetValueFull (shvarFile *s, const char *key, gboolean verbatim);
+gint svParseBoolean (const char *value, gint def);
+
/* return TRUE if <key> resolves to any truth value (e.g. "yes", "y", "true")
* return FALSE if <key> resolves to any non-truth value (e.g. "no", "n", "false")
* return <def> otherwise
*/
-gint svTrueValue (shvarFile *s, const char *key, gint def);
+gint svGetValueBoolean (shvarFile *s, const char *key, gint def);
gint64 svGetValueInt64 (shvarFile *s, const char *key, guint base, gint64 min, gint64 max, gint64 fallback);
diff --git a/src/settings/plugins/ifcfg-rh/writer.c b/src/settings/plugins/ifcfg-rh/writer.c
index e6e68bce5f..376f38c0bd 100644
--- a/src/settings/plugins/ifcfg-rh/writer.c
+++ b/src/settings/plugins/ifcfg-rh/writer.c
@@ -1772,6 +1772,18 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
FALSE);
}
+ switch (nm_setting_connection_get_lldp (s_con)) {
+ case NM_SETTING_CONNECTION_LLDP_ENABLE_RX:
+ tmp = "rx";
+ break;
+ case NM_SETTING_CONNECTION_LLDP_DISABLE:
+ tmp = "no";
+ break;
+ default:
+ tmp = NULL;
+ }
+ svSetValue (ifcfg, "LLDP", tmp, FALSE);
+
/* Permissions */
svSetValue (ifcfg, "USERS", NULL, FALSE);
n = nm_setting_connection_get_num_permissions (s_con);