summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--vnet/Makefile.am12
-rw-r--r--vnet/vnet/lldp/dir.dox18
-rw-r--r--vnet/vnet/lldp/lldp.md84
-rw-r--r--vnet/vnet/lldp/lldp_cli.c646
-rw-r--r--vnet/vnet/lldp/lldp_input.c257
-rw-r--r--vnet/vnet/lldp/lldp_node.c331
-rw-r--r--vnet/vnet/lldp/lldp_node.h145
-rw-r--r--vnet/vnet/lldp/lldp_output.c216
-rw-r--r--vnet/vnet/lldp/lldp_protocol.h142
9 files changed, 1851 insertions, 0 deletions
diff --git a/vnet/Makefile.am b/vnet/Makefile.am
index 1c28ebc40bb..1c47c658ac7 100644
--- a/vnet/Makefile.am
+++ b/vnet/Makefile.am
@@ -230,6 +230,18 @@ nobase_include_HEADERS += \
vnet/cdp/cdp_protocol.h
########################################
+# Layer 2 / LLDP
+########################################
+libvnet_la_SOURCES += \
+ vnet/lldp/lldp_input.c \
+ vnet/lldp/lldp_node.c \
+ vnet/lldp/lldp_output.c \
+ vnet/lldp/lldp_cli.c
+
+nobase_include_HEADERS += \
+ vnet/lldp/lldp_protocol.h
+
+########################################
# Layer 2/3 "classify"
########################################
libvnet_la_SOURCES += \
diff --git a/vnet/vnet/lldp/dir.dox b/vnet/vnet/lldp/dir.dox
new file mode 100644
index 00000000000..6aa45f70f16
--- /dev/null
+++ b/vnet/vnet/lldp/dir.dox
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ * 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.
+ */
+/**
+ @dir vnet/vnet/lldp
+ @brief Link Layer Discovery Protocol (LLDP) implementation
+*/
diff --git a/vnet/vnet/lldp/lldp.md b/vnet/vnet/lldp/lldp.md
new file mode 100644
index 00000000000..55de8f06151
--- /dev/null
+++ b/vnet/vnet/lldp/lldp.md
@@ -0,0 +1,84 @@
+# VPP Link Layer Discovery Protocol (LLDP) implementation
+
+This is a memo intended to contain documentation of the VPP LLDP implementation
+Everything that is not directly obvious should come here.
+
+
+## LLDP
+LLDP is a link layer protocol to advertise the capabilities and current status of the system.
+
+There are 2 nodes handling LLDP
+
+1.) input-node which processes incoming packets and updates the local database
+2.) process-node which is responsible for sending out LLDP packets from VPP side
+
+
+### Configuration
+
+LLDP has a global configuration and a per-interface enable setting.
+
+Global configuration is modified using the "set lldp" command
+
+set lldp [system-name <string>] [tx-hold <value>] [tx-interval <value>]
+
+system-name: the name of the VPP system sent to peers in the system-name TLV
+tx-hold: multiplier for tx-interval when setting time-to-live (TTL) value in the LLDP packets (TTL = tx-hold * tx-interval + 1, if TTL > 65535, then TTL = 65535)
+tx-interval: time interval between sending out LLDP packets
+
+Per interface setting is done using the "set interface lldp" command
+
+set interface lldp <interface> (enable | disable)
+
+interface: the name of the interface for which to enable/disable LLDP
+
+
+### Configuration example
+
+Configure system-name as "VPP" and transmit interval to 10 seconds:
+
+set lldp system-name VPP tx-interval 10
+
+Enable LLDP on interface TenGigabitEthernet5/0/1
+
+set interface lldp TenGigabitEthernet5/0/1 enable
+
+
+### Operational data
+
+The list of LLDP-enabled interfaces which are up can be shown using "show lldp" command
+
+Example:
+DBGvpp# show lldp
+Local interface Peer chassis ID Remote port ID Last heard Last sent Status
+GigabitEthernet2/0/1 never 27.0s ago inactive
+TenGigabitEthernet5/0/1 8c:60:4f:dd:ca:52 Eth1/3/3 20.1s ago 18.3s ago active
+
+All LLDP configuration data with all LLDP-enabled interfaces can be shown using "show lldp detail" command
+
+Example:
+DBGvpp# show lldp detail
+LLDP configuration:
+Configured system name: vpp
+Configured tx-hold: 4
+Configured tx-interval: 30
+
+LLDP-enabled interface table:
+
+Interface name: GigabitEthernet2/0/1
+Interface/peer state: inactive(timeout)
+Last known peer chassis ID:
+Last known peer port ID:
+Last packet sent: 12.4s ago
+Last packet received: never
+
+Interface name: GigabitEthernet2/0/2
+Interface/peer state: interface down
+Last packet sent: never
+
+Interface name: TenGigabitEthernet5/0/1
+Interface/peer state: active
+Peer chassis ID: 8c:60:4f:dd:ca:52(MAC address)
+Remote port ID: Eth1/3/3(Locally assigned)
+Last packet sent: 3.6s ago
+Last packet received: 5.5s ago
+
diff --git a/vnet/vnet/lldp/lldp_cli.c b/vnet/vnet/lldp/lldp_cli.c
new file mode 100644
index 00000000000..45f688c58b4
--- /dev/null
+++ b/vnet/vnet/lldp/lldp_cli.c
@@ -0,0 +1,646 @@
+/*
+ * Copyright (c) 2011-2016 Cisco and/or its affiliates.
+ * 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.
+ */
+
+/**
+ * @file
+ * @brief LLDP CLI handling
+ *
+ */
+#include <vnet/lisp-cp/lisp_types.h>
+#include <vnet/lldp/lldp_node.h>
+
+#ifndef ETHER_ADDR_LEN
+#include <net/ethernet.h>
+#endif
+
+typedef enum lldp_cfg_err
+{
+ lldp_ok,
+ lldp_not_supported,
+ lldp_invalid_arg,
+} lldp_cfg_err_t;
+
+static clib_error_t *
+lldp_cfg_err_to_clib_err (lldp_cfg_err_t e)
+{
+
+ switch (e)
+ {
+ case lldp_ok:
+ return 0;
+ case lldp_not_supported:
+ return clib_error_return (0, "not supported");
+ case lldp_invalid_arg:
+ return clib_error_return (0, "invalid argument");
+ }
+ return 0;
+}
+
+static lldp_cfg_err_t
+lldp_cfg_intf_set (u32 hw_if_index, int enable)
+{
+ lldp_main_t *lm = &lldp_main;
+ vnet_main_t *vnm = lm->vnet_main;
+ ethernet_main_t *em = &ethernet_main;
+ const vnet_hw_interface_t *hi = vnet_get_hw_interface (vnm, hw_if_index);
+ const ethernet_interface_t *eif = ethernet_get_interface (em, hw_if_index);
+
+ if (!eif)
+ {
+ return lldp_not_supported;
+ }
+
+ if (enable)
+ {
+ lldp_intf_t *n = lldp_get_intf (lm, hw_if_index);
+ if (n)
+ {
+ /* already enabled */
+ return 0;
+ }
+ n = lldp_create_intf (lm, hw_if_index);
+ const vnet_sw_interface_t *sw =
+ vnet_get_sw_interface (lm->vnet_main, hi->sw_if_index);
+ if (sw->flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP)
+ {
+ lldp_schedule_intf (lm, n);
+ }
+ }
+ else
+ {
+ lldp_intf_t *n = lldp_get_intf (lm, hi->sw_if_index);
+ lldp_delete_intf (lm, n);
+ }
+
+ return 0;
+}
+
+static clib_error_t *
+lldp_intf_cmd (vlib_main_t * vm, unformat_input_t * input,
+ vlib_cli_command_t * cmd)
+{
+ lldp_main_t *lm = &lldp_main;
+ vnet_main_t *vnm = lm->vnet_main;
+ u32 hw_if_index;
+ int enable = 0;
+
+ if (unformat (input, "%U %U", unformat_vnet_hw_interface, vnm, &hw_if_index,
+ unformat_vlib_enable_disable, &enable))
+ {
+ return
+ lldp_cfg_err_to_clib_err (lldp_cfg_intf_set (hw_if_index, enable));
+ }
+ else
+ {
+ return clib_error_return (0, "unknown input `%U'",
+ format_unformat_error, input);
+ }
+ return 0;
+}
+
+static lldp_cfg_err_t
+lldp_cfg_set (u8 ** host, int hold_time, int tx_interval)
+{
+ lldp_main_t *lm = &lldp_main;
+ int reschedule = 0;
+ if (host && *host)
+ {
+ vec_free (lm->sys_name);
+ lm->sys_name = *host;
+ *host = NULL;
+ }
+ if (hold_time)
+ {
+ if (hold_time < LLDP_MIN_TX_HOLD || hold_time > LLDP_MAX_TX_HOLD)
+ {
+ return lldp_invalid_arg;
+ }
+ if (lm->msg_tx_hold != hold_time)
+ {
+ lm->msg_tx_hold = hold_time;
+ reschedule = 1;
+ }
+ }
+ if (tx_interval)
+ {
+ if (tx_interval < LLDP_MIN_TX_INTERVAL ||
+ tx_interval > LLDP_MAX_TX_INTERVAL)
+ {
+ return lldp_invalid_arg;
+ }
+ if (lm->msg_tx_interval != tx_interval)
+ {
+ reschedule = 1;
+ lm->msg_tx_interval = tx_interval;
+ }
+ }
+ if (reschedule)
+ {
+ vlib_process_signal_event (lm->vlib_main, lm->lldp_process_node_index,
+ LLDP_EVENT_RESCHEDULE, 0);
+ }
+ return lldp_ok;
+}
+
+static clib_error_t *
+lldp_cfg_cmd (vlib_main_t * vm, unformat_input_t * input,
+ vlib_cli_command_t * cmd)
+{
+ int hold_time = 0;
+ int tx_interval = 0;
+ u8 *host = NULL;
+ clib_error_t *ret = NULL;
+
+ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (input, "system-name %s", &host))
+ {
+ }
+ else if (unformat (input, "tx-hold %d", &hold_time))
+ {
+ if (hold_time < LLDP_MIN_TX_HOLD || hold_time > LLDP_MAX_TX_HOLD)
+ {
+ ret =
+ clib_error_return (0,
+ "invalid tx-hold `%d' (out of range <%d,%d>)",
+ hold_time, LLDP_MIN_TX_HOLD,
+ LLDP_MAX_TX_HOLD);
+ goto out;
+ }
+ }
+ else if (unformat (input, "tx-interval %d", &tx_interval))
+ {
+ if (tx_interval < LLDP_MIN_TX_INTERVAL ||
+ tx_interval > LLDP_MAX_TX_INTERVAL)
+ {
+ ret =
+ clib_error_return (0,
+ "invalid tx-interval `%d' (out of range <%d,%d>)",
+ tx_interval, LLDP_MIN_TX_INTERVAL,
+ LLDP_MAX_TX_INTERVAL);
+ goto out;
+ }
+ }
+ else
+ {
+ ret = clib_error_return (0, "unknown input `%U'",
+ format_unformat_error, input);
+ goto out;
+ }
+ }
+ ret =
+ lldp_cfg_err_to_clib_err (lldp_cfg_set (&host, hold_time, tx_interval));
+out:
+ vec_free (host);
+ return ret;
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND(set_interface_lldp_cmd, static) = {
+ .path = "set interface lldp",
+ .short_help = "set interface lldp <interface> (enable | disable) ",
+ .function = lldp_intf_cmd,
+};
+
+VLIB_CLI_COMMAND(set_lldp_cmd, static) = {
+ .path = "set lldp",
+ .short_help = "set lldp [system-name <string>] [tx-hold <value>] "
+ "[tx-interval <value>]",
+ .function = lldp_cfg_cmd,
+};
+/* *INDENT-ON* */
+
+static const char *
+lldp_chassis_id_subtype_str (lldp_chassis_id_subtype_t t)
+{
+ switch (t)
+ {
+#define F(num, val, str) \
+ case num: \
+ return str;
+ foreach_chassis_id_subtype (F)
+#undef F
+ }
+ return "unknown chassis subtype";
+}
+
+static const char *
+lldp_port_id_subtype_str (lldp_port_id_subtype_t t)
+{
+ switch (t)
+ {
+#define F(num, val, str) \
+ case num: \
+ return str;
+ foreach_port_id_subtype (F)
+#undef F
+ }
+ return "unknown port subtype";
+}
+
+/*
+ * format port id subtype&value
+ *
+ * @param va - 1st argument - unsigned - port id subtype
+ * @param va - 2nd argument - u8* - port id
+ * @param va - 3rd argument - unsigned - port id length
+ * @param va - 4th argument - int - 1 for detailed output, 0 for simple
+ */
+u8 *
+format_lldp_port_id (u8 * s, va_list * va)
+{
+ const lldp_port_id_subtype_t subtype = va_arg (*va, unsigned);
+ const u8 *id = va_arg (*va, u8 *);
+ const unsigned len = va_arg (*va, unsigned);
+ const int detail = va_arg (*va, int);
+ if (!id)
+ {
+ return s;
+ }
+ switch (subtype)
+ {
+ case LLDP_PORT_ID_SUBTYPE_NAME (intf_alias):
+ /* fallthrough */
+ case LLDP_PORT_ID_SUBTYPE_NAME (port_comp):
+ /* fallthrough */
+ case LLDP_PORT_ID_SUBTYPE_NAME (local):
+ /* fallthrough */
+ case LLDP_PORT_ID_SUBTYPE_NAME (intf_name):
+ if (detail)
+ {
+ s = format (s, "%U(%s)", format_ascii_bytes, id, len,
+ lldp_port_id_subtype_str (subtype));
+ }
+ else
+ {
+ s = format (s, "%U", format_ascii_bytes, id, len);
+ }
+ break;
+ case LLDP_PORT_ID_SUBTYPE_NAME (mac_addr):
+ if (ETHER_ADDR_LEN == len)
+ {
+ if (detail)
+ {
+ s = format (s, "%U(%s)", format_mac_address, id,
+ lldp_port_id_subtype_str (subtype));
+ }
+ else
+ {
+ s = format (s, "%U", format_mac_address, id);
+ }
+ break;
+ }
+ /* fallthrough */
+ case LLDP_PORT_ID_SUBTYPE_NAME (net_addr):
+ /* TODO */
+ /* fallthrough */
+ default:
+ if (detail)
+ {
+ s = format (s, "%U(%s)", format_hex_bytes, id, len,
+ lldp_port_id_subtype_str (subtype));
+ }
+ else
+ {
+ s = format (s, "%U", format_hex_bytes, id, len);
+ }
+ break;
+ }
+ return s;
+}
+
+/*
+ * format chassis id subtype&value
+ *
+ * @param s format string
+ * @param va - 1st argument - unsigned - chassis id subtype
+ * @param va - 2nd argument - u8* - chassis id
+ * @param va - 3rd argument - unsigned - chassis id length
+ * @param va - 4th argument - int - 1 for detailed output, 0 for simple
+ */
+u8 *
+format_lldp_chassis_id (u8 * s, va_list * va)
+{
+ const lldp_chassis_id_subtype_t subtype =
+ va_arg (*va, lldp_chassis_id_subtype_t);
+ const u8 *id = va_arg (*va, u8 *);
+ const unsigned len = va_arg (*va, unsigned);
+ const int detail = va_arg (*va, int);
+ if (!id)
+ {
+ return s;
+ }
+ switch (subtype)
+ {
+ case LLDP_CHASS_ID_SUBTYPE_NAME (chassis_comp):
+ /* fallthrough */
+ case LLDP_CHASS_ID_SUBTYPE_NAME (intf_alias):
+ /* fallthrough */
+ case LLDP_CHASS_ID_SUBTYPE_NAME (port_comp):
+ /* fallthrough */
+ case LLDP_PORT_ID_SUBTYPE_NAME (local):
+ /* fallthrough */
+ case LLDP_CHASS_ID_SUBTYPE_NAME (intf_name):
+ if (detail)
+ {
+ s = format (s, "%U(%s)", format_ascii_bytes, id, len,
+ lldp_chassis_id_subtype_str (subtype));
+ }
+ else
+ {
+ s = format (s, "%U", format_ascii_bytes, id, len);
+ }
+ break;
+ case LLDP_CHASS_ID_SUBTYPE_NAME (mac_addr):
+ if (ETHER_ADDR_LEN == len)
+ {
+ if (detail)
+ {
+ s = format (s, "%U(%s)", format_mac_address, id,
+ lldp_chassis_id_subtype_str (subtype));
+ }
+ else
+ {
+ s = format (s, "%U", format_mac_address, id);
+ }
+ break;
+ }
+ /* fallthrough */
+ case LLDP_CHASS_ID_SUBTYPE_NAME (net_addr):
+ /* TODO */
+ default:
+ if (detail)
+ {
+ s = format (s, "%U(%s)", format_hex_bytes, id, len,
+ lldp_chassis_id_subtype_str (subtype));
+ }
+ else
+ {
+ s = format (s, "%U", format_hex_bytes, id, len);
+ }
+ break;
+ }
+ return s;
+}
+
+/*
+ * convert a tlv code to human-readable string
+ */
+static const char *
+lldp_tlv_code_str (lldp_tlv_code_t t)
+{
+ switch (t)
+ {
+#define F(n, t, s) \
+ case n: \
+ return s;
+ foreach_lldp_tlv_type (F)
+#undef F
+ }
+ return "unknown lldp tlv";
+}
+
+/*
+ * format a single LLDP TLV
+ *
+ * @param s format string
+ * @param va variable list - pointer to lldp_tlv_t is expected
+ */
+u8 *
+format_lldp_tlv (u8 * s, va_list * va)
+{
+ const lldp_tlv_t *tlv = va_arg (*va, lldp_tlv_t *);
+ if (!tlv)
+ {
+ return s;
+ }
+ u16 l = lldp_tlv_get_length (tlv);
+ switch (lldp_tlv_get_code (tlv))
+ {
+ case LLDP_TLV_NAME (chassis_id):
+ s = format (s, "%U", format_lldp_chassis_id,
+ ((lldp_chassis_id_tlv_t *) tlv)->subtype,
+ ((lldp_chassis_id_tlv_t *) tlv)->id,
+ l - STRUCT_SIZE_OF (lldp_chassis_id_tlv_t, subtype), 1);
+ break;
+ case LLDP_TLV_NAME (port_id):
+ s = format (s, "%U", format_lldp_port_id,
+ ((lldp_port_id_tlv_t *) tlv)->subtype,
+ ((lldp_port_id_tlv_t *) tlv)->id,
+ l - STRUCT_SIZE_OF (lldp_port_id_tlv_t, subtype), 1);
+ break;
+ case LLDP_TLV_NAME (ttl):
+ s = format (s, "%d", ntohs (((lldp_ttl_tlv_t *) tlv)->ttl));
+ break;
+ case LLDP_TLV_NAME (sys_name):
+ /* fallthrough */
+ case LLDP_TLV_NAME (sys_desc):
+ s = format (s, "%U", format_ascii_bytes, tlv->v, l);
+ break;
+ default:
+ s = format (s, "%U", format_hex_bytes, tlv->v, l);
+ }
+
+ return s;
+}
+
+static u8 *
+format_time_ago (u8 * s, va_list * va)
+{
+ f64 ago = va_arg (*va, double);
+ f64 now = va_arg (*va, double);
+ if (ago < 0.01)
+ {
+ return format (s, "never");
+ }
+ return format (s, "%.1fs ago", now - ago);
+}
+
+static u8 *
+format_lldp_intfs_detail (u8 * s, vlib_main_t * vm, const lldp_main_t * lm)
+{
+ vnet_main_t *vnm = &vnet_main;
+ const lldp_intf_t *n;
+ const vnet_hw_interface_t *hw;
+ const vnet_sw_interface_t *sw;
+ s = format (s, "LLDP configuration:\n");
+ if (lm->sys_name)
+ {
+ s = format (s, "Configured system name: %U\n", format_ascii_bytes,
+ lm->sys_name, vec_len (lm->sys_name));
+ }
+ s = format (s, "Configured tx-hold: %d\n", (int) lm->msg_tx_hold);
+ s = format (s, "Configured tx-interval: %d\n", (int) lm->msg_tx_interval);
+ s = format (s, "\nLLDP-enabled interface table:\n");
+ f64 now = vlib_time_now (vm);
+
+ /* *INDENT-OFF* */
+ pool_foreach(
+ n, lm->intfs, ({
+ hw = vnet_get_hw_interface(vnm, n->hw_if_index);
+ sw = vnet_get_sw_interface(lm->vnet_main, hw->sw_if_index);
+ /* Interface shutdown */
+ if (!(sw->flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP))
+ {
+ s = format(s, "\nInterface name: %s\nInterface/peer state: "
+ "interface down\nLast packet sent: %U\n",
+ hw->name, format_time_ago, n->last_sent, now);
+ }
+ else if (now < n->last_heard + n->ttl)
+ {
+ s = format(s,
+ "\nInterface name: %s\nInterface/peer state: "
+ "active\nPeer chassis ID: %U\nRemote port ID: %U\nLast "
+ "packet sent: %U\nLast packet received: %U\n",
+ hw->name, format_lldp_chassis_id, n->chassis_id_subtype,
+ n->chassis_id, vec_len(n->chassis_id), 1,
+ format_lldp_port_id, n->port_id_subtype, n->port_id,
+ vec_len(n->port_id), 1, format_time_ago, n->last_sent,
+ now, format_time_ago, n->last_heard, now);
+ }
+ else
+ {
+ s = format(s, "\nInterface name: %s\nInterface/peer state: "
+ "inactive(timeout)\nLast known peer chassis ID: "
+ "%U\nLast known peer port ID: %U\nLast packet sent: "
+ "%U\nLast packet received: %U\n",
+ hw->name, format_lldp_chassis_id, n->chassis_id_subtype,
+ n->chassis_id, vec_len(n->chassis_id), 1,
+ format_lldp_port_id, n->port_id_subtype, n->port_id,
+ vec_len(n->port_id), 1, format_time_ago, n->last_sent,
+ now, format_time_ago, n->last_heard, now);
+ }
+ }));
+ /* *INDENT-ON* */
+ return s;
+}
+
+static u8 *
+format_lldp_intfs (u8 * s, va_list * va)
+{
+ vlib_main_t *vm = va_arg (*va, vlib_main_t *);
+ const lldp_main_t *lm = va_arg (*va, lldp_main_t *);
+ const int detail = va_arg (*va, int);
+ vnet_main_t *vnm = &vnet_main;
+ const lldp_intf_t *n;
+
+ if (detail)
+ {
+ return format_lldp_intfs_detail (s, vm, lm);
+ }
+
+ f64 now = vlib_time_now (vm);
+ s = format (s, "%-25s %-25s %-25s %=15s %=15s %=10s\n", "Local interface",
+ "Peer chassis ID", "Remote port ID", "Last heard", "Last sent",
+ "Status");
+
+ /* *INDENT-OFF* */
+ pool_foreach(
+ n, lm->intfs, ({
+ const vnet_hw_interface_t *hw =
+ vnet_get_hw_interface(vnm, n->hw_if_index);
+ const vnet_sw_interface_t *sw =
+ vnet_get_sw_interface(lm->vnet_main, hw->sw_if_index);
+ /* Interface shutdown */
+ if (!(sw->flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP))
+ continue;
+ if (now < n->last_heard + n->ttl)
+ {
+ s = format(s, "%-25s %-25U %-25U %=15U %=15U %=10s\n", hw->name,
+ format_lldp_chassis_id, n->chassis_id_subtype,
+ n->chassis_id, vec_len(n->chassis_id), 0,
+ format_lldp_port_id, n->port_id_subtype, n->port_id,
+ vec_len(n->port_id), 0, format_time_ago, n->last_heard,
+ now, format_time_ago, n->last_sent, now, "active");
+ }
+ else
+ {
+ s = format(s, "%-25s %-25s %-25s %=15U %=15U %=10s\n", hw->name,
+ "", "", format_time_ago, n->last_heard, now,
+ format_time_ago, n->last_sent, now, "inactive");
+ }
+ }));
+ /* *INDENT-ON* */
+ return s;
+}
+
+static clib_error_t *
+show_lldp (vlib_main_t * vm, unformat_input_t * input,
+ CLIB_UNUSED (vlib_cli_command_t * lmd))
+{
+ lldp_main_t *lm = &lldp_main;
+
+ if (unformat (input, "detail"))
+ {
+ vlib_cli_output (vm, "%U\n", format_lldp_intfs, vm, lm, 1);
+ }
+ else
+ {
+ vlib_cli_output (vm, "%U\n", format_lldp_intfs, vm, lm, 0);
+ }
+ return 0;
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND(show_lldp_command, static) = {
+ .path = "show lldp",
+ .short_help = "show lldp [detail]",
+ .function = show_lldp,
+};
+/* *INDENT-ON* */
+
+/*
+ * packet trace format function, very similar to
+ * lldp_packet_scan except that we call the per TLV format
+ * functions instead of the per TLV processing functions
+ */
+u8 *
+lldp_input_format_trace (u8 * s, va_list * args)
+{
+ CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
+ CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
+ const lldp_input_trace_t *t = va_arg (*args, lldp_input_trace_t *);
+ const u8 *cur;
+ const lldp_tlv_t *tlv;
+ cur = t->data;
+ while (((cur + lldp_tlv_get_length ((lldp_tlv_t *) cur)) <
+ t->data + t->len))
+ {
+ tlv = (lldp_tlv_t *) cur;
+ if (cur == t->data)
+ {
+ s = format (s, "TLV #%d(%s): %U\n", lldp_tlv_get_code (tlv),
+ lldp_tlv_code_str (lldp_tlv_get_code (tlv)),
+ format_lldp_tlv, tlv);
+ }
+ else
+ {
+ s = format (s, " TLV #%d(%s): %U\n", lldp_tlv_get_code (tlv),
+ lldp_tlv_code_str (lldp_tlv_get_code (tlv)),
+ format_lldp_tlv, tlv);
+ }
+ cur += STRUCT_SIZE_OF (lldp_tlv_t, head) + lldp_tlv_get_length (tlv);
+ }
+
+ return s;
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/vnet/vnet/lldp/lldp_input.c b/vnet/vnet/lldp/lldp_input.c
new file mode 100644
index 00000000000..5a5655398f2
--- /dev/null
+++ b/vnet/vnet/lldp/lldp_input.c
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2011-2016 Cisco and/or its affiliates.
+ * 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.
+ */
+/**
+ * @file
+ * @brief LLDP packet parsing implementation
+ */
+#include <vnet/lldp/lldp_node.h>
+#include <vnet/lldp/lldp_protocol.h>
+
+lldp_tlv_code_t
+lldp_tlv_get_code (const lldp_tlv_t * tlv)
+{
+ return tlv->head.byte1 >> 1;
+}
+
+void
+lldp_tlv_set_code (lldp_tlv_t * tlv, lldp_tlv_code_t code)
+{
+ tlv->head.byte1 = (tlv->head.byte1 & 1) + (code << 1);
+}
+
+u16
+lldp_tlv_get_length (const lldp_tlv_t * tlv)
+{
+ return (((u16) (tlv->head.byte1 & 1)) << 8) + tlv->head.byte2;
+}
+
+void
+lldp_tlv_set_length (lldp_tlv_t * tlv, u16 length)
+{
+ tlv->head.byte2 = length & ((1 << 8) - 1);
+ if (length > (1 << 8) - 1)
+ {
+ tlv->head.byte1 |= 1;
+ }
+ else
+ {
+ tlv->head.byte1 &= (1 << 8) - 2;
+ }
+}
+
+lldp_main_t lldp_main;
+
+static int
+lldp_packet_scan (lldp_main_t * lm, lldp_intf_t * n, const lldp_tlv_t * pkt)
+{
+ lldp_error_t e = LLDP_ERROR_NONE;
+ const lldp_tlv_t *tlv = pkt;
+
+/* first check if the header fits in before extracting data from it */
+#define TLV_VIOLATES_PKT_BOUNDARY(pkt, tlv) \
+ (((((u8 *)tlv) + sizeof(lldp_tlv_t)) > ((u8 *)pkt + vec_len(pkt))) || \
+ ((((u8 *)tlv) + lldp_tlv_get_length(tlv)) > ((u8 *)pkt + vec_len(pkt))))
+
+ /* first tlv is always chassis id, followed by port id and ttl tlvs */
+ if (TLV_VIOLATES_PKT_BOUNDARY (pkt, tlv) ||
+ LLDP_TLV_NAME (chassis_id) != lldp_tlv_get_code (tlv))
+ {
+ return LLDP_ERROR_BAD_TLV;
+ }
+
+ u16 l = lldp_tlv_get_length (tlv);
+ if (l < STRUCT_SIZE_OF (lldp_chassis_id_tlv_t, subtype) +
+ LLDP_MIN_CHASS_ID_LEN ||
+ l > STRUCT_SIZE_OF (lldp_chassis_id_tlv_t, subtype) +
+ LLDP_MAX_CHASS_ID_LEN)
+ {
+ return LLDP_ERROR_BAD_TLV;
+ }
+
+ u8 chid_subtype = ((lldp_chassis_id_tlv_t *) tlv)->subtype;
+ u8 *chid = ((lldp_chassis_id_tlv_t *) tlv)->id;
+ u8 chid_len = l - STRUCT_SIZE_OF (lldp_chassis_id_tlv_t, subtype);
+
+ tlv = (lldp_tlv_t *) ((u8 *) tlv + STRUCT_SIZE_OF (lldp_tlv_t, head) + l);
+
+ if (TLV_VIOLATES_PKT_BOUNDARY (pkt, tlv) ||
+ LLDP_TLV_NAME (port_id) != lldp_tlv_get_code (tlv))
+ {
+ return LLDP_ERROR_BAD_TLV;
+ }
+ l = lldp_tlv_get_length (tlv);
+ if (l < STRUCT_SIZE_OF (lldp_port_id_tlv_t, subtype) +
+ LLDP_MIN_PORT_ID_LEN ||
+ l > STRUCT_SIZE_OF (lldp_chassis_id_tlv_t, subtype) +
+ LLDP_MAX_PORT_ID_LEN)
+ {
+ return LLDP_ERROR_BAD_TLV;
+ }
+
+ u8 portid_subtype = ((lldp_port_id_tlv_t *) tlv)->subtype;
+ u8 *portid = ((lldp_port_id_tlv_t *) tlv)->id;
+ u8 portid_len = l - STRUCT_SIZE_OF (lldp_port_id_tlv_t, subtype);
+
+ tlv = (lldp_tlv_t *) ((u8 *) tlv + STRUCT_SIZE_OF (lldp_tlv_t, head) + l);
+
+ if (TLV_VIOLATES_PKT_BOUNDARY (pkt, tlv) ||
+ LLDP_TLV_NAME (ttl) != lldp_tlv_get_code (tlv))
+ {
+ return LLDP_ERROR_BAD_TLV;
+ }
+ l = lldp_tlv_get_length (tlv);
+ if (l != STRUCT_SIZE_OF (lldp_ttl_tlv_t, ttl))
+ {
+ return LLDP_ERROR_BAD_TLV;
+ }
+ u16 ttl = ntohs (((lldp_ttl_tlv_t *) tlv)->ttl);
+ tlv = (lldp_tlv_t *) ((u8 *) tlv + STRUCT_SIZE_OF (lldp_tlv_t, head) + l);
+ while (!TLV_VIOLATES_PKT_BOUNDARY (pkt, tlv) &&
+ LLDP_TLV_NAME (pdu_end) != lldp_tlv_get_code (tlv))
+ {
+ switch (lldp_tlv_get_code (tlv))
+ {
+#define F(num, type, str) \
+ case LLDP_TLV_NAME(type): \
+ /* ignore optional TLV */ \
+ break;
+ foreach_lldp_optional_tlv_type (F);
+#undef F
+ default:
+ return LLDP_ERROR_BAD_TLV;
+ }
+ if (e)
+ {
+ return e;
+ }
+ tlv = (lldp_tlv_t *) ((u8 *) tlv + STRUCT_SIZE_OF (lldp_tlv_t, head) +
+ lldp_tlv_get_length (tlv));
+ }
+ /* last tlv is pdu_end */
+ if (TLV_VIOLATES_PKT_BOUNDARY (pkt, tlv) ||
+ LLDP_TLV_NAME (pdu_end) != lldp_tlv_get_code (tlv) ||
+ 0 != lldp_tlv_get_length (tlv))
+ {
+ return LLDP_ERROR_BAD_TLV;
+ }
+ /* LLDP PDU validated, now store data */
+ if (n->chassis_id)
+ {
+ _vec_len (n->chassis_id) = 0;
+ }
+ vec_add (n->chassis_id, chid, chid_len);
+ n->chassis_id_subtype = chid_subtype;
+ if (n->port_id)
+ {
+ _vec_len (n->port_id) = 0;
+ }
+ vec_add (n->port_id, portid, portid_len);
+ n->port_id_subtype = portid_subtype;
+ n->ttl = ttl;
+ return LLDP_ERROR_NONE;
+}
+
+lldp_intf_t *
+lldp_get_intf (lldp_main_t * lm, u32 hw_if_index)
+{
+ uword *p = hash_get (lm->intf_by_hw_if_index, hw_if_index);
+
+ if (p)
+ {
+ return pool_elt_at_index (lm->intfs, p[0]);
+ }
+ return NULL;
+}
+
+lldp_intf_t *
+lldp_create_intf (lldp_main_t * lm, u32 hw_if_index)
+{
+
+ uword *p;
+ lldp_intf_t *n;
+ p = hash_get (lm->intf_by_hw_if_index, hw_if_index);
+
+ if (p == 0)
+ {
+ pool_get (lm->intfs, n);
+ memset (n, 0, sizeof (*n));
+ n->hw_if_index = hw_if_index;
+ hash_set (lm->intf_by_hw_if_index, n->hw_if_index, n - lm->intfs);
+ }
+ else
+ {
+ n = pool_elt_at_index (lm->intfs, p[0]);
+ }
+ return n;
+}
+
+/*
+ * lldp input routine
+ */
+lldp_error_t
+lldp_input (vlib_main_t * vm, vlib_buffer_t * b0, u32 bi0)
+{
+ lldp_main_t *lm = &lldp_main;
+ lldp_error_t e;
+
+ /* find our interface */
+ lldp_intf_t *n = lldp_get_intf (lm, vnet_buffer (b0)->sw_if_index[VLIB_RX]);
+
+ if (!n)
+ {
+ /* lldp disabled on this interface, we're done */
+ return LLDP_ERROR_DISABLED;
+ }
+
+ /* Actually scan the packet */
+ e = lldp_packet_scan (lm, n, vlib_buffer_get_current (b0));
+
+ if (LLDP_ERROR_NONE == e)
+ {
+ n->last_heard = vlib_time_now (vm);
+ }
+
+ return e;
+}
+
+/*
+ * setup function
+ */
+static clib_error_t *
+lldp_init (vlib_main_t * vm)
+{
+ clib_error_t *error;
+ lldp_main_t *lm = &lldp_main;
+
+ if ((error = vlib_call_init_function (vm, lldp_template_init)))
+ return error;
+
+ lm->vlib_main = vm;
+ lm->vnet_main = vnet_get_main ();
+ lm->msg_tx_hold = 4; /* default value per IEEE 802.1AB-2009 */
+ lm->msg_tx_interval = 30; /* default value per IEEE 802.1AB-2009 */
+
+ return 0;
+}
+
+VLIB_INIT_FUNCTION (lldp_init);
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/vnet/vnet/lldp/lldp_node.c b/vnet/vnet/lldp/lldp_node.c
new file mode 100644
index 00000000000..2eb27e0634e
--- /dev/null
+++ b/vnet/vnet/lldp/lldp_node.c
@@ -0,0 +1,331 @@
+/*
+ * Copyright (c) 2011-2016 Cisco and/or its affiliates.
+ * 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.
+ */
+/**
+ * @file
+ * @brief LLDP nodes implementation
+ */
+#include <vnet/lldp/lldp_node.h>
+#include <vnet/ethernet/ethernet.h>
+#include <vnet/ethernet/packet.h>
+
+/* set this to 1 to turn on debug prints via clib_warning() */
+#define LLDP_DEBUG (0)
+
+static vlib_node_registration_t lldp_process_node;
+
+#define F(sym, string) static char LLDP_ERR_##sym##_STR[] = string;
+foreach_lldp_error (F);
+#undef F
+
+/*
+ * packet counter strings
+ * Dump these counters via the "show error" CLI command
+ */
+static char *lldp_error_strings[] = {
+#define F(sym, string) LLDP_ERR_##sym##_STR,
+ foreach_lldp_error (F)
+#undef F
+};
+
+/*
+ * We actually send all lldp pkts to the "error" node after scanning
+ * them, so the graph node has only one next-index. The "error-drop"
+ * node automatically bumps our per-node packet counters for us.
+ */
+typedef enum
+{
+ LLDP_INPUT_NEXT_NORMAL,
+ LLDP_INPUT_N_NEXT,
+} lldp_next_t;
+
+/*
+ * Process a frame of lldp packets
+ * Expect 1 packet / frame
+ */
+static uword
+lldp_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node,
+ vlib_frame_t * frame)
+{
+ u32 n_left_from, *from;
+ lldp_input_trace_t *t0;
+
+ from = vlib_frame_vector_args (frame); /* array of buffer indices */
+ n_left_from = frame->n_vectors; /* number of buffer indices */
+
+ while (n_left_from > 0)
+ {
+ u32 bi0;
+ vlib_buffer_t *b0;
+ u32 next0, error0;
+
+ bi0 = from[0];
+ b0 = vlib_get_buffer (vm, bi0);
+
+ next0 = LLDP_INPUT_NEXT_NORMAL;
+
+ /* scan this lldp pkt. error0 is the counter index to bump */
+ error0 = lldp_input (vm, b0, bi0);
+ b0->error = node->errors[error0];
+
+ /* If this pkt is traced, snapshoot the data */
+ if (b0->flags & VLIB_BUFFER_IS_TRACED)
+ {
+ int len;
+ t0 = vlib_add_trace (vm, node, b0, sizeof (*t0));
+ len = (b0->current_length < sizeof (t0->data)) ? b0->current_length
+ : sizeof (t0->data);
+ t0->len = len;
+ clib_memcpy (t0->data, vlib_buffer_get_current (b0), len);
+ }
+ /* push this pkt to the next graph node, always error-drop */
+ vlib_set_next_frame_buffer (vm, node, next0, bi0);
+
+ from += 1;
+ n_left_from -= 1;
+ }
+
+ return frame->n_vectors;
+}
+
+/*
+ * lldp input graph node declaration
+ */
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE(lldp_input_node, static) = {
+ .function = lldp_node_fn,
+ .name = "lldp-input",
+ .vector_size = sizeof(u32),
+ .type = VLIB_NODE_TYPE_INTERNAL,
+
+ .n_errors = LLDP_N_ERROR,
+ .error_strings = lldp_error_strings,
+
+ .format_trace = lldp_input_format_trace,
+
+ .n_next_nodes = LLDP_INPUT_N_NEXT,
+ .next_nodes =
+ {
+ [LLDP_INPUT_NEXT_NORMAL] = "error-drop",
+ },
+};
+/* *INDENT-ON* */
+
+/*
+ * lldp process node function
+ */
+static uword
+lldp_process (vlib_main_t * vm, vlib_node_runtime_t * rt, vlib_frame_t * f)
+{
+ lldp_main_t *lm = &lldp_main;
+ f64 timeout = 0;
+ uword event_type, *event_data = 0;
+
+ /* So we can send events to the lldp process */
+ lm->lldp_process_node_index = lldp_process_node.index;
+
+ /* with ethernet input */
+ ethernet_register_input_type (vm, ETHERNET_TYPE_802_1_LLDP /* LLDP */ ,
+ lldp_input_node.index);
+
+ while (1)
+ {
+ if (vec_len (lm->intfs_timeouts))
+ {
+#if LLDP_DEBUG
+ clib_warning ("DEBUG: wait for event with timeout %f", timeout);
+#endif
+ (void) vlib_process_wait_for_event_or_clock (vm, timeout);
+ }
+ else
+ {
+#if LLDP_DEBUG
+ clib_warning ("DEBUG: wait for event without timeout");
+#endif
+ (void) vlib_process_wait_for_event (vm);
+ }
+ event_type = vlib_process_get_events (vm, &event_data);
+ switch (event_type)
+ {
+ case ~0: /* no events => timeout */
+ /* nothing to do here */
+ break;
+ case LLDP_EVENT_RESCHEDULE:
+ /* nothing to do here - reschedule is done automatically after
+ * each event or timeout */
+ break;
+ default:
+ clib_warning ("BUG: event type 0x%wx", event_type);
+ break;
+ }
+ if (!vec_len (lm->intfs_timeouts))
+ {
+ continue;
+ }
+ /* send packet(s) and schedule another timeut */
+ const f64 now = vlib_time_now (lm->vlib_main);
+ while (1)
+ {
+ lldp_intf_t *n = pool_elt_at_index (lm->intfs,
+ lm->intfs_timeouts
+ [lm->intfs_timeouts_idx]);
+ if (n->last_sent < 0.01 || now > n->last_sent + lm->msg_tx_interval)
+ {
+#if LLDP_DEBUG
+ clib_warning ("send packet to lldp %p, if idx %d", n,
+ n->hw_if_index);
+#endif
+ lldp_send_ethernet (lm, n, 0);
+ ++lm->intfs_timeouts_idx;
+ if (lm->intfs_timeouts_idx >= vec_len (lm->intfs_timeouts))
+ {
+ lm->intfs_timeouts_idx = 0;
+ }
+ continue;
+ }
+ else
+ {
+ timeout = n->last_sent + lm->msg_tx_interval - now;
+ break;
+ }
+ }
+#if LLDP_DEBUG
+ clib_warning ("DEBUG: timeout set to %f", timeout);
+ u8 *s = NULL;
+ u32 i;
+ vec_foreach_index (i, lm->intfs_timeouts)
+ {
+ if (i == lm->intfs_timeouts_idx)
+ {
+ s = format (s, " [%d]", lm->intfs_timeouts[i]);
+ }
+ else
+ {
+ s = format (s, " %d", lm->intfs_timeouts[i]);
+ }
+ }
+ clib_warning ("DEBUG: timeout schedule: %s", s);
+ vec_free (s);
+#endif
+ if (event_data)
+ {
+ _vec_len (event_data) = 0;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * lldp process node declaration
+ */
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE(lldp_process_node, static) = {
+ .function = lldp_process,
+ .type = VLIB_NODE_TYPE_PROCESS,
+ .name = "lldp-process",
+};
+/* *INDENT-ON* */
+
+void
+lldp_schedule_intf (lldp_main_t * lm, lldp_intf_t * n)
+{
+ n->last_sent = 0; /* ensure that a packet is sent out immediately */
+ /* put the interface at the current position in the timeouts - it
+ * will timeout immediately */
+ vec_insert (lm->intfs_timeouts, 1, lm->intfs_timeouts_idx);
+ lm->intfs_timeouts[lm->intfs_timeouts_idx] = n - lm->intfs;
+ vlib_process_signal_event (lm->vlib_main, lm->lldp_process_node_index,
+ LLDP_EVENT_RESCHEDULE, 0);
+#if LLDP_DEBUG
+ clib_warning ("DEBUG: schedule interface %p, if idx %d", n, n->hw_if_index);
+#endif
+}
+
+void
+lldp_unschedule_intf (lldp_main_t * lm, lldp_intf_t * n)
+{
+ if (!n)
+ {
+ return;
+ }
+#if LLDP_DEBUG
+ clib_warning ("DEBUG: unschedule interface %p, if idx %d", n,
+ n->hw_if_index);
+#endif
+ const int idx = n - lm->intfs;
+ u32 v;
+ /* remove intf index from timeouts vector */
+ vec_foreach_index (v, lm->intfs_timeouts)
+ {
+ if (lm->intfs_timeouts[v] == idx)
+ {
+ vec_delete (lm->intfs_timeouts, 1, v);
+ break;
+ }
+ }
+ /* wrap current timeout index to first element if needed */
+ if (lm->intfs_timeouts_idx >= vec_len (lm->intfs_timeouts))
+ {
+ lm->intfs_timeouts_idx = 0;
+ }
+ vlib_process_signal_event (lm->vlib_main, lm->lldp_process_node_index,
+ LLDP_EVENT_RESCHEDULE, 0);
+}
+
+static clib_error_t *
+lldp_sw_interface_up_down (vnet_main_t * vnm, u32 sw_if_index, u32 flags)
+{
+ lldp_main_t *lm = &lldp_main;
+ vnet_hw_interface_t *hi = vnet_get_sup_hw_interface (vnm, sw_if_index);
+ lldp_intf_t *n = lldp_get_intf (lm, hi->hw_if_index);
+ if (n)
+ {
+ if (!(flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP))
+ {
+ /* FIXME - the packet sent here isn't send properly - need to find a
+ * way to send the packet before interface goes down */
+ lldp_send_ethernet (lm, n, 1);
+ lldp_unschedule_intf (lm, n);
+ }
+ }
+ return 0;
+}
+
+VNET_SW_INTERFACE_ADMIN_UP_DOWN_FUNCTION (lldp_sw_interface_up_down);
+
+static clib_error_t *
+lldp_hw_interface_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags)
+{
+ lldp_main_t *lm = &lldp_main;
+ lldp_intf_t *n = lldp_get_intf (lm, hw_if_index);
+ if (n)
+ {
+ if (flags & VNET_HW_INTERFACE_FLAG_LINK_UP)
+ {
+ lldp_schedule_intf (lm, n);
+ }
+ }
+ return 0;
+}
+
+VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION (lldp_hw_interface_up_down);
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/vnet/vnet/lldp/lldp_node.h b/vnet/vnet/lldp/lldp_node.h
new file mode 100644
index 00000000000..39140f25cac
--- /dev/null
+++ b/vnet/vnet/lldp/lldp_node.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2011-2016 Cisco and/or its affiliates.
+ * 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.
+ */
+/**
+ * @file
+ * @brief LLDP global declarations
+ */
+#ifndef __included_lldp_node_h__
+#define __included_lldp_node_h__
+
+#include <vlib/vlib.h>
+#include <vlib/unix/unix.h>
+#include <vnet/snap/snap.h>
+#include <vppinfra/format.h>
+#include <vppinfra/hash.h>
+
+#include <vnet/lldp/lldp_protocol.h>
+
+typedef struct lldp_intf
+{
+ /* hw interface index */
+ u32 hw_if_index;
+
+ /* Timers */
+ f64 last_heard;
+ f64 last_sent;
+
+ /* Info received from peer */
+ u8 *chassis_id;
+ u8 *port_id;
+ u16 ttl;
+ lldp_port_id_subtype_t port_id_subtype;
+ lldp_chassis_id_subtype_t chassis_id_subtype;
+
+} lldp_intf_t;
+
+typedef struct
+{
+ /* pool of lldp-enabled interface context data */
+ lldp_intf_t *intfs;
+
+ /* rapidly find an interface by vlib hw interface index */
+ uword *intf_by_hw_if_index;
+
+ /* Background process node index */
+ u32 lldp_process_node_index;
+
+ /* interface idxs (into intfs pool) in the order of timing out */
+ u32 *intfs_timeouts;
+
+ /* index of the interface which will time out next */
+ u32 intfs_timeouts_idx;
+
+ /* packet template for sending out packets */
+ vlib_packet_template_t packet_template;
+
+ /* convenience variables */
+ vlib_main_t *vlib_main;
+ vnet_main_t *vnet_main;
+
+ /* system name advertised over LLDP (default is none) */
+ u8 *sys_name;
+
+ /* IEEE Std 802.1AB-2009:
+ * 9.2.5.6 msgTxHold
+ * This variable is used, as a multiplier of msgTxInterval, to determine the
+ * value of txTTL that is carried in LLDP frames transmitted by the LLDP
+ * agent. The recommended default value of msgTxHold is 4; this value can
+ * be changed by management to any value in the range 1 through 100.
+ */
+ u8 msg_tx_hold;
+
+ /* IEEE Std 802.1AB-2009:
+ * 9.2.5.7 msgTxInterval
+ * This variable defines the time interval in timer ticks between
+ * transmissions during normal transmission periods (i.e., txFast is zero).
+ * The recommended default value for msgTxInterval is 30 s; this value can
+ * be changed by management to any value in the range 1 through 3600.
+ */
+ u16 msg_tx_interval;
+} lldp_main_t;
+
+#define LLDP_MIN_TX_HOLD (1)
+#define LLDP_MAX_TX_HOLD (100)
+#define LLDP_MIN_TX_INTERVAL (1)
+#define LLDP_MAX_TX_INTERVAL (3600)
+
+lldp_main_t lldp_main;
+
+/* Packet counters */
+#define foreach_lldp_error(F) \
+ F(NONE, "good lldp packets (processed)") \
+ F(CACHE_HIT, "good lldp packets (cache hit)") \
+ F(BAD_TLV, "lldp packets with bad TLVs") \
+ F(DISABLED, "lldp packets received on disabled interfaces")
+
+typedef enum
+{
+#define F(sym, str) LLDP_ERROR_##sym,
+ foreach_lldp_error (F)
+#undef F
+ LLDP_N_ERROR,
+} lldp_error_t;
+
+/* lldp packet trace capture */
+typedef struct
+{
+ u32 len;
+ u8 data[400];
+} lldp_input_trace_t;
+
+enum
+{
+ LLDP_EVENT_RESCHEDULE = 1,
+} lldp_process_event_t;
+
+lldp_intf_t *lldp_get_intf (lldp_main_t * lm, u32 hw_if_index);
+lldp_intf_t *lldp_create_intf (lldp_main_t * lm, u32 hw_if_index);
+void lldp_delete_intf (lldp_main_t * lm, lldp_intf_t * n);
+lldp_error_t lldp_input (vlib_main_t * vm, vlib_buffer_t * b0, u32 bi0);
+u8 *lldp_input_format_trace (u8 * s, va_list * args);
+void lldp_send_ethernet (lldp_main_t * lm, lldp_intf_t * n, int shutdown);
+void lldp_schedule_intf (lldp_main_t * lm, lldp_intf_t * n);
+void lldp_unschedule_intf (lldp_main_t * lm, lldp_intf_t * n);
+
+#endif /* __included_lldp_node_h__ */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/vnet/vnet/lldp/lldp_output.c b/vnet/vnet/lldp/lldp_output.c
new file mode 100644
index 00000000000..6cb2627098b
--- /dev/null
+++ b/vnet/vnet/lldp/lldp_output.c
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2011-2016 Cisco and/or its affiliates.
+ * 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.
+ */
+/**
+ * @file
+ * @brief LLDP packet generation implementation
+ */
+#include <vnet/lldp/lldp_node.h>
+
+static void
+lldp_add_chassis_id (const vnet_hw_interface_t * hw, u8 ** t0p)
+{
+ lldp_chassis_id_tlv_t *t = (lldp_chassis_id_tlv_t *) * t0p;
+
+ lldp_tlv_set_code ((lldp_tlv_t *) t, LLDP_TLV_NAME (chassis_id));
+ t->subtype = LLDP_CHASS_ID_SUBTYPE_NAME (mac_addr);
+
+ const size_t addr_len = 6;
+ clib_memcpy (&t->id, hw->hw_address, addr_len);
+ const size_t len =
+ STRUCT_SIZE_OF (lldp_chassis_id_tlv_t, subtype) + addr_len;
+ lldp_tlv_set_length ((lldp_tlv_t *) t, len);
+ *t0p += STRUCT_SIZE_OF (lldp_tlv_t, head) + len;
+}
+
+static void
+lldp_add_port_id (const vnet_hw_interface_t * hw, u8 ** t0p)
+{
+ lldp_port_id_tlv_t *t = (lldp_port_id_tlv_t *) * t0p;
+
+ lldp_tlv_set_code ((lldp_tlv_t *) t, LLDP_TLV_NAME (port_id));
+ t->subtype = LLDP_PORT_ID_SUBTYPE_NAME (intf_name);
+
+ const size_t name_len = vec_len (hw->name);
+ clib_memcpy (&t->id, hw->name, name_len);
+ const size_t len = STRUCT_SIZE_OF (lldp_port_id_tlv_t, subtype) + name_len;
+ lldp_tlv_set_length ((lldp_tlv_t *) t, len);
+ *t0p += STRUCT_SIZE_OF (lldp_tlv_t, head) + len;
+}
+
+static void
+lldp_add_ttl (const lldp_main_t * lm, u8 ** t0p, int shutdown)
+{
+ lldp_ttl_tlv_t *t = (lldp_ttl_tlv_t *) * t0p;
+ lldp_tlv_set_code ((lldp_tlv_t *) t, LLDP_TLV_NAME (ttl));
+ if (shutdown)
+ {
+ t->ttl = 0;
+ }
+ else
+ {
+ if ((size_t) lm->msg_tx_interval * lm->msg_tx_hold + 1 > (1 << 16) - 1)
+ {
+ t->ttl = htons ((1 << 16) - 1);
+ }
+ else
+ {
+ t->ttl = htons (lm->msg_tx_hold * lm->msg_tx_interval + 1);
+ }
+ }
+ const size_t len = STRUCT_SIZE_OF (lldp_ttl_tlv_t, ttl);
+ lldp_tlv_set_length ((lldp_tlv_t *) t, len);
+ *t0p += STRUCT_SIZE_OF (lldp_tlv_t, head) + len;
+}
+
+static void
+lldp_add_sys_name (const lldp_main_t * lm, u8 ** t0p)
+{
+ const size_t len = vec_len (lm->sys_name);
+ if (len)
+ {
+ lldp_tlv_t *t = (lldp_tlv_t *) * t0p;
+ lldp_tlv_set_code (t, LLDP_TLV_NAME (sys_name));
+ lldp_tlv_set_length (t, len);
+ clib_memcpy (t->v, lm->sys_name, len);
+ *t0p += STRUCT_SIZE_OF (lldp_tlv_t, head) + len;
+ }
+}
+
+static void
+lldp_add_pdu_end (u8 ** t0p)
+{
+ lldp_tlv_t *t = (lldp_tlv_t *) * t0p;
+ lldp_tlv_set_code (t, LLDP_TLV_NAME (pdu_end));
+ lldp_tlv_set_length (t, 0);
+ *t0p += STRUCT_SIZE_OF (lldp_tlv_t, head);
+}
+
+static void
+lldp_add_tlvs (lldp_main_t * lm, vnet_hw_interface_t * hw, u8 ** t0p,
+ int shutdown)
+{
+ lldp_add_chassis_id (hw, t0p);
+ lldp_add_port_id (hw, t0p);
+ lldp_add_ttl (lm, t0p, shutdown);
+ lldp_add_sys_name (lm, t0p);
+ lldp_add_pdu_end (t0p);
+}
+
+/*
+ * send a lldp pkt on an ethernet interface
+ */
+void
+lldp_send_ethernet (lldp_main_t * lm, lldp_intf_t * n, int shutdown)
+{
+ u32 *to_next;
+ ethernet_header_t *h0;
+ vnet_hw_interface_t *hw;
+ u32 bi0;
+ vlib_buffer_t *b0;
+ u8 *t0;
+ vlib_frame_t *f;
+ vlib_main_t *vm = lm->vlib_main;
+ vnet_main_t *vnm = lm->vnet_main;
+
+ /*
+ * see lldp_template_init() to understand what's already painted
+ * into the buffer by the packet template mechanism
+ */
+ h0 = vlib_packet_template_get_packet (vm, &lm->packet_template, &bi0);
+
+ /* Add the interface's ethernet source address */
+ hw = vnet_get_hw_interface (vnm, n->hw_if_index);
+
+ clib_memcpy (h0->src_address, hw->hw_address, vec_len (hw->hw_address));
+
+ u8 *data = ((u8 *) h0) + sizeof (*h0);
+ t0 = data;
+
+ /* add TLVs */
+ lldp_add_tlvs (lm, hw, &t0, shutdown);
+
+ /* Set the outbound packet length */
+ b0 = vlib_get_buffer (vm, bi0);
+ b0->current_length = sizeof (*h0) + t0 - data;
+
+ /* And the outbound interface */
+ vnet_buffer (b0)->sw_if_index[VLIB_TX] = hw->sw_if_index;
+
+ /* And output the packet on the correct interface */
+ f = vlib_get_frame_to_node (vm, hw->output_node_index);
+ to_next = vlib_frame_vector_args (f);
+ to_next[0] = bi0;
+ f->n_vectors = 1;
+
+ vlib_put_frame_to_node (vm, hw->output_node_index, f);
+ n->last_sent = vlib_time_now (vm);
+}
+
+void
+lldp_delete_intf (lldp_main_t * lm, lldp_intf_t * n)
+{
+ if (n)
+ {
+ lldp_unschedule_intf (lm, n);
+ hash_unset (lm->intf_by_hw_if_index, n->hw_if_index);
+ vec_free (n->chassis_id);
+ vec_free (n->port_id);
+ pool_put (lm->intfs, n);
+ }
+}
+
+static clib_error_t *
+lldp_template_init (vlib_main_t * vm)
+{
+ lldp_main_t *lm = &lldp_main;
+
+ /* Create the ethernet lldp packet template */
+ {
+ ethernet_header_t h;
+
+ memset (&h, 0, sizeof (h));
+
+ /*
+ * Send to 01:80:C2:00:00:0E - propagation constrained to a single
+ * physical link - stopped by all type of bridge
+ */
+ h.dst_address[0] = 0x01;
+ h.dst_address[1] = 0x80;
+ h.dst_address[2] = 0xC2;
+ /* h.dst_address[3] = 0x00; (memset) */
+ /* h.dst_address[4] = 0x00; (memset) */
+ h.dst_address[5] = 0x0E;
+
+ /* leave src address blank (fill in at send time) */
+
+ h.type = htons (ETHERNET_TYPE_802_1_LLDP);
+
+ vlib_packet_template_init (vm, &lm->packet_template,
+ /* data */ &h, sizeof (h),
+ /* alloc chunk size */ 8, "lldp-ethernet");
+ }
+
+ return 0;
+}
+
+VLIB_INIT_FUNCTION (lldp_template_init);
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/vnet/vnet/lldp/lldp_protocol.h b/vnet/vnet/lldp/lldp_protocol.h
new file mode 100644
index 00000000000..e641b26e20d
--- /dev/null
+++ b/vnet/vnet/lldp/lldp_protocol.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2011-2016 Cisco and/or its affiliates.
+ * 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 __included_lldp_protocol_h__
+#define __included_lldp_protocol_h__
+/**
+ * @file
+ * @brief LLDP protocol declarations
+ */
+#include <vnet/srp/packet.h>
+
+/*
+ * optional TLV codes.
+ */
+#define foreach_lldp_optional_tlv_type(F) \
+ F (4, port_desc, "Port Description") \
+ F (5, sys_name, "System name") \
+ F (6, sys_desc, "System Description") \
+ F (7, sys_caps, "System Capabilities") \
+ F (8, mgmt_addr, "Management Address") \
+ F (127, org_spec, "Organizationally Specific TLV")
+
+/*
+ * all TLV codes.
+ */
+#define foreach_lldp_tlv_type(F) \
+ F (0, pdu_end, "End of LLDPDU") \
+ F (1, chassis_id, "Chassis ID") \
+ F (2, port_id, "Port ID") \
+ F (3, ttl, "Time To Live") \
+ foreach_lldp_optional_tlv_type (F)
+
+#define LLDP_TLV_NAME(t) LLDP_TLV_##t
+
+typedef enum
+{
+#define F(n, t, s) LLDP_TLV_NAME (t) = n,
+ foreach_lldp_tlv_type (F)
+#undef F
+} lldp_tlv_code_t;
+
+struct lldp_tlv_head
+{
+ u8 byte1; /* contains TLV code in the upper 7 bits + MSB of length */
+ u8 byte2; /* contains the lower bits of length */
+};
+
+/* *INDENT-OFF* */
+typedef CLIB_PACKED (struct {
+ struct lldp_tlv_head head;
+ u8 v[0];
+}) lldp_tlv_t;
+/* *INDENT-ON* */
+
+lldp_tlv_code_t lldp_tlv_get_code (const lldp_tlv_t * tlv);
+void lldp_tlv_set_code (lldp_tlv_t * tlv, lldp_tlv_code_t code);
+u16 lldp_tlv_get_length (const lldp_tlv_t * tlv);
+void lldp_tlv_set_length (lldp_tlv_t * tlv, u16 length);
+
+#define foreach_chassis_id_subtype(F) \
+ F (0, reserved, "Reserved") \
+ F (1, chassis_comp, "Chassis component") \
+ F (2, intf_alias, "Interface alias") \
+ F (3, port_comp, "Port component") \
+ F (4, mac_addr, "MAC address") \
+ F (5, net_addr, "Network address") \
+ F (6, intf_name, "Interface name") \
+ F (7, local, "Locally assigned")
+
+#define LLDP_CHASS_ID_SUBTYPE_NAME(t) LLDP_CHASS_ID_SUBTYPE_##t
+#define LLDP_MIN_CHASS_ID_LEN (1)
+#define LLDP_MAX_CHASS_ID_LEN (255)
+
+typedef enum
+{
+#define F(n, t, s) LLDP_CHASS_ID_SUBTYPE_NAME (t) = n,
+ foreach_chassis_id_subtype (F)
+#undef F
+} lldp_chassis_id_subtype_t;
+
+/* *INDENT-OFF* */
+typedef CLIB_PACKED (struct {
+ struct lldp_tlv_head head;
+ u8 subtype;
+ u8 id[0];
+}) lldp_chassis_id_tlv_t;
+/* *INDENT-ON* */
+
+#define foreach_port_id_subtype(F) \
+ F (0, reserved, "Reserved") \
+ F (1, intf_alias, "Interface alias") \
+ F (2, port_comp, "Port component") \
+ F (3, mac_addr, "MAC address") \
+ F (4, net_addr, "Network address") \
+ F (5, intf_name, "Interface name") \
+ F (6, agent_circuit_id, "Agent circuit ID") \
+ F (7, local, "Locally assigned")
+
+#define LLDP_PORT_ID_SUBTYPE_NAME(t) LLDP_PORT_ID_SUBTYPE_##t
+#define LLDP_MIN_PORT_ID_LEN (1)
+#define LLDP_MAX_PORT_ID_LEN (255)
+
+typedef enum
+{
+#define F(n, t, s) LLDP_PORT_ID_SUBTYPE_NAME (t) = n,
+ foreach_port_id_subtype (F)
+#undef F
+} lldp_port_id_subtype_t;
+
+/* *INDENT-OFF* */
+typedef CLIB_PACKED (struct {
+ struct lldp_tlv_head head;
+ u8 subtype;
+ u8 id[0];
+}) lldp_port_id_tlv_t;
+
+typedef CLIB_PACKED (struct {
+ struct lldp_tlv_head head;
+ u16 ttl;
+}) lldp_ttl_tlv_t;
+/* *INDENT-ON* */
+
+#endif /* __included_lldp_protocol_h__ */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */