noq_udp/cmsg/
unix.rs

1use std::{
2    ffi::{c_int, c_uchar},
3    mem::MaybeUninit,
4};
5
6use super::{CMsgHdr, Encoder, MsgHdr};
7// netbsd sends no IP_TOS control message, so it has no payload type for one.
8#[cfg(not(target_os = "netbsd"))]
9use crate::imp::IpTosTy;
10
11/// Every payload we put into, or read out of, a control message on this platform.
12///
13/// A payload slot holds any one of these, so the largest of them sizes a message.
14#[derive(Copy, Clone)]
15#[repr(C)]
16#[allow(dead_code)] // the fields are here for their size, nothing reads them
17pub(crate) union Payload {
18    #[cfg(not(target_os = "netbsd"))]
19    ecn_v4: IpTosTy,
20    ecn_v6: c_int,
21    /// `IP_TOS` and, on Darwin, `IPV6_TCLASS` come back as a single byte.
22    ecn_byte: u8,
23    segment_size: u16,
24    #[cfg(not(target_os = "redox"))]
25    pktinfo_v6: libc::in6_pktinfo,
26    #[cfg(any(target_os = "linux", target_os = "android"))]
27    pktinfo_v4: libc::in_pktinfo,
28    #[cfg(any(bsd, apple, solarish))]
29    dst_addr_v4: libc::in_addr,
30    #[cfg(any(target_os = "linux", target_os = "android"))]
31    timestamp: libc::timespec,
32}
33
34/// Set in `msg_flags` when control messages did not fit in the buffer.
35pub(crate) const MSG_CTRUNC: c_int = libc::MSG_CTRUNC;
36
37/// The buffer space one control message with a payload of this size takes up.
38///
39/// <https://man7.org/linux/man-pages/man3/cmsg.3.html>
40const fn cmsg_space(payload_len: usize) -> usize {
41    unsafe { libc::CMSG_SPACE(payload_len as _) as usize }
42}
43
44/// The weaker of two alignments, i.e. the largest power of two dividing both.
45const fn common_align(a: usize, b: usize) -> usize {
46    // The lower of the two lowest set bits decides the trailing zeros of the OR.
47    1 << (a | b).trailing_zeros()
48}
49
50/// The alignment a control message payload is guaranteed to have.
51///
52/// Payloads sit `CMSG_LEN(0)` into their message and messages a sum of `CMSG_SPACE`s into
53/// the buffer, so it is what those offsets and [`ControlBuf`]'s alignment share.
54pub(crate) const PAYLOAD_ALIGN: usize = common_align(
55    common_align(unsafe { libc::CMSG_LEN(0) } as usize, cmsg_space(1)),
56    align_of::<ControlBuf<0>>(),
57);
58
59/// Space for one control message carrying any of our payloads.
60const MESSAGE_LEN: usize = cmsg_space(size_of::<Payload>());
61
62/// Space for the control messages one `sendmsg` can carry.
63///
64/// ECN, GSO segment size and source address, one each; the v4 and v6 forms are exclusive.
65pub(crate) const SEND_LEN: usize = 3 * MESSAGE_LEN;
66
67/// Space for the control messages the kernel can attach to one received datagram.
68///
69/// TOS or traffic class, packet info, GRO segment size and receive timestamp, one each,
70/// matching the options `UdpSocketState::new` enables.
71pub(crate) const RECV_LEN: usize = 4 * MESSAGE_LEN;
72
73/// A control message buffer of `N` bytes.
74#[derive(Copy, Clone)]
75#[repr(C)]
76pub(crate) struct ControlBuf<const N: usize> {
77    /// Aligns the buffer like the `size_t` the `CMSG_*` macros round offsets to, which
78    /// covers the headers too: no platform aligns `cmsghdr` more strictly than that.
79    /// Zero sized: `repr(align)` takes a literal, not an expression.
80    _align: [usize; 0],
81    bytes: [MaybeUninit<u8>; N],
82}
83
84/// Control message buffer for one `sendmsg`.
85pub(crate) type SendBuf = ControlBuf<SEND_LEN>;
86
87/// Control message buffer for one `recvmsg`.
88pub(crate) type RecvBuf = ControlBuf<RECV_LEN>;
89
90impl<const N: usize> ControlBuf<N> {
91    /// A zeroed buffer, for sending.
92    pub(crate) const fn zeroed() -> Self {
93        Self {
94            _align: [],
95            bytes: [MaybeUninit::new(0); N],
96        }
97    }
98
99    /// An uninitialised buffer, for receiving: the kernel initialises what it uses.
100    pub(crate) const fn uninit() -> Self {
101        Self {
102            _align: [],
103            bytes: [MaybeUninit::uninit(); N],
104        }
105    }
106
107    pub(crate) fn as_mut_ptr(&mut self) -> *mut u8 {
108        self.bytes.as_mut_ptr().cast()
109    }
110
111    /// The size of the buffer, for `msg_controllen`.
112    pub(crate) const fn len(&self) -> usize {
113        N
114    }
115}
116
117/// The control messages we send.
118///
119/// One method each rather than a generic `push`, keeping the set next to the [`SEND_LEN`]
120/// covering it.
121impl<M: MsgHdr<ControlMessage = libc::cmsghdr>> Encoder<'_, M> {
122    /// Sets the ECN codepoint of an IPv4 or IPv4-mapped datagram.
123    #[cfg(not(target_os = "netbsd"))]
124    pub(crate) fn push_ecn_v4(&mut self, ecn: IpTosTy) {
125        self.push(libc::IPPROTO_IP, libc::IP_TOS, ecn);
126    }
127
128    /// Sets the IPv6 traffic class, which carries the ECN codepoint.
129    #[cfg(not(target_os = "redox"))]
130    pub(crate) fn push_ecn_v6(&mut self, ecn: c_int) {
131        self.push(libc::IPPROTO_IPV6, libc::IPV6_TCLASS, ecn);
132    }
133
134    /// Sets the GSO segment size the kernel splits an oversized datagram into.
135    #[cfg(any(target_os = "linux", target_os = "android"))]
136    pub(crate) fn push_segment_size(&mut self, segment_size: u16) {
137        self.push(libc::SOL_UDP, libc::UDP_SEGMENT, segment_size);
138    }
139
140    /// Sets the source address of an IPv4 datagram.
141    #[cfg(any(target_os = "linux", target_os = "android"))]
142    pub(crate) fn push_pktinfo_v4(&mut self, pktinfo: libc::in_pktinfo) {
143        self.push(libc::IPPROTO_IP, libc::IP_PKTINFO, pktinfo);
144    }
145
146    /// Sets the source address of an IPv4 datagram.
147    ///
148    /// `IP_RECVDSTADDR` is `IP_SENDSRCADDR` on FreeBSD, the two have the same value.
149    #[cfg(any(bsd, apple, solarish))]
150    pub(crate) fn push_src_addr_v4(&mut self, addr: libc::in_addr) {
151        self.push(libc::IPPROTO_IP, libc::IP_RECVDSTADDR, addr);
152    }
153
154    /// Sets the source address of an IPv6 datagram.
155    #[cfg(not(target_os = "redox"))]
156    pub(crate) fn push_pktinfo_v6(&mut self, pktinfo: libc::in6_pktinfo) {
157        self.push(libc::IPPROTO_IPV6, libc::IPV6_PKTINFO, pktinfo);
158    }
159}
160
161/// Helpers for [`libc::msghdr`]
162impl MsgHdr for libc::msghdr {
163    type ControlMessage = libc::cmsghdr;
164
165    fn cmsg_first_hdr(&self) -> *mut Self::ControlMessage {
166        unsafe { libc::CMSG_FIRSTHDR(self) }
167    }
168
169    fn cmsg_nxt_hdr(&self, cmsg: &Self::ControlMessage) -> *mut Self::ControlMessage {
170        unsafe { libc::CMSG_NXTHDR(self, cmsg) }
171    }
172
173    fn set_control_len(&mut self, len: usize) {
174        self.msg_controllen = len as _;
175        if len == 0 {
176            // netbsd is particular about this being a NULL pointer if there are no control
177            // messages.
178            self.msg_control = std::ptr::null_mut();
179        }
180    }
181
182    fn control_len(&self) -> usize {
183        self.msg_controllen as _
184    }
185
186    fn recv_flags(&self) -> c_int {
187        self.msg_flags
188    }
189}
190
191/// Helpers for [`libc::cmsghdr`]
192impl CMsgHdr for libc::cmsghdr {
193    fn cmsg_len(length: usize) -> usize {
194        unsafe { libc::CMSG_LEN(length as _) as usize }
195    }
196
197    fn cmsg_space(length: usize) -> usize {
198        unsafe { libc::CMSG_SPACE(length as _) as usize }
199    }
200
201    fn cmsg_data(&self) -> *mut c_uchar {
202        unsafe { libc::CMSG_DATA(self) }
203    }
204
205    fn set(&mut self, level: c_int, ty: c_int, len: usize) {
206        self.cmsg_level = level as _;
207        self.cmsg_type = ty as _;
208        self.cmsg_len = len as _;
209    }
210
211    fn len(&self) -> usize {
212        self.cmsg_len as _
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use std::mem;
219
220    use super::*;
221
222    /// The payload of every control message we can send in one `sendmsg`.
223    ///
224    /// `IpTosTy` is `c_int` or smaller everywhere it exists, so `c_int` stands in for it.
225    fn sent_payload_lens() -> Vec<usize> {
226        vec![
227            size_of::<c_int>(), // IP_TOS or IPV6_TCLASS
228            size_of::<u16>(),   // UDP_SEGMENT
229            // IP_PKTINFO, IP_RECVDSTADDR or IPV6_PKTINFO
230            size_of::<Payload>(),
231        ]
232    }
233
234    /// The payload of every control message the kernel can attach to one datagram.
235    fn received_payload_lens() -> Vec<usize> {
236        vec![
237            size_of::<c_int>(),   // IP_TOS or IPV6_TCLASS
238            size_of::<Payload>(), // IP_PKTINFO or IPV6_PKTINFO
239            size_of::<c_int>(),   // UDP_GRO
240            #[cfg(any(target_os = "linux", target_os = "android"))]
241            size_of::<libc::timespec>(), // SCM_TIMESTAMPNS
242        ]
243    }
244
245    fn libc_cmsg_space(payload_lens: &[usize]) -> usize {
246        payload_lens
247            .iter()
248            .map(|len| unsafe { libc::CMSG_SPACE(*len as _) as usize })
249            .sum()
250    }
251
252    /// The buffers hold every control message they have to.
253    ///
254    /// The constants count messages and assume the largest payload; this adds up the real
255    /// ones, so a message we failed to count shows up here, not as a truncated datagram.
256    #[test]
257    fn control_len_covers_libc() {
258        let sent = libc_cmsg_space(&sent_payload_lens());
259        assert!(SEND_LEN >= sent, "SEND_LEN is {SEND_LEN}, need {sent}");
260
261        let received = libc_cmsg_space(&received_payload_lens());
262        assert!(
263            RECV_LEN >= received,
264            "RECV_LEN is {RECV_LEN}, need {received}"
265        );
266    }
267
268    /// Every payload in a full buffer is aligned for the type read out of it.
269    ///
270    /// What `cmsg::decode` relies on. musl aligns `cmsghdr` to 4 where glibc aligns it to
271    /// 8, so aligning the buffer for it rather than for the macros breaks there.
272    ///
273    /// <https://git.musl-libc.org/cgit/musl/tree/include/sys/socket.h#n44>
274    /// <https://sourceware.org/git/gitweb.cgi?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/bits/socket.h#l283>
275    #[test]
276    fn payloads_are_aligned() {
277        let mut buf = RecvBuf::zeroed();
278        let mut hdr: libc::msghdr = unsafe { mem::zeroed() };
279        hdr.msg_control = buf.as_mut_ptr().cast();
280        hdr.msg_controllen = buf.len() as _;
281
282        // The largest payload we use, so the messages after the first sit where a real
283        // receive would put them.
284        let mut encoder = unsafe { Encoder::new(&mut hdr) };
285        for _ in 0..received_payload_lens().len() {
286            encoder.push(libc::SOL_SOCKET, 0, Payload { ecn_v6: 0 });
287        }
288        encoder.finish();
289
290        let mut count = 0;
291        for cmsg in unsafe { super::super::Iter::new(&hdr) } {
292            assert_eq!(
293                cmsg.cmsg_data() as usize % PAYLOAD_ALIGN,
294                0,
295                "payload {count} is not aligned to {PAYLOAD_ALIGN}",
296            );
297            count += 1;
298        }
299        assert_eq!(count, received_payload_lens().len());
300    }
301}