Skip to main content

bpf_api/collections/
ringbuffer.rs

1use crate::error::{Error, Result};
2use crate::platform::{Map, MapType};
3
4use byteorder::{ByteOrder, NativeEndian};
5
6#[derive(Copy, Clone, Default)]
7struct Void {}
8
9/// A helper class used as a single place to deal with modular arithmetic.
10struct RingMeta {
11    cons_pos: usize,
12    prod_pos: usize,
13    capacity: usize,
14}
15
16impl RingMeta {
17    /// Returns the amount of data left to consume.
18    fn len(&self) -> usize {
19        if self.cons_pos <= self.prod_pos {
20            self.prod_pos - self.cons_pos
21        } else {
22            self.capacity - self.cons_pos + self.prod_pos + 1
23        }
24    }
25
26    /// Returns whether there is data available to consume.
27    fn is_empty(&self) -> bool {
28        self.len() == 0
29    }
30
31    /// Safely gets the consumer position.
32    fn get_cons_pos(&self) -> usize {
33        self.cons_pos % self.capacity
34    }
35
36    /// Advances the consumer position by size, up to the producer position.
37    fn consume(&mut self, mut size: usize) -> usize {
38        if size > self.len() {
39            size = self.len();
40        }
41
42        self.cons_pos = (self.cons_pos + size) % self.capacity;
43        self.cons_pos
44    }
45}
46
47/// An interface for using BPF ringbuffer maps.
48pub struct RingBuffer {
49    capacity: usize,
50    map: Map<Void, Void>,
51}
52
53impl RingBuffer {
54    const PAGE_SIZE: usize = 4096; // this could be different on different platforms.
55    const CONSUMER_OFFSET: usize = 0;
56    const PRODUCER_OFFSET: usize = Self::PAGE_SIZE;
57    const BUFFER_OFFSET: usize = Self::PAGE_SIZE * 2;
58
59    /// Creates a new ring buffer with the given number of pages. The capacity of
60    /// a BPF ring buffer has to be a power of 2 pages. The min value given is
61    /// rounded up to this capacity; `get_capacity` will return the actual allocated
62    /// capacity of the ring buffer.
63    ///
64    /// # Arguments
65    ///
66    /// * `min_capacity` - The minimum capacity of the ringbuffer.
67    ///
68    /// # Example
69    /// ```
70    /// use bpf_api::collections::RingBuffer;
71    ///
72    /// for capacity in [ 1, 4095, 4096, 8191, 8192, 16535, 16536, 40000 ] {
73    ///     let ringbuffer = RingBuffer::with_capacity(capacity).expect("Failed to create ringbuffer");
74    ///     assert!(ringbuffer.get_capacity() >= capacity as usize);
75    /// }
76    ///
77    /// let ringbuffer = RingBuffer::with_capacity(4096).expect("Failed to create ringbuffer");
78    /// assert_eq!(ringbuffer.get_capacity(), 4096);
79    /// ```
80    pub fn with_capacity(min_capacity: u32) -> Result<Self> {
81        let min_capacity: usize = min_capacity.try_into()?;
82        if min_capacity == 0 {
83            return Err(Error::InvalidArgument);
84        }
85
86        // round up to the next power of 2 pages.
87        let pages = (min_capacity + (Self::PAGE_SIZE - 1)) / Self::PAGE_SIZE;
88        let mut capacity = pages - 1;
89        capacity |= capacity >> 1;
90        capacity |= capacity >> 2;
91        capacity |= capacity >> 4;
92        capacity |= capacity >> 8;
93        capacity |= capacity >> 16;
94        capacity += 1;
95        capacity *= Self::PAGE_SIZE;
96
97        let mut map = Map::with_capacity(MapType::RingBuf, capacity.try_into()?)?;
98
99        // These calls premap the underlying ring buffer areas. Map caches the requested
100        // mappings and returns them immediately on subsequent calls. This ensures that 1)
101        // the mappings are successful and 2) calls like len(), get_buf(), etc don't fail.
102        //
103        // The consumer page is mapped twice: once as readable and once as writable so that
104        // calls to functions that don't mutate the constants don't need to take a mutable
105        // reference on self.
106        //
107        // The actual buffer itself is mapped 2x its size; this is an old trick to make dealing
108        // with the boundary area of a ringbuffer easier. ie: you can map the full capacity size
109        // from the start of the buffer til the last byte and get a contiguous VA mapping that
110        // loops back onto itself.
111        map.get_map::<u8>(Self::CONSUMER_OFFSET, Self::PAGE_SIZE)?;
112        map.get_map_mut::<u8>(Self::CONSUMER_OFFSET, Self::PAGE_SIZE)?;
113        map.get_map::<u8>(Self::PRODUCER_OFFSET, Self::PAGE_SIZE)?;
114        map.get_map::<u8>(Self::BUFFER_OFFSET, capacity * 2)?;
115
116        Ok(Self { capacity, map })
117    }
118
119    /// Returns the capacity of the ring buffer.
120    pub fn get_capacity(&self) -> usize {
121        self.capacity
122    }
123
124    /// Returns the amount of data available for reading.
125    ///
126    /// # Example
127    /// ```
128    /// use bpf_api::collections::RingBuffer;
129    ///
130    /// let ringbuffer = RingBuffer::with_capacity(4096).expect("Failed to create ringbuffer");
131    /// assert_eq!(ringbuffer.len(), 0);
132    /// ```
133    pub fn len(&self) -> usize {
134        self.get_meta().len()
135    }
136
137    /// Returns whether the ring buffer is empty or not.
138    ///
139    /// # Example
140    /// ```
141    /// use bpf_api::collections::RingBuffer;
142    ///
143    /// let ringbuffer = RingBuffer::with_capacity(4096).expect("Failed to create ringbuffer");
144    /// assert!(ringbuffer.is_empty());
145    /// ```
146    pub fn is_empty(&self) -> bool {
147        self.get_meta().is_empty()
148    }
149
150    /// Returns a slice that represents the readable range.
151    ///
152    /// # Example
153    /// ```
154    /// use bpf_api::collections::RingBuffer;
155    ///
156    /// let ringbuffer = RingBuffer::with_capacity(4096).expect("Failed to create ringbuffer");
157    /// assert_eq!(ringbuffer.get_buf().len(), 0);
158    /// ```
159    pub fn get_buf(&self) -> &[u8] {
160        // Ring buffers are single producer, single consumer. There's a race here between
161        // reading the metadata and accessing the buffer. However, since there's only 1
162        // reader and 1 writer, the buffer can only get larger between calls.
163        let meta = self.get_meta();
164        let cons_pos = meta.get_cons_pos();
165        let len = meta.len();
166        let buf = self
167            .map
168            .get_map::<u8>(Self::BUFFER_OFFSET, self.capacity * 2)
169            .expect("Failed to map buffer");
170        &buf[cons_pos..cons_pos + len]
171    }
172
173    /// Advances the read position by the given size. If the size is more than the number
174    /// of bytes available, the read position is advanced to the write position, clearing
175    /// the buffer.
176    ///
177    /// # Example
178    /// ```
179    /// use bpf_api::collections::RingBuffer;
180    ///
181    /// let mut ringbuffer = RingBuffer::with_capacity(4096).expect("Failed to create ringbuffer");
182    /// ringbuffer.consume(100);
183    /// ```
184    pub fn consume(&mut self, size: usize) {
185        let mut meta = self.get_meta();
186        let new_pos = meta.consume(size);
187        let cons_buf = self
188            .map
189            .get_map_mut::<u8>(Self::CONSUMER_OFFSET, Self::PAGE_SIZE)
190            .expect("Failed to map buffer");
191        NativeEndian::write_u32(&mut cons_buf[0..4], new_pos as u32);
192    }
193
194    /// Retrieve the BPF identifier for this map. This is the underlying file
195    /// descriptor that's used in eBPF programs.
196    ///
197    /// # Example
198    /// ```
199    /// use bpf_api::collections::RingBuffer;
200    ///
201    /// let ringbuffer = RingBuffer::with_capacity(8).expect("Failed to create ringbuffer");
202    /// ringbuffer.get_identifier();
203    /// ```
204    pub fn get_identifier(&self) -> u32 {
205        self.map.get_identifier()
206    }
207
208    /// Returns meta info about the ring buffer like read/write pos and capacity.
209    fn get_meta(&self) -> RingMeta {
210        let cons_buf = self
211            .map
212            .get_map(Self::CONSUMER_OFFSET, Self::PAGE_SIZE)
213            .expect("Failed to get consumer mapping");
214        let prod_buf = self
215            .map
216            .get_map(Self::PRODUCER_OFFSET, Self::PAGE_SIZE)
217            .expect("Failed to get producer mapping");
218        let cons_pos = NativeEndian::read_u32(&cons_buf[0..4]) as usize;
219        let prod_pos = NativeEndian::read_u32(&prod_buf[0..4]) as usize;
220
221        RingMeta {
222            cons_pos,
223            prod_pos,
224            capacity: self.capacity,
225        }
226    }
227}