linux-cp: Add support for LACP packets 24/42124/6
authorAkeel Ali <[email protected]>
Thu, 9 Jan 2025 17:01:13 +0000 (12:01 -0500)
committerMatthew Smith <[email protected]>
Wed, 26 Feb 2025 15:39:58 +0000 (15:39 +0000)
This patch adds support to mirror LACP packets between host and phy.
It is needed for the Sonic-VPP project to support LAG and allow Sonic
to run LACP in the control plane.

The change has 3 parts:

(1) Converted lip_punt_node to lip_punt_xc_inline, which now supports
the creation of two distinct nodes: lip_punt_node and lip_punt_xc_node.
lip_punt_node retains its original punt functionality.
lip_punt_xc_node supports both punt and x-connect between host & phy.

(2) Add 2 new API (and corresponding CLI) to the linux-cp plugin:
(A) lcp_ethertype_enable ("lcp ethertype enable <ethertype>")
(B) lcp_ethertype_get ("show lcp ethertype")

(3) Add UT to test the new functionality and API/CLI for LACP and LLDP.

Type: improvement

Change-Id: Iab66e3b29351dcf2c471babd4f1ef4bdd19da46e
Signed-off-by: Akeel Ali <[email protected]>
src/plugins/linux-cp/lcp.api
src/plugins/linux-cp/lcp_api.c
src/plugins/linux-cp/lcp_cli.c
src/plugins/linux-cp/lcp_interface.c
src/plugins/linux-cp/lcp_interface.h
src/plugins/linux-cp/lcp_node.c
test/test_linux_cp.py

index e7eaa5a..8b0fdb5 100644 (file)
@@ -177,6 +177,42 @@ autoendian define lcp_itf_pair_details
   option in_progress;
 };
 
+/** \brief Enable linux-cp-punt-xc for a given ethertype
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param ethertype - the ethertype to enable
+*/
+autoreply define lcp_ethertype_enable
+{
+  u32 client_index;
+  u32 context;
+  u16 ethertype;
+};
+
+/** \brief Get the enabled ethertypes for linux-cp-punt-xc
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+*/
+define lcp_ethertype_get
+{
+  u32 client_index;
+  u32 context;
+};
+
+/** \brief Reply to get the enabled ethertypes for linux-cp-punt-xc
+    @param context - sender context, to match reply w/ request
+    @param retval - return code for the request
+    @param count - number of enabled ethertypes
+    @param ethertypes - array of enabled ethertypes
+*/
+define lcp_ethertype_get_reply
+{
+  u32 context;
+  i32 retval;
+  u16 count;
+  u16 ethertypes[count];
+};
+
 service {
   rpc lcp_itf_pair_get returns lcp_itf_pair_get_reply
     stream lcp_itf_pair_details;
index 7442123..0db5029 100644 (file)
@@ -280,6 +280,40 @@ vl_api_lcp_itf_pair_replace_end_t_handler (
   REPLY_MACRO (VL_API_LCP_ITF_PAIR_REPLACE_END_REPLY);
 }
 
+static void
+vl_api_lcp_ethertype_enable_t_handler (vl_api_lcp_ethertype_enable_t *mp)
+{
+  vl_api_lcp_ethertype_enable_reply_t *rmp;
+  int rv;
+
+  rv = lcp_ethertype_enable (mp->ethertype);
+
+  REPLY_MACRO (VL_API_LCP_ETHERTYPE_ENABLE_REPLY);
+}
+
+static void
+vl_api_lcp_ethertype_get_t_handler (vl_api_lcp_ethertype_get_t *mp)
+{
+  vl_api_lcp_ethertype_get_reply_t *rmp;
+  ethernet_type_t *ethertypes = vec_new (ethernet_type_t, 0);
+  u16 count = 0;
+  int rv = 0;
+
+  rv = lcp_ethertype_get_enabled (&ethertypes);
+  if (!rv)
+    count = vec_len (ethertypes);
+
+  REPLY_MACRO3 (VL_API_LCP_ETHERTYPE_GET_REPLY, sizeof (u16) * count, ({
+                 rmp->count = htons (count);
+                 for (int i = 0; i < count; i++)
+                   {
+                     rmp->ethertypes[i] = htons (ethertypes[i]);
+                   }
+               }));
+
+  vec_free (ethertypes);
+}
+
 /*
  * Set up the API message handling tables
  */
index 0dcf600..e89afd2 100644 (file)
@@ -337,6 +337,62 @@ VLIB_CLI_COMMAND (lcp_itf_pair_show_cmd_node, static) = {
   .is_mp_safe = 1,
 };
 
+static clib_error_t *
+lcp_ethertype_enable_cmd (vlib_main_t *vm, unformat_input_t *input,
+                         vlib_cli_command_t *cmd)
+{
+  ethernet_type_t ethertype;
+  int rv;
+
+  if (!unformat (input, "%U", unformat_ethernet_type_host_byte_order,
+                &ethertype))
+    return clib_error_return (0, "Invalid ethertype");
+
+  rv = lcp_ethertype_enable (ethertype);
+  if (rv)
+    return clib_error_return (0, "Failed to enable ethertype (%d)", rv);
+
+  return 0;
+}
+
+VLIB_CLI_COMMAND (lcp_ethertype_enable_command, static) = {
+  .path = "lcp ethertype enable",
+  .short_help =
+    "lcp ethertype enable (<hex_ethertype_num>|<uc_ethertype_name>)",
+  .function = lcp_ethertype_enable_cmd,
+};
+
+static clib_error_t *
+lcp_ethertype_show_cmd (vlib_main_t *vm, unformat_input_t *input,
+                       vlib_cli_command_t *cmd)
+{
+  ethernet_type_t *ethertypes = vec_new (ethernet_type_t, 0);
+  ethernet_type_t *etype;
+  int rv;
+
+  rv = lcp_ethertype_get_enabled (&ethertypes);
+  if (rv)
+    {
+      vec_free (ethertypes);
+      return clib_error_return (0, "Failed to get enabled ethertypes (%d)",
+                               rv);
+    }
+
+  vec_foreach (etype, ethertypes)
+    {
+      vlib_cli_output (vm, "0x%04x", *etype);
+    }
+
+  vec_free (ethertypes);
+  return 0;
+}
+
+VLIB_CLI_COMMAND (lcp_ethertype_show_command, static) = {
+  .path = "show lcp ethertype",
+  .short_help = "show lcp ethertype",
+  .function = lcp_ethertype_show_cmd,
+};
+
 clib_error_t *
 lcp_cli_init (vlib_main_t *vm)
 {
index 9a6b9b1..31864f7 100644 (file)
@@ -1230,6 +1230,53 @@ lcp_itf_pair_link_up_down (vnet_main_t *vnm, u32 hw_if_index, u32 flags)
   return 0;
 }
 
+int
+lcp_ethertype_enable (ethernet_type_t ethertype)
+{
+  ethernet_main_t *em = &ethernet_main;
+  ethernet_type_info_t *eti;
+  vlib_main_t *vm = vlib_get_main ();
+  vlib_node_t *node = vlib_get_node_by_name (vm, (u8 *) "linux-cp-punt-xc");
+
+  if (!node)
+    return VNET_API_ERROR_UNIMPLEMENTED;
+
+  eti = ethernet_get_type_info (em, ethertype);
+  if (!eti)
+    return VNET_API_ERROR_INVALID_VALUE;
+
+  if (eti->node_index != ~0 && eti->node_index != node->index)
+    return VNET_API_ERROR_INVALID_REGISTRATION;
+
+  ethernet_register_input_type (vm, ethertype, node->index);
+  return 0;
+}
+
+int
+lcp_ethertype_get_enabled (ethernet_type_t **ethertypes_vec)
+{
+  ethernet_main_t *em = &ethernet_main;
+  ethernet_type_info_t *eti;
+  vlib_main_t *vm = vlib_get_main ();
+  vlib_node_t *node = vlib_get_node_by_name (vm, (u8 *) "linux-cp-punt-xc");
+
+  if (!ethertypes_vec)
+    return VNET_API_ERROR_INVALID_ARGUMENT;
+
+  if (!node)
+    return VNET_API_ERROR_UNIMPLEMENTED;
+
+  vec_foreach (eti, em->type_infos)
+    {
+      if (eti->node_index == node->index)
+       {
+         vec_add1 (*ethertypes_vec, eti->type);
+       }
+    }
+
+  return 0;
+}
+
 VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION (lcp_itf_pair_link_up_down);
 
 static clib_error_t *
index cfcd392..8cf6d3f 100644 (file)
@@ -18,6 +18,7 @@
 #include <vnet/dpo/dpo.h>
 #include <vnet/adj/adj.h>
 #include <vnet/ip/ip_types.h>
+#include <vnet/ethernet/ethernet.h>
 
 #include <plugins/linux-cp/lcp.h>
 
@@ -198,6 +199,18 @@ void lcp_itf_pair_sync_state (lcp_itf_pair_t *lip);
 void lcp_itf_pair_sync_state_hw (vnet_hw_interface_t *hi);
 void lcp_itf_pair_sync_state_all ();
 
+/**
+ * Enable linux-cp-punt-xc for a given ethertype.
+ * @param ethertype - ethertype to enable
+ */
+int lcp_ethertype_enable (ethernet_type_t ethertype);
+
+/**
+ * Get the list of ethertypes enabled for linux-cp-punt-xc.
+ * @param ethertypes_vec - pointer to a vector to store the list of ethertypes
+ */
+int lcp_ethertype_get_enabled (ethernet_type_t **ethertypes_vec);
+
 /*
  * fd.io coding-style-patch-verification: ON
  *
index 241cc5e..9fa1aa5 100644 (file)
 
 typedef enum
 {
-#define _(sym, str) LIP_PUNT_NEXT_##sym,
+#define _(sym, str) LIP_PUNT_XC_NEXT_##sym,
   foreach_lip_punt
 #undef _
-    LIP_PUNT_N_NEXT,
-} lip_punt_next_t;
+    LIP_PUNT_XC_N_NEXT,
+} lip_punt_xc_next_t;
 
-typedef struct lip_punt_trace_t_
+typedef struct lip_punt_xc_trace_t_
 {
+  bool is_xc;
   u32 phy_sw_if_index;
   u32 host_sw_if_index;
-} lip_punt_trace_t;
+} lip_punt_xc_trace_t;
 
 /* packet trace format function */
 static u8 *
-format_lip_punt_trace (u8 *s, va_list *args)
+format_lip_punt_xc_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 *);
-  lip_punt_trace_t *t = va_arg (*args, lip_punt_trace_t *);
+  lip_punt_xc_trace_t *t = va_arg (*args, lip_punt_xc_trace_t *);
 
-  s =
-    format (s, "lip-punt: %u -> %u", t->phy_sw_if_index, t->host_sw_if_index);
+  if (t->is_xc)
+    {
+      s = format (s, "lip-xc: %u -> %u", t->host_sw_if_index,
+                 t->phy_sw_if_index);
+    }
+  else
+    {
+      s = format (s, "lip-punt: %u -> %u", t->phy_sw_if_index,
+                 t->host_sw_if_index);
+    }
 
   return s;
 }
 
 /**
  * Pass punted packets from the PHY to the HOST.
+ * Conditionally x-connect packets from the HOST to the PHY.
  */
-VLIB_NODE_FN (lip_punt_node)
-(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+static_always_inline u32
+lip_punt_xc_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
+                   vlib_frame_t *frame, bool check_xc)
 {
   u32 n_left_from, *from, *to_next, n_left_to_next;
-  lip_punt_next_t next_index;
+  lip_punt_xc_next_t next_index;
 
   next_index = node->cached_next_index;
   n_left_from = frame->n_vectors;
@@ -89,6 +100,7 @@ VLIB_NODE_FN (lip_punt_node)
          u32 next0 = ~0;
          u32 bi0, lipi0;
          u32 sw_if_index0;
+         bool is_xc0 = 0;
          u8 len0;
 
          bi0 = to_next[0] = from[0];
@@ -97,18 +109,33 @@ VLIB_NODE_FN (lip_punt_node)
          to_next += 1;
          n_left_from -= 1;
          n_left_to_next -= 1;
-         next0 = LIP_PUNT_NEXT_DROP;
+         next0 = LIP_PUNT_XC_NEXT_DROP;
 
          b0 = vlib_get_buffer (vm, bi0);
 
          sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
          lipi0 = lcp_itf_pair_find_by_phy (sw_if_index0);
-         if (PREDICT_FALSE (lipi0 == INDEX_INVALID))
-           goto trace0;
+
+         /*
+          * lip_punt_node: expect sw_if_index0 is phy in an itf pair
+          * lip_punt_xc_node: if sw_if_index0 is not phy, expect it is host
+          */
+         if (!check_xc && (PREDICT_FALSE (lipi0 == INDEX_INVALID)))
+           {
+             goto trace0;
+           }
+         else if (check_xc && (lipi0 == INDEX_INVALID))
+           {
+             is_xc0 = 1;
+             lipi0 = lcp_itf_pair_find_by_host (sw_if_index0);
+             if (PREDICT_FALSE (lipi0 == INDEX_INVALID))
+               goto trace0;
+           }
 
          lip0 = lcp_itf_pair_get (lipi0);
-         next0 = LIP_PUNT_NEXT_IO;
-         vnet_buffer (b0)->sw_if_index[VLIB_TX] = lip0->lip_host_sw_if_index;
+         next0 = LIP_PUNT_XC_NEXT_IO;
+         vnet_buffer (b0)->sw_if_index[VLIB_TX] =
+           is_xc0 ? lip0->lip_phy_sw_if_index : lip0->lip_host_sw_if_index;
 
          if (PREDICT_TRUE (lip0->lip_host_type == LCP_ITF_HOST_TAP))
            {
@@ -129,10 +156,22 @@ VLIB_NODE_FN (lip_punt_node)
        trace0:
          if (PREDICT_FALSE ((b0->flags & VLIB_BUFFER_IS_TRACED)))
            {
-             lip_punt_trace_t *t = vlib_add_trace (vm, node, b0, sizeof (*t));
-             t->phy_sw_if_index = sw_if_index0;
-             t->host_sw_if_index =
-               (lipi0 == INDEX_INVALID) ? ~0 : lip0->lip_host_sw_if_index;
+             lip_punt_xc_trace_t *t =
+               vlib_add_trace (vm, node, b0, sizeof (*t));
+
+             t->is_xc = is_xc0;
+             if (is_xc0)
+               {
+                 t->phy_sw_if_index =
+                   (lipi0 == INDEX_INVALID) ? ~0 : lip0->lip_phy_sw_if_index;
+                 t->host_sw_if_index = sw_if_index0;
+               }
+             else
+               {
+                 t->phy_sw_if_index = sw_if_index0;
+                 t->host_sw_if_index =
+                   (lipi0 == INDEX_INVALID) ? ~0 : lip0->lip_host_sw_if_index;
+               }
            }
 
          vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
@@ -145,16 +184,41 @@ VLIB_NODE_FN (lip_punt_node)
   return frame->n_vectors;
 }
 
+VLIB_NODE_FN (lip_punt_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return (lip_punt_xc_inline (vm, node, frame, false /* xc */));
+}
+
+VLIB_NODE_FN (lip_punt_xc_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return (lip_punt_xc_inline (vm, node, frame, true /* xc */));
+}
+
 VLIB_REGISTER_NODE (lip_punt_node) = {
   .name = "linux-cp-punt",
   .vector_size = sizeof (u32),
-  .format_trace = format_lip_punt_trace,
+  .format_trace = format_lip_punt_xc_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_next_nodes = LIP_PUNT_XC_N_NEXT,
+  .next_nodes = {
+    [LIP_PUNT_XC_NEXT_DROP] = "error-drop",
+    [LIP_PUNT_XC_NEXT_IO] = "interface-output",
+  },
+};
+
+VLIB_REGISTER_NODE (lip_punt_xc_node) = {
+  .name = "linux-cp-punt-xc",
+  .vector_size = sizeof (u32),
+  .format_trace = format_lip_punt_xc_trace,
   .type = VLIB_NODE_TYPE_INTERNAL,
 
-  .n_next_nodes = LIP_PUNT_N_NEXT,
+  .n_next_nodes = LIP_PUNT_XC_N_NEXT,
   .next_nodes = {
-    [LIP_PUNT_NEXT_DROP] = "error-drop",
-    [LIP_PUNT_NEXT_IO] = "interface-output",
+    [LIP_PUNT_XC_NEXT_DROP] = "error-drop",
+    [LIP_PUNT_XC_NEXT_IO] = "interface-output",
   },
 };
 
@@ -190,7 +254,7 @@ VLIB_NODE_FN (lcp_punt_l3_node)
 (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
 {
   u32 n_left_from, *from, *to_next, n_left_to_next;
-  lip_punt_next_t next_index;
+  lip_punt_xc_next_t next_index;
 
   next_index = node->cached_next_index;
   n_left_from = frame->n_vectors;
index ff6023c..d711623 100644 (file)
@@ -6,6 +6,14 @@ import socket
 from scapy.layers.inet import IP, UDP
 from scapy.layers.inet6 import IPv6, Raw
 from scapy.layers.l2 import Ether, ARP
+from scapy.contrib.lacp import LACP
+from scapy.contrib.lldp import (
+    LLDPDUChassisID,
+    LLDPDUPortID,
+    LLDPDUTimeToLive,
+    LLDPDUEndOfLLDPDU,
+    LLDPDU,
+)
 
 from util import reassemble4
 from vpp_object import VppObject
@@ -427,5 +435,131 @@ class TestLinuxCPIpsec(TemplateIpsec, TemplateIpsecItf4, IpsecTun4):
         self.unconfig_network(p)
 
 
[email protected]("linux-cp" in config.excluded_plugins, "Exclude linux-cp plugin tests")
+class TestLinuxCPEthertype(VppTestCase):
+    """Linux CP Ethertype"""
+
+    extra_vpp_plugin_config = [
+        "plugin",
+        "linux_cp_plugin.so",
+        "{",
+        "enable",
+        "}",
+        "plugin",
+        "linux_cp_unittest_plugin.so",
+        "{",
+        "enable",
+        "}",
+        "plugin",
+        "lldp_plugin.so",
+        "{",
+        "disable",
+        "}",
+    ]
+
+    LACP_ETHERTYPE = 0x8809
+    LLDP_ETHERTYPE = 0x88CC
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestLinuxCPEthertype, cls).setUpClass()
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestLinuxCPEthertype, cls).tearDownClass()
+
+    def setUp(self):
+        super(TestLinuxCPEthertype, self).setUp()
+        self.create_pg_interfaces(range(2))
+        for i in self.pg_interfaces:
+            i.admin_up()
+
+        self.host = self.pg0
+        self.phy = self.pg1
+
+        self.pair = VppLcpPair(self, self.phy, self.host).add_vpp_config()
+        self.logger.info(self.vapi.cli("sh lcp"))
+
+    def tearDown(self):
+        self.pair.remove_vpp_config()
+
+        for i in self.pg_interfaces:
+            i.admin_down()
+        super(TestLinuxCPEthertype, self).tearDown()
+
+    def send_packet(self, sender, receiver, ethertype, dst, data, expect_copy=True):
+        packet = Ether(src=sender.remote_mac, dst=dst, type=ethertype) / data
+        if expect_copy:
+            rxs = self.send_and_expect(sender, [packet], receiver)
+            for rx in rxs:
+                self.assertEqual(packet.show2(True), rx.show2(True))
+        else:
+            self.send_and_assert_no_replies(sender, [packet])
+
+    def send_lacp_packet(self, sender, receiver, expect_copy=True):
+        data = LACP(
+            actor_system="00:00:00:00:00:01", partner_system="00:00:00:00:00:02"
+        )
+        self.send_packet(
+            sender,
+            receiver,
+            self.LACP_ETHERTYPE,
+            "01:80:c2:00:00:02",
+            data,
+            expect_copy,
+        )
+
+    def send_lldp_packet(self, sender, receiver, expect_copy=True):
+        data = (
+            LLDPDUChassisID(subtype=4, id="01:02:03:04:05:06")
+            / LLDPDUPortID(subtype=3, id="07:08:09:0a:0b:0c")
+            / LLDPDUTimeToLive(ttl=120)
+            / LLDPDUEndOfLLDPDU()
+        )
+        self.send_packet(
+            sender,
+            receiver,
+            self.LLDP_ETHERTYPE,
+            "01:80:c2:00:00:0e",
+            data,
+            expect_copy,
+        )
+
+    def check_ethertype_enabled(self, ethertype, enabled=True):
+        reply = self.vapi.lcp_ethertype_get()
+        output = self.vapi.cli("show lcp ethertype")
+
+        if enabled:
+            self.assertIn(ethertype, reply.ethertypes)
+            self.assertIn(hex(ethertype), output)
+        else:
+            self.assertNotIn(ethertype, reply.ethertypes)
+            self.assertNotIn(hex(ethertype), output)
+
+    def test_linux_cp_lacp(self):
+        """Linux CP LACP Test"""
+        self.check_ethertype_enabled(self.LACP_ETHERTYPE, enabled=False)
+        self.send_lacp_packet(self.phy, self.host, expect_copy=False)
+        self.send_lacp_packet(self.host, self.phy, expect_copy=False)
+
+        self.vapi.cli("lcp ethertype enable " + str(self.LACP_ETHERTYPE))
+
+        self.check_ethertype_enabled(self.LACP_ETHERTYPE, enabled=True)
+        self.send_lacp_packet(self.phy, self.host, expect_copy=True)
+        self.send_lacp_packet(self.host, self.phy, expect_copy=True)
+
+    def test_linux_cp_lldp(self):
+        """Linux CP LLDP Test"""
+        self.check_ethertype_enabled(self.LLDP_ETHERTYPE, enabled=False)
+        self.send_lldp_packet(self.phy, self.host, expect_copy=False)
+        self.send_lldp_packet(self.host, self.phy, expect_copy=False)
+
+        self.vapi.cli("lcp ethertype enable " + str(self.LLDP_ETHERTYPE))
+
+        self.check_ethertype_enabled(self.LLDP_ETHERTYPE, enabled=True)
+        self.send_lldp_packet(self.phy, self.host, expect_copy=True)
+        self.send_lldp_packet(self.host, self.phy, expect_copy=True)
+
+
 if __name__ == "__main__":
     unittest.main(testRunner=VppTestRunner)