VPP-344 Add LLDP feature 52/2852/3
authorKlement Sekera <ksekera@cisco.com>
Tue, 13 Sep 2016 10:27:08 +0000 (12:27 +0200)
committerDamjan Marion <dmarion.lists@gmail.com>
Tue, 13 Sep 2016 16:05:04 +0000 (16:05 +0000)
Change-Id: Iac96773e7f9028c0f09c3388893e69f67177439d
Signed-off-by: Klement Sekera <ksekera@cisco.com>
vnet/Makefile.am
vnet/vnet/lldp/dir.dox [new file with mode: 0644]
vnet/vnet/lldp/lldp.md [new file with mode: 0644]
vnet/vnet/lldp/lldp_cli.c [new file with mode: 0644]
vnet/vnet/lldp/lldp_input.c [new file with mode: 0644]
vnet/vnet/lldp/lldp_node.c [new file with mode: 0644]
vnet/vnet/lldp/lldp_node.h [new file with mode: 0644]
vnet/vnet/lldp/lldp_output.c [new file with mode: 0644]
vnet/vnet/lldp/lldp_protocol.h [new file with mode: 0644]

index 1c28ebc..1c47c65 100644 (file)
@@ -229,6 +229,18 @@ libvnet_la_SOURCES +=                              \
 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"
 ########################################
diff --git a/vnet/vnet/lldp/dir.dox b/vnet/vnet/lldp/dir.dox
new file mode 100644 (file)
index 0000000..6aa45f7
--- /dev/null
@@ -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 (file)
index 0000000..55de8f0
--- /dev/null
@@ -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 (file)
index 0000000..45f688c
--- /dev/null
@@ -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 (file)
index 0000000..5a56553
--- /dev/null
@@ -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 (file)
index 0000000..2eb27e0
--- /dev/null
@@ -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 (file)
index 0000000..39140f2
--- /dev/null
@@ -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 (file)
index 0000000..6cb2627
--- /dev/null
@@ -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 (file)
index 0000000..e641b26
--- /dev/null
@@ -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:
+ */