Skip to main content

agave_xdp/
socket.rs

1use {
2    crate::{
3        device::{
4            DeviceQueue, RingConsumer, RingMmap, RingProducer, RxFillRing, TxCompletionRing,
5            XdpDesc, mmap_ring,
6        },
7        umem::{Frame, Umem},
8    },
9    libc::{
10        AF_XDP, SOCK_RAW, SOL_XDP, XDP_COPY, XDP_MMAP_OFFSETS, XDP_PGOFF_RX_RING,
11        XDP_PGOFF_TX_RING, XDP_RING_NEED_WAKEUP, XDP_RX_RING, XDP_TX_RING,
12        XDP_UMEM_COMPLETION_RING, XDP_UMEM_FILL_RING, XDP_UMEM_PGOFF_COMPLETION_RING,
13        XDP_UMEM_PGOFF_FILL_RING, XDP_USE_NEED_WAKEUP, XDP_ZEROCOPY, bind, getsockopt, sa_family_t,
14        sendto, setsockopt, sockaddr, sockaddr_xdp, socket, socklen_t, xdp_mmap_offsets,
15        xdp_umem_reg,
16    },
17    std::{
18        io,
19        marker::PhantomData,
20        mem,
21        os::fd::{AsFd, AsRawFd as _, BorrowedFd, FromRawFd as _, OwnedFd, RawFd},
22        ptr,
23        sync::atomic::Ordering,
24    },
25};
26
27pub struct Socket<U: Umem> {
28    fd: OwnedFd,
29    dev_queue: DeviceQueue,
30    umem: U,
31}
32
33impl<U: Umem> Socket<U> {
34    #[allow(clippy::type_complexity)]
35    pub fn new(
36        dev_queue: DeviceQueue,
37        mut umem: U,
38        zero_copy: bool,
39        rx_fill_ring_size: usize,
40        rx_ring_size: usize,
41        tx_completion_ring_size: usize,
42        tx_ring_size: usize,
43    ) -> Result<(Self, Rx<U::Frame>, Tx<U::Frame>), io::Error> {
44        unsafe {
45            let fd = socket(AF_XDP, SOCK_RAW, 0);
46            if fd < 0 {
47                return Err(Error::syscall(
48                    "socket(AF_XDP, SOCK_RAW) failed",
49                    io::Error::last_os_error(),
50                )
51                .into());
52            }
53            let fd = OwnedFd::from_raw_fd(fd);
54
55            let reg = xdp_umem_reg {
56                addr: umem.as_ptr() as u64,
57                len: umem.len() as u64,
58                chunk_size: umem.frame_size() as u32,
59                headroom: 0,
60                flags: 0,
61                tx_metadata_len: 0,
62            };
63
64            if setsockopt(
65                fd.as_raw_fd(),
66                libc::SOL_XDP,
67                libc::XDP_UMEM_REG,
68                &reg as *const _ as *const libc::c_void,
69                mem::size_of::<xdp_umem_reg>() as libc::socklen_t,
70            ) < 0
71            {
72                return Err(Error::syscall(
73                    "setsockopt(XDP_UMEM_REG) failed",
74                    io::Error::last_os_error(),
75                )
76                .into());
77            }
78
79            for (ring, size) in [
80                (XDP_UMEM_COMPLETION_RING, tx_completion_ring_size),
81                (XDP_UMEM_FILL_RING, rx_fill_ring_size),
82                (XDP_TX_RING, tx_ring_size),
83                (XDP_RX_RING, rx_ring_size),
84            ] {
85                if ring == XDP_RX_RING && size == 0 {
86                    // tx only
87                    continue;
88                }
89
90                if setsockopt(
91                    fd.as_raw_fd(),
92                    SOL_XDP,
93                    ring,
94                    &size as *const _ as *const libc::c_void,
95                    mem::size_of::<u32>() as socklen_t,
96                ) < 0
97                {
98                    return Err(Error::syscall(
99                        format!("setsockopt(SOL_XDP, ring={ring}, size={size}) failed",),
100                        io::Error::last_os_error(),
101                    )
102                    .into());
103                }
104            }
105
106            let mut offsets: xdp_mmap_offsets = mem::zeroed();
107            let mut optlen = mem::size_of::<xdp_mmap_offsets>() as socklen_t;
108            if getsockopt(
109                fd.as_raw_fd(),
110                SOL_XDP,
111                XDP_MMAP_OFFSETS,
112                &mut offsets as *mut _ as *mut libc::c_void,
113                &mut optlen,
114            ) < 0
115            {
116                return Err(Error::syscall(
117                    "getsockopt(XDP_MMAP_OFFSETS) failed",
118                    io::Error::last_os_error(),
119                )
120                .into());
121            }
122
123            let tx_completion_ring = TxCompletionRing::new(
124                mmap_ring(
125                    fd.as_raw_fd(),
126                    tx_completion_ring_size.saturating_mul(mem::size_of::<u64>()),
127                    &offsets.cr,
128                    XDP_UMEM_PGOFF_COMPLETION_RING,
129                )
130                .map_err(|source| Error::syscall("mmap completion ring failed", source))?,
131                tx_completion_ring_size as u32,
132            );
133
134            let mut rx_fill_ring = RxFillRing::new(
135                mmap_ring(
136                    fd.as_raw_fd(),
137                    rx_fill_ring_size.saturating_mul(mem::size_of::<u64>()),
138                    &offsets.fr,
139                    XDP_UMEM_PGOFF_FILL_RING,
140                )
141                .map_err(|source| Error::syscall("mmap fill ring failed", source))?,
142                rx_fill_ring_size as u32,
143                fd.as_raw_fd(),
144            );
145
146            if zero_copy {
147                // most drivers (intel) are buggy if ZC is enabled and the fill ring is not
148                // pre-populated before calling bind()
149                for _ in 0..rx_fill_ring_size {
150                    let Some(frame) = umem.reserve() else {
151                        return Err(Error::InsufficientUmemFrames {
152                            required: rx_fill_ring_size,
153                            available: umem.available(),
154                        }
155                        .into());
156                    };
157                    rx_fill_ring
158                        .write(frame)
159                        .map_err(|source| Error::syscall("RX fill ring write failed", source))?;
160                }
161                rx_fill_ring.commit();
162            }
163
164            let tx_ring = Some(TxRing::new(
165                mmap_ring(
166                    fd.as_raw_fd(),
167                    tx_ring_size.saturating_mul(mem::size_of::<XdpDesc>()),
168                    &offsets.tx,
169                    XDP_PGOFF_TX_RING as u64,
170                )
171                .map_err(|source| Error::syscall("mmap tx ring failed", source))?,
172                tx_ring_size as u32,
173                fd.as_raw_fd(),
174            ));
175
176            let rx_ring = if rx_ring_size > 0 {
177                Some(RxRing::new(
178                    mmap_ring(
179                        fd.as_raw_fd(),
180                        rx_ring_size.saturating_mul(mem::size_of::<XdpDesc>()),
181                        &offsets.rx,
182                        XDP_PGOFF_RX_RING as u64,
183                    )
184                    .map_err(|source| Error::syscall("mmap rx ring failed", source))?,
185                    rx_ring_size as u32,
186                    fd.as_raw_fd(),
187                ))
188            } else {
189                None
190            };
191
192            let sxdp = sockaddr_xdp {
193                sxdp_family: AF_XDP as sa_family_t,
194                // do NEED_WAKEUP and don't do zero copy for now for maximum compatibility
195                sxdp_flags: XDP_USE_NEED_WAKEUP | if zero_copy { XDP_ZEROCOPY } else { XDP_COPY },
196                sxdp_ifindex: dev_queue.if_index(),
197                sxdp_queue_id: dev_queue.id().0 as u32,
198                sxdp_shared_umem_fd: 0,
199            };
200
201            if bind(
202                fd.as_raw_fd(),
203                &sxdp as *const _ as *const sockaddr,
204                mem::size_of::<sockaddr_xdp>() as socklen_t,
205            ) < 0
206            {
207                return Err(Error::syscall(
208                    format!(
209                        "bind(AF_XDP, ifindex={}, queue={}, flags=0x{:x}) failed",
210                        sxdp.sxdp_ifindex, sxdp.sxdp_queue_id, sxdp.sxdp_flags
211                    ),
212                    io::Error::last_os_error(),
213                )
214                .into());
215            }
216
217            let tx = Tx {
218                completion: tx_completion_ring,
219                ring: tx_ring,
220            };
221            let rx = Rx {
222                fill: rx_fill_ring,
223                ring: rx_ring,
224            };
225            Ok((
226                Self {
227                    fd,
228                    dev_queue,
229                    umem,
230                },
231                rx,
232                tx,
233            ))
234        }
235    }
236
237    pub fn tx(
238        queue: DeviceQueue,
239        umem: U,
240        zero_copy: bool,
241        completion_size: usize,
242        ring_size: usize,
243    ) -> Result<(Self, Tx<U::Frame>), io::Error> {
244        let (fill_size, rx_size) = if zero_copy {
245            // See Socket::new() as to why this is needed
246            let rx = queue
247                .ring_sizes()
248                .ok_or_else(|| io::Error::other("zero copy requires a set ring size"))?
249                .rx;
250            (rx, rx)
251        } else {
252            // no RX fill ring needed for TX only sockets
253            (1, 0)
254        };
255        let (socket, _, tx) = Self::new(
256            queue,
257            umem,
258            zero_copy,
259            fill_size,
260            rx_size,
261            completion_size,
262            ring_size,
263        )?;
264        Ok((socket, tx))
265    }
266
267    pub fn rx(
268        queue: DeviceQueue,
269        umem: U,
270        zero_copy: bool,
271        fill_size: usize,
272        ring_size: usize,
273    ) -> Result<(Self, Rx<U::Frame>), io::Error> {
274        let (socket, rx, _) = Self::new(queue, umem, zero_copy, fill_size, ring_size, 0, 0)?;
275        Ok((socket, rx))
276    }
277
278    pub fn queue(&self) -> &DeviceQueue {
279        &self.dev_queue
280    }
281
282    pub fn umem(&mut self) -> &mut U {
283        &mut self.umem
284    }
285}
286
287impl<U: Umem> AsFd for Socket<U> {
288    fn as_fd(&self) -> BorrowedFd<'_> {
289        self.fd.as_fd()
290    }
291}
292
293pub struct Tx<F: Frame> {
294    pub completion: TxCompletionRing,
295    pub ring: Option<TxRing<F>>,
296}
297
298pub struct Rx<F: Frame> {
299    pub fill: RxFillRing<F>,
300    pub ring: Option<RxRing>,
301}
302
303pub struct TxRing<F: Frame> {
304    mmap: RingMmap<XdpDesc>,
305    producer: RingProducer,
306    size: u32,
307    fd: RawFd,
308    _frame: PhantomData<F>,
309}
310
311#[derive(Debug)]
312pub struct RingFull<F: Frame>(pub F);
313
314impl<F: Frame> TxRing<F> {
315    fn new(mmap: RingMmap<XdpDesc>, size: u32, fd: RawFd) -> Self {
316        debug_assert!(size.is_power_of_two());
317        Self {
318            producer: RingProducer::new(mmap.producer, mmap.consumer, size),
319            mmap,
320            size,
321            fd,
322            _frame: PhantomData,
323        }
324    }
325
326    pub fn write(&mut self, frame: F, options: u32) -> Result<(), RingFull<F>> {
327        let Some(index) = self.producer.produce() else {
328            return Err(RingFull(frame));
329        };
330        let index = index & self.size.saturating_sub(1);
331        unsafe {
332            let desc = self.mmap.desc.add(index as usize);
333            desc.write(XdpDesc {
334                addr: frame.offset().0 as u64,
335                len: frame.len() as u32,
336                options,
337            });
338        }
339        Ok(())
340    }
341
342    pub fn needs_wakeup(&self) -> bool {
343        unsafe { (*self.mmap.flags).load(Ordering::Relaxed) & XDP_RING_NEED_WAKEUP != 0 }
344    }
345
346    pub fn wake(&self) -> Result<u64, io::Error> {
347        let result = unsafe { sendto(self.fd, ptr::null(), 0, libc::MSG_DONTWAIT, ptr::null(), 0) };
348        if result < 0 {
349            return Err(io::Error::last_os_error());
350        }
351        Ok(result as u64)
352    }
353
354    pub fn capacity(&self) -> usize {
355        self.size as usize
356    }
357
358    pub fn available(&self) -> usize {
359        self.producer.available() as usize
360    }
361
362    pub fn commit(&mut self) {
363        self.producer.commit();
364    }
365
366    pub fn sync(&mut self, commit: bool) {
367        self.producer.sync(commit);
368    }
369}
370
371pub struct RxRing {
372    #[allow(dead_code)]
373    mmap: RingMmap<XdpDesc>,
374    consumer: RingConsumer,
375    size: u32,
376    #[allow(dead_code)]
377    fd: RawFd,
378}
379
380impl RxRing {
381    fn new(mmap: RingMmap<XdpDesc>, size: u32, fd: RawFd) -> Self {
382        debug_assert!(size.is_power_of_two());
383        Self {
384            consumer: RingConsumer::new(mmap.producer, mmap.consumer),
385            mmap,
386            size,
387            fd,
388        }
389    }
390
391    pub fn capacity(&self) -> usize {
392        self.size as usize
393    }
394
395    pub fn available(&self) -> usize {
396        self.consumer.available() as usize
397    }
398
399    pub fn commit(&mut self) {
400        self.consumer.commit();
401    }
402
403    pub fn sync(&mut self, commit: bool) {
404        self.consumer.sync(commit);
405    }
406}
407
408#[derive(Debug, thiserror::Error)]
409enum Error {
410    #[error("{message}: {source}")]
411    Syscall {
412        message: String,
413        #[source]
414        source: io::Error,
415    },
416    #[error(
417        "insufficient UMEM frames for RX fill ring prefill: required={required}, \
418         available={available}"
419    )]
420    InsufficientUmemFrames { required: usize, available: usize },
421}
422
423impl Error {
424    fn syscall(message: impl Into<String>, source: io::Error) -> Self {
425        Self::Syscall {
426            message: message.into(),
427            source,
428        }
429    }
430}
431
432impl From<Error> for io::Error {
433    fn from(error: Error) -> io::Error {
434        io::Error::other(error)
435    }
436}