From c0845e113bfd783d1d37b5b54a49c1e4fe64bbc1 Mon Sep 17 00:00:00 2001 From: Daniel Noland Date: Tue, 24 Mar 2026 19:25:19 -0600 Subject: [PATCH 01/12] cleanup(net): remove kani annotations Remove all kani proof annotations and cfg(kani) guards from net crate tests. Remove the [lints.rust] unexpected_cfgs kani check-cfg from net/Cargo.toml. Kani is not actively used and these annotations add noise. --- net/Cargo.toml | 3 --- net/src/icmp6/mod.rs | 2 -- net/src/ipv4/mod.rs | 4 ---- net/src/ipv6/addr.rs | 1 - net/src/ipv6/mod.rs | 4 ---- net/src/tcp/mod.rs | 5 ----- net/src/udp/mod.rs | 5 ----- net/src/vlan/mod.rs | 6 ------ net/src/vxlan/mod.rs | 6 ------ 9 files changed, 36 deletions(-) diff --git a/net/Cargo.toml b/net/Cargo.toml index 5cf3dc327..26ae27a4b 100644 --- a/net/Cargo.toml +++ b/net/Cargo.toml @@ -37,6 +37,3 @@ tracing = { workspace = true } ahash = { workspace = true, features = ["no-rng"] } bolero = { workspace = true, features = ["alloc", "arbitrary", "std"] } etherparse = { workspace = true, default-features = false, features = ["std"] } - -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(kani)'] } diff --git a/net/src/icmp6/mod.rs b/net/src/icmp6/mod.rs index f646e257b..e6d460372 100644 --- a/net/src/icmp6/mod.rs +++ b/net/src/icmp6/mod.rs @@ -475,7 +475,6 @@ mod test { } #[test] - #[cfg_attr(kani, kani::proof)] fn parse_back() { bolero::check!() .with_type() @@ -483,7 +482,6 @@ mod test { } #[test] - #[cfg_attr(kani, kani::proof)] fn parse_arbitrary_bytes() { bolero::check!() .with_type() diff --git a/net/src/ipv4/mod.rs b/net/src/ipv4/mod.rs index d30b53dcf..54e9ac885 100644 --- a/net/src/ipv4/mod.rs +++ b/net/src/ipv4/mod.rs @@ -525,7 +525,6 @@ mod test { const MAX_LEN_USIZE: usize = 60; #[test] - #[cfg_attr(kani, kani::proof)] fn parse_back() { bolero::check!().with_type().for_each(|header: &Ipv4| { let mut buffer = [0u8; MIN_LEN_USIZE]; @@ -540,14 +539,12 @@ mod test { assert_eq!(header.protocol(), parse_back.protocol()); assert_eq!(header.ecn(), parse_back.ecn()); assert_eq!(header.dscp(), parse_back.dscp()); - #[cfg(not(kani))] // remove when we fix options generation assert_eq!(header, &parse_back); assert_eq!(bytes_written, bytes_read); }); } #[test] - #[cfg_attr(kani, kani::proof)] fn parse_arbitrary_bytes() { bolero::check!() .with_type() @@ -561,7 +558,6 @@ mod test { // reserved bit in ipv4 flags should serialize to zero assert_eq!(slice[6] & 0b0111_1111, buf[6]); assert_eq!(&slice[7..MIN_LEN_USIZE], &buf.as_slice()[7..MIN_LEN_USIZE]); - #[cfg(not(kani))] // remove when we fix options generation assert_eq!( &slice[MIN_LEN_USIZE..consumed.into_non_zero_usize().get()], &buf.as_slice()[MIN_LEN_USIZE..consumed.into_non_zero_usize().get()] diff --git a/net/src/ipv6/addr.rs b/net/src/ipv6/addr.rs index f67b2938d..355cc66c7 100644 --- a/net/src/ipv6/addr.rs +++ b/net/src/ipv6/addr.rs @@ -115,7 +115,6 @@ mod test { use crate::ipv6::addr::UnicastIpv6Addr; #[test] - #[cfg_attr(kani, kani::proof)] fn generated_unicast_ipv6_address_is_unicast() { bolero::check!() .with_type() diff --git a/net/src/ipv6/mod.rs b/net/src/ipv6/mod.rs index 86a6988d4..2ab7c190e 100644 --- a/net/src/ipv6/mod.rs +++ b/net/src/ipv6/mod.rs @@ -651,7 +651,6 @@ mod test { const MIN_LEN: usize = Ipv6::MIN_LEN.get() as usize; #[test] - #[cfg_attr(kani, kani::proof)] fn parse_back() { bolero::check!().with_type().for_each(|header: &Ipv6| { let mut buf = [0u8; MIN_LEN]; @@ -664,7 +663,6 @@ mod test { } #[test] - #[cfg_attr(kani, kani::proof)] fn parse_arbitrary_bytes() { bolero::check!() .with_type() @@ -697,7 +695,6 @@ mod test { } #[test] - #[cfg_attr(kani, kani::proof)] fn parse_arbitrary_bytes_too_short() { bolero::check!() .with_type() @@ -711,7 +708,6 @@ mod test { } #[test] - #[cfg_attr(kani, kani::proof)] fn parse_arbitrary_bytes_above_minimum() { bolero::check!() .with_type() diff --git a/net/src/tcp/mod.rs b/net/src/tcp/mod.rs index 60586ac55..4a657e923 100644 --- a/net/src/tcp/mod.rs +++ b/net/src/tcp/mod.rs @@ -410,7 +410,6 @@ mod test { const MIN_LEN: usize = Tcp::MIN_LENGTH.get() as usize; #[test] - #[cfg_attr(kani, kani::proof)] fn parse_back() { bolero::check!().with_type().for_each(|tcp: &Tcp| { let mut buffer = [0u8; 64]; @@ -439,14 +438,12 @@ mod test { assert_eq!(tcp.urg(), parsed.urg()); assert_eq!(tcp.window_size(), parsed.window_size()); assert_eq!(tcp.urgent_pointer(), parsed.urgent_pointer()); - #[cfg(not(kani))] // remove after fixing options assert_eq!(tcp, &parsed); assert_eq!(consumed, consumed2); }); } #[test] - #[cfg_attr(kani, kani::proof)] fn parse_noise() { bolero::check!() .with_type() @@ -477,13 +474,11 @@ mod test { let (parsed_back, consumed3) = Tcp::parse(&slice2[..consumed2.into_non_zero_usize().get()]).unwrap(); assert_eq!(consumed2, consumed3); - #[cfg(not(kani))] // remove after fixing options assert_eq!(parsed, parsed_back); assert_eq!(&slice[..12], &slice2[..12]); // check for reserved bits getting zeroed by `write` (regardless of inputs) assert_eq!(slice[12] & 0b1111_0001, slice2[12]); assert_eq!(&slice[13..MIN_LEN], &slice2[13..MIN_LEN]); - #[cfg(not(kani))] // remove after fixing options assert_eq!( &slice[MIN_LEN..consumed1.into_non_zero_usize().get()], &slice2[MIN_LEN..consumed1.into_non_zero_usize().get()] diff --git a/net/src/udp/mod.rs b/net/src/udp/mod.rs index 02dc34767..d173feecc 100644 --- a/net/src/udp/mod.rs +++ b/net/src/udp/mod.rs @@ -283,7 +283,6 @@ mod test { const MIN_LENGTH_USIZE: usize = 8; #[test] - #[cfg_attr(kani, kani::proof)] fn parse_back() { bolero::check!().with_type().for_each(|input: &Udp| { let mut buffer = [0u8; MIN_LENGTH_USIZE]; @@ -306,7 +305,6 @@ mod test { } #[test] - #[cfg_attr(kani, kani::proof)] fn parse_arbitrary_bytes() { bolero::check!() .with_type() @@ -335,7 +333,6 @@ mod test { } #[test] - #[cfg_attr(kani, kani::proof)] fn too_short_buffer_parse_fails_gracefully() { bolero::check!() .with_type() @@ -353,7 +350,6 @@ mod test { } #[test] - #[cfg_attr(kani, kani::proof)] fn longer_buffer_parses_ok() { bolero::check!() .with_type() @@ -385,7 +381,6 @@ mod test { // evolve an arbitrary source towards an arbitrary target to make sure mutation methods work #[test] - #[cfg_attr(kani, kani::proof)] fn arbitrary_mutation() { bolero::check!() .with_type() diff --git a/net/src/vlan/mod.rs b/net/src/vlan/mod.rs index 572b8cc92..b992f8451 100644 --- a/net/src/vlan/mod.rs +++ b/net/src/vlan/mod.rs @@ -500,7 +500,6 @@ mod test { } #[test] - #[cfg_attr(kani, kani::proof)] fn pcp_bounds_respected() { bolero::check!() .with_type() @@ -519,7 +518,6 @@ mod test { } #[test] - #[cfg_attr(kani, kani::proof)] fn parse_back() { bolero::check!().with_type().for_each(|vlan: &Vlan| { let mut buf = [0u8; MIN_LENGTH_USIZE]; // vlan headers are always 4 bytes long @@ -538,7 +536,6 @@ mod test { } #[test] - #[cfg_attr(kani, kani::proof)] fn parse_noise() { bolero::check!().with_type().for_each(|buf: &[u8; MIN_LENGTH_USIZE]| { let (vlan, consumed) = match Vlan::parse(buf) { @@ -561,7 +558,6 @@ mod test { } #[test] - #[cfg_attr(kani, kani::proof)] fn parse_noise_too_short() { bolero::check!().with_type().for_each( |buf: &[u8; MIN_LENGTH_USIZE - 1]| match Vlan::parse(buf) { @@ -575,7 +571,6 @@ mod test { } #[test] - #[cfg_attr(kani, kani::proof)] fn arbitrary_mutation() { bolero::check!() .with_type() @@ -595,7 +590,6 @@ mod test { } #[test] - #[cfg_attr(kani, kani::proof)] fn deparse_to_insufficient_buffer_is_graceful() { bolero::check!().with_type().for_each(|vlan: &Vlan| { let mut buf = [0u8; MIN_LENGTH_USIZE - 1]; diff --git a/net/src/vxlan/mod.rs b/net/src/vxlan/mod.rs index 6f84a77aa..02c545d39 100644 --- a/net/src/vxlan/mod.rs +++ b/net/src/vxlan/mod.rs @@ -152,7 +152,6 @@ mod test { const MIN_LENGTH_USIZE: usize = 8; #[test] - #[cfg_attr(kani, kani::proof)] fn parse_back() { bolero::check!().with_type().for_each(|vxlan: &Vxlan| { assert_eq!(vxlan.size(), Vxlan::MIN_LENGTH); @@ -167,7 +166,6 @@ mod test { } #[test] - #[cfg_attr(kani, kani::proof)] fn creation_identity_check() { bolero::check!().with_type().for_each(|vxlan: &Vxlan| { assert_eq!(vxlan, &Vxlan::new(vxlan.vni())); @@ -176,7 +174,6 @@ mod test { } #[test] - #[cfg_attr(kani, kani::proof)] fn parse_noise() { bolero::check!() .with_type() @@ -223,7 +220,6 @@ mod test { } #[test] - #[cfg_attr(kani, kani::proof)] fn write_to_insufficient_buffer_fails_gracefully() { bolero::check!().with_type().for_each(|vni: &Vxlan| { let mut too_small_buffer = [0u8; MIN_LENGTH_USIZE - 1]; @@ -238,7 +234,6 @@ mod test { } #[test] - #[cfg_attr(kani, kani::proof)] fn parse_of_insufficient_buffer_fails_gracefully() { bolero::check!() .with_type() @@ -254,7 +249,6 @@ mod test { } #[test] - #[cfg_attr(kani, kani::proof)] fn mutation_of_header_preserves_contract() { bolero::check!() .with_type() From 88e5d2000ef94d36565bbf00f8773c49f6045c13 Mon Sep 17 00:00:00 2001 From: Daniel Noland Date: Tue, 24 Mar 2026 19:25:35 -0600 Subject: [PATCH 02/12] build(net): reduce bolero requirements Slim down bolero feature flags from [alloc, arbitrary, std] to [std] in both dependencies and dev-dependencies. Remove duplicate etherparse dev-dependency (already present in [dependencies]). This drops the transitive 'arbitrary' crate dependency. --- net/Cargo.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/net/Cargo.toml b/net/Cargo.toml index 26ae27a4b..4b9df60ef 100644 --- a/net/Cargo.toml +++ b/net/Cargo.toml @@ -17,7 +17,7 @@ test_buffer = [] arrayvec = { workspace = true, features = ["serde", "std"] } atomic-instant-full = { workspace = true } bitflags = { workspace = true } -bolero = { workspace = true, features = ["alloc", "arbitrary", "std"], optional = true } +bolero = { workspace = true, features = ["std"], optional = true } bytecheck = { workspace = true } concurrency = { workspace = true } derive_builder = { workspace = true, features = ["alloc"] } @@ -35,5 +35,4 @@ tracing = { workspace = true } [dev-dependencies] ahash = { workspace = true, features = ["no-rng"] } -bolero = { workspace = true, features = ["alloc", "arbitrary", "std"] } -etherparse = { workspace = true, default-features = false, features = ["std"] } +bolero = { workspace = true, features = ["std"] } From 7f5ee1037923806a903d2cc48d349055dadaa969 Mon Sep 17 00:00:00 2001 From: Daniel Noland Date: Tue, 24 Mar 2026 19:25:52 -0600 Subject: [PATCH 03/12] style(net): fix clippy lints in headers - Add #[must_use] to pure accessor methods - Add #[allow(clippy::cast_possible_truncation)] and #[allow(clippy::too_many_lines)] where needed - Replace .map(...).unwrap_or(0) with .map_or(0, ...) - Collapse nested Some(A) | Some(B) match arms into Some(A | B) - Replace Default::default() with ArrayVec::default() in contract generators - Convert match on bool to if/else in bolero generators - Fix stray semicolons and narrow wildcard imports in contract module - Add Errors doc sections to fallible public methods --- net/src/headers/embedded.rs | 67 +++++++++++++++++++-------- net/src/headers/mod.rs | 90 +++++++++++++++++++++++++------------ 2 files changed, 109 insertions(+), 48 deletions(-) diff --git a/net/src/headers/embedded.rs b/net/src/headers/embedded.rs index cac4780e1..821bcb6bb 100644 --- a/net/src/headers/embedded.rs +++ b/net/src/headers/embedded.rs @@ -50,6 +50,7 @@ pub struct EmbeddedHeaders { impl EmbeddedHeaders { #[cfg(any(test, feature = "bolero"))] + #[must_use] pub fn new( net: Option, transport: Option, @@ -58,8 +59,8 @@ impl EmbeddedHeaders { ) -> Self { Self { net, - transport, net_ext, + transport, full_payload_length, } } @@ -95,25 +96,29 @@ impl EmbeddedHeaders { } } + #[must_use] pub fn net_headers_len(&self) -> u16 { - self.net.as_ref().map(|net| net.size().get()).unwrap_or(0) + self.net.as_ref().map_or(0, |net| net.size().get()) } + #[must_use] pub fn transport_headers_len(&self) -> u16 { self.transport .as_ref() - .map(|transport| transport.size().get()) - .unwrap_or(0) + .map_or(0, |transport| transport.size().get()) } + #[must_use] pub fn is_full_payload(&self) -> bool { self.full_payload_length.is_some() } + #[must_use] pub fn payload_length(&self) -> Option { self.full_payload_length } + #[allow(clippy::cast_possible_truncation, clippy::too_many_lines)] pub fn check_full_payload( &mut self, buf: &[u8], @@ -125,18 +130,22 @@ impl EmbeddedHeaders { match &mut self.transport { None - | Some(EmbeddedTransport::Tcp(TruncatedTcp::PartialHeader(_))) - | Some(EmbeddedTransport::Udp(TruncatedUdp::PartialHeader(_))) - | Some(EmbeddedTransport::Icmp4(TruncatedIcmp4::PartialHeader(_))) - | Some(EmbeddedTransport::Icmp6(TruncatedIcmp6::PartialHeader(_))) => { + | Some( + EmbeddedTransport::Tcp(TruncatedTcp::PartialHeader(_)) + | EmbeddedTransport::Udp(TruncatedUdp::PartialHeader(_)) + | EmbeddedTransport::Icmp4(TruncatedIcmp4::PartialHeader(_)) + | EmbeddedTransport::Icmp6(TruncatedIcmp6::PartialHeader(_)), + ) => { // We couldn't parse the full transport header, of course we don't have the full, // valid payload return; } - Some(EmbeddedTransport::Tcp(TruncatedTcp::FullHeader(_))) - | Some(EmbeddedTransport::Udp(TruncatedUdp::FullHeader(_))) - | Some(EmbeddedTransport::Icmp4(TruncatedIcmp4::FullHeader(_))) - | Some(EmbeddedTransport::Icmp6(TruncatedIcmp6::FullHeader(_))) => { + Some( + EmbeddedTransport::Tcp(TruncatedTcp::FullHeader(_)) + | EmbeddedTransport::Udp(TruncatedUdp::FullHeader(_)) + | EmbeddedTransport::Icmp4(TruncatedIcmp4::FullHeader(_)) + | EmbeddedTransport::Icmp6(TruncatedIcmp6::FullHeader(_)), + ) => { // There's a chance payload is full, keep going } } @@ -467,6 +476,7 @@ pub enum EmbeddedTransport { } impl EmbeddedTransport { + #[must_use] pub fn icmp_identifier(&self) -> Option { match self { EmbeddedTransport::Icmp4(icmp) => icmp.identifier(), @@ -475,6 +485,7 @@ impl EmbeddedTransport { } } + #[must_use] pub fn source(&self) -> Option> { match self { EmbeddedTransport::Tcp(tcp) => Some(tcp.source().into()), @@ -483,6 +494,7 @@ impl EmbeddedTransport { } } + #[must_use] pub fn destination(&self) -> Option> { match self { EmbeddedTransport::Tcp(tcp) => Some(tcp.destination().into()), @@ -491,6 +503,12 @@ impl EmbeddedTransport { } } + /// Set the source port of the embedded transport header. + /// + /// # Errors + /// + /// Returns [`EmbeddedHeaderError::NoPorts`] if the transport header does not have ports + /// (i.e., ICMP). pub fn set_source(&mut self, port: NonZero) -> Result<(), EmbeddedHeaderError> { match self { EmbeddedTransport::Tcp(tcp) => { @@ -505,6 +523,12 @@ impl EmbeddedTransport { } } + /// Set the destination port of the embedded transport header. + /// + /// # Errors + /// + /// Returns [`EmbeddedHeaderError::NoPorts`] if the transport header does not have ports + /// (i.e., ICMP). pub fn set_destination(&mut self, port: NonZero) -> Result<(), EmbeddedHeaderError> { match self { EmbeddedTransport::Tcp(tcp) => { @@ -1145,6 +1169,7 @@ mod tests { // Basic parsing, deparsing checks #[test] + #[allow(clippy::cast_possible_truncation)] fn test_parse_ipv4_with_truncated_tcp() { let buf = create_truncated_ipv4_tcp_packet(); @@ -1162,6 +1187,7 @@ mod tests { } #[test] + #[allow(clippy::cast_possible_truncation)] fn test_parse_ipv4_with_full_udp() { let buf = create_full_ipv4_udp_packet(); @@ -1179,6 +1205,7 @@ mod tests { } #[test] + #[allow(clippy::cast_possible_truncation)] fn test_parse_ipv6_with_truncated_tcp() { let buf = create_truncated_ipv6_tcp_packet(); @@ -1196,6 +1223,7 @@ mod tests { } #[test] + #[allow(clippy::cast_possible_truncation)] fn test_parse_ipv6_with_full_udp() { let buf = create_full_ipv6_udp_packet(); @@ -1592,7 +1620,7 @@ mod tests { #[cfg(any(test, feature = "bolero"))] mod contract { - use super::*; + use super::{EmbeddedHeaders, EmbeddedTransport}; use crate::headers::Net; use crate::ipv4; use crate::ipv6; @@ -1606,17 +1634,18 @@ mod contract { type Output = EmbeddedHeaders; fn generate(&self, driver: &mut D) -> Option { - let (ipv4_next_header, ipv6_next_header, transport) = match driver.produce::()? { - true => ( + let (ipv4_next_header, ipv6_next_header, transport) = if driver.produce::()? { + ( ipv4::CommonNextHeader::Tcp, ipv6::CommonNextHeader::Tcp, EmbeddedTransport::Tcp(driver.produce::()?), - ), - false => ( + ) + } else { + ( ipv4::CommonNextHeader::Udp, ipv6::CommonNextHeader::Udp, EmbeddedTransport::Udp(driver.produce::()?), - ), + ) }; let is_ipv4 = driver.produce::()?; @@ -1680,6 +1709,6 @@ mod tests_fuzzing { None => { unreachable!() } - }) + }); } } diff --git a/net/src/headers/mod.rs b/net/src/headers/mod.rs index 668299567..fae70af3e 100644 --- a/net/src/headers/mod.rs +++ b/net/src/headers/mod.rs @@ -2,7 +2,7 @@ // Copyright Open Network Fabric Authors //! Definition of [`Headers`] and related methods and types. -#![allow(missing_docs, clippy::pedantic)] // temporary +#![allow(missing_docs)] // temporary use crate::checksum::Checksum; use crate::eth::ethtype::EthType; @@ -65,6 +65,7 @@ pub enum Net { } impl Net { + #[must_use] pub fn dst_addr(&self) -> IpAddr { match self { Net::Ipv4(ip) => IpAddr::V4(ip.destination()), @@ -72,6 +73,7 @@ impl Net { } } + #[must_use] pub fn src_addr(&self) -> IpAddr { match self { Net::Ipv4(ip) => IpAddr::V4(ip.source().inner()), @@ -79,6 +81,7 @@ impl Net { } } + #[must_use] pub fn next_header(&self) -> NextHeader { match self { Net::Ipv4(ip) => ip.protocol().into(), @@ -86,6 +89,12 @@ impl Net { } } + /// Sets the source address of the network header. + /// + /// # Errors + /// + /// Returns [`NetError::InvalidIpVersion`] if the IP version of `addr` does not match the + /// IP version of the network header. pub fn try_set_source(&mut self, addr: UnicastIpAddr) -> Result<(), NetError> { match (self, addr) { (Net::Ipv4(ip), UnicastIpAddr::V4(addr)) => { @@ -101,6 +110,12 @@ impl Net { Ok(()) } + /// Sets the destination address of the network header. + /// + /// # Errors + /// + /// Returns [`NetError::InvalidIpVersion`] if the IP version of `addr` does not match the + /// IP version of the network header. pub fn try_set_destination(&mut self, addr: IpAddr) -> Result<(), NetError> { match (self, addr) { (Net::Ipv4(ip), IpAddr::V4(addr)) => { @@ -195,7 +210,7 @@ impl Transport { icmp4 .update_checksum(payload.as_ref()) .unwrap_or_else(|()| unreachable!()); // Updating ICMPv4 checksum never fails - }; + } } (Net::Ipv6(ip), Transport::Icmp6(icmp6)) => { if icmp6.is_error_message() && embedded_headers.is_some() { @@ -315,6 +330,12 @@ impl Transport { Ok(()) } + /// Sets the ICMP identifier field. + /// + /// # Errors + /// + /// Returns [`TransportError::UnsupportedIdentifier`] if the transport header does not + /// support identifiers (i.e., TCP or UDP). pub fn try_set_identifier(&mut self, identifier: u16) -> Result<(), TransportError> { match self { Transport::Icmp4(icmp4) => icmp4 @@ -467,7 +488,7 @@ impl DeParse for Headers { fn size(&self) -> NonZero { // TODO(blocking): Deal with ip{v4,v6} extensions - let eth = self.eth.as_ref().map(|x| x.size().get()).unwrap_or(0); + let eth = self.eth.as_ref().map_or(0, |x| x.size().get()); let vlan = self.vlan.iter().map(|v| v.size().get()).sum::(); let net = match self.net { None => { @@ -482,7 +503,7 @@ impl DeParse for Headers { }; let encap = match self.udp_encap { None => 0, - Some(UdpEncap::Vxlan(vxlan)) => vxlan.size().get(), + Some(UdpEncap::Vxlan(vx)) => vx.size().get(), }; let embedded_ip = self .embedded_ip @@ -582,6 +603,7 @@ pub enum PopVlanError { impl Headers { /// Create a new [`Headers`] with the supplied `Eth` header. + #[must_use] pub fn new() -> Headers { Headers::default() } @@ -622,6 +644,12 @@ impl Headers { /// /// This method will ensure that the `eth` field has its [`EthType`] adjusted to /// [`EthType::VLAN`] if there are no [`Vlan`]s on the stack at the time this method was called. + /// + /// # Errors + /// + /// Returns [`PushVlanError::TooManyVlans`] if there are already [`MAX_VLANS`] VLANs on the + /// stack. + /// Returns [`PushVlanError::NoEthernetHeader`] if no Ethernet header is present. pub fn push_vlan(&mut self, vid: Vid) -> Result<(), PushVlanError> { if self.vlan.len() >= MAX_VLANS { return Err(PushVlanError::TooManyVlans); @@ -646,6 +674,10 @@ impl Headers { /// preserve the structure. /// /// If `None` is returned, the [`Headers`] is not modified. + /// + /// # Errors + /// + /// Returns [`PopVlanError::NoEthernetHeader`] if no Ethernet header is present. pub fn pop_vlan(&mut self) -> Result, PopVlanError> { match &mut self.eth { None => Err(PopVlanError::NoEthernetHeader), @@ -1371,6 +1403,7 @@ mod contract { impl ValueGenerator for CommonHeaders { type Output = Headers; + #[allow(clippy::too_many_lines)] fn generate(&self, driver: &mut D) -> Option { let common_eth_type: CommonEthType = driver.produce()?; let eth = GenWithEthType(common_eth_type.into()).generate(driver)?; @@ -1384,9 +1417,9 @@ mod contract { let tcp: Tcp = driver.produce()?; let headers = Headers { eth: Some(eth), - vlan: Default::default(), + vlan: ArrayVec::default(), net: Some(Net::Ipv4(ipv4)), - net_ext: Default::default(), + net_ext: ArrayVec::default(), transport: Some(Transport::Tcp(tcp)), udp_encap: None, embedded_ip: None, @@ -1403,9 +1436,9 @@ mod contract { }; let headers = Headers { eth: Some(eth), - vlan: Default::default(), + vlan: ArrayVec::default(), net: Some(Net::Ipv4(ipv4)), - net_ext: Default::default(), + net_ext: ArrayVec::default(), transport: Some(Transport::Udp(udp)), udp_encap, embedded_ip: None, @@ -1418,7 +1451,7 @@ mod contract { eth: Some(eth), vlan: ArrayVec::default(), net: Some(Net::Ipv4(ipv4)), - net_ext: Default::default(), + net_ext: ArrayVec::default(), transport: Some(Transport::Icmp4(icmp)), udp_encap: None, embedded_ip: None, @@ -1436,9 +1469,9 @@ mod contract { let tcp: Tcp = driver.produce()?; let headers = Headers { eth: Some(eth), - vlan: Default::default(), + vlan: ArrayVec::default(), net: Some(Net::Ipv6(ipv6)), - net_ext: Default::default(), + net_ext: ArrayVec::default(), transport: Some(Transport::Tcp(tcp)), udp_encap: None, embedded_ip: None, @@ -1455,9 +1488,9 @@ mod contract { }; let headers = Headers { eth: Some(eth), - vlan: Default::default(), + vlan: ArrayVec::default(), net: Some(Net::Ipv6(ipv6)), - net_ext: Default::default(), + net_ext: ArrayVec::default(), transport: Some(Transport::Udp(udp)), udp_encap, embedded_ip: None, @@ -1468,9 +1501,9 @@ mod contract { let icmp6: Icmp6 = driver.produce()?; let headers = Headers { eth: Some(eth), - vlan: Default::default(), + vlan: ArrayVec::default(), net: Some(Net::Ipv6(ipv6)), - net_ext: Default::default(), + net_ext: ArrayVec::default(), transport: Some(Transport::Icmp6(icmp6)), udp_encap: None, embedded_ip: None, @@ -1484,21 +1517,22 @@ mod contract { } } -#[cfg(any(test, kani))] -#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] // fine to unwarp in tests +#[cfg(test)] +#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] // fine to unwrap in tests mod test { use std::net::Ipv4Addr; + use crate::checksum::Checksum; use crate::headers::Headers; use crate::headers::contract::CommonHeaders; use crate::icmp4::Icmp4Checksum; use crate::parse::{DeParse, DeParseError, IntoNonZeroUSize, Parse, ParseError}; - use super::*; + use super::{Net, Transport}; use crate::icmp6::{Icmp6Checksum, Icmp6ChecksumPayload}; use crate::ipv4::{Ipv4Checksum, UnicastIpv4Addr}; - use crate::tcp::{TcpChecksum, TcpChecksumPayload}; - use crate::udp::{UdpChecksum, UdpChecksumPayload}; + use crate::tcp::{TcpChecksum, TcpChecksumPayload, TcpPort}; + use crate::udp::{UdpChecksum, UdpChecksumPayload, UdpPort}; fn parse_back_test(headers: &Headers) { let mut buffer = [0_u8; 1024]; @@ -1521,17 +1555,15 @@ mod test { } #[test] - #[cfg_attr(kani, kani::proof)] fn parse_back() { - bolero::check!().with_type().for_each(parse_back_test) + bolero::check!().with_type().for_each(parse_back_test); } #[test] - #[cfg_attr(kani, kani::proof)] fn parse_back_common() { bolero::check!() .with_generator(CommonHeaders) - .for_each(parse_back_test) + .for_each(parse_back_test); } mod sample { @@ -1696,6 +1728,7 @@ mod test { } } + #[allow(clippy::too_many_lines)] fn test_checksum(mut headers: Headers) { match &headers.transport { None => {} @@ -1794,7 +1827,7 @@ mod test { // Check incremental updates match &mut headers.transport { - None => {} + None | Some(Transport::Icmp6(_)) => {} Some(Transport::Udp(transport)) => { let net = headers.net.clone().unwrap(); let old_value = transport.source().into(); @@ -1845,9 +1878,8 @@ mod test { .expect("expected valid checksum"); } Net::Ipv6(_) => panic!("unexpected ipv6"), - }; + } } - Some(Transport::Icmp6(_)) => {} } } @@ -1927,7 +1959,7 @@ mod test { _ => unreachable!(), } } - _ => unreachable!(), + Net::Ipv6(_) => unreachable!(), }, _ => unreachable!(), } @@ -1980,7 +2012,7 @@ mod test { _ => unreachable!(), } } - _ => unreachable!(), + Net::Ipv6(_) => unreachable!(), }, _ => unreachable!(), } From 56c067d7fce27e115d0e9a0587095adffd02b6ba Mon Sep 17 00:00:00 2001 From: Daniel Noland Date: Tue, 24 Mar 2026 19:27:06 -0600 Subject: [PATCH 04/12] style(config): minor cleanup - Remove 4 crate-wide #![allow(...)] from config/src/lib.rs, replacing with targeted #[allow(clippy::struct_excessive_bools)] on the 5 structs that need it - Fix redundant_closure_for_method_calls: use method references instead of closures (e.g. .map(LegalValue::take), .for_each(GwGroup::sort_members)) - Fix doc_markdown lints: backtick-wrap type names in doc comments - Add Errors doc sections to ~20 fallible public methods - Convert // comments to /// doc comments on VpcExpose NAT methods - Remove unused import (crate::converters::k8s::config::peering) --- config/src/converters/k8s/status/bgp.rs | 2 +- .../converters/k8s/status/dataplane_status.rs | 2 +- config/src/display.rs | 2 +- config/src/external/communities.rs | 4 + config/src/external/gwgroup.rs | 13 ++- config/src/external/mod.rs | 5 ++ config/src/external/overlay/mod.rs | 8 ++ .../src/external/overlay/validation_tests.rs | 2 +- config/src/external/overlay/vpc.rs | 1 - config/src/external/overlay/vpcpeering.rs | 89 +++++++++++++------ config/src/external/underlay/mod.rs | 7 +- config/src/gwconfig.rs | 6 +- config/src/internal/device/mod.rs | 5 ++ config/src/internal/interfaces/interface.rs | 5 ++ config/src/internal/mod.rs | 5 ++ config/src/internal/routing/bgp.rs | 5 ++ config/src/internal/routing/bmp.rs | 1 + config/src/internal/routing/prefixlist.rs | 10 +++ config/src/internal/routing/routemap.rs | 5 ++ config/src/internal/routing/vrf.rs | 4 + config/src/lib.rs | 4 - 21 files changed, 144 insertions(+), 41 deletions(-) diff --git a/config/src/converters/k8s/status/bgp.rs b/config/src/converters/k8s/status/bgp.rs index 979ada8c9..e718badea 100644 --- a/config/src/converters/k8s/status/bgp.rs +++ b/config/src/converters/k8s/status/bgp.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Open Network Fabric Authors -//! Converters for internal BGP status -> K8s GatewayAgentStatusStateBgp CRD. +//! Converters for internal BGP status -> K8s `GatewayAgentStatusStateBgp` CRD. use std::collections::BTreeMap; diff --git a/config/src/converters/k8s/status/dataplane_status.rs b/config/src/converters/k8s/status/dataplane_status.rs index 78d404dec..e6ab7375c 100644 --- a/config/src/converters/k8s/status/dataplane_status.rs +++ b/config/src/converters/k8s/status/dataplane_status.rs @@ -136,7 +136,7 @@ mod tests { let last_applied_time = time_gen.generate(d)?; let status = d .produce::>>()? - .map(|v| v.take()); + .map(LegalValue::take); let last_collected_time_raw = time_gen.generate(d)?; let last_collected_time = status.as_ref().map(|_| last_collected_time_raw); let last_heartbeat_raw = time_gen.generate(d)?; diff --git a/config/src/display.rs b/config/src/display.rs index 4842ff7ed..09731c563 100644 --- a/config/src/display.rs +++ b/config/src/display.rs @@ -333,7 +333,7 @@ impl Display for GwConfigMeta { let error = self .error .as_ref() - .map_or("none".to_string(), |e| e.to_string()); + .map_or("none".to_string(), std::string::ToString::to_string); let is_rollback = if self.is_rollback { "(rollback)" } else { "" }; diff --git a/config/src/external/communities.rs b/config/src/external/communities.rs index 0f2ebbecf..139983723 100644 --- a/config/src/external/communities.rs +++ b/config/src/external/communities.rs @@ -16,6 +16,10 @@ impl PriorityCommunityTable { Self::default() } /// Insert a community + /// + /// # Errors + /// + /// Returns [`ConfigError::DuplicateCommunity`] if the community already exists in the table. pub fn insert(&mut self, order: usize, community: &str) -> Result<(), ConfigError> { if self.0.iter().any(|(_, comm)| comm == community) { return Err(ConfigError::DuplicateCommunity(community.to_string())); diff --git a/config/src/external/gwgroup.rs b/config/src/external/gwgroup.rs index 377657024..c7b7e2735 100644 --- a/config/src/external/gwgroup.rs +++ b/config/src/external/gwgroup.rs @@ -77,6 +77,12 @@ impl GwGroup { //N.B. we reverse the operands since want the most preferred first self.members.sort_by(|m1, m2| m2.cmp(m1)); } + /// Add a member to the gateway group. + /// + /// # Errors + /// + /// Returns [`ConfigError::DuplicateMember`] if a member with the same name already exists. + /// Returns [`ConfigError::DuplicateMemberAddress`] if a member with the same IP address already exists. pub fn add_member(&mut self, member: GwGroupMember) -> Result<(), ConfigError> { if self.get_member_by_name(&member.name).is_some() { return Err(ConfigError::DuplicateMember(member.name.clone())); @@ -126,6 +132,11 @@ impl GwGroupTable { pub fn new() -> Self { Self::default() } + /// Add a gateway group to the table. + /// + /// # Errors + /// + /// Returns [`ConfigError::DuplicateGroup`] if a group with the same name already exists. pub fn add_group(&mut self, group: GwGroup) -> Result<(), ConfigError> { if self.0.contains_key(group.name()) { return Err(ConfigError::DuplicateGroup(group.name().to_owned())); @@ -332,7 +343,7 @@ mod test { fn test_bgp_community_setup() { let comtable = sample_community_table(); let mut gwtable = build_sample_gw_groups(); - gwtable.iter_mut().for_each(|group| group.sort_members()); + gwtable.iter_mut().for_each(GwGroup::sort_members); println!("{gwtable}"); println!("{comtable}"); diff --git a/config/src/external/mod.rs b/config/src/external/mod.rs index e8c2ecc3e..d8be5c668 100644 --- a/config/src/external/mod.rs +++ b/config/src/external/mod.rs @@ -93,6 +93,11 @@ impl ExternalConfig { } Ok(()) } + /// Validate the external configuration. + /// + /// # Errors + /// + /// Returns a [`ConfigError`] if validation fails. pub fn validate(&mut self) -> ConfigResult { self.device.validate()?; self.underlay.validate()?; diff --git a/config/src/external/overlay/mod.rs b/config/src/external/overlay/mod.rs index 525c78283..01fe756fe 100644 --- a/config/src/external/overlay/mod.rs +++ b/config/src/external/overlay/mod.rs @@ -39,6 +39,10 @@ impl Overlay { } /// Validate all peerings, checking if the VPCs they refer to exist in vpc table + /// + /// # Errors + /// + /// Returns an error if a peering references a VPC that does not exist in the VPC table. pub fn validate_peerings(&self) -> ConfigResult { debug!("Validating VPC peerings..."); for peering in self.peering_table.values() { @@ -60,6 +64,10 @@ impl Overlay { } /// Top most validation function for `Overlay` configuration + /// + /// # Errors + /// + /// Returns an error if the overlay configuration is invalid. pub fn validate(&mut self) -> ConfigResult { debug!("Validating overlay configuration..."); diff --git a/config/src/external/overlay/validation_tests.rs b/config/src/external/overlay/validation_tests.rs index 9bb8b6c7b..75b2ecf59 100644 --- a/config/src/external/overlay/validation_tests.rs +++ b/config/src/external/overlay/validation_tests.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Open Network Fabric Authors -//! Validation tests for VpcExpose / VpcPeering / Overlay +//! Validation tests for `VpcExpose` / `VpcPeering` / Overlay //! //! These tests cover the expected semantics and restrictions for Expose objects in VPC peerings. //! diff --git a/config/src/external/overlay/vpc.rs b/config/src/external/overlay/vpc.rs index 55395fb9c..6107f3da3 100644 --- a/config/src/external/overlay/vpc.rs +++ b/config/src/external/overlay/vpc.rs @@ -12,7 +12,6 @@ use std::collections::BTreeMap; use std::collections::BTreeSet; use tracing::{debug, error, warn}; -use crate::converters::k8s::config::peering; use crate::external::overlay::VpcManifest; use crate::external::overlay::VpcPeeringTable; use crate::external::overlay::vpcpeering::VpcExposeNatConfig; diff --git a/config/src/external/overlay/vpcpeering.rs b/config/src/external/overlay/vpcpeering.rs index 1d7b7d8e7..5f207648b 100644 --- a/config/src/external/overlay/vpcpeering.rs +++ b/config/src/external/overlay/vpcpeering.rs @@ -83,11 +83,11 @@ pub struct VpcExpose { pub nat: Option, } impl VpcExpose { - // Make the [`VpcExpose`] use stateless NAT. - // - // # Errors - // - // Returns an error if the [`VpcExpose`] already has a different NAT mode. + /// Make the [`VpcExpose`] use stateless NAT. + /// + /// # Errors + /// + /// Returns an error if the [`VpcExpose`] already has a different NAT mode. pub fn make_stateless_nat(mut self) -> Result { match self.nat.as_mut() { Some(nat) if nat.is_stateless() => Ok(self), @@ -103,12 +103,12 @@ impl VpcExpose { } } - // Make the [`VpcExpose`] use stateful NAT, with the given idle timeout, if provided. - // If the [`VpcExpose`] is already in stateful mode, the idle timeout is overwritten. - // - // # Errors - // - // Returns an error if the [`VpcExpose`] already has a different NAT mode. + /// Make the [`VpcExpose`] use stateful NAT, with the given idle timeout, if provided. + /// If the [`VpcExpose`] is already in stateful mode, the idle timeout is overwritten. + /// + /// # Errors + /// + /// Returns an error if the [`VpcExpose`] already has a different NAT mode. pub fn make_stateful_nat( mut self, idle_timeout: Option, @@ -132,15 +132,15 @@ impl VpcExpose { } } - // Make the [`VpcExpose`] use port forwarding, with the given idle timeout, if provided, and the - // given L4 protocol, if provided. - // - // If the [`VpcExpose`] is already in port forwarding mode, the idle timeout and L4 protocol are - // overwritten. - // - // # Errors - // - // Returns an error if the [`VpcExpose`] already has a different NAT mode. + /// Make the [`VpcExpose`] use port forwarding, with the given idle timeout, if provided, and the + /// given L4 protocol, if provided. + /// + /// If the [`VpcExpose`] is already in port forwarding mode, the idle timeout and L4 protocol are + /// overwritten. + /// + /// # Errors + /// + /// Returns an error if the [`VpcExpose`] already has a different NAT mode. pub fn make_port_forwarding( mut self, idle_timeout: Option, @@ -223,6 +223,11 @@ impl VpcExpose { self.nots.insert(prefix); self } + /// Add a prefix to the NAT `as` range. + /// + /// # Errors + /// + /// Returns an error if the expose has no NAT configuration. pub fn as_range(mut self, prefix: PrefixWithOptionalPorts) -> Result { let nat = self.nat.as_mut().ok_or(ConfigError::MissingParameter( "'as' block requires NAT configuration for the expose", @@ -230,6 +235,11 @@ impl VpcExpose { nat.as_range.insert(prefix); Ok(self) } + /// Add a prefix to the NAT `not as` exclusion set. + /// + /// # Errors + /// + /// Returns an error if the expose has no NAT configuration. pub fn not_as(mut self, prefix: PrefixWithOptionalPorts) -> Result { let nat = self.nat.as_mut().ok_or(ConfigError::MissingParameter( "'not' prefix for 'as' block requires NAT configuration for the expose", @@ -249,9 +259,15 @@ impl VpcExpose { // only V4 atm vec![Prefix::root_v4()] } else if let Some(nat) = self.nat.as_ref() { - nat.as_range.iter().map(|p| p.prefix()).collect::>() + nat.as_range + .iter() + .map(PrefixWithOptionalPorts::prefix) + .collect::>() } else { - self.ips.iter().map(|p| p.prefix()).collect::>() + self.ips + .iter() + .map(PrefixWithOptionalPorts::prefix) + .collect::>() } } @@ -361,7 +377,11 @@ impl VpcExpose { Ok(()) } - /// Validate the [`VpcExpose`]: + /// Validate the [`VpcExpose`]. + /// + /// # Errors + /// + /// Returns an error if the expose configuration is invalid. #[allow(clippy::too_many_lines)] pub fn validate(&self) -> ConfigResult { // Check default exposes and prefixes @@ -442,7 +462,7 @@ impl VpcExpose { fn prefixes_size(prefixes: &PrefixPortsSet) -> PrefixWithPortsSize { prefixes .iter() - .map(|p| p.size()) + .map(PrefixWithOptionalPorts::size) .sum::() } let zero_size = ppsize_zero(); @@ -528,7 +548,7 @@ impl VpcManifest { } #[must_use] pub fn has_host_prefixes(&self) -> bool { - self.exposes.iter().any(|expose| expose.has_host_prefixes()) + self.exposes.iter().any(VpcExpose::has_host_prefixes) } fn validate_expose_collisions(&self) -> ConfigResult { // Check that prefixes in each expose don't overlap with prefixes in other exposes @@ -602,6 +622,11 @@ impl VpcManifest { pub fn add_exposes(&mut self, exposes: impl IntoIterator) { self.exposes.extend(exposes); } + /// Validate the [`VpcManifest`]. + /// + /// # Errors + /// + /// Returns an error if the manifest configuration is invalid. pub fn validate(&self) -> ConfigResult { if self.name.is_empty() { return Err(ConfigError::MissingIdentifier("Manifest name")); @@ -695,7 +720,7 @@ impl VpcPeering { } } - /// Create a VpcPeering mapped to a group called "default". + /// Create a `VpcPeering` mapped to a group called "default". /// This should only be used for tests #[must_use] pub fn with_default_group(name: &str, left: VpcManifest, right: VpcManifest) -> Self { @@ -708,7 +733,11 @@ impl VpcPeering { } #[cfg(test)] - /// Validate A VpcPeering. Only used in tests. Dataplane validates `Peerings` + /// Validate A `VpcPeering`. Only used in tests. Dataplane validates `Peerings` + /// + /// # Errors + /// + /// Returns an error if the peering configuration is invalid. pub fn validate(&self) -> ConfigResult { self.left.validate()?; self.right.validate()?; @@ -744,7 +773,11 @@ impl VpcPeeringTable { self.0.is_empty() } - /// Add a [`VpcPeering`] to a [`VpcPeeringTable`] + /// Add a [`VpcPeering`] to a [`VpcPeeringTable`]. + /// + /// # Errors + /// + /// Returns an error if the peering name is missing or a duplicate peering exists. pub fn add(&mut self, peering: VpcPeering) -> ConfigResult { if peering.name.is_empty() { return Err(ConfigError::MissingIdentifier("Peering name")); diff --git a/config/src/external/underlay/mod.rs b/config/src/external/underlay/mod.rs index d47954d25..3c6f61976 100644 --- a/config/src/external/underlay/mod.rs +++ b/config/src/external/underlay/mod.rs @@ -72,6 +72,11 @@ impl Underlay { } } + /// Validate the underlay configuration. + /// + /// # Errors + /// + /// Returns an error if any interface is invalid or VTEP configuration is wrong. pub fn validate(&mut self) -> ConfigResult { debug!("Validating underlay configuration..."); @@ -79,7 +84,7 @@ impl Underlay { self.vrf .interfaces .values() - .try_for_each(|iface| iface.validate())?; + .try_for_each(InterfaceConfig::validate)?; // set vtep information if a vtep interface has been specified in the config self.vtep = self.get_vtep_info()?; diff --git a/config/src/gwconfig.rs b/config/src/gwconfig.rs index f0ecf12d1..4b7190502 100644 --- a/config/src/gwconfig.rs +++ b/config/src/gwconfig.rs @@ -109,9 +109,11 @@ impl GwConfig { self.external.genid } - ////////////////////////////////////////////////////////////////// /// Validate a [`GwConfig`]. We only validate the external. - ////////////////////////////////////////////////////////////////// + /// + /// # Errors + /// + /// Returns a [`ConfigError`] if the external configuration fails validation. pub fn validate(&mut self) -> ConfigResult { debug!("Validating external config with genid {} ..", self.genid()); self.external.validate() diff --git a/config/src/internal/device/mod.rs b/config/src/internal/device/mod.rs index 92b7f6797..1f2c33d93 100644 --- a/config/src/internal/device/mod.rs +++ b/config/src/internal/device/mod.rs @@ -22,6 +22,11 @@ impl DeviceConfig { pub fn set_tracing(&mut self, tracing: TracingConfig) { self.tracing = Some(tracing); } + /// Validate the device configuration. + /// + /// # Errors + /// + /// Returns an error if the device configuration is invalid. pub fn validate(&self) -> ConfigResult { debug!("Validating device configuration.."); if let Some(tracing) = &self.tracing { diff --git a/config/src/internal/interfaces/interface.rs b/config/src/internal/interfaces/interface.rs index bca4a9968..2b2e2aa17 100644 --- a/config/src/internal/interfaces/interface.rs +++ b/config/src/internal/interfaces/interface.rs @@ -156,6 +156,11 @@ impl InterfaceConfig { matches!(self.iftype, InterfaceType::Vtep(_)) } + /// Validate the interface configuration. + /// + /// # Errors + /// + /// Returns an error if the interface name is empty. pub fn validate(&self) -> ConfigResult { // name is mandatory if self.name.is_empty() { diff --git a/config/src/internal/mod.rs b/config/src/internal/mod.rs index c16a4035a..f1670c846 100644 --- a/config/src/internal/mod.rs +++ b/config/src/internal/mod.rs @@ -68,6 +68,11 @@ impl InternalConfig { pub fn get_vtep(&self) -> &Option { &self.vtep } + /// Add a VRF configuration to the internal config. + /// + /// # Errors + /// + /// Returns an error if the VRF has duplicate fields (name, table ID, or VNI). pub fn add_vrf_config(&mut self, vrf_cfg: VrfConfig) -> ConfigResult { self.vrfs.add_vrf_config(vrf_cfg) } diff --git a/config/src/internal/routing/bgp.rs b/config/src/internal/routing/bgp.rs index a27e6c5ed..f4c796d5a 100644 --- a/config/src/internal/routing/bgp.rs +++ b/config/src/internal/routing/bgp.rs @@ -51,6 +51,7 @@ pub struct AfIpv6Ucast { } #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct AfL2vpnEvpn { pub adv_all_vni: bool, pub adv_default_gw: bool, @@ -64,6 +65,7 @@ pub struct AfL2vpnEvpn { } #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct BgpNeighCapabilities { pub dynamic: bool, pub ext_nhop: bool, @@ -119,6 +121,7 @@ impl BgpNeighAF { #[derive(Clone, Debug, Default)] /// A BGP neighbor config +#[allow(clippy::struct_excessive_bools)] pub struct BgpNeighbor { pub ntype: BgpNeighType, pub remote_as: Option, @@ -159,6 +162,7 @@ pub struct BgpNeighbor { } #[derive(Clone, Debug, Default)] +#[allow(clippy::struct_excessive_bools)] pub struct BgpDefaultsAF { flow_spec: bool, labeled_unicast: bool, @@ -178,6 +182,7 @@ pub struct BgpDefaults { #[derive(Clone, Debug)] /// BGP global configuration options +#[allow(clippy::struct_excessive_bools)] pub struct BgpOptions { pub network_import_check: bool, pub ebgp_requires_policy: bool, diff --git a/config/src/internal/routing/bmp.rs b/config/src/internal/routing/bmp.rs index 126f3249d..5b297ef10 100644 --- a/config/src/internal/routing/bmp.rs +++ b/config/src/internal/routing/bmp.rs @@ -26,6 +26,7 @@ pub enum BmpSource { } #[derive(Clone, Debug)] +#[allow(clippy::struct_excessive_bools)] pub struct BmpOptions { /// Name for `bmp targets ` pub target_name: String, diff --git a/config/src/internal/routing/prefixlist.rs b/config/src/internal/routing/prefixlist.rs index 088e854dd..b2eddaae2 100644 --- a/config/src/internal/routing/prefixlist.rs +++ b/config/src/internal/routing/prefixlist.rs @@ -66,6 +66,11 @@ impl PrefixList { entries: BTreeMap::new(), } } + /// Add an entry to the prefix list. + /// + /// # Errors + /// + /// Returns an error if the entry has an incompatible IP version or the sequence number is a duplicate. pub fn add_entry(&mut self, seq: Option, mut entry: PrefixListEntry) -> ConfigResult { if !entry.is_version_compatible(self.ipver) { let msg = format!( @@ -94,6 +99,11 @@ impl PrefixList { self.entries.insert(seq, entry); Ok(()) } + /// Add multiple entries to the prefix list. + /// + /// # Errors + /// + /// Returns an error if any entry has an incompatible IP version or a duplicate sequence number. pub fn add_entries( &mut self, entries: impl IntoIterator, diff --git a/config/src/internal/routing/routemap.rs b/config/src/internal/routing/routemap.rs index b55a3b107..439c509f1 100644 --- a/config/src/internal/routing/routemap.rs +++ b/config/src/internal/routing/routemap.rs @@ -101,6 +101,11 @@ impl RouteMap { entries: BTreeMap::new(), } } + /// Add an entry to the route map. + /// + /// # Errors + /// + /// Returns an error if the sequence number is a duplicate. pub fn add_entry(&mut self, seq: Option, entry: RouteMapEntry) -> ConfigResult { let seq = if let Some(n) = seq { n diff --git a/config/src/internal/routing/vrf.rs b/config/src/internal/routing/vrf.rs index dd884fe53..86601af50 100644 --- a/config/src/internal/routing/vrf.rs +++ b/config/src/internal/routing/vrf.rs @@ -137,6 +137,10 @@ impl VrfConfigTable { ////////////////////////////////////////////////////////// /// Store a `VrfConfig` object in this `VrfConfigTable` + /// + /// # Errors + /// + /// Returns an error if the VRF has duplicate fields (name, table ID, or VNI). ////////////////////////////////////////////////////////// pub fn add_vrf_config(&mut self, vrf_cfg: VrfConfig) -> ConfigResult { let name = vrf_cfg.name.clone(); diff --git a/config/src/lib.rs b/config/src/lib.rs index 0cd826887..57115bd98 100644 --- a/config/src/lib.rs +++ b/config/src/lib.rs @@ -15,10 +15,6 @@ clippy::expect_used, clippy::panic )] -#![allow(clippy::redundant_closure_for_method_calls)] -#![allow(clippy::doc_markdown)] -#![allow(clippy::missing_errors_doc)] -#![allow(clippy::struct_excessive_bools)] pub mod converters; pub mod display; From 66b3f49a8ea7d6af077be33af4556e8809a9d398 Mon Sep 17 00:00:00 2001 From: Daniel Noland Date: Tue, 24 Mar 2026 19:27:33 -0600 Subject: [PATCH 05/12] build(net): prep for wasm32-wasip1 - Move atomic-instant-full under cfg(any(x86_64, aarch64)) target dep - Move linux-raw-sys under cfg(unix) target dep - Gate flows and packet modules with #![cfg(unix)] - Replace linux_raw_sys::if_ether::ETH_MAX_MTU with literal 65535 in mtu.rs - Add #![cfg_attr(not(unix), allow(unused))] to net/src/lib.rs --- net/Cargo.toml | 8 ++++++-- net/src/flows/mod.rs | 1 + net/src/interface/mtu.rs | 4 ++-- net/src/lib.rs | 3 +++ net/src/packet/mod.rs | 2 ++ 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/net/Cargo.toml b/net/Cargo.toml index 4b9df60ef..e425cd9cb 100644 --- a/net/Cargo.toml +++ b/net/Cargo.toml @@ -13,9 +13,14 @@ netdevsim = [] bolero = ["dep:bolero", "id/bolero"] test_buffer = [] +[target.'cfg(any(target_arch = "x86_64", target_arch = "aarch64"))'.dependencies] +atomic-instant-full = { workspace = true } + +[target.'cfg(unix)'.dependencies] +linux-raw-sys = { workspace = true, features = ["std", "if_ether"] } + [dependencies] arrayvec = { workspace = true, features = ["serde", "std"] } -atomic-instant-full = { workspace = true } bitflags = { workspace = true } bolero = { workspace = true, features = ["std"], optional = true } bytecheck = { workspace = true } @@ -24,7 +29,6 @@ derive_builder = { workspace = true, features = ["alloc"] } downcast-rs = { workspace = true, features = ["sync"] } etherparse = { workspace = true, features = ["std"] } id = { workspace = true } -linux-raw-sys = { workspace = true, features = ["std", "if_ether"] } multi_index_map = { workspace = true, default-features = false, features = ["serde"] } rapidhash = { workspace = true } rkyv = { workspace = true, features = ["alloc", "bytecheck"]} diff --git a/net/src/flows/mod.rs b/net/src/flows/mod.rs index 32f8d6e42..38a3de242 100644 --- a/net/src/flows/mod.rs +++ b/net/src/flows/mod.rs @@ -3,6 +3,7 @@ //! Definitions for flow keys +#![cfg(unix)] #![allow(missing_docs)] pub mod atomic_instant; diff --git a/net/src/interface/mtu.rs b/net/src/interface/mtu.rs index 2abbf1fba..e24b66383 100644 --- a/net/src/interface/mtu.rs +++ b/net/src/interface/mtu.rs @@ -1,7 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Open Network Fabric Authors -use linux_raw_sys::if_ether; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::fmt::{Display, Formatter}; @@ -16,7 +15,8 @@ pub struct Mtu(NonZero); impl Mtu { pub(crate) const MIN_U32: u32 = 1280; // 1280 IPv6 minimum MTU - pub(crate) const MAX_U32: u32 = if_ether::ETH_MAX_MTU; + pub(crate) const MAX_U32: u32 = 65535; + pub(crate) const DEFAULT_U32: u32 = 1500; /// The minimum legal MTU for an IPv6 interface is 1280 bytes. diff --git a/net/src/lib.rs b/net/src/lib.rs index 001df2670..13e89f75b 100644 --- a/net/src/lib.rs +++ b/net/src/lib.rs @@ -12,12 +12,14 @@ clippy::expect_used, clippy::panic )] +#![cfg_attr(not(unix), allow(unused))] // for wasm32 builds #![allow(clippy::should_panic_without_expect)] // we panic in contract checks with simple unwrap() pub mod addr_parse_error; pub mod buffer; pub mod checksum; pub mod eth; +#[cfg(unix)] pub mod flows; pub mod headers; pub mod icmp4; @@ -38,6 +40,7 @@ pub mod vlan; pub mod vxlan; // re-export +#[cfg(unix)] pub use flows::flow_key::{ self, ExtendedFlowKey, FlowKey, FlowKeyData, IcmpProtoKey, IpProtoKey, TcpProtoKey, UdpProtoKey, }; diff --git a/net/src/packet/mod.rs b/net/src/packet/mod.rs index 64e5cdab7..9970cc74e 100644 --- a/net/src/packet/mod.rs +++ b/net/src/packet/mod.rs @@ -3,6 +3,8 @@ //! Packet struct and methods +#![cfg(unix)] + mod display; mod hash; mod meta; From 2f71d95a7ed36863e57231b0f9bc78c384be6004 Mon Sep 17 00:00:00 2001 From: Daniel Noland Date: Tue, 24 Mar 2026 19:28:02 -0600 Subject: [PATCH 06/12] build(hardware): make sysfs dependency optional behind feature flag Add a 'sysfs' feature to the hardware crate (default-enabled) that gates the sysfs dependency and the nic module. This allows the hardware crate to be used in wasm32 builds where sysfs/procfs is unavailable. Update downstream consumers (args, init) to explicitly enable the sysfs feature. --- args/Cargo.toml | 4 ++-- hardware/Cargo.toml | 6 ++++-- hardware/src/lib.rs | 1 + init/Cargo.toml | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/args/Cargo.toml b/args/Cargo.toml index ba9226a76..2bfbdbbbd 100644 --- a/args/Cargo.toml +++ b/args/Cargo.toml @@ -7,7 +7,7 @@ version.workspace = true [dependencies] # internal -hardware = { workspace = true, features = ["serde"] } +hardware = { workspace = true, features = ["serde", "sysfs"] } id = { workspace = true, features = [] } net = { workspace = true, features = [] } @@ -27,7 +27,7 @@ uuid = { workspace = true, features = [] } [dev-dependencies] # internal -hardware = { workspace = true, features = ["serde"] } +hardware = { workspace = true, features = ["serde", "sysfs"] } net = { workspace = true, features = ["test_buffer"] } # external serde_yaml_ng = { workspace = true, features = [] } diff --git a/hardware/Cargo.toml b/hardware/Cargo.toml index 3c889842e..ecc58242a 100644 --- a/hardware/Cargo.toml +++ b/hardware/Cargo.toml @@ -7,18 +7,20 @@ publish.workspace = true version.workspace = true [features] -default = [] +default = ["sysfs"] # Enables property testing / fuzzing support for testing. bolero = ["dep:bolero"] # Enables hardware topology scanning using the `hwlocality` crate. scan = ["dep:hwlocality", "dep:pci-ids"] # Adds serialization support for all types using serde. serde = ["dep:serde", "id/serde"] +# Enables sysfs/procfs support (linux-only; disable for WASM targets). +sysfs = ["dep:sysfs"] [dependencies] # internal id = { workspace = true, features = ["rkyv"] } -sysfs = { workspace = true } +sysfs = { workspace = true, optional = true } # external bolero = { workspace = true, optional = true, features = ["alloc"] } diff --git a/hardware/src/lib.rs b/hardware/src/lib.rs index 70ed25389..4afbb98d4 100644 --- a/hardware/src/lib.rs +++ b/hardware/src/lib.rs @@ -17,6 +17,7 @@ use crate::pci::bridge::BridgeAttributes; pub mod group; pub mod mem; +#[cfg(feature = "sysfs")] pub mod nic; pub mod os; pub mod pci; diff --git a/init/Cargo.toml b/init/Cargo.toml index cfea1672a..cf0e0ea9c 100644 --- a/init/Cargo.toml +++ b/init/Cargo.toml @@ -11,7 +11,7 @@ sysroot = ["dep:dpdk-sysroot-helper"] [dependencies] # internal -hardware = { workspace = true, features = ["serde", "scan"] } +hardware = { workspace = true, features = ["serde", "scan", "sysfs"] } id = { workspace = true } sysfs = { workspace = true } From c4c273ef73cae05008ec13b255af27837092cb79 Mon Sep 17 00:00:00 2001 From: Daniel Noland Date: Tue, 24 Mar 2026 19:28:27 -0600 Subject: [PATCH 07/12] build(k8s-intf): feature-gate K8s client dependencies Add a 'client' feature (default-enabled) to k8s-intf that gates the full K8s client, watcher, and status-reporting functionality (futures, linkme, rustls, tokio, tracectl, and kube client/runtime/rustls-tls features). When disabled, only the generated CRD types are available. This allows the validator crate to depend on k8s-intf with default-features=false for wasm32-wasip1 builds that only need CRD type definitions. Update downstream consumers: - k8s-less, mgmt: explicitly enable the client feature - validator: use default-features = false --- k8s-intf/Cargo.toml | 32 +++++++++++++++++++++++--------- k8s-intf/src/lib.rs | 3 +++ k8s-less/Cargo.toml | 2 +- mgmt/Cargo.toml | 2 +- validator/Cargo.toml | 2 +- 5 files changed, 29 insertions(+), 12 deletions(-) diff --git a/k8s-intf/Cargo.toml b/k8s-intf/Cargo.toml index 51836cddb..59af332fe 100644 --- a/k8s-intf/Cargo.toml +++ b/k8s-intf/Cargo.toml @@ -6,36 +6,50 @@ publish.workspace = true version.workspace = true [features] +default = ["client"] +# Enables the full K8s client, watcher, and status-reporting functionality. +# Disable this feature (default-features = false) for WASM/WASI builds that +# only need the generated CRD types. +client = [ + "dep:futures", + "dep:linkme", + "dep:rustls", + "dep:tokio", + "dep:tracectl", + "kube/client", + "kube/runtime", + "kube/rustls-tls", +] bolero = ["dep:bolero", "dep:hardware", "dep:net", "dep:lpm", "net/test_buffer", "net/bolero", "hardware/bolero"] [dependencies] # internal -hardware = { workspace = true, optional = true, features = ["bolero"] } +hardware = { workspace = true, optional = true, features = ["bolero", "sysfs"] } lpm = { workspace = true, optional = true } net = { workspace = true, optional = true, features = ["bolero"] } -tracectl = { workspace = true } +tracectl = { workspace = true, optional = true } tracing = { workspace = true } # external bolero = { workspace = true, optional = true } -futures = { workspace = true } -kube = { workspace = true, features = ["client", "derive", "runtime", "rustls-tls"] } +futures = { workspace = true, optional = true } +kube = { workspace = true, features = ["derive"] } kube-core = { workspace = true } k8s-openapi = { workspace = true, features = ["latest", "schemars", "std"] } -linkme = { workspace = true } -rustls = { workspace = true, features = ["aws-lc-rs"] } +linkme = { workspace = true, optional = true } +rustls = { workspace = true, features = ["aws-lc-rs"], optional = true } schemars = { workspace = true, features = ["derive", "std"] } serde = { workspace = true } serde_json = { workspace = true } serde_yaml_ng = { workspace = true } thiserror = { workspace = true } -tokio = { workspace = true } +tokio = { workspace = true, optional = true } [dev-dependencies] bolero = { workspace = true } -hardware = { workspace = true, features = ["bolero"] } +hardware = { workspace = true, features = ["bolero", "sysfs"] } lpm = { workspace = true, features = [] } net = { workspace = true, features = ["bolero", "test_buffer"] } [build-dependencies] -dpdk-sysroot-helper = { workspace = true } +dpdk-sysroot-helper = { workspace = true } \ No newline at end of file diff --git a/k8s-intf/src/lib.rs b/k8s-intf/src/lib.rs index 0d5bad7a1..cf0d486d8 100644 --- a/k8s-intf/src/lib.rs +++ b/k8s-intf/src/lib.rs @@ -7,7 +7,9 @@ #[cfg(any(test, feature = "bolero"))] pub mod bolero; +#[cfg(feature = "client")] pub mod client; +#[cfg(feature = "client")] pub mod utils; #[allow(clippy::all, clippy::pedantic)] @@ -16,4 +18,5 @@ pub mod gateway_agent_crd { include!(concat!(env!("OUT_DIR"), "/gateway_agent_crd.rs")); } +#[cfg(feature = "client")] pub use client::watch_gateway_agent_crd; diff --git a/k8s-less/Cargo.toml b/k8s-less/Cargo.toml index 71250994c..c7c38407e 100644 --- a/k8s-less/Cargo.toml +++ b/k8s-less/Cargo.toml @@ -8,7 +8,7 @@ repository.workspace = true [dependencies] inotify = { workspace = true, features = ["stream"] } -k8s-intf = { workspace = true } +k8s-intf = { workspace = true, features = ["client"] } tokio = { workspace = true, features = ["macros", "rt", "fs"] } tracectl = { workspace = true } tracing = { workspace = true } diff --git a/mgmt/Cargo.toml b/mgmt/Cargo.toml index aed33e2e5..5eccb98ed 100644 --- a/mgmt/Cargo.toml +++ b/mgmt/Cargo.toml @@ -23,7 +23,7 @@ concurrency = { workspace = true } flow-filter = { workspace = true } id = { workspace = true } interface-manager = { workspace = true } -k8s-intf = { workspace = true } +k8s-intf = { workspace = true, features = ["client"] } k8s-less = { workspace = true } lpm = { workspace = true } nat = { workspace = true } diff --git a/validator/Cargo.toml b/validator/Cargo.toml index 7da65435f..b1c1c662d 100644 --- a/validator/Cargo.toml +++ b/validator/Cargo.toml @@ -11,7 +11,7 @@ path = "src/main.rs" [dependencies] config = { workspace = true } -k8s-intf = { workspace = true } +k8s-intf = { workspace = true, default-features = false } serde = { workspace = true } serde_yaml_ng = { workspace = true } From 56afc64510b87abff149b818333aebc4a442bc7d Mon Sep 17 00:00:00 2001 From: Daniel Noland Date: Tue, 24 Mar 2026 19:28:53 -0600 Subject: [PATCH 08/12] build(config): gate tracectl and linkme behind cfg(unix) Move linkme and tracectl to [target.'cfg(unix)'.dependencies] in config/Cargo.toml. Add #[cfg(unix)] / #[cfg(not(unix))] conditional imports and fallback definitions for LevelFilter and DEFAULT_DEFAULT_LOGLEVEL on non-unix platforms. Split TracingConfig::validate() into platform-specific implementations: - Unix: real validation via tracectl - Non-unix: no-op (returns Ok) Gate trace_target! macro and ConfigError::Tracing variant with cfg(unix). Also: clean up derive_builder features, add sysfs to hardware dev-dep, simplify bolero dev-dep features. --- config/Cargo.toml | 14 ++++++---- config/src/converters/k8s/config/tracecfg.rs | 3 ++ config/src/errors.rs | 4 +++ config/src/internal/device/tracecfg.rs | 29 ++++++++++++++++++-- config/src/lib.rs | 2 ++ 5 files changed, 44 insertions(+), 8 deletions(-) diff --git a/config/Cargo.toml b/config/Cargo.toml index 48bc05abd..ff4bb3856 100644 --- a/config/Cargo.toml +++ b/config/Cargo.toml @@ -9,31 +9,33 @@ version.workspace = true # internal common = { workspace = true } hardware = { workspace = true } +k8s-intf = { workspace = true } net = { workspace = true } lpm = { workspace = true } -tracectl = { workspace = true } -k8s-intf = { workspace = true } # external arc-swap = { workspace = true } chrono = { workspace = true, features = ["alloc", "std"] } -derive_builder = { workspace = true, default-features = false, features = ["default"] } +derive_builder = { workspace = true, features = [] } ipnet = { workspace = true } -linkme = { workspace = true } multi_index_map = { workspace = true, features = ["serde"] } ordermap = { workspace = true, features = ["std"] } thiserror = { workspace = true } tracing = { workspace = true, features = ["attributes"] } +[target.'cfg(unix)'.dependencies] +linkme = { workspace = true } +tracectl = { workspace = true } + [dev-dependencies] # internal pipeline = { workspace = true } # should be removed w/ NAT lpm = { workspace = true, features = ["testing"] } -hardware = { workspace = true, features = ["bolero"] } +hardware = { workspace = true, features = ["bolero", "sysfs"] } k8s-intf = { workspace = true, features = ["bolero"] } # external -bolero = { workspace = true, default-features = false, features = ["alloc"] } +bolero = { workspace = true, features = ["alloc"] } caps = { workspace = true } ipnet = { workspace = true } pretty_assertions = { workspace = true, features = ["std"] } diff --git a/config/src/converters/k8s/config/tracecfg.rs b/config/src/converters/k8s/config/tracecfg.rs index 19f893eb2..6f000ec00 100644 --- a/config/src/converters/k8s/config/tracecfg.rs +++ b/config/src/converters/k8s/config/tracecfg.rs @@ -1,7 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Open Network Fabric Authors +#[cfg(unix)] use tracectl::LevelFilter; +#[cfg(not(unix))] +use tracing::metadata::LevelFilter; use k8s_intf::gateway_agent_crd::GatewayAgentGatewayLogs; diff --git a/config/src/errors.rs b/config/src/errors.rs index f671b61a4..ef5a4221f 100644 --- a/config/src/errors.rs +++ b/config/src/errors.rs @@ -95,8 +95,12 @@ pub enum ConfigError { Invalid(String), // tracing + #[cfg(unix)] #[error("Failed to set tracing configuration: {0}")] Tracing(#[from] tracectl::TraceCtlError), + #[cfg(not(unix))] + #[error("Failed to set tracing configuration: {0}")] + Tracing(String), // Community mappings #[error("Could not assign BGP community to VPC peering {0}")] diff --git a/config/src/internal/device/tracecfg.rs b/config/src/internal/device/tracecfg.rs index 20851dd3a..f38806bd3 100644 --- a/config/src/internal/device/tracecfg.rs +++ b/config/src/internal/device/tracecfg.rs @@ -7,9 +7,17 @@ use crate::ConfigResult; use ordermap::OrderMap; +use tracing::debug; + +#[cfg(unix)] use tracectl::DEFAULT_DEFAULT_LOGLEVEL; +#[cfg(unix)] use tracectl::{LevelFilter, get_trace_ctl}; -use tracing::debug; + +#[cfg(not(unix))] +use tracing::metadata::LevelFilter; +#[cfg(not(unix))] +const DEFAULT_DEFAULT_LOGLEVEL: LevelFilter = LevelFilter::INFO; #[derive(Clone, Debug)] pub struct TracingConfig { @@ -35,9 +43,26 @@ impl TracingConfig { pub fn add_tag(&mut self, tag: &str, level: LevelFilter) { let _ = self.tags.insert(tag.to_string(), level); } + /// Validate the tracing configuration. + /// + /// # Errors + /// + /// Returns an error if any configured trace tag is unknown. + #[cfg(unix)] pub fn validate(&self) -> ConfigResult { debug!("Validating tracing configuration.."); - let tags: Vec<&str> = self.tags.keys().map(|k| k.as_str()).collect(); + let tags: Vec<&str> = self.tags.keys().map(String::as_str).collect(); Ok(get_trace_ctl().check_tags(&tags)?) } + + /// Validate the tracing configuration (no-op on non-unix platforms). + /// + /// # Errors + /// + /// Always returns `Ok(())` on non-unix platforms. + #[cfg(not(unix))] + pub fn validate(&self) -> ConfigResult { + debug!("Validating tracing configuration (no-op on this platform).."); + Ok(()) + } } diff --git a/config/src/lib.rs b/config/src/lib.rs index 57115bd98..833755edf 100644 --- a/config/src/lib.rs +++ b/config/src/lib.rs @@ -32,5 +32,7 @@ pub use gwconfig::{GwConfig, GwConfigMeta}; pub use internal::InternalConfig; pub use internal::device::DeviceConfig; +#[cfg(unix)] use tracectl::trace_target; +#[cfg(unix)] trace_target!("mgmt", LevelFilter::DEBUG, &["management"]); From e7386ab1e281895dc49f1f64e2b8a0f8e6913fba Mon Sep 17 00:00:00 2001 From: Daniel Noland Date: Tue, 24 Mar 2026 19:29:47 -0600 Subject: [PATCH 09/12] build: wire up feature flags for wasm32 validator build Workspace-level changes to support cross-platform builds: - Set default-features = false for hardware and k8s-intf workspace deps - Move rt-multi-thread from workspace tokio to routing/Cargo.toml - Move codec from workspace tokio-util to routing/Cargo.toml - Fix trailing whitespace on netgauze-bgp-pkt line Update Cargo.lock to reflect dependency changes. --- Cargo.lock | 37 +++++++++++++++---------------------- Cargo.toml | 10 +++++----- routing/Cargo.toml | 2 +- 3 files changed, 21 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 514182d03..f03c42f1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -126,7 +126,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -137,7 +137,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -146,12 +146,6 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" -[[package]] -name = "arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" - [[package]] name = "arc-swap" version = "1.9.0" @@ -507,7 +501,6 @@ version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98a5782f2650f80d533f58ec339c6dce4cc5428f9c2755894f98156f52af81f2" dependencies = [ - "arbitrary", "bolero-generator-derive", "either", "getrandom 0.3.4", @@ -877,7 +870,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2091,7 +2084,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc 0.2.183", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3897,7 +3890,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4911,7 +4904,7 @@ dependencies = [ "errno", "libc 0.2.183", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5426,7 +5419,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc 0.2.183", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5533,9 +5526,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" [[package]] name = "symbolic-common" -version = "12.17.2" +version = "12.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "751a2823d606b5d0a7616499e4130a516ebd01a44f39811be2b9600936509c23" +checksum = "52ca086c1eb5c7ee74b151ba83c6487d5d33f8c08ad991b86f3f58f6629e68d5" dependencies = [ "debugid", "memmap2 0.9.10", @@ -5545,9 +5538,9 @@ dependencies = [ [[package]] name = "symbolic-demangle" -version = "12.17.2" +version = "12.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b237cfbe320601dd24b4ac817a5b68bb28f5508e33f08d42be0682cadc8ac9" +checksum = "baa911a28a62823aaf2cc2e074212492a3ee69d0d926cc8f5b12b4a108ff5c0c" dependencies = [ "cpp_demangle", "rustc-demangle", @@ -5612,7 +5605,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5628,12 +5621,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c0c0a69df..01ad55e34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,11 +61,11 @@ dplane-rpc = { git = "https://github.com/githedgehog/dplane-rpc.git", rev = "e8f errno = { path = "./errno", package = "dataplane-errno", features = [] } flow-entry = { path = "./flow-entry", package = "dataplane-flow-entry", features = [] } flow-filter = { path = "./flow-filter", package = "dataplane-flow-filter", features = [] } -hardware = { path = "./hardware", package = "dataplane-hardware", features = [] } +hardware = { path = "./hardware", package = "dataplane-hardware", default-features = false, features = [] } id = { path = "./id", package = "dataplane-id", features = [] } init = { path = "./init", package = "dataplane-init", features = [] } interface-manager = { path = "./interface-manager", package = "dataplane-interface-manager", features = [] } -k8s-intf = { path = "./k8s-intf", package = "dataplane-k8s-intf", features = [] } +k8s-intf = { path = "./k8s-intf", package = "dataplane-k8s-intf", default-features = false, features = [] } k8s-less = { path = "./k8s-less", package = "dataplane-k8s-less", features = [] } left-right-tlcache = { path = "./left-right-tlcache", package = "dataplane-left-right-tlcache", features = [] } lpm = { path = "./lpm", package = "dataplane-lpm", features = [] } @@ -141,7 +141,7 @@ multi_index_map = { version = "0.15.1", default-features = false, features = [] n-vm = { git = "https://github.com/githedgehog/testn.git", tag = "v0.0.9", default-features = false, features = [], package = "n-vm" } netdev = { version = "0.41.0", default-features = false, features = [] } nix = { version = "0.31.2", default-features = false, features = [] } -netgauze-bgp-pkt = { version = "0.10.0", features = [] } +netgauze-bgp-pkt = { version = "0.10.0", features = [] } netgauze-bmp-pkt = { version = "0.10.0", features = ["codec"] } num-derive = { version = "0.4.2", default-features = false, features = [] } num-traits = { version = "0.2.19", default-features = false, features = [] } @@ -178,9 +178,9 @@ strum_macros = { version = "0.28.0", default-features = false, features = [] } syn = { version = "2.0.117", default-features = false, features = [] } thiserror = { version = "2.0.18", default-features = false, features = [] } thread_local = { version = "1.1.9", default-features = false, features = [] } -tokio = { version = "1.50.0", default-features = false, features = ["rt-multi-thread"] } +tokio = { version = "1.50.0", default-features = false, features = [] } tracing = { version = "0.1.44", default-features = false, features = [] } -tokio-util = { version = "0.7.18", default-features = false, features = ["codec"] } +tokio-util = { version = "0.7.18", default-features = false, features = [] } tonic = { version = "0.14.5", default-features = false, features = [] } tracing-error = { version = "0.2.1", default-features = false, features = [] } tracing-subscriber = { version = "0.3.23", default-features = false, features = [] } diff --git a/routing/Cargo.toml b/routing/Cargo.toml index 508e1f89f..b5ee666bd 100644 --- a/routing/Cargo.toml +++ b/routing/Cargo.toml @@ -39,7 +39,7 @@ netgauze-bmp-pkt = { workspace = true } serde = { workspace = true, features = ["derive"] } strum = { workspace = true } thiserror = { workspace = true } -tokio = { workspace = true, features = ["fs", "io-util", "sync", "rt", "net", "macros"] } +tokio = { workspace = true, features = ["fs", "io-util", "sync", "rt", "net", "macros", "rt-multi-thread"] } tokio-util = { workspace = true, features = ["codec"] } tracing = { workspace = true } From 07ea33b9671c402dc6486e17355bd09cc4f0e789 Mon Sep 17 00:00:00 2001 From: Daniel Noland Date: Tue, 24 Mar 2026 19:29:56 -0600 Subject: [PATCH 10/12] build(nix): add wasm32-wasip1 rust targets - Add wasm32-wasip1 platform entry in nix/platforms.nix (arch table + info) - Accept kernel parameter in platforms.nix for wasip1 support - Add wasm32-wasip1 to rust toolchain targets in nix/overlays/llvm.nix - Add empty march.wasm32 in nix/profiles.nix --- nix/overlays/llvm.nix | 1 + nix/platforms.nix | 14 ++++++++++++++ nix/profiles.nix | 1 + 3 files changed, 16 insertions(+) diff --git a/nix/overlays/llvm.nix b/nix/overlays/llvm.nix index 5bffc8823..8b22cbc74 100644 --- a/nix/overlays/llvm.nix +++ b/nix/overlays/llvm.nix @@ -42,6 +42,7 @@ let ]; targets = [ platform.info.target + "wasm32-wasip1" ]; }; rustPlatform' = final.makeRustPlatform { diff --git a/nix/platforms.nix b/nix/platforms.nix index 9d8fc92c7..872d78e3c 100644 --- a/nix/platforms.nix +++ b/nix/platforms.nix @@ -76,6 +76,11 @@ let NIX_CFLAGS_LINK = [ ]; }; }; + wasm32-wasip1 = { + arch = "wasm32"; + march = "wasm32"; + override.stdenv.env = { }; + }; }; in lib.fix ( @@ -126,6 +131,15 @@ lib.fix ( }; }; }; + wasm32 = { + wasip1 = { + unknown = { + target = "wasm32-wasip1"; + machine = "wasm32"; + nixarch = "wasi32"; + }; + }; + }; } .${final.arch}.${kernel}.${libc}; } diff --git a/nix/profiles.nix b/nix/profiles.nix index 75bc1a38c..c59fa9443 100644 --- a/nix/profiles.nix +++ b/nix/profiles.nix @@ -96,6 +96,7 @@ let march.aarch64.NIX_CXXFLAGS_COMPILE = march.aarch64.NIX_CFLAGS_COMPILE; march.aarch64.NIX_CFLAGS_LINK = [ ]; march.aarch64.RUSTFLAGS = [ ] ++ (map (flag: "-Clink-arg=${flag}") march.aarch64.NIX_CFLAGS_LINK); + march.wasm32 = { }; sanitize.address.NIX_CFLAGS_COMPILE = [ "-fsanitize=address,local-bounds" ]; From 2547df0d1949044aaf49864c2d7bd39ad90a51d4 Mon Sep 17 00:00:00 2001 From: Daniel Noland Date: Tue, 24 Mar 2026 19:30:11 -0600 Subject: [PATCH 11/12] build(nix): refactor default.nix for wasm32 support Major restructuring to support wasm32-wasip1 alongside native builds: - Add kernel parameter for platform selection - Merge dev-pkgs and frr-pkgs into unified pkgs (with conditional pkgsCross) - Conditional sysroot: full native deps for linux, minimal for wasm - Replace dev-pkgs references with pkgs.pkgsBuildHost - Conditional RUSTFLAGS: linker flags for native, empty for wasm - Conditional postInstall: strip/objcopy for native, wasm-opt for wasm - Add workspace-builder-wasm and workspace-wasm derivations - Fix crane import (remove unnecessary pkgs arg) - Fix src filter (use baseNameOf and lib.cleanSource) - Add oras to devshell for OCI artifact pushing - Add binary symlinks to dataplane.tar - Remove fakeNss from frr-host container - Remove dev-pkgs and frr-pkgs from exports, add workspace-wasm --- default.nix | 268 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 166 insertions(+), 102 deletions(-) diff --git a/default.nix b/default.nix index 8b8364d78..5a00f29e6 100644 --- a/default.nix +++ b/default.nix @@ -8,6 +8,7 @@ sanitize ? "", features ? "", default-features ? "true", + kernel ? "linux", tag ? "dev", }: let @@ -21,7 +22,12 @@ let builtins.filter (elm: builtins.isString elm) (builtins.split split-on string); lib = (import sources.nixpkgs { }).lib; platform' = import ./nix/platforms.nix { - inherit lib platform libc; + inherit + lib + platform + libc + kernel + ; }; sanitizers = split-str ",+" sanitize; cargo-features = split-str ",+" features; @@ -44,54 +50,53 @@ let profile = profile'; platform = platform'; }; - dev-pkgs = import sources.nixpkgs { - overlays = [ - overlays.rust - overlays.llvm - overlays.dataplane-dev - ]; - }; pkgs = - (import sources.nixpkgs { - overlays = [ - overlays.rust - overlays.llvm - overlays.dataplane - ]; - }).pkgsCross.${platform'.info.nixarch}; - frr-pkgs = - (import sources.nixpkgs { - overlays = [ - overlays.rust - overlays.llvm - overlays.dataplane - overlays.frr - ]; - }).pkgsCross.${platform'.info.nixarch}; - sysroot = pkgs.pkgsHostHost.symlinkJoin { - name = "sysroot"; - paths = with pkgs.pkgsHostHost; [ - pkgs.pkgsHostHost.libc.dev # fully qualified: bare `libc` resolves to the "gnu" function argument, not pkgs.pkgsHostHost.libc - pkgs.pkgsHostHost.libc.out # (same as above) - fancy.dpdk-wrapper.dev - fancy.dpdk-wrapper.out - fancy.dpdk.dev - fancy.dpdk.static - fancy.hwloc.dev - fancy.hwloc.static - fancy.libbsd.dev - fancy.libbsd.static - fancy.libmd.dev - fancy.libmd.static - fancy.libnl.dev - fancy.libnl.static - fancy.libunwind.out - fancy.numactl.dev - fancy.numactl.static - fancy.rdma-core.dev - fancy.rdma-core.static - ]; - }; + let + over = import sources.nixpkgs { + overlays = [ + overlays.rust + overlays.llvm + overlays.dataplane + overlays.dataplane-dev + overlays.frr + ]; + }; + in + if platform != "wasm32-wasip1" then over.pkgsCross.${platform'.info.nixarch} else over; + sysroot = + if platform != "wasm32-wasip1" then + pkgs.symlinkJoin { + name = "sysroot"; + paths = with pkgs.pkgsHostHost; [ + pkgs.pkgsHostHost.libc.dev # fully qualified: bare `libc` resolves to the "gnu" function argument, not pkgs.pkgsHostHost.libc + pkgs.pkgsHostHost.libc.out # (same as above) + fancy.dpdk-wrapper.dev + fancy.dpdk-wrapper.out + fancy.dpdk.dev + fancy.dpdk.static + fancy.hwloc.dev + fancy.hwloc.static + fancy.libbsd.dev + fancy.libbsd.static + fancy.libmd.dev + fancy.libmd.static + fancy.libnl.dev + fancy.libnl.static + fancy.libunwind.out + fancy.numactl.dev + fancy.numactl.static + fancy.rdma-core.dev + fancy.rdma-core.static + ]; + } + else + pkgs.symlinkJoin { + name = "sysroot"; + paths = with pkgs.pkgsHostHost; [ + fancy.hwloc.dev + fancy.hwloc.static + ]; + }; clangd-config = pkgs.writeTextFile { name = ".clangd"; text = '' @@ -104,7 +109,7 @@ let executable = false; destination = "/.clangd"; }; - crane = import sources.crane { pkgs = pkgs; }; + crane = import sources.crane { }; craneLib = crane.craneLib.overrideToolchain pkgs.rust-toolchain; devroot = pkgs.symlinkJoin { name = "dataplane-dev-shell"; @@ -117,7 +122,7 @@ let libclang.lib lld ]) - ++ (with dev-pkgs; [ + ++ (with pkgs.pkgsBuildHost; [ bash cargo-bolero cargo-deny @@ -132,6 +137,7 @@ let llvmPackages'.clang # you need the host compiler in order to link proc macros llvmPackages'.llvm # needed for coverage npins + oras pkg-config rust-toolchain skopeo @@ -149,7 +155,7 @@ let LIBRARY_PATH = "${sysroot}/lib"; PKG_CONFIG_PATH = "${sysroot}/lib/pkgconfig"; LIBCLANG_PATH = "${devroot}/lib"; - GW_CRD_PATH = "${dev-pkgs.gateway-crd}/src/fabric/config/crd/bases"; + GW_CRD_PATH = "${pkgs.pkgsBuildHost.gateway-crd}/src/fabric/config/crd/bases"; }; }; justfileFilter = p: _type: builtins.match ".*\.justfile$" p != null; @@ -159,13 +165,17 @@ let outputsFilter = p: _type: (p != "target") && (p != "sysroot") && (p != "devroot") && (p != ".git"); src = pkgs.lib.cleanSourceWith { filter = - p: t: + full-path: t: + let + p = baseNameOf full-path; + in (justfileFilter p t) || (markdownFilter p t) || (jsonFilter p t) || (cHeaderFilter p t) - || ((outputsFilter p t) && (craneLib.filterCargoSources p t)); - src = ./.; + || ((outputsFilter p t) && (craneLib.filterCargoSources full-path t)); + src = lib.cleanSource ./.; + name = "source"; }; cargoVendorDir = craneLib.vendorMultipleCargoDeps { cargoLockList = [ @@ -173,17 +183,22 @@ let "${pkgs.rust-toolchain.passthru.availableComponents.rust-src}/lib/rustlib/src/rust/library/Cargo.lock" ]; }; - target = pkgs.stdenv'.targetPlatform.rust.rustcTarget; - is-cross-compile = dev-pkgs.stdenv.hostPlatform.rust.rustcTarget != target; - cxx = if is-cross-compile then "${target}-clang++" else "clang++"; - strip = if is-cross-compile then "${target}-strip" else "strip"; - objcopy = if is-cross-compile then "${target}-objcopy" else "objcopy"; + ctarget = pkgs.stdenv'.targetPlatform.rust.rustcTarget; + rustc-target = + if platform == "wasm32-wasip1" then + "wasm32-wasip1" + else + pkgs.stdenv'.targetPlatform.rust.rustcTarget; + is-cross-compile = pkgs.stdenv'.hostPlatform.rust.rustcTarget != ctarget; + cxx = if is-cross-compile then "${ctarget}-clang++" else "clang++"; + strip = if is-cross-compile then "${ctarget}-strip" else "strip"; + objcopy = if is-cross-compile then "${ctarget}-objcopy" else "objcopy"; package-list = builtins.fromJSON ( builtins.readFile ( pkgs.runCommandLocal "package-list" { - TOMLQ = "${dev-pkgs.yq}/bin/tomlq"; - JQ = "${dev-pkgs.jq}/bin/jq"; + TOMLQ = "${pkgs.pkgsBuildHost.yq}/bin/tomlq"; + JQ = "${pkgs.pkgsBuildHost.jq}/bin/jq"; } '' $TOMLQ -r '.workspace.members | sort[]' ${src}/Cargo.toml | while read -r p; do @@ -197,7 +212,7 @@ let "-Zunstable-options" "-Zbuild-std=compiler_builtins,core,alloc,std,panic_unwind,panic_abort,sysroot,unwind" "-Zbuild-std-features=backtrace,panic-unwind,mem,compiler-builtins-mem" - "--target=${target}" + "--target=${rustc-target}" ] ++ (if default-features == "false" then [ "--no-default-features" ] else [ ]) ++ ( @@ -207,6 +222,7 @@ let [ ] ); invoke = + # if platform != "wasm32-wasip1" then { builder, args ? { @@ -234,7 +250,7 @@ let removeReferencesToVendorDir = true; nativeBuildInputs = [ - (dev-pkgs.kopium) + (pkgs.pkgsBuildHost.kopium) cargo-nextest llvmPackages'.clang llvmPackages'.lld @@ -253,28 +269,32 @@ let C_INCLUDE_PATH = "${sysroot}/include"; LIBRARY_PATH = "${sysroot}/lib"; PKG_CONFIG_PATH = "${sysroot}/lib/pkgconfig"; - GW_CRD_PATH = "${dev-pkgs.gateway-crd}/src/fabric/config/crd/bases"; + GW_CRD_PATH = "${pkgs.pkgsBuildHost.gateway-crd}/src/fabric/config/crd/bases"; RUSTC_BOOTSTRAP = "1"; - RUSTFLAGS = builtins.concatStringsSep " " ( - profile'.RUSTFLAGS - ++ [ - "-Clinker=${pkgs.pkgsBuildHost.llvmPackages'.clang}/bin/${cxx}" - "-Clink-arg=--ld-path=${pkgs.pkgsBuildHost.llvmPackages'.lld}/bin/ld.lld" - "-Clink-arg=-L${sysroot}/lib" - # NOTE: this is basically a trick to make our source code available to debuggers. - # Normally remap-path-prefix takes the form --remap-path-prefix=FROM=TO where FROM and TO are directories. - # This is intended to map source code paths to generic, relative, or redacted paths. - # We are sorta using that mechanism in reverse here in that the empty FROM in the next expression maps our - # source code in the debug info from the current working directory to ${src} (the nix store path where we - # have copied our source code). - # - # This is nice in that it should allow us to include ${src} in a container with gdb / lldb + the debug files - # we strip out of the final binaries we cook and include a gdbserver binary in some - # debug/release-with-debug-tools containers. Then, connecting from the gdb/lldb container to the - # gdb/lldbserver container should allow us to actually debug binaries deployed to test machines. - "--remap-path-prefix==${src}" - ] - ); + RUSTFLAGS = + if rustc-target != "wasm32-wasip1" then + builtins.concatStringsSep " " ( + profile'.RUSTFLAGS + ++ [ + "-Clinker=${pkgs.pkgsBuildHost.llvmPackages'.clang}/bin/${cxx}" + "-Clink-arg=--ld-path=${pkgs.pkgsBuildHost.llvmPackages'.lld}/bin/ld.lld" + "-Clink-arg=-L${sysroot}/lib" + # NOTE: this is basically a trick to make our source code available to debuggers. + # Normally remap-path-prefix takes the form --remap-path-prefix=FROM=TO where FROM and TO are directories. + # This is intended to map source code paths to generic, relative, or redacted paths. + # We are sorta using that mechanism in reverse here in that the empty FROM in the next expression maps our + # source code in the debug info from the current working directory to ${src} (the nix store path where we + # have copied our source code). + # + # This is nice in that it should allow us to include ${src} in a container with gdb / lldb + the debug files + # we strip out of the final binaries we cook and include a gdbserver binary in some + # debug/release-with-debug-tools containers. Then, connecting from the gdb/lldb container to the + # gdb/lldbserver container should allow us to actually debug binaries deployed to test machines. + "--remap-path-prefix==${src}" + ] + ) + else + ""; }; } // args @@ -291,14 +311,28 @@ let postBuild = (orig.postBuild or "") + '' unset RUSTFLAGS; ''; - postInstall = (orig.postInstall or "") + '' - mkdir -p $debug/bin - for f in $out/bin/*; do - mv "$f" "$debug/bin/$(basename "$f")" - ${strip} --strip-debug "$debug/bin/$(basename "$f")" -o "$f" - ${objcopy} --add-gnu-debuglink="$debug/bin/$(basename "$f")" "$f" - done - ''; + postInstall = + (orig.postInstall or "") + + ( + if rustc-target != "wasm32-wasip1" then + '' + mkdir -p $debug/bin + for f in $out/bin/*; do + mv "$f" "$debug/bin/$(basename "$f")" + ${strip} --strip-debug "$debug/bin/$(basename "$f")" -o "$f" + ${objcopy} --add-gnu-debuglink="$debug/bin/$(basename "$f")" "$f" + done + '' + else + '' + mkdir -p $debug/bin + for f in $out/bin/*; do + mv "$f" "$debug/bin/$(basename "$f")" + ${pkgs.pkgsBuildHost.binaryen}/bin/wasm-opt "$debug/bin/$(basename "$f")" --strip-debug -O4 -o "$f" + # sadly there is no equivalent of gnu-debuglink in wasm world yet + done + '' + ); postFixup = (orig.postFixup or "") + '' rm -f $out/target.tar.zst ''; @@ -335,6 +369,38 @@ let } ) package-list; + workspace-builder-wasm = + { + pname ? null, + cargoArtifacts ? null, + }: + pkgs.callPackage invoke { + builder = craneLib.buildPackage; + args = { + inherit pname cargoArtifacts; + buildPhaseCargoCommand = builtins.concatStringsSep " " ( + [ + "cargoBuildLog=$(mktemp cargoBuildLogXXXX.json);" + "cargo" + "build" + "--package=${pname}" + "--profile=${cargo-profile}" + ] + ++ cargo-cmd-prefix + ++ [ + "--message-format json-render-diagnostics > $cargoBuildLog" + ] + ); + }; + }; + + workspace-wasm = builtins.mapAttrs ( + dir: pname: + workspace-builder-wasm { + inherit pname; + } + ) package-list; + test-builder = { package ? null, @@ -464,12 +530,12 @@ let ln -s "${workspace.dataplane}/bin/dataplane" "$tmp/bin/dataplane" ln -s "${workspace.cli}/bin/cli" "$tmp/bin/cli" ln -s "${workspace.init}/bin/dataplane-init" "$tmp/bin/dataplane-init" - ln -s "${workspace.dataplane}/bin/dataplane" "$tmp/dataplane" - ln -s "${workspace.cli}/bin/cli" "$tmp/dataplane-cli" - ln -s "${workspace.init}/bin/dataplane-init" "$tmp/dataplane-init" for i in "${pkgs.pkgsHostHost.busybox}/bin/"*; do ln -s "${pkgs.pkgsHostHost.busybox}/bin/busybox" "$tmp/bin/$(basename "$i")" done + ln -s "${workspace.dataplane}/bin/dataplane" "$tmp/dataplane" + ln -s "${workspace.init}/bin/dataplane-init" "$tmp/dataplane-init" + ln -s "${workspace.cli}/bin/cli" "$tmp/dataplane-cli" # we take some care to make the tar file reproducible here tar \ --create \ @@ -595,7 +661,7 @@ let contents = pkgs.buildEnv { name = "dataplane-frr-env"; pathsToLink = [ "/" ]; - paths = with frr-pkgs; [ + paths = with pkgs; [ bash coreutils dockerTools.usrBinEnv @@ -615,7 +681,7 @@ let }; fakeRootCommands = '' - #!${frr-pkgs.bash}/bin/bash + #!${pkgs.bash}/bin/bash set -euxo pipefail mkdir /tmp mkdir -p /run/frr/hh @@ -642,10 +708,9 @@ let pathsToLink = [ "/" ]; - paths = with frr-pkgs; [ + paths = with pkgs; [ bash coreutils - dockerTools.fakeNss dockerTools.usrBinEnv # TODO: frr-config's docker-start launches /bin/frr-agent which is not # present in the host container. A host-specific entrypoint script may @@ -662,7 +727,7 @@ let ]; }; fakeRootCommands = '' - #!${frr-pkgs.bash}/bin/bash + #!${pkgs.bash}/bin/bash set -euxo pipefail mkdir /tmp mkdir -p /run/frr/hh @@ -686,11 +751,9 @@ in inherit clippy containers - dev-pkgs devenv devroot docs - frr-pkgs dataplane package-list pkgs @@ -698,6 +761,7 @@ in sysroot tests workspace + workspace-wasm ; profile = profile'; platform = platform'; From 9b16b891f06e7f5cb1bdf84190feb4f492ea38c8 Mon Sep 17 00:00:00 2001 From: Daniel Noland Date: Tue, 24 Mar 2026 19:30:20 -0600 Subject: [PATCH 12/12] build: add justfile and CI support for wasm32 validator Justfile: - Add libc and kernel variables derived from platform - Pass libc and kernel to nix build invocations - Default jobs to 8 - Add validator target to build-container and push-container (via oras) - Update push recipe to include validator with wasm32-wasip1 platform CI (dev.yml): - Add validator to build matrix - Reorder nix-install before checkout for caching - Remove Go setup (no longer needed) - Add nix-shell caching step - Add oras login alongside docker login - Add platform logic for wasm vs native builds - Fix script quoting (double to single quotes) --- .github/workflows/dev.yml | 69 +++++++++++++++++++++------------------ justfile | 35 +++++++++++++++++--- 2 files changed, 68 insertions(+), 36 deletions(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index ab5514dfc..ddc634ba8 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -132,6 +132,7 @@ jobs: - tests.all - frr.dataplane - dataplane + - validator build: - name: "debug" profile: "debug" @@ -151,16 +152,11 @@ jobs: username: "${{ github.actor }}" password: "${{ secrets.GITHUB_TOKEN }}" - - name: "login to image cache" - run: | - echo "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USERNAME" --password-stdin "$REGISTRY_URL" - - # it's temporarily needed to install skopeo - - name: Setup Go - uses: actions/setup-go@v6 + - name: "Install nix" + uses: cachix/install-nix-action@v31 with: - go-version: stable - cache: true + github_access_token: ${{ secrets.GITHUB_TOKEN }} + nix_path: nixpkgs=channel:nixpkgs-unstable - name: "Checkout" uses: "actions/checkout@v6" @@ -168,12 +164,6 @@ jobs: persist-credentials: "false" fetch-depth: "0" - - name: "Install nix" - uses: cachix/install-nix-action@v31 - with: - github_access_token: ${{ secrets.GITHUB_TOKEN }} - nix_path: nixpkgs=channel:nixpkgs-unstable - - uses: "cachix/cachix-action@v17" with: name: "hedgehog" @@ -182,6 +172,17 @@ jobs: # prettier-ignore authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' + # this step exists to avoid attributing the amount of time it takes to fetch the nix-shell dependencies to + # whatever step happens to access the nix-shell first. + - name: "setup nix shell" + run: | + nix-shell --run "echo nix shell env cached" + + - name: "login to image cache" + run: | + docker login -u "$REGISTRY_USERNAME" --password-stdin "$REGISTRY_URL" <<<"${REGISTRY_PASSWORD}" + nix-shell --run "oras login -u \"$REGISTRY_USERNAME\" --password-stdin \"$REGISTRY_URL\" <<<\"${REGISTRY_PASSWORD}\"" + - name: "run pre-flight checks" if: ${{ matrix.nix-target == 'tests.all' }} run: | @@ -202,22 +203,28 @@ jobs: - name: "push container" if: ${{ matrix.nix-target != 'tests.all' }} run: | - nix-shell --run "just \ - debug_justfile=${{matrix.debug_justfile}} \ - check-dependencies" - for v in "" "version=${{ needs.version.outputs.version }}-${{ matrix.build.profile }}"; do - nix-shell --run " - just \ - docker_sock=/run/docker/docker.sock \ - debug_justfile=${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_justfile || false }} \ - profile=${{ matrix.build.profile }} \ - sanitize=${{ matrix.build.sanitize }} \ - instrument=${{ matrix.build.instrument }} \ - oci_repo=ghcr.io \ - $v \ - push-container ${{ matrix.nix-target }} - " - done + nix-shell --run ' + just \ + debug_justfile=${{matrix.debug_justfile}} \ + check-dependencies + for v in "" "version=${{ needs.version.outputs.version }}-${{ matrix.build.profile }}"; do + if [ "${{ matrix.nix-target }}" = "validator" ]; then + platform="wasm32-wasip1" + else + platform="x86-64-v3" + fi + just \ + docker_sock=/run/docker/docker.sock \ + debug_justfile=${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_justfile || false }} \ + profile=${{ matrix.build.profile }} \ + platform=${platform} \ + sanitize=${{ matrix.build.sanitize }} \ + instrument=${{ matrix.build.instrument }} \ + oci_repo=ghcr.io \ + $v \ + push-container ${{ matrix.nix-target }} + done + ' - name: "Setup tmate session for debug" if: ${{ failure() && github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} diff --git a/justfile b/justfile index 835ff2275..68d16de39 100644 --- a/justfile +++ b/justfile @@ -12,7 +12,13 @@ debug_justfile := "false" _just_debuggable_ := if debug_justfile == "true" { "set -x" } else { "" } # number of nix jobs to run in parallel -jobs := "1" +jobs := "8" + +# libc +libc := if platform == "wasm32-wasip1" { "unknown" } else { "gnu" } + +# kernel (linux or wasip1) +kernel := if platform == "wasm32-wasip1" { "wasip1" } else { "linux" } # List out the available commands [private] @@ -70,6 +76,7 @@ oci_name := "githedgehog/dataplane" oci_frr_prefix := "githedgehog/dpdk-sys/frr" oci_image_dataplane := oci_repo + "/" + oci_name + ":" + version oci_image_dataplane_debugger := oci_repo + "/" + oci_name + "/debugger:" + version +oci_image_dataplane_validator := oci_repo + "/" + oci_name + "/validator:" + version oci_image_frr_dataplane := oci_repo + "/" + oci_frr_prefix + ":" + version oci_image_frr_host := oci_repo + "/" + oci_frr_prefix + "-host:" + version @@ -88,6 +95,8 @@ build target="dataplane.tar" *args: nix build -f default.nix "${target}" \ --argstr profile '{{ profile }}' \ --argstr sanitize '{{ sanitize }}' \ + --argstr libc '{{ libc }}' \ + --argstr kernel '{{ kernel }}' \ --argstr features '{{ features }}' \ --argstr default-features '{{ default_features }}' \ --argstr instrumentation '{{ instrument }}' \ @@ -139,7 +148,7 @@ setup-roots *args: # Build the dataplane container image [script] -build-container target="dataplane" *args: (build (if target == "dataplane" { "dataplane.tar" } else { "containers." + target }) args) +build-container target="dataplane" *args: (build (if target == "dataplane" { "dataplane.tar" } else if target == "validator" { "workspace.validator" } else { "containers." + target }) args) {{ _just_debuggable_ }} declare -xr DOCKER_HOST="${DOCKER_HOST:-unix://{{docker_sock}}}" case "{{target}}" in @@ -165,6 +174,9 @@ build-container target="dataplane" *args: (build (if target == "dataplane" { "da docker tag "ghcr.io/githedgehog/dpdk-sys/frr-host:{{version}}" "{{oci_image_frr_host}}" echo "imported {{oci_image_frr_host}}" ;; + "validator") + echo "NOTE: validator image is wasm and not containerized" + ;; *) >&2 echo "{{target}}" not a valid container exit 99 @@ -192,18 +204,31 @@ push-container target="dataplane" *args: (build-container target args) && versio skopeo copy --src-daemon-host="${DOCKER_HOST}" {{ _skopeo_dest_insecure }} docker-daemon:{{oci_image_frr_host}} docker://{{oci_image_frr_host}} echo "Pushed {{ oci_image_frr_host }}" ;; + "validator") + if [ "{{platform}}" != "wasm32-wasip1" ]; then + >&2 echo "Pushing non wasm32-wasip1 validator images is not supported, set platform=wasm32-wasip1" + exit 1 + fi + oras push --annotation version="{{ version }}" "{{ oci_image_dataplane_validator }}" ./results/workspace.validator/bin/validator.wasm + echo "Pushed {{ oci_image_dataplane_validator }}" + ;; *) >&2 echo "{{target}}" not a valid container exit 99 esac +# Note: deliberately ignores all recipe parameters save version, debug_justfile, and oci_repo. # Pushes all release container images. -# Note: deliberately ignores all recipe parameters save version and debug_justfile. [script] push: {{ _just_debuggable_ }} - for container in dataplane frr.dataplane; do - nix-shell --run "just debug_justfile={{debug_justfile}} oci_repo={{oci_repo}} version={{version}} profile=release platform=x86-64-v3 sanitize= instrument=none push-container ${container}" + for container in dataplane frr.dataplane validator; do + if [ "${container}" = "validator" ]; + platform="wasm32-wasip1" + else + platform="x86-64-v3" + fi + nix-shell --run "just debug_justfile={{debug_justfile}} oci_repo={{oci_repo}} version={{version}} profile=release platform=${platform} sanitize= instrument=none push-container ${container}" done # Print names of container images to build or push