sd-lldp: rework sd-lldp API

This reworks the sd-lldp substantially, simplifying things on one hand, and
extending the logic a bit on the other.

Specifically:

- Besides the sd_lldp object only one other object is maintained now,
  sd_lldp_neighbor. It's used both as storage for literal LLDP packets, and for
  maintainging info about peers in the database. Separation between packet, TLV
  and chassis data is not maintained anymore. This should be a major
  simplification.

- The sd-lldp API has been extended so that a couple of per-neighbor fields may
  be queried directly, without iterating through the object. Other fields that
  may appear multiple times, OTOH have to be iterated through.

- The maximum number of entries in the neighbor database is now configurable
  during runtime.

- The generation of callbacks from sd_lldp objects is more restricted:
  callbacks are only invoked when actual data changed.

- The TTL information is now hooked with a timer event, so that removals from
  the neighbor database due to TTLs now result in a callback event.

- Querying LLDP neighbor database will now return a strictly ordered array, to
  guarantee stability.

- A "capabilities" mask may now be configured, that selects what type of LLDP
  neighbor data is collected. This may be used to restrict collection of LLDP
  info about routers instead of all neighbors. This is now exposed via
  networkd's LLDP= setting.

- sd-lldp's API to serialize the collected data to text files has been removed.
  Instead, there's now an API to extract the raw binary data from LLDP neighbor
  objects, as well as one to convert this raw binary data back to an LLDP
  neighbor object. networkd will save this raw binary data to /run now, and the
  client side can simply parse the information.

- support for parsing the more exotic TLVs has been removed, since we are not
  using that. Instead there are now APIs to extract the raw data from TLVs.
  Given how easy it is to parse the TLVs clients should do so now directly
  instead of relying on our APIs for that.

- A lot of the APIs that parse out LLDP strings have been simplified so that
  they actually return strings, instead of char arrays with a length. To deal
  with possibly dangerous characters the strings are escaped if needed.

- APIs to extract and format the chassis and port IDs as strings has been
  added.

- lldp.h has been simplified a lot. The enums are anonymous now, since they
  were never used as enums, but simply as constants. Most definitions we don't
  actually use ourselves have eben removed.
This commit is contained in:
Lennart Poettering 2016-02-19 17:58:52 +01:00
parent 1b4cd0cf11
commit 34437b4f9c
19 changed files with 1506 additions and 2282 deletions

View File

@ -3299,12 +3299,10 @@ libsystemd_network_la_SOURCES = \
src/libsystemd-network/dhcp-identifier.h \
src/libsystemd-network/dhcp-identifier.c \
src/libsystemd-network/lldp.h \
src/libsystemd-network/lldp-tlv.h \
src/libsystemd-network/lldp-tlv.c \
src/libsystemd-network/lldp-network.h \
src/libsystemd-network/lldp-network.c \
src/libsystemd-network/lldp-internal.h \
src/libsystemd-network/lldp-internal.c \
src/libsystemd-network/lldp-neighbor.h \
src/libsystemd-network/lldp-neighbor.c \
src/libsystemd-network/sd-lldp.c
libsystemd_network_la_LIBADD = \
@ -3387,9 +3385,6 @@ test_dhcp6_client_LDADD = \
libshared.la
test_lldp_SOURCES = \
src/libsystemd-network/lldp.h \
src/libsystemd-network/lldp-tlv.h \
src/libsystemd-network/lldp-tlv.c \
src/libsystemd-network/test-lldp.c
test_lldp_LDADD = \

View File

@ -335,7 +335,13 @@
<varlistentry>
<term><varname>LLDP=</varname></term>
<listitem>
<para>A boolean. When true, enables LLDP link receive support.
<para>Controls support for LLDP reception. Accepts a boolean or the special value
<literal>routers-only</literal>. When true, incoming LLDP packets are accepted and a database of all LLDP
neighbors maintained. If <literal>routers-only</literal> is set only LLDP data of various types of routers
is collected and LLDP data about other types of devices ignored (such as stations, telephones and
others). If false, LLDP reception is disabled. Defaults to <literal>false</literal>. Use
<citerefentry><refentrytitle>networkctl</refentrytitle><manvolnum>1</manvolnum></citerefentry> to query the
collected neighbor data.
</para>
</listitem>
</varlistentry>

View File

@ -1,360 +0,0 @@
/***
This file is part of systemd.
Copyright (C) 2014 Tom Gundersen
Copyright (C) 2014 Susant Sahani
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "sd-lldp.h"
#include "alloc-util.h"
#include "lldp-internal.h"
/* We store maximum 1K chassis entries */
#define LLDP_MIB_MAX_CHASSIS 1024
/* Maximum Ports can be attached to any chassis */
#define LLDP_MIB_MAX_PORT_PER_CHASSIS 32
/* 10.5.5.2.2 mibUpdateObjects ()
* The mibUpdateObjects () procedure updates the MIB objects corresponding to
* the TLVs contained in the received LLDPDU for the LLDP remote system
* indicated by the LLDP remote systems update process defined in 10.3.5 */
int lldp_mib_update_objects(lldp_chassis *c, tlv_packet *tlv) {
lldp_neighbour_port *p;
uint16_t length, ttl;
uint8_t *data;
uint8_t type;
int r;
assert_return(c, -EINVAL);
assert_return(tlv, -EINVAL);
r = sd_lldp_packet_read_port_id(tlv, &type, &data, &length);
if (r < 0)
return r;
/* Update the packet if we already have */
LIST_FOREACH(port, p, c->ports) {
if ((p->type == type && p->length == length && !memcmp(p->data, data, p->length))) {
r = sd_lldp_packet_read_ttl(tlv, &ttl);
if (r < 0)
return r;
p->until = ttl * USEC_PER_SEC + now(clock_boottime_or_monotonic());
sd_lldp_packet_unref(p->packet);
p->packet = tlv;
prioq_reshuffle(p->c->by_expiry, p, &p->prioq_idx);
return 0;
}
}
return -1;
}
int lldp_mib_remove_objects(lldp_chassis *c, tlv_packet *tlv) {
lldp_neighbour_port *p, *q;
uint8_t *data;
uint16_t length;
uint8_t type;
int r;
assert_return(c, -EINVAL);
assert_return(tlv, -EINVAL);
r = sd_lldp_packet_read_port_id(tlv, &type, &data, &length);
if (r < 0)
return r;
LIST_FOREACH_SAFE(port, p, q, c->ports) {
/* Find the port */
if (p->type == type && p->length == length && !memcmp(p->data, data, p->length)) {
lldp_neighbour_port_remove_and_free(p);
break;
}
}
return 0;
}
int lldp_mib_add_objects(Prioq *by_expiry,
Hashmap *neighbour_mib,
tlv_packet *tlv) {
_cleanup_lldp_neighbour_port_free_ lldp_neighbour_port *p = NULL;
_cleanup_lldp_chassis_free_ lldp_chassis *c = NULL;
lldp_chassis_id chassis_id;
bool new_chassis = false;
uint8_t subtype, *data;
uint16_t ttl, length;
int r;
assert_return(by_expiry, -EINVAL);
assert_return(neighbour_mib, -EINVAL);
assert_return(tlv, -EINVAL);
r = sd_lldp_packet_read_chassis_id(tlv, &subtype, &data, &length);
if (r < 0)
goto drop;
r = sd_lldp_packet_read_ttl(tlv, &ttl);
if (r < 0)
goto drop;
/* Make hash key */
chassis_id.type = subtype;
chassis_id.length = length;
chassis_id.data = data;
/* Try to find the Chassis */
c = hashmap_get(neighbour_mib, &chassis_id);
if (!c) {
/* Don't create chassis if ttl 0 is received . Silently drop it */
if (ttl == 0) {
log_lldp("TTL value 0 received. Skiping Chassis creation.");
goto drop;
}
/* Admission Control: Can we store this packet ? */
if (hashmap_size(neighbour_mib) >= LLDP_MIB_MAX_CHASSIS) {
log_lldp("Exceeding number of chassie: %d. Dropping ...",
hashmap_size(neighbour_mib));
goto drop;
}
r = lldp_chassis_new(tlv, by_expiry, neighbour_mib, &c);
if (r < 0)
goto drop;
new_chassis = true;
r = hashmap_put(neighbour_mib, &c->chassis_id, c);
if (r < 0)
goto drop;
} else {
/* When the TTL field is set to zero, the receiving LLDP agent is notified all
* system information associated with the LLDP agent/port is to be deleted */
if (ttl == 0) {
log_lldp("TTL value 0 received . Deleting associated Port ...");
lldp_mib_remove_objects(c, tlv);
c = NULL;
goto drop;
}
/* if we already have this port just update it */
r = lldp_mib_update_objects(c, tlv);
if (r >= 0) {
c = NULL;
return r;
}
/* Admission Control: Can this port attached to the existing chassis ? */
if (c->n_ref >= LLDP_MIB_MAX_PORT_PER_CHASSIS) {
log_lldp("Port limit reached. Chassis has: %d ports. Dropping ...", c->n_ref);
c = NULL;
goto drop;
}
}
/* This is a new port */
r = lldp_neighbour_port_new(c, tlv, &p);
if (r < 0)
goto drop;
r = prioq_put(c->by_expiry, p, &p->prioq_idx);
if (r < 0)
goto drop;
/* Attach new port to chassis */
LIST_PREPEND(port, c->ports, p);
c->n_ref ++;
p = NULL;
c = NULL;
return 0;
drop:
sd_lldp_packet_unref(tlv);
if (new_chassis)
hashmap_remove(neighbour_mib, &c->chassis_id);
return r;
}
void lldp_neighbour_port_remove_and_free(lldp_neighbour_port *p) {
lldp_chassis *c;
assert(p);
assert(p->c);
c = p->c;
prioq_remove(c->by_expiry, p, &p->prioq_idx);
LIST_REMOVE(port, c->ports, p);
lldp_neighbour_port_free(p);
/* Drop the Chassis if no port is attached */
c->n_ref --;
if (c->n_ref <= 1) {
hashmap_remove(c->neighbour_mib, &c->chassis_id);
lldp_chassis_free(c);
}
}
void lldp_neighbour_port_free(lldp_neighbour_port *p) {
if(!p)
return;
sd_lldp_packet_unref(p->packet);
free(p->data);
free(p);
}
int lldp_neighbour_port_new(lldp_chassis *c,
tlv_packet *tlv,
lldp_neighbour_port **ret) {
_cleanup_lldp_neighbour_port_free_ lldp_neighbour_port *p = NULL;
uint16_t length, ttl;
uint8_t *data;
uint8_t type;
int r;
assert(tlv);
r = sd_lldp_packet_read_port_id(tlv, &type, &data, &length);
if (r < 0)
return r;
r = sd_lldp_packet_read_ttl(tlv, &ttl);
if (r < 0)
return r;
p = new0(lldp_neighbour_port, 1);
if (!p)
return -ENOMEM;
p->c = c;
p->type = type;
p->length = length;
p->packet = tlv;
p->prioq_idx = PRIOQ_IDX_NULL;
p->until = ttl * USEC_PER_SEC + now(clock_boottime_or_monotonic());
p->data = memdup(data, length);
if (!p->data)
return -ENOMEM;
*ret = p;
p = NULL;
return 0;
}
void lldp_chassis_free(lldp_chassis *c) {
if (!c)
return;
if (c->n_ref > 1)
return;
free(c->chassis_id.data);
free(c);
}
int lldp_chassis_new(tlv_packet *tlv,
Prioq *by_expiry,
Hashmap *neighbour_mib,
lldp_chassis **ret) {
_cleanup_lldp_chassis_free_ lldp_chassis *c = NULL;
uint16_t length;
uint8_t *data;
uint8_t type;
int r;
assert(tlv);
r = sd_lldp_packet_read_chassis_id(tlv, &type, &data, &length);
if (r < 0)
return r;
c = new0(lldp_chassis, 1);
if (!c)
return -ENOMEM;
c->n_ref = 1;
c->chassis_id.type = type;
c->chassis_id.length = length;
c->chassis_id.data = memdup(data, length);
if (!c->chassis_id.data)
return -ENOMEM;
LIST_HEAD_INIT(c->ports);
c->by_expiry = by_expiry;
c->neighbour_mib = neighbour_mib;
*ret = c;
c = NULL;
return 0;
}
int lldp_receive_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
_cleanup_(sd_lldp_packet_unrefp) tlv_packet *packet = NULL;
tlv_packet *p;
uint16_t length;
int r;
assert(fd);
assert(userdata);
r = tlv_packet_new(&packet);
if (r < 0)
return r;
length = read(fd, &packet->pdu, sizeof(packet->pdu));
/* Silently drop the packet */
if ((size_t) length > ETHER_MAX_LEN)
return 0;
packet->userdata = userdata;
p = packet;
packet = NULL;
return lldp_handle_packet(p, (uint16_t) length);
}

View File

@ -21,71 +21,31 @@
***/
#include "sd-event.h"
#include "sd-lldp.h"
#include "list.h"
#include "lldp-tlv.h"
#include "hashmap.h"
#include "log.h"
#include "prioq.h"
typedef struct lldp_neighbour_port lldp_neighbour_port;
typedef struct lldp_chassis lldp_chassis;
typedef struct lldp_chassis_id lldp_chassis_id;
typedef struct lldp_agent_statistics lldp_agent_statistics;
struct sd_lldp {
int ifindex;
int fd;
struct lldp_neighbour_port {
uint8_t type;
uint8_t *data;
sd_event *event;
int64_t event_priority;
sd_event_source *io_event_source;
sd_event_source *timer_event_source;
uint16_t length;
usec_t until;
Prioq *neighbor_by_expiry;
Hashmap *neighbor_by_id;
unsigned prioq_idx;
uint64_t neighbors_max;
lldp_chassis *c;
tlv_packet *packet;
sd_lldp_callback_t callback;
void *userdata;
LIST_FIELDS(lldp_neighbour_port, port);
uint16_t capability_mask;
};
int lldp_neighbour_port_new(lldp_chassis *c, tlv_packet *tlv, lldp_neighbour_port **ret);
void lldp_neighbour_port_free(lldp_neighbour_port *p);
void lldp_neighbour_port_remove_and_free(lldp_neighbour_port *p);
DEFINE_TRIVIAL_CLEANUP_FUNC(lldp_neighbour_port *, lldp_neighbour_port_free);
#define _cleanup_lldp_neighbour_port_free_ _cleanup_(lldp_neighbour_port_freep)
struct lldp_chassis_id {
uint8_t type;
uint16_t length;
uint8_t *data;
};
struct lldp_chassis {
unsigned n_ref;
lldp_chassis_id chassis_id;
Prioq *by_expiry;
Hashmap *neighbour_mib;
LIST_HEAD(lldp_neighbour_port, ports);
};
int lldp_chassis_new(tlv_packet *tlv,
Prioq *by_expiry,
Hashmap *neighbour_mib,
lldp_chassis **ret);
void lldp_chassis_free(lldp_chassis *c);
DEFINE_TRIVIAL_CLEANUP_FUNC(lldp_chassis *, lldp_chassis_free);
#define _cleanup_lldp_chassis_free_ _cleanup_(lldp_chassis_freep)
int lldp_mib_update_objects(lldp_chassis *c, tlv_packet *tlv);
int lldp_mib_add_objects(Prioq *by_expiry, Hashmap *neighbour_mib, tlv_packet *tlv);
int lldp_mib_remove_objects(lldp_chassis *c, tlv_packet *tlv);
int lldp_handle_packet(tlv_packet *m, uint16_t length);
int lldp_receive_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata);
#define log_lldp(fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "LLDP: " fmt, ##__VA_ARGS__)
#define log_lldp_errno(error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "LLDP: " fmt, ##__VA_ARGS__)
#define log_lldp(fmt, ...) log_lldp_errno(0, fmt, ##__VA_ARGS__)

View File

@ -0,0 +1,792 @@
/***
This file is part of systemd.
Copyright 2016 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "alloc-util.h"
#include "escape.h"
#include "ether-addr-util.h"
#include "hexdecoct.h"
#include "in-addr-util.h"
#include "lldp-internal.h"
#include "lldp-neighbor.h"
#include "lldp.h"
#include "unaligned.h"
static void lldp_neighbor_id_hash_func(const void *p, struct siphash *state) {
const LLDPNeighborID *id = p;
siphash24_compress(id->chassis_id, id->chassis_id_size, state);
siphash24_compress(&id->chassis_id_size, sizeof(id->chassis_id_size), state);
siphash24_compress(id->port_id, id->port_id_size, state);
siphash24_compress(&id->port_id_size, sizeof(id->port_id_size), state);
}
static int lldp_neighbor_id_compare_func(const void *a, const void *b) {
const LLDPNeighborID *x = a, *y = b;
int r;
r = memcmp(x->chassis_id, y->chassis_id, MIN(x->chassis_id_size, y->chassis_id_size));
if (r != 0)
return r;
if (x->chassis_id_size < y->chassis_id_size)
return -1;
if (x->chassis_id_size > y->chassis_id_size)
return 1;
r = memcmp(x->port_id, y->port_id, MIN(x->port_id_size, y->port_id_size));
if (r != 0)
return r;
if (x->port_id_size < y->port_id_size)
return -1;
if (x->port_id_size > y->port_id_size)
return 1;
return 0;
}
const struct hash_ops lldp_neighbor_id_hash_ops = {
.hash = lldp_neighbor_id_hash_func,
.compare = lldp_neighbor_id_compare_func
};
int lldp_neighbor_prioq_compare_func(const void *a, const void *b) {
const sd_lldp_neighbor *x = a, *y = b;
if (x->until < y->until)
return -1;
if (x->until > y->until)
return 1;
return 0;
}
_public_ sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n) {
if (!n)
return NULL;
assert(n->n_ref > 0 || n->lldp);
n->n_ref++;
return n;
}
static void lldp_neighbor_free(sd_lldp_neighbor *n) {
assert(n);
free(n->id.port_id);
free(n->id.chassis_id);
free(n->port_description);
free(n->system_name);
free(n->system_description);
free(n->chassis_id_as_string);
free(n->port_id_as_string);
free(n);
}
_public_ sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n) {
/* Drops one reference from the neighbor. Note that the object is not freed unless it is already unlinked from
* the sd_lldp object. */
if (!n)
return NULL;
assert(n->n_ref > 0);
n->n_ref--;
if (n->n_ref <= 0 && !n->lldp)
lldp_neighbor_free(n);
return NULL;
}
sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n) {
/* Removes the neighbor object from the LLDP object, and frees it if it also has no other reference. */
if (!n)
return NULL;
if (!n->lldp)
return NULL;
assert_se(hashmap_remove(n->lldp->neighbor_by_id, &n->id) == n);
assert_se(prioq_remove(n->lldp->neighbor_by_expiry, n, &n->prioq_idx) >= 0);
n->lldp = NULL;
if (n->n_ref <= 0)
lldp_neighbor_free(n);
return NULL;
}
sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size) {
sd_lldp_neighbor *n;
n = malloc0(ALIGN(sizeof(sd_lldp_neighbor)) + raw_size);
if (!n)
return NULL;
n->raw_size = raw_size;
n->n_ref = 1;
return n;
}
static int parse_string(char **s, const void *q, size_t n) {
const char *p = q;
char *k;
assert(s);
assert(p || n == 0);
if (*s) {
log_lldp("Found duplicate string, ignoring field.");
return 0;
}
/* Strip trailing NULs, just to be nice */
while (n > 0 && p[n-1] == 0)
n--;
if (n <= 0) /* Ignore empty strings */
return 0;
/* Look for inner NULs */
if (memchr(p, 0, n)) {
log_lldp("Found inner NUL in string, ignoring field.");
return 0;
}
/* Let's escape weird chars, for security reasons */
k = cescape_length(p, n);
if (!k)
return -ENOMEM;
free(*s);
*s = k;
return 1;
}
int lldp_neighbor_parse(sd_lldp_neighbor *n) {
struct ether_header h;
const uint8_t *p;
size_t left;
int r;
assert(n);
if (n->raw_size < sizeof(struct ether_header)) {
log_lldp("Recieved truncated packet, ignoring.");
return -EBADMSG;
}
memcpy(&h, LLDP_NEIGHBOR_RAW(n), sizeof(h));
if (h.ether_type != htobe16(ETHERTYPE_LLDP)) {
log_lldp("Received packet with wrong type, ignoring.");
return -EBADMSG;
}
if (h.ether_dhost[0] != 0x01 ||
h.ether_dhost[1] != 0x80 ||
h.ether_dhost[2] != 0xc2 ||
h.ether_dhost[3] != 0x00 ||
h.ether_dhost[4] != 0x00 ||
!IN_SET(h.ether_dhost[5], 0x00, 0x03, 0x0e)) {
log_lldp("Received packet with wrong destination address, ignoring.");
return -EBADMSG;
}
memcpy(&n->source_address, h.ether_shost, sizeof(struct ether_addr));
memcpy(&n->destination_address, h.ether_dhost, sizeof(struct ether_addr));
p = (const uint8_t*) LLDP_NEIGHBOR_RAW(n) + sizeof(struct ether_header);
left = n->raw_size - sizeof(struct ether_header);
for (;;) {
uint8_t type;
uint16_t length;
if (left < 2) {
log_lldp("TLV lacks header, ignoring.");
return -EBADMSG;
}
type = p[0] >> 1;
length = p[1] + (((uint16_t) (p[0] & 1)) << 8);
p += 2, left -= 2;
if (left < length) {
log_lldp("TLV truncated, ignoring datagram.");
return -EBADMSG;
}
switch (type) {
case LLDP_TYPE_END:
if (length != 0) {
log_lldp("End marker TLV not zero-sized, ignoring datagram.");
return -EBADMSG;
}
if (left != 0) {
log_lldp("Trailing garbage in datagram, ignoring datagram.");
return -EBADMSG;
}
goto end_marker;
case LLDP_TYPE_CHASSIS_ID:
if (length < 2 || length > 256) { /* includes the chassis subtype, hence one extra byte */
log_lldp("Chassis ID field size out of range, ignoring datagram.");
return -EBADMSG;
}
if (n->id.chassis_id) {
log_lldp("Duplicate chassis ID field, ignoring datagram.");
return -EBADMSG;
}
n->id.chassis_id = memdup(p, length);
if (!n->id.chassis_id)
return -ENOMEM;
n->id.chassis_id_size = length;
break;
case LLDP_TYPE_PORT_ID:
if (length < 2 || length > 256) { /* includes the port subtype, hence one extra byte */
log_lldp("Port ID field size out of range, ignoring datagram.");
return -EBADMSG;
}
if (n->id.port_id) {
log_lldp("Duplicate port ID field, ignoring datagram.");
return -EBADMSG;
}
n->id.port_id = memdup(p, length);
if (!n->id.port_id)
return -ENOMEM;
n->id.port_id_size = length;
break;
case LLDP_TYPE_TTL:
if (length != 2) {
log_lldp("TTL field has wrong size, ignoring datagram.");
return -EBADMSG;
}
if (n->has_ttl) {
log_lldp("Duplicate TTL field, ignoring datagram.");
return -EBADMSG;
}
n->ttl = unaligned_read_be16(p);
n->has_ttl = true;
break;
case LLDP_TYPE_PORT_DESCRIPTION:
r = parse_string(&n->port_description, p, length);
if (r < 0)
return r;
break;
case LLDP_TYPE_SYSTEM_NAME:
r = parse_string(&n->system_name, p, length);
if (r < 0)
return r;
break;
case LLDP_TYPE_SYSTEM_DESCRIPTION:
r = parse_string(&n->system_description, p, length);
if (r < 0)
return r;
break;
case LLDP_TYPE_SYSTEM_CAPABILITIES:
if (length != 4)
log_lldp("System capabilities field has wrong size, ignoring.");
else {
n->system_capabilities = unaligned_read_be16(p);
n->enabled_capabilities = unaligned_read_be16(p + 2);
n->has_capabilities = true;
}
break;
case LLDP_TYPE_PRIVATE:
if (length < 4)
log_lldp("Found private TLV that is too short, ignoring.");
break;
}
p += length, left -= length;
}
end_marker:
if (!n->id.chassis_id || !n->id.port_id || !n->has_ttl) {
log_lldp("One or more mandatory TLV missing in datagram. Ignoring.");
return -EBADMSG;
}
n->rindex = sizeof(struct ether_header);
return 0;
}
void lldp_neighbor_start_ttl(sd_lldp_neighbor *n) {
assert(n);
if (n->ttl > 0)
n->until = usec_add(now(clock_boottime_or_monotonic()), n->ttl * USEC_PER_SEC);
else
n->until = 0;
if (n->lldp)
prioq_reshuffle(n->lldp->neighbor_by_expiry, n, &n->prioq_idx);
}
bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b) {
if (a == b)
return true;
if (!a || !b)
return false;
if (a->raw_size != b->raw_size)
return false;
return memcmp(LLDP_NEIGHBOR_RAW(a), LLDP_NEIGHBOR_RAW(b), a->raw_size) == 0;
}
_public_ int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address) {
assert_return(n, -EINVAL);
assert_return(address, -EINVAL);
*address = n->source_address;
return 0;
}
_public_ int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address) {
assert_return(n, -EINVAL);
assert_return(address, -EINVAL);
*address = n->destination_address;
return 0;
}
_public_ int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) {
assert_return(n, -EINVAL);
assert_return(ret, -EINVAL);
assert_return(size, -EINVAL);
*ret = LLDP_NEIGHBOR_RAW(n);
*size = n->raw_size;
return 0;
}
_public_ int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) {
assert_return(n, -EINVAL);
assert_return(type, -EINVAL);
assert_return(ret, -EINVAL);
assert_return(size, -EINVAL);
assert(n->id.chassis_id_size > 0);
*type = *(uint8_t*) n->id.chassis_id;
*ret = (uint8_t*) n->id.chassis_id + 1;
*size = n->id.chassis_id_size - 1;
return 0;
}
static int format_mac_address(const void *data, size_t sz, char **ret) {
struct ether_addr a;
char *k;
assert(data || sz <= 0);
if (sz != 7)
return 0;
memcpy(&a, (uint8_t*) data + 1, sizeof(a));
k = new(char, ETHER_ADDR_TO_STRING_MAX);
if (!k)
return -ENOMEM;
*ret = ether_addr_to_string(&a, k);
return 1;
}
static int format_network_address(const void *data, size_t sz, char **ret) {
union in_addr_union a;
int family;
if (sz == 6 && ((uint8_t*) data)[1] == 1) {
memcpy(&a.in, (uint8_t*) data + 2, sizeof(a.in));
family = AF_INET;
} else if (sz == 18 && ((uint8_t*) data)[1] == 2) {
memcpy(&a.in6, (uint8_t*) data + 2, sizeof(a.in6));
family = AF_INET6;
} else
return 0;
return in_addr_to_string(family, &a, ret);
}
_public_ int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret) {
char *k;
int r;
assert_return(n, -EINVAL);
assert_return(ret, -EINVAL);
if (n->chassis_id_as_string) {
*ret = n->chassis_id_as_string;
return 0;
}
assert(n->id.chassis_id_size > 0);
switch (*(uint8_t*) n->id.chassis_id) {
case LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT:
case LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS:
case LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT:
case LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME:
case LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED:
k = cescape_length((char*) n->id.chassis_id + 1, n->id.chassis_id_size - 1);
if (!k)
return -ENOMEM;
goto done;
case LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS:
r = format_mac_address(n->id.chassis_id, n->id.chassis_id_size, &k);
if (r < 0)
return r;
if (r > 0)
goto done;
break;
case LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS:
r = format_network_address(n->id.chassis_id, n->id.chassis_id_size, &k);
if (r < 0)
return r;
if (r > 0)
goto done;
break;
}
/* Generic fallback */
k = hexmem(n->id.chassis_id, n->id.chassis_id_size);
if (!k)
return -ENOMEM;
done:
*ret = n->chassis_id_as_string = k;
return 0;
}
_public_ int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) {
assert_return(n, -EINVAL);
assert_return(type, -EINVAL);
assert_return(ret, -EINVAL);
assert_return(size, -EINVAL);
assert(n->id.port_id_size > 0);
*type = *(uint8_t*) n->id.port_id;
*ret = (uint8_t*) n->id.port_id + 1;
*size = n->id.port_id_size - 1;
return 0;
}
_public_ int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret) {
char *k;
int r;
assert_return(n, -EINVAL);
assert_return(ret, -EINVAL);
if (n->port_id_as_string) {
*ret = n->port_id_as_string;
return 0;
}
assert(n->id.port_id_size > 0);
switch (*(uint8_t*) n->id.port_id) {
case LLDP_PORT_SUBTYPE_INTERFACE_ALIAS:
case LLDP_PORT_SUBTYPE_PORT_COMPONENT:
case LLDP_PORT_SUBTYPE_INTERFACE_NAME:
case LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED:
k = cescape_length((char*) n->id.port_id + 1, n->id.port_id_size - 1);
if (!k)
return -ENOMEM;
goto done;
case LLDP_PORT_SUBTYPE_MAC_ADDRESS:
r = format_mac_address(n->id.port_id, n->id.port_id_size, &k);
if (r < 0)
return r;
if (r > 0)
goto done;
break;
case LLDP_PORT_SUBTYPE_NETWORK_ADDRESS:
r = format_network_address(n->id.port_id, n->id.port_id_size, &k);
if (r < 0)
return r;
if (r > 0)
goto done;
break;
}
/* Generic fallback */
k = hexmem(n->id.port_id, n->id.port_id_size);
if (!k)
return -ENOMEM;
done:
*ret = n->port_id_as_string = k;
return 0;
}
_public_ int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret) {
assert_return(n, -EINVAL);
assert_return(ret, -EINVAL);
*ret = n->ttl;
return 0;
}
_public_ int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret) {
assert_return(n, -EINVAL);
assert_return(ret, -EINVAL);
if (!n->system_name)
return -ENODATA;
*ret = n->system_name;
return 0;
}
_public_ int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret) {
assert_return(n, -EINVAL);
assert_return(ret, -EINVAL);
if (!n->system_description)
return -ENODATA;
*ret = n->system_description;
return 0;
}
_public_ int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret) {
assert_return(n, -EINVAL);
assert_return(ret, -EINVAL);
if (!n->port_description)
return -ENODATA;
*ret = n->port_description;
return 0;
}
_public_ int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret) {
assert_return(n, -EINVAL);
assert_return(ret, -EINVAL);
if (!n->has_capabilities)
return -ENODATA;
*ret = n->system_capabilities;
return 0;
}
_public_ int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret) {
assert_return(n, -EINVAL);
assert_return(ret, -EINVAL);
if (!n->has_capabilities)
return -ENODATA;
*ret = n->enabled_capabilities;
return 0;
}
int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size) {
_cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
int r;
assert_return(ret, -EINVAL);
assert_return(raw || raw_size <= 0, -EINVAL);
n = lldp_neighbor_new(raw_size);
if (!n)
return -ENOMEM;
memcpy(LLDP_NEIGHBOR_RAW(n), raw, raw_size);
r = lldp_neighbor_parse(n);
if (r < 0)
return r;
*ret = n;
n = 0;
return r;
}
_public_ int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n) {
assert_return(n, -EINVAL);
assert(n->raw_size >= sizeof(struct ether_header));
n->rindex = sizeof(struct ether_header);
return 0;
}
_public_ int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n) {
size_t length;
assert_return(n, -EINVAL);
if (n->rindex == n->raw_size) /* EOF */
return -ESPIPE;
if (n->rindex + 2 > n->raw_size) /* Truncated message */
return -EBADMSG;
length = LLDP_NEIGHBOR_LENGTH(n);
if (n->rindex + 2 + length > n->raw_size)
return -EBADMSG;
n->rindex += 2 + length;
return n->rindex < n->raw_size;
}
_public_ int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type) {
assert_return(n, -EINVAL);
assert_return(type, -EINVAL);
if (n->rindex == n->raw_size) /* EOF */
return -ESPIPE;
if (n->rindex + 2 > n->raw_size)
return -EBADMSG;
*type = LLDP_NEIGHBOR_TYPE(n);
return 0;
}
_public_ int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type) {
uint8_t k;
int r;
assert_return(n, -EINVAL);
r = sd_lldp_neighbor_tlv_get_type(n, &k);
if (r < 0)
return r;
return type == k;
}
_public_ int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[3], uint8_t *subtype) {
const uint8_t *d;
size_t length;
int r;
assert_return(n, -EINVAL);
assert_return(oui, -EINVAL);
assert_return(subtype, -EINVAL);
r = sd_lldp_neighbor_tlv_is_type(n, LLDP_TYPE_PRIVATE);
if (r < 0)
return r;
if (r == 0)
return -ENXIO;
length = LLDP_NEIGHBOR_LENGTH(n);
if (length < 4)
return -EBADMSG;
if (n->rindex + 2 + length > n->raw_size)
return -EBADMSG;
d = LLDP_NEIGHBOR_DATA(n);
memcpy(oui, d, 3);
*subtype = d[3];
return 0;
}
_public_ int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[3], uint8_t subtype) {
uint8_t k[3], st;
int r;
r = sd_lldp_neighbor_tlv_get_oui(n, k, &st);
if (r == -ENXIO)
return 0;
if (r < 0)
return r;
return memcmp(k, oui, 3) == 0 && st == subtype;
}
_public_ int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) {
size_t length;
assert_return(n, -EINVAL);
assert_return(ret, -EINVAL);
assert_return(size, -EINVAL);
/* Note that this returns the full TLV, including the TLV header */
if (n->rindex + 2 > n->raw_size)
return -EBADMSG;
length = LLDP_NEIGHBOR_LENGTH(n);
if (n->rindex + 2 + length > n->raw_size)
return -EBADMSG;
*ret = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex;
*size = length + 2;
return 0;
}

View File

@ -0,0 +1,106 @@
#pragma once
/***
This file is part of systemd.
Copyright 2016 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <inttypes.h>
#include <stdbool.h>
#include <sys/types.h>
#include "sd-lldp.h"
#include "hash-funcs.h"
#include "lldp-internal.h"
#include "time-util.h"
typedef struct LLDPNeighborID {
/* The spec calls this an "MSAP identifier" */
void *chassis_id;
size_t chassis_id_size;
void *port_id;
size_t port_id_size;
} LLDPNeighborID;
struct sd_lldp_neighbor {
/* Neighbor objects stay around as long as they are linked into an "sd_lldp" object or n_ref > 0. */
sd_lldp *lldp;
unsigned n_ref;
usec_t until;
unsigned prioq_idx;
struct ether_addr source_address;
struct ether_addr destination_address;
LLDPNeighborID id;
/* The raw packet size. The data is appended to the object, accessible via LLDP_NEIGHBOR_RAW() */
size_t raw_size;
/* The current read index for the iterative TLV interface */
size_t rindex;
/* And a couple of fields parsed out. */
bool has_ttl:1;
bool has_capabilities:1;
bool has_port_vlan_id:1;
uint16_t ttl;
uint16_t system_capabilities;
uint16_t enabled_capabilities;
char *port_description;
char *system_name;
char *system_description;
uint16_t port_vlan_id;
char *chassis_id_as_string;
char *port_id_as_string;
};
static inline void *LLDP_NEIGHBOR_RAW(const sd_lldp_neighbor *n) {
return (uint8_t*) n + ALIGN(sizeof(sd_lldp_neighbor));
}
static inline uint8_t LLDP_NEIGHBOR_TYPE(const sd_lldp_neighbor *n) {
return ((uint8_t*) LLDP_NEIGHBOR_RAW(n))[n->rindex] >> 1;
}
static inline size_t LLDP_NEIGHBOR_LENGTH(const sd_lldp_neighbor *n) {
uint8_t *p;
p = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex;
return p[1] + (((size_t) (p[0] & 1)) << 8);
}
static inline void* LLDP_NEIGHBOR_DATA(const sd_lldp_neighbor *n) {
return ((uint8_t*) LLDP_NEIGHBOR_RAW(n)) + n->rindex + 2;
}
extern const struct hash_ops lldp_neighbor_id_hash_ops;
int lldp_neighbor_prioq_compare_func(const void *a, const void *b);
sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n);
sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size);
int lldp_neighbor_parse(sd_lldp_neighbor *n);
void lldp_neighbor_start_ttl(sd_lldp_neighbor *n);
bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b);

View File

@ -1,638 +0,0 @@
/***
This file is part of systemd.
Copyright (C) 2014 Tom Gundersen
Copyright (C) 2014 Susant Sahani
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <arpa/inet.h>
#include <net/ethernet.h>
#include "alloc-util.h"
#include "lldp-tlv.h"
#include "macro.h"
int tlv_section_new(tlv_section **ret) {
tlv_section *s;
s = new0(tlv_section, 1);
if (!s)
return -ENOMEM;
*ret = s;
return 0;
}
void tlv_section_free(tlv_section *m) {
if (!m)
return;
free(m);
}
int tlv_packet_new(tlv_packet **ret) {
tlv_packet *m;
m = new0(tlv_packet, 1);
if (!m)
return -ENOMEM;
LIST_HEAD_INIT(m->sections);
m->n_ref = 1;
*ret = m;
return 0;
}
tlv_packet *sd_lldp_packet_ref(tlv_packet *m) {
if (!m)
return NULL;
assert(m->n_ref > 0);
m->n_ref++;
return m;
}
tlv_packet *sd_lldp_packet_unref(tlv_packet *m) {
tlv_section *s, *n;
if (!m)
return NULL;
assert(m->n_ref > 0);
m->n_ref--;
if (m->n_ref > 0)
return m;
LIST_FOREACH_SAFE(section, s, n, m->sections)
tlv_section_free(s);
free(m);
return NULL;
}
int tlv_packet_append_bytes(tlv_packet *m, const void *data, size_t data_length) {
uint8_t *p;
assert_return(m, -EINVAL);
assert_return(data, -EINVAL);
assert_return(data_length, -EINVAL);
if (m->length + data_length > ETHER_MAX_LEN)
return -ENOMEM;
p = m->pdu + m->length;
memcpy(p, data, data_length);
m->length += data_length;
return 0;
}
int tlv_packet_append_u8(tlv_packet *m, uint8_t data) {
assert_return(m, -EINVAL);
return tlv_packet_append_bytes(m, &data, sizeof(uint8_t));
}
int tlv_packet_append_u16(tlv_packet *m, uint16_t data) {
uint16_t type;
assert_return(m, -EINVAL);
type = htons(data);
return tlv_packet_append_bytes(m, &type, sizeof(uint16_t));
}
int tlv_packet_append_u32(tlv_packet *m, uint32_t data) {
uint32_t type;
assert_return(m, -EINVAL);
type = htonl(data);
return tlv_packet_append_bytes(m, &type, sizeof(uint32_t));
}
int tlv_packet_append_string(tlv_packet *m, char *data, uint16_t size) {
assert_return(m, -EINVAL);
return tlv_packet_append_bytes(m, data, size);
}
int lldp_tlv_packet_open_container(tlv_packet *m, uint16_t type) {
assert_return(m, -EINVAL);
m->container_pos = m->pdu + m->length;
return tlv_packet_append_u16(m, type << 9);
}
int lldp_tlv_packet_close_container(tlv_packet *m) {
uint16_t type;
assert_return(m, -EINVAL);
assert_return(m->container_pos, -EINVAL);
memcpy(&type, m->container_pos, sizeof(uint16_t));
type |= htons(((m->pdu + m->length) - (m->container_pos + 2)) & 0x01ff);
memcpy(m->container_pos, &type, sizeof(uint16_t));
return 0;
}
static inline int tlv_packet_read_internal(tlv_section *m, void **data) {
assert_return(m->read_pos, -EINVAL);
*data = m->read_pos;
return 0;
}
int tlv_packet_read_u8(tlv_packet *m, uint8_t *data) {
void *val = NULL;
int r;
assert_return(m, -EINVAL);
r = tlv_packet_read_internal(m->container, &val);
if (r < 0)
return r;
memcpy(data, val, sizeof(uint8_t));
m->container->read_pos ++;
return 0;
}
int tlv_packet_read_u16(tlv_packet *m, uint16_t *data) {
uint16_t t;
void *val = NULL;
int r;
assert_return(m, -EINVAL);
r = tlv_packet_read_internal(m->container, &val);
if (r < 0)
return r;
memcpy(&t, val, sizeof(uint16_t));
*data = ntohs(t);
m->container->read_pos += 2;
return 0;
}
int tlv_packet_read_u32(tlv_packet *m, uint32_t *data) {
uint32_t t;
void *val;
int r;
assert_return(m, -EINVAL);
r = tlv_packet_read_internal(m->container, &val);
if (r < 0)
return r;
memcpy(&t, val, sizeof(uint32_t));
*data = ntohl(t);
m->container->read_pos += 4;
return r;
}
int tlv_packet_read_string(tlv_packet *m, char **data, uint16_t *data_length) {
void *val = NULL;
int r;
assert_return(m, -EINVAL);
r = tlv_packet_read_internal(m->container, &val);
if (r < 0)
return r;
*data = (char *) val;
*data_length = m->container->data + m->container->length - m->container->read_pos;
m->container->read_pos += *data_length;
return 0;
}
int tlv_packet_read_bytes(tlv_packet *m, uint8_t **data, uint16_t *data_length) {
void *val = NULL;
int r;
assert_return(m, -EINVAL);
r = tlv_packet_read_internal(m->container, &val);
if (r < 0)
return r;
*data = (uint8_t *) val;
*data_length = m->container->data + m->container->length - m->container->read_pos;
m->container->read_pos += *data_length;
return 0;
}
/* parse raw TLV packet */
int tlv_packet_parse_pdu(tlv_packet *m, uint16_t size) {
tlv_section *section, *tail;
uint16_t t, l;
uint8_t *p;
int r;
assert_return(m, -EINVAL);
assert_return(size, -EINVAL);
p = m->pdu;
/* extract Ethernet header */
memcpy(&m->mac, p, ETH_ALEN);
p += sizeof(struct ether_header);
for (l = 0; l <= size; ) {
r = tlv_section_new(&section);
if (r < 0)
return r;
memcpy(&t, p, sizeof(uint16_t));
section->type = ntohs(t) >> 9;
section->length = ntohs(t) & 0x01ff;
if (section->type == LLDP_TYPE_END || section->type >=_LLDP_TYPE_MAX) {
tlv_section_free(section);
break;
}
p += 2;
if (section->type == LLDP_TYPE_PRIVATE &&
section->length >= LLDP_OUI_LEN + 1) {
section->oui = p;
p += LLDP_OUI_LEN;
section->subtype = *p++;
section->length -= LLDP_OUI_LEN + 1;
l += LLDP_OUI_LEN + 1;
}
section->data = p;
LIST_FIND_TAIL(section, m->sections, tail);
LIST_INSERT_AFTER(section, m->sections, tail, section);
p += section->length;
l += (section->length + 2);
}
return 0;
}
int lldp_tlv_packet_enter_container(tlv_packet *m, uint16_t type) {
tlv_section *s;
assert_return(m, -EINVAL);
assert_return(type != LLDP_TYPE_PRIVATE, -EINVAL);
LIST_FOREACH(section, s, m->sections)
if (s->type == type)
break;
if (!s)
return -1;
m->container = s;
m->container->read_pos = s->data;
if (!m->container->read_pos) {
m->container = NULL;
return -1;
}
return 0;
}
int lldp_tlv_packet_enter_container_oui(tlv_packet *m, const uint8_t *oui, uint8_t subtype) {
tlv_section *s;
assert_return(m, -EINVAL);
assert_return(oui, -EINVAL);
LIST_FOREACH(section, s, m->sections) {
if (s->type == LLDP_TYPE_PRIVATE &&
s->oui &&
s->subtype == subtype &&
!memcmp(s->oui, oui, LLDP_OUI_LEN))
break;
}
if (!s)
return -1;
m->container = s;
m->container->read_pos = s->data;
if (!m->container->read_pos) {
m->container = NULL;
return -1;
}
return 0;
}
int lldp_tlv_packet_exit_container(tlv_packet *m) {
assert_return(m, -EINVAL);
m->container = 0;
return 0;
}
static int lldp_tlv_packet_read_u16_tlv(tlv_packet *tlv, uint16_t type, uint16_t *value) {
int r, r2;
assert_return(tlv, -EINVAL);
r = lldp_tlv_packet_enter_container(tlv, type);
if (r < 0)
return r;
r = tlv_packet_read_u16(tlv, value);
r2 = lldp_tlv_packet_exit_container(tlv);
return r < 0 ? r : r2;
}
static int lldp_tlv_packet_read_string_tlv(tlv_packet *tlv, uint16_t type, char **data, uint16_t *length) {
char *s;
int r, r2;
assert_return(tlv, -EINVAL);
r = lldp_tlv_packet_enter_container(tlv, type);
if (r < 0)
return r;
r = tlv_packet_read_string(tlv, &s, length);
if (r < 0)
goto out;
*data = (char *) s;
out:
r2 = lldp_tlv_packet_exit_container(tlv);
return r < 0 ? r : r2;
}
int sd_lldp_packet_read_chassis_id(tlv_packet *tlv,
uint8_t *type,
uint8_t **data,
uint16_t *length) {
uint8_t subtype;
int r, r2;
assert_return(tlv, -EINVAL);
r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_CHASSIS_ID);
if (r < 0)
return r;
r = tlv_packet_read_u8(tlv, &subtype);
if (r < 0)
goto out;
switch (subtype) {
case LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS:
r = tlv_packet_read_bytes(tlv, data, length);
if (r < 0)
goto out;
break;
default:
r = -EOPNOTSUPP;
break;
}
*type = subtype;
out:
r2 = lldp_tlv_packet_exit_container(tlv);
return r < 0 ? r : r2;
}
int sd_lldp_packet_read_port_id(tlv_packet *tlv,
uint8_t *type,
uint8_t **data,
uint16_t *length) {
uint8_t subtype;
char *s;
int r, r2;
assert_return(tlv, -EINVAL);
r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_PORT_ID);
if (r < 0)
return r;
r = tlv_packet_read_u8(tlv, &subtype);
if (r < 0)
goto out;
switch (subtype) {
case LLDP_PORT_SUBTYPE_PORT_COMPONENT:
case LLDP_PORT_SUBTYPE_INTERFACE_ALIAS:
case LLDP_PORT_SUBTYPE_INTERFACE_NAME:
case LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED:
r = tlv_packet_read_string(tlv, &s, length);
if (r < 0)
goto out;
*data = (uint8_t *) s;
break;
case LLDP_PORT_SUBTYPE_MAC_ADDRESS:
r = tlv_packet_read_bytes(tlv, data, length);
if (r < 0)
goto out;
break;
default:
r = -EOPNOTSUPP;
break;
}
*type = subtype;
out:
r2 = lldp_tlv_packet_exit_container(tlv);
return r < 0 ? r : r2;
}
int sd_lldp_packet_read_ttl(tlv_packet *tlv, uint16_t *ttl) {
return lldp_tlv_packet_read_u16_tlv(tlv, LLDP_TYPE_TTL, ttl);
}
int sd_lldp_packet_read_system_name(tlv_packet *tlv,
char **data,
uint16_t *length) {
return lldp_tlv_packet_read_string_tlv(tlv, LLDP_TYPE_SYSTEM_NAME, data, length);
}
int sd_lldp_packet_read_system_description(tlv_packet *tlv,
char **data,
uint16_t *length) {
return lldp_tlv_packet_read_string_tlv(tlv, LLDP_TYPE_SYSTEM_DESCRIPTION, data, length);
}
int sd_lldp_packet_read_port_description(tlv_packet *tlv,
char **data,
uint16_t *length) {
return lldp_tlv_packet_read_string_tlv(tlv, LLDP_TYPE_PORT_DESCRIPTION, data, length);
}
int sd_lldp_packet_read_system_capability(tlv_packet *tlv, uint16_t *data) {
return lldp_tlv_packet_read_u16_tlv(tlv, LLDP_TYPE_SYSTEM_CAPABILITIES, data);
}
int sd_lldp_packet_read_port_vlan_id(tlv_packet *tlv, uint16_t *id) {
int r, r2;
assert_return(tlv, -EINVAL);
r = lldp_tlv_packet_enter_container_oui(tlv, LLDP_OUI_802_1, LLDP_OUI_SUBTYPE_802_1_PORT_VLAN_ID);
if (r < 0)
return r;
r = tlv_packet_read_u16(tlv, id);
r2 = lldp_tlv_packet_exit_container(tlv);
return r < 0 ? r : r2;
}
int sd_lldp_packet_read_port_protocol_vlan_id(sd_lldp_packet *tlv, uint8_t *flags, uint16_t *id) {
int r, r2;
assert_return(tlv, -EINVAL);
r = lldp_tlv_packet_enter_container_oui(tlv, LLDP_OUI_802_1, LLDP_OUI_SUBTYPE_802_1_PORT_PROTOCOL_VLAN_ID);
if (r < 0)
return r;
r = tlv_packet_read_u8(tlv, flags);
if (r >= 0)
r = tlv_packet_read_u16(tlv, id);
r2 = lldp_tlv_packet_exit_container(tlv);
return r < 0 ? r : r2;
}
int sd_lldp_packet_read_vlan_name(tlv_packet *tlv, uint16_t *vlan_id, char **name, uint16_t *length) {
int r, r2;
uint8_t len = 0;
assert_return(tlv, -EINVAL);
r = lldp_tlv_packet_enter_container_oui(tlv, LLDP_OUI_802_1, LLDP_OUI_SUBTYPE_802_1_VLAN_NAME);
if (r < 0)
return r;
r = tlv_packet_read_u16(tlv, vlan_id);
if (r >= 0)
r = tlv_packet_read_u8(tlv, &len);
if (r >= 0)
r = tlv_packet_read_string(tlv, name, length);
if (r >= 0 && len < *length)
*length = len;
r2 = lldp_tlv_packet_exit_container(tlv);
return r < 0 ? r : r2;
}
int sd_lldp_packet_read_management_vid(tlv_packet *tlv, uint16_t *id) {
int r, r2;
assert_return(tlv, -EINVAL);
r = lldp_tlv_packet_enter_container_oui(tlv, LLDP_OUI_802_1, LLDP_OUI_SUBTYPE_802_1_MANAGEMENT_VID);
if (r < 0)
return r;
r = tlv_packet_read_u16(tlv, id);
r2 = lldp_tlv_packet_exit_container(tlv);
return r < 0 ? r : r2;
}
int sd_lldp_packet_read_link_aggregation(sd_lldp_packet *tlv, uint8_t *status, uint32_t *id) {
int r, r2;
assert_return(tlv, -EINVAL);
r = lldp_tlv_packet_enter_container_oui(tlv, LLDP_OUI_802_1, LLDP_OUI_SUBTYPE_802_1_LINK_AGGREGATION);
if (r < 0)
return r;
r = tlv_packet_read_u8(tlv, status);
if (r >= 0)
r = tlv_packet_read_u32(tlv, id);
r2 = lldp_tlv_packet_exit_container(tlv);
return r < 0 ? r : r2;
}
int sd_lldp_packet_get_destination_type(tlv_packet *tlv, int *dest) {
assert_return(tlv, -EINVAL);
assert_return(dest, -EINVAL);
/* 802.1AB-2009, Table 7-1 */
if (!memcmp(&tlv->mac, LLDP_MAC_NEAREST_BRIDGE, ETH_ALEN))
*dest = SD_LLDP_DESTINATION_TYPE_NEAREST_BRIDGE;
else if (!memcmp(&tlv->mac, LLDP_MAC_NEAREST_NON_TPMR_BRIDGE, ETH_ALEN))
*dest = SD_LLDP_DESTINATION_TYPE_NEAREST_NON_TPMR_BRIDGE;
else if (!memcmp(&tlv->mac, LLDP_MAC_NEAREST_CUSTOMER_BRIDGE, ETH_ALEN))
*dest = SD_LLDP_DESTINATION_TYPE_NEAREST_CUSTOMER_BRIDGE;
else
return -EINVAL;
return 0;
}

View File

@ -1,94 +0,0 @@
#pragma once
/***
This file is part of systemd.
Copyright (C) 2014 Tom Gundersen
Copyright (C) 2014 Susant Sahani
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <net/ethernet.h>
#include "sd-lldp.h"
#include "list.h"
#include "lldp.h"
#include "util.h"
typedef struct sd_lldp_packet tlv_packet;
typedef struct sd_lldp_section tlv_section;
#define LLDP_OUI_LEN 3
struct sd_lldp_section {
uint16_t type;
uint16_t length;
uint8_t *oui;
uint8_t subtype;
uint8_t *read_pos;
uint8_t *data;
LIST_FIELDS(tlv_section, section);
};
#define LLDP_MAC_NEAREST_BRIDGE (uint8_t[]) { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e }
#define LLDP_MAC_NEAREST_NON_TPMR_BRIDGE (uint8_t[]) { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 }
#define LLDP_MAC_NEAREST_CUSTOMER_BRIDGE (uint8_t[]) { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 }
int tlv_section_new(tlv_section **ret);
void tlv_section_free(tlv_section *ret);
struct sd_lldp_packet {
unsigned n_ref;
uint16_t type;
uint16_t length;
usec_t ts;
uint8_t *container_pos;
uint8_t pdu[ETHER_MAX_LEN];
void *userdata;
struct ether_addr mac;
tlv_section *container;
LIST_HEAD(tlv_section, sections);
};
int tlv_packet_new(tlv_packet **ret);
int lldp_tlv_packet_open_container(tlv_packet *m, uint16_t type);
int lldp_tlv_packet_close_container(tlv_packet *m);
int tlv_packet_append_bytes(tlv_packet *m, const void *data, size_t data_length);
int tlv_packet_append_u8(tlv_packet *m, uint8_t data);
int tlv_packet_append_u16(tlv_packet *m, uint16_t data);
int tlv_packet_append_u32(tlv_packet *m, uint32_t data);
int tlv_packet_append_string(tlv_packet *m, char *data, uint16_t size);
int lldp_tlv_packet_enter_container(tlv_packet *m, uint16_t type);
int lldp_tlv_packet_enter_container_oui(tlv_packet *m, const uint8_t *oui, uint8_t subtype);
int lldp_tlv_packet_exit_container(tlv_packet *m);
int tlv_packet_read_bytes(tlv_packet *m, uint8_t **data, uint16_t *data_length);
int tlv_packet_read_string(tlv_packet *m, char **data, uint16_t *data_length);
int tlv_packet_read_u8(tlv_packet *m, uint8_t *data);
int tlv_packet_read_u16(tlv_packet *m, uint16_t *data);
int tlv_packet_read_u32(tlv_packet *m, uint32_t *data);
int tlv_packet_parse_pdu(tlv_packet *t, uint16_t size);

View File

@ -23,7 +23,7 @@
#define LLDP_MULTICAST_ADDR { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e }
/* IEEE 802.3AB Clause 9: TLV Types */
typedef enum LLDPTypes {
enum {
LLDP_TYPE_END = 0,
LLDP_TYPE_CHASSIS_ID = 1,
LLDP_TYPE_PORT_ID = 2,
@ -34,12 +34,10 @@ typedef enum LLDPTypes {
LLDP_TYPE_SYSTEM_CAPABILITIES = 7,
LLDP_TYPE_MGMT_ADDRESS = 8,
LLDP_TYPE_PRIVATE = 127,
_LLDP_TYPE_MAX,
_LLDP_TYPE_INVALID = -1,
} LLDPTypes;
};
/* IEEE 802.3AB Clause 9.5.2: Chassis subtypes */
typedef enum LLDPChassisSubtypes {
enum {
LLDP_CHASSIS_SUBTYPE_RESERVED = 0,
LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT = 1,
LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS = 2,
@ -48,25 +46,21 @@ typedef enum LLDPChassisSubtypes {
LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS = 5,
LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME = 6,
LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED = 7,
_LLDP_CHASSIS_SUBTYPE_MAX,
_LLDP_CHASSIS_SUBTYPE_INVALID = -1,
} LLDPChassisSubtypes;
};
/* IEEE 802.3AB Clause 9.5.3: Port subtype */
typedef enum LLDPPortSubtypes {
enum {
LLDP_PORT_SUBTYPE_RESERVED = 0,
LLDP_PORT_SUBTYPE_INTERFACE_ALIAS = 1,
LLDP_PORT_SUBTYPE_PORT_COMPONENT = 2,
LLDP_PORT_SUBTYPE_MAC_ADDRESS = 3,
LLDP_PORT_SUBTYPE_NETWORK = 4,
LLDP_PORT_SUBTYPE_NETWORK_ADDRESS = 4,
LLDP_PORT_SUBTYPE_INTERFACE_NAME = 5,
LLDP_PORT_SUBTYPE_AGENT_CIRCUIT_ID = 6,
LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED = 7,
_LLDP_PORT_SUBTYPE_MAX,
_LLDP_PORT_SUBTYPE_INVALID = -1
} LLDPPortSubtypes;
};
typedef enum LLDPSystemCapabilities {
enum {
LLDP_SYSTEM_CAPABILITIES_OTHER = 1 << 0,
LLDP_SYSTEM_CAPABILITIES_REPEATER = 1 << 1,
LLDP_SYSTEM_CAPABILITIES_BRIDGE = 1 << 2,
@ -78,47 +72,31 @@ typedef enum LLDPSystemCapabilities {
LLDP_SYSTEM_CAPABILITIES_CVLAN = 1 << 8,
LLDP_SYSTEM_CAPABILITIES_SVLAN = 1 << 9,
LLDP_SYSTEM_CAPABILITIES_TPMR = 1 << 10,
_LLDP_SYSTEM_CAPABILITIES_MAX,
_LLDP_SYSTEM_CAPABILITIES_INVALID = -1,
} LLDPSystemCapabilities;
};
typedef enum LLDPMedSubtype {
LLDP_MED_SUBTYPE_RESERVED = 0,
LLDP_MED_SUBTYPE_CAPABILITIES = 1,
LLDP_MED_SUBTYPE_NETWORK_POLICY = 2,
LLDP_MED_SUBTYPE_LOCATION_ID = 3,
LLDP_MED_SUBTYPE_EXTENDED_PVMDI = 4,
LLDP_MED_SUBTYPE_INV_HWREV = 5,
LLDP_MED_SUBTYPE_INV_FWREV = 6,
LLDP_MED_SUBTYPE_INV_SWREV = 7,
LLDP_MED_SUBTYPE_INV_SERIAL = 8,
LLDP_MED_SUBTYPE_INV_MANUFACTURER = 9,
LLDP_MED_SUBTYPE_INV_MODELNAME = 10,
LLDP_MED_SUBTYPE_INV_ASSETID = 11,
_LLDP_MED_SUBTYPE_MAX,
_LLDP_MED_SUBTYPE_INVALID = -1,
} LLDPMedSubtype;
#define _LLDP_SYSTEM_CAPABILITIES_ALL ((uint16_t) -1)
#define _LLDP_SYSTEM_CAPABILITIES_ALL_ROUTERS \
((uint16_t) \
(LLDP_SYSTEM_CAPABILITIES_REPEATER| \
LLDP_SYSTEM_CAPABILITIES_BRIDGE| \
LLDP_SYSTEM_CAPABILITIES_WLAN_AP| \
LLDP_SYSTEM_CAPABILITIES_ROUTER| \
LLDP_SYSTEM_CAPABILITIES_DOCSIS| \
LLDP_SYSTEM_CAPABILITIES_CVLAN| \
LLDP_SYSTEM_CAPABILITIES_SVLAN| \
LLDP_SYSTEM_CAPABILITIES_TPMR))
typedef enum LLDPMedCapability {
LLDP_MED_CAPABILITY_CAPAPILITIES = 1 << 0,
LLDP_MED_CAPABILITY_NETWORK_POLICY = 1 << 1,
LLDP_MED_CAPABILITY_LOCATION_ID = 1 << 2,
LLDP_MED_CAPABILITY_EXTENDED_PSE = 1 << 3,
LLDP_MED_CAPABILITY_EXTENDED_PD = 1 << 4,
LLDP_MED_CAPABILITY_INVENTORY = 1 << 5,
LLDP_MED_CAPABILITY_MAX,
LLDP_MED_CAPABILITY_INVALID = -1,
} LLDPMedCapability;
#define LLDP_OUI_802_1 (uint8_t[]) { 0x00, 0x80, 0xc2 }
#define LLDP_OUI_802_3 (uint8_t[]) { 0x00, 0x12, 0x0f }
enum {
LLDP_OUI_SUBTYPE_802_1_PORT_VLAN_ID = 1,
LLDP_OUI_SUBTYPE_802_1_PORT_PROTOCOL_VLAN_ID = 2,
LLDP_OUI_SUBTYPE_802_1_VLAN_NAME = 3,
LLDP_OUI_SUBTYPE_802_1_PROTOCOL_IDENTITY = 4,
LLDP_OUI_SUBTYPE_802_1_VID_USAGE_DIGEST = 5,
LLDP_OUI_SUBTYPE_802_1_MANAGEMENT_VID = 6,
LLDP_OUI_SUBTYPE_802_1_LINK_AGGREGATION = 7,
LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID = 1,
LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID = 2,
LLDP_OUI_802_1_SUBTYPE_VLAN_NAME = 3,
LLDP_OUI_802_1_SUBTYPE_PROTOCOL_IDENTITY = 4,
LLDP_OUI_802_1_SUBTYPE_VID_USAGE_DIGEST = 5,
LLDP_OUI_802_1_SUBTYPE_MANAGEMENT_VID = 6,
LLDP_OUI_802_1_SUBTYPE_LINK_AGGREGATION = 7,
};

View File

@ -1,21 +1,21 @@
/***
This file is part of systemd.
This file is part of systemd.
Copyright (C) 2014 Tom Gundersen
Copyright (C) 2014 Susant Sahani
Copyright (C) 2014 Tom Gundersen
Copyright (C) 2014 Susant Sahani
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <arpa/inet.h>
@ -24,484 +24,162 @@
#include "alloc-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "hashmap.h"
#include "lldp-internal.h"
#include "lldp-neighbor.h"
#include "lldp-network.h"
#include "lldp-tlv.h"
#include "prioq.h"
#include "siphash24.h"
#include "string-util.h"
#include "socket-util.h"
struct sd_lldp {
int ifindex;
int fd;
#define LLDP_DEFAULT_NEIGHBORS_MAX 128U
sd_event *event;
int64_t event_priority;
sd_event_source *event_source;
Prioq *by_expiry;
Hashmap *neighbour_mib;
sd_lldp_callback_t callback;
void *userdata;
};
static void chassis_id_hash_func(const void *p, struct siphash *state) {
const lldp_chassis_id *id = p;
assert(id);
assert(id->data);
siphash24_compress(&id->length, sizeof(id->length), state);
siphash24_compress(id->data, id->length, state);
}
static int chassis_id_compare_func(const void *_a, const void *_b) {
const lldp_chassis_id *a, *b;
a = _a;
b = _b;
assert(!a->length || a->data);
assert(!b->length || b->data);
if (a->type != b->type)
return -1;
if (a->length != b->length)
return a->length < b->length ? -1 : 1;
return memcmp(a->data, b->data, a->length);
}
static const struct hash_ops chassis_id_hash_ops = {
.hash = chassis_id_hash_func,
.compare = chassis_id_compare_func
};
static void lldp_mib_delete_objects(sd_lldp *lldp);
static int lldp_receive_frame(sd_lldp *lldp, tlv_packet *tlv) {
int r;
static void lldp_flush_neighbors(sd_lldp *lldp) {
sd_lldp_neighbor *n;
assert(lldp);
assert(tlv);
/* Remove expired packets */
if (prioq_size(lldp->by_expiry) > 0)
lldp_mib_delete_objects(lldp);
r = lldp_mib_add_objects(lldp->by_expiry, lldp->neighbour_mib, tlv);
if (r < 0)
goto out;
if (lldp->callback)
lldp->callback(lldp, SD_LLDP_EVENT_UPDATE_INFO, lldp->userdata);
log_lldp("Packet added. MIB size: %d , PQ size: %d",
hashmap_size(lldp->neighbour_mib),
prioq_size(lldp->by_expiry));
out:
if (r < 0)
log_lldp("Receive frame failed: %s", strerror(-r));
return 0;
while ((n = hashmap_first(lldp->neighbor_by_id)))
lldp_neighbor_unlink(n);
}
/* 10.3.2 LLDPDU validation: rxProcessFrame() */
int lldp_handle_packet(tlv_packet *tlv, uint16_t length) {
bool system_description = false, system_name = false, chassis_id = false;
bool port_id = false, ttl = false, end = false;
uint16_t type, len, i, l, t;
uint8_t *p, *q;
sd_lldp *lldp;
int r;
static int lldp_make_space(sd_lldp *lldp, size_t extra) {
sd_lldp_neighbor *n;
usec_t t = USEC_INFINITY;
bool changed = false;
assert(tlv);
assert(length > 0);
assert(lldp);
lldp = tlv->userdata;
/* Remove all entries that are past their TTL, and more until at least the specified number of extra entries
* are free. */
p = tlv->pdu;
p += sizeof(struct ether_header);
for (i = 1, l = 0; l <= length; i++) {
memcpy(&t, p, sizeof(uint16_t));
type = ntohs(t) >> 9;
len = ntohs(t) & 0x01ff;
if (type == LLDP_TYPE_END) {
if (len != 0) {
log_lldp("TLV type end must be length 0 (not %d). Dropping.", len);
goto out;
}
end = true;
break;
} else if (type >=_LLDP_TYPE_MAX) {
log_lldp("TLV type: %d not recognized. Dropping.", type);
goto out;
}
/* skip type and length encoding */
p += 2;
q = p;
p += len;
l += (len + 2);
if (i <= 3) {
if (i != type) {
log_lldp("TLV missing or out of order. Dropping.");
goto out;
}
}
switch(type) {
case LLDP_TYPE_CHASSIS_ID:
if (len < 2) {
log_lldp("Received malformed Chassis ID TLV length: %d. Dropping.", len);
goto out;
}
if (chassis_id) {
log_lldp("Duplicate Chassis ID TLV found. Dropping.");
goto out;
}
/* Look what subtype it has */
if (*q == LLDP_CHASSIS_SUBTYPE_RESERVED || *q > LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED) {
log_lldp("Unknown subtype: %d found in Chassis ID TLV. Dropping.", *q);
goto out;
}
chassis_id = true;
break;
case LLDP_TYPE_PORT_ID:
if (len < 2) {
log_lldp("Received malformed Port ID TLV length: %d. Dropping.", len);
goto out;
}
if (port_id) {
log_lldp("Duplicate Port ID TLV found. Dropping.");
goto out;
}
/* Look what subtype it has */
if (*q == LLDP_PORT_SUBTYPE_RESERVED || *q > LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED) {
log_lldp("Unknown subtype: %d found in Port ID TLV. Dropping.", *q);
goto out;
}
port_id = true;
break;
case LLDP_TYPE_TTL:
if(len != 2) {
log_lldp("Received invalid TTL TLV lenth: %d. Dropping.", len);
goto out;
}
if (ttl) {
log_lldp("Duplicate TTL TLV found. Dropping.");
goto out;
}
ttl = true;
break;
case LLDP_TYPE_SYSTEM_NAME:
/* According to RFC 1035 the length of a FQDN is limited to 255 characters */
if (len > 255) {
log_lldp("Received invalid system name length: %d. Dropping.", len);
goto out;
}
if (system_name) {
log_lldp("Duplicate system name found. Dropping.");
goto out;
}
system_name = true;
break;
case LLDP_TYPE_SYSTEM_DESCRIPTION:
/* 0 <= n <= 255 octets */
if (len > 255) {
log_lldp("Received invalid system description length: %d. Dropping.", len);
goto out;
}
if (system_description) {
log_lldp("Duplicate system description found. Dropping.");
goto out;
}
system_description = true;
break;
default:
if (len == 0) {
log_lldp("TLV type: %d length 0 received. Dropping.", type);
goto out;
}
break;
}
}
if(!chassis_id || !port_id || !ttl || !end) {
log_lldp("One or more mandatory TLV missing. Dropping.");
goto out;
}
r = tlv_packet_parse_pdu(tlv, length);
if (r < 0) {
log_lldp("Failed to parse the TLV. Dropping.");
goto out;
}
return lldp_receive_frame(lldp, tlv);
out:
sd_lldp_packet_unref(tlv);
return 0;
}
static int ttl_expiry_item_prioq_compare_func(const void *a, const void *b) {
const lldp_neighbour_port *p = a, *q = b;
if (p->until < q->until)
return -1;
if (p->until > q->until)
return 1;
return 0;
}
/* 10.5.5.2.1 mibDeleteObjects ()
* The mibDeleteObjects () procedure deletes all information in the LLDP remote
* systems MIB associated with the MSAP identifier if an LLDPDU is received with
* an rxTTL value of zero (see 10.3.2) or the timing counter rxInfoTTL expires. */
static void lldp_mib_delete_objects(sd_lldp *lldp) {
lldp_neighbour_port *p;
usec_t t = 0;
/* Remove all entries that are past their TTL */
for (;;) {
if (prioq_size(lldp->by_expiry) <= 0)
n = prioq_peek(lldp->neighbor_by_expiry);
if (!n)
break;
p = prioq_peek(lldp->by_expiry);
if (!p)
break;
if (hashmap_size(lldp->neighbor_by_id) > LESS_BY(lldp->neighbors_max, extra))
goto remove_one;
if (t <= 0)
if (t == USEC_INFINITY)
t = now(clock_boottime_or_monotonic());
if (p->until > t)
if (n->until > t)
break;
lldp_neighbour_port_remove_and_free(p);
}
}
static void lldp_mib_objects_flush(sd_lldp *lldp) {
lldp_neighbour_port *p, *q;
lldp_chassis *c;
assert(lldp);
assert(lldp->neighbour_mib);
assert(lldp->by_expiry);
/* Drop all packets */
while ((c = hashmap_steal_first(lldp->neighbour_mib))) {
LIST_FOREACH_SAFE(port, p, q, c->ports) {
lldp_neighbour_port_remove_and_free(p);
}
remove_one:
lldp_neighbor_unlink(n);
changed = true;
}
assert(hashmap_size(lldp->neighbour_mib) == 0);
assert(prioq_size(lldp->by_expiry) == 0);
return changed;
}
int sd_lldp_save(sd_lldp *lldp, const char *lldp_file) {
_cleanup_free_ char *temp_path = NULL;
_cleanup_fclose_ FILE *f = NULL;
uint8_t *mac, *port_id, type;
lldp_neighbour_port *p;
uint16_t data = 0, length = 0;
char buf[LINE_MAX];
lldp_chassis *c;
usec_t time;
Iterator i;
static int lldp_add_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
sd_lldp_neighbor *old;
bool changed = false;
int r;
assert(lldp);
assert(lldp_file);
assert(n);
assert(!n->lldp);
r = fopen_temporary(lldp_file, &f, &temp_path);
if (r < 0)
goto fail;
fchmod(fileno(f), 0644);
HASHMAP_FOREACH(c, lldp->neighbour_mib, i) {
LIST_FOREACH(port, p, c->ports) {
_cleanup_free_ char *s = NULL;
char *k, *t;
r = sd_lldp_packet_read_chassis_id(p->packet, &type, &mac, &length);
if (r < 0)
continue;
sprintf(buf, "'_Chassis=%02x:%02x:%02x:%02x:%02x:%02x' '_CType=%d' ",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], type);
s = strdup(buf);
if (!s) {
r = -ENOMEM;
goto fail;
}
r = sd_lldp_packet_read_port_id(p->packet, &type, &port_id, &length);
if (r < 0)
continue;
if (type != LLDP_PORT_SUBTYPE_MAC_ADDRESS) {
k = strndup((char *) port_id, length -1);
if (!k) {
r = -ENOMEM;
goto fail;
}
sprintf(buf, "'_Port=%s' '_PType=%d' ", k , type);
free(k);
} else {
mac = port_id;
sprintf(buf, "'_Port=%02x:%02x:%02x:%02x:%02x:%02x' '_PType=%d' ",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], type);
}
k = strappend(s, buf);
if (!k) {
r = -ENOMEM;
goto fail;
}
free(s);
s = k;
time = now(clock_boottime_or_monotonic());
/* Don't write expired packets */
if (time - p->until <= 0)
continue;
sprintf(buf, "'_TTL="USEC_FMT"' ", p->until);
k = strappend(s, buf);
if (!k) {
r = -ENOMEM;
goto fail;
}
free(s);
s = k;
r = sd_lldp_packet_read_system_name(p->packet, &k, &length);
if (r < 0)
k = strappend(s, "'_NAME=N/A' ");
else {
t = strndup(k, length);
if (!t) {
r = -ENOMEM;
goto fail;
}
k = strjoin(s, "'_NAME=", t, "' ", NULL);
free(t);
}
if (!k) {
r = -ENOMEM;
goto fail;
}
free(s);
s = k;
(void) sd_lldp_packet_read_system_capability(p->packet, &data);
sprintf(buf, "'_CAP=%x'", data);
k = strappend(s, buf);
if (!k) {
r = -ENOMEM;
goto fail;
}
free(s);
s = k;
fprintf(f, "%s\n", s);
/* First retrieve the old entry for this MSAP */
old = hashmap_get(lldp->neighbor_by_id, &n->id);
if (old) {
if (lldp_neighbor_equal(n, old)) {
/* Is this equal, then restart the TTL counter, but don't do anyting else. */
lldp_neighbor_start_ttl(old);
return 0;
}
/* Data changed, remove the old entry, and add a new one */
lldp_neighbor_unlink(old);
changed = true;
}
r = fflush_and_check(f);
/* Then, add the new entry in its place, but only if it has a non-zero TTL. */
if (n->ttl <= 0)
return changed;
/* Only add if the neighbor has a capability we are interested in. Note that we also store all neighbors with
* no caps field set. */
if (n->has_capabilities &&
(n->enabled_capabilities & lldp->capability_mask) == 0)
return changed;
/* Then, make room for at least one new neighbor */
lldp_make_space(lldp, 1);
r = hashmap_put(lldp->neighbor_by_id, &n->id, n);
if (r < 0)
goto fail;
return r;
if (rename(temp_path, lldp_file) < 0) {
r = -errno;
goto fail;
r = prioq_put(lldp->neighbor_by_expiry, n, &n->prioq_idx);
if (r < 0) {
assert_se(hashmap_remove(lldp->neighbor_by_id, &n->id) == n);
return r;
}
return 0;
n->lldp = lldp;
fail:
if (temp_path)
(void) unlink(temp_path);
return log_error_errno(r, "Failed to save lldp data %s: %m", lldp_file);
return true;
}
int sd_lldp_start(sd_lldp *lldp) {
static int lldp_handle_datagram(sd_lldp *lldp, sd_lldp_neighbor *n) {
int r;
assert(lldp);
assert(n);
r = lldp_neighbor_parse(n);
if (r == -EBADMSG) /* Ignore bad messages */
return 0;
if (r < 0)
return r;
lldp_neighbor_start_ttl(n);
r = lldp_add_neighbor(lldp, n);
if (r < 0) {
log_lldp_errno(r, "Failed to add datagram. Ignoring.");
return 0;
}
log_lldp("Successfully processed LLDP datagram.");
if (r > 0 && lldp->callback) /* Only invoke the callback if something actually changed. */
lldp->callback(lldp, lldp->userdata);
return 0;
}
static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
_cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
ssize_t space, length;
sd_lldp *lldp = userdata;
assert(fd >= 0);
assert(lldp);
space = next_datagram_size_fd(fd);
if (space < 0)
return log_lldp_errno(space, "Failed to determine datagram size to read: %m");
n = lldp_neighbor_new(space);
if (!n)
return -ENOMEM;
length = recv(fd, LLDP_NEIGHBOR_RAW(n), n->raw_size, MSG_DONTWAIT);
if (length < 0)
return log_lldp_errno(errno, "Failed to read LLDP datagram: %m");
if ((size_t) length != n->raw_size) {
log_lldp("Packet size mismatch.");
return -EINVAL;
}
return lldp_handle_datagram(lldp, n);
}
_public_ int sd_lldp_start(sd_lldp *lldp) {
int r;
assert_return(lldp, -EINVAL);
@ -509,48 +187,49 @@ int sd_lldp_start(sd_lldp *lldp) {
if (lldp->fd >= 0)
return 0;
assert(!lldp->event_source);
assert(!lldp->io_event_source);
lldp->fd = lldp_network_bind_raw_socket(lldp->ifindex);
if (lldp->fd < 0)
return lldp->fd;
if (lldp->event) {
r = sd_event_add_io(lldp->event, &lldp->event_source, lldp->fd, EPOLLIN, lldp_receive_packet, lldp);
r = sd_event_add_io(lldp->event, &lldp->io_event_source, lldp->fd, EPOLLIN, lldp_receive_datagram, lldp);
if (r < 0)
goto fail;
r = sd_event_source_set_priority(lldp->event_source, lldp->event_priority);
r = sd_event_source_set_priority(lldp->io_event_source, lldp->event_priority);
if (r < 0)
goto fail;
(void) sd_event_source_set_description(lldp->event_source, "lldp");
(void) sd_event_source_set_description(lldp->io_event_source, "lldp-io");
}
return 1;
fail:
lldp->event_source = sd_event_source_unref(lldp->event_source);
lldp->io_event_source = sd_event_source_unref(lldp->io_event_source);
lldp->fd = safe_close(lldp->fd);
return r;
}
int sd_lldp_stop(sd_lldp *lldp) {
_public_ int sd_lldp_stop(sd_lldp *lldp) {
assert_return(lldp, -EINVAL);
if (lldp->fd < 0)
return 0;
lldp->event_source = sd_event_source_unref(lldp->event_source);
lldp->timer_event_source = sd_event_source_unref(lldp->timer_event_source);
lldp->io_event_source = sd_event_source_unref(lldp->io_event_source);
lldp->fd = safe_close(lldp->fd);
lldp_mib_objects_flush(lldp);
lldp_flush_neighbors(lldp);
return 1;
}
int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority) {
_public_ int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority) {
int r;
assert_return(lldp, -EINVAL);
@ -570,17 +249,16 @@ int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority) {
return 0;
}
int sd_lldp_detach_event(sd_lldp *lldp) {
_public_ int sd_lldp_detach_event(sd_lldp *lldp) {
assert_return(lldp, -EINVAL);
assert_return(lldp->fd < 0, -EBUSY);
lldp->event = sd_event_unref(lldp->event);
return 0;
}
int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata) {
_public_ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata) {
assert_return(lldp, -EINVAL);
lldp->callback = cb;
@ -589,26 +267,27 @@ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata) {
return 0;
}
sd_lldp* sd_lldp_unref(sd_lldp *lldp) {
_public_ sd_lldp* sd_lldp_unref(sd_lldp *lldp) {
if (!lldp)
return NULL;
/* Drop all packets */
lldp_mib_objects_flush(lldp);
lldp_flush_neighbors(lldp);
hashmap_free(lldp->neighbour_mib);
prioq_free(lldp->by_expiry);
hashmap_free(lldp->neighbor_by_id);
prioq_free(lldp->neighbor_by_expiry);
sd_event_source_unref(lldp->event_source);
sd_event_source_unref(lldp->io_event_source);
sd_event_source_unref(lldp->timer_event_source);
sd_event_unref(lldp->event);
safe_close(lldp->fd);
free(lldp);
return NULL;
}
int sd_lldp_new(int ifindex, sd_lldp **ret) {
_public_ int sd_lldp_new(sd_lldp **ret, int ifindex) {
_cleanup_(sd_lldp_unrefp) sd_lldp *lldp = NULL;
int r;
@ -621,12 +300,14 @@ int sd_lldp_new(int ifindex, sd_lldp **ret) {
lldp->fd = -1;
lldp->ifindex = ifindex;
lldp->neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX;
lldp->capability_mask = (uint16_t) -1;
lldp->neighbour_mib = hashmap_new(&chassis_id_hash_ops);
if (!lldp->neighbour_mib)
lldp->neighbor_by_id = hashmap_new(&lldp_neighbor_id_hash_ops);
if (!lldp->neighbor_by_id)
return -ENOMEM;
r = prioq_ensure_allocated(&lldp->by_expiry, ttl_expiry_item_prioq_compare_func);
r = prioq_ensure_allocated(&lldp->neighbor_by_expiry, lldp_neighbor_prioq_compare_func);
if (r < 0)
return r;
@ -636,34 +317,124 @@ int sd_lldp_new(int ifindex, sd_lldp **ret) {
return 0;
}
int sd_lldp_get_packets(sd_lldp *lldp, sd_lldp_packet ***tlvs) {
lldp_neighbour_port *p;
lldp_chassis *c;
Iterator iter;
unsigned count = 0, i;
static int neighbor_compare_func(const void *a, const void *b) {
const sd_lldp_neighbor * const*x = a, * const *y = b;
assert_return(lldp, -EINVAL);
assert_return(tlvs, -EINVAL);
return lldp_neighbor_id_hash_ops.compare(&(*x)->id, &(*y)->id);
}
HASHMAP_FOREACH(c, lldp->neighbour_mib, iter) {
LIST_FOREACH(port, p, c->ports)
count++;
}
static int lldp_start_timer(sd_lldp *lldp);
static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) {
sd_lldp *lldp = userdata;
int r, q;
r = lldp_make_space(lldp, 0);
if (r < 0)
return log_lldp_errno(r, "Failed to make space: %m");
q = lldp_start_timer(lldp);
if (q < 0)
return log_lldp_errno(q, "Failed to restart timer: %m");
log_lldp("LLDP timer event hit.");
if (r > 0 && lldp->callback) /* Invoke callback if we dropped an entry */
lldp->callback(lldp, lldp->userdata);
return 0;
}
static int lldp_start_timer(sd_lldp *lldp) {
sd_lldp_neighbor *n;
int r;
assert(lldp);
n = prioq_peek(lldp->neighbor_by_expiry);
if (!n) {
if (lldp->timer_event_source)
return sd_event_source_set_enabled(lldp->timer_event_source, SD_EVENT_OFF);
if (!count) {
*tlvs = NULL;
return 0;
}
*tlvs = new(sd_lldp_packet *, count);
if (!*tlvs)
return -ENOMEM;
if (lldp->timer_event_source) {
r = sd_event_source_set_time(lldp->timer_event_source, n->until);
if (r < 0)
return r;
i = 0;
HASHMAP_FOREACH(c, lldp->neighbour_mib, iter) {
LIST_FOREACH(port, p, c->ports)
(*tlvs)[i++] = sd_lldp_packet_ref(p->packet);
return sd_event_source_set_enabled(lldp->timer_event_source, SD_EVENT_ONESHOT);
}
return count;
if (!lldp->event)
return 0;
r = sd_event_add_time(lldp->event, &lldp->timer_event_source, clock_boottime_or_monotonic(), n->until, 0, on_timer_event, lldp);
if (r < 0)
return r;
r = sd_event_source_set_priority(lldp->timer_event_source, lldp->event_priority);
if (r < 0)
return r;
(void) sd_event_source_set_description(lldp->timer_event_source, "lldp-timer");
return 0;
}
_public_ int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***ret) {
sd_lldp_neighbor **l = NULL, *n;
Iterator i;
int k = 0, r;
assert_return(lldp, -EINVAL);
assert_return(ret, -EINVAL);
/* Flush out old entries, before we return data */
(void) lldp_make_space(lldp, 0);
if (hashmap_isempty(lldp->neighbor_by_id)) { /* Special shortcut */
*ret = NULL;
return 0;
}
l = new0(sd_lldp_neighbor*, hashmap_size(lldp->neighbor_by_id));
if (!l)
return -ENOMEM;
r = lldp_start_timer(lldp);
if (r < 0) {
free(l);
return r;
}
HASHMAP_FOREACH(n, lldp->neighbor_by_id, i)
l[k++] = sd_lldp_neighbor_ref(n);
assert((size_t) k == hashmap_size(lldp->neighbor_by_id));
/* Return things in a stable order */
qsort(l, k, sizeof(sd_lldp_neighbor*), neighbor_compare_func);
*ret = l;
return k;
}
_public_ int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t m) {
assert_return(lldp, -EINVAL);
assert_return(m <= 0, -EINVAL);
lldp->neighbors_max = m;
lldp_make_space(lldp, 0);
return 0;
}
_public_ int sd_lldp_match_capabilities(sd_lldp *lldp, uint16_t mask) {
assert_return(lldp, -EINVAL);
assert_return(mask != 0, -EINVAL);
lldp->capability_mask = mask;
return 0;
}

View File

@ -22,6 +22,7 @@
#include <net/ethernet.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "sd-event.h"
#include "sd-lldp.h"
@ -29,7 +30,6 @@
#include "alloc-util.h"
#include "fd-util.h"
#include "lldp-network.h"
#include "lldp-tlv.h"
#include "lldp.h"
#include "macro.h"
#include "string-util.h"
@ -38,211 +38,8 @@
#define TEST_LLDP_TYPE_SYSTEM_NAME "systemd-lldp"
#define TEST_LLDP_TYPE_SYSTEM_DESC "systemd-lldp-desc"
static int test_fd[2];
static struct ether_addr mac_addr = {
.ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}
};
static int lldp_build_tlv_packet(tlv_packet **ret) {
_cleanup_(sd_lldp_packet_unrefp) tlv_packet *m = NULL;
const uint8_t lldp_dst[] = LLDP_MULTICAST_ADDR;
struct ether_header ether = {
.ether_type = htons(ETHERTYPE_LLDP),
};
/* Append Ethernet header */
memcpy(&ether.ether_dhost, lldp_dst, ETHER_ADDR_LEN);
memcpy(&ether.ether_shost, &mac_addr, ETHER_ADDR_LEN);
assert_se(tlv_packet_new(&m) >= 0);
assert_se(tlv_packet_append_bytes(m, &ether, sizeof(struct ether_header)) >= 0);
assert_se(lldp_tlv_packet_open_container(m, LLDP_TYPE_CHASSIS_ID) >= 0);
assert_se(tlv_packet_append_u8(m, LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS) >= 0);
assert_se(tlv_packet_append_bytes(m, &mac_addr, ETHER_ADDR_LEN) >= 0);
assert_se(lldp_tlv_packet_close_container(m) >= 0);
/* port name */
assert_se(lldp_tlv_packet_open_container(m, LLDP_TYPE_PORT_ID) >= 0);
assert_se(tlv_packet_append_u8(m, LLDP_PORT_SUBTYPE_INTERFACE_NAME) >= 0);
assert_se(tlv_packet_append_bytes(m, TEST_LLDP_PORT, strlen(TEST_LLDP_PORT) + 1) >= 0);
assert_se(lldp_tlv_packet_close_container(m) >= 0);
/* ttl */
assert_se(lldp_tlv_packet_open_container(m, LLDP_TYPE_TTL) >= 0);
assert_se(tlv_packet_append_u16(m, 170) >= 0);
assert_se(lldp_tlv_packet_close_container(m) >= 0);
/* system name */
assert_se(lldp_tlv_packet_open_container(m, LLDP_TYPE_SYSTEM_NAME) >= 0);
assert_se(tlv_packet_append_bytes(m, TEST_LLDP_TYPE_SYSTEM_NAME,
strlen(TEST_LLDP_TYPE_SYSTEM_NAME)) >= 0);
assert_se(lldp_tlv_packet_close_container(m) >= 0);
/* system descrition */
assert_se(lldp_tlv_packet_open_container(m, LLDP_TYPE_SYSTEM_DESCRIPTION) >= 0);
assert_se(tlv_packet_append_bytes(m, TEST_LLDP_TYPE_SYSTEM_DESC,
strlen(TEST_LLDP_TYPE_SYSTEM_DESC)) >= 0);
assert_se(lldp_tlv_packet_close_container(m) >= 0);
/* Mark end of packet */
assert_se(lldp_tlv_packet_open_container(m, LLDP_TYPE_END) >= 0);
assert_se(lldp_tlv_packet_close_container(m) >= 0);
*ret = m;
m = NULL;
return 0;
}
static int lldp_parse_chassis_tlv(tlv_packet *m, uint8_t *type) {
uint8_t *p, subtype;
uint16_t length;
assert_se(lldp_tlv_packet_enter_container(m, LLDP_TYPE_CHASSIS_ID) >= 0);
assert_se(tlv_packet_read_u8(m, &subtype) >= 0);
switch (subtype) {
case LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS:
*type = LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS;
assert_se(tlv_packet_read_bytes(m, &p, &length) >= 0);
assert_se(memcmp(p, &mac_addr.ether_addr_octet, ETHER_ADDR_LEN) == 0);
break;
default:
assert_not_reached("Unhandled option");
}
assert_se(lldp_tlv_packet_exit_container(m) >= 0);
return 0;
}
static int lldp_parse_port_id_tlv(tlv_packet *m) {
_cleanup_free_ char *p = NULL;
char *str = NULL;
uint16_t length;
uint8_t subtype;
assert_se(lldp_tlv_packet_enter_container(m, LLDP_TYPE_PORT_ID) >= 0);
assert_se(tlv_packet_read_u8(m, &subtype) >= 0);
switch (subtype) {
case LLDP_PORT_SUBTYPE_INTERFACE_NAME:
assert_se(tlv_packet_read_string(m, &str, &length) >= 0);
p = strndup(str, length-1);
assert_se(p);
assert_se(streq(p, TEST_LLDP_PORT) == 1);
break;
default:
assert_not_reached("Unhandled option");
}
assert_se(lldp_tlv_packet_exit_container(m) >= 0);
return 0;
}
static int lldp_parse_system_name_tlv(tlv_packet *m) {
_cleanup_free_ char *p = NULL;
char *str = NULL;
uint16_t length;
assert_se(lldp_tlv_packet_enter_container(m, LLDP_TYPE_SYSTEM_NAME) >= 0);
assert_se(tlv_packet_read_string(m, &str, &length) >= 0);
p = strndup(str, length);
assert_se(p);
assert_se(streq(p, TEST_LLDP_TYPE_SYSTEM_NAME) == 1);
assert_se(lldp_tlv_packet_exit_container(m) >= 0);
return 1;
}
static int lldp_parse_system_desc_tlv(tlv_packet *m) {
_cleanup_free_ char *p = NULL;
char *str = NULL;
uint16_t length;
assert_se(lldp_tlv_packet_enter_container(m, LLDP_TYPE_SYSTEM_DESCRIPTION) >= 0);
assert_se(tlv_packet_read_string(m, &str, &length) >= 0);
p = strndup(str, length);
assert_se(p);
assert_se(streq(p, TEST_LLDP_TYPE_SYSTEM_DESC) == 1);
assert_se(lldp_tlv_packet_exit_container(m) >= 0);
return 0;
}
static int lldp_parse_ttl_tlv(tlv_packet *m) {
uint16_t ttl;
assert_se(lldp_tlv_packet_enter_container(m, LLDP_TYPE_TTL) >= 0);
assert_se(tlv_packet_read_u16(m, &ttl) >= 0);
assert_se(ttl == 170);
assert_se(lldp_tlv_packet_exit_container(m) >= 0);
return 0;
}
static int lldp_get_destination_type(tlv_packet *m) {
int dest;
assert_se(sd_lldp_packet_get_destination_type(m, &dest) >= 0);
assert_se(dest == SD_LLDP_DESTINATION_TYPE_NEAREST_BRIDGE);
return 0;
}
static int lldp_parse_tlv_packet(tlv_packet *m, int len) {
uint8_t subtype;
assert_se(tlv_packet_parse_pdu(m, len) >= 0);
assert_se(lldp_parse_chassis_tlv(m, &subtype) >= 0);
assert_se(lldp_parse_port_id_tlv(m) >= 0);
assert_se(lldp_parse_system_name_tlv(m) >= 0);
assert_se(lldp_parse_ttl_tlv(m) >= 0);
assert_se(lldp_parse_system_desc_tlv(m) >= 0);
assert_se(lldp_get_destination_type(m) >= 0);
return 0;
}
static void test_parser(void) {
_cleanup_(sd_lldp_packet_unrefp) tlv_packet *tlv = NULL;
/* form a packet */
lldp_build_tlv_packet(&tlv);
/* parse the packet */
tlv_packet_parse_pdu(tlv, tlv->length);
/* verify */
lldp_parse_tlv_packet(tlv, tlv->length);
}
static int test_fd[2] = { -1, -1 };
static int lldp_handler_calls;
int lldp_network_bind_raw_socket(int ifindex) {
if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0, test_fd) < 0)
@ -251,15 +48,14 @@ int lldp_network_bind_raw_socket(int ifindex) {
return test_fd[0];
}
static int lldp_handler_calls;
static void lldp_handler (sd_lldp *lldp, int event, void *userdata) {
static void lldp_handler (sd_lldp *lldp, void *userdata) {
lldp_handler_calls++;
}
static int start_lldp(sd_lldp **lldp, sd_event *e, sd_lldp_callback_t cb, void *cb_data) {
int r;
r = sd_lldp_new(42, lldp);
r = sd_lldp_new(lldp, 42);
if (r < 0)
return r;
@ -296,13 +92,8 @@ static int stop_lldp(sd_lldp *lldp) {
}
static void test_receive_basic_packet(sd_event *e) {
sd_lldp *lldp;
sd_lldp_packet **packets;
uint8_t type, *data;
uint16_t length, ttl;
int dest_type;
char *str;
uint8_t frame[] = {
static const uint8_t frame[] = {
/* Ethernet header */
0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC*/
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */
@ -319,51 +110,53 @@ static void test_receive_basic_packet(sd_event *e) {
0x00, 0x00 /* End Of LLDPDU */
};
sd_lldp *lldp;
sd_lldp_neighbor **neighbors;
uint8_t type;
const void *data;
uint16_t ttl;
size_t length;
const char *str;
lldp_handler_calls = 0;
assert_se(start_lldp(&lldp, e, lldp_handler, NULL) == 0);
assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame));
sd_event_run(e, 0);
assert_se(lldp_handler_calls == 1);
assert_se(sd_lldp_get_packets(lldp, &packets) == 1);
assert_se(sd_lldp_get_neighbors(lldp, &neighbors) == 1);
assert_se(sd_lldp_packet_read_chassis_id(packets[0], &type, &data, &length) == 0);
assert_se(sd_lldp_neighbor_get_chassis_id(neighbors[0], &type, &data, &length) == 0);
assert_se(type == LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS);
assert_se(length == ETH_ALEN);
assert_se(!memcmp(data, "\x00\x01\x02\x03\x04\x05", ETH_ALEN));
assert_se(sd_lldp_packet_read_port_id(packets[0], &type, &data, &length) == 0);
assert_se(sd_lldp_neighbor_get_port_id(neighbors[0], &type, &data, &length) == 0);
assert_se(type == LLDP_PORT_SUBTYPE_INTERFACE_NAME);
assert_se(length == 3);
assert_se(strneq((char *) data, "1/3", 3));
assert_se(sd_lldp_packet_read_port_description(packets[0], &str, &length) == 0);
assert_se(length == 4);
assert_se(strneq(str, "Port", 4));
assert_se(sd_lldp_neighbor_get_port_description(neighbors[0], &str) == 0);
assert_se(streq(str, "Port"));
assert_se(sd_lldp_packet_read_system_name(packets[0], &str, &length) == 0);
assert_se(length == 3);
assert_se(strneq(str, "SYS", 3));
assert_se(sd_lldp_neighbor_get_system_name(neighbors[0], &str) == 0);
assert_se(streq(str, "SYS"));
assert_se(sd_lldp_packet_read_system_description(packets[0], &str, &length) == 0);
assert_se(length == 4); /* This is the real length in the TLV packet */
assert_se(strneq(str, "foo", 3));
assert_se(sd_lldp_neighbor_get_system_description(neighbors[0], &str) == 0);
assert_se(streq(str, "foo"));
assert_se(sd_lldp_packet_read_ttl(packets[0], &ttl) == 0);
assert_se(sd_lldp_neighbor_get_ttl(neighbors[0], &ttl) == 0);
assert_se(ttl == 120);
assert_se(sd_lldp_packet_get_destination_type(packets[0], &dest_type) == 0);
assert_se(dest_type == SD_LLDP_DESTINATION_TYPE_NEAREST_NON_TPMR_BRIDGE);
sd_lldp_packet_unref(packets[0]);
free(packets);
sd_lldp_neighbor_unref(neighbors[0]);
free(neighbors);
assert_se(stop_lldp(lldp) == 0);
}
static void test_receive_incomplete_packet(sd_event *e) {
sd_lldp *lldp;
sd_lldp_packet **packets;
sd_lldp_neighbor **neighbors;
uint8_t frame[] = {
/* Ethernet header */
0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC*/
@ -383,18 +176,14 @@ static void test_receive_incomplete_packet(sd_event *e) {
assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame));
sd_event_run(e, 0);
assert_se(lldp_handler_calls == 0);
assert_se(sd_lldp_get_packets(lldp, &packets) == 0);
assert_se(sd_lldp_get_neighbors(lldp, &neighbors) == 0);
assert_se(stop_lldp(lldp) == 0);
}
static void test_receive_oui_packet(sd_event *e) {
sd_lldp *lldp;
sd_lldp_packet **packets;
uint32_t id32;
uint16_t id16, len;
uint8_t flags;
char *str;
sd_lldp_neighbor **neighbors;
uint8_t frame[] = {
/* Ethernet header */
0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC*/
@ -426,29 +215,30 @@ static void test_receive_oui_packet(sd_event *e) {
assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame));
sd_event_run(e, 0);
assert_se(lldp_handler_calls == 1);
assert_se(sd_lldp_get_packets(lldp, &packets) == 1);
assert_se(sd_lldp_get_neighbors(lldp, &neighbors) == 1);
assert_se(sd_lldp_packet_read_port_vlan_id(packets[0], &id16) == 0);
assert_se(id16 == 0x1234);
assert_se(sd_lldp_neighbor_tlv_rewind(neighbors[0]) >= 0);
assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], LLDP_TYPE_CHASSIS_ID) > 0);
assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], LLDP_TYPE_PORT_ID) > 0);
assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], LLDP_TYPE_TTL) > 0);
assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], LLDP_OUI_802_1, LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID) > 0);
assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], LLDP_OUI_802_1, LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID) > 0);
assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], LLDP_OUI_802_1, LLDP_OUI_802_1_SUBTYPE_VLAN_NAME) > 0);
assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], LLDP_OUI_802_1, LLDP_OUI_802_1_SUBTYPE_MANAGEMENT_VID) > 0);
assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], LLDP_OUI_802_1, LLDP_OUI_802_1_SUBTYPE_LINK_AGGREGATION) > 0);
assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0);
assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], LLDP_TYPE_END) > 0);
assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) == 0);
assert_se(sd_lldp_packet_read_port_protocol_vlan_id(packets[0], &flags, &id16) == 0);
assert_se(flags == 1);
assert_se(id16 == 0x7788);
assert_se(sd_lldp_packet_read_vlan_name(packets[0], &id16, &str, &len) == 0);
assert_se(id16 == 0x1234);
assert_se(len == 6);
assert_se(strneq(str, "Vlan51", 6));
assert_se(sd_lldp_packet_read_management_vid(packets[0], &id16) == 0);
assert_se(id16 == 0x0102);
assert_se(sd_lldp_packet_read_link_aggregation(packets[0], &flags, &id32) == 0);
assert_se(flags == 1);
assert_se(id32 == 0x00140012);
sd_lldp_packet_unref(packets[0]);
free(packets);
sd_lldp_neighbor_unref(neighbors[0]);
free(neighbors);
assert_se(stop_lldp(lldp) == 0);
}
@ -456,7 +246,7 @@ static void test_receive_oui_packet(sd_event *e) {
int main(int argc, char *argv[]) {
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
test_parser();
log_set_max_level(LOG_DEBUG);
/* LLDP reception tests */
assert_se(sd_event_new(&e) == 0);

View File

@ -187,32 +187,7 @@ _public_ int sd_network_link_get_dnssec_negative_trust_anchors(int ifindex, char
return network_link_get_strv(ifindex, "DNSSEC_NTA", nta);
}
_public_ int sd_network_link_get_lldp(int ifindex, char **lldp) {
_cleanup_free_ char *s = NULL, *p = NULL;
size_t size;
int r;
assert_return(ifindex > 0, -EINVAL);
assert_return(lldp, -EINVAL);
if (asprintf(&p, "/run/systemd/netif/lldp/%d", ifindex) < 0)
return -ENOMEM;
r = read_full_file(p, &s, &size);
if (r == -ENOENT)
return -ENODATA;
if (r < 0)
return r;
if (size <= 0)
return -ENODATA;
*lldp = s;
s = NULL;
return 0;
}
int sd_network_link_get_timezone(int ifindex, char **ret) {
_public_ int sd_network_link_get_timezone(int ifindex, char **ret) {
return network_link_get_string(ifindex, "TIMEZONE", ret);
}

View File

@ -23,6 +23,7 @@
#include "sd-device.h"
#include "sd-hwdb.h"
#include "sd-lldp.h"
#include "sd-netlink.h"
#include "sd-network.h"
@ -30,6 +31,7 @@
#include "arphrd-list.h"
#include "device-util.h"
#include "ether-addr-util.h"
#include "fd-util.h"
#include "hwdb-util.h"
#include "lldp.h"
#include "local-addresses.h"
@ -38,6 +40,7 @@
#include "pager.h"
#include "parse-util.h"
#include "socket-util.h"
#include "sparse-endian.h"
#include "stdio-util.h"
#include "string-table.h"
#include "string-util.h"
@ -745,162 +748,29 @@ static int link_status(int argc, char *argv[], void *userdata) {
return 0;
}
const char *lldp_system_capability_to_string(LLDPSystemCapabilities d) _const_;
LLDPSystemCapabilities lldp_system_capability_from_string(const char *d) _pure_;
static char *lldp_capabilities_to_string(uint16_t x) {
static const char characters[] = {
'o', 'p', 'b', 'w', 'r', 't', 'd', 'a', 'c', 's', 'm',
};
char *ret;
unsigned i;
static const char* const lldp_system_capability_table[_LLDP_SYSTEM_CAPABILITIES_MAX + 1] = {
[LLDP_SYSTEM_CAPABILITIES_OTHER] = "O",
[LLDP_SYSTEM_CAPABILITIES_REPEATER] = "P",
[LLDP_SYSTEM_CAPABILITIES_BRIDGE] = "B",
[LLDP_SYSTEM_CAPABILITIES_WLAN_AP] = "W",
[LLDP_SYSTEM_CAPABILITIES_ROUTER] = "R",
[LLDP_SYSTEM_CAPABILITIES_PHONE] = "T",
[LLDP_SYSTEM_CAPABILITIES_DOCSIS] = "D",
[LLDP_SYSTEM_CAPABILITIES_STATION] = "A",
[LLDP_SYSTEM_CAPABILITIES_CVLAN] = "C",
[LLDP_SYSTEM_CAPABILITIES_SVLAN] = "S",
[LLDP_SYSTEM_CAPABILITIES_TPMR] = "M",
[_LLDP_SYSTEM_CAPABILITIES_MAX] = "N/A",
};
DEFINE_STRING_TABLE_LOOKUP(lldp_system_capability, LLDPSystemCapabilities);
static char *lldp_system_caps(uint16_t cap) {
_cleanup_free_ char *s = NULL, *t = NULL;
char *capability;
t = strdup("[ ");
if (!t)
ret = new(char, ELEMENTSOF(characters) + 1);
if (!ret)
return NULL;
if (cap & LLDP_SYSTEM_CAPABILITIES_OTHER) {
s = strjoin(t, lldp_system_capability_to_string(LLDP_SYSTEM_CAPABILITIES_OTHER), " ", NULL);
if (!s)
return NULL;
for (i = 0; i < ELEMENTSOF(characters); i++)
ret[i] = (x & (1U << i)) ? characters[i] : '.';
free(t);
t = s;
}
if (cap & LLDP_SYSTEM_CAPABILITIES_REPEATER) {
s = strjoin(t, lldp_system_capability_to_string(LLDP_SYSTEM_CAPABILITIES_REPEATER), " ", NULL);
if (!s)
return NULL;
free(t);
t = s;
}
if (cap & LLDP_SYSTEM_CAPABILITIES_BRIDGE) {
s = strjoin(t, lldp_system_capability_to_string(LLDP_SYSTEM_CAPABILITIES_BRIDGE), " ", NULL);
if (!s)
return NULL;
free(t);
t = s;
}
if (cap & LLDP_SYSTEM_CAPABILITIES_WLAN_AP) {
s = strjoin(t, lldp_system_capability_to_string(LLDP_SYSTEM_CAPABILITIES_WLAN_AP), " ", NULL);
if (!s)
return NULL;
free(t);
t = s;
}
if (cap & LLDP_SYSTEM_CAPABILITIES_ROUTER) {
s = strjoin(t, lldp_system_capability_to_string(LLDP_SYSTEM_CAPABILITIES_ROUTER), " ", NULL);
if (!s)
return NULL;
free(t);
t = s;
}
if (cap & LLDP_SYSTEM_CAPABILITIES_PHONE) {
s = strjoin(t, lldp_system_capability_to_string(LLDP_SYSTEM_CAPABILITIES_PHONE), " ", NULL);
if (!s)
return NULL;
free(t);
t = s;
}
if (cap & LLDP_SYSTEM_CAPABILITIES_DOCSIS) {
s = strjoin(t, lldp_system_capability_to_string(LLDP_SYSTEM_CAPABILITIES_DOCSIS), " ", NULL);
if (!s)
return NULL;
free(t);
t = s;
}
if (cap & LLDP_SYSTEM_CAPABILITIES_STATION) {
s = strjoin(t, lldp_system_capability_to_string(LLDP_SYSTEM_CAPABILITIES_STATION), " ", NULL);
if (!s)
return NULL;
free(t);
t = s;
}
if (cap & LLDP_SYSTEM_CAPABILITIES_CVLAN) {
s = strjoin(t, lldp_system_capability_to_string(LLDP_SYSTEM_CAPABILITIES_CVLAN), " ", NULL);
if (!s)
return NULL;
free(t);
t = s;
}
if (cap & LLDP_SYSTEM_CAPABILITIES_SVLAN) {
s = strjoin(t, lldp_system_capability_to_string(LLDP_SYSTEM_CAPABILITIES_SVLAN), " ", NULL);
if (!s)
return NULL;
free(t);
t = s;
}
if (cap & LLDP_SYSTEM_CAPABILITIES_TPMR) {
s = strappend(t, lldp_system_capability_to_string(LLDP_SYSTEM_CAPABILITIES_TPMR));
if (!s)
return NULL;
free(t);
}
if (!s) {
s = strappend(t, lldp_system_capability_to_string(_LLDP_SYSTEM_CAPABILITIES_MAX));
if (!s)
return NULL;
free(t);
}
t = strappend(s, "]");
if (!t)
return NULL;
free(s);
capability = t;
s = NULL;
t = NULL;
return capability;
ret[i] = 0;
return ret;
}
static int link_lldp_status(int argc, char *argv[], void *userdata) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
_cleanup_free_ LinkInfo *links = NULL;
double ttl = -1;
uint32_t capability;
int i, r, c, j;
const char *p;
char **s;
pager_open_if_enabled();
@ -925,88 +795,85 @@ static int link_lldp_status(int argc, char *argv[], void *userdata) {
return rtnl_log_parse_error(c);
if (arg_legend)
printf("%s %16s %24s %16s %16s\n", "Local Intf", "Device ID", "Port ID", "TTL", "Capability");
printf("%-16s %-17s %-16s %-11s %-17s %-16s\n",
"LINK",
"CHASSIS ID",
"SYSTEM NAME",
"CAPS",
"PORT ID",
"PORT DESCRIPTION");
for (i = j = 0; i < c; i++) {
_cleanup_free_ char *chassis = NULL, *port = NULL, *cap = NULL, *lldp = NULL;
_cleanup_strv_free_ char **l = NULL;
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *p = NULL;
r = sd_network_link_get_lldp(links[i].ifindex, &lldp);
if (r < 0)
if (asprintf(&p, "/run/systemd/netif/lldp/%i", links[i].ifindex) < 0)
return log_oom();
f = fopen(p, "re");
if (!f) {
if (errno == ENOENT)
continue;
log_warning_errno(errno, "Failed to open %s, ignoring: %m", p);
continue;
}
l = strv_split_newlines(lldp);
if (!l)
return -ENOMEM;
STRV_FOREACH(s, l) {
p = *s;
for (;;) {
_cleanup_free_ char *a = NULL, *b = NULL, *word = NULL;
r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES);
if (r < 0)
return log_error_errno(r, "Failed to parse LLDP syntax \"%s\": %m", *s);
if (r == 0)
break;
r = split_pair(word, "=", &a, &b);
if (r < 0)
continue;
if (streq(a, "_Chassis")) {
r = free_and_strdup(&chassis, b);
if (r < 0)
return r;
} else if (streq(a, "_Port")) {
r = free_and_strdup(&port, b);
if (r < 0)
return r;
} else if (streq(a, "_TTL")) {
long long unsigned x = 0;
usec_t time;
r = safe_atollu(b, &x);
if (r < 0 || (usec_t) x != x)
return log_warning_errno(r < 0 ? r : ERANGE,
"Failed to parse TTL \"%s\": %m", b);
time = now(clock_boottime_or_monotonic());
if (x < time)
continue;
ttl = (double) (x - time) / USEC_PER_SEC;
} else if (streq(a, "_CAP")) {
sscanf(b, "%x", &capability);
cap = lldp_system_caps(capability);
}
for (;;) {
const char *chassis_id = NULL, *port_id = NULL, *system_name = NULL, *port_description = NULL, *capabilities = NULL;
_cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
_cleanup_free_ void *raw = NULL;
uint16_t cc;
le64_t u;
size_t l;
l = fread(&u, 1, sizeof(u), f);
if (l == 0 && feof(f)) /* EOF */
break;
if (l != sizeof(u)) {
log_warning("Premature end of file, ignoring.");
break;
}
if (ttl >= 0) {
printf("%10s %24s %16s %16f %16s\n",
links[i].name,
strna(chassis), strna(port),
ttl, cap);
j++;
raw = new(uint8_t, le64toh(u));
if (!raw)
return log_oom();
if (fread(raw, 1, le64toh(u), f) != le64toh(u)) {
log_warning("Premature end of file, ignoring.");
break;
}
r = sd_lldp_neighbor_from_raw(&n, raw, le64toh(u));
if (r < 0) {
log_warning_errno(r, "Failed to parse LLDP data, ignoring: %m");
break;
}
(void) sd_lldp_neighbor_get_chassis_id_as_string(n, &chassis_id);
(void) sd_lldp_neighbor_get_port_id_as_string(n, &port_id);
(void) sd_lldp_neighbor_get_system_name(n, &system_name);
(void) sd_lldp_neighbor_get_port_description(n, &port_description);
if (sd_lldp_neighbor_get_enabled_capabilities(n, &cc) >= 0)
capabilities = lldp_capabilities_to_string(cc);
printf("%-16s %-17s %-16s %-11s %-17s %-16s\n",
links[i].name,
strna(chassis_id),
strna(system_name),
strna(capabilities),
strna(port_id),
strna(port_description));
}
}
if (arg_legend) {
printf("\nCapability Codes:\n"
"(O) - Other, (P) - Repeater, (B) - Bridge , (W) - WLAN Access Point, (R) = Router,\n"
"(T) - Telephone, (D) - Data Over Cable Service Interface Specifications, (A) - Station,\n"
"(C) - Customer VLAN, (S) - Service VLAN, (M) - Two-port MAC Relay (TPMR)\n\n");
printf("Total entries displayed: %d\n", j);
}
if (arg_legend)
printf("\nCapabilities:\n"
"o - Other; p - Repeater; b - Bridge; w - WLAN Access Point; r - Router;\n"
"t - Telephone; d - DOCSIS cable device; a - Station; c - Customer VLAN;\n"
"s - Service VLAN, m - Two-port MAC Relay (TPMR)\n\n"
"Total entries displayed: %i\n", j);
return 0;
}
@ -1022,7 +889,7 @@ static void help(void) {
"Commands:\n"
" list List links\n"
" status [LINK...] Show link status\n"
" lldp Show lldp information\n"
" lldp Show lldp neighbors\n"
, program_invocation_short_name);
}

View File

@ -26,6 +26,7 @@
#include "dhcp-lease-internal.h"
#include "fd-util.h"
#include "fileio.h"
#include "lldp.h"
#include "netlink-util.h"
#include "network-internal.h"
#include "networkd-link.h"
@ -103,7 +104,7 @@ bool link_lldp_enabled(Link *link) {
if (link->network->bridge)
return false;
return link->network->lldp;
return link->network->lldp_mode != LLDP_MODE_NO;
}
static bool link_ipv4_forward_enabled(Link *link) {
@ -347,18 +348,15 @@ static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) {
r = sd_netlink_message_read_ether_addr(message, IFLA_ADDRESS, &link->mac);
if (r < 0)
log_link_debug(link, "MAC address not found for new device, continuing without");
log_link_debug_errno(link, r, "MAC address not found for new device, continuing without");
r = asprintf(&link->state_file, "/run/systemd/netif/links/%d", link->ifindex);
if (r < 0)
if (asprintf(&link->state_file, "/run/systemd/netif/links/%d", link->ifindex) < 0)
return -ENOMEM;
r = asprintf(&link->lease_file, "/run/systemd/netif/leases/%d", link->ifindex);
if (r < 0)
if (asprintf(&link->lease_file, "/run/systemd/netif/leases/%d", link->ifindex) < 0)
return -ENOMEM;
r = asprintf(&link->lldp_file, "/run/systemd/netif/lldp/%d", link->ifindex);
if (r < 0)
if (asprintf(&link->lldp_file, "/run/systemd/netif/lldp/%d", link->ifindex) < 0)
return -ENOMEM;
r = hashmap_ensure_allocated(&manager->links, NULL);
@ -409,7 +407,6 @@ static void link_free(Link *link) {
free(link->lease_file);
sd_lldp_unref(link->lldp);
free(link->lldp_file);
sd_ipv4ll_unref(link->ipv4ll);
@ -1218,7 +1215,7 @@ static int link_set_bridge(Link *link) {
if (r < 0)
return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_UNICAST_FLOOD attribute: %m");
if(link->network->cost != 0) {
if (link->network->cost != 0) {
r = sd_netlink_message_append_u32(req, IFLA_BRPORT_COST, link->network->cost);
if (r < 0)
return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_COST attribute: %m");
@ -1237,24 +1234,83 @@ static int link_set_bridge(Link *link) {
return r;
}
static void lldp_handler(sd_lldp *lldp, int event, void *userdata) {
Link *link = userdata;
int r;
static int link_lldp_save(Link *link) {
_cleanup_free_ char *temp_path = NULL;
_cleanup_fclose_ FILE *f = NULL;
sd_lldp_neighbor **l = NULL;
int n = 0, r, i;
assert(link);
assert(link->network);
assert(link->manager);
assert(link->lldp_file);
switch (event) {
case SD_LLDP_EVENT_UPDATE_INFO:
r = sd_lldp_save(link->lldp, link->lldp_file);
if (r < 0)
log_link_warning_errno(link, r, "Could not save LLDP: %m");
break;
default:
break;
if (!link->lldp) {
(void) unlink(link->lldp_file);
return 0;
}
r = sd_lldp_get_neighbors(link->lldp, &l);
if (r < 0)
goto finish;
if (r == 0) {
(void) unlink(link->lldp_file);
goto finish;
}
n = r;
r = fopen_temporary(link->lldp_file, &f, &temp_path);
if (r < 0)
goto finish;
fchmod(fileno(f), 0644);
for (i = 0; i < n; i++) {
const void *p;
le64_t u;
size_t sz;
r = sd_lldp_neighbor_get_raw(l[i], &p, &sz);
if (r < 0)
goto finish;
u = htole64(sz);
(void) fwrite(&u, 1, sizeof(u), f);
(void) fwrite(p, 1, sz, f);
}
r = fflush_and_check(f);
if (r < 0)
goto finish;
if (rename(temp_path, link->lldp_file) < 0) {
r = -errno;
goto finish;
}
finish:
if (r < 0) {
(void) unlink(link->lldp_file);
if (temp_path)
(void) unlink(temp_path);
log_link_error_errno(link, r, "Failed to save LLDP data to %s: %m", link->lldp_file);
}
if (l) {
for (i = 0; i < n; i++)
sd_lldp_neighbor_unref(l[i]);
free(l);
}
return r;
}
static void lldp_handler(sd_lldp *lldp, void *userdata) {
Link *link = userdata;
assert(link);
(void) link_lldp_save(link);
}
static int link_acquire_ipv6_conf(Link *link) {
@ -2116,7 +2172,14 @@ static int link_configure(Link *link) {
}
if (link_lldp_enabled(link)) {
r = sd_lldp_new(link->ifindex, &link->lldp);
r = sd_lldp_new(&link->lldp, link->ifindex);
if (r < 0)
return r;
r = sd_lldp_match_capabilities(link->lldp,
link->network->lldp_mode == LLDP_MODE_ROUTERS_ONLY ?
_LLDP_SYSTEM_CAPABILITIES_ALL_ROUTERS :
_LLDP_SYSTEM_CAPABILITIES_ALL);
if (r < 0)
return r;
@ -2714,6 +2777,8 @@ int link_save(Link *link) {
return 0;
}
link_lldp_save(link);
admin_state = link_state_to_string(link->state);
assert(admin_state);
@ -2953,19 +3018,6 @@ int link_save(Link *link) {
}
}
if (link->lldp) {
assert(link->network);
r = sd_lldp_save(link->lldp, link->lldp_file);
if (r < 0)
goto fail;
fprintf(f,
"LLDP_FILE=%s\n",
link->lldp_file);
} else
unlink(link->lldp_file);
r = fflush_and_check(f);
if (r < 0)
goto fail;

View File

@ -40,7 +40,7 @@ Network.DHCPServer, config_parse_bool,
Network.LinkLocalAddressing, config_parse_address_family_boolean, 0, offsetof(Network, link_local)
Network.IPv4LLRoute, config_parse_bool, 0, offsetof(Network, ipv4ll_route)
Network.IPv6Token, config_parse_ipv6token, 0, offsetof(Network, ipv6_token)
Network.LLDP, config_parse_bool, 0, offsetof(Network, lldp)
Network.LLDP, config_parse_lldp_mode, 0, offsetof(Network, lldp_mode)
Network.Address, config_parse_address, 0, 0
Network.Gateway, config_parse_gateway, 0, 0
Network.Domains, config_parse_domains, 0, 0

View File

@ -1013,3 +1013,13 @@ static const char* const dhcp_use_domains_table[_DHCP_USE_DOMAINS_MAX] = {
};
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dhcp_use_domains, DHCPUseDomains, DHCP_USE_DOMAINS_YES);
DEFINE_CONFIG_PARSE_ENUM(config_parse_lldp_mode, lldp_mode, LLDPMode, "Failed to parse LLDP= setting.");
static const char* const lldp_mode_table[_LLDP_MODE_MAX] = {
[LLDP_MODE_NO] = "no",
[LLDP_MODE_YES] = "yes",
[LLDP_MODE_ROUTERS_ONLY] = "routers-only",
};
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(lldp_mode, LLDPMode, LLDP_MODE_YES);

View File

@ -58,6 +58,14 @@ typedef enum DHCPUseDomains {
_DHCP_USE_DOMAINS_INVALID = -1,
} DHCPUseDomains;
typedef enum LLDPMode {
LLDP_MODE_NO = 0,
LLDP_MODE_YES = 1,
LLDP_MODE_ROUTERS_ONLY = 2,
_LLDP_MODE_MAX,
_LLDP_MODE_INVALID = -1,
} LLDPMode;
struct Network {
Manager *manager;
@ -137,7 +145,7 @@ struct Network {
struct ether_addr *mac;
unsigned mtu;
bool lldp;
LLDPMode lldp_mode;
LIST_HEAD(Address, static_addresses);
LIST_HEAD(Route, static_routes);
@ -181,6 +189,7 @@ int config_parse_dhcp_server_dns(const char *unit, const char *filename, unsigne
int config_parse_dhcp_server_ntp(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_dnssec_negative_trust_anchors(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_dhcp_use_domains(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_lldp_mode(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
/* Legacy IPv4LL support */
int config_parse_ipv4ll(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
@ -197,3 +206,6 @@ IPv6PrivacyExtensions ipv6_privacy_extensions_from_string(const char *s) _pure_;
const char* dhcp_use_domains_to_string(DHCPUseDomains p) _const_;
DHCPUseDomains dhcp_use_domains_from_string(const char *s) _pure_;
const char* lldp_mode_to_string(LLDPMode m) _const_;
LLDPMode lldp_mode_from_string(const char *s) _pure_;

View File

@ -30,22 +30,12 @@
_SD_BEGIN_DECLARATIONS;
enum {
SD_LLDP_EVENT_UPDATE_INFO = 0,
};
enum {
SD_LLDP_DESTINATION_TYPE_NEAREST_BRIDGE,
SD_LLDP_DESTINATION_TYPE_NEAREST_NON_TPMR_BRIDGE,
SD_LLDP_DESTINATION_TYPE_NEAREST_CUSTOMER_BRIDGE,
};
typedef struct sd_lldp sd_lldp;
typedef struct sd_lldp_packet sd_lldp_packet;
typedef struct sd_lldp_neighbor sd_lldp_neighbor;
typedef void (*sd_lldp_callback_t)(sd_lldp *lldp, int event, void *userdata);
typedef void (*sd_lldp_callback_t)(sd_lldp *lldp, void *userdata);
int sd_lldp_new(int ifindex, sd_lldp **ret);
int sd_lldp_new(sd_lldp **ret, int ifindex);
sd_lldp* sd_lldp_unref(sd_lldp *lldp);
int sd_lldp_start(sd_lldp *lldp);
@ -55,32 +45,46 @@ int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority);
int sd_lldp_detach_event(sd_lldp *lldp);
int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata);
int sd_lldp_save(sd_lldp *lldp, const char *file);
int sd_lldp_packet_read_chassis_id(sd_lldp_packet *tlv, uint8_t *type, uint8_t **data, uint16_t *length);
int sd_lldp_packet_read_port_id(sd_lldp_packet *tlv, uint8_t *type, uint8_t **data, uint16_t *length);
int sd_lldp_packet_read_ttl(sd_lldp_packet *tlv, uint16_t *ttl);
int sd_lldp_packet_read_system_name(sd_lldp_packet *tlv, char **data, uint16_t *length);
int sd_lldp_packet_read_system_description(sd_lldp_packet *tlv, char **data, uint16_t *length);
int sd_lldp_packet_read_system_capability(sd_lldp_packet *tlv, uint16_t *data);
int sd_lldp_packet_read_port_description(sd_lldp_packet *tlv, char **data, uint16_t *length);
/* Controls how much and what to store in the neighbors database */
int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t n);
int sd_lldp_match_capabilities(sd_lldp *lldp, uint16_t mask);
/* IEEE 802.1 organizationally specific TLVs */
int sd_lldp_packet_read_port_vlan_id(sd_lldp_packet *tlv, uint16_t *id);
int sd_lldp_packet_read_port_protocol_vlan_id(sd_lldp_packet *tlv, uint8_t *flags, uint16_t *id);
int sd_lldp_packet_read_vlan_name(sd_lldp_packet *tlv, uint16_t *vlan_id, char **name, uint16_t *length);
int sd_lldp_packet_read_management_vid(sd_lldp_packet *tlv, uint16_t *id);
int sd_lldp_packet_read_link_aggregation(sd_lldp_packet *tlv, uint8_t *status, uint32_t *id);
int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***neighbors);
sd_lldp_packet *sd_lldp_packet_ref(sd_lldp_packet *tlv);
sd_lldp_packet *sd_lldp_packet_unref(sd_lldp_packet *tlv);
int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size);
sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n);
sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n);
int sd_lldp_packet_get_destination_type(sd_lldp_packet *tlv, int *dest);
/* Access to LLDP frame metadata */
int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address);
int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address);
int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size);
int sd_lldp_get_packets(sd_lldp *lldp, sd_lldp_packet ***tlvs);
/* High-level, direct, parsed out field access. These fields exist at most once, hence may be queried directly. */
int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size);
int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret);
int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size);
int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret);
int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret);
int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret);
int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret);
int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret);
int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret);
int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret);
/* Low-level, iterative TLV access. This is for evertyhing else, it iteratively goes through all available TLVs
* (including the ones covered with the calls above), and allows multiple TLVs for the same fields. */
int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n);
int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n);
int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type);
int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type);
int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[3], uint8_t *subtype);
int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[3], uint8_t subtype);
int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size);
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp, sd_lldp_unref);
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp_packet, sd_lldp_packet_unref);
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp_neighbor, sd_lldp_neighbor_unref);
_SD_END_DECLARATIONS;

View File

@ -133,8 +133,6 @@ int sd_network_link_get_dnssec(int ifindex, char **dnssec);
*/
int sd_network_link_get_dnssec_negative_trust_anchors(int ifindex, char ***nta);
int sd_network_link_get_lldp(int ifindex, char **lldp);
/* Get the search DNS domain names for a given link. */
int sd_network_link_get_search_domains(int ifindex, char ***domains);