summaryrefslogtreecommitdiffstats
path: root/src/daemon/protocols/edp.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/daemon/protocols/edp.c')
-rw-r--r--src/daemon/protocols/edp.c514
1 files changed, 514 insertions, 0 deletions
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 */