/*-*- 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 . ***/ #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; }