noq_udp/
unix.rs

1#[cfg(not(any(apple, target_os = "openbsd", solarish)))]
2use std::ptr;
3use std::{
4    io::{self, IoSliceMut},
5    mem::{self, MaybeUninit},
6    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
7    num::NonZeroUsize,
8    os::fd::AsRawFd,
9    sync::{
10        Mutex,
11        atomic::{AtomicBool, AtomicUsize, Ordering},
12    },
13    time::{Duration, Instant},
14};
15
16use socket2::SockRef;
17
18use super::{
19    EcnCodepoint, IO_ERROR_LOG_INTERVAL, RecvMeta, Transmit, UdpSockRef, cmsg, log_sendmsg_error,
20};
21
22#[cfg(apple_fast)]
23use super::apple_fast::{msghdr_x, recv_via_recvmsg_x, send};
24#[cfg(any(target_os = "linux", target_os = "android"))]
25use super::linux::gso;
26
27/// Tokio-compatible UDP socket with some useful specializations.
28///
29/// Unlike a standard tokio UDP socket, this allows ECN bits to be read and written on some
30/// platforms.
31#[derive(Debug)]
32pub struct UdpSocketState {
33    last_send_error: Mutex<Instant>,
34    max_gso_segments: AtomicUsize,
35    gro_segments: NonZeroUsize,
36    may_fragment: bool,
37
38    /// True if we have received EINVAL error from `sendmsg` system call at least once.
39    ///
40    /// If enabled, we assume that old kernel is used and switch to fallback mode.
41    /// In particular, we do not use IP_TOS cmsg_type in this case,
42    /// which is not supported on Linux <3.13 and results in not sending the UDP packet at all.
43    sendmsg_einval: AtomicBool,
44
45    /// Whether to use Apple's fast `sendmsg_x`/`recvmsg_x` APIs.
46    ///
47    /// These private APIs provide better performance but may not be available on all
48    /// Apple OS versions. Callers must verify availability before enabling.
49    #[cfg(apple_fast)]
50    apple_fast_path: AtomicBool,
51}
52
53impl UdpSocketState {
54    pub fn new(sock: UdpSockRef<'_>) -> io::Result<Self> {
55        let io = sock.0;
56        io.set_nonblocking(true)?;
57
58        let addr = io.local_addr()?;
59        let is_ipv4 = addr.family() == libc::AF_INET as libc::sa_family_t;
60
61        // mac and ios do not support IP_RECVTOS on dual-stack sockets :(
62        // older macos versions also don't have the flag and will error out if we don't ignore it
63        #[cfg(not(any(
64            target_os = "openbsd",
65            target_os = "netbsd",
66            target_os = "dragonfly",
67            solarish
68        )))]
69        if (is_ipv4 || !io.only_v6()?)
70            && let Err(_err) =
71                set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_RECVTOS, OPTION_ON)
72        {
73            crate::log::debug!("Ignoring error setting IP_RECVTOS on socket: {_err:?}");
74        }
75
76        let mut may_fragment = false;
77        #[cfg_attr(
78            not(any(target_os = "linux", target_os = "android")),
79            expect(unused_mut)
80        )]
81        let mut gro_segments = NonZeroUsize::MIN;
82
83        #[cfg(any(target_os = "linux", target_os = "android"))]
84        {
85            // Forbid IPv4 fragmentation. Set even for IPv6 to account for IPv6 mapped IPv4 addresses.
86            // Set `may_fragment` to `true` if this option is not supported on the platform.
87            may_fragment |= !set_socket_option_supported(
88                &*io,
89                libc::IPPROTO_IP,
90                libc::IP_MTU_DISCOVER,
91                libc::IP_PMTUDISC_PROBE,
92            )?;
93
94            if is_ipv4 {
95                set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_PKTINFO, OPTION_ON)?;
96            } else {
97                // Set `may_fragment` to `true` if this option is not supported on the platform.
98                may_fragment |= !set_socket_option_supported(
99                    &*io,
100                    libc::IPPROTO_IPV6,
101                    libc::IPV6_MTU_DISCOVER,
102                    libc::IPV6_PMTUDISC_PROBE,
103                )?;
104            }
105
106            if set_socket_option(&*io, libc::SOL_UDP, libc::UDP_GRO, OPTION_ON).is_ok() {
107                // As defined in net/ipv4/udp_offload.c
108                // #define UDP_GRO_CNT_MAX 64
109                //
110                // NOTE: this MUST be set to UDP_GRO_CNT_MAX to ensure that the receive buffer size
111                // (get_max_udp_payload_size() * gro_segments()) is large enough to hold the largest GRO
112                // list the kernel might potentially produce. See
113                // https://github.com/quinn-rs/quinn/pull/1354.
114                gro_segments = NonZeroUsize::new(64).expect("known");
115            }
116
117            if let Err(_err) =
118                set_socket_option(&*io, libc::SOL_SOCKET, libc::SO_TIMESTAMPNS, OPTION_ON)
119            {
120                crate::log::debug!("Ignoring error setting SO_TIMESTAMPNS on socket: {_err:?}");
121            }
122        }
123        #[cfg(any(target_os = "freebsd", apple))]
124        {
125            if is_ipv4 {
126                // Set `may_fragment` to `true` if this option is not supported on the platform.
127                may_fragment |= !set_socket_option_supported(
128                    &*io,
129                    libc::IPPROTO_IP,
130                    libc::IP_DONTFRAG,
131                    OPTION_ON,
132                )?;
133            }
134        }
135        #[cfg(any(bsd, apple, solarish))]
136        // IP_RECVDSTADDR == IP_SENDSRCADDR on FreeBSD
137        // macOS uses only IP_RECVDSTADDR, no IP_SENDSRCADDR on macOS (the same on Solaris)
138        // macOS also supports IP_PKTINFO
139        {
140            if is_ipv4 {
141                set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_RECVDSTADDR, OPTION_ON)?;
142            }
143        }
144
145        // Options standardized in RFC 3542
146        #[cfg(not(target_os = "redox"))]
147        if !is_ipv4 {
148            set_socket_option(&*io, libc::IPPROTO_IPV6, libc::IPV6_RECVPKTINFO, OPTION_ON)?;
149            set_socket_option(&*io, libc::IPPROTO_IPV6, libc::IPV6_RECVTCLASS, OPTION_ON)?;
150            // Linux's IP_PMTUDISC_PROBE allows us to operate under interface MTU rather than the
151            // kernel's path MTU guess, but actually disabling fragmentation requires this too. See
152            // __ip6_append_data in ip6_output.c.
153            // Set `may_fragment` to `true` if this option is not supported on the platform.
154            may_fragment |= !set_socket_option_supported(
155                &*io,
156                libc::IPPROTO_IPV6,
157                libc::IPV6_DONTFRAG,
158                OPTION_ON,
159            )?;
160        }
161
162        let now = Instant::now();
163        Ok(Self {
164            last_send_error: Mutex::new(now.checked_sub(2 * IO_ERROR_LOG_INTERVAL).unwrap_or(now)),
165            max_gso_segments: AtomicUsize::new(gso::max_gso_segments(&*io)),
166            gro_segments,
167            may_fragment,
168            sendmsg_einval: AtomicBool::new(false),
169            #[cfg(apple_fast)]
170            apple_fast_path: AtomicBool::new(false),
171        })
172    }
173
174    /// Sends a [`Transmit`] on the given socket.
175    ///
176    /// This function will only ever return errors of kind [`io::ErrorKind::WouldBlock`].
177    /// All other errors will be logged and converted to `Ok`.
178    ///
179    /// UDP transmission errors are considered non-fatal because higher-level protocols must
180    /// employ retransmits and timeouts anyway in order to deal with UDP's unreliable nature.
181    /// Thus, logging is most likely the only thing you can do with these errors.
182    ///
183    /// If you would like to handle these errors yourself, use [`UdpSocketState::try_send`]
184    /// instead.
185    pub fn send(&self, socket: UdpSockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> {
186        match send(self, socket.0, transmit) {
187            Ok(()) => Ok(()),
188            Err(e) if e.kind() == io::ErrorKind::WouldBlock => Err(e),
189            // - EMSGSIZE is expected for MTU probes. Future work might be able to avoid
190            //   these by automatically clamping the MTUD upper bound to the interface MTU.
191            Err(e) if e.raw_os_error() == Some(libc::EMSGSIZE) => Ok(()),
192            Err(e) => {
193                log_sendmsg_error(&self.last_send_error, e, transmit);
194
195                Ok(())
196            }
197        }
198    }
199
200    /// Sends a [`Transmit`] on the given socket without any additional error handling.
201    pub fn try_send(&self, socket: UdpSockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> {
202        send(self, socket.0, transmit)
203    }
204
205    #[cfg(not(any(
206        apple,
207        target_os = "openbsd",
208        target_os = "netbsd",
209        target_os = "dragonfly",
210        target_os = "redox",
211        solarish
212    )))]
213    pub fn recv(
214        &self,
215        socket: UdpSockRef<'_>,
216        bufs: &mut [IoSliceMut<'_>],
217        meta: &mut [RecvMeta],
218    ) -> io::Result<usize> {
219        recv_via_recvmmsg(socket.0, bufs, meta)
220    }
221
222    #[cfg(apple_fast)]
223    pub fn recv(
224        &self,
225        socket: UdpSockRef<'_>,
226        bufs: &mut [IoSliceMut<'_>],
227        meta: &mut [RecvMeta],
228    ) -> io::Result<usize> {
229        if self.is_apple_fast_path_enabled() {
230            recv_via_recvmsg_x(self, socket.0, bufs, meta)
231        } else {
232            recv_single(socket.0, bufs, meta)
233        }
234    }
235
236    #[cfg(any(
237        target_os = "openbsd",
238        target_os = "netbsd",
239        target_os = "dragonfly",
240        target_os = "redox",
241        solarish,
242        apple_slow
243    ))]
244    pub fn recv(
245        &self,
246        socket: UdpSockRef<'_>,
247        bufs: &mut [IoSliceMut<'_>],
248        meta: &mut [RecvMeta],
249    ) -> io::Result<usize> {
250        recv_single(socket.0, bufs, meta)
251    }
252
253    /// Maximum number of segments to transmit if Generic Send Offload (GSO) is enabled.
254    ///
255    /// This is 1 if the platform doesn't support GSO.
256    ///
257    /// Subject to change if errors are detected while using GSO.
258    #[inline]
259    pub fn max_gso_segments(&self) -> NonZeroUsize {
260        self.max_gso_segments
261            .load(Ordering::Relaxed)
262            .try_into()
263            .expect("must have non zero GSO segments")
264    }
265
266    /// The number of segments to read when GRO is enabled.
267    ///
268    /// Used as a factor to compute the receive buffer size.
269    ///
270    /// Returns 1 if the platform doesn't support GRO.
271    #[inline]
272    pub fn gro_segments(&self) -> NonZeroUsize {
273        self.gro_segments
274    }
275
276    /// Resize the send buffer of `socket` to `bytes`
277    #[inline]
278    pub fn set_send_buffer_size(&self, socket: UdpSockRef<'_>, bytes: usize) -> io::Result<()> {
279        socket.0.set_send_buffer_size(bytes)
280    }
281
282    /// Resize the receive buffer of `socket` to `bytes`
283    #[inline]
284    pub fn set_recv_buffer_size(&self, socket: UdpSockRef<'_>, bytes: usize) -> io::Result<()> {
285        socket.0.set_recv_buffer_size(bytes)
286    }
287
288    /// Get the size of the `socket` send buffer
289    #[inline]
290    pub fn send_buffer_size(&self, socket: UdpSockRef<'_>) -> io::Result<usize> {
291        socket.0.send_buffer_size()
292    }
293
294    /// Get the size of the `socket` receive buffer
295    #[inline]
296    pub fn recv_buffer_size(&self, socket: UdpSockRef<'_>) -> io::Result<usize> {
297        socket.0.recv_buffer_size()
298    }
299
300    /// Whether transmitted datagrams might get fragmented by the IP layer
301    ///
302    /// Returns `false` on targets which employ e.g. the `IPV6_DONTFRAG` socket option.
303    #[inline]
304    pub fn may_fragment(&self) -> bool {
305        self.may_fragment
306    }
307
308    /// Returns true if we previously got an EINVAL error from `sendmsg` syscall.
309    pub(crate) fn sendmsg_einval(&self) -> bool {
310        self.sendmsg_einval.load(Ordering::Relaxed)
311    }
312
313    /// Sets the flag indicating we got EINVAL error from `sendmsg` syscall.
314    #[cfg(not(any(apple, target_os = "openbsd", target_os = "netbsd")))]
315    fn set_sendmsg_einval(&self) {
316        self.sendmsg_einval.store(true, Ordering::Relaxed)
317    }
318
319    /// Enables Apple's fast UDP datapath using private `sendmsg_x`/`recvmsg_x` APIs.
320    ///
321    /// Once enabled, this also updates [`max_gso_segments`] to allow batched sends.
322    ///
323    /// # Safety
324    ///
325    /// These APIs may crash on unsupported OS versions, so callers must verify
326    /// availability before enabling.
327    ///
328    /// [`max_gso_segments`]: Self::max_gso_segments
329    #[cfg(apple_fast)]
330    pub unsafe fn set_apple_fast_path(&self) {
331        self.apple_fast_path.store(true, Ordering::Relaxed);
332        self.max_gso_segments.store(BATCH_SIZE, Ordering::Relaxed);
333    }
334
335    /// Returns whether Apple's fast UDP datapath is enabled for this socket.
336    #[cfg(apple_fast)]
337    pub fn is_apple_fast_path_enabled(&self) -> bool {
338        self.apple_fast_path.load(Ordering::Relaxed)
339    }
340
341    /// Disables Apple's fast UDP datapath, reverting to `sendmsg`/`recvmsg`.
342    #[cfg(apple_fast)]
343    fn disable_apple_fast_path(&self) {
344        self.apple_fast_path.store(false, Ordering::Relaxed);
345        self.max_gso_segments.store(1, Ordering::Relaxed);
346    }
347
348    /// Resolves an Apple fast-path function pointer via `resolver`.
349    ///
350    /// Disables the fast path if the symbol is absent so that future calls use the slow path
351    /// directly.
352    #[cfg(apple_fast)]
353    pub(crate) fn resolve_apple_fast_fn<T>(&self, resolver: fn() -> Option<T>) -> Option<T> {
354        let f = resolver();
355        if f.is_none() {
356            self.disable_apple_fast_path();
357        }
358        f
359    }
360}
361
362#[cfg(not(any(apple, target_os = "openbsd", target_os = "netbsd")))]
363fn send(
364    #[allow(unused_variables)] // only used on Linux
365    state: &UdpSocketState,
366    io: SockRef<'_>,
367    transmit: &Transmit<'_>,
368) -> io::Result<()> {
369    #[allow(unused_mut)] // only mutable on FreeBSD
370    let mut encode_src_ip = true;
371    #[cfg(target_os = "freebsd")]
372    {
373        let addr = io.local_addr()?;
374        let is_ipv4 = addr.family() == libc::AF_INET as libc::sa_family_t;
375        if is_ipv4 {
376            if let Some(socket) = addr.as_socket_ipv4() {
377                encode_src_ip = socket.ip() == &Ipv4Addr::UNSPECIFIED;
378            }
379        }
380    }
381    let mut msg_hdr: libc::msghdr = unsafe { mem::zeroed() };
382    let mut iovec: libc::iovec = unsafe { mem::zeroed() };
383    let mut cmsgs = cmsg::SendBuf::zeroed();
384    let dst_addr = socket2::SockAddr::from(transmit.destination);
385    prepare_msg(
386        transmit,
387        &dst_addr,
388        &mut msg_hdr,
389        &mut iovec,
390        &mut cmsgs,
391        encode_src_ip,
392        state.sendmsg_einval(),
393    );
394
395    loop {
396        let n = unsafe { libc::sendmsg(io.as_raw_fd(), &msg_hdr, 0) };
397
398        if n >= 0 {
399            return Ok(());
400        }
401
402        let e = io::Error::last_os_error();
403        match e.kind() {
404            // Retry the transmission
405            io::ErrorKind::Interrupted => continue,
406            io::ErrorKind::WouldBlock => return Err(e),
407            _ => {
408                // Some network adapters and drivers do not support GSO. Unfortunately, Linux
409                // offers no easy way for us to detect this short of an EIO or sometimes EINVAL
410                // when we try to actually send datagrams using it.
411                #[cfg(any(target_os = "linux", target_os = "android"))]
412                if let Some(libc::EIO) | Some(libc::EINVAL) = e.raw_os_error() {
413                    // Prevent new transmits from being scheduled using GSO. Existing GSO transmits
414                    // may already be in the pipeline, so we need to tolerate additional failures.
415                    if state.max_gso_segments().get() > 1 {
416                        crate::log::info!(
417                            "`libc::sendmsg` failed with {e}; halting segmentation offload"
418                        );
419                        state.max_gso_segments.store(1, Ordering::Relaxed);
420                    }
421                }
422
423                // Some arguments to `sendmsg` are not supported. Switch to
424                // fallback mode and retry if we haven't already.
425                if e.raw_os_error() == Some(libc::EINVAL) && !state.sendmsg_einval() {
426                    state.set_sendmsg_einval();
427                    prepare_msg(
428                        transmit,
429                        &dst_addr,
430                        &mut msg_hdr,
431                        &mut iovec,
432                        &mut cmsgs,
433                        encode_src_ip,
434                        state.sendmsg_einval(),
435                    );
436                    continue;
437                }
438
439                return Err(e);
440            }
441        }
442    }
443}
444
445#[cfg(any(target_os = "openbsd", target_os = "netbsd", apple_slow))]
446fn send(state: &UdpSocketState, io: SockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> {
447    send_single(state, io, transmit)
448}
449
450#[cfg(any(target_os = "openbsd", target_os = "netbsd", apple))]
451#[cfg_attr(apple_fast, allow(dead_code))] // Unused when apple_fast is enabled
452pub(crate) fn send_single(
453    state: &UdpSocketState,
454    io: SockRef<'_>,
455    transmit: &Transmit<'_>,
456) -> io::Result<()> {
457    let mut hdr: libc::msghdr = unsafe { mem::zeroed() };
458    let mut iov: libc::iovec = unsafe { mem::zeroed() };
459    let mut ctrl = cmsg::SendBuf::zeroed();
460    let addr = socket2::SockAddr::from(transmit.destination);
461    prepare_msg(
462        transmit,
463        &addr,
464        &mut hdr,
465        &mut iov,
466        &mut ctrl,
467        cfg!(apple) || cfg!(target_os = "openbsd") || cfg!(target_os = "netbsd"),
468        state.sendmsg_einval(),
469    );
470    retry_if_interrupted(|| unsafe { libc::sendmsg(io.as_raw_fd(), &hdr, 0) })?;
471    Ok(())
472}
473
474/// Receive using the batched `recvmmsg` syscall.
475#[cfg(not(any(
476    apple,
477    target_os = "openbsd",
478    target_os = "netbsd",
479    target_os = "dragonfly",
480    target_os = "redox",
481    solarish
482)))]
483fn recv_via_recvmmsg(
484    io: SockRef<'_>,
485    bufs: &mut [IoSliceMut<'_>],
486    meta: &mut [RecvMeta],
487) -> io::Result<usize> {
488    let mut names = [MaybeUninit::<libc::sockaddr_storage>::uninit(); BATCH_SIZE];
489    let mut ctrls = [cmsg::RecvBuf::uninit(); BATCH_SIZE];
490    let mut hdrs = unsafe { mem::zeroed::<[libc::mmsghdr; BATCH_SIZE]>() };
491    let max_msg_count = bufs.len().min(BATCH_SIZE);
492    for i in 0..max_msg_count {
493        prepare_recv(
494            &mut bufs[i],
495            &mut names[i],
496            &mut ctrls[i],
497            &mut hdrs[i].msg_hdr,
498        );
499    }
500    let msg_count = retry_if_interrupted(|| unsafe {
501        libc::recvmmsg(
502            io.as_raw_fd(),
503            hdrs.as_mut_ptr(),
504            bufs.len().min(BATCH_SIZE) as _,
505            0,
506            ptr::null_mut::<libc::timespec>(),
507        ) as isize
508    })?;
509    for i in 0..(msg_count as usize) {
510        meta[i] = decode_recv(&names[i], &hdrs[i].msg_hdr, hdrs[i].msg_len as usize)?;
511    }
512    Ok(msg_count as usize)
513}
514
515#[cfg(any(
516    target_os = "openbsd",
517    target_os = "netbsd",
518    target_os = "dragonfly",
519    target_os = "redox",
520    solarish,
521    apple
522))]
523#[cfg_attr(apple_fast, allow(dead_code))] // Unused when apple_fast is enabled
524pub(crate) fn recv_single(
525    io: SockRef<'_>,
526    bufs: &mut [IoSliceMut<'_>],
527    meta: &mut [RecvMeta],
528) -> io::Result<usize> {
529    let mut name = MaybeUninit::<libc::sockaddr_storage>::uninit();
530    let mut ctrl = cmsg::RecvBuf::uninit();
531    let mut hdr = unsafe { mem::zeroed::<libc::msghdr>() };
532    prepare_recv(&mut bufs[0], &mut name, &mut ctrl, &mut hdr);
533    let n = loop {
534        let n = unsafe { libc::recvmsg(io.as_raw_fd(), &mut hdr, 0) };
535
536        if hdr.msg_flags & libc::MSG_TRUNC != 0 {
537            continue;
538        }
539
540        if n >= 0 {
541            break n;
542        }
543
544        let e = io::Error::last_os_error();
545        match e.kind() {
546            // Retry receiving
547            io::ErrorKind::Interrupted => continue,
548            _ => return Err(e),
549        }
550    };
551    meta[0] = decode_recv(&name, &hdr, n as usize)?;
552    Ok(1)
553}
554
555#[cfg_attr(apple_fast, allow(dead_code))] // Unused when apple_fast is enabled
556fn prepare_msg(
557    transmit: &Transmit<'_>,
558    dst_addr: &socket2::SockAddr,
559    hdr: &mut libc::msghdr,
560    iov: &mut libc::iovec,
561    ctrl: &mut cmsg::SendBuf,
562    #[allow(unused_variables)] // only used on FreeBSD & macOS
563    encode_src_ip: bool,
564    sendmsg_einval: bool,
565) {
566    iov.iov_base = transmit.contents.as_ptr() as *const _ as *mut _;
567    iov.iov_len = transmit.contents.len();
568
569    // SAFETY: Casting the pointer to a mutable one is legal,
570    // as sendmsg is guaranteed to not alter the mutable pointer
571    // as per the POSIX spec. See the section on the sys/socket.h
572    // header for details. The type is only mutable in the first
573    // place because it is reused by recvmsg as well.
574    let name = dst_addr.as_ptr() as *mut libc::c_void;
575    let namelen = dst_addr.len();
576    hdr.msg_name = name as *mut _;
577    hdr.msg_namelen = namelen;
578    hdr.msg_iov = iov;
579    hdr.msg_iovlen = 1;
580
581    hdr.msg_control = ctrl.as_mut_ptr() as _;
582    hdr.msg_controllen = ctrl.len() as _;
583    let mut encoder = unsafe { cmsg::Encoder::new(hdr) };
584    let ecn = transmit.ecn.map_or(0, |x| x as libc::c_int);
585    // True for IPv4 or IPv4-Mapped IPv6
586    let is_ipv4 = transmit.destination.is_ipv4()
587        || matches!(transmit.destination.ip(), IpAddr::V6(addr) if addr.to_ipv4_mapped().is_some());
588    if is_ipv4 {
589        if !sendmsg_einval {
590            #[cfg(not(target_os = "netbsd"))]
591            {
592                encoder.push_ecn_v4(ecn as IpTosTy);
593            }
594        }
595    } else {
596        #[cfg(not(target_os = "redox"))]
597        encoder.push_ecn_v6(ecn);
598    }
599
600    // On apple_fast, prepare_msg is only compiled for send_single (fallback path), while the main
601    // send path uses prepare_msg_x with msghdr_x. gso::set_segment_size has a different signature
602    // when apple_fast is enabled, and it's a no-op on non-Linux platforms anyway.
603    #[cfg(not(apple_fast))]
604    if let Some(segment_size) = transmit.effective_segment_size() {
605        gso::set_segment_size(&mut encoder, segment_size as u16);
606    }
607
608    if let Some(ip) = &transmit.src_ip {
609        match ip {
610            IpAddr::V4(v4) => {
611                #[cfg(any(target_os = "linux", target_os = "android"))]
612                {
613                    let pktinfo = libc::in_pktinfo {
614                        ipi_ifindex: 0,
615                        ipi_spec_dst: libc::in_addr {
616                            s_addr: u32::from_ne_bytes(v4.octets()),
617                        },
618                        ipi_addr: libc::in_addr { s_addr: 0 },
619                    };
620                    encoder.push_pktinfo_v4(pktinfo);
621                }
622                #[cfg(any(bsd, apple, solarish))]
623                {
624                    if encode_src_ip {
625                        let addr = libc::in_addr {
626                            s_addr: u32::from_ne_bytes(v4.octets()),
627                        };
628                        encoder.push_src_addr_v4(addr);
629                    }
630                }
631            }
632            #[cfg(target_os = "redox")]
633            IpAddr::V6(_) => {}
634            #[cfg(not(target_os = "redox"))]
635            IpAddr::V6(v6) => {
636                let pktinfo = libc::in6_pktinfo {
637                    ipi6_ifindex: 0,
638                    ipi6_addr: libc::in6_addr {
639                        s6_addr: v6.octets(),
640                    },
641                };
642                encoder.push_pktinfo_v6(pktinfo);
643            }
644        }
645    }
646
647    encoder.finish();
648}
649
650#[cfg_attr(apple_fast, allow(dead_code))] // Unused when apple_fast is enabled
651fn prepare_recv(
652    buf: &mut IoSliceMut<'_>,
653    name: &mut MaybeUninit<libc::sockaddr_storage>,
654    ctrl: &mut cmsg::RecvBuf,
655    hdr: &mut libc::msghdr,
656) {
657    hdr.msg_name = name.as_mut_ptr() as _;
658    hdr.msg_namelen = size_of::<libc::sockaddr_storage>() as _;
659    hdr.msg_iov = buf as *mut IoSliceMut<'_> as *mut libc::iovec;
660    hdr.msg_iovlen = 1;
661    hdr.msg_control = ctrl.as_mut_ptr() as _;
662    hdr.msg_controllen = ctrl.len() as _;
663    hdr.msg_flags = 0;
664}
665
666pub(crate) fn decode_recv<M: cmsg::MsgHdr<ControlMessage = libc::cmsghdr>>(
667    name: &MaybeUninit<libc::sockaddr_storage>,
668    hdr: &M,
669    len: usize,
670) -> io::Result<RecvMeta> {
671    let name = unsafe { name.assume_init() };
672    let mut ctrl = ControlMetadata {
673        ecn_bits: 0,
674        dst_ip: None,
675        interface_index: None,
676        stride: len,
677        timestamp: None,
678    };
679
680    cmsg::warn_if_control_truncated(hdr);
681
682    let cmsg_iter = unsafe { cmsg::Iter::new(hdr) };
683    for cmsg in cmsg_iter {
684        ctrl.decode(cmsg);
685    }
686
687    Ok(RecvMeta {
688        len,
689        stride: ctrl.stride,
690        addr: decode_socket_addr(&name)?,
691        ecn: EcnCodepoint::from_bits(ctrl.ecn_bits),
692        dst_ip: ctrl.dst_ip,
693        interface_index: ctrl.interface_index,
694        timestamp: ctrl.timestamp,
695    })
696}
697
698/// Metadata decoded from control messages
699struct ControlMetadata {
700    ecn_bits: u8,
701    dst_ip: Option<IpAddr>,
702    interface_index: Option<u32>,
703    stride: usize,
704    timestamp: Option<Duration>,
705}
706
707impl ControlMetadata {
708    /// Decodes a control message and updates the metadata state
709    fn decode(&mut self, cmsg: &libc::cmsghdr) {
710        match (cmsg.cmsg_level, cmsg.cmsg_type) {
711            (libc::IPPROTO_IP, libc::IP_TOS) => unsafe {
712                self.ecn_bits = cmsg::decode::<u8, libc::cmsghdr>(cmsg);
713            },
714            // FreeBSD uses IP_RECVTOS here, and we can be liberal because cmsgs are opt-in.
715            #[cfg(not(any(
716                target_os = "openbsd",
717                target_os = "netbsd",
718                target_os = "dragonfly",
719                solarish
720            )))]
721            (libc::IPPROTO_IP, libc::IP_RECVTOS) => unsafe {
722                self.ecn_bits = cmsg::decode::<u8, libc::cmsghdr>(cmsg);
723            },
724            #[cfg(not(target_os = "redox",))]
725            (libc::IPPROTO_IPV6, libc::IPV6_TCLASS) => unsafe {
726                // Temporary hack around broken macos ABI. Remove once upstream fixes it.
727                // https://bugreport.apple.com/web/?problemID=48761855
728                #[allow(clippy::unnecessary_cast)] // cmsg.cmsg_len defined as size_t
729                if cfg!(apple)
730                    && cmsg.cmsg_len as usize == libc::CMSG_LEN(size_of::<u8>() as _) as usize
731                {
732                    self.ecn_bits = cmsg::decode::<u8, libc::cmsghdr>(cmsg);
733                } else {
734                    self.ecn_bits = cmsg::decode::<libc::c_int, libc::cmsghdr>(cmsg) as u8;
735                }
736            },
737            #[cfg(any(target_os = "linux", target_os = "android"))]
738            (libc::IPPROTO_IP, libc::IP_PKTINFO) => {
739                let pktinfo = unsafe { cmsg::decode::<libc::in_pktinfo, libc::cmsghdr>(cmsg) };
740                self.dst_ip = Some(IpAddr::V4(Ipv4Addr::from(
741                    pktinfo.ipi_addr.s_addr.to_ne_bytes(),
742                )));
743                self.interface_index = Some(pktinfo.ipi_ifindex as u32);
744            }
745            #[cfg(any(bsd, apple))]
746            (libc::IPPROTO_IP, libc::IP_RECVDSTADDR) => {
747                let in_addr = unsafe { cmsg::decode::<libc::in_addr, libc::cmsghdr>(cmsg) };
748                self.dst_ip = Some(IpAddr::V4(Ipv4Addr::from(in_addr.s_addr.to_ne_bytes())));
749            }
750            #[cfg(not(target_os = "redox",))]
751            (libc::IPPROTO_IPV6, libc::IPV6_PKTINFO) => {
752                let pktinfo = unsafe { cmsg::decode::<libc::in6_pktinfo, libc::cmsghdr>(cmsg) };
753                self.dst_ip = Some(IpAddr::V6(Ipv6Addr::from(pktinfo.ipi6_addr.s6_addr)));
754                #[cfg_attr(not(target_os = "android"), expect(clippy::unnecessary_cast))]
755                {
756                    self.interface_index = Some(pktinfo.ipi6_ifindex as u32);
757                }
758            }
759            #[cfg(any(target_os = "linux", target_os = "android"))]
760            (libc::SOL_UDP, libc::UDP_GRO) => unsafe {
761                self.stride = cmsg::decode::<libc::c_int, libc::cmsghdr>(cmsg) as usize;
762            },
763            #[cfg(any(target_os = "linux", target_os = "android"))]
764            (libc::SOL_SOCKET, libc::SCM_TIMESTAMPNS) => {
765                let ts = unsafe { cmsg::decode::<libc::timespec, libc::cmsghdr>(cmsg) };
766                let secs = u64::try_from(ts.tv_sec).unwrap_or(0);
767                let nsecs = u32::try_from(ts.tv_nsec).unwrap_or(0);
768                self.timestamp = Some(Duration::new(secs, nsecs));
769            }
770            _ => {}
771        }
772    }
773}
774
775/// Decodes a `sockaddr_storage` into a `SocketAddr`
776pub(crate) fn decode_socket_addr(name: &libc::sockaddr_storage) -> io::Result<SocketAddr> {
777    match libc::c_int::from(name.ss_family) {
778        libc::AF_INET => {
779            // Safety: if the ss_family field is AF_INET then storage must be a sockaddr_in.
780            let addr: &libc::sockaddr_in =
781                unsafe { &*(name as *const _ as *const libc::sockaddr_in) };
782            Ok(SocketAddr::V4(SocketAddrV4::new(
783                Ipv4Addr::from(addr.sin_addr.s_addr.to_ne_bytes()),
784                u16::from_be(addr.sin_port),
785            )))
786        }
787        libc::AF_INET6 => {
788            // Safety: if the ss_family field is AF_INET6 then storage must be a sockaddr_in6.
789            let addr: &libc::sockaddr_in6 =
790                unsafe { &*(name as *const _ as *const libc::sockaddr_in6) };
791            Ok(SocketAddr::V6(SocketAddrV6::new(
792                Ipv6Addr::from(addr.sin6_addr.s6_addr),
793                u16::from_be(addr.sin6_port),
794                addr.sin6_flowinfo,
795                addr.sin6_scope_id,
796            )))
797        }
798        f => Err(io::Error::other(format!(
799            "expected AF_INET or AF_INET6, got {f}"
800        ))),
801    }
802}
803
804#[cfg(not(apple_slow))]
805// Chosen somewhat arbitrarily; might benefit from additional tuning.
806pub(crate) const BATCH_SIZE: usize = 32;
807
808#[cfg(apple_slow)]
809pub(crate) const BATCH_SIZE: usize = 1;
810
811// On Apple platforms using the `sendmsg_x` call, UDP datagram segmentation is not
812// offloaded to the NIC or even the kernel, but instead done here in user space in
813// [`send`]) and then passed to the OS as individual `iovec`s (up to `BATCH_SIZE`).
814// The initial value is 1 (no batching); callers can enable batching via
815// `UdpSocketState::set_apple_fast_path()` which updates `max_gso_segments`.
816#[cfg(not(any(target_os = "linux", target_os = "android")))]
817mod gso {
818    use super::*;
819
820    pub(super) fn max_gso_segments(_socket: &impl AsRawFd) -> usize {
821        1
822    }
823
824    #[cfg_attr(apple_fast, allow(dead_code))] // Unused when apple_fast is enabled
825    pub(super) fn set_segment_size(
826        #[cfg(not(apple_fast))] _encoder: &mut cmsg::Encoder<'_, libc::msghdr>,
827        #[cfg(apple_fast)] _encoder: &mut cmsg::Encoder<'_, msghdr_x>,
828        _segment_size: u16,
829    ) {
830    }
831}
832
833#[cfg(target_os = "freebsd")]
834pub(crate) type IpTosTy = libc::c_uchar;
835#[cfg(not(any(target_os = "freebsd", target_os = "netbsd")))]
836pub(crate) type IpTosTy = libc::c_int;
837
838/// Returns whether the given socket option is supported on the current platform
839///
840/// Yields `Ok(true)` if the option was set successfully, `Ok(false)` if setting
841/// the option raised an `ENOPROTOOPT` or `EOPNOTSUPP` error, and `Err` for any other error.
842fn set_socket_option_supported(
843    socket: &impl AsRawFd,
844    level: libc::c_int,
845    name: libc::c_int,
846    value: libc::c_int,
847) -> io::Result<bool> {
848    match set_socket_option(socket, level, name, value) {
849        Ok(()) => Ok(true),
850        Err(err) if err.raw_os_error() == Some(libc::ENOPROTOOPT) => Ok(false),
851        Err(err) if err.raw_os_error() == Some(libc::EOPNOTSUPP) => Ok(false),
852        Err(err) => Err(err),
853    }
854}
855
856pub(crate) fn set_socket_option(
857    socket: &impl AsRawFd,
858    level: libc::c_int,
859    name: libc::c_int,
860    value: libc::c_int,
861) -> io::Result<()> {
862    let rc = unsafe {
863        libc::setsockopt(
864            socket.as_raw_fd(),
865            level,
866            name,
867            &value as *const _ as _,
868            size_of_val(&value) as _,
869        )
870    };
871
872    match rc == 0 {
873        true => Ok(()),
874        false => Err(io::Error::last_os_error()),
875    }
876}
877
878const OPTION_ON: libc::c_int = 1;
879
880/// Calls `f` in a loop, retrying on `EINTR`.
881///
882/// Returns the non-negative result or the first non-`EINTR` error.
883pub(crate) fn retry_if_interrupted(mut f: impl FnMut() -> isize) -> io::Result<isize> {
884    loop {
885        let n = f();
886        if n >= 0 {
887            return Ok(n);
888        }
889        let e = io::Error::last_os_error();
890        if e.kind() != io::ErrorKind::Interrupted {
891            return Err(e);
892        }
893    }
894}