From 2685a3341541e228c1f0694561dbb3994b2caad3 Mon Sep 17 00:00:00 2001 From: Nicolas Kagami Date: Fri, 21 Aug 2026 14:39:42 -0300 Subject: [PATCH 1/2] fix lpm extraction and add vrf router test --- codegen/rust/src/pipeline.rs | 17 ++--- lang/p4rs/src/lib.rs | 10 +-- test/src/lib.rs | 2 + test/src/p4/vrf_router.p4 | 68 ++++++++++++++++++++ test/src/vrf_router.rs | 118 +++++++++++++++++++++++++++++++++++ 5 files changed, 203 insertions(+), 12 deletions(-) create mode 100644 test/src/p4/vrf_router.p4 create mode 100644 test/src/vrf_router.rs diff --git a/codegen/rust/src/pipeline.rs b/codegen/rust/src/pipeline.rs index dd7a673a..991bbf20 100644 --- a/codegen/rust/src/pipeline.rs +++ b/codegen/rust/src/pipeline.rs @@ -719,13 +719,16 @@ impl<'a> PipelineGenerator<'a> { }); offset += 1; // for care/dontcare indicator } - MatchKind::LongestPrefixMatch => keys.push(quote! { - p4rs::extract_lpm_key( - keyset_data, - #offset, - #sz, - ) - }), + MatchKind::LongestPrefixMatch => { + keys.push(quote! { + p4rs::extract_lpm_key( + keyset_data, + #offset, + #sz, + ) + }); + offset += 1; // for the prefix length byte + } MatchKind::Range => keys.push(quote! { p4rs::extract_range_key( keyset_data, diff --git a/lang/p4rs/src/lib.rs b/lang/p4rs/src/lib.rs index 4d9d49c8..debb77f3 100644 --- a/lang/p4rs/src/lib.rs +++ b/lang/p4rs/src/lib.rs @@ -334,23 +334,23 @@ pub fn extract_ternary_key( pub fn extract_lpm_key( keyset_data: &[u8], offset: usize, - _len: usize, + len: usize, ) -> table::Key { - let (addr, len) = match keyset_data.len() { + let (addr, len) = match len { // IPv4 - 5 => { + 4 => { let data: [u8; 4] = keyset_data[offset..offset + 4].try_into().unwrap(); (IpAddr::from(data), keyset_data[offset + 4]) } // IPv6 - 17 => { + 16 => { let data: [u8; 16] = keyset_data[offset..offset + 16].try_into().unwrap(); (IpAddr::from(data), keyset_data[offset + 16]) } x => { - panic!("lpm: key must be len 5 (ipv4) or 17 (ipv6) found {}", x); + panic!("lpm: data len must be 4 (ipv4) or 16 (ipv6) found {}", x); } }; diff --git a/test/src/lib.rs b/test/src/lib.rs index e04c91c4..97e4cba4 100644 --- a/test/src/lib.rs +++ b/test/src/lib.rs @@ -28,6 +28,8 @@ mod range; mod table_in_egress_and_ingress; #[cfg(test)] mod vlan; +#[cfg(test)] +mod vrf_router; pub mod data; pub mod packet; diff --git a/test/src/p4/vrf_router.p4 b/test/src/p4/vrf_router.p4 new file mode 100644 index 00000000..165fa59e --- /dev/null +++ b/test/src/p4/vrf_router.p4 @@ -0,0 +1,68 @@ +#include +#include +#include + +SoftNPU( + parse(), + ingress(), + egress() +) main; + +struct headers_t { + ethernet_h ethernet; + ipv4_h ipv4; +} + +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 == 16w0x0800) { + transition ipv4; + } + transition reject; + } + + state ipv4 { + pkt.extract(hdr.ipv4); + 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; + } + + // VRF segregated by ingress port + table vrf_router { + key = { + hdr.ipv4.dst: lpm; + ingress.port: exact; + } + actions = { + forward; + } + default_action = NoAction; + } + + apply { + if (hdr.ipv4.isValid()) { + vrf_router.apply(); + } + } +} + +control egress( + inout headers_t hdr, + inout ingress_metadata_t ingress, + inout egress_metadata_t egress, +) { +} diff --git a/test/src/vrf_router.rs b/test/src/vrf_router.rs new file mode 100644 index 00000000..59089252 --- /dev/null +++ b/test/src/vrf_router.rs @@ -0,0 +1,118 @@ +use crate::softnpu::{Interface4, RxFrame, SoftNpu}; +use crate::{expect_frames, muffins}; +use std::net::Ipv4Addr; + +p4_macro::use_p4!( + p4 = "test/src/p4/vrf_router.p4", + pipeline_name = "vrf_router", +); + +// A simple VRF router with composite (lpm, exact) key. +// +// ~~~~~~~~~~ +// ~ ~ * *=======* +// ~ p4 ~ | | | +// ~ ~ |---| phy 0 | vrf a +// ~~~~~~~~~~ | | | +// | | *=======* +// | | *=======* +// *==========* | | | +// | | <-- ( rx ) --- |---| phy 1 | vrf b +// | pipeline | | | | +// | | --- ( tx ) --> | *=======* +// *==========* | *=======* +// | | | +// |---| phy 2 | +// | | | +// | *=======* +// | *=======* +// | | | +// |---| phy 3 | +// | | | +// * *=======* +// + +/// Return key bytes (prefix, length, exact) +fn vrf_key(prefix: Ipv4Addr, len: u8, port: u16) -> Vec { + let mut buf = prefix.octets().to_vec(); + buf.push(len); + buf.extend_from_slice(&port.to_le_bytes()); + + buf +} + +#[test] +fn vrf_router() -> Result<(), anyhow::Error> { + let mut pipeline = main_pipeline::new(4); + + // + // add table entries + // + + // vrf a (port 0): 10.1.0.0/16 -> port 2 + let buf = vrf_key("10.1.0.0".parse().unwrap(), 16, 0); + pipeline.add_ingress_vrf_router_entry( + "forward", + &buf, + &2u16.to_le_bytes(), + 0, + ); + + // vrf b (port 0): 10.1.0.0/16 -> port 3 + let buf = vrf_key("10.1.0.0".parse().unwrap(), 16, 1); + pipeline.add_ingress_vrf_router_entry( + "forward", + &buf, + &3u16.to_le_bytes(), + 0, + ); + + // vrf a (port 0): 10.1.1.0/24 -> port 1 + let buf = vrf_key("10.1.1.0".parse().unwrap(), 24, 0); + pipeline.add_ingress_vrf_router_entry( + "forward", + &buf, + &1u16.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); + + let if0 = Interface4::new(phy0.clone(), "1.0.0.1".parse().unwrap()); + let if1 = Interface4::new(phy1.clone(), "1.0.0.2".parse().unwrap()); + let if2 = Interface4::new(phy2.clone(), "1.0.0.3".parse().unwrap()); + + npu.run(); + + let et = 0x0800; + let msg = muffins!(); + + // Each VRF routes this destination differently + if0.send(phy2.mac, "10.1.47.1".parse().unwrap(), msg.0)?; + expect_frames!(phy2, &[RxFrame::new(phy0.mac, et, msg.0)]); + if1.send(phy3.mac, "10.1.47.1".parse().unwrap(), msg.1)?; + expect_frames!(phy3, &[RxFrame::new(phy1.mac, et, msg.1)]); + + // More specific match for vrf a + if0.send(phy1.mac, "10.1.1.1".parse().unwrap(), msg.2)?; + expect_frames!(phy1, &[RxFrame::new(phy0.mac, et, msg.2)]); + + // No /24 for vrf b + if1.send(phy3.mac, "10.1.1.1".parse().unwrap(), msg.3)?; + expect_frames!(phy3, &[RxFrame::new(phy1.mac, et, msg.3)]); + + // No match: should drop coming from port 2. Port 0 should go through. + if2.send(phy2.mac, "10.1.47.1".parse().unwrap(), msg.4)?; + if0.send(phy2.mac, "10.1.47.1".parse().unwrap(), msg.5)?; + expect_frames!(phy2, &[RxFrame::new(phy0.mac, et, msg.5)]); + + Ok(()) +} From e12ac3ca99bc8e7e124ad7f39ec6054352e611de Mon Sep 17 00:00:00 2001 From: Nicolas Kagami Date: Tue, 25 Aug 2026 08:30:23 -0300 Subject: [PATCH 2/2] fix typo --- test/src/vrf_router.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/vrf_router.rs b/test/src/vrf_router.rs index 59089252..57143157 100644 --- a/test/src/vrf_router.rs +++ b/test/src/vrf_router.rs @@ -58,7 +58,7 @@ fn vrf_router() -> Result<(), anyhow::Error> { 0, ); - // vrf b (port 0): 10.1.0.0/16 -> port 3 + // vrf b (port 1): 10.1.0.0/16 -> port 3 let buf = vrf_key("10.1.0.0".parse().unwrap(), 16, 1); pipeline.add_ingress_vrf_router_entry( "forward",