diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/automake.mk | 12 | ||||
-rw-r--r-- | lib/lldp/aa-structs.h | 49 | ||||
-rw-r--r-- | lib/lldp/lldp-const.h | 230 | ||||
-rw-r--r-- | lib/lldp/lldp-tlv.h | 79 | ||||
-rw-r--r-- | lib/lldp/lldp.c | 752 | ||||
-rw-r--r-- | lib/lldp/lldpd-structs.c | 130 | ||||
-rw-r--r-- | lib/lldp/lldpd-structs.h | 228 | ||||
-rw-r--r-- | lib/lldp/lldpd.c | 655 | ||||
-rw-r--r-- | lib/lldp/lldpd.h | 120 | ||||
-rw-r--r-- | lib/ovs-lldp.c | 1041 | ||||
-rw-r--r-- | lib/ovs-lldp.h | 112 |
11 files changed, 3407 insertions, 1 deletions
diff --git a/lib/automake.mk b/lib/automake.mk index 87441f704..2acfe18a5 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -162,6 +162,8 @@ lib_libopenvswitch_la_SOURCES = \ lib/ovs-atomic-pthreads.h \ lib/ovs-atomic-x86_64.h \ lib/ovs-atomic.h \ + lib/ovs-lldp.c \ + lib/ovs-lldp.h \ lib/ovs-rcu.c \ lib/ovs-rcu.h \ lib/ovs-router.h \ @@ -267,7 +269,15 @@ lib_libopenvswitch_la_SOURCES = \ lib/vswitch-idl.c \ lib/vswitch-idl.h \ lib/vtep-idl.c \ - lib/vtep-idl.h + lib/vtep-idl.h \ + lib/lldp/aa-structs.h \ + lib/lldp/lldp.c \ + lib/lldp/lldp-const.h \ + lib/lldp/lldp-tlv.h \ + lib/lldp/lldpd.c \ + lib/lldp/lldpd.h \ + lib/lldp/lldpd-structs.c \ + lib/lldp/lldpd-structs.h if WIN32 lib_libopenvswitch_la_SOURCES += \ diff --git a/lib/lldp/aa-structs.h b/lib/lldp/aa-structs.h new file mode 100644 index 000000000..f58be76ad --- /dev/null +++ b/lib/lldp/aa-structs.h @@ -0,0 +1,49 @@ +/* aa-structs.h */ +/* contains tlv structures for various auto attach functionality */ + +/* Copyright (c) 2014 Avaya, Inc + * + * 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 AA_STRUCTS_H +#define AA_STRUCTS_H + +#include <stdint.h> +#include "list.h" + +struct lldp_aa_element_system_id { + uint8_t system_mac[6]; + uint16_t conn_type; + uint16_t smlt_id; + uint8_t mlt_id[2]; +}; + +struct lldpd_aa_element_tlv { + uint16_t type; + uint16_t mgmt_vlan; + struct lldp_aa_element_system_id system_id; +}; + +struct lldpd_aa_isid_vlan_map_data { + uint16_t status; + uint16_t vlan; + uint8_t isid[3]; +}; + +struct lldpd_aa_isid_vlan_maps_tlv { + struct ovs_list m_entries; + struct lldpd_aa_isid_vlan_map_data isid_vlan_data; +}; + +#endif diff --git a/lib/lldp/lldp-const.h b/lib/lldp/lldp-const.h new file mode 100644 index 000000000..eceb612d1 --- /dev/null +++ b/lib/lldp/lldp-const.h @@ -0,0 +1,230 @@ +/* -*- 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 _LLDP_H +#define _LLDP_H + +/* Definitions prefixed by `LLDP_` are constants from LLDP + * specifications. Definitions prefixed by `LLDPD_` are custom + * constants that are useful in the context of lldpd and its clients. + */ + +/* Chassis ID subtype */ +#define LLDP_CHASSISID_SUBTYPE_CHASSIS 1 +#define LLDP_CHASSISID_SUBTYPE_IFALIAS 2 +#define LLDP_CHASSISID_SUBTYPE_PORT 3 +#define LLDP_CHASSISID_SUBTYPE_LLADDR 4 +#define LLDP_CHASSISID_SUBTYPE_ADDR 5 +#define LLDP_CHASSISID_SUBTYPE_IFNAME 6 +#define LLDP_CHASSISID_SUBTYPE_LOCAL 7 + +/* Port ID subtype */ +#define LLDP_PORTID_SUBTYPE_UNKNOWN 0 +#define LLDP_PORTID_SUBTYPE_IFALIAS 1 +#define LLDP_PORTID_SUBTYPE_PORT 2 +#define LLDP_PORTID_SUBTYPE_LLADDR 3 +#define LLDP_PORTID_SUBTYPE_ADDR 4 +#define LLDP_PORTID_SUBTYPE_IFNAME 5 +#define LLDP_PORTID_SUBTYPE_AGENTCID 6 +#define LLDP_PORTID_SUBTYPE_LOCAL 7 +#define LLDP_PORTID_SUBTYPE_MAX LLDP_PORTID_SUBTYPE_LOCAL + +/* Operational MAU Type field, from RFC 3636 */ +#define LLDP_DOT3_MAU_AUI 1 +#define LLDP_DOT3_MAU_10BASE5 2 +#define LLDP_DOT3_MAU_FOIRL 3 +#define LLDP_DOT3_MAU_10BASE2 4 +#define LLDP_DOT3_MAU_10BASET 5 +#define LLDP_DOT3_MAU_10BASEFP 6 +#define LLDP_DOT3_MAU_10BASEFB 7 +#define LLDP_DOT3_MAU_10BASEFL 8 +#define LLDP_DOT3_MAU_10BROAD36 9 +#define LLDP_DOT3_MAU_10BASETHD 10 +#define LLDP_DOT3_MAU_10BASETFD 11 +#define LLDP_DOT3_MAU_10BASEFLHD 12 +#define LLDP_DOT3_MAU_10BASEFLFD 13 +#define LLDP_DOT3_MAU_10BASET4 14 +#define LLDP_DOT3_MAU_100BASETXHD 15 +#define LLDP_DOT3_MAU_100BASETXFD 16 +#define LLDP_DOT3_MAU_100BASEFXHD 17 +#define LLDP_DOT3_MAU_100BASEFXFD 18 +#define LLDP_DOT3_MAU_100BASET2HD 19 +#define LLDP_DOT3_MAU_100BASET2FD 20 +#define LLDP_DOT3_MAU_1000BASEXHD 21 +#define LLDP_DOT3_MAU_1000BASEXFD 22 +#define LLDP_DOT3_MAU_1000BASELXHD 23 +#define LLDP_DOT3_MAU_1000BASELXFD 24 +#define LLDP_DOT3_MAU_1000BASESXHD 25 +#define LLDP_DOT3_MAU_1000BASESXFD 26 +#define LLDP_DOT3_MAU_1000BASECXHD 27 +#define LLDP_DOT3_MAU_1000BASECXFD 28 +#define LLDP_DOT3_MAU_1000BASETHD 29 +#define LLDP_DOT3_MAU_1000BASETFD 30 +#define LLDP_DOT3_MAU_10GIGBASEX 31 +#define LLDP_DOT3_MAU_10GIGBASELX4 32 +#define LLDP_DOT3_MAU_10GIGBASER 33 +#define LLDP_DOT3_MAU_10GIGBASEER 34 +#define LLDP_DOT3_MAU_10GIGBASELR 35 +#define LLDP_DOT3_MAU_10GIGBASESR 36 +#define LLDP_DOT3_MAU_10GIGBASEW 37 +#define LLDP_DOT3_MAU_10GIGBASEEW 38 +#define LLDP_DOT3_MAU_10GIGBASELW 39 +#define LLDP_DOT3_MAU_10GIGBASESW 40 + +/* Dot3 Power Devicetype */ +#define LLDP_DOT3_POWER_PSE 1 +#define LLDP_DOT3_POWER_PD 2 + +/* Dot3 Power Pairs (RFC 3621) */ +#define LLDP_DOT3_POWERPAIRS_SIGNAL 1 +#define LLDP_DOT3_POWERPAIRS_SPARE 2 + +/* Dot3 Power type (for 802.3at) */ +#define LLDP_DOT3_POWER_8023AT_OFF 0 +#define LLDP_DOT3_POWER_8023AT_TYPE1 1 +#define LLDP_DOT3_POWER_8023AT_TYPE2 2 + +/* Dot3 power source */ +#define LLDP_DOT3_POWER_SOURCE_UNKNOWN 0 +#define LLDP_DOT3_POWER_SOURCE_PRIMARY 1 +#define LLDP_DOT3_POWER_SOURCE_PSE 1 +#define LLDP_DOT3_POWER_SOURCE_BACKUP 2 +#define LLDP_DOT3_POWER_SOURCE_LOCAL 2 +#define LLDP_DOT3_POWER_SOURCE_BOTH 3 + +/* Dot3 power priority */ +#define LLDP_DOT3_POWER_PRIO_UNKNOWN 0 +#define LLDP_DOT3_POWER_PRIO_CRITICAL 1 +#define LLDP_DOT3_POWER_PRIO_HIGH 2 +#define LLDP_DOT3_POWER_PRIO_LOW 3 + +/* PMD Auto-Negotiation Advertised Capability field, from RFC 3636 */ +#define LLDP_DOT3_LINK_AUTONEG_OTHER 0x8000 +#define LLDP_DOT3_LINK_AUTONEG_10BASE_T 0x4000 +#define LLDP_DOT3_LINK_AUTONEG_10BASET_FD 0x2000 +#define LLDP_DOT3_LINK_AUTONEG_100BASE_T4 0x1000 +#define LLDP_DOT3_LINK_AUTONEG_100BASE_TX 0x0800 +#define LLDP_DOT3_LINK_AUTONEG_100BASE_TXFD 0x0400 +#define LLDP_DOT3_LINK_AUTONEG_100BASE_T2 0x0200 +#define LLDP_DOT3_LINK_AUTONEG_100BASE_T2FD 0x0100 +#define LLDP_DOT3_LINK_AUTONEG_FDX_PAUSE 0x0080 +#define LLDP_DOT3_LINK_AUTONEG_FDX_APAUSE 0x0040 +#define LLDP_DOT3_LINK_AUTONEG_FDX_SPAUSE 0x0020 +#define LLDP_DOT3_LINK_AUTONEG_FDX_BPAUSE 0x0010 +#define LLDP_DOT3_LINK_AUTONEG_1000BASE_X 0x0008 +#define LLDP_DOT3_LINK_AUTONEG_1000BASE_XFD 0x0004 +#define LLDP_DOT3_LINK_AUTONEG_1000BASE_T 0x0002 +#define LLDP_DOT3_LINK_AUTONEG_1000BASE_TFD 0x0001 + +/* Capabilities */ +#define LLDP_CAP_OTHER 0x01 +#define LLDP_CAP_REPEATER 0x02 +#define LLDP_CAP_BRIDGE 0x04 +#define LLDP_CAP_WLAN 0x08 +#define LLDP_CAP_ROUTER 0x10 +#define LLDP_CAP_TELEPHONE 0x20 +#define LLDP_CAP_DOCSIS 0x40 +#define LLDP_CAP_STATION 0x80 + +#define LLDP_PPVID_CAP_SUPPORTED (1 << 1) +#define LLDP_PPVID_CAP_ENABLED (1 << 2) + +/* see http://www.iana.org/assignments/address-family-numbers */ +#define LLDP_MGMT_ADDR_NONE 0 +#define LLDP_MGMT_ADDR_IP4 1 +#define LLDP_MGMT_ADDR_IP6 2 + +#define LLDP_MGMT_IFACE_UNKNOWN 1 +#define LLDP_MGMT_IFACE_IFINDEX 2 +#define LLDP_MGMT_IFACE_SYSPORT 3 + +#define LLDP_MED_CLASS_I 1 +#define LLDP_MED_CLASS_II 2 +#define LLDP_MED_CLASS_III 3 +#define LLDP_MED_NETWORK_DEVICE 4 + +/* LLDP MED application ttpes */ +#define LLDP_MED_APPTYPE_UNDEFINED 0 +#define LLDP_MED_APPTYPE_VOICE 1 +#define LLDP_MED_APPTYPE_VOICESIGNAL 2 +#define LLDP_MED_APPTYPE_GUESTVOICE 3 +#define LLDP_MED_APPTYPE_GUESTVOICESIGNAL 4 +#define LLDP_MED_APPTYPE_SOFTPHONEVOICE 5 +#define LLDP_MED_APPTYPE_VIDEOCONFERENCE 6 +#define LLDP_MED_APPTYPE_VIDEOSTREAM 7 +#define LLDP_MED_APPTYPE_VIDEOSIGNAL 8 +#define LLDP_MED_APPTYPE_LAST LLDP_MED_APPTYPE_VIDEOSIGNAL + +/* LLDP MED location formats */ +#define LLDP_MED_LOCFORMAT_COORD 1 +#define LLDP_MED_LOCFORMAT_CIVIC 2 +#define LLDP_MED_LOCFORMAT_ELIN 3 +#define LLDP_MED_LOCFORMAT_LAST LLDP_MED_LOCFORMAT_ELIN + +#define LLDP_MED_LOCATION_GEOID_WGS84 1 +#define LLDP_MED_LOCATION_GEOID_NAD83 2 +#define LLDP_MED_LOCATION_GEOID_NAD83_MLLW 3 + +#define LLDP_MED_LOCATION_ALTITUDE_UNIT_METER 1 +#define LLDP_MED_LOCATION_ALTITUDE_UNIT_FLOOR 2 + +/* LLDP MED power related constants */ +#define LLDP_MED_POW_TYPE_PSE 1 +#define LLDP_MED_POW_TYPE_PD 2 +#define LLDP_MED_POW_TYPE_RESERVED 3 + +#define LLDP_MED_POW_SOURCE_UNKNOWN 1 +#define LLDP_MED_POW_SOURCE_PRIMARY 2 +#define LLDP_MED_POW_SOURCE_BACKUP 3 +#define LLDP_MED_POW_SOURCE_RESERVED 4 +#define LLDP_MED_POW_SOURCE_PSE 5 +#define LLDP_MED_POW_SOURCE_LOCAL 6 +#define LLDP_MED_POW_SOURCE_BOTH 7 + +#define LLDP_MED_POW_PRIO_UNKNOWN 0 +#define LLDP_MED_POW_PRIO_CRITICAL 1 +#define LLDP_MED_POW_PRIO_HIGH 2 +#define LLDP_MED_POW_PRIO_LOW 3 + +/* LLDP MED capabilities */ +#define LLDP_MED_CAP_CAP 0x01 +#define LLDP_MED_CAP_POLICY 0x02 +#define LLDP_MED_CAP_LOCATION 0x04 +#define LLDP_MED_CAP_MDI_PSE 0x08 +#define LLDP_MED_CAP_MDI_PD 0x10 +#define LLDP_MED_CAP_IV 0x20 + +/* Protocol constants for multi-protocol lldpd */ +#define LLDPD_MODE_LLDP 1 +#define LLDPD_MODE_CDPV1 2 +#define LLDPD_MODE_CDPV2 3 +#define LLDPD_MODE_SONMP 4 +#define LLDPD_MODE_EDP 5 +#define LLDPD_MODE_FDP 6 +#define LLDPD_MODE_MAX LLDPD_MODE_FDP + + +/* Bond slave src mac type constants */ +#define LLDP_BOND_SLAVE_SRC_MAC_TYPE_UNKNOWN 0 +#define LLDP_BOND_SLAVE_SRC_MAC_TYPE_REAL 1 +#define LLDP_BOND_SLAVE_SRC_MAC_TYPE_ZERO 2 +#define LLDP_BOND_SLAVE_SRC_MAC_TYPE_FIXED 3 +#define LLDP_BOND_SLAVE_SRC_MAC_TYPE_LOCALLY_ADMINISTERED 4 +#define LLDP_BOND_SLAVE_SRC_MAC_TYPE_MAX \ + LLDP_BOND_SLAVE_SRC_MAC_TYPE_LOCALLY_ADMINISTERED + +#endif /* _LLDP_H */ diff --git a/lib/lldp/lldp-tlv.h b/lib/lldp/lldp-tlv.h new file mode 100644 index 000000000..237414d5a --- /dev/null +++ b/lib/lldp/lldp-tlv.h @@ -0,0 +1,79 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 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 _LLDP_TLV_H +#define _LLDP_TLV_H + +#define LLDP_MULTICAST_ADDR { \ + 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e \ +} + +#define LLDP_TLV_END 0 +#define LLDP_TLV_CHASSIS_ID 1 +#define LLDP_TLV_PORT_ID 2 +#define LLDP_TLV_TTL 3 +#define LLDP_TLV_PORT_DESCR 4 +#define LLDP_TLV_SYSTEM_NAME 5 +#define LLDP_TLV_SYSTEM_DESCR 6 +#define LLDP_TLV_SYSTEM_CAP 7 +#define LLDP_TLV_MGMT_ADDR 8 +#define LLDP_TLV_ORG 127 + +#define LLDP_TLV_ORG_DOT1 {0x00, 0x80, 0xc2} +#define LLDP_TLV_ORG_DOT3 {0x00, 0x12, 0x0f} +#define LLDP_TLV_ORG_MED {0x00, 0x12, 0xbb} +#define LLDP_TLV_ORG_AVAYA {0x00, 0x40, 0x0D} +#define LLDP_TLV_ORG_DCBX {0x00, 0x1b, 0x21} + +#define LLDP_TLV_DOT1_PVID 1 +#define LLDP_TLV_DOT1_PPVID 2 +#define LLDP_TLV_DOT1_VLANNAME 3 +#define LLDP_TLV_DOT1_PI 4 + +#define LLDP_TLV_DOT3_MAC 1 +#define LLDP_TLV_DOT3_POWER 2 +#define LLDP_TLV_DOT3_LA 3 +#define LLDP_TLV_DOT3_MFS 4 + +#define LLDP_TLV_MED_CAP 1 +#define LLDP_TLV_MED_POLICY 2 +#define LLDP_TLV_MED_LOCATION 3 +#define LLDP_TLV_MED_MDI 4 +#define LLDP_TLV_MED_IV_HW 5 +#define LLDP_TLV_MED_IV_FW 6 +#define LLDP_TLV_MED_IV_SW 7 +#define LLDP_TLV_MED_IV_SN 8 +#define LLDP_TLV_MED_IV_MANUF 9 +#define LLDP_TLV_MED_IV_MODEL 10 +#define LLDP_TLV_MED_IV_ASSET 11 + +#define LLDP_TLV_AA_ELEMENT_SUBTYPE 0x08 +#define LLDP_TLV_AA_ISID_VLAN_ASGNS_SUBTYPE 0x09 +#define LLDP_TLV_AA_ISID_VLAN_DIGEST_LENGTH 32 +#define LLDP_TLV_AA_ELEM_TYPE_UNKNOWN 1 +#define LLDP_TLV_AA_ELEM_TYPE_SERVER 2 +#define LLDP_TLV_AA_ELEM_TYPE_PROXY 3 +#define LLDP_TLV_AA_ELEM_TYPE_UNTAG_CLIENT 4 +#define LLDP_TLV_AA_ELEM_TYPE_TAG_CLIENT 5 +#define LLDP_TLV_AA_ELEM_TYPE_SERV_NO_AUTH 6 +#define LLDP_TLV_AA_ELEM_TYPE_PROXY_NO_AUTH 7 +#define LLDP_TLV_AA_ELEM_CONN_TYPE_SINGLE 0 +#define LLDP_TLV_AA_ELEM_CONN_TYPE_MLT 1 +#define LLDP_TLV_AA_ELEM_CONN_TYPE_SLT 2 +#define LLDP_TLV_AA_ELEM_CONN_TYPE_SMLT 3 + +#endif diff --git a/lib/lldp/lldp.c b/lib/lldp/lldp.c new file mode 100644 index 000000000..5838a0721 --- /dev/null +++ b/lib/lldp/lldp.c @@ -0,0 +1,752 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx> + * Copyright (c) 2014 Michael Chapman + * + * 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 <config.h> +#include "lldpd.h" +#include <errno.h> +#include <time.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/types.h> +#include "compiler.h" +#include "packets.h" +#include "ofpbuf.h" + +VLOG_DEFINE_THIS_MODULE(lldp); + +/* This set of macro are used to build packets. The current position in buffer + * is `pos'. The length of the remaining space in buffer is `length'. `type' + * should be a member of `types'. + * + * This was stolen from ladvd which was adapted from Net::CDP. The original + * author of those macros, Michael Chapman, has relicensed those macros under + * the ISC license. + */ + +#define POKE(value, type, func) \ + ((length >= sizeof type) && \ + ( \ + type = func(value), \ + memcpy(pos, &type, sizeof type), \ + length -= sizeof type, \ + pos += sizeof type, \ + 1 \ + ) \ + ) +#define POKE_UINT8(value) POKE(value, types.f_uint8, ) +#define POKE_UINT16(value) POKE(value, types.f_uint16, htons) +#define POKE_UINT32(value) POKE(value, types.f_uint32, htonl) +#define POKE_BYTES(value, bytes) \ + ((length >= (bytes)) && \ + ( \ + memcpy(pos, value, bytes), \ + length -= (bytes), \ + pos += (bytes), \ + 1 \ + ) \ + ) +#define POKE_SAVE(where) (where = pos, 1) +#define POKE_RESTORE(where) \ + do { \ + if ((where) > pos) \ + length -= ((where) - pos); \ + else \ + length += (pos - (where)); \ + pos = (where); \ + } while(0) + +/* This set of macro are used to parse packets. The same variable as for POKE_ + * are used. There is no check on boundaries. + */ + +#define PEEK(type, func) \ + ( \ + memcpy(&type, pos, sizeof type), \ + length -= sizeof type, \ + pos += sizeof type, \ + func(type) \ + ) +#define PEEK_UINT8 PEEK(types.f_uint8, ) +#define PEEK_UINT16 PEEK(types.f_uint16, ntohs) +#define PEEK_UINT32 PEEK(types.f_uint32, ntohl) +#define PEEK_BYTES(value, bytes) \ + do { \ + memcpy(value, pos, bytes); \ + length -= (bytes); \ + pos += (bytes); \ + } while (0) +#define PEEK_DISCARD(bytes) \ + do { \ + length -= (bytes); \ + pos += (bytes); \ + } while (0) +#define PEEK_DISCARD_UINT8 PEEK_DISCARD(1) +#define PEEK_DISCARD_UINT16 PEEK_DISCARD(2) +#define PEEK_DISCARD_UINT32 PEEK_DISCARD(3) +#define PEEK_CMP(value, bytes) \ + (length -= (bytes), \ + pos += (bytes), \ + memcmp(pos-bytes, value, bytes)) +#define PEEK_SAVE POKE_SAVE +#define PEEK_RESTORE POKE_RESTORE + +/* LLDP specific. We need a `tlv' pointer. */ +#define POKE_START_LLDP_TLV(type) \ + ( \ + tlv = pos, \ + POKE_UINT16(type << 9) \ + ) +#define POKE_END_LLDP_TLV \ + ( \ + memcpy(&types.f_uint16, tlv, sizeof(uint16_t)), \ + types.f_uint16 |= htons((pos - (tlv + 2)) & 0x01ff), \ + memcpy(tlv, &types.f_uint16, sizeof(uint16_t)), \ + 1 \ + ) + +#define CHECK_TLV_SIZE(x, name) \ + do { \ + if (tlv_size < (x)) { \ + VLOG_WARN(name " TLV too short received on %s", \ + hardware->h_ifname); \ + goto malformed; \ + } \ + } while (0) + +static union { + uint8_t f_uint8; + ovs_be16 f_uint16; + ovs_be32 f_uint32; +} types; + +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; + } +} + +int +lldp_send(struct lldpd *global OVS_UNUSED, + struct lldpd_hardware *hardware, + struct ofpbuf *p) +{ + struct lldpd_port *port; + struct lldpd_chassis *chassis; + struct lldpd_frame *frame; + uint8_t *packet, *pos, *tlv; + struct lldpd_mgmt *mgmt; + int length, proto; + const uint8_t avaya[] = LLDP_TLV_ORG_AVAYA; + struct lldpd_aa_isid_vlan_maps_tlv *vlan_isid_map; + uint8_t msg_auth_digest[LLDP_TLV_AA_ISID_VLAN_DIGEST_LENGTH]; + + port = &hardware->h_lport; + chassis = port->p_chassis; + + /* The ethernet header is filled in elsewhere, we must save room for it. */ + length = hardware->h_mtu - sizeof(struct eth_header); + packet = ofpbuf_l3(p); + VLOG_DBG("LLDP PDU send to %s mtu %d incoming with ptr=%p", + hardware->h_ifname, hardware->h_mtu, packet); + pos = packet; + + /* + * Make room in ofpbuf for chassis ID, Port ID, System Name, System Descr, + * System Cap + */ + pos = ofpbuf_put_uninit(p, sizeof chassis->c_id_subtype + + chassis->c_id_len + + sizeof port->p_id_subtype + + port->p_id_len + + sizeof chassis->c_ttl + + strlen(chassis->c_name) + + strlen(chassis->c_descr) + + sizeof chassis->c_cap_available + + sizeof chassis->c_cap_enabled + 12); + + /* Chassis ID */ + if (!(POKE_START_LLDP_TLV(LLDP_TLV_CHASSIS_ID) && + POKE_UINT8(chassis->c_id_subtype) && + POKE_BYTES(chassis->c_id, chassis->c_id_len) && + POKE_END_LLDP_TLV)) { + goto toobig; + } + + /* Port ID */ + if (!(POKE_START_LLDP_TLV(LLDP_TLV_PORT_ID) && + POKE_UINT8(port->p_id_subtype) && + POKE_BYTES(port->p_id, port->p_id_len) && + POKE_END_LLDP_TLV)) { + goto toobig; + } + + /* Time to live */ + if (!(POKE_START_LLDP_TLV(LLDP_TLV_TTL) && + POKE_UINT16(chassis->c_ttl) && + POKE_END_LLDP_TLV)) { + goto toobig; + } + + /* 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 (!(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; + } + + LIST_FOR_EACH (mgmt, m_entries, &chassis->c_mgmt.m_entries) { + /* + * Make room for 1 mgmt interface + */ + ofpbuf_put_uninit(p, 2 + sizeof(uint8_t) + + sizeof(uint8_t) + + mgmt->m_addrsize + + sizeof(uint8_t) + + sizeof(uint32_t) + + sizeof(uint8_t)); + + proto = lldpd_af_to_lldp_proto(mgmt->m_family); + 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') { + /* make room for port descr */ + ofpbuf_put_uninit(p, 2 + strlen(port->p_descr)); + + if (!(POKE_START_LLDP_TLV(LLDP_TLV_PORT_DESCR) && + POKE_BYTES(port->p_descr, strlen(port->p_descr)) && + POKE_END_LLDP_TLV)) { + goto toobig; + } + } + + /* Add Auto Attach tlvs to packet */ + /* AA-ELEMENT */ + if (port->p_element.type != 0) { + u_int8_t aa_element_first_byte; + u_int8_t aa_element_second_byte = 0; + u_int8_t aa_elem_sys_id_first_byte; + u_int8_t aa_elem_sys_id_second_byte; + + /* Element type should be first 4 most significant bits, so bitwise OR + * that with the first 4 bits of the 12-bit-wide mgmt_vlan + */ + aa_element_first_byte = ((port->p_element.type & 0xF) << 4) | + ((port->p_element.mgmt_vlan >> 8) & 0xF); + + /* Second byte should just be the remaining 8 bits of .mgmt_vlan */ + aa_element_second_byte = port->p_element.mgmt_vlan & 0x0FF; + + /* .conn_type should be 4 most sig. bits, so bitwise OR that + * with the first 4 bits of the 12-bit-wide .smlt_id + */ + aa_elem_sys_id_first_byte = + ((port->p_element.system_id.conn_type & 0xF) << 4) | + ((port->p_element.system_id.smlt_id >> 8) & 0xF); + + /* Second byte should just be the remaining 8 bits of .smlt_id */ + aa_elem_sys_id_second_byte = port->p_element.system_id.smlt_id & 0x0FF; + + /* make room for element type tlv */ + ofpbuf_put_uninit(p, 2 + sizeof avaya + + sizeof(uint8_t) + + sizeof aa_element_first_byte + + sizeof aa_element_second_byte + + sizeof port->p_element.system_id.system_mac + + sizeof aa_elem_sys_id_first_byte + + sizeof aa_elem_sys_id_second_byte + + sizeof port->p_element.system_id.mlt_id); + + if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) && + POKE_BYTES(avaya, sizeof avaya) && + POKE_UINT8(LLDP_TLV_AA_ELEMENT_SUBTYPE) && + POKE_UINT8(aa_element_first_byte) && + POKE_UINT8(aa_element_second_byte) && + POKE_BYTES(&port->p_element.system_id.system_mac, + sizeof port->p_element.system_id.system_mac) && + POKE_UINT8(aa_elem_sys_id_first_byte) && + POKE_UINT8(aa_elem_sys_id_second_byte) && + POKE_BYTES(&port->p_element.system_id.mlt_id, + sizeof port->p_element.system_id.mlt_id) && + POKE_END_LLDP_TLV)) { + goto toobig; + } + } + + if (!list_is_empty(&port->p_isid_vlan_maps.m_entries)) { + int j; + + /* + * make room for aa_isid_digest + */ + ofpbuf_put_uninit(p, 2 + sizeof avaya + + sizeof(uint8_t) + + sizeof msg_auth_digest); + + for (j = 0; j < LLDP_TLV_AA_ISID_VLAN_DIGEST_LENGTH; j++) { + msg_auth_digest[j] = 0; + } + + if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) && + POKE_BYTES(avaya, sizeof avaya) && + POKE_UINT8(LLDP_TLV_AA_ISID_VLAN_ASGNS_SUBTYPE) && + POKE_BYTES(msg_auth_digest, sizeof msg_auth_digest))) { + goto toobig; + } + + LIST_FOR_EACH (vlan_isid_map, + m_entries, + &hardware->h_lport.p_isid_vlan_maps.m_entries) { + u_int16_t status_vlan_word; + status_vlan_word = + (vlan_isid_map->isid_vlan_data.status << 12) | + vlan_isid_map->isid_vlan_data.vlan; + + /* + * Make room for one isid-vlan mapping + */ + ofpbuf_put_uninit(p, sizeof status_vlan_word + + sizeof vlan_isid_map->isid_vlan_data.isid); + + if (!(POKE_UINT16(status_vlan_word) && + POKE_BYTES(&vlan_isid_map->isid_vlan_data.isid, + sizeof vlan_isid_map->isid_vlan_data.isid))) { + goto toobig; + } + } + + if (!(POKE_END_LLDP_TLV)) { + goto toobig; + } + } + + /* Make room for the End TLV 0x0000 */ + ofpbuf_put_uninit(p, sizeof(uint16_t)); + + /* END */ + if (!(POKE_START_LLDP_TLV(LLDP_TLV_END) && + POKE_END_LLDP_TLV)) { + goto toobig; + } + + hardware->h_tx_cnt++; + + /* We assume that LLDP frame is the reference */ + if ((frame = malloc(sizeof(int) + pos - packet)) != NULL) { + frame->size = pos - packet; + length = frame->size; + 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); + } + } + + return length; + +toobig: + free(packet); + + return E2BIG; +} + +int +lldp_decode(struct lldpd *cfg OVS_UNUSED, char *frame, int s, + struct lldpd_hardware *hardware, struct lldpd_chassis **newchassis, + struct lldpd_port **newport) +{ + struct lldpd_chassis *chassis; + struct lldpd_port *port; + const char lldpaddr[] = LLDP_MULTICAST_ADDR; + const char dot1[] = LLDP_TLV_ORG_DOT1; + const char dot3[] = LLDP_TLV_ORG_DOT3; + const char med[] = LLDP_TLV_ORG_MED; + const char avaya_oid[] = LLDP_TLV_ORG_AVAYA; + const char dcbx[] = LLDP_TLV_ORG_DCBX; + char orgid[3]; + int length, gotend = 0, ttl_received = 0, af; + int tlv_size, tlv_type, tlv_subtype; + u_int8_t *pos, *tlv; + char *b; + struct lldpd_aa_isid_vlan_maps_tlv *isid_vlan_map = NULL; + u_int8_t msg_auth_digest[LLDP_TLV_AA_ISID_VLAN_DIGEST_LENGTH]; + struct lldpd_mgmt *mgmt; + u_int8_t addr_str_length, addr_str_buffer[32]; + u_int8_t addr_family, addr_length, *addr_ptr, iface_subtype; + u_int32_t iface_number, iface; + + VLOG_DBG("receive LLDP PDU on %s", hardware->h_ifname); + + if ((chassis = calloc(1, sizeof *chassis)) == NULL) { + VLOG_WARN("failed to allocate remote chassis"); + return -1; + } + list_init(&chassis->c_mgmt.m_entries); + + if ((port = calloc(1, sizeof *port)) == NULL) { + VLOG_WARN("failed to allocate remote port"); + free(chassis); + return -1; + } + list_init(&port->p_isid_vlan_maps.m_entries); + + length = s; + pos = (u_int8_t*) frame; + + if (length < 2 * ETH_ADDR_LEN + sizeof(u_int16_t)) { + VLOG_WARN("too short frame received on %s", hardware->h_ifname); + goto malformed; + } + if (PEEK_CMP(lldpaddr, ETH_ADDR_LEN) != 0) { + VLOG_INFO("frame not targeted at LLDP multicast address " + "received on %s", hardware->h_ifname); + goto malformed; + } + PEEK_DISCARD(ETH_ADDR_LEN); /* Skip source address */ + if (PEEK_UINT16 != ETHERTYPE_LLDP) { + VLOG_INFO("non LLDP frame received on %s", hardware->h_ifname); + goto malformed; + } + + while (length && (!gotend)) { + if (length < 2) { + VLOG_WARN("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) { + VLOG_WARN("frame too short for tlv received on %s", + hardware->h_ifname); + goto malformed; + } + + switch (tlv_type) { + case LLDP_TLV_END: + if (tlv_size != 0) { + VLOG_WARN("lldp end received with size not null on %s", + hardware->h_ifname); + goto malformed; + } + if (length) { + VLOG_DBG("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 Id"); + tlv_subtype = PEEK_UINT8; + if ((tlv_subtype == 0) || (tlv_subtype > 7)) { + VLOG_WARN("unknown subtype for tlv id received on %s", + hardware->h_ifname); + goto malformed; + } + if ((b = (char *) calloc(1, tlv_size - 1)) == NULL) { + VLOG_WARN("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) { + port->p_id_subtype = tlv_subtype; + port->p_id = b; + port->p_id_len = tlv_size - 1; + } else { + chassis->c_id_subtype = tlv_subtype; + chassis->c_id = b; + chassis->c_id_len = tlv_size - 1; + } + break; + + case LLDP_TLV_TTL: + CHECK_TLV_SIZE(2, "TTL"); + chassis->c_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) { + VLOG_DBG("empty tlv received on %s", hardware->h_ifname); + break; + } + if ((b = (char *) calloc(1, tlv_size + 1)) == NULL) { + VLOG_WARN("unable to allocate memory for string tlv " + "received on %s", + hardware->h_ifname); + goto malformed; + } + PEEK_BYTES(b, tlv_size); + if (tlv_type == LLDP_TLV_PORT_DESCR) { + port->p_descr = b; + } else if (tlv_type == LLDP_TLV_SYSTEM_NAME) { + chassis->c_name = b; + } else { + chassis->c_descr = b; + } + 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; + 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; + } + iface = iface_subtype == LLDP_MGMT_IFACE_IFINDEX ? + iface_number : 0; + mgmt = lldpd_alloc_mgmt(af, addr_ptr, addr_length, iface); + if (mgmt == NULL) { + VLOG_WARN("unable to allocate memory for management address"); + goto malformed; + } + list_push_back(&chassis->c_mgmt.m_entries, &mgmt->m_entries); + break; + + case LLDP_TLV_ORG: + CHECK_TLV_SIZE(4, "Organisational"); + PEEK_BYTES(orgid, sizeof orgid); + tlv_subtype = PEEK_UINT8; + if (memcmp(dot1, orgid, sizeof orgid) == 0) { + hardware->h_rx_unrecognized_cnt++; + } else if (memcmp(dot3, orgid, sizeof orgid) == 0) { + hardware->h_rx_unrecognized_cnt++; + } else if (memcmp(med, orgid, sizeof orgid) == 0) { + /* LLDP-MED */ + hardware->h_rx_unrecognized_cnt++; + } else if (memcmp(avaya_oid, orgid, sizeof orgid) == 0) { + u_int16_t aa_element_word; + u_int16_t aa_status_vlan_word; + u_int16_t aa_system_id_word; + unsigned short num_mappings; + + switch(tlv_subtype) { + case LLDP_TLV_AA_ELEMENT_SUBTYPE: + aa_element_word = PEEK_UINT16; + + /* Type is first 4 most-significant bits */ + port->p_element.type = aa_element_word >> 12; + + /* mgmt_vlan is last 12 bits */ + port->p_element.mgmt_vlan = aa_element_word & 0x0FFF; + VLOG_INFO("Element type: %X, Mgmt vlan: %X", + port->p_element.type, + port->p_element.mgmt_vlan); + PEEK_BYTES(&port->p_element.system_id.system_mac, + sizeof port->p_element.system_id.system_mac); + VLOG_INFO("System mac: 0x%.2X%.2X%.2X%.2X%.2X%.2X", + port->p_element.system_id.system_mac[0], + port->p_element.system_id.system_mac[1], + port->p_element.system_id.system_mac[2], + port->p_element.system_id.system_mac[3], + port->p_element.system_id.system_mac[4], + port->p_element.system_id.system_mac[5]); + aa_system_id_word = PEEK_UINT16; + port->p_element.system_id.conn_type = + aa_system_id_word >> 12; + port->p_element.system_id.smlt_id = + aa_system_id_word & 0x0FFF; + PEEK_BYTES(&port->p_element.system_id.mlt_id, + sizeof port->p_element.system_id.mlt_id); + break; + + case LLDP_TLV_AA_ISID_VLAN_ASGNS_SUBTYPE: + PEEK_BYTES(&msg_auth_digest, sizeof msg_auth_digest); + + /* Subtract off tlv type and length (2Bytes) + OUI (3B) + + * Subtype (1B) + MSG DIGEST (32B). + */ + num_mappings = tlv_size - 4 - + LLDP_TLV_AA_ISID_VLAN_DIGEST_LENGTH; + if ((num_mappings % 5) != 0) { + VLOG_INFO("malformed vlan-isid mappings tlv received"); + goto malformed; + } + + num_mappings /= 5; /* Each mapping is 5 Bytes */ + for(; num_mappings > 0; num_mappings--) { + isid_vlan_map = (struct lldpd_aa_isid_vlan_maps_tlv *) + calloc(1, sizeof *isid_vlan_map); + if (!isid_vlan_map) { + VLOG_WARN("unable to allocate memory " + "for aa_isid_vlan_maps_tlv struct"); + goto malformed; + } + aa_status_vlan_word = PEEK_UINT16; + + /* Status is first 4 most-significant bits. */ + isid_vlan_map->isid_vlan_data.status = + aa_status_vlan_word >> 12; + + /* Vlan is last 12 bits */ + isid_vlan_map->isid_vlan_data.vlan = + aa_status_vlan_word & 0x0FFF; + PEEK_BYTES(&isid_vlan_map->isid_vlan_data.isid, + sizeof isid_vlan_map->isid_vlan_data.isid); + list_push_back( + (struct ovs_list *) &port->p_isid_vlan_maps, + (struct ovs_list *) isid_vlan_map); + isid_vlan_map = NULL; + } + break; + + default: + hardware->h_rx_unrecognized_cnt++; + VLOG_INFO("Unrecogised tlv subtype received"); + break; + } + } else if (memcmp(dcbx, orgid, sizeof orgid) == 0) { + VLOG_DBG("unsupported DCBX tlv received on %s " + "- ignore", hardware->h_ifname); + hardware->h_rx_unrecognized_cnt++; + } else { + VLOG_INFO("unknown org tlv [%02x:%02x:%02x] received " + "on %s", orgid[0], orgid[1], orgid[2], + hardware->h_ifname); + hardware->h_rx_unrecognized_cnt++; + } + break; + default: + VLOG_WARN("unknown tlv (%d) received on %s", + tlv_type, + hardware->h_ifname); + goto malformed; + } + if (pos > tlv + tlv_size) { + VLOG_WARN("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)) { + VLOG_WARN("some mandatory 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; +} diff --git a/lib/lldp/lldpd-structs.c b/lib/lldp/lldpd-structs.c new file mode 100644 index 000000000..7a434ff25 --- /dev/null +++ b/lib/lldp/lldpd-structs.c @@ -0,0 +1,130 @@ +/* -*- 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 <config.h> +#include "lldpd-structs.h" +#include <stdlib.h> +#include <time.h> +#include <unistd.h> +#include "lldpd.h" + +VLOG_DEFINE_THIS_MODULE(lldpd_structs); + +void +lldpd_chassis_mgmt_cleanup(struct lldpd_chassis *chassis) +{ + struct lldpd_mgmt *mgmt, *mgmt_next; + + VLOG_DBG("cleanup management addresses for chassis %s", + chassis->c_name ? chassis->c_name : "(unknown)"); + + LIST_FOR_EACH_SAFE (mgmt, + mgmt_next, + m_entries, + &chassis->c_mgmt.m_entries) { + list_remove(&mgmt->m_entries); + free(mgmt); + } + + list_init(&chassis->c_mgmt.m_entries); +} + +void +lldpd_chassis_cleanup(struct lldpd_chassis *chassis, int all) +{ + lldpd_chassis_mgmt_cleanup(chassis); + VLOG_DBG("cleanup chassis %s", + chassis->c_name ? chassis->c_name : "(unkwnon)"); + free(chassis->c_id); + free(chassis->c_name); + free(chassis->c_descr); + if (all) { + free(chassis); + } +} + +/* Cleanup a remote port. The before last argument, `expire` is a function that + * should be called when a remote port is removed. If the last argument is 1, + * all remote ports are removed. + */ +void +lldpd_remote_cleanup(struct lldpd_hardware *hw, + void(*expire)(struct lldpd_hardware *, + struct lldpd_port *), + int all) +{ + struct lldpd_port *port, *port_next; + int del; + time_t now = time(NULL); + + VLOG_DBG("cleanup remote port on %s", hw->h_ifname); + LIST_FOR_EACH_SAFE (port, port_next, p_entries, &hw->h_rports.p_entries) { + del = all; + if (!all && expire && + (now >= port->p_lastupdate + port->p_chassis->c_ttl)) { + hw->h_ageout_cnt++; + hw->h_delete_cnt++; + del = 1; + } + if (del) { + if (expire) { + expire(hw, port); + } + + if (!all) { + list_remove(&port->p_entries); + } + lldpd_port_cleanup(port, 1); + free(port); + } + } + if (all) { + list_init(&hw->h_rports.p_entries); + } +} + +/* If `all' is true, clear all information, including information that + are not refreshed periodically. Port should be freed manually. */ +void +lldpd_port_cleanup(struct lldpd_port *port, int all) +{ + /* We set these to NULL so we don't free wrong memory */ + + free(port->p_id); + port->p_id = NULL; + free(port->p_descr); + port->p_descr = NULL; + if (all) { + free(port->p_lastframe); + /* Chassis may not have been attributed, yet.*/ + if (port->p_chassis) { + port->p_chassis->c_refcount--; + port->p_chassis = NULL; + } + } +} + +void +lldpd_config_cleanup(struct lldpd_config *config) +{ + VLOG_DBG("general configuration cleanup"); + free(config->c_mgmt_pattern); + free(config->c_cid_pattern); + free(config->c_iface_pattern); + free(config->c_platform); + free(config->c_description); +} diff --git a/lib/lldp/lldpd-structs.h b/lib/lldp/lldpd-structs.h new file mode 100644 index 000000000..98ebd5217 --- /dev/null +++ b/lib/lldp/lldpd-structs.h @@ -0,0 +1,228 @@ +/* -*- 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 _LLDPD_STRUCTS_H +#define _LLDPD_STRUCTS_H + +#include <net/if.h> +#ifndef _WIN32 +#include <netinet/in.h> +#include <netinet/if_ether.h> +#endif +#include <sys/socket.h> +#include <sys/types.h> +#include "aa-structs.h" +#include "lldp-const.h" +#include "packets.h" + +enum { + LLDPD_AF_UNSPEC = 0, + LLDPD_AF_IPV4, + LLDPD_AF_IPV6, + LLDPD_AF_LAST +}; + +inline static int +lldpd_af(int af) +{ + switch (af) { + case LLDPD_AF_IPV4: return AF_INET; + case LLDPD_AF_IPV6: return AF_INET6; + case LLDPD_AF_LAST: return AF_MAX; + default: return AF_UNSPEC; + } +} + +#define LLDPD_MGMT_MAXADDRSIZE 16 /* sizeof(struct in6_addr) */ +struct lldpd_mgmt { + struct ovs_list m_entries; + int m_family; + union { + struct in_addr inet; + struct in6_addr inet6; + u_int8_t octets[LLDPD_MGMT_MAXADDRSIZE]; + } m_addr; + size_t m_addrsize; + u_int32_t m_iface; +}; + +struct lldpd_chassis { + struct ovs_list list; + u_int16_t c_refcount; /* Reference count by ports */ + u_int16_t c_index; /* Monotonic index */ + u_int8_t c_protocol; /* Protocol used to get this chassis */ + u_int8_t c_id_subtype; + char *c_id; + int c_id_len; + char *c_name; + char *c_descr; + + u_int16_t c_cap_available; + u_int16_t c_cap_enabled; + + u_int16_t c_ttl; + + struct lldpd_mgmt c_mgmt; +}; +/* WARNING: any change to this structure should also be reflected into + `lldpd_copy_chassis()` which is not using marshaling. */ + +struct lldpd_port { + struct ovs_list p_entries; + struct lldpd_chassis *p_chassis; /* Attached chassis */ + time_t p_lastchange; /* Time of last change of values */ + time_t p_lastupdate; /* Time of last update received */ + struct lldpd_frame *p_lastframe; /* Frame received during last update */ + u_int8_t p_protocol; /* Protocol used to get this port */ + u_int8_t p_hidden_in:1; /* Considered hidden for reception */ + u_int8_t p_hidden_out:2; /* Considered hidden for emission */ + /* Important: all fields that should be ignored to check if a port has + * been changed should be before p_id_subtype. Check + * `lldpd_reset_timer()`. + */ + u_int8_t p_id_subtype; + char *p_id; + int p_id_len; + char *p_descr; + u_int16_t p_mfs; + struct lldpd_aa_element_tlv p_element; + struct lldpd_aa_isid_vlan_maps_tlv p_isid_vlan_maps; +}; + +/* Used to modify some port related settings */ +struct lldpd_port_set { + char *ifname; +}; + +/* Smart mode / Hide mode */ +#define SMART_INCOMING_FILTER (1<<0) /* Incoming filtering enabled */ +#define SMART_INCOMING_ONE_PROTO (1<<1) /* On reception, keep only 1 proto */ +#define SMART_INCOMING_ONE_NEIGH (1<<2) /* On recep., keep only 1 neighbor */ +#define SMART_OUTGOING_FILTER (1<<3) /* Outgoing filtering enabled */ +#define SMART_OUTGOING_ONE_PROTO (1<<4) /* On emission, keep only one proto */ +#define SMART_OUTGOING_ONE_NEIGH (1<<5) /* On emission, consider only + one neighbor */ +#define SMART_INCOMING (SMART_INCOMING_FILTER | \ + SMART_INCOMING_ONE_PROTO | \ + SMART_INCOMING_ONE_NEIGH) +#define SMART_OUTGOING (SMART_OUTGOING_FILTER | \ + SMART_OUTGOING_ONE_PROTO | \ + SMART_OUTGOING_ONE_NEIGH) + +struct lldpd_config { + int c_paused; /* lldpd is paused */ + int c_tx_interval; /* Transmit interval */ + int c_smart; /* Bitmask for smart configuration (see SMART_*) */ + int c_receiveonly; /* Receive only mode */ + int c_max_neighbors; /* Maximum number of neighbors (per protocol) */ + + char *c_mgmt_pattern; /* Pattern to match a management address */ + char *c_cid_pattern; /* Pattern to match interfaces to use for chassis + * ID */ + char *c_iface_pattern; /* Pattern to match interfaces to use */ + + char *c_platform; /* Override platform description (for CDP) */ + char *c_description; /* Override chassis description */ + char *c_hostname; /* Override system name */ + int c_advertise_version; /* Should the precise version be advertised? */ + int c_set_ifdescr; /* Set interface description */ + int c_promisc; /* Interfaces should be in promiscuous mode */ + int c_tx_hold; /* Transmit hold */ + int c_bond_slave_src_mac_type; /* Src mac type in lldp frames over bond + * slaves */ + int c_lldp_portid_type; /* The PortID type */ +}; + +struct lldpd_frame { + int size; + unsigned char frame[1]; +}; + +struct lldpd_hardware; +struct lldpd; +struct lldpd_ops { + int(*send)(struct lldpd *, + struct lldpd_hardware*, + char *, size_t); /* Function to send a frame */ + int(*recv)(struct lldpd *, + struct lldpd_hardware*, + int, char *, size_t); /* Function to receive a frame */ + int(*cleanup)(struct lldpd *, struct lldpd_hardware *); /* Cleanup */ +}; + +/* An interface is uniquely identified by h_ifindex, h_ifname and h_ops. This + * means if an interface becomes enslaved, it will be considered as a new + * interface. The same applies for renaming and we include the index in case of + * renaming to an existing interface. + */ +struct lldpd_hardware { + struct ovs_list h_entries; + + struct lldpd *h_cfg; /* Pointer to main configuration */ + void *h_recv; /* FD for reception */ + int h_sendfd; /* FD for sending, only used by h_ops */ + int h_mangle; /* 1 if we have to mangle the MAC address */ + struct lldpd_ops *h_ops; /* Hardware-dependent functions */ + void *h_data; /* Hardware-dependent data */ + void *h_timer; /* Timer for this port */ + + int h_mtu; + int h_flags; /* Packets will be sent only + * if IFF_RUNNING. Will be + * removed if this is left + * to 0. */ + int h_ifindex; /* Interface index, used by SNMP */ + char h_ifname[IFNAMSIZ]; /* Should be unique */ + u_int8_t h_lladdr[ETH_ADDR_LEN]; + + u_int64_t h_tx_cnt; + u_int64_t h_rx_cnt; + u_int64_t h_rx_discarded_cnt; + u_int64_t h_rx_unrecognized_cnt; + u_int64_t h_ageout_cnt; + u_int64_t h_insert_cnt; + u_int64_t h_delete_cnt; + u_int64_t h_drop_cnt; + + u_int16_t h_lport_cksum; /* Checksum on local port to see if there + * is a change + */ + struct lldpd_port h_lport; /* Port attached to this hardware port */ + struct lldpd_port h_rports; /* Remote ports */ +}; + +struct lldpd_interface; +struct lldpd_interface_list; + +struct lldpd_neighbor_change { + char *ifname; +#define NEIGHBOR_CHANGE_DELETED -1 +#define NEIGHBOR_CHANGE_ADDED 1 +#define NEIGHBOR_CHANGE_UPDATED 0 + int state; + struct lldpd_port *neighbor; +}; + +/* Cleanup functions */ +void lldpd_chassis_mgmt_cleanup(struct lldpd_chassis *); +void lldpd_chassis_cleanup(struct lldpd_chassis *, int); +void lldpd_remote_cleanup(struct lldpd_hardware *, + void (*expire)(struct lldpd_hardware *, struct lldpd_port *), int); +void lldpd_port_cleanup(struct lldpd_port *, int); +void lldpd_config_cleanup(struct lldpd_config *); + +#endif diff --git a/lib/lldp/lldpd.c b/lib/lldp/lldpd.c new file mode 100644 index 000000000..9c8173e98 --- /dev/null +++ b/lib/lldp/lldpd.c @@ -0,0 +1,655 @@ +/* -*- 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 <config.h> +#include "lldpd.h" +#include <arpa/inet.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <signal.h> +#include <stdio.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> +#ifndef _WIN32 +#include <grp.h> +#include <libgen.h> +#include <netinet/if_ether.h> +#include <pwd.h> +#include <sys/select.h> +#include <sys/utsname.h> +#endif +#include "compiler.h" +#include "list.h" +#include "packets.h" + +VLOG_DEFINE_THIS_MODULE(lldpd); + +static struct protocol protos[] = +{ + { LLDPD_MODE_LLDP, 1, "LLDP", 'l', lldp_send, lldp_decode, NULL, + LLDP_MULTICAST_ADDR }, + { 0, 0, "any", ' ', NULL, NULL, NULL, + { 0,0,0,0,0,0 } } +}; + +void lldpd_assign_cfg_to_protocols(struct lldpd *cfg) +{ + cfg->g_protocols = protos; +} + +struct lldpd_hardware * +lldpd_get_hardware(struct lldpd *cfg, char *name, int index, + struct lldpd_ops *ops) +{ + struct lldpd_hardware *hw; + + LIST_FOR_EACH (hw, h_entries, &cfg->g_hardware.h_entries) { + if ((strcmp(hw->h_ifname, name) == 0) && + (hw->h_ifindex == index) && + ((!ops) || (ops == hw->h_ops))) { + return hw; + } + } + + return NULL; +} + +struct lldpd_hardware * +lldpd_alloc_hardware(struct lldpd *cfg, char *name, int index) +{ + struct lldpd_hardware *hw; + + VLOG_DBG("allocate a new local hardware interface (%s)", name); + + if ((hw = (struct lldpd_hardware *) calloc(1, sizeof *hw)) == NULL) { + return NULL; + } + + hw->h_cfg = cfg; + ovs_strlcpy(hw->h_ifname, name, sizeof hw->h_ifname); + hw->h_ifindex = index; + hw->h_lport.p_chassis = (struct lldpd_chassis *) + list_front(&cfg->g_chassis.list); + hw->h_lport.p_chassis->c_refcount++; + list_init(&hw->h_rports.p_entries); + + return hw; +} + +struct lldpd_mgmt * +lldpd_alloc_mgmt(int family, void *addrptr, size_t addrsize, u_int32_t iface) +{ + struct lldpd_mgmt *mgmt; + + VLOG_DBG("allocate a new management address (family: %d)", family); + + if (family <= LLDPD_AF_UNSPEC || family >= LLDPD_AF_LAST) { + errno = EAFNOSUPPORT; + return NULL; + } + if (addrsize > LLDPD_MGMT_MAXADDRSIZE) { + errno = EOVERFLOW; + return NULL; + } + mgmt = calloc(1, sizeof *mgmt); + if (mgmt == NULL) { + errno = ENOMEM; + return NULL; + } + mgmt->m_family = family; + memcpy(&mgmt->m_addr, addrptr, addrsize); + mgmt->m_addrsize = addrsize; + mgmt->m_iface = iface; + + return mgmt; +} + +void +lldpd_hardware_cleanup(struct lldpd *cfg, struct lldpd_hardware *hardware) +{ + VLOG_DBG("cleanup hardware port %s", hardware->h_ifname); + + lldpd_port_cleanup(&hardware->h_lport, 1); + if (hardware->h_ops && hardware->h_ops->cleanup) { + hardware->h_ops->cleanup(cfg, hardware); + } + free(hardware); +} + +void +lldpd_cleanup(struct lldpd *cfg) +{ + struct lldpd_hardware *hw, *hw_next; + struct lldpd_chassis *chassis, *chassis_next; + + VLOG_DBG("cleanup all ports"); + + LIST_FOR_EACH_SAFE (hw, hw_next, h_entries, &cfg->g_hardware.h_entries) { + if (!hw->h_flags) { + list_remove(&hw->h_entries); + lldpd_remote_cleanup(hw, NULL, 1); + lldpd_hardware_cleanup(cfg, hw); + } else { + lldpd_remote_cleanup(hw, NULL, 0); + } + } + + VLOG_DBG("cleanup all chassis"); + + LIST_FOR_EACH_SAFE (chassis, chassis_next, list, &cfg->g_chassis.list) { + if (chassis->c_refcount == 0) { + list_remove(&chassis->list); + lldpd_chassis_cleanup(chassis, 1); + } + } +} + +/* Update chassis `ochassis' with values from `chassis'. The later one is not + * expected to be part of a list! It will also be wiped from memory. + */ +static void +lldpd_move_chassis(struct lldpd_chassis *ochassis, + struct lldpd_chassis *chassis) +{ + struct lldpd_mgmt *mgmt, *mgmt_next; + int refcount = ochassis->c_refcount; + int index = ochassis->c_index; + struct ovs_list listcopy; + + /* We want to keep refcount, index and list stuff from the current chassis + */ + memcpy(&listcopy, &ochassis->list, sizeof listcopy); + lldpd_chassis_cleanup(ochassis, 0); + + /* Make the copy. */ + /* WARNING: this is a kludgy hack, we need in-place copy and cannot use + * marshaling. + */ + memcpy(ochassis, chassis, sizeof *ochassis); + list_init(&ochassis->c_mgmt.m_entries); + + /* Copy of management addresses */ + LIST_FOR_EACH_SAFE (mgmt, + mgmt_next, + m_entries, + &chassis->c_mgmt.m_entries) { + list_remove(&mgmt->m_entries); + list_insert(&ochassis->c_mgmt.m_entries, &mgmt->m_entries); + } + + /* Restore saved values */ + ochassis->c_refcount = refcount; + ochassis->c_index = index; + memcpy(&ochassis->list, &listcopy, sizeof ochassis->list); + + /* Get rid of the new chassis */ + free(chassis); +} + +static int +lldpd_guess_type(struct lldpd *cfg, char *frame, int s) +{ + int i; + + if (s < ETH_ADDR_LEN) { + return -1; + } + + for (i = 0; cfg->g_protocols[i].mode != 0; i++) { + if (!cfg->g_protocols[i].enabled) { + continue; + } + if (cfg->g_protocols[i].guess == NULL) { + if (memcmp(frame, cfg->g_protocols[i].mac, ETH_ADDR_LEN) == 0) { + VLOG_DBG("guessed protocol is %s (from MAC address)", + cfg->g_protocols[i].name); + return cfg->g_protocols[i].mode; + } + } else { + if (cfg->g_protocols[i].guess(frame, s)) { + VLOG_DBG("guessed protocol is %s (from detector function)", + cfg->g_protocols[i].name); + return cfg->g_protocols[i].mode; + } + } + } + + return -1; +} + +static void +lldpd_decode(struct lldpd *cfg, char *frame, int s, + struct lldpd_hardware *hw) +{ + size_t listsize, i; + struct lldpd_chassis *chassis, *ochassis = NULL; + struct lldpd_port *port, *oport; + int guess = LLDPD_MODE_LLDP; + struct eth_header eheader; + int count = 0; + int found = 0; + + VLOG_DBG("decode a received frame on %s size %d", hw->h_ifname,s); + + if (s < sizeof(struct eth_header) + 4) { + /* Too short, just discard it */ + return; + } + + /* Decapsulate VLAN frames */ + memcpy(&eheader, frame, sizeof eheader); + if (eheader.eth_type == htons(ETH_TYPE_VLAN)) { + /* VLAN decapsulation means to shift 4 bytes left the frame from + * offset 2 * ETH_ADDR_LEN + */ + memmove(frame + 2 * ETH_ADDR_LEN, frame + 2 * ETH_ADDR_LEN + 4, + s - 2 * ETH_ADDR_LEN); + s -= 4; + } + + LIST_FOR_EACH (oport, p_entries, &hw->h_rports.p_entries) { + if ((oport->p_lastframe != NULL) && + (oport->p_lastframe->size == s) && + (memcmp(oport->p_lastframe->frame, frame, s) == 0)) { + /* Already received the same frame */ + VLOG_DBG("duplicate frame, no need to decode"); + oport->p_lastupdate = time(NULL); + return; + } + } + + guess = lldpd_guess_type(cfg, frame, s); + VLOG_DBG("guessed %d enabled:%d", guess, cfg->g_protocols[0].enabled); + + for (i = 0; cfg->g_protocols[i].mode != 0; i++) { + if (!cfg->g_protocols[i].enabled) { + continue; + } + if (cfg->g_protocols[i].mode == guess) { + VLOG_DBG("using decode function for %s protocol", + cfg->g_protocols[i].name); + if (cfg->g_protocols[i].decode(cfg, frame, s, hw, &chassis, &port) + == -1) { + VLOG_DBG("function for %s protocol did not " + "decode this frame", + cfg->g_protocols[i].name); + return; + } + chassis->c_protocol = port->p_protocol = cfg->g_protocols[i].mode; + break; + } + VLOG_DBG(" %"PRIuSIZE "mode:%d enabled:%d", + i, cfg->g_protocols[i].mode, cfg->g_protocols[i].enabled); + } + if (cfg->g_protocols[i].mode == 0) { + VLOG_DBG("unable to guess frame type on %s", hw->h_ifname); + return; + } + + /* Do we already have the same MSAP somewhere? */ + VLOG_DBG("search for the same MSAP"); + + LIST_FOR_EACH (oport, p_entries, &hw->h_rports.p_entries) { + if (port->p_protocol == oport->p_protocol) { + count++; + if ((port->p_id_subtype == oport->p_id_subtype) && + (port->p_id_len == oport->p_id_len) && + (memcmp(port->p_id, oport->p_id, port->p_id_len) == 0) && + (chassis->c_id_subtype == oport->p_chassis->c_id_subtype) && + (chassis->c_id_len == oport->p_chassis->c_id_len) && + (memcmp(chassis->c_id, oport->p_chassis->c_id, + chassis->c_id_len) == 0)) { + ochassis = oport->p_chassis; + VLOG_DBG("MSAP is already known"); + found = 1; + break; + } + } + } + + if (!found) { + oport = NULL; + } + + /* Do we have room for a new MSAP? */ + if (!oport && cfg->g_config.c_max_neighbors) { + if (count == (cfg->g_config.c_max_neighbors - 1)) { + VLOG_DBG("max neighbors %d reached for port %s, " + "dropping any new ones silently", + cfg->g_config.c_max_neighbors, + hw->h_ifname); + } else if (count > cfg->g_config.c_max_neighbors - 1) { + VLOG_DBG("too many neighbors for port %s, drop this new one", + hw->h_ifname); + lldpd_port_cleanup(port, 1); + lldpd_chassis_cleanup(chassis, 1); + free(port); + return; + } + } + + /* No, but do we already know the system? */ + if (!oport) { + int found = 0; + VLOG_DBG("MSAP is unknown, search for the chassis"); + + LIST_FOR_EACH (ochassis, list, &cfg->g_chassis.list) { + if ((chassis->c_protocol == ochassis->c_protocol) && + (chassis->c_id_subtype == ochassis->c_id_subtype) && + (chassis->c_id_len == ochassis->c_id_len) && + (memcmp(chassis->c_id, ochassis->c_id, + chassis->c_id_len) == 0)) { + found=1; + break; + } + } + + if (!found) { + ochassis = NULL; + } + } + + if (oport) { + /* The port is known, remove it before adding it back */ + list_remove(&oport->p_entries); + lldpd_port_cleanup(oport, 1); + free(oport); + } + + if (ochassis) { + lldpd_move_chassis(ochassis, chassis); + chassis = ochassis; + } else { + /* Chassis not known, add it */ + VLOG_DBG("unknown chassis, add it to the list"); + chassis->c_index = ++cfg->g_lastrid; + chassis->c_refcount = 0; + list_push_back(&cfg->g_chassis.list, &chassis->list); + listsize = list_size(&cfg->g_chassis.list); + VLOG_DBG("%"PRIuSIZE " different systems are known", listsize); + } + + /* Add port */ + port->p_lastchange = port->p_lastupdate = time(NULL); + if ((port->p_lastframe = malloc(s + sizeof(struct lldpd_frame))) != NULL) { + port->p_lastframe->size = s; + memcpy(port->p_lastframe->frame, frame, s); + } + list_insert(&hw->h_rports.p_entries, &port->p_entries); + + port->p_chassis = chassis; + port->p_chassis->c_refcount++; + /* Several cases are possible : + * 1. chassis is new, its refcount was 0. It is now attached + * to this port, its refcount is 1. + * 2. chassis already exists and was attached to another + * port, we increase its refcount accordingly. + * 3. chassis already exists and was attached to the same + * port, its refcount was decreased with + * lldpd_port_cleanup() and is now increased again. + * + * In all cases, if the port already existed, it has been + * freed with lldpd_port_cleanup() and therefore, the refcount + * of the chassis that was attached to it is decreased. + */ + /* coverity[use_after_free] TAILQ_REMOVE does the right thing */ + i = list_size((struct ovs_list *) &hw->h_rports); + VLOG_DBG("%"PRIuSIZE " neighbors for %s", i, hw->h_ifname); + + if (!oport) { + hw->h_insert_cnt++; + } + + return; +} + +static void +lldpd_hide_ports(struct lldpd *cfg, + struct lldpd_hardware *hw, + int mask) { + struct lldpd_port *port; + int protocols[LLDPD_MODE_MAX + 1]; + char buffer[256]; + int i, j, k, found = 0; + unsigned int min; + + VLOG_DBG("apply smart filter for port %s", hw->h_ifname); + + /* Compute the number of occurrences of each protocol */ + for (i = 0; i <= LLDPD_MODE_MAX; i++) { + protocols[i] = 0; + } + + LIST_FOR_EACH (port, p_entries, &hw->h_rports.p_entries) { + protocols[port->p_protocol]++; + } + + /* Turn the protocols[] array into an array of + * enabled/disabled protocols. 1 means enabled, 0 + * means disabled. + */ + min = (unsigned int) - 1; + for (i = 0; i <= LLDPD_MODE_MAX; i++) { + if (protocols[i] && (protocols[i] < min)) { + min = protocols[i]; + } + } + for (i = 0; i <= LLDPD_MODE_MAX; i++) { + if ((protocols[i] == min) && !found) { + /* If we need a tie breaker, we take the first protocol only */ + if (cfg->g_config.c_smart & mask & + (SMART_OUTGOING_ONE_PROTO | SMART_INCOMING_ONE_PROTO)) { + found = 1; + } + protocols[i] = 1; + } else { + protocols[i] = 0; + } + } + + /* We set the p_hidden flag to 1 if the protocol is disabled */ + LIST_FOR_EACH (port, p_entries, &hw->h_rports.p_entries) { + if (mask == SMART_OUTGOING) { + port->p_hidden_out = protocols[port->p_protocol] ? 0 : 1; + } else { + port->p_hidden_in = protocols[port->p_protocol] ? 0 : 1; + } + } + + /* If we want only one neighbor, we take the first one */ + if (cfg->g_config.c_smart & mask & + (SMART_OUTGOING_ONE_NEIGH | SMART_INCOMING_ONE_NEIGH)) { + found = 0; + + LIST_FOR_EACH (port, p_entries, &hw->h_rports.p_entries) { + if (mask == SMART_OUTGOING) { + if (found) { + port->p_hidden_out = 1; + } + if (!port->p_hidden_out) { + found = 1; + } + } + if (mask == SMART_INCOMING) { + if (found) { + port->p_hidden_in = 1; + } + if (!port->p_hidden_in) { + found = 1; + } + } + } + } + + /* Print a debug message summarizing the operation */ + for (i = 0; i <= LLDPD_MODE_MAX; i++) { + protocols[i] = 0; + } + + k = j = 0; + LIST_FOR_EACH (port, p_entries, &hw->h_rports.p_entries) { + if (!(((mask == SMART_OUTGOING) && port->p_hidden_out) || + ((mask == SMART_INCOMING) && port->p_hidden_in))) { + k++; + protocols[port->p_protocol] = 1; + } + j++; + } + + buffer[0] = '\0'; + for (i = 0; cfg->g_protocols[i].mode != 0; i++) { + if (cfg->g_protocols[i].enabled && + protocols[cfg->g_protocols[i].mode]) { + if (strlen(buffer) + + strlen(cfg->g_protocols[i].name) + 3 > sizeof(buffer)) { + /* Unlikely, our buffer is too small */ + memcpy(buffer + sizeof(buffer) - 4, "...", 4); + break; + } + if (buffer[0]) { + strncat(buffer, ", ", 2); + strncat(buffer, cfg->g_protocols[i].name, + strlen(cfg->g_protocols[i].name)); + } + } + } + VLOG_DBG("%s: %s: %d visible neighbors (out of %d)", + hw->h_ifname, + (mask == SMART_OUTGOING) ? "out filter" : "in filter", + k, j); + VLOG_DBG("%s: protocols: %s", + hw->h_ifname, buffer[0] ? buffer : "(none)"); +} + +/* Hide unwanted ports depending on smart mode set by the user */ +static void +lldpd_hide_all(struct lldpd *cfg) +{ + struct lldpd_hardware *hw; + + if (!cfg->g_config.c_smart) { + return; + } + + VLOG_DBG("apply smart filter results on all ports"); + + LIST_FOR_EACH (hw, h_entries, &cfg->g_hardware.h_entries) { + if (cfg->g_config.c_smart & SMART_INCOMING_FILTER) { + lldpd_hide_ports(cfg, hw, SMART_INCOMING); + } + if (cfg->g_config.c_smart & SMART_OUTGOING_FILTER) { + lldpd_hide_ports(cfg, hw, SMART_OUTGOING); + } + } +} + +void +lldpd_recv(struct lldpd *cfg, + struct lldpd_hardware *hw, + char *buffer, + size_t bufSize) +{ + int n = bufSize; + + VLOG_DBG("receive a frame on %s", hw->h_ifname); + if (cfg->g_config.c_paused) { + VLOG_DBG("paused, ignore the frame on %s", hw->h_ifname); + return; + } + hw->h_rx_cnt++; + VLOG_DBG("decode received frame on %s h_rx_cnt=%" PRIu64, + hw->h_ifname, hw->h_rx_cnt); + lldpd_decode(cfg, buffer, n, hw); + lldpd_hide_all(cfg); /* Immediatly hide */ +} + +uint32_t +lldpd_send(struct lldpd_hardware *hw, struct ofpbuf *p) +{ + struct lldpd *cfg = hw->h_cfg; + struct lldpd_port *port; + int i, sent = 0; + int lldp_size = 0; + + if (cfg->g_config.c_receiveonly || cfg->g_config.c_paused) { + return 0; + } +#ifndef _WIN32 + if ((hw->h_flags & IFF_RUNNING) == 0) { + return 0; + } +#endif + + for (i = 0; cfg->g_protocols[i].mode != 0; i++) { + if (!cfg->g_protocols[i].enabled) { + continue; + } + + /* We send only if we have at least one remote system + * speaking this protocol or if the protocol is forced */ + if (cfg->g_protocols[i].enabled > 1) { + if ((lldp_size = cfg->g_protocols[i].send(cfg, hw, p)) != E2BIG) { + sent++; + continue; + } else { + VLOG_DBG("send PDU on %s failed E2BIG", hw->h_ifname); + continue; + } + } + + LIST_FOR_EACH (port, p_entries, &hw->h_rports.p_entries) { + /* If this remote port is disabled, we don't consider it */ + if (port->p_hidden_out) { + continue; + } + if (port->p_protocol == cfg->g_protocols[i].mode) { + VLOG_DBG("send PDU on %s with protocol %s", + hw->h_ifname, cfg->g_protocols[i].name); + lldp_size = cfg->g_protocols[i].send(cfg, hw, p); + sent++; + break; + } + } + } + + if (!sent) { + /* Nothing was sent for this port, let's speak the first + * available protocol. + */ + for (i = 0; cfg->g_protocols[i].mode != 0; i++) { + if (!cfg->g_protocols[i].enabled) { + continue; + } + VLOG_DBG("fallback to protocol %s for %s", + cfg->g_protocols[i].name, hw->h_ifname); + lldp_size = cfg->g_protocols[i].send(cfg, hw, p); + break; + } + if (cfg->g_protocols[i].mode == 0) { + VLOG_WARN("no protocol enabled, dunno what to send"); + } + } + + return lldp_size; +} diff --git a/lib/lldp/lldpd.h b/lib/lldp/lldpd.h new file mode 100644 index 000000000..f142180bb --- /dev/null +++ b/lib/lldp/lldpd.h @@ -0,0 +1,120 @@ +/* -*- 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 _LLDPD_H +#define _LLDPD_H + +#ifndef _WIN32 +#include <netinet/if_ether.h> +#include <netinet/in.h> +#endif +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <sys/types.h> +#include <sys/un.h> +#include "list.h" +#include "lldpd-structs.h" +#include "lldp-tlv.h" +#include "packets.h" +#include "openvswitch/vlog.h" +#include "ofpbuf.h" + +#define SYSCONFDIR "" +#define LLDPD_CTL_SOCKET "" +#define LLDPCLI_PATH "" +#define PRIVSEP_USER "" +#define PRIVSEP_GROUP "" +#define PRIVSEP_CHROOT "" + +#define ETHERTYPE_LLDP 0x88cc + +struct event; +struct event_base; + +#define LLDPD_TX_INTERVAL 5 +#define LLDPD_TX_HOLD 4 +#define LLDPD_TTL LLDPD_TX_INTERVAL * LLDPD_TX_HOLD +#define LLDPD_TX_MSGDELAY 1 +#define LLDPD_MAX_NEIGHBORS 4 +#define LLDPD_FAST_TX_INTERVAL 1 +#define LLDPD_FAST_INIT 4 + +#define USING_AGENTX_SUBAGENT_MODULE 1 + +#define PROTO_SEND_SIG struct lldpd *, struct lldpd_hardware *,struct ofpbuf * +#define PROTO_DECODE_SIG struct lldpd *, char *, int, struct lldpd_hardware *,\ + struct lldpd_chassis **, struct lldpd_port ** +#define PROTO_GUESS_SIG char *, int + +#define ALIGNED_CAST(TYPE, ATTR) ((TYPE) (void *) (ATTR)) + +struct protocol { + int mode; /* > 0 mode identifier (unique per protocol) */ + int enabled; /* Is this protocol enabled? */ + char *name; /* Name of protocol */ + char arg; /* Argument to enable this protocol */ + int(*send)(PROTO_SEND_SIG); /* How to send a frame */ + int(*decode)(PROTO_DECODE_SIG); /* How to decode a frame */ + int(*guess)(PROTO_GUESS_SIG); /* Can be NULL, use MAC address in this + * case + */ + u_int8_t mac[ETH_ADDR_LEN]; /* Destination MAC address used by this + * protocol + */ +}; + +#define SMART_HIDDEN(port) (port->p_hidden_in) + +struct lldpd { + int g_sock; + struct lldpd_config g_config; + struct protocol *g_protocols; + int g_lastrid; + + /* Unix socket handling */ + const char *g_ctlname; + int g_ctl; + + char *g_lsb_release; + + struct lldpd_chassis g_chassis; + struct lldpd_hardware g_hardware; +}; + +/* lldpd.c */ +struct lldpd_hardware *lldpd_get_hardware(struct lldpd *, + char *, int, struct lldpd_ops *); +struct lldpd_hardware *lldpd_alloc_hardware(struct lldpd *, char *, int); +void lldpd_hardware_cleanup(struct lldpd*, struct lldpd_hardware *); +struct lldpd_mgmt *lldpd_alloc_mgmt(int family, void *addr, size_t addrsize, + u_int32_t iface); +void lldpd_recv(struct lldpd *, struct lldpd_hardware *, char *, size_t); +uint32_t lldpd_send(struct lldpd_hardware *, struct ofpbuf *); +void lldpd_loop(struct lldpd *); + +int lldpd_main(int, char **); +void lldpd_update_localports(struct lldpd *); +void lldpd_cleanup(struct lldpd *); + +void lldpd_assign_cfg_to_protocols(struct lldpd *); + +/* lldp.c */ +int lldp_send(PROTO_SEND_SIG); +int lldp_decode(PROTO_DECODE_SIG); + +#endif /* _LLDPD_H */ diff --git a/lib/ovs-lldp.c b/lib/ovs-lldp.c new file mode 100644 index 000000000..5fbde7e41 --- /dev/null +++ b/lib/ovs-lldp.c @@ -0,0 +1,1041 @@ +/* + * Copyright (c) 2014 WindRiver, Inc. + * Copyright (c) 2015 Avaya, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Implementation of Auto Attach. + * Based on sample implementation in 802.1ab. Above copyright and license + * applies to all modifications. + * Limitations: + * - No support for multiple bridge. + * - Auto Attach state machine not implemented. + * - Auto Attach and LLDP code are bundled together. The plan is to decoupled + * them. + */ + +#include <config.h> +#include "ovs-lldp.h" +#include <arpa/inet.h> +#include <inttypes.h> +#include <netinet/in.h> +#include <stdbool.h> +#include <stdlib.h> +#include <sys/types.h> +#include "dynamic-string.h" +#include "flow.h" +#include "list.h" +#include "lldp/lldpd.h" +#include "lldp/lldpd-structs.h" +#include "netdev.h" +#include "ofpbuf.h" +#include "openvswitch/types.h" +#include "packets.h" +#include "poll-loop.h" +#include "smap.h" +#include "unixctl.h" +#include "util.h" +#include "openvswitch/vlog.h" + +VLOG_DEFINE_THIS_MODULE(ovs_lldp); + +#define LLDP_PROTOCOL_ID 0x0000 +#define LLDP_PROTOCOL_VERSION 0x00 +#define LLDP_TYPE_CONFIG 0x00 +#define LLDP_CHASSIS_TTL 120 +#define ETH_TYPE_LLDP 0x88cc +#define MINIMUM_ETH_PACKET_SIZE 68 + +#define AA_STATUS_MULTIPLE \ + AA_STATUS(ACTIVE,2,Active) \ + AA_STATUS(REJECT_GENERIC,3,Reject (Generic)) \ + AA_STATUS(REJECT_AA_RES_NOTAVAIL,4,Reject (AA resources unavailable)) \ + AA_STATUS(REJECT_INVALID,6,Reject (Invalid)) \ + AA_STATUS(REJECT_VLAN_RES_UNAVAIL,8,Reject (VLAN resources unavailable)) \ + AA_STATUS(REJECT_VLAN_APP_ISSUE,9,Reject (Application interaction issue)) \ + AA_STATUS(PENDING,255,Pending) + +enum aa_status { +#define AA_STATUS(NAME, VALUE, STR) AA_STATUS_##NAME = VALUE, + AA_STATUS_MULTIPLE +#undef AA_STATUS + AA_STATUS_N_MULTIPLE +}; + +/* Internal structure for an Auto Attach mapping. + */ +struct aa_mapping_internal { + struct hmap_node hmap_node_isid; + struct hmap_node hmap_node_aux; + int64_t isid; + int64_t vlan; + void *aux; + enum aa_status status; +}; + +static struct ovs_mutex mutex = OVS_MUTEX_INITIALIZER; + +/* Hash map of all LLDP instances keyed by name (port at the moment). + */ +static struct hmap all_lldps__ = HMAP_INITIALIZER(&all_lldps__); +static struct hmap *const all_lldps OVS_GUARDED_BY(mutex) = &all_lldps__; + +/* Hash map of all the Auto Attach mappings. Global at the moment (but will + * be per bridge). Used when adding a new port to a bridge so that we can + * properly install all the configured mapping on the port and export them + * To the Auto Attach server via LLDP. + */ +static struct hmap all_mappings__ = HMAP_INITIALIZER(&all_mappings__); +static struct hmap *const all_mappings OVS_GUARDED_BY(mutex) = &all_mappings__; + +static struct lldp_aa_element_system_id system_id_null; + +/* Convert an array to an integer. I-SID are stored in an array of bytes + * in the LLDP hardware structrure. + */ +static uint32_t +array_to_int(uint8_t *array, size_t len) +{ + uint32_t res = 0; + unsigned int i = 0; + + ovs_assert(len <= sizeof(uint32_t)); + + for (i = 0; i < len; i++) { + res = res | (array[len - i - 1] << (i * 8)); + } + + return res; +} + +/* Convert an integer to an array of byte. + */ +static void +int_to_array(uint8_t *array, size_t len, uint32_t value) +{ + unsigned int i; + + ovs_assert(len <= sizeof(uint32_t)); + + for (i = 0; i < len; i++) { + array[len - i - 1] = value >> (8 * i); + } +} + +/* Convert an LLDP chassis ID to a string. + */ +static void +chassisid_to_string(uint8_t *array, size_t len, char **str) +{ + unsigned int i; + + *str = xmalloc(len * 3); + + for (i = 0; i < len; i++) { + snprintf(&(*str)[i * 3], 4, "%02x:", array[i]); + } + (*str)[(i * 3) - 1] = '\0'; +} + +/* Find an Auto Attach mapping keyed by I-SID. + */ +static struct aa_mapping_internal * +mapping_find_by_isid(struct lldp *lldp, const uint64_t isid) + OVS_REQUIRES(mutex) +{ + struct aa_mapping_internal *m; + + HMAP_FOR_EACH_IN_BUCKET (m, + hmap_node_isid, + hash_bytes(&isid, sizeof isid, 0), + &lldp->mappings_by_isid) { + if (isid == m->isid) { + return m; + } + } + + return NULL; +} + +/* Find an Auto Attach mapping keyed by aux. aux is an opaque pointer created + * by the bridge that refers to an OVSDB mapping record. + */ +static struct aa_mapping_internal * +mapping_find_by_aux(struct lldp *lldp, const void *aux) OVS_REQUIRES(mutex) +{ + struct aa_mapping_internal *m; + + HMAP_FOR_EACH_IN_BUCKET (m, hmap_node_aux, hash_pointer(aux, 0), + &lldp->mappings_by_aux) { + if (aux == m->aux) { + return m; + } + } + + return NULL; +} + +/* Convert an Auto Attach request status to a string. + */ +static char * +aa_status_to_str(uint8_t status) +{ + switch (status) { +#define AA_STATUS(NAME, VALUE, STR) case AA_STATUS_##NAME: return #STR; + AA_STATUS_MULTIPLE +#undef AA_STATUS + default: return "Undefined"; + } +} + +/* Display LLDP and Auto Attach statistics. + */ +static void +aa_print_lldp_and_aa_stats(struct ds *ds, struct lldp *lldp) + OVS_REQUIRES(mutex) +{ + struct lldpd_hardware *hw; + + ds_put_format(ds, "Statistics: %s\n", lldp->name); + + if (!lldp->lldpd) { + return; + } + + LIST_FOR_EACH (hw, h_entries, &lldp->lldpd->g_hardware.h_entries) { + ds_put_format(ds, "\ttx cnt: %"PRIu64"\n", hw->h_tx_cnt); + ds_put_format(ds, "\trx cnt: %"PRIu64"\n", hw->h_rx_cnt); + ds_put_format(ds, "\trx discarded cnt: %"PRIu64"\n", + hw->h_rx_discarded_cnt); + ds_put_format(ds, "\trx unrecognized cnt: %"PRIu64"\n", + hw->h_rx_unrecognized_cnt); + ds_put_format(ds, "\tageout cnt: %"PRIu64"\n", hw->h_ageout_cnt); + ds_put_format(ds, "\tinsert cnt: %"PRIu64"\n", hw->h_insert_cnt); + ds_put_format(ds, "\tdelete cnt: %"PRIu64"\n", hw->h_delete_cnt); + ds_put_format(ds, "\tdrop cnt: %"PRIu64"\n", hw->h_drop_cnt); + } +} + +static void +aa_print_element_status_port(struct ds *ds, struct lldpd_hardware *hw) +{ + struct lldpd_port *port; + + LIST_FOR_EACH (port, p_entries, &hw->h_rports.p_entries) { + if (memcmp(&port->p_element.system_id, + &system_id_null, + sizeof port->p_element.system_id)) { + static char *none_str = "<None>"; + char *id = none_str, *descr = none_str, *system = none_str; + + if (port->p_chassis) { + if (port->p_chassis->c_id_len > 0) { + chassisid_to_string((uint8_t *) port->p_chassis->c_id, + port->p_chassis->c_id_len, &id); + } + + descr = port->p_chassis->c_descr + ? port->p_chassis->c_descr : none_str; + } + + chassisid_to_string((uint8_t *) &port->p_element.system_id, + sizeof port->p_element.system_id, &system); + + ds_put_format(ds, + "\tAuto Attach Primary Server Id: %s\n", + id); + ds_put_format(ds, + "\tAuto Attach Primary Server Descr: %s\n", + descr); + ds_put_format(ds, + "\tAuto Attach Primary Server System Id: %s\n", + system); + + free(id); + free(system); + } + } +} + +/* Auto Attach server broadcast an LLDP message periodically. Display + * the discovered server. + */ +static void +aa_print_element_status(struct ds *ds, struct lldp *lldp) OVS_REQUIRES(mutex) +{ + struct lldpd_hardware *hw; + + ds_put_format(ds, "LLDP: %s\n", lldp->name); + + if (!lldp->lldpd) { + return; + } + + LIST_FOR_EACH (hw, h_entries, &lldp->lldpd->g_hardware.h_entries) { + aa_print_element_status_port(ds, hw); + } +} + +static void +aa_print_isid_status_port_isid(struct lldp *lldp, struct lldpd_port *port) + OVS_REQUIRES(mutex) +{ + struct lldpd_aa_isid_vlan_maps_tlv *mapping; + + if (list_is_empty(&port->p_isid_vlan_maps.m_entries)) { + return; + } + + LIST_FOR_EACH (mapping, m_entries, &port->p_isid_vlan_maps.m_entries) { + uint32_t isid = array_to_int(mapping->isid_vlan_data.isid, + sizeof mapping->isid_vlan_data.isid); + struct aa_mapping_internal *m = mapping_find_by_isid(lldp, isid); + + VLOG_INFO("h_rport: isid=%u, vlan=%u, status=%d", + isid, + mapping->isid_vlan_data.vlan, + mapping->isid_vlan_data.status); + + /* Update the status of our internal state for the mapping. + */ + if (m) { + VLOG_INFO("Setting status for ISID=%u to %u", + isid, + mapping->isid_vlan_data.status); + m->status = mapping->isid_vlan_data.status; + } else { + VLOG_WARN("Couldn't find mapping for I-SID=%u", isid); + } + } +} + +static void +aa_print_isid_status_port(struct lldp *lldp, struct lldpd_hardware *hw) + OVS_REQUIRES(mutex) +{ + struct lldpd_port *port; + + LIST_FOR_EACH (port, p_entries, &hw->h_rports.p_entries) { + aa_print_isid_status_port_isid(lldp, port); + } +} + +/* The Auto Attach server will broadcast the status of the configured mappings + * via LLDP. Display the status. + */ +static void +aa_print_isid_status(struct ds *ds, struct lldp *lldp) OVS_REQUIRES(mutex) +{ + struct lldpd_hardware *hw; + struct aa_mapping_internal *m; + + if (!lldp->lldpd) { + return; + } + + ds_put_format(ds, "LLDP: %s\n", lldp->name); + + LIST_FOR_EACH (hw, h_entries, &lldp->lldpd->g_hardware.h_entries) { + aa_print_isid_status_port(lldp, hw); + } + + ds_put_format(ds, "%-8s %-4s %-11s %-8s\n", + "I-SID", + "VLAN", + "Source", + "Status"); + ds_put_format(ds, "-------- ---- ----------- --------\n"); + + HMAP_FOR_EACH (m, hmap_node_isid, &lldp->mappings_by_isid) { + ds_put_format(ds, "%-8ld %-4ld %-11s %-11s\n", + (long int) m->isid, + (long int) m->vlan, + "Switch", + aa_status_to_str(m->status)); + } +} + +static void +aa_unixctl_status(struct unixctl_conn *conn, int argc OVS_UNUSED, + const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED) + OVS_EXCLUDED(mutex) +{ + struct lldp *lldp; + struct ds ds = DS_EMPTY_INITIALIZER; + + ovs_mutex_lock(&mutex); + + HMAP_FOR_EACH (lldp, hmap_node, all_lldps) { + aa_print_element_status(&ds, lldp); + } + unixctl_command_reply(conn, ds_cstr(&ds)); + ds_destroy(&ds); + + ovs_mutex_unlock(&mutex); +} + +static void +aa_unixctl_show_isid(struct unixctl_conn *conn, int argc OVS_UNUSED, + const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED) + OVS_EXCLUDED(mutex) +{ + struct lldp *lldp; + struct ds ds = DS_EMPTY_INITIALIZER; + + ovs_mutex_lock(&mutex); + + HMAP_FOR_EACH (lldp, hmap_node, all_lldps) { + aa_print_isid_status(&ds, lldp); + } + unixctl_command_reply(conn, ds_cstr(&ds)); + ds_destroy(&ds); + + ovs_mutex_unlock(&mutex); +} + +static void +aa_unixctl_statistics(struct unixctl_conn *conn, int argc OVS_UNUSED, + const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED) + OVS_EXCLUDED(mutex) +{ + struct ds ds = DS_EMPTY_INITIALIZER; + struct lldp *lldp; + + ovs_mutex_lock(&mutex); + + /* Cycle through all ports and dump the stats for each one */ + HMAP_FOR_EACH (lldp, hmap_node, all_lldps) { + aa_print_lldp_and_aa_stats(&ds, lldp); + } + + ovs_mutex_unlock(&mutex); + + unixctl_command_reply(conn, ds_cstr(&ds)); +} + +/* An Auto Attach mapping was configured. Populate the corresponding + * structures in the LLDP hardware. + */ +static void +update_mapping_on_lldp(struct lldp *lldp, struct lldpd_hardware *hardware, + struct aa_mapping_internal *m) +{ + struct lldpd_aa_isid_vlan_maps_tlv *lm = xzalloc(sizeof *lm); + + if (hardware->h_ifname) { + VLOG_INFO("\t\t hardware->h_ifname=%s", hardware->h_ifname); + } + + int_to_array(lm->isid_vlan_data.isid, + ARRAY_SIZE(lm->isid_vlan_data.isid), + (uint32_t) m->isid); + lm->isid_vlan_data.vlan = m->vlan; + + list_push_back(&hardware->h_lport.p_isid_vlan_maps.m_entries, + &lm->m_entries); + + /* TODO Should be done in the Auto Attach state machine when a mapping goes + * from "pending" to "active". + */ + { + struct bridge_aa_vlan *node = xmalloc(sizeof *node); + + node->port_name = xstrdup(hardware->h_ifname); + node->vlan = m->vlan; + node->oper = BRIDGE_AA_VLAN_OPER_ADD; + + list_push_back(&lldp->active_mapping_queue, &node->list_node); + } +} + +/* Bridge will poll the list of VLAN that needs to be auto configure based on + * the Auto Attach mappings that have been exchanged with the server. + */ +int +aa_get_vlan_queued(struct ovs_list *list) +{ + struct lldp *lldp; + + ovs_mutex_lock(&mutex); + + HMAP_FOR_EACH (lldp, hmap_node, all_lldps) { + struct bridge_aa_vlan *node, *node_next; + + LIST_FOR_EACH_SAFE (node, + node_next, + list_node, + &lldp->active_mapping_queue) { + struct bridge_aa_vlan *copy; + + copy = xmalloc(sizeof *copy); + copy->port_name = xstrdup(node->port_name); + copy->vlan = node->vlan; + copy->oper = node->oper; + + list_push_back(list, ©->list_node); + + /* Cleanup */ + list_remove(&node->list_node); + free(node->port_name); + free(node); + } + } + + ovs_mutex_unlock(&mutex); + + return 0; +} + +/* Bridge will poll whether or not VLAN have been auto-configured. + */ +unsigned int +aa_get_vlan_queue_size(void) +{ + struct lldp *lldp; + unsigned int size = 0; + + ovs_mutex_lock(&mutex); + + HMAP_FOR_EACH (lldp, hmap_node, all_lldps) { + size += list_size(&lldp->active_mapping_queue); + } + + ovs_mutex_unlock(&mutex); + + return size; +} + +/* Configure Auto Attach. + */ +int +aa_configure(const struct aa_settings *s) +{ + struct lldp *lldp; + + ovs_mutex_lock(&mutex); + + /* TODO Change all instances for now */ + HMAP_FOR_EACH (lldp, hmap_node, all_lldps) { + struct lldpd_chassis *chassis; + + LIST_FOR_EACH (chassis, list, &lldp->lldpd->g_chassis.list) { + /* System Description */ + if (chassis->c_descr) { + free(chassis->c_descr); + } + chassis->c_descr = s->system_description[0] ? + xstrdup(s->system_description) : xstrdup(PACKAGE_STRING); + + /* System Name */ + if (chassis->c_name) { + free(chassis->c_name); + } + chassis->c_name = xstrdup(s->system_name); + } + } + + ovs_mutex_unlock(&mutex); + + return 0; +} + +/* Add a new Auto Attach mapping. + */ +int +aa_mapping_register(void *aux, const struct aa_mapping_settings *s) +{ + struct aa_mapping_internal *bridge_m; + struct lldp *lldp; + + VLOG_INFO("Adding mapping ISID=%ld, VLAN=%ld, aux=%p", (long int) s->isid, + (long int) s->vlan, aux); + + ovs_mutex_lock(&mutex); + + /* TODO These mappings should be stores per bridge. This is used + * When a port is added. Auto Attach mappings need to be added on this + * port. + */ + bridge_m = xzalloc(sizeof *bridge_m); + bridge_m->isid = s->isid; + bridge_m->vlan = s->vlan; + bridge_m->aux = aux; + bridge_m->status = AA_STATUS_PENDING; + hmap_insert(all_mappings, + &bridge_m->hmap_node_isid, + hash_bytes((const void *) &bridge_m->isid, + sizeof bridge_m->isid, + 0)); + + /* Update mapping on the all the LLDP instances. */ + HMAP_FOR_EACH (lldp, hmap_node, all_lldps) { + struct lldpd_hardware *hw; + struct aa_mapping_internal *m; + + VLOG_INFO("\t lldp->name=%s", lldp->name); + + if (mapping_find_by_isid(lldp, s->isid)) { + continue; + } + + m = xzalloc(sizeof *m); + m->isid = s->isid; + m->vlan = s->vlan; + m->status = AA_STATUS_PENDING; + m->aux = aux; + hmap_insert(&lldp->mappings_by_isid, + &m->hmap_node_isid, + hash_bytes((const void *) &m->isid, + sizeof m->isid, + 0)); + hmap_insert(&lldp->mappings_by_aux, + &m->hmap_node_aux, + hash_pointer(m->aux, 0)); + + /* Configure the mapping on each port of the LLDP stack. */ + LIST_FOR_EACH (hw, h_entries, &lldp->lldpd->g_hardware.h_entries) { + update_mapping_on_lldp(lldp, hw, m); + } + } + + ovs_mutex_unlock(&mutex); + + return 0; +} + +static void +aa_mapping_unregister_mapping(struct lldp *lldp, + struct lldpd_hardware *hw, + struct aa_mapping_internal *m) +{ + struct lldpd_aa_isid_vlan_maps_tlv *lm, *lm_next; + + LIST_FOR_EACH_SAFE (lm, + lm_next, + m_entries, + &hw->h_lport.p_isid_vlan_maps.m_entries) { + uint32_t isid = array_to_int(lm->isid_vlan_data.isid, + sizeof lm->isid_vlan_data.isid); + + if (isid == (uint32_t) m->isid) { + VLOG_INFO("\t\t Removing lport, isid=%u, vlan=%u", + isid, + lm->isid_vlan_data.vlan); + + list_remove(&lm->m_entries); + + /* TODO Should be done in the AA SM when a mapping goes + * from "pending" to "active". + */ + { + struct bridge_aa_vlan *node = xmalloc(sizeof *node); + + node->port_name = xstrdup(hw->h_ifname); + node->vlan = (uint32_t) m->vlan; + node->oper = BRIDGE_AA_VLAN_OPER_REMOVE; + + list_push_back(&lldp->active_mapping_queue, &node->list_node); + } + + break; + } + } +} + +/* Remove an existing Auto Attach mapping. + */ +int +aa_mapping_unregister(void *aux) +{ + struct lldp *lldp; + + VLOG_INFO("Removing mapping aux=%p", aux); + + ovs_mutex_lock(&mutex); + + HMAP_FOR_EACH (lldp, hmap_node, all_lldps) { + struct lldpd_hardware *hw; + struct aa_mapping_internal *m = mapping_find_by_aux(lldp, aux); + int64_t isid_tmp = -1, vlan_tmp = -1; + + /* Remove from internal hash tables. */ + if (m) { + struct aa_mapping_internal *p = + mapping_find_by_isid(lldp, m->isid); + + isid_tmp = m->isid; + vlan_tmp = m->vlan; + VLOG_INFO("\t Removing mapping ISID=%ld, VLAN=%ld (lldp->name=%s)", + (long int) m->isid, (long int) m->vlan, lldp->name); + + if (p) { + hmap_remove(&lldp->mappings_by_isid, &p->hmap_node_isid); + } + + hmap_remove(&lldp->mappings_by_aux, &m->hmap_node_aux); + free(m); + + /* Remove from all the lldp instances */ + LIST_FOR_EACH (hw, h_entries, &lldp->lldpd->g_hardware.h_entries) { + if (hw->h_ifname) { + VLOG_INFO("\t\t hardware->h_ifname=%s", hw->h_ifname); + } + + aa_mapping_unregister_mapping(lldp, hw, m); + } + + if (isid_tmp >= 0 && vlan_tmp >= 0) { + /* Remove from the all_mappings */ + HMAP_FOR_EACH (m, hmap_node_isid, all_mappings) { + if (m && isid_tmp == m->isid && vlan_tmp == m->vlan) { + hmap_remove(all_mappings, &m->hmap_node_isid); + break; + } + } + } + } + } + + ovs_mutex_unlock(&mutex); + + return 0; +} + +void +lldp_init(void) +{ + unixctl_command_register("autoattach/status", "[bridge]", 0, 1, + aa_unixctl_status, NULL); + unixctl_command_register("autoattach/show-isid", "[bridge]", 0, 1, + aa_unixctl_show_isid, NULL); + unixctl_command_register("autoattach/statistics", "[bridge]", 0, 1, + aa_unixctl_statistics, NULL); +} + +/* Returns true if 'lldp' should process packets from 'flow'. Sets + * fields in 'wc' that were used to make the determination. + */ +bool +lldp_should_process_flow(const struct flow *flow) +{ + return (flow->dl_type == htons(ETH_TYPE_LLDP)); +} + + +/* Process an LLDP packet that was received on a bridge port. + */ +void +lldp_process_packet(struct lldp *lldp, const struct ofpbuf *p) +{ + if (lldp) { + lldpd_recv(lldp->lldpd, + (struct lldpd_hardware *) + lldp->lldpd->g_hardware.h_entries.next, + (char *) p->data_, + p->size_); + } +} + +/* This code is called periodically to check if the LLDP module has an LLDP + * message it wishes to send. It is called several times every second. + */ +bool +lldp_should_send_packet(struct lldp *cfg) OVS_EXCLUDED(mutex) +{ + bool ret; + + ovs_mutex_lock(&mutex); + ret = timer_expired(&cfg->tx_timer); + ovs_mutex_unlock(&mutex); + + return ret; +} + +/* Returns the next wake up time. + */ +long long int +lldp_wake_time(const struct lldp *lldp) OVS_EXCLUDED(mutex) +{ + long long int retval; + + if (!lldp) { + return LLONG_MAX; + } + + ovs_mutex_lock(&mutex); + retval = lldp->tx_timer.t; + ovs_mutex_unlock(&mutex); + + return retval; +} + +/* Put the monitor thread to sleep until it's next wake time. + */ +long long int +lldp_wait(struct lldp *lldp) OVS_EXCLUDED(mutex) +{ + long long int wake_time = lldp_wake_time(lldp); + poll_timer_wait_until(wake_time); + return wake_time; +} + +/* Prepare the LLDP packet to be sent on a bridge port. + */ +void +lldp_put_packet(struct lldp *lldp, struct ofpbuf *packet, + uint8_t eth_src[ETH_ADDR_LEN]) OVS_EXCLUDED(mutex) +{ + struct lldpd *mylldpd = lldp->lldpd; + struct lldpd_hardware *hw = (struct lldpd_hardware *) + mylldpd->g_hardware.h_entries.next; + uint32_t lldp_size = 0; + static const uint8_t eth_addr_lldp[6] = + {0x01, 0x80, 0xC2, 0x00, 0x00, 0x0e}; + + ovs_mutex_lock(&mutex); + + eth_compose(packet, eth_addr_lldp, eth_src, ETH_TYPE_LLDP, 0); + + lldp_size = lldpd_send(hw, packet); + if (lldp_size + ETH_HEADER_LEN < MINIMUM_ETH_PACKET_SIZE) { + lldp_size = MINIMUM_ETH_PACKET_SIZE; + } + + timer_set_duration(&lldp->tx_timer, lldp->lldpd->g_config.c_tx_interval); + ovs_mutex_unlock(&mutex); +} + +/* Configures the LLDP stack. + */ +bool +lldp_configure(struct lldp *lldp) OVS_EXCLUDED(mutex) +{ + if (lldp) { + ovs_mutex_lock(&mutex); + timer_set_expired(&lldp->tx_timer); + timer_set_duration(&lldp->tx_timer, LLDP_DEFAULT_TRANSMIT_INTERVAL_MS); + lldp->lldpd->g_config.c_tx_interval = + LLDP_DEFAULT_TRANSMIT_INTERVAL_MS; + ovs_mutex_unlock(&mutex); + } + + return true; +} + +/* Create an LLDP stack instance. At the moment there is one per bridge port. + */ +struct lldp * +lldp_create(const struct netdev *netdev, + const uint32_t mtu, + const struct smap *cfg) OVS_EXCLUDED(mutex) +{ + struct lldp *lldp; + struct lldpd_chassis *lchassis; + struct lldpd_hardware *hw; + struct aa_mapping_internal *m; + + if (!cfg || !smap_get_bool(cfg, "enable", false)) { + return NULL; + } + + lldp = xzalloc(sizeof *lldp); + lldp->name = xstrdup(netdev_get_name(netdev)); + lldp->lldpd = xzalloc(sizeof *lldp->lldpd); + + hmap_init(&lldp->mappings_by_isid); + hmap_init(&lldp->mappings_by_aux); + list_init(&lldp->active_mapping_queue); + + lchassis = xzalloc(sizeof *lchassis); + lchassis->c_cap_available = LLDP_CAP_BRIDGE; + lchassis->c_cap_enabled = LLDP_CAP_BRIDGE; + lchassis->c_id_subtype = LLDP_CHASSISID_SUBTYPE_LLADDR; + lchassis->c_id_len = ETH_ADDR_LEN; + lchassis->c_id = xmalloc(ETH_ADDR_LEN); + netdev_get_etheraddr(netdev, (uint8_t *) lchassis->c_id); + + list_init(&lchassis->c_mgmt.m_entries); + lchassis->c_ttl = lldp->lldpd->g_config.c_tx_interval * + lldp->lldpd->g_config.c_tx_hold; + lchassis->c_ttl = LLDP_CHASSIS_TTL; + lldpd_assign_cfg_to_protocols(lldp->lldpd); + list_init(&lldp->lldpd->g_chassis.list); + list_push_back(&lldp->lldpd->g_chassis.list, &lchassis->list); + + if ((hw = lldpd_alloc_hardware(lldp->lldpd, + (char *) netdev_get_name(netdev), + 0)) == NULL) { + VLOG_WARN("Unable to allocate space for %s", + (char *) netdev_get_name(netdev)); + out_of_memory(); + } + + ovs_refcount_init(&lldp->ref_cnt); +#ifndef _WIN32 + hw->h_flags |= IFF_RUNNING; +#endif + hw->h_mtu = mtu; + hw->h_lport.p_id_subtype = LLDP_PORTID_SUBTYPE_IFNAME; + hw->h_lport.p_id = xstrdup(netdev_get_name(netdev)); + + /* p_id is not necessarily a null terminated string. */ + hw->h_lport.p_id_len = strlen(netdev_get_name(netdev)); + + /* Auto Attach element tlv */ + hw->h_lport.p_element.type = LLDP_TLV_AA_ELEM_TYPE_TAG_CLIENT; + hw->h_lport.p_element.mgmt_vlan = 0; + memcpy(&hw->h_lport.p_element.system_id.system_mac, + lchassis->c_id, lchassis->c_id_len); + hw->h_lport.p_element.system_id.conn_type = + LLDP_TLV_AA_ELEM_CONN_TYPE_SINGLE; + + hw->h_lport.p_element.system_id.smlt_id = 0; + hw->h_lport.p_element.system_id.mlt_id[0] = 0; + hw->h_lport.p_element.system_id.mlt_id[1] = 0; + + list_init(&hw->h_lport.p_isid_vlan_maps.m_entries); + list_init(&lldp->lldpd->g_hardware.h_entries); + list_push_back(&lldp->lldpd->g_hardware.h_entries, &hw->h_entries); + + ovs_mutex_lock(&mutex); + + /* Update port with Auto Attach mappings configured. */ + HMAP_FOR_EACH (m, hmap_node_isid, all_mappings) { + struct aa_mapping_internal *p; + + if (mapping_find_by_isid(lldp, m->isid)) { + continue; + } + + p = xmemdup(m, sizeof *p); + hmap_insert(&lldp->mappings_by_isid, + &p->hmap_node_isid, + hash_bytes((const void *) &p->isid, + sizeof p->isid, + 0)); + hmap_insert(&lldp->mappings_by_aux, + &p->hmap_node_aux, + hash_pointer(p->aux, 0)); + + update_mapping_on_lldp(lldp, hw, p); + } + + hmap_insert(all_lldps, &lldp->hmap_node, + hash_string(netdev_get_name(netdev), 0)); + + ovs_mutex_unlock(&mutex); + + return lldp; +} + + +struct lldp * +lldp_create_dummy(void) +{ + struct lldp *lldp; + struct lldpd_chassis *lchassis; + struct lldpd_hardware *hw; + + lldp = xzalloc(sizeof *lldp); + lldp->name = "dummy-lldp"; + lldp->lldpd = xzalloc(sizeof *lldp->lldpd); + + hmap_init(&lldp->mappings_by_isid); + hmap_init(&lldp->mappings_by_aux); + list_init(&lldp->active_mapping_queue); + + lchassis = xzalloc(sizeof *lchassis); + lchassis->c_cap_available = LLDP_CAP_BRIDGE; + lchassis->c_cap_enabled = LLDP_CAP_BRIDGE; + lchassis->c_id_subtype = LLDP_CHASSISID_SUBTYPE_LLADDR; + lchassis->c_id_len = ETH_ADDR_LEN; + lchassis->c_id = xmalloc(ETH_ADDR_LEN); + + list_init(&lchassis->c_mgmt.m_entries); + lchassis->c_ttl = LLDP_CHASSIS_TTL; + lldpd_assign_cfg_to_protocols(lldp->lldpd); + list_init(&lldp->lldpd->g_chassis.list); + list_push_back(&lldp->lldpd->g_chassis.list, &lchassis->list); + + if ((hw = lldpd_alloc_hardware(lldp->lldpd, + "dummy-hw", + 0)) == NULL) { + VLOG_WARN("Unable to allocate space for dummy-hw"); + out_of_memory(); + } + + ovs_refcount_init(&lldp->ref_cnt); +#ifndef _WIN32 + hw->h_flags |= IFF_RUNNING; +#endif + hw->h_mtu = 1500; + hw->h_lport.p_id_subtype = LLDP_PORTID_SUBTYPE_IFNAME; + hw->h_lport.p_id = "dummy-port"; + + /* p_id is not necessarily a null terminated string. */ + hw->h_lport.p_id_len = strlen(hw->h_lport.p_id); + + /* Auto Attach element tlv */ + hw->h_lport.p_element.type = LLDP_TLV_AA_ELEM_TYPE_TAG_CLIENT; + hw->h_lport.p_element.mgmt_vlan = 0; + memcpy(&hw->h_lport.p_element.system_id.system_mac, + lchassis->c_id, lchassis->c_id_len); + hw->h_lport.p_element.system_id.conn_type = + LLDP_TLV_AA_ELEM_CONN_TYPE_SINGLE; + hw->h_lport.p_element.system_id.smlt_id = 0; + hw->h_lport.p_element.system_id.mlt_id[0] = 0; + hw->h_lport.p_element.system_id.mlt_id[1] = 0; + + list_init(&hw->h_lport.p_isid_vlan_maps.m_entries); + list_init(&lldp->lldpd->g_hardware.h_entries); + list_push_back(&lldp->lldpd->g_hardware.h_entries, &hw->h_entries); + + return lldp; +} + +/* Unreference a specific LLDP instance. + */ +void +lldp_unref(struct lldp *lldp) +{ + if (!lldp) { + return; + } + + ovs_mutex_lock(&mutex); + if (ovs_refcount_unref_relaxed(&lldp->ref_cnt) != 1) { + ovs_mutex_unlock(&mutex); + return; + } + + hmap_remove(all_lldps, &lldp->hmap_node); + ovs_mutex_unlock(&mutex); + + lldpd_cleanup(lldp->lldpd); + free(lldp->lldpd); + free(lldp->name); + free(lldp); +} + +/* Unreference a specific LLDP instance. + */ +struct lldp * +lldp_ref(const struct lldp *lldp_) +{ + struct lldp *lldp = CONST_CAST(struct lldp *, lldp_); + if (lldp) { + ovs_refcount_ref(&lldp->ref_cnt); + } + return lldp; +} diff --git a/lib/ovs-lldp.h b/lib/ovs-lldp.h new file mode 100644 index 000000000..e8167c7ee --- /dev/null +++ b/lib/ovs-lldp.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2014 Wind River Systems, Inc. + * Copyright (c) 2015 Avaya, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVS_LLDP_H +#define OVS_LLDP_H + +#include <stdint.h> +#include "hmap.h" +#include "list.h" +#include "lldp/lldpd.h" +#include "ofpbuf.h" +#include "ovsdb-data.h" +#include "ovs-thread.h" +#include "packets.h" +#include "timer.h" + +/* Transmit every LLDPD_TX_INTERVAL seconds. */ +#define LLDP_DEFAULT_TRANSMIT_INTERVAL_MS LLDPD_TX_INTERVAL * 1000 + +struct flow_wildcards; +struct flow; +struct netdev; +struct smap; + +struct lldp_status { + /* TODO should reflect lldp stack detail */ + char *stackdetail; /* Added because MSVC doesn't like empty structs */ +}; + +/* Structure per LLDP instance (at the moment per port when enabled). + */ +struct lldp { + struct hmap_node hmap_node; /* Node in all_lldps list. */ + struct lldpd *lldpd; + char *name; /* Name of the port. */ + struct timer tx_timer; /* Send LLDP when expired. */ + struct hmap mappings_by_isid; /* "struct" indexed by ISID */ + struct hmap mappings_by_aux; /* "struct" indexed by aux */ + struct ovs_list active_mapping_queue; + struct ovs_refcount ref_cnt; +}; + +/* Configuration specific to Auto Attach. + */ +struct aa_settings { + char *system_description; + char *system_name; +}; + +/* Configuration of Auto Attach mappings. + */ +struct aa_mapping_settings { + int64_t isid; + int64_t vlan; +}; + +enum bridge_aa_vlan_oper { + BRIDGE_AA_VLAN_OPER_UNDEF, + BRIDGE_AA_VLAN_OPER_ADD, + BRIDGE_AA_VLAN_OPER_REMOVE +}; + +/* Bridge Auto Attach operations. Mostly for adding/removing VLAN on + * the trunk port connected to the Auto Attach server. + */ +struct bridge_aa_vlan { + struct ovs_list list_node; + char *port_name; + uint32_t vlan; + enum bridge_aa_vlan_oper oper; +}; + +void lldp_init(void); +long long int lldp_wait(struct lldp *lldp); +long long int lldp_wake_time(const struct lldp *lldp); +void lldp_run(struct lldpd *cfg); +bool lldp_should_send_packet(struct lldp *cfg); +bool lldp_should_process_flow(const struct flow *flow); +bool lldp_configure(struct lldp *lldp); +void lldp_process_packet(struct lldp *cfg, const struct ofpbuf *p); +void lldp_put_packet(struct lldp *lldp, struct ofpbuf *packet, + uint8_t eth_src[ETH_ADDR_LEN]); +void lldpd_assign_cfg_to_protocols(struct lldpd *cfg); +struct lldp * lldp_create(const struct netdev *netdev, const uint32_t mtu, + const struct smap *cfg); +struct lldp * lldp_ref(const struct lldp *lldp_); +void lldp_unref(struct lldp *lldp); + +int aa_get_vlan_queued(struct ovs_list *list); +unsigned int aa_get_vlan_queue_size(void); +int aa_configure(const struct aa_settings *s); +int aa_mapping_register(void *aux, const struct aa_mapping_settings *s); +int aa_mapping_unregister(void *aux); + +/* Used by unit tests */ +struct lldp * lldp_create_dummy(void); + +#endif /* OVS_LLDP_H */ |