diff --git a/dist/setup.sh b/dist/setup.sh index c020182..01c2106 100644 --- a/dist/setup.sh +++ b/dist/setup.sh @@ -1,12 +1,14 @@ #!/bin/bash sudo apt update +sudo apt upgrade -y sudo apt install -y \ build-essential \ clang \ llvm \ m4 \ pkg-config \ + gnupg \ git \ libbpf-dev \ libxdp-dev \ @@ -16,7 +18,8 @@ sudo apt install -y \ unzip \ jq \ wget \ - zsh + zsh \ + htop wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(grep -oP '(?<=UBUNTU_CODENAME=).*' /etc/os-release || lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list diff --git a/examples/raw-receiver.rs b/examples/raw-receiver.rs new file mode 100644 index 0000000..34ee60c --- /dev/null +++ b/examples/raw-receiver.rs @@ -0,0 +1,99 @@ +use std::{ + ops::{Deref, DerefMut}, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, +}; + +use clap::Parser; + +use libvoid::{ + raw::RawReceiver, + xdp::{context::XdpContext, frame::BasicFrameBuffer}, +}; + +mod common; +use common::{BaseArgs, Stats}; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +struct Args { + #[command(flatten)] + base: BaseArgs, +} + +impl Deref for Args { + type Target = BaseArgs; + + fn deref(&self) -> &Self::Target { + &self.base + } +} + +impl DerefMut for Args { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.base + } +} + +fn main() { + let mut stats = Stats::new(); + let args = Args::parse(); + + // Every application starts with setting up an XdpContext, this loads the XDP kernel program and attaches it to the named + // interface. + let mut xdp_ctx = XdpContext::builder(&args.if_name) + .attach_mode(args.attach_mode) + .enable_fragmentation(args.enable_fragmentation) + .build() + .expect("Failed to create xdp context"); + + let mut reciever = RawReceiver::::builder(&args.if_name, args.queue) + .frame_size(args.frame_size) + .num_frames(args.busy_poll_batch_size) + .huge_tables(args.huge_tables) + .unaligned(args.unaligned) + .busy_poll(args.busy_poll) + .busy_poll_batch_size(args.busy_poll_batch_size) + .busy_poll_timeout_us(args.busy_poll_timeout_us) + .copy_mode(args.copy_mode) + .enable_fragmentation(args.enable_fragmentation) + .build::(&mut xdp_ctx) + .expect("Failed to create receiver"); + + // Always catch SIGINT/SIGTERM to ensure we clean up properly, we have a running XDP program attached to the interface. + // + // Note: In other words its very important to ensure that the Drop impl for XdpContext is run to detach the XDP program from + // the interface, OR manually call detach(). + let exit = Arc::new(AtomicBool::new(false)); + ctrlc::set_handler({ + let exit = exit.clone(); + move || { + exit.store(true, Ordering::Relaxed); + } + }) + .expect("Error setting Ctrl-C handler"); + + println!("Socket created, listening for packets..."); + + // Loop forever reading packets from the socket. + while !exit.load(Ordering::Relaxed) { + // Read some frames from the socket. + // + // This will return with 1 > N frames in the success case, as soon as possible. If there are no frames it will return an error, + // at this point we would have blocked so a retry is necessary this is left for the caller to handle. + match reciever.recv() { + Ok(frames) => { + for frame in frames { + stats.update(frame.len(), frame.is_fragment()); + } + } + Err(_) => { + continue; + } + } + + stats.maybe_print(); + } +} diff --git a/examples/raw-sender.rs b/examples/raw-sender.rs new file mode 100644 index 0000000..8fd0cdf --- /dev/null +++ b/examples/raw-sender.rs @@ -0,0 +1,106 @@ +use std::{ + ops::{Deref, DerefMut}, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, +}; + +use clap::Parser; + +use libvoid::{ + raw::RawSender, + xdp::{ + context::XdpContext, + frame::{BasicFrameBuffer, FrameBuffer}, + }, +}; + +mod common; +use common::{BaseArgs, GeneratorArgs, Stats, build_frame}; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +struct Args { + #[command(flatten)] + base: BaseArgs, + #[command(flatten)] + generator: GeneratorArgs, +} + +impl Deref for Args { + type Target = BaseArgs; + + fn deref(&self) -> &Self::Target { + &self.base + } +} + +impl DerefMut for Args { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.base + } +} + +fn main() { + let mut stats = Stats::new(); + let args = Args::parse(); + + // Every application starts with setting up an XdpContext, this loads the XDP kernel program and attaches it to the named + // interface. + let mut xdp_ctx = XdpContext::builder(&args.if_name) + .attach_mode(args.attach_mode) + .enable_fragmentation(args.enable_fragmentation) + .build() + .expect("Failed to create xdp context"); + + let (mut sender, mut buffer) = RawSender::builder(&args.if_name, args.queue) + .frame_size(args.frame_size) + .num_frames(args.busy_poll_batch_size) + .huge_tables(args.huge_tables) + .unaligned(args.unaligned) + .busy_poll(args.busy_poll) + .busy_poll_batch_size(args.busy_poll_batch_size) + .busy_poll_timeout_us(args.busy_poll_timeout_us) + .build::(&mut xdp_ctx) + .expect("Failed to create receiver"); + + // Always catch SIGINT/SIGTERM to ensure we clean up properly, we have a running XDP program attached to the interface. + // + // Note: In other words its very important to ensure that the Drop impl for XdpContext is run to detach the XDP program from + // the interface, OR manually call detach(). + let exit = Arc::new(AtomicBool::new(false)); + ctrlc::set_handler({ + let exit = exit.clone(); + move || { + exit.store(true, Ordering::Relaxed); + } + }) + .expect("Error setting Ctrl-C handler"); + + println!("Socket created, listening for packets..."); + // Generate our mock UDP packet to send. + let packet_data = build_frame(&args.generator); + + for frame in buffer.iter_frames_mut() { + frame.copy_from(&packet_data); + } + + // Loop forever reading packets from the socket. + while !exit.load(Ordering::Relaxed) { + for frame in buffer.iter_frames_mut() { + unsafe { frame.set_len(packet_data.len()) }; + } + + match sender.send(&mut buffer) { + Ok(sent) => { + stats.update_batch(sent as usize, packet_data.len()); + } + Err(_) => { + continue; + } + } + + stats.maybe_print(); + } +} diff --git a/examples/raw-transceiver.rs b/examples/raw-transceiver.rs new file mode 100644 index 0000000..bd914e7 --- /dev/null +++ b/examples/raw-transceiver.rs @@ -0,0 +1,101 @@ +use std::{ + ops::{Deref, DerefMut}, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, +}; + +use clap::Parser; + +use libvoid::{ + raw::RawTransceiver, + xdp::{ + context::XdpContext, + frame::{BasicFrameBuffer, FrameBuffer}, + }, +}; + +mod common; +use common::{BaseArgs, Stats}; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +struct Args { + #[command(flatten)] + base: BaseArgs, +} + +impl Deref for Args { + type Target = BaseArgs; + + fn deref(&self) -> &Self::Target { + &self.base + } +} + +impl DerefMut for Args { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.base + } +} + +#[inline(always)] +fn handler<'umem, 'buffer, T: FrameBuffer<'umem>>(frames: T::IterMut<'buffer>, stats: &mut Stats) { + for frame in frames { + stats.update(frame.len(), frame.is_fragment()); + } +} + +fn main() { + let mut stats = Stats::new(); + let args = Args::parse(); + + // Every application starts with setting up an XdpContext, this loads the XDP kernel program and attaches it to the named + // interface. + let mut xdp_ctx = XdpContext::builder(&args.if_name) + .attach_mode(args.attach_mode) + .enable_fragmentation(args.enable_fragmentation) + .build() + .expect("Failed to create xdp context"); + + let mut reciever = RawTransceiver::::builder(&args.if_name, args.queue) + .rx_ring_size(args.rx_ring_size) + .tx_ring_size(args.tx_ring_size) + .busy_poll(args.busy_poll) + .busy_poll_batch_size(args.busy_poll_batch_size) + .busy_poll_timeout_us(args.busy_poll_timeout_us) + .copy_mode(args.copy_mode) + .enable_fragmentation(args.enable_fragmentation) + .completion_ring_size(args.completion_ring_size) + .fill_ring_size(args.fill_ring_size) + .frame_size(args.frame_size) + .busy_poll(args.busy_poll) + .num_frames(args.busy_poll_batch_size) + .huge_tables(args.huge_tables) + .unaligned(args.unaligned) + .build::(&mut xdp_ctx) + .expect("Failed to create receiver"); + + // Always catch SIGINT/SIGTERM to ensure we clean up properly, we have a running XDP program attached to the interface. + // + // Note: In other words its very important to ensure that the Drop impl for XdpContext is run to detach the XDP program from + // the interface, OR manually call detach(). + let exit = Arc::new(AtomicBool::new(false)); + ctrlc::set_handler({ + let exit = exit.clone(); + move || { + exit.store(true, Ordering::Relaxed); + } + }) + .expect("Error setting Ctrl-C handler"); + + println!("Socket created, listening for packets..."); + + // Loop forever reading packets from the socket. + while !exit.load(Ordering::Relaxed) { + if let Ok(_) = reciever.echo(|frames| handler::(frames, &mut stats)) { + stats.maybe_print(); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 307b234..148fd10 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,2 @@ +pub mod raw; pub mod xdp; diff --git a/src/raw/mod.rs b/src/raw/mod.rs new file mode 100644 index 0000000..61b8938 --- /dev/null +++ b/src/raw/mod.rs @@ -0,0 +1,7 @@ +mod receiver; +mod sender; +mod transceiver; + +pub use receiver::{RawReceiver, RawReceiverBuilder}; +pub use sender::{RawSender, RawSenderBuilder}; +pub use transceiver::{RawTransceiver, RawTransceiverBuilder}; diff --git a/src/raw/receiver.rs b/src/raw/receiver.rs new file mode 100644 index 0000000..c5ce12f --- /dev/null +++ b/src/raw/receiver.rs @@ -0,0 +1,123 @@ +use crate::xdp::{ + context::XdpContext, + error::{NonBlocking, Result}, + frame::{Frame, FrameBuffer}, + socket::{CopyMode, Socket, SocketBuilder}, + umem::{Umem, UmemBuilder}, +}; + +pub struct RawReceiverBuilder<'if_name> { + umem_builder: UmemBuilder, + socket_builder: SocketBuilder<'if_name>, + num_frames: usize, +} + +impl<'if_name> RawReceiverBuilder<'if_name> { + pub(crate) fn new(if_name: &'if_name str, queue: u32) -> Self { + let umem_builder = UmemBuilder::new(); + let socket_builder = SocketBuilder::new(if_name, queue); + Self { + umem_builder, + socket_builder, + num_frames: 4096, + } + } + + pub fn frame_size(mut self, frame_size: usize) -> Self { + self.umem_builder = self.umem_builder.frame_size(frame_size); + self + } + + pub fn num_frames(mut self, num_frames: usize) -> Self { + self.num_frames = num_frames; + self + } + + pub fn huge_tables(mut self, huge_tables: bool) -> Self { + self.umem_builder = self.umem_builder.huge_tables(huge_tables); + self + } + + pub fn unaligned(mut self, unaligned: bool) -> Self { + self.umem_builder = self.umem_builder.unaligned(unaligned); + self + } + + pub fn busy_poll(mut self, busy_poll: bool) -> Self { + self.umem_builder = self.umem_builder.busy_poll(busy_poll); + self.socket_builder = self.socket_builder.busy_poll(busy_poll); + self + } + + pub fn busy_poll_batch_size(mut self, busy_poll_batch_size: usize) -> Self { + self.socket_builder = self + .socket_builder + .busy_poll_batch_size(busy_poll_batch_size); + self + } + + pub fn busy_poll_timeout_us(mut self, busy_poll_timeout_us: i32) -> Self { + self.socket_builder = self + .socket_builder + .busy_poll_timeout_us(busy_poll_timeout_us); + self + } + + pub fn copy_mode(mut self, copy_mode: CopyMode) -> Self { + self.socket_builder = self.socket_builder.copy_mode(copy_mode); + self + } + + pub fn enable_fragmentation(mut self, enable_fragmentation: bool) -> Self { + self.socket_builder = self + .socket_builder + .enable_fragmentation(enable_fragmentation); + self + } + + pub fn build<'umem, T: FrameBuffer<'umem> + FromIterator>>( + self, + ctx: &mut XdpContext, + ) -> Result> { + let umem = self + .umem_builder + .num_frames(self.num_frames) + .fill_ring_size(self.num_frames as u32) + .build()?; + let socket = self + .socket_builder + .rx_ring_size(self.num_frames as u32) + .build(ctx, umem.owner().clone())?; + Ok(RawReceiver::new(umem, socket)) + } +} + +pub struct RawReceiver<'umem, T: FrameBuffer<'umem>> { + umem: Umem<'umem>, + buffer: T, + socket: Socket<'umem>, +} + +impl<'umem, T: FrameBuffer<'umem> + FromIterator>> RawReceiver<'umem, T> { + pub fn builder(if_name: &str, queue: u32) -> RawReceiverBuilder<'_> { + RawReceiverBuilder::new(if_name, queue) + } + + fn new(umem: Umem<'umem>, socket: Socket<'umem>) -> Self { + let buffer = umem.init_buffer::().unwrap(); + Self { + umem, + buffer, + socket, + } + } + + pub fn recv(&mut self) -> NonBlocking> { + self.umem.maybe_wake_fill_queue(self.socket.fd()).unwrap(); + self.umem.process_fill_queue(&mut self.buffer); + + self.socket.recv(&mut self.buffer)?; + + Ok(self.buffer.iter_frames()) + } +} diff --git a/src/raw/sender.rs b/src/raw/sender.rs new file mode 100644 index 0000000..a535c3c --- /dev/null +++ b/src/raw/sender.rs @@ -0,0 +1,121 @@ +use crate::xdp::{ + context::XdpContext, + error::{NonBlocking, Result}, + frame::{Frame, FrameBuffer}, + socket::{Socket, SocketBuilder}, + umem::{Umem, UmemBuilder}, +}; + +pub struct RawSenderBuilder<'if_name> { + umem_builder: UmemBuilder, + socket_builder: SocketBuilder<'if_name>, + num_frames: usize, +} + +impl<'if_name> RawSenderBuilder<'if_name> { + pub fn new(if_name: &'if_name str, queue: u32) -> Self { + Self { + umem_builder: UmemBuilder::new(), + socket_builder: SocketBuilder::new(if_name, queue), + num_frames: 4096, + } + } + + pub fn frame_size(mut self, frame_size: usize) -> Self { + self.umem_builder = self.umem_builder.frame_size(frame_size); + self + } + + pub fn num_frames(mut self, num_frames: usize) -> Self { + self.num_frames = num_frames; + self + } + + pub fn huge_tables(mut self, huge_tables: bool) -> Self { + self.umem_builder = self.umem_builder.huge_tables(huge_tables); + self + } + + pub fn unaligned(mut self, unaligned: bool) -> Self { + self.umem_builder = self.umem_builder.unaligned(unaligned); + self + } + + pub fn busy_poll(mut self, busy_poll: bool) -> Self { + self.umem_builder = self.umem_builder.busy_poll(busy_poll); + self.socket_builder = self.socket_builder.busy_poll(busy_poll); + self + } + + pub fn busy_poll_batch_size(mut self, busy_poll_batch_size: usize) -> Self { + self.socket_builder = self + .socket_builder + .busy_poll_batch_size(busy_poll_batch_size); + self + } + + pub fn busy_poll_timeout_us(mut self, busy_poll_timeout_us: i32) -> Self { + self.socket_builder = self + .socket_builder + .busy_poll_timeout_us(busy_poll_timeout_us); + self + } + + pub fn build<'umem, T: FrameBuffer<'umem> + FromIterator>>( + self, + ctx: &mut XdpContext, + ) -> Result<(RawSender<'umem>, T)> { + let umem = self + .umem_builder + .num_frames(self.num_frames) + .completion_ring_size(self.num_frames as u32) + .build()?; + let socket = self + .socket_builder + .tx_ring_size(self.num_frames as u32) + .build(ctx, umem.owner().clone())?; + Ok(RawSender::new(umem, socket)) + } +} + +pub struct RawSender<'umem> { + umem: Umem<'umem>, + socket: Socket<'umem>, +} + +impl<'umem> RawSender<'umem> { + pub fn builder(if_name: &str, queue: u32) -> RawSenderBuilder<'_> { + RawSenderBuilder::new(if_name, queue) + } + + fn new + FromIterator>>( + umem: Umem<'umem>, + socket: Socket<'umem>, + ) -> (Self, T) { + let buffer = umem.init_buffer::().unwrap(); + (Self { umem, socket }, buffer) + } + + pub fn send>(&mut self, mut batch: B) -> NonBlocking { + let sent = match self.socket.send(&mut batch) { + Ok(sent) => sent, + e => { + self.socket.maybe_wake().unwrap(); + return e; + } + }; + + let mut processed = 0; + while processed < sent { + processed += match self.umem.process_completion_queue(&mut batch) { + Ok(processed) => processed, + _ => { + self.socket.maybe_wake().unwrap(); + continue; + } + }; + } + + Ok(sent) + } +} diff --git a/src/raw/transceiver.rs b/src/raw/transceiver.rs new file mode 100644 index 0000000..8070b97 --- /dev/null +++ b/src/raw/transceiver.rs @@ -0,0 +1,175 @@ +use crate::xdp::{ + context::XdpContext, + error::{NonBlocking, Result}, + frame::{Frame, FrameBuffer}, + socket::{CopyMode, Socket, SocketBuilder}, + umem::{Umem, UmemBuilder}, +}; + +pub struct RawTransceiverBuilder<'if_name> { + umem_builder: UmemBuilder, + socket_builder: SocketBuilder<'if_name>, +} + +impl<'if_name> RawTransceiverBuilder<'if_name> { + pub fn new(if_name: &'if_name str, queue: u32) -> Self { + Self { + umem_builder: UmemBuilder::new(), + socket_builder: SocketBuilder::new(if_name, queue), + } + } + + /// Sets the size of the completion ring, the maximum number of frames that can be outstanding in the completion ring. + /// + /// Note this value must be a power of two. + pub fn completion_ring_size(mut self, completion_ring_size: u32) -> Self { + self.umem_builder = self.umem_builder.completion_ring_size(completion_ring_size); + self + } + + /// Sets the size of the fill ring, the maximum number of frames that can be outstanding in the fill ring. + /// + /// Note this value must be a power of two. + pub fn fill_ring_size(mut self, fill_ring_size: u32) -> Self { + self.umem_builder = self.umem_builder.fill_ring_size(fill_ring_size); + self + } + + /// Sets the size of the frame, the size of each frame in the UMEM. + /// + /// Note this value must be a power of two. + pub fn frame_size(mut self, frame_size: usize) -> Self { + self.umem_builder = self.umem_builder.frame_size(frame_size); + self + } + + /// Sets the total number of frames in the UMEM. + /// + /// Note this value is optional, if not set it will be calculated as the sum of the completion and fill ring sizes. + pub fn num_frames(mut self, num_frames: usize) -> Self { + self.umem_builder = self.umem_builder.num_frames(num_frames); + self + } + + /// Sets whether to use huge tables for the UMEM. + pub fn huge_tables(mut self, huge_tables: bool) -> Self { + self.umem_builder = self.umem_builder.huge_tables(huge_tables); + self + } + + /// Sets whether to use unaligned chunks for the UMEM. + pub fn unaligned(mut self, unaligned: bool) -> Self { + self.umem_builder = self.umem_builder.unaligned(unaligned); + self + } + + /// Sets the size of the RX ring, the maximum number of frames that can be outstanding RX at a time in the kernel. + /// + /// Note: This value can be any power of two that fits in a u32, however note that the device driver has a limited number of RX descriptors available. That descriptor limit is effectively + /// the upper bound of this value, any value greater will work as intended but you will not get any more throughput. + pub fn rx_ring_size(mut self, rx_ring_size: u32) -> Self { + self.socket_builder = self.socket_builder.rx_ring_size(rx_ring_size); + self + } + + /// Sets the size of the TX ring, the maximum number of frames that can be outstanding TX at a time in the kernel. + /// + /// Note: This value can be any power of two that fits in a u32, however note that the device driver has a limited number of TX descriptors available. That limit is effectively + /// the upper bound of this value, any value greater will work as intended but you will not get any more throughput. + pub fn tx_ring_size(mut self, tx_ring_size: u32) -> Self { + self.socket_builder = self.socket_builder.tx_ring_size(tx_ring_size); + self + } + + /// Sets whether to use busy polling for the socket. + pub fn busy_poll(mut self, busy_poll: bool) -> Self { + self.umem_builder = self.umem_builder.busy_poll(busy_poll); + self.socket_builder = self.socket_builder.busy_poll(busy_poll); + self + } + + /// Sets the busy poll batch size, this is the maximum number of frames to wait for while busy polling. + pub fn busy_poll_batch_size(mut self, busy_poll_batch_size: usize) -> Self { + self.socket_builder = self + .socket_builder + .busy_poll_batch_size(busy_poll_batch_size); + self + } + + /// Sets the busy poll timeout in microseconds, this is the maximum time to wait for a frame while busy polling. + pub fn busy_poll_timeout_us(mut self, busy_poll_timeout_us: i32) -> Self { + self.socket_builder = self + .socket_builder + .busy_poll_timeout_us(busy_poll_timeout_us); + self + } + + /// Sets the copy mode, this is the mode to use for copying packets to/from the socket. + pub fn copy_mode(mut self, copy_mode: CopyMode) -> Self { + self.socket_builder = self.socket_builder.copy_mode(copy_mode); + self + } + + /// Sets whether to enable fragmentation, this is the mode to use for copying packets to/from the socket. + pub fn enable_fragmentation(mut self, enable_fragmentation: bool) -> Self { + self.socket_builder = self + .socket_builder + .enable_fragmentation(enable_fragmentation); + self + } + + pub fn build<'umem, T: FrameBuffer<'umem> + FromIterator>>( + self, + ctx: &mut XdpContext, + ) -> Result> { + let umem = self.umem_builder.build()?; + let socket = self.socket_builder.build(ctx, umem.owner().clone())?; + Ok(RawTransceiver::new(umem, socket)) + } +} + +pub struct RawTransceiver<'umem, T: FrameBuffer<'umem> + FromIterator>> { + umem: Umem<'umem>, + socket: Socket<'umem>, + buffer: T, +} + +impl<'umem, T: FrameBuffer<'umem> + FromIterator>> RawTransceiver<'umem, T> { + pub fn builder(if_name: &str, queue: u32) -> RawTransceiverBuilder<'_> { + RawTransceiverBuilder::new(if_name, queue) + } + + fn new(umem: Umem<'umem>, socket: Socket<'umem>) -> Self { + let buffer = umem.init_buffer::().unwrap(); + Self { + umem, + socket, + buffer, + } + } + + pub fn echo(&mut self, f: F) -> NonBlocking<()> + where + F: FnOnce(T::IterMut<'_>), + { + self.umem.maybe_wake_fill_queue(self.socket.fd()).unwrap(); + self.umem.process_fill_queue(&mut self.buffer); + + self.socket.recv(&mut self.buffer)?; + + f(self.buffer.iter_frames_mut()); + + while let Err(_) = self.socket.send(&mut self.buffer) { + self.socket.maybe_wake().unwrap(); + } + + while let Err(_) = self.umem.process_completion_queue(&mut self.buffer) { + self.socket.maybe_wake().unwrap(); + } + + self.umem.maybe_wake_fill_queue(self.socket.fd()).unwrap(); + self.umem.process_fill_queue(&mut self.buffer); + + Ok(()) + } +} diff --git a/src/xdp/ring/consumer.rs b/src/xdp/ring/consumer.rs index eb28d7a..a4a6bc4 100644 --- a/src/xdp/ring/consumer.rs +++ b/src/xdp/ring/consumer.rs @@ -96,12 +96,7 @@ impl Consumer { pub fn nb_avail(&mut self, batch_size: u32) -> u32 { let mut entries = self.ring.cached_prod - self.ring.cached_cons; - // TODO(csaide): This feels like a bug, but I'm not sure if its a bug or just my misunderstanding here. - // without this change i.e. checking if entries < batch_size, we end up with _very_ small numbers per batch, generally 1-2. - // This feels completely wrong and this works just fine, but I am reaching out to the libxdp authors to confirm. - // - // if entries == 0 { - if entries < batch_size { + if entries == 0 { self.ring.cached_prod = unsafe { AtomicU32::from_ptr(self.ring.producer) }.load(Ordering::Acquire); entries = self.ring.cached_prod - self.ring.cached_cons;