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#[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 sendmsg_einval: AtomicBool,
44
45 #[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 #[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 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 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 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 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 {
140 if is_ipv4 {
141 set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_RECVDSTADDR, OPTION_ON)?;
142 }
143 }
144
145 #[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 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 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 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 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 #[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 #[inline]
272 pub fn gro_segments(&self) -> NonZeroUsize {
273 self.gro_segments
274 }
275
276 #[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 #[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 #[inline]
290 pub fn send_buffer_size(&self, socket: UdpSockRef<'_>) -> io::Result<usize> {
291 socket.0.send_buffer_size()
292 }
293
294 #[inline]
296 pub fn recv_buffer_size(&self, socket: UdpSockRef<'_>) -> io::Result<usize> {
297 socket.0.recv_buffer_size()
298 }
299
300 #[inline]
304 pub fn may_fragment(&self) -> bool {
305 self.may_fragment
306 }
307
308 pub(crate) fn sendmsg_einval(&self) -> bool {
310 self.sendmsg_einval.load(Ordering::Relaxed)
311 }
312
313 #[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 #[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 #[cfg(apple_fast)]
337 pub fn is_apple_fast_path_enabled(&self) -> bool {
338 self.apple_fast_path.load(Ordering::Relaxed)
339 }
340
341 #[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 #[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)] state: &UdpSocketState,
366 io: SockRef<'_>,
367 transmit: &Transmit<'_>,
368) -> io::Result<()> {
369 #[allow(unused_mut)] 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 io::ErrorKind::Interrupted => continue,
406 io::ErrorKind::WouldBlock => return Err(e),
407 _ => {
408 #[cfg(any(target_os = "linux", target_os = "android"))]
412 if let Some(libc::EIO) | Some(libc::EINVAL) = e.raw_os_error() {
413 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 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))] pub(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#[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))] pub(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 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))] fn 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)] 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 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 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 #[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))] fn 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
698struct 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 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 #[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 #[allow(clippy::unnecessary_cast)] 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
775pub(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 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 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))]
805pub(crate) const BATCH_SIZE: usize = 32;
807
808#[cfg(apple_slow)]
809pub(crate) const BATCH_SIZE: usize = 1;
810
811#[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))] 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
838fn 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
880pub(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}