classify: honor pcap interface filter also when classify filter is used 65/31465/8
authorBenoît Ganne <bganne@cisco.com>
Fri, 26 Feb 2021 12:47:41 +0000 (13:47 +0100)
committerDave Barach <openvpp@barachs.net>
Sat, 17 Apr 2021 11:55:33 +0000 (11:55 +0000)
Type: fix

Change-Id: Ic32550ee9c5d76d232d8b67a7810611f6c8b9177
Signed-off-by: Benoît Ganne <bganne@cisco.com>
src/vnet/classify/pcap_classify.h [new file with mode: 0644]
src/vnet/ethernet/node.c
src/vnet/interface_output.c
test/test_trace_filter.py

diff --git a/src/vnet/classify/pcap_classify.h b/src/vnet/classify/pcap_classify.h
new file mode 100644 (file)
index 0000000..1f1c38f
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * pcap_classify.h - Use the classifier to decide if a packet is captured
+ *
+ * Copyright (c) 2021 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 <vlib/vlib.h>
+#include <vnet/vnet.h>
+#include <vnet/classify/vnet_classify.h>
+#include <vnet/classify/trace_classify.h>
+
+/** @file pcap_classify.h
+ * Use the vpp classifier to decide whether to capture packets
+ */
+
+/** @brief vnet_is_packet_pcaped
+ * @param vlib_buffer_t *b - packet to capture
+ * @return 0 => no capture, 1 => capture
+ */
+
+static_always_inline int
+vnet_is_packet_pcaped (vnet_pcap_t *pp, vlib_buffer_t *b, u32 sw_if_index)
+{
+  const u32 pcap_sw_if_index = pp->pcap_sw_if_index;
+  const u32 filter_classify_table_index = pp->filter_classify_table_index;
+
+  if (pcap_sw_if_index != 0)
+    {
+      if (~0 == sw_if_index)
+       sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX];
+      if (pcap_sw_if_index != sw_if_index)
+       return 0; /* wrong interface, skip */
+    }
+
+  if (filter_classify_table_index != ~0 &&
+      vnet_is_packet_traced_inline (b, filter_classify_table_index,
+                                   0 /* full classify */) != 1)
+    return 0; /* not matching the filter, skip */
+
+  return 1; /* success */
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
index c31e222..88b4a70 100644 (file)
@@ -44,7 +44,7 @@
 #include <vnet/devices/pipe/pipe.h>
 #include <vppinfra/sparse_vec.h>
 #include <vnet/l2/l2_bvi.h>
-#include <vnet/classify/trace_classify.h>
+#include <vnet/classify/pcap_classify.h>
 
 #define foreach_ethernet_input_next            \
   _ (PUNT, "error-punt")                       \
@@ -1169,38 +1169,13 @@ ethernet_input_trace (vlib_main_t * vm, vlib_node_runtime_t * node,
       n_left = from_frame->n_vectors;
       while (n_left > 0)
        {
-         int classify_filter_result;
          vlib_buffer_t *b0;
          bi0 = from[0];
          from++;
          n_left--;
          b0 = vlib_get_buffer (vm, bi0);
-         if (pp->filter_classify_table_index != ~0)
-           {
-             classify_filter_result =
-               vnet_is_packet_traced_inline
-               (b0, pp->filter_classify_table_index, 0 /* full classify */ );
-             if (classify_filter_result)
-               pcap_add_buffer (&pp->pcap_main, vm, bi0,
-                                pp->max_bytes_per_pkt);
-             continue;
-           }
-
-         if (pp->pcap_sw_if_index == 0 ||
-             pp->pcap_sw_if_index == vnet_buffer (b0)->sw_if_index[VLIB_RX])
-           {
-             vnet_hw_interface_t *hi =
-               vnet_get_sup_hw_interface
-               (vnm, vnet_buffer (b0)->sw_if_index[VLIB_RX]);
-
-             /* Capture pkt if not filtered, or if filter hits */
-             if (hi->trace_classify_table_index == ~0 ||
-                 vnet_is_packet_traced_inline
-                 (b0, hi->trace_classify_table_index,
-                  0 /* full classify */ ))
-               pcap_add_buffer (&pp->pcap_main, vm, bi0,
-                                pp->max_bytes_per_pkt);
-           }
+         if (vnet_is_packet_pcaped (pp, b0, ~0))
+           pcap_add_buffer (&pp->pcap_main, vm, bi0, pp->max_bytes_per_pkt);
        }
     }
 }
index fb32260..9ae0771 100644 (file)
@@ -45,7 +45,7 @@
 #include <vnet/ip/ip6.h>
 #include <vnet/udp/udp_packet.h>
 #include <vnet/feature/feature.h>
-#include <vnet/classify/trace_classify.h>
+#include <vnet/classify/pcap_classify.h>
 #include <vnet/interface_output.h>
 
 typedef struct
@@ -309,36 +309,16 @@ static_always_inline void vnet_interface_pcap_tx_trace
 
   while (n_left_from > 0)
     {
-      int classify_filter_result;
       u32 bi0 = from[0];
       vlib_buffer_t *b0 = vlib_get_buffer (vm, bi0);
       from++;
       n_left_from--;
 
-      if (pp->filter_classify_table_index != ~0)
-       {
-         classify_filter_result =
-           vnet_is_packet_traced_inline
-           (b0, pp->filter_classify_table_index, 0 /* full classify */ );
-         if (classify_filter_result)
-           pcap_add_buffer (&pp->pcap_main, vm, bi0, pp->max_bytes_per_pkt);
-         continue;
-       }
-
       if (sw_if_index_from_buffer)
        sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_TX];
 
-      if (pp->pcap_sw_if_index == 0 || pp->pcap_sw_if_index == sw_if_index)
-       {
-         vnet_main_t *vnm = vnet_get_main ();
-         vnet_hw_interface_t *hi =
-           vnet_get_sup_hw_interface (vnm, sw_if_index);
-         /* Capture pkt if not filtered, or if filter hits */
-         if (hi->trace_classify_table_index == ~0 ||
-             vnet_is_packet_traced_inline
-             (b0, hi->trace_classify_table_index, 0 /* full classify */ ))
-           pcap_add_buffer (&pp->pcap_main, vm, bi0, pp->max_bytes_per_pkt);
-       }
+      if (vnet_is_packet_pcaped (pp, b0, sw_if_index))
+       pcap_add_buffer (&pp->pcap_main, vm, bi0, pp->max_bytes_per_pkt);
     }
 }
 
@@ -837,8 +817,6 @@ pcap_drop_trace (vlib_main_t * vm,
   i16 save_current_data;
   u16 save_current_length;
   vlib_error_main_t *em = &vm->error_main;
-  int do_trace = 0;
-
 
   from = vlib_frame_vector_args (f);
 
@@ -860,97 +838,86 @@ pcap_drop_trace (vlib_main_t * vm,
          && hash_get (im->pcap_drop_filter_hash, b0->error))
        continue;
 
-      do_trace = (pp->pcap_sw_if_index == 0) ||
-       pp->pcap_sw_if_index == vnet_buffer (b0)->sw_if_index[VLIB_RX];
+      if (!vnet_is_packet_pcaped (pp, b0, ~0))
+       continue; /* not matching, skip */
 
-      if (PREDICT_FALSE
-         (do_trace == 0 && pp->filter_classify_table_index != ~0))
+      /* Trace all drops, or drops received on a specific interface */
+      save_current_data = b0->current_data;
+      save_current_length = b0->current_length;
+
+      /*
+       * Typically, we'll need to rewind the buffer
+       * if l2_hdr_offset is valid, make sure to rewind to the start of
+       * the L2 header. This may not be the buffer start in case we pop-ed
+       * vlan tags.
+       * Otherwise, rewind to buffer start and hope for the best.
+       */
+      if (b0->flags & VNET_BUFFER_F_L2_HDR_OFFSET_VALID)
        {
-         do_trace = vnet_is_packet_traced_inline
-           (b0, pp->filter_classify_table_index, 0 /* full classify */ );
+         if (b0->current_data > vnet_buffer (b0)->l2_hdr_offset)
+           vlib_buffer_advance (b0, vnet_buffer (b0)->l2_hdr_offset -
+                                      b0->current_data);
        }
-
-      /* Trace all drops, or drops received on a specific interface */
-      if (do_trace)
+      else if (b0->current_data > 0)
        {
-         save_current_data = b0->current_data;
-         save_current_length = b0->current_length;
-
-         /*
-          * Typically, we'll need to rewind the buffer
-          * if l2_hdr_offset is valid, make sure to rewind to the start of
-          * the L2 header. This may not be the buffer start in case we pop-ed
-          * vlan tags.
-          * Otherwise, rewind to buffer start and hope for the best.
-          */
-         if (b0->flags & VNET_BUFFER_F_L2_HDR_OFFSET_VALID)
-           {
-             if (b0->current_data > vnet_buffer (b0)->l2_hdr_offset)
-               vlib_buffer_advance (b0,
-                                    vnet_buffer (b0)->l2_hdr_offset -
-                                    b0->current_data);
-           }
-         else if (b0->current_data > 0)
-           vlib_buffer_advance (b0, (word) - b0->current_data);
+         vlib_buffer_advance (b0, (word) -b0->current_data);
+       }
 
+      {
+       vlib_buffer_t *last = b0;
+       u32 error_node_index;
+       int drop_string_len;
+       vlib_node_t *n;
+       /* Length of the error string */
+       int error_string_len =
+         clib_strnlen (em->counters_heap[b0->error].name, 128);
+
+       /* Dig up the drop node */
+       error_node_index = vm->node_main.node_by_error[b0->error];
+       n = vlib_get_node (vm, error_node_index);
+
+       /* Length of full drop string, w/ "nodename: " prepended */
+       drop_string_len = error_string_len + vec_len (n->name) + 2;
+
+       /* Find the last buffer in the chain */
+       while (last->flags & VLIB_BUFFER_NEXT_PRESENT)
+         last = vlib_get_buffer (vm, last->next_buffer);
+
+       /*
+        * Append <nodename>: <error-string> to the capture,
+        * only if we can do that without allocating a new buffer.
+        */
+       if (PREDICT_TRUE ((last->current_data + last->current_length) <
+                         (VLIB_BUFFER_DEFAULT_DATA_SIZE - drop_string_len)))
          {
-           vlib_buffer_t *last = b0;
-           u32 error_node_index;
-           int drop_string_len;
-           vlib_node_t *n;
-           /* Length of the error string */
-           int error_string_len =
-             clib_strnlen (em->counters_heap[b0->error].name, 128);
-
-           /* Dig up the drop node */
-           error_node_index = vm->node_main.node_by_error[b0->error];
-           n = vlib_get_node (vm, error_node_index);
-
-           /* Length of full drop string, w/ "nodename: " prepended */
-           drop_string_len = error_string_len + vec_len (n->name) + 2;
-
-           /* Find the last buffer in the chain */
-           while (last->flags & VLIB_BUFFER_NEXT_PRESENT)
-             last = vlib_get_buffer (vm, last->next_buffer);
-
-           /*
-            * Append <nodename>: <error-string> to the capture,
-            * only if we can do that without allocating a new buffer.
-            */
-           if (PREDICT_TRUE ((last->current_data + last->current_length)
-                             < (VLIB_BUFFER_DEFAULT_DATA_SIZE
-                                - drop_string_len)))
-             {
-               clib_memcpy_fast (last->data + last->current_data +
-                                 last->current_length, n->name,
-                                 vec_len (n->name));
-               clib_memcpy_fast (last->data + last->current_data +
-                                 last->current_length + vec_len (n->name),
-                                 ": ", 2);
-               clib_memcpy_fast (last->data + last->current_data +
-                                 last->current_length + vec_len (n->name) +
-                                 2, em->counters_heap[b0->error].name,
-                                 error_string_len);
-               last->current_length += drop_string_len;
-               b0->flags &= ~(VLIB_BUFFER_TOTAL_LENGTH_VALID);
-               pcap_add_buffer (&pp->pcap_main, vm, bi0,
-                                pp->max_bytes_per_pkt);
-               last->current_length -= drop_string_len;
-               b0->current_data = save_current_data;
-               b0->current_length = save_current_length;
-               continue;
-             }
+           clib_memcpy_fast (last->data + last->current_data +
+                               last->current_length,
+                             n->name, vec_len (n->name));
+           clib_memcpy_fast (last->data + last->current_data +
+                               last->current_length + vec_len (n->name),
+                             ": ", 2);
+           clib_memcpy_fast (last->data + last->current_data +
+                               last->current_length + vec_len (n->name) + 2,
+                             em->counters_heap[b0->error].name,
+                             error_string_len);
+           last->current_length += drop_string_len;
+           b0->flags &= ~(VLIB_BUFFER_TOTAL_LENGTH_VALID);
+           pcap_add_buffer (&pp->pcap_main, vm, bi0, pp->max_bytes_per_pkt);
+           last->current_length -= drop_string_len;
+           b0->current_data = save_current_data;
+           b0->current_length = save_current_length;
+           continue;
          }
+      }
 
-         /*
-          * Didn't have space in the last buffer, here's the dropped
-          * packet as-is
-          */
-         pcap_add_buffer (&pp->pcap_main, vm, bi0, pp->max_bytes_per_pkt);
+      /*
+       * Didn't have space in the last buffer, here's the dropped
+       * packet as-is
+       */
+      pcap_add_buffer (&pp->pcap_main, vm, bi0, pp->max_bytes_per_pkt);
 
-         b0->current_data = save_current_data;
-         b0->current_length = save_current_length;
-       }
+      b0->current_data = save_current_data;
+      b0->current_length = save_current_length;
     }
 }
 
index 89ab364..8869b32 100644 (file)
@@ -11,6 +11,7 @@ from scapy.layers.l2 import Ether
 from scapy.layers.inet import IP, UDP
 from scapy.layers.vxlan import VXLAN
 from scapy.compat import raw
+from scapy.utils import rdpcap
 
 
 class TestTracefilter(VppTestCase):
@@ -49,27 +50,33 @@ class TestTracefilter(VppTestCase):
 
     # check number of hits for classifier
     def assert_hits(self, n):
-        r = self.cli("show classify table verbose 2")
+        r = self.cli("show classify table verbose")
         self.assertTrue(r.reply.find("hits %i" % n) != -1)
 
-    def add_filter(self, mask, match):
-        r = self.cli("classify filter trace mask %s match %s" % (mask, match))
-        self.vapi.cli("clear trace")
-        r = self.cli("trace add pg-input 1000 filter")
+    def add_trace_filter(self, mask, match):
+        self.cli("classify filter trace mask %s match %s" % (mask, match))
+        self.cli("clear trace")
+        self.cli("trace add pg-input 1000 filter")
 
-    def del_all_filters(self):
+    def del_trace_filters(self):
         self.cli("classify filter trace del")
         r = self.cli("show classify filter")
         s = "packet tracer:                 first table none"
         self.assertTrue(r.reply.find(s) != -1)
 
+    def del_pcap_filters(self):
+        self.cli("classify filter pcap del")
+        r = self.cli("show classify filter")
+        s = "pcap rx/tx/drop:               first table none"
+        self.assertTrue(r.reply.find(s) != -1)
+
     def test_basic(self):
         """ Packet Tracer Filter Test """
-        self.add_filter(
+        self.add_trace_filter(
             "l3 ip4 src",
             "l3 ip4 src %s" %
             self.pg0.remote_hosts[5].ip4)
-        self.add_filter(
+        self.add_trace_filter(
             "l3 ip4 proto l4 src_port",
             "l3 ip4 proto 17 l4 src_port 2345")
         # the packet we are trying to match
@@ -91,14 +98,14 @@ class TestTracefilter(VppTestCase):
         self.assert_hits(9)
         self.assert_hits(17)
 
-        self.del_all_filters()
+        self.del_trace_filters()
 
     # install a classify rule, inject traffic and check for hits
     def assert_classify(self, mask, match, packets, n=None):
-        self.add_filter("hex %s" % mask, "hex %s" % match)
+        self.add_trace_filter("hex %s" % mask, "hex %s" % match)
         self.send_and_expect(self.pg0, packets, self.pg1, trace=False)
         self.assert_hits(n if n is not None else len(packets))
-        self.del_all_filters()
+        self.del_trace_filters()
 
     def test_encap(self):
         """ Packet Tracer Filter Test with encap """
@@ -156,6 +163,50 @@ class TestTracefilter(VppTestCase):
         match = "".join(("{:02x}".format(o ^ n) for o, n in zip(ori, new)))
         self.assert_classify(mask, match, [p] * 17)
 
+    def test_pcap(self):
+        """ Packet Capture Filter Test """
+        self.cli(
+            "classify filter pcap mask l3 ip4 src match l3 ip4 src %s" %
+            self.pg0.remote_hosts[5].ip4)
+        self.cli(
+            "classify filter pcap "
+            "mask l3 ip4 proto l4 src_port "
+            "match l3 ip4 proto 17 l4 src_port 2345")
+        self.cli(
+            "pcap trace rx tx max 1000 intfc pg0 "
+            "file vpp_test_trace_filter.pcap filter")
+        # the packet we are trying to match
+        p = list()
+        for i in range(100):
+            src = self.pg0.remote_hosts[i % len(self.pg0.remote_hosts)].ip4
+            p.append((Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+                      IP(src=src, dst=self.pg1.remote_ip4) /
+                      UDP(sport=1234, dport=2345) / Raw('\xa5' * 100)))
+        for i in range(17):
+            p.append((Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+                      IP(src=self.pg0.remote_hosts[0].ip4,
+                         dst=self.pg1.remote_ip4) /
+                      UDP(sport=2345, dport=1234) / Raw('\xa5' * 100)))
+
+        self.send_and_expect(self.pg0, p, self.pg1, trace=False)
+
+        # Check for 9 and 17 classifier hits, which is the right answer
+        self.assert_hits(9)
+        self.assert_hits(17)
+
+        self.cli("pcap trace rx tx off")
+        self.del_pcap_filters()
+
+        # check captured pcap
+        pcap = rdpcap("/tmp/vpp_test_trace_filter.pcap")
+        self.assertEqual(len(pcap), 9 + 17)
+        p_ = str(p[5])
+        for i in range(9):
+            self.assertEqual(str(pcap[i]), p_)
+        p_ = str(p[100])
+        for i in range(9, 9 + 17):
+            self.assertEqual(str(pcap[i]), p_)
+
 
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)