misc: classifier-based packet trace filter

See .../src/vnet/classify/trace_classify.h for the business end
of the scheme.

It would be best to hash pkts, prefetch buckets, and do the primary
table lookups two at a time. The inline as given works, but perf
tuning will be required. "At least it works..."

Add "classify filter" debug cli, for example:
classify filter mask l3 ip4 src dst \
  match l3 ip4 dst 192.168.2.10 src 192.168.1.10

Add "pcap rx | tx trace ... filter" to use the current classify filter chain

Patch includes sphinx documentation and doxygen tags.

Next step: device-driver integration

Type: feature

Signed-off-by: Dave Barach <dave@barachs.net>
Change-Id: I05b1358a769f61e6d32470e0c87058f640486b26
(cherry picked from commit 9137e5400699bed9f7c0095187839a8b38273100)
This commit is contained in:
Dave Barach
2019-09-13 17:47:50 -04:00
committed by Damjan Marion
parent 2abe699d10
commit 1b696ac9b3
10 changed files with 558 additions and 7 deletions

View File

@ -469,3 +469,111 @@ metadata changes, header checksum changes, and so forth.
This should be of significant value when developing new vpp graph
nodes. If new code mispositions b->current_data, it will be completely
obvious from looking at the dispatch trace in wireshark.
## pcap rx and tx tracing
vpp also supports rx and tx packet capture in pcap format, through the
"pcap rx trace" and "pcap tx trace" debug CLI commands
This command is used to start or stop a packet capture, or show
the status of packet capture. Note that both "pcap rx trace" and
"pcap tx trace" are implemented. The command syntax is identical,
simply substitute rx for tx as needed.
These commands have the following optional parameters:
on|off- Used to start or stop a packet capture.
- <b>max _nnnn_</b> - file size, number of packet captures. Once
<nnnn> packets have been received, the trace buffer buffer is flushed
to the indicated file. Defaults to 1000. Can only be updated if packet
capture is off.
- <b>intfc _interface_ | _any_</b> - Used to specify a given interface,
or use '<em>any</em>' to run packet capture on all interfaces.
'<em>any</em>' is the default if not provided. Settings from a previous
packet capture are preserved, so '<em>any</em>' can be used to reset
the interface setting.
- <b>file _filename_</b> - Used to specify the output filename. The
file will be placed in the '<em>/tmp</em>' directory. If _filename_
already exists, file will be overwritten. If no filename is
provided, '<em>/tmp/rx.pcap or tx.pcap</em>' will be used, depending
on capture direction. Can only be updated when pcap capture is off.
- <b>status</b> - Displays the current status and configured
attributes associated with a packet capture. If packet capture is in
progress, '<em>status</em>' also will return the number of packets
currently in the buffer. Any additional attributes entered on
command line with a '<em>status</em>' request will be ignored.
- <b>filter</b> - Capture packets which match the current packet
trace filter set. See next section. Configure the capture filter
first.
## packet trace capture filtering
The "classify filter" debug CLI command constructs an arbitrary set of
packet classifier tables for use with "pcap rx | tx trace," and
(eventually) with the vpp packet tracer
Packets which match a rule in the classifier table chain will be
traced. The tables are automatically ordered so that matches in the
most specific table are tried first.
It's reasonably likely that folks will configure a single table with
one or two matches. As a result, we configure 8 hash buckets and 128K
of match rule space by default. One can override the defaults by
specifiying "buckets <nnn>" and "memory-size <xxx>" as desired.
To build up complex filter chains, repeatedly issue the classify
filter debug CLI command. Each command must specify the desired mask
and match values. If a classifier table with a suitable mask already
exists, the CLI command adds a match rule to the existing table. If
not, the CLI command add a new table and the indicated mask rule
### Configure a simple classify filter
```
classify filter mask l3 ip4 src match l3 ip4 src 192.168.1.11"
pcap rx trace on max 100 filter
```
### Configure another fairly simple filter
```
classify filter mask l3 ip4 src dst match l3 ip4 src 192.168.1.10 dst 192.168.2.10
pcap tx trace on max 100 filter
```
### Clear all current classifier filters
```
classify filter del
```
### To inspect the classifier tables
```
show classify table [verbose]
```
The verbose form displays all of the match rules, with hit-counters.
### Terse description of the "mask <xxx>" syntax:
```
l2 src dst proto tag1 tag2 ignore-tag1 ignore-tag2 cos1 cos2 dot1q dot1ad
l3 ip4 <ip4-mask> ip6 <ip6-mask>
<ip4-mask> version hdr_length src[/width] dst[/width]
tos length fragment_id ttl protocol checksum
<ip6-mask> version traffic-class flow-label src dst proto
payload_length hop_limit protocol
l4 tcp <tcp-mask> udp <udp_mask> src_port dst_port
<tcp-mask> src dst # ports
<udp-mask> src_port dst_port
```
To construct **matches**, add the values to match after the indicated
keywords in the mask syntax. For example: "... mask l3 ip4 src" ->
"... match l3 ip4 src 192.168.1.11"

View File

@ -63,6 +63,7 @@ typedef struct
int pcap_enable;
u32 pcap_sw_if_index;
pcap_main_t pcap_main;
u32 filter_classify_table_index;
} vnet_pcap_t;
typedef struct vlib_main_t

View File

@ -384,6 +384,7 @@ list(APPEND VNET_API_FILES lldp/lldp.api)
##############################################################################
list(APPEND VNET_SOURCES
classify/vnet_classify.c
classify/trace_classify.h
classify/ip_classify.c
classify/in_out_acl.c
classify/policer_classify.c
@ -400,6 +401,7 @@ list(APPEND VNET_MULTIARCH_SOURCES
list(APPEND VNET_HEADERS
classify/vnet_classify.h
classify/trace_classify.h
classify/in_out_acl.h
classify/policer_classify.h
classify/flow_classify.h

View File

@ -0,0 +1,100 @@
/*
* trace_classify.h - Use the classifier to decide if a packet is traced
*
* Copyright (c) 2019 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 <vppinfra/error.h>
#include <vnet/classify/vnet_classify.h>
/** @file trace_classify.h
* Use the vpp classifier to decide whether to trace packets
*/
/** @brief vnet_is_packet_traced
* @param vlib_buffer_t *b - packet to classify
* @param int func - 0 => use classifier w/ supplied table index
* @param u32 classify_table_index - classifier table index
* @return 0 => no trace, 1 => trace, -1 => error
*/
static inline int
vnet_is_packet_traced_inline (vlib_buffer_t * b,
u32 classify_table_index, int func)
{
vnet_classify_main_t *vcm = &vnet_classify_main;
vnet_classify_table_t *t;
vnet_classify_entry_t *e;
u64 hash;
/*$$$ add custom classifiers here, if any */
if (func != 0)
return -1;
/* This will happen... */
if (pool_is_free_index (vcm->tables, classify_table_index))
return -1;
/* Get the table */
t = pool_elt_at_index (vcm->tables, classify_table_index);
/* Hash the packet */
hash = vnet_classify_hash_packet (t, vlib_buffer_get_current (b));
/* See if there's a matching entry */
e = vnet_classify_find_entry (t, vlib_buffer_get_current (b), hash,
0 /* time = 0, disables hit-counter */ );
/* Hit means trace the packet... */
if (e)
{
/* Manual hit accounting */
e->hits++;
return 1;
}
/*
* Look for a hit in a less-specific table.
* Performance hint: for this use-case, don't go there.
*/
while (1)
{
/* Most likely, we're done right now */
if (PREDICT_TRUE (t->next_table_index == ~0))
return 0;
t = pool_elt_at_index (vcm->tables, t->next_table_index);
/* Compute hash for this table */
hash = vnet_classify_hash_packet (t, vlib_buffer_get_current (b));
/* See if there's a matching entry */
e = vnet_classify_find_entry (t, vlib_buffer_get_current (b), hash,
0 /* time = 0, disables hit-counter */ );
if (e)
{
/* Manual hit accounting */
e->hits++;
return 1;
}
}
/* NOTREACHED */
}
/*
* fd.io coding-style-patch-verification: ON
*
* Local Variables:
* eval: (c-set-style "gnu")
* End:
*/

File diff suppressed because it is too large Load Diff

View File

@ -44,6 +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>
#define foreach_ethernet_input_next \
_ (PUNT, "error-punt") \
@ -993,15 +994,29 @@ ethernet_input_trace (vlib_main_t * vm, vlib_node_runtime_t * node,
if (PREDICT_FALSE (vlib_global_main.pcap[VLIB_RX].pcap_enable))
{
u32 bi0;
vnet_main_t *vnm = vnet_get_main ();
from = vlib_frame_vector_args (from_frame);
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 (vec_len (vnm->classify_filter_table_indices))
{
classify_filter_result =
vnet_is_packet_traced_inline
(b0, vnm->classify_filter_table_indices[0],
0 /* full classify */ );
if (classify_filter_result)
pcap_add_buffer (&vlib_global_main.pcap[VLIB_RX].pcap_main,
vm, bi0, 512);
continue;
}
if (vlib_global_main.pcap[VLIB_RX].pcap_sw_if_index == 0 ||
vlib_global_main.pcap[VLIB_RX].pcap_sw_if_index
@ -1010,7 +1025,6 @@ ethernet_input_trace (vlib_main_t * vm, vlib_node_runtime_t * node,
pcap_add_buffer (&vlib_global_main.pcap[VLIB_RX].pcap_main, vm,
bi0, 512);
}
n_left--;
}
}
}

View File

@ -901,6 +901,7 @@ typedef struct
u32 packets_to_capture;
vlib_rx_or_tx_t rxtx;
u32 sw_if_index;
int filter;
} vnet_pcap_dispatch_trace_args_t;
int vnet_pcap_dispatch_trace_configure (vnet_pcap_dispatch_trace_args_t *);

View File

@ -1699,6 +1699,7 @@ int
vnet_pcap_dispatch_trace_configure (vnet_pcap_dispatch_trace_args_t * a)
{
vlib_main_t *vm = vlib_get_main ();
vnet_main_t *vnm = vnet_get_main ();
vlib_rx_or_tx_t rxtx = a->rxtx;
vnet_pcap_t *pp = &vm->pcap[rxtx];
pcap_main_t *pm = &pp->pcap_main;
@ -1734,6 +1735,10 @@ vnet_pcap_dispatch_trace_configure (vnet_pcap_dispatch_trace_args_t * a)
&& (pm->n_packets_to_capture != a->packets_to_capture))
return VNET_API_ERROR_INVALID_VALUE_2;
if (a->enable && a->filter
&& (vec_len (vnm->classify_filter_table_indices) == 0))
return VNET_API_ERROR_NO_SUCH_LABEL;
if (a->enable)
{
/* Clean up from previous run, if any */
@ -1754,6 +1759,11 @@ vnet_pcap_dispatch_trace_configure (vnet_pcap_dispatch_trace_args_t * a)
pm->packet_type = PCAP_PACKET_TYPE_ethernet;
pm->n_packets_to_capture = a->packets_to_capture;
pp->pcap_sw_if_index = a->sw_if_index;
if (a->filter)
pp->filter_classify_table_index =
vnm->classify_filter_table_indices[0];
else
pp->filter_classify_table_index = ~0;
pp->pcap_enable = 1;
}
else
@ -1796,6 +1806,7 @@ pcap_trace_command_internal (vlib_main_t * vm,
int rv;
int enable = 0;
int status = 0;
int filter = 0;
u32 sw_if_index = 0;
/* Get a line of input. */
@ -1826,6 +1837,8 @@ pcap_trace_command_internal (vlib_main_t * vm,
;
else if (unformat (line_input, "intfc any"))
sw_if_index = 0;
else if (unformat (line_input, "filter"))
filter = 1;
else
{
return clib_error_return (0, "unknown input `%U'",
@ -1842,6 +1855,7 @@ pcap_trace_command_internal (vlib_main_t * vm,
a->packets_to_capture = max;
a->rxtx = rxtx;
a->sw_if_index = sw_if_index;
a->filter = filter;
rv = vnet_pcap_dispatch_trace_configure (a);
@ -1866,6 +1880,10 @@ pcap_trace_command_internal (vlib_main_t * vm,
case VNET_API_ERROR_NO_SUCH_ENTRY:
return clib_error_return (0, "No packets captured...");
case VNET_API_ERROR_NO_SUCH_LABEL:
return clib_error_return
(0, "No classify filter configured, see 'classify filter...'");
default:
vlib_cli_output (vm, "WARNING: trace configure returned %d", rv);
break;
@ -1958,7 +1976,6 @@ VLIB_CLI_COMMAND (pcap_rx_trace_command, static) = {
};
/* *INDENT-ON* */
/*
* fd.io coding-style-patch-verification: ON
*

View File

@ -43,6 +43,7 @@
#include <vnet/ip/ip6.h>
#include <vnet/udp/udp_packet.h>
#include <vnet/feature/feature.h>
#include <vnet/classify/trace_classify.h>
typedef struct
{
@ -813,13 +814,29 @@ static_always_inline void vnet_interface_pcap_tx_trace
else
sw_if_index = ~0;
vnet_main_t *vnm = vnet_get_main ();
n_left_from = frame->n_vectors;
from = vlib_frame_vector_args (frame);
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 (vec_len (vnm->classify_filter_table_indices))
{
classify_filter_result =
vnet_is_packet_traced_inline
(b0, vnm->classify_filter_table_indices[0],
0 /* full classify */ );
if (classify_filter_result)
pcap_add_buffer (&vlib_global_main.pcap[VLIB_TX].pcap_main, vm,
bi0, 512);
continue;
}
if (sw_if_index_from_buffer)
sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_TX];
@ -828,8 +845,6 @@ static_always_inline void vnet_interface_pcap_tx_trace
vlib_global_main.pcap[VLIB_TX].pcap_sw_if_index == sw_if_index)
pcap_add_buffer (&vlib_global_main.pcap[VLIB_TX].pcap_main, vm, bi0,
512);
from++;
n_left_from--;
}
}

View File

@ -77,6 +77,9 @@ typedef struct vnet_main_t
*/
vnet_api_error_t api_errno;
/* pcap rx/tx, packet tracer filter tables */
u32 *classify_filter_table_indices;
vlib_main_t *vlib_main;
} vnet_main_t;