(12) feat(acl): rte_acl backend + differential-vs-reference + benches#1576
(12) feat(acl): rte_acl backend + differential-vs-reference + benches#1576daniel-noland wants to merge 7 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds a DPDK rte_acl-powered backend for the dataplane-acl crate, plus differential/property tests and criterion benchmarks to validate correctness against the existing software reference backend and measure performance.
Changes:
- Introduces
acl::dpdkbackend (layout planning, rule lowering/splicing, install/build, typed + dynamic lookup APIs). - Adds DPDK-gated integration/property tests (reference-vs-DPDK differential checks, dynamic-shape fuzzing, header/metadata projection examples).
- Adds criterion benchmarks and Nix/Just plumbing to build and run bench binaries.
Reviewed changes
Copilot reviewed 20 out of 21 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| justfile | Adds bench recipe to build and execute bench binaries. |
| default.nix | Adds bench-builder derivation and exports benches. |
| Cargo.toml | Adds workspace dependency on criterion. |
| Cargo.lock | Locks new benchmark dependency graph (criterion + transitive deps). |
| acl/tests/property_predicate.rs | Differential property test for typed 5-tuple (v4/v6) against reference. |
| acl/tests/property_dyn_shape.rs | Differential property test for dynamic shapes (DPDK vs reference). |
| acl/tests/net_field_types.rs | Demonstrates net newtypes classification through DPDK and reference. |
| acl/tests/metadata_projection.rs | Demonstrates Projection-based classification using headers + metadata. |
| acl/tests/eal_install_classify.rs | EAL smoke test for install + classify + batch classify. |
| acl/tests/eal_classify_via_projection.rs | EAL smoke test classifying a real packet via projection. |
| acl/src/lib.rs | Exposes dpdk module and adds dpdk_table_alias! helper macro under feature gate. |
| acl/src/dpdk/mod.rs | Defines DPDK backend module surface. |
| acl/src/dpdk/rule.rs | Implements DPDK rule lowering + field splicing + related errors/tests. |
| acl/src/dpdk/lookup.rs | Implements typed DPDK lookup and batch classify helpers. |
| acl/src/dpdk/layout.rs | Implements layout planning + const extents for compile-time type aliases. |
| acl/src/dpdk/install.rs | Implements table installation/building into an AclContext. |
| acl/src/dpdk/dyn_table.rs | Implements dynamic-shape install + byte-key lookup path. |
| acl/Cargo.toml | Adds dpdk feature, DPDK/criterion dev-deps, and bench targets. |
| acl/benches/table_build.rs | Benchmarks table build cost (reference vs DPDK, v4/v6). |
| acl/benches/reference_five_tuple.rs | Benchmarks reference backend lookup patterns (v4/v6). |
| acl/benches/dpdk_five_tuple.rs | Benchmarks DPDK backend lookups (single + batch, v4/v6). |
3220e66 to
710b08b
Compare
16e2188 to
01d98b2
Compare
01d98b2 to
ddbc607
Compare
710b08b to
598ba60
Compare
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
19ca1a1 to
29232af
Compare
598ba60 to
08b4c60
Compare
| } | ||
| let mut results: ArrayVec<u32, MAX_BATCH> = ArrayVec::new(); | ||
| for _ in 0..keys.len() { | ||
| let _ = results.try_push(0); |
There was a problem hiding this comment.
I saw this paradigm before, surely there is a better way to get keys to be 0 while checking that it is large enough to have all of keys?
There was a problem hiding this comment.
Please fix this before merge.
Introduces the dataplane-acl crate with the software reference classifier. The DPDK rte_acl backend lands behind a feature gate in a follow-up PR. The reference backend is a linear-scan software classifier built on the canonical FieldPredicate form from match-action (rule.into_backend_fields::<Erased>()), so it speaks the same four predicate kinds (Prefix / Mask / Range / Exact) as every other backend. Two roles: 1. Differential-testing oracle against rte_acl (a future PR's differential property tests pit both backends against the same random rule + packet draws). 2. Non-lossy substrate for a small-delta cascade front over a slow tail backend. Layout: - src/lib.rs declares the crate-level docs and re-exports the reference module. The dpdk feature gate and dpdk_table_alias! macro land alongside the rte_acl backend itself in the next PR. - src/reference/table.rs is the typed surface: ReferenceTable<K, A> parameterised by a MatchKey and an action; RefRule wraps the lowered Erased predicates plus an action. Inline unit tests cover positional precedence (first match wins) and the four predicate kinds. - src/reference/dyn_table.rs is the runtime-shape twin: DynReferenceTable carries its FieldSpec layout at runtime so property tests can fuzz the schema itself. Returns DynShapeError on shape mismatch. just fmt; cargo check --workspace --all-targets passes. Signed-off-by: Daniel Noland <daniel@githedgehog.com>
29232af to
fbcf630
Compare
08b4c60 to
7a7d109
Compare
Lands the static type machinery for the DPDK `rte_acl` backend
behind the new `dpdk` feature gate: how a MatchKey's FIELD_SPECS maps
into rte_acl's per-field FieldDef array, and how the four
match-action *Spec predicate kinds lower into IntoBackendField for
the `Dpdk` backend marker. The runtime install / classify path
(install.rs, lookup.rs) and the dpdk_table_alias! macro land next.
acl/src/dpdk/:
- mod.rs declares the two submodules; carries a temporary
#![allow(dead_code)] because the layout's `stride` field and the
rule.rs RuleSpec fields are consumed only once install / lookup
arrive in the next PR. The allow goes away then.
- layout.rs has the rte_acl field planner: group fields by
input_index (rte_acl requires the first field to be one byte,
remaining fields grouped into <= 4-byte buckets), insert padding
for gaps, and yield a DpdkLayout { field_defs: [FieldDef; N],
stride, user_to_dpdk }. const_extents() is const fn so a const
alias can derive N / STRIDE from K::FIELD_SPECS without unstable
generic_const_exprs. Wide fields (Ipv6Addr, u128) decompose into
four u32 sub-fields the way l3fwd-acl does.
- rule.rs holds the Dpdk backend marker, the AclWord trait (blanket
impl over FixedSize via chunks()), the IntoBackendField impls
carrying each *Spec into a backend-typed AclField group, the
RuleSpec rule-field envelope, and splice_user_fields_to_dpdk for
reordering user-declared fields into rte_acl's layout-driven
ordering.
acl/src/lib.rs picks up the #[cfg(feature = "dpdk")] gate on
pub mod dpdk; (no macro yet -- the dpdk_table_alias! macro lands with
its lookup-side referent next PR).
acl/Cargo.toml grows the dpdk feature and the optional dpdk
workspace dep. No dev-deps yet.
just fmt; cargo check --workspace --all-targets and
cargo clippy -p dataplane-acl --features dpdk -- -D warnings pass.
Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Wires the layout planner and rule lowering from the previous PR into a working DPDK backend: build an AclContext from a MatchKey plus its rules, wrap it in a DpdkAclLookup, and classify packets through it. First EAL-touching PR in the acl stack. src/dpdk/: - install.rs is the from-K-plus-rules constructor: take a MatchKey, call plan_layout to get the rte_acl FieldDefs, build an AclContext, splice each user RuleSpec through layout's user_to_dpdk map into rte_acl's column order, hand the rules to the context, build, and wrap the built context in a DpdkAclLookup<K, N, STRIDE, A>. - lookup.rs is DpdkAclLookup itself: stack-packed key bytes (MAX_USER_KEY_BYTES sentinel feeds the compile-time guard in dpdk_table_alias!), the impl Lookup<K, A> single-shot path, and a batched classify_batch over a slice of K returning aligned actions. - mod.rs picks up pub mod install / pub mod lookup and drops the temporary #![allow(dead_code)] from the previous PR -- RuleSpec fields and DpdkLayout.stride now have readers. src/lib.rs gains the dpdk_table_alias! macro: dpdk_table_alias!(pub type FiveTupleTable<Verdict> = FiveTuple); yields a DpdkAclLookup<K, N, STRIDE, A> with N / STRIDE derived from K::FIELD_SPECS via const_extents. A const _: () = assert!(KEY_SIZE <= MAX_USER_KEY_BYTES) guards against keys that wouldn't fit the stack scratch buffer. The hidden __match_action module re-exports MatchKey so the macro resolves without a caller-side import. tests/eal_install_classify.rs is the smoke: derive a MatchKey, install two rules with priority precedence, classify via the single-shot path and the batch path, assert userdata. acl/Cargo.toml grows a single dev-dep -- self-overriding dpdk with the `test` feature on so #[with_eal] from dpdk-test-macros works. just fmt; cargo check --workspace --all-targets and cargo clippy -p dataplane-acl --features dpdk -- -D warnings pass. Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Adds the runtime-shape twin of DpdkAclLookup -- DynDpdkLookup carries its FieldSpec layout at runtime instead of in const generics -- and the shape-fuzz oracle that proves the byte-level pipeline agrees between the reference oracle and rte_acl over an unconstrained schema. src/dpdk/dyn_table.rs is DynDpdkLookup<A>: - new(name, max_rule_num, field_specs) plans the rte_acl layout from a Vec<FieldSpec> at runtime, builds an empty AclContext, and returns a typed lookup keyed by an Erased FieldPredicate vector. - add_rules takes Vec<DynRuleSpec> -- the runtime-shape rule carrier (priority, category_mask, lowered fields, action) -- and splices each rule's field bytes through the user_to_dpdk map into rte_acl's column order, then builds. - impl Lookup<Vec<FieldBytes>, A>: pack the probe bytes onto the stack scratch buffer in the layout's column order, hand them to rte_acl_classify, and translate the userdata hit back to &A. src/dpdk/mod.rs picks up pub mod dyn_table; alongside the typed path. tests/property_dyn_shape.rs is the schema fuzz: - bolero TypeGenerator yields a random Vec<FieldSpec>, a single rule matching that shape, and packet seeds. - For each shape: install the same rule into a DynReferenceTable (oracle) and a DynDpdkLookup, then probe both with both a hit-byte seed and a miss-byte seed. Assert agreement on every probe. - No MatchKey types involved -- exercises the byte-level pipeline end-to-end and catches drift in layout planning, the splice map, and rte_acl's per-predicate semantics simultaneously. acl/Cargo.toml gains bolero + match-action[bolero] dev-deps the test needs. just fmt; cargo check --workspace --all-targets and cargo clippy -p dataplane-acl --features dpdk -- -D warnings pass. Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Pure test broadening; no src changes. Adds the single-rule v4/v6 differential against the reference oracle and three Headers / metadata projection demos that exercise classify / classify_opt against real net::HeadersView packets. tests/property_predicate.rs is the differential. For a random 5-tuple rule + random hit/miss byte seeds drawn via match-action's FieldHit / FieldMiss generators, both the reference oracle and the DPDK backend must accept every hits() draw and reject every misses() draw. Parameterised over the address width via a sealed IpAddress trait so a single body covers v4 (Ipv4Addr) and v6 (Ipv6Addr) -- the DPDK wide-field split (one 16-byte address -> four 4-byte sub-fields) is exercised end-to-end by the v6 invocation. Single rule only; multi-rule differential is deferred (positional precedence vs numeric Priority). tests/eal_classify_via_projection.rs is the end-to-end projection demo: a real packet -> HeadersView -> Projection<FiveTuple> -> DPDK Lookup<FiveTuple, _> -> action. Shows Lookup::classify runs the projection and the lookup as a single call -- the call site reads table.classify(\&headers) and doesn't see the intermediate key construction. tests/metadata_projection.rs is the partial-projection demo. Header fields live in Headers; VRF / VNI live in PacketMeta. A projection source bundles &HeadersView with &PacketMeta and projects to Option<K>: the header part is total (shape proves presence), the metadata part narrows from its Option with ?. Missing metadata projects to None and Lookup::classify_opt turns that into a table miss with no explicit branch in user code. tests/net_field_types.rs uses net wire newtypes (TcpPort, UdpPort, Vni, UnicastIpv4Addr) directly as MatchKey fields with no acl-side AclWord impl, leaning on net's FixedSize impls (PR 2a) and the DPDK backend's blanket AclWord-over-FixedSize impl. acl/Cargo.toml grows the net[test_buffer, builder] dev-dep these projection demos need. just fmt; cargo check --workspace --all-targets and cargo clippy -p dataplane-acl --features dpdk -- -D warnings pass. Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Adds criterion benchmarks for both backends at v4 and v6 widths, plus the nix / just plumbing to produce bench binaries from a sandboxed build. acl/benches/: - reference_five_tuple.rs sweeps a deep miss (full per-rule scan) and an early hit through the reference's O(rules * fields) linear scan. Both widths. - dpdk_five_tuple.rs is the rte_acl companion: trie walk cost (close to flat in rule count), miss vs hit, single-shot vs SIMD batch. v6 exercises the wide-field split (one 16-byte address -> four 4-byte sub-fields). Requires a live EAL. - table_build.rs measures construction cost vs rule count: reference (lower + Vec wrap) and DPDK (rte_acl_build, the update-latency cost). Both widths. iter_batched so teardown is excluded. acl/Cargo.toml gets the criterion dev-dep and three harness = false [[bench]] entries. Workspace Cargo.toml gets the criterion = 0.5.1 shared dep entry. default.nix adds a bench-builder derivation: cargo bench --no-run under the profile-appropriate DPDK sysroot, then copies each compiled benchmark into $out/bin (stripping cargo's -<hash> suffix). Linked against the optimized DPDK when profile = release. justfile adds a bench recipe that builds the benches package and runs every binary under results/benches/bin/ in turn. just fmt; cargo check --workspace --all-targets and cargo clippy -p dataplane-acl --features dpdk -- -D warnings pass. Signed-off-by: Daniel Noland <daniel@githedgehog.com>
DpdkAclLookup<K,A> drops N_FIELDS/STRIDE and holds Box<dyn DynClassifier>; install_table<K,A> dispatches the field count at runtime via build_classifier_n shared with the dyn install path; adds DynClassifier::classify_batch so the typed batch path keeps real rte_acl batching through the type erasure; dispatch ceiling raised to MAX_FIELDS (64); keys packed into a single runtime-layout.stride arena (batch path drops the redundant per-key copy); removes lookup_via_bytes/dpdk_key_bytes and simplifies the dpdk_table_alias! macro (no const_extents). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
c795ed9 to
19de724
Compare
Stack (12). Base:
pr/daniel-noland/acl-reference.The
rte_aclbackend and everything proving it matches the reference oracle.This is the largest PR -- the irreducible DPDK lowering complexity:
feat(acl/dpdk): layout planner + rule lowering.feat(acl/dpdk): install +DpdkAclLookup+ EAL smoke.feat(acl/dpdk):DynDpdkLookup+ shape-fuzz property test.test(acl): differential property (reference vs DPDK) + Headers/metadataprojection demos.
feat(acl): criterion benchmarks + nix bench-builder.Review stack (merge bottom -> top):