| /*-*- 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 = -EOPNOTSUPP; |
| 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_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 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 = -EOPNOTSUPP; |
| 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; |
| } |
| |
| int lldp_read_system_name(tlv_packet *tlv, |
| uint16_t *length, |
| char **data) { |
| char *s; |
| int r; |
| |
| assert_return(tlv, -EINVAL); |
| |
| r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_SYSTEM_NAME); |
| if (r < 0) |
| return r; |
| |
| r = tlv_packet_read_string(tlv, &s, length); |
| if (r < 0) |
| goto out; |
| |
| *data = (char *) s; |
| |
| out: |
| (void) lldp_tlv_packet_exit_container(tlv); |
| |
| return r; |
| } |
| |
| int lldp_read_system_description(tlv_packet *tlv, |
| uint16_t *length, |
| char **data) { |
| char *s; |
| int r; |
| |
| assert_return(tlv, -EINVAL); |
| |
| r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_SYSTEM_DESCRIPTION); |
| if (r < 0) |
| return r; |
| |
| r = tlv_packet_read_string(tlv, &s, length); |
| if (r < 0) |
| goto out; |
| |
| *data = (char *) s; |
| |
| out: |
| (void) lldp_tlv_packet_exit_container(tlv); |
| |
| return r; |
| } |
| |
| int lldp_read_port_description(tlv_packet *tlv, |
| uint16_t *length, |
| char **data) { |
| char *s; |
| int r; |
| |
| assert_return(tlv, -EINVAL); |
| |
| r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_PORT_DESCRIPTION); |
| if (r < 0) |
| return r; |
| |
| r = tlv_packet_read_string(tlv, &s, length); |
| if (r < 0) |
| goto out; |
| |
| *data = (char *) s; |
| |
| out: |
| (void) lldp_tlv_packet_exit_container(tlv); |
| |
| return r; |
| } |
| |
| int lldp_read_system_capability(tlv_packet *tlv, uint16_t *data) { |
| int r; |
| |
| assert_return(tlv, -EINVAL); |
| |
| r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_SYSTEM_CAPABILITIES); |
| if (r < 0) |
| return r; |
| |
| r = tlv_packet_read_u16(tlv, data); |
| if (r < 0) |
| goto out; |
| |
| return 0; |
| out: |
| |
| (void) lldp_tlv_packet_exit_container(tlv); |
| |
| 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 (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: |
| 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 */ |
| 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; |
| |
| 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 = NULL; |
| 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 (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 = 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 = 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; |
| } |