summaryrefslogtreecommitdiffstats
path: root/src/plugins/lldp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/lldp')
-rw-r--r--src/plugins/lldp/CMakeLists.txt32
-rw-r--r--src/plugins/lldp/FEATURE.yaml8
-rw-r--r--src/plugins/lldp/dir.dox18
-rw-r--r--src/plugins/lldp/lldp.api58
-rw-r--r--src/plugins/lldp/lldp.h34
-rw-r--r--src/plugins/lldp/lldp_api.c159
-rw-r--r--src/plugins/lldp/lldp_cli.c731
-rw-r--r--src/plugins/lldp/lldp_doc.md86
-rw-r--r--src/plugins/lldp/lldp_input.c302
-rw-r--r--src/plugins/lldp/lldp_node.c341
-rw-r--r--src/plugins/lldp/lldp_node.h156
-rw-r--r--src/plugins/lldp/lldp_output.c308
-rw-r--r--src/plugins/lldp/lldp_protocol.h142
-rw-r--r--src/plugins/lldp/lldp_test.c147
14 files changed, 2522 insertions, 0 deletions
diff --git a/src/plugins/lldp/CMakeLists.txt b/src/plugins/lldp/CMakeLists.txt
new file mode 100644
index 00000000000..98df0bb0199
--- /dev/null
+++ b/src/plugins/lldp/CMakeLists.txt
@@ -0,0 +1,32 @@
+# Copyright (c) 2020 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.
+
+
+add_vpp_plugin(lldp
+ SOURCES
+ lldp_input.c
+ lldp_node.c
+ lldp_output.c
+ lldp_cli.c
+ lldp_api.c
+
+ API_FILES
+ lldp.api
+
+ INSTALL_HEADERS
+ lldp_protocol.h
+ lldp.h
+
+ API_TEST_SOURCES
+ lldp_test.c
+)
diff --git a/src/plugins/lldp/FEATURE.yaml b/src/plugins/lldp/FEATURE.yaml
new file mode 100644
index 00000000000..feda51ad4de
--- /dev/null
+++ b/src/plugins/lldp/FEATURE.yaml
@@ -0,0 +1,8 @@
+---
+name: Link Layer Discovery Protocol
+maintainer: Klement Sekera <ksekera@cisco.com>
+features:
+ - link layer discovery protocol implementation
+description: "Link Layer Discovery Protocol (LLDP) implementation"
+state: production
+properties: [API, CLI, STATS, MULTITHREAD]
diff --git a/src/plugins/lldp/dir.dox b/src/plugins/lldp/dir.dox
new file mode 100644
index 00000000000..84870f81901
--- /dev/null
+++ b/src/plugins/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 src/vnet/lldp
+ @brief Link Layer Discovery Protocol (LLDP) implementation
+*/
diff --git a/src/plugins/lldp/lldp.api b/src/plugins/lldp/lldp.api
new file mode 100644
index 00000000000..6be060b0dd1
--- /dev/null
+++ b/src/plugins/lldp/lldp.api
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2017 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.
+ */
+
+option version = "2.0.0";
+
+import "vnet/interface_types.api";
+import "vnet/ip/ip_types.api";
+
+/** \brief configure global parameter for LLDP
+ @param client_index - opaque cookie to identify the sender
+ @param context - sender context, to match reply w/ request
+ @param system_name - VPP system name
+ @param tx_hold - multiplier for tx_interval when setting time-to-live (TTL)
+ value in the LLDP packets
+ @param tx_interval - time interval, in seconds, between each LLDP frames
+*/
+autoreply define lldp_config
+{
+ u32 client_index;
+ u32 context;
+ u32 tx_hold;
+ u32 tx_interval;
+ string system_name[];
+};
+
+/** \brief Interface set LLDP request
+ @param client_index - opaque cookie to identify the sender
+ @param context - sender context, to match reply w/ request
+ @param sw_if_index - interface for which to enable/disable LLDP
+ @param mgmt_ip4_addr - management ip4 address of the interface
+ @param mgmt_ip6_addr - management ip6 address of the interface
+ @param mgmt_oid - OID(Object Identifier) of the interface
+ @param enable - if non-zero enable, else disable
+ @param port_desc - local port description
+*/
+autoreply define sw_interface_set_lldp
+{
+ u32 client_index;
+ u32 context;
+ vl_api_interface_index_t sw_if_index;
+ vl_api_ip4_address_t mgmt_ip4;
+ vl_api_ip6_address_t mgmt_ip6;
+ u8 mgmt_oid[128];
+ bool enable [default=true];
+ string port_desc[];
+};
diff --git a/src/plugins/lldp/lldp.h b/src/plugins/lldp/lldp.h
new file mode 100644
index 00000000000..df03dcea6ce
--- /dev/null
+++ b/src/plugins/lldp/lldp.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2017 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 external definition
+ */
+#ifndef __included_lldp_h__
+#define __included_lldp_h__
+
+typedef enum lldp_cfg_err
+{
+ lldp_ok,
+ lldp_not_supported,
+ lldp_invalid_arg,
+} lldp_cfg_err_t;
+
+lldp_cfg_err_t lldp_cfg_intf_set (u32 hw_if_index, u8 ** port_desc,
+ u8 **mgmt_ip4, u8 **mgmt_ip6, u8 **mgmt_oid, int enable);
+lldp_cfg_err_t lldp_cfg_set (u8 ** host, int hold_time, int tx_interval);
+
+
+#endif /* __included_lldp_h__ */
diff --git a/src/plugins/lldp/lldp_api.c b/src/plugins/lldp/lldp_api.c
new file mode 100644
index 00000000000..69eab6949c4
--- /dev/null
+++ b/src/plugins/lldp/lldp_api.c
@@ -0,0 +1,159 @@
+/*
+ *------------------------------------------------------------------
+ * lldp_api.c - lldp api
+ *
+ * Copyright (c) 2017 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.
+ *------------------------------------------------------------------
+ */
+
+#include <vnet/vnet.h>
+#include <vlibmemory/api.h>
+
+#include <vnet/interface.h>
+#include <vnet/api_errno.h>
+#include <lldp/lldp.h>
+
+#include <vnet/ip/ip4_packet.h>
+#include <vnet/ip/ip6_packet.h>
+#include <vnet/ip/ip_types_api.h>
+
+/* define message IDs */
+#include <vnet/format_fns.h>
+#include <lldp/lldp.api_enum.h>
+#include <lldp/lldp.api_types.h>
+
+/**
+ * Base message ID fot the plugin
+ */
+static u32 lldp_base_msg_id;
+#define REPLY_MSG_ID_BASE lldp_base_msg_id
+
+#include <vlibapi/api_helper_macros.h>
+
+static void
+vl_api_lldp_config_t_handler (vl_api_lldp_config_t * mp)
+{
+ vl_api_lldp_config_reply_t *rmp;
+ int rv = 0;
+ u8 *sys_name = 0;
+
+ sys_name = vl_api_from_api_to_new_vec (mp, &mp->system_name);
+
+ if (lldp_cfg_set (&sys_name, ntohl (mp->tx_hold), ntohl (mp->tx_interval))
+ != lldp_ok)
+ {
+ vec_free (sys_name);
+ rv = VNET_API_ERROR_INVALID_VALUE;
+ }
+
+ REPLY_MACRO (VL_API_LLDP_CONFIG_REPLY);
+}
+
+static void
+vl_api_sw_interface_set_lldp_t_handler (vl_api_sw_interface_set_lldp_t * mp)
+{
+ vl_api_sw_interface_set_lldp_reply_t *rmp;
+ int rv = 0;
+ u8 *mgmt_oid = 0, *mgmt_ip4 = 0, *mgmt_ip6 = 0;
+ char *port_desc = 0;
+ u8 no_data[128] = { 0 };
+ ip4_address_t ip4;
+ ip6_address_t ip6;
+
+ if (vl_api_string_len (&mp->port_desc) > 0)
+ {
+ port_desc = vl_api_from_api_to_new_c_string (&mp->port_desc);
+ }
+
+ ip4_address_decode (mp->mgmt_ip4, &ip4);
+
+ if (ip4.as_u32 != 0)
+ {
+ vec_validate (mgmt_ip4, sizeof (ip4_address_t) - 1);
+ clib_memcpy (mgmt_ip4, &ip4, vec_len (mgmt_ip4));
+ }
+
+ ip6_address_decode (mp->mgmt_ip6, &ip6);
+
+ if (!ip6_address_is_zero (&ip6))
+ {
+ vec_validate (mgmt_ip6, sizeof (ip6_address_t) - 1);
+ clib_memcpy (mgmt_ip6, &ip6, vec_len (mgmt_ip6));
+ }
+
+ if (memcmp (mp->mgmt_oid, no_data, strlen ((char *) mp->mgmt_oid)) != 0)
+ {
+ vec_validate (mgmt_oid, strlen ((char *) mp->mgmt_oid) - 1);
+ strncpy ((char *) mgmt_oid, (char *) mp->mgmt_oid, vec_len (mgmt_oid));
+ }
+
+ VALIDATE_SW_IF_INDEX (mp);
+
+ if (lldp_cfg_intf_set (ntohl (mp->sw_if_index), (u8 **) & port_desc,
+ &mgmt_ip4, &mgmt_ip6, &mgmt_oid,
+ mp->enable) != lldp_ok)
+ {
+ vec_free (port_desc);
+ vec_free (mgmt_ip4);
+ vec_free (mgmt_ip6);
+ vec_free (mgmt_oid);
+ rv = VNET_API_ERROR_INVALID_VALUE;
+ }
+
+ BAD_SW_IF_INDEX_LABEL;
+
+ REPLY_MACRO (VL_API_SW_INTERFACE_SET_LLDP_REPLY);
+}
+
+
+/*
+ * * lldp_api_hookup
+ * * Add vpe's API message handlers to the table.
+ * * vlib has already mapped shared memory and
+ * * added the client registration handlers.
+ * * See .../vlib-api/vlibmemory/memclnt_vlib.c:memclnt_process()
+ * */
+#include <lldp/lldp.api.c>
+
+static clib_error_t *
+lldp_api_hookup (vlib_main_t * vm)
+{
+ /*
+ * Set up the (msg_name, crc, message-id) table
+ */
+ lldp_base_msg_id = setup_message_id_table ();
+
+ return 0;
+}
+
+VLIB_API_INIT_FUNCTION (lldp_api_hookup);
+
+#include <vlib/unix/plugin.h>
+#include <vpp/app/version.h>
+
+/* *INDENT-OFF* */
+VLIB_PLUGIN_REGISTER () = {
+ .version = VPP_BUILD_VER,
+ .description = "Link Layer Discovery Protocol (LLDP)",
+};
+/* *INDENT-ON* */
+
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/lldp/lldp_cli.c b/src/plugins/lldp/lldp_cli.c
new file mode 100644
index 00000000000..2a0891a2e48
--- /dev/null
+++ b/src/plugins/lldp/lldp_cli.c
@@ -0,0 +1,731 @@
+/*
+ * 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/ethernet/ethernet.h>
+#include <vnet/ip/ip.h>
+#include <lldp/lldp.h>
+#include <lldp/lldp_node.h>
+
+#ifndef ETHER_ADDR_LEN
+#include <net/ethernet.h>
+#endif
+
+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;
+}
+
+lldp_cfg_err_t
+lldp_cfg_intf_set (u32 hw_if_index, u8 ** port_desc, u8 ** mgmt_ip4,
+ u8 ** mgmt_ip6, u8 ** mgmt_oid, 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;
+ const ethernet_interface_t *eif;
+
+ if (pool_is_free_index (vnm->interface_main.hw_interfaces, hw_if_index))
+ {
+ return lldp_invalid_arg;
+ }
+
+ hi = vnet_get_hw_interface (vnm, hw_if_index);
+ 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 lldp_ok;
+ }
+ n = lldp_create_intf (lm, hw_if_index);
+
+ if (port_desc && *port_desc)
+ {
+ n->port_desc = *port_desc;
+ *port_desc = NULL;
+ }
+
+ if (mgmt_ip4 && *mgmt_ip4)
+ {
+ n->mgmt_ip4 = *mgmt_ip4;
+ *mgmt_ip4 = NULL;
+ }
+
+ if (mgmt_ip6 && *mgmt_ip6)
+ {
+ n->mgmt_ip6 = *mgmt_ip6;
+ *mgmt_ip6 = NULL;
+ }
+
+ if (mgmt_oid && *mgmt_oid)
+ {
+ n->mgmt_oid = *mgmt_oid;
+ *mgmt_oid = NULL;
+ }
+
+ 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 lldp_ok;
+}
+
+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 sw_if_index = (u32) ~ 0;
+ int enable = 1;
+ u8 *port_desc = NULL;
+ u8 *mgmt_ip4 = NULL, *mgmt_ip6 = NULL, *mgmt_oid = NULL;
+ ip4_address_t ip4_addr;
+ ip6_address_t ip6_addr;
+
+ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (input, "sw_if_index %d", &sw_if_index))
+ ;
+ if (unformat
+ (input, "%U", unformat_vnet_sw_interface, vnm, &sw_if_index))
+ ;
+ else if (unformat (input, "disable"))
+ enable = 0;
+ else if (unformat (input, "port-desc %s", &port_desc))
+ ;
+ else
+ if (unformat (input, "mgmt-ip4 %U", unformat_ip4_address, &ip4_addr))
+ {
+ vec_validate (mgmt_ip4, sizeof (ip4_address_t) - 1);
+ clib_memcpy (mgmt_ip4, &ip4_addr, vec_len (mgmt_ip4));
+ }
+ else
+ if (unformat (input, "mgmt-ip6 %U", unformat_ip6_address, &ip6_addr))
+ {
+ vec_validate (mgmt_ip6, sizeof (ip6_address_t) - 1);
+ clib_memcpy (mgmt_ip6, &ip6_addr, vec_len (mgmt_ip6));
+ }
+ else if (unformat (input, "mgmt-oid %s", &mgmt_oid))
+ ;
+ else
+ break;
+ }
+
+ if (sw_if_index == (u32) ~ 0)
+ return clib_error_return (0, "Interface name is invalid!");
+
+ return lldp_cfg_err_to_clib_err (lldp_cfg_intf_set (sw_if_index,
+ &port_desc, &mgmt_ip4,
+ &mgmt_ip6, &mgmt_oid,
+ enable));
+}
+
+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
+ {
+ break;
+ }
+ }
+ 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> | sw_if_index <idx>"
+ " [port-desc <string>] [mgmt-ip4 <string>]"
+ " [mgmt-ip6 <string>] [mgmt-oid <string>] [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);
+
+ s = format(s, "\nLocal Interface name: %s\n"
+ "Local Port Description: %s\n",
+ hw->name, n->port_desc);
+ if (n->mgmt_ip4)
+ {
+ s = format (s, "Local Management address: %U\n",
+ format_ip4_address, n->mgmt_ip4, vec_len (n->mgmt_ip4));
+ }
+
+ if (n->mgmt_ip6)
+ {
+ s = format (s, "Local Management address IPV6: %U\n",
+ format_ip6_address, n->mgmt_ip6, vec_len (n->mgmt_ip6));
+ }
+
+ if (n->mgmt_oid)
+ {
+ s = format (s, "Local Management address OID: %U\n",
+ format_ascii_bytes, n->mgmt_oid, vec_len (n->mgmt_oid));
+ }
+
+ /* Interface shutdown */
+ if (!(sw->flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP))
+ {
+ s = format(s, "Interface/peer state: interface down\n"
+ "Last packet sent: %U\n",
+ format_time_ago, n->last_sent, now);
+ }
+ else if (now < n->last_heard + n->ttl)
+ {
+ s = format(s,
+ "Interface/peer state: active\n"
+ "Peer chassis ID: %U\nRemote port ID: %U\n"
+ "Last packet sent: %U\nLast packet received: %U\n",
+ 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,
+ "Interface/peer state: inactive(timeout)\n"
+ "Last known peer chassis ID: %U\n"
+ "Last known peer port ID: %U\nLast packet sent: %U\n"
+ "Last packet received: %U\n",
+ 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/src/plugins/lldp/lldp_doc.md b/src/plugins/lldp/lldp_doc.md
new file mode 100644
index 00000000000..717de898c4e
--- /dev/null
+++ b/src/plugins/lldp/lldp_doc.md
@@ -0,0 +1,86 @@
+# VPP Link Layer Discovery Protocol (LLDP) implementation {#lldp_doc}
+
+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> | if_index <idx> [port-desc <string>] [disable]
+
+interface: the name of the interface for which to enable/disable LLDP
+if_index: sw interface index can be used if interface name is not used.
+port-desc: port description
+disable: LLDP feature can be enabled or disabled per interface.
+
+### 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 with port description
+
+set interface lldp TenGigabitEthernet5/0/1 port-desc vtf:eth0
+
+
+### 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/src/plugins/lldp/lldp_input.c b/src/plugins/lldp/lldp_input.c
new file mode 100644
index 00000000000..327ef10f9de
--- /dev/null
+++ b/src/plugins/lldp/lldp_input.c
@@ -0,0 +1,302 @@
+/*
+ * 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 <lldp/lldp_node.h>
+#include <lldp/lldp_protocol.h>
+#include <vlibmemory/api.h>
+
+typedef struct
+{
+ u32 hw_if_index;
+ u8 chassis_id_len;
+ u8 chassis_id_subtype;
+ u8 portid_len;
+ u8 portid_subtype;
+ u16 ttl;
+ u8 data[0]; /* this contains both chassis id (chassis_id_len bytes) and port
+ id (portid_len bytes) */
+} lldp_intf_update_t;
+
+static void
+lldp_rpc_update_peer_cb (const lldp_intf_update_t * a)
+{
+ ASSERT (vlib_get_thread_index () == 0);
+
+ lldp_intf_t *n = lldp_get_intf (&lldp_main, a->hw_if_index);
+ if (!n)
+ {
+ /* LLDP turned off for this interface, ignore the update */
+ return;
+ }
+ const u8 *chassis_id = a->data;
+ const u8 *portid = a->data + a->chassis_id_len;
+
+ if (n->chassis_id)
+ {
+ _vec_len (n->chassis_id) = 0;
+ }
+ vec_add (n->chassis_id, chassis_id, a->chassis_id_len);
+ n->chassis_id_subtype = a->chassis_id_subtype;
+ if (n->port_id)
+ {
+ _vec_len (n->port_id) = 0;
+ }
+ vec_add (n->port_id, portid, a->portid_len);
+ n->port_id_subtype = a->portid_subtype;
+ n->ttl = a->ttl;
+ n->last_heard = vlib_time_now (lldp_main.vlib_main);
+}
+
+static void
+lldp_rpc_update_peer (u32 hw_if_index, const u8 * chid, u8 chid_len,
+ u8 chid_subtype, const u8 * portid,
+ u8 portid_len, u8 portid_subtype, u16 ttl)
+{
+ const size_t data_size =
+ sizeof (lldp_intf_update_t) + chid_len + portid_len;
+ u8 data[data_size];
+ lldp_intf_update_t *u = (lldp_intf_update_t *) data;
+ u->hw_if_index = hw_if_index;
+ u->chassis_id_len = chid_len;
+ u->chassis_id_subtype = chid_subtype;
+ u->ttl = ttl;
+ u->portid_len = portid_len;
+ u->portid_subtype = portid_subtype;
+ clib_memcpy (u->data, chid, chid_len);
+ clib_memcpy (u->data + chid_len, portid, portid_len);
+ vl_api_rpc_call_main_thread (lldp_rpc_update_peer_cb, data, data_size);
+}
+
+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 (u32 hw_if_index, const lldp_tlv_t * pkt)
+{
+ const lldp_tlv_t *tlv = pkt;
+
+#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;
+ }
+ 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_rpc_update_peer (hw_if_index, chid, chid_len, chid_subtype, portid,
+ portid_len, portid_subtype, 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);
+ clib_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 */
+ vnet_sw_interface_t *sw_interface = vnet_get_sw_interface (lm->vnet_main,
+ vnet_buffer
+ (b0)->sw_if_index
+ [VLIB_RX]);
+ lldp_intf_t *n = lldp_get_intf (lm, sw_interface->hw_if_index);
+
+ if (!n)
+ {
+ /* lldp disabled on this interface, we're done */
+ return LLDP_ERROR_DISABLED;
+ }
+
+ /* Actually scan the packet */
+ e = lldp_packet_scan (sw_interface->hw_if_index,
+ vlib_buffer_get_current (b0));
+
+ 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/src/plugins/lldp/lldp_node.c b/src/plugins/lldp/lldp_node.c
new file mode 100644
index 00000000000..dbb54af91f0
--- /dev/null
+++ b/src/plugins/lldp/lldp_node.c
@@ -0,0 +1,341 @@
+/*
+ * 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 <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, snapshot 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_fast (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 timeout */
+ 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)
+{
+ const int idx = n - lm->intfs;
+ u32 v;
+ vec_foreach_index (v, lm->intfs_timeouts)
+ {
+ if (lm->intfs_timeouts[v] == idx)
+ {
+ /* already scheduled */
+ return;
+ }
+ }
+ 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/src/plugins/lldp/lldp_node.h b/src/plugins/lldp/lldp_node.h
new file mode 100644
index 00000000000..887a70f137f
--- /dev/null
+++ b/src/plugins/lldp/lldp_node.h
@@ -0,0 +1,156 @@
+/*
+ * 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 <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;
+
+ /* Local info */
+ u8 *port_desc;
+
+ /* management ipv4 address */
+ u8 *mgmt_ip4;
+
+ /* management ipv6 address */
+ u8 *mgmt_ip6;
+
+ /* management object identifier */
+ u8 *mgmt_oid;
+} 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)
+
+extern 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;
+
+typedef 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/src/plugins/lldp/lldp_output.c b/src/plugins/lldp/lldp_output.c
new file mode 100644
index 00000000000..dc03faf4d5c
--- /dev/null
+++ b/src/plugins/lldp/lldp_output.c
@@ -0,0 +1,308 @@
+/*
+ * 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 <lldp/lldp_node.h>
+
+static void
+lldp_build_mgmt_addr_tlv (u8 ** t0p, u8 subtype, u8 addr_len, u8 * addr,
+ u32 if_index, u8 oid_len, u8 * oid)
+{
+ lldp_tlv_t *t = (lldp_tlv_t *) * t0p;
+
+ lldp_tlv_set_code (t, LLDP_TLV_NAME (mgmt_addr));
+ t->v[0] = addr_len + 1; /* address string length */
+ t->v[1] = subtype; /* address subtype */
+ clib_memcpy_fast (&(t->v[2]), addr, addr_len); /* address */
+ t->v[addr_len + 2] = 2; /* interface numbering subtype: ifIndex */
+ t->v[addr_len + 3] = (if_index >> 24) & 0xFF; /* interface number */
+ t->v[addr_len + 4] = (if_index >> 16) & 0xFF;
+ t->v[addr_len + 5] = (if_index >> 8) & 0xFF;
+ t->v[addr_len + 6] = (if_index >> 0) & 0xFF;
+ t->v[addr_len + 7] = oid_len; /* OID string length */
+
+ if (oid_len > 0)
+ clib_memcpy_fast ((u8 *) & (t->v[addr_len + 8]), oid, oid_len);
+
+ lldp_tlv_set_length (t, addr_len + oid_len + 8);
+ *t0p += STRUCT_SIZE_OF (lldp_tlv_t, head) + addr_len + oid_len + 8;
+}
+
+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_fast (&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_fast (&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_port_desc (const lldp_main_t * lm, lldp_intf_t * n, u8 ** t0p)
+{
+ const size_t len = vec_len (n->port_desc);
+ if (len)
+ {
+ lldp_tlv_t *t = (lldp_tlv_t *) * t0p;
+ lldp_tlv_set_code (t, LLDP_TLV_NAME (port_desc));
+ lldp_tlv_set_length (t, len);
+ clib_memcpy_fast (t->v, n->port_desc, 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_fast (t->v, lm->sys_name, len);
+ *t0p += STRUCT_SIZE_OF (lldp_tlv_t, head) + len;
+ }
+}
+
+static void
+lldp_add_mgmt_addr (const lldp_intf_t * n, const vnet_hw_interface_t * hw,
+ u8 ** t0p)
+{
+ const size_t len_ip4 = vec_len (n->mgmt_ip4);
+ const size_t len_ip6 = vec_len (n->mgmt_ip6);
+
+ if (!(len_ip4 | len_ip6))
+ {
+ /*
+ If no management address is configured, the interface port's MAC
+ address is sent in one TLV.
+ */
+
+ lldp_build_mgmt_addr_tlv (t0p, 1, /* address subtype: Ipv4 */
+ 6, /* address string length */
+ hw->hw_address, /* address */
+ hw->hw_if_index, /* if index */
+ vec_len (n->mgmt_oid), /* OID length */
+ n->mgmt_oid); /* OID */
+ return;
+ }
+
+ if (len_ip4)
+ {
+ lldp_build_mgmt_addr_tlv (t0p, 1, /* address subtype: Ipv4 */
+ len_ip4, /* address string length */
+ n->mgmt_ip4, /* address */
+ hw->hw_if_index, /* if index */
+ vec_len (n->mgmt_oid), /* OID length */
+ n->mgmt_oid); /* OID */
+ }
+
+ if (len_ip6)
+ {
+ lldp_build_mgmt_addr_tlv (t0p, 2, /* address subtype: Ipv6 */
+ len_ip6, /* address string length */
+ n->mgmt_ip6, /* address */
+ hw->hw_if_index, /* if index */
+ vec_len (n->mgmt_oid), /* OID length */
+ n->mgmt_oid); /* OID */
+ }
+}
+
+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_intf_t * n)
+{
+ lldp_add_chassis_id (hw, t0p);
+ lldp_add_port_id (hw, t0p);
+ lldp_add_ttl (lm, t0p, shutdown);
+ lldp_add_port_desc (lm, n, t0p);
+ lldp_add_sys_name (lm, t0p);
+ lldp_add_mgmt_addr (n, hw, 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);
+
+ if (!h0)
+ return;
+
+ /* Add the interface's ethernet source address */
+ hw = vnet_get_hw_interface (vnm, n->hw_if_index);
+
+ clib_memcpy_fast (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, n);
+
+ /* 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);
+ vec_free (n->port_desc);
+ vec_free (n->mgmt_ip4);
+ vec_free (n->mgmt_ip6);
+ vec_free (n->mgmt_oid);
+ 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;
+
+ clib_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; (clib_memset) */
+ /* h.dst_address[4] = 0x00; (clib_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/src/plugins/lldp/lldp_protocol.h b/src/plugins/lldp/lldp_protocol.h
new file mode 100644
index 00000000000..e641b26e20d
--- /dev/null
+++ b/src/plugins/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:
+ */
diff --git a/src/plugins/lldp/lldp_test.c b/src/plugins/lldp/lldp_test.c
new file mode 100644
index 00000000000..661487c7835
--- /dev/null
+++ b/src/plugins/lldp/lldp_test.c
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2015 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.
+ */
+
+#include <vat/vat.h>
+#include <vlibapi/api.h>
+#include <vlibmemory/api.h>
+#include <vppinfra/error.h>
+
+#include <vnet/ip/ip.h>
+#include <vnet/ip/ip_format_fns.h>
+#include <vnet/ethernet/ethernet_format_fns.h>
+
+/* define message IDs */
+#include <lldp/lldp.api_enum.h>
+#include <lldp/lldp.api_types.h>
+
+typedef struct
+{
+ /* API message ID base */
+ u16 msg_id_base;
+ vat_main_t *vat_main;
+} lldp_test_main_t;
+
+lldp_test_main_t lldp_test_main;
+
+#define __plugin_msg_base lldp_test_main.msg_id_base
+#include <vlibapi/vat_helper_macros.h>
+
+/* Macro to finish up custom dump fns */
+#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__)
+#define FINISH \
+ vec_add1 (s, 0); \
+ vl_print (handle, (char *)s); \
+ vec_free (s); \
+ return handle;
+
+static int
+api_lldp_config (vat_main_t * vam)
+{
+ unformat_input_t *i = vam->input;
+ vl_api_lldp_config_t *mp;
+ int tx_hold = 0;
+ int tx_interval = 0;
+ u8 *sys_name = NULL;
+ int ret;
+
+ while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (i, "system-name %s", &sys_name))
+ ;
+ else if (unformat (i, "tx-hold %d", &tx_hold))
+ ;
+ else if (unformat (i, "tx-interval %d", &tx_interval))
+ ;
+ else
+ {
+ clib_warning ("parse error '%U'", format_unformat_error, i);
+ return -99;
+ }
+ }
+
+ vec_add1 (sys_name, 0);
+
+ M (LLDP_CONFIG, mp);
+ mp->tx_hold = htonl (tx_hold);
+ mp->tx_interval = htonl (tx_interval);
+ vl_api_vec_to_api_string (sys_name, &mp->system_name);
+ vec_free (sys_name);
+
+ S (mp);
+ W (ret);
+ return ret;
+}
+
+static int
+api_sw_interface_set_lldp (vat_main_t * vam)
+{
+ unformat_input_t *i = vam->input;
+ vl_api_sw_interface_set_lldp_t *mp;
+ u32 sw_if_index = ~0;
+ u32 enable = 1;
+ u8 *port_desc = NULL, *mgmt_oid = NULL;
+ ip4_address_t ip4_addr;
+ ip6_address_t ip6_addr;
+ int ret;
+
+ clib_memset (&ip4_addr, 0, sizeof (ip4_addr));
+ clib_memset (&ip6_addr, 0, sizeof (ip6_addr));
+
+ while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (i, "disable"))
+ enable = 0;
+ else
+ if (unformat (i, "%U", unformat_sw_if_index, vam, &sw_if_index))
+ ;
+ else if (unformat (i, "sw_if_index %d", &sw_if_index))
+ ;
+ else if (unformat (i, "port-desc %s", &port_desc))
+ ;
+ else if (unformat (i, "mgmt-ip4 %U", unformat_ip4_address, &ip4_addr))
+ ;
+ else if (unformat (i, "mgmt-ip6 %U", unformat_ip6_address, &ip6_addr))
+ ;
+ else if (unformat (i, "mgmt-oid %s", &mgmt_oid))
+ ;
+ else
+ break;
+ }
+
+ if (sw_if_index == ~0)
+ {
+ errmsg ("missing interface name or sw_if_index");
+ return -99;
+ }
+
+ /* Construct the API message */
+ vec_add1 (port_desc, 0);
+ vec_add1 (mgmt_oid, 0);
+ M (SW_INTERFACE_SET_LLDP, mp);
+ mp->sw_if_index = ntohl (sw_if_index);
+ mp->enable = enable;
+ vl_api_vec_to_api_string (port_desc, &mp->port_desc);
+ clib_memcpy (mp->mgmt_oid, mgmt_oid, vec_len (mgmt_oid));
+ clib_memcpy (mp->mgmt_ip4, &ip4_addr, sizeof (ip4_addr));
+ clib_memcpy (mp->mgmt_ip6, &ip6_addr, sizeof (ip6_addr));
+ vec_free (port_desc);
+ vec_free (mgmt_oid);
+
+ S (mp);
+ W (ret);
+ return ret;
+}
+
+#include <lldp/lldp.api_test.c>