Skip to main content

datagram_socket/
mmsg.rs

1// Copyright (C) 2025, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//
11//     * Redistributions in binary form must reproduce the above copyright
12//       notice, this list of conditions and the following disclaimer in the
13//       documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27use std::io;
28use std::os::fd::AsRawFd;
29use std::os::fd::BorrowedFd;
30
31use smallvec::SmallVec;
32use tokio::io::ReadBuf;
33
34/// Maximum number of messages passed to a single `sendmmsg(2)` or
35/// `recvmmsg(2)` call.
36pub const MAX_MMSG: usize = 16;
37
38pub fn recvmmsg(fd: BorrowedFd, bufs: &mut [ReadBuf<'_>]) -> io::Result<usize> {
39    let mut msgvec: SmallVec<[libc::mmsghdr; MAX_MMSG]> = SmallVec::new();
40    let mut iovecs: SmallVec<[libc::iovec; MAX_MMSG]> = SmallVec::new();
41
42    let mut ret = 0;
43
44    for bufs in bufs.chunks_mut(MAX_MMSG) {
45        msgvec.clear();
46        iovecs.clear();
47
48        for buf in bufs.iter_mut() {
49            // Safety: the kernel only writes to, and never de-initializes,
50            // the unfilled region.
51            let unfilled = unsafe { buf.unfilled_mut() };
52
53            iovecs.push(libc::iovec {
54                iov_base: unfilled.as_mut_ptr().cast(),
55                iov_len: unfilled.len(),
56            });
57        }
58
59        // Only take pointers into `iovecs` once it is fully populated, as
60        // pushing to it would invalidate them.
61        for (buf, iov) in bufs.iter().zip(iovecs.iter_mut()) {
62            msgvec.push(libc::mmsghdr {
63                msg_hdr: libc::msghdr {
64                    msg_name: std::ptr::null_mut(),
65                    msg_namelen: 0,
66                    msg_iov: iov,
67                    msg_iovlen: 1,
68                    msg_control: std::ptr::null_mut(),
69                    msg_controllen: 0,
70                    msg_flags: 0,
71                },
72                msg_len: buf.capacity().try_into().unwrap(),
73            });
74        }
75
76        // SAFETY: `slices` and `msgvec` are `SmallVec`s with inline capacity
77        // `MAX_MMSG`, and each chunk has at most `MAX_MMSG` elements, so the
78        // pushes above cannot trigger a reallocation that would invalidate
79        // the pointers into `slices` taken by `msg_iov` above. Neither
80        // vector is modified before the syscall returns, and `fd` remains
81        // valid for the duration of the call.
82        let result = unsafe {
83            libc::recvmmsg(
84                fd.as_raw_fd(),
85                msgvec.as_mut_ptr(),
86                msgvec.len() as _,
87                0,
88                std::ptr::null_mut(),
89            )
90        };
91
92        if result == -1 {
93            break;
94        }
95
96        for i in 0..result as usize {
97            let filled = msgvec[i].msg_len as usize;
98            unsafe { bufs[i].assume_init(filled) };
99            bufs[i].advance(filled);
100            ret += 1;
101        }
102
103        if (result as usize) < MAX_MMSG {
104            break;
105        }
106    }
107
108    if ret == 0 {
109        return Err(io::Error::last_os_error());
110    }
111
112    Ok(ret)
113}
114
115/// Sends multiple datagrams with a single system call where possible.
116///
117/// The returned value is the number of datagrams sent.
118pub fn sendmmsg(fd: BorrowedFd, bufs: &[ReadBuf<'_>]) -> io::Result<usize> {
119    sendmmsg_impl(fd, bufs, None)
120}
121
122/// Sends multiple datagrams, appending the same suffix to every datagram.
123///
124/// This is useful for situations where the datagrams carry UDP payloads, which
125/// can create an ambiguous situation: an empty UDP payload is valid in a UDP IP
126/// packet, but reading 0 bytes from a datagram socket is also a signal of a
127/// closed socket. So the suffix can add a marker to every payload to tell those
128/// cases apart, and this allows to send that efficiently.
129///
130/// The returned value is the number of datagrams sent. The suffix is framing
131/// supplied on the caller's behalf and does not affect that count.
132pub fn sendmmsg_with_suffix(
133    fd: BorrowedFd, bufs: &[ReadBuf<'_>], suffix: &[u8],
134) -> io::Result<usize> {
135    sendmmsg_impl(fd, bufs, Some(suffix))
136}
137
138fn sendmmsg_impl(
139    fd: BorrowedFd, bufs: &[ReadBuf<'_>], suffix: Option<&[u8]>,
140) -> io::Result<usize> {
141    if bufs.is_empty() {
142        return Ok(0);
143    }
144
145    let mut msgvec: SmallVec<[libc::mmsghdr; MAX_MMSG]> = SmallVec::new();
146    let mut iovecs: SmallVec<[libc::iovec; 2 * MAX_MMSG]> = SmallVec::new();
147
148    let mut ret = 0;
149
150    for bufs in bufs.chunks(MAX_MMSG) {
151        msgvec.clear();
152        iovecs.clear();
153
154        for buf in bufs {
155            iovecs.push(iovec(buf.filled()));
156
157            if let Some(suffix) = suffix {
158                iovecs.push(iovec(suffix));
159            }
160        }
161
162        // Populate all iovecs before taking pointers into the vector. This
163        // ensures none of the pointers can be invalidated by a reallocation.
164        let iovecs_per_message = if suffix.is_some() { 2 } else { 1 };
165        for message_iovecs in iovecs.chunks_exact_mut(iovecs_per_message) {
166            msgvec.push(libc::mmsghdr {
167                msg_hdr: libc::msghdr {
168                    msg_name: std::ptr::null_mut(),
169                    msg_namelen: 0,
170                    msg_iov: message_iovecs.as_mut_ptr(),
171                    msg_iovlen: iovecs_per_message as _,
172                    msg_control: std::ptr::null_mut(),
173                    msg_controllen: 0,
174                    msg_flags: 0,
175                },
176                // Output field populated by the kernel.
177                msg_len: 0,
178            });
179        }
180
181        // SAFETY: `iovecs` was fully populated before the pointers in
182        // `msgvec` were created, and neither vector is modified before the
183        // syscall returns. Each header points to one or two live iovecs, and
184        // `fd` remains valid for the duration of the call.
185        let result = unsafe {
186            libc::sendmmsg(
187                fd.as_raw_fd(),
188                msgvec.as_mut_ptr(),
189                msgvec.len() as _,
190                0,
191            )
192        };
193
194        if result == -1 {
195            let err = io::Error::last_os_error();
196
197            if ret == 0 {
198                return Err(err);
199            }
200
201            break;
202        }
203
204        ret += result as usize;
205
206        if (result as usize) < bufs.len() {
207            break;
208        }
209    }
210
211    Ok(ret)
212}
213
214fn iovec(buf: &[u8]) -> libc::iovec {
215    libc::iovec {
216        // `sendmmsg(2)` does not mutate the memory described by an iovec, but
217        // the C API represents the pointer as mutable.
218        iov_base: buf.as_ptr().cast_mut().cast(),
219        iov_len: buf.len(),
220    }
221}
222
223#[macro_export]
224macro_rules! poll_recvmmsg {
225    ($self: expr, $cx: ident, $bufs: ident) => {
226        loop {
227            match $self.poll_recv_ready($cx)? {
228                Poll::Ready(()) => {
229                    match $self.try_io(tokio::io::Interest::READABLE, || {
230                        $crate::mmsg::recvmmsg($self.as_fd(), $bufs)
231                    }) {
232                        Err(err) if err.kind() == io::ErrorKind::WouldBlock => {}  // Have to poll for recv ready
233                        res => break Poll::Ready(res),
234                    }
235                }
236                Poll::Pending => break Poll::Pending,
237            }
238        }
239    };
240}
241
242#[macro_export]
243macro_rules! poll_sendmmsg {
244    ($self: expr, $cx: ident, $bufs: ident) => {
245        loop {
246            match $self.poll_send_ready($cx)? {
247                Poll::Ready(()) => {
248                    match $self.try_io(tokio::io::Interest::WRITABLE, || {
249                        $crate::mmsg::sendmmsg($self.as_fd(), $bufs)
250                    }) {
251                        Err(err) if err.kind() == io::ErrorKind::WouldBlock => {} // Have to poll for send ready
252                        res => break Poll::Ready(res),
253                    }
254                }
255                Poll::Pending => break Poll::Pending,
256            }
257        }
258    };
259}
260
261#[cfg(test)]
262mod tests {
263    use std::io;
264    use std::os::fd::AsFd;
265
266    use tokio::io::ReadBuf;
267    use tokio::net::UnixDatagram;
268
269    use super::sendmmsg;
270    use super::sendmmsg_with_suffix;
271    use super::MAX_MMSG;
272    use crate::DatagramSocketRecvExt;
273    use crate::DatagramSocketSendExt;
274
275    #[tokio::test]
276    async fn recvmmsg() -> io::Result<()> {
277        let (s, mut r) = UnixDatagram::pair()?;
278        let mut bufs = [[0u8; 128]; 128];
279
280        for i in 0..5 {
281            s.send(&[i; 128]).await?;
282        }
283
284        let mut rbufs: Vec<_> =
285            bufs.iter_mut().map(|s| ReadBuf::new(&mut s[..])).collect();
286        assert_eq!(r.recv_many(&mut rbufs).await?, 5);
287
288        for (i, buf) in rbufs[0..5].iter().enumerate() {
289            assert_eq!(buf.filled(), &[i as u8; 128]);
290        }
291
292        for i in 0..92 {
293            s.send(&[i; 128]).await?;
294        }
295
296        let mut rbufs: Vec<_> =
297            bufs.iter_mut().map(|s| ReadBuf::new(&mut s[..])).collect();
298        assert_eq!(r.recv_many(&mut rbufs).await?, 92);
299
300        for (i, buf) in rbufs[0..92].iter().enumerate() {
301            assert_eq!(buf.filled(), &[i as u8; 128]);
302        }
303
304        Ok(())
305    }
306
307    #[tokio::test]
308    async fn send_many() -> io::Result<()> {
309        let (s, r) = UnixDatagram::pair()?;
310        let mut bufs: [_; 128] = std::array::from_fn(|i| [i as u8; 128]);
311
312        let wbufs: Vec<_> = bufs
313            .iter_mut()
314            .map(|s| {
315                let mut b = ReadBuf::new(&mut s[..]);
316                b.set_filled(128);
317                b
318            })
319            .collect();
320
321        assert_eq!(s.send_many(&wbufs[..5]).await?, 5);
322
323        let mut rbuf = [0u8; 128];
324
325        for i in 0..5 {
326            assert_eq!(r.recv(&mut rbuf).await?, 128);
327            assert_eq!(rbuf, [i as u8; 128]);
328        }
329
330        Ok(())
331    }
332
333    #[tokio::test]
334    async fn sendmmsg_with_suffix_appends_to_every_datagram() -> io::Result<()> {
335        let (s, r) = UnixDatagram::pair()?;
336        let suffix = b"-suffix";
337        let mut payloads: Vec<Vec<u8>> =
338            (0..MAX_MMSG + 4).map(|i| vec![i as u8; i]).collect();
339        let bufs: Vec<_> = payloads
340            .iter_mut()
341            .map(|payload| {
342                let len = payload.len();
343                let mut buf = ReadBuf::new(payload);
344                buf.set_filled(len);
345                buf
346            })
347            .collect();
348
349        assert_eq!(
350            sendmmsg_with_suffix(s.as_fd(), &bufs, suffix)?,
351            payloads.len()
352        );
353
354        let mut received = vec![0; MAX_MMSG + suffix.len() + 4];
355        for expected in &payloads {
356            let received_len = r.recv(&mut received).await?;
357            assert_eq!(
358                &received[..received_len],
359                [expected.as_slice(), suffix].concat()
360            );
361        }
362
363        Ok(())
364    }
365
366    #[test]
367    fn empty_send_batches_are_noops() -> io::Result<()> {
368        let (s, _r) = std::os::unix::net::UnixDatagram::pair()?;
369
370        assert_eq!(sendmmsg(s.as_fd(), &[])?, 0);
371        assert_eq!(sendmmsg_with_suffix(s.as_fd(), &[], b"suffix")?, 0);
372
373        Ok(())
374    }
375
376    #[test]
377    fn sendmmsg_with_suffix_reports_an_error_without_progress() -> io::Result<()>
378    {
379        let (s, r) = std::os::unix::net::UnixDatagram::pair()?;
380        drop(r);
381
382        let mut payload = *b"payload";
383        let payload_len = payload.len();
384        let mut buf = ReadBuf::new(&mut payload);
385        buf.set_filled(payload_len);
386
387        assert!(sendmmsg_with_suffix(s.as_fd(), &[buf], b"suffix").is_err());
388
389        Ok(())
390    }
391}