From f068c3ed296c49dfbfe17677fc1ad2428fb4e3e4 Mon Sep 17 00:00:00 2001 From: Neale Ranns Date: Wed, 3 Jan 2018 04:18:48 -0800 Subject: [PATCH] DVR: run L3 output features - rename l2_bridged to is_dvr. Including on the ip.api this was new in the 18.01 release so no compatability issues. - steal the free space in vnet_buffer_opaque_t for use with flags. - run the ipX-output feature arc from the DVR DPO Change-Id: I040e5976d1dbe076fcdda3a40a7804f56337ce3f Signed-off-by: Neale Ranns --- src/vnet.am | 2 +- src/vnet/buffer.h | 24 ++ src/vnet/dpo/dpo.c | 4 +- src/vnet/dpo/dpo.h | 6 +- src/vnet/dpo/dvr_dpo.c | 591 +++++++++++++++++++++++++++++++++++++++++ src/vnet/dpo/dvr_dpo.h | 65 +++++ src/vnet/dpo/l2_bridge_dpo.c | 375 -------------------------- src/vnet/dpo/l2_bridge_dpo.h | 56 ---- src/vnet/fib/fib_api.h | 2 +- src/vnet/fib/fib_path.c | 106 +++++--- src/vnet/fib/fib_test.c | 26 +- src/vnet/fib/fib_types.h | 4 + src/vnet/ip/ip.api | 3 +- src/vnet/ip/ip4_forward.c | 2 +- src/vnet/ip/ip6_forward.c | 2 +- src/vnet/ip/ip_api.c | 10 +- src/vpp-api/vom/prefix.cpp | 13 + src/vpp-api/vom/prefix.hpp | 44 +-- src/vpp-api/vom/route.cpp | 29 +- src/vpp-api/vom/route.hpp | 30 +++ src/vpp-api/vom/route_cmds.cpp | 4 +- test/test_dvr.py | 60 +++-- test/vpp_ip_route.py | 8 +- test/vpp_papi_provider.py | 6 +- 24 files changed, 928 insertions(+), 544 deletions(-) create mode 100644 src/vnet/dpo/dvr_dpo.c create mode 100644 src/vnet/dpo/dvr_dpo.h delete mode 100644 src/vnet/dpo/l2_bridge_dpo.c delete mode 100644 src/vnet/dpo/l2_bridge_dpo.h diff --git a/src/vnet.am b/src/vnet.am index 9a985ba1936..2bae8678c4b 100644 --- a/src/vnet.am +++ b/src/vnet.am @@ -1114,7 +1114,7 @@ libvnet_la_SOURCES += \ vnet/dpo/mpls_disposition.c \ vnet/dpo/mpls_label_dpo.c \ vnet/dpo/l3_proxy_dpo.c \ - vnet/dpo/l2_bridge_dpo.c + vnet/dpo/dvr_dpo.c nobase_include_HEADERS += \ vnet/dpo/load_balance.h \ diff --git a/src/vnet/buffer.h b/src/vnet/buffer.h index 8cb55454462..317f8bb8e4a 100644 --- a/src/vnet/buffer.h +++ b/src/vnet/buffer.h @@ -42,6 +42,9 @@ #include +/** + * Flags that are set in the high order bits of ((vlib_buffer*)b)->flags + */ #define foreach_vnet_buffer_field \ _( 1, L4_CHECKSUM_COMPUTED, "l4-cksum-computed") \ _( 2, L4_CHECKSUM_CORRECT, "l4-cksum-correct") \ @@ -77,6 +80,26 @@ enum #undef _ }; +/** + * @brief Flags set in ((vnet_buffer(b)->flags + */ +#define foreach_vnet_opaque_flag \ + _( 1, IS_DVR, "DVR-processed") + +enum +{ +#define _(bit, name, v) VNET_OPAQUE_F_##name = (1 << bit), + foreach_vnet_opaque_flag +#undef _ +}; + +enum +{ +#define _(bit, name, v) VNET_OPAQUE_F_LOG2_##name = bit, + foreach_vnet_opaque_flag +#undef _ +}; + #define foreach_buffer_opaque_union_subtype \ _(ip) \ @@ -111,6 +134,7 @@ typedef struct i16 l2_hdr_offset; i16 l3_hdr_offset; i16 l4_hdr_offset; + u16 flags; union { diff --git a/src/vnet/dpo/dpo.c b/src/vnet/dpo/dpo.c index 1ee0727135d..51adbc3d55a 100644 --- a/src/vnet/dpo/dpo.c +++ b/src/vnet/dpo/dpo.c @@ -40,7 +40,7 @@ #include #include #include -#include +#include #include /** @@ -537,7 +537,7 @@ dpo_module_init (vlib_main_t * vm) interface_rx_dpo_module_init(); interface_tx_dpo_module_init(); mpls_disp_dpo_module_init(); - l2_bridge_dpo_module_init(); + dvr_dpo_module_init(); l3_proxy_dpo_module_init(); return (NULL); diff --git a/src/vnet/dpo/dpo.h b/src/vnet/dpo/dpo.h index 27c129ed444..3290a5d239e 100644 --- a/src/vnet/dpo/dpo.h +++ b/src/vnet/dpo/dpo.h @@ -116,7 +116,7 @@ typedef enum dpo_type_t_ { DPO_MFIB_ENTRY, DPO_INTERFACE_RX, DPO_INTERFACE_TX, - DPO_L2_BRIDGE, + DPO_DVR, DPO_L3_PROXY, DPO_BIER_TABLE, DPO_BIER_FMASK, @@ -147,10 +147,10 @@ typedef enum dpo_type_t_ { [DPO_CLASSIFY] = "dpo-classify", \ [DPO_MPLS_LABEL] = "dpo-mpls-label", \ [DPO_MPLS_DISPOSITION] = "dpo-mpls-diposition", \ - [DPO_MFIB_ENTRY] = "dpo-mfib_entry", \ + [DPO_MFIB_ENTRY] = "dpo-mfib-entry", \ [DPO_INTERFACE_RX] = "dpo-interface-rx", \ [DPO_INTERFACE_TX] = "dpo-interface-tx", \ - [DPO_L2_BRIDGE] = "dpo-l2-bridge", \ + [DPO_DVR] = "dpo-dvr", \ [DPO_L3_PROXY] = "dpo-l3-proxy", \ [DPO_BIER_TABLE] = "bier-table", \ [DPO_BIER_FMASK] = "bier-fmask", \ diff --git a/src/vnet/dpo/dvr_dpo.c b/src/vnet/dpo/dvr_dpo.c new file mode 100644 index 00000000000..1aa16546c82 --- /dev/null +++ b/src/vnet/dpo/dvr_dpo.c @@ -0,0 +1,591 @@ +/* + * 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 +#include +#include +#include + +/** + * The 'DB' of DVR DPOs. + * There is one per-interface per-L3 proto, so this is a per-interface vector + */ +static index_t *dvr_dpo_db[DPO_PROTO_NUM]; + +static dvr_dpo_t * +dvr_dpo_alloc (void) +{ + dvr_dpo_t *dd; + + pool_get(dvr_dpo_pool, dd); + + return (dd); +} + +static inline dvr_dpo_t * +dvr_dpo_get_from_dpo (const dpo_id_t *dpo) +{ + ASSERT(DPO_DVR == dpo->dpoi_type); + + return (dvr_dpo_get(dpo->dpoi_index)); +} + +static inline index_t +dvr_dpo_get_index (dvr_dpo_t *dd) +{ + return (dd - dvr_dpo_pool); +} + +static void +dvr_dpo_lock (dpo_id_t *dpo) +{ + dvr_dpo_t *dd; + + dd = dvr_dpo_get_from_dpo(dpo); + dd->dd_locks++; +} + +static void +dvr_dpo_unlock (dpo_id_t *dpo) +{ + dvr_dpo_t *dd; + + dd = dvr_dpo_get_from_dpo(dpo); + dd->dd_locks--; + + if (0 == dd->dd_locks) + { + if (DPO_PROTO_IP4 == dd->dd_proto) + { + vnet_feature_enable_disable ("ip4-output", "ip4-dvr-reinject", + dd->dd_sw_if_index, 0, 0, 0); + } + else + { + vnet_feature_enable_disable ("ip6-output", "ip6-dvr-reinject", + dd->dd_sw_if_index, 0, 0, 0); + } + + dvr_dpo_db[dd->dd_proto][dd->dd_sw_if_index] = INDEX_INVALID; + pool_put(dvr_dpo_pool, dd); + } +} + +void +dvr_dpo_add_or_lock (u32 sw_if_index, + dpo_proto_t dproto, + dpo_id_t *dpo) +{ + dvr_dpo_t *dd; + + vec_validate_init_empty(dvr_dpo_db[dproto], + sw_if_index, + INDEX_INVALID); + + if (INDEX_INVALID == dvr_dpo_db[dproto][sw_if_index]) + { + dd = dvr_dpo_alloc(); + + dd->dd_sw_if_index = sw_if_index; + dd->dd_proto = dproto; + + dvr_dpo_db[dproto][sw_if_index] = dvr_dpo_get_index(dd); + + /* + * enable the reinject into L2 path feature on the interface + */ + if (DPO_PROTO_IP4 == dproto) + vnet_feature_enable_disable ("ip4-output", "ip4-dvr-reinject", + dd->dd_sw_if_index, 1, 0, 0); + else if (DPO_PROTO_IP6 == dproto) + vnet_feature_enable_disable ("ip6-output", "ip6-dvr-reinject", + dd->dd_sw_if_index, 1, 0, 0); + else + ASSERT(0); + } + else + { + dd = dvr_dpo_get(dvr_dpo_db[dproto][sw_if_index]); + } + + dpo_set(dpo, DPO_DVR, dproto, dvr_dpo_get_index(dd)); +} + + +static clib_error_t * +dvr_dpo_interface_state_change (vnet_main_t * vnm, + u32 sw_if_index, + u32 flags) +{ + /* + */ + return (NULL); +} + +VNET_SW_INTERFACE_ADMIN_UP_DOWN_FUNCTION( + dvr_dpo_interface_state_change); + +/** + * @brief Registered callback for HW interface state changes + */ +static clib_error_t * +dvr_dpo_hw_interface_state_change (vnet_main_t * vnm, + u32 hw_if_index, + u32 flags) +{ + return (NULL); +} + +VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION( + dvr_dpo_hw_interface_state_change); + +static clib_error_t * +dvr_dpo_interface_delete (vnet_main_t * vnm, + u32 sw_if_index, + u32 is_add) +{ + return (NULL); +} + +VNET_SW_INTERFACE_ADD_DEL_FUNCTION( + dvr_dpo_interface_delete); + +u8* +format_dvr_dpo (u8* s, va_list *ap) +{ + index_t index = va_arg(*ap, index_t); + CLIB_UNUSED(u32 indent) = va_arg(*ap, u32); + vnet_main_t * vnm = vnet_get_main(); + dvr_dpo_t *dd = dvr_dpo_get(index); + + return (format(s, "dvr-%U-dpo", + format_vnet_sw_interface_name, + vnm, + vnet_get_sw_interface(vnm, dd->dd_sw_if_index))); +} + +static void +dvr_dpo_mem_show (void) +{ + fib_show_memory_usage("DVR", + pool_elts(dvr_dpo_pool), + pool_len(dvr_dpo_pool), + sizeof(dvr_dpo_t)); +} + + +const static dpo_vft_t dvr_dpo_vft = { + .dv_lock = dvr_dpo_lock, + .dv_unlock = dvr_dpo_unlock, + .dv_format = format_dvr_dpo, + .dv_mem_show = dvr_dpo_mem_show, +}; + +/** + * @brief The per-protocol VLIB graph nodes that are assigned to a glean + * object. + * + * this means that these graph nodes are ones from which a glean is the + * parent object in the DPO-graph. + */ +const static char* const dvr_dpo_ip4_nodes[] = +{ + "ip4-dvr-dpo", + NULL, +}; +const static char* const dvr_dpo_ip6_nodes[] = +{ + "ip6-dvr-dpo", + NULL, +}; + +const static char* const * const dvr_dpo_nodes[DPO_PROTO_NUM] = +{ + [DPO_PROTO_IP4] = dvr_dpo_ip4_nodes, + [DPO_PROTO_IP6] = dvr_dpo_ip6_nodes, +}; + +void +dvr_dpo_module_init (void) +{ + dpo_register(DPO_DVR, + &dvr_dpo_vft, + dvr_dpo_nodes); +} + +/** + * @brief Interface DPO trace data + */ +typedef struct dvr_dpo_trace_t_ +{ + u32 sw_if_index; +} dvr_dpo_trace_t; + +always_inline uword +dvr_dpo_inline (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * from_frame, + u8 is_ip6) +{ + u32 n_left_from, next_index, * from, * to_next; + ip_lookup_main_t *lm = (is_ip6? + &ip6_main.lookup_main: + &ip4_main.lookup_main); + + from = vlib_frame_vector_args (from_frame); + n_left_from = from_frame->n_vectors; + + next_index = node->cached_next_index; + + 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) + { + const dvr_dpo_t *dd0, *dd1; + u32 bi0, ddi0, bi1, ddi1; + vlib_buffer_t *b0, *b1; + u32 next0, next1; + u8 len0, len1; + + 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; + next0 = next1 = 0; + + b0 = vlib_get_buffer (vm, bi0); + b1 = vlib_get_buffer (vm, bi1); + + ddi0 = vnet_buffer(b0)->ip.adj_index[VLIB_TX]; + ddi1 = vnet_buffer(b1)->ip.adj_index[VLIB_TX]; + dd0 = dvr_dpo_get(ddi0); + dd1 = dvr_dpo_get(ddi1); + + vnet_buffer(b0)->sw_if_index[VLIB_TX] = dd0->dd_sw_if_index; + vnet_buffer(b1)->sw_if_index[VLIB_TX] = dd1->dd_sw_if_index; + + len0 = ((u8*)vlib_buffer_get_current(b0) - + (u8*)ethernet_buffer_get_header(b0)); + len1 = ((u8*)vlib_buffer_get_current(b1) - + (u8*)ethernet_buffer_get_header(b1)); + vnet_buffer(b0)->l2.l2_len = len0; + vnet_buffer(b1)->l2.l2_len = len1; + vnet_buffer(b0)->flags |= VNET_OPAQUE_F_IS_DVR; + vnet_buffer(b1)->flags |= VNET_OPAQUE_F_IS_DVR; + + vlib_buffer_advance(b0, -len0); + vlib_buffer_advance(b1, -len1); + + vnet_feature_arc_start (lm->output_feature_arc_index, + dd0->dd_sw_if_index, &next0, b0); + vnet_feature_arc_start (lm->output_feature_arc_index, + dd1->dd_sw_if_index, &next1, b1); + + if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) + { + dvr_dpo_trace_t *tr0; + + tr0 = vlib_add_trace (vm, node, b0, sizeof (*tr0)); + tr0->sw_if_index = dd0->dd_sw_if_index; + } + if (PREDICT_FALSE(b1->flags & VLIB_BUFFER_IS_TRACED)) + { + dvr_dpo_trace_t *tr1; + + tr1 = vlib_add_trace (vm, node, b1, sizeof (*tr1)); + tr1->sw_if_index = dd1->dd_sw_if_index; + } + + 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) + { + const dvr_dpo_t * dd0; + vlib_buffer_t * b0; + u32 bi0, ddi0; + u32 next0; + u8 len0; + + bi0 = from[0]; + to_next[0] = bi0; + from += 1; + to_next += 1; + n_left_from -= 1; + n_left_to_next -= 1; + next0 = 0; + + b0 = vlib_get_buffer (vm, bi0); + + ddi0 = vnet_buffer(b0)->ip.adj_index[VLIB_TX]; + dd0 = dvr_dpo_get(ddi0); + + vnet_buffer(b0)->sw_if_index[VLIB_TX] = dd0->dd_sw_if_index; + + /* + * take that, rewind it back... + */ + len0 = ((u8*)vlib_buffer_get_current(b0) - + (u8*)ethernet_buffer_get_header(b0)); + vnet_buffer(b0)->l2.l2_len = len0; + vnet_buffer(b0)->flags |= VNET_OPAQUE_F_IS_DVR; + vlib_buffer_advance(b0, -len0); + + /* + * start processing the ipX output features + */ + vnet_feature_arc_start(lm->output_feature_arc_index, + dd0->dd_sw_if_index, &next0, b0); + + if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) + { + dvr_dpo_trace_t *tr; + + tr = vlib_add_trace (vm, node, b0, sizeof (*tr)); + tr->sw_if_index = dd0->dd_sw_if_index; + } + + 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; +} + +static u8 * +format_dvr_dpo_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 *); + dvr_dpo_trace_t * t = va_arg (*args, dvr_dpo_trace_t *); + u32 indent = format_get_indent (s); + s = format (s, "%U sw_if_index:%d", + format_white_space, indent, + t->sw_if_index); + return s; +} + +static uword +ip4_dvr_dpo (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * from_frame) +{ + return (dvr_dpo_inline(vm, node, from_frame, 0)); +} + +static uword +ip6_dvr_dpo (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * from_frame) +{ + return (dvr_dpo_inline(vm, node, from_frame, 1)); +} + +VLIB_REGISTER_NODE (ip4_dvr_dpo_node) = { + .function = ip4_dvr_dpo, + .name = "ip4-dvr-dpo", + .vector_size = sizeof (u32), + .format_trace = format_dvr_dpo_trace, + .sibling_of = "ip4-rewrite", +}; +VLIB_REGISTER_NODE (ip6_dvr_dpo_node) = { + .function = ip6_dvr_dpo, + .name = "ip6-dvr-dpo", + .vector_size = sizeof (u32), + .format_trace = format_dvr_dpo_trace, + .sibling_of = "ip6-rewrite", +}; + +VLIB_NODE_FUNCTION_MULTIARCH (ip4_dvr_dpo_node, ip4_dvr_dpo) +VLIB_NODE_FUNCTION_MULTIARCH (ip6_dvr_dpo_node, ip6_dvr_dpo) + +typedef enum dvr_reinject_next_t_ +{ + DVR_REINJECT_OUTPUT = 0, +} dvr_reinject_next_t; + +always_inline uword +dvr_reinject_inline (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * from_frame) +{ + u32 n_left_from, next_index, * from, * to_next; + + from = vlib_frame_vector_args (from_frame); + n_left_from = from_frame->n_vectors; + + next_index = node->cached_next_index; + + 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) + { + dvr_reinject_next_t next0, next1; + vlib_buffer_t *b0, *b1; + u32 bi0, bi1; + + 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); + + if (vnet_buffer(b0)->flags & VNET_OPAQUE_F_IS_DVR) + next0 = DVR_REINJECT_OUTPUT; + else + vnet_feature_next(vnet_buffer(b0)->sw_if_index[VLIB_TX], + &next0, b0); + + if (vnet_buffer(b1)->flags & VNET_OPAQUE_F_IS_DVR) + next1 = DVR_REINJECT_OUTPUT; + else + vnet_feature_next(vnet_buffer(b1)->sw_if_index[VLIB_TX], + &next1, b1); + + if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) + { + dvr_dpo_trace_t *tr0; + + tr0 = vlib_add_trace (vm, node, b0, sizeof (*tr0)); + tr0->sw_if_index = vnet_buffer(b0)->sw_if_index[VLIB_TX]; + } + if (PREDICT_FALSE(b1->flags & VLIB_BUFFER_IS_TRACED)) + { + dvr_dpo_trace_t *tr1; + + tr1 = vlib_add_trace (vm, node, b1, sizeof (*tr1)); + tr1->sw_if_index = vnet_buffer(b1)->sw_if_index[VLIB_TX]; + } + + 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) + { + dvr_reinject_next_t next0; + vlib_buffer_t * b0; + u32 bi0; + + 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); + + if (vnet_buffer(b0)->flags & VNET_OPAQUE_F_IS_DVR) + next0 = DVR_REINJECT_OUTPUT; + else + vnet_feature_next(vnet_buffer(b0)->sw_if_index[VLIB_TX], + &next0, b0); + + if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) + { + dvr_dpo_trace_t *tr; + + tr = vlib_add_trace (vm, node, b0, sizeof (*tr)); + tr->sw_if_index = vnet_buffer(b0)->sw_if_index[VLIB_TX]; + } + + 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; +} + +static uword +ip4_dvr_reinject (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * from_frame) +{ + return (dvr_reinject_inline(vm, node, from_frame)); +} + +static uword +ip6_dvr_reinject (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * from_frame) +{ + return (dvr_reinject_inline(vm, node, from_frame)); +} + +VLIB_REGISTER_NODE (ip4_dvr_reinject_node) = { + .function = ip4_dvr_reinject, + .name = "ip4-dvr-reinject", + .vector_size = sizeof (u32), + .format_trace = format_dvr_dpo_trace, + + .n_next_nodes = 1, + .next_nodes = { + [DVR_REINJECT_OUTPUT] = "l2-output", + }, +}; + +VLIB_REGISTER_NODE (ip6_dvr_reinject_node) = { + .function = ip6_dvr_reinject, + .name = "ip6-dvr-reinject", + .vector_size = sizeof (u32), + .format_trace = format_dvr_dpo_trace, + + .n_next_nodes = 1, + .next_nodes = { + [DVR_REINJECT_OUTPUT] = "l2-output", + }, +}; + +VNET_FEATURE_INIT (ip4_dvr_reinject_feat_node, static) = +{ + .arc_name = "ip4-output", + .node_name = "ip4-dvr-reinject", + .runs_after = VNET_FEATURES ("nat44-in2out-output", + "acl-plugin-out-ip4-fa"), +}; +VNET_FEATURE_INIT (ip6_dvr_reinject_feat_node, static) = +{ + .arc_name = "ip6-output", + .node_name = "ip6-dvr-reinject", + .runs_after = VNET_FEATURES ("acl-plugin-out-ip6-fa"), +}; + +VLIB_NODE_FUNCTION_MULTIARCH (ip4_dvr_reinject_node, ip4_dvr_reinject) +VLIB_NODE_FUNCTION_MULTIARCH (ip6_dvr_reinject_node, ip6_dvr_reinject) diff --git a/src/vnet/dpo/dvr_dpo.h b/src/vnet/dpo/dvr_dpo.h new file mode 100644 index 00000000000..15fe113c596 --- /dev/null +++ b/src/vnet/dpo/dvr_dpo.h @@ -0,0 +1,65 @@ +/* + * 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 __DVR_DPO_H__ +#define __DVR_DPO_H__ + +#include + +/** + * @brief + * The DVR DPO. Used as the resolving object for a DVR route. + * This is used, in place of the usual L3 Adjacency, to retransmit + * the packet with the original L2 header intact but also to run L3 features. + * After running L3 features the packet is re-injected back into the L2 path + * so it can pick up the necessary VLAN tags of the egress interface. + * This re-injection is done with an output feature. + */ +typedef struct dvr_dpo_t_ +{ + /** + * The Software interface index that the packets will output on + */ + u32 dd_sw_if_index; + + /** + * The protocol of packets using this DPO + */ + dpo_proto_t dd_proto; + + /** + * number of locks. + */ + u16 dd_locks; +} dvr_dpo_t; + +extern void dvr_dpo_add_or_lock (u32 sw_if_index, + dpo_proto_t dproto, + dpo_id_t *dpo); + +extern void dvr_dpo_module_init(void); + +/** + * @brief pool of all interface DPOs + */ +dvr_dpo_t *dvr_dpo_pool; + +static inline dvr_dpo_t * +dvr_dpo_get (index_t index) +{ + return (pool_elt_at_index(dvr_dpo_pool, index)); +} + +#endif diff --git a/src/vnet/dpo/l2_bridge_dpo.c b/src/vnet/dpo/l2_bridge_dpo.c deleted file mode 100644 index fd92a61e960..00000000000 --- a/src/vnet/dpo/l2_bridge_dpo.c +++ /dev/null @@ -1,375 +0,0 @@ -/* - * 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 -#include -#include - -/* - * The 'DB' of L2 bridge DPOs. - * There is only one per-interface, so this is a per-interface vector - */ -static index_t *l2_bridge_dpo_db; - -static l2_bridge_dpo_t * -l2_bridge_dpo_alloc (void) -{ - l2_bridge_dpo_t *l2b; - - pool_get(l2_bridge_dpo_pool, l2b); - - return (l2b); -} - -static inline l2_bridge_dpo_t * -l2_bridge_dpo_get_from_dpo (const dpo_id_t *dpo) -{ - ASSERT(DPO_L2_BRIDGE == dpo->dpoi_type); - - return (l2_bridge_dpo_get(dpo->dpoi_index)); -} - -static inline index_t -l2_bridge_dpo_get_index (l2_bridge_dpo_t *l2b) -{ - return (l2b - l2_bridge_dpo_pool); -} - -static void -l2_bridge_dpo_lock (dpo_id_t *dpo) -{ - l2_bridge_dpo_t *l2b; - - l2b = l2_bridge_dpo_get_from_dpo(dpo); - l2b->l2b_locks++; -} - -static void -l2_bridge_dpo_unlock (dpo_id_t *dpo) -{ - l2_bridge_dpo_t *l2b; - - l2b = l2_bridge_dpo_get_from_dpo(dpo); - l2b->l2b_locks--; - - if (0 == l2b->l2b_locks) - { - l2_bridge_dpo_db[l2b->l2b_sw_if_index] = INDEX_INVALID; - pool_put(l2_bridge_dpo_pool, l2b); - } -} - -/* - * l2_bridge_dpo_add_or_lock - * - * Add/create and lock a new or lock an existing for the L2 Bridge - * on the interface given - */ -void -l2_bridge_dpo_add_or_lock (u32 sw_if_index, - dpo_id_t *dpo) -{ - l2_bridge_dpo_t *l2b; - - vec_validate_init_empty(l2_bridge_dpo_db, - sw_if_index, - INDEX_INVALID); - - if (INDEX_INVALID == l2_bridge_dpo_db[sw_if_index]) - { - l2b = l2_bridge_dpo_alloc(); - - l2b->l2b_sw_if_index = sw_if_index; - - l2_bridge_dpo_db[sw_if_index] = - l2_bridge_dpo_get_index(l2b); - } - else - { - l2b = l2_bridge_dpo_get(l2_bridge_dpo_db[sw_if_index]); - } - - dpo_set(dpo, DPO_L2_BRIDGE, DPO_PROTO_ETHERNET, l2_bridge_dpo_get_index(l2b)); -} - - -static clib_error_t * -l2_bridge_dpo_interface_state_change (vnet_main_t * vnm, - u32 sw_if_index, - u32 flags) -{ - /* - */ - return (NULL); -} - -VNET_SW_INTERFACE_ADMIN_UP_DOWN_FUNCTION( - l2_bridge_dpo_interface_state_change); - -/** - * @brief Registered callback for HW interface state changes - */ -static clib_error_t * -l2_bridge_dpo_hw_interface_state_change (vnet_main_t * vnm, - u32 hw_if_index, - u32 flags) -{ - return (NULL); -} - -VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION( - l2_bridge_dpo_hw_interface_state_change); - -static clib_error_t * -l2_bridge_dpo_interface_delete (vnet_main_t * vnm, - u32 sw_if_index, - u32 is_add) -{ - return (NULL); -} - -VNET_SW_INTERFACE_ADD_DEL_FUNCTION( - l2_bridge_dpo_interface_delete); - -u8* -format_l2_bridge_dpo (u8* s, va_list *ap) -{ - index_t index = va_arg(*ap, index_t); - CLIB_UNUSED(u32 indent) = va_arg(*ap, u32); - vnet_main_t * vnm = vnet_get_main(); - l2_bridge_dpo_t *l2b = l2_bridge_dpo_get(index); - - return (format(s, "l2-bridge-%U-dpo", - format_vnet_sw_interface_name, - vnm, - vnet_get_sw_interface(vnm, l2b->l2b_sw_if_index))); -} - -static void -l2_bridge_dpo_mem_show (void) -{ - fib_show_memory_usage("L2-bridge", - pool_elts(l2_bridge_dpo_pool), - pool_len(l2_bridge_dpo_pool), - sizeof(l2_bridge_dpo_t)); -} - - -const static dpo_vft_t l2_bridge_dpo_vft = { - .dv_lock = l2_bridge_dpo_lock, - .dv_unlock = l2_bridge_dpo_unlock, - .dv_format = format_l2_bridge_dpo, - .dv_mem_show = l2_bridge_dpo_mem_show, -}; - -/** - * @brief The per-protocol VLIB graph nodes that are assigned to a glean - * object. - * - * this means that these graph nodes are ones from which a glean is the - * parent object in the DPO-graph. - */ -const static char* const l2_bridge_dpo_l2_nodes[] = -{ - "l2-bridge-dpo", - NULL, -}; - -const static char* const * const l2_bridge_dpo_nodes[DPO_PROTO_NUM] = -{ - [DPO_PROTO_ETHERNET] = l2_bridge_dpo_l2_nodes, -}; - -void -l2_bridge_dpo_module_init (void) -{ - dpo_register(DPO_L2_BRIDGE, - &l2_bridge_dpo_vft, - l2_bridge_dpo_nodes); -} - -/** - * @brief Interface DPO trace data - */ -typedef struct l2_bridge_dpo_trace_t_ -{ - u32 sw_if_index; -} l2_bridge_dpo_trace_t; - -typedef enum l2_bridge_dpo_next_t_ -{ - L2_BRIDGE_DPO_DROP = 0, - L2_BRIDGE_DPO_OUTPUT = 1, -} l2_bridge_dpo_next_t; - -always_inline uword -l2_bridge_dpo_inline (vlib_main_t * vm, - vlib_node_runtime_t * node, - vlib_frame_t * from_frame) -{ - u32 n_left_from, next_index, * from, * to_next; - - from = vlib_frame_vector_args (from_frame); - n_left_from = from_frame->n_vectors; - - next_index = node->cached_next_index; - - 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) - { - const l2_bridge_dpo_t *l2b0, *l2b1; - u32 bi0, l2bi0, bi1, l2bi1; - vlib_buffer_t *b0, *b1; - u8 len0, len1; - - 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); - - l2bi0 = vnet_buffer(b0)->ip.adj_index[VLIB_TX]; - l2bi1 = vnet_buffer(b1)->ip.adj_index[VLIB_TX]; - l2b0 = l2_bridge_dpo_get(l2bi0); - l2b1 = l2_bridge_dpo_get(l2bi1); - - vnet_buffer(b0)->sw_if_index[VLIB_TX] = l2b0->l2b_sw_if_index; - vnet_buffer(b1)->sw_if_index[VLIB_TX] = l2b1->l2b_sw_if_index; - - len0 = ((u8*)vlib_buffer_get_current(b0) - - (u8*)ethernet_buffer_get_header(b0)); - len1 = ((u8*)vlib_buffer_get_current(b1) - - (u8*)ethernet_buffer_get_header(b1)); - vnet_buffer(b0)->l2.l2_len = len0; - vnet_buffer(b1)->l2.l2_len = len1; - - vlib_buffer_advance(b0, -len0); - vlib_buffer_advance(b1, -len1); - - if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) - { - l2_bridge_dpo_trace_t *tr0; - - tr0 = vlib_add_trace (vm, node, b0, sizeof (*tr0)); - tr0->sw_if_index = l2b0->l2b_sw_if_index; - } - if (PREDICT_FALSE(b1->flags & VLIB_BUFFER_IS_TRACED)) - { - l2_bridge_dpo_trace_t *tr1; - - tr1 = vlib_add_trace (vm, node, b1, sizeof (*tr1)); - tr1->sw_if_index = l2b1->l2b_sw_if_index; - } - - vlib_validate_buffer_enqueue_x2(vm, node, next_index, to_next, - n_left_to_next, bi0, bi1, - L2_BRIDGE_DPO_OUTPUT, - L2_BRIDGE_DPO_OUTPUT); - } - - while (n_left_from > 0 && n_left_to_next > 0) - { - const l2_bridge_dpo_t * l2b0; - vlib_buffer_t * b0; - u32 bi0, l2bi0; - u8 len0; - - 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); - - l2bi0 = vnet_buffer(b0)->ip.adj_index[VLIB_TX]; - l2b0 = l2_bridge_dpo_get(l2bi0); - - vnet_buffer(b0)->sw_if_index[VLIB_TX] = l2b0->l2b_sw_if_index; - - /* - * take that, rewind it back... - */ - len0 = ((u8*)vlib_buffer_get_current(b0) - - (u8*)ethernet_buffer_get_header(b0)); - vnet_buffer(b0)->l2.l2_len = len0; - vlib_buffer_advance(b0, -len0); - - if (PREDICT_FALSE(b0->flags & VLIB_BUFFER_IS_TRACED)) - { - l2_bridge_dpo_trace_t *tr; - - tr = vlib_add_trace (vm, node, b0, sizeof (*tr)); - tr->sw_if_index = l2b0->l2b_sw_if_index; - } - - vlib_validate_buffer_enqueue_x1(vm, node, next_index, to_next, - n_left_to_next, bi0, - L2_BRIDGE_DPO_OUTPUT); - } - vlib_put_next_frame (vm, node, next_index, n_left_to_next); - } - return from_frame->n_vectors; -} - -static u8 * -format_l2_bridge_dpo_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 *); - l2_bridge_dpo_trace_t * t = va_arg (*args, l2_bridge_dpo_trace_t *); - u32 indent = format_get_indent (s); - s = format (s, "%U sw_if_index:%d", - format_white_space, indent, - t->sw_if_index); - return s; -} - -static uword -l2_bridge_dpo_l2 (vlib_main_t * vm, - vlib_node_runtime_t * node, - vlib_frame_t * from_frame) -{ - return (l2_bridge_dpo_inline(vm, node, from_frame)); -} - - -VLIB_REGISTER_NODE (l2_bridge_dpo_l2_node) = { - .function = l2_bridge_dpo_l2, - .name = "l2-bridge-dpo", - .vector_size = sizeof (u32), - .format_trace = format_l2_bridge_dpo_trace, - - .n_next_nodes = 2, - .next_nodes = { - [L2_BRIDGE_DPO_DROP] = "error-drop", - [L2_BRIDGE_DPO_OUTPUT] = "l2-output", - }, -}; - -VLIB_NODE_FUNCTION_MULTIARCH (l2_bridge_dpo_l2_node, - l2_bridge_dpo_l2) diff --git a/src/vnet/dpo/l2_bridge_dpo.h b/src/vnet/dpo/l2_bridge_dpo.h deleted file mode 100644 index 0a20dd79335..00000000000 --- a/src/vnet/dpo/l2_bridge_dpo.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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 __L2_BRIDGE_DPO_H__ -#define __L2_BRIDGE_DPO_H__ - -#include - -/** - * @brief - * The data-path object representing an L2 bridge. - * If a packet encounters an object of this type in the L3 data-path, it - * is injected back into the L2 bridge. - */ -typedef struct l2_bridge_dpo_t_ -{ - /** - * The Software interface index that the packets will output on - */ - u32 l2b_sw_if_index; - - /** - * number of locks. - */ - u16 l2b_locks; -} l2_bridge_dpo_t; - -extern void l2_bridge_dpo_add_or_lock (u32 sw_if_index, - dpo_id_t *dpo); - -extern void l2_bridge_dpo_module_init(void); - -/** - * @brief pool of all interface DPOs - */ -l2_bridge_dpo_t *l2_bridge_dpo_pool; - -static inline l2_bridge_dpo_t * -l2_bridge_dpo_get (index_t index) -{ - return (pool_elt_at_index(l2_bridge_dpo_pool, index)); -} - -#endif diff --git a/src/vnet/fib/fib_api.h b/src/vnet/fib/fib_api.h index 655d305372b..bbe4eaac74b 100644 --- a/src/vnet/fib/fib_api.h +++ b/src/vnet/fib/fib_api.h @@ -40,7 +40,7 @@ add_del_route_t_handler (u8 is_multipath, u8 is_resolve_attached, u8 is_interface_rx, u8 is_rpf_id, - u8 is_l2_bridged, + u8 is_dvr, u8 is_source_lookup, u8 is_udp_encap, u32 fib_index, diff --git a/src/vnet/fib/fib_path.c b/src/vnet/fib/fib_path.c index fddb8ecdd5a..ffb7ad228f1 100644 --- a/src/vnet/fib/fib_path.c +++ b/src/vnet/fib/fib_path.c @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include @@ -99,6 +99,10 @@ typedef enum fib_path_type_t_ { * bier-fmask. it's via a BIER f-mask. */ FIB_PATH_TYPE_BIER_FMASK, + /** + * via a DVR. + */ + FIB_PATH_TYPE_DVR, /** * Marker. Add new types before this one, then update it. */ @@ -123,6 +127,7 @@ typedef enum fib_path_type_t_ { [FIB_PATH_TYPE_BIER_IMP] = "bier-imp", \ [FIB_PATH_TYPE_BIER_TABLE] = "bier-table", \ [FIB_PATH_TYPE_BIER_FMASK] = "bier-fmask", \ + [FIB_PATH_TYPE_DVR] = "dvr", \ } #define FOR_EACH_FIB_PATH_TYPE(_item) \ @@ -345,6 +350,12 @@ typedef struct fib_path_t_ { */ u32 fp_udp_encap_id; } udp_encap; + struct { + /** + * The interface + */ + u32 fp_interface; + } dvr; }; STRUCT_MARK(path_hash_end); @@ -574,6 +585,14 @@ format_fib_path (u8 * s, va_list * args) s = format (s, "via %U", format_bier_imp, path->bier_imp.fp_bier_imp, 0, BIER_SHOW_BRIEF); break; + case FIB_PATH_TYPE_DVR: + s = format (s, " %U", + format_vnet_sw_interface_name, + vnm, + vnet_get_sw_interface( + vnm, + path->dvr.fp_interface)); + break; case FIB_PATH_TYPE_RECEIVE: case FIB_PATH_TYPE_INTF_RX: case FIB_PATH_TYPE_SPECIAL: @@ -881,12 +900,9 @@ fib_path_unresolve (fib_path_t *path) adj_unlock(path->fp_dpo.dpoi_index); break; case FIB_PATH_TYPE_ATTACHED: - if (DPO_PROTO_ETHERNET != path->fp_nh_proto) - { - adj_child_remove(path->fp_dpo.dpoi_index, - path->fp_sibling); - adj_unlock(path->fp_dpo.dpoi_index); - } + adj_child_remove(path->fp_dpo.dpoi_index, + path->fp_sibling); + adj_unlock(path->fp_dpo.dpoi_index); break; case FIB_PATH_TYPE_UDP_ENCAP: udp_encap_unlock_w_index(path->fp_dpo.dpoi_index); @@ -898,6 +914,7 @@ fib_path_unresolve (fib_path_t *path) case FIB_PATH_TYPE_RECEIVE: case FIB_PATH_TYPE_INTF_RX: case FIB_PATH_TYPE_DEAG: + case FIB_PATH_TYPE_DVR: /* * these hold only the path's DPO, which is reset below. */ @@ -1101,6 +1118,7 @@ FIXME comment } break; case FIB_PATH_TYPE_ATTACHED: + case FIB_PATH_TYPE_DVR: /* * FIXME; this could schedule a lower priority walk, since attached * routes are not usually in ECMP configurations so the backwalk to @@ -1299,6 +1317,11 @@ fib_path_create (fib_node_index_t pl_index, path->fp_type = FIB_PATH_TYPE_DEAG; path->deag.fp_tbl_id = rpath->frp_fib_index; } + else if (rpath->frp_flags & FIB_ROUTE_PATH_DVR) + { + path->fp_type = FIB_PATH_TYPE_DVR; + path->dvr.fp_interface = rpath->frp_sw_if_index; + } else if (~0 != rpath->frp_sw_if_index) { if (ip46_address_is_zero(&rpath->frp_addr)) @@ -1546,6 +1569,9 @@ fib_path_cmp_i (const fib_path_t *path1, case FIB_PATH_TYPE_UDP_ENCAP: res = (path1->udp_encap.fp_udp_encap_id - path2->udp_encap.fp_udp_encap_id); break; + case FIB_PATH_TYPE_DVR: + res = (path1->dvr.fp_interface - path2->dvr.fp_interface); + break; case FIB_PATH_TYPE_SPECIAL: case FIB_PATH_TYPE_RECEIVE: case FIB_PATH_TYPE_EXCLUSIVE: @@ -1680,6 +1706,9 @@ fib_path_cmp_w_route_path (fib_node_index_t path_index, res = (path->deag.fp_rpf_id - rpath->frp_rpf_id); } break; + case FIB_PATH_TYPE_DVR: + res = (path->dvr.fp_interface - rpath->frp_sw_if_index); + break; case FIB_PATH_TYPE_SPECIAL: case FIB_PATH_TYPE_RECEIVE: case FIB_PATH_TYPE_EXCLUSIVE: @@ -1776,6 +1805,7 @@ fib_path_recursive_loop_detect (fib_node_index_t path_index, case FIB_PATH_TYPE_ATTACHED: case FIB_PATH_TYPE_SPECIAL: case FIB_PATH_TYPE_DEAG: + case FIB_PATH_TYPE_DVR: case FIB_PATH_TYPE_RECEIVE: case FIB_PATH_TYPE_INTF_RX: case FIB_PATH_TYPE_UDP_ENCAP: @@ -1821,35 +1851,27 @@ fib_path_resolve (fib_node_index_t path_index) fib_path_attached_next_hop_set(path); break; case FIB_PATH_TYPE_ATTACHED: - if (DPO_PROTO_ETHERNET == path->fp_nh_proto) + /* + * path->attached.fp_interface + */ + if (!vnet_sw_interface_is_admin_up(vnet_get_main(), + path->attached.fp_interface)) { - l2_bridge_dpo_add_or_lock(path->attached.fp_interface, - &path->fp_dpo); + path->fp_oper_flags &= ~FIB_PATH_OPER_FLAG_RESOLVED; } - else - { - /* - * path->attached.fp_interface - */ - if (!vnet_sw_interface_is_admin_up(vnet_get_main(), - path->attached.fp_interface)) - { - path->fp_oper_flags &= ~FIB_PATH_OPER_FLAG_RESOLVED; - } - dpo_set(&path->fp_dpo, - DPO_ADJACENCY, - path->fp_nh_proto, - fib_path_attached_get_adj(path, - dpo_proto_to_link(path->fp_nh_proto))); + dpo_set(&path->fp_dpo, + DPO_ADJACENCY, + path->fp_nh_proto, + fib_path_attached_get_adj(path, + dpo_proto_to_link(path->fp_nh_proto))); - /* - * become a child of the adjacency so we receive updates - * when the interface state changes - */ - path->fp_sibling = adj_child_add(path->fp_dpo.dpoi_index, - FIB_NODE_TYPE_PATH, - fib_path_get_index(path)); - } + /* + * become a child of the adjacency so we receive updates + * when the interface state changes + */ + path->fp_sibling = adj_child_add(path->fp_dpo.dpoi_index, + FIB_NODE_TYPE_PATH, + fib_path_get_index(path)); break; case FIB_PATH_TYPE_RECURSIVE: { @@ -1975,6 +1997,11 @@ fib_path_resolve (fib_node_index_t path_index) } break; } + case FIB_PATH_TYPE_DVR: + dvr_dpo_add_or_lock(path->attached.fp_interface, + path->fp_nh_proto, + &path->fp_dpo); + break; case FIB_PATH_TYPE_RECEIVE: /* * Resolve via a receive DPO. @@ -2031,6 +2058,8 @@ fib_path_get_resolving_interface (fib_node_index_t path_index) return (fib_entry_get_resolving_interface(path->fp_via_fib)); } break; + case FIB_PATH_TYPE_DVR: + return (path->dvr.fp_interface); case FIB_PATH_TYPE_INTF_RX: case FIB_PATH_TYPE_UDP_ENCAP: case FIB_PATH_TYPE_SPECIAL: @@ -2059,6 +2088,7 @@ fib_path_get_resolving_index (fib_node_index_t path_index) case FIB_PATH_TYPE_INTF_RX: case FIB_PATH_TYPE_SPECIAL: case FIB_PATH_TYPE_DEAG: + case FIB_PATH_TYPE_DVR: case FIB_PATH_TYPE_EXCLUSIVE: break; case FIB_PATH_TYPE_UDP_ENCAP: @@ -2187,6 +2217,9 @@ fib_path_contribute_urpf (fib_node_index_t path_index, } break; } + case FIB_PATH_TYPE_DVR: + fib_urpf_list_append(urpf, path->dvr.fp_interface); + break; case FIB_PATH_TYPE_DEAG: case FIB_PATH_TYPE_RECEIVE: case FIB_PATH_TYPE_INTF_RX: @@ -2250,6 +2283,7 @@ fib_path_stack_mpls_disp (fib_node_index_t path_index, case FIB_PATH_TYPE_BIER_FMASK: case FIB_PATH_TYPE_BIER_TABLE: case FIB_PATH_TYPE_BIER_IMP: + case FIB_PATH_TYPE_DVR: break; } } @@ -2400,11 +2434,6 @@ fib_path_contribute_forwarding (fib_node_index_t path_index, dpo_copy(dpo, &path->exclusive.fp_ex_dpo); break; case FIB_PATH_TYPE_ATTACHED: - if (DPO_PROTO_ETHERNET == path->fp_nh_proto) - { - dpo_copy(dpo, &path->fp_dpo); - break; - } switch (fct) { case FIB_FORW_CHAIN_TYPE_MPLS_NON_EOS: @@ -2462,6 +2491,7 @@ fib_path_contribute_forwarding (fib_node_index_t path_index, break; case FIB_PATH_TYPE_RECEIVE: case FIB_PATH_TYPE_SPECIAL: + case FIB_PATH_TYPE_DVR: dpo_copy(dpo, &path->fp_dpo); break; } diff --git a/src/vnet/fib/fib_test.c b/src/vnet/fib/fib_test.c index 555196d0092..9a8febb1396 100644 --- a/src/vnet/fib/fib_test.c +++ b/src/vnet/fib/fib_test.c @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include #include @@ -550,7 +550,7 @@ fib_test_validate_lb_v (const load_balance_t *lb, exp->adj.adj); break; case FT_LB_L2: - FIB_TEST_I((DPO_L2_BRIDGE == dpo->dpoi_type), + FIB_TEST_I((DPO_DVR == dpo->dpoi_type), "bucket %d stacks on %U", bucket, format_dpo_type, dpo->dpoi_type); @@ -4116,25 +4116,25 @@ fib_test_v4 (void) format_ip_flow_hash_config, lb->lb_hash_config); /* - * A route via an L2 Bridge + * A route via DVR DPO */ fei = fib_table_entry_path_add(fib_index, &pfx_10_10_10_3_s_32, FIB_SOURCE_API, FIB_ENTRY_FLAG_NONE, - DPO_PROTO_ETHERNET, + DPO_PROTO_IP4, &zero_addr, tm->hw[0]->sw_if_index, ~0, 1, NULL, - FIB_ROUTE_PATH_FLAG_NONE); - dpo_id_t l2_dpo = DPO_INVALID; - l2_bridge_dpo_add_or_lock(tm->hw[0]->sw_if_index, &l2_dpo); + FIB_ROUTE_PATH_DVR); + dpo_id_t dvr_dpo = DPO_INVALID; + dvr_dpo_add_or_lock(tm->hw[0]->sw_if_index, DPO_PROTO_IP4, &dvr_dpo); fib_test_lb_bucket_t ip_o_l2 = { .type = FT_LB_L2, .adj = { - .adj = l2_dpo.dpoi_index, + .adj = dvr_dpo.dpoi_index, }, }; @@ -4146,13 +4146,13 @@ fib_test_v4 (void) fib_table_entry_path_remove(fib_index, &pfx_10_10_10_3_s_32, FIB_SOURCE_API, - DPO_PROTO_ETHERNET, + DPO_PROTO_IP4, &zero_addr, tm->hw[0]->sw_if_index, fib_index, 1, - FIB_ROUTE_PATH_FLAG_NONE); - dpo_reset(&l2_dpo); + FIB_ROUTE_PATH_DVR); + dpo_reset(&dvr_dpo); /* * CLEANUP @@ -4253,8 +4253,8 @@ fib_test_v4 (void) pool_elts(load_balance_map_pool)); FIB_TEST((lb_count == pool_elts(load_balance_pool)), "LB pool size is %d", pool_elts(load_balance_pool)); - FIB_TEST((0 == pool_elts(l2_bridge_dpo_pool)), "L2 DPO pool size is %d", - pool_elts(l2_bridge_dpo_pool)); + FIB_TEST((0 == pool_elts(dvr_dpo_pool)), "L2 DPO pool size is %d", + pool_elts(dvr_dpo_pool)); return 0; } diff --git a/src/vnet/fib/fib_types.h b/src/vnet/fib/fib_types.h index 5742c69b7c7..e3aef7a58df 100644 --- a/src/vnet/fib/fib_types.h +++ b/src/vnet/fib/fib_types.h @@ -344,6 +344,10 @@ typedef enum fib_route_path_flags_t_ * A path that resolves via another table */ FIB_ROUTE_PATH_DEAG = (1 << 13), + /** + * A path that resolves via a DVR DPO + */ + FIB_ROUTE_PATH_DVR = (1 << 14), } fib_route_path_flags_t; /** diff --git a/src/vnet/ip/ip.api b/src/vnet/ip/ip.api index b22c4d999fa..54047b6ae93 100644 --- a/src/vnet/ip/ip.api +++ b/src/vnet/ip/ip.api @@ -374,6 +374,7 @@ autoreply define sw_interface_ip6_set_link_local_address @param is_udp_encap - The path describes a UDP-o-IP encapsulation. @param is_classify - @param is_multipath - Set to 1 if this is a multipath route, else 0 + @param is_dvr - Does the route resolve via a DVR interface. @param is_source_lookup - The the path is a deaggregate path (i.e. a lookup in another table) is the lookup on the packet's source address or destination. @@ -408,7 +409,7 @@ autoreply define ip_add_del_route u8 is_multipath; u8 is_resolve_host; u8 is_resolve_attached; - u8 is_l2_bridged; + u8 is_dvr; u8 is_source_lookup; u8 is_udp_encap; u8 next_hop_weight; diff --git a/src/vnet/ip/ip4_forward.c b/src/vnet/ip/ip4_forward.c index 9c5775792b0..201d391a77e 100755 --- a/src/vnet/ip/ip4_forward.c +++ b/src/vnet/ip/ip4_forward.c @@ -1113,7 +1113,7 @@ VNET_FEATURE_INIT (ip4_lookup_mc, static) = VNET_FEATURE_ARC_INIT (ip4_output, static) = { .arc_name = "ip4-output", - .start_nodes = VNET_FEATURES ("ip4-rewrite", "ip4-midchain"), + .start_nodes = VNET_FEATURES ("ip4-rewrite", "ip4-midchain", "ip4-dvr-dpo"), .arc_index_ptr = &ip4_main.lookup_main.output_feature_arc_index, }; diff --git a/src/vnet/ip/ip6_forward.c b/src/vnet/ip/ip6_forward.c index 29cd3ca8239..a7e7930a69f 100644 --- a/src/vnet/ip/ip6_forward.c +++ b/src/vnet/ip/ip6_forward.c @@ -666,7 +666,7 @@ VNET_FEATURE_INIT (ip6_mc_lookup, static) = { VNET_FEATURE_ARC_INIT (ip6_output, static) = { .arc_name = "ip6-output", - .start_nodes = VNET_FEATURES ("ip6-rewrite", "ip6-midchain"), + .start_nodes = VNET_FEATURES ("ip6-rewrite", "ip6-midchain", "ip6-dvr-dpo"), .arc_index_ptr = &ip6_main.lookup_main.output_feature_arc_index, }; diff --git a/src/vnet/ip/ip_api.c b/src/vnet/ip/ip_api.c index db88d7b0161..599a66b3672 100644 --- a/src/vnet/ip/ip_api.c +++ b/src/vnet/ip/ip_api.c @@ -851,7 +851,7 @@ add_del_route_t_handler (u8 is_multipath, u8 is_resolve_attached, u8 is_interface_rx, u8 is_rpf_id, - u8 is_l2_bridged, + u8 is_dvr, u8 is_source_lookup, u8 is_udp_encap, u32 fib_index, @@ -892,8 +892,8 @@ add_del_route_t_handler (u8 is_multipath, path.frp_local_label = next_hop_via_label; path.frp_eos = MPLS_NON_EOS; } - if (is_l2_bridged) - path.frp_proto = DPO_PROTO_ETHERNET; + if (is_dvr) + path_flags |= FIB_ROUTE_PATH_DVR; if (is_resolve_host) path_flags |= FIB_ROUTE_PATH_RESOLVE_VIA_HOST; if (is_resolve_attached) @@ -1143,7 +1143,7 @@ ip4_add_del_route_t_handler (vl_api_ip_add_del_route_t * mp) mp->classify_table_index, mp->is_resolve_host, mp->is_resolve_attached, 0, 0, - mp->is_l2_bridged, + mp->is_dvr, mp->is_source_lookup, mp->is_udp_encap, fib_index, &pfx, DPO_PROTO_IP4, @@ -1206,7 +1206,7 @@ ip6_add_del_route_t_handler (vl_api_ip_add_del_route_t * mp) mp->classify_table_index, mp->is_resolve_host, mp->is_resolve_attached, 0, 0, - mp->is_l2_bridged, + mp->is_dvr, mp->is_source_lookup, mp->is_udp_encap, fib_index, &pfx, DPO_PROTO_IP6, diff --git a/src/vpp-api/vom/prefix.cpp b/src/vpp-api/vom/prefix.cpp index e754999b6d1..abd589eef9f 100644 --- a/src/vpp-api/vom/prefix.cpp +++ b/src/vpp-api/vom/prefix.cpp @@ -53,6 +53,19 @@ l3_proto_t::from_address(const boost::asio::ip::address& addr) return IPV4; } +const nh_proto_t& +l3_proto_t::to_nh_proto() const +{ + if (*this == IPV4) + return nh_proto_t::IPV4; + else if (*this == IPV6) + return nh_proto_t::IPV6; + else if (*this == MPLS) + return nh_proto_t::MPLS; + + return nh_proto_t::IPV4; +} + std::ostream& operator<<(std::ostream& os, const l3_proto_t& l3p) { diff --git a/src/vpp-api/vom/prefix.hpp b/src/vpp-api/vom/prefix.hpp index 25b188ff86c..3950f6fd036 100644 --- a/src/vpp-api/vom/prefix.hpp +++ b/src/vpp-api/vom/prefix.hpp @@ -25,6 +25,27 @@ namespace VOM { * Types belonging to Routing */ +/** + * A next-hop protocol describes the protocol of a peer to which packets + * are sent after matching a route. + */ +class nh_proto_t : public enum_base +{ +public: + const static nh_proto_t IPV4; + const static nh_proto_t IPV6; + const static nh_proto_t MPLS; + const static nh_proto_t ETHERNET; + + static const nh_proto_t& from_address(const boost::asio::ip::address& addr); + +private: + /** + * Private constructor taking the value and the string name + */ + nh_proto_t(int v, const std::string& s); +}; + /** * An L3 protocol can be used to construct a prefix that is used * to match packets are part of a route. @@ -41,6 +62,8 @@ public: static const l3_proto_t& from_address(const boost::asio::ip::address& addr); + const nh_proto_t& to_nh_proto() const; + private: /** * Private constructor taking the value and the string name @@ -53,27 +76,6 @@ private: */ std::ostream& operator<<(std::ostream& os, const l3_proto_t& l3p); -/** - * A next-hop protocol describes the protocol of a peer to which packets - * are sent after matching a route. - */ -class nh_proto_t : public enum_base -{ -public: - const static nh_proto_t IPV4; - const static nh_proto_t IPV6; - const static nh_proto_t MPLS; - const static nh_proto_t ETHERNET; - - static const nh_proto_t& from_address(const boost::asio::ip::address& addr); - -private: - /** - * Private constructor taking the value and the string name - */ - nh_proto_t(int v, const std::string& s); -}; - namespace route { /** * type def the table-id diff --git a/src/vpp-api/vom/route.cpp b/src/vpp-api/vom/route.cpp index 661d99c4791..78ea8cebfad 100644 --- a/src/vpp-api/vom/route.cpp +++ b/src/vpp-api/vom/route.cpp @@ -33,9 +33,18 @@ path::special_t::special_t(int v, const std::string& s) { } +const path::flags_t path::flags_t::NONE(0, "none"); +const path::flags_t path::flags_t::DVR((1 << 0), "dvr"); + +path::flags_t::flags_t(int v, const std::string& s) + : enum_base(v, s) +{ +} + path::path(special_t special) : m_type(special) , m_nh_proto(nh_proto_t::IPV4) + , m_flags(flags_t::NONE) , m_nh() , m_rd(nullptr) , m_interface(nullptr) @@ -50,6 +59,7 @@ path::path(const boost::asio::ip::address& nh, uint8_t preference) : m_type(special_t::STANDARD) , m_nh_proto(nh_proto_t::from_address(nh)) + , m_flags(flags_t::NONE) , m_nh(nh) , m_rd(nullptr) , m_interface(interface.singular()) @@ -64,6 +74,7 @@ path::path(const route_domain& rd, uint8_t preference) : m_type(special_t::STANDARD) , m_nh_proto(nh_proto_t::from_address(nh)) + , m_flags(flags_t::NONE) , m_nh(nh) , m_rd(rd.singular()) , m_interface(nullptr) @@ -74,10 +85,12 @@ path::path(const route_domain& rd, path::path(const interface& interface, const nh_proto_t& proto, + const flags_t& flags, uint8_t weight, uint8_t preference) : m_type(special_t::STANDARD) , m_nh_proto(proto) + , m_flags(flags) , m_nh() , m_rd(nullptr) , m_interface(interface.singular()) @@ -89,6 +102,7 @@ path::path(const interface& interface, path::path(const path& p) : m_type(p.m_type) , m_nh_proto(p.m_nh_proto) + , m_flags(p.m_flags) , m_nh(p.m_nh) , m_rd(p.m_rd) , m_interface(p.m_interface) @@ -100,6 +114,10 @@ path::path(const path& p) bool path::operator<(const path& p) const { + if (m_nh_proto < p.m_nh_proto) + return true; + if (m_flags < p.m_flags) + return true; if (m_type < p.m_type) return true; if (m_rd && !p.m_rd) @@ -140,7 +158,8 @@ path::operator==(const path& p) const return false; if (m_interface && p.m_interface) result &= (*m_interface == *p.m_interface); - return (result && (m_type == p.m_type) && (m_nh == p.m_nh)); + return (result && (m_type == p.m_type) && (m_nh == p.m_nh) && + (m_nh_proto == p.m_nh_proto) && (m_flags == p.m_flags)); } std::string @@ -150,7 +169,7 @@ path::to_string() const s << "path:[" << "type:" << m_type.to_string() << " proto:" << m_nh_proto.to_string() - << " neighbour:" << m_nh.to_string(); + << " flags:" << m_flags.to_string() << " neighbour:" << m_nh.to_string(); if (m_rd) { s << " " << m_rd->to_string(); } @@ -175,6 +194,12 @@ path::nh_proto() const return m_nh_proto; } +path::flags_t +path::flags() const +{ + return m_flags; +} + const boost::asio::ip::address& path::nh() const { diff --git a/src/vpp-api/vom/route.hpp b/src/vpp-api/vom/route.hpp index 2fb855a13eb..65797b7c2f9 100644 --- a/src/vpp-api/vom/route.hpp +++ b/src/vpp-api/vom/route.hpp @@ -71,6 +71,29 @@ public: special_t(int v, const std::string& s); }; + /** + * Path flags + */ + class flags_t : public enum_base + { + public: + /** + * No flags + */ + const static flags_t NONE; + + /** + * A path that resolves via a DVR next-hop + */ + const static flags_t DVR; + + private: + /** + * Private constructor taking the value and the string name + */ + flags_t(int v, const std::string& s); + }; + /** * constructor for special paths */ @@ -97,6 +120,7 @@ public: */ path(const interface& interface, const nh_proto_t& proto, + const flags_t& flags = flags_t::NONE, uint8_t weight = 1, uint8_t preference = 0); @@ -130,6 +154,7 @@ public: */ special_t type() const; nh_proto_t nh_proto() const; + flags_t flags() const; const boost::asio::ip::address& nh() const; std::shared_ptr rd() const; std::shared_ptr itf() const; @@ -147,6 +172,11 @@ private: */ nh_proto_t m_nh_proto; + /** + * Flags for the path + */ + flags_t m_flags; + /** * The next-hop */ diff --git a/src/vpp-api/vom/route_cmds.cpp b/src/vpp-api/vom/route_cmds.cpp index 0d012a2946b..83d44ccc01d 100644 --- a/src/vpp-api/vom/route_cmds.cpp +++ b/src/vpp-api/vom/route_cmds.cpp @@ -33,8 +33,8 @@ to_vpp(const route::path& p, vapi_payload_ip_add_del_route& payload) payload.is_resolve_host = 0; payload.is_resolve_attached = 0; - if (nh_proto_t::ETHERNET == p.nh_proto()) { - payload.is_l2_bridged = 1; + if (route::path::flags_t::DVR & p.flags()) { + payload.is_dvr = 1; } if (route::path::special_t::STANDARD == p.type()) { diff --git a/test/test_dvr.py b/test/test_dvr.py index e7b68db2ac7..e2e960584a6 100644 --- a/test/test_dvr.py +++ b/test/test_dvr.py @@ -13,6 +13,7 @@ from scapy.packet import Raw from scapy.layers.l2 import Ether, Dot1Q, ARP from scapy.layers.inet import IP, UDP from util import ppp +from socket import AF_INET, inet_pton class TestDVR(VppTestCase): @@ -107,22 +108,16 @@ class TestDVR(VppTestCase): self, ip_non_tag_bridged, 32, [VppRoutePath("0.0.0.0", self.pg1.sw_if_index, - proto=DpoProto.DPO_PROTO_ETHERNET)]) + is_dvr=1)]) route_no_tag.add_vpp_config() # # Inject the packet that arrives and leaves on a non-tagged interface # Since it's 'bridged' expect that the MAC headed is unchanged. # - self.pg0.add_stream(pkt_no_tag) - - self.pg_enable_capture(self.pg_interfaces) - self.pg_start() - - rx = self.pg1.get_capture(1) - - self.assertEqual(rx[0][Ether].dst, pkt_no_tag[Ether].dst) - self.assertEqual(rx[0][Ether].src, pkt_no_tag[Ether].src) + rx = self.send_and_expect(self.pg0, pkt_no_tag * 65, self.pg1) + self.assert_same_mac_addr(pkt_no_tag, rx) + self.assert_has_no_tag(rx) # # Add routes to bridge the traffic via a tagged interface @@ -131,12 +126,12 @@ class TestDVR(VppTestCase): self, ip_tag_bridged, 32, [VppRoutePath("0.0.0.0", sub_if_on_pg3.sw_if_index, - proto=DpoProto.DPO_PROTO_ETHERNET)]) + is_dvr=1)]) route_with_tag.add_vpp_config() # - # Inject the packet that arrives and leaves on a non-tagged interface - # Since it's 'bridged' expect that the MAC headed is unchanged. + # Inject the packet that arrives non-tag and leaves on a tagged + # interface # rx = self.send_and_expect(self.pg0, pkt_tag * 65, self.pg3) self.assert_same_mac_addr(pkt_tag, rx) @@ -172,9 +167,42 @@ class TestDVR(VppTestCase): self.assert_same_mac_addr(pkt_tag_to_tag, rx) self.assert_has_no_tag(rx) + # + # Add an output L3 ACL that will block the traffic + # + 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, any_src_addr), + 'dstport_or_icmpcode_first': 1234, + 'dstport_or_icmpcode_last': 1234, + 'dst_ip_prefix_len': 32, + 'dst_ip_addr': inet_pton(AF_INET, ip_non_tag_bridged)}) + acl = self.vapi.acl_add_replace(acl_index=4294967295, + r=[rule_1]) + + # + # Apply the ACL on the output interface + # + self.vapi.acl_interface_set_acl_list(self.pg1.sw_if_index, + 0, + [acl.acl_index]) + + # + # Send packet's that should match the ACL and be dropped + # + rx = self.send_and_assert_no_replies(self.pg2, pkt_tag_to_non_tag * 65) + # # cleanup # + self.vapi.acl_interface_set_acl_list(self.pg1.sw_if_index, + 0, []) + self.vapi.acl_del(acl.acl_index) + self.vapi.sw_interface_set_l2_bridge(self.pg0.sw_if_index, 1, enable=0) self.vapi.sw_interface_set_l2_bridge(self.pg1.sw_if_index, 1, @@ -258,7 +286,7 @@ class TestDVR(VppTestCase): 93) # - # Disable UU flooding, learning and ARM terminaation. makes this test + # Disable UU flooding, learning and ARP terminaation. makes this test # easier as unicast packets are dropped if not extracted. # self.vapi.bridge_flags(1, 0, (1 << 0) | (1 << 3) | (1 << 4)) @@ -269,11 +297,11 @@ class TestDVR(VppTestCase): route_1 = VppIpRoute(self, "1.1.1.1", 32, [VppRoutePath("0.0.0.0", self.pg1.sw_if_index, - proto=DpoProto.DPO_PROTO_ETHERNET)]) + is_dvr=1)]) route_2 = VppIpRoute(self, "1.1.1.2", 32, [VppRoutePath("0.0.0.0", sub_if_on_pg2.sw_if_index, - proto=DpoProto.DPO_PROTO_ETHERNET)]) + is_dvr=1)]) route_1.add_vpp_config() route_2.add_vpp_config() diff --git a/test/vpp_ip_route.py b/test/vpp_ip_route.py index fedf1302e21..2d34f55efdf 100644 --- a/test/vpp_ip_route.py +++ b/test/vpp_ip_route.py @@ -110,6 +110,7 @@ class VppRoutePath(object): is_resolve_attached=0, is_source_lookup=0, is_udp_encap=0, + is_dvr=0, next_hop_id=0xffffffff, proto=DpoProto.DPO_PROTO_IP4): self.nh_itf = nh_sw_if_index @@ -135,6 +136,7 @@ class VppRoutePath(object): self.nh_itf = rpf_id self.is_udp_encap = is_udp_encap self.next_hop_id = next_hop_id + self.is_dvr = is_dvr class VppMRoutePath(VppRoutePath): @@ -206,8 +208,7 @@ class VppIpRoute(VppObject): next_hop_table_id=path.nh_table_id, next_hop_id=path.next_hop_id, is_ipv6=self.is_ip6, - is_l2_bridged=1 - if path.proto == DpoProto.DPO_PROTO_ETHERNET else 0, + is_dvr=path.is_dvr, is_resolve_host=path.is_resolve_host, is_resolve_attached=path.is_resolve_attached, is_source_lookup=path.is_source_lookup, @@ -241,7 +242,8 @@ class VppIpRoute(VppObject): next_hop_id=path.next_hop_id, is_add=0, is_udp_encap=path.is_udp_encap, - is_ipv6=self.is_ip6) + is_ipv6=self.is_ip6, + is_dvr=path.is_dvr) def query_vpp_config(self): return find_route(self._test, diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 16bc44b64b9..acd05a7a26d 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -759,7 +759,7 @@ class VppPapiProvider(object): is_local=0, is_classify=0, is_multipath=0, - is_l2_bridged=0, + is_dvr=0, is_udp_encap=0, is_source_lookup=0): """ @@ -780,7 +780,7 @@ class VppPapiProvider(object): :param is_multipath: (Default value = 0) :param is_resolve_host: (Default value = 0) :param is_resolve_attached: (Default value = 0) - :param is_l2_bridged: (Default value = 0) + :param is_dvr: (Default value = 0) :param is_source_lookup: (Default value = 0) :param next_hop_weight: (Default value = 1) @@ -802,7 +802,7 @@ class VppPapiProvider(object): 'is_multipath': is_multipath, 'is_resolve_host': is_resolve_host, 'is_resolve_attached': is_resolve_attached, - 'is_l2_bridged': is_l2_bridged, + 'is_dvr': is_dvr, 'is_source_lookup': is_source_lookup, 'is_udp_encap': is_udp_encap, 'next_hop_weight': next_hop_weight, -- 2.16.6