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..a0f0b16118d 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..40049699e7b --- /dev/null +++ b/doc/vpp/vpp-packet-trimming-HLD.md @@ -0,0 +1,1133 @@ +# 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 | +| v1.1 | 2026-08-26 | Aaron Bernardino | Document disabling / opt-out behavior (§9.1) | +| v1.2 | 2026-08-31 | Aaron Bernardino | Surface binary-admission and switch-wide-QoS limitations in summary (review: lolyu) | + +## 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. + +> **Maturity and key limitations (read first).** On VPP, packet trimming in this +> increment is **functionally demonstrated, not yet operationally useful under +> real congestion**, for two reasons that are load-bearing enough to state up +> front (both detailed in [13.2](#132-design-limitations)): +> +> 1. **Binary admission, not occupancy-driven.** The delivered SAI-VPP +> translation programs each eligible queue's token bucket as either +> always-admit or always-reject; there is no proportional rate or +> buffer-occupancy modeling. An admission failure is therefore produced only +> by an explicitly blocking scheduler (the test topology's `PIR=1`), never by +> organic queue buildup under real traffic ([7.10](#710-scheduler-and-capacity-inputs)). +> 2. **Switch-wide QoS classification, not per-port.** Original-queue +> classification uses a single switch-wide DSCP-to-queue table built from the +> first port that has both QoS maps. This is correct only under uniform +> per-port QoS and is **not safe for heterogeneous-QoS deployments**, where a +> non-matching port can misclassify the original queue ([6.1](#61-control-plane-flow)). + +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 + +
+
+
+
+
Green = delivered this increment · dashed amber = deferred target design · grey dashed = conceptual/hardware view. Each block is annotated with its step and behaviour; open the PNG for full resolution.
+ +This flowchart is the full conceptual design; **dashed amber nodes mark elements +deferred in this increment** (see the diagram legend). Specifically, the ACL +trim-disable gate and the asymmetric-DSCP (`FROM_TC`) alternative are deferred: +the datapath applies the 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)). Trim eligibility itself (buffer profile +`DROP_AND_TRIM`) is delivered. 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. + +### 9.1 Disabling and Opting Out + +Packet trimming is opt-in and disabled by default; it cannot perturb normal +forwarding unless explicitly configured. Four levels of control exist: + +1. **Default off.** The `sonic-ext-trim-admission` feature node is registered on + the `interface-output` arc but is enabled on no interface at initialization + ([7.1](#71-admission-provider)). Until a queue is bound to a `DROP_AND_TRIM` + buffer profile ([7.8](#78-buffer-profile-and-queue-relationships)), the arc is + enabled on no port and there is zero incremental trim datapath cost. +2. **Per-port/per-queue eligibility — the true teardown path.** The arc is + enabled on a port only while that port has at least one trim-eligible queue. + Removing eligibility from a port's last eligible queue — by unbinding or + changing that queue's `DROP_AND_TRIM` buffer-profile binding via SAI SET — + tears the arc back down and returns the port to zero incremental cost. This + teardown is applied only while trimming is still globally enabled; the SAI + refresh path early-returns once global trimming is off. Buffer profiles + should therefore be removed **before** a global disable to fully return to + zero cost. +3. **Global functional off switch.** Setting `SAI_SWITCH_ATTR_PACKET_TRIM_SIZE` + to `0` clears the global policy (`trim_configured = 0`), after which no packet + is trimmed switch-wide. This is a *functional* disable: the admission node + bypasses every packet at its first check. It does not, by itself, remove + already-enabled per-port arcs, so a port that previously had an eligible queue + retains the arc and each egress packet still incurs a cheap admission-node + dispatch that immediately bypasses — a no-op, not a structural detach. + Combining it with per-queue eligibility removal (in that order) also drops the + arcs. +4. **Whole-plugin disable.** VPP's generic plugin loader honors + `plugin sonic_ext_plugin.so { disable }` in `startup.conf`, which removes the + entire `sonic_ext` plugin and all trim behavior. This is a coarse control that + also disables the plugin's other functions and is not trim-specific. + +There is no packet-trim-specific compile-time flag; the trim sources are compiled +unconditionally into `sonic_ext_plugin.so`. + +## 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. Because the delivered translation is binary (always-admit vs + always-reject), an admission failure is produced only by an explicitly + blocking scheduler (the test topology's `PIR=1`), never by organic queue + occupancy under real traffic. Trimming on VPP is thus functionally + demonstrated, not yet operationally useful under real congestion + ([7.10](#710-scheduler-and-capacity-inputs)). +- 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. This design is therefore **not safe for + heterogeneous-QoS deployments**: a port whose QoS maps differ from the + reference port can have its original queue misclassified (it only changes + behavior when the misclassified queue is both trim-eligible and blocking). +- 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|