From 17ff3c1fa5687255a118c53223fa2cd49132d929 Mon Sep 17 00:00:00 2001 From: Neale Ranns Date: Wed, 4 Jul 2018 10:24:24 -0700 Subject: [PATCH] Pipes A pipe resembles a unix pipe. Each end of the pipe is a full VPP interface. pipes can be used for e.g. packet recirculation, inter-BD, etc. Change-Id: I185bb9fb43dd233ff45da63ac1b85ae2e1ceca16 Signed-off-by: Neale Ranns --- src/vnet.am | 13 + src/vnet/devices/pipe/pipe.api | 90 +++++ src/vnet/devices/pipe/pipe.c | 802 +++++++++++++++++++++++++++++++++++++++ src/vnet/devices/pipe/pipe.h | 72 ++++ src/vnet/devices/pipe/pipe_api.c | 177 +++++++++ src/vnet/ethernet/interface.c | 7 +- src/vnet/ethernet/node.c | 11 +- src/vnet/interface.c | 26 +- src/vnet/interface.h | 1 + src/vnet/interface_funcs.h | 18 +- src/vnet/ip/ip6_neighbor.c | 1 + src/vnet/vnet_all_api_h.h | 1 + test/test_pipe.py | 255 +++++++++++++ test/vpp_papi_provider.py | 12 + 14 files changed, 1479 insertions(+), 7 deletions(-) create mode 100644 src/vnet/devices/pipe/pipe.api create mode 100644 src/vnet/devices/pipe/pipe.c create mode 100644 src/vnet/devices/pipe/pipe.h create mode 100644 src/vnet/devices/pipe/pipe_api.c create mode 100644 test/test_pipe.py diff --git a/src/vnet.am b/src/vnet.am index 0a9cb5ffee4..d6a5d524e53 100644 --- a/src/vnet.am +++ b/src/vnet.am @@ -944,6 +944,19 @@ nobase_include_HEADERS += \ API_FILES += vnet/devices/tap/tapv2.api +######################################## +# tap interface (with virtio backend) +######################################## + +libvnet_la_SOURCES += \ + vnet/devices/pipe/pipe_api.c \ + vnet/devices/pipe/pipe.c + +nobase_include_HEADERS += \ + vnet/devices/pipe/pipe.h + +API_FILES += vnet/devices/pipe/pipe.api + ######################################## # session managmeent ######################################## diff --git a/src/vnet/devices/pipe/pipe.api b/src/vnet/devices/pipe/pipe.api new file mode 100644 index 00000000000..d3dfd16bcd4 --- /dev/null +++ b/src/vnet/devices/pipe/pipe.api @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2018 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. + */ + +/** \file + + This file defines vpe control-plane API messages for + the Linux kernel PIPE device driver +*/ + +option version = "1.0.0"; + +/** \brief Initialize a new pipe interface with the given paramters + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_specified - if non-0, a specific user_instance is being requested + @param user_instance - requested instance, ~0 => dynamically allocate +*/ +define pipe_create +{ + u32 client_index; + u32 context; + u8 is_specified; + u32 user_instance; +}; + +/** \brief Reply for pipe create reply + @param context - returned sender context, to match reply w/ request + @param retval - return code + @param parent_sw_if_index - software index allocated for the new pipe parent interface + Use the parent interface for link up/down and to delete + @param pipe_sw_if_index - the two SW indicies that form the ends of the pipe. +*/ +define pipe_create_reply +{ + u32 context; + i32 retval; + u32 parent_sw_if_index; + u32 pipe_sw_if_index[2]; +}; + +/** \brief Delete pipe interface + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param parnet_sw_if_index - interface index of existing pipe interface +*/ +autoreply define pipe_delete +{ + u32 client_index; + u32 context; + u32 parent_sw_if_index; +}; + +/** \brief Dump pipe interfaces request */ +define pipe_dump +{ + u32 client_index; + u32 context; +}; + +/** \brief Reply for pipe dump request + @param sw_if_index - software index of pipe interface + @param parent_sw_if_index - software index allocated for the pipe parent interface + @param pipe_sw_if_index - the two SW indicies that form the ends of the pipe. + @param instance - instance allocated +*/ +define pipe_details +{ + u32 context; + u32 parent_sw_if_index; + u32 pipe_sw_if_index[2]; + u32 instance; +}; + +/* + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/devices/pipe/pipe.c b/src/vnet/devices/pipe/pipe.c new file mode 100644 index 00000000000..29f54e19917 --- /dev/null +++ b/src/vnet/devices/pipe/pipe.c @@ -0,0 +1,802 @@ +/* + * Copyright (c) 2018 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 + +/** + * @file + * @brief Pipe Interfaces. + * + * A pipe interface, like the UNIX pipe, is a pair of interfaces + * that are joined. + */ +const static pipe_t PIPE_INVALID = { + .sw_if_index = ~0, + .subint = {0}, +}; + +/** + * Various 'module' lavel variables + */ +typedef struct pipe_main_t_ +{ + /** + * Allocated pipe instances + */ + uword *instances; + + /** + * the per-swif-index array of pipes. Each end of the pipe is stored againt + * its respective sw_if_index + */ + pipe_t *pipes; +} pipe_main_t; + +static pipe_main_t pipe_main; + +/* + * The pipe rewrite is the same size as an ethernet header (since it + * is an ethernet interface and the DP is optimised for writing + * sizeof(ethernet_header_t) rewirtes. Hwoever, there are no MAC addresses + * since pipes don't have them. + */ +static u8 * +pipe_build_rewrite (vnet_main_t * vnm, + u32 sw_if_index, + vnet_link_t link_type, const void *dst_address) +{ + ethernet_header_t *h; + ethernet_type_t type; + u8 *rewrite = NULL; + + switch (link_type) + { +#define _(a,b) case VNET_LINK_##a: type = ETHERNET_TYPE_##b; break + _(IP4, IP4); + _(IP6, IP6); + _(MPLS, MPLS); + _(ARP, ARP); +#undef _ + default: + return NULL; + } + + vec_validate (rewrite, sizeof (ethernet_header_t)); + + h = (ethernet_header_t *) rewrite; + h->type = clib_host_to_net_u16 (type); + + return (rewrite); +} + +/* *INDENT-OFF* */ +VNET_HW_INTERFACE_CLASS (pipe_hw_interface_class) = { + .name = "Pipe", + .build_rewrite = pipe_build_rewrite, + .flags = VNET_HW_INTERFACE_CLASS_FLAG_P2P, +}; +/* *INDENT-ON* */ + +pipe_t * +pipe_get (u32 sw_if_index) +{ + vec_validate_init_empty (pipe_main.pipes, sw_if_index, PIPE_INVALID); + + return (&pipe_main.pipes[sw_if_index]); +} + +uword +unformat_pipe_interface (unformat_input_t * input, va_list * args) +{ + vnet_main_t *vnm = va_arg (*args, vnet_main_t *); + u32 *result = va_arg (*args, u32 *); + u32 hw_if_index; + ethernet_main_t *em = ðernet_main; + ethernet_interface_t *eif; + + if (!unformat_user (input, unformat_vnet_hw_interface, vnm, &hw_if_index)) + return 0; + + eif = ethernet_get_interface (em, hw_if_index); + if (eif) + { + *result = hw_if_index; + return 1; + } + return 0; +} + +#define VNET_PIPE_TX_NEXT_ETHERNET_INPUT VNET_INTERFACE_TX_N_NEXT + +/* + * The TX function bounces the packets back to pipe-rx with the TX interface + * swapped to the RX. + */ +static uword +pipe_tx (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame) +{ + u32 n_left_from, n_left_to_next, n_copy, *from, *to_next; + u32 next_index = VNET_PIPE_TX_NEXT_ETHERNET_INPUT; + u32 i, sw_if_index = 0; + u32 n_pkts = 0, n_bytes = 0; + u32 thread_index = vm->thread_index; + vnet_main_t *vnm = vnet_get_main (); + vnet_interface_main_t *im = &vnm->interface_main; + vlib_buffer_t *b; + pipe_t *pipe; + + n_left_from = frame->n_vectors; + from = vlib_frame_args (frame); + + while (n_left_from > 0) + { + vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); + + n_copy = clib_min (n_left_from, n_left_to_next); + + clib_memcpy (to_next, from, n_copy * sizeof (from[0])); + n_left_to_next -= n_copy; + n_left_from -= n_copy; + i = 0; + while (i < n_copy) + { + b = vlib_get_buffer (vm, from[i]); + sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_TX]; + + pipe = &pipe_main.pipes[sw_if_index]; + // Set up RX index to be recv'd by the other end of the pipe + vnet_buffer (b)->sw_if_index[VLIB_RX] = pipe->sw_if_index; + vnet_buffer (b)->sw_if_index[VLIB_TX] = ~0; + + i++; + n_pkts++; + n_bytes += vlib_buffer_length_in_chain (vm, b); + } + from += n_copy; + + vlib_put_next_frame (vm, node, next_index, n_left_to_next); + + /* increment TX interface stat */ + vlib_increment_combined_counter (im->combined_sw_if_counters + + VNET_INTERFACE_COUNTER_TX, + thread_index, sw_if_index, n_pkts, + n_bytes); + } + + return n_left_from; +} + +static u8 * +format_pipe_name (u8 * s, va_list * args) +{ + u32 dev_instance = va_arg (*args, u32); + return format (s, "pipe%d", dev_instance); +} + +static clib_error_t * +pipe_admin_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags) +{ + vnet_hw_interface_t *hi; + u32 id, sw_if_index; + + u32 hw_flags = ((flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP) ? + VNET_HW_INTERFACE_FLAG_LINK_UP : 0); + vnet_hw_interface_set_flags (vnm, hw_if_index, hw_flags); + + /* *INDENT-OFF* */ + hi = vnet_get_hw_interface (vnm, hw_if_index); + hash_foreach (id, sw_if_index, hi->sub_interface_sw_if_index_by_id, + ({ + vnet_sw_interface_set_flags (vnm, sw_if_index, flags); + })); + /* *INDENT-ON* */ + + return (NULL); +} + +/* *INDENT-OFF* */ +VNET_DEVICE_CLASS (pipe_device_class) = { + .name = "Pipe", + .format_device_name = format_pipe_name, + .tx_function = pipe_tx, + .admin_up_down_function = pipe_admin_up_down, +}; +/* *INDENT-ON* */ + +#define foreach_pipe_rx_next \ + _ (DROP, "error-drop") + +typedef enum pipe_rx_next_t_ +{ +#define _(s,n) PIPE_RX_NEXT_##s, + foreach_pipe_rx_next +#undef _ + PIPE_RX_N_NEXT, +} pipe_rx_next_t; + +typedef struct pipe_rx_trace_t_ +{ + u8 packet_data[32]; +} pipe_rx_trace_t; + +static u8 * +format_pipe_rx_trace (u8 * s, va_list * va) +{ + CLIB_UNUSED (vlib_main_t * vm) = va_arg (*va, vlib_main_t *); + CLIB_UNUSED (vlib_node_t * node) = va_arg (*va, vlib_node_t *); + pipe_rx_trace_t *t = va_arg (*va, pipe_rx_trace_t *); + + s = format (s, "%U", format_ethernet_header, t->packet_data); + + return s; +} + +/* + * The pipe-rx node is a sibling of ethernet-input so steal it's + * next node mechanism + */ +static_always_inline void +pipe_determine_next_node (ethernet_main_t * em, + u32 is_l20, + u32 type0, + vlib_buffer_t * b0, pipe_rx_next_t * next0) +{ + if (is_l20) + { + *next0 = em->l2_next; + } + else if (type0 == ETHERNET_TYPE_IP4) + { + *next0 = em->l3_next.input_next_ip4; + } + else if (type0 == ETHERNET_TYPE_IP6) + { + *next0 = em->l3_next.input_next_ip6; + } + else if (type0 == ETHERNET_TYPE_MPLS) + { + *next0 = em->l3_next.input_next_mpls; + + } + else if (em->redirect_l3) + { + // L3 Redirect is on, the cached common next nodes will be + // pointing to the redirect node, catch the uncommon types here + *next0 = em->redirect_l3_next; + } + else + { + // uncommon ethertype, check table + u32 i0; + i0 = sparse_vec_index (em->l3_next.input_next_by_type, type0); + *next0 = vec_elt (em->l3_next.input_next_by_type, i0); + + // The table is not populated with LLC values, so check that now. + if (type0 < 0x600) + { + *next0 = PIPE_RX_NEXT_DROP; + } + } +} + +static_always_inline uword +pipe_rx (vlib_main_t * vm, + vlib_node_runtime_t * node, vlib_frame_t * from_frame) +{ + u32 n_left_from, next_index, *from, *to_next; + u32 n_left_to_next; + + from = vlib_frame_vector_args (from_frame); + n_left_from = from_frame->n_vectors; + + if (node->flags & VLIB_NODE_FLAG_TRACE) + vlib_trace_frame_buffers_only (vm, node, + from, + n_left_from, + sizeof (from[0]), + sizeof (pipe_rx_trace_t)); + + next_index = node->cached_next_index; + + while (n_left_from > 0) + { + 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, sw_if_index0, bi1, sw_if_index1; + pipe_rx_next_t next0, next1; + ethernet_header_t *e0, *e1; + vlib_buffer_t *b0, *b1; + pipe_t *pipe0, *pipe1; + u8 is_l20, is_l21; + u16 type0, type1; + + // Prefetch next iteration + { + vlib_buffer_t *p2, *p3; + + p2 = vlib_get_buffer (vm, from[2]); + p3 = vlib_get_buffer (vm, from[3]); + vlib_prefetch_buffer_header (p2, STORE); + vlib_prefetch_buffer_header (p3, STORE); + CLIB_PREFETCH (p2->data, CLIB_CACHE_LINE_BYTES, LOAD); + CLIB_PREFETCH (p3->data, CLIB_CACHE_LINE_BYTES, LOAD); + } + + bi0 = from[0]; + to_next[0] = bi0; + bi1 = from[1]; + to_next[1] = bi1; + from += 2; + to_next += 2; + n_left_from -= 2; + n_left_to_next -= 2; + + b0 = vlib_get_buffer (vm, bi0); + b1 = vlib_get_buffer (vm, bi1); + + e0 = vlib_buffer_get_current (b0); + e1 = vlib_buffer_get_current (b1); + sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX]; + sw_if_index1 = vnet_buffer (b1)->sw_if_index[VLIB_RX]; + type0 = clib_net_to_host_u16 (e0->type); + type1 = clib_net_to_host_u16 (e1->type); + pipe0 = &pipe_main.pipes[sw_if_index0]; + pipe1 = &pipe_main.pipes[sw_if_index1]; + + vnet_buffer (b0)->l3_hdr_offset = + vnet_buffer (b0)->l2_hdr_offset + vnet_buffer (b0)->l2.l2_len; + vnet_buffer (b1)->l3_hdr_offset = + vnet_buffer (b1)->l2_hdr_offset + vnet_buffer (b1)->l2.l2_len; + b0->flags |= VNET_BUFFER_F_L3_HDR_OFFSET_VALID; + b1->flags |= VNET_BUFFER_F_L3_HDR_OFFSET_VALID; + + is_l20 = pipe0->subint.flags & SUBINT_CONFIG_L2; + is_l21 = pipe1->subint.flags & SUBINT_CONFIG_L2; + pipe_determine_next_node (ðernet_main, is_l20, type0, b0, + &next0); + pipe_determine_next_node (ðernet_main, is_l21, type1, b1, + &next1); + + if (!is_l20) + vlib_buffer_advance (b0, sizeof (ethernet_header_t)); + else + { + u32 eth_start = vnet_buffer (b0)->l2_hdr_offset; + vnet_buffer (b0)->l2.l2_len = b0->current_data - eth_start; + } + if (!is_l21) + vlib_buffer_advance (b1, sizeof (ethernet_header_t)); + else + { + u32 eth_start = vnet_buffer (b1)->l2_hdr_offset; + vnet_buffer (b1)->l2.l2_len = b1->current_data - eth_start; + } + + vlib_validate_buffer_enqueue_x2 (vm, node, next_index, + to_next, n_left_to_next, + bi0, bi1, next0, next0); + } + while (n_left_from > 0 && n_left_to_next > 0) + { + u32 bi0, sw_if_index0; + vlib_buffer_t *b0; + pipe_rx_next_t next0; + ethernet_header_t *e0; + pipe_t *pipe0; + u16 type0; + u8 is_l20; + + bi0 = from[0]; + to_next[0] = bi0; + from += 1; + to_next += 1; + n_left_from -= 1; + n_left_to_next -= 1; + + b0 = vlib_get_buffer (vm, bi0); + + e0 = vlib_buffer_get_current (b0); + sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX]; + type0 = clib_net_to_host_u16 (e0->type); + pipe0 = &pipe_main.pipes[sw_if_index0]; + + vnet_buffer (b0)->l3_hdr_offset = + vnet_buffer (b0)->l2_hdr_offset + vnet_buffer (b0)->l2.l2_len; + b0->flags |= VNET_BUFFER_F_L3_HDR_OFFSET_VALID; + + is_l20 = pipe0->subint.flags & SUBINT_CONFIG_L2; + pipe_determine_next_node (ðernet_main, is_l20, type0, b0, + &next0); + + if (!is_l20) + vlib_buffer_advance (b0, sizeof (ethernet_header_t)); + else + { + u32 eth_start = vnet_buffer (b0)->l2_hdr_offset; + vnet_buffer (b0)->l2.l2_len = b0->current_data - eth_start; + } + + 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 from_frame->n_vectors; +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (pipe_rx_node) = { + .function = pipe_rx, + .name = "pipe-rx", + /* Takes a vector of packets. */ + .vector_size = sizeof (u32), + .format_trace = format_pipe_rx_trace, + + .sibling_of = "ethernet-input", +}; +/* *INDENT-ON* */ + +/* + * Maintain a bitmap of allocated pipe instance numbers. + */ +#define PIPE_MAX_INSTANCE (16 * 1024) + +static u32 +pipe_instance_alloc (u8 is_specified, u32 want) +{ + /* + * Check for dynamically allocaetd instance number. + */ + if (!is_specified) + { + u32 bit; + + bit = clib_bitmap_first_clear (pipe_main.instances); + if (bit >= PIPE_MAX_INSTANCE) + { + return ~0; + } + pipe_main.instances = clib_bitmap_set (pipe_main.instances, bit, 1); + return bit; + } + + /* + * In range? + */ + if (want >= PIPE_MAX_INSTANCE) + { + return ~0; + } + + /* + * Already in use? + */ + if (clib_bitmap_get (pipe_main.instances, want)) + { + return ~0; + } + + /* + * Grant allocation request. + */ + pipe_main.instances = clib_bitmap_set (pipe_main.instances, want, 1); + + return want; +} + +static int +pipe_instance_free (u32 instance) +{ + if (instance >= PIPE_MAX_INSTANCE) + { + return -1; + } + + if (clib_bitmap_get (pipe_main.instances, instance) == 0) + { + return -1; + } + + pipe_main.instances = clib_bitmap_set (pipe_main.instances, instance, 0); + return 0; +} + +static clib_error_t * +pipe_create_sub_interface (vnet_hw_interface_t * hi, + uint32_t sub_id, u32 * sw_if_index) +{ + vnet_sw_interface_t template; + + memset (&template, 0, sizeof (template)); + template.type = VNET_SW_INTERFACE_TYPE_PIPE; + template.flood_class = VNET_FLOOD_CLASS_NORMAL; + template.sup_sw_if_index = hi->sw_if_index; + template.sub.id = sub_id; + + return (vnet_create_sw_interface (vnet_get_main (), + &template, sw_if_index)); +} + +int +vnet_create_pipe_interface (u8 is_specified, + u32 user_instance, + u32 * parent_sw_if_index, u32 pipe_sw_if_index[2]) +{ + vnet_main_t *vnm = vnet_get_main (); + vlib_main_t *vm = vlib_get_main (); + uint8_t address[6] = { + [0] = 0x22, + [1] = 0x22, + }; + vnet_hw_interface_t *hi; + clib_error_t *error; + u32 hw_if_index; + u32 instance; + u32 slot; + int rv = 0; + + ASSERT (parent_sw_if_index); + + memset (address, 0, sizeof (address)); + + /* + * Allocate a pipe instance. Either select one dynamically + * or try to use the desired user_instance number. + */ + instance = pipe_instance_alloc (is_specified, user_instance); + if (instance == ~0) + { + return VNET_API_ERROR_INVALID_REGISTRATION; + } + + /* + * Default MAC address (0000:0000:0000 + instance) is allocated + */ + address[5] = instance; + + error = ethernet_register_interface (vnm, pipe_device_class.index, + instance, address, &hw_if_index, + /* flag change */ 0); + + if (error) + { + rv = VNET_API_ERROR_INVALID_REGISTRATION; + goto oops; + } + + hi = vnet_get_hw_interface (vnm, hw_if_index); + *parent_sw_if_index = hi->sw_if_index; + slot = vlib_node_add_named_next_with_slot (vm, hi->tx_node_index, + "pipe-rx", + VNET_PIPE_TX_NEXT_ETHERNET_INPUT); + ASSERT (slot == VNET_PIPE_TX_NEXT_ETHERNET_INPUT); + + /* + * create two sub-interfaces, one for each end of the pipe. + */ + error = pipe_create_sub_interface (hi, 0, &pipe_sw_if_index[0]); + + if (error) + goto oops; + + error = pipe_create_sub_interface (hi, 1, &pipe_sw_if_index[1]); + + if (error) + goto oops; + + hash_set (hi->sub_interface_sw_if_index_by_id, 0, pipe_sw_if_index[0]); + hash_set (hi->sub_interface_sw_if_index_by_id, 1, pipe_sw_if_index[1]); + + vec_validate_init_empty (pipe_main.pipes, pipe_sw_if_index[0], + PIPE_INVALID); + vec_validate_init_empty (pipe_main.pipes, pipe_sw_if_index[1], + PIPE_INVALID); + + pipe_main.pipes[pipe_sw_if_index[0]].sw_if_index = pipe_sw_if_index[1]; + pipe_main.pipes[pipe_sw_if_index[1]].sw_if_index = pipe_sw_if_index[0]; + + return 0; + +oops: + clib_error_report (error); + return rv; +} + +typedef struct pipe_hw_walk_ctx_t_ +{ + pipe_cb_fn_t cb; + void *ctx; +} pipe_hw_walk_ctx_t; + +static walk_rc_t +pipe_hw_walk (vnet_main_t * vnm, u32 hw_if_index, void *args) +{ + vnet_hw_interface_t *hi; + pipe_hw_walk_ctx_t *ctx; + + ctx = args; + hi = vnet_get_hw_interface (vnm, hw_if_index); + + if (hi->dev_class_index == pipe_device_class.index) + { + u32 pipe_sw_if_index[2], id, sw_if_index; + + /* *INDENT-OFF* */ + hash_foreach (id, sw_if_index, hi->sub_interface_sw_if_index_by_id, + ({ + ASSERT(id < 2); + pipe_sw_if_index[id] = sw_if_index; + })); + /* *INDENT-ON* */ + + ctx->cb (hi->sw_if_index, pipe_sw_if_index, hi->dev_instance, ctx->ctx); + } + + return (WALK_CONTINUE); +} + +void +pipe_walk (pipe_cb_fn_t fn, void *ctx) +{ + pipe_hw_walk_ctx_t wctx = { + .cb = fn, + .ctx = ctx, + }; + + ASSERT (fn); + + vnet_hw_interface_walk (vnet_get_main (), pipe_hw_walk, &wctx); +} + +static clib_error_t * +create_pipe_interfaces (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + int rv; + u32 sw_if_index; + u32 pipe_sw_if_index[2]; + u8 is_specified = 0; + u32 user_instance = 0; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "instance %d", &user_instance)) + is_specified = 1; + else + break; + } + + rv = vnet_create_pipe_interface (is_specified, user_instance, + &sw_if_index, pipe_sw_if_index); + + if (rv) + return clib_error_return (0, "vnet_create_pipe_interface failed"); + + vlib_cli_output (vm, "%U\n", format_vnet_sw_if_index_name, + vnet_get_main (), sw_if_index); + return 0; +} + +/*? + * Create a pipe interface. + * + * @cliexpar + * The following two command syntaxes are equivalent: + * @cliexcmd{pipe create-interface [mac ] [instance ]} + * Example of how to create a pipe interface: + * @cliexcmd{pipe create} + ?*/ +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (pipe_create_interface_command, static) = { + .path = "pipe create", + .short_help = "pipe create [instance ]", + .function = create_pipe_interfaces, +}; +/* *INDENT-ON* */ + +int +vnet_delete_pipe_interface (u32 sw_if_index) +{ + vnet_main_t *vnm = vnet_get_main (); + vnet_sw_interface_t *si; + vnet_hw_interface_t *hi; + u32 instance, id; + u32 hw_if_index; + + if (pool_is_free_index (vnm->interface_main.sw_interfaces, sw_if_index)) + return VNET_API_ERROR_INVALID_SW_IF_INDEX; + + si = vnet_get_sw_interface (vnm, sw_if_index); + hw_if_index = si->hw_if_index; + hi = vnet_get_hw_interface (vnm, hw_if_index); + instance = hi->dev_instance; + + if (pipe_instance_free (instance) < 0) + { + return VNET_API_ERROR_INVALID_SW_IF_INDEX; + } + + /* *INDENT-OFF* */ + hash_foreach (id, sw_if_index, hi->sub_interface_sw_if_index_by_id, + ({ + vnet_delete_sub_interface(sw_if_index); + pipe_main.pipes[sw_if_index] = PIPE_INVALID; + })); + /* *INDENT-ON* */ + + ethernet_delete_interface (vnm, hw_if_index); + + return 0; +} + +static clib_error_t * +delete_pipe_interfaces (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + vnet_main_t *vnm = vnet_get_main (); + u32 sw_if_index = ~0; + int rv; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "%U", + unformat_vnet_sw_interface, vnm, &sw_if_index)) + ; + else + break; + } + + if (sw_if_index == ~0) + return clib_error_return (0, "interface not specified"); + + rv = vnet_delete_pipe_interface (sw_if_index); + + if (rv) + return clib_error_return (0, "vnet_delete_pipe_interface failed"); + + return 0; +} + +/*? + * Delete a pipe interface. + * + * @cliexpar + * The following two command syntaxes are equivalent: + * @cliexcmd{pipe delete intfc } + * Example of how to delete a pipe interface: + * @cliexcmd{pipe delete-interface intfc loop0} + ?*/ +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (pipe_delete_interface_command, static) = { + .path = "pipe delete", + .short_help = "pipe delete ", + .function = delete_pipe_interfaces, +}; +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/devices/pipe/pipe.h b/src/vnet/devices/pipe/pipe.h new file mode 100644 index 00000000000..ef72934c3ba --- /dev/null +++ b/src/vnet/devices/pipe/pipe.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2018 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 __PIPE_H__ +#define __PIPE_H__ + +#include + +/** + * represenation of a pipe interface + */ +typedef struct pipe_t_ +{ + /** the SW if_index of the other end of the pipe */ + u32 sw_if_index; + + /** Sub-interface config */ + subint_config_t subint; +} pipe_t; + +/** + * Create a new pipe interface + * + * @param is_specified Has the user speficied a desired instance number + * @param user_instance The user's desired instnace + * @param parent_sw_index OUT the created parent interface + * @param pipe_sw_if_index OUT the ends of the pipe + */ +extern int vnet_create_pipe_interface (u8 is_specified, + u32 user_instance, + u32 * parent_sw_if_index, + u32 pipe_sw_if_index[2]); +extern int vnet_delete_pipe_interface (u32 parent_sw_if_index); + +/** + * Get the pipe instnace based on one end + */ +extern pipe_t *pipe_get (u32 sw_if_index); + +/** + * Call back function when walking all the pipes + */ +typedef walk_rc_t (*pipe_cb_fn_t) (u32 parent_sw_if_index, + u32 pipe_sw_if_index[2], + u32 instance, void *ctx); + +/** + * Walk all the of pipe interfaces + */ +extern void pipe_walk (pipe_cb_fn_t fn, void *ctx); + +#endif + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/devices/pipe/pipe_api.c b/src/vnet/devices/pipe/pipe_api.c new file mode 100644 index 00000000000..46bbea5f275 --- /dev/null +++ b/src/vnet/devices/pipe/pipe_api.c @@ -0,0 +1,177 @@ +/* + *------------------------------------------------------------------ + * Copyright (c) 2018 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 + +#define vl_typedefs /* define message structures */ +#include +#undef vl_typedefs + +#define vl_endianfun /* define message structures */ +#include +#undef vl_endianfun + +/* instantiate all the print functions we know about */ +#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__) +#define vl_printfun +#include +#undef vl_printfun + +#include +vpe_api_main_t vpe_api_main; + +#define foreach_vpe_api_msg \ + _(PIPE_CREATE, pipe_create) \ + _(PIPE_DELETE, pipe_delete) \ + _(PIPE_DUMP, pipe_dump) + +static void +vl_api_pipe_create_t_handler (vl_api_pipe_create_t * mp) +{ + vl_api_pipe_create_reply_t *rmp; + u32 parent_sw_if_index; + u32 pipe_sw_if_index[2]; + int rv; + u8 is_specified = mp->is_specified; + u32 user_instance = ntohl (mp->user_instance); + + rv = vnet_create_pipe_interface (is_specified, user_instance, + &parent_sw_if_index, pipe_sw_if_index); + + /* *INDENT-OFF* */ + REPLY_MACRO2(VL_API_PIPE_CREATE_REPLY, + ({ + rmp->parent_sw_if_index = ntohl (parent_sw_if_index); + rmp->pipe_sw_if_index[0] = ntohl (pipe_sw_if_index[0]); + rmp->pipe_sw_if_index[1] = ntohl (pipe_sw_if_index[1]); + })); + /* *INDENT-ON* */ +} + +static void +vl_api_pipe_delete_t_handler (vl_api_pipe_delete_t * mp) +{ + vl_api_pipe_delete_reply_t *rmp; + int rv; + + rv = vnet_delete_pipe_interface (ntohl (mp->parent_sw_if_index)); + + REPLY_MACRO (VL_API_PIPE_DELETE_REPLY); +} + +typedef struct pipe_dump_walk_t_ +{ + vl_api_registration_t *reg; + u32 context; +} pipe_dump_walk_t; + +static walk_rc_t +pipe_send_details (u32 parent_sw_if_index, + u32 pipe_sw_if_index[2], u32 instance, void *args) +{ + pipe_dump_walk_t *ctx = args; + vl_api_pipe_details_t *mp; + + mp = vl_msg_api_alloc (sizeof (*mp)); + if (!mp) + return (WALK_STOP); + + mp->_vl_msg_id = ntohs (VL_API_PIPE_DETAILS); + mp->context = ctx->context; + + mp->instance = ntohl (instance); + mp->parent_sw_if_index = ntohl (parent_sw_if_index); + mp->pipe_sw_if_index[0] = ntohl (pipe_sw_if_index[0]); + mp->pipe_sw_if_index[1] = ntohl (pipe_sw_if_index[1]); + + vl_api_send_msg (ctx->reg, (u8 *) mp); + + return (WALK_CONTINUE); +} + +static void +vl_api_pipe_dump_t_handler (vl_api_pipe_dump_t * mp) +{ + vl_api_registration_t *reg; + + reg = vl_api_client_index_to_registration (mp->client_index); + if (!reg) + return; + + pipe_dump_walk_t ctx = { + .reg = reg, + .context = mp->context, + }; + + pipe_walk (pipe_send_details, &ctx); +} + +/* + * vpe_api_hookup + * Add vpe's API message handlers to the table. + * vlib has alread mapped shared memory and + * added the client registration handlers. + * See .../vlib-api/vlibmemory/memclnt_vlib.c:memclnt_process() + */ +#define vl_msg_name_crc_list +#include +#undef vl_msg_name_crc_list + +static void +setup_message_id_table (api_main_t * am) +{ +#define _(id,n,crc) vl_msg_api_add_msg_name_crc (am, #n "_" #crc, id); + foreach_vl_msg_name_crc_pipe; +#undef _ +} + +static clib_error_t * +pipe_api_hookup (vlib_main_t * vm) +{ + api_main_t *am = &api_main; + +#define _(N,n) \ + vl_msg_api_set_handlers(VL_API_##N, #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_api_##n##_t_endian, \ + vl_api_##n##_t_print, \ + sizeof(vl_api_##n##_t), 1); + foreach_vpe_api_msg; +#undef _ + + /* + * Set up the (msg_name, crc, message-id) table + */ + setup_message_id_table (am); + + return 0; +} + +VLIB_API_INIT_FUNCTION (pipe_api_hookup); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/ethernet/interface.c b/src/vnet/ethernet/interface.c index b3ea983e50d..e39ae7b989e 100644 --- a/src/vnet/ethernet/interface.c +++ b/src/vnet/ethernet/interface.c @@ -91,7 +91,8 @@ ethernet_build_rewrite (vnet_main_t * vnm, u8 *rewrite = NULL; u8 is_p2p = 0; - if (sub_sw->type == VNET_SW_INTERFACE_TYPE_P2P) + if ((sub_sw->type == VNET_SW_INTERFACE_TYPE_P2P) || + (sub_sw->type == VNET_SW_INTERFACE_TYPE_PIPE)) is_p2p = 1; if (sub_sw != sup_sw) { @@ -197,7 +198,8 @@ ethernet_update_adjacency (vnet_main_t * vnm, u32 sw_if_index, u32 ai) adj = adj_get (ai); vnet_sw_interface_t *si = vnet_get_sw_interface (vnm, sw_if_index); - if (si->type == VNET_SW_INTERFACE_TYPE_P2P) + if ((si->type == VNET_SW_INTERFACE_TYPE_P2P) || + (si->type == VNET_SW_INTERFACE_TYPE_PIPE)) { default_update_adjacency (vnm, sw_if_index, ai); } @@ -748,6 +750,7 @@ vnet_delete_sub_interface (u32 sw_if_index) si = vnet_get_sw_interface (vnm, sw_if_index); if (si->type == VNET_SW_INTERFACE_TYPE_SUB || + si->type == VNET_SW_INTERFACE_TYPE_PIPE || si->type == VNET_SW_INTERFACE_TYPE_P2P) { vnet_interface_main_t *im = &vnm->interface_main; diff --git a/src/vnet/ethernet/node.c b/src/vnet/ethernet/node.c index 55d8285f81e..86676673ed3 100755 --- a/src/vnet/ethernet/node.c +++ b/src/vnet/ethernet/node.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -838,6 +839,14 @@ ethernet_sw_interface_get_config (vnet_main_t * vnm, subint = vec_elt_at_index (p2pm->p2p_subif_pool, si->p2p.pool_index); *flags = SUBINT_CONFIG_P2P; } + else if (si->type == VNET_SW_INTERFACE_TYPE_PIPE) + { + pipe_t *pipe; + + pipe = pipe_get (sw_if_index); + subint = &pipe->subint; + *flags = SUBINT_CONFIG_P2P; + } else if (si->sub.eth.flags.default_sub) { subint = &main_intf->default_subint; @@ -1127,7 +1136,7 @@ ethernet_sw_interface_add_del (vnet_main_t * vnm, } else { - // Note that config is L3 by defaulty + // Note that config is L3 by default subint->flags = SUBINT_CONFIG_VALID | match_flags; subint->sw_if_index = ~0; // because interfaces are initially down } diff --git a/src/vnet/interface.c b/src/vnet/interface.c index 34ad292a728..a9346a2b072 100644 --- a/src/vnet/interface.c +++ b/src/vnet/interface.c @@ -1042,8 +1042,9 @@ vnet_hw_interface_walk_sw (vnet_main_t * vnm, u32 id, sw_if_index; hi = vnet_get_hw_interface (vnm, hw_if_index); - /* the super first, then the and sub interfaces */ - fn (vnm, hi->sw_if_index, ctx); + /* the super first, then the sub interfaces */ + if (WALK_STOP == fn (vnm, hi->sw_if_index, ctx)) + return; /* *INDENT-OFF* */ hash_foreach (id, sw_if_index, @@ -1055,6 +1056,24 @@ vnet_hw_interface_walk_sw (vnet_main_t * vnm, /* *INDENT-ON* */ } +void +vnet_hw_interface_walk (vnet_main_t * vnm, + vnet_hw_interface_walk_t fn, void *ctx) +{ + vnet_interface_main_t *im; + vnet_hw_interface_t *hi; + + im = &vnm->interface_main; + + /* *INDENT-OFF* */ + pool_foreach (hi, im->hw_interfaces, + ({ + if (WALK_STOP == fn(vnm, hi->hw_if_index, ctx)) + break; + })); + /* *INDENT-ON* */ +} + void vnet_sw_interface_walk (vnet_main_t * vnm, vnet_sw_interface_walk_t fn, void *ctx) @@ -1257,7 +1276,8 @@ int vnet_sw_interface_is_p2p (vnet_main_t * vnm, u32 sw_if_index) { vnet_sw_interface_t *si = vnet_get_sw_interface (vnm, sw_if_index); - if (si->type == VNET_SW_INTERFACE_TYPE_P2P) + if ((si->type == VNET_SW_INTERFACE_TYPE_P2P) || + (si->type == VNET_SW_INTERFACE_TYPE_PIPE)) return 1; vnet_hw_interface_t *hw = vnet_get_sup_hw_interface (vnm, sw_if_index); diff --git a/src/vnet/interface.h b/src/vnet/interface.h index 1181c341a0c..f82cf9d2485 100644 --- a/src/vnet/interface.h +++ b/src/vnet/interface.h @@ -578,6 +578,7 @@ typedef enum /* A sub-interface. */ VNET_SW_INTERFACE_TYPE_SUB, VNET_SW_INTERFACE_TYPE_P2P, + VNET_SW_INTERFACE_TYPE_PIPE, } vnet_sw_interface_type_t; typedef struct diff --git a/src/vnet/interface_funcs.h b/src/vnet/interface_funcs.h index c4dd3b72e51..08af7f26151 100644 --- a/src/vnet/interface_funcs.h +++ b/src/vnet/interface_funcs.h @@ -82,6 +82,7 @@ vnet_get_sup_sw_interface (vnet_main_t * vnm, u32 sw_if_index) { vnet_sw_interface_t *sw = vnet_get_sw_interface (vnm, sw_if_index); if (sw->type == VNET_SW_INTERFACE_TYPE_SUB || + sw->type == VNET_SW_INTERFACE_TYPE_PIPE || sw->type == VNET_SW_INTERFACE_TYPE_P2P) sw = vnet_get_sw_interface (vnm, sw->sup_sw_if_index); return sw; @@ -91,7 +92,8 @@ always_inline vnet_hw_interface_t * vnet_get_sup_hw_interface (vnet_main_t * vnm, u32 sw_if_index) { vnet_sw_interface_t *sw = vnet_get_sup_sw_interface (vnm, sw_if_index); - ASSERT (sw->type == VNET_SW_INTERFACE_TYPE_HARDWARE); + ASSERT ((sw->type == VNET_SW_INTERFACE_TYPE_HARDWARE) || + (sw->type == VNET_SW_INTERFACE_TYPE_PIPE)); return vnet_get_hw_interface (vnm, sw->hw_if_index); } @@ -185,6 +187,20 @@ typedef walk_rc_t (*vnet_sw_interface_walk_t) (vnet_main_t * vnm, void vnet_sw_interface_walk (vnet_main_t * vnm, vnet_sw_interface_walk_t fn, void *ctx); + +/** + * Call back walk type for walking all HW indices + */ +typedef walk_rc_t (*vnet_hw_interface_walk_t) (vnet_main_t * vnm, + u32 hw_if_index, void *ctx); + +/** + * @brief + * Walk all the HW interface + */ +void vnet_hw_interface_walk (vnet_main_t * vnm, + vnet_hw_interface_walk_t fn, void *ctx); + /* Register a hardware interface instance. */ u32 vnet_register_interface (vnet_main_t * vnm, u32 dev_class_index, diff --git a/src/vnet/ip/ip6_neighbor.c b/src/vnet/ip/ip6_neighbor.c index 6d91224c0b4..87bda439ae5 100644 --- a/src/vnet/ip/ip6_neighbor.c +++ b/src/vnet/ip/ip6_neighbor.c @@ -4190,6 +4190,7 @@ enable_ip6_interface (vlib_main_t * vm, u32 sw_if_index) sw_if0 = vnet_get_sw_interface (vnm, sw_if_index); if (sw_if0->type == VNET_SW_INTERFACE_TYPE_SUB || + sw_if0->type == VNET_SW_INTERFACE_TYPE_PIPE || sw_if0->type == VNET_SW_INTERFACE_TYPE_P2P) { /* make up an interface id */ diff --git a/src/vnet/vnet_all_api_h.h b/src/vnet/vnet_all_api_h.h index 06b76a5e926..f07be653e20 100644 --- a/src/vnet/vnet_all_api_h.h +++ b/src/vnet/vnet_all_api_h.h @@ -74,6 +74,7 @@ #include #include #include +#include /* * fd.io coding-style-patch-verification: ON diff --git a/test/test_pipe.py b/test/test_pipe.py new file mode 100644 index 00000000000..a89c1e90236 --- /dev/null +++ b/test/test_pipe.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python +from socket import AF_INET, AF_INET6, inet_pton + +from framework import VppTestCase, VppTestRunner +from vpp_interface import VppInterface +from vpp_ip_route import VppIpTable, VppIpRoute, VppRoutePath + +from scapy.packet import Raw +from scapy.layers.l2 import Ether +from scapy.layers.inet import IP, UDP + + +class VppPipe(VppInterface): + """ + VPP Pipe + """ + + @property + def east(self): + return self.result.pipe_sw_if_index[1] + + @property + def west(self): + return self.result.pipe_sw_if_index[0] + + def __init__(self, test, instance=0xffffffff): + super(VppPipe, self).__init__(test) + self._test = test + self.instance = instance + + def add_vpp_config(self): + self.result = self._test.vapi.pipe_create( + 0 if self.instance == 0xffffffff else 1, + self.instance) + self.set_sw_if_index(self.result.parent_sw_if_index) + + def remove_vpp_config(self): + self._test.vapi.pipe_delete( + self.result.parent_sw_if_index) + + def __str__(self): + return self.object_id() + + def object_id(self): + return "pipe-%d" % (self._sw_if_index) + + def query_vpp_config(self): + pipes = self._test.vapi.pipe_dump() + for p in pipes: + if p.parent_sw_if_index == self.result.parent_sw_if_index: + return True + return False + + def set_unnumbered(self, ip_sw_if_index, is_add=True): + res = self._test.vapi.sw_interface_set_unnumbered( + self.east, ip_sw_if_index, is_add) + res = self._test.vapi.sw_interface_set_unnumbered( + self.west, ip_sw_if_index, is_add) + + +class TestPipe(VppTestCase): + """ Pipes """ + + def setUp(self): + super(TestPipe, self).setUp() + + self.create_pg_interfaces(range(4)) + + for i in self.pg_interfaces: + i.admin_up() + + def tearDown(self): + for i in self.pg_interfaces: + i.admin_down() + + super(TestPipe, self).tearDown() + + def test_pipe(self): + """ Pipes """ + + pipes = [] + pipes.append(VppPipe(self)) + pipes.append(VppPipe(self, 10)) + + for p in pipes: + p.add_vpp_config() + p.admin_up() + + # + # L2 cross-connect pipe0 east with pg0 and west with pg1 + # + self.vapi.sw_interface_set_l2_xconnect(self.pg0.sw_if_index, + pipes[0].east, + enable=1) + self.vapi.sw_interface_set_l2_xconnect(pipes[0].east, + self.pg0.sw_if_index, + enable=1) + self.vapi.sw_interface_set_l2_xconnect(self.pg1.sw_if_index, + pipes[0].west, + enable=1) + self.vapi.sw_interface_set_l2_xconnect(pipes[0].west, + self.pg1.sw_if_index, + enable=1) + + # test bi-drectional L2 flow pg0<->pg1 + p = (Ether(src=self.pg0.remote_mac, + dst=self.pg1.remote_mac) / + IP(src="1.1.1.1", + dst="1.1.1.2") / + UDP(sport=1234, dport=1234) / + Raw('\xa5' * 100)) + + self.send_and_expect(self.pg0, p * 65, self.pg1) + self.send_and_expect(self.pg1, p * 65, self.pg0) + + # + # Attach ACL to ensure features are run on the pipe + # + rule_1 = ({'is_permit': 0, + 'is_ipv6': 0, + 'proto': 17, + 'srcport_or_icmptype_first': 1234, + 'srcport_or_icmptype_last': 1234, + 'src_ip_prefix_len': 32, + 'src_ip_addr': inet_pton(AF_INET, "1.1.1.1"), + 'dstport_or_icmpcode_first': 1234, + 'dstport_or_icmpcode_last': 1234, + 'dst_ip_prefix_len': 32, + 'dst_ip_addr': inet_pton(AF_INET, "1.1.1.2")}) + acl = self.vapi.acl_add_replace(acl_index=4294967295, + r=[rule_1]) + + # Apply the ACL on the pipe on output + self.vapi.acl_interface_set_acl_list(pipes[0].east, + 0, + [acl.acl_index]) + self.send_and_assert_no_replies(self.pg0, p * 65) + self.send_and_expect(self.pg1, p * 65, self.pg0) + + # remove from output and apply on input + self.vapi.acl_interface_set_acl_list(pipes[0].east, + 0, + []) + self.vapi.acl_interface_set_acl_list(pipes[0].west, + 1, + [acl.acl_index]) + self.send_and_assert_no_replies(self.pg0, p * 65) + self.send_and_expect(self.pg1, p * 65, self.pg0) + self.vapi.acl_interface_set_acl_list(pipes[0].west, + 0, + []) + self.send_and_expect(self.pg0, p * 65, self.pg1) + self.send_and_expect(self.pg1, p * 65, self.pg0) + + # + # L3 routes in two separate tables so a pipe can be used to L3 + # x-connect + # + tables = [] + tables.append(VppIpTable(self, 1)) + tables.append(VppIpTable(self, 2)) + + for t in tables: + t.add_vpp_config() + + self.pg2.set_table_ip4(1) + self.pg2.config_ip4() + self.pg2.resolve_arp() + self.pg3.set_table_ip4(2) + self.pg3.config_ip4() + self.pg3.resolve_arp() + + routes = [] + routes.append(VppIpRoute(self, "1.1.1.1", 32, + [VppRoutePath(self.pg3.remote_ip4, + self.pg3.sw_if_index)], + table_id=2)) + routes.append(VppIpRoute(self, "1.1.1.1", 32, + [VppRoutePath("0.0.0.0", pipes[1].east)], + table_id=1)) + routes.append(VppIpRoute(self, "1.1.1.2", 32, + [VppRoutePath("0.0.0.0", pipes[1].west)], + table_id=2)) + routes.append(VppIpRoute(self, "1.1.1.2", 32, + [VppRoutePath(self.pg2.remote_ip4, + self.pg2.sw_if_index)], + table_id=1)) + + for r in routes: + r.add_vpp_config() + + p_east = (Ether(src=self.pg2.remote_mac, + dst=self.pg2.local_mac) / + IP(src="1.1.1.2", + dst="1.1.1.1") / + UDP(sport=1234, dport=1234) / + Raw('\xa5' * 100)) + + # bind the pipe ends to the correct tables + self.vapi.sw_interface_set_table(pipes[1].west, 0, 2) + self.vapi.sw_interface_set_table(pipes[1].east, 0, 1) + + # IP is not enabled on the pipes at this point + self.send_and_assert_no_replies(self.pg2, p_east * 65) + + # IP enable the Pipes by making them unnumbered + pipes[0].set_unnumbered(self.pg2.sw_if_index) + pipes[1].set_unnumbered(self.pg3.sw_if_index) + + self.send_and_expect(self.pg2, p_east * 65, self.pg3) + + # and the return path + p_west = (Ether(src=self.pg3.remote_mac, + dst=self.pg3.local_mac) / + IP(src="1.1.1.1", + dst="1.1.1.2") / + UDP(sport=1234, dport=1234) / + Raw('\xa5' * 100)) + self.send_and_expect(self.pg3, p_west * 65, self.pg2) + + # + # Use ACLs to test features run on the Pipes + # + self.vapi.acl_interface_set_acl_list(pipes[1].east, + 0, + [acl.acl_index]) + self.send_and_assert_no_replies(self.pg2, p_east * 65) + self.send_and_expect(self.pg3, p_west * 65, self.pg2) + + # remove from output and apply on input + self.vapi.acl_interface_set_acl_list(pipes[1].east, + 0, + []) + self.vapi.acl_interface_set_acl_list(pipes[1].west, + 1, + [acl.acl_index]) + self.send_and_assert_no_replies(self.pg2, p_east * 65) + self.send_and_expect(self.pg3, p_west * 65, self.pg2) + self.vapi.acl_interface_set_acl_list(pipes[1].west, + 0, + []) + self.send_and_expect(self.pg2, p_east * 65, self.pg3) + self.send_and_expect(self.pg3, p_west * 65, self.pg2) + + # cleanup (so the tables delete) + self.pg2.unconfig_ip4() + self.pg2.set_table_ip4(0) + self.pg3.unconfig_ip4() + self.pg3.set_table_ip4(0) + self.vapi.sw_interface_set_table(pipes[1].west, 0, 0) + self.vapi.sw_interface_set_table(pipes[1].east, 0, 0) + + +if __name__ == '__main__': + unittest.main(testRunner=VppTestRunner) diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 32c8eebef01..2d4b44764f3 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -3650,3 +3650,15 @@ class VppPapiProvider(object): def abf_itf_attach_dump(self): return self.api( self.papi.abf_itf_attach_dump, {}) + + def pipe_create(self, is_specified, user_instance): + return self.api(self.papi.pipe_create, + {'is_specified': is_specified, + 'user_instance': user_instance}) + + def pipe_delete(self, parent_sw_if_index): + return self.api(self.papi.pipe_delete, + {'parent_sw_if_index': parent_sw_if_index}) + + def pipe_dump(self): + return self.api(self.papi.pipe_dump, {}) -- 2.16.6