diff --git a/doc/vpp/images/packet-trimming-control-plane-flow.png b/doc/vpp/images/packet-trimming-control-plane-flow.png new file mode 100644 index 00000000000..62bf488042e Binary files /dev/null and b/doc/vpp/images/packet-trimming-control-plane-flow.png differ diff --git a/doc/vpp/images/packet-trimming-dataplane-flow.png b/doc/vpp/images/packet-trimming-dataplane-flow.png new file mode 100644 index 00000000000..707f0a285bf Binary files /dev/null and b/doc/vpp/images/packet-trimming-dataplane-flow.png differ diff --git a/doc/vpp/vpp-packet-trimming-HLD.md b/doc/vpp/vpp-packet-trimming-HLD.md new file mode 100644 index 00000000000..e5676a43d72 --- /dev/null +++ b/doc/vpp/vpp-packet-trimming-HLD.md @@ -0,0 +1,1059 @@ +# SONiC-VPP Packet Trimming HLD + +## Table of Contents + +1. [Revisions](#1-revisions) +2. [Scope](#2-scope) +3. [Definitions](#3-definitions) +4. [Background](#4-background) +5. [Requirements](#5-requirements) +6. [Architecture](#6-architecture) +7. [High-Level Design](#7-high-level-design) +8. [SAI API Mapping](#8-sai-api-mapping) +9. [Configuration and Management](#9-configuration-and-management) +10. [Warmboot and Fastboot](#10-warmboot-and-fastboot) +11. [Memory and Performance](#11-memory-and-performance) +12. [Error Handling](#12-error-handling) +13. [Limitations and Future Work](#13-limitations-and-future-work) +14. [Testing](#14-testing) +15. [Open Items](#15-open-items) +16. [References](#16-references) + +## 1. Revisions + +| Rev | Date | Author(s) | Changes | +|---|---|---|---| +| v1.0 | 2026-07-31 | Aaron Bernardino | Initial SONiC-VPP packet-trimming design | + +## 2. Scope + +This document describes how SONiC-VPP relates to the existing +[SONiC Packet Trimming](../packet_trimming/packet-trimming-design.md) design, +and defines the dataplane architecture implementing it on VPP. + +**Increment scope (this change).** A Gate 0 feasibility spike on the live VPP +KVM testbed (see [4. Background](#4-background)) established that the VPP +dataplane has no native egress QoS / queue-admission model, so a blocking SONiC +scheduler cannot by itself produce the admission failure that packet trimming +hangs off. Rather than defer the datapath, this increment **supplies the missing +substrate in software**: the `sonic_ext` VPP plugin gains a generic +per-`{egress port, queue}` token-bucket admission shim on the +`interface-output` feature arc, plus a trim action node that truncates the +rejected packet, rewrites its DSCP, and retries it on the configured static trim +queue. The binary API accepts generic rate and capacity values, while the +delivered SAI-VPP translation intentionally programs only the two states needed +by the supported topology: an unlimited bucket for a queue with no effective +rate limit and an empty, non-refilling bucket for a queue with any nonzero +scheduler `PIR`. Buffer profiles determine trim eligibility; their size is not +used as bucket depth in this increment. For the symmetric core that is complete +and validated end to end, SAI-VPP advertises the capability truthfully as +**supported** +(`SWITCH_TRIMMING_CAPABLE=true`), at which point the `sonic-mgmt` symmetric suite +runs. + +The capability stays honest by narrowing *what* is advertised rather than gating +the switch capability off: SAI-VPP advertises only the enum values the datapath +honors (`DSCP_VALUE` DSCP resolution, `STATIC` queue resolution), so orchagent +never programs the asymmetric `FROM_TC` or `DYNAMIC` modes VPP does not implement. +Cases that exercise unimplemented behavior are deferred in `sonic-mgmt` through +conditional_mark skips keyed on `asic_type == vpp`. The shared `sonic-sairedis` +virtual-switch base over-advertises every trim enum today; that enum +over-advertisement is corrected on VPP (see +[7.12](#712-capability-advertisement)). + +The delivered increment covers: + +- Translating the symmetric packet-trimming switch attributes into VPP state. +- Selecting eligibility from the buffer profile attached to the original queue. +- Classifying the original queue through a switch-wide DSCP-to-queue table. +- Producing a recoverable admission failure from the scheduler's binary + blocked/unlimited state. +- Truncating eligible IPv4 and IPv6 packets and writing the configured symmetric + DSCP value. +- Sending the shortened packet through a configured static trim queue on the + selected physical egress member. + +The full target design additionally includes asymmetric `FROM_TC`, dynamic +trim-queue resolution, ACL `DISABLE_TRIM`, SAI switch/port/queue counter +sourcing, and validated replay/persistence behavior. Those items are deferred +as listed in [13.1](#131-integration-status). + +This document is a VPP platform adaptation. It does not redefine the existing +SONiC CLI, Config DB schema, YANG model, orchestration, or SAI contract. + +The design does not support dynamic trim-queue resolution. That mode requires the +complete DSCP-to-TC-to-queue pipeline to select the trim queue after DSCP +remapping and is left for future work. The shared `sonic-sairedis` virtual-switch +base currently advertises the dynamic mode as supported; VPP advertises only the +`STATIC` mode it honors and omits `DYNAMIC` from the enum (see +[7.12](#712-capability-advertisement)). + +## 3. Definitions + +| Term | Meaning | +|---|---| +| Admission failure | The selected egress queue's admission provider rejects the packet. In the delivered VPP mapping this means the queue is trim-eligible and has a nonzero scheduler `PIR`, which is represented by an empty, non-refilling bucket | +| Original queue | The egress queue selected for the unmodified packet | +| Trim queue | The egress queue selected for the shortened packet | +| Trim attempt | An eligible original packet reaches the packet-trimming path after original-queue admission fails | +| Trimmed packet | A packet processed by the trim path, whether or not its original length exceeded the configured trim size | +| Admission provider | The VPP feature node that evaluates the programmed per-queue token-bucket state and diverts rejected buffers to packet-trimming policy | +| DSCP | Differentiated Services Code Point | +| TC | Traffic Class | +| LAG | Link Aggregation Group, represented by a VPP BondEthernet interface | +| SAI-VPP | The `sonic-sairedis` virtual switch implementation that translates SAI objects to VPP | +| VPP plugin | The SONiC-local VPP packet-trimming plugin and binary API | + +## 4. Background + +Packet trimming lets a receiver learn about congestion sooner than it would +after a complete packet drop and retransmission timeout. When admission to a +trim-eligible lossy queue fails, the switch: + +1. Keeps at most the configured number of bytes. +2. Assigns a configured DSCP, or resolves DSCP from a configured trim TC. +3. Tries to transmit the shortened packet through a high-priority trim queue. + +SONiC already provides the platform-independent control-plane implementation: + +```text +CLI / Config DB + | + v +switchorch + bufferorch + qosorch + aclorch + | + v +SAI switch / buffer profile / QoS / ACL objects + | + v +syncd + | + v +platform SAI +``` + +The **pre-change SAI-VPP baseline** is important to state precisely, because the +control-plane capability already existed before this increment: + +- The shared `sonic-sairedis` virtual-switch base already advertises full + packet-trimming capability. It reports the switch, buffer-profile, and stats + enum capabilities (DSCP `DSCP_VALUE`/`FROM_TC`, queue `STATIC`/`DYNAMIC`, + buffer action `DROP`/`DROP_AND_TRIM`, and the switch/port/queue trim stats), + and `SwitchVpp::queryAttributeCapability` reports every attribute as + create/set/get implemented. +- As a result, `sonic-swss` already computed + `isSwitchTrimmingSupported() == true` and published + `SWITCH_CAPABILITY|switch:SWITCH_TRIMMING_CAPABLE=true` on VPP. The + configuration path was functional at the virtual-object layer: switch, + buffer-profile, QoS, and ACL objects were accepted and stored. +- What was missing was entirely below SAI: VPP performed no trimming, and the + trim statistics read back as zero because the base returned no values for + them. + +VPP also has no native equivalent of +`SAI_BUFFER_PROFILE_PACKET_ADMISSION_FAIL_ACTION_DROP_AND_TRIM`. + +Because capability was already advertised while the datapath did nothing, the +pre-change platform reported support it did not provide. A Gate 0 feasibility +spike on the live `vms-kvm-vpp-t1-lag` testbed confirmed *why* and established +the implementation direction: + +- **Forwarding path (measured).** Front-panel transit egresses through + `BondEthernetXXX` (mode `xor`, load-balance `l34-inner`) onto member + interfaces that are **DPDK virtio** ports (`Red Hat Virtio`, one TX queue, + 4096 descriptors). SONiC `EthernetX` ports are VPP **Linux Control Plane + (LCP) TAP** interfaces paired to those members. (An AF_PACKET + `host-interface` backend exists in `vpp_init.sh` only for veth-based ports; + this testbed presents virtio PCI, so it takes the DPDK path.) +- **No QoS / admission substrate (measured).** The running VPP graph has no + policer (the CLI is not even compiled in), no scheduler, shaper, HQoS, or + per-queue admission node, no per-queue buffer accounting, and no + congestion/tail-drop counter in the transit path. There is no native + equivalent of `SAI_BUFFER_PROFILE_PACKET_ADMISSION_FAIL_ACTION_DROP_AND_TRIM`. +- **The SONiC scheduler is a dataplane no-op.** SAI-VPP stores the + `SAI_SCHEDULER_GROUP` / `SAI_QUEUE` object model so orchagent is satisfied, + but programs no VPP rate/shaper. The packet-trimming test creates congestion + by binding a `PIR=1` blocking scheduler to a queue; on VPP that scheduler + changes no dataplane state, so the "blocked" queue keeps forwarding at line + rate. Without congestion there is no admission failure, so a trim action + would never fire. + +Consequently, real trimming on VPP first requires an egress per-queue admission +model that reacts to the SONiC scheduler state, so that a blocked queue can +produce a *cannot-admit* result. Rather than treat that as a separate +prerequisite, this design **provides it in software**: a generic +per-`{port, queue}` token-bucket admission shim on the `interface-output` arc +(see [7.1](#71-admission-provider)). The delivered SAI-VPP mapping represents +the test's nonzero `PIR` blocking scheduler with a zero-rate, zero-capacity +bucket, yielding a real, recoverable admission failure; a queue without a rate +limit receives an always-admitting bucket. The trim action node +([7.4](#74-truncation-and-header-handling)–[7.6](#76-static-trim-queue-and-retry)) +hangs off that failure. Because the shim is generic — it carries no +packet-trimming SAI policy or test constants — it is the minimal substrate +needed and keeps the SONiC-specific logic in SAI-VPP and the plugin control path. + +The SAI-VPP translation that drives this datapath is completed and validated for +the symmetric core, so the platform advertises packet trimming as *supported* +(`SWITCH_TRIMMING_CAPABLE=true`). To keep the advertisement honest, VPP corrects +the base's enum over-advertisement — narrowing the advertised trim enum values to +the modes the datapath honors (`DSCP_VALUE`, `STATIC`) so orchagent never +programs an unimplemented mode — rather than gating the switch capability off +(see [7.12](#712-capability-advertisement)). + +## 5. Requirements + +### 5.1 Functional Requirements + +| ID | Requirement | Increment status | +|---|---|---| +| REQ-1 | VPP must trim only after admission to the original queue fails | Delivered | +| REQ-2 | Trimming eligibility must follow the buffer profile attached to the original queue | Delivered | +| REQ-3 | `DROP` must retain normal drop behavior; `DROP_AND_TRIM` must invoke trim policy | Delivered — a `DROP` (or unbound) queue is never made trim-eligible, so the shim does not police it and existing forwarding is unchanged; `DROP_AND_TRIM` marks the queue eligible and invokes trim policy | +| REQ-4 | The trim size must be runtime configurable | Delivered | +| REQ-5 | Symmetric mode must write the configured DSCP value | Delivered | +| REQ-6 | Asymmetric mode must resolve DSCP from the configured trim TC and the selected egress port's TC-to-DSCP map | Deferred | +| REQ-7 | The initial implementation must transmit through the configured static trim queue | Delivered | +| REQ-8 | ACL `DISABLE_TRIM` must suppress trimming for matching packets | Deferred | +| REQ-9 | A packet must enter the trim path no more than once | Delivered | +| REQ-10 | Trim attempts, successful trim transmissions, and trim-queue drops must be counted at the required SAI scopes | Plugin summary counters delivered; SAI scope attribution deferred | +| REQ-11 | LAG traffic must remain associated with the physical member selected before original-queue admission | Delivered | +| REQ-12 | A failed runtime VPP update must be surfaced so orchagent can retry the idempotent full policy and converge SAI and VPP state | Delivered for set; create refresh is best effort | + +### 5.2 Non-Functional Requirements + +- No per-packet heap allocation is allowed in the dataplane path. +- Packet trimming must be inactive when no eligible buffer profile is bound. +- Existing forwarding behavior must remain unchanged while the feature is + disabled. +- The admission integration point must be generic and must not contain SONiC + SAI policy. +- The design must not depend on scheduler names, a particular configured rate, + or constants from the packet-trimming test suite. +- Capability advertisement must reflect only functionality that is initialized + and usable at runtime. + +### 5.3 Non-Goals + +- Changes to the generic packet-trimming Config DB schema or CLI. +- A new SONiC application extension. +- Full hardware-MMU emulation in VPP. +- Dynamic trim-queue resolution in the initial implementation. +- Recalculation of IP or transport length and checksum fields after truncation. +- Replacing VPP's existing forwarding, LAG hashing, QoS, or ACL architecture. + +## 6. Architecture + +> **Scope note.** Sections [6](#6-architecture)–[9](#9-configuration-and-management) +> describe both the full target architecture and the delivered symmetric core. +> Status labels in the detailed sections are authoritative. The delivered path +> is the software admission shim, symmetric DSCP rewrite, static trim queue, +> binary API/CLI, and SAI-VPP programming validated end to end. Deferred behavior +> is summarized in [13](#13-limitations-and-future-work). + +### 6.1 Control-Plane Flow + +

+ Control-plane flow: Config DB packet-trimming configuration flows through SONiC orchagent, SAI switch/buffer/QoS/ACL objects, syncd, and SAI-VPP, which fans out to the VPP packet-trim binary API and plugin policy, egress admission provider configuration, and capability mapping (SAI counter sourcing deferred). +

+ +SAI-VPP owns object relationships and translates the platform-independent SAI +contract into compact VPP policy: + +- Global trim size, symmetric DSCP value, and static trim queue. The DSCP mode + and TC value are stored, but `FROM_TC` is not honored in this increment. +- Effective eligibility for each `{egress port, original queue}` pair. +- A switch-wide DSCP-to-queue table composed from the first front-panel port + that has both `DSCP_TO_TC` and `TC_TO_QUEUE` maps. +- Binary blocked/unlimited admission state derived from the effective scheduler. + +ACL trim-disable programming, per-port TC-to-DSCP resolution, and SAI trim +counter sourcing are deferred. + +### 6.2 Dataplane Flow + +

+ Dataplane flow: after a packet reaches final egress classification and the physical egress and original queue are resolved, the original queue is checked for admission. If it admits, the original packet is transmitted. If not, and the queue is trim-eligible and not ACL-disabled, trim-attempt counters increment, the packet is truncated to the configured size, DSCP is applied, and the configured static trim queue on the same physical egress is selected; if that trim queue admits, the packet is transmitted and trim-sent counters increment, otherwise it is dropped and trim-drop counters increment. Non-eligible queues take the normal original-packet path. +

+ +This flowchart is the full conceptual design. In the delivered increment the +"ACL disabled" branch and the asymmetric-DSCP step are deferred: the datapath +applies symmetric `DSCP_VALUE` only and does not yet honor an ACL trim-disable +flag ([7.5](#75-dscp-resolution), [7.9](#79-acl-disable-trim), +[13.1](#131-integration-status)). The delivered counters are switch-global +plugin summaries; the port/queue increments shown are target SAI attribution +([7.11](#711-counters)). The `Normal original-packet drop` node likewise +represents the conceptual/hardware admission model: on the delivered VPP +datapath a non-eligible (or unconfigured) queue is never policed by the shim — +the admission node passes it straight through on the `interface-output` arc to +normal port TX — so the shim implements no separate original-packet drop of its +own, and existing forwarding is preserved unchanged. + +### 6.3 Component Responsibilities + +| Component | Responsibility | +|---|---| +| VPP admission integration | Software per-`{port, queue}` token-bucket admission shim on the `interface-output` arc that turns a rate-limited (blocked) queue into a recoverable admission failure; generic, carrying no packet-trimming SAI policy | +| VPP packet-trim plugin | Eligibility lookup, truncation, DSCP rewrite, static trim-queue retry with one-shot `interface-output-arc-end` re-injection, counters, binary API, and debug CLI | +| SAI-VPP | Translate the delivered symmetric policy onto the plugin: global size/DSCP/static queue, per-queue eligibility from the buffer profile, binary blocked/unlimited state from the scheduler, and a switch-wide DSCP-to-queue table. Advertise only `DSCP_VALUE` and `STATIC`, while leaving SAI trim-counter sourcing and the asymmetric/ACL modes deferred | +| sonic-swss | No VPP-specific change expected; the generic packet-trimming orchestration and capability publication already function on VPP | +| sonic-mgmt | Make the test setup self-sufficient on the minimal VPP config, clean up test-created buffer bindings/profiles, exempt VPP from the hardware-SKU gate, and skip deferred files/cases on `asic_type == vpp` ([14.2](#142-system-tests), [14.3](#143-enablement-gating)) | + +## 7. High-Level Design + +### 7.1 Admission Provider + +Packet trimming needs a point where an egress packet can be *recoverably* +rejected — rejected while VPP still owns the buffer, so the trim action can run +instead of the packet being freed. VPP has no such point natively, so the plugin +provides one as a software admission shim. + +**`sonic-ext-trim-admission`** is a feature node on the `interface-output` arc, +enabled per egress `sw_if_index` whenever that port has at least one +trim-eligible queue. For each packet it resolves the egress `{port, queue}` — the +queue from the packet DSCP via the SAI-pushed DSCP-to-queue table — and runs that +queue's software token bucket: + +| Result | Meaning | Action | +|---|---|---| +| Non-eligible / unconfigured queue | not policed | pass straight through the arc (normal traffic is never perturbed) | +| Eligible queue, bucket admits | within rate | continue the arc (normal egress) | +| Eligible queue, bucket rejects | recoverable admission failure | divert to the trim action node ([7.4](#74-truncation-and-header-handling)) | + +The plugin API and token-bucket implementation accept generic rate/capacity +inputs. The delivered SAI-VPP translation deliberately binarizes them: a queue +with no effective rate limit receives `UINT64_MAX` rate and capacity (always +admit), while any nonzero scheduler `PIR` — including the +`SCHEDULER_BLOCK_DATA_PLANE` profile's `PIR=1` — receives zero rate and capacity +(always reject). The attached buffer profile controls only whether the queue is +trim-eligible; its size does not set the bucket depth in this increment. The +plugin recognizes no scheduler names or test constants, and non-eligible queues +are never policed. + +Because the shim is a normal feature-arc node, no VPP core change is required for +the KVM DPDK-virtio backend. Backends that cannot be policed on the +`interface-output` arc (for example the AF_PACKET `host-interface` path) would +reuse the same generic node at their own pre-transmit point. + +### 7.2 Packet-Trim Policy State + +The VPP plugin maintains: + +- One global packet-trimming configuration. +- One eligibility record per effective egress-port/original-queue binding. +- One switch-wide DSCP-to-queue table used for original-queue classification. +- Three switch-global summary counters: admission failures, trimmed packets + sent, and trimmed packets dropped. + +Per-port TC-to-DSCP state for `FROM_TC` and per-port/per-queue counter +attribution are target behavior, not delivered state. + +The global configuration contains: + +| Field | Meaning | +|---|---| +| `trim_size` | Maximum number of bytes retained | +| `dscp_mode` | Stored DSCP mode; only direct `DSCP_VALUE` is honored | +| `dscp_value` | Symmetric DSCP value | +| `trim_tc` | Stored TC for the deferred asymmetric egress-map lookup | +| `trim_queue` | Queue index used for the shortened packet | + +An eligibility record is active only when the queue's effective buffer profile +uses `DROP_AND_TRIM`. + +### 7.3 Original-Queue Admission Failure + +On a recoverable admission failure, the admission node has resolved: + +- The buffer index. +- The final physical egress `sw_if_index` (`VLIB_TX`). +- The original queue index (stamped into the buffer for the trim node). +- That the queue is trim-eligible. + +The plugin diverts the packet to the trim action node only when the global +configuration is valid and the original queue is trim-eligible; otherwise the +packet stays on the normal arc (admitted) or follows the existing drop path. +Re-entry is prevented **structurally** rather than with a per-buffer flag: the +trimmed packet is re-injected at `interface-output-arc-end`, past the admission +feature, so it can never re-enter admission and be trimmed twice (see +[7.6](#76-static-trim-queue-and-retry)). This avoids depending on `opaque2` being +cleared on buffer recycle, which VPP does not guarantee. + +The target ACL-driven trim-suppression path would add a per-buffer flag to this +decision. It is not present in the delivered node +([7.9](#79-acl-disable-trim)). + +### 7.4 Truncation and Header Handling + +When a packet is longer than `trim_size`, VPP shortens the buffer chain to at +most `trim_size` bytes, freeing any tail buffers in a chained packet. When a +packet is shorter than or equal to `trim_size`, it is sent at its original +length. In both cases the packet is processed by the trim path and receives the +configured DSCP behavior. + +**Multi-segment (jumbo) buffers.** On the KVM/DPDK backend, jumbo frames arrive +scatter-gathered into a chain of VLIB buffers, each backed by a DPDK `rte_mbuf` +(the `interface-output` arc runs with `VLIB_BUFFER_EXT_HDR_VALID` set on RX +buffers). Truncating the chain clears the VLIB `VLIB_BUFFER_NEXT_PRESENT` flag +and frees the tail buffers, but that alone does **not** update the backing +`rte_mbuf`: it still carries the RX-time `nb_segs` and a `->next` pointer to the +now-freed tail segment. `dpdk_validate_rte_mbuf()` only rebuilds the mbuf header +(via `rte_pktmbuf_reset()`, which sets `next = NULL` and `nb_segs = 1`) when +`EXT_HDR_VALID` is clear, and the virtio PMD TX path walks `mb->next` until NULL +rather than trusting `nb_segs`. Without intervention the freed tail therefore +rides onto the wire — a 5000-byte frame trimmed to 256 was observed egressing as +256 + 952 = 1208 bytes. The trim path fixes this by clearing +`VLIB_BUFFER_EXT_HDR_VALID` on the severed segment, forcing the mbuf header to be +rebuilt cleanly so only the retained bytes are transmitted. This case is covered +end to end by `test_packet_size_after_trimming` (jumbo 5000 → 4084 and a second +multi-segment case, 3000 → 256). + +The trim path deliberately preserves the original L3/L4 length fields as the +congestion signal — it does **not** recalculate: + +- IPv4 total length (the header still reports the pre-trim length). +- IPv6 payload length. +- TCP or UDP length and checksum fields. + +The one field that *is* updated is the **IPv4 header checksum**, and only because +the DSCP rewrite ([7.5](#75-dscp-resolution)) changes the ToS byte: the checksum +is recomputed over the 20-byte header so the header stays valid after the DSCP +change, while the (deliberately unmodified) total-length field is unaffected. The +IPv4 header checksum covers only the header, so leaving the truncated payload out +does not invalidate it; IPv6 carries no header checksum. This matches the +existing SONiC packet-trimming validation contract, which inspects the preserved +length fields together with the rewritten DSCP. + +### 7.5 DSCP Resolution + +For IPv4, VPP rewrites the six DSCP bits in the Type of Service field while +preserving ECN bits. For IPv6, VPP rewrites the DSCP portion of Traffic Class +while preserving ECN bits. + +| Mode | Resolution | +|---|---| +| `DSCP_VALUE` | Use the global configured `dscp_value` | +| `FROM_TC` (deferred) | Use `trim_tc` to look up DSCP in the TC-to-DSCP map attached to the selected egress port | + +The delivered datapath implements **only** the symmetric `DSCP_VALUE` mode and +always rewrites with the global `dscp_value`; asymmetric `FROM_TC` is deferred +and omitted from the advertised DSCP-resolution enum +([7.12](#712-capability-advertisement), [13.1](#131-integration-status)), so +orchagent never programs it. The `FROM_TC` behavior below is the target design +for a future increment: if asymmetric mode has no usable egress map entry, the +trim operation fails, the packet is dropped, and a configuration/runtime error +counter is incremented. SAI-VPP should reject configurations that are invalid at +programming time. + +### 7.6 Static Trim Queue and Retry + +The initial implementation uses +`SAI_PACKET_TRIM_QUEUE_RESOLUTION_MODE_STATIC`. + +The trim action node: + +1. Rewrites the packet DSCP ([7.5](#75-dscp-resolution)) and truncates it + ([7.4](#74-truncation-and-header-handling)). +2. Steers the shortened packet to the configured static `trim_queue` on the **same** physical + egress interface (`VLIB_TX` is unchanged) and runs that queue's token bucket. +3. If the trim queue admits, transmits the packet by enqueuing it directly to + `interface-output-arc-end` — the node the `interface-output` feature arc + terminates at — so it goes straight to the port TX, *past* the admission + feature. If the trim queue rejects (it is itself congested), the packet is + dropped. + +Enqueuing the transmitted packet to the arc end is what makes the one-shot +guarantee **structural**: a trimmed packet never re-enters the admission feature, +so it can never be trimmed a second time and no per-buffer "already trimmed" flag +is needed. This is deliberately more robust than a buffer flag, because VPP does +not zero `opaque2` on buffer recycle, so a flag could survive into an unrelated +future packet and wrongly suppress its trimming. + +### 7.7 Physical Ports and LAGs + +For a physical-port egress, the trim retry uses the same physical interface. + +For a LAG egress, VPP bond selection occurs before queue admission. The +admission provider records the selected physical member. The trim retry uses +that member directly instead of re-entering bond hashing, which: + +- Keeps the original and trimmed packet on one physical port; future + per-port/per-queue counter sourcing can therefore use that member. +- Avoids moving a congestion notification to a different LAG member. +- Prevents a second flow-hash decision from changing packet ordering. + +The delivered plugin records the selected physical `VLIB_TX` member. SAI +port/queue trim counters are not sourced yet; the target attribution is the +physical member and its queues. + +### 7.8 Buffer Profile and Queue Relationships + +SAI-VPP tracks the object chain from a queue to its attached buffer profile. +Whenever the queue binding or +`SAI_BUFFER_PROFILE_ATTR_PACKET_ADMISSION_FAIL_ACTION` changes, SAI-VPP +recomputes and programs effective eligibility. + +Because sonic-vpp creates front-panel ports dynamically as orchagent brings the +switch up — not at switch-create time — SAI-VPP enumerates the ports to program +from the live SAI port-object store on each recompute, rather than from a port +list captured at initialization. A recompute therefore always covers every +front-panel port that currently exists, including ports created after the first +trim-relevant attribute was observed, and a late buffer-profile binding is picked +up by the next recompute rather than being missed. + +On a trim-relevant set, SAI-VPP first commits the virtual object attribute, then +rebuilds and pushes the complete eligibility state. A push failure is returned +to orchagent for retry; the committed attribute is retained. On create, the +object is committed and the trim refresh is best effort. Normal orchagent remove +sequencing first unbinds a profile or scheduler through a set (which refreshes +eligibility), then removes the now-unreferenced object; direct object removal +does not itself trigger a trim refresh ([7.13](#713-update-and-remove-ordering)). + +### 7.9 ACL Disable Trim + +> **Status: deferred (target design).** The ACL trim-disable path is not +> implemented in this increment; SAI-VPP accepts and stores the ACL objects as +> virtual state but does not yet program a per-buffer trim-disable flag into the +> VPP datapath. The `sonic-mgmt` `test_acl_action_with_trimming` case is skipped +> on `vpp` ([14.3](#143-enablement-gating)). The design below is the target for a +> future increment ([13.1](#131-integration-status)). + +SAI-VPP extends the existing VPP ACL translation for +`SAI_ACL_ENTRY_ATTR_ACTION_PACKET_TRIM_DISABLE`. + +A matching ACL rule sets a per-buffer `trim_disabled` flag. The flag is metadata +only and does not change the packet action by itself. If original-queue admission +later fails, trim policy observes the flag and follows normal drop behavior. + +The target implementation must use the same explicit failure propagation and +retry/convergence model as other trim-relevant updates. + +### 7.10 Scheduler and Capacity Inputs + +The plugin admission API supports a byte rate and byte capacity, but the +delivered SAI-VPP translation does not model proportional scheduling or derive a +capacity from the buffer profile. It resolves the queue's effective scheduler +(direct queue binding first, then the parent leaf scheduler group used by +QosOrch) and programs one of two states: + +| Effective scheduler | Programmed rate | Programmed capacity | Behavior | +|---|---:|---:|---| +| No scheduler or `MAX_BANDWIDTH_RATE=0` | `UINT64_MAX` | `UINT64_MAX` | Always admit | +| Any nonzero `MAX_BANDWIDTH_RATE` | `0` | `0` | Always reject when trim-eligible | + +The buffer profile contributes only +`PACKET_ADMISSION_FAIL_ACTION=DROP_AND_TRIM` eligibility. Modeling a +proportional sub-line-rate `PIR`, actual queue occupancy, or buffer-profile +capacity as token-bucket depth is future work. + +Full SAI scheduler hierarchy, shared-buffer accounting across ports, headroom, +and hardware-specific threshold behavior remain outside this design. + +### 7.11 Counters + +The VPP plugin currently maintains three switch-global summary counters and +exposes them through `sonic_ext_trim_counters_get` and `show sonic-ext trim`: + +- `trim_admit_fail` — eligible original-queue admission failures. +- `trim_sent` — shortened packets transmitted. +- `trim_drop` — shortened packets rejected by the trim queue. + +The **target** design adds the per-port/per-queue accounting needed to map these +events onto the existing SAI statistics and serve them from `getStatsExt`: + +| Event | Counter attribution | +|---|---| +| Eligible original-queue admission failure | `TRIM_PACKETS` on the original queue and physical egress port | +| Trim packet transmitted | `TX_TRIM_PACKETS` on the switch, physical egress port, and trim queue | +| Trim-queue admission failure | `DROPPED_TRIM_PACKETS` on the switch, physical egress port, and trim queue | + +Counter reads must be monotonic and safe while workers update them, and a clear +operation resets only the requested scope and statistics. + +The target queue-index attribution must match the `sonic-mgmt` counter +assertions: the suite verifies that the switch-level trim-sent counter equals the +sum of the per-port counters and that each port's counter is consistent with its +per-queue counters, and the feature-toggle test reads the queue-level +`trimpacket` counter on the *trim* queue index (`UC` + configured trim queue). + +**Delivered scope (initial enablement).** The global plugin summaries are +available through the binary API/CLI, but SAI-VPP does **not** source them +through `getStatsExt`, and the plugin does not yet maintain the per-port and +per-queue breakdown required by the SAI contract. `SwitchVpp::getStatsExt` +delegates trim stat IDs to the shared virtual-switch base, which returns zero. +SAI stat sourcing and scoped accounting are deferred, and the `sonic-mgmt` +trim-counter cases are skipped on `vpp`. + +### 7.12 Capability Advertisement + +Packet-trimming capability on VPP must track the datapath: `sonic-swss` must be +told which trim modes VPP can actually satisfy so orchagent never programs one it +cannot. The shared virtual-switch base advertises packet trimming on VPP: + +- `SwitchStateBase::queryAttrEnumValuesCapability` returns the DSCP modes + (`DSCP_VALUE`, `FROM_TC`), the queue modes (`STATIC`, `DYNAMIC`), and the + buffer-profile action modes (`DROP`, `DROP_AND_TRIM`). +- `SwitchStateBase::queryStatsCapability` lists the switch, port, and queue trim + stats as supported. +- `SwitchVpp::queryAttributeCapability` reports every switch trim attribute as + create/set/get implemented. + +Together these make `sonic-swss` publish `SWITCH_TRIMMING_CAPABLE=true`. The +initial enablement **keeps** trimming advertised as supported — the symmetric +datapath is wired and validated ([13.1](#131-integration-status)) — but narrows +the advertised *enum values* to exactly what the software admission shim honors: + +| Seam | Delivered behavior | +|---|---| +| `SwitchVpp::queryAttributeCapability` | Inherits the base: the six `SAI_SWITCH_ATTR_PACKET_TRIM_*` attributes (size, DSCP mode, DSCP value, TC value, queue mode, queue index) stay create/set/get implemented, so `sonic-swss`'s `isSwitchTrimmingSupported()` follows the `set` capability and `SWITCH_TRIMMING_CAPABLE` is `true` | +| `SwitchVpp::queryAttrEnumValuesCapability` | Overrides the base to advertise a single value per trim enum — `DSCP_VALUE` for `SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_RESOLUTION_MODE` and `STATIC` for `SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE` — so orchagent never programs the asymmetric `FROM_TC` or `DYNAMIC` modes VPP does not implement. `DROP`/`DROP_AND_TRIM` buffer-profile actions are left to the base metadata | + +Because the capability stays `true`, the symmetric `packet_trimming` PTF suite +runs on sonic-vpp. Cases that exercise unimplemented behavior — asymmetric +per-port DSCP (`FROM_TC`), `DYNAMIC` queue resolution, ACL disable-trim, trim +counters, GCU config validation, feature/port toggles, port mirroring, SRv6, and +reload/reboot persistence — are deferred in `sonic-mgmt` through +conditional_mark skips keyed on `asic_type == vpp` +([14.3](#143-enablement-gating)), rather than by gating the switch capability to +`false`. This keeps the supported symmetric core enabled while excluding the +out-of-scope cases from the run. + +The enum-advertisement decision is factored into a small pure predicate +(`SwitchVpp::getTrimEnumValuesCapability`) that the override calls, so it is +covered directly by a SAI-VPP unit test ([14.1](#141-unit-and-component-tests)). + +### 7.13 Update and Remove Ordering + +For set operations on a trim-relevant object, SAI-VPP: + +1. Receives the metadata-validated SAI attribute and resolves available object + references. +2. Commits the SAI virtual object state. +3. Rebuilds the affected VPP policy from the committed state (the global policy, + or the full per-`{port, queue}` admission/eligibility set) and pushes it. +4. If the VPP push fails, returns the failure so orchagent retries the set. The + committed SAI state is intentionally left in place; because each push is a + full idempotent re-send, the retry re-pushes the same state and converges. The + switch-global trim attributes program VPP first and then commit, but likewise + surface a push failure to the caller. + +For create operations, the SAI object is created unconditionally and a trim +re-program failure is **not** propagated (the object exists regardless); the +subsequent set path is where a dataplane push failure is reported. + +For remove operations, the generic `remove_internal` path removes only the +virtual object state and does not invoke a trim refresh. Normal orchagent +sequencing must first unbind trim-relevant references through a set operation, +which refreshes the VPP policy, and then remove the unreferenced object. + +## 8. SAI API Mapping + +### 8.1 Switch Attributes + +| SAI attribute | VPP mapping | +|---|---| +| `SAI_SWITCH_ATTR_PACKET_TRIM_SIZE` | Global `trim_size` | +| `SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_RESOLUTION_MODE` | Stored globally; only `DSCP_VALUE` is advertised and honored | +| `SAI_SWITCH_ATTR_PACKET_TRIM_DSCP_VALUE` | Symmetric DSCP value | +| `SAI_SWITCH_ATTR_PACKET_TRIM_TC_VALUE` | Stored for the deferred asymmetric path; not used by the delivered trim node | +| `SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_RESOLUTION_MODE` | Accept `STATIC`; `DYNAMIC` is omitted from the advertised enum ([7.12](#712-capability-advertisement)) so orchagent never programs it. A stored `DYNAMIC` value is not honored by the datapath (an explicit set is accepted as a no-op) | +| `SAI_SWITCH_ATTR_PACKET_TRIM_QUEUE_INDEX` | Global static trim queue; the plugin stores the low three bits (`queue & 7`). The validated configuration uses queue 6 | + +### 8.2 Buffer Profile and ACL + +| SAI attribute/action | VPP mapping | +|---|---| +| `SAI_BUFFER_PROFILE_ATTR_PACKET_ADMISSION_FAIL_ACTION=DROP` | Disable trim eligibility for queues using the profile | +| `SAI_BUFFER_PROFILE_ATTR_PACKET_ADMISSION_FAIL_ACTION=DROP_AND_TRIM` | Enable trim eligibility for queues using the profile | +| `SAI_ACL_ENTRY_ATTR_ACTION_PACKET_TRIM_DISABLE` | **Deferred** — the ACL trim-disable path is not implemented in this increment ([7.9](#79-acl-disable-trim), [13.1](#131-integration-status)); the corresponding `sonic-mgmt` case is skipped on `vpp` | + +### 8.3 Statistics + +The trim statistics below are listed as supported by the shared virtual-switch +stats capability, so `sonic-swss` reads them. The VPP plugin maintains the +global summary counters in the dataplane and exposes them over its binary API +and debug CLI, but **SAI sourcing through `getStatsExt` is deferred** +([7.11](#711-counters), +[13.1](#131-integration-status)): trim stat IDs currently delegate to the shared +virtual-switch base, which returns zero, and the `sonic-mgmt` trim-counter cases +are skipped on `vpp` until real sourcing lands. + +- `SAI_SWITCH_STAT_DROPPED_TRIM_PACKETS` +- `SAI_SWITCH_STAT_TX_TRIM_PACKETS` +- `SAI_PORT_STAT_TRIM_PACKETS` +- `SAI_PORT_STAT_DROPPED_TRIM_PACKETS` +- `SAI_PORT_STAT_TX_TRIM_PACKETS` +- `SAI_QUEUE_STAT_TRIM_PACKETS` +- `SAI_QUEUE_STAT_DROPPED_TRIM_PACKETS` +- `SAI_QUEUE_STAT_TX_TRIM_PACKETS` + +No SAI API change is required. + +## 9. Configuration and Management + +This design introduces no new CLI, Config DB, APP DB, State DB, YANG, REST, or +gNMI schema. SONiC-VPP uses the configuration and capability fields defined by +the generic packet-trimming HLD. + +The VPP plugin adds `show sonic-ext trim`, which reports: + +- The current global trim configuration. +- Configured per-port/per-queue admission buckets, including eligibility, + binary rate/capacity, and current tokens. +- The three global plugin counters (`sent`, `drop`, and `admit-fail`). + +The CLI does not expose per-port TC-to-DSCP resolution or SAI-scoped counters in +this increment. + +These commands are for serviceability and do not replace the standard SONiC CLI. + +## 10. Warmboot and Fastboot + +Reload, warmboot, and fastboot persistence are **not validated in this +increment**; `test_trimming_with_reload_and_reboot` is skipped on `vpp`. + +The expected replay behavior is that switch attributes, queue/profile bindings, +scheduler bindings, and QoS maps trigger the same full-state programming used +at runtime. ACL trim-disable state is deferred and therefore is not rebuilt. +Plugin counters and token-bucket state are process-local and reset when VPP +restarts; no SAI counter persistence is provided. A packet in flight during a +restart may be dropped. + +The implementation adds no sleeps or file I/O to the boot path. Broader +warmboot/fastboot behavior remains future validation work. + +## 11. Memory and Performance + +The per-port state is a vector indexed by VPP `sw_if_index`, with eight fixed +queue slots per entry, plus one switch-wide DSCP-to-queue table: + +```text +O((maximum programmed sw_if_index + 1) * SONIC_EXT_TRIM_MAX_QUEUES + 64) +``` + +The per-buffer trim metadata currently stores only the resolved original queue +in existing VPP buffer opaque space. The target ACL trim-disable flag is not +implemented. The one-shot guarantee is structural (arc-end re-injection), not a +stored flag, and the dataplane performs no per-packet heap allocation. + +When no eligible profile is bound, the admission feature is disabled on that +port, so there is no per-packet feature-node cost. If eligible queues remain but +the global trim policy is disabled, the node executes one early configuration +check and bypasses. Packets admitted to their original queue do not enter packet +mutation. + +The software admission shim does not buffer packets; it only accounts byte +credits and immediately admits or rejects each buffer. Each `{port, queue}` has +one shared bucket with no synchronization. This is safe for the supported +single-worker topology; per-worker buckets or atomic accounting are deferred. + +## 12. Error Handling + +| Error case | Handling | +|---|---| +| Out-of-range scalar values reaching the vendor layer | The vendor code performs no additional rejection: trim size is narrowed to `u16`, DSCP is masked to six bits by the plugin, and the static trim queue is masked to three bits. The supported control path programs valid values (size 256/4084, DSCP 48, queue 6) | +| Queue is multicast or has an out-of-range unicast index | Multicast queues are skipped by `SAI_QUEUE_ATTR_TYPE` so a colliding multicast index cannot overwrite the corresponding unicast admission slot. Unicast indices at or above `SONIC_EXT_TRIM_MAX_QUEUES` (8) are skipped as SUCCESS. Every eligible lossy queue in the supported configuration is unicast and below the cap ([13.2](#132-design-limitations)) | +| `DYNAMIC` queue-resolution mode | Omitted from the advertised enum so orchagent never programs it; a stored value is not honored by the datapath ([7.12](#712-capability-advertisement)) | +| Queue attributes or bindings are not present during replay | Skip the incomplete queue for that refresh; a later trim-relevant set triggers another full recompute | +| No front-panel port currently has both DSCP-to-TC and TC-to-queue maps | Keep the plugin's last-good switch-wide DSCP-to-queue table rather than replacing it with an all-zero table | +| VPP plugin/API programming is unavailable | Capability advertisement is not dynamically probed. A set reports the VPP API failure for orchagent retry; create refresh remains best effort | +| VPP binary API update fails on a set | Commit the SAI object state and return the failure so orchagent retries; the next retry re-pushes the same full (idempotent) policy ([7.13](#713-update-and-remove-ordering)) | +| Asymmetric DSCP cannot be resolved at runtime (deferred `FROM_TC`) | Target design: drop the trim packet and increment a diagnostic error counter. Not reachable in this increment — only symmetric `DSCP_VALUE` is advertised and honored ([7.5](#75-dscp-resolution)) | +| Egress metadata is missing at admission failure | Pass the packet through unpoliced on the normal `interface-output` arc rather than guessing an interface or queue; the shim adds no drop of its own | +| Trim queue rejects the packet | Drop once, increment the plugin trim-drop counter, and do not retry | +| SAI trim counter read | Trim stat IDs currently delegate to the shared virtual-switch base and return zero; SAI sourcing through `getStatsExt` is deferred ([7.11](#711-counters)) | + +## 13. Limitations and Future Work + +### 13.1 Integration status + +The datapath in [6](#6-architecture)–[9](#9-configuration-and-management) — the software +admission shim, the trim action node, the binary API, and the debug CLI — is +implemented in the `sonic_ext` plugin and validated on standalone VPP +(happy-path trim plus the recycled-buffer regression that confirms the arc-end +bypass one-shot guarantee). + +The SAI-VPP translation that drives it from live SONiC configuration is +implemented and validated at the datapath-programming layer on the `t1-lag` +testbed dev VM, confirmed by reading VPP's own plugin state (`show sonic-ext +trim`) after pushing configuration through CONFIG_DB: + +1. **Per-`{port, queue}` eligibility** is programmed from + `SAI_BUFFER_PROFILE_ATTR_PACKET_ADMISSION_FAIL_ACTION` and the + queue↔buffer-profile binding. **Validated:** with `egress_lossy_profile` set + to `DROP_AND_TRIM`, VPP reports the lossy queues (`0,1,2,5,6`) eligible and + the lossless queues (`3,4,7`) bypassed on every front-panel port. +2. **Scheduler state** maps onto the admission shim as binary unlimited/blocked; + the buffer profile controls eligibility but does not provide bucket depth + ([7.10](#710-scheduler-and-capacity-inputs)). **Validated:** unlimited + eligible queues report `UINT64_MAX` rate/capacity and always admit; a queue + with any nonzero scheduler `PIR` reports zero rate/capacity and drives the + admission-failure branch. +3. **The global trim policy and switch-wide DSCP-to-queue table** are pushed and + confirmed in VPP. **Validated:** `show sonic-ext trim` reports `size=256, + dscp=48 (symmetric), queue=6`. + +The remaining integration work, tracked by the feature story, is: + +4. Source the switch/port/queue trim counters through `getStatsExt` + ([7.11](#711-counters)) instead of returning the base's zero values, and + enable the deferred `sonic-mgmt` trim-counter cases. +5. Complete or validate the deferred surfaces — asymmetric per-port DSCP + (`FROM_TC`), `DYNAMIC` queue resolution, ACL disable-trim, GCU config, + feature/port toggles, port mirroring, SRv6, and reload/reboot persistence — + and remove the corresponding conditional_mark skips + ([14.3](#143-enablement-gating)). + +Capability is advertised as supported (`SWITCH_TRIMMING_CAPABLE=true`) and the +symmetric `packet_trimming` suite is enabled now that its end-to-end PTF cases +pass on the `t1-lag` testbed; the out-of-scope cases above remain skipped on +`vpp` until each is implemented and validated. + +### 13.2 Design limitations + +- Dynamic trim-queue resolution is not supported in the initial implementation; + only `STATIC` queue resolution is advertised and honored. +- The plugin masks the configured static trim queue to the range 0–7. The + supported configuration uses queue 6; explicit values above 7 are not rejected + by the vendor path. +- The admission shim models only the token-bucket behavior needed to produce a + recoverable admission failure; it is not full hardware-MMU or multi-level HQoS + emulation. +- SAI-VPP programs only unicast (or `ALL`) queue objects into the admission + shim. Multicast queues are skipped by type regardless of index so a colliding + multicast index cannot overwrite the same-indexed unicast admission slot. + Unicast queue indices at or above the plugin's + `SONIC_EXT_TRIM_MAX_QUEUES` (8) are also skipped. The configured trim queue + and every eligible lossy queue in the supported topology are unicast and below + the cap. +- Eligibility is recomputed on each trim-relevant attribute change rather than + cached at initialization ([7.8](#78-buffer-profile-and-queue-relationships)), + so a binding that lands after an earlier recompute (for example while buffer + configuration is still replaying on a cold boot) is picked up by the next + trim-relevant update rather than being missed. +- The IPv4 header checksum is updated after the DSCP rewrite, but IP and + transport **length** fields are deliberately not recalculated after truncation + (the original length is the congestion signal — + [7.4](#74-truncation-and-header-handling)). +- The admission shim uses one token bucket per `{port, queue}` updated on the + egress worker; multi-worker per-queue bucket sharding is future work and is not + required by the single-worker KVM topology. +- The global trim policy and the DSCP-to-queue table are switch-wide; per-port + QoS-map variation is not modeled in the initial implementation, which matches + the uniform testbed configuration. +- When no front-panel port currently has both a `DSCP_TO_TC` and a `TC_TO_QUEUE` + map bound, SAI-VPP keeps the plugin's last-good switch-wide DSCP-to-queue table + rather than pushing an all-zero table (which would collapse every DSCP onto + queue 0). A stale mapping only changes behavior if a misclassified queue is + both trim-eligible and blocking, and normal orchagent sequencing removes the + QoS maps together with the eligibility that would make the mapping matter + ([12](#12-error-handling)). +- The first implementation targets the DPDK virtio egress backend used by the + supported VPP KVM topology. Additional backends (for example the AF_PACKET + `host-interface` path used for veth ports) may reuse the same generic admission + node at their own pre-transmit point. +- Advanced interactions such as mirroring and SRv6 are enabled in sonic-mgmt only + after their focused validation passes. + +Future work includes dynamic queue resolution, per-worker bucket sharding, +per-port QoS-map fidelity, broader software QoS scheduling, and upstreaming any +generic VPP admission-failure integration through FD.io. + +## 14. Testing + +### 14.1 Unit and Component Tests + +**Target coverage.** The full trim design is intended to be covered by VPP +framework tests and SAI-VPP unit tests: + +- VPP dataplane: IPv4/IPv6 TCP/UDP packets; packets larger than, equal to, and + smaller than the trim size; symmetric and asymmetric DSCP resolution; ECN + preservation; ineligible buffer profiles; ACL trim disable; original-queue and + trim-queue admission failure; one-shot retry behavior; physical and LAG-member + egress attribution; chained-buffer truncation; counter read and clear behavior. +- SAI-VPP: capability query results; switch attribute handling and update + ordering; buffer-profile-to-queue relationship changes; QoS map create, + replace, and remove; ACL action create, update, and remove; VPP failure + propagation/retry; switch, port, and queue statistic mapping. + +**Delivered in this enablement.** The datapath itself was validated on standalone +VPP and on the `t1-lag` dev VM by reading the plugin state directly (`show +sonic-ext trim`, [13.1](#131-integration-status)); no automated VPP framework +tests are added for `sonic_ext` yet. The SAI-VPP unit tests +(`unittest/vslib/TestSwitchVpp.cpp`, built under `USE_VPP`) cover the pure +decision logic that gates the enablement: + +- `getTrimEnumValuesCapability` — the trim enum-values advertisement + ([7.12](#712-capability-advertisement)): `DSCP_VALUE` for the DSCP mode, + `STATIC` for the queue mode, and no match for unrelated attributes or object + types. +- `isTrimDataplaneAttr` — the set/create attribute classification that triggers a + per-queue trim re-resolve, including the port QoS-map bindings and QoS-map + edits. +- `getLagMemberEgressDisableAction` — pre-existing LAG-member egress logic. + +The remaining target coverage (VPP framework tests and +counter/failure-propagation/QoS-map SAI tests) is future work tracked with the +deferred datapath modes +([13.1](#131-integration-status)). + +### 14.2 System Tests + +The packet-trimming suite is enabled on the VPP `t1-lag` KVM topology in +stages: + +1. Runtime configuration and capability tests (not the deferred GCU config + modules). +2. Symmetric packet size and DSCP tests. +3. Asymmetric `FROM_TC` tests. +4. Feature and port-state toggles. +5. ACL disable trim. +6. Switch, port, and queue counters. +7. Trim-queue congestion. +8. Reload and reboot persistence. +9. Mirror and SRv6 interactions. + +Target regression coverage includes normal forwarding, LAG hashing and +failover, QoS mapping, ACL actions, and the existing SONiC-VPP regression +suites. + +**Validation status (2026-07-30).** Stages 1–2 are validated end to end on the +live `vms-kvm-vpp-t1-lag` testbed with the delivered `sonic_ext` datapath and the +SAI-VPP capability raised to `true`: + +| Test | Result | +|---|---| +| `test_trimming_configuration` | pass | +| `test_packet_size_after_trimming` (single-segment 400→256) | pass | +| `test_packet_size_after_trimming` (jumbo/multi-segment 5000→4084, 3000→256) | pass | +| `test_dscp_remapping_after_trimming` (trimmed and small-untrimmed) | pass | + +The jumbo case exercised the multi-segment DPDK mbuf fix in +[7.4](#74-truncation-and-header-handling). The asymmetric module, both GCU +config modules, counters, ACL disable-trim, feature/port toggles, reload/reboot +persistence, mirror, and SRv6 remain out of scope for this increment and are +tracked as future work in +[13.1](#131-integration-status). + +**Test-layer self-sufficiency (2026-07-30).** The suite now runs unattended on +VPP with no manual `redis-cli` preparation. Three `sonic-mgmt` gaps surfaced on the +minimal `Force10-S6000` config and were closed in the test helpers: + +- The per-block-queue `queue{1,3}_{uplink,downlink}_lossy_profile` BUFFER_PROFILEs + the suite references ship only in the real Mellanox SN5640 / Arista 7060X6 QoS + templates. `set_buffer_profile_for_block_queue` now creates the profile when it + is absent (idempotent no-op on the real SKUs), so the block-queue scheduler + wiring is available on VPP. +- `config load` of the pre-test backup is a merge, so a test-created + `BUFFER_QUEUE||` reference is not removed on platforms whose + base config lacks it (VPP). Teardown now clears those references before + deleting `trim_queue_test_profile`, so post-test YANG validation stays clean. +- The blocking-queue setup can also create lossy buffer profiles and + `BUFFER_QUEUE` bindings on VPP. Teardown records those bindings, removes them, + and deletes only profiles created by the fixture before restoring the backup. + +With these fixes the three in-scope cases pass with zero setup/teardown errors. + +### 14.3 Enablement Gating + +The suite is gated by two mechanisms that act at different granularities: + +1. The `sonic-mgmt` `conditional_mark` packet-trimming rule (OR-combined + conditions). On the VPP testbed `asic_type` is `vpp` (not `vs`) and `hwsku` + is `Force10-S6000`, so the hardware-SKU allowlist disjunct is the one that + would otherwise skip the suite; it is amended to exempt VPP (see the + implemented enablement below). This is the primary gate for **scoping** the + suite on VPP: it selects exactly the in-scope symmetric cases to run and + defers every out-of-scope case, keyed on `asic_type == vpp`. +2. The suite's own `skip_if_packet_trimming_not_supported` fixture, which reads + `STATE_DB SWITCH_CAPABILITY|switch:SWITCH_TRIMMING_CAPABLE` and skips the + whole suite when it is not `true`. + +Mechanism 2 is a coarse on/off switch, and it is **satisfied** on VPP: +`SWITCH_TRIMMING_CAPABLE` is `true` (the shared virtual-switch base reports the +switch trim capability as implemented; VPP narrows only the advertised trim +*enum values* to the modes the datapath honors — see +[7.12](#712-capability-advertisement)), so the fixture does not skip. Case-level +scoping is therefore performed entirely by Mechanism 1: `conditional_mark` runs +the in-scope symmetric cases on VPP and defers the rest. The suite is brought up +in the stages listed in [14.2](#142-system-tests), deferring each +not-yet-validated stage through `conditional_mark` rather than by gating the +switch capability off. + +**Implemented enablement (2026-07-30).** The `packet_trimming` `conditional_mark` +rule is updated so exactly the three in-scope symmetric cases run on VPP: + +- The hardware-SKU allowlist disjunct becomes + `hwsku not in [<4 real SKUs>] and asic_type != 'vpp'`, so the SKU gate no + longer skips the suite on VPP while every non-VPP platform keeps its prior + behavior. +- The deferred cases are skipped on VPP with per-test / per-file rules keyed on + `asic_type in ['vpp']`: the whole `test_packet_trimming_asymmetric.py` module, + both `test_packet_trimming_config_asymmetric.py` and + `test_packet_trimming_config_symmetric.py`, + and the symmetric `test_acl_action_with_trimming`, `test_trimming_with_srv6`, + `test_stability_during_feature_toggles`, + `test_trimming_during_port_admin_toggle`, + `test_trimming_with_reload_and_reboot`, `test_trimming_counters`, + `test_trimming_counters_with_feature_toggle`, and + `test_port_mirror_with_trimming` cases. Each carries a reason pointing at the + deferral in [13.1](#131-integration-status) and the tracking issue. + +This was validated by driving the plugin's own `find_all_matches` / +`evaluate_conditions` logic against the deployed rule file with the DUT's real +facts: on VPP the three in-scope cases run and all deferred/asymmetric cases +skip; on the real SN5640 SKU every case still runs; on a non-listed SKU and on +the generic `vs` KVM every case still skips. The longest-match-per-mark +semantics keep the VPP disjunct and the general rule mutually exclusive per +platform, so there is no cross-interference. + +The local KVM harness cannot exercise a `conditional_mark`-enforcing `pytest` +run because its `dut_basic_facts` ansible module is incompatible with the +harness ansible-core (`No module named ansible.module_utils.parse_utils`), which +is why in-harness runs use `--ignore-conditional-mark` and select cases with +`-k`. The rule is therefore validated at the plugin-decision level and behaves +normally in standard CI, where DUT fact-gathering works. + +## 15. Open Items + +Gate 0 (feasibility) is **resolved positively**: the VPP egress backend is DPDK +virtio with LCP TAP control-plane interfaces and has no native QoS/admission +substrate, so this design supplies that substrate as a generic software admission +shim in the `sonic_ext` plugin ([7.1](#71-admission-provider)) and builds the +trim action on top of it. The former open question — whether the backend could +supply recoverable admission directly or needed a software shim — is answered: +the shim approach is implemented and validated on standalone VPP. + +The remaining integration details and their current status are tracked by the +feature story ([13.1](#131-integration-status)): + +1. **Resolved.** SAI-VPP maps the SONiC scheduler onto the shim as open/closed: + no effective rate limit admits, while any nonzero + `MAX_BANDWIDTH_RATE` (including `SCHEDULER_BLOCK_DATA_PLANE`) programs a + zero-rate, zero-capacity bucket and rejects. The buffer profile controls + eligibility only. A proportional sub-line-rate `PIR` and real buffer capacity + are not modeled ([7.10](#710-scheduler-and-capacity-inputs)). +2. Add the target per-`{port, queue}` counters and `getStatsExt` sourcing, then + confirm the queue-index attribution expected by the `sonic-mgmt` counter + assertions (which read `trimpacket` on the trim-queue index) + ([7.11](#711-counters)). +3. **Resolved.** The [7.12](#712-capability-advertisement) enum-narrowing + override is in place: `SWITCH_TRIMMING_CAPABLE` is advertised `true` for the + symmetric core, with the trim enum values restricted to `DSCP_VALUE` and + `STATIC` so orchagent never programs the asymmetric modes VPP does not + implement. Out-of-scope cases are deferred in `sonic-mgmt` via + `conditional_mark` rather than by gating the capability off + ([14.3](#143-enablement-gating)). + +These items do not change the SONiC configuration or SAI contract, nor the +generic packet-trimming design described above. + +## 16. References + +- [SONiC Packet Trimming HLD](../packet_trimming/packet-trimming-design.md) +- [SONiC-VPP LAG Member Egress Disable HLD](vpp-lag-member-egress-disable-HLD.md) +- [SONiC HLD Template](../guidelines/hld_template.md) +- [sonic-buildimage issue #25789](https://github.com/sonic-net/sonic-buildimage/issues/25789) +- [SAI repository](https://github.com/opencomputeproject/SAI) +- [FD.io VPP](https://github.com/FDio/vpp)