lldp-internal.c revision ad1ad5c8e36ea795034fcdac660b15d7c141d55b
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
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 "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
int lldp_read_chassis_id(tlv_packet *tlv,
uint8_t *type,
uint16_t *length,
uint8_t **data) {
uint8_t subtype;
int r;
assert_return(tlv, -EINVAL);
r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_CHASSIS_ID);
if (r < 0)
goto out2;
r = tlv_packet_read_u8(tlv, &subtype);
if (r < 0)
goto out1;
switch (subtype) {
case LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS:
r = tlv_packet_read_bytes(tlv, data, length);
if (r < 0)
goto out1;
break;
default:
r = -ENOTSUP;
break;
}
*type = subtype;
out1:
(void)lldp_tlv_packet_exit_container(tlv);
out2:
return r;
}
int lldp_read_port_id(tlv_packet *tlv,
uint8_t *type,
uint16_t *length,
uint8_t **data) {
uint8_t subtype;
char *s;
int r;
assert_return(tlv, -EINVAL);
r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_PORT_ID);
if (r < 0)
goto out2;
r = tlv_packet_read_u8(tlv, &subtype);
if (r < 0)
goto out1;
switch (subtype) {
case LLDP_PORT_SUBTYPE_INTERFACE_NAME:
r = tlv_packet_read_string(tlv, &s, length);
if (r < 0)
goto out1;
*data = (uint8_t *) s;
break;
case LLDP_PORT_SUBTYPE_MAC_ADDRESS:
r = tlv_packet_read_bytes(tlv, data, length);
if (r < 0)
goto out1;
break;
default:
r = -ENOTSUP;
break;
}
*type = subtype;
out1:
(void)lldp_tlv_packet_exit_container(tlv);
out2:
return r;
}
int lldp_read_ttl(tlv_packet *tlv, uint16_t *ttl) {
int r;
assert_return(tlv, -EINVAL);
r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_TTL);
if (r < 0)
goto out;
r = tlv_packet_read_u16(tlv, ttl);
(void) lldp_tlv_packet_exit_container(tlv);
out:
return r;
}
/* 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 = lldp_read_port_id(tlv, &type, &length, &data);
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 = lldp_read_ttl(tlv, &ttl);
if (r < 0)
return r;
p->until = ttl * USEC_PER_SEC + now(clock_boottime_or_monotonic());
tlv_packet_free(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 = lldp_read_port_id(tlv, &type, &length, &data);
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 = lldp_read_chassis_id(tlv, &subtype, &length, &data);
if (r < 0)
goto drop;
r = lldp_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 (REFCNT_GET(c->n_ref) >= LLDP_MIB_MAX_PORT_PER_CHASSIS) {
log_lldp("Port limit reached. Chassis has: %d ports. Dropping ...",
REFCNT_GET(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);
REFCNT_INC(c->n_ref);
p = NULL;
c = NULL;
return 0;
drop:
tlv_packet_free(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 */
if (REFCNT_DEC(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;
tlv_packet_free(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;
uint16_t length, ttl;
uint8_t *data;
uint8_t type;
int r;
assert(tlv);
r = lldp_read_port_id(tlv, &type, &length, &data);
if (r < 0)
return r;
r = lldp_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 (REFCNT_GET(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;
uint16_t length;
uint8_t *data;
uint8_t type;
int r;
assert(tlv);
r = lldp_read_chassis_id(tlv, &type, &length, &data);
if (r < 0)
return r;
c = new0(lldp_chassis, 1);
if (!c)
return -ENOMEM;
c->n_ref = REFCNT_INIT;
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;
}