diff options
Diffstat (limited to 'src/daemon/protocols')
-rw-r--r-- | src/daemon/protocols/cdp.c | 711 | ||||
-rw-r--r-- | src/daemon/protocols/cdp.h | 70 | ||||
-rw-r--r-- | src/daemon/protocols/edp.c | 514 | ||||
-rw-r--r-- | src/daemon/protocols/edp.h | 41 | ||||
-rw-r--r-- | src/daemon/protocols/lldp.c | 1315 | ||||
-rw-r--r-- | src/daemon/protocols/sonmp.c | 410 | ||||
-rw-r--r-- | src/daemon/protocols/sonmp.h | 42 |
7 files changed, 3103 insertions, 0 deletions
diff --git a/src/daemon/protocols/cdp.c b/src/daemon/protocols/cdp.c new file mode 100644 index 0000000..620455b --- /dev/null +++ b/src/daemon/protocols/cdp.c @@ -0,0 +1,711 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* We also supports FDP which is very similar to CDPv1 */ +#include "../lldpd.h" +#include "../frame.h" + +/* + * CDP Requests Power at the switch output and therefore has to take into + * account the loss in the PoE cable. This is done by the switch automatically + * if lldp is used as the protocol. + */ +#define CDP_CLASS_3_MAX_PSE_POE 154 /* 15.4W Max PoE at PSE class 3 */ +#define CDP_SWTICH_DEFAULT_POE_PD 130 /* 13.W default PoE at PD */ +#define CDP_SWTICH_DEFAULT_POE_PSE 154 /* 15.4W default PoE at PSE */ +#define CDP_SWITCH_POE_CLASS_4_OFFSET 45 /* 4.5W max loss from cable */ +#define CDP_SWITCH_POE_CLASS_3_OFFSET 24 /* 2.4W max loss from cable */ + +#if defined ENABLE_CDP || defined ENABLE_FDP + +# include <stdio.h> +# include <unistd.h> +# include <errno.h> +# include <arpa/inet.h> + +static int +cdp_send(struct lldpd *global, struct lldpd_hardware *hardware, int version) +{ + const char *platform = "Unknown"; + struct lldpd_chassis *chassis; + struct lldpd_mgmt *mgmt; + struct lldpd_port *port; + u_int8_t mcastaddr[] = CDP_MULTICAST_ADDR; + u_int8_t llcorg[] = LLC_ORG_CISCO; +# ifdef ENABLE_FDP + const char *capstr; +# endif + u_int16_t checksum; + int length, i; + u_int32_t cap; + u_int8_t *packet; + u_int8_t *pos, *pos_len_eh, *pos_llc, *pos_cdp, *pos_checksum, *tlv, *end; + + log_debug("cdp", "send CDP frame on %s", hardware->h_ifname); + + port = &(hardware->h_lport); + chassis = port->p_chassis; + +# ifdef ENABLE_FDP + if (version == 0) { + /* With FDP, change multicast address and LLC PID */ + const u_int8_t fdpmcastaddr[] = FDP_MULTICAST_ADDR; + const u_int8_t fdpllcorg[] = LLC_ORG_FOUNDRY; + memcpy(mcastaddr, fdpmcastaddr, sizeof(mcastaddr)); + memcpy(llcorg, fdpllcorg, sizeof(llcorg)); + } +# endif + + length = hardware->h_mtu; + if ((packet = (u_int8_t *)calloc(1, length)) == NULL) return ENOMEM; + pos = packet; + + /* Ethernet header */ + if (!(POKE_BYTES(mcastaddr, sizeof(mcastaddr)) && + POKE_BYTES(&hardware->h_lladdr, ETHER_ADDR_LEN) && + POKE_SAVE(pos_len_eh) && /* We compute the len later */ + POKE_UINT16(0))) + goto toobig; + + /* LLC */ + if (!(POKE_SAVE(pos_llc) && POKE_UINT8(0xaa) && /* SSAP */ + POKE_UINT8(0xaa) && /* DSAP */ + POKE_UINT8(0x03) && /* Control field */ + POKE_BYTES(llcorg, sizeof(llcorg)) && POKE_UINT16(LLC_PID_CDP))) + goto toobig; + + /* CDP header */ + if (!(POKE_SAVE(pos_cdp) && POKE_UINT8((version == 0) ? 1 : version) && + POKE_UINT8(global ? global->g_config.c_ttl : 180) && + POKE_SAVE(pos_checksum) && /* Save checksum position */ + POKE_UINT16(0))) + goto toobig; + + /* Chassis ID */ + const char *chassis_name = chassis->c_name ? chassis->c_name : ""; + if (!(POKE_START_CDP_TLV(CDP_TLV_CHASSIS) && + POKE_BYTES(chassis_name, strlen(chassis_name)) && POKE_END_CDP_TLV)) + goto toobig; + + /* Adresses */ + /* See: + * http://www.cisco.com/univercd/cc/td/doc/product/lan/trsrb/frames.htm#xtocid12 + * + * It seems that Cisco implies that CDP supports IPv6 using + * 802.2 address format with 0xAAAA03 0x000000 0x0800, but + * 0x0800 is the Ethernet protocol type for IPv4. Therefore, + * we support only IPv4. */ + i = 0; + TAILQ_FOREACH (mgmt, &chassis->c_mgmt, m_entries) + if (mgmt->m_family == LLDPD_AF_IPV4) i++; + if (i > 0) { + if (!(POKE_START_CDP_TLV(CDP_TLV_ADDRESSES) && POKE_UINT32(i))) + goto toobig; + TAILQ_FOREACH (mgmt, &chassis->c_mgmt, m_entries) { + switch (mgmt->m_family) { + case LLDPD_AF_IPV4: + if (!(POKE_UINT8(1) && /* Type: NLPID */ + POKE_UINT8(1) && /* Length: 1 */ + POKE_UINT8(CDP_ADDRESS_PROTO_IP) && /* IP */ + POKE_UINT16(sizeof( + struct in_addr)) && /* Address length */ + POKE_BYTES(&mgmt->m_addr, + sizeof(struct in_addr)))) + goto toobig; + break; + } + } + if (!(POKE_END_CDP_TLV)) goto toobig; + } + + /* Port ID */ + const char *port_descr = + hardware->h_lport.p_descr ? hardware->h_lport.p_descr : ""; + if (!(POKE_START_CDP_TLV(CDP_TLV_PORT) && + POKE_BYTES(port_descr, strlen(port_descr)) && POKE_END_CDP_TLV)) + goto toobig; + + /* Capabilities */ + if (version != 0) { + cap = 0; + if (chassis->c_cap_enabled & LLDP_CAP_ROUTER) cap |= CDP_CAP_ROUTER; + if (chassis->c_cap_enabled & LLDP_CAP_BRIDGE) cap |= CDP_CAP_SWITCH; + cap |= CDP_CAP_HOST; + if (!(POKE_START_CDP_TLV(CDP_TLV_CAPABILITIES) && POKE_UINT32(cap) && + POKE_END_CDP_TLV)) + goto toobig; +# ifdef ENABLE_FDP + } else { + /* With FDP, it seems that a string is used in place of an int */ + if (chassis->c_cap_enabled & LLDP_CAP_ROUTER) + capstr = "Router"; + else if (chassis->c_cap_enabled & LLDP_CAP_BRIDGE) + capstr = "Switch"; + else if (chassis->c_cap_enabled & LLDP_CAP_REPEATER) + capstr = "Bridge"; + else + capstr = "Host"; + if (!(POKE_START_CDP_TLV(CDP_TLV_CAPABILITIES) && + POKE_BYTES(capstr, strlen(capstr)) && POKE_END_CDP_TLV)) + goto toobig; +# endif + } + + /* Native VLAN */ +# ifdef ENABLE_DOT1 + if (version >= 2 && hardware->h_lport.p_pvid != 0) { + if (!(POKE_START_CDP_TLV(CDP_TLV_NATIVEVLAN) && + POKE_UINT16(hardware->h_lport.p_pvid) && POKE_END_CDP_TLV)) + goto toobig; + } +# endif + + /* Software version */ + const char *chassis_descr = chassis->c_descr ? chassis->c_descr : ""; + if (!(POKE_START_CDP_TLV(CDP_TLV_SOFTWARE) && + POKE_BYTES(chassis_descr, strlen(chassis_descr)) && POKE_END_CDP_TLV)) + goto toobig; + + /* Platform */ + if (global && global->g_config.c_platform) + platform = global->g_config.c_platform; + + if (!(POKE_START_CDP_TLV(CDP_TLV_PLATFORM) && + POKE_BYTES(platform, strlen(platform)) && POKE_END_CDP_TLV)) + goto toobig; + +# ifdef ENABLE_DOT3 + if ((version >= 2) && (port->p_power.powertype != LLDP_DOT3_POWER_8023AT_OFF) && + (port->p_power.devicetype == LLDP_DOT3_POWER_PD) && + (port->p_power.requested > 0) && (port->p_power.requested <= 655)) { + u_int16_t requested; + u_int16_t consumption; + + if (port->p_power.requested != port->p_power.allocated) { + port->p_cdp_power.request_id++; + log_debug("cdp", "requested: %d, allocated:%d", + port->p_power.requested, port->p_power.allocated); + } + consumption = port->p_power.allocated ? port->p_power.allocated : + CDP_SWTICH_DEFAULT_POE_PD; + if (consumption > 130) { + consumption += CDP_SWITCH_POE_CLASS_4_OFFSET; + } else { + consumption += CDP_SWITCH_POE_CLASS_3_OFFSET; + } + if (port->p_power.requested > 130) { /* Class 4 */ + requested = + port->p_power.requested + CDP_SWITCH_POE_CLASS_4_OFFSET; + } else { /* Class 3 */ + requested = + port->p_power.requested + CDP_SWITCH_POE_CLASS_3_OFFSET; + } + if (!(POKE_START_CDP_TLV(CDP_TLV_POWER_CONSUMPTION) && + POKE_UINT16(consumption * 100) && POKE_END_CDP_TLV)) + goto toobig; + /* Avoid request id 0 from overflow */ + if (!port->p_cdp_power.request_id) { + port->p_cdp_power.request_id = 1; + } + if (!port->p_cdp_power.management_id) { + port->p_cdp_power.management_id = 1; + } + if (!(POKE_START_CDP_TLV(CDP_TLV_POWER_REQUESTED) && + POKE_UINT16(port->p_cdp_power.request_id) && + POKE_UINT16(port->p_cdp_power.management_id) && + POKE_UINT32(requested * 100) && POKE_END_CDP_TLV)) + goto toobig; + } +# elif defined ENABLE_LLDPMED + /* Power use */ + if ((version >= 2) && port->p_med_cap_enabled && + (port->p_med_power.source != LLDP_MED_POW_SOURCE_LOCAL) && + (port->p_med_power.val > 0) && (port->p_med_power.val <= 655)) { + if (!(POKE_START_CDP_TLV(CDP_TLV_POWER_CONSUMPTION) && + POKE_UINT16(port->p_med_power.val * 100) && POKE_END_CDP_TLV)) + goto toobig; + } +# endif + + (void)POKE_SAVE(end); + + /* Compute len and checksum */ + POKE_RESTORE(pos_len_eh); + if (!(POKE_UINT16(end - pos_llc))) goto toobig; + checksum = frame_checksum(pos_cdp, end - pos_cdp, (version != 0) ? 1 : 0); + POKE_RESTORE(pos_checksum); + if (!(POKE_UINT16(checksum))) goto toobig; + + if (interfaces_send_helper(global, hardware, (char *)packet, end - packet) == + -1) { + log_warn("cdp", "unable to send packet on real device for %s", + hardware->h_ifname); + free(packet); + return ENETDOWN; + } + + hardware->h_tx_cnt++; + + free(packet); + return 0; +toobig: + free(packet); + return -1; +} + +# define CHECK_TLV_SIZE(x, name) \ + do { \ + if (tlv_len < (x)) { \ + log_warnx("cdp", name " CDP/FDP TLV too short received on %s", \ + hardware->h_ifname); \ + goto malformed; \ + } \ + } while (0) +/* cdp_decode also decodes FDP */ +int +cdp_decode(struct lldpd *cfg, char *frame, int s, struct lldpd_hardware *hardware, + struct lldpd_chassis **newchassis, struct lldpd_port **newport) +{ + struct lldpd_chassis *chassis; + struct lldpd_port *port; + struct lldpd_mgmt *mgmt; + struct in_addr addr; +# if 0 + u_int16_t cksum; +# endif + u_int8_t *software = NULL, *platform = NULL; + int software_len = 0, platform_len = 0, proto, version, nb, caps; + const unsigned char cdpaddr[] = CDP_MULTICAST_ADDR; +# ifdef ENABLE_FDP + const unsigned char fdpaddr[] = CDP_MULTICAST_ADDR; + int fdp = 0; +# endif + u_int8_t *pos, *tlv, *pos_address, *pos_next_address; + int length, len_eth, tlv_type, tlv_len, addresses_len, address_len; +# ifdef ENABLE_DOT1 + struct lldpd_vlan *vlan; +# endif + + log_debug("cdp", "decode CDP frame received on %s", hardware->h_ifname); + + if ((chassis = calloc(1, sizeof(struct lldpd_chassis))) == NULL) { + log_warn("cdp", "failed to allocate remote chassis"); + return -1; + } + TAILQ_INIT(&chassis->c_mgmt); + if ((port = calloc(1, sizeof(struct lldpd_port))) == NULL) { + log_warn("cdp", "failed to allocate remote port"); + free(chassis); + return -1; + } +# ifdef ENABLE_DOT1 + TAILQ_INIT(&port->p_vlans); +# endif + + length = s; + pos = (u_int8_t *)frame; + + if (length < 2 * ETHER_ADDR_LEN + sizeof(u_int16_t) /* Ethernet */ + + 8 /* LLC */ + 4 /* CDP header */) { + log_warn("cdp", "too short CDP/FDP frame received on %s", + hardware->h_ifname); + goto malformed; + } + + if (PEEK_CMP(cdpaddr, sizeof(cdpaddr)) != 0) { +# ifdef ENABLE_FDP + PEEK_RESTORE((u_int8_t *)frame); + if (PEEK_CMP(fdpaddr, sizeof(fdpaddr)) != 0) + fdp = 1; + else { +# endif + log_info("cdp", + "frame not targeted at CDP/FDP multicast address received on %s", + hardware->h_ifname); + goto malformed; +# ifdef ENABLE_FDP + } +# endif + } + PEEK_DISCARD(ETHER_ADDR_LEN); /* Don't care of source address */ + len_eth = PEEK_UINT16; + if (len_eth > length) { + log_warnx("cdp", "incorrect 802.3 frame size reported on %s", + hardware->h_ifname); + goto malformed; + } + + /* This is the correct length of the CDP + LLC packets */ + length = len_eth; + + PEEK_DISCARD(6); /* Skip beginning of LLC */ + proto = PEEK_UINT16; + if (proto != LLC_PID_CDP) { + if ((proto != LLC_PID_DRIP) && (proto != LLC_PID_PAGP) && + (proto != LLC_PID_PVSTP) && (proto != LLC_PID_UDLD) && + (proto != LLC_PID_VTP) && (proto != LLC_PID_DTP) && + (proto != LLC_PID_STP)) + log_debug("cdp", "incorrect LLC protocol ID received on %s", + hardware->h_ifname); + goto malformed; + } + +# if 0 + /* Check checksum */ + cksum = frame_checksum(pos, len_eth - 8, +# ifdef ENABLE_FDP + !fdp /* fdp = 0 -> cisco checksum */ +# else + 1 /* cisco checksum */ +# endif + ); + if (cksum != 0) { + log_info("cdp", "incorrect CDP/FDP checksum for frame received on %s (%d)", + hardware->h_ifname, cksum); + goto malformed; + } +# endif + + /* Check version */ + version = PEEK_UINT8; + if ((version != 1) && (version != 2)) { + log_warnx("cdp", + "incorrect CDP/FDP version (%d) for frame received on %s", version, + hardware->h_ifname); + goto malformed; + } + port->p_ttl = PEEK_UINT8; /* TTL */ + PEEK_DISCARD_UINT16; /* Checksum, already checked */ + + while (length) { + if (length < 4) { + log_warnx("cdp", + "CDP/FDP TLV header is too large for " + "frame received on %s", + hardware->h_ifname); + goto malformed; + } + tlv_type = PEEK_UINT16; + tlv_len = PEEK_UINT16 - 4; + + (void)PEEK_SAVE(tlv); + if ((tlv_len < 0) || (length < tlv_len)) { + log_warnx("cdp", + "incorrect size in CDP/FDP TLV header for frame " + "received on %s", + hardware->h_ifname); + goto malformed; + } + switch (tlv_type) { + case CDP_TLV_CHASSIS: + free(chassis->c_name); + if ((chassis->c_name = (char *)calloc(1, tlv_len + 1)) == + NULL) { + log_warn("cdp", + "unable to allocate memory for chassis name"); + goto malformed; + } + PEEK_BYTES(chassis->c_name, tlv_len); + chassis->c_id_subtype = LLDP_CHASSISID_SUBTYPE_LOCAL; + free(chassis->c_id); + if ((chassis->c_id = (char *)malloc(tlv_len)) == NULL) { + log_warn("cdp", + "unable to allocate memory for chassis ID"); + goto malformed; + } + memcpy(chassis->c_id, chassis->c_name, tlv_len); + chassis->c_id_len = tlv_len; + break; + case CDP_TLV_ADDRESSES: + CHECK_TLV_SIZE(4, "Address"); + addresses_len = tlv_len - 4; + for (nb = PEEK_UINT32; nb > 0; nb--) { + (void)PEEK_SAVE(pos_address); + /* We first try to get the real length of the packet */ + if (addresses_len < 2) { + log_warn("cdp", + "too short address subframe " + "received on %s", + hardware->h_ifname); + goto malformed; + } + PEEK_DISCARD_UINT8; + addresses_len--; + address_len = PEEK_UINT8; + addresses_len--; + if (addresses_len < address_len + 2) { + log_warn("cdp", + "too short address subframe " + "received on %s", + hardware->h_ifname); + goto malformed; + } + PEEK_DISCARD(address_len); + addresses_len -= address_len; + address_len = PEEK_UINT16; + addresses_len -= 2; + if (addresses_len < address_len) { + log_warn("cdp", + "too short address subframe " + "received on %s", + hardware->h_ifname); + goto malformed; + } + PEEK_DISCARD(address_len); + addresses_len -= address_len; + (void)PEEK_SAVE(pos_next_address); + /* Next, we go back and try to extract + IPv4 address */ + PEEK_RESTORE(pos_address); + if ((PEEK_UINT8 == 1) && (PEEK_UINT8 == 1) && + (PEEK_UINT8 == CDP_ADDRESS_PROTO_IP) && + (PEEK_UINT16 == sizeof(struct in_addr))) { + PEEK_BYTES(&addr, sizeof(struct in_addr)); + mgmt = lldpd_alloc_mgmt(LLDPD_AF_IPV4, &addr, + sizeof(struct in_addr), 0); + if (mgmt == NULL) { + if (errno == ENOMEM) + log_warn("cdp", + "unable to allocate memory for management address"); + else + log_warn("cdp", + "too large management address received on %s", + hardware->h_ifname); + goto malformed; + } + TAILQ_INSERT_TAIL(&chassis->c_mgmt, mgmt, + m_entries); + } + /* Go to the end of the address */ + PEEK_RESTORE(pos_next_address); + } + break; + case CDP_TLV_PORT: + if (tlv_len == 0) { + log_warn("cdp", "too short port description received"); + goto malformed; + } + free(port->p_descr); + if ((port->p_descr = (char *)calloc(1, tlv_len + 1)) == NULL) { + log_warn("cdp", + "unable to allocate memory for port description"); + goto malformed; + } + PEEK_BYTES(port->p_descr, tlv_len); + port->p_id_subtype = LLDP_PORTID_SUBTYPE_IFNAME; + free(port->p_id); + if ((port->p_id = (char *)calloc(1, tlv_len)) == NULL) { + log_warn("cdp", + "unable to allocate memory for port ID"); + goto malformed; + } + memcpy(port->p_id, port->p_descr, tlv_len); + port->p_id_len = tlv_len; + break; + case CDP_TLV_CAPABILITIES: +# ifdef ENABLE_FDP + if (fdp) { + /* Capabilities are string with FDP */ + if (!strncmp("Router", (char *)pos, tlv_len)) + chassis->c_cap_enabled = LLDP_CAP_ROUTER; + else if (!strncmp("Switch", (char *)pos, tlv_len)) + chassis->c_cap_enabled = LLDP_CAP_BRIDGE; + else if (!strncmp("Bridge", (char *)pos, tlv_len)) + chassis->c_cap_enabled = LLDP_CAP_REPEATER; + else + chassis->c_cap_enabled = LLDP_CAP_STATION; + chassis->c_cap_available = chassis->c_cap_enabled; + break; + } +# endif + CHECK_TLV_SIZE(4, "Capabilities"); + caps = PEEK_UINT32; + if (caps & CDP_CAP_ROUTER) + chassis->c_cap_enabled |= LLDP_CAP_ROUTER; + if (caps & 0x0e) chassis->c_cap_enabled |= LLDP_CAP_BRIDGE; + if (chassis->c_cap_enabled == 0) + chassis->c_cap_enabled = LLDP_CAP_STATION; + chassis->c_cap_available = chassis->c_cap_enabled; + break; + case CDP_TLV_SOFTWARE: + software_len = tlv_len; + (void)PEEK_SAVE(software); + break; + case CDP_TLV_PLATFORM: + platform_len = tlv_len; + (void)PEEK_SAVE(platform); + break; +# ifdef ENABLE_DOT1 + case CDP_TLV_NATIVEVLAN: + CHECK_TLV_SIZE(2, "Native VLAN"); + if ((vlan = (struct lldpd_vlan *)calloc(1, + sizeof(struct lldpd_vlan))) == NULL) { + log_warn("cdp", + "unable to alloc vlan " + "structure for " + "tlv received on %s", + hardware->h_ifname); + goto malformed; + } + vlan->v_vid = port->p_pvid = PEEK_UINT16; + if (asprintf(&vlan->v_name, "VLAN #%d", vlan->v_vid) == -1) { + log_warn("cdp", + "unable to alloc VLAN name for " + "TLV received on %s", + hardware->h_ifname); + free(vlan); + goto malformed; + } + TAILQ_INSERT_TAIL(&port->p_vlans, vlan, v_entries); + break; +# endif +# ifdef ENABLE_DOT3 + case CDP_TLV_POWER_AVAILABLE: + CHECK_TLV_SIZE(12, "Power Available"); + /* check if it is a respone to a request id */ + if (PEEK_UINT16 > 0) { + port->p_cdp_power.management_id = PEEK_UINT16; + port->p_power.allocated = PEEK_UINT32; + port->p_power.allocated /= 100; + port->p_power.supported = 1; + port->p_power.enabled = 1; + port->p_power.devicetype = LLDP_DOT3_POWER_PSE; + port->p_power.powertype = LLDP_DOT3_POWER_8023AT_TYPE2; + log_debug("cdp", "Allocated power %d00", + port->p_power.allocated); + if (port->p_power.allocated > CDP_CLASS_3_MAX_PSE_POE) { + port->p_power.allocated -= + CDP_SWITCH_POE_CLASS_4_OFFSET; + } else if (port->p_power.allocated > + CDP_SWITCH_POE_CLASS_3_OFFSET) { + port->p_power.allocated -= + CDP_SWITCH_POE_CLASS_3_OFFSET; + } else { + port->p_power.allocated = 0; + } + port->p_power.requested = + hardware->h_lport.p_power.requested; + } + break; +# endif + default: + log_debug("cdp", "unknown CDP/FDP TLV type (%d) received on %s", + ntohs(tlv_type), hardware->h_ifname); + hardware->h_rx_unrecognized_cnt++; + } + PEEK_DISCARD(tlv + tlv_len - pos); + } + if (!software && platform) { + if ((chassis->c_descr = (char *)calloc(1, platform_len + 1)) == NULL) { + log_warn("cdp", + "unable to allocate memory for chassis description"); + goto malformed; + } + memcpy(chassis->c_descr, platform, platform_len); + } else if (software && !platform) { + if ((chassis->c_descr = (char *)calloc(1, software_len + 1)) == NULL) { + log_warn("cdp", + "unable to allocate memory for chassis description"); + goto malformed; + } + memcpy(chassis->c_descr, software, software_len); + } else if (software && platform) { +# define CONCAT_PLATFORM " running on\n" + if ((chassis->c_descr = (char *)calloc(1, + software_len + platform_len + strlen(CONCAT_PLATFORM) + 1)) == + NULL) { + log_warn("cdp", + "unable to allocate memory for chassis description"); + goto malformed; + } + memcpy(chassis->c_descr, platform, platform_len); + memcpy(chassis->c_descr + platform_len, CONCAT_PLATFORM, + strlen(CONCAT_PLATFORM)); + memcpy(chassis->c_descr + platform_len + strlen(CONCAT_PLATFORM), + software, software_len); + } + if ((chassis->c_id == NULL) || (port->p_id == NULL) || + (chassis->c_name == NULL) || (chassis->c_descr == NULL) || + (port->p_descr == NULL) || (port->p_ttl == 0) || + (chassis->c_cap_enabled == 0)) { + log_warnx("cdp", + "some mandatory CDP/FDP tlv are missing for frame received on %s", + hardware->h_ifname); + goto malformed; + } + *newchassis = chassis; + *newport = port; + return 1; + +malformed: + lldpd_chassis_cleanup(chassis, 1); + lldpd_port_cleanup(port, 1); + free(port); + return -1; +} + +# ifdef ENABLE_CDP +int +cdpv1_send(struct lldpd *global, struct lldpd_hardware *hardware) +{ + return cdp_send(global, hardware, 1); +} + +int +cdpv2_send(struct lldpd *global, struct lldpd_hardware *hardware) +{ + return cdp_send(global, hardware, 2); +} +# endif + +# ifdef ENABLE_FDP +int +fdp_send(struct lldpd *global, struct lldpd_hardware *hardware) +{ + return cdp_send(global, hardware, 0); +} +# endif + +# ifdef ENABLE_CDP +static int +cdp_guess(char *pos, int length, int version) +{ + const u_int8_t mcastaddr[] = CDP_MULTICAST_ADDR; + if (length < 2 * ETHER_ADDR_LEN + sizeof(u_int16_t) /* Ethernet */ + + 8 /* LLC */ + 4 /* CDP header */) + return 0; + if (PEEK_CMP(mcastaddr, ETHER_ADDR_LEN) != 0) return 0; + PEEK_DISCARD(ETHER_ADDR_LEN); + PEEK_DISCARD_UINT16; /* Ethernet */ + PEEK_DISCARD(8); /* LLC */ + return (PEEK_UINT8 == version); +} + +int +cdpv1_guess(char *frame, int len) +{ + return cdp_guess(frame, len, 1); +} + +int +cdpv2_guess(char *frame, int len) +{ + return cdp_guess(frame, len, 2); +} +# endif + +#endif /* defined (ENABLE_CDP) || defined (ENABLE_FDP) */ diff --git a/src/daemon/protocols/cdp.h b/src/daemon/protocols/cdp.h new file mode 100644 index 0000000..f83ffef --- /dev/null +++ b/src/daemon/protocols/cdp.h @@ -0,0 +1,70 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _CDP_H +#define _CDP_H + +#define CDP_MULTICAST_ADDR \ + { \ + 0x01, 0x00, 0x0c, 0xcc, 0xcc, 0xcc \ + } +#define FDP_MULTICAST_ADDR \ + { \ + 0x01, 0xe0, 0x52, 0xcc, 0xcc, 0xcc, \ + } +#define LLC_ORG_CISCO \ + { \ + 0x00, 0x00, 0x0c \ + } +#define LLC_ORG_FOUNDRY \ + { \ + 0x00, 0xe0, 0x52 \ + } +#define LLC_PID_CDP 0x2000 +/* Other protocols */ +#define LLC_PID_DRIP 0x102 +#define LLC_PID_PAGP 0x104 +#define LLC_PID_PVSTP 0x10b +#define LLC_PID_UDLD 0x111 +#define LLC_PID_VTP 0x2003 +#define LLC_PID_DTP 0x2004 +#define LLC_PID_STP 0x200a + +enum { + CDP_TLV_CHASSIS = 1, + CDP_TLV_ADDRESSES = 2, + CDP_TLV_PORT = 3, + CDP_TLV_CAPABILITIES = 4, + CDP_TLV_SOFTWARE = 5, + CDP_TLV_PLATFORM = 6, + CDP_TLV_NATIVEVLAN = 10, + CDP_TLV_POWER_CONSUMPTION = 0x10, + CDP_TLV_POWER_REQUESTED = 0x19, + CDP_TLV_POWER_AVAILABLE = 0x1A +}; + +#define CDP_ADDRESS_PROTO_IP 0xcc + +#define CDP_CAP_ROUTER 0x01 +#define CDP_CAP_TRANSPARENT_BRIDGE 0x02 +#define CDP_CAP_SOURCE_BRIDGE 0x04 +#define CDP_CAP_SWITCH 0x08 +#define CDP_CAP_HOST 0x10 +#define CDP_CAP_IGMP 0x20 +#define CDP_CAP_REPEATER 0x40 + +#endif /* _CDP_H */ diff --git a/src/daemon/protocols/edp.c b/src/daemon/protocols/edp.c new file mode 100644 index 0000000..b55130b --- /dev/null +++ b/src/daemon/protocols/edp.c @@ -0,0 +1,514 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "../lldpd.h" +#include "../frame.h" + +#ifdef ENABLE_EDP + +# include <stdio.h> +# include <unistd.h> +# include <errno.h> +# include <arpa/inet.h> +# include <fnmatch.h> + +static int seq = 0; + +int +edp_send(struct lldpd *global, struct lldpd_hardware *hardware) +{ + const u_int8_t mcastaddr[] = EDP_MULTICAST_ADDR; + const u_int8_t llcorg[] = LLC_ORG_EXTREME; + struct lldpd_chassis *chassis; + int length, i, v; + u_int8_t *packet, *pos, *pos_llc, *pos_len_eh, *pos_len_edp, *pos_edp, *tlv, + *end; + u_int16_t checksum; +# ifdef ENABLE_DOT1 + struct lldpd_vlan *vlan; + unsigned int state = 0; +# endif + u_int8_t edp_fakeversion[] = { 7, 6, 4, 99 }; + /* Subsequent XXX can be replaced by other values. We place + them here to ensure the position of "" to be a bit + invariant with version changes. */ + const char *deviceslot[] = { "eth", "veth", "XXX", "XXX", "XXX", "XXX", "XXX", + "XXX", "", NULL }; + + log_debug("edp", "send EDP frame on port %s", hardware->h_ifname); + + chassis = hardware->h_lport.p_chassis; +# ifdef ENABLE_DOT1 + while (state != 2) { +# endif + length = hardware->h_mtu; + if ((packet = (u_int8_t *)calloc(1, length)) == NULL) return ENOMEM; + pos = packet; + v = 0; + + /* Ethernet header */ + if (!(POKE_BYTES(mcastaddr, sizeof(mcastaddr)) && + POKE_BYTES(&hardware->h_lladdr, ETHER_ADDR_LEN) && + POKE_SAVE(pos_len_eh) && /* We compute the len later */ + POKE_UINT16(0))) + goto toobig; + + /* LLC */ + if (!(POKE_SAVE(pos_llc) && /* We need to save our + current position to + compute ethernet len */ + /* SSAP and DSAP */ + POKE_UINT8(0xaa) && POKE_UINT8(0xaa) && + /* Control field */ + POKE_UINT8(0x03) && + /* ORG */ + POKE_BYTES(llcorg, sizeof(llcorg)) && POKE_UINT16(LLC_PID_EDP))) + goto toobig; + + /* EDP header */ + if ((chassis->c_id_len != ETHER_ADDR_LEN) || + (chassis->c_id_subtype != LLDP_CHASSISID_SUBTYPE_LLADDR)) { + log_warnx("edp", + "local chassis does not use MAC address as chassis ID!?"); + free(packet); + return EINVAL; + } + if (!(POKE_SAVE(pos_edp) && /* Save the start of EDP frame */ + POKE_UINT8(1) && POKE_UINT8(0) && + POKE_SAVE(pos_len_edp) && /* We compute the len + and the checksum + later */ + POKE_UINT32(0) && /* Len + Checksum */ + POKE_UINT16(seq) && POKE_UINT16(0) && + POKE_BYTES(chassis->c_id, ETHER_ADDR_LEN))) + goto toobig; + seq++; + +# ifdef ENABLE_DOT1 + switch (state) { + case 0: +# endif + /* Display TLV */ + if (!(POKE_START_EDP_TLV(EDP_TLV_DISPLAY) && + POKE_BYTES(chassis->c_name, strlen(chassis->c_name)) && + POKE_UINT8(0) && /* Add a NULL character + for better + compatibility */ + POKE_END_EDP_TLV)) + goto toobig; + + /* Info TLV */ + if (!(POKE_START_EDP_TLV(EDP_TLV_INFO))) goto toobig; + /* We try to emulate the slot thing */ + for (i = 0; deviceslot[i] != NULL; i++) { + if (strncmp(hardware->h_ifname, deviceslot[i], + strlen(deviceslot[i])) == 0) { + if (!(POKE_UINT16(i) && + POKE_UINT16(atoi(hardware->h_ifname + + strlen(deviceslot[i]))))) + goto toobig; + break; + } + } + /* If we don't find a "slot", we say that the + interface is in slot 8 */ + if (deviceslot[i] == NULL) { + if (!(POKE_UINT16(8) && + POKE_UINT16(hardware->h_ifindex))) + goto toobig; + } + if (!(POKE_UINT16(0) && /* vchassis */ + POKE_UINT32(0) && POKE_UINT16(0) && /* Reserved */ + /* Version */ + POKE_BYTES(edp_fakeversion, sizeof(edp_fakeversion)) && + /* Connections, we say that we won't + have more interfaces than this + mask. */ + POKE_UINT32(0xffffffff) && POKE_UINT32(0) && + POKE_UINT32(0) && POKE_UINT32(0) && POKE_END_EDP_TLV)) + goto toobig; + +# ifdef ENABLE_DOT1 + break; + case 1: + TAILQ_FOREACH (vlan, &hardware->h_lport.p_vlans, v_entries) { + v++; + if (!(POKE_START_EDP_TLV(EDP_TLV_VLAN) && + POKE_UINT8(0) && /* Flags: no IP address */ + POKE_UINT8(0) && /* Reserved */ + POKE_UINT16(vlan->v_vid) && + POKE_UINT32(0) && /* Reserved */ + POKE_UINT32(0) && /* IP address */ + /* VLAN name */ + POKE_BYTES(vlan->v_name, + strlen(vlan->v_name)) && + POKE_UINT8(0) && POKE_END_EDP_TLV)) + goto toobig; + } + break; + } + + if ((state == 1) && (v == 0)) { + /* No VLAN, no need to send another TLV */ + free(packet); + break; + } +# endif + + /* Null TLV */ + if (!(POKE_START_EDP_TLV(EDP_TLV_NULL) && POKE_END_EDP_TLV && + POKE_SAVE(end))) + goto toobig; + + /* Compute len and checksum */ + i = end - pos_llc; /* Ethernet length */ + v = end - pos_edp; /* EDP length */ + POKE_RESTORE(pos_len_eh); + if (!(POKE_UINT16(i))) goto toobig; + POKE_RESTORE(pos_len_edp); + if (!(POKE_UINT16(v))) goto toobig; + checksum = frame_checksum(pos_edp, v, 0); + if (!(POKE_UINT16(checksum))) goto toobig; + + if (interfaces_send_helper(global, hardware, (char *)packet, + end - packet) == -1) { + log_warn("edp", "unable to send packet on real device for %s", + hardware->h_ifname); + free(packet); + return ENETDOWN; + } + free(packet); + +# ifdef ENABLE_DOT1 + state++; + } +# endif + + hardware->h_tx_cnt++; + return 0; +toobig: + free(packet); + return E2BIG; +} + +# define CHECK_TLV_SIZE(x, name) \ + do { \ + if (tlv_len < (x)) { \ + log_warnx("edp", name " EDP TLV too short received on %s", \ + hardware->h_ifname); \ + goto malformed; \ + } \ + } while (0) + +int +edp_decode(struct lldpd *cfg, char *frame, int s, struct lldpd_hardware *hardware, + struct lldpd_chassis **newchassis, struct lldpd_port **newport) +{ + struct lldpd_chassis *chassis; + struct lldpd_port *port; +# ifdef ENABLE_DOT1 + struct lldpd_mgmt *mgmt, *mgmt_next, *m; + struct lldpd_vlan *lvlan = NULL, *lvlan_next; +# endif + const unsigned char edpaddr[] = EDP_MULTICAST_ADDR; + int length, gotend = 0, gotvlans = 0, edp_len, tlv_len, tlv_type; + int edp_port, edp_slot; + u_int8_t *pos, *pos_edp, *tlv; + u_int8_t version[4]; +# ifdef ENABLE_DOT1 + struct in_addr address; + struct lldpd_port *oport; +# endif + + log_debug("edp", "decode EDP frame on port %s", hardware->h_ifname); + + if ((chassis = calloc(1, sizeof(struct lldpd_chassis))) == NULL) { + log_warn("edp", "failed to allocate remote chassis"); + return -1; + } + TAILQ_INIT(&chassis->c_mgmt); + if ((port = calloc(1, sizeof(struct lldpd_port))) == NULL) { + log_warn("edp", "failed to allocate remote port"); + free(chassis); + return -1; + } +# ifdef ENABLE_DOT1 + TAILQ_INIT(&port->p_vlans); +# endif + + length = s; + pos = (u_int8_t *)frame; + + if (length < 2 * ETHER_ADDR_LEN + sizeof(u_int16_t) + 8 /* LLC */ + 10 + + ETHER_ADDR_LEN /* EDP header */) { + log_warnx("edp", "too short EDP frame received on %s", + hardware->h_ifname); + goto malformed; + } + + if (PEEK_CMP(edpaddr, sizeof(edpaddr)) != 0) { + log_info("edp", + "frame not targeted at EDP multicast address received on %s", + hardware->h_ifname); + goto malformed; + } + PEEK_DISCARD(ETHER_ADDR_LEN); + PEEK_DISCARD_UINT16; + PEEK_DISCARD(6); /* LLC: DSAP + SSAP + control + org */ + if (PEEK_UINT16 != LLC_PID_EDP) { + log_debug("edp", "incorrect LLC protocol ID received on %s", + hardware->h_ifname); + goto malformed; + } + + (void)PEEK_SAVE(pos_edp); /* Save the start of EDP packet */ + if (PEEK_UINT8 != 1) { + log_warnx("edp", "incorrect EDP version for frame received on %s", + hardware->h_ifname); + goto malformed; + } + PEEK_DISCARD_UINT8; /* Reserved */ + edp_len = PEEK_UINT16; + PEEK_DISCARD_UINT16; /* Checksum */ + PEEK_DISCARD_UINT16; /* Sequence */ + if (PEEK_UINT16 != 0) { /* ID Type = 0 = MAC */ + log_warnx("edp", "incorrect device id type for frame received on %s", + hardware->h_ifname); + goto malformed; + } + if (edp_len > length + 10) { + log_warnx("edp", "incorrect size for EDP frame received on %s", + hardware->h_ifname); + goto malformed; + } + port->p_ttl = cfg ? cfg->g_config.c_tx_interval * cfg->g_config.c_tx_hold : 0; + port->p_ttl = (port->p_ttl + 999) / 1000; + chassis->c_id_subtype = LLDP_CHASSISID_SUBTYPE_LLADDR; + chassis->c_id_len = ETHER_ADDR_LEN; + if ((chassis->c_id = (char *)malloc(ETHER_ADDR_LEN)) == NULL) { + log_warn("edp", "unable to allocate memory for chassis ID"); + goto malformed; + } + PEEK_BYTES(chassis->c_id, ETHER_ADDR_LEN); + +# ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + /* Let's check checksum */ + if (frame_checksum(pos_edp, edp_len, 0) != 0) { + log_warnx("edp", "incorrect EDP checksum for frame received on %s", + hardware->h_ifname); + goto malformed; + } +# endif + + while (length && !gotend) { + if (length < 4) { + log_warnx("edp", + "EDP TLV header is too large for " + "frame received on %s", + hardware->h_ifname); + goto malformed; + } + if (PEEK_UINT8 != EDP_TLV_MARKER) { + log_warnx("edp", + "incorrect marker starting EDP TLV header for frame " + "received on %s", + hardware->h_ifname); + goto malformed; + } + tlv_type = PEEK_UINT8; + tlv_len = PEEK_UINT16 - 4; + (void)PEEK_SAVE(tlv); + if ((tlv_len < 0) || (tlv_len > length)) { + log_debug("edp", + "incorrect size in EDP TLV header for frame " + "received on %s", + hardware->h_ifname); + /* Some poor old Extreme Summit are quite bogus */ + gotend = 1; + break; + } + switch (tlv_type) { + case EDP_TLV_INFO: + CHECK_TLV_SIZE(32, "Info"); + port->p_id_subtype = LLDP_PORTID_SUBTYPE_IFNAME; + edp_slot = PEEK_UINT16; + edp_port = PEEK_UINT16; + free(port->p_id); + port->p_id_len = + asprintf(&port->p_id, "%d/%d", edp_slot + 1, edp_port + 1); + if (port->p_id_len == -1) { + log_warn("edp", + "unable to allocate memory for " + "port ID"); + goto malformed; + } + free(port->p_descr); + if (asprintf(&port->p_descr, "Slot %d / Port %d", edp_slot + 1, + edp_port + 1) == -1) { + log_warn("edp", + "unable to allocate memory for " + "port description"); + goto malformed; + } + PEEK_DISCARD_UINT16; /* vchassis */ + PEEK_DISCARD(6); /* Reserved */ + PEEK_BYTES(version, 4); + free(chassis->c_descr); + if (asprintf(&chassis->c_descr, + "EDP enabled device, version %d.%d.%d.%d", version[0], + version[1], version[2], version[3]) == -1) { + log_warn("edp", + "unable to allocate memory for " + "chassis description"); + goto malformed; + } + break; + case EDP_TLV_DISPLAY: + free(chassis->c_name); + if ((chassis->c_name = (char *)calloc(1, tlv_len + 1)) == + NULL) { + log_warn("edp", + "unable to allocate memory for chassis " + "name"); + goto malformed; + } + /* TLV display contains a lot of garbage */ + PEEK_BYTES(chassis->c_name, tlv_len); + break; + case EDP_TLV_NULL: + if (tlv_len != 0) { + log_warnx("edp", + "null tlv with incorrect size in frame " + "received on %s", + hardware->h_ifname); + goto malformed; + } + if (length) + log_debug("edp", "extra data after edp frame on %s", + hardware->h_ifname); + gotend = 1; + break; + case EDP_TLV_VLAN: +# ifdef ENABLE_DOT1 + CHECK_TLV_SIZE(12, "VLAN"); + if ((lvlan = (struct lldpd_vlan *)calloc(1, + sizeof(struct lldpd_vlan))) == NULL) { + log_warn("edp", "unable to allocate vlan"); + goto malformed; + } + PEEK_DISCARD_UINT16; /* Flags + reserved */ + lvlan->v_vid = PEEK_UINT16; /* VID */ + PEEK_DISCARD(4); /* Reserved */ + PEEK_BYTES(&address, sizeof(address)); + + if (address.s_addr != INADDR_ANY) { + mgmt = lldpd_alloc_mgmt(LLDPD_AF_IPV4, &address, + sizeof(struct in_addr), 0); + if (mgmt == NULL) { + log_warn("edp", "Out of memory"); + goto malformed; + } + TAILQ_INSERT_TAIL(&chassis->c_mgmt, mgmt, m_entries); + } + + if ((lvlan->v_name = (char *)calloc(1, tlv_len + 1 - 12)) == + NULL) { + log_warn("edp", "unable to allocate vlan name"); + goto malformed; + } + PEEK_BYTES(lvlan->v_name, tlv_len - 12); + + TAILQ_INSERT_TAIL(&port->p_vlans, lvlan, v_entries); + lvlan = NULL; +# endif + gotvlans = 1; + break; + default: + log_debug("edp", "unknown EDP TLV type (%d) received on %s", + tlv_type, hardware->h_ifname); + hardware->h_rx_unrecognized_cnt++; + } + PEEK_DISCARD(tlv + tlv_len - pos); + } + if ((chassis->c_id == NULL) || (port->p_id == NULL) || + (chassis->c_name == NULL) || (chassis->c_descr == NULL) || + (port->p_descr == NULL) || (gotend == 0)) { +# ifdef ENABLE_DOT1 + if (gotvlans && gotend) { + /* VLAN can be sent in a separate frames. We need to add + * those vlans to an existing port */ + TAILQ_FOREACH (oport, &hardware->h_rports, p_entries) { + if (!((oport->p_protocol == LLDPD_MODE_EDP) && + (oport->p_chassis->c_id_subtype == + chassis->c_id_subtype) && + (oport->p_chassis->c_id_len == + chassis->c_id_len) && + (memcmp(oport->p_chassis->c_id, chassis->c_id, + chassis->c_id_len) == 0))) + continue; + /* We attach the VLANs to the found port */ + lldpd_vlan_cleanup(oport); + for (lvlan = TAILQ_FIRST(&port->p_vlans); lvlan != NULL; + lvlan = lvlan_next) { + lvlan_next = TAILQ_NEXT(lvlan, v_entries); + TAILQ_REMOVE(&port->p_vlans, lvlan, v_entries); + TAILQ_INSERT_TAIL(&oport->p_vlans, lvlan, + v_entries); + } + /* And the IP addresses */ + for (mgmt = TAILQ_FIRST(&chassis->c_mgmt); mgmt != NULL; + mgmt = mgmt_next) { + mgmt_next = TAILQ_NEXT(mgmt, m_entries); + TAILQ_REMOVE(&chassis->c_mgmt, mgmt, m_entries); + /* Don't add an address that already exists! */ + TAILQ_FOREACH (m, &chassis->c_mgmt, m_entries) + if (m->m_family == mgmt->m_family && + !memcmp(&m->m_addr, &mgmt->m_addr, + sizeof(m->m_addr))) + break; + if (m == NULL) + TAILQ_INSERT_TAIL( + &oport->p_chassis->c_mgmt, mgmt, + m_entries); + } + } + /* We discard the remaining frame */ + goto malformed; + } +# else + if (gotvlans) goto malformed; +# endif + log_warnx("edp", + "some mandatory tlv are missing for frame received on %s", + hardware->h_ifname); + goto malformed; + } + *newchassis = chassis; + *newport = port; + return 1; + +malformed: +# ifdef ENABLE_DOT1 + free(lvlan); +# endif + lldpd_chassis_cleanup(chassis, 1); + lldpd_port_cleanup(port, 1); + free(port); + return -1; +} + +#endif /* ENABLE_EDP */ diff --git a/src/daemon/protocols/edp.h b/src/daemon/protocols/edp.h new file mode 100644 index 0000000..f4ee183 --- /dev/null +++ b/src/daemon/protocols/edp.h @@ -0,0 +1,41 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _EDP_H +#define _EDP_H + +#define EDP_MULTICAST_ADDR \ + { \ + 0x00, 0xe0, 0x2b, 0x00, 0x00, 0x00 \ + } +#define LLC_ORG_EXTREME \ + { \ + 0x00, 0xe0, 0x2b \ + } +#define LLC_PID_EDP 0x00bb + +#define EDP_TLV_MARKER 0x99 + +enum { + EDP_TLV_NULL = 0, + EDP_TLV_DISPLAY = 1, + EDP_TLV_INFO = 2, + EDP_TLV_VLAN = 5, + EDP_TLV_ESRP = 8, +}; + +#endif /* _EDP_H */ diff --git a/src/daemon/protocols/lldp.c b/src/daemon/protocols/lldp.c new file mode 100644 index 0000000..6e73237 --- /dev/null +++ b/src/daemon/protocols/lldp.c @@ -0,0 +1,1315 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "../lldpd.h" +#include "../frame.h" + +#include <unistd.h> +#include <errno.h> +#include <time.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/ioctl.h> + +static int +lldpd_af_to_lldp_proto(int af) +{ + switch (af) { + case LLDPD_AF_IPV4: + return LLDP_MGMT_ADDR_IP4; + case LLDPD_AF_IPV6: + return LLDP_MGMT_ADDR_IP6; + default: + return LLDP_MGMT_ADDR_NONE; + } +} + +static int +lldpd_af_from_lldp_proto(int proto) +{ + switch (proto) { + case LLDP_MGMT_ADDR_IP4: + return LLDPD_AF_IPV4; + case LLDP_MGMT_ADDR_IP6: + return LLDPD_AF_IPV6; + default: + return LLDPD_AF_UNSPEC; + } +} + +static int +_lldp_send(struct lldpd *global, struct lldpd_hardware *hardware, u_int8_t c_id_subtype, + char *c_id, int c_id_len, u_int8_t p_id_subtype, char *p_id, int p_id_len, + int shutdown, int without_vlans) +{ + struct lldpd_port *port; + struct lldpd_chassis *chassis; + struct lldpd_frame *frame; + int length; + u_int8_t *packet, *pos, *tlv; + struct lldpd_mgmt *mgmt; + int proto; + int vlans = 0; + + u_int8_t mcastaddr_regular[] = LLDP_ADDR_NEAREST_BRIDGE; + u_int8_t mcastaddr_nontpmr[] = LLDP_ADDR_NEAREST_NONTPMR_BRIDGE; + u_int8_t mcastaddr_customer[] = LLDP_ADDR_NEAREST_CUSTOMER_BRIDGE; + u_int8_t *mcastaddr; +#ifdef ENABLE_DOT1 + const u_int8_t dot1[] = LLDP_TLV_ORG_DOT1; + struct lldpd_vlan *vlan; + struct lldpd_ppvid *ppvid; + struct lldpd_pi *pi; +#endif +#ifdef ENABLE_DOT3 + const u_int8_t dot3[] = LLDP_TLV_ORG_DOT3; +#endif +#ifdef ENABLE_LLDPMED + int i; + const u_int8_t med[] = LLDP_TLV_ORG_MED; +#endif +#ifdef ENABLE_CUSTOM + struct lldpd_custom *custom; +#endif + port = &hardware->h_lport; + chassis = port->p_chassis; + length = hardware->h_mtu; + if ((packet = (u_int8_t *)calloc(1, length)) == NULL) return ENOMEM; + pos = packet; + + /* Ethernet header */ + switch (global->g_config.c_lldp_agent_type) { + case LLDP_AGENT_TYPE_NEAREST_NONTPMR_BRIDGE: + mcastaddr = mcastaddr_nontpmr; + break; + case LLDP_AGENT_TYPE_NEAREST_CUSTOMER_BRIDGE: + mcastaddr = mcastaddr_customer; + break; + case LLDP_AGENT_TYPE_NEAREST_BRIDGE: + default: + mcastaddr = mcastaddr_regular; + break; + } + if (!( + /* LLDP multicast address */ + POKE_BYTES(mcastaddr, ETHER_ADDR_LEN) && + /* Source MAC address */ + POKE_BYTES(&hardware->h_lladdr, ETHER_ADDR_LEN))) + goto toobig; + + /* Insert VLAN tag if needed */ + if (port->p_vlan_tx_enabled) { + if (!( + /* VLAN ethertype */ + POKE_UINT16(ETHERTYPE_VLAN) && + /* VLAN Tag Control Information (TCI) */ + /* Priority(3bits) | DEI(1bit) | VID(12bit) */ + POKE_UINT16(port->p_vlan_tx_tag))) + goto toobig; + } + + if (!( + /* LLDP frame */ + POKE_UINT16(ETH_P_LLDP))) + goto toobig; + + /* Chassis ID */ + if (!(POKE_START_LLDP_TLV(LLDP_TLV_CHASSIS_ID) && POKE_UINT8(c_id_subtype) && + POKE_BYTES(c_id, c_id_len) && POKE_END_LLDP_TLV)) + goto toobig; + + /* Port ID */ + if (!(POKE_START_LLDP_TLV(LLDP_TLV_PORT_ID) && POKE_UINT8(p_id_subtype) && + POKE_BYTES(p_id, p_id_len) && POKE_END_LLDP_TLV)) + goto toobig; + + /* Time to live */ + if (!(POKE_START_LLDP_TLV(LLDP_TLV_TTL) && + POKE_UINT16(shutdown ? 0 : (global ? global->g_config.c_ttl : 180)) && + POKE_END_LLDP_TLV)) + goto toobig; + + if (shutdown) goto end; + + /* System name */ + if (chassis->c_name && *chassis->c_name != '\0') { + if (!(POKE_START_LLDP_TLV(LLDP_TLV_SYSTEM_NAME) && + POKE_BYTES(chassis->c_name, strlen(chassis->c_name)) && + POKE_END_LLDP_TLV)) + goto toobig; + } + + /* System description (skip it if empty) */ + if (chassis->c_descr && *chassis->c_descr != '\0') { + if (!(POKE_START_LLDP_TLV(LLDP_TLV_SYSTEM_DESCR) && + POKE_BYTES(chassis->c_descr, strlen(chassis->c_descr)) && + POKE_END_LLDP_TLV)) + goto toobig; + } + + /* System capabilities */ + if (global->g_config.c_cap_advertise && chassis->c_cap_available) { + if (!(POKE_START_LLDP_TLV(LLDP_TLV_SYSTEM_CAP) && + POKE_UINT16(chassis->c_cap_available) && + POKE_UINT16(chassis->c_cap_enabled) && POKE_END_LLDP_TLV)) + goto toobig; + } + + /* Management addresses */ + TAILQ_FOREACH (mgmt, &chassis->c_mgmt, m_entries) { + proto = lldpd_af_to_lldp_proto(mgmt->m_family); + if (proto == LLDP_MGMT_ADDR_NONE) continue; + if (!(POKE_START_LLDP_TLV(LLDP_TLV_MGMT_ADDR) && + /* Size of the address, including its type */ + POKE_UINT8(mgmt->m_addrsize + 1) && POKE_UINT8(proto) && + POKE_BYTES(&mgmt->m_addr, mgmt->m_addrsize))) + goto toobig; + + /* Interface port type, OID */ + if (mgmt->m_iface == 0) { + if (!( + /* We don't know the management interface */ + POKE_UINT8(LLDP_MGMT_IFACE_UNKNOWN) && POKE_UINT32(0))) + goto toobig; + } else { + if (!( + /* We have the index of the management interface */ + POKE_UINT8(LLDP_MGMT_IFACE_IFINDEX) && + POKE_UINT32(mgmt->m_iface))) + goto toobig; + } + if (!( + /* We don't provide an OID for management */ + POKE_UINT8(0) && POKE_END_LLDP_TLV)) + goto toobig; + } + + /* Port description */ + if (port->p_descr && *port->p_descr != '\0') { + if (!(POKE_START_LLDP_TLV(LLDP_TLV_PORT_DESCR) && + POKE_BYTES(port->p_descr, strlen(port->p_descr)) && + POKE_END_LLDP_TLV)) + goto toobig; + } + +#ifdef ENABLE_DOT1 + /* Port VLAN ID */ + if (port->p_pvid != 0) { + if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) && + POKE_BYTES(dot1, sizeof(dot1)) && + POKE_UINT8(LLDP_TLV_DOT1_PVID) && POKE_UINT16(port->p_pvid) && + POKE_END_LLDP_TLV)) { + goto toobig; + } + } + /* Port and Protocol VLAN IDs */ + TAILQ_FOREACH (ppvid, &port->p_ppvids, p_entries) { + if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) && + POKE_BYTES(dot1, sizeof(dot1)) && + POKE_UINT8(LLDP_TLV_DOT1_PPVID) && + POKE_UINT8(ppvid->p_cap_status) && + POKE_UINT16(ppvid->p_ppvid) && POKE_END_LLDP_TLV)) { + goto toobig; + } + } + /* VLANs */ + if (!without_vlans) { + TAILQ_FOREACH (vlan, &port->p_vlans, v_entries) { + vlans++; + if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) && + POKE_BYTES(dot1, sizeof(dot1)) && + POKE_UINT8(LLDP_TLV_DOT1_VLANNAME) && + POKE_UINT16(vlan->v_vid) && + POKE_UINT8(strlen(vlan->v_name)) && + POKE_BYTES(vlan->v_name, strlen(vlan->v_name)) && + POKE_END_LLDP_TLV)) + goto toobig; + } + } + /* Protocol Identities */ + TAILQ_FOREACH (pi, &port->p_pids, p_entries) { + if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) && + POKE_BYTES(dot1, sizeof(dot1)) && + POKE_UINT8(LLDP_TLV_DOT1_PI) && POKE_UINT8(pi->p_pi_len) && + POKE_BYTES(pi->p_pi, pi->p_pi_len) && POKE_END_LLDP_TLV)) + goto toobig; + } +#endif + +#ifdef ENABLE_DOT3 + /* Aggregation status */ + if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) && POKE_BYTES(dot3, sizeof(dot3)) && + POKE_UINT8(LLDP_TLV_DOT3_LA) && + /* Bit 0 = capability ; Bit 1 = status */ + POKE_UINT8((port->p_aggregid) ? 3 : 1) && + POKE_UINT32(port->p_aggregid) && POKE_END_LLDP_TLV)) + goto toobig; + + /* MAC/PHY */ + if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) && POKE_BYTES(dot3, sizeof(dot3)) && + POKE_UINT8(LLDP_TLV_DOT3_MAC) && + POKE_UINT8(port->p_macphy.autoneg_support | + (port->p_macphy.autoneg_enabled << 1)) && + POKE_UINT16(port->p_macphy.autoneg_advertised) && + POKE_UINT16(port->p_macphy.mau_type) && POKE_END_LLDP_TLV)) + goto toobig; + + /* MFS */ + if (port->p_mfs) { + if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) && + POKE_BYTES(dot3, sizeof(dot3)) && + POKE_UINT8(LLDP_TLV_DOT3_MFS) && POKE_UINT16(port->p_mfs) && + POKE_END_LLDP_TLV)) + goto toobig; + } + /* Power */ + if (port->p_power.devicetype) { + if (!((POKE_START_LLDP_TLV(LLDP_TLV_ORG)) && + POKE_BYTES(dot3, sizeof(dot3)) && + POKE_UINT8(LLDP_TLV_DOT3_POWER) && + POKE_UINT8(((((2 - port->p_power.devicetype) % (1 << 1)) << 0) | + ((port->p_power.supported % (1 << 1)) << 1) | + ((port->p_power.enabled % (1 << 1)) << 2) | + ((port->p_power.paircontrol % (1 << 1)) << 3))) && + POKE_UINT8(port->p_power.pairs) && + POKE_UINT8(port->p_power.class))) + goto toobig; + /* 802.3at */ + if (port->p_power.powertype != LLDP_DOT3_POWER_8023AT_OFF) { + if (!(POKE_UINT8(((((port->p_power.powertype == + LLDP_DOT3_POWER_8023AT_TYPE1) ? + 1 : + 0) + << 7) | + (((port->p_power.devicetype == LLDP_DOT3_POWER_PSE) ? + 0 : + 1) + << 6) | + ((port->p_power.source % (1 << 2)) << 4) | + ((port->p_power.priority % (1 << 2)) << 0))) && + POKE_UINT16(port->p_power.requested) && + POKE_UINT16(port->p_power.allocated))) + goto toobig; + } + if (!(POKE_END_LLDP_TLV)) goto toobig; + } +#endif + +#ifdef ENABLE_LLDPMED + if (port->p_med_cap_enabled) { + /* LLDP-MED cap */ + if (port->p_med_cap_enabled & LLDP_MED_CAP_CAP) { + if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) && + POKE_BYTES(med, sizeof(med)) && + POKE_UINT8(LLDP_TLV_MED_CAP) && + POKE_UINT16(chassis->c_med_cap_available) && + POKE_UINT8(chassis->c_med_type) && POKE_END_LLDP_TLV)) + goto toobig; + } + + /* LLDP-MED inventory */ +# define LLDP_INVENTORY(value, subtype) \ + if (value) { \ + if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) && POKE_BYTES(med, sizeof(med)) && \ + POKE_UINT8(subtype) && \ + POKE_BYTES(value, (strlen(value) > 32) ? 32 : strlen(value)) && \ + POKE_END_LLDP_TLV)) \ + goto toobig; \ + } + + if (port->p_med_cap_enabled & LLDP_MED_CAP_IV) { + LLDP_INVENTORY(chassis->c_med_hw, LLDP_TLV_MED_IV_HW); + LLDP_INVENTORY(chassis->c_med_fw, LLDP_TLV_MED_IV_FW); + LLDP_INVENTORY(chassis->c_med_sw, LLDP_TLV_MED_IV_SW); + LLDP_INVENTORY(chassis->c_med_sn, LLDP_TLV_MED_IV_SN); + LLDP_INVENTORY(chassis->c_med_manuf, LLDP_TLV_MED_IV_MANUF); + LLDP_INVENTORY(chassis->c_med_model, LLDP_TLV_MED_IV_MODEL); + LLDP_INVENTORY(chassis->c_med_asset, LLDP_TLV_MED_IV_ASSET); + } + + /* LLDP-MED location */ + for (i = 0; i < LLDP_MED_LOCFORMAT_LAST; i++) { + if (port->p_med_location[i].format == i + 1) { + if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) && + POKE_BYTES(med, sizeof(med)) && + POKE_UINT8(LLDP_TLV_MED_LOCATION) && + POKE_UINT8(port->p_med_location[i].format) && + POKE_BYTES(port->p_med_location[i].data, + port->p_med_location[i].data_len) && + POKE_END_LLDP_TLV)) + goto toobig; + } + } + + /* LLDP-MED network policy */ + for (i = 0; i < LLDP_MED_APPTYPE_LAST; i++) { + if (port->p_med_policy[i].type == i + 1) { + if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) && + POKE_BYTES(med, sizeof(med)) && + POKE_UINT8(LLDP_TLV_MED_POLICY) && + POKE_UINT32(( + ((port->p_med_policy[i].type % (1 << 8)) + << 24) | + ((port->p_med_policy[i].unknown % (1 << 1)) + << 23) | + ((port->p_med_policy[i].tagged % (1 << 1)) + << 22) | + /*((0 %(1<< + 1))<<21) |*/ + ((port->p_med_policy[i].vid % (1 << 12)) + << 9) | + ((port->p_med_policy[i].priority % (1 << 3)) + << 6) | + ((port->p_med_policy[i].dscp % (1 << 6)) + << 0))) && + POKE_END_LLDP_TLV)) + goto toobig; + } + } + + /* LLDP-MED POE-MDI */ + if ((port->p_med_power.devicetype == LLDP_MED_POW_TYPE_PSE) || + (port->p_med_power.devicetype == LLDP_MED_POW_TYPE_PD)) { + int devicetype = 0, source = 0; + if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) && + POKE_BYTES(med, sizeof(med)) && + POKE_UINT8(LLDP_TLV_MED_MDI))) + goto toobig; + switch (port->p_med_power.devicetype) { + case LLDP_MED_POW_TYPE_PSE: + devicetype = 0; + switch (port->p_med_power.source) { + case LLDP_MED_POW_SOURCE_PRIMARY: + source = 1; + break; + case LLDP_MED_POW_SOURCE_BACKUP: + source = 2; + break; + case LLDP_MED_POW_SOURCE_RESERVED: + source = 3; + break; + default: + source = 0; + break; + } + break; + case LLDP_MED_POW_TYPE_PD: + devicetype = 1; + switch (port->p_med_power.source) { + case LLDP_MED_POW_SOURCE_PSE: + source = 1; + break; + case LLDP_MED_POW_SOURCE_LOCAL: + source = 2; + break; + case LLDP_MED_POW_SOURCE_BOTH: + source = 3; + break; + default: + source = 0; + break; + } + break; + } + if (!(POKE_UINT8((((devicetype % (1 << 2)) << 6) | + ((source % (1 << 2)) << 4) | + ((port->p_med_power.priority % (1 << 4)) << 0))) && + POKE_UINT16(port->p_med_power.val) && + POKE_END_LLDP_TLV)) + goto toobig; + } + } +#endif + +#ifdef ENABLE_CUSTOM + TAILQ_FOREACH (custom, &port->p_custom_list, next) { + if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) && + POKE_BYTES(custom->oui, sizeof(custom->oui)) && + POKE_UINT8(custom->subtype) && + POKE_BYTES(custom->oui_info, custom->oui_info_len) && + POKE_END_LLDP_TLV)) + goto toobig; + } +#endif + +end: + /* END */ + if (!(POKE_START_LLDP_TLV(LLDP_TLV_END) && POKE_END_LLDP_TLV)) goto toobig; + + if (interfaces_send_helper(global, hardware, (char *)packet, pos - packet) == + -1) { + log_warn("lldp", "unable to send packet on real device for %s", + hardware->h_ifname); + free(packet); + return ENETDOWN; + } + + hardware->h_tx_cnt++; + + /* We assume that LLDP frame is the reference */ + if (!shutdown && + (frame = (struct lldpd_frame *)malloc(sizeof(int) + pos - packet)) != + NULL) { + frame->size = pos - packet; + memcpy(&frame->frame, packet, frame->size); + if ((hardware->h_lport.p_lastframe == NULL) || + (hardware->h_lport.p_lastframe->size != frame->size) || + (memcmp(hardware->h_lport.p_lastframe->frame, frame->frame, + frame->size) != 0)) { + free(hardware->h_lport.p_lastframe); + hardware->h_lport.p_lastframe = frame; + hardware->h_lport.p_lastchange = time(NULL); + } else + free(frame); + } + + free(packet); + return 0; + +toobig: + free(packet); + if (vlans > 0 && !without_vlans) { + /* Retry without VLANs */ + return _lldp_send(global, hardware, c_id_subtype, c_id, c_id_len, + p_id_subtype, p_id, p_id_len, shutdown, 1); + } + log_info("lldp", "Cannot send LLDP packet for %s, too big message", + hardware->h_ifname); + return E2BIG; +} + +/* Send a shutdown LLDPDU. */ +int +lldp_send_shutdown(struct lldpd *global, struct lldpd_hardware *hardware) +{ + if (hardware->h_lchassis_previous_id == NULL || + hardware->h_lport_previous_id == NULL) + return 0; + return _lldp_send(global, hardware, hardware->h_lchassis_previous_id_subtype, + hardware->h_lchassis_previous_id, hardware->h_lchassis_previous_id_len, + hardware->h_lport_previous_id_subtype, hardware->h_lport_previous_id, + hardware->h_lport_previous_id_len, 1, 0); +} + +int +lldp_send(struct lldpd *global, struct lldpd_hardware *hardware) +{ + struct lldpd_port *port = &hardware->h_lport; + struct lldpd_chassis *chassis = port->p_chassis; + int ret; + + /* Check if we have a change. */ + if (hardware->h_lchassis_previous_id != NULL && + hardware->h_lport_previous_id != NULL && + (hardware->h_lchassis_previous_id_subtype != chassis->c_id_subtype || + hardware->h_lchassis_previous_id_len != chassis->c_id_len || + hardware->h_lport_previous_id_subtype != port->p_id_subtype || + hardware->h_lport_previous_id_len != port->p_id_len || + memcmp(hardware->h_lchassis_previous_id, chassis->c_id, + chassis->c_id_len) || + memcmp(hardware->h_lport_previous_id, port->p_id, port->p_id_len))) { + log_info("lldp", + "MSAP has changed for port %s, sending a shutdown LLDPDU", + hardware->h_ifname); + if ((ret = lldp_send_shutdown(global, hardware)) != 0) return ret; + } + + log_debug("lldp", "send LLDP PDU to %s", hardware->h_ifname); + + if ((ret = _lldp_send(global, hardware, chassis->c_id_subtype, chassis->c_id, + chassis->c_id_len, port->p_id_subtype, port->p_id, port->p_id_len, 0, + 0)) != 0) + return ret; + + /* Record current chassis and port ID */ + free(hardware->h_lchassis_previous_id); + hardware->h_lchassis_previous_id_subtype = chassis->c_id_subtype; + hardware->h_lchassis_previous_id_len = chassis->c_id_len; + if ((hardware->h_lchassis_previous_id = malloc(chassis->c_id_len)) != NULL) + memcpy(hardware->h_lchassis_previous_id, chassis->c_id, + chassis->c_id_len); + free(hardware->h_lport_previous_id); + hardware->h_lport_previous_id_subtype = port->p_id_subtype; + hardware->h_lport_previous_id_len = port->p_id_len; + if ((hardware->h_lport_previous_id = malloc(port->p_id_len)) != NULL) + memcpy(hardware->h_lport_previous_id, port->p_id, port->p_id_len); + + return 0; +} + +#define CHECK_TLV_SIZE(x, name) \ + do { \ + if (tlv_size < (x)) { \ + log_warnx("lldp", name " TLV too short received on %s", hardware->h_ifname); \ + goto malformed; \ + } \ + } while (0) +#define CHECK_TLV_MAX_SIZE(x, name) \ + do { \ + if (tlv_size > (x)) { \ + log_warnx("lldp", name " TLV too large received on %s", hardware->h_ifname); \ + goto malformed; \ + } \ + } while (0) + +int +lldp_decode(struct lldpd *cfg, char *frame, int s, struct lldpd_hardware *hardware, + struct lldpd_chassis **newchassis, struct lldpd_port **newport) +{ + struct lldpd_chassis *chassis; + struct lldpd_port *port; + char lldpaddr[ETHER_ADDR_LEN]; + const char dot1[] = LLDP_TLV_ORG_DOT1; + const char dot3[] = LLDP_TLV_ORG_DOT3; + const char med[] = LLDP_TLV_ORG_MED; + const char dcbx[] = LLDP_TLV_ORG_DCBX; + unsigned char orgid[3]; + int length, gotend = 0, ttl_received = 0; + int tlv_size, tlv_type, tlv_subtype, tlv_count = 0; + u_int8_t *pos, *tlv; + char *b; +#ifdef ENABLE_DOT1 + struct lldpd_vlan *vlan = NULL; + int vlan_len; + struct lldpd_ppvid *ppvid; + struct lldpd_pi *pi = NULL; +#endif + struct lldpd_mgmt *mgmt; + int af; + u_int8_t addr_str_length, addr_str_buffer[32] = { 0 }; + u_int8_t addr_family, addr_length, *addr_ptr, iface_subtype; + u_int32_t iface_number, iface; + int unrecognized; +#ifdef ENABLE_CUSTOM + struct lldpd_custom *custom = NULL; +#endif + + log_debug("lldp", "receive LLDP PDU on %s", hardware->h_ifname); + + if ((chassis = calloc(1, sizeof(struct lldpd_chassis))) == NULL) { + log_warn("lldp", "failed to allocate remote chassis"); + return -1; + } + TAILQ_INIT(&chassis->c_mgmt); + if ((port = calloc(1, sizeof(struct lldpd_port))) == NULL) { + log_warn("lldp", "failed to allocate remote port"); + free(chassis); + return -1; + } +#ifdef ENABLE_DOT1 + TAILQ_INIT(&port->p_vlans); + TAILQ_INIT(&port->p_ppvids); + TAILQ_INIT(&port->p_pids); +#endif +#ifdef ENABLE_CUSTOM + TAILQ_INIT(&port->p_custom_list); +#endif + + length = s; + pos = (u_int8_t *)frame; + + if (length < 2 * ETHER_ADDR_LEN + sizeof(u_int16_t)) { + log_warnx("lldp", "too short frame received on %s", hardware->h_ifname); + goto malformed; + } + PEEK_BYTES(lldpaddr, ETHER_ADDR_LEN); + if (memcmp(lldpaddr, (const char[])LLDP_ADDR_NEAREST_BRIDGE, ETHER_ADDR_LEN) && + memcmp(lldpaddr, (const char[])LLDP_ADDR_NEAREST_NONTPMR_BRIDGE, + ETHER_ADDR_LEN) && + memcmp(lldpaddr, (const char[])LLDP_ADDR_NEAREST_CUSTOMER_BRIDGE, + ETHER_ADDR_LEN)) { + log_info("lldp", + "frame not targeted at LLDP multicast address received on %s", + hardware->h_ifname); + goto malformed; + } + PEEK_DISCARD(ETHER_ADDR_LEN); /* Skip source address */ + if (PEEK_UINT16 != ETH_P_LLDP) { + log_info("lldp", "non LLDP frame received on %s", hardware->h_ifname); + goto malformed; + } + + while (length && (!gotend)) { + if (length < 2) { + log_warnx("lldp", "tlv header too short received on %s", + hardware->h_ifname); + goto malformed; + } + tlv_size = PEEK_UINT16; + tlv_type = tlv_size >> 9; + tlv_size = tlv_size & 0x1ff; + (void)PEEK_SAVE(tlv); + if (length < tlv_size) { + log_warnx("lldp", "frame too short for tlv received on %s", + hardware->h_ifname); + goto malformed; + } + /* Check order for mandatory TLVs */ + tlv_count++; + switch (tlv_type) { + case LLDP_TLV_CHASSIS_ID: + if (tlv_count != 1) { + log_warnx("lldp", + "Chassis ID TLV should be first on %s, but it is on position %d", + hardware->h_ifname, tlv_count); + goto malformed; + } + break; + case LLDP_TLV_PORT_ID: + if (tlv_count != 2) { + log_warnx("lldp", + "Port ID TLV should be second on %s, but it is on position %d", + hardware->h_ifname, tlv_count); + goto malformed; + } + break; + case LLDP_TLV_TTL: + if (tlv_count != 3) { + log_warnx("lldp", + "TTL TLV should be third on %s, but it is on position %d", + hardware->h_ifname, tlv_count); + goto malformed; + } + break; + } + + switch (tlv_type) { + case LLDP_TLV_END: + if (tlv_size != 0) { + log_warnx("lldp", + "lldp end received with size not null on %s", + hardware->h_ifname); + goto malformed; + } + if (length) + log_debug("lldp", "extra data after lldp end on %s", + hardware->h_ifname); + gotend = 1; + break; + case LLDP_TLV_CHASSIS_ID: + case LLDP_TLV_PORT_ID: + CHECK_TLV_SIZE(2, "Port/Chassis Id"); + CHECK_TLV_MAX_SIZE(256, "Port/Chassis Id"); + tlv_subtype = PEEK_UINT8; + if ((tlv_subtype == 0) || (tlv_subtype > 7)) { + log_warnx("lldp", + "unknown subtype for tlv id received on %s", + hardware->h_ifname); + goto malformed; + } + if ((b = (char *)calloc(1, tlv_size - 1)) == NULL) { + log_warn("lldp", + "unable to allocate memory for id tlv " + "received on %s", + hardware->h_ifname); + goto malformed; + } + PEEK_BYTES(b, tlv_size - 1); + if (tlv_type == LLDP_TLV_PORT_ID) { + if (port->p_id != NULL) { + log_warnx("lldp", + "Port ID TLV received twice on %s", + hardware->h_ifname); + free(b); + goto malformed; + } + port->p_id_subtype = tlv_subtype; + port->p_id = b; + port->p_id_len = tlv_size - 1; + } else { + if (chassis->c_id != NULL) { + log_warnx("lldp", + "Chassis ID TLV received twice on %s", + hardware->h_ifname); + free(b); + goto malformed; + } + chassis->c_id_subtype = tlv_subtype; + chassis->c_id = b; + chassis->c_id_len = tlv_size - 1; + } + break; + case LLDP_TLV_TTL: + if (ttl_received) { + log_warnx("lldp", "TTL TLV received twice on %s", + hardware->h_ifname); + goto malformed; + } + CHECK_TLV_SIZE(2, "TTL"); + port->p_ttl = PEEK_UINT16; + ttl_received = 1; + break; + case LLDP_TLV_PORT_DESCR: + case LLDP_TLV_SYSTEM_NAME: + case LLDP_TLV_SYSTEM_DESCR: + if (tlv_size < 1) { + log_debug("lldp", "empty tlv received on %s", + hardware->h_ifname); + break; + } + if ((b = (char *)calloc(1, tlv_size + 1)) == NULL) { + log_warn("lldp", + "unable to allocate memory for string tlv " + "received on %s", + hardware->h_ifname); + goto malformed; + } + PEEK_BYTES(b, tlv_size); + switch (tlv_type) { + case LLDP_TLV_PORT_DESCR: + free(port->p_descr); + port->p_descr = b; + break; + case LLDP_TLV_SYSTEM_NAME: + free(chassis->c_name); + chassis->c_name = b; + break; + case LLDP_TLV_SYSTEM_DESCR: + free(chassis->c_descr); + chassis->c_descr = b; + break; + default: + /* unreachable */ + free(b); + break; + } + break; + case LLDP_TLV_SYSTEM_CAP: + CHECK_TLV_SIZE(4, "System capabilities"); + chassis->c_cap_available = PEEK_UINT16; + chassis->c_cap_enabled = PEEK_UINT16; + break; + case LLDP_TLV_MGMT_ADDR: + CHECK_TLV_SIZE(1, "Management address"); + addr_str_length = PEEK_UINT8; + if (addr_str_length > sizeof(addr_str_buffer)) { + log_warnx("lldp", "too large management address on %s", + hardware->h_ifname); + goto malformed; + } + CHECK_TLV_SIZE(1 + addr_str_length, "Management address"); + PEEK_BYTES(addr_str_buffer, addr_str_length); + addr_length = addr_str_length - 1; + addr_family = addr_str_buffer[0]; + addr_ptr = &addr_str_buffer[1]; + CHECK_TLV_SIZE(1 + addr_str_length + 5, "Management address"); + iface_subtype = PEEK_UINT8; + iface_number = PEEK_UINT32; + + af = lldpd_af_from_lldp_proto(addr_family); + if (af == LLDPD_AF_UNSPEC) break; + if (iface_subtype == LLDP_MGMT_IFACE_IFINDEX) + iface = iface_number; + else + iface = 0; + mgmt = lldpd_alloc_mgmt(af, addr_ptr, addr_length, iface); + if (mgmt == NULL) { + if (errno == ENOMEM) + log_warn("lldp", + "unable to allocate memory " + "for management address"); + else + log_warn("lldp", + "too large management address " + "received on %s", + hardware->h_ifname); + goto malformed; + } + TAILQ_INSERT_TAIL(&chassis->c_mgmt, mgmt, m_entries); + break; + case LLDP_TLV_ORG: + CHECK_TLV_SIZE(1 + (int)sizeof(orgid), "Organisational"); + PEEK_BYTES(orgid, sizeof(orgid)); + unrecognized = 0; + tlv_subtype = PEEK_UINT8; + if (memcmp(dot1, orgid, sizeof(orgid)) == 0) { +#ifndef ENABLE_DOT1 + unrecognized = 1; +#else + /* Dot1 */ + switch (tlv_subtype) { + case LLDP_TLV_DOT1_VLANNAME: + CHECK_TLV_SIZE(7, "VLAN"); + if ((vlan = (struct lldpd_vlan *)calloc(1, + sizeof(struct lldpd_vlan))) == NULL) { + log_warn("lldp", + "unable to alloc vlan " + "structure for " + "tlv received on %s", + hardware->h_ifname); + goto malformed; + } + vlan->v_vid = PEEK_UINT16; + vlan_len = PEEK_UINT8; + CHECK_TLV_SIZE(7 + vlan_len, "VLAN"); + if ((vlan->v_name = (char *)calloc(1, + vlan_len + 1)) == NULL) { + log_warn("lldp", + "unable to alloc vlan name for " + "tlv received on %s", + hardware->h_ifname); + goto malformed; + } + PEEK_BYTES(vlan->v_name, vlan_len); + TAILQ_INSERT_TAIL(&port->p_vlans, vlan, + v_entries); + vlan = NULL; + break; + case LLDP_TLV_DOT1_PVID: + CHECK_TLV_SIZE(6, "PVID"); + port->p_pvid = PEEK_UINT16; + break; + case LLDP_TLV_DOT1_PPVID: + CHECK_TLV_SIZE(7, "PPVID"); + /* validation needed */ + /* PPVID has to be unique if more than + one PPVID TLVs are received - + discard if duplicate */ + /* if support bit is not set and + enabled bit is set - PPVID TLV is + considered error and discarded */ + /* if PPVID > 4096 - bad and discard */ + if ((ppvid = (struct lldpd_ppvid *)calloc(1, + sizeof(struct lldpd_ppvid))) == NULL) { + log_warn("lldp", + "unable to alloc ppvid " + "structure for " + "tlv received on %s", + hardware->h_ifname); + goto malformed; + } + ppvid->p_cap_status = PEEK_UINT8; + ppvid->p_ppvid = PEEK_UINT16; + TAILQ_INSERT_TAIL(&port->p_ppvids, ppvid, + p_entries); + break; + case LLDP_TLV_DOT1_PI: + /* validation needed */ + /* PI has to be unique if more than + one PI TLVs are received - discard + if duplicate ?? */ + CHECK_TLV_SIZE(5, "PI"); + if ((pi = (struct lldpd_pi *)calloc(1, + sizeof(struct lldpd_pi))) == NULL) { + log_warn("lldp", + "unable to alloc PI " + "structure for " + "tlv received on %s", + hardware->h_ifname); + goto malformed; + } + pi->p_pi_len = PEEK_UINT8; + CHECK_TLV_SIZE(5 + pi->p_pi_len, "PI"); + if ((pi->p_pi = (char *)calloc(1, + pi->p_pi_len)) == NULL) { + log_warn("lldp", + "unable to alloc pid name for " + "tlv received on %s", + hardware->h_ifname); + goto malformed; + } + PEEK_BYTES(pi->p_pi, pi->p_pi_len); + TAILQ_INSERT_TAIL(&port->p_pids, pi, p_entries); + pi = NULL; + break; + default: + /* Unknown Dot1 TLV, ignore it */ + unrecognized = 1; + } +#endif + } else if (memcmp(dot3, orgid, sizeof(orgid)) == 0) { +#ifndef ENABLE_DOT3 + unrecognized = 1; +#else + /* Dot3 */ + switch (tlv_subtype) { + case LLDP_TLV_DOT3_MAC: + CHECK_TLV_SIZE(9, "MAC/PHY"); + port->p_macphy.autoneg_support = PEEK_UINT8; + port->p_macphy.autoneg_enabled = + (port->p_macphy.autoneg_support & 0x2) >> 1; + port->p_macphy.autoneg_support = + port->p_macphy.autoneg_support & 0x1; + port->p_macphy.autoneg_advertised = PEEK_UINT16; + port->p_macphy.mau_type = PEEK_UINT16; + break; + case LLDP_TLV_DOT3_LA: + CHECK_TLV_SIZE(9, "Link aggregation"); + PEEK_DISCARD_UINT8; + port->p_aggregid = PEEK_UINT32; + break; + case LLDP_TLV_DOT3_MFS: + CHECK_TLV_SIZE(6, "MFS"); + port->p_mfs = PEEK_UINT16; + break; + case LLDP_TLV_DOT3_POWER: + CHECK_TLV_SIZE(7, "Power"); + port->p_power.devicetype = PEEK_UINT8; + port->p_power.supported = + (port->p_power.devicetype & 0x2) >> 1; + port->p_power.enabled = + (port->p_power.devicetype & 0x4) >> 2; + port->p_power.paircontrol = + (port->p_power.devicetype & 0x8) >> 3; + port->p_power.devicetype = + (port->p_power.devicetype & 0x1) ? + LLDP_DOT3_POWER_PSE : + LLDP_DOT3_POWER_PD; + port->p_power.pairs = PEEK_UINT8; + port->p_power.class = PEEK_UINT8; + /* 802.3at? */ + if (tlv_size >= 12) { + port->p_power.powertype = PEEK_UINT8; + port->p_power.source = + (port->p_power.powertype & + (1 << 5 | 1 << 4)) >> + 4; + port->p_power.priority = + (port->p_power.powertype & + (1 << 1 | 1 << 0)); + port->p_power.powertype = + (port->p_power.powertype & + (1 << 7)) ? + LLDP_DOT3_POWER_8023AT_TYPE1 : + LLDP_DOT3_POWER_8023AT_TYPE2; + port->p_power.requested = PEEK_UINT16; + port->p_power.allocated = PEEK_UINT16; + } else + port->p_power.powertype = + LLDP_DOT3_POWER_8023AT_OFF; + /* 802.3bt? */ + if (tlv_size >= 29) { + port->p_power.requested_a = PEEK_UINT16; + port->p_power.requested_b = PEEK_UINT16; + port->p_power.allocated_a = PEEK_UINT16; + port->p_power.allocated_b = PEEK_UINT16; + port->p_power.pse_status = PEEK_UINT16; + port->p_power.pd_status = + (port->p_power.pse_status & + (1 << 13 | 1 << 12)) >> + 12; + port->p_power.pse_pairs_ext = + (port->p_power.pse_status & + (1 << 11 | 1 << 10)) >> + 10; + port->p_power.class_a = + (port->p_power.pse_status & + (1 << 9 | 1 << 8 | 1 << 7)) >> + 7; + port->p_power.class_b = + (port->p_power.pse_status & + (1 << 6 | 1 << 5 | 1 << 4)) >> + 4; + port->p_power.class_ext = + (port->p_power.pse_status & 0xf); + port->p_power.pse_status = + (port->p_power.pse_status & + (1 << 15 | 1 << 14)) >> + 14; + port->p_power.type_ext = PEEK_UINT8; + port->p_power.pd_load = + (port->p_power.type_ext & 0x1); + port->p_power.type_ext = + ((port->p_power.type_ext & + (1 << 3 | 1 << 2 | 1 << 1)) + + 1); + port->p_power.pse_max = PEEK_UINT16; + } else { + port->p_power.type_ext = + LLDP_DOT3_POWER_8023BT_OFF; + } + break; + default: + /* Unknown Dot3 TLV, ignore it */ + unrecognized = 1; + } +#endif + } else if (memcmp(med, orgid, sizeof(orgid)) == 0) { + /* LLDP-MED */ +#ifndef ENABLE_LLDPMED + unrecognized = 1; +#else + u_int32_t policy; + unsigned loctype; + unsigned power; + + switch (tlv_subtype) { + case LLDP_TLV_MED_CAP: + CHECK_TLV_SIZE(7, "LLDP-MED capabilities"); + chassis->c_med_cap_available = PEEK_UINT16; + chassis->c_med_type = PEEK_UINT8; + port->p_med_cap_enabled |= LLDP_MED_CAP_CAP; + break; + case LLDP_TLV_MED_POLICY: + CHECK_TLV_SIZE(8, "LLDP-MED policy"); + policy = PEEK_UINT32; + if (((policy >> 24) < 1) || + ((policy >> 24) > LLDP_MED_APPTYPE_LAST)) { + log_info("lldp", + "unknown policy field %d " + "received on %s", + policy, hardware->h_ifname); + break; + } + port->p_med_policy[(policy >> 24) - 1].type = + (policy >> 24); + port->p_med_policy[(policy >> 24) - 1].unknown = + ((policy & 0x800000) != 0); + port->p_med_policy[(policy >> 24) - 1].tagged = + ((policy & 0x400000) != 0); + port->p_med_policy[(policy >> 24) - 1].vid = + (policy & 0x001FFE00) >> 9; + port->p_med_policy[(policy >> 24) - 1] + .priority = (policy & 0x1C0) >> 6; + port->p_med_policy[(policy >> 24) - 1].dscp = + policy & 0x3F; + port->p_med_cap_enabled |= LLDP_MED_CAP_POLICY; + break; + case LLDP_TLV_MED_LOCATION: + CHECK_TLV_SIZE(5, "LLDP-MED Location"); + loctype = PEEK_UINT8; + if ((loctype < 1) || + (loctype > LLDP_MED_LOCFORMAT_LAST)) { + log_info("lldp", + "unknown location type " + "received on %s", + hardware->h_ifname); + break; + } + free(port->p_med_location[loctype - 1].data); + if ((port->p_med_location[loctype - 1].data = + (char *)malloc(tlv_size - 5)) == + NULL) { + log_warn("lldp", + "unable to allocate memory " + "for LLDP-MED location for " + "frame received on %s", + hardware->h_ifname); + goto malformed; + } + PEEK_BYTES( + port->p_med_location[loctype - 1].data, + tlv_size - 5); + port->p_med_location[loctype - 1].data_len = + tlv_size - 5; + port->p_med_location[loctype - 1].format = + loctype; + port->p_med_cap_enabled |= + LLDP_MED_CAP_LOCATION; + break; + case LLDP_TLV_MED_MDI: + CHECK_TLV_SIZE(7, "LLDP-MED PoE-MDI"); + power = PEEK_UINT8; + switch (power & 0xC0) { + case 0x0: + port->p_med_power.devicetype = + LLDP_MED_POW_TYPE_PSE; + port->p_med_cap_enabled |= + LLDP_MED_CAP_MDI_PSE; + switch (power & 0x30) { + case 0x0: + port->p_med_power.source = + LLDP_MED_POW_SOURCE_UNKNOWN; + break; + case 0x10: + port->p_med_power.source = + LLDP_MED_POW_SOURCE_PRIMARY; + break; + case 0x20: + port->p_med_power.source = + LLDP_MED_POW_SOURCE_BACKUP; + break; + default: + port->p_med_power.source = + LLDP_MED_POW_SOURCE_RESERVED; + } + break; + case 0x40: + port->p_med_power.devicetype = + LLDP_MED_POW_TYPE_PD; + port->p_med_cap_enabled |= + LLDP_MED_CAP_MDI_PD; + switch (power & 0x30) { + case 0x0: + port->p_med_power.source = + LLDP_MED_POW_SOURCE_UNKNOWN; + break; + case 0x10: + port->p_med_power.source = + LLDP_MED_POW_SOURCE_PSE; + break; + case 0x20: + port->p_med_power.source = + LLDP_MED_POW_SOURCE_LOCAL; + break; + default: + port->p_med_power.source = + LLDP_MED_POW_SOURCE_BOTH; + } + break; + default: + port->p_med_power.devicetype = + LLDP_MED_POW_TYPE_RESERVED; + } + if ((power & 0x0F) > LLDP_MED_POW_PRIO_LOW) + port->p_med_power.priority = + LLDP_MED_POW_PRIO_UNKNOWN; + else + port->p_med_power.priority = + power & 0x0F; + port->p_med_power.val = PEEK_UINT16; + break; + case LLDP_TLV_MED_IV_HW: + case LLDP_TLV_MED_IV_SW: + case LLDP_TLV_MED_IV_FW: + case LLDP_TLV_MED_IV_SN: + case LLDP_TLV_MED_IV_MANUF: + case LLDP_TLV_MED_IV_MODEL: + case LLDP_TLV_MED_IV_ASSET: + if (tlv_size <= 4) + b = NULL; + else { + if ((b = (char *)malloc( + tlv_size - 3)) == NULL) { + log_warn("lldp", + "unable to allocate " + "memory for LLDP-MED " + "inventory for frame " + "received on %s", + hardware->h_ifname); + goto malformed; + } + PEEK_BYTES(b, tlv_size - 4); + b[tlv_size - 4] = '\0'; + } + switch (tlv_subtype) { + case LLDP_TLV_MED_IV_HW: + free(chassis->c_med_hw); + chassis->c_med_hw = b; + break; + case LLDP_TLV_MED_IV_FW: + free(chassis->c_med_fw); + chassis->c_med_fw = b; + break; + case LLDP_TLV_MED_IV_SW: + free(chassis->c_med_sw); + chassis->c_med_sw = b; + break; + case LLDP_TLV_MED_IV_SN: + free(chassis->c_med_sn); + chassis->c_med_sn = b; + break; + case LLDP_TLV_MED_IV_MANUF: + free(chassis->c_med_manuf); + chassis->c_med_manuf = b; + break; + case LLDP_TLV_MED_IV_MODEL: + free(chassis->c_med_model); + chassis->c_med_model = b; + break; + case LLDP_TLV_MED_IV_ASSET: + free(chassis->c_med_asset); + chassis->c_med_asset = b; + break; + default: + /* unreachable */ + free(b); + break; + } + port->p_med_cap_enabled |= LLDP_MED_CAP_IV; + break; + default: + /* Unknown LLDP MED, ignore it */ + hardware->h_rx_unrecognized_cnt++; + } +#endif /* ENABLE_LLDPMED */ + } else if (memcmp(dcbx, orgid, sizeof(orgid)) == 0) { + log_debug("lldp", + "unsupported DCBX tlv received on %s - ignore", + hardware->h_ifname); + unrecognized = 1; + } else { + log_debug("lldp", + "unknown org tlv [%02x:%02x:%02x] received on %s", + orgid[0], orgid[1], orgid[2], hardware->h_ifname); + unrecognized = 1; + } + if (unrecognized) { + hardware->h_rx_unrecognized_cnt++; +#ifdef ENABLE_CUSTOM + custom = (struct lldpd_custom *)calloc(1, + sizeof(struct lldpd_custom)); + if (!custom) { + log_warn("lldp", + "unable to allocate memory for custom TLV"); + goto malformed; + } + custom->oui_info_len = tlv_size > 4 ? tlv_size - 4 : 0; + memcpy(custom->oui, orgid, sizeof(custom->oui)); + custom->subtype = tlv_subtype; + if (custom->oui_info_len > 0) { + custom->oui_info = malloc(custom->oui_info_len); + if (!custom->oui_info) { + log_warn("lldp", + "unable to allocate memory for custom TLV data"); + goto malformed; + } + PEEK_BYTES(custom->oui_info, + custom->oui_info_len); + } + TAILQ_INSERT_TAIL(&port->p_custom_list, custom, next); + custom = NULL; +#endif + } + break; + default: + log_warnx("lldp", "unknown tlv (%d) received on %s", tlv_type, + hardware->h_ifname); + hardware->h_rx_unrecognized_cnt++; + break; + } + if (pos > tlv + tlv_size) { + log_warnx("lldp", "BUG: already past TLV!"); + goto malformed; + } + PEEK_DISCARD(tlv + tlv_size - pos); + } + + /* Some random check */ + if ((chassis->c_id == NULL) || (port->p_id == NULL) || (!ttl_received) || + (gotend == 0)) { + log_warnx("lldp", + "some mandatory tlv are missing for frame received on %s", + hardware->h_ifname); + goto malformed; + } + *newchassis = chassis; + *newport = port; + return 1; +malformed: +#ifdef ENABLE_CUSTOM + free(custom); +#endif +#ifdef ENABLE_DOT1 + free(vlan); + free(pi); +#endif + lldpd_chassis_cleanup(chassis, 1); + lldpd_port_cleanup(port, 1); + free(port); + return -1; +} diff --git a/src/daemon/protocols/sonmp.c b/src/daemon/protocols/sonmp.c new file mode 100644 index 0000000..34ebcd7 --- /dev/null +++ b/src/daemon/protocols/sonmp.c @@ -0,0 +1,410 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "../lldpd.h" +#include "../frame.h" + +#ifdef ENABLE_SONMP + +# include <stdio.h> +# include <unistd.h> +# include <errno.h> +# include <arpa/inet.h> + +static struct sonmp_chassis sonmp_chassis_types[] = { + { 1, "unknown (via SONMP)" }, + { 2, "Nortel 3000" }, + { 3, "Nortel 3030" }, + { 4, "Nortel 2310" }, + { 5, "Nortel 2810" }, + { 6, "Nortel 2912" }, + { 7, "Nortel 2914" }, + { 8, "Nortel 271x" }, + { 9, "Nortel 2813" }, + { 10, "Nortel 2814" }, + { 11, "Nortel 2915" }, + { 12, "Nortel 5000" }, + { 13, "Nortel 2813SA" }, + { 14, "Nortel 2814SA" }, + { 15, "Nortel 810M" }, + { 16, "Nortel EtherCell" }, + { 17, "Nortel 5005" }, + { 18, "Alcatel Ethernet workgroup conc." }, + { 20, "Nortel 2715SA" }, + { 21, "Nortel 2486" }, + { 22, "Nortel 28000 series" }, + { 23, "Nortel 23000 series" }, + { 24, "Nortel 5DN00x series" }, + { 25, "BayStack Ethernet" }, + { 26, "Nortel 23100 series" }, + { 27, "Nortel 100Base-T Hub" }, + { 28, "Nortel 3000 Fast Ethernet" }, + { 29, "Nortel Orion switch" }, + { 30, "unknown" }, + { 31, "Nortel DDS " }, + { 32, "Nortel Centillion" }, + { 33, "Nortel Centillion" }, + { 34, "Nortel Centillion" }, + { 35, "BayStack 301" }, + { 36, "BayStack TokenRing Hub" }, + { 37, "Nortel FVC Multimedia Switch" }, + { 38, "Nortel Switch Node" }, + { 39, "BayStack 302 Switch" }, + { 40, "BayStack 350 Switch" }, + { 41, "BayStack 150 Ethernet Hub" }, + { 42, "Nortel Centillion 50N switch" }, + { 43, "Nortel Centillion 50T switch" }, + { 44, "BayStack 303 and 304 Switches" }, + { 45, "BayStack 200 Ethernet Hub" }, + { 46, "BayStack 250 10/100 Ethernet Hub" }, + { 48, "BayStack 450 10/100/1000 Switches" }, + { 49, "BayStack 410 10/100 Switches" }, + { 50, "Nortel Ethernet Routing 1200 L3 Switch" }, + { 51, "Nortel Ethernet Routing 1250 L3 Switch" }, + { 52, "Nortel Ethernet Routing 1100 L3 Switch" }, + { 53, "Nortel Ethernet Routing 1150 L3 Switch" }, + { 54, "Nortel Ethernet Routing 1050 L3 Switch" }, + { 55, "Nortel Ethernet Routing 1051 L3 Switch" }, + { 56, "Nortel Ethernet Routing 8610 L3 Switch" }, + { 57, "Nortel Ethernet Routing 8606 L3 Switch" }, + { 58, "Nortel Ethernet Routing Switch 8010" }, + { 59, "Nortel Ethernet Routing Switch 8006" }, + { 60, "BayStack 670 wireless access point" }, + { 61, "Nortel Ethernet Routing Switch 740 " }, + { 62, "Nortel Ethernet Routing Switch 750 " }, + { 63, "Nortel Ethernet Routing Switch 790" }, + { 64, "Nortel Business Policy Switch 2000 10/100 Switches" }, + { 65, "Nortel Ethernet Routing 8110 L2 Switch" }, + { 66, "Nortel Ethernet Routing 8106 L2 Switch" }, + { 67, "BayStack 3580 Gig Switch" }, + { 68, "BayStack 10 Power Supply Unit" }, + { 69, "BayStack 420 10/100 Switch" }, + { 70, "OPTera Metro 1200 Ethernet Service Module" }, + { 71, "Nortel Ethernet Routing Switch 8010co" }, + { 72, "Nortel Ethernet Routing 8610co L3 switch" }, + { 73, "Nortel Ethernet Routing 8110co L2 switch" }, + { 74, "Nortel Ethernet Routing 8003" }, + { 75, "Nortel Ethernet Routing 8603 L3 switch" }, + { 76, "Nortel Ethernet Routing 8103 L2 switch" }, + { 77, "BayStack 380 10/100/1000 Switch" }, + { 78, "Nortel Ethernet Switch 470-48T" }, + { 79, "OPTera Metro 1450 Ethernet Service Module" }, + { 80, "OPTera Metro 1400 Ethernet Service Module" }, + { 81, "Alteon Switch Family" }, + { 82, "Ethernet Switch 460-24T-PWR" }, + { 83, "OPTera Metro 8010 OPM L2 Switch" }, + { 84, "OPTera Metro 8010co OPM L2 Switch" }, + { 85, "OPTera Metro 8006 OPM L2 Switch" }, + { 86, "OPTera Metro 8003 OPM L2 Switch" }, + { 87, "Alteon 180e" }, + { 88, "Alteon AD3" }, + { 89, "Alteon 184" }, + { 90, "Alteon AD4" }, + { 91, "Nortel Ethernet Routing 1424 L3 switch" }, + { 92, "Nortel Ethernet Routing 1648 L3 switch" }, + { 93, "Nortel Ethernet Routing 1612 L3 switch" }, + { 94, "Nortel Ethernet Routing 1624 L3 switch " }, + { 95, "BayStack 380-24F Fiber 1000 Switch" }, + { 96, "Nortel Ethernet Routing Switch 5510-24T" }, + { 97, "Nortel Ethernet Routing Switch 5510-48T" }, + { 98, "Nortel Ethernet Switch 470-24T" }, + { 99, "Nortel Networks Wireless LAN Access Point 2220" }, + { 100, "Ethernet Routing RBS 2402 L3 switch" }, + { 101, "Alteon Application Switch 2424 " }, + { 102, "Alteon Application Switch 2224 " }, + { 103, "Alteon Application Switch 2208 " }, + { 104, "Alteon Application Switch 2216" }, + { 105, "Alteon Application Switch 3408" }, + { 106, "Alteon Application Switch 3416" }, + { 107, "Nortel Networks Wireless LAN SecuritySwitch 2250" }, + { 108, "Ethernet Switch 425-48T" }, + { 109, "Ethernet Switch 425-24T" }, + { 110, "Nortel Networks Wireless LAN Access Point 2221" }, + { 111, "Nortel Metro Ethernet Service Unit 24-T SPF switch" }, + { 112, "Nortel Metro Ethernet Service Unit 24-T LX DC switch" }, + { 113, "Nortel Ethernet Routing Switch 8300 10-slot chassis" }, + { 114, "Nortel Ethernet Routing Switch 8300 6-slot chassis" }, + { 115, "Nortel Ethernet Routing Switch 5520-24T-PWR" }, + { 116, "Nortel Ethernet Routing Switch 5520-48T-PWR" }, + { 117, "Nortel Networks VPN Gateway 3050" }, + { 118, "Alteon SSL 310 10/100" }, + { 119, "Alteon SSL 310 10/100 Fiber" }, + { 120, "Alteon SSL 310 10/100 FIPS" }, + { 121, "Alteon SSL 410 10/100/1000" }, + { 122, "Alteon SSL 410 10/100/1000 Fiber" }, + { 123, "Alteon Application Switch 2424-SSL" }, + { 124, "Nortel Ethernet Switch 325-24T" }, + { 125, "Nortel Ethernet Switch 325-24G" }, + { 126, "Nortel Networks Wireless LAN Access Point 2225" }, + { 127, "Nortel Networks Wireless LAN SecuritySwitch 2270" }, + { 128, "Nortel 24-port Ethernet Switch 470-24T-PWR" }, + { 129, "Nortel 48-port Ethernet Switch 470-48T-PWR" }, + { 130, "Nortel Ethernet Routing Switch 5530-24TFD" }, + { 131, "Nortel Ethernet Switch 3510-24T" }, + { 132, "Nortel Metro Ethernet Service Unit 12G AC L3 switch" }, + { 133, "Nortel Metro Ethernet Service Unit 12G DC L3 switch" }, + { 134, "Nortel Secure Access Switch" }, + { 135, "Networks VPN Gateway 3070" }, + { 136, "OPTera Metro 3500" }, + { 137, "SMB BES 1010 24T" }, + { 138, "SMB BES 1010 48T" }, + { 139, "SMB BES 1020 24T PWR" }, + { 140, "SMB BES 1020 48T PWR" }, + { 141, "SMB BES 2010 24T" }, + { 142, "SMB BES 2010 48T" }, + { 143, "SMB BES 2020 24T PWR" }, + { 144, "SMB BES 2020 48T PWR" }, + { 145, "SMB BES 110 24T" }, + { 146, "SMB BES 110 48T" }, + { 147, "SMB BES 120 24T PWR" }, + { 148, "SMB BES 120 48T PWR" }, + { 149, "SMB BES 210 24T" }, + { 150, "SMB BES 210 48T" }, + { 151, "SMB BES 220 24T PWR" }, + { 152, "SMB BES 220 48T PWR" }, + { 153, "OME 6500" }, + { 0, "unknown (via SONMP)" }, +}; + +int +sonmp_send(struct lldpd *global, struct lldpd_hardware *hardware) +{ + const u_int8_t mcastaddr[] = SONMP_MULTICAST_ADDR; + const u_int8_t llcorg[] = LLC_ORG_NORTEL; + struct lldpd_chassis *chassis; + struct lldpd_mgmt *mgmt; + u_int8_t *packet, *pos, *pos_pid, *end; + int length; + struct in_addr address; + + log_debug("sonmp", "send SONMP PDU to %s", hardware->h_ifname); + + chassis = hardware->h_lport.p_chassis; + length = hardware->h_mtu; + if ((packet = (u_int8_t *)calloc(1, length)) == NULL) return ENOMEM; + pos = packet; + + /* Ethernet header */ + if (!( + /* SONMP multicast address as target */ + POKE_BYTES(mcastaddr, sizeof(mcastaddr)) && + /* Source MAC addresss */ + POKE_BYTES(&hardware->h_lladdr, ETHER_ADDR_LEN) && + /* SONMP frame is of fixed size */ + POKE_UINT16(SONMP_SIZE))) + goto toobig; + + /* LLC header */ + if (!( + /* DSAP and SSAP */ + POKE_UINT8(0xaa) && POKE_UINT8(0xaa) && + /* Control field */ + POKE_UINT8(0x03) && + /* ORG */ + POKE_BYTES(llcorg, sizeof(llcorg)) && + POKE_SAVE(pos_pid) && /* We will modify PID later to + create a new frame */ + POKE_UINT16(LLC_PID_SONMP_HELLO))) + goto toobig; + + address.s_addr = htonl(INADDR_ANY); + TAILQ_FOREACH (mgmt, &chassis->c_mgmt, m_entries) { + if (mgmt->m_family == LLDPD_AF_IPV4) { + address.s_addr = mgmt->m_addr.inet.s_addr; + } + break; + } + + /* SONMP */ + if (!( + /* Our IP address */ + POKE_BYTES(&address, sizeof(struct in_addr)) && + /* Segment on three bytes, we don't have slots, so we + skip the first two bytes */ + POKE_UINT16(0) && POKE_UINT8(hardware->h_ifindex) && + POKE_UINT8(1) && /* Chassis: Other */ + POKE_UINT8(12) && /* Back: Ethernet, Fast Ethernet and Gigabit */ + POKE_UINT8(SONMP_TOPOLOGY_NEW) && /* Should work. We have no state */ + POKE_UINT8(1) && /* Links: Dunno what it is */ + POKE_SAVE(end))) + goto toobig; + + if (interfaces_send_helper(global, hardware, (char *)packet, end - packet) == + -1) { + log_warn("sonmp", "unable to send packet on real device for %s", + hardware->h_ifname); + free(packet); + return ENETDOWN; + } + + POKE_RESTORE(pos_pid); /* Modify LLC PID */ + (void)POKE_UINT16(LLC_PID_SONMP_FLATNET); + POKE_RESTORE(packet); /* Go to the beginning */ + PEEK_DISCARD(ETHER_ADDR_LEN - 1); /* Modify the last byte of the MAC address */ + (void)POKE_UINT8(1); + + if (interfaces_send_helper(global, hardware, (char *)packet, end - packet) == + -1) { + log_warn("sonmp", + "unable to send second SONMP packet on real device for %s", + hardware->h_ifname); + free(packet); + return ENETDOWN; + } + + free(packet); + hardware->h_tx_cnt++; + return 0; +toobig: + free(packet); + return -1; +} + +int +sonmp_decode(struct lldpd *cfg, char *frame, int s, struct lldpd_hardware *hardware, + struct lldpd_chassis **newchassis, struct lldpd_port **newport) +{ + const u_int8_t mcastaddr[] = SONMP_MULTICAST_ADDR; + struct lldpd_chassis *chassis; + struct lldpd_port *port; + struct lldpd_mgmt *mgmt; + int length, i; + u_int8_t *pos; + u_int8_t seg[3], rchassis; + struct in_addr address; + + log_debug("sonmp", "decode SONMP PDU from %s", hardware->h_ifname); + + if ((chassis = calloc(1, sizeof(struct lldpd_chassis))) == NULL) { + log_warn("sonmp", "failed to allocate remote chassis"); + return -1; + } + TAILQ_INIT(&chassis->c_mgmt); + if ((port = calloc(1, sizeof(struct lldpd_port))) == NULL) { + log_warn("sonmp", "failed to allocate remote port"); + free(chassis); + return -1; + } +# ifdef ENABLE_DOT1 + TAILQ_INIT(&port->p_vlans); +# endif + + length = s; + pos = (u_int8_t *)frame; + if (length < SONMP_SIZE + 2 * ETHER_ADDR_LEN + sizeof(u_int16_t)) { + log_warnx("sonmp", "too short SONMP frame received on %s", + hardware->h_ifname); + goto malformed; + } + if (PEEK_CMP(mcastaddr, sizeof(mcastaddr)) != 0) + /* There is two multicast address. We just handle only one of + * them. */ + goto malformed; + /* We skip to LLC PID */ + PEEK_DISCARD(ETHER_ADDR_LEN); + PEEK_DISCARD_UINT16; + PEEK_DISCARD(6); + if (PEEK_UINT16 != LLC_PID_SONMP_HELLO) { + log_debug("sonmp", "incorrect LLC protocol ID received for SONMP on %s", + hardware->h_ifname); + goto malformed; + } + + chassis->c_id_subtype = LLDP_CHASSISID_SUBTYPE_ADDR; + if ((chassis->c_id = calloc(1, sizeof(struct in_addr) + 1)) == NULL) { + log_warn("sonmp", "unable to allocate memory for chassis id on %s", + hardware->h_ifname); + goto malformed; + } + chassis->c_id_len = sizeof(struct in_addr) + 1; + chassis->c_id[0] = 1; + PEEK_BYTES(&address, sizeof(struct in_addr)); + memcpy(chassis->c_id + 1, &address, sizeof(struct in_addr)); + if (asprintf(&chassis->c_name, "%s", inet_ntoa(address)) == -1) { + log_warnx("sonmp", "unable to write chassis name for %s", + hardware->h_ifname); + goto malformed; + } + PEEK_BYTES(seg, sizeof(seg)); + rchassis = PEEK_UINT8; + for (i = 0; sonmp_chassis_types[i].type != 0; i++) { + if (sonmp_chassis_types[i].type == rchassis) break; + } + if (asprintf(&chassis->c_descr, "%s", sonmp_chassis_types[i].description) == + -1) { + log_warnx("sonmp", "unable to write chassis description for %s", + hardware->h_ifname); + goto malformed; + } + mgmt = lldpd_alloc_mgmt(LLDPD_AF_IPV4, &address, sizeof(struct in_addr), 0); + if (mgmt == NULL) { + if (errno == ENOMEM) + log_warn("sonmp", + "unable to allocate memory for management address"); + else + log_warn("sonmp", "too large management address received on %s", + hardware->h_ifname); + goto malformed; + } + TAILQ_INSERT_TAIL(&chassis->c_mgmt, mgmt, m_entries); + port->p_ttl = + cfg ? (cfg->g_config.c_tx_interval * cfg->g_config.c_tx_hold) : LLDPD_TTL; + port->p_ttl = (port->p_ttl + 999) / 1000; + + port->p_id_subtype = LLDP_PORTID_SUBTYPE_LOCAL; + + port->p_id_len = + asprintf(&port->p_id, "%02x-%02x-%02x", seg[0], seg[1], seg[2]); + if (port->p_id_len == -1) { + log_warn("sonmp", "unable to allocate memory for port id on %s", + hardware->h_ifname); + goto malformed; + } + + /* Port description depend on the number of segments */ + if ((seg[0] == 0) && (seg[1] == 0)) { + if (asprintf(&port->p_descr, "port %d", seg[2]) == -1) { + log_warnx("sonmp", "unable to write port description for %s", + hardware->h_ifname); + goto malformed; + } + } else if (seg[0] == 0) { + if (asprintf(&port->p_descr, "port %d/%d", seg[1], seg[2]) == -1) { + log_warnx("sonmp", "unable to write port description for %s", + hardware->h_ifname); + goto malformed; + } + } else { + if (asprintf(&port->p_descr, "port %x:%x:%x", seg[0], seg[1], seg[2]) == + -1) { + log_warnx("sonmp", "unable to write port description for %s", + hardware->h_ifname); + goto malformed; + } + } + *newchassis = chassis; + *newport = port; + return 1; + +malformed: + lldpd_chassis_cleanup(chassis, 1); + lldpd_port_cleanup(port, 1); + free(port); + return -1; +} + +#endif /* ENABLE_SONMP */ diff --git a/src/daemon/protocols/sonmp.h b/src/daemon/protocols/sonmp.h new file mode 100644 index 0000000..513c4bb --- /dev/null +++ b/src/daemon/protocols/sonmp.h @@ -0,0 +1,42 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _SONMP_H +#define _SONMP_H + +#define SONMP_MULTICAST_ADDR \ + { \ + 0x01, 0x00, 0x81, 0x00, 0x01, 0x00 \ + } +#define LLC_ORG_NORTEL \ + { \ + 0x00, 0x00, 0x81 \ + } +#define LLC_PID_SONMP_HELLO 0x01a2 +#define LLC_PID_SONMP_FLATNET 0x01a1 +#define SONMP_SIZE 19 + +struct sonmp_chassis { + int type; + const char *description; +}; + +#define SONMP_TOPOLOGY_CHANGED 1 +#define SONMP_TOPOLOGY_UNCHANGED 2 +#define SONMP_TOPOLOGY_NEW 3 + +#endif /* _SONMP_H */ |