From 30a819579cb396db1c975ae8e08477129aa7950d Mon Sep 17 00:00:00 2001 From: =?utf8?q?Beno=C3=AEt=20Ganne?= Date: Fri, 26 Feb 2021 13:47:41 +0100 Subject: [PATCH] classify: honor pcap interface filter also when classify filter is used MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Type: fix Change-Id: Ic32550ee9c5d76d232d8b67a7810611f6c8b9177 Signed-off-by: Benoît Ganne --- src/vnet/classify/pcap_classify.h | 59 +++++++++++++ src/vnet/ethernet/node.c | 31 +------ src/vnet/interface_output.c | 181 ++++++++++++++++---------------------- test/test_trace_filter.py | 73 ++++++++++++--- 4 files changed, 198 insertions(+), 146 deletions(-) create mode 100644 src/vnet/classify/pcap_classify.h diff --git a/src/vnet/classify/pcap_classify.h b/src/vnet/classify/pcap_classify.h new file mode 100644 index 00000000000..1f1c38fd6e6 --- /dev/null +++ b/src/vnet/classify/pcap_classify.h @@ -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 +#include +#include +#include + +/** @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: + */ diff --git a/src/vnet/ethernet/node.c b/src/vnet/ethernet/node.c index c31e22299b3..88b4a70a28a 100644 --- a/src/vnet/ethernet/node.c +++ b/src/vnet/ethernet/node.c @@ -44,7 +44,7 @@ #include #include #include -#include +#include #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); } } } diff --git a/src/vnet/interface_output.c b/src/vnet/interface_output.c index fb322604eda..9ae07714485 100644 --- a/src/vnet/interface_output.c +++ b/src/vnet/interface_output.c @@ -45,7 +45,7 @@ #include #include #include -#include +#include #include 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 : 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 : 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; } } diff --git a/test/test_trace_filter.py b/test/test_trace_filter.py index 89ab3648169..8869b3255d3 100644 --- a/test/test_trace_filter.py +++ b/test/test_trace_filter.py @@ -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) -- 2.16.6