diff --git a/rules/vpp.mk b/rules/vpp.mk index f4c65bfc..1d59ed0c 100644 --- a/rules/vpp.mk +++ b/rules/vpp.mk @@ -7,7 +7,7 @@ VPP_VERSION_BASE = 2606 # https://packages.buildkite.com/sonic-vpp/vpp; if the suffix isn't bumped, # downstream sonic-buildimage builds will silently pull stale debs that # pre-date the new patch series and end up with VPP/SAI CRC drift. -VPP_VERSION = $(VPP_VERSION_BASE)-0.4 +VPP_VERSION = $(VPP_VERSION_BASE)-0.5 VPP_VERSION_SONIC = $(VPP_VERSION)+b1sonic1 VPP_SRC_PATH = platform/vpp/vppbld diff --git a/vppbld/plugins/sonic_ext/CMakeLists.txt b/vppbld/plugins/sonic_ext/CMakeLists.txt index 40562f6e..96a66617 100644 --- a/vppbld/plugins/sonic_ext/CMakeLists.txt +++ b/vppbld/plugins/sonic_ext/CMakeLists.txt @@ -16,6 +16,7 @@ add_vpp_plugin(sonic_ext sonic_ext.c capture_node.c aggr_tap_redirect_node.c + glean_redirect_node.c host_xc_node.c l2_trap_fixup_node.c cli.c diff --git a/vppbld/plugins/sonic_ext/cli.c b/vppbld/plugins/sonic_ext/cli.c index d0de38b4..7bd1fc7f 100644 --- a/vppbld/plugins/sonic_ext/cli.c +++ b/vppbld/plugins/sonic_ext/cli.c @@ -96,6 +96,7 @@ show_sonic_ext_command_fn (vlib_main_t *vm, unformat_input_t *input, sem->host_xc ? "on" : "off"); vlib_cli_output (vm, " captures : %llu", sem->captures); vlib_cli_output (vm, " aggr-tap redir : %llu", sem->aggr_tap_redirects); + vlib_cli_output (vm, " glean redirect : %llu", sem->glean_redirects); vlib_cli_output (vm, " host-xc direct : %llu", sem->host_xc_direct); vlib_cli_output (vm, " l2 trap fixups : %llu", sem->l2_trap_fixups); return 0; diff --git a/vppbld/plugins/sonic_ext/glean_redirect_node.c b/vppbld/plugins/sonic_ext/glean_redirect_node.c new file mode 100644 index 00000000..7d9eab16 --- /dev/null +++ b/vppbld/plugins/sonic_ext/glean_redirect_node.c @@ -0,0 +1,486 @@ +/* + * 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 +#include +#include +#include + +/* + * sonic-ext-glean-redirect + * + * Feature node on the `ip4-drop` / `ip6-drop` arcs. Enabled once, + * globally (sw_if_index 0), because those arcs are always dispatched + * with a hardcoded sw_if_index 0 -- see ip_drop_or_punt() in + * ip_punt_drop.h. Run-time control is sem->punt_via_member. + * + * The node therefore sees every dropped packet in the system and does + * its own filtering; see sonic_ext_glean_src_nodes below. + * + * PROBLEM. A transit packet whose next-hop is a directly-connected + * neighbour that has NOT yet been resolved hits a *glean* (connected + * prefix) or *arp* (incomplete /32) adjacency. VPP's `ip4-glean` / + * `ip4-arp` node emits its own ARP request and then drops the packet + * to `ip4-drop`. Because SONiC-VPP does not use lcp-sync and relies + * on SAI for neighbours, the ARP *reply* is punted to the kernel tap + * but the kernel treats it as unsolicited (it never sent the request, + * VPP did) and, with arp_accept=0, ignores it. The neighbour is + * therefore never learned by the kernel, never programmed into SAI, + * and never installed in VPP -- so transit forwarding to freshly-seen + * sub-interface / interface neighbours is black-holed. (for-us + * traffic works because it needs no egress ARP.) + * + * FIX (hardware / Fred model: "the NPU punts the packet to CPU on an + * unresolved next-hop; the control plane resolves it"). Steal the + * would-be-dropped packet and deliver it to Linux on the *ingress* + * phy's LCP host tap, re-pushing the wire VLAN tag from the capture + * snapshot. The kernel 8021q layer demuxes it to the ingress + * sub-interface netdev, the kernel routes it, and -- crucially -- + * sends its OWN, *solicited*, ARP. The reply is accepted, the kernel + * neighbour is learned, neighsyncd -> SAI -> SwitchVppNbr installs it + * in VPP, and subsequent packets forward in the data plane. No + * arp_accept, no pre-warm, no lcp-sync. + * + * We redirect to the ingress phy's host tap (recovered from the + * capture cookie) rather than the egress interface so that kernel + * reverse-path filtering passes: the packet's source is a connected + * neighbour of the ingress sub-interface. + * + * A per-(adjacency) throttle rate-limits the redirect so a genuinely + * unreachable destination cannot flood the CPU: one punt copy per + * throttle window is enough to (re)arm the kernel's own ARP. Once the + * neighbour resolves via SAI the packets forward in VPP and never + * reach this node again. + */ + +typedef struct +{ + u32 orig_rx_sw_if_index; + u32 host_tap_sw_if_index; + u16 pushed_vlan_id; + u8 redirected; +} sonic_ext_glean_redirect_trace_t; + +static u8 * +format_sonic_ext_glean_redirect_trace (u8 *s, va_list *args) +{ + CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); + CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); + sonic_ext_glean_redirect_trace_t *t = + va_arg (*args, sonic_ext_glean_redirect_trace_t *); + + s = format (s, + "SONIC-EXT-GLEAN-REDIRECT: orig-rx %u host-tap %u vlan %u %s", + t->orig_rx_sw_if_index, t->host_tap_sw_if_index, + t->pushed_vlan_id, t->redirected ? "REDIRECTED" : "passthru"); + return s; +} + +#define foreach_sonic_ext_glean_redirect_error \ + _ (REDIRECTED, "unresolved-nexthop punt redirected to ingress host tap") \ + _ (NOT_NBR_NODE, "not dropped by a neighbour-resolution node -- passed " \ + "through") \ + _ (NOT_GLEAN, "not a glean/arp-incomplete drop -- passed through") \ + _ (NO_COOKIE, "no capture cookie -- passed through") \ + _ (NO_LCP, "no LCP pair for ingress phy -- passed through") \ + _ (THROTTLED, "throttled -- passed through to drop") \ + _ (DISABLED, "punt-via-member disabled -- passed through") + +typedef enum +{ +#define _(sym, str) SONIC_EXT_GLEAN_REDIRECT_ERROR_##sym, + foreach_sonic_ext_glean_redirect_error +#undef _ + SONIC_EXT_GLEAN_REDIRECT_N_ERROR, +} sonic_ext_glean_redirect_error_t; + +static char *sonic_ext_glean_redirect_error_strings[] = { +#define _(sym, str) str, + foreach_sonic_ext_glean_redirect_error +#undef _ +}; + +typedef enum +{ + SONIC_EXT_GLEAN_REDIRECT_NEXT_INTERFACE_OUTPUT, + SONIC_EXT_GLEAN_REDIRECT_N_NEXT, +} sonic_ext_glean_redirect_next_t; + +/* Rate-limit the redirect per resolving adjacency so an unreachable + * destination cannot flood the CPU. Seeded once threads are known. */ +static throttle_t sonic_ext_glean_throttle; + +/* + * The only nodes whose drops we are allowed to steal. + * + * This node is a feature on the ip4-drop / ip6-drop arcs, which are + * dispatched with a hardcoded sw_if_index 0 (ip_punt_drop.h, + * ip_drop_or_punt) and therefore see *every* dropped packet in the + * system -- not just the ones that failed neighbour resolution. + * + * That matters because we key off vnet_buffer(b)->ip.adj_index[VLIB_TX], + * and on the drop arcs that field is only an adjacency index when the + * packet came from a neighbour-resolution node. ip4-lookup stores + * dpo0->dpoi_index there unconditionally, whatever the DPO type + * (ip4_forward.h, ip4_lookup_inline), so a packet with no route + * arrives carrying the *drop* DPO's index -- which is just the + * dpo_proto (0 for v4, 1 for v6, drop_dpo.c) and is a perfectly valid + * index into the adjacency pool. Feeding that to adj_get() reads an + * unrelated adjacency; if it happens to be a glean we would redirect a + * packet the FIB deliberately dropped (no route, blackhole, ip4 not + * enabled) into the kernel, bypassing the programmed forwarding + * policy. Load-balance and receive DPO indices collide the same way. + * + * So gate on the node that produced the drop. All of these stamp + * b->error from their own node before enqueuing to the drop arc, and + * vlib_error_get_node() maps that back to the node index. + * + * ip6-discover-neighbor is DELIBERATELY EXCLUDED. Unlike its v4 + * counterpart it rewrites the packet in place before dropping it: + * + * if (!is_glean) { + * ip0->dst_address.as_u64[0] = adj0->sub_type.nbr.next_hop.ip6.as_u64[0]; + * ip0->dst_address.as_u64[1] = adj0->sub_type.nbr.next_hop.ip6.as_u64[1]; + * } + * + * (ip6_neighbor.c, ip6_discover_neighbor_inline). ip4_arp_inline only + * *reads* ip0->dst_address. Redirecting a v6 incomplete-adjacency drop + * would therefore hand the kernel a packet whose destination has been + * replaced by the next hop, with a stale L4 checksum -- silently + * mis-delivered transit traffic, and if the next hop is local to the + * DUT the kernel would consume it. The original destination is + * unrecoverable, so v6 is glean-only. v6 glean does not touch the + * packet and is safe. + */ +static u32 sonic_ext_glean_src_nodes[3]; + +static_always_inline int +sonic_ext_glean_from_nbr_node (vlib_main_t *vm, vlib_buffer_t *b) +{ + u32 ni = vlib_error_get_node (&vm->node_main, b->error); + int i; + + for (i = 0; i < ARRAY_LEN (sonic_ext_glean_src_nodes); i++) + if (sonic_ext_glean_src_nodes[i] != ~0 && + ni == sonic_ext_glean_src_nodes[i]) + return 1; + + return 0; +} + +/* + * Should this ip4-drop / ip6-drop packet be punted to the kernel to + * (re)arm neighbour resolution? Yes iff its VLIB_TX adjacency is an + * unresolved connected (glean) or incomplete (arp) adjacency and the + * egress interface of that adjacency is not an aggregate. + * + * Scope covers physical sub-interfaces *and* main interfaces: in both + * cases a transit packet to an unresolved neighbour makes VPP glean on + * its own, so the ARP reply arrives at the kernel unsolicited and is + * discarded (arp_accept=0) and the neighbour never reaches SAI. A + * main interface is only safe to skip when the DUT itself initiates + * the traffic (e.g. a BGP peer); a routed transit destination behind a + * main-interface RIF has no such trigger, which is exactly what + * test_routing_between_sub_ports_and_port exercises on the return leg. + * + * Bonds are in scope, main interface and sub-interface alike, and for + * the very same reason: a routed PortChannel RIF whose neighbour is a + * transit destination has no DUT-originated trigger either. BGP peers + * on a PortChannel are unaffected because the DUT initiates that + * traffic, so their neighbours are already resolved by the time it + * matters. The redirect lands correctly because orig_rx is the + * receiving physical *member*, not the bond: the packet re-enters the + * kernel on the member netdev, is delivered up through the team device + * and, for a sub-port, demultiplexed by 8021q onto + * PortChannel. -- in both cases the netdev that actually + * carries the RIF address, so the kernel ARPs with the right source. + * (Note the LCP host tap of a bond sub-interface, be., + * carries no address and would not have worked as a redirect target.) + * + * A BVI/SVI egress is in scope for the same reason: the redirected + * packet reaches the kernel Vlan netdev which ARPs and bridges out. + */ +static_always_inline int +sonic_ext_glean_should_redirect (u32 adj_index) +{ + vnet_main_t *vnm = vnet_get_main (); + ip_adjacency_t *adj; + vnet_sw_interface_t *swo; + u32 egress_sw; + + if (adj_index == ADJ_INDEX_INVALID || !adj_is_valid (adj_index)) + return 0; + + adj = adj_get (adj_index); + if (adj->lookup_next_index != IP_LOOKUP_NEXT_GLEAN && + adj->lookup_next_index != IP_LOOKUP_NEXT_ARP) + return 0; + + egress_sw = adj->rewrite_header.sw_if_index; + swo = vnet_get_sw_interface_or_null (vnm, egress_sw); + if (!swo) + return 0; + + return 1; +} + +VLIB_NODE_FN (sonic_ext_glean_redirect_node) +(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) +{ + sonic_ext_main_t *sem = &sonic_ext_main; + vnet_main_t *vnm = vnet_get_main (); + clib_thread_index_t thread_index = vm->thread_index; + u32 n_left_from, *from; + vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b; + u16 nexts[VLIB_FRAME_SIZE], *next; + u32 n_redirected = 0, n_not_glean = 0, n_no_cookie = 0, n_no_lcp = 0; + u32 n_throttled = 0, n_disabled = 0, n_not_nbr_node = 0; + u64 seed; + + seed = throttle_seed (&sonic_ext_glean_throttle, thread_index, + vlib_time_now (vm)); + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + vlib_get_buffers (vm, from, bufs, n_left_from); + b = bufs; + next = nexts; + + while (n_left_from > 0) + { + u32 next0 = 0; + sonic_ext_buffer_opaque_t *seb; + vnet_sw_interface_t *swo; + const lcp_itf_pair_t *ilip; + index_t ilipi; + u32 adj_index0 = vnet_buffer (b[0])->ip.adj_index[VLIB_TX]; + u32 orig_rx = ~0; + u32 host_tap = ~0; + u32 saved_vlan_tag = 0; + u16 pushed_vlan_id = 0; + int did_redirect = 0; + u64 key; + i32 adv; + + /* Default: continue down the drop arc (real drop). */ + vnet_feature_next (&next0, b[0]); + next[0] = (u16) next0; + + if (PREDICT_FALSE (!sem->punt_via_member)) + { + n_disabled++; + goto trace0; + } + + /* Only steal packets a neighbour-resolution node dropped, and + * only when their VLIB_TX adjacency really is an unresolved + * connected (glean) or incomplete (arp) adjacency. Every other + * ip4-drop / ip6-drop reason falls straight through. + * + * The node gate has to come first: on any other drop path + * adj_index[VLIB_TX] is not an adjacency index at all (see the + * comment on sonic_ext_glean_src_nodes), so it is not safe to + * hand to adj_get(). */ + if (!sonic_ext_glean_from_nbr_node (vm, b[0])) + { + n_not_nbr_node++; + goto trace0; + } + + if (!sonic_ext_glean_should_redirect (adj_index0)) + { + n_not_glean++; + goto trace0; + } + + seb = sonic_ext_buffer (b[0]); + if (PREDICT_FALSE (seb->magic != SONIC_EXT_BUFFER_MAGIC)) + { + n_no_cookie++; + goto trace0; + } + orig_rx = seb->orig_rx_sw_if_index; + saved_vlan_tag = seb->orig_vlan_tag; + + /* Rate-limit per resolving adjacency: one punt per window is + * enough to (re)arm the kernel's own ARP; the rest keep + * dropping until the neighbour is programmed via SAI. */ + key = ((u64) adj_index0 << 32) | orig_rx; + if (throttle_check (&sonic_ext_glean_throttle, thread_index, key, seed)) + { + n_throttled++; + goto trace0; + } + + swo = vnet_get_sw_interface (vnm, orig_rx); + { + u32 phy_sw = (swo->type == VNET_SW_INTERFACE_TYPE_SUB) + ? swo->sup_sw_if_index + : orig_rx; + ilipi = lcp_itf_pair_find_by_phy (phy_sw); + } + if (PREDICT_FALSE (ilipi == INDEX_INVALID)) + { + n_no_lcp++; + goto trace0; + } + ilip = lcp_itf_pair_get (ilipi); + host_tap = ilip->lip_host_sw_if_index; + + /* Clear the cookie before letting the buffer out -- defends + * against re-entering this node on the redirected pass and + * against a future packet on a recycled buffer. */ + seb->magic = 0; + + /* Rewind to the original wire L2 (recovers vlan bytes that + * ethernet-input parsed past), then re-push the outer VLAN tag + * from the wire-time capture snapshot so the kernel 8021q layer + * demuxes the frame to the right sub-interface netdev. Mirrors + * sonic-ext-aggr-tap-redirect. */ + adv = + (i32) vnet_buffer (b[0])->l2_hdr_offset - (i32) b[0]->current_data; + vlib_buffer_advance (b[0], adv); + + if (saved_vlan_tag && b[0]->current_data >= 4) + { + u8 *cur = vlib_buffer_get_current (b[0]); + u16 cur_etype = clib_net_to_host_u16 (*(u16 *) (cur + 12)); + if (cur_etype != ETHERNET_TYPE_VLAN && + cur_etype != ETHERNET_TYPE_DOT1AD && + cur_etype != ETHERNET_TYPE_VLAN_9100) + { + u8 save_macs[12]; + u8 *new_cur; + clib_memcpy_fast (save_macs, cur, 12); + vlib_buffer_advance (b[0], -4); + new_cur = vlib_buffer_get_current (b[0]); + clib_memcpy_fast (new_cur, save_macs, 12); + clib_memcpy_fast (new_cur + 12, &saved_vlan_tag, 4); + vnet_buffer (b[0])->l2_hdr_offset -= 4; + pushed_vlan_id = + clib_net_to_host_u16 (*((u16 *) &saved_vlan_tag + 1)) & 0x0fff; + } + } + + vnet_buffer (b[0])->sw_if_index[VLIB_TX] = host_tap; + next[0] = SONIC_EXT_GLEAN_REDIRECT_NEXT_INTERFACE_OUTPUT; + did_redirect = 1; + n_redirected++; + + trace0: + if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE) && + (b[0]->flags & VLIB_BUFFER_IS_TRACED))) + { + sonic_ext_glean_redirect_trace_t *t = + vlib_add_trace (vm, node, b[0], sizeof (*t)); + t->orig_rx_sw_if_index = orig_rx; + t->host_tap_sw_if_index = host_tap; + t->pushed_vlan_id = pushed_vlan_id; + t->redirected = did_redirect; + } + + b += 1; + next += 1; + n_left_from -= 1; + } + + vlib_buffer_enqueue_to_next (vm, node, from, nexts, frame->n_vectors); + +#define _inc(SYM, N) \ + if (N) \ + vlib_node_increment_counter (vm, sonic_ext_glean_redirect_node.index, \ + SONIC_EXT_GLEAN_REDIRECT_ERROR_##SYM, N) + _inc (REDIRECTED, n_redirected); + _inc (NOT_NBR_NODE, n_not_nbr_node); + _inc (NOT_GLEAN, n_not_glean); + _inc (NO_COOKIE, n_no_cookie); + _inc (NO_LCP, n_no_lcp); + _inc (THROTTLED, n_throttled); + _inc (DISABLED, n_disabled); +#undef _inc + + sem->glean_redirects += n_redirected; + return frame->n_vectors; +} + +VLIB_REGISTER_NODE (sonic_ext_glean_redirect_node) = { + .name = "sonic-ext-glean-redirect", + .vector_size = sizeof (u32), + .format_trace = format_sonic_ext_glean_redirect_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + .n_errors = ARRAY_LEN (sonic_ext_glean_redirect_error_strings), + .error_strings = sonic_ext_glean_redirect_error_strings, + .n_next_nodes = SONIC_EXT_GLEAN_REDIRECT_N_NEXT, + .next_nodes = { + [SONIC_EXT_GLEAN_REDIRECT_NEXT_INTERFACE_OUTPUT] = "interface-output", + }, +}; + +/* Steal unresolved-next-hop transit packets on the way to the bit + * bucket. Enabled once, globally: the drop arcs are always dispatched + * with sw_if_index 0 (ip_punt_drop.h, ip_drop_or_punt), so a + * per-interface enable would never fire. Gated at run time by + * sem->punt_via_member instead. */ +VNET_FEATURE_INIT (sonic_ext_glean_redirect_ip4, static) = { + .arc_name = "ip4-drop", + .node_name = "sonic-ext-glean-redirect", +}; + +VNET_FEATURE_INIT (sonic_ext_glean_redirect_ip6, static) = { + .arc_name = "ip6-drop", + .node_name = "sonic-ext-glean-redirect", +}; + +static clib_error_t * +sonic_ext_glean_redirect_main_loop_enter (vlib_main_t *vm) +{ + vlib_thread_main_t *tm = &vlib_thread_main; + /* v6 is glean-only on purpose -- ip6-discover-neighbor mangles the + * packet's destination address before dropping it. See the comment + * on sonic_ext_glean_src_nodes. */ + static const char *const nbr_node_names[] = { "ip4-arp", "ip4-glean", + "ip6-glean" }; + int i; + + STATIC_ASSERT (ARRAY_LEN (nbr_node_names) == + ARRAY_LEN (sonic_ext_glean_src_nodes), + "neighbour-resolution node table size mismatch"); + + for (i = 0; i < ARRAY_LEN (nbr_node_names); i++) + { + vlib_node_t *n = vlib_get_node_by_name (vm, (u8 *) nbr_node_names[i]); + + sonic_ext_glean_src_nodes[i] = n ? n->index : ~0; + if (!n) + clib_warning ("sonic-ext glean-redirect: node '%s' not found; drops " + "from it will not be redirected", + nbr_node_names[i]); + } + + /* ~1ms window, matching VPP's own ARP throttle granularity. MUST be + * a main-loop-enter function (not VLIB_INIT_FUNCTION): the throttle's + * per-thread arrays are sized from n_vlib_mains, which only includes + * the worker threads once they have been started -- init-function time + * is too early, and an undersized array makes a worker's + * throttle_seed / throttle_check read out of bounds. This mirrors + * ip4_neighbor's arp_throttle (ip4_neighbor_main_loop_enter). */ + throttle_init (&sonic_ext_glean_throttle, tm->n_vlib_mains, THROTTLE_BITS, + 1e-3); + return 0; +} + +VLIB_MAIN_LOOP_ENTER_FUNCTION (sonic_ext_glean_redirect_main_loop_enter); diff --git a/vppbld/plugins/sonic_ext/sonic_ext.c b/vppbld/plugins/sonic_ext/sonic_ext.c index 3c8d7677..f197e192 100644 --- a/vppbld/plugins/sonic_ext/sonic_ext.c +++ b/vppbld/plugins/sonic_ext/sonic_ext.c @@ -30,6 +30,41 @@ VLIB_PLUGIN_REGISTER () = { .description = "SONiC VPP extensions: punt-via-member, host-xc", }; +/* + * A "device-input" feature must never be toggled on a sub-interface. + * + * The driver input nodes start that arc from the *device's* own + * sw_if_index -- e.g. virtio (all linux-cp taps) calls + * vnet_feature_start_device_input (vif->sw_if_index, ...) and dpdk + * calls it with xd->sw_if_index -- so a sub-interface never gets a + * device-input dispatch of its own; it always rides its parent's. + * Enabling there is therefore a no-op, but *disabling* is actively + * destructive: vnet_config feature strings are interned and shared by + * every interface with an identical feature set, so removing the + * feature via the sub-interface rewrites the very config the parent is + * still pointing at. The parent silently loses the feature and its + * input node falls back to the arc's end node (ethernet-input). + * + * Concretely: creating and then deleting a physical sub-port such as + * Ethernet64.20 used to strip host-xc off Ethernet64's tap. Every + * later Linux-originated VLAN-tagged frame on that tap then reached + * ethernet-input instead of host-xc, and -- the tap having no VLAN + * sub-interface of its own -- was dropped as "unknown vlan". That is + * exactly the path a LAG sub-port's ARP takes once the port has been + * enslaved (PortChannel1.20 -> team -> Ethernet64 tap), so the + * neighbour never resolved and LAG sub-port forwarding died -- but + * only when a physical sub-port had been created and removed earlier + * in the same session. + */ +static int +sonic_ext_sw_is_sub (u32 sw_if_index) +{ + vnet_sw_interface_t *swi = + vnet_get_sw_interface_or_null (vnet_get_main (), sw_if_index); + + return swi && swi->type == VNET_SW_INTERFACE_TYPE_SUB; +} + /* * Per-interface feature enable helpers. All three live in this file * (rather than in the per-node files) so that the LCP pair add/del @@ -39,6 +74,9 @@ VLIB_PLUGIN_REGISTER () = { void sonic_ext_capture_enable_disable (u32 sw_if_index, int enable) { + if (sonic_ext_sw_is_sub (sw_if_index)) + return; + vnet_feature_enable_disable ("device-input", "sonic-ext-capture", sw_if_index, enable, 0, 0); } @@ -46,6 +84,9 @@ sonic_ext_capture_enable_disable (u32 sw_if_index, int enable) void sonic_ext_host_xc_enable_disable (u32 sw_if_index, int enable) { + if (sonic_ext_sw_is_sub (sw_if_index)) + return; + vnet_feature_enable_disable ("device-input", "sonic-ext-host-xc", sw_if_index, enable, 0, 0); } @@ -58,6 +99,23 @@ sonic_ext_aggr_tap_redirect_enable_disable (u32 sw_if_index, int enable) enable, 0, 0); } +/* + * Enable / disable sonic-ext-glean-redirect on the ip4-drop / ip6-drop + * arcs. Those arcs are dispatched with sw_if_index 0 (ip_drop_or_punt + * hardcodes it), so this is a single global toggle -- not per phy. + * The node scopes itself per packet: it only acts on buffers that + * carry a capture cookie (i.e. ingressed on a real wire phy) whose + * VLIB_TX adjacency is an unresolved glean / arp adjacency. + */ +void +sonic_ext_glean_redirect_enable_disable (int enable) +{ + vnet_feature_enable_disable ("ip4-drop", "sonic-ext-glean-redirect", 0, + enable, 0, 0); + vnet_feature_enable_disable ("ip6-drop", "sonic-ext-glean-redirect", 0, + enable, 0, 0); +} + /* * Is `phy_sw_if_index` a BVI (bridge-virtual interface)? Used by * the aggregate-detection helper. Distinct from is_aggregate so @@ -194,6 +252,17 @@ sonic_ext_set_punt_via_member (u8 is_enable) sem->capture_enabled = 1; } + /* Glean-redirect is a single global feature on the ip4/ip6-drop + * arcs (dispatched with sw_if_index 0). Enable once; the node + * self-scopes via the capture cookie + glean/arp adjacency check + * and short-circuits when punt_via_member is off, so we never need + * to disable it per-interface. */ + if (is_enable && !sem->glean_redirect_enabled) + { + sonic_ext_glean_redirect_enable_disable (1); + sem->glean_redirect_enabled = 1; + } + /* The aggr-tap-redirect feature itself is wired per-interface from * the LCP pair add/del callback (sonic_ext_lcp_pair_add_cb) -- it * only needs to fire on the host tap of BVI/bond masters, never on diff --git a/vppbld/plugins/sonic_ext/sonic_ext.h b/vppbld/plugins/sonic_ext/sonic_ext.h index d7171ef6..c7ba8da9 100644 --- a/vppbld/plugins/sonic_ext/sonic_ext.h +++ b/vppbld/plugins/sonic_ext/sonic_ext.h @@ -107,11 +107,13 @@ typedef struct * interfaces, so that toggling on/off is idempotent. */ u8 capture_enabled; u8 host_xc_enabled; + u8 glean_redirect_enabled; /* Counters (per-feature, per-thread accounting kept in node * registrations; these are summary counters for `show sonic-ext`). */ u64 captures; u64 aggr_tap_redirects; + u64 glean_redirects; u64 host_xc_direct; u64 l2_trap_fixups; } sonic_ext_main_t; @@ -120,6 +122,7 @@ extern sonic_ext_main_t sonic_ext_main; extern vlib_node_registration_t sonic_ext_capture_node; extern vlib_node_registration_t sonic_ext_aggr_tap_redirect_node; +extern vlib_node_registration_t sonic_ext_glean_redirect_node; extern vlib_node_registration_t sonic_ext_host_xc_node; extern vlib_node_registration_t sonic_ext_l2_trap_fixup_node; @@ -135,6 +138,12 @@ void sonic_ext_host_xc_enable_disable (u32 sw_if_index, int enable); * tomorrow). Driven from the LCP pair add/del callback. */ void sonic_ext_aggr_tap_redirect_enable_disable (u32 sw_if_index, int enable); +/* Enable / disable sonic-ext-glean-redirect. Global (the ip4-drop / + * ip6-drop arcs dispatch with sw_if_index 0), so there is no + * per-interface argument; the node scopes itself per packet via the + * capture cookie and the glean/arp adjacency check. */ +void sonic_ext_glean_redirect_enable_disable (int enable); + /* Toggle accessors used by CLI and node fast paths. */ void sonic_ext_set_punt_via_member (u8 is_enable); void sonic_ext_set_host_xc (u8 is_enable);