diff --git a/vppbld/patches/0015-vxlan-encap-inner-aware-flow-hash.patch b/vppbld/patches/0015-vxlan-encap-inner-aware-flow-hash.patch new file mode 100644 index 00000000..d6e2bf22 --- /dev/null +++ b/vppbld/patches/0015-vxlan-encap-inner-aware-flow-hash.patch @@ -0,0 +1,89 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aaron Bernardino +Date: Fri, 10 Jul 2026 00:00:00 +0000 +Subject: [PATCH] vxlan: inner-aware flow hash in the encap node + +The VXLAN encap node derives the outer UDP source port (and the buffer +flow hash used for underlay ECMP) from vnet_l2_compute_flow_hash(). That +helper reads the inner ethertype at current_data + l2.l2_len. For an +L3-routed VXLAN tunnel the inner packet arrives already routed (the +ip4/ip6-rewrite adjacency prepends the inner Ethernet header) and +l2.l2_len is 0/unset, so the helper misparses the frame and returns a +value that does not depend on the inner L3/L4 headers. Every inner flow +to a given tunnel endpoint then maps to the same outer UDP source port +and collapses onto a single underlay ECMP path / bond member. + +This is the exact case that patch 0011 (inner-aware flow hash) left out of +scope on the assumption that "VXLAN ... their outer UDP source port is the +standard-mandated entropy carrier (RFC 7348 section 4.2), so the existing +outer-5-tuple hash already distributes inner flows." That assumption only +holds if the encap node actually populates the outer UDP source port from +the inner flow -- which it does not for L3-routed tunnels. This patch +fixes the entropy carrier at its source rather than adding the peek-inner +machinery of 0011 to the underlay. + +Read the inner Ethernet ethertype directly at the buffer current pointer +(the inner Ethernet header is present for both L2 and L3 tunnels at the +encap call sites) and hash the inner IPv4/IPv6 5-tuple. Fall back to the +generic vnet_l2_compute_flow_hash() (which honours l2_len and VLAN tags) +for anything that is not plain inner IPv4/IPv6. + +Signed-off-by: Aaron Bernardino +--- +diff --git a/src/plugins/vxlan/encap.c b/src/plugins/vxlan/encap.c +index 9d304b76d..71024b3b4 100644 +--- a/src/plugins/vxlan/encap.c ++++ b/src/plugins/vxlan/encap.c +@@ -57,6 +57,30 @@ format_vxlan_encap_trace (u8 * s, va_list * args) + } + #endif + ++static_always_inline u32 ++vxlan_encap_inner_flow_hash (vlib_buffer_t * b) ++{ ++ /* For L3 VXLAN tunnels the inner packet is routed (not bridged) into this ++ * encap node, so vnet_buffer(b)->l2.l2_len is not set. The generic ++ * vnet_l2_compute_flow_hash() then misparses the frame and returns a value ++ * that does not depend on the inner L3/L4 headers, collapsing every inner ++ * flow for a given tunnel endpoint onto a single outer UDP source port and ++ * a single underlay ECMP path. The inner Ethernet header is present at the ++ * current data pointer, so read its ethertype directly and hash the inner ++ * IP 5-tuple. Fall back to the generic L2 hash (which honours l2_len and ++ * VLAN tags) for anything that is not plain inner IPv4/IPv6. */ ++ ethernet_header_t *eh = vlib_buffer_get_current (b); ++ u16 ethertype = clib_net_to_host_u16 (eh->type); ++ u8 *l3h = (u8 *) (eh + 1); ++ ++ if (ethertype == ETHERNET_TYPE_IP4) ++ return ip4_compute_flow_hash ((ip4_header_t *) l3h, IP_FLOW_HASH_DEFAULT); ++ else if (ethertype == ETHERNET_TYPE_IP6) ++ return ip6_compute_flow_hash ((ip6_header_t *) l3h, IP_FLOW_HASH_DEFAULT); ++ ++ return vnet_l2_compute_flow_hash (b); ++} ++ + always_inline uword + vxlan_encap_inline (vlib_main_t *vm, vlib_node_runtime_t *node, + vlib_frame_t *from_frame, u8 is_ip4) +@@ -125,8 +149,8 @@ vxlan_encap_inline (vlib_main_t *vm, vlib_node_runtime_t *node, + vlib_buffer_t *b1 = b[1]; + b += 2; + +- u32 flow_hash0 = vnet_l2_compute_flow_hash (b0); +- u32 flow_hash1 = vnet_l2_compute_flow_hash (b1); ++ u32 flow_hash0 = vxlan_encap_inner_flow_hash (b0); ++ u32 flow_hash1 = vxlan_encap_inner_flow_hash (b1); + + /* Get next node index and adj index from tunnel next_dpo */ + if (sw_if_index0 != vnet_buffer (b0)->sw_if_index[VLIB_TX]) +@@ -351,7 +375,7 @@ vxlan_encap_inline (vlib_main_t *vm, vlib_node_runtime_t *node, + vlib_buffer_t *b0 = b[0]; + b += 1; + +- u32 flow_hash0 = vnet_l2_compute_flow_hash (b0); ++ u32 flow_hash0 = vxlan_encap_inner_flow_hash (b0); + + /* Get next node index and adj index from tunnel next_dpo */ + if (sw_if_index0 != vnet_buffer (b0)->sw_if_index[VLIB_TX]) +-- +2.34.1 diff --git a/vppbld/patches/0017-sonic-vxlan-vnet-source-independent-decap.patch b/vppbld/patches/0017-sonic-vxlan-vnet-source-independent-decap.patch new file mode 100644 index 00000000..49806f34 --- /dev/null +++ b/vppbld/patches/0017-sonic-vxlan-vnet-source-independent-decap.patch @@ -0,0 +1,388 @@ +Subject: [PATCH] vxlan: source-independent (decap-any) VNET decap + +SONiC VNET decap must accept VXLAN packets from any outer source IP for a +given local VTEP IP + VNI (RIOT / secondary-VTEP decap), whereas VPP keys +decap on the exact (src,dst,vni) tuple. Register an additional source- +independent bihash entry keyed on local dst ip + vni (outer src wildcarded) +on tunnel add, maintain it on delete, and consult it in vxlan4/6_find_tunnel +only after the exact lookup misses so existing decap behaviour is unchanged. + +The source-independent wildcard and the L3-promiscuous BVI receive are scoped +to secondary-VTEP decap terms only, via a decap-any flag the SAI layer sets in +the high bit (VXLAN_DECAP_ANY_FLAG) of the wire decap_next_index; ordinary +VXLAN tunnels keep exact outer-source matching and strict BVI my-mac. All +wildcard bihash add/del return codes are checked: an add failure rolls back +the tunnel, a delete-time re-point failure drops the stale entry. The +L3-promiscuous ingress set is an O(1) sw_if_index bitmap in l2_bvi (no +per-packet device-class/string lookups). + +Limitations: this covers L3 VNET decap-and-route only. L2 EVPN VXLAN +(bridged, MAC-learning) is out of scope - such tunnels are not flagged +decap-any, so they keep exact outer-source matching and strict BVI my-mac +and are unaffected. The decap BVI is L3-enabled for both address families +but set_interface_vrf carries a single family flag, so mixed-family inner +traffic (e.g. inner IPv6 over an IPv4 VTEP) is a known gap shared with the +primary-VTEP decap path. +--- +diff --git a/src/plugins/vxlan/decap.c b/src/plugins/vxlan/decap.c +index 89422fd..301ec59 100644 +--- a/src/plugins/vxlan/decap.c ++++ b/src/plugins/vxlan/decap.c +@@ -86,6 +86,20 @@ vxlan4_find_tunnel (vxlan_main_t * vxm, last_tunnel_cache4 * cache, + return di; + } + ++ /* try source-independent decap (SONiC VNET decap-any): match on local ++ * dst ip + vni, ignoring the outer source. Consulted only after the exact ++ * (src,dst,vni) lookup misses, so it never changes existing decap. */ ++ { ++ vxlan4_tunnel_key_t key4w = key4; ++ key4w.key[0] = ((u64) dst << 32); ++ if (clib_bihash_search_inline_16_8 (&vxm->vxlan4_tunnel_by_key, &key4w) == 0) ++ { ++ vxlan_decap_info_t diw = {.as_u64 = key4w.value }; ++ *stats_sw_if_index = diw.sw_if_index; ++ return diw; ++ } ++ } ++ + /* try multicast */ + if (PREDICT_TRUE (!ip4_address_is_multicast (&ip4_0->dst_address))) + return decap_not_found; +@@ -135,7 +149,24 @@ vxlan6_find_tunnel (vxlan_main_t * vxm, last_tunnel_cache6 * cache, + int rv = + clib_bihash_search_inline_24_8 (&vxm->vxlan6_tunnel_by_key, &key6); + if (PREDICT_FALSE (rv != 0)) +- return decap_not_found; ++ { ++ /* source-independent decap (SONiC VNET decap-any): match on local ++ * dst ip6 + vni, ignoring the outer source. Consulted only after the ++ * exact (src,dst,vni) lookup misses, so existing decap is unchanged. */ ++ vxlan6_tunnel_key_t key6w = key6; ++ key6w.key[0] = ip6_0->dst_address.as_u64[0]; ++ key6w.key[1] = ip6_0->dst_address.as_u64[1]; ++ if (clib_bihash_search_inline_24_8 (&vxm->vxlan6_tunnel_by_key, ++ &key6w) != 0) ++ return decap_not_found; ++ vxlan_tunnel_t *tw = pool_elt_at_index (vxm->tunnels, key6w.value); ++ *stats_sw_if_index = tw->sw_if_index; ++ vxlan_decap_info_t diw = { ++ .sw_if_index = tw->sw_if_index, ++ .next_index = tw->decap_next_index, ++ }; ++ return diw; ++ } + + *cache = key6; + } +diff --git a/src/plugins/vxlan/vxlan.c b/src/plugins/vxlan/vxlan.c +index 46c0149..eeadd65 100644 +--- a/src/plugins/vxlan/vxlan.c ++++ b/src/plugins/vxlan/vxlan.c +@@ -4,6 +4,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -406,6 +407,18 @@ int vnet_vxlan_add_del_tunnel + if (p) + return VNET_API_ERROR_TUNNEL_EXIST; + ++ /* SONiC VNET decap-any: SAI signals a source-independent decap term by ++ * setting VXLAN_DECAP_ANY_FLAG in the wire decap_next_index. Decode it ++ * into a local flag and strip it so the remaining value is a normal ++ * next index. */ ++ u8 decap_any = 0; ++ if (a->decap_next_index != ~0 && ++ (a->decap_next_index & VXLAN_DECAP_ANY_FLAG)) ++ { ++ decap_any = 1; ++ a->decap_next_index &= ~VXLAN_DECAP_ANY_FLAG; ++ } ++ + /*if not set explicitly, default to l2 */ + if (a->decap_next_index == ~0) + a->decap_next_index = VXLAN_INPUT_NEXT_L2_INPUT; +@@ -421,6 +434,7 @@ int vnet_vxlan_add_del_tunnel + #define _(x) t->x = a->x; + foreach_copy_field; + #undef _ ++ t->decap_any = decap_any; + + vxlan_rewrite (t, is_ip6); + /* +@@ -480,6 +494,27 @@ int vnet_vxlan_add_del_tunnel + key6.value = (u64) dev_instance; + add_failed = clib_bihash_add_del_24_8 (&vxm->vxlan6_tunnel_by_key, + &key6, 1 /*add */ ); ++ /* SONiC VNET decap-any: also register a source-independent decap ++ * entry keyed on local dst ip6 + vni (outer src wildcarded). Only ++ * for decap-any terms so ordinary tunnels keep outer-src checking. */ ++ if (!add_failed && t->decap_any && ++ !ip46_address_is_multicast (&t->dst)) ++ { ++ vxlan6_tunnel_key_t key6w; ++ key6w.key[0] = t->src.ip6.as_u64[0]; ++ key6w.key[1] = t->src.ip6.as_u64[1]; ++ key6w.key[2] = key6.key[2]; ++ key6w.value = (u64) dev_instance; ++ if (clib_bihash_add_del_24_8 (&vxm->vxlan6_tunnel_by_key, &key6w, ++ 1 /*add */ )) ++ { ++ /* roll back the exact entry to avoid a half-programmed ++ * tunnel, then fail the create. */ ++ clib_bihash_add_del_24_8 (&vxm->vxlan6_tunnel_by_key, &key6, ++ 0 /*del */ ); ++ add_failed = 1; ++ } ++ } + } + else + { +@@ -491,6 +526,24 @@ int vnet_vxlan_add_del_tunnel + key4.value = di.as_u64; + add_failed = clib_bihash_add_del_16_8 (&vxm->vxlan4_tunnel_by_key, + &key4, 1 /*add */ ); ++ /* SONiC VNET decap-any: also register a source-independent decap ++ * entry keyed on local dst ip + vni (outer src wildcarded to 0). ++ * Only for decap-any terms. */ ++ if (!add_failed && t->decap_any && ++ !ip46_address_is_multicast (&t->dst)) ++ { ++ vxlan4_tunnel_key_t key4w; ++ key4w.key[0] = ((u64) t->src.ip4.as_u32) << 32; ++ key4w.key[1] = key4.key[1]; ++ key4w.value = di.as_u64; ++ if (clib_bihash_add_del_16_8 (&vxm->vxlan4_tunnel_by_key, &key4w, ++ 1 /*add */ )) ++ { ++ clib_bihash_add_del_16_8 (&vxm->vxlan4_tunnel_by_key, &key4, ++ 0 /*del */ ); ++ add_failed = 1; ++ } ++ } + } + + if (add_failed) +@@ -508,6 +561,11 @@ int vnet_vxlan_add_del_tunnel + ~0); + vxm->tunnel_index_by_sw_if_index[sw_if_index] = dev_instance; + ++ /* SONiC VNET decap-any: mark this tunnel's ingress sw_if_index so ++ * l2_to_bvi routes its decapped inner frames regardless of dst MAC. */ ++ if (t->decap_any) ++ l2_bvi_set_l3_promiscuous (sw_if_index, 1); ++ + /* 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; +@@ -626,6 +684,10 @@ int vnet_vxlan_add_del_tunnel + + vxm->tunnel_index_by_sw_if_index[sw_if_index] = ~0; + ++ /* SONiC VNET decap-any: clear the L3-promiscuous mark for this ingress. */ ++ if (t->decap_any) ++ l2_bvi_set_l3_promiscuous (sw_if_index, 0); ++ + if (!is_ip6) + clib_bihash_add_del_16_8 (&vxm->vxlan4_tunnel_by_key, &key4, + 0 /*del */ ); +@@ -633,6 +695,93 @@ int vnet_vxlan_add_del_tunnel + clib_bihash_add_del_24_8 (&vxm->vxlan6_tunnel_by_key, &key6, + 0 /*del */ ); + ++ /* SONiC VNET decap-any: keep or drop the source-independent entry. ++ * Only decap-any terms own a wildcard entry. If another decap-any ++ * tunnel shares the same local ip + vni it must stay, so re-point the ++ * wildcard entry at that survivor; otherwise remove it. */ ++ if (t->decap_any && !is_ip6 && !ip46_address_is_multicast (&t->dst)) ++ { ++ vxlan4_tunnel_key_t key4w; ++ key4w.key[0] = ((u64) t->src.ip4.as_u32) << 32; ++ key4w.key[1] = key4.key[1]; ++ vxlan_tunnel_t *st, *survivor = 0; ++ pool_foreach (st, vxm->tunnels) ++ { ++ if (st == t || !st->decap_any || ++ ip46_address_is_multicast (&st->dst)) ++ continue; ++ if (st->src.ip4.as_u32 == t->src.ip4.as_u32 && ++ st->vni == t->vni && ++ st->encap_fib_index == t->encap_fib_index && ++ st->src_port == t->src_port) ++ { ++ survivor = st; ++ break; ++ } ++ } ++ if (survivor) ++ { ++ vxlan_decap_info_t diw = { ++ .sw_if_index = survivor->sw_if_index, ++ .next_index = survivor->decap_next_index ++ }; ++ key4w.value = diw.as_u64; ++ if (clib_bihash_add_del_16_8 (&vxm->vxlan4_tunnel_by_key, &key4w, ++ 1 /*add */ )) ++ { ++ /* re-point failed: drop the wildcard rather than leave it ++ * pointing at the tunnel slot we are about to free. */ ++ clib_bihash_add_del_16_8 (&vxm->vxlan4_tunnel_by_key, &key4w, ++ 0 /*del */ ); ++ clib_warning ("vxlan decap-any: ip4 wildcard re-point failed," ++ " dropped stale entry for vni %u", t->vni); ++ } ++ } ++ else ++ clib_bihash_add_del_16_8 (&vxm->vxlan4_tunnel_by_key, &key4w, ++ 0 /*del */ ); ++ } ++ ++ /* SONiC VNET decap-any (ip6): keep or drop the source-independent ++ * entry; re-point at a surviving same-local-ip+vni decap-any tunnel. */ ++ if (t->decap_any && is_ip6 && !ip46_address_is_multicast (&t->dst)) ++ { ++ vxlan6_tunnel_key_t key6w; ++ key6w.key[0] = t->src.ip6.as_u64[0]; ++ key6w.key[1] = t->src.ip6.as_u64[1]; ++ key6w.key[2] = key6.key[2]; ++ vxlan_tunnel_t *st, *survivor = 0; ++ pool_foreach (st, vxm->tunnels) ++ { ++ if (st == t || !st->decap_any || ++ ip46_address_is_multicast (&st->dst)) ++ continue; ++ if (ip6_address_is_equal (&st->src.ip6, &t->src.ip6) && ++ st->vni == t->vni && ++ st->encap_fib_index == t->encap_fib_index && ++ st->src_port == t->src_port) ++ { ++ survivor = st; ++ break; ++ } ++ } ++ if (survivor) ++ { ++ key6w.value = (u64) (survivor - vxm->tunnels); ++ if (clib_bihash_add_del_24_8 (&vxm->vxlan6_tunnel_by_key, &key6w, ++ 1 /*add */ )) ++ { ++ clib_bihash_add_del_24_8 (&vxm->vxlan6_tunnel_by_key, &key6w, ++ 0 /*del */ ); ++ clib_warning ("vxlan decap-any: ip6 wildcard re-point failed," ++ " dropped stale entry for vni %u", t->vni); ++ } ++ } ++ else ++ clib_bihash_add_del_24_8 (&vxm->vxlan6_tunnel_by_key, &key6w, ++ 0 /*del */ ); ++ } ++ + if (!ip46_address_is_multicast (&t->dst)) + { + if (t->flow_index != ~0) +diff --git a/src/plugins/vxlan/vxlan.h b/src/plugins/vxlan/vxlan.h +index b89e75b..be5fed9 100644 +--- a/src/plugins/vxlan/vxlan.h ++++ b/src/plugins/vxlan/vxlan.h +@@ -123,6 +123,12 @@ typedef struct + u32 dev_instance; /* Real device instance in tunnel vector */ + u32 user_instance; /* Instance name being shown to user */ + ++ /* SONiC VNET decap-any: this tunnel is a secondary-VTEP decap term that ++ * accepts VXLAN packets from any outer source for its local dst+vni. Only ++ * such tunnels register the source-independent wildcard bihash entry and ++ * enable L3-promiscuous receive on their BVI. */ ++ u8 decap_any; ++ + VNET_DECLARE_REWRITE; + } vxlan_tunnel_t; + +@@ -212,6 +218,11 @@ typedef struct + u16 dst_port; + } vnet_vxlan_add_del_tunnel_args_t; + ++/* SONiC VNET decap-any: high bit of the wire decap_next_index used by SAI to ++ * flag a source-independent decap term. Stripped by vnet_vxlan_add_del_tunnel ++ * before the value is validated/used as a real next index. */ ++#define VXLAN_DECAP_ANY_FLAG (1u << 31) ++ + int vnet_vxlan_add_del_tunnel + (vnet_vxlan_add_del_tunnel_args_t * a, u32 * sw_if_indexp); + +diff --git a/src/vnet/l2/l2_bvi.c b/src/vnet/l2/l2_bvi.c +index a3c6654..beecc7f 100644 +--- a/src/vnet/l2/l2_bvi.c ++++ b/src/vnet/l2/l2_bvi.c +@@ -9,6 +9,40 @@ + #include + #include + #include ++#include ++ ++/* SONiC VNET decap-and-route (RIOT): a frame that ingressed on a secondary- ++ * VTEP "decap-any" VXLAN tunnel and is being flooded to a BVI is a decap-and- ++ * route packet. On real hardware VXLAN decap-and-route is MAC-agnostic, but ++ * VPP's BVI applies a strict my-mac L2 filter, so decapped frames whose inner ++ * dst MAC != the router/BVI MAC are dropped instead of being L3-routed in the ++ * VNET VRF. For those tunnels only, treat the BVI as L3-promiscuous and route ++ * regardless of dst MAC. Ordinary L2/EVPN VXLAN tunnels and normal L2 ports ++ * keep strict my-mac semantics. ++ * ++ * The set of decap-any ingress sw_if_indexes is maintained as a bitmap owned ++ * here (vnet layer) and populated by the vxlan plugin via ++ * l2_bvi_set_l3_promiscuous() at tunnel add/del. This keeps the data-plane ++ * check an O(1) bitmap test - no per-packet pool/device-class lookups - and ++ * scopes the behaviour to exactly the decap-any terms. */ ++static uword *l2_bvi_l3_promiscuous_bitmap; ++ ++void ++l2_bvi_set_l3_promiscuous (u32 sw_if_index, int enable) ++{ ++ if (sw_if_index == (u32) ~0) ++ return; ++ l2_bvi_l3_promiscuous_bitmap = ++ clib_bitmap_set (l2_bvi_l3_promiscuous_bitmap, sw_if_index, enable ? 1 : 0); ++} ++ ++int ++l2_bvi_l3_promiscuous (u32 rx_sw_if_index) ++{ ++ if (rx_sw_if_index == (u32) ~0) ++ return 0; ++ return clib_bitmap_get (l2_bvi_l3_promiscuous_bitmap, rx_sw_if_index) ? 1 : 0; ++} + + /* Allocated BVI instances */ + static uword *l2_bvi_instances; +diff --git a/src/vnet/l2/l2_bvi.h b/src/vnet/l2/l2_bvi.h +index 1087190..305ab7a 100644 +--- a/src/vnet/l2/l2_bvi.h ++++ b/src/vnet/l2/l2_bvi.h +@@ -17,6 +17,9 @@ + #define TO_BVI_ERR_BAD_MAC 1 + #define TO_BVI_ERR_ETHERTYPE 2 + ++extern int l2_bvi_l3_promiscuous (u32 sw_if_index); ++extern void l2_bvi_set_l3_promiscuous (u32 sw_if_index, int enable); ++ + static_always_inline u32 + l2_to_bvi_dmac_check (vnet_hw_interface_t * hi, u8 * dmac, + ethernet_interface_t * ei, u8 have_sec_dmac) +@@ -69,7 +72,12 @@ l2_to_bvi (vlib_main_t * vlib_main, + 0 /* have_sec_dmac */ ); + + if (rv != TO_BVI_ERR_OK) +- return rv; ++ { ++ /* VXLAN decap-and-route (RIOT): if this frame ingressed on a VXLAN ++ tunnel, route via the BVI regardless of inner dst MAC. */ ++ if (!l2_bvi_l3_promiscuous (vnet_buffer (b0)->sw_if_index[VLIB_RX])) ++ return rv; ++ } + } + + /* Save L2 header position which may be changed due to packet replication */ diff --git a/vppbld/patches/series b/vppbld/patches/series index a7793375..43f96210 100644 --- a/vppbld/patches/series +++ b/vppbld/patches/series @@ -29,4 +29,9 @@ 0013-sflow-per-port-direction.patch # 14. Adding new VPP API handlers for per port and direction patches 0014-sflow-per-port-api.patch - +# 15. VXLAN encap inner-aware flow hash: hash inner IP 5-tuple on L3 VXLAN +# encap so inner flows spread across underlay ECMP / LAG paths +0015-vxlan-encap-inner-aware-flow-hash.patch +# 17. VXLAN VNET source-independent ("decap-any") decap: match local dst+vni +# ignoring outer src (RIOT / secondary-VTEP VNET decap) + l2_bvi helper +0017-sonic-vxlan-vnet-source-independent-decap.patch