From 8a0a0ae60b8dd9da7cf53c895e85dc6daf67143d Mon Sep 17 00:00:00 2001 From: Hongjun Ni Date: Sat, 27 May 2017 20:23:09 +0800 Subject: Rework vxlan-gpe to support FIB 2.0 and bypass mode Change-Id: I0324f945bdb4dd3b19151be6f3ce24a47a000104 Signed-off-by: Hongjun Ni --- src/plugins/ioam/lib-vxlan-gpe/ioam_transit.c | 2 +- src/vnet/fib/fib_node.h | 4 +- src/vnet/mfib/mfib_types.h | 2 + src/vnet/udp/udp.h | 4 +- src/vnet/vxlan-gpe/decap.c | 479 +++++++++++++++++- src/vnet/vxlan-gpe/vxlan_gpe.api | 19 + src/vnet/vxlan-gpe/vxlan_gpe.c | 688 +++++++++++++++++++++++--- src/vnet/vxlan-gpe/vxlan_gpe.h | 38 ++ src/vnet/vxlan-gpe/vxlan_gpe_api.c | 20 + test/test_vxlan_gpe.py | 313 ++++++++++++ test/vpp_papi_provider.py | 35 ++ 11 files changed, 1517 insertions(+), 87 deletions(-) create mode 100644 test/test_vxlan_gpe.py diff --git a/src/plugins/ioam/lib-vxlan-gpe/ioam_transit.c b/src/plugins/ioam/lib-vxlan-gpe/ioam_transit.c index d90cd5e4..60eabc22 100644 --- a/src/plugins/ioam/lib-vxlan-gpe/ioam_transit.c +++ b/src/plugins/ioam/lib-vxlan-gpe/ioam_transit.c @@ -107,7 +107,7 @@ vxlan_gpe_transit_ioam (vlib_main_t * vm, if (PREDICT_FALSE ((ip0->protocol == IP_PROTOCOL_UDP) && (clib_net_to_host_u16 (udp_hdr0->dst_port) == - UDP_DST_PORT_vxlan_gpe))) + UDP_DST_PORT_VXLAN_GPE))) { /* Check the iOAM header */ diff --git a/src/vnet/fib/fib_node.h b/src/vnet/fib/fib_node.h index 496929ad..ec517e15 100644 --- a/src/vnet/fib/fib_node.h +++ b/src/vnet/fib/fib_node.h @@ -42,6 +42,7 @@ typedef enum fib_node_type_t_ { FIB_NODE_TYPE_GRE_TUNNEL, FIB_NODE_TYPE_VXLAN_TUNNEL, FIB_NODE_TYPE_MAP_E, + FIB_NODE_TYPE_VXLAN_GPE_TUNNEL, /** * Marker. New types before this one. leave the test last. */ @@ -65,6 +66,7 @@ typedef enum fib_node_type_t_ { [FIB_NODE_TYPE_GRE_TUNNEL] = "gre-tunnel", \ [FIB_NODE_TYPE_VXLAN_TUNNEL] = "vxlan-tunnel", \ [FIB_NODE_TYPE_MAP_E] = "map-e", \ + [FIB_NODE_TYPE_VXLAN_GPE_TUNNEL] = "vxlan-gpe-tunnel", \ } /** @@ -272,7 +274,7 @@ typedef struct fib_node_vft_t_ { /** * An node in the FIB graph * - * Objects in the FIB form a graph. + * Objects in the FIB form a graph. */ typedef struct fib_node_t_ { #if CLIB_DEBUG > 0 diff --git a/src/vnet/mfib/mfib_types.h b/src/vnet/mfib/mfib_types.h index e278af7f..863fad16 100644 --- a/src/vnet/mfib/mfib_types.h +++ b/src/vnet/mfib/mfib_types.h @@ -168,6 +168,7 @@ typedef enum mfib_source_t_ MFIB_SOURCE_SRv6, MFIB_SOURCE_DEFAULT_ROUTE, MFIB_SOURCE_GTPU, + MFIB_SOURCE_VXLAN_GPE, } mfib_source_t; #define MFIB_SOURCE_NAMES { \ @@ -179,6 +180,7 @@ typedef enum mfib_source_t_ [MFIB_SOURCE_SRv6] = "SRv6", \ [MFIB_SOURCE_DEFAULT_ROUTE] = "Default Route", \ [MFIB_SOURCE_GTPU] = "GTPU", \ + [MFIB_SOURCE_VXLAN_GPE] = "VXLAN-GPE", \ } /** diff --git a/src/vnet/udp/udp.h b/src/vnet/udp/udp.h index 5b63f44d..a9a62c27 100644 --- a/src/vnet/udp/udp.h +++ b/src/vnet/udp/udp.h @@ -89,7 +89,7 @@ _ (4342, lisp_cp) \ _ (4739, ipfix) \ _ (4789, vxlan) \ _ (4789, vxlan6) \ -_ (4790, vxlan_gpe) \ +_ (4790, VXLAN_GPE) \ _ (6633, vpath_3) @@ -101,7 +101,7 @@ _ (3784, bfd6) \ _ (3785, bfd_echo6) \ _ (4341, lisp_gpe6) \ _ (4342, lisp_cp6) \ -_ (4790, vxlan6_gpe) \ +_ (4790, VXLAN6_GPE) \ _ (6633, vpath6_3) typedef enum diff --git a/src/vnet/vxlan-gpe/decap.c b/src/vnet/vxlan-gpe/decap.c index d4fe4231..075b0f51 100644 --- a/src/vnet/vxlan-gpe/decap.c +++ b/src/vnet/vxlan-gpe/decap.c @@ -108,8 +108,8 @@ vxlan_gpe_input (vlib_main_t * vm, u8 is_ip4) { u32 n_left_from, next_index, *from, *to_next; - vxlan_gpe_main_t * ngm = &vxlan_gpe_main; - vnet_main_t * vnm = ngm->vnet_main; + vxlan_gpe_main_t * nngm = &vxlan_gpe_main; + vnet_main_t * vnm = nngm->vnet_main; vnet_interface_main_t * im = &vnm->interface_main; u32 last_tunnel_index = ~0; vxlan4_gpe_tunnel_key_t last_key4; @@ -213,11 +213,11 @@ vxlan_gpe_input (vlib_main_t * vm, { next0 = (iuvn4_0->vxlan.protocol < VXLAN_GPE_PROTOCOL_MAX)? - ngm->decap_next_node_list[iuvn4_0->vxlan.protocol]: \ + nngm->decap_next_node_list[iuvn4_0->vxlan.protocol]: \ VXLAN_GPE_INPUT_NEXT_DROP; next1 = (iuvn4_1->vxlan.protocol < VXLAN_GPE_PROTOCOL_MAX)? - ngm->decap_next_node_list[iuvn4_1->vxlan.protocol]: \ + nngm->decap_next_node_list[iuvn4_1->vxlan.protocol]: \ VXLAN_GPE_INPUT_NEXT_DROP; key4_0.local = iuvn4_0->ip4.dst_address.as_u32; @@ -260,7 +260,7 @@ vxlan_gpe_input (vlib_main_t * vm, if (PREDICT_FALSE((key4_0.as_u64[0] != last_key4.as_u64[0]) || (key4_0.as_u64[1] != last_key4.as_u64[1]))) { - p0 = hash_get_mem(ngm->vxlan4_gpe_tunnel_by_key, &key4_0); + p0 = hash_get_mem(nngm->vxlan4_gpe_tunnel_by_key, &key4_0); if (p0 == 0) { @@ -279,11 +279,11 @@ vxlan_gpe_input (vlib_main_t * vm, { next0 = (iuvn6_0->vxlan.protocol < VXLAN_GPE_PROTOCOL_MAX)? - ngm->decap_next_node_list[iuvn6_0->vxlan.protocol]: \ + nngm->decap_next_node_list[iuvn6_0->vxlan.protocol]: \ VXLAN_GPE_INPUT_NEXT_DROP; next1 = (iuvn6_1->vxlan.protocol < VXLAN_GPE_PROTOCOL_MAX)? - ngm->decap_next_node_list[iuvn6_1->vxlan.protocol]: \ + nngm->decap_next_node_list[iuvn6_1->vxlan.protocol]: \ VXLAN_GPE_INPUT_NEXT_DROP; key6_0.local.as_u64[0] = iuvn6_0->ip6.dst_address.as_u64[0]; @@ -302,7 +302,7 @@ vxlan_gpe_input (vlib_main_t * vm, /* Processing for key6_0 */ if (PREDICT_FALSE(memcmp (&key6_0, &last_key6, sizeof(last_key6)) != 0)) { - p0 = hash_get_mem(ngm->vxlan6_gpe_tunnel_by_key, &key6_0); + p0 = hash_get_mem(nngm->vxlan6_gpe_tunnel_by_key, &key6_0); if (p0 == 0) { @@ -317,7 +317,7 @@ vxlan_gpe_input (vlib_main_t * vm, tunnel_index0 = last_tunnel_index; } - t0 = pool_elt_at_index(ngm->tunnels, tunnel_index0); + t0 = pool_elt_at_index(nngm->tunnels, tunnel_index0); sw_if_index0 = t0->sw_if_index; @@ -366,7 +366,7 @@ vxlan_gpe_input (vlib_main_t * vm, (key4_1.as_u64[0] != last_key4.as_u64[0]) || (key4_1.as_u64[1] != last_key4.as_u64[1]))) { - p1 = hash_get_mem(ngm->vxlan4_gpe_tunnel_by_key, &key4_1); + p1 = hash_get_mem(nngm->vxlan4_gpe_tunnel_by_key, &key4_1); if (p1 == 0) { @@ -386,7 +386,7 @@ vxlan_gpe_input (vlib_main_t * vm, /* Processing for key6_1 */ if (PREDICT_FALSE(memcmp (&key6_1, &last_key6, sizeof(last_key6)) != 0)) { - p1 = hash_get_mem(ngm->vxlan6_gpe_tunnel_by_key, &key6_1); + p1 = hash_get_mem(nngm->vxlan6_gpe_tunnel_by_key, &key6_1); if (p1 == 0) { @@ -401,7 +401,7 @@ vxlan_gpe_input (vlib_main_t * vm, tunnel_index1 = last_tunnel_index; } - t1 = pool_elt_at_index(ngm->tunnels, tunnel_index1); + t1 = pool_elt_at_index(nngm->tunnels, tunnel_index1); sw_if_index1 = t1->sw_if_index; len1 = vlib_buffer_length_in_chain (vm, b1); @@ -502,7 +502,7 @@ vxlan_gpe_input (vlib_main_t * vm, { next0 = (iuvn4_0->vxlan.protocol < VXLAN_GPE_PROTOCOL_MAX)? - ngm->decap_next_node_list[iuvn4_0->vxlan.protocol]: \ + nngm->decap_next_node_list[iuvn4_0->vxlan.protocol]: \ VXLAN_GPE_INPUT_NEXT_DROP; key4_0.local = iuvn4_0->ip4.dst_address.as_u32; @@ -515,7 +515,7 @@ vxlan_gpe_input (vlib_main_t * vm, (key4_0.as_u64[0] != last_key4.as_u64[0]) || (key4_0.as_u64[1] != last_key4.as_u64[1]))) { - p0 = hash_get_mem(ngm->vxlan4_gpe_tunnel_by_key, &key4_0); + p0 = hash_get_mem(nngm->vxlan4_gpe_tunnel_by_key, &key4_0); if (p0 == 0) { @@ -534,7 +534,7 @@ vxlan_gpe_input (vlib_main_t * vm, { next0 = (iuvn6_0->vxlan.protocol < VXLAN_GPE_PROTOCOL_MAX)? - ngm->decap_next_node_list[iuvn6_0->vxlan.protocol]: \ + nngm->decap_next_node_list[iuvn6_0->vxlan.protocol]: \ VXLAN_GPE_INPUT_NEXT_DROP; key6_0.local.as_u64[0] = iuvn6_0->ip6.dst_address.as_u64[0]; @@ -546,7 +546,7 @@ vxlan_gpe_input (vlib_main_t * vm, /* Processing for key6_0 */ if (PREDICT_FALSE(memcmp (&key6_0, &last_key6, sizeof(last_key6)) != 0)) { - p0 = hash_get_mem(ngm->vxlan6_gpe_tunnel_by_key, &key6_0); + p0 = hash_get_mem(nngm->vxlan6_gpe_tunnel_by_key, &key6_0); if (p0 == 0) { @@ -561,7 +561,7 @@ vxlan_gpe_input (vlib_main_t * vm, tunnel_index0 = last_tunnel_index; } - t0 = pool_elt_at_index(ngm->tunnels, tunnel_index0); + t0 = pool_elt_at_index(nngm->tunnels, tunnel_index0); sw_if_index0 = t0->sw_if_index; @@ -731,3 +731,448 @@ VLIB_REGISTER_NODE (vxlan6_gpe_input_node) = { }; VLIB_NODE_FUNCTION_MULTIARCH (vxlan6_gpe_input_node, vxlan6_gpe_input); +typedef enum { + IP_VXLAN_BYPASS_NEXT_DROP, + IP_VXLAN_BYPASS_NEXT_VXLAN, + IP_VXLAN_BYPASS_N_NEXT, +} ip_vxan_bypass_next_t; + +always_inline uword +ip_vxlan_gpe_bypass_inline (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame, + u32 is_ip4) +{ + vxlan_gpe_main_t * ngm = &vxlan_gpe_main; + u32 * from, * to_next, n_left_from, n_left_to_next, next_index; + vlib_node_runtime_t * error_node = vlib_node_get_runtime (vm, ip4_input_node.index); + ip4_address_t addr4; /* last IPv4 address matching a local VTEP address */ + ip6_address_t addr6; /* last IPv6 address matching a local VTEP address */ + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + next_index = node->cached_next_index; + + if (node->flags & VLIB_NODE_FLAG_TRACE) + ip4_forward_next_trace (vm, node, frame, VLIB_TX); + + if (is_ip4) addr4.data_u32 = ~0; + else ip6_address_set_zero (&addr6); + + 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) + { + vlib_buffer_t * b0, * b1; + ip4_header_t * ip40, * ip41; + ip6_header_t * ip60, * ip61; + udp_header_t * udp0, * udp1; + u32 bi0, ip_len0, udp_len0, flags0, next0; + u32 bi1, ip_len1, udp_len1, flags1, next1; + i32 len_diff0, len_diff1; + u8 error0, good_udp0, proto0; + u8 error1, good_udp1, proto1; + + /* 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, LOAD); + vlib_prefetch_buffer_header (p3, LOAD); + + CLIB_PREFETCH (p2->data, 2*CLIB_CACHE_LINE_BYTES, LOAD); + CLIB_PREFETCH (p3->data, 2*CLIB_CACHE_LINE_BYTES, LOAD); + } + + bi0 = to_next[0] = from[0]; + bi1 = to_next[1] = from[1]; + from += 2; + n_left_from -= 2; + to_next += 2; + n_left_to_next -= 2; + + b0 = vlib_get_buffer (vm, bi0); + b1 = vlib_get_buffer (vm, bi1); + if (is_ip4) + { + ip40 = vlib_buffer_get_current (b0); + ip41 = vlib_buffer_get_current (b1); + } + else + { + ip60 = vlib_buffer_get_current (b0); + ip61 = vlib_buffer_get_current (b1); + } + + /* Setup packet for next IP feature */ + vnet_feature_next(vnet_buffer(b0)->sw_if_index[VLIB_RX], &next0, b0); + vnet_feature_next(vnet_buffer(b1)->sw_if_index[VLIB_RX], &next1, b1); + + if (is_ip4) + { + proto0 = ip40->protocol; + proto1 = ip41->protocol; + } + else + { + proto0 = ip60->protocol; + proto1 = ip61->protocol; + } + + /* Process packet 0 */ + if (proto0 != IP_PROTOCOL_UDP) + goto exit0; /* not UDP packet */ + + if (is_ip4) + udp0 = ip4_next_header (ip40); + else + udp0 = ip6_next_header (ip60); + + if (udp0->dst_port != clib_host_to_net_u16 (UDP_DST_PORT_VXLAN_GPE)) + goto exit0; /* not VXLAN packet */ + + /* Validate DIP against VTEPs*/ + if (is_ip4) + { + if (addr4.as_u32 != ip40->dst_address.as_u32) + { + if (!hash_get (ngm->vtep4, ip40->dst_address.as_u32)) + goto exit0; /* no local VTEP for VXLAN packet */ + addr4 = ip40->dst_address; + } + } + else + { + if (!ip6_address_is_equal (&addr6, &ip60->dst_address)) + { + if (!hash_get_mem (ngm->vtep6, &ip60->dst_address)) + goto exit0; /* no local VTEP for VXLAN packet */ + addr6 = ip60->dst_address; + } + } + + flags0 = b0->flags; + good_udp0 = (flags0 & IP_BUFFER_L4_CHECKSUM_CORRECT) != 0; + + /* Don't verify UDP checksum for packets with explicit zero checksum. */ + good_udp0 |= udp0->checksum == 0; + + /* Verify UDP length */ + if (is_ip4) + ip_len0 = clib_net_to_host_u16 (ip40->length); + else + ip_len0 = clib_net_to_host_u16 (ip60->payload_length); + udp_len0 = clib_net_to_host_u16 (udp0->length); + len_diff0 = ip_len0 - udp_len0; + + /* Verify UDP checksum */ + if (PREDICT_FALSE (!good_udp0)) + { + if ((flags0 & IP_BUFFER_L4_CHECKSUM_COMPUTED) == 0) + { + if (is_ip4) + flags0 = ip4_tcp_udp_validate_checksum (vm, b0); + else + flags0 = ip6_tcp_udp_icmp_validate_checksum (vm, b0); + good_udp0 = + (flags0 & IP_BUFFER_L4_CHECKSUM_CORRECT) != 0; + } + } + + if (is_ip4) + { + error0 = good_udp0 ? 0 : IP4_ERROR_UDP_CHECKSUM; + error0 = (len_diff0 >= 0) ? error0 : IP4_ERROR_UDP_LENGTH; + } + else + { + error0 = good_udp0 ? 0 : IP6_ERROR_UDP_CHECKSUM; + error0 = (len_diff0 >= 0) ? error0 : IP6_ERROR_UDP_LENGTH; + } + + next0 = error0 ? + IP_VXLAN_BYPASS_NEXT_DROP : IP_VXLAN_BYPASS_NEXT_VXLAN; + b0->error = error0 ? error_node->errors[error0] : 0; + + /* vxlan_gpe-input node expect current at VXLAN header */ + if (is_ip4) + vlib_buffer_advance (b0, sizeof(ip4_header_t)+sizeof(udp_header_t)); + else + vlib_buffer_advance (b0, sizeof(ip6_header_t)+sizeof(udp_header_t)); + + exit0: + /* Process packet 1 */ + if (proto1 != IP_PROTOCOL_UDP) + goto exit1; /* not UDP packet */ + + if (is_ip4) + udp1 = ip4_next_header (ip41); + else + udp1 = ip6_next_header (ip61); + + if (udp1->dst_port != clib_host_to_net_u16 (UDP_DST_PORT_VXLAN_GPE)) + goto exit1; /* not VXLAN packet */ + + /* Validate DIP against VTEPs*/ + if (is_ip4) + { + if (addr4.as_u32 != ip41->dst_address.as_u32) + { + if (!hash_get (ngm->vtep4, ip41->dst_address.as_u32)) + goto exit1; /* no local VTEP for VXLAN packet */ + addr4 = ip41->dst_address; + } + } + else + { + if (!ip6_address_is_equal (&addr6, &ip61->dst_address)) + { + if (!hash_get_mem (ngm->vtep6, &ip61->dst_address)) + goto exit1; /* no local VTEP for VXLAN packet */ + addr6 = ip61->dst_address; + } + } + + flags1 = b1->flags; + good_udp1 = (flags1 & IP_BUFFER_L4_CHECKSUM_CORRECT) != 0; + + /* Don't verify UDP checksum for packets with explicit zero checksum. */ + good_udp1 |= udp1->checksum == 0; + + /* Verify UDP length */ + if (is_ip4) + ip_len1 = clib_net_to_host_u16 (ip41->length); + else + ip_len1 = clib_net_to_host_u16 (ip61->payload_length); + udp_len1 = clib_net_to_host_u16 (udp1->length); + len_diff1 = ip_len1 - udp_len1; + + /* Verify UDP checksum */ + if (PREDICT_FALSE (!good_udp1)) + { + if ((flags1 & IP_BUFFER_L4_CHECKSUM_COMPUTED) == 0) + { + if (is_ip4) + flags1 = ip4_tcp_udp_validate_checksum (vm, b1); + else + flags1 = ip6_tcp_udp_icmp_validate_checksum (vm, b1); + good_udp1 = + (flags1 & IP_BUFFER_L4_CHECKSUM_CORRECT) != 0; + } + } + + if (is_ip4) + { + error1 = good_udp1 ? 0 : IP4_ERROR_UDP_CHECKSUM; + error1 = (len_diff1 >= 0) ? error1 : IP4_ERROR_UDP_LENGTH; + } + else + { + error1 = good_udp1 ? 0 : IP6_ERROR_UDP_CHECKSUM; + error1 = (len_diff1 >= 0) ? error1 : IP6_ERROR_UDP_LENGTH; + } + + next1 = error1 ? + IP_VXLAN_BYPASS_NEXT_DROP : IP_VXLAN_BYPASS_NEXT_VXLAN; + b1->error = error1 ? error_node->errors[error1] : 0; + + /* vxlan_gpe-input node expect current at VXLAN header */ + if (is_ip4) + vlib_buffer_advance (b1, sizeof(ip4_header_t)+sizeof(udp_header_t)); + else + vlib_buffer_advance (b1, sizeof(ip6_header_t)+sizeof(udp_header_t)); + + exit1: + 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) + { + vlib_buffer_t * b0; + ip4_header_t * ip40; + ip6_header_t * ip60; + udp_header_t * udp0; + u32 bi0, ip_len0, udp_len0, flags0, next0; + i32 len_diff0; + u8 error0, good_udp0, proto0; + + bi0 = to_next[0] = from[0]; + from += 1; + n_left_from -= 1; + to_next += 1; + n_left_to_next -= 1; + + b0 = vlib_get_buffer (vm, bi0); + if (is_ip4) + ip40 = vlib_buffer_get_current (b0); + else + ip60 = vlib_buffer_get_current (b0); + + /* Setup packet for next IP feature */ + vnet_feature_next(vnet_buffer(b0)->sw_if_index[VLIB_RX], &next0, b0); + + if (is_ip4) + proto0 = ip40->protocol; + else + proto0 = ip60->protocol; + + if (proto0 != IP_PROTOCOL_UDP) + goto exit; /* not UDP packet */ + + if (is_ip4) + udp0 = ip4_next_header (ip40); + else + udp0 = ip6_next_header (ip60); + + if (udp0->dst_port != clib_host_to_net_u16 (UDP_DST_PORT_VXLAN_GPE)) + goto exit; /* not VXLAN packet */ + + /* Validate DIP against VTEPs*/ + if (is_ip4) + { + if (addr4.as_u32 != ip40->dst_address.as_u32) + { + if (!hash_get (ngm->vtep4, ip40->dst_address.as_u32)) + goto exit; /* no local VTEP for VXLAN packet */ + addr4 = ip40->dst_address; + } + } + else + { + if (!ip6_address_is_equal (&addr6, &ip60->dst_address)) + { + if (!hash_get_mem (ngm->vtep6, &ip60->dst_address)) + goto exit; /* no local VTEP for VXLAN packet */ + addr6 = ip60->dst_address; + } + } + + flags0 = b0->flags; + good_udp0 = (flags0 & IP_BUFFER_L4_CHECKSUM_CORRECT) != 0; + + /* Don't verify UDP checksum for packets with explicit zero checksum. */ + good_udp0 |= udp0->checksum == 0; + + /* Verify UDP length */ + if (is_ip4) + ip_len0 = clib_net_to_host_u16 (ip40->length); + else + ip_len0 = clib_net_to_host_u16 (ip60->payload_length); + udp_len0 = clib_net_to_host_u16 (udp0->length); + len_diff0 = ip_len0 - udp_len0; + + /* Verify UDP checksum */ + if (PREDICT_FALSE (!good_udp0)) + { + if ((flags0 & IP_BUFFER_L4_CHECKSUM_COMPUTED) == 0) + { + if (is_ip4) + flags0 = ip4_tcp_udp_validate_checksum (vm, b0); + else + flags0 = ip6_tcp_udp_icmp_validate_checksum (vm, b0); + good_udp0 = + (flags0 & IP_BUFFER_L4_CHECKSUM_CORRECT) != 0; + } + } + + if (is_ip4) + { + error0 = good_udp0 ? 0 : IP4_ERROR_UDP_CHECKSUM; + error0 = (len_diff0 >= 0) ? error0 : IP4_ERROR_UDP_LENGTH; + } + else + { + error0 = good_udp0 ? 0 : IP6_ERROR_UDP_CHECKSUM; + error0 = (len_diff0 >= 0) ? error0 : IP6_ERROR_UDP_LENGTH; + } + + next0 = error0 ? + IP_VXLAN_BYPASS_NEXT_DROP : IP_VXLAN_BYPASS_NEXT_VXLAN; + b0->error = error0 ? error_node->errors[error0] : 0; + + /* vxlan_gpe-input node expect current at VXLAN header */ + if (is_ip4) + vlib_buffer_advance (b0, sizeof(ip4_header_t)+sizeof(udp_header_t)); + else + vlib_buffer_advance (b0, sizeof(ip6_header_t)+sizeof(udp_header_t)); + + exit: + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, + to_next, n_left_to_next, + bi0, next0); + } + + vlib_put_next_frame (vm, node, next_index, n_left_to_next); + } + + return frame->n_vectors; +} + +static uword +ip4_vxlan_gpe_bypass (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + return ip_vxlan_gpe_bypass_inline (vm, node, frame, /* is_ip4 */ 1); +} + +VLIB_REGISTER_NODE (ip4_vxlan_gpe_bypass_node) = { + .function = ip4_vxlan_gpe_bypass, + .name = "ip4-vxlan-gpe-bypass", + .vector_size = sizeof (u32), + + .n_next_nodes = IP_VXLAN_BYPASS_N_NEXT, + .next_nodes = { + [IP_VXLAN_BYPASS_NEXT_DROP] = "error-drop", + [IP_VXLAN_BYPASS_NEXT_VXLAN] = "vxlan4-gpe-input", + }, + + .format_buffer = format_ip4_header, + .format_trace = format_ip4_forward_next_trace, +}; + +VLIB_NODE_FUNCTION_MULTIARCH (ip4_vxlan_gpe_bypass_node,ip4_vxlan_gpe_bypass) + +/* Dummy init function to get us linked in. */ +clib_error_t * ip4_vxlan_gpe_bypass_init (vlib_main_t * vm) +{ return 0; } + +VLIB_INIT_FUNCTION (ip4_vxlan_gpe_bypass_init); + +static uword +ip6_vxlan_gpe_bypass (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + return ip_vxlan_gpe_bypass_inline (vm, node, frame, /* is_ip4 */ 0); +} + +VLIB_REGISTER_NODE (ip6_vxlan_gpe_bypass_node) = { + .function = ip6_vxlan_gpe_bypass, + .name = "ip6-vxlan-gpe-bypass", + .vector_size = sizeof (u32), + + .n_next_nodes = IP_VXLAN_BYPASS_N_NEXT, + .next_nodes = { + [IP_VXLAN_BYPASS_NEXT_DROP] = "error-drop", + [IP_VXLAN_BYPASS_NEXT_VXLAN] = "vxlan6-gpe-input", + }, + + .format_buffer = format_ip6_header, + .format_trace = format_ip6_forward_next_trace, +}; + +VLIB_NODE_FUNCTION_MULTIARCH (ip6_vxlan_gpe_bypass_node,ip6_vxlan_gpe_bypass) + +/* Dummy init function to get us linked in. */ +clib_error_t * ip6_vxlan_gpe_bypass_init (vlib_main_t * vm) +{ return 0; } + +VLIB_INIT_FUNCTION (ip6_vxlan_gpe_bypass_init); diff --git a/src/vnet/vxlan-gpe/vxlan_gpe.api b/src/vnet/vxlan-gpe/vxlan_gpe.api index 6c6973f8..41b10316 100644 --- a/src/vnet/vxlan-gpe/vxlan_gpe.api +++ b/src/vnet/vxlan-gpe/vxlan_gpe.api @@ -20,6 +20,7 @@ define vxlan_gpe_add_del_tunnel u8 is_ipv6; u8 local[16]; u8 remote[16]; + u32 mcast_sw_if_index; u32 encap_vrf_id; u32 decap_vrf_id; u8 protocol; @@ -49,11 +50,29 @@ define vxlan_gpe_tunnel_details u8 remote[16]; u32 vni; u8 protocol; + u32 mcast_sw_if_index; u32 encap_vrf_id; u32 decap_vrf_id; + u8 is_ipv6; }; +/** \brief Interface set vxlan-gpe-bypass request + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param sw_if_index - interface used to reach neighbor + @param is_ipv6 - if non-zero, enable ipv6-vxlan-bypass, else ipv4-vxlan-bypass + @param enable - if non-zero enable, else disable +*/ +autoreply define sw_interface_set_vxlan_gpe_bypass +{ + u32 client_index; + u32 context; + u32 sw_if_index; + u8 is_ipv6; + u8 enable; +}; + /* * Local Variables: * eval: (c-set-style "gnu") diff --git a/src/vnet/vxlan-gpe/vxlan_gpe.c b/src/vnet/vxlan-gpe/vxlan_gpe.c index 1e674085..3a92c88f 100644 --- a/src/vnet/vxlan-gpe/vxlan_gpe.c +++ b/src/vnet/vxlan-gpe/vxlan_gpe.c @@ -20,6 +20,27 @@ #include #include #include +#include +#include +#include +#include +#include +#include + +/** + * @file + * @brief VXLAN-GPE. + * + * VXLAN-GPE provides the features needed to allow L2 bridge domains (BDs) + * to span multiple servers. This is done by building an L2 overlay on + * top of an L3 network underlay using VXLAN-GPE tunnels. + * + * This makes it possible for servers to be co-located in the same data + * center or be separated geographically as long as they are reachable + * through the underlay L3 network. + * + * You can refer to this kind of L2 overlay bridge domain as a VXLAN-GPE segment. + */ vxlan_gpe_main_t vxlan_gpe_main; @@ -35,10 +56,10 @@ vxlan_gpe_main_t vxlan_gpe_main; u8 * format_vxlan_gpe_tunnel (u8 * s, va_list * args) { vxlan_gpe_tunnel_t * t = va_arg (*args, vxlan_gpe_tunnel_t *); - vxlan_gpe_main_t * gm = &vxlan_gpe_main; + vxlan_gpe_main_t * ngm = &vxlan_gpe_main; s = format (s, "[%d] local: %U remote: %U ", - t - gm->tunnels, + t - ngm->tunnels, format_ip46_address, &t->local, IP46_TYPE_ANY, format_ip46_address, &t->remote, IP46_TYPE_ANY); @@ -105,10 +126,9 @@ static uword dummy_interface_tx (vlib_main_t * vm, static clib_error_t * vxlan_gpe_interface_admin_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags) { - if (flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP) - vnet_hw_interface_set_flags (vnm, hw_if_index, VNET_HW_INTERFACE_FLAG_LINK_UP); - else - vnet_hw_interface_set_flags (vnm, hw_if_index, 0); + 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); return 0; } @@ -142,13 +162,84 @@ VNET_HW_INTERFACE_CLASS (vxlan_gpe_hw_class) = { .name = "VXLAN_GPE", .format_header = format_vxlan_gpe_header_with_length, .build_rewrite = default_build_rewrite, - .flags = VNET_HW_INTERFACE_CLASS_FLAG_P2P, }; +static void +vxlan_gpe_tunnel_restack_dpo(vxlan_gpe_tunnel_t * t) +{ + dpo_id_t dpo = DPO_INVALID; + u32 encap_index = vxlan_gpe_encap_node.index; + fib_forward_chain_type_t forw_type = ip46_address_is_ip4(&t->remote) ? + FIB_FORW_CHAIN_TYPE_UNICAST_IP4 : FIB_FORW_CHAIN_TYPE_UNICAST_IP6; + + fib_entry_contribute_forwarding (t->fib_entry_index, forw_type, &dpo); + dpo_stack_from_node (encap_index, &t->next_dpo, &dpo); + dpo_reset(&dpo); +} + +static vxlan_gpe_tunnel_t * +vxlan_gpe_tunnel_from_fib_node (fib_node_t *node) +{ +#if (CLIB_DEBUG > 0) + ASSERT(FIB_NODE_TYPE_VXLAN_GPE_TUNNEL == node->fn_type); +#endif + return ((vxlan_gpe_tunnel_t*) (((char*)node) - + STRUCT_OFFSET_OF(vxlan_gpe_tunnel_t, node))); +} + +/** + * Function definition to backwalk a FIB node - + * Here we will restack the new dpo of VXLAN_GPE DIP to encap node. + */ +static fib_node_back_walk_rc_t +vxlan_gpe_tunnel_back_walk (fib_node_t *node, + fib_node_back_walk_ctx_t *ctx) +{ + vxlan_gpe_tunnel_restack_dpo(vxlan_gpe_tunnel_from_fib_node(node)); + return (FIB_NODE_BACK_WALK_CONTINUE); +} + +/** + * Function definition to get a FIB node from its index + */ +static fib_node_t* +vxlan_gpe_tunnel_fib_node_get (fib_node_index_t index) +{ + vxlan_gpe_tunnel_t * t; + vxlan_gpe_main_t * ngm = &vxlan_gpe_main; + + t = pool_elt_at_index(ngm->tunnels, index); + + return (&t->node); +} + +/** + * Function definition to inform the FIB node that its last lock has gone. + */ +static void +vxlan_gpe_tunnel_last_lock_gone (fib_node_t *node) +{ + /* + * The VXLAN_GPE tunnel is a root of the graph. As such + * it never has children and thus is never locked. + */ + ASSERT(0); +} + +/* + * Virtual function table registered by VXLAN_GPE tunnels + * for participation in the FIB object graph. + */ +const static fib_node_vft_t vxlan_gpe_vft = { + .fnv_get = vxlan_gpe_tunnel_fib_node_get, + .fnv_last_lock = vxlan_gpe_tunnel_last_lock_gone, + .fnv_back_walk = vxlan_gpe_tunnel_back_walk, +}; #define foreach_gpe_copy_field \ _(vni) \ -_(protocol) \ +_(protocol) \ +_(mcast_sw_if_index) \ _(encap_fib_index) \ _(decap_fib_index) @@ -173,7 +264,7 @@ _(decap_fib_index) * @return rc * */ -int vxlan4_gpe_rewrite (vxlan_gpe_tunnel_t * t, u32 extension_size, +int vxlan4_gpe_rewrite (vxlan_gpe_tunnel_t * t, u32 extension_size, u8 protocol_override, uword encap_next_node) { u8 *rw = 0; @@ -201,7 +292,7 @@ int vxlan4_gpe_rewrite (vxlan_gpe_tunnel_t * t, u32 extension_size, /* UDP header, randomize src port on something, maybe? */ h0->udp.src_port = clib_host_to_net_u16 (4790); - h0->udp.dst_port = clib_host_to_net_u16 (UDP_DST_PORT_vxlan_gpe); + h0->udp.dst_port = clib_host_to_net_u16 (UDP_DST_PORT_VXLAN_GPE); /* VXLAN header. Are we having fun yet? */ h0->vxlan.flags = VXLAN_GPE_FLAGS_I | VXLAN_GPE_FLAGS_P; @@ -230,7 +321,7 @@ int vxlan4_gpe_rewrite (vxlan_gpe_tunnel_t * t, u32 extension_size, * @return rc * */ -int vxlan6_gpe_rewrite (vxlan_gpe_tunnel_t * t, u32 extension_size, +int vxlan6_gpe_rewrite (vxlan_gpe_tunnel_t * t, u32 extension_size, u8 protocol_override, uword encap_next_node) { u8 *rw = 0; @@ -258,7 +349,7 @@ int vxlan6_gpe_rewrite (vxlan_gpe_tunnel_t * t, u32 extension_size, /* UDP header, randomize src port on something, maybe? */ h0->udp.src_port = clib_host_to_net_u16 (4790); - h0->udp.dst_port = clib_host_to_net_u16 (UDP_DST_PORT_vxlan_gpe); + h0->udp.dst_port = clib_host_to_net_u16 (UDP_DST_PORT_VXLAN_GPE); /* VXLAN header. Are we having fun yet? */ h0->vxlan.flags = VXLAN_GPE_FLAGS_I | VXLAN_GPE_FLAGS_P; @@ -279,6 +370,100 @@ int vxlan6_gpe_rewrite (vxlan_gpe_tunnel_t * t, u32 extension_size, return (0); } +static void +hash_set_key_copy (uword ** h, void * key, uword v) { + size_t ksz = hash_header(*h)->user; + void * copy = clib_mem_alloc (ksz); + clib_memcpy (copy, key, ksz); + hash_set_mem (*h, copy, v); +} + +static void +hash_unset_key_free (uword ** h, void * key) { + hash_pair_t * hp = hash_get_pair_mem (*h, key); + ASSERT (hp); + key = uword_to_pointer (hp->key, void *); + hash_unset_mem (*h, key); + clib_mem_free (key); +} + +static uword +vtep_addr_ref(ip46_address_t *ip) +{ + uword *vtep = ip46_address_is_ip4(ip) ? + hash_get (vxlan_gpe_main.vtep4, ip->ip4.as_u32) : + hash_get_mem (vxlan_gpe_main.vtep6, &ip->ip6); + if (vtep) + return ++(*vtep); + ip46_address_is_ip4(ip) ? + hash_set (vxlan_gpe_main.vtep4, ip->ip4.as_u32, 1) : + hash_set_key_copy (&vxlan_gpe_main.vtep6, &ip->ip6, 1); + return 1; +} + +static uword +vtep_addr_unref(ip46_address_t *ip) +{ + uword *vtep = ip46_address_is_ip4(ip) ? + hash_get (vxlan_gpe_main.vtep4, ip->ip4.as_u32) : + hash_get_mem (vxlan_gpe_main.vtep6, &ip->ip6); + ASSERT(vtep); + if (--(*vtep) != 0) + return *vtep; + ip46_address_is_ip4(ip) ? + hash_unset (vxlan_gpe_main.vtep4, ip->ip4.as_u32) : + hash_unset_key_free (&vxlan_gpe_main.vtep6, &ip->ip6); + return 0; +} + +typedef CLIB_PACKED(union { + struct { + fib_node_index_t mfib_entry_index; + adj_index_t mcast_adj_index; + }; + u64 as_u64; +}) mcast_shared_t; + +static inline mcast_shared_t +mcast_shared_get(ip46_address_t * ip) +{ + ASSERT(ip46_address_is_multicast(ip)); + uword * p = hash_get_mem (vxlan_gpe_main.mcast_shared, ip); + ASSERT(p); + return (mcast_shared_t) { .as_u64 = *p }; +} + +static inline void +mcast_shared_add(ip46_address_t *remote, + fib_node_index_t mfei, + adj_index_t ai) +{ + mcast_shared_t new_ep = { + .mcast_adj_index = ai, + .mfib_entry_index = mfei, + }; + + hash_set_key_copy (&vxlan_gpe_main.mcast_shared, remote, new_ep.as_u64); +} + +static inline void +mcast_shared_remove(ip46_address_t *remote) +{ + mcast_shared_t ep = mcast_shared_get(remote); + + adj_unlock(ep.mcast_adj_index); + mfib_table_entry_delete_index(ep.mfib_entry_index, + MFIB_SOURCE_VXLAN_GPE); + + hash_unset_key_free (&vxlan_gpe_main.mcast_shared, remote); +} + +static inline fib_protocol_t +fib_ip_proto(bool is_ip6) +{ + return (is_ip6) ? FIB_PROTOCOL_IP6 : FIB_PROTOCOL_IP4; +} + /** * @brief Add or Del a VXLAN GPE tunnel * @@ -291,9 +476,9 @@ int vxlan6_gpe_rewrite (vxlan_gpe_tunnel_t * t, u32 extension_size, int vnet_vxlan_gpe_add_del_tunnel (vnet_vxlan_gpe_add_del_tunnel_args_t *a, u32 * sw_if_indexp) { - vxlan_gpe_main_t * gm = &vxlan_gpe_main; + vxlan_gpe_main_t * ngm = &vxlan_gpe_main; vxlan_gpe_tunnel_t *t = 0; - vnet_main_t * vnm = gm->vnet_main; + vnet_main_t * vnm = ngm->vnet_main; vnet_hw_interface_t * hi; uword * p; u32 hw_if_index = ~0; @@ -301,16 +486,16 @@ int vnet_vxlan_gpe_add_del_tunnel int rv; vxlan4_gpe_tunnel_key_t key4, *key4_copy; vxlan6_gpe_tunnel_key_t key6, *key6_copy; - hash_pair_t *hp; + u32 is_ip6 = a->is_ip6; - if (!a->is_ip6) + if (!is_ip6) { key4.local = a->local.ip4.as_u32; key4.remote = a->remote.ip4.as_u32; key4.vni = clib_host_to_net_u32 (a->vni << 8); key4.pad = 0; - p = hash_get_mem(gm->vxlan4_gpe_tunnel_by_key, &key4); + p = hash_get_mem(ngm->vxlan4_gpe_tunnel_by_key, &key4); } else { @@ -320,16 +505,18 @@ int vnet_vxlan_gpe_add_del_tunnel key6.remote.as_u64[1] = a->remote.ip6.as_u64[1]; key6.vni = clib_host_to_net_u32 (a->vni << 8); - p = hash_get_mem(gm->vxlan6_gpe_tunnel_by_key, &key6); + p = hash_get_mem(ngm->vxlan6_gpe_tunnel_by_key, &key6); } if (a->is_add) { + l2input_main_t * l2im = &l2input_main; + /* adding a tunnel: tunnel must not already exist */ if (p) - return VNET_API_ERROR_INVALID_VALUE; + return VNET_API_ERROR_TUNNEL_EXIST; - pool_get_aligned (gm->tunnels, t, CLIB_CACHE_LINE_BYTES); + pool_get_aligned (ngm->tunnels, t, CLIB_CACHE_LINE_BYTES); memset (t, 0, sizeof (*t)); /* copy from arg structure */ @@ -349,51 +536,167 @@ int vnet_vxlan_gpe_add_del_tunnel if (rv) { - pool_put (gm->tunnels, t); + pool_put (ngm->tunnels, t); return rv; } - if (!a->is_ip6) + if (!is_ip6) { key4_copy = clib_mem_alloc (sizeof (*key4_copy)); clib_memcpy (key4_copy, &key4, sizeof (*key4_copy)); - hash_set_mem (gm->vxlan4_gpe_tunnel_by_key, key4_copy, - t - gm->tunnels); + hash_set_mem (ngm->vxlan4_gpe_tunnel_by_key, key4_copy, + t - ngm->tunnels); } else { key6_copy = clib_mem_alloc (sizeof (*key6_copy)); clib_memcpy (key6_copy, &key6, sizeof (*key6_copy)); - hash_set_mem (gm->vxlan6_gpe_tunnel_by_key, key6_copy, - t - gm->tunnels); + hash_set_mem (ngm->vxlan6_gpe_tunnel_by_key, key6_copy, + t - ngm->tunnels); } - if (vec_len (gm->free_vxlan_gpe_tunnel_hw_if_indices) > 0) + if (vec_len (ngm->free_vxlan_gpe_tunnel_hw_if_indices) > 0) { - hw_if_index = gm->free_vxlan_gpe_tunnel_hw_if_indices - [vec_len (gm->free_vxlan_gpe_tunnel_hw_if_indices)-1]; - _vec_len (gm->free_vxlan_gpe_tunnel_hw_if_indices) -= 1; + vnet_interface_main_t * im = &vnm->interface_main; + hw_if_index = ngm->free_vxlan_gpe_tunnel_hw_if_indices + [vec_len (ngm->free_vxlan_gpe_tunnel_hw_if_indices)-1]; + _vec_len (ngm->free_vxlan_gpe_tunnel_hw_if_indices) -= 1; hi = vnet_get_hw_interface (vnm, hw_if_index); - hi->dev_instance = t - gm->tunnels; + hi->dev_instance = t - ngm->tunnels; hi->hw_instance = hi->dev_instance; + /* clear old stats of freed tunnel before reuse */ + sw_if_index = hi->sw_if_index; + vnet_interface_counter_lock(im); + vlib_zero_combined_counter + (&im->combined_sw_if_counters[VNET_INTERFACE_COUNTER_TX], sw_if_index); + vlib_zero_combined_counter + (&im->combined_sw_if_counters[VNET_INTERFACE_COUNTER_RX], sw_if_index); + vlib_zero_simple_counter + (&im->sw_if_counters[VNET_INTERFACE_COUNTER_DROP], sw_if_index); + vnet_interface_counter_unlock(im); } else { hw_if_index = vnet_register_interface - (vnm, vxlan_gpe_device_class.index, t - gm->tunnels, - vxlan_gpe_hw_class.index, t - gm->tunnels); + (vnm, vxlan_gpe_device_class.index, t - ngm->tunnels, + vxlan_gpe_hw_class.index, t - ngm->tunnels); hi = vnet_get_hw_interface (vnm, hw_if_index); hi->output_node_index = vxlan_gpe_encap_node.index; } t->hw_if_index = hw_if_index; t->sw_if_index = sw_if_index = hi->sw_if_index; - vec_validate_init_empty (gm->tunnel_index_by_sw_if_index, sw_if_index, ~0); - gm->tunnel_index_by_sw_if_index[sw_if_index] = t - gm->tunnels; + vec_validate_init_empty (ngm->tunnel_index_by_sw_if_index, sw_if_index, ~0); + ngm->tunnel_index_by_sw_if_index[sw_if_index] = t - ngm->tunnels; + + /* setup l2 input config with l2 feature and bd 0 to drop packet */ + vec_validate (l2im->configs, sw_if_index); + l2im->configs[sw_if_index].feature_bitmap = L2INPUT_FEAT_DROP; + l2im->configs[sw_if_index].bd_index = 0; + vnet_sw_interface_t * si = vnet_get_sw_interface (vnm, sw_if_index); + si->flags &= ~VNET_SW_INTERFACE_FLAG_HIDDEN; vnet_sw_interface_set_flags (vnm, hi->sw_if_index, VNET_SW_INTERFACE_FLAG_ADMIN_UP); + fib_node_init(&t->node, FIB_NODE_TYPE_VXLAN_GPE_TUNNEL); + fib_prefix_t tun_remote_pfx; + u32 encap_index = vxlan_gpe_encap_node.index; + vnet_flood_class_t flood_class = VNET_FLOOD_CLASS_TUNNEL_NORMAL; + + fib_prefix_from_ip46_addr(&t->remote, &tun_remote_pfx); + if (!ip46_address_is_multicast(&t->remote)) + { + /* Unicast tunnel - + * source the FIB entry for the tunnel's destination + * and become a child thereof. The tunnel will then get poked + * when the forwarding for the entry updates, and the tunnel can + * re-stack accordingly + */ + vtep_addr_ref(&t->local); + t->fib_entry_index = fib_table_entry_special_add + (t->encap_fib_index, &tun_remote_pfx, FIB_SOURCE_RR, + FIB_ENTRY_FLAG_NONE); + t->sibling_index = fib_entry_child_add + (t->fib_entry_index, FIB_NODE_TYPE_VXLAN_GPE_TUNNEL, t - ngm->tunnels); + vxlan_gpe_tunnel_restack_dpo(t); + } + else + { + /* Multicast tunnel - + * as the same mcast group can be used for mutiple mcast tunnels + * with different VNIs, create the output fib adjecency only if + * it does not already exist + */ + fib_protocol_t fp = fib_ip_proto(is_ip6); + + if (vtep_addr_ref(&t->remote) == 1) + { + fib_node_index_t mfei; + adj_index_t ai; + fib_route_path_t path = { + .frp_proto = fp, + .frp_addr = zero_addr, + .frp_sw_if_index = 0xffffffff, + .frp_fib_index = ~0, + .frp_weight = 0, + .frp_flags = FIB_ROUTE_PATH_LOCAL, + }; + const mfib_prefix_t mpfx = { + .fp_proto = fp, + .fp_len = (is_ip6 ? 128 : 32), + .fp_grp_addr = tun_remote_pfx.fp_addr, + }; + + /* + * Setup the (*,G) to receive traffic on the mcast group + * - the forwarding interface is for-us + * - the accepting interface is that from the API + */ + mfib_table_entry_path_update(t->encap_fib_index, + &mpfx, + MFIB_SOURCE_VXLAN_GPE, + &path, + MFIB_ITF_FLAG_FORWARD); + + path.frp_sw_if_index = a->mcast_sw_if_index; + path.frp_flags = FIB_ROUTE_PATH_FLAG_NONE; + mfei = mfib_table_entry_path_update(t->encap_fib_index, + &mpfx, + MFIB_SOURCE_VXLAN_GPE, + &path, + MFIB_ITF_FLAG_ACCEPT); + + /* + * Create the mcast adjacency to send traffic to the group + */ + ai = adj_mcast_add_or_lock(fp, + fib_proto_to_link(fp), + a->mcast_sw_if_index); + + /* + * create a new end-point + */ + mcast_shared_add(&t->remote, mfei, ai); + } + + dpo_id_t dpo = DPO_INVALID; + mcast_shared_t ep = mcast_shared_get(&t->remote); + + /* Stack shared mcast remote mac addr rewrite on encap */ + dpo_set (&dpo, DPO_ADJACENCY_MCAST, + fib_proto_to_dpo(fp), + ep.mcast_adj_index); + + dpo_stack_from_node (encap_index, &t->next_dpo, &dpo); + dpo_reset (&dpo); + flood_class = VNET_FLOOD_CLASS_TUNNEL_MASTER; + } + + /* Set vxlan tunnel output node */ + hi->output_node_index = encap_index; + + vnet_get_sw_interface (vnet_get_main(), sw_if_index)->flood_class = flood_class; } else { @@ -401,30 +704,36 @@ int vnet_vxlan_gpe_add_del_tunnel if (!p) return VNET_API_ERROR_NO_SUCH_ENTRY; - t = pool_elt_at_index (gm->tunnels, p[0]); + t = pool_elt_at_index (ngm->tunnels, p[0]); + sw_if_index = t->sw_if_index; vnet_sw_interface_set_flags (vnm, t->sw_if_index, 0 /* down */); - vec_add1 (gm->free_vxlan_gpe_tunnel_hw_if_indices, t->hw_if_index); + vnet_sw_interface_t * si = vnet_get_sw_interface (vnm, t->sw_if_index); + si->flags |= VNET_SW_INTERFACE_FLAG_HIDDEN; + set_int_l2_mode(ngm->vlib_main, vnm, MODE_L3, t->sw_if_index, 0, 0, 0, 0); + vec_add1 (ngm->free_vxlan_gpe_tunnel_hw_if_indices, t->hw_if_index); - gm->tunnel_index_by_sw_if_index[t->sw_if_index] = ~0; + ngm->tunnel_index_by_sw_if_index[t->sw_if_index] = ~0; - if (!a->is_ip6) - { - hp = hash_get_pair (gm->vxlan4_gpe_tunnel_by_key, &key4); - key4_copy = (void *)(hp->key); - hash_unset_mem (gm->vxlan4_gpe_tunnel_by_key, &key4); - clib_mem_free (key4_copy); - } + if (!is_ip6) + hash_unset (ngm->vxlan4_gpe_tunnel_by_key, key4.as_u64); else - { - hp = hash_get_pair (gm->vxlan6_gpe_tunnel_by_key, &key6); - key6_copy = (void *)(hp->key); - hash_unset_mem (gm->vxlan4_gpe_tunnel_by_key, &key6); - clib_mem_free (key6_copy); - } + hash_unset_key_free (&ngm->vxlan6_gpe_tunnel_by_key, &key6); + + if (!ip46_address_is_multicast(&t->remote)) + { + vtep_addr_unref(&t->local); + fib_entry_child_remove(t->fib_entry_index, t->sibling_index); + fib_table_entry_delete_index(t->fib_entry_index, FIB_SOURCE_RR); + } + else if (vtep_addr_unref(&t->remote) == 0) + { + mcast_shared_remove(&t->remote); + } + fib_node_deinit(&t->node); vec_free (t->rewrite); - pool_put (gm->tunnels, t); + pool_put (ngm->tunnels, t); } if (sw_if_indexp) @@ -443,8 +752,10 @@ vxlan_gpe_add_del_tunnel_command_fn (vlib_main_t * vm, ip46_address_t local, remote; u8 local_set = 0; u8 remote_set = 0; + u8 grp_set = 0; u8 ipv4_set = 0; u8 ipv6_set = 0; + u32 mcast_sw_if_index = ~0; u32 encap_fib_index = 0; u32 decap_fib_index = 0; u8 protocol = VXLAN_GPE_PROTOCOL_IP4; @@ -487,6 +798,22 @@ vxlan_gpe_add_del_tunnel_command_fn (vlib_main_t * vm, remote_set = 1; ipv6_set = 1; } + else if (unformat (line_input, "group %U %U", + unformat_ip4_address, &remote.ip4, + unformat_vnet_sw_interface, + vnet_get_main(), &mcast_sw_if_index)) + { + grp_set = remote_set = 1; + ipv4_set = 1; + } + else if (unformat (line_input, "group %U %U", + unformat_ip6_address, &remote.ip6, + unformat_vnet_sw_interface, + vnet_get_main(), &mcast_sw_if_index)) + { + grp_set = remote_set = 1; + ipv6_set = 1; + } else if (unformat (line_input, "encap-vrf-id %d", &tmp)) { if (ipv6_set) @@ -543,6 +870,23 @@ vxlan_gpe_add_del_tunnel_command_fn (vlib_main_t * vm, goto done; } + if (grp_set && !ip46_address_is_multicast(&remote)) + { + error = clib_error_return (0, "tunnel group address not multicast"); + goto done; + } + + if (grp_set == 0 && ip46_address_is_multicast(&remote)) + { + error = clib_error_return (0, "remote address must be unicast"); + goto done; + } + + if (grp_set && mcast_sw_if_index == ~0) + { + error = clib_error_return (0, "tunnel nonexistent multicast device"); + goto done; + } if (ipv4_set && ipv6_set) { error = clib_error_return (0, "both IPv4 and IPv6 addresses specified"); @@ -552,7 +896,7 @@ vxlan_gpe_add_del_tunnel_command_fn (vlib_main_t * vm, if ((ipv4_set && memcmp(&local.ip4, &remote.ip4, sizeof(local.ip4)) == 0) || (ipv6_set && memcmp(&local.ip6, &remote.ip6, sizeof(local.ip6)) == 0)) { - error = clib_error_return (0, "src and dst addresses are identical"); + error = clib_error_return (0, "src and remote addresses are identical"); goto done; } @@ -604,15 +948,36 @@ done: return error; } +/*? + * Add or delete a VXLAN-GPE Tunnel. + * + * VXLAN-GPE provides the features needed to allow L2 bridge domains (BDs) + * to span multiple servers. This is done by building an L2 overlay on + * top of an L3 network underlay using VXLAN-GPE tunnels. + * + * This makes it possible for servers to be co-located in the same data + * center or be separated geographically as long as they are reachable + * through the underlay L3 network. + * + * You can refer to this kind of L2 overlay bridge domain as a VXLAN-GPE sengment. + * + * @cliexpar + * Example of how to create a VXLAN-GPE Tunnel: + * @cliexcmd{create vxlan-gpe tunnel local 10.0.3.1 local 10.0.3.3 vni 13 encap-vrf-id 7} + * Example of how to delete a VXLAN Tunnel: + * @cliexcmd{create vxlan tunnel src 10.0.3.1 remote 10.0.3.3 vni 13 del} + ?*/ +/* *INDENT-OFF* */ VLIB_CLI_COMMAND (create_vxlan_gpe_tunnel_command, static) = { .path = "create vxlan-gpe tunnel", .short_help = - "create vxlan-gpe tunnel local remote " + "create vxlan-gpe tunnel local " + " {remote |group }" " vni [next-ip4][next-ip6][next-ethernet][next-nsh]" - " [encap-vrf-id ] [decap-vrf-id ]" - " [del]\n", + " [encap-vrf-id ] [decap-vrf-id ] [del]\n", .function = vxlan_gpe_add_del_tunnel_command_fn, }; +/* *INDENT-ON* */ /** * @brief CLI function for showing VXLAN GPE tunnels @@ -629,13 +994,13 @@ show_vxlan_gpe_tunnel_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { - vxlan_gpe_main_t * gm = &vxlan_gpe_main; + vxlan_gpe_main_t * ngm = &vxlan_gpe_main; vxlan_gpe_tunnel_t * t; - if (pool_elts (gm->tunnels) == 0) + if (pool_elts (ngm->tunnels) == 0) vlib_cli_output (vm, "No vxlan-gpe tunnels configured."); - pool_foreach (t, gm->tunnels, + pool_foreach (t, ngm->tunnels, ({ vlib_cli_output (vm, "%U", format_vxlan_gpe_tunnel, t); })); @@ -643,10 +1008,194 @@ show_vxlan_gpe_tunnel_command_fn (vlib_main_t * vm, return 0; } +/*? + * Display all the VXLAN-GPE Tunnel entries. + * + * @cliexpar + * Example of how to display the VXLAN-GPE Tunnel entries: + * @cliexstart{show vxlan-gpe tunnel} + * [0] local 10.0.3.1 remote 10.0.3.3 vni 13 encap_fib_index 0 sw_if_index 5 decap_next l2 + * @cliexend + ?*/ +/* *INDENT-OFF* */ VLIB_CLI_COMMAND (show_vxlan_gpe_tunnel_command, static) = { .path = "show vxlan-gpe", .function = show_vxlan_gpe_tunnel_command_fn, }; +/* *INDENT-ON* */ + +void vnet_int_vxlan_gpe_bypass_mode (u32 sw_if_index, + u8 is_ip6, + u8 is_enable) +{ + if (is_ip6) + vnet_feature_enable_disable ("ip6-unicast", "ip6-vxlan-gpe-bypass", + sw_if_index, is_enable, 0, 0); + else + vnet_feature_enable_disable ("ip4-unicast", "ip4-vxlan-gpe-bypass", + sw_if_index, is_enable, 0, 0); +} + + +static clib_error_t * +set_ip_vxlan_gpe_bypass (u32 is_ip6, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + unformat_input_t _line_input, * line_input = &_line_input; + vnet_main_t * vnm = vnet_get_main(); + clib_error_t * error = 0; + u32 sw_if_index, is_enable; + + sw_if_index = ~0; + is_enable = 1; + + if (! unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat_user (line_input, unformat_vnet_sw_interface, vnm, &sw_if_index)) + ; + else if (unformat (line_input, "del")) + is_enable = 0; + else + { + error = unformat_parse_error (line_input); + goto done; + } + } + + if (~0 == sw_if_index) + { + error = clib_error_return (0, "unknown interface `%U'", + format_unformat_error, line_input); + goto done; + } + + vnet_int_vxlan_gpe_bypass_mode (sw_if_index, is_ip6, is_enable); + + done: + unformat_free (line_input); + + return error; +} + +static clib_error_t * +set_ip4_vxlan_gpe_bypass (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + return set_ip_vxlan_gpe_bypass (0, input, cmd); +} + +/*? + * This command adds the 'ip4-vxlan-gpe-bypass' graph node for a given interface. + * By adding the IPv4 vxlan-gpe-bypass graph node to an interface, the node checks + * for and validate input vxlan_gpe packet and bypass ip4-lookup, ip4-local, + * ip4-udp-lookup nodes to speedup vxlan_gpe packet forwarding. This node will + * cause extra overhead to for non-vxlan_gpe packets which is kept at a minimum. + * + * @cliexpar + * @parblock + * Example of graph node before ip4-vxlan-gpe-bypass is enabled: + * @cliexstart{show vlib graph ip4-vxlan-gpe-bypass} + * Name Next Previous + * ip4-vxlan-gpe-bypass error-drop [0] + * vxlan4-gpe-input [1] + * ip4-lookup [2] + * @cliexend + * + * Example of how to enable ip4-vxlan-gpe-bypass on an interface: + * @cliexcmd{set interface ip vxlan-gpe-bypass GigabitEthernet2/0/0} + * + * Example of graph node after ip4-vxlan-gpe-bypass is enabled: + * @cliexstart{show vlib graph ip4-vxlan-gpe-bypass} + * Name Next Previous + * ip4-vxlan-gpe-bypass error-drop [0] ip4-input + * vxlan4-gpe-input [1] ip4-input-no-checksum + * ip4-lookup [2] + * @cliexend + * + * Example of how to display the feature enabed on an interface: + * @cliexstart{show ip interface features GigabitEthernet2/0/0} + * IP feature paths configured on GigabitEthernet2/0/0... + * ... + * ipv4 unicast: + * ip4-vxlan-gpe-bypass + * ip4-lookup + * ... + * @cliexend + * + * Example of how to disable ip4-vxlan-gpe-bypass on an interface: + * @cliexcmd{set interface ip vxlan-gpe-bypass GigabitEthernet2/0/0 del} + * @endparblock +?*/ +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (set_interface_ip_vxlan_gpe_bypass_command, static) = { + .path = "set interface ip vxlan-gpe-bypass", + .function = set_ip4_vxlan_gpe_bypass, + .short_help = "set interface ip vxlan-gpe-bypass [del]", +}; +/* *INDENT-ON* */ + +static clib_error_t * +set_ip6_vxlan_gpe_bypass (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + return set_ip_vxlan_gpe_bypass (1, input, cmd); +} + +/*? + * This command adds the 'ip6-vxlan-gpe-bypass' graph node for a given interface. + * By adding the IPv6 vxlan-gpe-bypass graph node to an interface, the node checks + * for and validate input vxlan_gpe packet and bypass ip6-lookup, ip6-local, + * ip6-udp-lookup nodes to speedup vxlan_gpe packet forwarding. This node will + * cause extra overhead to for non-vxlan_gpe packets which is kept at a minimum. + * + * @cliexpar + * @parblock + * Example of graph node before ip6-vxlan-gpe-bypass is enabled: + * @cliexstart{show vlib graph ip6-vxlan-gpe-bypass} + * Name Next Previous + * ip6-vxlan-gpe-bypass error-drop [0] + * vxlan6-gpe-input [1] + * ip6-lookup [2] + * @cliexend + * + * Example of how to enable ip6-vxlan-gpe-bypass on an interface: + * @cliexcmd{set interface ip6 vxlan-gpe-bypass GigabitEthernet2/0/0} + * + * Example of graph node after ip6-vxlan-gpe-bypass is enabled: + * @cliexstart{show vlib graph ip6-vxlan-gpe-bypass} + * Name Next Previous + * ip6-vxlan-gpe-bypass error-drop [0] ip6-input + * vxlan6-gpe-input [1] ip4-input-no-checksum + * ip6-lookup [2] + * @cliexend + * + * Example of how to display the feature enabed on an interface: + * @cliexstart{show ip interface features GigabitEthernet2/0/0} + * IP feature paths configured on GigabitEthernet2/0/0... + * ... + * ipv6 unicast: + * ip6-vxlan-gpe-bypass + * ip6-lookup + * ... + * @cliexend + * + * Example of how to disable ip6-vxlan-gpe-bypass on an interface: + * @cliexcmd{set interface ip6 vxlan-gpe-bypass GigabitEthernet2/0/0 del} + * @endparblock +?*/ +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (set_interface_ip6_vxlan_gpe_bypass_command, static) = { + .path = "set interface ip6 vxlan-gpe-bypass", + .function = set_ip6_vxlan_gpe_bypass, + .short_help = "set interface ip vxlan-gpe-bypass [del]", +}; +/* *INDENT-ON* */ /** * @brief Feature init function for VXLAN GPE @@ -658,21 +1207,25 @@ VLIB_CLI_COMMAND (show_vxlan_gpe_tunnel_command, static) = { */ clib_error_t *vxlan_gpe_init (vlib_main_t *vm) { - vxlan_gpe_main_t *gm = &vxlan_gpe_main; + vxlan_gpe_main_t *ngm = &vxlan_gpe_main; - gm->vnet_main = vnet_get_main(); - gm->vlib_main = vm; + ngm->vnet_main = vnet_get_main(); + ngm->vlib_main = vm; - gm->vxlan4_gpe_tunnel_by_key + ngm->vxlan4_gpe_tunnel_by_key = hash_create_mem (0, sizeof(vxlan4_gpe_tunnel_key_t), sizeof (uword)); - gm->vxlan6_gpe_tunnel_by_key + ngm->vxlan6_gpe_tunnel_by_key = hash_create_mem (0, sizeof(vxlan6_gpe_tunnel_key_t), sizeof (uword)); - udp_register_dst_port (vm, UDP_DST_PORT_vxlan_gpe, + ngm->mcast_shared = hash_create_mem(0, + sizeof(ip46_address_t), + sizeof(mcast_shared_t)); + + udp_register_dst_port (vm, UDP_DST_PORT_VXLAN_GPE, vxlan4_gpe_input_node.index, 1 /* is_ip4 */); - udp_register_dst_port (vm, UDP_DST_PORT_vxlan6_gpe, + udp_register_dst_port (vm, UDP_DST_PORT_VXLAN6_GPE, vxlan6_gpe_input_node.index, 0 /* is_ip4 */); /* Register the list of standard decap protocols supported */ @@ -682,6 +1235,9 @@ clib_error_t *vxlan_gpe_init (vlib_main_t *vm) VXLAN_GPE_INPUT_NEXT_IP6_INPUT); vxlan_gpe_register_decap_protocol (VXLAN_GPE_PROTOCOL_ETHERNET, VXLAN_GPE_INPUT_NEXT_ETHERNET_INPUT); + + fib_node_register_type(FIB_NODE_TYPE_VXLAN_GPE_TUNNEL, &vxlan_gpe_vft); + return 0; } diff --git a/src/vnet/vxlan-gpe/vxlan_gpe.h b/src/vnet/vxlan-gpe/vxlan_gpe.h index e768d230..c348b5d5 100644 --- a/src/vnet/vxlan-gpe/vxlan_gpe.h +++ b/src/vnet/vxlan-gpe/vxlan_gpe.h @@ -25,11 +25,15 @@ #include #include #include +#include +#include #include #include #include #include #include +#include +#include /** * @brief VXLAN GPE header struct @@ -94,11 +98,16 @@ typedef struct { /** encapsulated protocol */ u8 protocol; + /* FIB DPO for IP forwarding of VXLAN-GPE encap packet */ + dpo_id_t next_dpo; /** tunnel local address */ ip46_address_t local; /** tunnel remote address */ ip46_address_t remote; + /* mcast packet output intfc index (used only if dst is mcast) */ + u32 mcast_sw_if_index; + /** FIB indices - tunnel partner lookup here */ u32 encap_fib_index; /** FIB indices - inner IP packet lookup here */ @@ -120,6 +129,27 @@ typedef struct { /** Next node after VxLAN-GPE encap */ uword encap_next_node; + + /** + * Linkage into the FIB object graph + */ + fib_node_t node; + + /* + * The FIB entry for (depending on VXLAN-GPE tunnel is unicast or mcast) + * sending unicast VXLAN-GPE encap packets or receiving mcast VXLAN-GPE packets + */ + fib_node_index_t fib_entry_index; + adj_index_t mcast_adj_index; + + /** + * The tunnel is a child of the FIB entry for its desintion. This is + * so it receives updates when the forwarding information for that entry + * changes. + * The tunnels sibling index on the FIB entry's dependency list. + */ + u32 sibling_index; + } vxlan_gpe_tunnel_t; /** Flags for vxlan_gpe_tunnel_t */ @@ -158,6 +188,12 @@ typedef struct { /** lookup IPv6 VXLAN GPE tunnel by key */ uword * vxlan6_gpe_tunnel_by_key; + /* local VTEP IPs ref count used by vxlan-bypass node to check if + received VXLAN packet DIP matches any local VTEP address */ + uword * vtep4; /* local ip4 VTEPs keyed on their ip4 addr */ + uword * vtep6; /* local ip6 VTEPs keyed on their ip6 addr */ + /* mcast shared info */ + uword * mcast_shared; /* keyed on mcast ip46 addr */ /** Free vlib hw_if_indices */ u32 * free_vxlan_gpe_tunnel_hw_if_indices; @@ -187,6 +223,7 @@ typedef struct { u8 is_ip6; ip46_address_t local, remote; u8 protocol; + u32 mcast_sw_if_index; u32 encap_fib_index; u32 decap_fib_index; u32 vni; @@ -217,5 +254,6 @@ void vxlan_gpe_unregister_decap_protocol (u8 protocol_id, uword next_node_index) void vxlan_gpe_register_decap_protocol (u8 protocol_id, uword next_node_index); +void vnet_int_vxlan_gpe_bypass_mode (u32 sw_if_index, u8 is_ip6, u8 is_enable); #endif /* included_vnet_vxlan_gpe_h */ diff --git a/src/vnet/vxlan-gpe/vxlan_gpe_api.c b/src/vnet/vxlan-gpe/vxlan_gpe_api.c index 0215054d..3675fc55 100644 --- a/src/vnet/vxlan-gpe/vxlan_gpe_api.c +++ b/src/vnet/vxlan-gpe/vxlan_gpe_api.c @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -44,9 +45,26 @@ #include #define foreach_vpe_api_msg \ +_(SW_INTERFACE_SET_VXLAN_GPE_BYPASS, sw_interface_set_vxlan_gpe_bypass) \ _(VXLAN_GPE_ADD_DEL_TUNNEL, vxlan_gpe_add_del_tunnel) \ _(VXLAN_GPE_TUNNEL_DUMP, vxlan_gpe_tunnel_dump) \ +static void + vl_api_sw_interface_set_vxlan_gpe_bypass_t_handler + (vl_api_sw_interface_set_vxlan_gpe_bypass_t * mp) +{ + vl_api_sw_interface_set_vxlan_gpe_bypass_reply_t *rmp; + int rv = 0; + u32 sw_if_index = ntohl (mp->sw_if_index); + + VALIDATE_SW_IF_INDEX (mp); + + vnet_int_vxlan_gpe_bypass_mode (sw_if_index, mp->is_ipv6, mp->enable); + BAD_SW_IF_INDEX_LABEL; + + REPLY_MACRO (VL_API_SW_INTERFACE_SET_VXLAN_GPE_BYPASS_REPLY); +} + static void vl_api_vxlan_gpe_add_del_tunnel_t_handler (vl_api_vxlan_gpe_add_del_tunnel_t * mp) @@ -109,6 +127,7 @@ static void clib_memcpy (&(a->local.ip4), mp->local, 4); clib_memcpy (&(a->remote.ip4), mp->remote, 4); } + a->mcast_sw_if_index = ntohl (mp->mcast_sw_if_index); a->encap_fib_index = encap_fib_index; a->decap_fib_index = decap_fib_index; a->protocol = protocol; @@ -149,6 +168,7 @@ static void send_vxlan_gpe_tunnel_details rmp->encap_vrf_id = htonl (im4->fibs[t->encap_fib_index].ft_table_id); rmp->decap_vrf_id = htonl (im4->fibs[t->decap_fib_index].ft_table_id); } + rmp->mcast_sw_if_index = htonl (t->mcast_sw_if_index); rmp->vni = htonl (t->vni); rmp->protocol = t->protocol; rmp->sw_if_index = htonl (t->sw_if_index); diff --git a/test/test_vxlan_gpe.py b/test/test_vxlan_gpe.py new file mode 100644 index 00000000..40975bf1 --- /dev/null +++ b/test/test_vxlan_gpe.py @@ -0,0 +1,313 @@ +#!/usr/bin/env python + +import socket +from util import ip4n_range +import unittest +from framework import VppTestCase, VppTestRunner, running_extended_tests +from template_bd import BridgeDomain + +from scapy.layers.l2 import Ether, Raw +from scapy.layers.inet import IP, UDP +from scapy.layers.vxlan import VXLAN +from scapy.utils import atol + + +@unittest.skipUnless(running_extended_tests(), "part of extended tests") +class TestVxlanGpe(BridgeDomain, VppTestCase): + """ VXLAN-GPE Test Case """ + + def __init__(self, *args): + BridgeDomain.__init__(self) + VppTestCase.__init__(self, *args) + + def encapsulate(self, pkt, vni): + """ + Encapsulate the original payload frame by adding VXLAN-GPE header + with its UDP, IP and Ethernet fields + """ + return (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) / + IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) / + UDP(sport=self.dport, dport=self.dport, chksum=0) / + VXLAN(vni=vni, flags=self.flags) / + pkt) + + def encap_mcast(self, pkt, src_ip, src_mac, vni): + """ + Encapsulate the original payload frame by adding VXLAN-GPE header + with its UDP, IP and Ethernet fields + """ + return (Ether(src=src_mac, dst=self.mcast_mac) / + IP(src=src_ip, dst=self.mcast_ip4) / + UDP(sport=self.dport, dport=self.dport, chksum=0) / + VXLAN(vni=vni, flags=self.flags) / + pkt) + + def decapsulate(self, pkt): + """ + Decapsulate the original payload frame by removing VXLAN-GPE header + """ + # check if is set I and P flag + self.assertEqual(pkt[VXLAN].flags, int('0xc', 16)) + return pkt[VXLAN].payload + + # Method for checking VXLAN-GPE encapsulation. + # + def check_encapsulation(self, pkt, vni, local_only=False, mcast_pkt=False): + # Verify source MAC is VPP_MAC and destination MAC is MY_MAC resolved + # by VPP using ARP. + self.assertEqual(pkt[Ether].src, self.pg0.local_mac) + if not local_only: + if not mcast_pkt: + self.assertEqual(pkt[Ether].dst, self.pg0.remote_mac) + else: + self.assertEqual(pkt[Ether].dst, type(self).mcast_mac) + # Verify VXLAN-GPE tunnel src IP is VPP_IP and dst IP is MY_IP. + self.assertEqual(pkt[IP].src, self.pg0.local_ip4) + if not local_only: + if not mcast_pkt: + self.assertEqual(pkt[IP].dst, self.pg0.remote_ip4) + else: + self.assertEqual(pkt[IP].dst, type(self).mcast_ip4) + # Verify UDP destination port is VXLAN-GPE 4790, source UDP port + # could be arbitrary. + self.assertEqual(pkt[UDP].dport, type(self).dport) + # Verify VNI + self.assertEqual(pkt[VXLAN].VNI, vni) + + def test_decap(self): + """ Decapsulation test + Send encapsulated frames from pg0 + Verify receipt of decapsulated frames on pg1 + """ + + encapsulated_pkt = self.encapsulate(self.frame_request, + self.single_tunnel_bd) + + self.pg0.add_stream([encapsulated_pkt, ]) + + self.pg1.enable_capture() + + self.pg_start() + + # Pick first received frame and check if it's the non-encapsulated + # frame + out = self.pg1.get_capture(1) + pkt = out[0] + self.assert_eq_pkts(pkt, self.frame_request) + + def test_encap(self): + """ Encapsulation test + Send frames from pg1 + Verify receipt of encapsulated frames on pg0 + """ + self.pg1.add_stream([self.frame_reply]) + + self.pg0.enable_capture() + + self.pg_start() + + # Pick first received frame and check if it's corectly encapsulated. + out = self.pg0.get_capture(1) + pkt = out[0] + self.check_encapsulation(pkt, self.single_tunnel_bd) + + # payload = self.decapsulate(pkt) + # self.assert_eq_pkts(payload, self.frame_reply) + + def test_ucast_flood(self): + """ Unicast flood test + Send frames from pg3 + Verify receipt of encapsulated frames on pg0 + """ + self.pg3.add_stream([self.frame_reply]) + + self.pg0.enable_capture() + + self.pg_start() + + # Get packet from each tunnel and assert it's corectly encapsulated. + out = self.pg0.get_capture(self.n_ucast_tunnels) + for pkt in out: + self.check_encapsulation(pkt, self.ucast_flood_bd, True) + # payload = self.decapsulate(pkt) + # self.assert_eq_pkts(payload, self.frame_reply) + + def test_mcast_flood(self): + """ Multicast flood test + Send frames from pg2 + Verify receipt of encapsulated frames on pg0 + """ + self.pg2.add_stream([self.frame_reply]) + + self.pg0.enable_capture() + + self.pg_start() + + # Pick first received frame and check if it's corectly encapsulated. + out = self.pg0.get_capture(1) + pkt = out[0] + self.check_encapsulation(pkt, self.mcast_flood_bd, + local_only=False, mcast_pkt=True) + + # payload = self.decapsulate(pkt) + # self.assert_eq_pkts(payload, self.frame_reply) + + @classmethod + def create_vxlan_gpe_flood_test_bd(cls, vni, n_ucast_tunnels): + # Create 10 ucast vxlan_gpe tunnels under bd + ip_range_start = 10 + ip_range_end = ip_range_start + n_ucast_tunnels + next_hop_address = cls.pg0.remote_ip4n + for dest_ip4n in ip4n_range(next_hop_address, ip_range_start, + ip_range_end): + # add host route so dest_ip4n will not be resolved + cls.vapi.ip_add_del_route(dest_ip4n, 32, next_hop_address) + r = cls.vapi.vxlan_gpe_add_del_tunnel( + src_addr=cls.pg0.local_ip4n, + dst_addr=dest_ip4n, + vni=vni) + cls.vapi.sw_interface_set_l2_bridge(r.sw_if_index, bd_id=vni) + + @classmethod + def add_del_shared_mcast_dst_load(cls, is_add): + """ + add or del tunnels sharing the same mcast dst + to test vxlan_gpe ref_count mechanism + """ + n_shared_dst_tunnels = 20 + vni_start = 1000 + vni_end = vni_start + n_shared_dst_tunnels + for vni in range(vni_start, vni_end): + r = cls.vapi.vxlan_gpe_add_del_tunnel( + src_addr=cls.pg0.local_ip4n, + dst_addr=cls.mcast_ip4n, + mcast_sw_if_index=1, + vni=vni, + is_add=is_add) + if r.sw_if_index == 0xffffffff: + raise "bad sw_if_index" + + @classmethod + def add_shared_mcast_dst_load(cls): + cls.add_del_shared_mcast_dst_load(is_add=1) + + @classmethod + def del_shared_mcast_dst_load(cls): + cls.add_del_shared_mcast_dst_load(is_add=0) + + @classmethod + def add_del_mcast_tunnels_load(cls, is_add): + """ + add or del tunnels to test vxlan_gpe stability + """ + n_distinct_dst_tunnels = 20 + ip_range_start = 10 + ip_range_end = ip_range_start + n_distinct_dst_tunnels + for dest_ip4n in ip4n_range(cls.mcast_ip4n, ip_range_start, + ip_range_end): + vni = bytearray(dest_ip4n)[3] + cls.vapi.vxlan_gpe_add_del_tunnel( + src_addr=cls.pg0.local_ip4n, + dst_addr=dest_ip4n, + mcast_sw_if_index=1, + vni=vni, + is_add=is_add) + + @classmethod + def add_mcast_tunnels_load(cls): + cls.add_del_mcast_tunnels_load(is_add=1) + + @classmethod + def del_mcast_tunnels_load(cls): + cls.add_del_mcast_tunnels_load(is_add=0) + + # Class method to start the VXLAN-GPE test case. + # Overrides setUpClass method in VppTestCase class. + # Python try..except statement is used to ensure that the tear down of + # the class will be executed even if exception is raised. + # @param cls The class pointer. + @classmethod + def setUpClass(cls): + super(TestVxlanGpe, cls).setUpClass() + + try: + cls.dport = 4790 + cls.flags = 0xc + + # Create 2 pg interfaces. + cls.create_pg_interfaces(range(4)) + for pg in cls.pg_interfaces: + pg.admin_up() + + # Configure IPv4 addresses on VPP pg0. + cls.pg0.config_ip4() + + # Resolve MAC address for VPP's IP address on pg0. + cls.pg0.resolve_arp() + + # Our Multicast address + cls.mcast_ip4 = '239.1.1.1' + cls.mcast_ip4n = socket.inet_pton(socket.AF_INET, cls.mcast_ip4) + iplong = atol(cls.mcast_ip4) + cls.mcast_mac = "01:00:5e:%02x:%02x:%02x" % ( + (iplong >> 16) & 0x7F, (iplong >> 8) & 0xFF, iplong & 0xFF) + + # Create VXLAN-GPE VTEP on VPP pg0, and put vxlan_gpe_tunnel0 + # and pg1 into BD. + cls.single_tunnel_bd = 11 + r = cls.vapi.vxlan_gpe_add_del_tunnel( + src_addr=cls.pg0.local_ip4n, + dst_addr=cls.pg0.remote_ip4n, + vni=cls.single_tunnel_bd) + cls.vapi.sw_interface_set_l2_bridge(r.sw_if_index, + bd_id=cls.single_tunnel_bd) + cls.vapi.sw_interface_set_l2_bridge(cls.pg1.sw_if_index, + bd_id=cls.single_tunnel_bd) + + # Setup vni 2 to test multicast flooding + cls.n_ucast_tunnels = 10 + cls.mcast_flood_bd = 12 + cls.create_vxlan_gpe_flood_test_bd(cls.mcast_flood_bd, + cls.n_ucast_tunnels) + r = cls.vapi.vxlan_gpe_add_del_tunnel( + src_addr=cls.pg0.local_ip4n, + dst_addr=cls.mcast_ip4n, + mcast_sw_if_index=1, + vni=cls.mcast_flood_bd) + cls.vapi.sw_interface_set_l2_bridge(r.sw_if_index, + bd_id=cls.mcast_flood_bd) + cls.vapi.sw_interface_set_l2_bridge(cls.pg2.sw_if_index, + bd_id=cls.mcast_flood_bd) + + # Add and delete mcast tunnels to check stability + cls.add_shared_mcast_dst_load() + cls.add_mcast_tunnels_load() + cls.del_shared_mcast_dst_load() + cls.del_mcast_tunnels_load() + + # Setup vni 3 to test unicast flooding + cls.ucast_flood_bd = 13 + cls.create_vxlan_gpe_flood_test_bd(cls.ucast_flood_bd, + cls.n_ucast_tunnels) + cls.vapi.sw_interface_set_l2_bridge(cls.pg3.sw_if_index, + bd_id=cls.ucast_flood_bd) + except Exception: + super(TestVxlanGpe, cls).tearDownClass() + raise + + # Method to define VPP actions before tear down of the test case. + # Overrides tearDown method in VppTestCase class. + # @param self The object pointer. + def tearDown(self): + super(TestVxlanGpe, self).tearDown() + if not self.vpp_dead: + self.logger.info(self.vapi.cli("show bridge-domain 11 detail")) + self.logger.info(self.vapi.cli("show bridge-domain 12 detail")) + self.logger.info(self.vapi.cli("show bridge-domain 13 detail")) + self.logger.info(self.vapi.cli("show int")) + self.logger.info(self.vapi.cli("show vxlan-gpe tunnel")) + self.logger.info(self.vapi.cli("show trace")) + + +if __name__ == '__main__': + unittest.main(testRunner=VppTestRunner) diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index b8a7a470..b310d094 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -1828,3 +1828,38 @@ class VppPapiProvider(object): 'encap_vrf_id': encap_vrf_id, 'decap_next_index': decap_next_index, 'teid': teid}) + + def vxlan_gpe_add_del_tunnel( + self, + src_addr, + dst_addr, + mcast_sw_if_index=0xFFFFFFFF, + is_add=1, + is_ipv6=0, + encap_vrf_id=0, + decap_vrf_id=0, + protocol=3, + vni=0): + """ + + :param local: + :param remote: + :param is_add: (Default value = 1) + :param is_ipv6: (Default value = 0) + :param encap_vrf_id: (Default value = 0) + :param decap_vrf_id: (Default value = 0) + :param mcast_sw_if_index: (Default value = 0xFFFFFFFF) + :param protocol: (Default value = 3) + :param vni: (Default value = 0) + + """ + return self.api(self.papi.vxlan_gpe_add_del_tunnel, + {'is_add': is_add, + 'is_ipv6': is_ipv6, + 'local': src_addr, + 'remote': dst_addr, + 'mcast_sw_if_index': mcast_sw_if_index, + 'encap_vrf_id': encap_vrf_id, + 'decap_vrf_id': decap_vrf_id, + 'protocol': protocol, + 'vni': vni}) -- cgit 1.2.3-korg