From 0f721dd3e6666b265b479b79cde0824d8a6c2550 Mon Sep 17 00:00:00 2001 From: Nicolas Kagami Date: Fri, 21 Aug 2026 16:26:02 -0300 Subject: [PATCH] fix range offset for composite key and add vlan trunk test --- codegen/rust/src/pipeline.rs | 17 +++--- test/src/lib.rs | 2 + test/src/p4/vlan_trunk.p4 | 67 +++++++++++++++++++++++ test/src/vlan_trunk.rs | 102 +++++++++++++++++++++++++++++++++++ 4 files changed, 181 insertions(+), 7 deletions(-) create mode 100644 test/src/p4/vlan_trunk.p4 create mode 100644 test/src/vlan_trunk.rs diff --git a/codegen/rust/src/pipeline.rs b/codegen/rust/src/pipeline.rs index 991bbf20..851fb19c 100644 --- a/codegen/rust/src/pipeline.rs +++ b/codegen/rust/src/pipeline.rs @@ -729,13 +729,16 @@ impl<'a> PipelineGenerator<'a> { }); offset += 1; // for the prefix length byte } - MatchKind::Range => keys.push(quote! { - p4rs::extract_range_key( - keyset_data, - #offset, - #sz, - ) - }), + MatchKind::Range => { + keys.push(quote! { + p4rs::extract_range_key( + keyset_data, + #offset, + #sz, + ) + }); + offset += sz; // range takes len + len + } } offset += sz; } diff --git a/test/src/lib.rs b/test/src/lib.rs index 97e4cba4..a6d4f38b 100644 --- a/test/src/lib.rs +++ b/test/src/lib.rs @@ -29,6 +29,8 @@ mod table_in_egress_and_ingress; #[cfg(test)] mod vlan; #[cfg(test)] +mod vlan_trunk; +#[cfg(test)] mod vrf_router; pub mod data; diff --git a/test/src/p4/vlan_trunk.p4 b/test/src/p4/vlan_trunk.p4 new file mode 100644 index 00000000..8f6123e9 --- /dev/null +++ b/test/src/p4/vlan_trunk.p4 @@ -0,0 +1,67 @@ +#include +#include +#include + +SoftNPU( + parse(), + ingress(), + egress() +) main; + +struct headers_t { + ethernet_h ethernet; + vlan_h vlan; +} + +parser parse( + packet_in pkt, + out headers_t hdr, + inout ingress_metadata_t ingress, +){ + state start { + pkt.extract(hdr.ethernet); + if (hdr.ethernet.ether_type == 16w0x8100) { + transition vlan; + } + transition reject; + } + + state vlan { + pkt.extract(hdr.vlan); + transition accept; + } +} + +control ingress( + inout headers_t hdr, + inout ingress_metadata_t ingress, + inout egress_metadata_t egress, +) { + action forward(bit<16> port) { + egress.port = port; + } + + table trunk { + key = { + hdr.vlan.vid: range; + ingress.port: exact; + } + actions = { + forward; + } + default_action = NoAction; + } + + apply { + if (hdr.vlan.isValid()) { + trunk.apply(); + } + } +} + +control egress( + inout headers_t hdr, + inout ingress_metadata_t ingress, + inout egress_metadata_t egress, +) { +} diff --git a/test/src/vlan_trunk.rs b/test/src/vlan_trunk.rs new file mode 100644 index 00000000..953d0249 --- /dev/null +++ b/test/src/vlan_trunk.rs @@ -0,0 +1,102 @@ +use crate::softnpu::{RxFrame, SoftNpu, TxFrame}; +use crate::{expect_frames, muffins}; + +p4_macro::use_p4!( + p4 = "test/src/p4/vlan_trunk.p4", + pipeline_name = "vlan_trunk", +); + +// +// ~~~~~~~~~~ +// ~ ~ * *=======* +// ~ p4 ~ | | | +// ~ ~ |---| phy 0 | ingress +// ~~~~~~~~~~ | | | +// | | *=======* +// | | *=======* +// *==========* | | | +// | | <-- ( rx ) --- |---| phy 1 | ingress +// | pipeline | | | | +// | | --- ( tx ) --> | *=======* +// *==========* | *=======* +// | | | +// |---| phy 2 | uplink +// | | | +// | *=======* +// | *=======* +// | | | +// |---| phy 3 | uplink +// | | | +// * *=======* +// + +/// Return key bytes (range, exact) +fn trunk_key(begin: u16, end: u16, port: u16) -> Vec { + let mut buf = begin.to_le_bytes().to_vec(); + buf.extend_from_slice(&end.to_le_bytes()); + buf.extend_from_slice(&port.to_le_bytes()); + + buf +} + +#[test] +fn vlan_trunk() -> Result<(), anyhow::Error> { + let mut pipeline = main_pipeline::new(4); + + // + // add table entries + // + + // port 0: [100, 199] -> port 2 + let buf = trunk_key(100, 199, 0); + pipeline.add_ingress_trunk_entry("forward", &buf, &2u16.to_le_bytes(), 0); + + // port 1: [100, 199] -> port 3 + let buf = trunk_key(100, 199, 1); + pipeline.add_ingress_trunk_entry("forward", &buf, &3u16.to_le_bytes(), 0); + + // port 0: [300, 350] -> port 3 + let buf = trunk_key(300, 350, 0); + pipeline.add_ingress_trunk_entry("forward", &buf, &3u16.to_le_bytes(), 0); + + // + // run program + // + + let mut npu = SoftNpu::new(4, pipeline, false); + let phy0 = npu.phy(0); + let phy1 = npu.phy(1); + let phy2 = npu.phy(2); + let phy3 = npu.phy(3); + + npu.run(); + + let et = 0; + let msg = muffins!(); + + // Different redirects for each port + phy0.send(&[TxFrame::newv(phy2.mac, et, msg.0, 150)])?; + expect_frames!(phy2, &[RxFrame::newv(phy0.mac, et, msg.0, 150)]); + phy1.send(&[TxFrame::newv(phy3.mac, et, msg.1, 150)])?; + expect_frames!(phy3, &[RxFrame::newv(phy1.mac, et, msg.1, 150)]); + + // Range bounds are inclusive + phy0.send(&[TxFrame::newv(phy2.mac, et, msg.2, 100)])?; + expect_frames!(phy2, &[RxFrame::newv(phy0.mac, et, msg.2, 100)]); + phy0.send(&[TxFrame::newv(phy2.mac, et, msg.3, 199)])?; + expect_frames!(phy2, &[RxFrame::newv(phy0.mac, et, msg.3, 199)]); + + // Testing [300, 350] block for port 0 + phy0.send(&[TxFrame::newv(phy3.mac, et, msg.4, 320)])?; + expect_frames!(phy3, &[RxFrame::newv(phy0.mac, et, msg.4, 320)]); + + // No match -> should drop + phy0.send(&[TxFrame::newv(phy2.mac, et, b"dropped muffin", 250)])?; + phy1.send(&[TxFrame::newv(phy3.mac, et, b"lost muffin", 320)])?; + phy2.send(&[TxFrame::newv(phy2.mac, et, b"stray muffin", 150)])?; + + // Push a good one through + phy0.send(&[TxFrame::newv(phy2.mac, et, msg.5, 150)])?; + expect_frames!(phy2, &[RxFrame::newv(phy0.mac, et, msg.5, 150)]); + Ok(()) +}