(12) feat(acl): rte_acl backend + differential-vs-reference + benches#1576
Open
daniel-noland wants to merge 8 commits into
Open
(12) feat(acl): rte_acl backend + differential-vs-reference + benches#1576daniel-noland wants to merge 8 commits into
daniel-noland wants to merge 8 commits into
Conversation
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.
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.
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.
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.
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.
This was referenced May 31, 2026
Contributor
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). |
| caps = { version = "0.5.6", default-features = false, features = [] } | ||
| chrono = { version = "0.4.44", default-features = false, features = [] } | ||
| clap = { version = "4.6.1", default-features = true, features = [] } | ||
| criterion = { version = "0.8.2", default-features = false, features = ["cargo_bench_support"] } |
|
|
||
| [dev-dependencies] | ||
| bolero = { workspace = true, features = ["std"] } | ||
| criterion = { workspace = true } |
Comment on lines
+12
to
17
| #![allow(missing_docs)] | ||
| #![allow(clippy::missing_errors_doc, clippy::missing_panics_doc)] | ||
|
|
||
| #[cfg(feature = "dpdk")] | ||
| pub mod dpdk; | ||
| pub mod reference; |
Comment on lines
+113
to
+116
| let mut ptrs: ArrayVec<*const u8, MAX_BATCH> = ArrayVec::new(); | ||
| for buf in &bufs { | ||
| let _ = ptrs.try_push(buf.as_ptr()); | ||
| } |
Comment on lines
+117
to
+120
| let mut results: ArrayVec<u32, MAX_BATCH> = ArrayVec::new(); | ||
| for _ in 0..keys.len() { | ||
| let _ = results.try_push(0); | ||
| } |
Comment on lines
+312
to
+315
| pub fn lookup_bytes(&self, key: &[u8]) -> Option<&A> { | ||
| let expected = self.user_key_size(); | ||
| assert_eq!(key.len(), expected, "key length must equal user_key_size"); | ||
| let mut dpdk_buf = [0u8; crate::dpdk::lookup::MAX_USER_KEY_BYTES]; |
Comment on lines
+164
to
+166
| bench: (build "benches") | ||
| {{ _just_debuggable_ }} | ||
| for bench in ./results/benches/bin/*; do $bench --bench; done |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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):