span: add feature (rx only) (VPP-185)
Change-Id: I0f7cbf06b5a5acd745d13c9f5c761ea18132107b
Signed-off-by: marek <mazavods@gmail.com>
Signed-off-by: Damjan Marion <damarion@cisco.com>
Signed-off-by: Pavel Kotucek <pkotucek@cisco.com>
Signed-off-by: Damjan Marion <damarion@cisco.com>
diff --git a/vnet/Makefile.am b/vnet/Makefile.am
index fef928a..3ffcca8 100644
--- a/vnet/Makefile.am
+++ b/vnet/Makefile.am
@@ -632,6 +632,17 @@
vnet/lawful-intercept/lawful_intercept.h
########################################
+# SPAN (port mirroring)
+########################################
+
+libvnet_la_SOURCES += \
+ vnet/span/span.c \
+ vnet/span/node.c
+
+nobase_include_HEADERS += \
+ vnet/span/span.h
+
+########################################
# Packet generator
########################################
diff --git a/vnet/vnet/devices/devices.c b/vnet/vnet/devices/devices.c
index 928b0b4..3eef95b 100644
--- a/vnet/vnet/devices/devices.c
+++ b/vnet/vnet/devices/devices.c
@@ -52,6 +52,12 @@
.runs_before = VNET_FEATURES ("ethernet-input"),
};
+VNET_FEATURE_INIT (span_input, static) = {
+ .arc_name = "device-input",
+ .node_name = "span-input",
+ .runs_before = VNET_FEATURES ("ethernet-input"),
+};
+
VNET_FEATURE_INIT (ethernet_input, static) = {
.arc_name = "device-input",
.node_name = "ethernet-input",
diff --git a/vnet/vnet/feature/feature.c b/vnet/vnet/feature/feature.c
index 1f46285..c8cde36 100644
--- a/vnet/vnet/feature/feature.c
+++ b/vnet/vnet/feature/feature.c
@@ -187,6 +187,11 @@
vec_validate_init_empty (cm->config_index_by_sw_if_index, sw_if_index, ~0);
ci = cm->config_index_by_sw_if_index[sw_if_index];
+ vec_validate (fm->feature_count_by_sw_if_index[arc_index], sw_if_index);
+ if (!enable_disable
+ && fm->feature_count_by_sw_if_index[arc_index][sw_if_index] < 1)
+ return 0;
+
ci = (enable_disable
? vnet_config_add_feature
: vnet_config_del_feature)
diff --git a/vnet/vnet/feature/feature.h b/vnet/vnet/feature/feature.h
index b026cdf..667c5e3 100644
--- a/vnet/vnet/feature/feature.h
+++ b/vnet/vnet/feature/feature.h
@@ -145,6 +145,13 @@
return &fm->feature_config_mains[arc_index];
}
+static_always_inline vnet_feature_config_main_t *
+vnet_feature_get_config_main (u16 arc)
+{
+ vnet_feature_main_t *fm = &feature_main;
+ return &fm->feature_config_mains[arc];
+}
+
static_always_inline int
vnet_have_features (u8 arc, u32 sw_if_index)
{
diff --git a/vnet/vnet/span/node.c b/vnet/vnet/span/node.c
new file mode 100644
index 0000000..2012283
--- /dev/null
+++ b/vnet/vnet/span/node.c
@@ -0,0 +1,260 @@
+/*
+ * 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.
+ */
+
+#include <vlib/vlib.h>
+#include <vnet/vnet.h>
+#include <vppinfra/error.h>
+
+#include <vnet/span/span.h>
+
+#include <vppinfra/error.h>
+#include <vppinfra/elog.h>
+
+vlib_node_registration_t span_node;
+
+/* packet trace format function */
+u8 *
+format_span_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 *);
+ span_trace_t *t = va_arg (*args, span_trace_t *);
+
+ vnet_main_t *vnm = &vnet_main;
+ s = format (s, "SPAN: mirrored %U -> %U",
+ format_vnet_sw_if_index_name, vnm, t->src_sw_if_index,
+ format_vnet_sw_if_index_name, vnm, t->mirror_sw_if_index);
+
+ return s;
+}
+
+#define foreach_span_error \
+_(HITS, "SPAN incomming packets processed")
+
+typedef enum
+{
+#define _(sym,str) SPAN_ERROR_##sym,
+ foreach_span_error
+#undef _
+ SPAN_N_ERROR,
+} span_error_t;
+
+static char *span_error_strings[] = {
+#define _(sym,string) string,
+ foreach_span_error
+#undef _
+};
+
+static uword
+span_node_fn (vlib_main_t * vm,
+ vlib_node_runtime_t * node, vlib_frame_t * frame)
+{
+ span_main_t *sm = &span_main;
+ vnet_main_t *vnm = &vnet_main;
+ u32 n_left_from, *from, *to_next, *to_mirror_next = 0;
+ u32 n_span_packets = 0;
+ u32 next_index, mirror_sw_if_index0, mirror_sw_if_index1;
+ u32 last_mirror_sw_if_index = ~0;
+ vlib_frame_t *mirror_frame = 0;
+
+ from = vlib_frame_vector_args (frame);
+ n_left_from = frame->n_vectors;
+ next_index = node->cached_next_index;
+
+ /* TODO dual loop */
+ while (n_left_from > 0)
+ {
+ u32 n_left_to_next;
+
+ vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
+
+ while (n_left_from >= 4 && n_left_to_next >= 2)
+ {
+ u32 bi0;
+ u32 bi1;
+ vlib_buffer_t *b0, *c0;
+ vlib_buffer_t *b1, *c1;
+ u32 sw_if_index0;
+ u32 next0 = 0; //SPAN_NEXT_ORIG_ETHERNET;
+ u32 sw_if_index1;
+ u32 next1 = 1; //SPAN_NEXT_ORIG_ETHERNET;
+
+ /* speculatively enqueue b0, b1 to the current next frame */
+ to_next[0] = bi0 = from[0];
+ to_next[1] = bi1 = from[1];
+ to_next += 2;
+ n_left_to_next -= 2;
+ from += 2;
+ n_left_from -= 2;
+
+ b0 = vlib_get_buffer (vm, bi0);
+ sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
+ mirror_sw_if_index0 = sm->dst_by_src_sw_if_index[sw_if_index0];
+ b1 = vlib_get_buffer (vm, bi1);
+ sw_if_index1 = vnet_buffer (b1)->sw_if_index[VLIB_RX];
+ mirror_sw_if_index1 = sm->dst_by_src_sw_if_index[sw_if_index1];
+
+ /* get frame to mirror interface */
+ if (PREDICT_FALSE
+ ((last_mirror_sw_if_index != mirror_sw_if_index0)
+ || mirror_frame == 0))
+ {
+ if (mirror_frame)
+ vnet_put_frame_to_sw_interface (vnm, last_mirror_sw_if_index,
+ mirror_frame);
+ last_mirror_sw_if_index = mirror_sw_if_index0;
+ mirror_frame =
+ vnet_get_frame_to_sw_interface (vnm, mirror_sw_if_index0);
+ to_mirror_next = vlib_frame_vector_args (mirror_frame);
+ }
+ /* get frame to mirror interface */
+ if (PREDICT_FALSE
+ ((last_mirror_sw_if_index != mirror_sw_if_index1)
+ || mirror_frame == 0))
+ {
+ if (mirror_frame)
+ vnet_put_frame_to_sw_interface (vnm, last_mirror_sw_if_index,
+ mirror_frame);
+ last_mirror_sw_if_index = mirror_sw_if_index1;
+ mirror_frame =
+ vnet_get_frame_to_sw_interface (vnm, mirror_sw_if_index0);
+ to_mirror_next = vlib_frame_vector_args (mirror_frame);
+ }
+ c0 = vlib_buffer_copy (vm, b0);
+ vnet_buffer (c0)->sw_if_index[VLIB_TX] = mirror_sw_if_index0;
+ to_mirror_next[0] = vlib_get_buffer_index (vm, c0);
+ to_mirror_next += 1;
+ mirror_frame->n_vectors++;
+
+ vnet_feature_next (sw_if_index0, &next0, b0);
+
+ c1 = vlib_buffer_copy (vm, b1);
+ vnet_buffer (c1)->sw_if_index[VLIB_TX] = mirror_sw_if_index1;
+ to_mirror_next[0] = vlib_get_buffer_index (vm, c1);
+ to_mirror_next += 1;
+ mirror_frame->n_vectors++;
+
+ vnet_feature_next (sw_if_index1, &next1, b1);
+
+ if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
+ {
+ span_trace_t *t = vlib_add_trace (vm, node, b0, sizeof (*t));
+ t->src_sw_if_index = sw_if_index0;
+ t->mirror_sw_if_index =
+ sm->dst_by_src_sw_if_index[sw_if_index0];
+ }
+
+ if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
+ {
+ span_trace_t *t = vlib_add_trace (vm, node, b1, sizeof (*t));
+ t->src_sw_if_index = sw_if_index1;
+ t->mirror_sw_if_index =
+ sm->dst_by_src_sw_if_index[sw_if_index1];
+ }
+ /* verify speculative enqueue, maybe switch current next frame */
+ vlib_validate_buffer_enqueue_x2 (vm, node, next_index,
+ to_next, n_left_to_next,
+ bi0, bi1, next0, next1);
+ }
+ while (n_left_from > 0 && n_left_to_next > 0)
+ {
+ u32 bi0;
+ vlib_buffer_t *b0, *c0;
+ u32 sw_if_index0;
+ u32 next0 = 0; //SPAN_NEXT_ORIG_ETHERNET;
+
+ /* speculatively enqueue b0 to the current next frame */
+ to_next[0] = bi0 = from[0];
+ to_next += 1;
+ n_left_to_next -= 1;
+ from += 1;
+ n_left_from -= 1;
+
+ b0 = vlib_get_buffer (vm, bi0);
+ sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
+ mirror_sw_if_index0 = sm->dst_by_src_sw_if_index[sw_if_index0];
+
+ /* get frame to mirror interface */
+ if (PREDICT_FALSE
+ ((last_mirror_sw_if_index != mirror_sw_if_index0)
+ || mirror_frame == 0))
+ {
+ if (mirror_frame)
+ vnet_put_frame_to_sw_interface (vnm, last_mirror_sw_if_index,
+ mirror_frame);
+ last_mirror_sw_if_index = mirror_sw_if_index0;
+ mirror_frame =
+ vnet_get_frame_to_sw_interface (vnm, mirror_sw_if_index0);
+ to_mirror_next = vlib_frame_vector_args (mirror_frame);
+ }
+ c0 = vlib_buffer_copy (vm, b0);
+ vnet_buffer (c0)->sw_if_index[VLIB_TX] = mirror_sw_if_index0;
+ to_mirror_next[0] = vlib_get_buffer_index (vm, c0);
+ to_mirror_next += 1;
+ mirror_frame->n_vectors++;
+
+ vnet_feature_next (sw_if_index0, &next0, b0);
+
+ if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
+ {
+ span_trace_t *t = vlib_add_trace (vm, node, b0, sizeof (*t));
+ t->src_sw_if_index = sw_if_index0;
+ t->mirror_sw_if_index =
+ sm->dst_by_src_sw_if_index[sw_if_index0];
+ }
+ /* verify speculative enqueue, maybe switch current next frame */
+ vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
+ n_left_to_next, bi0, next0);
+ }
+
+ vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+ }
+
+ vnet_put_frame_to_sw_interface (vnm, last_mirror_sw_if_index, mirror_frame);
+ vlib_node_increment_counter (vm, span_node.index, SPAN_ERROR_HITS,
+ n_span_packets);
+
+ return frame->n_vectors;
+}
+
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE (span_node) = {
+ .function = span_node_fn,
+ .name = "span-input",
+ .vector_size = sizeof (u32),
+ .format_trace = format_span_trace,
+ .type = VLIB_NODE_TYPE_INTERNAL,
+
+ .n_errors = ARRAY_LEN(span_error_strings),
+ .error_strings = span_error_strings,
+
+ .n_next_nodes = 0,
+
+ /* edit / add dispositions here */
+ .next_nodes = {
+ [0] = "error-drop",
+ },
+};
+
+/* *INDENT-ON* */
+
+VLIB_NODE_FUNCTION_MULTIARCH (span_node, span_node_fn)
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/vnet/vnet/span/span.c b/vnet/vnet/span/span.c
new file mode 100644
index 0000000..de43af0
--- /dev/null
+++ b/vnet/vnet/span/span.c
@@ -0,0 +1,139 @@
+/*
+ * 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.
+ */
+
+#include <vlib/vlib.h>
+#include <vppinfra/error.h>
+#include <vnet/feature/feature.h>
+
+#include <vnet/span/span.h>
+
+int
+span_add_delete_entry (vlib_main_t * vm,
+ u32 src_sw_if_index, u32 dst_sw_if_index, u8 is_add)
+{
+ span_main_t *sm = &span_main;
+
+ if ((src_sw_if_index == ~0) || (dst_sw_if_index == ~0 && is_add)
+ || (src_sw_if_index == dst_sw_if_index))
+ return VNET_API_ERROR_INVALID_INTERFACE;
+
+ vnet_sw_interface_t *sw =
+ vnet_get_sw_interface (sm->vnet_main, src_sw_if_index);
+
+ vec_validate_aligned (sm->dst_by_src_sw_if_index, sw->hw_if_index,
+ CLIB_CACHE_LINE_BYTES);
+ sm->dst_by_src_sw_if_index[sw->hw_if_index] = is_add ? dst_sw_if_index : 0;
+ vnet_feature_enable_disable ("device-input", "span-input",
+ sw->hw_if_index, is_add, 0, 0);
+ return 0;
+}
+
+static clib_error_t *
+set_interface_span_command_fn (vlib_main_t * vm,
+ unformat_input_t * input,
+ vlib_cli_command_t * cmd)
+{
+ span_main_t *sm = &span_main;
+ u32 src_sw_if_index = ~0;
+ u32 dst_sw_if_index = ~0;
+ u8 is_add = 1;
+
+ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (input, "%U", unformat_vnet_sw_interface,
+ sm->vnet_main, &src_sw_if_index))
+ ;
+ else if (unformat (input, "destination %U", unformat_vnet_sw_interface,
+ sm->vnet_main, &dst_sw_if_index))
+ ;
+ else if (unformat (input, "disable"))
+ is_add = 0;
+ else
+ break;
+ }
+
+ int rv =
+ span_add_delete_entry (vm, src_sw_if_index, dst_sw_if_index, is_add);
+ if (rv == VNET_API_ERROR_INVALID_INTERFACE)
+ return clib_error_return (0, "Invalid interface");
+ return 0;
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (set_interface_span_command, static) = {
+ .path = "set interface span",
+ .short_help = "set interface span <if-name> [disable | destination <if-name>]",
+ .function = set_interface_span_command_fn,
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+show_interfaces_span_command_fn (vlib_main_t * vm,
+ unformat_input_t * input,
+ vlib_cli_command_t * cmd)
+{
+
+ span_main_t *sm = &span_main;
+ vnet_main_t *vnm = &vnet_main;
+ u32 src_sw_if_index = 0, *dst_sw_if_index;
+ u8 header = 1;
+
+ vec_foreach (dst_sw_if_index, sm->dst_by_src_sw_if_index)
+ {
+ if (*dst_sw_if_index > 0) // && *dst_sw_if_index != ~0)
+ {
+ if (header)
+ {
+ vlib_cli_output (vm,
+ "SPAN source interface to destination interface table");
+ header = 0;
+ }
+ vlib_cli_output (vm, "%32U => %-32U",
+ format_vnet_sw_if_index_name, vnm, src_sw_if_index,
+ format_vnet_sw_if_index_name, vnm, *dst_sw_if_index);
+ }
+ src_sw_if_index++;
+ }
+ return 0;
+}
+
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (show_interfaces_span_command, static) = {
+ .path = "show interfaces span",
+ .short_help = "Shows SPAN mirror table",
+ .function = show_interfaces_span_command_fn,
+};
+/* *INDENT-ON* */
+
+static clib_error_t *
+span_init (vlib_main_t * vm)
+{
+ span_main_t *sm = &span_main;
+
+ sm->vlib_main = vm;
+ sm->vnet_main = vnet_get_main ();
+
+ return 0;
+}
+
+VLIB_INIT_FUNCTION (span_init);
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/vnet/vnet/span/span.h b/vnet/vnet/span/span.h
new file mode 100644
index 0000000..751bebf
--- /dev/null
+++ b/vnet/vnet/span/span.h
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+#ifndef __span_h__
+#define __span_h__
+
+#include <vnet/vnet.h>
+#include <vnet/ip/ip.h>
+
+typedef struct
+{
+ /* destination interface index by source interface index */
+ u32 *dst_by_src_sw_if_index;
+
+ /* convenience */
+ vlib_main_t *vlib_main;
+ vnet_main_t *vnet_main;
+} span_main_t;
+
+span_main_t span_main;
+
+typedef struct
+{
+ u32 src_sw_if_index; /* mirrored interface index */
+ u32 mirror_sw_if_index; /* output interface index */
+} span_trace_t;
+
+#endif /* __span_h__ */
+
+int
+span_add_delete_entry (vlib_main_t * vm, u32 src_sw_if_index,
+ u32 dst_sw_if_index, u8 is_add);
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/vnet/vnet/span/span.md b/vnet/vnet/span/span.md
new file mode 100644
index 0000000..ee3f814
--- /dev/null
+++ b/vnet/vnet/span/span.md
@@ -0,0 +1,65 @@
+# VPP SPAN implementation
+
+This is a memo intended to contain documentation of the VPP SPAN implementation.
+Everything that is not directly obvious should come here.
+
+
+## Switched Port Analyzer (SPAN)
+Port mirroring is used on a network switch to send a copy of network packets seen on one switch port to a network monitoring connection on another switch port.
+Can be used by network engineers or administrators to measure performnce, analyze and debug data or diagnose errors on a network.
+
+### RX traffic node
+There is one static node to mirror incomming packets.
+* span-input: Creates a copy of incomming buffer due to incomming buffers can be reused internally.
+
+Chaining: dpdk-input -> span-input ->
+* original buffer is sent to ethernet-input for processing
+* buffer copy is sent to interface-output
+
+### Configuration
+SPAN supports the following CLI configuration commands:
+
+#### Enable/Disable SPAN (CLI)
+ set interface span <if-name> [disable | destination <if-name>]
+
+<if-name>: mirrored interface name
+destination <if-name>: monitoring interface name
+disable: delete mirroring
+
+#### Enable/Disabl SPAN (API)
+SPAN supports the following API configuration command:
+ sw_interface_span_enable_disable src GigabitEthernet0/8/0 dst GigabitEthernet0/9/0
+ sw_interface_span_enable_disable src_sw_if_index 1 dst_sw_if_index 2
+
+src/src_sw_if_index: mirrored interface name
+dst/dst_sw_if_index: monitoring interface name
+
+#### Remove SPAN entry (API)
+SPAN supports the following API configuration command:
+ sw_interface_span_enable_disable src_sw_if_index 1 dst_sw_if_index 2 disable
+
+src_sw_if_index: mirrored interface name
+dst_sw_if_index: monitoring interface name
+
+### Configuration example
+
+Mirror all packets on interface GigabitEthernet0/10/0 to interface GigabitEthernet0/11/0.
+
+Configure IPv4 addresses on mirrored interface:
+set interface ip address GigabitEthernet0/10/0 192.168.1.13/24
+set interface state GigabitEthernet0/10/0 up
+
+Configure IPv4 addresses on monitoring interface:
+set interface ip address GigabitEthernet0/11/0 192.168.2.13/24
+set interface state GigabitEthernet0/11/0 up
+
+Configure SPAN
+set span src GigabitEthernet0/10/0 dst GigabitEthernet0/11/0
+
+### Operational data
+
+Active SPAN mirroring CLI show command:
+ show interfaces span
+
+Active SPAN mirroring API dump command:
+ sw_interface_span_dump