nm-lldp-listener.c 27.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/* -*- 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.
 */

21
#include "nm-default.h"
22

23 24
#include "nm-lldp-listener.h"

25 26
#include <net/ethernet.h>

27
#include "platform/nm-platform.h"
28
#include "nm-utils/unaligned.h"
29 30
#include "nm-utils.h"

31
#include "systemd/nm-sd.h"
32

33
#define MAX_NEIGHBORS         4096
34
#define MIN_UPDATE_INTERVAL_NS (2 * NM_UTILS_NS_PER_SECOND)
35

36 37 38 39
#define LLDP_MAC_NEAREST_BRIDGE          ((const struct ether_addr *) ((uint8_t[ETH_ALEN]) { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e }))
#define LLDP_MAC_NEAREST_NON_TPMR_BRIDGE ((const struct ether_addr *) ((uint8_t[ETH_ALEN]) { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 }))
#define LLDP_MAC_NEAREST_CUSTOMER_BRIDGE ((const struct ether_addr *) ((uint8_t[ETH_ALEN]) { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 }))

40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
typedef enum {
	LLDP_ATTR_TYPE_NONE,
	LLDP_ATTR_TYPE_UINT32,
	LLDP_ATTR_TYPE_STRING,
} LldpAttrType;

typedef enum {
	/* the order of the enum values determines the order of the fields in
	 * the variant. */
	LLDP_ATTR_ID_PORT_DESCRIPTION,
	LLDP_ATTR_ID_SYSTEM_NAME,
	LLDP_ATTR_ID_SYSTEM_DESCRIPTION,
	LLDP_ATTR_ID_SYSTEM_CAPABILITIES,
	LLDP_ATTR_ID_IEEE_802_1_PVID,
	LLDP_ATTR_ID_IEEE_802_1_PPVID,
	LLDP_ATTR_ID_IEEE_802_1_PPVID_FLAGS,
	LLDP_ATTR_ID_IEEE_802_1_VID,
	LLDP_ATTR_ID_IEEE_802_1_VLAN_NAME,
	_LLDP_PROP_ID_COUNT,
} LldpAttrId;

typedef struct {
	LldpAttrType attr_type;
	union {
		guint32 v_uint32;
		char *v_string;
	};
} LldpAttrData;

69 70 71 72 73 74
/*****************************************************************************/

NM_GOBJECT_PROPERTIES_DEFINE (NMLldpListener,
	PROP_NEIGHBORS,
);

75 76 77 78 79
typedef struct {
	char         *iface;
	int           ifindex;
	sd_lldp      *lldp_handle;
	GHashTable   *lldp_neighbors;
80 81 82 83 84

	/* the timestamp in nsec until which we delay updates. */
	gint64        ratelimit_next;
	guint         ratelimit_id;

85 86 87
	GVariant     *variant;
} NMLldpListenerPrivate;

88 89 90 91 92 93 94 95
struct _NMLldpListener {
	GObject parent;
	NMLldpListenerPrivate _priv;
};

struct _NMLldpListenerClass {
	GObjectClass parent;
};
96 97 98

G_DEFINE_TYPE (NMLldpListener, nm_lldp_listener, G_TYPE_OBJECT)

99 100 101
#define NM_LLDP_LISTENER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMLldpListener, NM_IS_LLDP_LISTENER)

/*****************************************************************************/
102 103 104 105 106 107 108

typedef struct {
	guint8 chassis_id_type;
	guint8 port_id_type;
	char *chassis_id;
	char *port_id;

109
	struct ether_addr destination_address;
110

111 112
	bool valid:1;

113
	LldpAttrData attrs[_LLDP_PROP_ID_COUNT];
114 115

	GVariant *variant;
116
} LldpNeighbor;
117

118 119 120 121 122 123 124 125 126 127 128 129
/*****************************************************************************/

#define _NMLOG_PREFIX_NAME                "lldp"
#define _NMLOG_DOMAIN                     LOGD_DEVICE
#define _NMLOG(level, ...) \
    G_STMT_START { \
        const NMLogLevel _level = (level); \
        \
        if (nm_logging_enabled (_level, _NMLOG_DOMAIN)) { \
            char _sbuf[64]; \
            int _ifindex = (self) ? NM_LLDP_LISTENER_GET_PRIVATE (self)->ifindex : 0; \
            \
130
            _nm_log (_level, _NMLOG_DOMAIN, 0, \
131
                     _ifindex > 0 ? nm_platform_link_get_name (NM_PLATFORM_GET, _ifindex) : NULL, \
132
                     NULL, \
133 134 135 136 137 138 139 140 141 142 143
                     "%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
                     _NMLOG_PREFIX_NAME, \
                     ((_ifindex > 0) \
                        ? nm_sprintf_buf (_sbuf, "[%p,%d]", (self), _ifindex) \
                        : ((self) \
                            ? nm_sprintf_buf (_sbuf, "[%p]", (self)) \
                            : "")) \
                     _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
        } \
    } G_STMT_END \

Thomas Haller's avatar
Thomas Haller committed
144 145 146
#define LOG_NEIGH_FMT        "CHASSIS=%s%s%s PORT=%s%s%s"
#define LOG_NEIGH_ARG(neigh) NM_PRINT_FMT_QUOTE_STRING ((neigh)->chassis_id), NM_PRINT_FMT_QUOTE_STRING ((neigh)->port_id)

147 148
/*****************************************************************************/

149 150 151 152 153 154 155 156 157 158
static gboolean
ether_addr_equal (const struct ether_addr *a1, const struct ether_addr *a2)
{
	nm_assert (a1);
	nm_assert (a2);

	G_STATIC_ASSERT_EXPR (sizeof (*a1) == ETH_ALEN);
	return memcmp (a1, a2, ETH_ALEN) == 0;
}

159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
/*****************************************************************************/

NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_lldp_attr_id_to_name, LldpAttrId,
	NM_UTILS_LOOKUP_DEFAULT_WARN (NULL),
	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_PORT_DESCRIPTION,        NM_LLDP_ATTR_PORT_DESCRIPTION),
	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_SYSTEM_NAME,             NM_LLDP_ATTR_SYSTEM_NAME),
	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_SYSTEM_DESCRIPTION,      NM_LLDP_ATTR_SYSTEM_DESCRIPTION),
	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_SYSTEM_CAPABILITIES,     NM_LLDP_ATTR_SYSTEM_CAPABILITIES),
	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_PVID,         NM_LLDP_ATTR_IEEE_802_1_PVID),
	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_PPVID,        NM_LLDP_ATTR_IEEE_802_1_PPVID),
	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_PPVID_FLAGS,  NM_LLDP_ATTR_IEEE_802_1_PPVID_FLAGS),
	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_VID,          NM_LLDP_ATTR_IEEE_802_1_VID),
	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_IEEE_802_1_VLAN_NAME,    NM_LLDP_ATTR_IEEE_802_1_VLAN_NAME),
	NM_UTILS_LOOKUP_ITEM_IGNORE (_LLDP_PROP_ID_COUNT),
);

_NM_UTILS_LOOKUP_DEFINE (static, _lldp_attr_id_to_type, LldpAttrId, LldpAttrType,
	NM_UTILS_LOOKUP_DEFAULT_WARN (LLDP_ATTR_TYPE_NONE),
	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_PORT_DESCRIPTION,            LLDP_ATTR_TYPE_STRING),
	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_SYSTEM_NAME,                 LLDP_ATTR_TYPE_STRING),
	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_SYSTEM_DESCRIPTION,          LLDP_ATTR_TYPE_STRING),
	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_SYSTEM_CAPABILITIES,         LLDP_ATTR_TYPE_UINT32),
	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_1_PVID,             LLDP_ATTR_TYPE_UINT32),
	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_1_PPVID,            LLDP_ATTR_TYPE_UINT32),
	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_1_PPVID_FLAGS,      LLDP_ATTR_TYPE_UINT32),
	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_1_VID,              LLDP_ATTR_TYPE_UINT32),
	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_IEEE_802_1_VLAN_NAME,        LLDP_ATTR_TYPE_STRING),
	NM_UTILS_LOOKUP_ITEM_IGNORE (_LLDP_PROP_ID_COUNT),
);

static void
_lldp_attr_set_str (LldpAttrData *pdata, LldpAttrId attr_id, const char *v_string)
191
{
192 193 194 195
	nm_assert (pdata);
	nm_assert (_lldp_attr_id_to_type (attr_id) == LLDP_ATTR_TYPE_STRING);

	pdata = &pdata[attr_id];
196

197 198 199 200 201
	/* we ignore duplicate fields silently. */
	if (pdata->attr_type != LLDP_ATTR_TYPE_NONE)
		return;
	pdata->attr_type = LLDP_ATTR_TYPE_STRING;
	pdata->v_string = g_strdup (v_string ?: "");
202 203
}

204 205
static void
_lldp_attr_set_str_ptr (LldpAttrData *pdata, LldpAttrId attr_id, const void *str, gsize len)
206 207 208 209 210
{
	const char *s = str;
	const char *tmp;
	gsize len0 = len;
	gs_free char *str_free = NULL;
211 212 213 214 215 216 217 218 219 220 221

	nm_assert (pdata);
	nm_assert (_lldp_attr_id_to_type (attr_id) == LLDP_ATTR_TYPE_STRING);

	pdata = &pdata[attr_id];

	/* we ignore duplicate fields silently. */
	if (pdata->attr_type != LLDP_ATTR_TYPE_NONE)
		return;

	pdata->attr_type = LLDP_ATTR_TYPE_STRING;
222 223 224 225 226 227

	/* truncate at first NUL, including removing trailing NULs*/
	tmp = memchr (s, '\0', len);
	if (tmp)
		len = tmp - s;

228 229 230 231
	if (!len) {
		pdata->v_string = g_strdup ("");
		return;
	}
232 233 234 235 236 237

	if (len0 <= len || s[len] != '\0') {
		/* hmpf, g_strescape needs a trailing NUL. Need to clone */
		s = str_free = g_strndup (s, len);
	}

238
	pdata->v_string = g_strescape (s, NULL);
239 240
}

241 242
static void
_lldp_attr_set_uint32 (LldpAttrData *pdata, LldpAttrId attr_id, guint32 v_uint32)
243
{
244 245
	nm_assert (pdata);
	nm_assert (_lldp_attr_id_to_type (attr_id) == LLDP_ATTR_TYPE_UINT32);
246

247
	pdata = &pdata[attr_id];
248

249 250 251 252 253
	/* we ignore duplicate fields silently. */
	if (pdata->attr_type != LLDP_ATTR_TYPE_NONE)
		return;
	pdata->attr_type = LLDP_ATTR_TYPE_UINT32;
	pdata->v_uint32 = v_uint32;
254 255
}

256 257
/*****************************************************************************/

258 259 260
static guint
lldp_neighbor_id_hash (gconstpointer ptr)
{
261
	const LldpNeighbor *neigh = ptr;
262 263 264
	NMHashState h;

	nm_hash_init (&h, 23423423u);
265 266
	nm_hash_update_str0 (&h, neigh->chassis_id);
	nm_hash_update_str0 (&h, neigh->port_id);
267 268 269
	nm_hash_update_vals (&h,
	                     neigh->chassis_id_type,
	                     neigh->port_id_type);
270
	return nm_hash_complete (&h);
271 272
}

273
static int
274
lldp_neighbor_id_cmp (const LldpNeighbor *x, const LldpNeighbor *y)
275
{
276 277 278 279 280 281 282 283 284 285 286 287 288
	NM_CMP_SELF (x, y);
	NM_CMP_FIELD (x, y, chassis_id_type);
	NM_CMP_FIELD (x, y, port_id_type);
	NM_CMP_FIELD_STR0 (x, y, chassis_id);
	NM_CMP_FIELD_STR0 (x, y, port_id);
	return 0;
}

static int
lldp_neighbor_id_cmp_p (gconstpointer a, gconstpointer b, gpointer user_data)
{
	return lldp_neighbor_id_cmp (*((const LldpNeighbor *const*) a),
	                             *((const LldpNeighbor *const*) b));
289
}
290

291 292 293 294
static gboolean
lldp_neighbor_id_equal (gconstpointer a, gconstpointer b)
{
	return lldp_neighbor_id_cmp (a, b) == 0;
295 296 297
}

static void
298
lldp_neighbor_free (LldpNeighbor *neighbor)
299
{
300 301
	LldpAttrId attr_id;

302 303 304
	if (neighbor) {
		g_free (neighbor->chassis_id);
		g_free (neighbor->port_id);
305 306 307 308
		for (attr_id = 0; attr_id < _LLDP_PROP_ID_COUNT; attr_id++) {
			if (neighbor->attrs[attr_id].attr_type == LLDP_ATTR_TYPE_STRING)
				g_free (neighbor->attrs[attr_id].v_string);
		}
309
		g_clear_pointer (&neighbor->variant, g_variant_unref);
310
		g_slice_free (LldpNeighbor, neighbor);
311 312 313
	}
}

314
static void
315
lldp_neighbor_freep (LldpNeighbor **ptr)
316 317 318 319
{
	lldp_neighbor_free (*ptr);
}

320
static gboolean
321
lldp_neighbor_equal (LldpNeighbor *a, LldpNeighbor *b)
322
{
323
	LldpAttrId attr_id;
324

325 326
	nm_assert (a);
	nm_assert (b);
327 328 329

	if (   a->chassis_id_type != b->chassis_id_type
	    || a->port_id_type != b->port_id_type
330
	    || ether_addr_equal (&a->destination_address, &b->destination_address)
331 332
	    || !nm_streq0 (a->chassis_id, b->chassis_id)
	    || !nm_streq0 (a->port_id, b->port_id))
333 334
		return FALSE;

335 336
	for (attr_id = 0; attr_id < _LLDP_PROP_ID_COUNT; attr_id++) {
		if (a->attrs[attr_id].attr_type != b->attrs[attr_id].attr_type)
337
			return FALSE;
338 339 340
		switch (a->attrs[attr_id].attr_type) {
		case LLDP_ATTR_TYPE_UINT32:
			if (a->attrs[attr_id].v_uint32 != b->attrs[attr_id].v_uint32)
341
				return FALSE;
342 343 344
			break;
		case LLDP_ATTR_TYPE_STRING:
			if (!nm_streq (a->attrs[attr_id].v_string, b->attrs[attr_id].v_string))
345
				return FALSE;
346 347 348 349 350
			break;
		default:
			nm_assert (a->attrs[attr_id].attr_type == LLDP_ATTR_TYPE_NONE);
			break;
		}
351 352 353 354 355
	}

	return TRUE;
}

356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
static LldpNeighbor *
lldp_neighbor_new (sd_lldp_neighbor *neighbor_sd, GError **error)
{
	nm_auto (lldp_neighbor_freep) LldpNeighbor *neigh = NULL;
	uint8_t chassis_id_type, port_id_type;
	uint16_t data16;
	uint8_t *data8;
	const void *chassis_id, *port_id;
	gsize chassis_id_len, port_id_len, len;
	const char *str;
	int r;

	r = sd_lldp_neighbor_get_chassis_id (neighbor_sd, &chassis_id_type,
	                                     &chassis_id, &chassis_id_len);
	if (r < 0) {
		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
372
		             "failed reading chassis-id: %s", nm_strerror_native (-r));
373 374 375 376 377 378 379 380 381 382 383 384
		return NULL;
	}
	if (chassis_id_len < 1) {
		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
		             "empty chassis-id");
		return NULL;
	}

	r = sd_lldp_neighbor_get_port_id (neighbor_sd, &port_id_type,
	                                  &port_id, &port_id_len);
	if (r < 0) {
		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
385
		             "failed reading port-id: %s", nm_strerror_native (-r));
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
		return NULL;
	}
	if (port_id_len < 1) {
		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
		             "empty port-id");
		return NULL;
	}

	neigh = g_slice_new0 (LldpNeighbor);
	neigh->chassis_id_type = chassis_id_type;
	neigh->port_id_type = port_id_type;

	r = sd_lldp_neighbor_get_destination_address (neighbor_sd, &neigh->destination_address);
	if (r < 0) {
		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
401
		             "failed getting destination address: %s", nm_strerror_native (-r));
402
		goto out;
403 404 405
	}

	switch (chassis_id_type) {
406 407 408 409
	case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS:
	case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME:
	case SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED:
	case SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT:
410 411
		neigh->chassis_id = g_strndup ((const char *) chassis_id, chassis_id_len);
		break;
412
	case SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS:
413 414 415 416 417
		neigh->chassis_id = nm_utils_hwaddr_ntoa (chassis_id, chassis_id_len);
		break;
	default:
		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
		             "unsupported chassis-id type %d", chassis_id_type);
418
		goto out;
419 420 421
	}

	switch (port_id_type) {
422 423 424 425
	case SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS:
	case SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME:
	case SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED:
	case SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT:
426 427
		neigh->port_id = strndup ((char *) port_id, port_id_len);
		break;
428
	case SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS:
429 430 431 432 433
		neigh->port_id = nm_utils_hwaddr_ntoa (port_id, port_id_len);
		break;
	default:
		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
		             "unsupported port-id type %d", port_id_type);
434
		goto out;
435 436
	}

437 438
	if (sd_lldp_neighbor_get_port_description (neighbor_sd, &str) == 0)
		_lldp_attr_set_str (neigh->attrs, LLDP_ATTR_ID_PORT_DESCRIPTION, str);
439

440 441
	if (sd_lldp_neighbor_get_system_name (neighbor_sd, &str) == 0)
		_lldp_attr_set_str (neigh->attrs, LLDP_ATTR_ID_SYSTEM_NAME, str);
442

443 444
	if (sd_lldp_neighbor_get_system_description (neighbor_sd, &str) == 0)
		_lldp_attr_set_str (neigh->attrs, LLDP_ATTR_ID_SYSTEM_DESCRIPTION, str);
445

446 447
	if (sd_lldp_neighbor_get_system_capabilities (neighbor_sd, &data16) == 0)
		_lldp_attr_set_uint32 (neigh->attrs, LLDP_ATTR_ID_SYSTEM_CAPABILITIES, data16);
448 449 450 451

	r = sd_lldp_neighbor_tlv_rewind (neighbor_sd);
	if (r < 0) {
		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
452
		             "failed reading tlv (rewind): %s", nm_strerror_native (-r));
453
		goto out;
454 455 456 457 458 459 460 461 462 463
	}
	do {
		guint8 oui[3];
		guint8 subtype;

		r = sd_lldp_neighbor_tlv_get_oui (neighbor_sd, oui, &subtype);
		if (r < 0) {
			if (r == -ENXIO)
				continue;
			g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
464
			             "failed reading tlv: %s", nm_strerror_native (-r));
465
			goto out;
466 467
		}

468
		if (!(   memcmp (oui, SD_LLDP_OUI_802_1, sizeof (oui)) == 0
469
		      && NM_IN_SET (subtype,
470 471 472
		                    SD_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID,
		                    SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID,
		                    SD_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME)))
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
			continue;

		if (sd_lldp_neighbor_tlv_get_raw (neighbor_sd, (void *) &data8, &len) < 0)
			continue;

		/* skip over leading TLV, OUI and subtype */
#ifdef WITH_MORE_ASSERTS
		{
			guint8 check_hdr[] = {
				0xfe | (((len - 2) >> 8) & 0x01), ((len - 2) & 0xFF),
				oui[0], oui[1], oui[2],
				subtype
			};

			nm_assert (len > 2 + 3 +1);
			nm_assert (memcmp (data8, check_hdr, sizeof check_hdr) == 0);
		}
#endif
		if (len <= 6)
			continue;
		data8 += 6;
		len -= 6;

496
		/*if (memcmp (oui, SD_LLDP_OUI_802_1, sizeof (oui)) == 0)*/
497 498
		{
			switch (subtype) {
499
			case SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID:
500 501
				if (len != 2)
					continue;
502
				_lldp_attr_set_uint32 (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_PVID,
503
				                       unaligned_read_be16 (data8));
504
				break;
505
			case SD_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID:
506 507
				if (len != 3)
					continue;
508
				_lldp_attr_set_uint32 (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_PPVID_FLAGS,
509
				                       data8[0]);
510
				_lldp_attr_set_uint32 (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_PPVID,
511
				                       unaligned_read_be16 (&data8[1]));
512
				break;
513
			case SD_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME: {
514 515 516 517 518 519 520 521
				int l;

				if (len <= 3)
					continue;

				l = data8[2];
				if (len != 3 + l)
					continue;
522 523
				if (l > 32)
					continue;
524

525
				_lldp_attr_set_uint32 (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_VID,
526
				                       unaligned_read_be16 (&data8[0]));
527
				_lldp_attr_set_str_ptr (neigh->attrs, LLDP_ATTR_ID_IEEE_802_1_VLAN_NAME,
528
				                        &data8[3], l);
529 530 531 532 533 534 535 536
				break;
			}
			default:
				g_assert_not_reached ();
			}
		}
	} while (sd_lldp_neighbor_tlv_next (neighbor_sd) > 0);

537 538 539
	neigh->valid = TRUE;

out:
540
	return g_steal_pointer (&neigh);
541 542
}

543 544 545 546 547
static GVariant *
lldp_neighbor_to_variant (LldpNeighbor *neigh)
{
	GVariantBuilder builder;
	const char *dest_str;
548
	LldpAttrId attr_id;
549

550 551 552
	if (neigh->variant)
		return neigh->variant;

553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581
	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));

	g_variant_builder_add (&builder, "{sv}",
	                       NM_LLDP_ATTR_CHASSIS_ID_TYPE,
	                       g_variant_new_uint32 (neigh->chassis_id_type));
	g_variant_builder_add (&builder, "{sv}",
	                       NM_LLDP_ATTR_CHASSIS_ID,
	                       g_variant_new_string (neigh->chassis_id));
	g_variant_builder_add (&builder, "{sv}",
	                       NM_LLDP_ATTR_PORT_ID_TYPE,
	                       g_variant_new_uint32 (neigh->port_id_type));
	g_variant_builder_add (&builder, "{sv}",
	                       NM_LLDP_ATTR_PORT_ID,
	                       g_variant_new_string (neigh->port_id));

	if (ether_addr_equal (&neigh->destination_address, LLDP_MAC_NEAREST_BRIDGE))
		dest_str = NM_LLDP_DEST_NEAREST_BRIDGE;
	else if (ether_addr_equal (&neigh->destination_address, LLDP_MAC_NEAREST_NON_TPMR_BRIDGE))
		dest_str = NM_LLDP_DEST_NEAREST_NON_TPMR_BRIDGE;
	else if (ether_addr_equal (&neigh->destination_address, LLDP_MAC_NEAREST_CUSTOMER_BRIDGE))
		dest_str = NM_LLDP_DEST_NEAREST_CUSTOMER_BRIDGE;
	else
		dest_str = NULL;
	if (dest_str) {
		g_variant_builder_add (&builder, "{sv}",
		                       NM_LLDP_ATTR_DESTINATION,
		                       g_variant_new_string (dest_str));
	}

582 583
	for (attr_id = 0; attr_id < _LLDP_PROP_ID_COUNT; attr_id++) {
		const LldpAttrData *data = &neigh->attrs[attr_id];
584

585 586 587
		nm_assert (NM_IN_SET (data->attr_type, _lldp_attr_id_to_type (attr_id), LLDP_ATTR_TYPE_NONE));
		switch (data->attr_type) {
		case LLDP_ATTR_TYPE_UINT32:
588
			g_variant_builder_add (&builder, "{sv}",
589 590 591 592
			                       _lldp_attr_id_to_name (attr_id),
			                       g_variant_new_uint32 (data->v_uint32));
			break;
		case LLDP_ATTR_TYPE_STRING:
593
			g_variant_builder_add (&builder, "{sv}",
594 595 596 597 598
			                       _lldp_attr_id_to_name (attr_id),
			                       g_variant_new_string (data->v_string));
			break;
		default:
			break;
599 600 601
		}
	}

602
	return (neigh->variant = g_variant_ref_sink (g_variant_builder_end (&builder)));
603 604
}

605 606
/*****************************************************************************/

607 608 609 610 611 612 613
static void
data_changed_notify (NMLldpListener *self, NMLldpListenerPrivate *priv)
{
	nm_clear_g_variant (&priv->variant);
	_notify (self, PROP_NEIGHBORS);
}

614
static gboolean
615
data_changed_timeout (gpointer user_data)
616
{
617 618 619 620 621 622
	NMLldpListener *self = user_data;
	NMLldpListenerPrivate *priv;

	g_return_val_if_fail (NM_IS_LLDP_LISTENER (self), G_SOURCE_REMOVE);

	priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
623

624 625 626 627 628
	priv->ratelimit_id = 0;
	priv->ratelimit_next = nm_utils_get_monotonic_timestamp_ns() + MIN_UPDATE_INTERVAL_NS;
	data_changed_notify (self, priv);
	return G_SOURCE_REMOVE;
}
629

630 631 632 633 634 635 636 637 638 639 640 641 642 643
static void
data_changed_schedule (NMLldpListener *self)
{
	NMLldpListenerPrivate *priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
	gint64 now;

	now = nm_utils_get_monotonic_timestamp_ns ();
	if (now >= priv->ratelimit_next) {
		nm_clear_g_source (&priv->ratelimit_id);
		priv->ratelimit_next = now + MIN_UPDATE_INTERVAL_NS;
		data_changed_notify (self, priv);
	} else if (!priv->ratelimit_id)
		priv->ratelimit_id = g_timeout_add (NM_UTILS_NS_TO_MSEC_CEIL (priv->ratelimit_next - now), data_changed_timeout, self);
}
644

645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690
static void
process_lldp_neighbor (NMLldpListener *self, sd_lldp_neighbor *neighbor_sd, gboolean neighbor_valid)
{
	NMLldpListenerPrivate *priv;
	nm_auto (lldp_neighbor_freep) LldpNeighbor *neigh = NULL;
	LldpNeighbor *neigh_old;
	gs_free_error GError *parse_error = NULL;
	GError **p_parse_error;
	gboolean changed = FALSE;

	g_return_if_fail (NM_IS_LLDP_LISTENER (self));

	priv = NM_LLDP_LISTENER_GET_PRIVATE (self);

	g_return_if_fail (priv->lldp_handle);
	g_return_if_fail (neighbor_sd);

	p_parse_error = _LOGT_ENABLED () ? &parse_error : NULL;

	neigh = lldp_neighbor_new (neighbor_sd, p_parse_error);
	if (!neigh) {
		_LOGT ("process: failed to parse neighbor: %s", parse_error->message);
		return;
	}

	if (!neigh->valid)
		neighbor_valid = FALSE;

	neigh_old = g_hash_table_lookup (priv->lldp_neighbors, neigh);
	if (neigh_old) {
		if (!neighbor_valid) {
			_LOGT ("process: %s neigh: "LOG_NEIGH_FMT"%s%s%s",
			       "remove", LOG_NEIGH_ARG (neigh),
			       NM_PRINT_FMT_QUOTED (parse_error, " (failed to parse: ", parse_error->message, ")", ""));

			g_hash_table_remove (priv->lldp_neighbors, neigh_old);
			changed = TRUE;
			goto done;
		} else if (lldp_neighbor_equal (neigh_old, neigh))
			return;
	} else if (!neighbor_valid) {
		if (parse_error)
			_LOGT ("process: failed to parse neighbor: %s", parse_error->message);
		return;
	}

691
	/* ensure that we have at most MAX_NEIGHBORS entries */
692 693 694 695 696 697 698 699 700 701 702
	if (   !neigh_old /* only matters in the "add" case. */
	    && (g_hash_table_size (priv->lldp_neighbors) + 1 > MAX_NEIGHBORS)) {
		_LOGT ("process: ignore neighbor due to overall limit of %d", MAX_NEIGHBORS);
		return;
	}

	_LOGD ("process: %s neigh: "LOG_NEIGH_FMT,
	        neigh_old ? "update" : "new",
	        LOG_NEIGH_ARG (neigh));

	changed = TRUE;
703
	g_hash_table_add (priv->lldp_neighbors, g_steal_pointer (&neigh));
704 705 706 707

done:
	if (changed)
		data_changed_schedule (self);
708 709 710
}

static void
711
lldp_event_handler (sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata)
712
{
713
	process_lldp_neighbor (userdata, n, event != SD_LLDP_EVENT_REMOVED);
714 715 716
}

gboolean
717
nm_lldp_listener_start (NMLldpListener *self, int ifindex, GError **error)
718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733
{
	NMLldpListenerPrivate *priv;
	int ret;

	g_return_val_if_fail (NM_IS_LLDP_LISTENER (self), FALSE);
	g_return_val_if_fail (ifindex > 0, FALSE);
	g_return_val_if_fail (!error || !*error, FALSE);

	priv = NM_LLDP_LISTENER_GET_PRIVATE (self);

	if (priv->lldp_handle) {
		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
		                     "already running");
		return FALSE;
	}

734
	ret = sd_lldp_new (&priv->lldp_handle);
735
	if (ret < 0) {
736 737 738 739 740
		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
		                     "initialization failed");
		return FALSE;
	}

741 742 743 744 745 746 747
	ret = sd_lldp_set_ifindex (priv->lldp_handle, ifindex);
	if (ret < 0) {
		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
		                     "failed setting ifindex");
		goto err;
	}

748
	ret = sd_lldp_set_callback (priv->lldp_handle, lldp_event_handler, self);
749
	if (ret < 0) {
750
		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
751 752
		                     "set callback failed");
		goto err;
753 754
	}

755 756 757
	priv->ifindex = ifindex;

	ret = sd_lldp_attach_event (priv->lldp_handle, NULL, 0);
758
	if (ret < 0) {
759
		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
760 761
		                     "attach event failed");
		goto err_free;
762 763 764
	}

	ret = sd_lldp_start (priv->lldp_handle);
765
	if (ret < 0) {
766 767 768 769 770
		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
		                     "start failed");
		goto err;
	}

771
	_LOGD ("start");
772

773 774 775 776 777
	return TRUE;

err:
	sd_lldp_detach_event (priv->lldp_handle);
err_free:
778
	sd_lldp_unref (priv->lldp_handle);
779
	priv->lldp_handle = NULL;
780
	priv->ifindex = 0;
781 782 783 784 785 786 787 788
	return FALSE;
}

void
nm_lldp_listener_stop (NMLldpListener *self)
{
	NMLldpListenerPrivate *priv;
	guint size;
789
	gboolean changed = FALSE;
790 791 792 793 794

	g_return_if_fail (NM_IS_LLDP_LISTENER (self));
	priv = NM_LLDP_LISTENER_GET_PRIVATE (self);

	if (priv->lldp_handle) {
795
		_LOGD ("stop");
796 797
		sd_lldp_stop (priv->lldp_handle);
		sd_lldp_detach_event (priv->lldp_handle);
798
		sd_lldp_unref (priv->lldp_handle);
799 800 801 802
		priv->lldp_handle = NULL;

		size = g_hash_table_size (priv->lldp_neighbors);
		g_hash_table_remove_all (priv->lldp_neighbors);
803 804
		if (size || priv->ratelimit_id)
			changed = TRUE;
805 806
	}

807 808
	nm_clear_g_source (&priv->ratelimit_id);
	priv->ratelimit_next = 0;
809
	priv->ifindex = 0;
810 811 812

	if (changed)
		data_changed_notify (self, priv);
813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830
}

gboolean
nm_lldp_listener_is_running (NMLldpListener *self)
{
	NMLldpListenerPrivate *priv;

	g_return_val_if_fail (NM_IS_LLDP_LISTENER (self), FALSE);

	priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
	return !!priv->lldp_handle;
}

GVariant *
nm_lldp_listener_get_neighbors (NMLldpListener *self)
{
	NMLldpListenerPrivate *priv;

831 832
	g_return_val_if_fail (NM_IS_LLDP_LISTENER (self), FALSE);

833 834
	priv = NM_LLDP_LISTENER_GET_PRIVATE (self);

835 836 837 838 839
	if (G_UNLIKELY (!priv->variant)) {
		GVariantBuilder array_builder;
		gs_free LldpNeighbor **neighbors = NULL;
		guint i, n;

840
		g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("aa{sv}"));
841 842 843 844 845 846
		neighbors = (LldpNeighbor **) nm_utils_hash_keys_to_array (priv->lldp_neighbors,
		                                                           lldp_neighbor_id_cmp_p,
		                                                           NULL,
		                                                           &n);
		for (i = 0; i < n; i++)
			g_variant_builder_add_value (&array_builder, lldp_neighbor_to_variant (neighbors[i]));
847 848
		priv->variant = g_variant_ref_sink (g_variant_builder_end (&array_builder));
	}
849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874
	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,
875
	                                              (GDestroyNotify) lldp_neighbor_free, NULL);
876 877

	_LOGT ("lldp listener created");
878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904
}

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);

905 906
	_LOGT ("lldp listener destroyed");

907 908 909 910 911 912 913 914 915 916 917 918
	G_OBJECT_CLASS (nm_lldp_listener_parent_class)->finalize (object);
}

static void
nm_lldp_listener_class_init (NMLldpListenerClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	object_class->dispose = dispose;
	object_class->finalize = finalize;
	object_class->get_property = get_property;

919 920 921 922 923 924 925 926
	obj_properties[PROP_NEIGHBORS] =
	    g_param_spec_variant (NM_LLDP_LISTENER_NEIGHBORS, "", "",
	                          G_VARIANT_TYPE ("aa{sv}"),
	                          NULL,
	                          G_PARAM_READABLE |
	                          G_PARAM_STATIC_STRINGS);

	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
927 928
}