diff --git a/lib/opte-test-utils/src/dhcp.rs b/lib/opte-test-utils/src/dhcp.rs index 84ca5ce2..8b760382 100644 --- a/lib/opte-test-utils/src/dhcp.rs +++ b/lib/opte-test-utils/src/dhcp.rs @@ -53,7 +53,8 @@ pub fn packet_from_client_dhcpv4_message( let headers = (eth, ip, udp); let total_len = msg.buffer_len() + headers.packet_length(); - let mut pkt = MsgBlk::new_ethernet(total_len); + let mut pkt = + MsgBlk::new_ethernet(total_len).expect("infallible in std context"); pkt.emit_back(&headers).unwrap(); let dhcp_off = pkt.len(); pkt.resize(total_len).unwrap(); @@ -101,7 +102,8 @@ pub fn write_dhcpv6_packet( let headers = (eth, ip, udp); let total_len = msg.buffer_len() + headers.packet_length(); - let mut pkt = MsgBlk::new_ethernet(total_len); + let mut pkt = + MsgBlk::new_ethernet(total_len).expect("infallible in std context"); pkt.emit_back(&headers).unwrap(); let dhcp_off = pkt.len(); pkt.resize(total_len).unwrap(); diff --git a/lib/opte-test-utils/src/geneve_verify.rs b/lib/opte-test-utils/src/geneve_verify.rs index 65a72395..eedb76f5 100644 --- a/lib/opte-test-utils/src/geneve_verify.rs +++ b/lib/opte-test-utils/src/geneve_verify.rs @@ -58,13 +58,15 @@ pub fn parse_geneve_packet(bytes: &[u8]) -> Result { /// /// # Example /// ```no_run -/// let snoop_output = snoop_underlay.assert_packet("on underlay"); -/// let stdout = String::from_utf8_lossy(&snoop_output.stdout); +/// use opte_test_utils::geneve_verify; +/// use opte::api::MulticastUnderlay; +/// +/// let mcast_underlay = MulticastUnderlay::new("ff04::1".parse().unwrap()).unwrap(); /// geneve_verify::assert_geneve_packet( -/// &stdout, -/// vni, +/// "<...SnoopGuard output...>", +/// 77.try_into().unwrap(), /// mcast_underlay, -/// Replication::External, +/// oxide_vpc::api::Replication::External, /// ); /// ``` pub fn assert_geneve_packet( diff --git a/lib/opte-test-utils/src/icmp.rs b/lib/opte-test-utils/src/icmp.rs index e248b291..dbbc593b 100644 --- a/lib/opte-test-utils/src/icmp.rs +++ b/lib/opte-test-utils/src/icmp.rs @@ -147,27 +147,28 @@ pub fn gen_icmp_echo( let mut segments = vec![]; + // Note: all unwraps below are expected to be infallible in test/std contexts. match n_segments { 1 => { - return MsgBlk::new_ethernet_pkt((ð, &ip, &icmp_bytes)); + return MsgBlk::new_ethernet_pkt((ð, &ip, &icmp_bytes)).unwrap(); } 2 => { - segments.push(MsgBlk::new_ethernet_pkt(eth)); - segments.push(MsgBlk::new_pkt((&ip, &icmp_bytes))); + segments.push(MsgBlk::new_ethernet_pkt(eth).unwrap()); + segments.push(MsgBlk::new_pkt((&ip, &icmp_bytes)).unwrap()); } 3 => { - segments.push(MsgBlk::new_ethernet_pkt(eth)); - segments.push(MsgBlk::new_pkt(ip)); - segments.push(MsgBlk::new_pkt(&icmp_bytes)); + segments.push(MsgBlk::new_ethernet_pkt(eth).unwrap()); + segments.push(MsgBlk::new_pkt(ip).unwrap()); + segments.push(MsgBlk::new_pkt(&icmp_bytes).unwrap()); } 4 => { // Used to test pullup behaviour around longer mblks // which still have pkt bodies in guest memory. assert!(icmp_bytes.len() > 8); - segments.push(MsgBlk::new_ethernet_pkt(eth)); - segments.push(MsgBlk::new_pkt(ip)); - segments.push(MsgBlk::new_pkt(&icmp_bytes[..8])); - segments.push(MsgBlk::new_pkt(&icmp_bytes[8..])); + segments.push(MsgBlk::new_ethernet_pkt(eth).unwrap()); + segments.push(MsgBlk::new_pkt(ip).unwrap()); + segments.push(MsgBlk::new_pkt(&icmp_bytes[..8]).unwrap()); + segments.push(MsgBlk::new_pkt(&icmp_bytes[8..]).unwrap()); } _ => { panic!("only 1 2 or 3 segments allowed") @@ -261,27 +262,28 @@ pub fn gen_icmpv6_echo( let mut segments = vec![]; + // Note: all unwraps below are expected to be infallible in test/std contexts. match n_segments { 1 => { - return MsgBlk::new_ethernet_pkt((ð, &ip, &body_bytes)); + return MsgBlk::new_ethernet_pkt((ð, &ip, &body_bytes)).unwrap(); } 2 => { - segments.push(MsgBlk::new_ethernet_pkt(eth)); - segments.push(MsgBlk::new_pkt((&ip, &body_bytes))); + segments.push(MsgBlk::new_ethernet_pkt(eth).unwrap()); + segments.push(MsgBlk::new_pkt((&ip, &body_bytes)).unwrap()); } 3 => { - segments.push(MsgBlk::new_ethernet_pkt(eth)); - segments.push(MsgBlk::new_pkt(ip)); - segments.push(MsgBlk::new_pkt(&body_bytes)); + segments.push(MsgBlk::new_ethernet_pkt(eth).unwrap()); + segments.push(MsgBlk::new_pkt(ip).unwrap()); + segments.push(MsgBlk::new_pkt(&body_bytes).unwrap()); } 4 => { // Used to test pullup behaviour around longer mblks // which still have pkt bodies in guest memory. assert!(body_bytes.len() > 8); - segments.push(MsgBlk::new_ethernet_pkt(eth)); - segments.push(MsgBlk::new_pkt(ip)); - segments.push(MsgBlk::new_pkt(&body_bytes[..8])); - segments.push(MsgBlk::new_pkt(&body_bytes[8..])); + segments.push(MsgBlk::new_ethernet_pkt(eth).unwrap()); + segments.push(MsgBlk::new_pkt(ip).unwrap()); + segments.push(MsgBlk::new_pkt(&body_bytes[..8]).unwrap()); + segments.push(MsgBlk::new_pkt(&body_bytes[8..]).unwrap()); } _ => { panic!("only 1 2 or 3 segments allowed") @@ -325,7 +327,8 @@ pub fn generate_ndisc( let headers = (eth, ip); let total_len = req.buffer_len() + headers.packet_length(); - let mut pkt = MsgBlk::new_ethernet(total_len); + let mut pkt = + MsgBlk::new_ethernet(total_len).expect("infallible in std context"); pkt.emit_back(&headers).unwrap(); let ndisc_off = pkt.len(); pkt.resize(total_len).unwrap(); diff --git a/lib/opte-test-utils/src/lib.rs b/lib/opte-test-utils/src/lib.rs index e3c4da18..3926599e 100644 --- a/lib/opte-test-utils/src/lib.rs +++ b/lib/opte-test-utils/src/lib.rs @@ -109,7 +109,7 @@ macro_rules! expect_modified { ); #[allow(unused_assignments)] if let Ok(Modified(spec)) = $res { - $pkt = spec.apply($pkt); + $pkt = spec.apply($pkt).unwrap(); } }; } @@ -503,7 +503,8 @@ pub fn ulp_pkt< ulp: U, body: &[u8], ) -> MsgBlk { - let mut pkt = MsgBlk::new_ethernet_pkt((eth, ip, ulp, body)); + let mut pkt = MsgBlk::new_ethernet_pkt((eth, ip, ulp, body)) + .expect("infallible in std context"); let view = Packet::parse_outbound(pkt.iter_mut(), GenericUlp {}).unwrap(); let mut view = view.to_full_meta(); @@ -1070,7 +1071,8 @@ fn _encap( outer_ip, outer_udp, outer_geneve, - )); + )) + .expect("infallible in std context"); encap_pkt.append(inner_pkt); encap_pkt diff --git a/lib/opte/src/ddi/mblk.rs b/lib/opte/src/ddi/mblk.rs index e52742c2..c30ed9f4 100644 --- a/lib/opte/src/ddi/mblk.rs +++ b/lib/opte/src/ddi/mblk.rs @@ -2,7 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use crate::engine::packet::BufferState; use crate::engine::packet::Pullup; @@ -286,20 +286,17 @@ impl MsgBlk { /// `allocb(9F)` and `freeb(9F)`, which contains enough scaffolding /// to satisfy OPTE's use of the underlying `mblk_t` and `dblk_t` /// structures. - pub fn new(len: usize) -> Self { - let inner = NonNull::new(allocb(len)) - .expect("somehow failed to get an mblk..."); - - Self(inner) + pub fn new(len: usize) -> Result { + NonNull::new(allocb(len)).map(Self).ok_or(PktAllocError) } /// Allocates a new [`MsgBlk`] of size `buf.len()`, copying its /// contents. - pub fn copy(buf: impl AsRef<[u8]>) -> Self { - let mut out = Self::new(buf.as_ref().len()); + pub fn copy(buf: impl AsRef<[u8]>) -> Result { + let mut out = Self::new(buf.as_ref().len())?; // Unwrap safety -- just allocated length of input buffer. out.write_bytes_back(buf).unwrap(); - out + Ok(out) } /// Copy the first `n` bytes of this packet into a new `mblk_t`, @@ -339,7 +336,7 @@ impl MsgBlk { // in our userland mblk abstraction. // Do the segmentation right, but otherwise it's fully cloned. let to_ensure = n.map(|v| v.get()).unwrap_or(totlen); - let mut top_mblk = MsgBlk::new(to_ensure); + let mut top_mblk = MsgBlk::new(to_ensure)?; let mut still_to_write = to_ensure; for chunk in self.iter() { @@ -355,7 +352,7 @@ impl MsgBlk { left_in_chunk -= to_take; if left_in_chunk != 0 { - top_mblk.append(MsgBlk::copy(&chunk[to_take..])); + top_mblk.append(MsgBlk::copy(&chunk[to_take..])?); } } @@ -365,10 +362,12 @@ impl MsgBlk { } /// Creates a new [`MsgBlk`] using a given set of packet headers. - pub fn new_pkt(emit: impl Emit + EmitDoesNotRelyOnBufContents) -> Self { - let mut pkt = Self::new(emit.packet_length()); + pub fn new_pkt( + emit: impl Emit + EmitDoesNotRelyOnBufContents, + ) -> Result { + let mut pkt = Self::new(emit.packet_length())?; pkt.emit_back(emit).unwrap(); - pkt + Ok(pkt) } /// Returns the number of bytes available for writing ahead of the @@ -408,7 +407,7 @@ impl MsgBlk { /// bytes with 2B of headroom/alignment. /// /// This sets up 4B alignment on all post-ethernet headers. - pub fn new_ethernet(len: usize) -> Self { + pub fn new_ethernet(len: usize) -> Result { Self::new_with_headroom(2, len) } @@ -418,10 +417,10 @@ impl MsgBlk { /// This sets up 4B alignment on all post-ethernet headers. pub fn new_ethernet_pkt( emit: impl Emit + EmitDoesNotRelyOnBufContents, - ) -> Self { - let mut pkt = Self::new_ethernet(emit.packet_length()); + ) -> Result { + let mut pkt = Self::new_ethernet(emit.packet_length())?; pkt.emit_back(emit).unwrap(); - pkt + Ok(pkt) } /// Return the number of initialised bytes in this `MsgBlk` over @@ -482,8 +481,11 @@ impl MsgBlk { /// /// The read/write pointer is set to have `head_len` bytes of /// headroom and `body_len` bytes of capacity at the back. - pub fn new_with_headroom(head_len: usize, body_len: usize) -> Self { - let out = Self::new(head_len + body_len); + pub fn new_with_headroom( + head_len: usize, + body_len: usize, + ) -> Result { + let out = Self::new(head_len + body_len)?; // SAFETY: alloc is contiguous and always larger than head_len. let mut_out = out.0.as_ptr(); @@ -492,7 +494,7 @@ impl MsgBlk { (*mut_out).b_wptr = (*mut_out).b_rptr; } - out + Ok(out) } /// Provides a slice of length `n_bytes` at the back of an [`MsgBlk`] @@ -1023,11 +1025,11 @@ impl MsgBlkIterMut<'_> { } impl Pullup for MsgBlkIterMut<'_> { - fn pullup(&self, prepend: Option<&[u8]>) -> MsgBlk { + fn pullup(&self, prepend: Option<&[u8]>) -> Result { let prepend = prepend.unwrap_or_default(); let bytes_in_self = BufferState::len(self); let needed_alloc = prepend.len() + bytes_in_self; - let mut new_seg = MsgBlk::new(needed_alloc); + let mut new_seg = MsgBlk::new(needed_alloc)?; new_seg .write_bytes_back(prepend) @@ -1077,7 +1079,19 @@ impl Pullup for MsgBlkIterMut<'_> { } } - new_seg + Ok(new_seg) + } +} + +/// A new [`MsgBlk`] could not be allocated. +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)] +pub struct PktAllocError; + +impl core::error::Error for PktAllocError {} + +impl core::fmt::Display for PktAllocError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("failed to allocate an mblk_t") } } @@ -1118,6 +1132,12 @@ impl core::fmt::Display for PktPullupError { } } +impl From for PktPullupError { + fn from(_val: PktAllocError) -> Self { + Self::AllocFailed + } +} + /// Counts the number of segments in an `mblk_t` from `head`, linked /// via `b_cont`. unsafe fn count_mblk_chain(mut head: Option>) -> usize { @@ -1397,7 +1417,7 @@ mod test { #[test] fn zero_byte_packet() { - let mut pkt = MsgBlk::new(0); + let mut pkt = MsgBlk::new(0).unwrap(); assert_eq!(pkt.len(), 0); assert_eq!(pkt.seg_len(), 1); assert_eq!(pkt.tail_capacity(), 16); @@ -1413,7 +1433,7 @@ mod test { _ => panic!("expected failure, accidentally succeeded at parsing"), } - let pkt2 = MsgBlk::copy([]); + let pkt2 = MsgBlk::copy([]).unwrap(); assert_eq!(pkt2.len(), 0); assert_eq!(pkt2.seg_len(), 1); assert_eq!(pkt2.tail_capacity(), 16); @@ -1470,9 +1490,9 @@ mod test { #[test] fn truncate() { - let mut p1 = MsgBlk::copy([0, 1, 2, 3]); - p1.append(MsgBlk::copy([4, 5, 6, 7])); - p1.append(MsgBlk::copy([8, 9, 10, 11])); + let mut p1 = MsgBlk::copy([0, 1, 2, 3]).unwrap(); + p1.append(MsgBlk::copy([4, 5, 6, 7]).unwrap()); + p1.append(MsgBlk::copy([8, 9, 10, 11]).unwrap()); assert_eq!(p1.seg_len(), 3); assert_eq!(p1.byte_len(), 12); @@ -1491,7 +1511,7 @@ mod test { // Verify uninitialized packet. #[test] fn uninitialized_packet() { - let pkt = MsgBlk::new(200); + let pkt = MsgBlk::new(200).unwrap(); assert_eq!(pkt.len(), 0); assert_eq!(pkt.seg_len(), 1); assert_eq!(pkt.tail_capacity(), 200); @@ -1499,7 +1519,7 @@ mod test { #[test] fn expand_and_shrink() { - let mut seg = MsgBlk::new(18); + let mut seg = MsgBlk::new(18).unwrap(); assert_eq!(seg.len(), 0); seg.resize(18).unwrap(); assert_eq!(seg.len(), 18); @@ -1515,7 +1535,7 @@ mod test { #[test] fn prefix_len() { - let mut seg = MsgBlk::new(18); + let mut seg = MsgBlk::new(18).unwrap(); assert_eq!(seg.head_capacity(), 0); seg.resize(18).unwrap(); assert_eq!(seg.head_capacity(), 0); diff --git a/lib/opte/src/engine/arp.rs b/lib/opte/src/engine/arp.rs index 9107e0c5..42327a4e 100644 --- a/lib/opte/src/engine/arp.rs +++ b/lib/opte/src/engine/arp.rs @@ -2,10 +2,11 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company //! ARP headers and data. +use super::HdlPktError; use super::ether::Ethernet; use crate::ddi::mblk::MsgBlk; use core::fmt; @@ -74,7 +75,7 @@ pub fn gen_arp_reply( spa: Ipv4Addr, tha: MacAddr, tpa: Ipv4Addr, -) -> MsgBlk { +) -> Result { MsgBlk::new_ethernet_pkt(( Ethernet { destination: tha, source: sha, ethertype: Ethertype::ARP }, ArpEthIpv4 { @@ -86,6 +87,7 @@ pub fn gen_arp_reply( ..Default::default() }, )) + .map_err(|_| HdlPktError("could not allocate new mblk_t for ARP reply")) } /// An ARP packet containing Ethernet (MAC) to IPv4 address mappings. diff --git a/lib/opte/src/engine/dhcp.rs b/lib/opte/src/engine/dhcp.rs index 3a80ad0c..f83b92e1 100644 --- a/lib/opte/src/engine/dhcp.rs +++ b/lib/opte/src/engine/dhcp.rs @@ -2,7 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company //! DHCP headers, data, and actions. @@ -479,7 +479,7 @@ impl HairpinAction for DhcpAction { } fn gen_packet(&self, meta: &MblkPacketData) -> GenPacketResult { - let body = meta.copy_remaining(); + let body = meta.copy_remaining()?; let client_pkt = DhcpPacket::new_checked(&body)?; let client_dhcp = DhcpRepr::parse(&client_pkt)?; let mt = MessageType::from(self.reply_type); @@ -599,7 +599,7 @@ impl HairpinAction for DhcpAction { let ingot_layers = (ð, &ip, &udp); let total_sz = ingot_layers.packet_length() + reply_len; - let mut pkt = MsgBlk::new_ethernet(total_sz); + let mut pkt = MsgBlk::new_ethernet(total_sz)?; pkt.emit_back(ingot_layers) .expect("MsgBlk should have enough bytes by construction"); let l = pkt.len(); diff --git a/lib/opte/src/engine/dhcpv6/protocol.rs b/lib/opte/src/engine/dhcpv6/protocol.rs index b696c12c..d47ff3bf 100644 --- a/lib/opte/src/engine/dhcpv6/protocol.rs +++ b/lib/opte/src/engine/dhcpv6/protocol.rs @@ -2,7 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company //! Implementation of the main message types for DHCPv6. @@ -643,7 +643,7 @@ fn generate_packet<'a>( let ingot_layers = (ð, &ip, &udp); let total_sz = ingot_layers.packet_length() + msg.buffer_len(); - let mut pkt = MsgBlk::new_ethernet(total_sz); + let mut pkt = MsgBlk::new_ethernet(total_sz)?; pkt.emit_back(ingot_layers) .expect("MsgBlk should have enough bytes by construction"); let l = pkt.len(); @@ -692,7 +692,7 @@ impl HairpinAction for Dhcpv6Action { // here and reply accordingly. So the `Dhcpv6Action` is really a full // server, to the extent we emulate one. fn gen_packet(&self, meta: &MblkPacketData) -> GenPacketResult { - let body = meta.copy_remaining(); + let body = meta.copy_remaining()?; if let Some(client_msg) = Message::from_bytes(&body) { if let Some(reply) = process_client_message(self, meta, &client_msg) { @@ -747,7 +747,7 @@ mod test { #[test] fn test_predicates_match_snooped_solicit_message() { - let mut pkt = MsgBlk::copy(test_data::TEST_SOLICIT_PACKET); + let mut pkt = MsgBlk::copy(test_data::TEST_SOLICIT_PACKET).unwrap(); let pkt = Packet::parse_outbound(pkt.iter_mut(), GenericUlp {}) .unwrap() .to_full_meta(); diff --git a/lib/opte/src/engine/icmp/v4.rs b/lib/opte/src/engine/icmp/v4.rs index 93aa1e52..31afb09a 100644 --- a/lib/opte/src/engine/icmp/v4.rs +++ b/lib/opte/src/engine/icmp/v4.rs @@ -2,7 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company //! ICMPv4 headers and processing. @@ -85,7 +85,7 @@ impl HairpinAction for IcmpEchoReply { let mut csum = match icmp.checksum() { 0 => { let mut csum = OpteCsum::new(); - csum.add_bytes(meta.body()); + csum.add_bytes(meta.body()?); csum.add_bytes(icmp.rest_of_hdr_ref()); csum } @@ -102,7 +102,7 @@ impl HairpinAction for IcmpEchoReply { csum.add_bytes(&[ty.0, code]); // Build the reply in place, and send it out. - let body_len: usize = meta.body().len(); + let body_len: usize = meta.body()?.len(); let icmp = IcmpV4 { ty, @@ -127,14 +127,9 @@ impl HairpinAction for IcmpEchoReply { ethertype: Ethertype::IPV4, }; - let total_len = body_len + (ð, &ip4, &icmp).packet_length(); - - let mut pkt_out = MsgBlk::new_ethernet(total_len); - pkt_out - .emit_back((ð, &ip4, &icmp, meta.body())) - .expect("Allocated space for pkt headers and body"); - - Ok(AllowOrDeny::Allow(pkt_out)) + MsgBlk::new_ethernet_pkt((ð, &ip4, &icmp, meta.body()?)) + .map(AllowOrDeny::Allow) + .map_err(Into::into) } } diff --git a/lib/opte/src/engine/icmp/v6.rs b/lib/opte/src/engine/icmp/v6.rs index 5562c19c..d36151e1 100644 --- a/lib/opte/src/engine/icmp/v6.rs +++ b/lib/opte/src/engine/icmp/v6.rs @@ -2,7 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company //! ICMPv6 headers and processing. @@ -145,7 +145,7 @@ impl HairpinAction for Icmpv6EchoReply { 0 => { let mut csum = OpteCsum::new(); - csum.add_bytes(meta.body()); + csum.add_bytes(meta.body()?); csum.add_bytes(icmp6.rest_of_hdr_ref()); @@ -164,7 +164,7 @@ impl HairpinAction for Icmpv6EchoReply { csum.add_bytes(&[ty.0, code]); // Build the reply in place, and send it out. - let body_len: usize = meta.body().len(); + let body_len: usize = meta.body()?.len(); let icmp = IcmpV6 { ty, @@ -189,13 +189,9 @@ impl HairpinAction for Icmpv6EchoReply { ethertype: Ethertype::IPV6, }; - let total_len = body_len + (ð, &ip6, &icmp).packet_length(); - let mut pkt_out = MsgBlk::new_ethernet(total_len); - pkt_out - .emit_back((ð, &ip6, &icmp, meta.body())) - .expect("Allocated space for pkt headers and body"); - - Ok(AllowOrDeny::Allow(pkt_out)) + MsgBlk::new_ethernet_pkt((ð, &ip6, &icmp, meta.body()?)) + .map(AllowOrDeny::Allow) + .map_err(Into::into) } } @@ -262,7 +258,7 @@ impl HairpinAction for RouterAdvertisement { // `Icmpv6Packet` requires the ICMPv6 header and not just the message payload. // Given we successfully got the ICMPv6 metadata, rewinding here is fine. let mut body = icmp6.emit_vec(); - meta.append_remaining(&mut body); + meta.append_remaining(&mut body)?; let src_pkt = Icmpv6Packet::new_checked(&body)?; let mut csum = Csum::ignored(); @@ -365,9 +361,9 @@ impl HairpinAction for RouterAdvertisement { ethertype: Ethertype::IPV6, }; - Ok(AllowOrDeny::Allow(MsgBlk::new_ethernet_pkt(( - ð, &ip6, &ulp_body, - )))) + MsgBlk::new_ethernet_pkt((ð, &ip6, &ulp_body)) + .map(AllowOrDeny::Allow) + .map_err(Into::into) } } @@ -574,7 +570,7 @@ impl HairpinAction for NeighborAdvertisement { // `Icmpv6Packet` requires the ICMPv6 header and not just the message payload. // Given we successfully got the ICMPv6 metadata, rewinding here is fine. let mut body = icmp6.emit_vec(); - meta.append_remaining(&mut body); + meta.append_remaining(&mut body)?; // Validate the ICMPv6 packet is actually a Neighbor Solicitation, and // that its data is appopriate. @@ -625,9 +621,9 @@ impl HairpinAction for NeighborAdvertisement { ethertype: Ethertype::IPV6, }; - Ok(AllowOrDeny::Allow(MsgBlk::new_ethernet_pkt(( - ð, &ip6, &ulp_body, - )))) + MsgBlk::new_ethernet_pkt((ð, &ip6, &ulp_body)) + .map(AllowOrDeny::Allow) + .map_err(Into::into) } } diff --git a/lib/opte/src/engine/layer.rs b/lib/opte/src/engine/layer.rs index 643da97e..731e99bd 100644 --- a/lib/opte/src/engine/layer.rs +++ b/lib/opte/src/engine/layer.rs @@ -39,6 +39,7 @@ use crate::ddi::kstat::KStatProvider; use crate::ddi::kstat::KStatU64; use crate::ddi::mblk::MsgBlk; use crate::ddi::time::Moment; +use crate::engine::rule::GenErr; use alloc::ffi::CString; use alloc::string::String; use alloc::string::ToString; @@ -443,6 +444,14 @@ struct LayerStats { /// either explicitly or due to the default action. in_deny: KStatU64, + /// The number of inbound packets dropped by this layer due to an + /// allocation failure. + in_err_alloc: KStatU64, + + /// The number of inbound packets dropped by this layer due to an + /// error reported by the action. + in_err_action: KStatU64, + /// The current number of inbound rules. in_rules: KStatU64, @@ -468,6 +477,14 @@ struct LayerStats { /// either explicitly or due to the default action. out_deny: KStatU64, + /// The number of outbound packets dropped by this layer due to an + /// allocation failure. + out_err_alloc: KStatU64, + + /// The number of outbound packets dropped by this layer due to an + /// error reported by the action. + out_err_action: KStatU64, + /// The current number of outbound rules. out_rules: KStatU64, @@ -797,6 +814,33 @@ impl Layer { Out => self.process_out(ectx, pkt, xforms, ameta), In => self.process_in(ectx, pkt, xforms, ameta), }; + if let Err(e) = &res { + (match (dir, e) { + (In, LayerError::FlowTableFull { .. }) => { + &self.stats.vals.in_lft_full + } + (Out, LayerError::FlowTableFull { .. }) => { + &self.stats.vals.out_lft_full + } + ( + In, + LayerError::BodyTransform( + BodyTransformError::PayloadPullup, + ) + | LayerError::GenPacket(GenErr::Allocation), + ) => &self.stats.vals.in_err_alloc, + ( + Out, + LayerError::BodyTransform( + BodyTransformError::PayloadPullup, + ) + | LayerError::GenPacket(GenErr::Allocation), + ) => &self.stats.vals.out_err_alloc, + (In, _) => &self.stats.vals.in_err_action, + (Out, _) => &self.stats.vals.out_err_action, + }) + .incr(1); + } self.layer_process_return_probe(dir, &flow_before, pkt.flow(), &res); res } @@ -896,7 +940,6 @@ impl Layer { Action::StatefulAllow => { if self.ft.count == self.ft.limit.get() { - self.stats.vals.in_lft_full += 1; return Err(LayerError::FlowTableFull { layer: self.name, dir: In, @@ -1006,7 +1049,6 @@ impl Layer { // that it gets an FT entry. If there are no slots // available, then we must fail until one opens up. if self.ft.count == self.ft.limit.get() { - self.stats.vals.in_lft_full += 1; return Err(LayerError::FlowTableFull { layer: self.name, dir: In, @@ -1182,7 +1224,6 @@ impl Layer { Action::StatefulAllow => { if self.ft.count == self.ft.limit.get() { - self.stats.vals.out_lft_full += 1; return Err(LayerError::FlowTableFull { layer: self.name, dir: Out, @@ -1294,7 +1335,6 @@ impl Layer { // that it gets an FT entry. If there are no slots // available, then we must fail until one opens up. if self.ft.count == self.ft.limit.get() { - self.stats.vals.out_lft_full += 1; return Err(LayerError::FlowTableFull { layer: self.name, dir: Out, @@ -1844,7 +1884,8 @@ mod test { window_size: 64240, ..Default::default() }, - )); + )) + .unwrap(); let pmeta = Packet::parse_outbound(test_pkt.iter_mut(), GenericUlp {}) .unwrap() diff --git a/lib/opte/src/engine/nat.rs b/lib/opte/src/engine/nat.rs index 99e26267..15033f9e 100644 --- a/lib/opte/src/engine/nat.rs +++ b/lib/opte/src/engine/nat.rs @@ -501,7 +501,8 @@ mod test { ethertype: Ethertype::IPV4, }; - let mut pkt_m = MsgBlk::new_ethernet_pkt((ð, &ip4, &tcp, &body)); + let mut pkt_m = + MsgBlk::new_ethernet_pkt((ð, &ip4, &tcp, &body)).unwrap(); let mut pkt = Packet::parse_outbound(pkt_m.iter_mut(), GenericUlp {}) .unwrap() .to_full_meta(); @@ -569,7 +570,8 @@ mod test { ethertype: Ethertype::IPV4, }; - let mut pkt_m = MsgBlk::new_ethernet_pkt((ð, &ip4, &tcp, &body)); + let mut pkt_m = + MsgBlk::new_ethernet_pkt((ð, &ip4, &tcp, &body)).unwrap(); let mut pkt = Packet::parse_inbound(pkt_m.iter_mut(), GenericUlp {}) .unwrap() .to_full_meta(); diff --git a/lib/opte/src/engine/packet.rs b/lib/opte/src/engine/packet.rs index 25ab6af3..f332f401 100644 --- a/lib/opte/src/engine/packet.rs +++ b/lib/opte/src/engine/packet.rs @@ -2,7 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company //! Types for creating, reading, and writing network packets. @@ -45,6 +45,8 @@ use crate::d_error::DError; use crate::ddi::mblk::MsgBlk; use crate::ddi::mblk::MsgBlkIterMut; use crate::ddi::mblk::MsgBlkNode; +use crate::ddi::mblk::PktAllocError; +use crate::ddi::mblk::PktPullupError; use crate::engine::geneve::GeneveMeta; use alloc::boxed::Box; use alloc::string::String; @@ -127,6 +129,7 @@ dyn_clone::clone_trait_object!(BodyTransform); #[derive(Debug)] pub enum BodyTransformError { + PayloadPullup, NoPayload, ParseFailure(String), Todo(String), @@ -140,6 +143,12 @@ impl From for BodyTransformError { } } +impl From for BodyTransformError { + fn from(_: PktPullupError) -> Self { + Self::PayloadPullup + } +} + #[derive(Clone, Copy, Debug)] pub enum SegAdjustError { /// Attempt to place the end of the writable/readable area of the @@ -351,13 +360,13 @@ impl PktBodyWalker { impl PktBodyWalker { #[inline(always)] - fn prepare(&self) { + fn prepare(&self) -> Result<(), PktPullupError> { let BodySegState::NeedsPullup = self.state.clone().into_inner() else { - return; + return Ok(()); }; let prepend_slice = self.last_chunk.as_ref().map(|v| &v[..]); - let mblk = self.remainder.pullup(prepend_slice); + let mblk = self.remainder.pullup(prepend_slice)?; let mblk_ptr = mblk.unwrap_mblk(); @@ -370,12 +379,13 @@ impl PktBodyWalker { ) .expect("invariant violated: tried to double-prepare mblk"); self.state.set(BodySegState::PulledUp); + Ok(()) } - fn body(&self) -> &[u8] { - self.prepare(); + fn body(&self) -> Result<&[u8], PktPullupError> { + self.prepare()?; - match self.state.clone().into_inner() { + Ok(match self.state.clone().into_inner() { BodySegState::NoPullup => { self.last_chunk.as_ref().map(|v| &v[..]).unwrap_or_default() } @@ -395,16 +405,16 @@ impl PktBodyWalker { core::mem::transmute::<&[u8], &[u8]>(&mblk_ref[..]) } } - } + }) } - fn body_mut(&mut self) -> &mut [u8] + fn body_mut(&mut self) -> Result<&mut [u8], PktPullupError> where T::Chunk: ByteSliceMut, { - self.prepare(); + self.prepare()?; - match self.state.clone().into_inner() { + Ok(match self.state.clone().into_inner() { BodySegState::NoPullup => { self.last_chunk.as_mut().map(|v| &mut v[..]).unwrap_or_default() } @@ -429,7 +439,7 @@ impl PktBodyWalker { ) } } - } + }) } fn extract_mblk(&mut self) -> Option { @@ -590,7 +600,7 @@ impl PacketData { matches!(self.inner_ulp(), Some(Ulp::Tcp(_))) } - pub fn prep_body(&mut self) + pub fn prep_body(&mut self) -> Result<(), PktPullupError> where T::Chunk: ByteSliceMut, T: Pullup, @@ -598,7 +608,7 @@ impl PacketData { self.body.prepare() } - pub fn body(&self) -> &[u8] + pub fn body(&self) -> Result<&[u8], PktPullupError> where T::Chunk: ByteSliceMut, T: Pullup, @@ -606,25 +616,28 @@ impl PacketData { self.body.body() } - pub fn copy_remaining(&self) -> Vec + pub fn copy_remaining(&self) -> Result, PktPullupError> where T::Chunk: ByteSliceMut, T: Pullup, { - let base = self.body(); - base.to_vec() + self.body().map(|v| v.to_vec()) } - pub fn append_remaining(&self, buf: &mut Vec) + pub fn append_remaining( + &self, + buf: &mut Vec, + ) -> Result<(), PktPullupError> where T::Chunk: ByteSliceMut, T: Pullup, { - let base = self.body(); + let base = self.body()?; buf.extend_from_slice(base); + Ok(()) } - pub fn body_mut(&mut self) -> &mut [u8] + pub fn body_mut(&mut self) -> Result<&mut [u8], PktPullupError> where T::Chunk: ByteSliceMut, T: Pullup, @@ -1125,7 +1138,7 @@ impl Packet> { // this does the job as nothing that needs top performance // should make use of body transformations. self.state.body_modified = true; - self.state.meta.body.prepare(); + self.state.meta.body.prepare()?; let ulp = self.state.meta.inner_ulp().map(|v| v.repr()); @@ -1144,7 +1157,7 @@ impl Packet> { T::Chunk: ByteSliceMut, T: Pullup, { - let out = self.state.meta.body(); + let out = self.state.meta.body().ok()?; if out.is_empty() { None } else { Some(out) } } @@ -1154,7 +1167,7 @@ impl Packet> { T::Chunk: ByteSliceMut, T: Pullup, { - let out = self.state.meta.body_mut(); + let out = self.state.meta.body_mut().ok()?; if out.is_empty() { None } else { Some(out) } } @@ -1467,7 +1480,7 @@ pub trait BufferState { pub trait Pullup { /// Pulls all remaining segments of a packet into a new /// `Self` containing a single buffer. - fn pullup(&self, prepend: Option<&[u8]>) -> MsgBlk; + fn pullup(&self, prepend: Option<&[u8]>) -> Result; } /// A set of headers to be emitted at the head of a packet, and @@ -1541,8 +1554,7 @@ impl EmitSpec { /// Perform final structural transformations to a packet (removal of /// existing headers, and copying in new/replacement headers). #[inline] - #[must_use] - pub fn apply(self, mut pkt: MsgBlk) -> MsgBlk { + pub fn apply(self, mut pkt: MsgBlk) -> Result { // Rewind { let mut slots = heapless::Vec::<&mut MsgBlkNode, 6>::new(); @@ -1599,7 +1611,7 @@ impl EmitSpec { let needed_alloc = needed_push; let mut prepend = if needed_alloc > 0 { - let mut new_mblk = MsgBlk::new_ethernet(needed_alloc); + let mut new_mblk = MsgBlk::new_ethernet(needed_alloc)?; new_mblk.pop_all(); Some(new_mblk) } else { @@ -1671,15 +1683,15 @@ impl EmitSpec { target.emit_front(outer_eth).unwrap(); } - if let Some(mut prepend) = prepend { + Ok(if let Some(mut prepend) = prepend { pkt.copy_offload_info_to(&mut prepend); prepend.append(pkt); prepend } else { pkt - } + }) } - PushSpec::NoOp => pkt, + PushSpec::NoOp => Ok(pkt), } } @@ -1909,7 +1921,7 @@ mod test { ethertype: Ethertype::IPV4, }; - MsgBlk::new_ethernet_pkt((eth, ip4, tcp, body)) + MsgBlk::new_ethernet_pkt((eth, ip4, tcp, body)).unwrap() } #[test] @@ -1943,7 +1955,8 @@ mod test { destination: DST_MAC, source: SRC_MAC, ethertype: Ethertype::IPV4, - }); + }) + .unwrap(); let tcp = Tcp { source: 3839, @@ -1961,7 +1974,7 @@ mod test { ..Default::default() }; - let mp2 = MsgBlk::new_pkt((ip4, tcp)); + let mp2 = MsgBlk::new_pkt((ip4, tcp)).unwrap(); mp1.append(mp2); @@ -1993,8 +2006,8 @@ mod test { fn straddled_tcp() { let base = tcp_pkt(&[]); - let mut st1 = MsgBlk::copy(&base[..42]); - let st2 = MsgBlk::copy(&base[42..]); + let mut st1 = MsgBlk::copy(&base[..42]).unwrap(); + let st2 = MsgBlk::copy(&base[42..]).unwrap(); st1.append(st2); @@ -2054,7 +2067,8 @@ mod test { }; let mut pkt = - MsgBlk::new_ethernet_pkt((eth, ip6, ext_hdrs, tcp)); + MsgBlk::new_ethernet_pkt((eth, ip6, ext_hdrs, tcp)) + .unwrap(); let pkt = Packet::parse_outbound(pkt.iter_mut(), GenericUlp {}) .unwrap() .to_full_meta(); @@ -2097,7 +2111,7 @@ mod test { // Note that we do NOT update any of the packet headers themselves // as this padding process should be transparent to the upper // layers. - let mut padding_seg = MsgBlk::new(padding_len); + let mut padding_seg = MsgBlk::new(padding_len).unwrap(); padding_seg.resize(padding_len).unwrap(); pkt.append(padding_seg); @@ -2148,14 +2162,15 @@ mod test { let pkt_sz = eth.packet_length() + ip6.packet_length() + usize::from(ip6.payload_len); - let mut pkt = MsgBlk::new_ethernet_pkt((eth, ip6, udp, &body[..])); + let mut pkt = + MsgBlk::new_ethernet_pkt((eth, ip6, udp, &body[..])).unwrap(); assert_eq!(pkt.len(), pkt_sz); // Tack on a new segment filled zero padding at // the end that's not part of the payload as indicated // by the packet headers. let padding_len = 8; - let mut padding_seg = MsgBlk::new(padding_len); + let mut padding_seg = MsgBlk::new(padding_len).unwrap(); padding_seg.resize(padding_len).unwrap(); pkt.append(padding_seg); assert_eq!(pkt.byte_len(), pkt_sz + padding_len); diff --git a/lib/opte/src/engine/predicate.rs b/lib/opte/src/engine/predicate.rs index c23c44a3..2e7e883d 100644 --- a/lib/opte/src/engine/predicate.rs +++ b/lib/opte/src/engine/predicate.rs @@ -701,7 +701,9 @@ impl DataPredicate { Self::Not(pred) => !pred.is_match(meta), Self::DhcpMsgType(mt) => { - let bytes = meta.body(); + let Ok(bytes) = meta.body() else { + return false; + }; let pkt = match DhcpPacket::new_checked(&bytes) { Ok(v) => v, @@ -727,7 +729,7 @@ impl DataPredicate { } Self::Dhcpv6MsgType(mt) => { - let body = meta.body(); + let Ok(body) = meta.body() else { return false }; if body.is_empty() { super::err!( "Failed to read DHCPv6 message type from packet" diff --git a/lib/opte/src/engine/rule.rs b/lib/opte/src/engine/rule.rs index 9cc4f9c0..b211eb25 100644 --- a/lib/opte/src/engine/rule.rs +++ b/lib/opte/src/engine/rule.rs @@ -40,6 +40,8 @@ use super::port::meta::ActionMeta; use super::predicate::DataPredicate; use super::predicate::Predicate; use crate::ddi::mblk::MsgBlk; +use crate::ddi::mblk::PktAllocError; +use crate::ddi::mblk::PktPullupError; use alloc::boxed::Box; use alloc::ffi::CString; use alloc::string::String; @@ -499,7 +501,11 @@ pub enum CompiledEncap { impl CompiledEncap { #[inline] - pub fn prepend(&self, mut pkt: MsgBlk, ulp_len: usize) -> MsgBlk { + pub fn prepend( + &self, + mut pkt: MsgBlk, + ulp_len: usize, + ) -> Result { let Self::Push { bytes, l3_len_offset, @@ -509,12 +515,12 @@ impl CompiledEncap { .. } = self else { - return pkt; + return Ok(pkt); }; let mut prepend = if pkt.ref_count() > 1 || pkt.head_capacity() < bytes.len() { - let mut pkt = MsgBlk::new_ethernet(bytes.len()); + let mut pkt = MsgBlk::new_ethernet(bytes.len())?; pkt.pop_all(); Some(pkt) } else { @@ -547,13 +553,13 @@ impl CompiledEncap { *l4_len_slot = u16::try_from(l4_len).unwrap_or(u16::MAX).to_be_bytes(); - if let Some(mut prepend) = prepend { + Ok(if let Some(mut prepend) = prepend { pkt.copy_offload_info_to(&mut prepend); prepend.append(pkt); prepend } else { pkt - } + }) } } @@ -813,6 +819,8 @@ pub trait MetaAction: Display + Send + Sync { #[derive(Debug)] pub enum GenErr { + Allocation, + BodyAccess, Malformed, MissingMeta, Unexpected(String), @@ -824,6 +832,18 @@ impl From for GenErr { } } +impl From for GenErr { + fn from(_: PktAllocError) -> Self { + Self::Allocation + } +} + +impl From for GenErr { + fn from(_: PktPullupError) -> Self { + Self::BodyAccess + } +} + pub type GenPacketResult = ActionResult; /// An error while generating a [`BodyTransform`]. @@ -1226,7 +1246,7 @@ fn rule_matching() { let eth = Ethernet { ethertype: Ethertype::IPV4, ..Default::default() }; - let mut pkt_m = MsgBlk::new_ethernet_pkt((ð, &ip4, &tcp)); + let mut pkt_m = MsgBlk::new_ethernet_pkt((ð, &ip4, &tcp)).unwrap(); let mut pkt = Packet::parse_outbound(pkt_m.iter_mut(), GenericUlp {}) .unwrap() .to_full_meta(); diff --git a/lib/opte/src/engine/snat.rs b/lib/opte/src/engine/snat.rs index 1765abd7..f427fdba 100644 --- a/lib/opte/src/engine/snat.rs +++ b/lib/opte/src/engine/snat.rs @@ -555,7 +555,8 @@ mod test { ethertype: Ethertype::IPV4, }; - let mut pkt_m = MsgBlk::new_ethernet_pkt((ð, &ip4, &tcp, &body)); + let mut pkt_m = + MsgBlk::new_ethernet_pkt((ð, &ip4, &tcp, &body)).unwrap(); let mut pkt = Packet::parse_outbound(pkt_m.iter_mut(), GenericUlp {}) .unwrap() .to_full_meta(); @@ -622,7 +623,8 @@ mod test { ethertype: Ethertype::IPV4, }; - let mut pkt_m = MsgBlk::new_ethernet_pkt((ð, &ip4, &tcp, &body)); + let mut pkt_m = + MsgBlk::new_ethernet_pkt((ð, &ip4, &tcp, &body)).unwrap(); let mut pkt = Packet::parse_inbound(pkt_m.iter_mut(), GenericUlp {}) .unwrap() .to_full_meta(); diff --git a/lib/oxide-vpc/src/engine/mod.rs b/lib/oxide-vpc/src/engine/mod.rs index c87044fe..f0215eed 100644 --- a/lib/oxide-vpc/src/engine/mod.rs +++ b/lib/oxide-vpc/src/engine/mod.rs @@ -93,8 +93,8 @@ impl VpcNetwork { if is_arp_req_for_tpa(gw_ip, &arp) { let gw_mac = self.cfg.gateway_mac; - let hp = arp::gen_arp_reply(gw_mac, gw_ip, arp.sha(), arp.spa()); - return Ok(HdlPktAction::Hairpin(hp)); + return arp::gen_arp_reply(gw_mac, gw_ip, arp.sha(), arp.spa()) + .map(HdlPktAction::Hairpin); } Ok(HdlPktAction::Deny) diff --git a/lib/oxide-vpc/tests/firewall_tests.rs b/lib/oxide-vpc/tests/firewall_tests.rs index fb88df99..db10f5f0 100644 --- a/lib/oxide-vpc/tests/firewall_tests.rs +++ b/lib/oxide-vpc/tests/firewall_tests.rs @@ -99,7 +99,7 @@ fn firewall_replace_rules() { let mut pkt3_m = pkt2_m; let pkt3_bytes = pkt3_m.copy_all(); - let mut pkt3_copy_m = MsgBlk::copy(pkt3_bytes); + let mut pkt3_copy_m = MsgBlk::copy(pkt3_bytes).unwrap(); let pkt3 = parse_inbound(&mut pkt3_m, VpcParser {}).unwrap(); let pkt3_copy = parse_inbound(&mut pkt3_copy_m, VpcParser {}).unwrap(); diff --git a/lib/oxide-vpc/tests/fuzz_regression.rs b/lib/oxide-vpc/tests/fuzz_regression.rs index 2f454b78..0d6d3064 100644 --- a/lib/oxide-vpc/tests/fuzz_regression.rs +++ b/lib/oxide-vpc/tests/fuzz_regression.rs @@ -110,7 +110,7 @@ fn run_tests( #[test] fn parse_in_regression() { run_tests("parse_in", |data| { - let mut msg = MsgBlk::copy(data); + let mut msg = MsgBlk::copy(data).unwrap(); let _ = Packet::parse_inbound(msg.iter_mut(), VpcParser {}); }); } @@ -118,7 +118,7 @@ fn parse_in_regression() { #[test] fn parse_out_regression() { run_tests("parse_out", |data| { - let mut msg = MsgBlk::copy(data); + let mut msg = MsgBlk::copy(data).unwrap(); let _ = Packet::parse_outbound(msg.iter_mut(), VpcParser {}); }); } diff --git a/lib/oxide-vpc/tests/integration_tests.rs b/lib/oxide-vpc/tests/integration_tests.rs index 875c6b02..652e896c 100644 --- a/lib/oxide-vpc/tests/integration_tests.rs +++ b/lib/oxide-vpc/tests/integration_tests.rs @@ -434,7 +434,7 @@ fn gateway_icmp4_ping() { } let mut reply_body = meta.inner_ulp().expect("ICMPv4 is a ULP").emit_vec(); - reply.meta().append_remaining(&mut reply_body); + reply.meta().append_remaining(&mut reply_body).unwrap(); let reply_pkt = Icmpv4Packet::new_checked(&reply_body).unwrap(); let mut csum = CsumCapab::ignored(); csum.ipv4 = smoltcp::phy::Checksum::Rx; @@ -1616,7 +1616,7 @@ fn unpack_and_verify_icmp4( // Because we treat ICMPv4 as a full-fledged ULP, we need to // unsplit the emitted header from the body. let mut icmp = pkt.meta().inner_ulp().unwrap().emit_vec(); - pkt.meta().append_remaining(&mut icmp); + pkt.meta().append_remaining(&mut icmp).unwrap(); let icmp = Icmpv4Packet::new_checked(&icmp[..]).unwrap(); @@ -1638,7 +1638,7 @@ fn unpack_and_verify_icmp6( // Because we treat ICMPv4 as a full-fledged ULP, we need to // unsplit the emitted header from the body. let mut icmp = pkt.meta().inner_ulp().unwrap().emit_vec(); - pkt.meta().append_remaining(&mut icmp); + pkt.meta().append_remaining(&mut icmp).unwrap(); let icmp = Icmpv6Packet::new_checked(&icmp[..]).unwrap(); assert!(icmp.verify_checksum(&src_ip, &dst_ip)); @@ -1895,7 +1895,7 @@ fn bad_ip_len() { let udp = Udp { source: 68, destination: 67, ..Default::default() }; - let mut pkt_m = MsgBlk::new_ethernet_pkt((eth, ip, udp)); + let mut pkt_m = MsgBlk::new_ethernet_pkt((eth, ip, udp)).unwrap(); let res = Packet::parse_outbound(pkt_m.iter_mut(), VpcParser {}).err().unwrap(); assert_eq!( @@ -1941,7 +1941,7 @@ fn arp_gateway() { // let mut bytes = eth_hdr.emit_vec(); // bytes.extend_from_slice(ArpEthIpv4Raw::from(&arp).as_bytes()); - let mut pkt_m = MsgBlk::new_ethernet_pkt((eth_hdr, arp)); + let mut pkt_m = MsgBlk::new_ethernet_pkt((eth_hdr, arp)).unwrap(); let pkt = parse_outbound(&mut pkt_m, VpcParser {}).unwrap(); let res = g1.port.process(Out, pkt); @@ -1957,7 +1957,7 @@ fn arp_gateway() { assert_eq!(ethm.source(), cfg.gateway_mac); assert_eq!(ethm.ethertype(), Ethertype::ARP); - let body = hppkt.to_full_meta().meta().copy_remaining(); + let body = hppkt.to_full_meta().meta().copy_remaining().unwrap(); let (arp, ..) = ValidArpEthIpv4::parse(&body[..]).unwrap(); assert_eq!(arp.op(), ArpOp::REPLY); @@ -2110,7 +2110,7 @@ fn test_guest_to_gateway_icmpv6_ping( let msg_type = Icmpv6Message::from(icmp6.ty().0); let msg_code = icmp6.code(); - reply_body.extend(reply.to_full_meta().meta().copy_remaining()); + reply_body.extend(reply.to_full_meta().meta().copy_remaining().unwrap()); let reply_pkt = Icmpv6Packet::new_checked(&reply_body).unwrap(); // Verify the parsed metadata matches the packet @@ -2224,7 +2224,7 @@ fn gateway_router_advert_reply() { let ip6_src = ip6.source(); let ip6_dst = ip6.destination(); - reply_body.extend(reply.to_full_meta().meta().copy_remaining()); + reply_body.extend(reply.to_full_meta().meta().copy_remaining().unwrap()); let reply_pkt = Icmpv6Packet::new_checked(&reply_body).unwrap(); let mut csum = CsumCapab::ignored(); @@ -2461,7 +2461,7 @@ fn validate_hairpin_advert( let ip6_src = ip6.source(); let ip6_dst = ip6.destination(); - reply_body.extend(reply.to_full_meta().meta().copy_remaining()); + reply_body.extend(reply.to_full_meta().meta().copy_remaining().unwrap()); let reply_pkt = Icmpv6Packet::new_checked(&reply_body).unwrap(); // Validate the details of the Neighbor Advertisement itself. @@ -2717,7 +2717,7 @@ fn write_dhcpv6_packet( ) -> MsgBlk { let total_len = msg.buffer_len() + (ð, &ip, &udp).packet_length(); - let mut pkt = MsgBlk::new_ethernet(total_len); + let mut pkt = MsgBlk::new_ethernet(total_len).unwrap(); pkt.emit_back((eth, ip, udp)).unwrap(); let l = pkt.len(); pkt.resize(total_len).unwrap(); @@ -2885,7 +2885,7 @@ fn test_reply_to_dhcpv6_solicit_or_request() { let reply_pkt = parse_inbound(&mut hp, GenericUlp {}).unwrap().to_full_meta(); - let out_body = reply_pkt.meta().copy_remaining(); + let out_body = reply_pkt.meta().copy_remaining().unwrap(); drop(reply_pkt); let reply = @@ -5022,7 +5022,7 @@ fn icmp_inner_has_nat_applied() { .into(); ip.compute_checksum(); - let mut pkt_m = MsgBlk::new_ethernet_pkt((ð, &ip, &body_bytes)); + let mut pkt_m = MsgBlk::new_ethernet_pkt((ð, &ip, &body_bytes)).unwrap(); pcap.add_pkt(&pkt_m); let pkt = Packet::parse_outbound(pkt_m.iter_mut(), VpcParser {}).unwrap(); @@ -5096,7 +5096,7 @@ fn icmpv6_inner_has_nat_applied() { ..Default::default() }; - let pkt_m = MsgBlk::new_ethernet_pkt((ð, &ip, &body_bytes)); + let pkt_m = MsgBlk::new_ethernet_pkt((ð, &ip, &body_bytes)).unwrap(); let mut pkt_m = encap_external( pkt_m, *BSVC_PHYS, @@ -5186,7 +5186,7 @@ fn test_ipv6_multicast_encapsulation() { let Modified(spec) = res else { panic!("Expected Modified result, got {res:?}"); }; - let mut pkt_m = spec.apply(pkt_m); + let mut pkt_m = spec.apply(pkt_m).unwrap(); // Parse the encapsulated packet as inbound (it's now on the wire with Geneve) let parsed = Packet::parse_inbound(pkt_m.iter_mut(), VpcParser {}).unwrap(); @@ -5312,7 +5312,8 @@ fn test_drop_on_unknown_critical_option() { }; // Build inner packet first - let inner_pkt = MsgBlk::new_ethernet_pkt((inner_eth, inner_ip, inner_udp)); + let inner_pkt = + MsgBlk::new_ethernet_pkt((inner_eth, inner_ip, inner_udp)).unwrap(); let inner_len = inner_pkt.byte_len(); // Create an unknown critical Geneve option (class=0xffff, type=0x80) @@ -5363,7 +5364,8 @@ fn test_drop_on_unknown_critical_option() { outer_ip, outer_udp, outer_geneve, - )); + )) + .unwrap(); pkt.append(inner_pkt); // Attempt to parse the packet through VpcParser's parse_inbound, which @@ -5488,7 +5490,8 @@ fn test_v6_ext_hdr_geneve_offset_ok() { }; // Append inner packet bytes - let inner_pkt = MsgBlk::new_ethernet_pkt((inner_eth, inner_ip, inner_udp)); + let inner_pkt = + MsgBlk::new_ethernet_pkt((inner_eth, inner_ip, inner_udp)).unwrap(); for chunk in inner_pkt.iter() { buf.extend_from_slice(chunk); } @@ -5505,7 +5508,7 @@ fn test_v6_ext_hdr_geneve_offset_ok() { // Parse through the full pipeline using `parse_inbound()` with VpcParser // This tests that the parser correctly handles IPv6 extension headers and // finds the Geneve header after navigating the extension header chain - let mut pkt = MsgBlk::copy(&buf); + let mut pkt = MsgBlk::copy(&buf).unwrap(); let parse_result = common::parse_inbound(&mut pkt, VpcParser {}); // Parsing should succeed diff --git a/xde/src/stats.rs b/xde/src/stats.rs index d738d7fc..722f28b8 100644 --- a/xde/src/stats.rs +++ b/xde/src/stats.rs @@ -1,6 +1,7 @@ use opte::api::Direction; use opte::ddi::kstat::KStatProvider; use opte::ddi::kstat::KStatU64; +use opte::ddi::mblk::PktAllocError; use opte::engine::packet::ParseError; use opte::ingot::types::ParseError as IngotError; @@ -28,6 +29,9 @@ pub struct XdeStats { /// The number of inbound packets dropped due to the presence of /// unrecognised critical options. in_drop_bad_tun_opt: KStatU64, + /// The number of inbound packets dropped because we failed to allocate + /// or pullup an `mblk_t` when applying a packet transform. + in_drop_alloc: KStatU64, /// The number of inbound packets dropped for other reasons, including /// parser programming errors. in_drop_misc: KStatU64, @@ -50,9 +54,13 @@ pub struct XdeStats { /// The number of outbound packets dropped due to reporting more /// bytes than the packet contains. out_drop_bad_len: KStatU64, + /// The number of inbound packets dropped because we failed to allocate + /// or pullup an `mblk_t` when applying a packet transform. + out_drop_alloc: KStatU64, /// The number of outbound packets dropped for other reasons, including /// parser programming errors. out_drop_misc: KStatU64, + // NOTE: tun_opt is not relevant to outbound packets -- no encapsulation // is in use. /// The number of multicast packets delivered to local guest instances @@ -175,4 +183,12 @@ impl XdeStats { }) .incr(1) } + + pub fn emit_error(&self, dir: Direction, err: &PktAllocError) { + (match (dir, err) { + (Direction::In, PktAllocError) => &self.in_drop_alloc, + (Direction::Out, PktAllocError) => &self.out_drop_alloc, + }) + .incr(1) + } } diff --git a/xde/src/xde.rs b/xde/src/xde.rs index fe2e0be6..2b86793d 100644 --- a/xde/src/xde.rs +++ b/xde/src/xde.rs @@ -2099,7 +2099,14 @@ fn guest_loopback( match dst_dev.port.process(In, parsed_pkt) { Ok(ProcessResult::Modified(emit_spec)) => { - let mut pkt = emit_spec.apply(pkt); + let mut pkt = match emit_spec.apply(pkt) { + Ok(p) => p, + Err(e) => { + get_xde_state().stats.vals.emit_error(Direction::In, &e); + return; + } + }; + if let Err(e) = pkt.fill_parse_info(&ulp_meoi, None) { opte::engine::err!("failed to set offload info: {}", e); } @@ -2822,7 +2829,13 @@ fn xde_mc_tx_one<'a>( let mtu_unrestricted = emit_spec.mtu_unrestricted(); let l4_hash = emit_spec.l4_hash(); - let mut out_pkt = emit_spec.apply(pkt); + let mut out_pkt = match emit_spec.apply(pkt) { + Ok(p) => p, + Err(e) => { + get_xde_state().stats.vals.emit_error(Direction::Out, &e); + return; + } + }; let new_len = out_pkt.byte_len(); if ip6_src == ip6_dst { @@ -3451,7 +3464,13 @@ fn xde_rx_one( match res { Ok(ProcessResult::Modified(emit_spec)) => { - let mut npkt = emit_spec.apply(pkt); + let mut npkt = match emit_spec.apply(pkt) { + Ok(p) => p, + Err(e) => { + get_xde_state().stats.vals.emit_error(Direction::In, &e); + return None; + } + }; let len = npkt.byte_len(); let pay_len = len - usize::try_from(non_payl_bytes) @@ -3564,7 +3583,13 @@ fn xde_rx_one_direct( match res { Ok(ProcessResult::Modified(emit_spec)) => { - let mut npkt = emit_spec.apply(pkt); + let mut npkt = match emit_spec.apply(pkt) { + Ok(p) => p, + Err(e) => { + get_xde_state().stats.vals.emit_error(Direction::In, &e); + return; + } + }; let len = npkt.byte_len(); let pay_len = len - usize::try_from(non_payl_bytes)