Repository: bytecodealliance/rustix (language: rust)

Issue:
Feature request: add missing `RecvFlags` for `recvmsg`
Per [`man 3p recvmsg`](https://www.man7.org/linux/man-pages/man3/recvmsg.3p.html), `recvmsg()` may return the following flags:
>       MSG_EOR     End-of-record was received (if supported by the protocol).
>       MSG_OOB     Out-of-band data was received.
>       MSG_TRUNC   Normal data was truncated.
>       MSG_CTRUNC  Control data was truncated.

Currently `RecvFlags`  contains the flags that may be passed _to_ `recvmsg`, like `OOB` and `TRUNC`, but does not contain the flags which are only received, specifically `EOR` and `CTRUNC`.



Discussion / hints:

Fix patch:
diff --git a/src/backend/libc/net/read_sockaddr.rs b/src/backend/libc/net/read_sockaddr.rs
index d2c9b69e6..eaaec897e 100644
--- a/src/backend/libc/net/read_sockaddr.rs
+++ b/src/backend/libc/net/read_sockaddr.rs
@@ -93,6 +93,18 @@ unsafe fn read_ss_family(storage: *const c::sockaddr_storage) -> u16 {
     (*storage.cast::<sockaddr_header>()).ss_family.into()
 }
 
+/// Read the first byte of the `sun_path` field, assuming we have an `AF_UNIX`
+/// socket address.
+#[cfg(apple)]
+#[inline]
+unsafe fn read_sun_path0(storage: *const c::sockaddr_storage) -> u8 {
+    // In `read_ss_family` we assert that we know the layout of `sockaddr`.
+    storage
+        .cast::<u8>()
+        .add(super::addr::offsetof_sun_path())
+        .read()
+}
+
 /// Set the `ss_family` field of a socket address to `AF_UNSPEC`, so that we
 /// can test for `AF_UNSPEC` to test whether it was stored to.
 pub(crate) unsafe fn initialize_family_to_unspec(storage: *mut c::sockaddr_storage) {
@@ -233,10 +245,17 @@ pub(crate) unsafe fn maybe_read_sockaddr_os(
     assert!(len >= size_of::<c::sa_family_t>());
     let family = read_ss_family(storage).into();
     if family == c::AF_UNSPEC {
-        None
-    } else {
-        Some(inner_read_sockaddr_os(family, storage, len))
+        return None;
     }
+
+    // On macOS, if we get an `AF_UNIX` with an empty path, treat it as
+    // an absent address.
+    #[cfg(apple)]
+    if family == c::AF_UNIX && read_sun_path0(storage) == 0 {
+        return None;
+    }
+
+    Some(inner_read_sockaddr_os(family, storage, len))
 }
 
 /// Read a socket address returned from the OS.
diff --git a/src/backend/libc/net/send_recv.rs b/src/backend/libc/net/send_recv.rs
index fb0740c21..510afd622 100644
--- a/src/backend/libc/net/send_recv.rs
+++ b/src/backend/libc/net/send_recv.rs
@@ -63,6 +63,7 @@ bitflags! {
     #[repr(transparent)]
     #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
     pub struct RecvFlags: u32 {
+        /// `MSG_CMSG_CLOEXEC`
         #[cfg(not(any(
             apple,
             solarish,
@@ -73,7 +74,6 @@ bitflags! {
             target_os = "nto",
             target_os = "vita",
         )))]
-        /// `MSG_CMSG_CLOEXEC`
         const CMSG_CLOEXEC = bitcast!(c::MSG_CMSG_CLOEXEC);
         /// `MSG_DONTWAIT`
         #[cfg(not(windows))]
@@ -96,6 +96,10 @@ bitflags! {
         /// `MSG_PEEK`
         const PEEK = bitcast!(c::MSG_PEEK);
         /// `MSG_TRUNC`
+        // Apple, illumos, and NetBSD have `MSG_TRUNC` but it's not documented
+        // for use with `recv` and friends, and in practice appears to be
+        // ignored.
+        #[cfg(not(any(apple, solarish, target_os = "netbsd")))]
         const TRUNC = bitcast!(c::MSG_TRUNC);
         /// `MSG_WAITALL`
         const WAITALL = bitcast!(c::MSG_WAITALL);
@@ -104,3 +108,34 @@ bitflags! {
         const _ = !0;
     }
 }
+
+bitflags! {
+    /// `MSG_*` flags returned from [`recvmsg`], in the `flags` field of
+    /// [`RecvMsgReturn`]
+    ///
+    /// [`recvmsg`]: crate::net::recvmsg
+    /// [`RecvMsgReturn`]: crate::net::RecvMsgReturn
+    #[repr(transparent)]
+    #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+    pub struct ReturnFlags: u32 {
+        /// `MSG_OOB`
+        const OOB = bitcast!(c::MSG_OOB);
+        /// `MSG_EOR`
+        #[cfg(not(windows))]
+        const EOR = bitcast!(c::MSG_EOR);
+        /// `MSG_TRUNC`
+        const TRUNC = bitcast!(c::MSG_TRUNC);
+        /// `MSG_CTRUNC`
+        const CTRUNC = bitcast!(c::MSG_CTRUNC);
+
+        /// `MSG_CMSG_CLOEXEC`
+        #[cfg(linux_kernel)]
+        const CMSG_CLOEXEC = bitcast!(c::MSG_CMSG_CLOEXEC);
+        /// `MSG_ERRQUEUE`
+        #[cfg(linux_kernel)]
+        const ERRQUEUE = bitcast!(c::MSG_ERRQUEUE);
+
+        /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+        const _ = !0;
+    }
+}
diff --git a/src/backend/libc/net/syscalls.rs b/src/backend/libc/net/syscalls.rs
index 3013f9922..deb12c366 100644
--- a/src/backend/libc/net/syscalls.rs
+++ b/src/backend/libc/net/syscalls.rs
@@ -30,7 +30,7 @@ use {
 #[cfg(not(any(target_os = "redox", target_os = "wasi")))]
 use {
     super::read_sockaddr::{initialize_family_to_unspec, maybe_read_sockaddr_os, read_sockaddr_os},
-    super::send_recv::{RecvFlags, SendFlags},
+    super::send_recv::{RecvFlags, ReturnFlags, SendFlags},
     super::write_sockaddr::{encode_sockaddr_v4, encode_sockaddr_v6},
     crate::net::{AddressFamily, Protocol, Shutdown, SocketFlags, SocketType},
     core::ptr::null_mut,
@@ -344,7 +344,7 @@ pub(crate) fn recvmsg(
             RecvMsgReturn {
                 bytes,
                 address: addr,
-                flags: RecvFlags::from_bits_retain(bitcast!(msghdr.msg_flags)),
+                flags: ReturnFlags::from_bits_retain(bitcast!(msghdr.msg_flags)),
             }
         })
     })
diff --git a/src/backend/linux_raw/c.rs b/src/backend/linux_raw/c.rs
index 92709c17a..d1aa6e0f0 100644
--- a/src/backend/linux_raw/c.rs
+++ b/src/backend/linux_raw/c.rs
@@ -71,12 +71,12 @@ pub(crate) use linux_raw_sys::{
         IPV6_MULTICAST_LOOP, IPV6_RECVTCLASS, IPV6_TCLASS, IPV6_UNICAST_HOPS, IPV6_V6ONLY,
         IP_ADD_MEMBERSHIP, IP_ADD_SOURCE_MEMBERSHIP, IP_DROP_MEMBERSHIP, IP_DROP_SOURCE_MEMBERSHIP,
         IP_FREEBIND, IP_MULTICAST_LOOP, IP_MULTICAST_TTL, IP_RECVTOS, IP_TOS, IP_TTL,
-        MSG_CMSG_CLOEXEC, MSG_CONFIRM, MSG_DONTROUTE, MSG_DONTWAIT, MSG_EOR, MSG_ERRQUEUE,
-        MSG_MORE, MSG_NOSIGNAL, MSG_OOB, MSG_PEEK, MSG_TRUNC, MSG_WAITALL, SCM_CREDENTIALS,
-        SCM_RIGHTS, SHUT_RD, SHUT_RDWR, SHUT_WR, SOCK_DGRAM, SOCK_RAW, SOCK_RDM, SOCK_SEQPACKET,
-        SOCK_STREAM, SOL_SOCKET, SOL_XDP, SO_ACCEPTCONN, SO_BROADCAST, SO_COOKIE, SO_DOMAIN,
-        SO_ERROR, SO_INCOMING_CPU, SO_KEEPALIVE, SO_LINGER, SO_OOBINLINE, SO_ORIGINAL_DST,
-        SO_PASSCRED, SO_PROTOCOL, SO_RCVBUF, SO_RCVBUFFORCE, SO_RCVTIMEO_NEW,
+        MSG_CMSG_CLOEXEC, MSG_CONFIRM, MSG_CTRUNC, MSG_DONTROUTE, MSG_DONTWAIT, MSG_EOR,
+        MSG_ERRQUEUE, MSG_MORE, MSG_NOSIGNAL, MSG_OOB, MSG_PEEK, MSG_TRUNC, MSG_WAITALL,
+        SCM_CREDENTIALS, SCM_RIGHTS, SHUT_RD, SHUT_RDWR, SHUT_WR, SOCK_DGRAM, SOCK_RAW, SOCK_RDM,
+        SOCK_SEQPACKET, SOCK_STREAM, SOL_SOCKET, SOL_XDP, SO_ACCEPTCONN, SO_BROADCAST, SO_COOKIE,
+        SO_DOMAIN, SO_ERROR, SO_INCOMING_CPU, SO_KEEPALIVE, SO_LINGER, SO_OOBINLINE,
+        SO_ORIGINAL_DST, SO_PASSCRED, SO_PROTOCOL, SO_RCVBUF, SO_RCVBUFFORCE, SO_RCVTIMEO_NEW,
         SO_RCVTIMEO_NEW as SO_RCVTIMEO, SO_RCVTIMEO_OLD, SO_REUSEADDR, SO_REUSEPORT, SO_SNDBUF,
         SO_SNDTIMEO_NEW, SO_SNDTIMEO_NEW as SO_SNDTIMEO, SO_SNDTIMEO_OLD, SO_TYPE, TCP_CONGESTION,
         TCP_CORK, TCP_KEEPCNT, TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_NODELAY, TCP_QUICKACK,
diff --git a/src/backend/linux_raw/net/send_recv.rs b/src/backend/linux_raw/net/send_recv.rs
index aa175536a..12a1cfef0 100644
--- a/src/backend/linux_raw/net/send_recv.rs
+++ b/src/backend/linux_raw/net/send_recv.rs
@@ -58,3 +58,30 @@ bitflags! {
         const _ = !0;
     }
 }
+
+bitflags! {
+    /// `MSG_*` flags returned from [`recvmsg`], in the `flags` field of
+    /// [`RecvMsgReturn`]
+    ///
+    /// [`recvmsg`]: crate::net::recvmsg
+    /// [`RecvMsgReturn`]: crate::net::RecvMsgReturn
+    #[repr(transparent)]
+    #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+    pub struct ReturnFlags: u32 {
+        /// `MSG_OOB`
+        const OOB = c::MSG_OOB;
+        /// `MSG_EOR`
+        const EOR = c::MSG_EOR;
+        /// `MSG_TRUNC`
+        const TRUNC = c::MSG_TRUNC;
+        /// `MSG_CTRUNC`
+        const CTRUNC = c::MSG_CTRUNC;
+        /// `MSG_ERRQUEUE`
+        const ERRQUEUE = c::MSG_ERRQUEUE;
+        /// `MSG_CMSG_CLOEXEC`
+        const CMSG_CLOEXEC = c::MSG_CMSG_CLOEXEC;
+
+        /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
+        const _ = !0;
+    }
+}
diff --git a/src/backend/linux_raw/net/syscalls.rs b/src/backend/linux_raw/net/syscalls.rs
index 213ac9c4a..10737643a 100644
--- a/src/backend/linux_raw/net/syscalls.rs
+++ b/src/backend/linux_raw/net/syscalls.rs
@@ -11,7 +11,7 @@ use super::msghdr::{
     with_noaddr_msghdr, with_recv_msghdr, with_unix_msghdr, with_v4_msghdr, with_v6_msghdr,
 };
 use super::read_sockaddr::{initialize_family_to_unspec, maybe_read_sockaddr_os, read_sockaddr_os};
-use super::send_recv::{RecvFlags, SendFlags};
+use super::send_recv::{RecvFlags, ReturnFlags, SendFlags};
 #[cfg(target_os = "linux")]
 use super::write_sockaddr::encode_sockaddr_xdp;
 use super::write_sockaddr::{encode_sockaddr_v4, encode_sockaddr_v6};
@@ -293,7 +293,7 @@ pub(crate) fn recvmsg(
             RecvMsgReturn {
                 bytes,
                 address: addr,
-                flags: RecvFlags::from_bits_retain(msghdr.msg_flags),
+                flags: ReturnFlags::from_bits_retain(msghdr.msg_flags),
             }
         })
     })
diff --git a/src/net/send_recv/mod.rs b/src/net/send_recv/mod.rs
index e10b81db8..2a4d1db35 100644
--- a/src/net/send_recv/mod.rs
+++ b/src/net/send_recv/mod.rs
@@ -13,7 +13,7 @@ use backend::fd::{AsFd, BorrowedFd};
 use core::cmp::min;
 use core::mem::MaybeUninit;
 
-pub use backend::net::send_recv::{RecvFlags, SendFlags};
+pub use backend::net::send_recv::{RecvFlags, ReturnFlags, SendFlags};
 
 #[cfg(not(any(
     windows,
diff --git a/src/net/send_recv/msg.rs b/src/net/send_recv/msg.rs
index 7456e7e86..3e5e6e6c5 100644
--- a/src/net/send_recv/msg.rs
+++ b/src/net/send_recv/msg.rs
@@ -7,7 +7,8 @@ use crate::fd::{AsFd, BorrowedFd, OwnedFd};
 use crate::io::{self, IoSlice, IoSliceMut};
 #[cfg(linux_kernel)]
 use crate::net::UCred;
-
+#[cfg(feature = "std")]
+use core::fmt;
 use core::iter::FusedIterator;
 use core::marker::PhantomData;
 use core::mem::{align_of, size_of, size_of_val, take};
@@ -15,7 +16,7 @@ use core::mem::{align_of, size_of, size_of_val, take};
 use core::ptr::addr_of;
 use core::{ptr, slice};
 
-use super::{RecvFlags, SendFlags, SocketAddrAny, SocketAddrV4, SocketAddrV6};
+use super::{RecvFlags, ReturnFlags, SendFlags, SocketAddrAny, SocketAddrV4, SocketAddrV6};
 
 /// Macro for defining the amount of space to allocate in a buffer for use with
 /// [`RecvAncillaryBuffer::new`] and [`SendAncillaryBuffer::new`].
@@ -819,15 +820,30 @@ pub fn recvmsg<Fd: AsFd>(
 /// The result of a successful [`recvmsg`] call.
 pub struct RecvMsgReturn {
     /// The number of bytes received.
+    ///
+    /// When `RecvFlags::TRUNC` is in use, this may be greater than the
+    /// length of the buffer, as it reflects the number of bytes received
+    /// before truncation into the buffer.
     pub bytes: usize,
 
     /// The flags received.
-    pub flags: RecvFlags,
+    pub flags: ReturnFlags,
 
     /// The address of the socket we received from, if any.
     pub address: Option<SocketAddrAny>,
 }
 
+#[cfg(feature = "std")]
+impl fmt::Debug for RecvMsgReturn {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("RecvMsgReturn")
+            .field("bytes", &self.bytes)
+            .field("flags", &self.flags)
+            .field("address", &self.address)
+            .finish()
+    }
+}
+
 /// An iterator over data in an ancillary buffer.
 pub struct AncillaryIter<'data, T> {
     /// The data we're iterating over.
diff --git a/src/termios/types.rs b/src/termios/types.rs
index ec1d26aee..394c930b4 100644
--- a/src/termios/types.rs
+++ b/src/termios/types.rs
@@ -1211,24 +1211,28 @@ impl core::fmt::Debug for SpecialCodeIndex {
             Self::VKILL => write!(f, "VKILL"),
             #[cfg(not(any(
                 solarish,
+                target_os = "aix",
                 all(linux_kernel, any(target_arch = "sparc", target_arch = "sparc64"))
             )))]
             Self::VEOF => write!(f, "VEOF"),
             #[cfg(not(any(
                 solarish,
+                target_os = "aix",
                 all(linux_kernel, any(target_arch = "sparc", target_arch = "sparc64"))
             )))]
             Self::VTIME => write!(f, "VTIME"),
             #[cfg(not(any(
                 solarish,
+                target_os = "aix",
                 all(linux_kernel, any(target_arch = "sparc", target_arch = "sparc64"))
             )))]
             Self::VMIN => write!(f, "VMIN"),
 
-            // On Solarish platforms, and Linux on SPARC, `VMIN` and `VTIME`
+            // On Solarish platforms, Linux on SPARC, and AIX, `VMIN` and `VTIME`
             // have the same value as `VEOF` and `VEOL`.
             #[cfg(any(
                 solarish,
+                target_os = "aix",
                 all(linux_kernel, any(target_arch = "sparc", target_arch = "sparc64"))
             ))]
             Self::VMIN => write!(f, "VMIN/VEOF"),


Test patch:
diff --git a/tests/net/recv_trunc.rs b/tests/net/recv_trunc.rs
index c5b10468e..3e17c9c93 100644
--- a/tests/net/recv_trunc.rs
+++ b/tests/net/recv_trunc.rs
@@ -1,4 +1,5 @@
-use rustix::net::{AddressFamily, RecvFlags, SendFlags, SocketAddrUnix, SocketType};
+use rustix::io::IoSliceMut;
+use rustix::net::{AddressFamily, RecvFlags, ReturnFlags, SendFlags, SocketAddrUnix, SocketType};
 use std::mem::MaybeUninit;
 
 /// Test `recv_uninit` with the `RecvFlags::Trunc` flag.
@@ -17,13 +18,130 @@ fn net_recv_uninit_trunc() {
     let request = b"Hello, World!!!";
     let n = rustix::net::sendto_unix(&sender, request, SendFlags::empty(), &name).expect("send");
     assert_eq!(n, request.len());
-    drop(sender);
 
+    // Test with `RecvFlags::TRUNC`, which is not supported on Apple, illumos, or NetBSD.
+    #[cfg(not(any(apple, solarish, target_os = "netbsd")))]
+    {
+        let mut response = [MaybeUninit::<u8>::zeroed(); 5];
+        let (init, uninit) = rustix::net::recv_uninit(&receiver, &mut response, RecvFlags::TRUNC)
+            .expect("recv_uninit");
+
+        // We used the `TRUNC` flag, so we should have only gotten 5 bytes.
+        assert_eq!(init, b"Hello");
+        assert!(uninit.is_empty());
+
+        // Send the message again.
+        let n =
+            rustix::net::sendto_unix(&sender, request, SendFlags::empty(), &name).expect("send");
+        assert_eq!(n, request.len());
+    }
+
+    // This time receive it without `TRUNC`. This should fail.
     let mut response = [MaybeUninit::<u8>::zeroed(); 5];
-    let (init, uninit) =
-        rustix::net::recv_uninit(&receiver, &mut response, RecvFlags::TRUNC).expect("recv_uninit");
+    let (init, uninit) = rustix::net::recv_uninit(&receiver, &mut response, RecvFlags::empty())
+        .expect("recv_uninit");
 
-    // We used the `TRUNC` flag, so we should have only gotten 5 bytes.
+    // We didn't use the `TRUNC` flag, so we should have received 15 bytes,
+    // truncated to 5 bytes.
     assert_eq!(init, b"Hello");
     assert!(uninit.is_empty());
 }
+
+/// Test `recvmsg` with the `RecvFlags::Trunc` flag.
+#[test]
+fn net_recvmsg_trunc() {
+    crate::init();
+
+    let tmpdir = tempfile::tempdir().unwrap();
+    let path = tmpdir.path().join("recvmsg_trunc");
+    let name = SocketAddrUnix::new(&path).unwrap();
+
+    let receiver = rustix::net::socket(AddressFamily::UNIX, SocketType::DGRAM, None).unwrap();
+    rustix::net::bind_unix(&receiver, &name).expect("bind");
+
+    let sender = rustix::net::socket(AddressFamily::UNIX, SocketType::DGRAM, None).unwrap();
+    let request = b"Hello, World!!!";
+
+    // Test with `RecvFlags::TRUNC`, which is not supported on Apple, illumos, or NetBSD.
+    #[cfg(not(any(apple, solarish, target_os = "netbsd")))]
+    {
+        let n =
+            rustix::net::sendto_unix(&sender, request, SendFlags::empty(), &name).expect("send");
+        assert_eq!(n, request.len());
+
+        let mut response = [0_u8; 5];
+        let result = rustix::net::recvmsg(
+            &receiver,
+            &mut [IoSliceMut::new(&mut response)],
+            &mut Default::default(),
+            RecvFlags::TRUNC,
+        )
+        .expect("recvmsg");
+
+        // We used the `TRUNC` flag, so we should have received 15 bytes,
+        // truncated to 5 bytes, and the `TRUNC` flag should have been returned.
+        assert_eq!(&response, b"Hello");
+        assert_eq!(result.bytes, 15);
+        assert_eq!(result.flags, ReturnFlags::TRUNC);
+
+        // Send the message again.
+        let n =
+            rustix::net::sendto_unix(&sender, request, SendFlags::empty(), &name).expect("send");
+        assert_eq!(n, request.len());
+
+        // This time receive it with `TRUNC` and a big enough buffer.
+        let mut response = [0_u8; 30];
+        let result = rustix::net::recvmsg(
+            &receiver,
+            &mut [IoSliceMut::new(&mut response)],
+            &mut Default::default(),
+            RecvFlags::TRUNC,
+        )
+        .expect("recvmsg");
+
+        // We used the `TRUNC` flag, so we should have received 15 bytes
+        // and the buffer was big enough so the `TRUNC` flag should not have
+        // been returned.
+        assert_eq!(&response[..result.bytes], request);
+        assert_eq!(result.flags, ReturnFlags::empty());
+    }
+
+    // Send the message again.
+    let n = rustix::net::sendto_unix(&sender, request, SendFlags::empty(), &name).expect("send");
+    assert_eq!(n, request.len());
+
+    // This time receive it without `TRUNC` but a big enough buffer.
+    let mut response = [0_u8; 30];
+    let result = rustix::net::recvmsg(
+        &receiver,
+        &mut [IoSliceMut::new(&mut response)],
+        &mut Default::default(),
+        RecvFlags::empty(),
+    )
+    .expect("recvmsg");
+
+    // We didn't use the `TRUNC` flag, but the buffer was big enough,
+    // so we should have received 15 bytes, and the `TRUNC` flag should
+    // have been returned.
+    assert_eq!(&response[..result.bytes], request);
+    assert_eq!(result.flags, ReturnFlags::empty());
+
+    // Send the message again.
+    let n = rustix::net::sendto_unix(&sender, request, SendFlags::empty(), &name).expect("send");
+    assert_eq!(n, request.len());
+
+    // This time receive it without `TRUNC` and a small buffer.
+    let mut response = [0_u8; 5];
+    let result = rustix::net::recvmsg(
+        &receiver,
+        &mut [IoSliceMut::new(&mut response)],
+        &mut Default::default(),
+        RecvFlags::empty(),
+    )
+    .expect("recvmsg");
+
+    // We didn't use the `TRUNC` flag, so we should have received 15 bytes,
+    // truncated to 5 bytes, and the `TRUNC` flag should have been returned.
+    assert_eq!(&response[..result.bytes], b"Hello");
+    assert_eq!(result.flags, ReturnFlags::TRUNC);
+}
diff --git a/tests/net/unix.rs b/tests/net/unix.rs
index a84b79d26..cdf389599 100644
--- a/tests/net/unix.rs
+++ b/tests/net/unix.rs
@@ -129,7 +129,7 @@ fn test_unix() {
 #[cfg(not(any(target_os = "espidf", target_os = "redox", target_os = "wasi")))]
 fn do_test_unix_msg(addr: SocketAddrUnix) {
     use rustix::io::{IoSlice, IoSliceMut};
-    use rustix::net::{recvmsg, sendmsg, RecvFlags, SendFlags};
+    use rustix::net::{recvmsg, sendmsg, RecvFlags, ReturnFlags, SendFlags};
 
     let server = {
         let connection_socket = socket(AddressFamily::UNIX, SocketType::SEQPACKET, None).unwrap();
@@ -142,14 +142,16 @@ fn do_test_unix_msg(addr: SocketAddrUnix) {
                 let data_socket = accept(&connection_socket).unwrap();
                 let mut sum = 0;
                 loop {
-                    let nread = recvmsg(
+                    let result = recvmsg(
                         &data_socket,
                         &mut [IoSliceMut::new(&mut buffer)],
                         &mut Default::default(),
                         RecvFlags::empty(),
                     )
-                    .unwrap()
-                    .bytes;
+                    .unwrap();
+                    let nread = result.bytes;
+
+                    assert_eq!(result.flags, ReturnFlags::empty());
 
                     if &buffer[..nread] == b"exit" {
                         break 'exit;
@@ -210,6 +212,7 @@ fn do_test_unix_msg(addr: SocketAddrUnix) {
                 RecvFlags::empty(),
             )
             .unwrap();
+            assert_eq!(result.flags, ReturnFlags::empty());
             let nread = result.bytes;
             assert_eq!(
                 i32::from_str(&String::from_utf8_lossy(&buffer[..nread])).unwrap(),
@@ -259,7 +262,7 @@ fn do_test_unix_msg(addr: SocketAddrUnix) {
 #[cfg(not(any(target_os = "espidf", target_os = "redox", target_os = "wasi")))]
 fn do_test_unix_msg_unconnected(addr: SocketAddrUnix) {
     use rustix::io::{IoSlice, IoSliceMut};
-    use rustix::net::{recvmsg, sendmsg_unix, RecvFlags, SendFlags};
+    use rustix::net::{recvmsg, sendmsg_unix, RecvFlags, ReturnFlags, SendFlags};
 
     let server = {
         let runs: &[i32] = &[3, 184, 187, 0];
@@ -271,16 +274,17 @@ fn do_test_unix_msg_unconnected(addr: SocketAddrUnix) {
             for expected_sum in runs {
                 let mut sum = 0;
                 loop {
-                    let nread = recvmsg(
+                    let result = recvmsg(
                         &data_socket,
                         &mut [IoSliceMut::new(&mut buffer)],
                         &mut Default::default(),
                         RecvFlags::empty(),
                     )
-                    .unwrap()
-                    .bytes;
+                    .unwrap();
+                    let nread = result.bytes;
 
                     assert_ne!(&buffer[..nread], b"exit");
+                    assert_eq!(result.flags, ReturnFlags::empty());
                     if &buffer[..nread] == b"sum" {
                         break;
                     }
@@ -290,16 +294,17 @@ fn do_test_unix_msg_unconnected(addr: SocketAddrUnix) {
 
                 assert_eq!(sum, *expected_sum);
             }
-            let nread = recvmsg(
+            let result = recvmsg(
                 &data_socket,
                 &mut [IoSliceMut::new(&mut buffer)],
                 &mut Default::default(),
                 RecvFlags::empty(),
             )
-            .unwrap()
-            .bytes;
+            .unwrap();
+            let nread = result.bytes;
 
             assert_eq!(&buffer[..nread], b"exit");
+            assert_eq!(result.flags, ReturnFlags::empty());
         }
     };
 
@@ -425,7 +430,7 @@ fn test_unix_msg_with_scm_rights() {
     use rustix::fd::AsFd;
     use rustix::io::{IoSlice, IoSliceMut};
     use rustix::net::{
-        recvmsg, sendmsg, RecvAncillaryBuffer, RecvAncillaryMessage, RecvFlags,
+        recvmsg, sendmsg, RecvAncillaryBuffer, RecvAncillaryMessage, RecvFlags, ReturnFlags,
         SendAncillaryBuffer, SendAncillaryMessage, SendFlags,
     };
     use rustix::pipe::pipe;
@@ -454,14 +459,16 @@ fn test_unix_msg_with_scm_rights() {
                 let mut sum = 0;
                 loop {
                     let mut cmsg_buffer = RecvAncillaryBuffer::new(&mut cmsg_space);
-                    let nread = recvmsg(
+                    let result = recvmsg(
                         &data_socket,
                         &mut [IoSliceMut::new(&mut buffer)],
                         &mut cmsg_buffer,
                         RecvFlags::empty(),
                     )
-                    .unwrap()
-                    .bytes;
+                    .unwrap();
+                    let nread = result.bytes;
+
+                    assert_eq!(result.flags, ReturnFlags::empty());
 
                     // Read out the pipe if we got it.
                     if let Some(end) = cmsg_buffer
@@ -536,18 +543,19 @@ fn test_unix_msg_with_scm_rights() {
             )
             .unwrap();
 
-            let nread = recvmsg(
+            let result = recvmsg(
                 &data_socket,
                 &mut [IoSliceMut::new(&mut buffer)],
                 &mut Default::default(),
                 RecvFlags::empty(),
             )
-            .unwrap()
-            .bytes;
+            .unwrap();
+            let nread = result.bytes;
             assert_eq!(
                 i32::from_str(&String::from_utf8_lossy(&buffer[..nread])).unwrap(),
                 *sum
             );
+            assert_eq!(result.flags, ReturnFlags::empty());
         }
 
         let data_socket = socket(AddressFamily::UNIX, SocketType::SEQPACKET, None).unwrap();
@@ -600,7 +608,7 @@ fn test_unix_peercred_explicit() {
     use rustix::io::{IoSlice, IoSliceMut};
     use rustix::net::{
         recvmsg, sendmsg, sockopt, RecvAncillaryBuffer, RecvAncillaryMessage, RecvFlags,
-        SendAncillaryBuffer, SendAncillaryMessage, SendFlags, SocketFlags,
+        ReturnFlags, SendAncillaryBuffer, SendAncillaryMessage, SendFlags, SocketFlags,
     };
 
     let (send_sock, recv_sock) = rustix::net::socketpair(
@@ -631,7 +639,7 @@ fn test_unix_peercred_explicit() {
     let mut cmsg_buffer = RecvAncillaryBuffer::new(&mut cmsg_space);
 
     let mut buffer = [0; BUFFER_SIZE];
-    recvmsg(
+    let result = recvmsg(
         &recv_sock,
         &mut [IoSliceMut::new(&mut buffer)],
         &mut cmsg_buffer,
@@ -639,6 +647,8 @@ fn test_unix_peercred_explicit() {
     )
     .unwrap();
 
+    assert_eq!(result.flags, ReturnFlags::empty());
+
     match cmsg_buffer.drain().next().unwrap() {
         RecvAncillaryMessage::ScmCredentials(ucred2) => assert_eq!(ucred2, ucred),
         _ => panic!("Unexpected ancillary message"),
@@ -656,7 +666,7 @@ fn test_unix_peercred_implicit() {
     use rustix::io::{IoSlice, IoSliceMut};
     use rustix::net::{
         recvmsg, sendmsg, sockopt, RecvAncillaryBuffer, RecvAncillaryMessage, RecvFlags,
-        SendAncillaryBuffer, SendFlags, SocketFlags,
+        ReturnFlags, SendAncillaryBuffer, SendFlags, SocketFlags,
     };
     use rustix::process::{getgid, getpid, getuid};
 
@@ -689,7 +699,7 @@ fn test_unix_peercred_implicit() {
     let mut cmsg_buffer = RecvAncillaryBuffer::new(&mut cmsg_space);
 
     let mut buffer = [0; BUFFER_SIZE];
-    recvmsg(
+    let result = recvmsg(
         &recv_sock,
         &mut [IoSliceMut::new(&mut buffer)],
         &mut cmsg_buffer,
@@ -697,6 +707,8 @@ fn test_unix_peercred_implicit() {
     )
     .unwrap();
 
+    assert_eq!(result.flags, ReturnFlags::empty());
+
     match cmsg_buffer.drain().next().unwrap() {
         RecvAncillaryMessage::ScmCredentials(ucred2) => assert_eq!(ucred2, ucred),
         _ => panic!("Unexpected ancillary message"),
@@ -714,7 +726,7 @@ fn test_unix_msg_with_combo() {
     use rustix::fd::AsFd;
     use rustix::io::{IoSlice, IoSliceMut};
     use rustix::net::{
-        recvmsg, sendmsg, RecvAncillaryBuffer, RecvAncillaryMessage, RecvFlags,
+        recvmsg, sendmsg, RecvAncillaryBuffer, RecvAncillaryMessage, RecvFlags, ReturnFlags,
         SendAncillaryBuffer, SendAncillaryMessage, SendFlags,
     };
     use rustix::pipe::pipe;
@@ -745,14 +757,16 @@ fn test_unix_msg_with_combo() {
                 let mut sum = 0;
                 loop {
                     let mut cmsg_buffer = RecvAncillaryBuffer::new(&mut cmsg_space);
-                    let nread = recvmsg(
+                    let result = recvmsg(
                         &data_socket,
                         &mut [IoSliceMut::new(&mut buffer)],
                         &mut cmsg_buffer,
                         RecvFlags::empty(),
                     )
-                    .unwrap()
-                    .bytes;
+                    .unwrap();
+                    let nread = result.bytes;
+
+                    assert_eq!(result.flags, ReturnFlags::empty());
 
                     // Read out the pipe if we got it.
                     for cmsg in cmsg_buffer.drain() {
@@ -844,18 +858,19 @@ fn test_unix_msg_with_combo() {
             )
             .unwrap();
 
-            let nread = recvmsg(
+            let result = recvmsg(
                 &data_socket,
                 &mut [IoSliceMut::new(&mut buffer)],
                 &mut Default::default(),
                 RecvFlags::empty(),
             )
-            .unwrap()
-            .bytes;
+            .unwrap();
+            let nread = result.bytes;
             assert_eq!(
                 i32::from_str(&String::from_utf8_lossy(&buffer[..nread])).unwrap(),
                 *sum
             );
+            assert_eq!(result.flags, ReturnFlags::empty());
         }
 
         let data_socket = socket(AddressFamily::UNIX, SocketType::SEQPACKET, None).unwrap();
diff --git a/tests/net/unix_alloc.rs b/tests/net/unix_alloc.rs
index 964b031cd..5a5278215 100644
--- a/tests/net/unix_alloc.rs
+++ b/tests/net/unix_alloc.rs
@@ -127,7 +127,7 @@ fn test_unix() {
 #[cfg(not(any(target_os = "espidf", target_os = "redox", target_os = "wasi")))]
 fn do_test_unix_msg(addr: SocketAddrUnix) {
     use rustix::io::{IoSlice, IoSliceMut};
-    use rustix::net::{recvmsg, sendmsg, RecvFlags, SendFlags};
+    use rustix::net::{recvmsg, sendmsg, RecvFlags, ReturnFlags, SendFlags};
 
     let server = {
         let connection_socket = socket(AddressFamily::UNIX, SocketType::SEQPACKET, None).unwrap();
@@ -140,14 +140,16 @@ fn do_test_unix_msg(addr: SocketAddrUnix) {
                 let data_socket = accept(&connection_socket).unwrap();
                 let mut sum = 0;
                 loop {
-                    let nread = recvmsg(
+                    let result = recvmsg(
                         &data_socket,
                         &mut [IoSliceMut::new(&mut buffer)],
                         &mut Default::default(),
                         RecvFlags::empty(),
                     )
-                    .unwrap()
-                    .bytes;
+                    .unwrap();
+                    let nread = result.bytes;
+
+                    assert_eq!(result.flags, ReturnFlags::empty());
 
                     if &buffer[..nread] == b"exit" {
                         break 'exit;
@@ -257,7 +259,7 @@ fn do_test_unix_msg(addr: SocketAddrUnix) {
 #[cfg(not(any(target_os = "espidf", target_os = "redox", target_os = "wasi")))]
 fn do_test_unix_msg_unconnected(addr: SocketAddrUnix) {
     use rustix::io::{IoSlice, IoSliceMut};
-    use rustix::net::{recvmsg, sendmsg_unix, RecvFlags, SendFlags};
+    use rustix::net::{recvmsg, sendmsg_unix, RecvFlags, ReturnFlags, SendFlags};
 
     let server = {
         let runs: &[i32] = &[3, 184, 187, 0];
@@ -269,14 +271,16 @@ fn do_test_unix_msg_unconnected(addr: SocketAddrUnix) {
             for expected_sum in runs {
                 let mut sum = 0;
                 loop {
-                    let nread = recvmsg(
+                    let result = recvmsg(
                         &data_socket,
                         &mut [IoSliceMut::new(&mut buffer)],
                         &mut Default::default(),
                         RecvFlags::empty(),
                     )
-                    .unwrap()
-                    .bytes;
+                    .unwrap();
+                    let nread = result.bytes;
+
+                    assert_eq!(result.flags, ReturnFlags::empty());
 
                     assert_ne!(&buffer[..nread], b"exit");
                     if &buffer[..nread] == b"sum" {
@@ -288,16 +292,17 @@ fn do_test_unix_msg_unconnected(addr: SocketAddrUnix) {
 
                 assert_eq!(sum, *expected_sum);
             }
-            let nread = recvmsg(
+            let result = recvmsg(
                 &data_socket,
                 &mut [IoSliceMut::new(&mut buffer)],
                 &mut Default::default(),
                 RecvFlags::empty(),
             )
-            .unwrap()
-            .bytes;
+            .unwrap();
+            let nread = result.bytes;
 
             assert_eq!(&buffer[..nread], b"exit");
+            assert_eq!(result.flags, ReturnFlags::empty());
         }
     };
 
@@ -423,7 +428,7 @@ fn test_unix_msg_with_scm_rights() {
     use rustix::fd::AsFd;
     use rustix::io::{IoSlice, IoSliceMut};
     use rustix::net::{
-        recvmsg, sendmsg, RecvAncillaryBuffer, RecvAncillaryMessage, RecvFlags,
+        recvmsg, sendmsg, RecvAncillaryBuffer, RecvAncillaryMessage, RecvFlags, ReturnFlags,
         SendAncillaryBuffer, SendAncillaryMessage, SendFlags,
     };
     use rustix::pipe::pipe;
@@ -452,14 +457,16 @@ fn test_unix_msg_with_scm_rights() {
                 let mut sum = 0;
                 loop {
                     let mut cmsg_buffer = RecvAncillaryBuffer::new(&mut cmsg_space);
-                    let nread = recvmsg(
+                    let result = recvmsg(
                         &data_socket,
                         &mut [IoSliceMut::new(&mut buffer)],
                         &mut cmsg_buffer,
                         RecvFlags::empty(),
                     )
-                    .unwrap()
-                    .bytes;
+                    .unwrap();
+                    let nread = result.bytes;
+
+                    assert_eq!(result.flags, ReturnFlags::empty());
 
                     // Read out the pipe if we got it.
                     if let Some(end) = cmsg_buffer
@@ -534,18 +541,19 @@ fn test_unix_msg_with_scm_rights() {
             )
             .unwrap();
 
-            let nread = recvmsg(
+            let result = recvmsg(
                 &data_socket,
                 &mut [IoSliceMut::new(&mut buffer)],
                 &mut Default::default(),
                 RecvFlags::empty(),
             )
-            .unwrap()
-            .bytes;
+            .unwrap();
+            let nread = result.bytes;
             assert_eq!(
                 i32::from_str(&String::from_utf8_lossy(&buffer[..nread])).unwrap(),
                 *sum
             );
+            assert_eq!(result.flags, ReturnFlags::empty());
         }
 
         let data_socket = socket(AddressFamily::UNIX, SocketType::SEQPACKET, None).unwrap();
@@ -659,7 +667,7 @@ fn test_unix_msg_with_combo() {
     use rustix::fd::AsFd;
     use rustix::io::{IoSlice, IoSliceMut};
     use rustix::net::{
-        recvmsg, sendmsg, RecvAncillaryBuffer, RecvAncillaryMessage, RecvFlags,
+        recvmsg, sendmsg, RecvAncillaryBuffer, RecvAncillaryMessage, RecvFlags, ReturnFlags,
         SendAncillaryBuffer, SendAncillaryMessage, SendFlags,
     };
     use rustix::pipe::pipe;
@@ -690,14 +698,16 @@ fn test_unix_msg_with_combo() {
                 let mut sum = 0;
                 loop {
                     let mut cmsg_buffer = RecvAncillaryBuffer::new(&mut cmsg_space);
-                    let nread = recvmsg(
+                    let result = recvmsg(
                         &data_socket,
                         &mut [IoSliceMut::new(&mut buffer)],
                         &mut cmsg_buffer,
                         RecvFlags::empty(),
                     )
-                    .unwrap()
-                    .bytes;
+                    .unwrap();
+                    let nread = result.bytes;
+
+                    assert_eq!(result.flags, ReturnFlags::empty());
 
                     // Read out the pipe if we got it.
                     for cmsg in cmsg_buffer.drain() {
@@ -789,18 +799,19 @@ fn test_unix_msg_with_combo() {
             )
             .unwrap();
 
-            let nread = recvmsg(
+            let result = recvmsg(
                 &data_socket,
                 &mut [IoSliceMut::new(&mut buffer)],
                 &mut Default::default(),
                 RecvFlags::empty(),
             )
-            .unwrap()
-            .bytes;
+            .unwrap();
+            let nread = result.bytes;
             assert_eq!(
                 i32::from_str(&String::from_utf8_lossy(&buffer[..nread])).unwrap(),
                 *sum
             );
+            assert_eq!(result.flags, ReturnFlags::empty());
         }
 
         let data_socket = socket(AddressFamily::UNIX, SocketType::SEQPACKET, None).unwrap();
diff --git a/tests/net/v4.rs b/tests/net/v4.rs
index d770b657b..aedeffdc4 100644
--- a/tests/net/v4.rs
+++ b/tests/net/v4.rs
@@ -6,7 +6,7 @@
 
 use rustix::net::{
     accept, bind_v4, connect_v4, getsockname, listen, recv, send, socket, AddressFamily, Ipv4Addr,
-    RecvFlags, SendFlags, SocketAddrAny, SocketAddrV4, SocketType,
+    RecvFlags, ReturnFlags, SendFlags, SocketAddrAny, SocketAddrV4, SocketType,
 };
 use std::sync::{Arc, Condvar, Mutex};
 use std::thread;
@@ -127,6 +127,7 @@ fn test_v4_msg() {
             String::from_utf8_lossy(&buffer[..res.bytes]),
             "hello, world"
         );
+        assert_eq!(res.flags, ReturnFlags::empty());
 
         sendmsg(
             &data_socket,
@@ -172,6 +173,7 @@ fn test_v4_msg() {
             String::from_utf8_lossy(&buffer[..res.bytes]),
             "goodnight, moon"
         );
+        assert_eq!(res.flags, ReturnFlags::empty());
     }
 
     let ready = Arc::new((Mutex::new(0_u16), Condvar::new()));
diff --git a/tests/net/v6.rs b/tests/net/v6.rs
index 0d0a596c9..ca63abf6e 100644
--- a/tests/net/v6.rs
+++ b/tests/net/v6.rs
@@ -6,7 +6,7 @@
 
 use rustix::net::{
     accept, bind_v6, connect_v6, getsockname, listen, recv, send, socket, AddressFamily, Ipv6Addr,
-    RecvFlags, SendFlags, SocketAddrAny, SocketAddrV6, SocketType,
+    RecvFlags, ReturnFlags, SendFlags, SocketAddrAny, SocketAddrV6, SocketType,
 };
 use std::sync::{Arc, Condvar, Mutex};
 use std::thread;
@@ -127,6 +127,7 @@ fn test_v6_msg() {
             String::from_utf8_lossy(&buffer[..result.bytes]),
             "hello, world"
         );
+        assert_eq!(result.flags, ReturnFlags::empty());
 
         sendmsg(
             &data_socket,
@@ -172,6 +173,7 @@ fn test_v6_msg() {
             String::from_utf8_lossy(&buffer[..nread.bytes]),
             "goodnight, moon"
         );
+        assert_eq!(nread.flags, ReturnFlags::empty());
     }
 
     let ready = Arc::new((Mutex::new(0_u16), Condvar::new()));