From a77ae4708906b2a7894f7ac694bf55d5f0558d5f Mon Sep 17 00:00:00 2001 From: Steven Luong Date: Sun, 14 Feb 2021 11:37:02 -0800 Subject: arping: add arping command Add linux similar arping command to VPP. syntax: arping [gratuitous]
[repeat ] [interval ] Type: feature Signed-off-by: Steven Luong Change-Id: I9267c054235207b8fae8e3f159246777eb0340dd --- src/plugins/arping/CMakeLists.txt | 24 + src/plugins/arping/FEATURE.yaml | 9 + src/plugins/arping/arping.api | 61 +++ src/plugins/arping/arping.c | 797 +++++++++++++++++++++++++++++++++ src/plugins/arping/arping.h | 74 +++ src/plugins/arping/arping_api.c | 87 ++++ src/plugins/arping/arping_test.c | 167 +++++++ src/plugins/arping/test/test_arping.py | 251 +++++++++++ 8 files changed, 1470 insertions(+) create mode 100644 src/plugins/arping/CMakeLists.txt create mode 100644 src/plugins/arping/FEATURE.yaml create mode 100644 src/plugins/arping/arping.api create mode 100644 src/plugins/arping/arping.c create mode 100644 src/plugins/arping/arping.h create mode 100644 src/plugins/arping/arping_api.c create mode 100644 src/plugins/arping/arping_test.c create mode 100644 src/plugins/arping/test/test_arping.py (limited to 'src/plugins/arping') diff --git a/src/plugins/arping/CMakeLists.txt b/src/plugins/arping/CMakeLists.txt new file mode 100644 index 00000000000..afec21e7d8e --- /dev/null +++ b/src/plugins/arping/CMakeLists.txt @@ -0,0 +1,24 @@ +# 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. + +add_vpp_plugin(arping + SOURCES + arping.c + arping_api.c + + API_FILES + arping.api + + API_TEST_SOURCES + arping_test.c +) diff --git a/src/plugins/arping/FEATURE.yaml b/src/plugins/arping/FEATURE.yaml new file mode 100644 index 00000000000..c947b17c6a4 --- /dev/null +++ b/src/plugins/arping/FEATURE.yaml @@ -0,0 +1,9 @@ +--- +name: arping command +maintainer: Steven Luong +features: + - arping command to send either gratuitous or ARP request to the remote + - support both IPv4 and IPv6 +description: "arping command" +state: production +properties: [API, CLI, STATS, MULTITHREAD] diff --git a/src/plugins/arping/arping.api b/src/plugins/arping/arping.api new file mode 100644 index 00000000000..f797b8cf3aa --- /dev/null +++ b/src/plugins/arping/arping.api @@ -0,0 +1,61 @@ +/* + *------------------------------------------------------------------ + * 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. + *------------------------------------------------------------------ + */ + +option version = "1.0.0"; +import "vnet/interface_types.api"; +import "vnet/ip/ip_types.api"; + +/** \brief + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param address - address to send arp request or gratuitous arp. + @param sw_if_index - interface to send + @param repeat - number of packets to send + @param interval - if more than 1 packet is sent, the delay between send + @param is_garp - is garp or arp request +*/ + +define arping +{ + u32 client_index; + u32 context; + vl_api_address_t address; + vl_api_interface_index_t sw_if_index; + bool is_garp; + u32 repeat [default=1]; + f64 interval [default=1.0]; + option vat_help = "
[gratuitouss] [repeat ] [interval ]"; +}; + +/** \brief + @param context - sender context, to match reply w/ request + @param retval - return value for request + @reply_count - return value for reply count +*/ + +define arping_reply +{ + u32 context; + i32 retval; + u32 reply_count; +}; + +/* + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/arping/arping.c b/src/plugins/arping/arping.c new file mode 100644 index 00000000000..6c5836b9b9b --- /dev/null +++ b/src/plugins/arping/arping.c @@ -0,0 +1,797 @@ +/* + * 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 +#include +#include +#include +#include + +arping_main_t arping_main; + +#define foreach_arping_error _ (NONE, "no error") + +typedef enum +{ +#define _(f, s) ARPING_ERROR_##f, + foreach_arping_error +#undef _ + ARPING_N_ERROR, +} arping__error_t; + +static char *arping_error_strings[] = { +#define _(n, s) s, + foreach_arping_error +#undef _ +}; + +#define foreach_arping \ + _ (DROP, "error-drop") \ + _ (IO, "interface-output") + +typedef enum +{ +#define _(sym, str) ARPING_NEXT_##sym, + foreach_arping +#undef _ + ARPING_N_NEXT, +} arping_next_t; + +typedef struct arping_trace_t_ +{ + u32 sw_if_index; + u16 arp_opcode; + ethernet_arp_ip4_over_ethernet_address_t reply; +} arping_trace_t; + +typedef enum +{ +#define _(sym, str) ARPING6_NEXT_##sym, + foreach_arping +#undef _ + ARPING6_N_NEXT, +} arping6_next_t; + +typedef CLIB_PACKED (struct { + mac_address_t mac; + ip6_address_t ip6; +}) ethernet_arp_ip6_over_ethernet_address_t; + +typedef struct arping6_trace_t_ +{ + u32 sw_if_index; + u8 type; + ethernet_arp_ip6_over_ethernet_address_t reply; +} arping6_trace_t; + +/* packet trace format function */ +static u8 * +format_arping_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 *); + arping_trace_t *t = va_arg (*args, arping_trace_t *); + + s = format (s, "sw-if-index: %u, opcode: %U, from %U (%U)", t->sw_if_index, + format_ethernet_arp_opcode, t->arp_opcode, format_mac_address, + &t->reply.mac, format_ip4_address, &t->reply.ip4); + + return s; +} + +static u8 * +format_arping6_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 *); + arping6_trace_t *t = va_arg (*args, arping6_trace_t *); + + s = format (s, "sw-if-index: %u, type: %u, from %U (%U)", t->sw_if_index, + t->type, format_mac_address, &t->reply.mac, format_ip6_address, + &t->reply.ip6); + + return s; +} + +VLIB_NODE_FN (arping_input_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; + arping_next_t next_index; + arping_main_t *am = &arping_main; + + next_index = node->cached_next_index; + n_left_from = frame->n_vectors; + from = vlib_frame_vector_args (frame); + + while (n_left_from > 0) + { + vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); + + while (n_left_from >= 2 && n_left_to_next >= 2) + { + u32 next0, next1, bi0, bi1; + vlib_buffer_t *b0, *b1; + ethernet_arp_header_t *arp0, *arp1; + u32 sw_if_index0, sw_if_index1; + arping_intf_t *aif0, *aif1; + + bi0 = to_next[0] = from[0]; + bi1 = to_next[1] = from[1]; + + from += 2; + n_left_from -= 2; + to_next += 2; + n_left_to_next -= 2; + + next0 = next1 = ARPING_NEXT_DROP; + + b0 = vlib_get_buffer (vm, bi0); + b1 = vlib_get_buffer (vm, bi1); + + arp0 = vlib_buffer_get_current (b0); + arp1 = vlib_buffer_get_current (b1); + + vnet_feature_next (&next0, b0); + vnet_feature_next (&next1, b1); + + sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX]; + sw_if_index1 = vnet_buffer (b1)->sw_if_index[VLIB_RX]; + + if (PREDICT_TRUE (arp0->opcode == + clib_host_to_net_u16 (ETHERNET_ARP_OPCODE_reply))) + { + aif0 = am->interfaces[sw_if_index0]; + if (PREDICT_TRUE (aif0->address.ip.ip4.as_u32 == + arp0->ip4_over_ethernet[0].ip4.as_u32)) + { + aif0->recv.from4.ip4.as_u32 = + arp0->ip4_over_ethernet[0].ip4.as_u32; + clib_memcpy_fast (&aif0->recv.from4.mac, + &arp0->ip4_over_ethernet[0].mac, 6); + aif0->reply_count++; + } + } + if (PREDICT_TRUE (arp1->opcode == + clib_host_to_net_u16 (ETHERNET_ARP_OPCODE_reply))) + { + aif1 = am->interfaces[sw_if_index1]; + if (PREDICT_TRUE (aif1->address.ip.ip4.as_u32 == + arp1->ip4_over_ethernet[0].ip4.as_u32)) + { + aif1->recv.from4.ip4.as_u32 = + arp1->ip4_over_ethernet[0].ip4.as_u32; + clib_memcpy_fast (&aif1->recv.from4.mac, + &arp0->ip4_over_ethernet[0].mac, 6); + aif1->reply_count++; + } + } + + if (PREDICT_FALSE ((b0->flags & VLIB_BUFFER_IS_TRACED))) + { + arping_trace_t *t = vlib_add_trace (vm, node, b0, sizeof (*t)); + t->sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_RX]; + t->arp_opcode = clib_host_to_net_u16 (arp0->opcode); + t->reply.ip4.as_u32 = arp0->ip4_over_ethernet[0].ip4.as_u32; + clib_memcpy_fast (&t->reply.mac, &arp0->ip4_over_ethernet[0].mac, + 6); + } + if (PREDICT_FALSE ((b1->flags & VLIB_BUFFER_IS_TRACED))) + { + arping_trace_t *t = vlib_add_trace (vm, node, b1, sizeof (*t)); + t->sw_if_index = vnet_buffer (b1)->sw_if_index[VLIB_RX]; + t->arp_opcode = clib_host_to_net_u16 (arp1->opcode); + t->reply.ip4.as_u32 = arp1->ip4_over_ethernet[0].ip4.as_u32; + clib_memcpy_fast (&t->reply.mac, &arp1->ip4_over_ethernet[0].mac, + 6); + } + + 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 next0, bi0; + vlib_buffer_t *b0; + ethernet_arp_header_t *arp0; + arping_intf_t *aif0; + u32 sw_if_index0; + + bi0 = to_next[0] = from[0]; + + from += 1; + n_left_from -= 1; + to_next += 1; + n_left_to_next -= 1; + next0 = ARPING_NEXT_DROP; + + b0 = vlib_get_buffer (vm, bi0); + arp0 = vlib_buffer_get_current (b0); + + vnet_feature_next (&next0, b0); + + sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX]; + + if (PREDICT_TRUE (arp0->opcode == + clib_host_to_net_u16 (ETHERNET_ARP_OPCODE_reply))) + { + aif0 = am->interfaces[sw_if_index0]; + if (PREDICT_TRUE (aif0->address.ip.ip4.as_u32 == + arp0->ip4_over_ethernet[0].ip4.as_u32)) + { + aif0->recv.from4.ip4.as_u32 = + arp0->ip4_over_ethernet[0].ip4.as_u32; + clib_memcpy_fast (&aif0->recv.from4.mac, + &arp0->ip4_over_ethernet[0].mac, 6); + aif0->reply_count++; + } + } + + if (PREDICT_FALSE ((b0->flags & VLIB_BUFFER_IS_TRACED))) + { + arping_trace_t *t = vlib_add_trace (vm, node, b0, sizeof (*t)); + t->sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_RX]; + t->arp_opcode = clib_host_to_net_u16 (arp0->opcode); + t->reply.ip4.as_u32 = arp0->ip4_over_ethernet[0].ip4.as_u32; + clib_memcpy_fast (&t->reply.mac, &arp0->ip4_over_ethernet[0].mac, + 6); + } + + 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); + } + + return frame->n_vectors; +} + +VLIB_REGISTER_NODE (arping_input_node) = +{ + .name = "arping-input",.vector_size = sizeof (u32),.format_trace = + format_arping_trace,.type = VLIB_NODE_TYPE_INTERNAL,.n_errors = + ARPING_N_ERROR,.error_strings = arping_error_strings,.n_next_nodes = + ARPING_N_NEXT,.next_nodes = + { + [ARPING_NEXT_DROP] = "error-drop",[ARPING_NEXT_IO] = "interface-output",} +,}; + +VNET_FEATURE_INIT (arping_feat_node, static) = { + .arc_name = "arp", + .node_name = "arping-input", + .runs_before = VNET_FEATURES ("arp-reply"), +}; + +VLIB_NODE_FN (arping6_input_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; + arping_next_t next_index; + arping_main_t *am = &arping_main; + + next_index = node->cached_next_index; + n_left_from = frame->n_vectors; + from = vlib_frame_vector_args (frame); + + while (n_left_from > 0) + { + vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); + + while (n_left_from >= 2 && n_left_to_next >= 2) + { + u32 next0, next1, bi0, bi1; + vlib_buffer_t *b0, *b1; + ip6_header_t *ip60, *ip61; + u32 sw_if_index0, sw_if_index1; + arping_intf_t *aif0, *aif1; + icmp6_neighbor_solicitation_or_advertisement_header_t *sol_adv0, + *sol_adv1; + icmp6_neighbor_discovery_ethernet_link_layer_address_option_t + *lladdr0, + *lladdr1; + + bi0 = to_next[0] = from[0]; + bi1 = to_next[1] = from[1]; + + from += 2; + n_left_from -= 2; + to_next += 2; + n_left_to_next -= 2; + + next0 = next1 = ARPING6_NEXT_DROP; + + b0 = vlib_get_buffer (vm, bi0); + b1 = vlib_get_buffer (vm, bi1); + + ip60 = vlib_buffer_get_current (b0); + ip61 = vlib_buffer_get_current (b1); + + vnet_feature_next (&next0, b0); + vnet_feature_next (&next1, b1); + + sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX]; + sw_if_index1 = vnet_buffer (b1)->sw_if_index[VLIB_RX]; + + sol_adv0 = ip6_next_header (ip60); + lladdr0 = + (icmp6_neighbor_discovery_ethernet_link_layer_address_option_t + *) (sol_adv0 + 1); + + if (PREDICT_TRUE (sol_adv0->icmp.type == + ICMP6_neighbor_advertisement)) + { + aif0 = am->interfaces[sw_if_index0]; + if (PREDICT_TRUE (clib_memcmp (&aif0->address.ip.ip6, + &sol_adv0->target_address, + sizeof (aif0->address.ip.ip6)) == + 0)) + { + clib_memcpy_fast (&aif0->recv.from6.ip6, + &sol_adv0->target_address, + sizeof (aif0->recv.from6.ip6)); + clib_memcpy_fast (&aif0->recv.from6.mac, + lladdr0->ethernet_address, 6); + aif0->reply_count++; + } + } + + sol_adv1 = ip6_next_header (ip61); + lladdr1 = + (icmp6_neighbor_discovery_ethernet_link_layer_address_option_t + *) (sol_adv1 + 1); + + if (PREDICT_TRUE (sol_adv1->icmp.type == + ICMP6_neighbor_advertisement)) + { + aif1 = am->interfaces[sw_if_index1]; + if (PREDICT_TRUE (clib_memcmp (&aif1->address.ip.ip6, + &sol_adv1->target_address, + sizeof (aif1->address.ip.ip6)) == + 0)) + { + clib_memcpy_fast (&aif1->recv.from6.ip6, + &sol_adv1->target_address, + sizeof (aif1->recv.from6.ip6)); + clib_memcpy_fast (&aif1->recv.from6.mac, + lladdr1->ethernet_address, 6); + aif1->reply_count++; + } + } + + if (PREDICT_FALSE ((b0->flags & VLIB_BUFFER_IS_TRACED))) + { + arping6_trace_t *t = vlib_add_trace (vm, node, b0, sizeof (*t)); + t->sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_RX]; + t->type = sol_adv0->icmp.type; + clib_memcpy_fast (&t->reply.ip6, &sol_adv0->target_address, + sizeof (t->reply.ip6)); + clib_memcpy_fast (&t->reply.mac, lladdr0->ethernet_address, 6); + } + if (PREDICT_FALSE ((b1->flags & VLIB_BUFFER_IS_TRACED))) + { + arping6_trace_t *t = vlib_add_trace (vm, node, b1, sizeof (*t)); + t->sw_if_index = vnet_buffer (b1)->sw_if_index[VLIB_RX]; + t->type = sol_adv1->icmp.type; + clib_memcpy_fast (&t->reply.ip6, &sol_adv1->target_address, + sizeof (t->reply.ip6)); + clib_memcpy_fast (&t->reply.mac, lladdr1->ethernet_address, 6); + } + + 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 next0, bi0; + vlib_buffer_t *b0; + arping_intf_t *aif0; + u32 sw_if_index0; + ip6_header_t *ip60; + icmp6_neighbor_solicitation_or_advertisement_header_t *sol_adv0; + icmp6_neighbor_discovery_ethernet_link_layer_address_option_t + *lladdr0; + + bi0 = to_next[0] = from[0]; + + from += 1; + n_left_from -= 1; + to_next += 1; + n_left_to_next -= 1; + next0 = ARPING_NEXT_DROP; + + b0 = vlib_get_buffer (vm, bi0); + ip60 = vlib_buffer_get_current (b0); + + vnet_feature_next (&next0, b0); + + sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX]; + + sol_adv0 = ip6_next_header (ip60); + lladdr0 = + (icmp6_neighbor_discovery_ethernet_link_layer_address_option_t + *) (sol_adv0 + 1); + if (PREDICT_TRUE (sol_adv0->icmp.type == + ICMP6_neighbor_advertisement)) + { + aif0 = am->interfaces[sw_if_index0]; + if (PREDICT_TRUE (clib_memcmp (&aif0->address.ip.ip6, + &sol_adv0->target_address, + sizeof (aif0->address.ip.ip6)) == + 0)) + { + clib_memcpy_fast (&aif0->recv.from6.ip6, + &sol_adv0->target_address, + sizeof (aif0->recv.from6.ip6)); + clib_memcpy_fast (&aif0->recv.from6.mac, + lladdr0->ethernet_address, 6); + aif0->reply_count++; + } + } + + if (PREDICT_FALSE ((b0->flags & VLIB_BUFFER_IS_TRACED))) + { + arping6_trace_t *t = vlib_add_trace (vm, node, b0, sizeof (*t)); + t->sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_RX]; + t->type = sol_adv0->icmp.type; + clib_memcpy_fast (&t->reply.ip6, &sol_adv0->target_address, + sizeof (t->reply.ip6)); + clib_memcpy_fast (&t->reply.mac, lladdr0->ethernet_address, 6); + } + + 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); + } + + return frame->n_vectors; +} + +VLIB_REGISTER_NODE (arping6_input_node) = +{ + .name = "arping6-input",.vector_size = sizeof (u32),.format_trace = + format_arping6_trace,.type = VLIB_NODE_TYPE_INTERNAL,.n_errors = + ARPING_N_ERROR,.error_strings = arping_error_strings,.n_next_nodes = + ARPING_N_NEXT,.next_nodes = + { + [ARPING6_NEXT_DROP] = "error-drop",[ARPING6_NEXT_IO] = "interface-output",} +,}; + +VNET_FEATURE_INIT (arping6_feat_node, static) = { + .arc_name = "ip6-local", + .node_name = "arping6-input", + .runs_before = VNET_FEATURES ("ip6-local-end-of-arc"), +}; + +static clib_error_t * +arping_neighbor_advertisement (vlib_main_t *vm, arping_args_t *args) +{ + vnet_main_t *vnm = vnet_get_main (); + u32 send_count = 0; + + while (args->repeat > 0) + { + send_count++; + if (args->address.version == AF_IP4) + { + if (args->silence == 0) + vlib_cli_output (vm, "Sending %u GARP to %U", send_count, + format_ip4_address, &args->address.ip.ip4); + ip4_neighbor_advertise (vm, vnm, args->sw_if_index, + &args->address.ip.ip4); + } + else + { + if (args->silence == 0) + vlib_cli_output (vm, "Sending %u Neighbor Advertisement to %U", + send_count, format_ip6_address, + &args->address.ip.ip6); + ip6_neighbor_advertise (vm, vnm, args->sw_if_index, + &args->address.ip.ip6); + } + args->repeat--; + if ((args->interval > 0.0) && (args->repeat > 0)) + vlib_process_suspend (vm, args->interval); + } + + return 0; +} + +static void +arping_vnet_feature_enable_disable (vlib_main_t *vm, const char *arc_name, + const char *node_name, u32 sw_if_index, + int enable_disable, void *feature_config, + u32 n_feature_config_bytes) +{ + vlib_worker_thread_barrier_sync (vm); + vnet_feature_enable_disable (arc_name, node_name, sw_if_index, + enable_disable, feature_config, + n_feature_config_bytes); + vlib_worker_thread_barrier_release (vm); +} + +static void +arping_vec_validate (vlib_main_t *vm, u32 sw_if_index) +{ + arping_main_t *am = &arping_main; + + if (sw_if_index >= vec_len (am->interfaces)) + { + vlib_worker_thread_barrier_sync (vm); + vec_validate (am->interfaces, sw_if_index); + vlib_worker_thread_barrier_release (vm); + } +} + +static clib_error_t * +arping_neighbor_probe_dst (vlib_main_t *vm, arping_args_t *args) +{ + arping_main_t *am = &arping_main; + u32 send_count = 0; + clib_error_t *error; + arping_intf_t aif; + + /* Disallow multiple sends on the same interface for now. Who needs it? */ + if (am->interfaces && (am->interfaces[args->sw_if_index] != 0)) + { + error = clib_error_return ( + 0, "arping command is in progress for the same interface. " + "Please try again later."); + args->rv = VNET_API_ERROR_INVALID_VALUE; + return error; + } + + arping_vec_validate (vm, args->sw_if_index); + clib_memset (&aif, 0, sizeof (aif)); + aif.interval = args->interval; + aif.repeat = args->repeat; + aif.reply_count = 0; + am->interfaces[args->sw_if_index] = &aif; + + clib_memcpy (&aif.address, &args->address, sizeof (aif.address)); + if (args->address.version == AF_IP4) + arping_vnet_feature_enable_disable (vm, "arp", "arping-input", + args->sw_if_index, 1, 0, 0); + else + arping_vnet_feature_enable_disable (vm, "ip6-local", "arping6-input", + args->sw_if_index, 1, 0, 0); + + while (args->repeat > 0) + { + send_count++; + if (args->address.version == AF_IP4) + { + if (args->silence == 0) + vlib_cli_output (vm, "Sending %u ARP Request to %U", send_count, + format_ip4_address, &args->address.ip.ip4); + ip4_neighbor_probe_dst (args->sw_if_index, &args->address.ip.ip4); + } + else + { + if (args->silence == 0) + vlib_cli_output (vm, "Sending %u Neighbor Solicitation to %U", + send_count, format_ip6_address, + &args->address.ip.ip6); + ip6_neighbor_probe_dst (args->sw_if_index, &args->address.ip.ip6); + } + args->repeat--; + if ((args->interval > 0.0) && (args->repeat > 0)) + vlib_process_suspend (vm, args->interval); + } + + /* wait for a second on the reply */ + u32 wait_count = 0; + while ((aif.reply_count < send_count) && (wait_count < 10)) + { + vlib_process_suspend (vm, 0.1); + wait_count++; + } + + if (args->address.version == AF_IP4) + { + clib_memcpy (&args->recv.from4, &aif.recv.from4, + sizeof (args->recv.from4)); + arping_vnet_feature_enable_disable (vm, "arp", "arping-input", + args->sw_if_index, 0, 0, 0); + } + else + { + clib_memcpy (&args->recv.from6, &aif.recv.from6, + sizeof (args->recv.from6)); + arping_vnet_feature_enable_disable (vm, "ip6-local", "arping6-input", + args->sw_if_index, 0, 0, 0); + } + args->reply_count = aif.reply_count; + + am->interfaces[args->sw_if_index] = 0; + + return 0; +} + +void +arping_run_command (vlib_main_t *vm, arping_args_t *args) +{ + if (args->is_garp) + args->error = arping_neighbor_advertisement (vm, args); + else + args->error = arping_neighbor_probe_dst (vm, args); +} + +static clib_error_t * +arping_ip_address (vlib_main_t *vm, unformat_input_t *input, + vlib_cli_command_t *cmd) +{ + clib_error_t *error = 0; + vnet_main_t *vnm = vnet_get_main (); + arping_args_t args = { 0 }; + f64 interval = ARPING_DEFAULT_INTERVAL; + + args.repeat = ARPING_DEFAULT_REPEAT; + args.interval = ARPING_DEFAULT_INTERVAL; + args.sw_if_index = ~0; + args.silence = 0; + + if (unformat (input, "gratuitous")) + args.is_garp = 1; + + if (unformat (input, "%U", unformat_ip4_address, &args.address.ip.ip4)) + args.address.version = AF_IP4; + else if (unformat (input, "%U", unformat_ip6_address, &args.address.ip.ip6)) + args.address.version = AF_IP6; + else + { + error = clib_error_return ( + 0, + "expecting IP4/IP6 address `%U'. Usage: arping [gratuitous] " + " [repeat ] [interval ]", + format_unformat_error, input); + goto done; + } + + if (!unformat_user (input, unformat_vnet_sw_interface, vnm, + &args.sw_if_index)) + { + error = clib_error_return (0, "unknown interface `%U'", + format_unformat_error, input); + goto done; + } + + /* parse the rest of the parameters in a cycle */ + while (!unformat_eof (input, NULL)) + { + if (unformat (input, "interval")) + { + if (!unformat (input, "%f", &interval)) + { + error = clib_error_return ( + 0, "expecting interval (floating point number) got `%U'", + format_unformat_error, input); + goto done; + } + args.interval = interval; + } + else if (unformat (input, "repeat")) + { + if (!unformat (input, "%u", &args.repeat)) + { + error = + clib_error_return (0, "expecting repeat count but got `%U'", + format_unformat_error, input); + goto done; + } + } + else + { + error = clib_error_return (0, "unknown input `%U'", + format_unformat_error, input); + goto done; + } + } + + arping_run_command (vm, &args); + + if (args.reply_count) + { + if (args.address.version == AF_IP4) + vlib_cli_output (vm, "Received %u ARP Replies from %U (%U)", + args.reply_count, format_mac_address, + &args.recv.from4.mac, format_ip4_address, + &args.recv.from4.ip4); + else + vlib_cli_output ( + vm, "Received %u ICMP6 neighbor advertisements from %U (%U)", + args.reply_count, format_mac_address, &args.recv.from6.mac, + format_ip6_address, &args.recv.from6.ip6); + } + else if (args.is_garp == 0) + vlib_cli_output (vm, "Received 0 Reply"); + + error = args.error; +done: + return error; +} +// clang-format off +/*? + * This command sends an ARP REQUEST or gratuitous ARP to network hosts. The + * address can be an IPv4 or IPv6 address. + * + * @cliexpar + * @parblock + * Example of how to send an IPv4 ARP REQUEST + * @cliexstart{arping 100.1.1.10 VirtualEthernet0/0/0 repeat 3 interval 1} + * Sending 1 ARP Request to 100.1.1.10 + * Sending 2 ARP Request to 100.1.1.10 + * Sending 3 ARP Request to 100.1.1.10 + * Received 3 ARP Replies from 52:53:00:00:04:01 (100.1.1.10) + * @cliexend + * + * Example of how to send an IPv6 Neighbor Solicitation + * @cliexstart{arping 2001:192::2 VirtualEthernet0/0/0 repeat 3 interval 1} + * Sending 1 Neighbor Solicitation to 2001:192::2 + * Sending 2 Neighbor Solicitation to 2001:192::2 + * Sending 3 Neighbor Solicitation to 2001:192::2 + * Received 3 ICMP6 neighbor advertisements from 52:53:00:00:04:01 (2001:192::2) + * @cliexend + * + * Example of how to send an IPv4 gratuitous ARP + * @cliexstart{arping gratuitous 100.1.1.100 VirtualEthernet0/0/0 repeat 2} + * Sending 1 GARP to 100.1.1.100 + * Sending 2 GARP to 100.1.1.100 + * @cliexend + * @endparblock + * +?*/ +// clang-format on +VLIB_CLI_COMMAND (arping_command, static) = { + .path = "arping", + .function = arping_ip_address, + .short_help = "arping [gratuitous] {addr} {interface}" + " [interval {sec}] [repeat {cnt}]", + .is_mp_safe = 1, +}; + +static clib_error_t * +arping_cli_init (vlib_main_t *vm) +{ + /* initialize binary API */ + arping_plugin_api_hookup (vm); + + return 0; +} + +VLIB_INIT_FUNCTION (arping_cli_init); + +VLIB_PLUGIN_REGISTER () = { + .version = VPP_BUILD_VER, + .description = "Arping (arping)", +}; + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/arping/arping.h b/src/plugins/arping/arping.h new file mode 100644 index 00000000000..d07e7cfb683 --- /dev/null +++ b/src/plugins/arping/arping.h @@ -0,0 +1,74 @@ +/* + * 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. + */ +#ifndef included_arping_arping_h +#define included_arping_arping_h + +#include +#include + +#define ARPING_DEFAULT_INTERVAL 1.0 +#define ARPING_DEFAULT_REPEAT 1 + +typedef struct arping6_ip6_reply_t +{ + mac_address_t mac; + ip6_address_t ip6; +} arping6_ip6_reply_t; + +typedef CLIB_PACKED (union arping46_reply_ { + ethernet_arp_ip4_over_ethernet_address_t from4; + arping6_ip6_reply_t from6; +}) arping46_reply_t; + +typedef struct arping_intf_t +{ + CLIB_CACHE_LINE_ALIGN_MARK (cacheline0); + f64 interval; + u32 repeat; + ip_address_t address; + + arping46_reply_t recv; + u32 reply_count; +} arping_intf_t; + +typedef struct arping_main_t +{ + arping_intf_t *arping_interfaces; + arping_intf_t **interfaces; + u16 msg_id_base; +} arping_main_t; + +typedef struct arping_args_t +{ + ip_address_t address; + u32 sw_if_index; + u32 repeat; + f64 interval; + u8 is_garp; + u8 silence; + + /* reply */ + i32 rv; + u32 reply_count; + arping46_reply_t recv; + clib_error_t *error; +} arping_args_t; + +extern arping_main_t arping_main; + +extern clib_error_t *arping_plugin_api_hookup (vlib_main_t *vm); +extern void arping_run_command (vlib_main_t *vm, arping_args_t *args); + +#endif /* included_arping_arping_h */ diff --git a/src/plugins/arping/arping_api.c b/src/plugins/arping/arping_api.c new file mode 100644 index 00000000000..015c6148f5e --- /dev/null +++ b/src/plugins/arping/arping_api.c @@ -0,0 +1,87 @@ +/* + *------------------------------------------------------------------ + * 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 +#include +#include + +#include + +#include +#include + +/* define message IDs */ +#include +#include + +#include + +static void +vl_api_arping_t_handler (vl_api_arping_t *mp) +{ + vlib_main_t *vm = vlib_get_main (); + arping_main_t *am = &arping_main; + vl_api_arping_reply_t *rmp; + arping_args_t args = { 0 }; + int rv; + + if (mp->sw_if_index != ~0) + VALIDATE_SW_IF_INDEX (mp); + + ip_address_decode2 (&mp->address, &args.address); + args.interval = clib_net_to_host_f64 (mp->interval); + args.repeat = ntohl (mp->repeat); + args.is_garp = mp->is_garp; + args.sw_if_index = ntohl (mp->sw_if_index); + args.silence = 1; + + arping_run_command (vm, &args); + rv = args.rv; + + BAD_SW_IF_INDEX_LABEL; + + REPLY_MACRO2 (VL_API_ARPING_REPLY + am->msg_id_base, + ({ rmp->reply_count = ntohl (args.reply_count); })); +} + +/* set tup the API message handling tables */ +#include +clib_error_t * +arping_plugin_api_hookup (vlib_main_t *vm) +{ + arping_main_t *am = &arping_main; + api_main_t *vam = vlibapi_get_main (); + + /* ask for a correctly-sized block of API message decode slots */ + am->msg_id_base = setup_message_id_table (); + + /* Mark API as mp safe */ + vam->is_mp_safe[am->msg_id_base + VL_API_ARPING] = 1; + + return 0; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/arping/arping_test.c b/src/plugins/arping/arping_test.c new file mode 100644 index 00000000000..9001b7098a7 --- /dev/null +++ b/src/plugins/arping/arping_test.c @@ -0,0 +1,167 @@ +/* + * arping VAT support + * + * 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 + +#include +#include + +#define __plugin_msg_base arping_test_main.msg_id_base +#include + +/* declare message IDs */ +#include +#include +#include +#include +#include + +typedef struct +{ + /* API message ID base */ + u16 msg_id_base; + u32 ping_id; + vat_main_t *vat_main; +} arping_test_main_t; + +arping_test_main_t arping_test_main; + +/* arping request API */ +static int +api_arping (vat_main_t *vam) +{ + vl_api_arping_t *mp; + arping_args_t args = { 0 }; + int ret; + unformat_input_t *input = vam->input; + vnet_main_t *vnm = vnet_get_main (); + f64 interval = ARPING_DEFAULT_INTERVAL; + vl_api_control_ping_t *mp_ping; + arping_test_main_t *atm = &arping_test_main; + + args.repeat = ARPING_DEFAULT_REPEAT; + args.interval = ARPING_DEFAULT_INTERVAL; + args.sw_if_index = ~0; + + if (unformat (input, "gratuitous")) + args.is_garp = 1; + + if (unformat (input, "%U", unformat_ip4_address, &args.address.ip.ip4)) + args.address.version = AF_IP4; + else if (unformat (input, "%U", unformat_ip6_address, &args.address.ip.ip6)) + args.address.version = AF_IP6; + else + { + errmsg ("expecting IP4/IP6 address `%U'. Usage: arping [gratuitous] " + " [repeat ] [interval ]", + format_unformat_error, input); + return -99; + } + + if (!unformat_user (input, unformat_vnet_sw_interface, vnm, + &args.sw_if_index)) + { + errmsg ("unknown interface `%U'", format_unformat_error, input); + return -99; + } + + /* parse the rest of the parameters in a cycle */ + while (!unformat_eof (input, NULL)) + { + if (unformat (input, "interval")) + { + if (!unformat (input, "%f", &interval)) + { + errmsg ("expecting interval (floating point number) got `%U'", + format_unformat_error, input); + return -99; + } + args.interval = interval; + } + else if (unformat (input, "repeat")) + { + if (!unformat (input, "%u", &args.repeat)) + { + errmsg ("expecting repeat count but got `%U'", + format_unformat_error, input); + return -99; + } + } + else + { + errmsg ("unknown input `%U'", format_unformat_error, input); + return -99; + } + } + + M (ARPING, mp); + + mp->interval = clib_host_to_net_f64 (args.interval); + mp->repeat = clib_host_to_net_u32 (args.repeat); + mp->is_garp = args.is_garp; + mp->sw_if_index = clib_host_to_net_u32 (args.sw_if_index); + ip_address_encode2 (&args.address, &mp->address); + + S (mp); + + /* Use a control ping for synchronization */ + if (!atm->ping_id) + atm->ping_id = vl_msg_api_get_msg_index ((u8 *) (VL_API_CONTROL_PING_CRC)); + mp_ping = vl_msg_api_alloc_as_if_client (sizeof (*mp_ping)); + mp_ping->_vl_msg_id = htons (atm->ping_id); + mp_ping->client_index = vam->my_client_index; + + fformat (vam->ofp, "Sending ping id=%d\n", atm->ping_id); + + vam->result_ready = 0; + S (mp_ping); + + W (ret); + + return ret; +} + +/* arping-create reply handler */ +static void +vl_api_arping_reply_t_handler (vl_api_arping_reply_t *mp) +{ + vat_main_t *vam = arping_test_main.vat_main; + i32 retval = ntohl (mp->retval); + + if (retval == 0) + { + fformat (vam->ofp, "arping request reply count = %d\n", + ntohl (mp->reply_count)); + } + + vam->retval = retval; + vam->result_ready = 1; +} + +#include + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/arping/test/test_arping.py b/src/plugins/arping/test/test_arping.py new file mode 100644 index 00000000000..bd8b6250a54 --- /dev/null +++ b/src/plugins/arping/test/test_arping.py @@ -0,0 +1,251 @@ +from scapy.layers.l2 import ARP +from scapy.layers.inet6 import ICMPv6ND_NS, ICMPv6ND_NA, IPv6 + +from framework import VppTestCase + +""" TestArping is a subclass of VPPTestCase classes. + +Basic test for sanity check of arping. + +""" + + +class TestArping(VppTestCase): + """ Arping Test Case """ + + @classmethod + def setUpClass(cls): + super(TestArping, cls).setUpClass() + try: + cls.create_pg_interfaces(range(2)) + cls.interfaces = list(cls.pg_interfaces) + + for i in cls.interfaces: + i.admin_up() + i.config_ip4() + i.config_ip6() + i.disable_ipv6_ra() + i.resolve_arp() + i.resolve_ndp() + except Exception: + super(TestArping, cls).tearDownClass() + raise + + @classmethod + def tearDownClass(cls): + super(TestArping, cls).tearDownClass() + + def tearDown(self): + super(TestArping, self).tearDown() + + def show_commands_at_teardown(self): + self.logger.info(self.vapi.cli("show hardware")) + + def verify_arping_request(self, p, src, dst): + arp = p[ARP] + self.assertEqual(arp.hwtype, 0x0001) + self.assertEqual(arp.ptype, 0x0800) + self.assertEqual(arp.hwlen, 6) + self.assertEqual(arp.op, 1) + self.assertEqual(arp.psrc, src) + self.assertEqual(arp.pdst, dst) + + def verify_arping_ip6_ns(self, p, src, dst): + icmpv6 = p[ICMPv6ND_NS] + self.assertEqual(icmpv6.type, 135) + self.assertEqual(icmpv6.tgt, dst) + ipv6 = p[IPv6] + self.assertEqual(src, ipv6.src) + + def verify_arping_ip6_na(self, p, src, dst): + icmpv6 = p[ICMPv6ND_NA] + self.assertEqual(icmpv6.type, 136) + self.assertEqual(icmpv6.tgt, dst) + ipv6 = p[IPv6] + self.assertEqual(src, ipv6.src) + + def test_arping_ip4_arp_request_cli(self): + """ arping IP4 arp request CLI test """ + try: + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + remote_ip4 = self.pg1.remote_ip4 + + ping_cmd = "arping " + remote_ip4 + "pg1 repeat 5 interval 0.1" + ret = self.vapi.cli(ping_cmd) + self.logger.info(ret) + + ping_cmd = "arping " + remote_ip4 + "pg1" + ret = self.vapi.cli(ping_cmd) + self.logger.info(ret) + + out = self.pg1.get_capture(6) + for p in out: + self.verify_arping_request(p, self.pg1.local_ip4, + self.pg1.remote_ip4) + finally: + self.vapi.cli("show error") + + def test_arping_ip4_garp_cli(self): + """ arping ip4 gratuitous arp CLI test """ + try: + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + ping_cmd = ("arping gratuitous" + self.pg1.local_ip4 + + "pg1 repeat 5 interval 0.1") + ret = self.vapi.cli(ping_cmd) + self.logger.info(ret) + + ping_cmd = "arping gratuitous" + self.pg1.local_ip4 + "pg1" + ret = self.vapi.cli(ping_cmd) + self.logger.info(ret) + + out = self.pg1.get_capture(6) + for p in out: + self.verify_arping_request(p, self.pg1.local_ip4, + self.pg1.local_ip4) + finally: + self.vapi.cli("show error") + + def test_arping_ip4_arp_request_api(self): + """ arping ip4 arp request API test """ + try: + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + remote_ip4 = self.pg1.remote_ip4 + + ret = self.vapi.arping(address=remote_ip4, + sw_if_index=self.pg1.sw_if_index, + is_garp=0, repeat=5, interval=0.1) + self.logger.info(ret) + + ret = self.vapi.arping(address=remote_ip4, + sw_if_index=self.pg1.sw_if_index, + is_garp=0) + self.logger.info(ret) + + out = self.pg1.get_capture(6) + for p in out: + self.verify_arping_request(p, self.pg1.local_ip4, + self.pg1.remote_ip4) + finally: + self.vapi.cli("show error") + + def test_arping_ip4_garp_api(self): + """ arping ip4 gratuitous arp API test """ + try: + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + ret = self.vapi.arping(address=self.pg1.local_ip4, + sw_if_index=self.pg1.sw_if_index, + is_garp=1, repeat=5, interval=0.1) + self.logger.info(ret) + + ret = self.vapi.arping(address=self.pg1.local_ip4, + sw_if_index=self.pg1.sw_if_index, + is_garp=1) + self.logger.info(ret) + + out = self.pg1.get_capture(6) + for p in out: + self.verify_arping_request(p, self.pg1.local_ip4, + self.pg1.local_ip4) + finally: + self.vapi.cli("show error") + + def test_arping_ip6_ns_cli(self): + """ arping IP6 neighbor solicitation CLI test """ + try: + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + remote_ip6 = self.pg1.remote_ip6 + + ping_cmd = "arping " + remote_ip6 + "pg1 repeat 5 interval 0.1" + ret = self.vapi.cli(ping_cmd) + self.logger.info(ret) + + ping_cmd = "arping " + remote_ip6 + "pg1" + ret = self.vapi.cli(ping_cmd) + self.logger.info(ret) + + out = self.pg1.get_capture(6) + for p in out: + self.verify_arping_ip6_ns(p, self.pg1.local_ip6, + self.pg1.remote_ip6) + finally: + self.vapi.cli("show error") + + def test_arping_ip6_ns_api(self): + """ arping ip6 neighbor solicitation API test """ + try: + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + remote_ip6 = self.pg1.remote_ip6 + + ret = self.vapi.arping(address=remote_ip6, + sw_if_index=self.pg1.sw_if_index, + is_garp=0, repeat=5, interval=0.1) + self.logger.info(ret) + + ret = self.vapi.arping(address=remote_ip6, + sw_if_index=self.pg1.sw_if_index, + is_garp=0) + self.logger.info(ret) + + out = self.pg1.get_capture(6) + for p in out: + self.verify_arping_ip6_ns(p, self.pg1.local_ip6, + self.pg1.remote_ip6) + finally: + self.vapi.cli("show error") + + def test_arping_ip6_na_cli(self): + """ arping ip6 neighbor advertisement CLI test """ + try: + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + ping_cmd = ("arping gratuitous" + self.pg1.local_ip6 + + "pg1 repeat 5 interval 0.1") + ret = self.vapi.cli(ping_cmd) + self.logger.info(ret) + + ping_cmd = "arping gratuitous" + self.pg1.local_ip6 + "pg1" + ret = self.vapi.cli(ping_cmd) + self.logger.info(ret) + + out = self.pg1.get_capture(6) + for p in out: + self.verify_arping_ip6_na(p, self.pg1.local_ip6, + self.pg1.local_ip6) + finally: + self.vapi.cli("show error") + + def test_arping_ip6_na_api(self): + """ arping ip6 neighbor advertisement API test """ + try: + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + ret = self.vapi.arping(address=self.pg1.local_ip6, + sw_if_index=self.pg1.sw_if_index, + is_garp=1, repeat=5, interval=0.1) + self.logger.info(ret) + + ret = self.vapi.arping(address=self.pg1.local_ip6, + sw_if_index=self.pg1.sw_if_index, + is_garp=1) + self.logger.info(ret) + + out = self.pg1.get_capture(6) + for p in out: + self.verify_arping_ip6_na(p, self.pg1.local_ip6, + self.pg1.local_ip6) + finally: + self.vapi.cli("show error") + + +if __name__ == '__main__': + unittest.main(testRunner=VppTestRunner) -- cgit 1.2.3-korg