Skip to content

userspace: add bounded port mirror runtime slice#1408

Merged
psaab merged 12 commits into
masterfrom
codex/1403-1376-port-mirror-runtime
May 18, 2026
Merged

userspace: add bounded port mirror runtime slice#1408
psaab merged 12 commits into
masterfrom
codex/1403-1376-port-mirror-runtime

Conversation

@psaab
Copy link
Copy Markdown
Owner

@psaab psaab commented May 17, 2026

Summary

Refs #1376.

Adds a bounded forwarded-path port-mirror runtime slice for the userspace dataplane:

  • wires mirror config into ForwardingState
  • samples per ingress binding and clones the full L2 frame while original bytes are still available
  • queues the clone as a discardable prepared TX request to the configured output binding
  • keeps clone pressure lossy via TX-frame reserve and pending-backlog limit
  • records admitted/no-frame/no-binding/queue-full mirror counters through Rust status, Go protocol, and Go status formatting

This intentionally leaves the userspace capability gate in place. Full #1376 closure still needs ingress/transmit-path parity beyond pending-forward TX, prebuilt/deferred path coverage, tcpdump validation on the mirror output, and pressure evidence proving mirror load does not hurt primary forwarding.

Validation

  • go test ./pkg/dataplane/userspace
  • cargo test --manifest-path userspace-dp/Cargo.toml mirror::
  • cargo test --manifest-path userspace-dp/Cargo.toml binding_counters_snapshot_serializes_with_expected_wire_keys
  • cargo test --manifest-path userspace-dp/Cargo.toml config_snapshot_mirror_configs_roundtrip
  • cargo test --manifest-path userspace-dp/Cargo.toml mirror_counters_binding_status_wire_roundtrip_and_projection
  • git diff --check

Copilot AI review requested due to automatic review settings May 17, 2026 18:52
@psaab
Copy link
Copy Markdown
Owner Author

psaab commented May 17, 2026

@copilot review

Copy link
Copy Markdown
Contributor

Copilot AI commented May 17, 2026

@copilot review

Adversarial review complete on 0f02ae2: no additional correctness/perf regressions found that warrant code changes in this PR. Validation run: go test ./pkg/dataplane/userspace and targeted mirror/status tests passed; Rust cargo test ... mirror:: is blocked in this runner by missing libelf headers (libelf.h/gelf.h).

Copilot finished work on behalf of psaab May 17, 2026 18:55
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds initial userspace-dp port-mirroring support (#1376) by implementing a bounded “forwarded-path” mirror clone path, wiring mirror configs into runtime forwarding state, and surfacing mirror counters through Rust/Go status and CLI formatting (while intentionally keeping the userspace capability gate in place).

Changes:

  • Implement bounded mirror cloning in the pending-forward TX path with per-binding sampling and lossy backpressure controls.
  • Wire mirror configs into ForwardingState and add per-binding mirror counters in live state, status snapshots, and protocol structs.
  • Extend Go status formatting/tests and update docs to reflect partial runtime support and remaining gating.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
userspace-dp/src/protocol.rs Adds mirror counters to BindingStatus + BindingCountersSnapshot and tests for wire round-trip/projection.
userspace-dp/src/main_tests.rs Extends wire-key serialization tests to include mirror counter keys.
userspace-dp/src/afxdp/worker/mod.rs Adds per-binding mirror sampling counter and a test-only BindingWorker constructor for mirror tests; extends live snapshot struct.
userspace-dp/src/afxdp/umem/mod.rs Adds atomic mirror counters to BindingLiveState and includes them in snapshots.
userspace-dp/src/afxdp/types/forwarding.rs Adds mirror_configs to ForwardingState and defines MirrorRuntimeConfig.
userspace-dp/src/afxdp/tx/dispatch.rs Hooks mirror selection + clone enqueue into the forwarded pending-TX path.
userspace-dp/src/afxdp/mod.rs Wires the new mirror module into the AF_XDP dataplane module tree.
userspace-dp/src/afxdp/mirror.rs New module implementing sampling, clone enqueue, and mirror counter recording + unit tests.
userspace-dp/src/afxdp/forwarding_build.rs Populates runtime mirror config map from the config snapshot.
userspace-dp/src/afxdp/coordinator/mod.rs Threads mirror counters into coordinator-maintained binding status and reset paths.
pkg/dataplane/userspace/statusfmt.go Includes mirror counters in the CLI status summary aggregation/output.
pkg/dataplane/userspace/statusfmt_test.go Updates summary output expectations for mirror counters.
pkg/dataplane/userspace/protocol.go Adds mirror counter fields to Go protocol types for status decoding/encoding.
pkg/dataplane/userspace/protocol_test.go Adds JSON wire-key and round-trip tests for mirror counters in Go types.
docs/userspace-dataplane-gaps.md Updates capability-gap doc to reflect “gated; partial runtime” port-mirroring support.
docs/pr/1373-retire-ebpf-dataplane/plan-1376-port-mirroring.md Documents the current runtime slice, counters, and expanded test expectations.

Comment thread userspace-dp/src/afxdp/umem/mod.rs Outdated
Comment on lines +374 to +375
/// #1376: mirror clone dropped because the output binding did not
/// have enough reserved TX frames available.
@psaab
Copy link
Copy Markdown
Owner Author

psaab commented May 17, 2026

Round-1 dual-review consolidated synthesis on 0f02ae24

Reviewer Verdict
Codex MERGE-NEEDS-MAJOR (2 MAJOR + 1 MEDIUM)
Gemini Pro 3 MERGE-NEEDS-MAJOR (3 MAJOR)

Both converge MERGE-NEEDS-MAJOR. 4 distinct issues across reviewers.

Codex MAJOR 1 — VLAN/logical interface mirror handling broken

pkg/dataplane/userspace/snapshot.go:153 keys mirrors by configured interface ifindex. VLAN/logical units carry child/synthetic Ifindex + ParentIfindex (snapshot.go:660). Runtime lookup uses raw request.meta.ingress_ifindex (afxdp/tx/dispatch.rs:184) and output binding uses config.output_ifindex directly (afxdp/mirror.rs:51). Never resolves (parent_ifindex, vlan_id) → logical_ifindex or output egress_ifindex → bind_ifindex.

Worked failure: parent-bound VLAN/reth mirror inputs miss entirely; logical outputs drop as NoBinding.

Codex MAJOR 2 — Flow-cache fast path bypasses mirroring

Cached forward hits rewrite and queue PreparedTxRequest directly at poll_descriptor.rs:221-315, skipping enqueue_pending_forwards which is the ONLY mirror hook (tx/dispatch.rs:184-201).

Worked failure: Established session mirrors the MISS packet, then stops mirroring all cache-hit packets. Operator sees first packet of every flow mirrored but nothing else.

Gemini MAJOR 1 — Cross-worker blackholing

userspace-dp/src/afxdp/mirror.rs:51-61:

let target_binding_index = binding_lookup.target_index(
    ingress_index, ingress_binding.ifindex, ingress_queue_id, config.output_ifindex,
);
let Some(target_binding) = target_binding_index.and_then(...) else {
    return MirrorCloneResult::NoBinding;
};

WorkerBindingLookup is per-coordinator and only indexes BindingWorker slices on the LOCAL thread. If output_ifindex is on a different worker, target_index = None → silently dropped as NoBinding.

Worked failure: Mirror ingress on worker-0, output on worker-3 → every clone dropped. No telemetry signal beyond NoBinding counter.

Gemini MAJOR 2 — CoS bypass + telemetry contamination

userspace-dp/src/afxdp/mirror.rs:105 hardcodes cos_queue_id: None. Mirror clones with cos_queue_id=None skip token bucket queues and dump directly into TX ring via drain_pending_tx post-CoS backup loop.

Worked failure: Mirror under pressure bypasses #760 CoS limits, saturates physical link, AND fires binding.live.owner_profile_owner.post_drain_backup_bytes metric (designed to hunt cap-bypass bugs) → operator-visible false alarm masking the real bypass.

Gemini MAJOR 3 — Plan-doc claims unmet

  • Plan: "commit validation rejects duplicate ingress mirror entries" — not implemented; compiler.go blindly invokes dp.SetMirrorConfig() which silently overwrites
  • Plan: lists mirror::duplicate_ingress_ifindex_rejected + mirror::ipv4_ipv6_full_frame_preservation tests — NEITHER exists

Codex MEDIUM — Tests don't pin end-to-end

Mostly DTO round-trips + enqueue_mirror_clone helper tests. No test that builds snapshot → ForwardingState → runs enqueue_pending_forwards → asserts clone output/counters. No VLAN/logical-interface test. No flow-cache bypass test.

Wins (both reviewers)

  • ✓ Simple physical/unit-0 pending-forward path is wired
  • ✓ Backwards-compat for absent mirror_configs and missing status counters
  • ✓ L2 frame preservation (Gemini: copies full L2 frame including 802.1Q tags)
  • ✓ Capability gate still rejects port mirroring → production fail-closed

Recommendation

Block on:

  1. Resolve VLAN/logical ifindex via parent+vlan→logical lookup (Codex MAJOR 1)
  2. Wire mirror enqueue into flow-cache hit fast path (Codex MAJOR 2)
  3. Handle cross-worker mirror via shared queue/IPC or document the limitation (Gemini MAJOR 1)
  4. Respect CoS via proper cos_queue_id assignment OR document mirror-bypasses-CoS as a stated design choice (Gemini MAJOR 2)
  5. Implement compile-time duplicate-ingress validation; add the two tests the plan committed to (Gemini MAJOR 3)
  6. Add end-to-end snapshot→ForwardingState→clone-output test pinning the actual production shape

Codex task-mpa6l22e-qa1tu4; Gemini task-mpa6lmrq-oll8cz. Not merging — author's decision.

@psaab
Copy link
Copy Markdown
Owner Author

psaab commented May 17, 2026

@copilot review

Copilot AI review requested due to automatic review settings May 17, 2026 20:19
@psaab psaab review requested due to automatic review settings May 17, 2026 20:19
Copy link
Copy Markdown
Contributor

Copilot AI commented May 17, 2026

Round-1 dual-review consolidated synthesis on 0f02ae24

| Reviewer | Verdict |...

Addressed in 9bdcaa2. Mirror output now resolves through resolve_tx_binding_ifindex before binding/live-target lookup (fixes logical output no-binding drops), and added production-shape tests in mirror.rs covering parent+VLAN ingress resolution plus logical-output live enqueue. Flow-cache fast-path mirror hook, cross-worker mirror live-target map, and CoS queue assignment were already in c2c3403. Duplicate-ingress validation + test already exist in Go (buildMirrorConfigSnapshots / TestBuildMirrorConfigSnapshotsRejectsDuplicateIngressIfindex) and still pass.

@copilot review

Adversarial re-review complete on latest head (9bdcaa2): fixed remaining logical-output bind resolution in mirror enqueue path, added production-shape mirror tests for logical ingress/output mapping, and corrected mirror drop-counter docs to match runtime semantics. Validation: go test ./pkg/dataplane/userspace, go test ./pkg/dataplane/userspace -run 'TestBuildMirrorConfigSnapshotsRejectsDuplicateIngressIfindex|TestBuildMirrorConfigSnapshots', and git diff --check passed; Rust cargo test ... mirror:: remains blocked in this runner by missing libelf headers/pkg-config.

Copilot finished work on behalf of psaab May 17, 2026 20:19
@psaab
Copy link
Copy Markdown
Owner Author

psaab commented May 17, 2026

Round-2 Codex review on c2c3403c

Codex verdict: MERGE-NEEDS-MAJOR (2 NEW MAJORs from r2 changes)

r1 finding verification

  • VLAN/logical ingress lookup: addressed. resolve_mirror_config() now resolves (ingress_ifindex, vlan) before parent fallback.
  • Pending-only coverage: partially. Self-target flow-cache path wired, but pre-sampling copy bug (see MAJOR 1).
  • Cross-worker mirror delivery: functionally addressed but lacks isolation (see MAJOR 2).
  • CoS egress/default queue + backup escape: mostly addressed. Mirror CoS queue selection set; default-queue-on-CoS-interface backup drops handled.

NEW MAJOR 1 — Flow-cache mirror copies BEFORE sampling

userspace-dp/src/afxdp/poll_descriptor.rs:263-268: configured mirror does packet_frame.to_vec() BEFORE select_mirror_config() runs inside enqueue_sampled_mirror_clone_to_live().

Worked failure: mirror with rate = 1000 still allocates+copies EVERY flow-cache hit, then discards 999/1000 after the fact. Hot-path production regression for low-rate sampling.

Fix: sample first (cheap predicate), copy only if admitted.

NEW MAJOR 2 — Cross-worker mirror output lacks mirror-specific bound

Same-worker path checks MIRROR_PENDING_LIMIT at userspace-dp/src/afxdp/mirror.rs:95-101.

Cross-worker goes straight through target_live.try_enqueue_tx_owned() at mirror.rs:156-168, bounded ONLY by the shared redirect inbox cap (umem/mod.rs:1123-1133).

Worked failure: mirror clones occupy the NORMAL cross-worker TX inbox. Mirror pressure is NOT isolated from primary forwarding (contradicts plan claim of "small pending-backlog limit"). Heavy mirror traffic on worker-A → worker-B's TX inbox saturates with mirror clones → worker-B's primary forwarding stalls.

Fix: add MIRROR_PENDING_LIMIT-equivalent reserve/cap on cross-worker mirror path.

Tests + Plan-doc carryovers

  • Tests still helper-level; don't drive poll_binding_process_descriptor() through flow-cache branch, don't prove sampled flow-cache mirrors avoid unsampled allocation, don't prove cross-worker mirror pressure can't harm primary forwarding
  • Plan still lists mirror::duplicate_ingress_ifindex_rejected + mirror::ipv4_ipv6_full_frame_preservation — neither exists
  • Plan claims "small pending-backlog limit" but cross-worker output bypasses that limit

Codex task: task-mpa7x6pf-3z63lf. Awaiting Gemini r2. Not merging — author's decision.

@psaab
Copy link
Copy Markdown
Owner Author

psaab commented May 17, 2026

Round-2 dual-review consolidated synthesis on c2c3403c

Reviewer Verdict
Codex MERGE-NEEDS-MAJOR (2 NEW MAJORs from r2 changes)
Gemini Pro 3 MERGE-NEEDS-MAJOR (2 MAJOR: flow-cache double-allocation)

Both converge MERGE-NEEDS-MAJOR. Both independently caught the flow-cache pre-sampling allocation.

r1 wins (both reviewers confirm)

  • ✓ VLAN/logical ingress lookup: resolve_mirror_config resolves (ingress_ifindex, vlan) before parent fallback
  • ✓ Cross-worker mirror delivery: functionally works
  • ✓ CoS egress/default queue + backup escape: addressed (mirror clones now bound by mirror_cos_queue_id)
  • Codex #784 mixed-head invariant honored in CoS fallback scans

Codex MAJOR 1 + Gemini MAJOR 1 — Flow-cache mirror copies before sampling

userspace-dp/src/afxdp/poll_descriptor.rs:263-268:

let mirror_frame = resolve_mirror_config(
    worker_ctx.forwarding,
    meta.ingress_ifindex as i32,
    meta.ingress_vlan_id,
).map(|_| packet_frame.to_vec());

Worked failure (both reviewers): mirror with rate = 10000. Every flow-cache hit on mirror-configured interface allocates+copies the full frame BEFORE select_mirror_config() evaluates sampling. 9,999/10,000 allocations are discarded post-fact.

Fix: sample first (cheap predicate), to_vec() only if admitted.

Gemini MAJOR 2 — Double-allocation of admitted clones

When admitted, system pays allocation tax TWICE:

  1. poll_descriptor.rs creates mirror_frame = Vec<u8> and passes as &[u8] to enqueue_sampled_mirror_clone_to_live
  2. enqueue_mirror_clone_to_live then does TxRequest { bytes: frame.to_vec(), ... } — second allocation

Fix: either check sample rate before the first allocation, OR construct TxRequest to take ownership of the buffer (move semantics).

Codex MAJOR 2 — Cross-worker mirror lacks isolation

Same-worker path uses MIRROR_PENDING_LIMIT at mirror.rs:95-101. Cross-worker uses target_live.try_enqueue_tx_owned() at :156-168, bounded ONLY by shared redirect inbox (umem/mod.rs:1123-1133).

Worked failure: mirror clones occupy worker-B's normal TX inbox. Heavy mirror on worker-A → worker-B primary forwarding stalls.

Fix: add MIRROR_PENDING_LIMIT-equivalent reserve/cap on cross-worker mirror path.

Tests + Plan-doc (both reviewers)

  • Tests still helper-level; don't drive production through flow-cache branch or prove sampled mirrors avoid unsampled allocation, or prove cross-worker mirror pressure can't harm primary forwarding
  • Plan still lists mirror::duplicate_ingress_ifindex_rejected + mirror::ipv4_ipv6_full_frame_preservation tests that don't exist
  • Plan claims "small pending-backlog limit" but cross-worker output bypasses it

Recommendation

Block on:

  1. Sample-before-allocate in poll_descriptor.rs:263 (move select_mirror_config() ahead of packet_frame.to_vec())
  2. Eliminate double-allocation by passing owned Vec<u8> directly into TxRequest
  3. Add MIRROR_PENDING_LIMIT-equivalent reserve on cross-worker mirror path

Codex task-mpa7x6pf-3z63lf; Gemini task-mpa7xgdb-lvvimk. Not merging — author's decision.

@psaab
Copy link
Copy Markdown
Owner Author

psaab commented May 17, 2026

Round-3 Codex review on 9bdcaa29

Codex verdict: MERGE-NEEDS-MAJOR — r3 does NOT address either r2 MAJOR.

Still open from r2

  • Allocate-before-sample: poll_descriptor.rs still does resolve_mirror_config(...).map(|_| packet_frame.to_vec()) BEFORE enqueue_sampled_mirror_clone_to_live. Sampling still happens later inside select_mirror_config. rate=10000 still allocates on every configured mirror hit.

  • Cross-worker isolation: same-worker mirror has MIRROR_PENDING_LIMIT + MIRROR_TX_FRAME_RESERVE pre-copy. Cross-worker path still only does length/target lookup → TxRequest { bytes: frame.to_vec(), ... } → generic try_enqueue_tx_owned. No mirror-specific reserve.

What r3 DID fix (different issue)

resolve_tx_binding_ifindex() helper translates logical→physical for mirror output target. Closes a logical-output-binding gap that wasn't called out as a r2 MAJOR.

NEW r3 issue — possible test arity breakage

Codex by inspection: r3 appears to have added an extra 22 argument to two existing enqueue_mirror_clone(...) test calls while the function signature did NOT gain a parameter. Source mismatch visible in commit. If this is real, the tests don't compile.

Codex couldn't run tests (read-only env). Author should verify locally with cargo test.

Recommendation

Block on:

  1. Verify test arity (cargo test -p userspace-dp afxdp::mirror) — fix if broken
  2. Move sampling decision BEFORE to_vec() in poll_descriptor.rs:263 (r2 MAJOR 1)
  3. Add cross-worker mirror reserve/limit (r2 MAJOR 2)

Codex task: task-mpa9bcof-37poou. Not merging — author's decision.

Copilot AI review requested due to automatic review settings May 17, 2026 21:21
@psaab
Copy link
Copy Markdown
Owner Author

psaab commented May 17, 2026

@copilot review

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

userspace-dp/src/afxdp/umem/mod.rs:1106

  • try_admit_tx_owned() records record_redirect_inbox_overflow() (incrementing tx_errors + redirect_inbox_overflow_drops) even though it hasn't actually attempted an enqueue yet. When used as a preflight (e.g. mirror admission), this can double-count drops once the subsequent enqueue also fails, and it also attributes mirror pressure to the generic redirect overflow counters. Consider making this a pure capacity check (no counter side effects) and leaving counter bumps to the actual push path, or folding admission+push into a single method that can return QueueFull without touching redirect overflow counters for mirror clones.
    pub(super) fn try_admit_tx_owned(&self) -> Result<(), ()> {
        let pending_len = self.pending_tx.len();
        let max_pending = self.max_pending_tx.load(Ordering::Relaxed) as usize;
        if (max_pending > 0 && pending_len >= max_pending)
            || pending_len >= self.pending_tx.capacity()
        {
            self.record_redirect_inbox_overflow();
            return Err(());
        }
        Ok(())

Comment thread userspace-dp/src/afxdp/umem/mod.rs Outdated
Comment on lines +1097 to +1124
pub(super) fn try_admit_tx_owned(&self) -> Result<(), ()> {
let pending_len = self.pending_tx.len();
let max_pending = self.max_pending_tx.load(Ordering::Relaxed) as usize;
if (max_pending > 0 && pending_len >= max_pending)
|| pending_len >= self.pending_tx.capacity()
{
self.record_redirect_inbox_overflow();
return Err(());
}
Ok(())
}

pub(super) fn try_admit_mirror_tx_owned(&self, mirror_pending_limit: usize) -> Result<(), ()> {
let pending_len = self.pending_tx.len();
let mut admission_cap = self.pending_tx.capacity();
let max_pending = self.max_pending_tx.load(Ordering::Relaxed) as usize;
if max_pending > 0 {
admission_cap = admission_cap.min(max_pending);
}
if mirror_pending_limit > 0 {
admission_cap = admission_cap.min(mirror_pending_limit);
}
if pending_len >= admission_cap {
return Err(());
}
Ok(())
}

Comment thread userspace-dp/src/afxdp/mirror.rs Outdated
Comment on lines +192 to +208
pub(in crate::afxdp) fn admit_mirror_clone_to_live(
mirror_targets: &MirrorTargetMap,
mirror_tx_ifindex: i32,
ingress_queue_id: u32,
frame_len: usize,
) -> Result<Arc<BindingLiveState>, MirrorCloneResult> {
if frame_len > tx_frame_capacity() {
return Err(MirrorCloneResult::NoFrame);
}
let Some(target_live) = mirror_targets.target_live(mirror_tx_ifindex, ingress_queue_id)
else {
return Err(MirrorCloneResult::NoBinding);
};
if target_live
.try_admit_mirror_tx_owned(MIRROR_PENDING_LIMIT)
.is_err()
{
Copy link
Copy Markdown
Contributor

Copilot AI commented May 17, 2026

Round-2 Codex review on c2c3403c

Codex verdict: MERGE-NEEDS-MAJOR (2 NEW MAJORs from r2 changes)...

Addressed in 6014c1d. Flow-cache mirror path now samples/admission-checks before cloning, so unsampled hits do not allocate/copy. Cross-worker mirror now has a mirror-specific admission cap (try_admit_mirror_tx_owned(MIRROR_PENDING_LIMIT)) before enqueue, and regression coverage was added for queue headroom isolation.

Round-2 dual-review consolidated synthesis on c2c3403c

| Reviewer | Verdict |...

Fixed in 6014c1d. The flow-cache path no longer allocates before sampling, and admitted cross-worker clones now avoid double-allocation by moving the owned frame into TxRequest (enqueue_admitted_mirror_clone_to_live). Cross-worker mirror admission is now bounded by MIRROR_PENDING_LIMIT via explicit pre-admission.

Round-3 Codex review on 9bdcaa29

Codex verdict: MERGE-NEEDS-MAJOR — r3 does NOT address either r2 MAJOR....

Verified and addressed in 6014c1d: sample-before-allocate is now in place on the flow-cache path, and cross-worker mirror admission now enforces a mirror-specific cap before inbox enqueue. Test-call arity is consistent with the current enqueue_mirror_clone_to_live(...) signature in head.

@copilot review

Adversarial re-review on 6014c1d: addressed the remaining r2/r3 concerns. Flow-cache mirror now samples/admission-checks before cloning, admitted cross-worker clones move owned buffers (no second clone), and cross-worker mirror admission is explicitly bounded by MIRROR_PENDING_LIMIT. Validation run: go test ./pkg/dataplane/userspace and git diff --check passed; cargo test --manifest-path userspace-dp/Cargo.toml mirror:: remains blocked in this runner by missing libelf headers/pkg-config.

Copilot finished work on behalf of psaab May 17, 2026 21:28
@psaab
Copy link
Copy Markdown
Owner Author

psaab commented May 17, 2026

Claude r4 review on 6014c1d1

Verdict: MERGE-READY (pending Codex/Gemini cross-check)

r3 MAJOR closures verified

r3 MAJOR 1 — Sample-before-allocate: CLOSED.

New poll_descriptor.rs:260-300 flow (commit c964f52):

  1. resolve_mirror_config(...).map(|config| { mirror_sample_allows(config.rate, &mut next_counter); (config, next_counter, selected) }) — sample decision happens FIRST, returns (config, next_counter, selected) tuple, no allocation yet.
  2. .filter(|(_, _, selected)| *selected) — drops sample-rejected frames.
  3. .map(|(config, _, _)| ... admit_mirror_clone_to_live(...)) — admission check (no allocation).
  4. mirror_frame = mirror_admission.as_ref().and_then(|(_, admission)| admission.as_ref().ok()).map(|_| packet_frame.to_vec()) — allocation ONLY after admission succeeded.

r3 MAJOR 2 — Cross-worker isolation: CLOSED.

  • New try_admit_mirror_tx_owned(MIRROR_PENDING_LIMIT) in umem/mod.rs:1109-1122 (commit 6014c1d) enforces a mirror-specific cap distinct from general TX cap.
  • admit_mirror_clone_to_live (mirror.rs:185) uses this admission check — same enforcement path for same-worker AND cross-worker (the cross-worker path went through enqueue_mirror_clone_to_live which now calls admit_mirror_clone_to_live first).
  • New test live_mirror_queue_full_reserves_headroom_above_mirror_limit (mirror.rs:623+) fills mirror slots to limit and asserts a subsequent non-mirror TX is NOT rejected — pins the headroom-reserve contract.

Verification

  • Walked poll_descriptor.rs:260-355 line-by-line. Order is: resolve → sample → admit → allocate.
  • Walked umem/mod.rs:1106-1123 try_admit_mirror_tx_owned. Three caps min'd: pending_tx.capacity(), max_pending_tx, mirror_pending_limit. Cap = min(...).
  • New test asserts result == MirrorCloneResult::QueueFull AND queued.len() == MIRROR_PENDING_LIMIT — pins both rejection AND no spillover.

Open risk / observation

  • try_admit_mirror_tx_owned reads pending_tx.len() (Relaxed read of VecDeque length implied) without a lock — between admit and enqueue, another thread may push and exceed the soft cap by a small margin. Documented "drop-newest on overflow" behavior in enqueue_tx_owned makes this benign.
  • enqueue_sampled_mirror_clone_to_live is now #[cfg_attr(not(test), allow(dead_code))] — keep an eye that it doesn't get reintroduced into a hot path by mistake.

Awaiting Codex (task-mpaahj6y-407546) + Gemini Pro 3 (task-mpaahy4h-g5h2p8). Not merging — author's decision.

@psaab
Copy link
Copy Markdown
Owner Author

psaab commented May 17, 2026

Round-4 triple-review synthesis on 6014c1d1

Reviewer Verdict
Claude MERGE-READY
Codex MERGE-NEEDS-MAJOR
Gemini Pro 3 MERGE-READY

Sample-before-allocate — CLOSED (all three agree)

Codex walked poll_descriptor.rs:263-295: resolve → sample (mirror_sample_allows) → admit → packet_frame.to_vec() happens last. Gemini quoted the same chain. Closed.

Cross-worker isolation — Codex MAJOR

Codex argues the cross-worker preflight is racy AND lacks a TX-frame reserve:

"try_admit_mirror_tx_owned() is not a reservation. MpscInbox::len() is explicitly approximate/non-linearizable at mpsc_inbox.rs:86-95, and the final push goes through try_enqueue_tx_owned() / try_push_redirect_inbox() at umem/mod.rs:1093-1095 and 1152-1162, which only re-check max_pending_tx/hard capacity, not MIRROR_PENDING_LIMIT. Concurrent producers can all pass admission before any one producer publishes the push."

"Cross-worker never checks target free_tx_frames, and the eventual owner-side TX path still allocates with free_tx_frames.pop_front() in tx/transmit.rs:116-118 with no mirror reserve. Cross-worker mirror TxRequests are also indistinguishable from primary redirect requests once queued."

Gemini counterpoint:

"The same-worker path checks MIRROR_TX_FRAME_RESERVE because it pulls directly from the local free_tx_frames ring at enqueue time. The cross-worker path cannot safely evaluate free_tx_frames across threads; instead, it caps heap allocations to MIRROR_PENDING_LIMIT. The egress worker evaluates its free_tx_frames reserve upon dequeuing."

My read

Codex's race claim is sharp and substantiated. Whether it rises to MAJOR depends on threat model:

  • Heap OOM protection: bound holds (heap can't grow unbounded — TX_BATCH_SIZE caps cross-worker queue per target via try_push_redirect_inbox hard cap).
  • Strict mirror-vs-primary isolation: bound does NOT hold under contention. N concurrent producers each see len < MIRROR_PENDING_LIMIT, all push, queue reaches MIRROR_PENDING_LIMIT + N - 1. Then non-mirror TX waits behind that backlog.
  • Egress TX reserve: confirmed absent (tx/transmit.rs:116-118 free_tx_frames.pop_front() has no mirror gate).
  • Observability: both same-worker-full and cross-worker-full route to mirror_drops_queue_full — operator can't distinguish.

Gemini's argument that "cross-thread reserve isn't possible" understates it — you CAN reserve via an atomic counter on the target binding, just not via direct ring observation. The egress worker could decrement a mirror_frames_in_flight AtomicU32 on transmit and the producer could check mirror_frames_in_flight < limit atomically.

Observability gap — VERIFIED

record_mirror_clone_result() at mirror.rs:299-300 maps all QueueFull cases to one counter. Operator can't tell "my mirror destination worker is backed up" from "my own worker is backed up". Distinct counters would unblock triage.

Recommendation

Block candidates (Codex MAJORs):

  1. Cross-worker race: add atomic in-flight counter on target binding, or restructure to do reservation+commit pattern.
  2. Add mirror_drops_cross_worker_queue_full counter distinct from same-worker.
  3. Consider cross-worker mirror TX-frame reserve (decrement on transmit, gate at admit).

Strongly consider: add a concurrent-producer test that asserts MIRROR_PENDING_LIMIT is honored under contention — current test is serial.

Sample-before-allocate is closed; that's a real win this round. Author's call on whether the residual cross-worker gaps rise to block this PR or get their own issue.

Codex task: task-mpaahj6y-407546. Gemini task: task-mpaahy4h-g5h2p8. Not merging — author's decision.

@psaab
Copy link
Copy Markdown
Owner Author

psaab commented May 17, 2026

@copilot review

Copy link
Copy Markdown
Contributor

Copilot AI commented May 17, 2026

Round-4 triple-review synthesis on 6014c1d1

| Reviewer | Verdict |...

Addressed by 9c64cc2. Cross-worker mirror admission is now reservation-based (CAS on pending_tx_admitted) and enqueue consumes that reservation, so the mirror cap is enforced at commit-time rather than best-effort len() preflight; mirror-cap rejects no longer bump generic redirect-overflow counters.

@copilot review

Adversarial re-review on head (9c64cc2): the previous cross-worker isolation race is closed via reservation-backed mirror admission (PendingTxAdmission over pending_tx_admitted CAS) and sample-before-allocate remains intact on the flow-cache path. Validation: go test ./pkg/dataplane/userspace and git diff --check passed; cargo test --manifest-path userspace-dp/Cargo.toml mirror:: is still blocked here by missing libelf headers/pkg-config.

Copilot finished work on behalf of psaab May 17, 2026 22:04
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 45 out of 45 changed files in this pull request and generated 1 comment.

"TX completions: 5",
"Mirrored packets: 10",
"Mirrored bytes: 1280",
"Mirror drops: no-frame=1 tx-frame-reserve=0 no-binding=2 queue-full=3",
@psaab
Copy link
Copy Markdown
Owner Author

psaab commented May 18, 2026

Claude r8 review on 7a382d10

Verdict: MERGE-READY (pending Codex/Gemini)

r7 third-strip MAJOR closure verified

Both cross_binding.rs redirect sites now propagate mirror_clone:

cos/cross_binding.rs:211:

let local_req = TxRequest {
    ...
    dscp_rewrite: req.dscp_rewrite,
    mirror_clone: req.mirror_clone,   // was: hardcoded false
};

cos/cross_binding.rs:269:

let local_req = TxRequest {
    ...
    dscp_rewrite: req.dscp_rewrite,
    mirror_clone: req.mirror_clone,   // was: hardcoded false
};

Both redirect_prepared_cos_request_to_owner and redirect_prepared_cos_request_to_owner_binding now preserve identity through the cross-binding redirect.

95-LOC tests in cross_binding_tests.rs

The new test additions should pin BOTH redirect paths. Need Codex to verify they exercise:

  • redirect_prepared_cos_request_to_owner with PreparedTxRequest { mirror_clone: true, ... } → resulting TxRequest at owner has mirror_clone: true
  • redirect_prepared_cos_request_to_owner_binding (live owner-queue) with same input → preserved identity

Hostile fourth-site grep — clean

I grepped EVERY mirror_clone: field assignment in userspace-dp/src/:

mirror.rs:224         mirror_clone: true              (production - mirror enqueue creates the tag)
mirror.rs:315         mirror_clone: true              (production - enqueue_admitted_mirror_clone_to_live)
mirror.rs:447         mirror_clone: false             (TEST FIXTURE - test_meta helper)
mirror.rs:1264,1311   mirror_clone: true              (TEST)
cos/cross_binding.rs:211,269  mirror_clone: req.mirror_clone   (r8 fix)
types/tx.rs:24,69     field definitions

No fourth strip site. All other TxRequest/PreparedTxRequest constructions (in forward_request.rs, tunnel.rs, inject.rs, dispatch.rs, neighbor_dispatch.rs, tcp_segmentation.rs, cos_classify.rs:452) are non-mirror paths that correctly set mirror_clone: false. Rust struct-literal semantics prevent default-defaulting, so any new construction site must explicitly decide.

Defensive note for future PRs

This is the third round of the same bug pattern (r6: clone_prepared_request_for_cos strip; r7: cos/queue_service/drain.rs reserve gates; r8: cross_binding redirect strip). Memory entry feedback_wire_protocol_both_sides.md already captures the discipline. Consider a builder pattern or From impl for cross-clone construction so the field is explicit:

impl From<&PreparedTxRequest> for TxRequest {
    fn from(p: &PreparedTxRequest) -> Self {
        TxRequest {
            bytes: ...,
            mirror_clone: p.mirror_clone,
            ...
        }
    }
}

Then cross_binding.rs would do let local_req = TxRequest::from(&req).with_bytes(frame) — no risk of forgetting a field.

This is a follow-up issue suggestion, not blocking.

Recommendation

MERGE-READY on the r7 third-strip MAJOR. Author has closed every cited site across three rounds (r6 → r7 → r8) and no fourth recurrence visible.

Awaiting Codex (task-mpamc0pa-2dz07u) + Gemini Pro 3 (task-mpamcdp4-72kk5y). Not merging — author's decision.

@psaab
Copy link
Copy Markdown
Owner Author

psaab commented May 18, 2026

Follow-up hardening on 82704447

Read the linked #1376 issue/contract and closed the r8 MINOR as structural hardening. The key invariant from #1376 is that mirror clones are discardable diagnostic traffic, so the clone identity must survive every prepared/local transition; otherwise reserve/drop accounting can silently treat mirror traffic as primary traffic.

Changes:

  • Added TxRequest::into_prepared_request(...) to centralize local -> prepared identity propagation.
  • Added PreparedTxRequest::to_local_request(...) to centralize prepared -> local identity propagation.
  • Switched both CoS owner redirect paths to PreparedTxRequest::to_local_request(...).
  • Switched the CoS prepared-demotion helper to the same builder.
  • Switched local-to-prepared CoS materialization to TxRequest::into_prepared_request(...).

This removes the repeated explicit-field discipline that caused the mirror_clone strip recurrence. The existing r8 redirect-path tests still pin both owner redirect paths with mirror_clone=true, and the existing clone_prepared_request_for_cos_returns_local_copy_with_metadata test now exercises the shared prepared -> local conversion path.

Validation:

  • cargo test --manifest-path userspace-dp/Cargo.toml redirect_prepared_cos_request_to_owner -- --test-threads=1
  • cargo test --manifest-path userspace-dp/Cargo.toml cos_classify::tests:: --no-fail-fast
  • cargo test --manifest-path userspace-dp/Cargo.toml mirror:: --no-fail-fast
  • go test ./pkg/dataplane/userspace
  • git diff --check

@psaab
Copy link
Copy Markdown
Owner Author

psaab commented May 18, 2026

Round-8 synthesis on 7a382d10 (Gemini pending — see note)

Reviewer Verdict
Claude MERGE-READY
Codex MERGE-NEEDS-MINOR (hardening recommendation)
Gemini Pro 3 (still tool-calling at 11m35s — partial result will follow)

r7 third-strip MAJOR — CLOSED (both Claude + Codex agree)

Codex confirmed:

"Both cited functions now propagate req.mirror_clone: cross_binding.rs:211 and cross_binding.rs:269."

"Tests pin both paths: redirect_prepared_cos_request_to_owner_preserves_mirror_clone_on_worker_command constructs a prepared mirror request and asserts the WorkerCommand::EnqueueShapedLocal TxRequest has mirror_clone == true. redirect_prepared_cos_request_to_owner_binding_preserves_mirror_clone_on_live_queue asserts the owner-live queued TxRequest has mirror_clone == true."

Hostile fourth-site search — clean

Codex independently grepped all TxRequest {} / PreparedTxRequest {} construction sites in userspace-dp/src/afxdp/ at 7a382d10:

"Runtime mirror_clone: false sites remain in origin/forwarding paths: coordinator/inject.rs, neighbor_dispatch.rs, poll_descriptor.rs, tunnel.rs, tx/dispatch.rs, and tx/tcp_segmentation.rs. I do not see another prepared/local mirror conversion that strips an existing mirror clone. Mirror producers in mirror.rs set true; CoS conversion helpers and the two r8 sites now propagate."

Claude grep matched exactly: all mirror_clone: true sites are mirror enqueue creation; all mirror_clone: false outside tests are non-mirror originator paths (forward/inject/tunnel/ICMP/segmentation/redirect/dispatch); the two r8 sites correctly use req.mirror_clone.

Codex MINOR — structural hardening recommendation

"Still explicit-field discipline. TxRequest and PreparedTxRequest are plain structs with a raw mirror_clone: bool; there is no builder, From, or typed constructor forcing clone identity propagation. The compiler forces the field to be mentioned, but it does not force the correct value."

"This is just the cited sites, plus targeted tests. It is not systematic. Given this is round 3 of the same failure mode, I'd require a minor hardening patch before calling it merge-ready: centralize prepared-to-local cloning into one helper or constructor and make the mirror/origin distinction impossible to casually lose."

Codex's framing is sharp. Three rounds of the same bug pattern (r6 clone_prepared_request_for_cos, r7 CoS drain reserve gates, r8 cross_binding redirect) suggest the bug isn't a one-off — it's a structural risk that recurs whenever a new prepared-to-local conversion is added. A From<&PreparedTxRequest> for TxRequest impl (or PreparedTxRequest::to_local_tx_request() method) that mechanically propagates mirror_clone removes the failure mode entirely.

Recommendation

Merge-ready functionally — the three flagged sites across r6/r7/r8 are all closed. Strongly consider the From-impl / centralized-helper hardening before merge (or as immediate follow-up issue), per Codex's MINOR. This is the third recurrence of the same class.

Gemini Pro 3 task task-mpamcdp4-72kk5y is still tool-calling at 11m35s. Will update synthesis if Gemini surfaces a new finding.

Codex task: task-mpamc0pa-2dz07u. Not merging — author's decision.

@psaab
Copy link
Copy Markdown
Owner Author

psaab commented May 18, 2026

Claude r9 review on 82704447

Verdict: MERGE-READY (pending Codex/Gemini)

Codex r8 MINOR closure — structural hardening landed

Codex r8 recommended:

"centralize prepared-to-local cloning into one helper or constructor and make the mirror/origin distinction impossible to casually lose."

r9 ships exactly that. types/tx.rs +40:

impl TxRequest {
    pub(in crate::afxdp) fn into_prepared_request(
        self, offset: u64, recycle: PreparedTxRecycle,
    ) -> PreparedTxRequest {
        PreparedTxRequest {
            offset, len: self.bytes.len() as u32, recycle,
            expected_ports: self.expected_ports,
            expected_addr_family: self.expected_addr_family,
            expected_protocol: self.expected_protocol,
            flow_key: self.flow_key,
            egress_ifindex: self.egress_ifindex,
            cos_queue_id: self.cos_queue_id,
            dscp_rewrite: self.dscp_rewrite,
            mirror_clone: self.mirror_clone,   // ← propagated
        }
    }
}

impl PreparedTxRequest {
    pub(in crate::afxdp) fn to_local_request(&self, bytes: Vec<u8>) -> TxRequest {
        TxRequest {
            bytes,
            expected_ports: self.expected_ports,
            expected_addr_family: self.expected_addr_family,
            expected_protocol: self.expected_protocol,
            flow_key: self.flow_key.clone(),
            egress_ifindex: self.egress_ifindex,
            cos_queue_id: self.cos_queue_id,
            dscp_rewrite: self.dscp_rewrite,
            mirror_clone: self.mirror_clone,   // ← propagated
        }
    }
}

Call sites collapsed — verified via grep

All four prior conversion sites now one-line:

cos/cross_binding.rs:201 (was lines 199-211, 13 lines):

let local_req = req.to_local_request(frame);

cos/cross_binding.rs:249 (was lines 257-269, 13 lines):

let local_req = req.to_local_request(frame);

tx/cos_classify.rs:456 (was lines 453-466, 14 lines):

Ok(req.into_prepared_request(offset, PreparedTxRecycle::FreeTxFrame))

tx/cos_classify.rs:521 (was lines 521-534, 14 lines):

Some(req.to_local_request(frame))

Hostile grep — no surviving strip risk

grep -rn 'mirror_clone *[:=]' userspace-dp/src/afxdp/ at 82704447:

mirror.rs:224         mirror_clone: true              (mirror enqueue creation — correct)
mirror.rs:315         mirror_clone: true              (admitted mirror enqueue — correct)
mirror.rs:447         mirror_clone: false             (TEST fixture)
mirror.rs:1264,1311   mirror_clone: true              (tests)
types/tx.rs:24,92     pub mirror_clone: bool          (field definitions)
types/tx.rs:45,107    mirror_clone: self.mirror_clone (propagation in the two helpers)

cross_binding.rs:211,269 and cos_classify.rs:467,545 (the four sites that had explicit mirror_clone: assignments in r6-r8) are GONE — replaced by helper calls. The only production mirror_clone: literals are the two true settings at mirror creation. Any future PR that adds a new prepared↔local conversion site will either use the helpers (structural propagation) OR have to manually list all 11 fields (compiler-forced, but discouraged).

Why this is the right fix

The three-round bug pattern (r6 clone_prepared_request_for_cos strip, r7 CoS drain gates, r8 cross_binding redirect strip) was fundamentally a structural risk of manual field-listing for TxRequest{} / PreparedTxRequest{} conversions. The compiler forced the field to be mentioned but didn't force propagation. r9 removes the failure mode by making propagation the default. Future PRs touching this layer cannot accidentally strip identity without writing strictly more code than the helper.

Recommendation

MERGE-READY. Structural fix matches Codex's r8 recommendation verbatim. Closes the recurrence class.

Awaiting Codex (task-mpamv35p-lh6dms) + Gemini Pro 3 (task-mpamvi8p-kn95gq). Not merging — author's decision.

@psaab
Copy link
Copy Markdown
Owner Author

psaab commented May 18, 2026

Round-9 synthesis on 82704447 (Gemini pending — Claude+Codex converged)

Reviewer Verdict
Claude MERGE-READY
Codex MERGE-READY (no code findings)
Gemini Pro 3 (still tool-calling at 5m42s — second prolonged session in a row)

Codex confirmation

"The helpers preserve all identity fields, including mirror_clone, flow_key, CoS/DSCP, expected metadata, and egress ifindex in tx.rs:29 and tx.rs:97. The four target sites are converted: cross_binding.rs:202, cross_binding.rs:250, cos_classify.rs:456, cos_classify.rs:524."

"I found no remaining manual prepared-to-local or local-to-prepared identity-copy sites. Production TxRequest { ... } / PreparedTxRequest { ... } constructors outside these helpers are origin builders, not clones from the opposite request type."

"No default-value or field-shadowing trap found. The helpers enumerate fields explicitly, so future field additions fail compile instead of silently defaulting."

"to_local_request(&self, bytes) is the right shape here because callers still need the original prepared request to recycle or return on failure."

Independent grep verification (Claude)

mirror_clone: field assignments on 82704447 (7-PR Rust files):

  • mirror.rs:224, 315mirror_clone: true (mirror enqueue creation, correct)
  • mirror.rs:447, 1264, 1311 — test fixtures
  • types/tx.rs:24, 92 — field definitions
  • types/tx.rs:45, 107mirror_clone: self.mirror_clone in the two new helpers

The 4 prior strip-risk sites (cross_binding.rs:211,269 + cos_classify.rs:467,545) no longer have explicit mirror_clone: literals — they use the helpers.

Why this closes the recurrence class

Three rounds of the same bug pattern (r6/r7/r8 mirror_clone strip at three different sites) → r9 makes propagation the default rather than a discipline. Adding a 5th conversion site would now require either:

  1. Using the helpers (structural propagation) — easy
  2. Manually listing all 10 fields explicitly — strictly more code than the helper

The economic incentive flips. Code review still matters but the failure mode is no longer "casual oversight" — it's "deliberate inversion of a clearer pattern."

Recommendation

Merge-ready. Four-round mirror PR arc concluded with a structural hardening commit. The three flagged sites across r6/r7/r8 are functionally closed, and r9 prevents the next site from recurring.

Will update if Gemini surfaces a finding after its long tool-call session resolves. Per feedback_gemini_infra_outage_merge_policy: not blocking on Gemini given Claude + Codex have converged with quoted-line evidence.

Codex task: task-mpamv35p-lh6dms. Gemini task: task-mpamvi8p-kn95gq (pending). Not merging — author's decision.

@psaab
Copy link
Copy Markdown
Owner Author

psaab commented May 18, 2026

Round-9 final synthesis on 82704447

Reviewer Verdict
Claude MERGE-READY
Codex MERGE-READY
Gemini Pro 3 MERGE-READY

All three converge with quoted evidence. Updating prior partial synthesis.

Gemini confirmation (with quoted code at 82704447)

"A. PreparedTxRequest::to_local_request(&self, bytes) — does it propagate mirror_clone? Yes. The helper correctly propagates the field:" (with verbatim quote of to_local_request showing mirror_clone: self.mirror_clone)

"B. TxRequest::into_prepared_request(self, offset, recycle) — does it propagate mirror_clone? Yes. The field is cleanly mapped:" (verbatim quote)

"C. All 4 callsites converted to use the helpers? Yes. The diff confirms exact application across all four targeting sites. A repository-wide grep on production code confirms no other cross-conversions were missed."

"D. Any subtle field omission risk in the helpers? No. Because the helpers do not use the .. struct update syntax (e.g. ..Default::default()), Rust's struct initialization rules mandate that every field is explicitly provided. Any future fields added to either TxRequest or PreparedTxRequest will trigger a hard compiler error inside these helpers until explicitly accounted for. This eliminates the risk of silent field omission across the codebase."

Net

  • The three-round bug pattern (r6 strip → r7 strip → r8 strip) is structurally closed by r9's centralized helpers.
  • All 4 prior strip-risk sites collapse to helper calls.
  • The compiler now enforces field completeness on the conversion boundary — silent omission impossible.

Recommendation

Merge-ready. Four-round port-mirror runtime PR concluded with structural hardening.

Codex task: task-mpamv35p-lh6dms. Gemini task: task-mpamvi8p-kn95gq. Not merging — author's decision.

Copilot AI review requested due to automatic review settings May 18, 2026 03:26
@psaab psaab force-pushed the codex/1403-1376-port-mirror-runtime branch from 8270444 to 8cc149d Compare May 18, 2026 03:26
@psaab psaab force-pushed the codex/1403-1376-port-mirror-runtime branch from 8cc149d to 8a43bad Compare May 18, 2026 03:28
@psaab psaab merged commit 449a39c into master May 18, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 46 out of 46 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

pkg/dataplane/userspace/statusfmt_test.go:83

  • The status summary output now prints mirror drops with same-worker and cross-worker fields, but this test only asserts the prefix up through queue-full=.... Consider updating the expected substring to include same-worker=... cross-worker=... so regressions in the newly added fields are caught.
		"Bound bindings:            2/2",
		"XSK-registered bindings:   1/2",
		"Zerocopy bindings:         1/2",
		"Shared UMEM bindings:      1/2",
		"Armed queues:              0/2",

- Counters exposed through Rust status, Go protocol, CLI, and Prometheus:
- Counters exposed through Rust status, Go protocol, and CLI summary:
`mirrored_packets`, `mirrored_bytes`, `mirror_drops_no_frame`,
`mirror_drops_no_binding`, and `mirror_drops_queue_full`.
psaab added a commit that referenced this pull request May 18, 2026
Adds bounded port mirror runtime slice (#1408) in userspace.

- Fix port mirror runtime forwarding paths
- userspace-dp: fix mirror logical output bind resolution
- userspace-dp: harden mirror admission isolation
- userspace-dp: sample-first mirror and isolate cross-worker backlog
- userspace-dp: reserve cross-worker mirror inbox slots
- userspace-dp: preserve tx frame reserve for mirrors

Touches userspace-dp/src/afxdp, userspace-dp/src/protocol.rs,
pkg/dataplane/userspace, and docs/pr/1373-retire-ebpf-dataplane across
documentation, tests, userspace dataplane, and dataplane integration. The
largest file deltas are userspace-dp/src/afxdp/mirror.rs,
userspace-dp/src/afxdp/umem/mod.rs, and
userspace-dp/src/afxdp/worker/mod.rs. The diff is 2622 additions and 86
deletions across 46 files.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants