diff --git a/CHANGELOG.md b/CHANGELOG.md index 88538df..1d8ef5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - ReleaseDate +### Fixed +- [PR#20](https://github.com/Jake-Shadle/xdp/pull/20) changed `EtherType` and `IpProto` from enums to scoped constants to avoid UB in the presence of invalid/corrupt data that didn't match a variant. Also removed a bunch of the `IpProto` variants as most will never be used, and since it's now scoped constants users can provide their own constants without needing them in the lib themselves. Resolved [#19](https://github.com/Jake-Shadle/xdp/issues/19). +- [PR#23](https://github.com/Jake-Shadle/xdp/pull/23) added sanity checks to avoid subtraction underflow if the user provides wildly out of range offsets and/or slices to `Packet` methods. +- [PR#23](https://github.com/Jake-Shadle/xdp/pull/23) fixed a bug in `Packet::array_at_offset` where the offset was incorrect if `head` was not 0. +- [PR#23](https://github.com/Jake-Shadle/xdp/pull/23) added a check in `UdpHeader::parse_packet` to ensure the UDP length matches the packet buffer length. + +### Changed +- [PR#22](https://github.com/Jake-Shadle/xdp/pull/22) removed the `Index/Mut` impls from `XskProducer/Consumer` as they were unneccessary fluff in favor of much simpler internal methods. +- [PR#23](https://github.com/Jake-Shadle/xdp/pull/23) changed `data_offset` and `data_length` to just `data`, a range that is convertible to/from `std::ops::Range`. `data_length` is now a method that just returns `data.end - data.start`. + +### Added +- [PR#23](https://github.com/Jake-Shadle/xdp/pull/23) added `Packet::append` as a simpler way to add data to the tail of the packet. +- [PR#23](https://github.com/Jake-Shadle/xdp/pull/23) added `csum::DataChecksum` as a simpler way to calculate the checksum of the data portion of a payload. `UdpHeaders::calc_checksum` now uses this instead of separate length and checksum arguments. + ## [0.6.0] - 2025-03-04 ### Changed - [PR#16](https://github.com/Jake-Shadle/xdp/pull/16) changed `RxRing` and `TxRing` to use the new `slab::Slab` trait. diff --git a/crates/integ/tests/tx_checksum.rs b/crates/integ/tests/tx_checksum.rs index 3993fe3..c16b325 100644 --- a/crates/integ/tests/tx_checksum.rs +++ b/crates/integ/tests/tx_checksum.rs @@ -121,8 +121,8 @@ fn do_checksum_test(software: bool, vpair: &VethPair) { .expect("not a UDP packet"); // For this packet, we calculate the full checksum - packet.adjust_tail(-(udp.data_length as i32)).unwrap(); - packet.insert(udp.data_offset, serverp).unwrap(); + packet.adjust_tail(-(udp.data_length() as i32)).unwrap(); + packet.insert(udp.data.start, serverp).unwrap(); let nt::IpHdr::V4(mut copy) = udp.ip else { unreachable!() @@ -130,27 +130,25 @@ fn do_checksum_test(software: bool, vpair: &VethPair) { std::mem::swap(&mut copy.destination, &mut copy.source); copy.time_to_live -= 1; - let mut new = nt::UdpHeaders { - eth: nt::EthHdr { + let mut new = nt::UdpHeaders::new( + nt::EthHdr { source: udp.eth.destination, destination: udp.eth.source, ether_type: udp.eth.ether_type, }, - ip: nt::IpHdr::V4(copy), - udp: nt::UdpHdr { + nt::IpHdr::V4(copy), + nt::UdpHdr { destination: udp.udp.source, source: sport.into(), length: 0.into(), check: 0, }, - data_offset: udp.data_offset, - data_length: serverp.len(), - }; + udp.data.start..udp.data.start + serverp.len(), + ); // For this packet, we calculate the full checksum - let data_checksum = csum::partial(serverp, 0); - let full_checksum = new.calc_checksum(serverp.len(), data_checksum); - new.set_packet_headers(&mut packet, true).unwrap(); + let full_checksum = new.calc_checksum(csum::DataChecksum::calculate(serverp)); + new.set_packet_headers(&mut packet).unwrap(); println!("Full checksum: {full_checksum:04x}"); slab.push_front(packet); @@ -175,8 +173,8 @@ fn do_checksum_test(software: bool, vpair: &VethPair) { .expect("failed to parse packet") .expect("not a UDP packet"); - packet.adjust_tail(-(udp.data_length as i32)).unwrap(); - packet.insert(udp.data_offset, serverp).unwrap(); + packet.adjust_tail(-(udp.data_length() as i32)).unwrap(); + packet.insert(udp.data.start, serverp).unwrap(); let nt::IpHdr::V4(mut copy) = udp.ip else { unreachable!() @@ -184,23 +182,22 @@ fn do_checksum_test(software: bool, vpair: &VethPair) { std::mem::swap(&mut copy.destination, &mut copy.source); copy.time_to_live -= 1; - let mut new = nt::UdpHeaders { - eth: nt::EthHdr { + let mut new = nt::UdpHeaders::new( + nt::EthHdr { source: udp.eth.destination, destination: udp.eth.source, ether_type: udp.eth.ether_type, }, - ip: nt::IpHdr::V4(copy), - udp: nt::UdpHdr { + nt::IpHdr::V4(copy), + nt::UdpHdr { destination: udp.udp.source, source: sport.into(), length: 0.into(), check: 0, }, - data_offset: udp.data_offset, - data_length: serverp.len(), - }; - new.set_packet_headers(&mut packet, true).unwrap(); + udp.data.start..udp.data.start + serverp.len(), + ); + new.set_packet_headers(&mut packet).unwrap(); println!( "partial checksum: {:04x}", packet.calc_udp_checksum().unwrap() diff --git a/crates/tests/tests/csum.rs b/crates/tests/tests/csum.rs index 439d89b..6f69b9f 100644 --- a/crates/tests/tests/csum.rs +++ b/crates/tests/tests/csum.rs @@ -3,7 +3,7 @@ use etherparse::PacketBuilder; use std::net::*; use tests::*; -use xdp::packet::{net_types::*, *}; +use xdp::packet::{csum, net_types::*, *}; /// Ensures we generate the correct IPv4 header checksum #[test] @@ -80,8 +80,7 @@ fn combines_partial_checksums() { let expected = udp.udp.check; assert_eq!(packet.calc_udp_checksum().unwrap(), expected); - let data_checksum = csum::partial(LARGER, 0); - udp.calc_checksum(LARGER.len(), data_checksum); + udp.calc_checksum(csum::DataChecksum::calculate(LARGER)); assert_eq!(udp.udp.check, expected); } @@ -101,8 +100,7 @@ fn combines_partial_checksums() { let expected = udp.udp.check; assert_eq!(packet.calc_udp_checksum().unwrap(), expected); - let data_checksum = csum::partial(LARGER, 0); - udp.calc_checksum(LARGER.len(), data_checksum); + udp.calc_checksum(csum::DataChecksum::calculate(LARGER)); assert_eq!(udp.udp.check, expected); } } diff --git a/crates/tests/tests/packet.rs b/crates/tests/tests/packet.rs index 2f46ac4..8de6a1b 100644 --- a/crates/tests/tests/packet.rs +++ b/crates/tests/tests/packet.rs @@ -31,6 +31,9 @@ fn simple() { tot_len - xdp::libc::xdp::XDP_PACKET_HEADROOM as usize, ); + packet.adjust_tail(21).unwrap(); + packet.adjust_head(21).unwrap(); + let val = b"deadbeef"; packet.insert(0, val).unwrap(); @@ -72,6 +75,18 @@ fn simple() { packet.adjust_tail(-(packet.len() as i32)).unwrap(); assert!(packet.is_empty()); + + packet + .insert(0, &0xf3f3f3f3f3f3f3f3u64.to_ne_bytes()) + .unwrap(); + packet.append(&0x1212121212121212u64.to_ne_bytes()).unwrap(); + + assert_eq!(packet.len(), 16); + let mut arr6 = [0u8; 8]; + packet.array_at_offset(0, &mut arr6).unwrap(); + assert_eq!(0xf3f3f3f3f3f3f3f3, u64::from_ne_bytes(arr6)); + packet.array_at_offset(8, &mut arr6).unwrap(); + assert_eq!(0x1212121212121212, u64::from_ne_bytes(arr6)); } #[test] @@ -125,25 +140,26 @@ fn udp_send() { ipv6.reset(64, nt::IpProto::Udp); ipv6.source = [10; 16]; ipv6.destination = [1; 16]; - let mut udp = UdpHeaders { - eth: nt::EthHdr { + let data_offset = nt::EthHdr::LEN + nt::Ipv6Hdr::LEN + nt::UdpHdr::LEN; + + let mut udp = UdpHeaders::new( + nt::EthHdr { source: MacAddress([1; 6]), destination: MacAddress([2; 6]), ether_type: nt::EtherType::Ipv6, }, - ip: nt::IpHdr::V6(ipv6), - udp: nt::UdpHdr { + nt::IpHdr::V6(ipv6), + nt::UdpHdr { source: 8900.into(), destination: 9001.into(), length: 0.into(), check: 0, }, - data_offset: nt::EthHdr::LEN + nt::Ipv6Hdr::LEN + nt::UdpHdr::LEN, - data_length: payload.len(), - }; + data_offset..data_offset + payload.len(), + ); - udp.set_packet_headers(&mut packet, false).unwrap(); - packet.insert(udp.data_offset, &payload).unwrap(); + udp.set_packet_headers(&mut packet).unwrap(); + packet.insert(udp.data.start, &payload).unwrap(); let check = packet.calc_udp_checksum().unwrap(); @@ -213,10 +229,7 @@ fn parses_ipv4() { destination: Ipv4Addr::new(192, 168, 1, 1), } ); - assert_eq!( - &packet[udp.data_offset..udp.data_offset + udp.data_length], - IPV4_DATA - ); + assert_eq!(&packet[udp.data], IPV4_DATA); } /// Ensures we can parse an IPv6 UDP packet @@ -246,8 +259,71 @@ fn parses_ipv6() { destination: DST, } ); + assert_eq!(&packet[udp.data], IPV6_DATA); +} + +/// Ensures the UDP length field matches the packet +#[test] +fn rejects_invalid_udp_length() { + let mut buf = [0u8; 2048]; + let mut packet = Packet::testing_new(&mut buf); + + PacketBuilder::ethernet2(SRC_MAC.0, DST_MAC.0) + .ipv6([20; 16], [33; 16], 64) + .udp(5353, 1111) + .write(&mut packet, IPV6_DATA) + .unwrap(); + + let mut udp_hdr: nt::UdpHdr = packet.read(nt::EthHdr::LEN + nt::Ipv6Hdr::LEN).unwrap(); + assert_eq!(udp_hdr.source.host(), 5353); + assert_eq!(udp_hdr.destination.host(), 1111); assert_eq!( - &packet[udp.data_offset..udp.data_offset + udp.data_length], - IPV6_DATA + udp_hdr.length.host() as usize, + IPV6_DATA.len() + nt::UdpHdr::LEN ); + + // too long + { + udp_hdr.length = u16::MAX.into(); + packet + .write(nt::EthHdr::LEN + nt::Ipv6Hdr::LEN, udp_hdr) + .unwrap(); + assert!(UdpHeaders::parse_packet(&packet).is_err()); + } + + // too short + { + udp_hdr.length = (nt::UdpHdr::LEN as u16).into(); + packet + .write(nt::EthHdr::LEN + nt::Ipv6Hdr::LEN, udp_hdr) + .unwrap(); + assert!(UdpHeaders::parse_packet(&packet).is_err()); + } + + // off by 1 + { + udp_hdr.length = ((nt::UdpHdr::LEN + IPV6_DATA.len() + 1) as u16).into(); + packet + .write(nt::EthHdr::LEN + nt::Ipv6Hdr::LEN, udp_hdr) + .unwrap(); + assert!(UdpHeaders::parse_packet(&packet).is_err()); + } +} + +#[test] +#[should_panic] +fn data_range() { + let mut buf = [0u8; 2 * 1024]; + let mut packet = Packet::testing_new(&mut buf); + + const DATA: &[u8] = &[0x43; 31]; + + packet.append(DATA).unwrap(); + assert_eq!(packet.len(), DATA.len()); + + let mut range: xdp::packet::net_types::DataRange = (0..DATA.len()).into(); + assert_eq!(&packet[range], DATA); + + range.start = range.end + 1; + dbg!(&packet[range]); } diff --git a/src/packet.rs b/src/packet.rs index 11556a9..c90ea4e 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -7,6 +7,17 @@ pub mod net_types; use crate::libc; use std::fmt; +/// The maximum size of an XDP chunk is 4k, so any offsets or sizes larger than +/// that indicates calling code is probably not correct +const SANE: usize = 4096; + +// TODO: Replace with `std::intrinsics::unlikely` if/when it is stabilized +// https://github.com/rust-lang/rust/issues/136873 +#[inline(always)] +pub(crate) const fn unlikely(x: T) -> T { + x +} + /// Errors that can occur when reading/writing [`Packet`] contents #[derive(Debug)] pub enum PacketError { @@ -423,16 +434,18 @@ impl Packet { /// ``` #[inline] pub fn read(&self, offset: usize) -> Result { - assert!( - offset < 4096, - "'offset' is wildly out of range and indicates a bug" - ); + if unlikely(offset >= SANE) { + return Err(PacketError::InvalidOffset { + offset, + length: self.len(), + }); + } let start = self.head + offset; if start > self.tail { return Err(PacketError::InvalidOffset { offset, - length: self.tail - self.head, + length: self.len(), }); } @@ -504,16 +517,18 @@ impl Packet { /// ``` #[inline] pub fn write(&mut self, offset: usize, item: T) -> Result<(), PacketError> { - assert!( - offset < 4096, - "'offset' is wildly out of range and indicates a bug" - ); + if unlikely(offset >= SANE) { + return Err(PacketError::InvalidOffset { + offset, + length: self.len(), + }); + } let start = self.head + offset; if start > self.tail { return Err(PacketError::InvalidOffset { offset, - length: self.tail - self.head, + length: self.len(), }); } @@ -582,10 +597,12 @@ impl Packet { assert_reasonable::(); - assert!( - offset < 4096, - "'offset' is wildly out of range and indicates a bug" - ); + if unlikely(offset >= SANE) { + return Err(PacketError::InvalidOffset { + offset, + length: self.len(), + }); + } let start = self.head + offset; if start + N > self.tail { @@ -596,13 +613,9 @@ impl Packet { }); } - // SAFETY: we've validated the range of data we are reading is valid + // SAFETY: we've validated the range of data we are reading unsafe { - std::ptr::copy_nonoverlapping( - self.data.byte_offset(offset as _), - array.as_mut_ptr(), - N, - ); + std::ptr::copy_nonoverlapping(self.data.byte_offset(start as _), array.as_mut_ptr(), N); } Ok(()) } @@ -646,15 +659,9 @@ impl Packet { /// ``` #[inline] pub fn insert(&mut self, offset: usize, slice: &[u8]) -> Result<(), PacketError> { - assert!( - offset < 4096, - "'offset' is wildly out of range and indicates a bug" - ); - assert!(slice.len() <= 4096, "the slice length is far too large"); - - if self.tail + slice.len() > self.capacity { + if unlikely(slice.len() > SANE) || self.tail + slice.len() > self.capacity { return Err(PacketError::InvalidPacketLength {}); - } else if offset > self.tail { + } else if offset > self.tail || unlikely(offset >= SANE) { return Err(PacketError::InvalidOffset { offset, length: self.len(), @@ -687,6 +694,31 @@ impl Packet { Ok(()) } + /// Inserts a slice at tail of the packet + /// + /// # Errors + /// + /// - The current tail + `slice.len()` would exceed the capacity + #[inline] + pub fn append(&mut self, slice: &[u8]) -> Result<(), PacketError> { + if unlikely(slice.len() > SANE) || self.tail + slice.len() > self.capacity { + return Err(PacketError::InvalidPacketLength {}); + } + + // SAFETY: we validate we're within bounds before doing any writes to the + // pointer, which is alive as long as the owning mmap + unsafe { + std::ptr::copy_nonoverlapping( + slice.as_ptr(), + self.data.byte_offset(self.tail as _), + slice.len(), + ); + } + + self.tail += slice.len(); + Ok(()) + } + /// Sets the specified [TX metadata](https://github.com/torvalds/linux/blob/ae90f6a6170d7a7a1aa4fddf664fbd093e3023bc/Documentation/networking/xsk-tx-metadata.rst) /// /// Calling this function requires that the [`crate::umem::UmemCfgBuilder::tx_checksum`] @@ -756,7 +788,7 @@ impl Packet { #[doc(hidden)] #[inline] - pub fn inner_copy(&mut self) -> Self { + pub fn __inner_copy(&mut self) -> Self { Self { data: self.data, capacity: self.capacity, @@ -805,7 +837,7 @@ impl From for libc::xdp::xdp_desc { impl std::io::Write for Packet { #[inline] fn write(&mut self, buf: &[u8]) -> std::io::Result { - match self.insert(self.tail - self.head, buf) { + match self.append(buf) { Ok(()) => Ok(buf.len()), Err(_) => Err(std::io::Error::new( std::io::ErrorKind::StorageFull, diff --git a/src/packet/csum.rs b/src/packet/csum.rs index 255eeab..083f493 100644 --- a/src/packet/csum.rs +++ b/src/packet/csum.rs @@ -218,6 +218,59 @@ pub fn partial(mut buf: &[u8], sum: u32) -> u32 { finalize(sum) } +/// The checksum of a block of data +#[derive(Copy, Clone)] +pub struct DataChecksum { + checksum: u32, + length: usize, +} + +impl DataChecksum { + /// Calculates the checksum of the specified slice + /// + /// # Errors + /// + /// This method panics if the slice is more than 4KiB in length, as that + /// indicates a bug in the caller as `crate::Packet` has a maximum capacity + /// of (slightly less than) 4KiB + #[inline] + pub fn calculate(data: &[u8]) -> Self { + assert!( + data.len() <= 4096, + "the specified slice is too large to fit in a Packet" + ); + + Self { + checksum: partial(data, 0), + length: data.len(), + } + } + + /// Calculates the checksum of the specified slice, but only if the specified + /// packet doesn't support checksum offload + /// + /// # Errors + /// + /// This method panics if the slice is more than 4KiB in length, as that + /// indicates a bug in the caller as `crate::Packet` has a maximum capacity + /// of (slightly less than) 4KiB + pub fn calculate_if_needed(data: &[u8], packet: &super::Packet) -> Self { + assert!( + data.len() <= 4096, + "the specified slice is too large to fit in a Packet" + ); + + Self { + checksum: if packet.can_offload_checksum() { + 0 + } else { + partial(data, 0) + }, + length: data.len(), + } + } +} + use crate::packet::net_types as nt; /// Errors that can occur during UDP checksum calculation @@ -400,11 +453,11 @@ impl nt::UdpHeaders { /// Given an already calculated checksum for the data payload, or 0 if using /// tx checksum offload, checksums the pseudo IP and UDP header #[inline] - pub fn calc_checksum(&mut self, length: usize, data_checksum: u32) -> u16 { - self.data_length = length; + pub fn calc_checksum(&mut self, data_checksum: DataChecksum) -> u16 { + self.data.end = self.data.start + data_checksum.length; - let mut sum = data_checksum as u64; - let data_len = self.data_length + nt::UdpHdr::LEN; + let mut sum = data_checksum.checksum as u64; + let data_len = data_checksum.length + nt::UdpHdr::LEN; match &self.ip { nt::IpHdr::V4(v4) => { diff --git a/src/packet/net_types.rs b/src/packet/net_types.rs index cdcd34d..1d88df9 100644 --- a/src/packet/net_types.rs +++ b/src/packet/net_types.rs @@ -517,6 +517,41 @@ impl PartialEq for IpHdr { } } +/// A small replacement for `std::ops::Range` due to the annoying lack +/// of `Copy` +#[derive(Copy, Clone)] +pub struct DataRange { + /// The lower bound of the range (inclusive). + pub start: usize, + /// The upper bound of the range (exclusive). + pub end: usize, +} + +impl From> for DataRange { + #[inline] + fn from(value: std::ops::Range) -> Self { + Self { + start: value.start, + end: value.end, + } + } +} + +impl fmt::Debug for DataRange { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}..{}", self.start, self.end) + } +} + +impl std::ops::Index for [u8] { + type Output = [u8]; + + #[inline] + fn index(&self, index: DataRange) -> &Self::Output { + self.index(index.start..index.end) + } +} + /// A [UDP](https://en.wikipedia.org/wiki/User_Datagram_Protocol) packet #[cfg_attr(feature = "__debug", derive(Debug))] pub struct UdpHeaders { @@ -526,13 +561,22 @@ pub struct UdpHeaders { pub ip: IpHdr, /// The transport header pub udp: UdpHdr, - /// The offset from the beginning of the packet where the data payload begins - pub data_offset: usize, - /// The length of the data payload - pub data_length: usize, + /// The range where the packet's data is located + pub data: DataRange, } impl UdpHeaders { + /// Creates a [`Self`] + #[inline] + pub fn new(eth: EthHdr, ip: IpHdr, udp: UdpHdr, data: impl Into) -> Self { + Self { + eth, + ip, + udp, + data: data.into(), + } + } + /// Attempts to parse a [`Self`] from a packet. /// /// Returns `Ok(None)` if the packet doesn't seem corrupted, but doesn't @@ -596,9 +640,9 @@ impl UdpHeaders { /// /// assert_eq!(udp_hdrs.udp.source.host(), 50000); /// - /// assert_eq!(udp_hdrs.data_offset, nt::EthHdr::LEN + nt::Ipv4Hdr::LEN + nt::UdpHdr::LEN); - /// assert_eq!(udp_hdrs.data_length, DATA_LEN); - /// assert_eq!(&packet[udp_hdrs.data_offset..udp_hdrs.data_offset + udp_hdrs.data_length], &[0xf0; DATA_LEN]); + /// assert_eq!(udp_hdrs.data.start, nt::EthHdr::LEN + nt::Ipv4Hdr::LEN + nt::UdpHdr::LEN); + /// assert_eq!(udp_hdrs.data_length(), DATA_LEN); + /// assert_eq!(&packet[udp_hdrs.data], &[0xf0; DATA_LEN]); /// ``` pub fn parse_packet(packet: &super::Packet) -> Result, super::PacketError> { let mut offset = 0; @@ -632,14 +676,22 @@ impl UdpHeaders { }; let udp = packet.read::(offset)?; - let data_length = udp.length.host() as usize - UdpHdr::LEN; + let data_length = udp.length.host() as usize; + if offset + data_length != packet.len() { + return Err(super::PacketError::InsufficientData { + offset, + size: data_length, + length: packet.len(), + }); + } + + let start = offset + UdpHdr::LEN; Ok(Some(Self { eth, ip, udp, - data_offset: offset + UdpHdr::LEN, - data_length, + data: (start..start + data_length - UdpHdr::LEN).into(), })) } @@ -661,6 +713,12 @@ impl UdpHeaders { + UdpHdr::LEN } + /// The length of the data portion of the packet + #[inline(always)] + pub fn data_length(&self) -> usize { + self.data.end - self.data.start + } + /// Decrements the hop counter #[inline] pub fn decrement_hop(&mut self) -> u8 { @@ -709,30 +767,21 @@ impl UdpHeaders { /// Writes the headers to the front of the packet buffer. /// - /// If `calculate_ipv4_checksum` is `true`, the IPv4 header checksum is - /// calculated, otherwise it is set to 0, as the IPv4 checksum is optional - /// for UDP packets - /// /// # Errors /// /// The packet buffer must have enough space for all of the headers pub fn set_packet_headers( &mut self, packet: &mut super::Packet, - calculate_ipv4_checksum: bool, ) -> Result<(), super::PacketError> { let mut offset = EthHdr::LEN; - let length = (self.data_length + UdpHdr::LEN) as u16; + let length = (self.data.end - self.data.start + UdpHdr::LEN) as u16; self.eth.ether_type = match &mut self.ip { IpHdr::V4(v4) => { v4.total_length = (length + Ipv4Hdr::LEN as u16).into(); - if calculate_ipv4_checksum { - v4.calc_checksum(); - } else { - v4.check = 0; - } + v4.calc_checksum(); packet.write(offset, *v4)?; offset += Ipv4Hdr::LEN; EtherType::Ipv4 diff --git a/src/slab.rs b/src/slab.rs index 54f1627..92446ce 100644 --- a/src/slab.rs +++ b/src/slab.rs @@ -139,7 +139,7 @@ macro_rules! slab { let index = self.read as usize % N; self.read = self.read.wrapping_add(1); - Some(self.ring[index].inner_copy()) + Some(self.ring[index].__inner_copy()) } /// Pushes a packet to the front, returning `Some` if the slab is at capacity