summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/automake.mk12
-rw-r--r--lib/lldp/aa-structs.h49
-rw-r--r--lib/lldp/lldp-const.h230
-rw-r--r--lib/lldp/lldp-tlv.h79
-rw-r--r--lib/lldp/lldp.c752
-rw-r--r--lib/lldp/lldpd-structs.c130
-rw-r--r--lib/lldp/lldpd-structs.h228
-rw-r--r--lib/lldp/lldpd.c655
-rw-r--r--lib/lldp/lldpd.h120
-rw-r--r--lib/ovs-lldp.c1041
-rw-r--r--lib/ovs-lldp.h112
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, &copy->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 */