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}