From 39e587c077a3dcd189134b80d4d447c02641744a Mon Sep 17 00:00:00 2001 From: Lun Yue <17232861+lunyue-ms@users.noreply.github.com> Date: Wed, 29 Jul 2026 23:19:10 +0000 Subject: [PATCH] [sonic_ext] Punt unresolved nexthops to kernel Add sonic-ext-glean-redirect on the IPv4 and IPv6 drop arcs so unresolved transit nexthops are punted through the ingress LCP host tap. Preserve the ingress interface and VLAN metadata, validate the producing glean/ARP node, and throttle redirects per adjacency. Avoid toggling device-input features on sub-interfaces because they share their parent device feature configuration. This prevents physical sub-port teardown from stripping host-xc from a later LAG member. Keep the bond and aggregate support provided by merged PR #257 unchanged. Add glean redirect counters and bump the VPP package version for downstream builds. Validated with sonic-net/sonic-sairedis#1907 on the vms-kvm-vpp-t1-lag testbed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ea640ff9-0a8c-4eb3-a41e-b316db32e773 Signed-off-by: Lun Yue <17232861+lunyue-ms@users.noreply.github.com> --- rules/vpp.mk | 2 +- vppbld/plugins/sonic_ext/CMakeLists.txt | 1 + vppbld/plugins/sonic_ext/cli.c | 1 + .../plugins/sonic_ext/glean_redirect_node.c | 486 ++++++++++++++++++ vppbld/plugins/sonic_ext/sonic_ext.c | 69 +++ vppbld/plugins/sonic_ext/sonic_ext.h | 9 + 6 files changed, 567 insertions(+), 1 deletion(-) create mode 100644 vppbld/plugins/sonic_ext/glean_redirect_node.c 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);