diff options
Diffstat (limited to 'plugins/ioam-plugin/ioam/lib-vxlan-gpe')
10 files changed, 925 insertions, 57 deletions
diff --git a/plugins/ioam-plugin/ioam/lib-vxlan-gpe/ioam_decap.c b/plugins/ioam-plugin/ioam/lib-vxlan-gpe/ioam_decap.c index f938a330..fd308657 100644 --- a/plugins/ioam-plugin/ioam/lib-vxlan-gpe/ioam_decap.c +++ b/plugins/ioam-plugin/ioam/lib-vxlan-gpe/ioam_decap.c @@ -52,10 +52,10 @@ vxlan_gpe_decap_ioam_v4_two_inline (vlib_main_t * vm, vxlan_gpe_ioam_main_t *hm = &vxlan_gpe_ioam_main; next0[0] = next1[0] = hm->decap_v4_next_override; - vxlan_gpe_encap_decap_ioam_v4_one_inline (vm, node, ngm, b0, &next0[0], + vxlan_gpe_encap_decap_ioam_v4_one_inline (vm, node, b0, &next0[0], VXLAN_GPE_DECAP_IOAM_V4_NEXT_DROP, 0 /* use_adj */ ); - vxlan_gpe_encap_decap_ioam_v4_one_inline (vm, node, ngm, b1, &next0[1], + vxlan_gpe_encap_decap_ioam_v4_one_inline (vm, node, b1, &next0[1], VXLAN_GPE_DECAP_IOAM_V4_NEXT_DROP, 0 /* use_adj */ ); } @@ -163,7 +163,7 @@ vxlan_gpe_decap_ioam (vlib_main_t * vm, sizeof (vxlan_gpe_header_t))); next0 = hm->decap_v4_next_override; - vxlan_gpe_encap_decap_ioam_v4_one_inline (vm, node, ngm, b0, + vxlan_gpe_encap_decap_ioam_v4_one_inline (vm, node, b0, &next0, VXLAN_GPE_DECAP_IOAM_V4_NEXT_DROP, 0 /* use_adj */ ); diff --git a/plugins/ioam-plugin/ioam/lib-vxlan-gpe/ioam_encap.c b/plugins/ioam-plugin/ioam/lib-vxlan-gpe/ioam_encap.c index 7add6d03..4b18bfea 100644 --- a/plugins/ioam-plugin/ioam/lib-vxlan-gpe/ioam_encap.c +++ b/plugins/ioam-plugin/ioam/lib-vxlan-gpe/ioam_encap.c @@ -56,10 +56,10 @@ vxlan_gpe_encap_ioam_v4_two_inline (vlib_main_t * vm, u32 * next0, u32 * next1) { next0[0] = next1[0] = VXLAN_GPE_ENCAP_IOAM_V4_NEXT_IP4_LOOKUP; - vxlan_gpe_encap_decap_ioam_v4_one_inline (vm, node, ngm, b0, next0, + vxlan_gpe_encap_decap_ioam_v4_one_inline (vm, node, b0, next0, VXLAN_GPE_ENCAP_IOAM_V4_NEXT_DROP, 0 /* use_adj */ ); - vxlan_gpe_encap_decap_ioam_v4_one_inline (vm, node, ngm, b1, next1, + vxlan_gpe_encap_decap_ioam_v4_one_inline (vm, node, b1, next1, VXLAN_GPE_ENCAP_IOAM_V4_NEXT_DROP, 0 /* use_adj */ ); } @@ -142,7 +142,7 @@ vxlan_gpe_encap_ioam_v4 (vlib_main_t * vm, b0 = vlib_get_buffer (vm, bi0); - vxlan_gpe_encap_decap_ioam_v4_one_inline (vm, node, ngm, b0, + vxlan_gpe_encap_decap_ioam_v4_one_inline (vm, node, b0, &next0, VXLAN_GPE_ENCAP_IOAM_V4_NEXT_DROP, 0 /* use_adj */ ); diff --git a/plugins/ioam-plugin/ioam/lib-vxlan-gpe/ioam_transit.c b/plugins/ioam-plugin/ioam/lib-vxlan-gpe/ioam_transit.c new file mode 100644 index 00000000..b42c357c --- /dev/null +++ b/plugins/ioam-plugin/ioam/lib-vxlan-gpe/ioam_transit.c @@ -0,0 +1,188 @@ + /* + * Copyright (c) 2015 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 <vppinfra/error.h> +#include <vppinfra/hash.h> +#include <vnet/vnet.h> +#include <vnet/ip/ip.h> +#include <vnet/ip/udp.h> +#include <vnet/ethernet/ethernet.h> +#include <vnet/vxlan-gpe/vxlan_gpe.h> +#include <ioam/lib-vxlan-gpe/vxlan_gpe_ioam_packet.h> +#include <ioam/lib-vxlan-gpe/vxlan_gpe_ioam.h> +#include <ioam/lib-vxlan-gpe/vxlan_gpe_ioam_util.h> +#include <vnet/fib/ip6_fib.h> +#include <vnet/fib/ip4_fib.h> +#include <vnet/fib/fib_entry.h> + +/* Statistics (not really errors) */ +#define foreach_vxlan_gpe_transit_ioam_error \ +_(ENCAPSULATED, "good packets encapsulated") + +static char *vxlan_gpe_transit_ioam_error_strings[] = { +#define _(sym,string) string, + foreach_vxlan_gpe_transit_ioam_error +#undef _ +}; + +typedef enum +{ +#define _(sym,str) VXLAN_GPE_TRANSIT_IOAM_ERROR_##sym, + foreach_vxlan_gpe_transit_ioam_error +#undef _ + VXLAN_GPE_TRANSIT_IOAM_N_ERROR, +} vxlan_gpe_transit_ioam_error_t; + +typedef enum +{ + VXLAN_GPE_TRANSIT_IOAM_NEXT_OUTPUT, + VXLAN_GPE_TRANSIT_IOAM_NEXT_DROP, + VXLAN_GPE_TRANSIT_IOAM_N_NEXT +} vxlan_gpe_transit_ioam_next_t; + + +/* *INDENT-OFF* */ +VNET_FEATURE_INIT (vxlan_gpe_transit_ioam, static) = +{ + .arc_name = "ip4-output", + .node_name = "vxlan-gpe-transit-ioam", + .runs_before = VNET_FEATURES ("interface-output"), +}; +/* *INDENT-ON* */ + +static uword +vxlan_gpe_transit_ioam (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 > 0 && n_left_to_next > 0) + { + u32 bi0; + vlib_buffer_t *b0; + u32 next0 = VXLAN_GPE_TRANSIT_IOAM_NEXT_OUTPUT; + + bi0 = from[0]; + to_next[0] = bi0; + from += 1; + to_next += 1; + n_left_from -= 1; + n_left_to_next -= 1; + ip4_header_t *ip0; + u32 iph_offset = 0; + + b0 = vlib_get_buffer (vm, bi0); + iph_offset = vnet_buffer (b0)->ip.save_rewrite_length; + ip0 = (ip4_header_t *) ((u8 *) vlib_buffer_get_current (b0) + + iph_offset); + + /* just forward non ipv4 packets */ + if (PREDICT_FALSE + ((ip0->ip_version_and_header_length & 0xF0) == 0x40)) + { + /* ipv4 packets */ + udp_header_t *udp_hdr0 = (udp_header_t *) (ip0 + 1); + if (PREDICT_FALSE + ((ip0->protocol == IP_PROTOCOL_UDP) && + (clib_net_to_host_u16 (udp_hdr0->dst_port) == + UDP_DST_PORT_vxlan_gpe))) + { + + /* Check the iOAM header */ + vxlan_gpe_header_t *gpe_hdr0 = + (vxlan_gpe_header_t *) (udp_hdr0 + 1); + + if (PREDICT_FALSE + (gpe_hdr0->protocol == VXLAN_GPE_PROTOCOL_IOAM)) + { + uword *t = NULL; + vxlan_gpe_ioam_main_t *hm = &vxlan_gpe_ioam_main; + fib_prefix_t key4; + memset (&key4, 0, sizeof (key4)); + key4.fp_proto = FIB_PROTOCOL_IP4; + key4.fp_addr.ip4.as_u32 = ip0->dst_address.as_u32; + t = hash_get_mem (hm->dst_by_ip4, &key4); + if (t) + { + + + vlib_buffer_advance (b0, + (word) (sizeof + (ethernet_header_t))); + vxlan_gpe_encap_decap_ioam_v4_one_inline (vm, node, + b0, + &next0, + VXLAN_GPE_TRANSIT_IOAM_NEXT_DROP, + 1 + /* use_adj */ + ); + vlib_buffer_advance (b0, + -(word) (sizeof + (ethernet_header_t))); + } + } + } + } + + 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 (vxlan_gpe_transit_ioam_node) = { + .function = vxlan_gpe_transit_ioam, + .name = "vxlan-gpe-transit-ioam", + .vector_size = sizeof (u32), + .format_trace = format_vxlan_gpe_ioam_v4_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_errors = ARRAY_LEN(vxlan_gpe_transit_ioam_error_strings), + .error_strings = vxlan_gpe_transit_ioam_error_strings, + + .n_next_nodes = VXLAN_GPE_TRANSIT_IOAM_N_NEXT, + + .next_nodes = { + [VXLAN_GPE_TRANSIT_IOAM_NEXT_OUTPUT] = "interface-output", + [VXLAN_GPE_TRANSIT_IOAM_NEXT_DROP] = "error-drop", + }, + +}; +/* *INDENT-ON* */ + + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe.api b/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe.api index 2cf7a597..056529a4 100644 --- a/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe.api +++ b/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe.api @@ -124,3 +124,58 @@ define vxlan_gpe_ioam_vni_disable_reply { }; +/** \brief Enable iOAM for a VXLAN-GPE transit + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param dst_addr - IPv4/6 Address of the local VTEP + @param outer_fib_index- FIB index + +*/ +define vxlan_gpe_ioam_transit_enable { + u32 client_index; + u32 context; + u32 outer_fib_index; + u8 dst_addr[16]; + u8 is_ipv6; +}; + +/** \brief Reply to enable iOAM for VXLAN-GPE transit + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param retval - return value for request + +*/ +define vxlan_gpe_ioam_transit_enable_reply { + u32 client_index; + u32 context; + i32 retval; +}; + +/** \brief Disable iOAM for VXLAN-GPE transit + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param dst_addr - IPv4/6 Address of the local VTEP + @param outer_fib_index- FIB index + +*/ +define vxlan_gpe_ioam_transit_disable { + u32 client_index; + u32 context; + u32 outer_fib_index; + u8 dst_addr[16]; + u8 is_ipv6; +}; + +/** \brief Reply to disable iOAM for VXLAN-GPE transit + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param retval - return value for request + +*/ +define vxlan_gpe_ioam_transit_disable_reply { + u32 client_index; + u32 context; + i32 retval; +}; + + diff --git a/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_api.c b/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_api.c index 3e143850..68752365 100644 --- a/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_api.c +++ b/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_api.c @@ -94,11 +94,13 @@ do { \ /* List of message types that this plugin understands */ -#define foreach_vxlan_gpe_plugin_api_msg \ -_(VXLAN_GPE_IOAM_ENABLE, vxlan_gpe_ioam_enable) \ -_(VXLAN_GPE_IOAM_DISABLE, vxlan_gpe_ioam_disable) \ -_(VXLAN_GPE_IOAM_VNI_ENABLE, vxlan_gpe_ioam_vni_enable) \ -_(VXLAN_GPE_IOAM_VNI_DISABLE, vxlan_gpe_ioam_vni_disable) \ +#define foreach_vxlan_gpe_plugin_api_msg \ +_(VXLAN_GPE_IOAM_ENABLE, vxlan_gpe_ioam_enable) \ +_(VXLAN_GPE_IOAM_DISABLE, vxlan_gpe_ioam_disable) \ +_(VXLAN_GPE_IOAM_VNI_ENABLE, vxlan_gpe_ioam_vni_enable) \ +_(VXLAN_GPE_IOAM_VNI_DISABLE, vxlan_gpe_ioam_vni_disable) \ +_(VXLAN_GPE_IOAM_TRANSIT_ENABLE, vxlan_gpe_ioam_transit_enable) \ +_(VXLAN_GPE_IOAM_TRANSIT_DISABLE, vxlan_gpe_ioam_transit_disable) \ static void vl_api_vxlan_gpe_ioam_enable_t_handler @@ -239,6 +241,50 @@ static void vl_api_vxlan_gpe_ioam_vni_disable_t_handler VXLAN_GPE_REPLY_MACRO (VL_API_VXLAN_GPE_IOAM_VNI_DISABLE_REPLY); } +static void vl_api_vxlan_gpe_ioam_transit_enable_t_handler + (vl_api_vxlan_gpe_ioam_transit_enable_t * mp) +{ + int rv = 0; + vl_api_vxlan_gpe_ioam_transit_enable_reply_t *rmp; + vxlan_gpe_ioam_main_t *sm = &vxlan_gpe_ioam_main; + ip46_address_t dst_addr; + + memset (&dst_addr.ip4, 0, sizeof (dst_addr.ip4)); + if (!mp->is_ipv6) + { + clib_memcpy (&dst_addr.ip4, &mp->dst_addr, sizeof (dst_addr.ip4)); + } + rv = vxlan_gpe_enable_disable_ioam_for_dest (sm->vlib_main, + dst_addr, + ntohl (mp->outer_fib_index), + mp->is_ipv6 ? 0 : 1, + 1 /* is_add */ ); + + VXLAN_GPE_REPLY_MACRO (VL_API_VXLAN_GPE_IOAM_TRANSIT_ENABLE_REPLY); +} + +static void vl_api_vxlan_gpe_ioam_transit_disable_t_handler + (vl_api_vxlan_gpe_ioam_transit_disable_t * mp) +{ + int rv = 0; + vl_api_vxlan_gpe_ioam_transit_disable_reply_t *rmp; + vxlan_gpe_ioam_main_t *sm = &vxlan_gpe_ioam_main; + ip46_address_t dst_addr; + + memset (&dst_addr.ip4, 0, sizeof (dst_addr.ip4)); + if (!mp->is_ipv6) + { + clib_memcpy (&dst_addr.ip4, &mp->dst_addr, sizeof (dst_addr.ip4)); + } + + rv = vxlan_gpe_ioam_disable_for_dest (sm->vlib_main, + dst_addr, + ntohl (mp->outer_fib_index), + mp->is_ipv6 ? 0 : 1); + VXLAN_GPE_REPLY_MACRO (VL_API_VXLAN_GPE_IOAM_TRANSIT_DISABLE_REPLY); +} + + /* * This routine exists to convince the vlib plugin framework that * we haven't accidentally copied a random .dll into the plugin directory. @@ -255,6 +301,8 @@ vlib_plugin_register (vlib_main_t * vm, vnet_plugin_handoff_t * h, sm->vlib_main = vm; sm->vnet_main = h->vnet_main; + sm->unix_time_0 = (u32) time (0); /* Store starting time */ + sm->vlib_time_0 = vlib_time_now (vm); return error; } @@ -308,6 +356,12 @@ vxlan_gpe_init (vlib_main_t * vm) vlib_node_add_next (vm, vxlan_gpe_decap_node->index, decap_node_index); vxlan_gpe_register_decap_protocol (VXLAN_GPE_PROTOCOL_IOAM, next_node); + vec_new (vxlan_gpe_ioam_sw_interface_t, pool_elts (sm->sw_interfaces)); + sm->dst_by_ip4 = hash_create_mem (0, sizeof (fib_prefix_t), sizeof (uword)); + + sm->dst_by_ip6 = hash_create_mem (0, sizeof (fib_prefix_t), sizeof (uword)); + + vxlan_gpe_ioam_interface_init (); vec_free (name); return error; diff --git a/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_ioam.c b/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_ioam.c index 066f5821..6c04d9af 100644 --- a/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_ioam.c +++ b/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_ioam.c @@ -16,6 +16,9 @@ #include <vnet/vxlan-gpe/vxlan_gpe_packet.h> #include <vnet/ip/format.h> #include <ioam/lib-vxlan-gpe/vxlan_gpe_ioam.h> +#include <vnet/fib/ip6_fib.h> +#include <vnet/fib/ip4_fib.h> +#include <vnet/fib/fib_entry.h> vxlan_gpe_ioam_main_t vxlan_gpe_ioam_main; @@ -163,11 +166,257 @@ vxlan_gpe_ioam_set (vxlan_gpe_tunnel_t * t, } +static void +vxlan_gpe_set_clear_output_feature_on_intf (vlib_main_t * vm, + u32 sw_if_index0, u8 is_add) +{ + + + + vnet_feature_enable_disable ("ip4-output", "vxlan-gpe-transit-ioam", + sw_if_index0, is_add, + 0 /* void *feature_config */ , + 0 /* u32 n_feature_config_bytes */ ); + return; +} + +void +vxlan_gpe_clear_output_feature_on_all_intfs (vlib_main_t * vm) +{ + vnet_sw_interface_t *si = 0; + vnet_main_t *vnm = vnet_get_main (); + vnet_interface_main_t *im = &vnm->interface_main; + + pool_foreach (si, im->sw_interfaces, ( + { + vxlan_gpe_set_clear_output_feature_on_intf + (vm, si->sw_if_index, 0); + })); + return; +} + + +extern fib_forward_chain_type_t +fib_entry_get_default_chain_type (const fib_entry_t * fib_entry); + +int +vxlan_gpe_enable_disable_ioam_for_dest (vlib_main_t * vm, + ip46_address_t dst_addr, + u32 outer_fib_index, + u8 is_ipv4, u8 is_add) +{ + vxlan_gpe_ioam_main_t *hm = &vxlan_gpe_ioam_main; + u32 fib_index0 = 0; + u32 sw_if_index0 = ~0; + + fib_node_index_t fei = ~0; + fib_entry_t *fib_entry; + u32 adj_index0; + ip_adjacency_t *adj0; + fib_prefix_t fib_prefix; + //fib_forward_chain_type_t fct; + load_balance_t *lb_m, *lb_b; + const dpo_id_t *dpo0, *dpo1; + u32 i, j; + //vnet_hw_interface_t *hw; + + if (is_ipv4) + { + memset (&fib_prefix, 0, sizeof (fib_prefix_t)); + fib_prefix.fp_len = 32; + fib_prefix.fp_proto = FIB_PROTOCOL_IP4; + fib_prefix.fp_addr = dst_addr; + } + else + { + return 0; + } + + fei = fib_table_lookup (fib_index0, &fib_prefix); + fib_entry = fib_entry_get (fei); + + //fct = fib_entry_get_default_chain_type (fib_entry); + + if (!dpo_id_is_valid (&fib_entry->fe_lb /*[fct] */ )) + { + return (-1); + } + + lb_m = load_balance_get (fib_entry->fe_lb /*[fct] */ .dpoi_index); + + for (i = 0; i < lb_m->lb_n_buckets; i++) + { + dpo0 = load_balance_get_bucket_i (lb_m, i); + + if (dpo0->dpoi_type == DPO_LOAD_BALANCE) + { + lb_b = load_balance_get (dpo0->dpoi_index); + + for (j = 0; j < lb_b->lb_n_buckets; j++) + { + dpo1 = load_balance_get_bucket_i (lb_b, j); + + if (dpo1->dpoi_type == DPO_ADJACENCY) + { + adj_index0 = dpo1->dpoi_index; + + if (ADJ_INDEX_INVALID == adj_index0) + { + continue; + } + + adj0 = + ip_get_adjacency (&(ip4_main.lookup_main), adj_index0); + sw_if_index0 = adj0->rewrite_header.sw_if_index; + + if (~0 == sw_if_index0) + { + continue; + } + + + if (is_add) + { + vnet_feature_enable_disable ("ip4-output", + "vxlan-gpe-transit-ioam", + sw_if_index0, is_add, 0 + /* void *feature_config */ + , 0 /* u32 n_feature_config_bytes */ + ); + + vec_validate_init_empty (hm->bool_ref_by_sw_if_index, + sw_if_index0, ~0); + hm->bool_ref_by_sw_if_index[sw_if_index0] = 1; + } + else + { + hm->bool_ref_by_sw_if_index[sw_if_index0] = ~0; + } + } + } + } + } + + if (is_ipv4) + { + + uword *t = NULL; + vxlan_gpe_ioam_dest_tunnels_t *t1; + fib_prefix_t key4, *key4_copy; + hash_pair_t *hp; + memset (&key4, 0, sizeof (key4)); + key4.fp_proto = FIB_PROTOCOL_IP4; + key4.fp_addr.ip4.as_u32 = fib_prefix.fp_addr.ip4.as_u32; + t = hash_get_mem (hm->dst_by_ip4, &key4); + if (is_add) + { + if (t) + { + return 0; + } + pool_get_aligned (hm->dst_tunnels, t1, CLIB_CACHE_LINE_BYTES); + memset (t1, 0, sizeof (*t1)); + t1->fp_proto = FIB_PROTOCOL_IP4; + t1->dst_addr.ip4.as_u32 = fib_prefix.fp_addr.ip4.as_u32; + key4_copy = clib_mem_alloc (sizeof (*key4_copy)); + clib_memcpy (key4_copy, &key4, sizeof (*key4_copy)); + hash_set_mem (hm->dst_by_ip4, key4_copy, t1 - hm->dst_tunnels); + /* + * Attach to the FIB entry for the VxLAN-GPE destination + * and become its child. The dest route will invoke a callback + * when the fib entry changes, it can be used to + * re-program the output feature on the egress interface. + */ + + const fib_prefix_t tun_dst_pfx = { + .fp_len = 32, + .fp_proto = FIB_PROTOCOL_IP4, + .fp_addr = {.ip4 = t1->dst_addr.ip4,} + }; + + t1->fib_entry_index = + fib_table_entry_special_add (outer_fib_index, + &tun_dst_pfx, + FIB_SOURCE_RR, + FIB_ENTRY_FLAG_NONE, + ADJ_INDEX_INVALID); + t1->sibling_index = + fib_entry_child_add (t1->fib_entry_index, + hm->fib_entry_type, t1 - hm->dst_tunnels); + t1->outer_fib_index = outer_fib_index; + + } + else + { + if (!t) + { + return 0; + } + t1 = pool_elt_at_index (hm->dst_tunnels, t[0]); + hp = hash_get_pair (hm->dst_by_ip4, &key4); + key4_copy = (void *) (hp->key); + hash_unset_mem (hm->dst_by_ip4, &key4); + clib_mem_free (key4_copy); + pool_put (hm->dst_tunnels, t1); + } + } + else + { + // TBD for IPv6 + } + + return 0; +} + +void +vxlan_gpe_refresh_output_feature_on_all_dest (void) +{ + vxlan_gpe_ioam_main_t *hm = &vxlan_gpe_ioam_main; + vxlan_gpe_ioam_dest_tunnels_t *t; + u32 i; + if (pool_elts (hm->dst_tunnels) == 0) + return; + vxlan_gpe_clear_output_feature_on_all_intfs (hm->vlib_main); + i = vec_len (hm->bool_ref_by_sw_if_index); + vec_free (hm->bool_ref_by_sw_if_index); + vec_validate_init_empty (hm->bool_ref_by_sw_if_index, i, ~0); + pool_foreach (t, hm->dst_tunnels, ( + { + vxlan_gpe_enable_disable_ioam_for_dest + (hm->vlib_main, + t->dst_addr, + t->outer_fib_index, + (t->fp_proto == FIB_PROTOCOL_IP4), 1 + /* is_add */ + ); + } + )); + return; +} + +void +vxlan_gpe_clear_output_feature_on_select_intfs (void) +{ + vxlan_gpe_ioam_main_t *hm = &vxlan_gpe_ioam_main; + u32 sw_if_index0 = 0; + for (sw_if_index0 = 0; + sw_if_index0 < vec_len (hm->bool_ref_by_sw_if_index); sw_if_index0++) + { + if (hm->bool_ref_by_sw_if_index[sw_if_index0] == 0xFF) + { + vxlan_gpe_set_clear_output_feature_on_intf + (hm->vlib_main, sw_if_index0, 0); + } + } + + return; +} static clib_error_t * -vxlan_gpe_set_ioam_rewrite_command_fn (vlib_main_t * vm, - unformat_input_t * input, - vlib_cli_command_t * cmd) +vxlan_gpe_set_ioam_rewrite_command_fn (vlib_main_t * + vm, + unformat_input_t + * input, vlib_cli_command_t * cmd) { vxlan_gpe_ioam_main_t *hm = &vxlan_gpe_ioam_main; ip46_address_t local, remote; @@ -184,7 +433,6 @@ vxlan_gpe_set_ioam_rewrite_command_fn (vlib_main_t * vm, uword *p; vxlan_gpe_main_t *gm = &vxlan_gpe_main; vxlan_gpe_tunnel_t *t = 0; - while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { if (unformat (input, "local %U", unformat_ip4_address, &local.ip4)) @@ -192,8 +440,8 @@ vxlan_gpe_set_ioam_rewrite_command_fn (vlib_main_t * vm, local_set = 1; ipv4_set = 1; } - else if (unformat (input, "remote %U", - unformat_ip4_address, &remote.ip4)) + else + if (unformat (input, "remote %U", unformat_ip4_address, &remote.ip4)) { remote_set = 1; ipv4_set = 1; @@ -203,8 +451,8 @@ vxlan_gpe_set_ioam_rewrite_command_fn (vlib_main_t * vm, local_set = 1; ipv6_set = 1; } - else if (unformat (input, "remote %U", - unformat_ip6_address, &remote.ip6)) + else + if (unformat (input, "remote %U", unformat_ip6_address, &remote.ip6)) { remote_set = 1; ipv6_set = 1; @@ -219,29 +467,27 @@ vxlan_gpe_set_ioam_rewrite_command_fn (vlib_main_t * vm, if (local_set == 0) return clib_error_return (0, "tunnel local address not specified"); - if (remote_set == 0) return clib_error_return (0, "tunnel remote address not specified"); - if (ipv4_set && ipv6_set) return clib_error_return (0, "both IPv4 and IPv6 addresses specified"); - - if ((ipv4_set && memcmp (&local.ip4, &remote.ip4, sizeof (local.ip4)) == 0) - || (ipv6_set - && memcmp (&local.ip6, &remote.ip6, sizeof (local.ip6)) == 0)) + if ((ipv4_set + && memcmp (&local.ip4, &remote.ip4, + sizeof (local.ip4)) == 0) || (ipv6_set + && + memcmp + (&local.ip6, + &remote.ip6, + sizeof (local.ip6)) == 0)) return clib_error_return (0, "src and dst addresses are identical"); - if (vni_set == 0) return clib_error_return (0, "vni not specified"); - - if (!ipv6_set) { key4.local = local.ip4.as_u32; key4.remote = remote.ip4.as_u32; key4.vni = clib_host_to_net_u32 (vni << 8); key4.pad = 0; - p = hash_get_mem (gm->vxlan4_gpe_tunnel_by_key, &key4); } else @@ -251,20 +497,17 @@ vxlan_gpe_set_ioam_rewrite_command_fn (vlib_main_t * vm, key6.remote.as_u64[0] = remote.ip6.as_u64[0]; key6.remote.as_u64[1] = remote.ip6.as_u64[1]; key6.vni = clib_host_to_net_u32 (vni << 8); - p = hash_get_mem (gm->vxlan6_gpe_tunnel_by_key, &key6); } if (!p) return clib_error_return (0, "VxLAN Tunnel not found"); - t = pool_elt_at_index (gm->tunnels, p[0]); - if (!disable) { - rv = vxlan_gpe_ioam_set (t, hm->has_trace_option, - hm->has_pot_option, - hm->has_ppc_option, ipv6_set); + rv = + vxlan_gpe_ioam_set (t, hm->has_trace_option, + hm->has_pot_option, hm->has_ppc_option, ipv6_set); } else { @@ -285,11 +528,10 @@ VLIB_CLI_COMMAND (vxlan_gpe_set_ioam_rewrite_cmd, static) = { clib_error_t * -vxlan_gpe_ioam_enable (int has_trace_option, int has_pot_option, - int has_ppc_option) +vxlan_gpe_ioam_enable (int has_trace_option, + int has_pot_option, int has_ppc_option) { vxlan_gpe_ioam_main_t *hm = &vxlan_gpe_ioam_main; - hm->has_trace_option = has_trace_option; hm->has_pot_option = has_pot_option; hm->has_ppc_option = has_ppc_option; @@ -302,11 +544,11 @@ vxlan_gpe_ioam_enable (int has_trace_option, int has_pot_option, } clib_error_t * -vxlan_gpe_ioam_disable (int has_trace_option, int has_pot_option, - int has_ppc_option) +vxlan_gpe_ioam_disable (int + has_trace_option, + int has_pot_option, int has_ppc_option) { vxlan_gpe_ioam_main_t *hm = &vxlan_gpe_ioam_main; - hm->has_trace_option = has_trace_option; hm->has_pot_option = has_pot_option; hm->has_ppc_option = has_ppc_option; @@ -328,14 +570,13 @@ vxlan_gpe_set_next_override (uword next) static clib_error_t * vxlan_gpe_set_ioam_flags_command_fn (vlib_main_t * vm, - unformat_input_t * input, - vlib_cli_command_t * cmd) + unformat_input_t + * input, vlib_cli_command_t * cmd) { int has_trace_option = 0; int has_pot_option = 0; int has_ppc_option = 0; clib_error_t *rv = 0; - while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { if (unformat (input, "trace")) @@ -355,7 +596,6 @@ vxlan_gpe_set_ioam_flags_command_fn (vlib_main_t * vm, rv = vxlan_gpe_ioam_enable (has_trace_option, has_pot_option, has_ppc_option); - return rv; } @@ -368,12 +608,100 @@ VLIB_CLI_COMMAND (vxlan_gpe_set_ioam_flags_cmd, static) = /* *INDENT-ON* */ +int vxlan_gpe_ioam_disable_for_dest + (vlib_main_t * vm, ip46_address_t dst_addr, u32 outer_fib_index, + u8 ipv4_set) +{ + vxlan_gpe_ioam_dest_tunnels_t *t; + vxlan_gpe_ioam_main_t *hm = &vxlan_gpe_ioam_main; + + vxlan_gpe_enable_disable_ioam_for_dest (hm->vlib_main, + dst_addr, outer_fib_index, ipv4_set, + 0); + if (pool_elts (hm->dst_tunnels) == 0) + { + vxlan_gpe_clear_output_feature_on_select_intfs (); + return 0; + } + + pool_foreach (t, hm->dst_tunnels, ( + { + vxlan_gpe_enable_disable_ioam_for_dest + (hm->vlib_main, + t->dst_addr, + t->outer_fib_index, + (t->fp_proto == + FIB_PROTOCOL_IP4), 1 /* is_add */ ); + } + )); + vxlan_gpe_clear_output_feature_on_select_intfs (); + return (0); +} -clib_error_t * -clear_vxlan_gpe_ioam_rewrite_command_fn (vlib_main_t * vm, - unformat_input_t * input, - vlib_cli_command_t * cmd) +static clib_error_t *vxlan_gpe_set_ioam_transit_rewrite_command_fn + (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) +{ + vxlan_gpe_ioam_main_t *hm = &vxlan_gpe_ioam_main; + ip46_address_t dst_addr; + u8 dst_addr_set = 0; + u8 ipv4_set = 0; + u8 ipv6_set = 0; + u8 disable = 0; + clib_error_t *rv = 0; + u32 outer_fib_index = 0; + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "dst-ip %U", unformat_ip4_address, &dst_addr.ip4)) + { + dst_addr_set = 1; + ipv4_set = 1; + } + else + if (unformat + (input, "dst-ip %U", unformat_ip6_address, &dst_addr.ip6)) + { + dst_addr_set = 1; + ipv6_set = 1; + } + else if (unformat (input, "outer-fib-index %d", &outer_fib_index)) + { + } + + else if (unformat (input, "disable")) + disable = 1; + else + break; + } + + if (dst_addr_set == 0) + return clib_error_return (0, "tunnel destination address not specified"); + if (ipv4_set && ipv6_set) + return clib_error_return (0, "both IPv4 and IPv6 addresses specified"); + if (!disable) + { + vxlan_gpe_enable_disable_ioam_for_dest (hm->vlib_main, + dst_addr, outer_fib_index, + ipv4_set, 1); + } + else + { + vxlan_gpe_ioam_disable_for_dest + (vm, dst_addr, outer_fib_index, ipv4_set); + } + return rv; +} + + /* *INDENT-OFF* */ +VLIB_CLI_COMMAND (vxlan_gpe_set_ioam_transit_rewrite_cmd, static) = { + .path = "set vxlan-gpe-ioam-transit", + .short_help = "set vxlan-gpe-ioam-transit dst-ip <dst_ip> [outer-fib-index <outer_fib_index>] [disable]", + .function = vxlan_gpe_set_ioam_transit_rewrite_command_fn, +}; +/* *INDENT-ON* */ + +clib_error_t *clear_vxlan_gpe_ioam_rewrite_command_fn + (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { return (vxlan_gpe_ioam_disable (0, 0, 0)); } @@ -387,6 +715,55 @@ VLIB_CLI_COMMAND (vxlan_gpe_clear_ioam_flags_cmd, static) = }; /* *INDENT-ON* */ + +/** + * Function definition to backwalk a FIB node + */ +static fib_node_back_walk_rc_t +vxlan_gpe_ioam_back_walk (fib_node_t * node, fib_node_back_walk_ctx_t * ctx) +{ + vxlan_gpe_refresh_output_feature_on_all_dest (); + return (FIB_NODE_BACK_WALK_CONTINUE); +} + +/** + * Function definition to get a FIB node from its index + */ +static fib_node_t * +vxlan_gpe_ioam_fib_node_get (fib_node_index_t index) +{ + vxlan_gpe_ioam_main_t *hm = &vxlan_gpe_ioam_main; + return (&hm->node); +} + +/** + * Function definition to inform the FIB node that its last lock has gone. + */ +static void +vxlan_gpe_ioam_last_lock_gone (fib_node_t * node) +{ + ASSERT (0); +} + + +/* + * Virtual function table registered by MPLS GRE tunnels + * for participation in the FIB object graph. + */ +const static fib_node_vft_t vxlan_gpe_ioam_vft = { + .fnv_get = vxlan_gpe_ioam_fib_node_get, + .fnv_last_lock = vxlan_gpe_ioam_last_lock_gone, + .fnv_back_walk = vxlan_gpe_ioam_back_walk, +}; + +void +vxlan_gpe_ioam_interface_init (void) +{ + vxlan_gpe_ioam_main_t *hm = &vxlan_gpe_ioam_main; + hm->fib_entry_type = fib_node_register_new_type (&vxlan_gpe_ioam_vft); + return; +} + /* * fd.io coding-style-patch-verification: ON * diff --git a/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_ioam.h b/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_ioam.h index ac824809..3b7d72cf 100644 --- a/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_ioam.h +++ b/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_ioam.h @@ -21,8 +21,27 @@ #include <vnet/ip/ip.h> +typedef struct vxlan_gpe_sw_interface_ +{ + u32 sw_if_index; +} vxlan_gpe_ioam_sw_interface_t; + +typedef struct vxlan_gpe_dest_tunnels_ +{ + ip46_address_t dst_addr; + u32 fp_proto; + u32 sibling_index; + fib_node_index_t fib_entry_index; + u32 outer_fib_index; +} vxlan_gpe_ioam_dest_tunnels_t; + typedef struct vxlan_gpe_ioam_main_ { + /** + * Linkage into the FIB object graph + */ + fib_node_t node; + /* time scale transform. Joy. */ u32 unix_time_0; f64 vlib_time_0; @@ -65,6 +84,18 @@ typedef struct vxlan_gpe_ioam_main_ uword encap_v4_next_node; uword encap_v6_next_node; + /* Software interfaces. */ + vxlan_gpe_ioam_sw_interface_t *sw_interfaces; + + /* hash ip4/ip6 -> list of destinations for doing transit iOAM operation */ + vxlan_gpe_ioam_dest_tunnels_t *dst_tunnels; + uword *dst_by_ip4; + uword *dst_by_ip6; + + /** per sw_if_index, to maintain bitmap */ + u8 *bool_ref_by_sw_if_index; + fib_node_type_t fib_entry_type; + /** State convenience vlib_main_t */ vlib_main_t *vlib_main; /** State convenience vnet_main_t */ @@ -87,6 +118,7 @@ typedef struct vlib_node_registration_t vxlan_gpe_encap_ioam_v4_node; vlib_node_registration_t vxlan_gpe_decap_ioam_v4_node; +vlib_node_registration_t vxlan_gpe_transit_ioam_v4_node; clib_error_t *vxlan_gpe_ioam_enable (int has_trace_option, int has_pot_option, int has_ppc_option); @@ -123,6 +155,15 @@ int vxlan_gpe_ioam_unregister_option (u8 option); int vxlan_gpe_trace_profile_setup (void); int vxlan_gpe_trace_profile_cleanup (void); +extern void vxlan_gpe_ioam_interface_init (void); +int +vxlan_gpe_enable_disable_ioam_for_dest (vlib_main_t * vm, + ip46_address_t dst_addr, + u32 outer_fib_index, + u8 is_ipv4, u8 is_add); +int vxlan_gpe_ioam_disable_for_dest + (vlib_main_t * vm, ip46_address_t dst_addr, u32 outer_fib_index, + u8 ipv4_set); typedef enum { diff --git a/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_ioam_trace.c b/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_ioam_trace.c index a10e85a7..e37b1642 100644 --- a/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_ioam_trace.c +++ b/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_ioam_trace.c @@ -288,7 +288,11 @@ vxlan_gpe_ioam_trace_data_list_handler (vlib_buffer_t * b, if (trace->ioam_trace_type & BIT_TTL_NODEID) { ip4_header_t *ip0 = vlib_buffer_get_current (b); - *elt = clib_host_to_net_u32 (((ip0->ttl - 1) << 24) | + /* The transit case is the only case where the TTL decrement happens + * before iOAM processing. For now, use the use_adj flag as an overload. + * We can probably use a separate flag instead of overloading the use_adj flag. + */ + *elt = clib_host_to_net_u32 (((ip0->ttl - 1 + use_adj) << 24) | profile->node_id); elt++; } diff --git a/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_ioam_util.h b/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_ioam_util.h index 138eba1a..c0ad8d9d 100644 --- a/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_ioam_util.h +++ b/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_ioam_util.h @@ -83,7 +83,6 @@ format_vxlan_gpe_ioam_v4_trace (u8 * s, va_list * args) always_inline void vxlan_gpe_encap_decap_ioam_v4_one_inline (vlib_main_t * vm, vlib_node_runtime_t * node, - vxlan_gpe_main_t * ngm, vlib_buffer_t * b0, u32 * next0, u32 drop_node_val, u8 use_adj) diff --git a/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_test.c b/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_test.c index 500e0569..47253eb6 100644 --- a/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_test.c +++ b/plugins/ioam-plugin/ioam/lib-vxlan-gpe/vxlan_gpe_test.c @@ -64,7 +64,9 @@ vxlan_gpe_test_main_t vxlan_gpe_test_main; _(vxlan_gpe_ioam_enable_reply) \ _(vxlan_gpe_ioam_disable_reply) \ _(vxlan_gpe_ioam_vni_enable_reply) \ -_(vxlan_gpe_ioam_vni_disable_reply) +_(vxlan_gpe_ioam_vni_disable_reply) \ +_(vxlan_gpe_ioam_transit_enable_reply) \ +_(vxlan_gpe_ioam_transit_disable_reply) #define _(n) \ static void vl_api_##n##_t_handler \ @@ -86,11 +88,13 @@ foreach_standard_reply_retval_handler; * Table of message reply handlers, must include boilerplate handlers * we just generated */ -#define foreach_vpe_api_reply_msg \ -_(VXLAN_GPE_IOAM_ENABLE_REPLY, vxlan_gpe_ioam_enable_reply) \ -_(VXLAN_GPE_IOAM_DISABLE_REPLY, vxlan_gpe_ioam_disable_reply) \ -_(VXLAN_GPE_IOAM_VNI_ENABLE_REPLY, vxlan_gpe_ioam_vni_enable_reply) \ -_(VXLAN_GPE_IOAM_VNI_DISABLE_REPLY, vxlan_gpe_ioam_vni_disable_reply) \ +#define foreach_vpe_api_reply_msg \ +_(VXLAN_GPE_IOAM_ENABLE_REPLY, vxlan_gpe_ioam_enable_reply) \ +_(VXLAN_GPE_IOAM_DISABLE_REPLY, vxlan_gpe_ioam_disable_reply) \ +_(VXLAN_GPE_IOAM_VNI_ENABLE_REPLY, vxlan_gpe_ioam_vni_enable_reply) \ +_(VXLAN_GPE_IOAM_VNI_DISABLE_REPLY, vxlan_gpe_ioam_vni_disable_reply) \ +_(VXLAN_GPE_IOAM_TRANSIT_ENABLE_REPLY, vxlan_gpe_ioam_transit_enable_reply) \ +_(VXLAN_GPE_IOAM_TRANSIT_DISABLE_REPLY, vxlan_gpe_ioam_transit_disable_reply) \ /* M: construct, but don't yet send a message */ @@ -379,8 +383,150 @@ api_vxlan_gpe_ioam_vni_disable (vat_main_t * vam) return 0; } +static int +api_vxlan_gpe_ioam_transit_enable (vat_main_t * vam) +{ + vxlan_gpe_test_main_t *sm = &vxlan_gpe_test_main; + + unformat_input_t *line_input = vam->input; + vl_api_vxlan_gpe_ioam_transit_enable_t *mp; + ip4_address_t local4; + ip6_address_t local6; + u8 ipv4_set = 0, ipv6_set = 0; + u8 local_set = 0; + u32 outer_fib_index = 0; + f64 timeout; + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "dst-ip %U", unformat_ip4_address, &local4)) + { + local_set = 1; + ipv4_set = 1; + } + else if (unformat (line_input, "dst-ip %U", + unformat_ip6_address, &local6)) + { + local_set = 1; + ipv6_set = 1; + } + + else if (unformat (line_input, "outer-fib-index %d", &outer_fib_index)) + ; + else + { + errmsg ("parse error '%U'\n", format_unformat_error, line_input); + return -99; + } + } + + if (local_set == 0) + { + errmsg ("destination address not specified\n"); + return -99; + } + if (ipv4_set && ipv6_set) + { + errmsg ("both IPv4 and IPv6 addresses specified"); + return -99; + } + + + M (VXLAN_GPE_IOAM_TRANSIT_ENABLE, vxlan_gpe_ioam_transit_enable); + + + if (ipv6_set) + { + errmsg ("IPv6 currently unsupported"); + return -1; + } + else + { + clib_memcpy (&mp->dst_addr, &local4, sizeof (local4)); + } + + mp->outer_fib_index = htonl (outer_fib_index); + mp->is_ipv6 = ipv6_set; + + S; + W; + + return (0); +} + +static int +api_vxlan_gpe_ioam_transit_disable (vat_main_t * vam) +{ + vxlan_gpe_test_main_t *sm = &vxlan_gpe_test_main; + + unformat_input_t *line_input = vam->input; + vl_api_vxlan_gpe_ioam_transit_disable_t *mp; + ip4_address_t local4; + ip6_address_t local6; + u8 ipv4_set = 0, ipv6_set = 0; + u8 local_set = 0; + u32 outer_fib_index; + f64 timeout; + + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "dst-ip %U", unformat_ip4_address, &local4)) + { + local_set = 1; + ipv4_set = 1; + } + else if (unformat (line_input, "dst-ip %U", + unformat_ip6_address, &local6)) + { + local_set = 1; + ipv6_set = 1; + } + + else if (unformat (line_input, "outer-fib-index %d", &outer_fib_index)) + ; + else + { + errmsg ("parse error '%U'\n", format_unformat_error, line_input); + return -99; + } + } + + if (local_set == 0) + { + errmsg ("destination address not specified\n"); + return -99; + } + if (ipv4_set && ipv6_set) + { + errmsg ("both IPv4 and IPv6 addresses specified"); + return -99; + } + + + M (VXLAN_GPE_IOAM_TRANSIT_DISABLE, vxlan_gpe_ioam_transit_disable); + + + if (ipv6_set) + { + return -1; + } + else + { + clib_memcpy (&mp->dst_addr, &local4, sizeof (local4)); + } + + mp->outer_fib_index = htonl (outer_fib_index); + mp->is_ipv6 = ipv6_set; + + S; + W; + + + return (0); +} + /* * List of messages that the api test plugin sends, * and that the data plane plugin processes @@ -393,6 +539,10 @@ _(vxlan_gpe_ioam_vni_enable, ""\ "local <local_vtep_ip> remote <remote_vtep_ip> vni <vnid>") \ _(vxlan_gpe_ioam_vni_disable, ""\ "local <local_vtep_ip> remote <remote_vtep_ip> vni <vnid>") \ +_(vxlan_gpe_ioam_transit_enable, ""\ + "dst-ip <dst_ip> [outer-fib-index <outer_fib_index>]") \ +_(vxlan_gpe_ioam_transit_disable, ""\ + "dst-ip <dst_ip> [outer-fib-index <outer_fib_index>]") \ void |