diff --git a/docker-sonic-vpp/conf/startup.conf.tmpl b/docker-sonic-vpp/conf/startup.conf.tmpl index 6513b6d6..8e2df93f 100644 --- a/docker-sonic-vpp/conf/startup.conf.tmpl +++ b/docker-sonic-vpp/conf/startup.conf.tmpl @@ -253,6 +253,7 @@ plugins { plugin tunterm_acl_plugin.so { enable } plugin ip_validate_plugin.so { enable } plugin sonic_ext_plugin.so { enable } + plugin gre_plugin.so { enable } ## Enable all plugins by default and then selectively disable specific plugins # plugin dpdk_plugin.so { disable } diff --git a/docker-syncd-vpp/conf/startup.conf.tmpl b/docker-syncd-vpp/conf/startup.conf.tmpl index 40a6a537..c98759e1 100644 --- a/docker-syncd-vpp/conf/startup.conf.tmpl +++ b/docker-syncd-vpp/conf/startup.conf.tmpl @@ -243,6 +243,7 @@ plugins { plugin ip_validate_plugin.so { enable } plugin sflow_plugin.so { enable } plugin sonic_ext_plugin.so { enable } + plugin gre_plugin.so { enable } ## Enable all plugins by default and then selectively disable specific plugins # plugin dpdk_plugin.so { disable } diff --git a/vppbld/patches/0019-acl-add-permit-mirror-action-for-everflow.patch b/vppbld/patches/0019-acl-add-permit-mirror-action-for-everflow.patch new file mode 100644 index 00000000..51eb7abc --- /dev/null +++ b/vppbld/patches/0019-acl-add-permit-mirror-action-for-everflow.patch @@ -0,0 +1,313 @@ +From 2974f89529bf57019ce59a8f96b6911339fac8f2 Mon Sep 17 00:00:00 2001 +From: taran9999 +Date: Thu, 10 Sep 2026 11:08:28 -0700 +Subject: acl: add PERMIT_MIRROR action for SONiC Everflow + +Add a fourth ACL action, ACL_ACTION_API_PERMIT_MIRROR (3), which permits +the packet and additionally sends a copy to a mirror destination. This is +the dataplane half of SONiC Everflow (ACL-based mirroring): the SAI layer +maps SAI_ACL_ENTRY_ATTR_ACTION_MIRROR_INGRESS / _MIRROR_EGRESS onto it. + +The destination is carried in a new packed u32 mirror_action, appended to +acl_rule: bits 27:0 hold the mirror destination sw_if_index and bits 31:28 +hold action flags. The only flag defined today is ACL_MIRROR_F_DEFERRED, +set for SAI MIRROR_EGRESS. A mirror_action of 0 means no mirror +destination, since sw_if_index 0 is local0 and is never a mirror target. + +Two clone behaviours are selected by that flag: + + - Immediate (MIRROR_INGRESS, or any match on an output arc): clone in + this node. The ACL feature runs on the ip4/ip6-unicast arc, so + current_data points at the L3 header while the original L2 frame sits + in the headroom; rewind to l2_hdr_offset before vlib_buffer_copy() so + the clone carries the full L2 frame the ERSPAN/GRE tunnel expects, + then restore the original position. + + - Deferred (MIRROR_EGRESS matched on an ingress arc): the copy has to be + taken on the egress arc to reflect the post-route/post-encap wire + form, and this plugin owns no egress node. Rather than reach into + another plugin's per-packet metadata, export + acl_register_deferred_mirror_stamp() so a platform plugin can claim + the handoff; it records the destination however it likes and performs + the late clone. The hook is resolved with vlib_get_plugin_symbol(), so + neither plugin gains a link-time dependency on the other, and the ACL + plugin still builds and runs standalone. When no stamper is registered + the deferred rule simply clones immediately, so mirroring degrades + rather than silently disappearing. + +Scoping a mirror rule to specific ingress ports +(SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS, used by Everflow per-interface +mirroring) needs no new field here: the preceding in_sw_if_index patch +already matches a rule on its ingress interface, and the SAI layer emits +one rule per port. + +Two dataplane guards are folded in because both are required for the +clone to be safe: + + - A buffer that is already a mirror clone is never mirrored again. The + injected encapped copy can re-enter this node and match again, + stacking another outer encap on every pass until the buffer headroom + is exhausted and the real mirror is dropped. vnet SPAN has the same + check; the packet is still permitted and forwarded. + + - mirror_sw_if_index is baked into the rule at program time and is + mutated by the control plane (re-pointing a mirror session deletes and + recreates its GRE tunnel), so a data packet can match while the index + is stale or the tunnel is not yet admin-up. + vnet_get_frame_to_sw_interface() does no validity check of its own and + would dereference freed per-interface state, so the clone is gated on + the interface being a live pool slot and admin-up first. +--- + src/plugins/acl/acl.c | 17 ++++++ + src/plugins/acl/acl.h | 22 ++++++++ + src/plugins/acl/acl_types.api | 13 +++++ + src/plugins/acl/dataplane_node.c | 96 ++++++++++++++++++++++++++++++++ + src/plugins/acl/types.h | 8 +++ + 5 files changed, 156 insertions(+) + +diff --git a/src/plugins/acl/acl.c b/src/plugins/acl/acl.c +index f0bdfdf28..e5cf3b5aa 100644 +--- a/src/plugins/acl/acl.c ++++ b/src/plugins/acl/acl.c +@@ -305,6 +305,17 @@ acl_api_invalid_prefix (const vl_api_prefix_t * prefix) + return (!valid_af) || ip_prefix_decode2 (prefix, &ip_prefix); + } + ++/* ++ * Exported so a platform plugin can claim the deferred (egress-arc) ++ * mirror clone. Looked up with vlib_get_plugin_symbol (), so the caller ++ * needs no link-time dependency on this plugin. Pass 0 to unregister. ++ */ ++__clib_export void ++acl_register_deferred_mirror_stamp (acl_deferred_mirror_stamp_fn fn) ++{ ++ acl_main.deferred_mirror_stamp = fn; ++} ++ + static int + acl_add_list (u32 count, vl_api_acl_rule_t rules[], + u32 * acl_list_index, u8 * tag) +@@ -346,6 +357,10 @@ acl_add_list (u32 count, vl_api_acl_rule_t rules[], + */ + if (ntohl (rules[i].in_sw_if_index) > 0xffff) + return VNET_API_ERROR_INVALID_SW_IF_INDEX; ++ if (rules[i].is_permit == 3 && ++ ((ntohl (rules[i].mirror_action) >> ACL_MIRROR_FLAGS_SHIFT) & ++ ~ACL_MIRROR_F_DEFERRED)) ++ return VNET_API_ERROR_INVALID_VALUE; + } + + if (*acl_list_index != ~0) +@@ -389,6 +404,8 @@ acl_add_list (u32 count, vl_api_acl_rule_t rules[], + r->tcp_flags_value = rules[i].tcp_flags_value; + r->tcp_flags_mask = rules[i].tcp_flags_mask; + r->in_sw_if_index = ntohl (rules[i].in_sw_if_index); ++ r->mirror_action = ++ (rules[i].is_permit == 3) ? ntohl (rules[i].mirror_action) : 0; + } + + if (~0 == *acl_list_index) +diff --git a/src/plugins/acl/acl.h b/src/plugins/acl/acl.h +index 98ed735ef..1bf07c6f0 100644 +--- a/src/plugins/acl/acl.h ++++ b/src/plugins/acl/acl.h +@@ -25,6 +25,25 @@ + #include "hash_lookup_types.h" + #include "lookup_context.h" + ++/** ++ * Deferred mirror handoff for the PERMIT_MIRROR action. ++ * ++ * A rule carrying ACL_MIRROR_F_DEFERRED wants its clone taken later, on ++ * the egress arc, so the copy reflects the post-route/post-encap wire ++ * form. The ACL plugin owns no egress node, so a platform plugin ++ * registers a stamper that records the destination in its own per-buffer ++ * metadata and performs the late clone itself. ++ * ++ * Returns non-zero if the stamper took ownership of the mirror. When no ++ * stamper is registered, or it declines, the ACL node falls back to ++ * cloning immediately, so a deferred rule still mirrors. ++ */ ++typedef int (*acl_deferred_mirror_stamp_fn) (vlib_buffer_t *b, ++ u32 rx_sw_if_index, ++ u32 mirror_sw_if_index); ++ ++void acl_register_deferred_mirror_stamp (acl_deferred_mirror_stamp_fn fn); ++ + #define ACL_PLUGIN_VERSION_MAJOR 1 + #define ACL_PLUGIN_VERSION_MINOR 4 + +@@ -292,6 +311,9 @@ typedef struct { + vlib_combined_counter_main_t *combined_acl_counters; + /* enable/disable ACL counters for interface processing */ + u32 interface_acl_counters_enabled; ++ ++ /* deferred mirror stamper, NULL when no platform plugin registered */ ++ acl_deferred_mirror_stamp_fn deferred_mirror_stamp; + } acl_main_t; + + #define acl_log_err(...) \ +diff --git a/src/plugins/acl/acl_types.api b/src/plugins/acl/acl_types.api +index a239f3795..d93f5833a 100644 +--- a/src/plugins/acl/acl_types.api ++++ b/src/plugins/acl/acl_types.api +@@ -24,6 +24,7 @@ enum acl_action : u8 + ACL_ACTION_API_DENY = 0, + ACL_ACTION_API_PERMIT = 1, + ACL_ACTION_API_PERMIT_REFLECT = 2, ++ ACL_ACTION_API_PERMIT_MIRROR = 3, + }; + + /** \brief Access List Rule entry +@@ -79,6 +80,18 @@ typedef acl_rule + * bits is rejected rather than matched as a different interface. + */ + u32 in_sw_if_index; ++/* ++ * For is_permit == PERMIT_MIRROR (3) only: the mirror destination ++ * sw_if_index in bits 27:0 and mirror action flags in bits 31:28. ++ * ACL_MIRROR_F_DEFERRED asks for the clone to be taken later, on the ++ * egress arc, by whichever platform plugin registered a deferred mirror ++ * stamper; otherwise the clone is taken in the ACL node itself. ++ * ++ * 0 => no mirror destination. sw_if_index 0 is local0, which is never a ++ * mirror target, so it doubles as the "none" value. Ignored for every ++ * other action. ++ */ ++ u32 mirror_action; + }; + + +diff --git a/src/plugins/acl/dataplane_node.c b/src/plugins/acl/dataplane_node.c +index 471228726..bb71860f9 100644 +--- a/src/plugins/acl/dataplane_node.c ++++ b/src/plugins/acl/dataplane_node.c +@@ -550,6 +550,102 @@ acl_fa_inner_node_fn (vlib_main_t * vm, + next[0] = action ? next[0] : 0; + } + ++ if (PREDICT_FALSE (action == 3)) ++ { ++ vnet_main_t *vnm = vnet_get_main (); ++ acl_rule_t *mir_rule = ++ &am->acls[match_acl_in_index].rules[match_rule_index]; ++ u32 mirror_sw_if_index = ++ mir_rule->mirror_action & ACL_MIRROR_SW_IF_INDEX_MASK; ++ u32 mirror_flags = ++ mir_rule->mirror_action >> ACL_MIRROR_FLAGS_SHIFT; ++ ++ pkts_acl_permit++; ++ ++ /* Never mirror a packet that is itself a mirror clone: the ++ * injected ERSPAN/GRE-encapped copy can re-enter this node and ++ * match again, stacking another outer encap on every pass until ++ * the buffer headroom is exhausted and the real mirror is ++ * dropped. vnet SPAN has the same check. The packet is still ++ * permitted and forwarded. ++ * ++ * mirror_sw_if_index is baked into the rule at program time and ++ * is mutated by the control plane (re-pointing a mirror session ++ * deletes and recreates its GRE tunnel), so it can be stale or ++ * not yet admin-up when a packet matches during that window. ++ * vnet_get_frame_to_sw_interface () does no validity check of ++ * its own and would dereference freed per-interface state, so ++ * require a live, admin-up interface first; ++ * vnet_sw_interface_is_valid () never dereferences, and the ++ * short-circuit && keeps vnet_sw_interface_is_admin_up () off a ++ * stale index. */ ++ if (mirror_sw_if_index != 0 && ++ !(b[0]->flags & VNET_BUFFER_F_SPAN_CLONE) && ++ vnet_sw_interface_is_valid (vnm, mirror_sw_if_index) && ++ vnet_sw_interface_is_admin_up (vnm, mirror_sw_if_index)) ++ { ++ int deferred = 0; ++ ++ /* A deferred rule matched on an ingress arc wants the clone ++ * taken on the egress arc instead, so it reflects the ++ * post-route/post-encap wire form. Hand it to the registered ++ * platform stamper; clone here if there is none. */ ++ if ((mirror_flags & ACL_MIRROR_F_DEFERRED) && is_input && ++ am->deferred_mirror_stamp) ++ deferred = am->deferred_mirror_stamp ( ++ b[0], sw_if_index[0], mirror_sw_if_index); ++ ++ if (!deferred) ++ { ++ /* The ACL feature runs on the ip4/ip6-unicast arc, so ++ * current_data points at the L3 header while the ++ * original L2 frame sits in the headroom. ++ * vlib_buffer_copy () only copies from current_data ++ * forward, so rewind to l2_hdr_offset first to capture ++ * the full L2 frame the ERSPAN/GRE tunnel expects, then ++ * restore the original position. */ ++ word l2_rewind = 0; ++ vlib_buffer_t *clone; ++ ++ if (PREDICT_TRUE (b[0]->flags & ++ VNET_BUFFER_F_L2_HDR_OFFSET_VALID)) ++ { ++ l2_rewind = (word) b[0]->current_data - ++ vnet_buffer (b[0])->l2_hdr_offset; ++ if (l2_rewind > 0) ++ vlib_buffer_advance (b[0], -l2_rewind); ++ else ++ l2_rewind = 0; ++ } ++ ++ clone = vlib_buffer_copy (vm, b[0]); ++ ++ if (l2_rewind > 0) ++ vlib_buffer_advance (b[0], l2_rewind); ++ ++ if (PREDICT_TRUE (clone != 0)) ++ { ++ vlib_frame_t *f; ++ u32 *to_next; ++ ++ vnet_buffer (clone)->sw_if_index[VLIB_TX] = ++ mirror_sw_if_index; ++ clone->flags |= VNET_BUFFER_F_SPAN_CLONE; ++ ++ f = vnet_get_frame_to_sw_interface ( ++ vnm, mirror_sw_if_index); ++ to_next = vlib_frame_vector_args (f); ++ to_next += f->n_vectors; ++ to_next[0] = vlib_get_buffer_index (vm, clone); ++ f->n_vectors++; ++ vnet_put_frame_to_sw_interface (vnm, ++ mirror_sw_if_index, ++ f); ++ } ++ } ++ } ++ } ++ + if (node_trace_on) // PREDICT_FALSE (node->flags & VLIB_NODE_FLAG_TRACE)) + { + maybe_trace_buffer (vm, node, b[0], sw_if_index[0], lc_index0, +diff --git a/src/plugins/acl/types.h b/src/plugins/acl/types.h +index 92412a9dd..dee23aada 100644 +--- a/src/plugins/acl/types.h ++++ b/src/plugins/acl/types.h +@@ -9,6 +9,12 @@ + #include + #include + ++/* PERMIT_MIRROR action: mirror destination sw_if_index in bits 27:0, ++ * mirror action flags in bits 31:28. */ ++#define ACL_MIRROR_SW_IF_INDEX_MASK 0x0fffffffU ++#define ACL_MIRROR_FLAGS_SHIFT 28 ++#define ACL_MIRROR_F_DEFERRED (1U << 0) ++ + typedef struct + { + u8 is_permit; +@@ -26,6 +32,8 @@ typedef struct + u8 tcp_flags_mask; + /* Ingress interface to match, 0 for any. See acl_types.api. */ + u32 in_sw_if_index; ++ /* PERMIT_MIRROR only, 0 for no mirror. See acl_types.api. */ ++ u32 mirror_action; + } acl_rule_t; + + diff --git a/vppbld/patches/0020-gre-per-tunnel-ttl-and-protocol-override.patch b/vppbld/patches/0020-gre-per-tunnel-ttl-and-protocol-override.patch new file mode 100644 index 00000000..fcf837bd --- /dev/null +++ b/vppbld/patches/0020-gre-per-tunnel-ttl-and-protocol-override.patch @@ -0,0 +1,429 @@ +From ca2347589d58e23735f1da463427e28b6d749486 Mon Sep 17 00:00:00 2001 +From: taran9999 +Date: Wed, 9 Sep 2026 13:50:03 -0700 +Subject: gre: per-tunnel outer TTL and GRE protocol override for Everflow + ERSPAN + +SONiC Everflow expects the mirrored copy on the wire as plain GRE carrying +protocol 0x88BE over the raw mirrored L2 frame, with no sequence number, +no ERSPAN type-II shim, and the outer TTL taken from the mirror session. +None of that is reachable through the existing GRE tunnel API. + +Add a gre_tunnel_v3 API type - a copy of gre_tunnel_v2 plus gre_protocol +and hop_limit - together with gre_tunnel_add_del_v3, gre_tunnel_dump_v3 +and gre_tunnel_details_v3. gre_tunnel_v2 is left byte-for-byte unchanged +so existing clients are unaffected. + +hop_limit sets the outer IPv4 TTL / IPv6 hop-limit written into the encap +rewrite; 0 keeps the current defaults (254 / 255). gre_protocol overrides +the GRE protocol field; 0 keeps deriving it from the tunnel type and +payload link type. + +gre_protocol additionally selects a plain-GRE mode for ERSPAN tunnels. +When it is set, an ERSPAN tunnel emits the configured protocol with +flags_and_version = 0 and gre_encap_inline suppresses both the sequence +number and the 8-byte ERSPAN type-II shim, while still reusing the ERSPAN +tunnel's working L2 delivery path. Plain ERSPAN tunnels (gre_protocol == 0) +keep emitting GRE_PROTOCOL_erspan with the sequence bit and the type-II +shim and are byte-for-byte unchanged. + +Neither field is part of the tunnel lookup key, so add/remove keying is +unchanged. +--- + src/plugins/gre/gre.api | 83 +++++++++++++++++++++++++ + src/plugins/gre/gre.c | 32 +++++++--- + src/plugins/gre/gre.h | 14 +++++ + src/plugins/gre/gre_api.c | 117 ++++++++++++++++++++++++++++++++++++ + src/plugins/gre/interface.c | 2 + + 5 files changed, 239 insertions(+), 9 deletions(-) + +diff --git a/src/plugins/gre/gre.api b/src/plugins/gre/gre.api +index 159e1ce3f..5f7e1a456 100644 +--- a/src/plugins/gre/gre.api ++++ b/src/plugins/gre/gre.api +@@ -26,6 +26,7 @@ import "vnet/ip/ip_types.api"; + */ + service { + rpc gre_tunnel_dump_v2 returns gre_tunnel_dump_v2_reply events gre_tunnel_details_v2; ++ rpc gre_tunnel_dump_v3 returns gre_tunnel_dump_v3_reply events gre_tunnel_details_v3; + }; + + /** \brief A GRE tunnel type +@@ -89,6 +90,39 @@ typedef gre_tunnel_v2 + u32 key; + }; + ++/** \brief A composite type uniquely defining a GRE tunnel with key support ++ plus a GRE protocol/ethertype override and a configurable outer TTL. ++ @param type - tunnel type (see enum definition), 0: L3, 1: TEB, 2: ERSPAN ++ @param mode - P2P or P2MP ++ @param flags - to control encap/decap behaviour ++ @param session_id - session for ERSPAN tunnel, range 0-1023 ++ @param instance - optional unique custom device instance, else ~0. ++ @param outer_table_id - Encap FIB table ID ++ @param sw_if_index - ignored on create/delete, present in details. ++ @param src - Source IP address ++ @param dst - Destination IP address, can be multicast ++ @param key - GRE key value (RFC 2890), 0 for no key ++ @param gre_protocol - override GRE protocol/ethertype in the encap header, ++ 0 to derive it from the tunnel type/payload link. ++ @param hop_limit - outer IPv4 TTL / IPv6 hop-limit to write into the encap ++ header, 0 to use the VPP default (254 / 255). ++*/ ++typedef gre_tunnel_v3 ++{ ++ vl_api_gre_tunnel_type_t type; ++ vl_api_tunnel_mode_t mode; ++ vl_api_tunnel_encap_decap_flags_t flags; ++ u16 session_id; ++ u32 instance; ++ u32 outer_table_id; ++ vl_api_interface_index_t sw_if_index; ++ vl_api_address_t src; ++ vl_api_address_t dst; ++ u32 key; ++ u16 gre_protocol; ++ u8 hop_limit; ++}; ++ + /** \brief Add or delete a single GRE tunnel. + @param client_index - opaque cookie to identify the sender. + @param context - sender context, to match reply w/ request. +@@ -141,6 +175,33 @@ define gre_tunnel_add_del_v2_reply + vl_api_interface_index_t sw_if_index; + }; + ++/** \brief Add or delete a single GRE tunnel with key, GRE protocol override ++ and configurable outer TTL. ++ @param client_index - opaque cookie to identify the sender. ++ @param context - sender context, to match reply w/ request. ++ @param is_add - add if true, delete if false. ++ @param tunnel - tunnel definition to add or delete (v3). ++*/ ++define gre_tunnel_add_del_v3 ++{ ++ u32 client_index; ++ u32 context; ++ bool is_add; ++ vl_api_gre_tunnel_v3_t tunnel; ++}; ++ ++/** \brief Add or delete a single GRE tunnel (v3). ++ @param context - sender context, to match reply w/ request. ++ @param retval - return code for the request. ++ @param sw_if_index - the interface corresponding to the affected tunnel. ++*/ ++define gre_tunnel_add_del_v3_reply ++{ ++ u32 context; ++ i32 retval; ++ vl_api_interface_index_t sw_if_index; ++}; ++ + /** \brief Dump details of all or just a single GRE tunnel. + @param client_index - opaque cookie to identify the sender. + @param context - sender context, to match reply w/ request. +@@ -181,6 +242,18 @@ autoreply define gre_tunnel_dump_v2 + vl_api_interface_index_t sw_if_index; + }; + ++/** \brief Dump details of all or just a single GRE tunnel (v3). ++ @param client_index - opaque cookie to identify the sender. ++ @param context - sender context, to match reply w/ request. ++ @param sw_if_index - filter for tunnel of this interface index, ~0 for all. ++*/ ++autoreply define gre_tunnel_dump_v3 ++{ ++ u32 client_index; ++ u32 context; ++ vl_api_interface_index_t sw_if_index; ++}; ++ + + /** \brief Details response for one of the requested GRE tunnels. + @param context - sender context, to match reply w/ request. +@@ -228,6 +301,16 @@ define gre_tunnel_v2_details + vl_api_gre_tunnel_v2_t tunnel; + }; + ++/** \brief Details response for one of the requested GRE tunnels (v3). ++ @param context - sender context, to match reply w/ request. ++ @param tunnel - definition of the dumped tunnel (v3). ++*/ ++define gre_tunnel_details_v3 ++{ ++ u32 context; ++ vl_api_gre_tunnel_v3_t tunnel; ++}; ++ + /* + * Local Variables: + * eval: (c-set-style "gnu") +diff --git a/src/plugins/gre/gre.c b/src/plugins/gre/gre.c +index dcb44010a..1f36da22e 100644 +--- a/src/plugins/gre/gre.c ++++ b/src/plugins/gre/gre.c +@@ -229,7 +229,7 @@ gre_build_rewrite (vnet_main_t *vnm, u32 sw_if_index, vnet_link_t link_type, + h4 = (ip4_and_gre_header_t *) rewrite; + gre = &h4->gre; + h4->ip4.ip_version_and_header_length = 0x45; +- h4->ip4.ttl = 254; ++ h4->ip4.ttl = t->hop_limit ? t->hop_limit : 254; + h4->ip4.protocol = IP_PROTOCOL_GRE; + /* fixup ip4 header length and checksum after-the-fact */ + h4->ip4.src_address.as_u32 = t->tunnel_src.ip4.as_u32; +@@ -247,7 +247,7 @@ gre_build_rewrite (vnet_main_t *vnm, u32 sw_if_index, vnet_link_t link_type, + gre = &h6->gre; + h6->ip6.ip_version_traffic_class_and_flow_label = + clib_host_to_net_u32 (6 << 28); +- h6->ip6.hop_limit = 255; ++ h6->ip6.hop_limit = t->hop_limit ? t->hop_limit : 255; + h6->ip6.protocol = IP_PROTOCOL_GRE; + /* fixup ip6 header length and checksum after-the-fact */ + h6->ip6.src_address.as_u64[0] = t->tunnel_src.ip6.as_u64[0]; +@@ -258,13 +258,27 @@ gre_build_rewrite (vnet_main_t *vnm, u32 sw_if_index, vnet_link_t link_type, + + if (PREDICT_FALSE (t->type == GRE_TUNNEL_TYPE_ERSPAN)) + { +- gre->protocol = clib_host_to_net_u16 (GRE_PROTOCOL_erspan); +- gre->flags_and_version = clib_host_to_net_u16 (GRE_FLAGS_SEQUENCE); ++ if (t->gre_protocol) ++ { ++ /* SONiC Everflow "ERSPAN": emit plain GRE carrying the configured ++ * protocol (e.g. 0x88BE) with NO sequence number. The ERSPAN ++ * type-II shim is likewise suppressed in gre_encap_inline. This keeps ++ * the ERSPAN tunnel's working L2 delivery path while producing a ++ * plain-GRE-over-raw-L2 packet. */ ++ gre->protocol = clib_host_to_net_u16 (t->gre_protocol); ++ gre->flags_and_version = 0; ++ } ++ else ++ { ++ gre->protocol = clib_host_to_net_u16 (GRE_PROTOCOL_erspan); ++ gre->flags_and_version = clib_host_to_net_u16 (GRE_FLAGS_SEQUENCE); ++ } + } + else + { +- gre->protocol = +- clib_host_to_net_u16 (gre_proto_from_vnet_link (link_type)); ++ gre->protocol = clib_host_to_net_u16 ( ++ t->gre_protocol ? t->gre_protocol ++ : gre_proto_from_vnet_link (link_type)); + gre->flags_and_version = 0; // Clear flags first + /* Add key only for non-ERSPAN tunnels */ + if (gre_key_is_valid (t->gre_key)) +@@ -551,7 +565,7 @@ gre_encap_inline (vlib_main_t *vm, vlib_node_runtime_t *node, + vnet_buffer (b[0])->ip.adj_index[VLIB_TX] = adj_index[0]; + vnet_buffer (b[1])->ip.adj_index[VLIB_TX] = adj_index[1]; + +- if (type == GRE_TUNNEL_TYPE_ERSPAN) ++ if (type == GRE_TUNNEL_TYPE_ERSPAN && gt[0]->gre_protocol == 0) + { + /* Encap GRE seq# and ERSPAN type II header */ + erspan_t2_t *h0; +@@ -565,7 +579,7 @@ gre_encap_inline (vlib_main_t *vm, vlib_node_runtime_t *node, + h0->t2_u64 = hdr; + h0->t2.cos_en_t_session |= clib_host_to_net_u16 (gt[0]->session_id); + } +- if (type == GRE_TUNNEL_TYPE_ERSPAN) ++ if (type == GRE_TUNNEL_TYPE_ERSPAN && gt[1]->gre_protocol == 0) + { + /* Encap GRE seq# and ERSPAN type II header */ + erspan_t2_t *h0; +@@ -616,7 +630,7 @@ gre_encap_inline (vlib_main_t *vm, vlib_node_runtime_t *node, + + vnet_buffer (b[0])->ip.adj_index[VLIB_TX] = adj_index[0]; + +- if (type == GRE_TUNNEL_TYPE_ERSPAN) ++ if (type == GRE_TUNNEL_TYPE_ERSPAN && gt[0]->gre_protocol == 0) + { + /* Encap GRE seq# and ERSPAN type II header */ + erspan_t2_t *h0; +diff --git a/src/plugins/gre/gre.h b/src/plugins/gre/gre.h +index 4b71f0fe4..80926ff25 100644 +--- a/src/plugins/gre/gre.h ++++ b/src/plugins/gre/gre.h +@@ -234,6 +234,18 @@ typedef struct + + u32 dev_instance; /* Real device instance in tunnel vector */ + u32 user_instance; /* Instance name being shown to user */ ++ ++ /** ++ * Override GRE protocol/ethertype written into the encap header. 0 means ++ * derive it from the tunnel type / payload link type (default behaviour). ++ */ ++ u16 gre_protocol; ++ ++ /** ++ * Outer IPv4 TTL / IPv6 hop-limit written into the encap header. 0 means ++ * use the VPP default (254 for IPv4, 255 for IPv6). ++ */ ++ u8 hop_limit; + } gre_tunnel_t; + + typedef struct +@@ -374,6 +386,8 @@ typedef struct + u16 session_id; + gre_key_t gre_key; + tunnel_encap_decap_flags_t flags; ++ u16 gre_protocol; ++ u8 hop_limit; + } vnet_gre_tunnel_add_del_args_t; + + extern int vnet_gre_tunnel_add_del (vnet_gre_tunnel_add_del_args_t *a, +diff --git a/src/plugins/gre/gre_api.c b/src/plugins/gre/gre_api.c +index df8db48e4..8d9a69ba6 100644 +--- a/src/plugins/gre/gre_api.c ++++ b/src/plugins/gre/gre_api.c +@@ -167,6 +167,63 @@ out: + ({ rmp->sw_if_index = ntohl (sw_if_index); })); + } + ++static void ++vl_api_gre_tunnel_add_del_v3_t_handler (vl_api_gre_tunnel_add_del_v3_t *mp) ++{ ++ vnet_gre_tunnel_add_del_args_t _a = {}, *a = &_a; ++ vl_api_gre_tunnel_add_del_v3_reply_t *rmp; ++ tunnel_encap_decap_flags_t flags; ++ u32 sw_if_index = ~0; ++ ip46_type_t itype[2]; ++ int rv = 0; ++ ++ itype[0] = ip_address_decode (&mp->tunnel.src, &a->src); ++ itype[1] = ip_address_decode (&mp->tunnel.dst, &a->dst); ++ ++ if (itype[0] != itype[1]) ++ { ++ rv = VNET_API_ERROR_INVALID_PROTOCOL; ++ goto out; ++ } ++ ++ if (ip46_address_is_equal (&a->src, &a->dst)) ++ { ++ rv = VNET_API_ERROR_SAME_SRC_DST; ++ goto out; ++ } ++ ++ rv = gre_tunnel_type_decode (mp->tunnel.type, &a->type); ++ ++ if (rv) ++ goto out; ++ ++ rv = tunnel_mode_decode (mp->tunnel.mode, &a->mode); ++ ++ if (rv) ++ goto out; ++ ++ rv = tunnel_encap_decap_flags_decode (mp->tunnel.flags, &flags); ++ ++ if (rv) ++ goto out; ++ ++ a->is_add = mp->is_add; ++ a->is_ipv6 = (itype[0] == IP46_TYPE_IP6); ++ a->instance = ntohl (mp->tunnel.instance); ++ a->session_id = ntohs (mp->tunnel.session_id); ++ a->outer_table_id = ntohl (mp->tunnel.outer_table_id); ++ a->flags = flags; ++ a->gre_key = ntohl (mp->tunnel.key); // Key field present in v3 API ++ a->gre_protocol = ntohs (mp->tunnel.gre_protocol); ++ a->hop_limit = mp->tunnel.hop_limit; ++ ++ rv = vnet_gre_tunnel_add_del (a, &sw_if_index); ++ ++out: ++ REPLY_MACRO2 (VL_API_GRE_TUNNEL_ADD_DEL_V3_REPLY, ++ ({ rmp->sw_if_index = ntohl (sw_if_index); })); ++} ++ + static void + send_gre_tunnel_details (gre_tunnel_t *t, vl_api_gre_tunnel_dump_t *mp) + { +@@ -240,6 +297,32 @@ send_gre_tunnel_v2_details (gre_tunnel_t *t, vl_api_gre_tunnel_v2_dump_t *mp) + })); + } + ++static void ++send_gre_tunnel_details_v3 (gre_tunnel_t *t, vl_api_gre_tunnel_dump_v3_t *mp) ++{ ++ vl_api_gre_tunnel_details_v3_t *rmp; ++ ++ REPLY_MACRO_DETAILS2 ( ++ VL_API_GRE_TUNNEL_DETAILS_V3, ({ ++ ip_address_encode (&t->tunnel_src, IP46_TYPE_ANY, &rmp->tunnel.src); ++ ip_address_encode (&t->tunnel_dst.fp_addr, IP46_TYPE_ANY, ++ &rmp->tunnel.dst); ++ ++ rmp->tunnel.outer_table_id = htonl ( ++ fib_table_get_table_id (t->outer_fib_index, t->tunnel_dst.fp_proto)); ++ ++ rmp->tunnel.type = gre_tunnel_type_encode (t->type); ++ rmp->tunnel.mode = tunnel_mode_encode (t->mode); ++ rmp->tunnel.flags = tunnel_encap_decap_flags_encode (t->flags); ++ rmp->tunnel.instance = htonl (t->user_instance); ++ rmp->tunnel.sw_if_index = htonl (t->sw_if_index); ++ rmp->tunnel.session_id = htons (t->session_id); ++ rmp->tunnel.key = htonl (t->gre_key); ++ rmp->tunnel.gre_protocol = htons (t->gre_protocol); ++ rmp->tunnel.hop_limit = t->hop_limit; ++ })); ++} ++ + static void + vl_api_gre_tunnel_dump_t_handler (vl_api_gre_tunnel_dump_t *mp) + { +@@ -344,6 +427,40 @@ vl_api_gre_tunnel_v2_dump_t_handler (vl_api_gre_tunnel_v2_dump_t *mp) + } + } + ++static void ++vl_api_gre_tunnel_dump_v3_t_handler (vl_api_gre_tunnel_dump_v3_t *mp) ++{ ++ vl_api_registration_t *reg; ++ gre_main_t *gm = &gre_main; ++ gre_tunnel_t *t; ++ u32 sw_if_index; ++ ++ reg = vl_api_client_index_to_registration (mp->client_index); ++ if (!reg) ++ return; ++ ++ sw_if_index = ntohl (mp->sw_if_index); ++ ++ if (~0 == sw_if_index) ++ { ++ pool_foreach (t, gm->tunnels) ++ { ++ send_gre_tunnel_details_v3 (t, mp); ++ } ++ } ++ ++ else ++ { ++ if ((sw_if_index >= vec_len (gm->tunnel_index_by_sw_if_index)) || ++ (~0 == gm->tunnel_index_by_sw_if_index[sw_if_index])) ++ { ++ return; ++ } ++ t = &gm->tunnels[gm->tunnel_index_by_sw_if_index[sw_if_index]]; ++ send_gre_tunnel_details_v3 (t, mp); ++ } ++} ++ + /* + * gre_api_hookup + * Add vpe's API message handlers to the table. +diff --git a/src/plugins/gre/interface.c b/src/plugins/gre/interface.c +index d7c86e526..4947462dc 100644 +--- a/src/plugins/gre/interface.c ++++ b/src/plugins/gre/interface.c +@@ -403,6 +403,8 @@ vnet_gre_tunnel_add (vnet_gre_tunnel_add_del_args_t *a, u32 outer_fib_index, + t->type = a->type; + t->mode = a->mode; + t->flags = a->flags; ++ t->gre_protocol = a->gre_protocol; ++ t->hop_limit = a->hop_limit; + if (t->type == GRE_TUNNEL_TYPE_ERSPAN) + t->session_id = a->session_id; + diff --git a/vppbld/patches/0021-vnet-guard-stale-interface-state-on-teardown.patch b/vppbld/patches/0021-vnet-guard-stale-interface-state-on-teardown.patch new file mode 100644 index 00000000..ee569f7d --- /dev/null +++ b/vppbld/patches/0021-vnet-guard-stale-interface-state-on-teardown.patch @@ -0,0 +1,123 @@ +From 473ca2c6d0880795ffc2accaab17b17bf1bd27d3 Mon Sep 17 00:00:00 2001 +From: taran9999 +Date: Wed, 9 Sep 2026 13:50:59 -0700 +Subject: vnet: guard the drop node and L2 midchain against stale interface + state + +SONiC Everflow re-points a mirror session by deleting and recreating its +GRE tunnel, so interfaces come and go underneath in-flight packets and +mirror clones. Two places in the forwarding graph assume the state they +are about to touch still exists, and both have been observed killing a +worker thread during that churn. + +interface_drop_punt() indexes the per-interface simple counter, the +sw_interface pool and the drop feature arc by the buffer's RX +sw_if_index with no bounds check. A dropped packet or clone can carry +the RX index of an interface that was just removed, which turns into an +out-of-bounds counter write and a pool dereference at a high heap +address (SIGSEGV in interface_drop_fn). Coalesce an RX index that no +longer maps to a live interface to sw_if_index 0 (local0, which always +exists) as it is collected, so every downstream lookup is in range. The +bond original-member index recorded for drop accounting is validated the +same way. vnet_sw_interface_is_valid() is a pool_is_free_index() bitmap +test that never dereferences, so it is safe on a stale index, and this +is the cold drop path. + +adj_l2_rewrite_inline() calls adj->sub_type.midchain.fixup_func() +unconditionally, unlike adj_midchain_fixup() in adj_dp.h which first +checks the pointer. A GRE/ERSPAN L2 midchain adjacency torn down while +packets are still forwarded for it leaves fixup_func == NULL, and the +next packet jumps to PC 0. Match the NULL check adj_dp.h already +performs, so a stale adjacency forwards a malformed frame that is +dropped downstream instead of crashing the dataplane. +--- + src/vnet/adj/adj_l2.c | 9 ++++++++- + src/vnet/interface_output.c | 34 ++++++++++++++++++++++++++++------ + 2 files changed, 36 insertions(+), 7 deletions(-) + +diff --git a/src/vnet/adj/adj_l2.c b/src/vnet/adj/adj_l2.c +index 7e9e985ec..7058dcaba 100644 +--- a/src/vnet/adj/adj_l2.c ++++ b/src/vnet/adj/adj_l2.c +@@ -105,7 +105,14 @@ adj_l2_rewrite_inline (vlib_main_t * vm, + p0->current_length += rw_len0; + tx_sw_if_index0 = adj0[0].rewrite_header.sw_if_index; + +- if (is_midchain) ++/* ++ * A midchain adjacency can be torn down while packets are ++ * still in flight for it, leaving fixup_func == NULL. ++ * Calling it unconditionally jumps through a NULL pointer ++ * and kills the worker. adj_midchain_fixup() in adj_dp.h ++ * already performs this check; match it here. ++ */ ++ if (is_midchain && adj0->sub_type.midchain.fixup_func) + { + adj0->sub_type.midchain.fixup_func( + vm, adj0, p0, +diff --git a/src/vnet/interface_output.c b/src/vnet/interface_output.c +index 8817157bb..28495edf9 100644 +--- a/src/vnet/interface_output.c ++++ b/src/vnet/interface_output.c +@@ -929,6 +929,22 @@ drop_catchup_trace (vlib_main_t * vm, + } + } + ++/* During interface/tunnel teardown churn (e.g. mirror-clone packets whose ++ * source interface was just deleted) a buffer can reach the drop/punt node ++ * carrying an RX sw_if_index that no longer maps to a live interface. The ++ * per-interface counter, super-interface and drop-feature-arc lookups below ++ * index vectors/pools by that index without any bounds checking, so a stale ++ * index causes an out-of-bounds access and a worker SIGSEGV. Coalesce any ++ * invalid index to sw_if_index 0 (local0), which always exists, before it is ++ * used for a counter/pool/feature-arc lookup. */ ++static_always_inline u32 ++interface_drop_punt_safe_sw_if_index (vnet_main_t * vnm, u32 sw_if_index) ++{ ++ if (PREDICT_FALSE (!vnet_sw_interface_is_valid (vnm, sw_if_index))) ++ return 0; ++ return sw_if_index; ++} ++ + static_always_inline uword + interface_drop_punt (vlib_main_t * vm, + vlib_node_runtime_t * node, +@@ -1007,10 +1023,14 @@ interface_drop_punt (vlib_main_t * vm, + vlib_prefetch_buffer_header (b[6], LOAD); + vlib_prefetch_buffer_header (b[7], LOAD); + } +- sw_if_index[0] = vnet_buffer (b[0])->sw_if_index[VLIB_RX]; +- sw_if_index[1] = vnet_buffer (b[1])->sw_if_index[VLIB_RX]; +- sw_if_index[2] = vnet_buffer (b[2])->sw_if_index[VLIB_RX]; +- sw_if_index[3] = vnet_buffer (b[3])->sw_if_index[VLIB_RX]; ++ sw_if_index[0] = interface_drop_punt_safe_sw_if_index ++ (vnm, vnet_buffer (b[0])->sw_if_index[VLIB_RX]); ++ sw_if_index[1] = interface_drop_punt_safe_sw_if_index ++ (vnm, vnet_buffer (b[1])->sw_if_index[VLIB_RX]); ++ sw_if_index[2] = interface_drop_punt_safe_sw_if_index ++ (vnm, vnet_buffer (b[2])->sw_if_index[VLIB_RX]); ++ sw_if_index[3] = interface_drop_punt_safe_sw_if_index ++ (vnm, vnet_buffer (b[3])->sw_if_index[VLIB_RX]); + + sw_if_index += 4; + n_left -= 4; +@@ -1018,7 +1038,8 @@ interface_drop_punt (vlib_main_t * vm, + } + while (n_left) + { +- sw_if_index[0] = vnet_buffer (b[0])->sw_if_index[VLIB_RX]; ++ sw_if_index[0] = interface_drop_punt_safe_sw_if_index ++ (vnm, vnet_buffer (b[0])->sw_if_index[VLIB_RX]); + + sw_if_index += 1; + n_left -= 1; +@@ -1057,7 +1078,8 @@ interface_drop_punt (vlib_main_t * vm, + { + vlib_buffer_t *ob = bufs[orig_off + j]; + u32 orig = vnet_buffer2 (ob)->orig_rx_sw_if_index; +- if (orig && orig != sw_if_index[0]) ++ if (orig && orig != sw_if_index[0] ++ && vnet_sw_interface_is_valid (vnm, orig)) + vlib_increment_simple_counter (cm, thread_index, orig, 1); + } + } diff --git a/vppbld/patches/series b/vppbld/patches/series index 80f09fc0..ea0bc2b8 100644 --- a/vppbld/patches/series +++ b/vppbld/patches/series @@ -39,3 +39,22 @@ # rule be scoped to specific ports, so SAI_ACL_ENTRY_ATTR_FIELD_IN_PORTS # no longer has to be silently dropped by the SAI layer. 0018-acl-match-on-ingress-interface.patch +# 19. acl: PERMIT_MIRROR action (is_permit == 3) for SONiC Everflow. Packed +# mirror_action carries the destination sw_if_index in bits 27:0 and +# action flags in bits 31:28; ACL_MIRROR_F_DEFERRED hands the clone to a +# platform plugin registered via acl_register_deferred_mirror_stamp() +# (resolved with vlib_get_plugin_symbol, so no link dependency either +# way), otherwise the clone is taken in the ACL node. Includes the +# SPAN_CLONE re-mirror guard and the mirror-interface liveness guard. +# Per-port scoping reuses in_sw_if_index from patch 0018. +0019-acl-add-permit-mirror-action-for-everflow.patch +# 20. gre: gre_tunnel_v3 API adding a per-tunnel outer TTL (hop_limit) and a +# GRE protocol override. gre_protocol also selects a plain-GRE mode for +# ERSPAN tunnels (no sequence number, no type-II shim) so Everflow emits +# GRE 0x88BE over the raw mirrored L2 frame. gre_tunnel_v2 unchanged. +0020-gre-per-tunnel-ttl-and-protocol-override.patch +# 21. vnet: guard interface_drop_punt against a stale RX sw_if_index and the +# adj L2 midchain against a NULL fixup_func. Everflow re-points a mirror +# session by deleting/recreating its GRE tunnel, so both paths can be +# reached with interface state that has just been freed. +0021-vnet-guard-stale-interface-state-on-teardown.patch diff --git a/vppbld/plugins/sonic_ext/CMakeLists.txt b/vppbld/plugins/sonic_ext/CMakeLists.txt index 4d9d170a..eccb47b6 100644 --- a/vppbld/plugins/sonic_ext/CMakeLists.txt +++ b/vppbld/plugins/sonic_ext/CMakeLists.txt @@ -23,6 +23,7 @@ add_vpp_plugin(sonic_ext l2_vlan_filter_node.c ip2me_node.c cli.c + egress_mirror_node.c API_FILES sonic_ext.api diff --git a/vppbld/plugins/sonic_ext/egress_mirror_node.c b/vppbld/plugins/sonic_ext/egress_mirror_node.c new file mode 100644 index 00000000..19c7b271 --- /dev/null +++ b/vppbld/plugins/sonic_ext/egress_mirror_node.c @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2026 SONiC-VPP contributors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include + +#include + +/* Claims the ACL plugin's deferred mirror handoff: record the destination in + * the sonic_ext cookie so sonic-ext-egress-mirror can clone the packet once it + * reaches interface-output in its final wire form. */ +int +sonic_ext_acl_deferred_mirror_stamp (vlib_buffer_t *b, u32 rx_sw_if_index, + u32 mirror_sw_if_index) +{ + sonic_ext_buffer_opaque_t *opaque = sonic_ext_buffer (b); + + /* magic/orig_rx_sw_if_index/orig_vlan_tag are owned by sonic-ext-capture and + * consumed by the punt redirect nodes. A matched packet may still be punted + * to the host, so only claim mirror_sw_if_index here. */ + (void) rx_sw_if_index; + opaque->mirror_sw_if_index = mirror_sw_if_index; + b->flags |= SONIC_EXT_BUFFER_F_MIRROR_PENDING; + + return 1; +} + +VLIB_NODE_FN (sonic_ext_egress_mirror_node) (vlib_main_t *vm, + vlib_node_runtime_t *node, + vlib_frame_t *frame) +{ + vnet_main_t *vnm = vnet_get_main (); + u32 n_left_from = frame->n_vectors; + u32 *from = vlib_frame_vector_args (frame); + u32 next_index = node->cached_next_index; + + while (n_left_from > 0) + { + u32 *to_next; + 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 buffer_index = from[0]; + vlib_buffer_t *buffer = vlib_get_buffer (vm, buffer_index); + u32 next = 0; + + from++; + n_left_from--; + to_next[0] = buffer_index; + to_next++; + n_left_to_next--; + + vnet_feature_next (&next, buffer); + + if (PREDICT_FALSE ( + buffer->flags & SONIC_EXT_BUFFER_F_MIRROR_PENDING)) + { + sonic_ext_buffer_opaque_t *opaque = sonic_ext_buffer (buffer); + u32 mirror_sw_if_index = opaque->mirror_sw_if_index; + + buffer->flags &= ~SONIC_EXT_BUFFER_F_MIRROR_PENDING; + opaque->mirror_sw_if_index = SONIC_EXT_INVALID_SW_IF_INDEX; + + /* MIRROR_PENDING is set only by the stamp above, so it is the + * validity signal; magic may already have been consumed by a + * punt redirect node. */ + if (mirror_sw_if_index != SONIC_EXT_INVALID_SW_IF_INDEX && + !(buffer->flags & VNET_BUFFER_F_SPAN_CLONE) && + vnet_sw_interface_is_valid (vnm, mirror_sw_if_index) && + vnet_sw_interface_is_up (vnm, mirror_sw_if_index)) + { + vlib_buffer_t *clone = vlib_buffer_copy (vm, buffer); + + if (PREDICT_TRUE (clone != 0)) + { + vlib_frame_t *mirror_frame; + u32 *mirror_to_next; + + vnet_buffer (clone)->sw_if_index[VLIB_TX] = + mirror_sw_if_index; + clone->flags |= VNET_BUFFER_F_SPAN_CLONE; + + mirror_frame = vnet_get_frame_to_sw_interface ( + vnm, mirror_sw_if_index); + mirror_to_next = vlib_frame_vector_args (mirror_frame); + mirror_to_next += mirror_frame->n_vectors; + mirror_to_next[0] = vlib_get_buffer_index (vm, clone); + mirror_frame->n_vectors++; + vnet_put_frame_to_sw_interface ( + vnm, mirror_sw_if_index, mirror_frame); + } + } + } + + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next, + n_left_to_next, buffer_index, next); + } + + vlib_put_next_frame (vm, node, next_index, n_left_to_next); + } + + return frame->n_vectors; +} + +VLIB_REGISTER_NODE (sonic_ext_egress_mirror_node) = { + .name = "sonic-ext-egress-mirror", + .vector_size = sizeof (u32), + .type = VLIB_NODE_TYPE_INTERNAL, + .n_next_nodes = 0, +}; + +VNET_FEATURE_INIT (sonic_ext_egress_mirror_feature, static) = { + .arc_name = "interface-output", + .node_name = "sonic-ext-egress-mirror", + /* Clone the fully-encapsulated wire form, then let the packet continue. + * Run before aggr-tap-redirect (which may rewrite VLIB_TX) and the arc + * end so both features coexist on interface-output (HLD 12.5). */ + .runs_before = + VNET_FEATURES ("sonic-ext-aggr-tap-redirect", "interface-output-arc-end"), +}; \ No newline at end of file diff --git a/vppbld/plugins/sonic_ext/sonic_ext.api b/vppbld/plugins/sonic_ext/sonic_ext.api index 476bac98..2e3802e5 100644 --- a/vppbld/plugins/sonic_ext/sonic_ext.api +++ b/vppbld/plugins/sonic_ext/sonic_ext.api @@ -33,3 +33,19 @@ autoreply define sonic_ext_ip2me_enable_disable vl_api_interface_index_t sw_if_index; bool enable; }; + +/** \brief Install / remove a deferred egress-mirror (Everflow MIRROR_EGRESS) + action. Refcounted: enable=1 on the first action enables the + sonic-ext-egress-mirror feature on every interface-output arc; enable=0 + on the last removes it. The mirror destination itself is carried per + packet by the ACL dataplane node, not by this message. + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param enable - 1 to install an action, 0 to remove one +*/ +autoreply define sonic_ext_egress_mirror_enable_disable +{ + u32 client_index; + u32 context; + bool enable; +}; diff --git a/vppbld/plugins/sonic_ext/sonic_ext.c b/vppbld/plugins/sonic_ext/sonic_ext.c index 63d9305e..5154ab31 100644 --- a/vppbld/plugins/sonic_ext/sonic_ext.c +++ b/vppbld/plugins/sonic_ext/sonic_ext.c @@ -22,7 +22,9 @@ #include #include #include +#include #include +#include sonic_ext_main_t sonic_ext_main; @@ -431,6 +433,125 @@ sonic_ext_lcp_pair_del_cb (lcp_itf_pair_t *lip) sonic_ext_aggr_tap_redirect_enable_disable (lip->lip_host_sw_if_index, 0); } +/* + * Deferred egress mirror (Everflow MIRROR_EGRESS). + * + * The ACL dataplane node stamps the mirror destination into the per-buffer + * sonic_ext cookie and sets MIRROR_PENDING; the sonic-ext-egress-mirror + * feature on interface-output does the late post-route/post-encap clone. + * A mirrored data packet can egress any port, so the feature is enabled on + * *every* interface -- but only while at least one MIRROR_EGRESS action is + * installed, tracked by active_egress_mirror_actions so the arc cost is + * paid only when the feature is in use (HLD 12.4). + */ +static int +sonic_ext_egress_mirror_arc_set (int enable) +{ + vnet_main_t *vnm = vnet_get_main (); + vnet_interface_main_t *im = &vnm->interface_main; + vnet_sw_interface_t *sw_if; + u32 *changed = 0; + int rv = 0; + + enable = !!enable; + if (sonic_ext_main.egress_mirror_arc_enabled == enable) + return 0; + + pool_foreach (sw_if, im->sw_interfaces) + { + rv = vnet_feature_enable_disable ("interface-output", + "sonic-ext-egress-mirror", + sw_if->sw_if_index, enable, 0, 0); + if (rv) + goto rollback; + vec_add1 (changed, sw_if->sw_if_index); + } + + sonic_ext_main.egress_mirror_arc_enabled = enable; + vec_free (changed); + return 0; + +rollback: + while (vec_len (changed) > 0) + { + u32 sw_if_index = vec_pop (changed); + vnet_feature_enable_disable ("interface-output", + "sonic-ext-egress-mirror", sw_if_index, + !enable, 0, 0); + } + vec_free (changed); + return rv; +} + +int +sonic_ext_egress_mirror_enable_disable (u8 enable) +{ + sonic_ext_main_t *sem = &sonic_ext_main; + + if (enable) + { + if (sem->active_egress_mirror_actions++ == 0) + return sonic_ext_egress_mirror_arc_set (1); + return 0; + } + + if (sem->active_egress_mirror_actions == 0) + return 0; /* balanced disable underflow guard */ + if (--sem->active_egress_mirror_actions == 0) + return sonic_ext_egress_mirror_arc_set (0); + return 0; +} + +/* New interfaces created while at least one MIRROR_EGRESS action is + * installed must have the feature enabled too, since the mirrored data + * packet could egress the new port. */ +static clib_error_t * +sonic_ext_egress_mirror_sw_interface_add_del (vnet_main_t *vnm, + u32 sw_if_index, u32 is_add) +{ + int rv; + (void) vnm; + + if (!is_add || !sonic_ext_main.egress_mirror_arc_enabled) + return 0; + + rv = vnet_feature_enable_disable ("interface-output", + "sonic-ext-egress-mirror", sw_if_index, 1, + 0, 0); + if (rv) + return clib_error_return ( + 0, "sonic_ext: enable egress mirror on sw_if_index %u failed: %d", + sw_if_index, rv); + return 0; +} + +VNET_SW_INTERFACE_ADD_DEL_FUNCTION ( + sonic_ext_egress_mirror_sw_interface_add_del); + +/* + * Claim the ACL plugin's deferred (egress-arc) mirror clone for Everflow + * MIRROR_EGRESS. Resolved at runtime so sonic_ext neither links against nor + * requires the ACL plugin: if it is not loaded, the ACL side simply clones + * immediately instead. + */ +static void +sonic_ext_register_acl_deferred_mirror (void) +{ + void (*acl_register) (acl_deferred_mirror_stamp_fn); + + acl_register = vlib_get_plugin_symbol ( + "acl_plugin.so", "acl_register_deferred_mirror_stamp"); + + if (acl_register == 0) + { + clib_warning ("sonic_ext: acl plugin has no deferred mirror hook; " + "Everflow egress mirroring will clone on the ACL arc"); + return; + } + + acl_register (sonic_ext_acl_deferred_mirror_stamp); +} + static clib_error_t * sonic_ext_init (vlib_main_t *vm) { @@ -452,6 +573,8 @@ sonic_ext_init (vlib_main_t *vm) sonic_ext_set_punt_via_member (1); sonic_ext_set_host_xc (1); + sonic_ext_register_acl_deferred_mirror (); + return 0; } diff --git a/vppbld/plugins/sonic_ext/sonic_ext.h b/vppbld/plugins/sonic_ext/sonic_ext.h index b21e0966..2ea256db 100644 --- a/vppbld/plugins/sonic_ext/sonic_ext.h +++ b/vppbld/plugins/sonic_ext/sonic_ext.h @@ -56,6 +56,15 @@ */ #define SONIC_EXT_BUFFER_MAGIC 0x534e4358u /* 'SNCX' */ +/* --- Deferred egress mirror (Everflow MIRROR_EGRESS) extension ----------- * + * mirror_sw_if_index carries the ERSPAN/GRE tunnel the deferred clone is + * sent to; the ACL dataplane node stamps it into the cookie alongside the + * magic when a matched rule has ACL_MIRROR_F_DEFERRED set. */ +#define SONIC_EXT_INVALID_SW_IF_INDEX ~0U +#define SONIC_EXT_INVALID_VLAN_TAG ~0U +/* Buffer flag: a deferred egress-mirror clone is pending on this packet. */ +#define SONIC_EXT_BUFFER_F_MIRROR_PENDING VNET_BUFFER_F_AVAIL1 + /* * orig_vlan_tag: outermost 802.1Q (or 802.1ad) tag observed on the * wire frame at sonic-ext-capture time, stored as raw 4 bytes in @@ -85,6 +94,7 @@ typedef struct u32 magic; u32 orig_rx_sw_if_index; u32 orig_vlan_tag; + u32 mirror_sw_if_index; /* deferred egress mirror dst; ~0 = none */ } sonic_ext_buffer_opaque_t; STATIC_ASSERT (sizeof (sonic_ext_buffer_opaque_t) <= @@ -112,6 +122,13 @@ typedef struct u8 host_xc_enabled; u8 glean_redirect_enabled; + /* Deferred egress mirror: set once the sonic-ext-egress-mirror feature + * has been enabled on all interface-output arcs; gated by a refcount of + * installed MIRROR_EGRESS actions so the arc cost is only paid while the + * feature is in use (HLD 12.4). */ + u8 egress_mirror_arc_enabled; + u32 active_egress_mirror_actions; + /* Counters (per-feature, per-thread accounting kept in node * registrations; these are summary counters for `show sonic-ext`). */ u64 captures; @@ -132,6 +149,7 @@ extern vlib_node_registration_t sonic_ext_l2_trap_fixup_node; extern vlib_node_registration_t sonic_ext_l2_vlan_filter_node; extern vlib_node_registration_t sonic_ext_ip2me_ip4_node; extern vlib_node_registration_t sonic_ext_ip2me_ip6_node; +extern vlib_node_registration_t sonic_ext_egress_mirror_node; /* Enable / disable sonic-ext-capture on a given interface. No-op if * the capture sidecar is not yet initialized. */ @@ -189,4 +207,17 @@ int sonic_ext_phy_is_bvi (u32 phy_sw_if_index); * punts to the bond master host tap. */ int sonic_ext_phy_is_bond (u32 phy_sw_if_index); +/* Deferred egress mirror refcount toggle. enable=1 installs a + * MIRROR_EGRESS action (enabling sonic-ext-egress-mirror on every + * interface-output arc on the 0->1 transition); enable=0 removes one + * (disabling on the 1->0 transition). Driven from the SAI-VPP layer per + * Everflow egress mirror session. */ +int sonic_ext_egress_mirror_enable_disable (u8 enable); + +/* Registered with the ACL plugin as its deferred mirror stamper: records the + * mirror destination in the sonic_ext cookie for the late interface-output + * clone. Always accepts, so returns 1. */ +int sonic_ext_acl_deferred_mirror_stamp (vlib_buffer_t *b, u32 rx_sw_if_index, + u32 mirror_sw_if_index); + #endif /* __included_sonic_ext_h__ */ diff --git a/vppbld/plugins/sonic_ext/sonic_ext_api.c b/vppbld/plugins/sonic_ext/sonic_ext_api.c index 997be95d..f894bcb0 100644 --- a/vppbld/plugins/sonic_ext/sonic_ext_api.c +++ b/vppbld/plugins/sonic_ext/sonic_ext_api.c @@ -56,6 +56,16 @@ vl_api_sonic_ext_ip2me_enable_disable_t_handler ( REPLY_MACRO (VL_API_SONIC_EXT_IP2ME_ENABLE_DISABLE_REPLY); } +static void +vl_api_sonic_ext_egress_mirror_enable_disable_t_handler ( + vl_api_sonic_ext_egress_mirror_enable_disable_t *mp) +{ + vl_api_sonic_ext_egress_mirror_enable_disable_reply_t *rmp; + int rv = sonic_ext_egress_mirror_enable_disable (mp->enable ? 1 : 0); + + REPLY_MACRO (VL_API_SONIC_EXT_EGRESS_MIRROR_ENABLE_DISABLE_REPLY); +} + /* API definitions */ #include