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 let mut cmsg_platform_space = 0;
57 #[cfg(not(target_os = "redox"))]
58 if cfg!(target_os = "linux")
59 || cfg!(bsd)
60 || cfg!(apple)
61 || cfg!(target_os = "android")
62 || cfg!(solarish)
63 {
64 cmsg_platform_space +=
65 unsafe { libc::CMSG_SPACE(size_of::<libc::in6_pktinfo>() as _) as usize };
66 }
67
68 assert!(
69 cmsg::LEN
70 >= unsafe { libc::CMSG_SPACE(size_of::<libc::c_int>() as _) as usize }
71 + cmsg_platform_space
72 );
73 assert!(
74 align_of::<libc::cmsghdr>() <= align_of::<cmsg::Aligned<[u8; 0]>>(),
75 "control message buffers will be misaligned"
76 );
77
78 io.set_nonblocking(true)?;
79
80 let addr = io.local_addr()?;
81 let is_ipv4 = addr.family() == libc::AF_INET as libc::sa_family_t;
82
83 #[cfg(not(any(
86 target_os = "openbsd",
87 target_os = "netbsd",
88 target_os = "dragonfly",
89 solarish
90 )))]
91 if (is_ipv4 || !io.only_v6()?)
92 && let Err(_err) =
93 set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_RECVTOS, OPTION_ON)
94 {
95 crate::log::debug!("Ignoring error setting IP_RECVTOS on socket: {_err:?}");
96 }
97
98 let mut may_fragment = false;
99 #[cfg_attr(
100 not(any(target_os = "linux", target_os = "android")),
101 expect(unused_mut)
102 )]
103 let mut gro_segments = NonZeroUsize::MIN;
104
105 #[cfg(any(target_os = "linux", target_os = "android"))]
106 {
107 may_fragment |= !set_socket_option_supported(
111 &*io,
112 libc::IPPROTO_IP,
113 libc::IP_MTU_DISCOVER,
114 libc::IP_PMTUDISC_PROBE,
115 )?;
116
117 if is_ipv4 {
118 set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_PKTINFO, OPTION_ON)?;
119 } else {
120 may_fragment |= !set_socket_option_supported(
122 &*io,
123 libc::IPPROTO_IPV6,
124 libc::IPV6_MTU_DISCOVER,
125 libc::IPV6_PMTUDISC_PROBE,
126 )?;
127 }
128
129 if set_socket_option(&*io, libc::SOL_UDP, libc::UDP_GRO, OPTION_ON).is_ok() {
130 gro_segments = NonZeroUsize::new(64).expect("known");
138 }
139
140 }
147 #[cfg(any(target_os = "freebsd", apple))]
148 {
149 if is_ipv4 {
150 may_fragment |= !set_socket_option_supported(
152 &*io,
153 libc::IPPROTO_IP,
154 libc::IP_DONTFRAG,
155 OPTION_ON,
156 )?;
157 }
158 }
159 #[cfg(any(bsd, apple, solarish))]
160 {
164 if is_ipv4 {
165 set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_RECVDSTADDR, OPTION_ON)?;
166 }
167 }
168
169 #[cfg(not(target_os = "redox"))]
171 if !is_ipv4 {
172 set_socket_option(&*io, libc::IPPROTO_IPV6, libc::IPV6_RECVPKTINFO, OPTION_ON)?;
173 set_socket_option(&*io, libc::IPPROTO_IPV6, libc::IPV6_RECVTCLASS, OPTION_ON)?;
174 may_fragment |= !set_socket_option_supported(
179 &*io,
180 libc::IPPROTO_IPV6,
181 libc::IPV6_DONTFRAG,
182 OPTION_ON,
183 )?;
184 }
185
186 let now = Instant::now();
187 Ok(Self {
188 last_send_error: Mutex::new(now.checked_sub(2 * IO_ERROR_LOG_INTERVAL).unwrap_or(now)),
189 max_gso_segments: AtomicUsize::new(gso::max_gso_segments(&*io)),
190 gro_segments,
191 may_fragment,
192 sendmsg_einval: AtomicBool::new(false),
193 #[cfg(apple_fast)]
194 apple_fast_path: AtomicBool::new(false),
195 })
196 }
197
198 pub fn send(&self, socket: UdpSockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> {
210 match send(self, socket.0, transmit) {
211 Ok(()) => Ok(()),
212 Err(e) if e.kind() == io::ErrorKind::WouldBlock => Err(e),
213 Err(e) if e.raw_os_error() == Some(libc::EMSGSIZE) => Ok(()),
216 Err(e) => {
217 log_sendmsg_error(&self.last_send_error, e, transmit);
218
219 Ok(())
220 }
221 }
222 }
223
224 pub fn try_send(&self, socket: UdpSockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> {
226 send(self, socket.0, transmit)
227 }
228
229 #[cfg(not(any(
230 apple,
231 target_os = "openbsd",
232 target_os = "netbsd",
233 target_os = "dragonfly",
234 target_os = "redox",
235 solarish
236 )))]
237 pub fn recv(
238 &self,
239 socket: UdpSockRef<'_>,
240 bufs: &mut [IoSliceMut<'_>],
241 meta: &mut [RecvMeta],
242 ) -> io::Result<usize> {
243 recv_via_recvmmsg(socket.0, bufs, meta)
244 }
245
246 #[cfg(apple_fast)]
247 pub fn recv(
248 &self,
249 socket: UdpSockRef<'_>,
250 bufs: &mut [IoSliceMut<'_>],
251 meta: &mut [RecvMeta],
252 ) -> io::Result<usize> {
253 if self.is_apple_fast_path_enabled() {
254 recv_via_recvmsg_x(self, socket.0, bufs, meta)
255 } else {
256 recv_single(socket.0, bufs, meta)
257 }
258 }
259
260 #[cfg(any(
261 target_os = "openbsd",
262 target_os = "netbsd",
263 target_os = "dragonfly",
264 target_os = "redox",
265 solarish,
266 apple_slow
267 ))]
268 pub fn recv(
269 &self,
270 socket: UdpSockRef<'_>,
271 bufs: &mut [IoSliceMut<'_>],
272 meta: &mut [RecvMeta],
273 ) -> io::Result<usize> {
274 recv_single(socket.0, bufs, meta)
275 }
276
277 #[inline]
283 pub fn max_gso_segments(&self) -> NonZeroUsize {
284 self.max_gso_segments
285 .load(Ordering::Relaxed)
286 .try_into()
287 .expect("must have non zero GSO segments")
288 }
289
290 #[inline]
296 pub fn gro_segments(&self) -> NonZeroUsize {
297 self.gro_segments
298 }
299
300 #[inline]
302 pub fn set_send_buffer_size(&self, socket: UdpSockRef<'_>, bytes: usize) -> io::Result<()> {
303 socket.0.set_send_buffer_size(bytes)
304 }
305
306 #[inline]
308 pub fn set_recv_buffer_size(&self, socket: UdpSockRef<'_>, bytes: usize) -> io::Result<()> {
309 socket.0.set_recv_buffer_size(bytes)
310 }
311
312 #[inline]
314 pub fn send_buffer_size(&self, socket: UdpSockRef<'_>) -> io::Result<usize> {
315 socket.0.send_buffer_size()
316 }
317
318 #[inline]
320 pub fn recv_buffer_size(&self, socket: UdpSockRef<'_>) -> io::Result<usize> {
321 socket.0.recv_buffer_size()
322 }
323
324 #[inline]
328 pub fn may_fragment(&self) -> bool {
329 self.may_fragment
330 }
331
332 pub(crate) fn sendmsg_einval(&self) -> bool {
334 self.sendmsg_einval.load(Ordering::Relaxed)
335 }
336
337 #[cfg(not(any(apple, target_os = "openbsd", target_os = "netbsd")))]
339 fn set_sendmsg_einval(&self) {
340 self.sendmsg_einval.store(true, Ordering::Relaxed)
341 }
342
343 #[cfg(apple_fast)]
354 pub unsafe fn set_apple_fast_path(&self) {
355 self.apple_fast_path.store(true, Ordering::Relaxed);
356 self.max_gso_segments.store(BATCH_SIZE, Ordering::Relaxed);
357 }
358
359 #[cfg(apple_fast)]
361 pub fn is_apple_fast_path_enabled(&self) -> bool {
362 self.apple_fast_path.load(Ordering::Relaxed)
363 }
364
365 #[cfg(apple_fast)]
367 fn disable_apple_fast_path(&self) {
368 self.apple_fast_path.store(false, Ordering::Relaxed);
369 self.max_gso_segments.store(1, Ordering::Relaxed);
370 }
371
372 #[cfg(apple_fast)]
377 pub(crate) fn resolve_apple_fast_fn<T>(&self, resolver: fn() -> Option<T>) -> Option<T> {
378 let f = resolver();
379 if f.is_none() {
380 self.disable_apple_fast_path();
381 }
382 f
383 }
384}
385
386#[cfg(not(any(apple, target_os = "openbsd", target_os = "netbsd")))]
387fn send(
388 #[allow(unused_variables)] state: &UdpSocketState,
390 io: SockRef<'_>,
391 transmit: &Transmit<'_>,
392) -> io::Result<()> {
393 #[allow(unused_mut)] let mut encode_src_ip = true;
395 #[cfg(target_os = "freebsd")]
396 {
397 let addr = io.local_addr()?;
398 let is_ipv4 = addr.family() == libc::AF_INET as libc::sa_family_t;
399 if is_ipv4 {
400 if let Some(socket) = addr.as_socket_ipv4() {
401 encode_src_ip = socket.ip() == &Ipv4Addr::UNSPECIFIED;
402 }
403 }
404 }
405 let mut msg_hdr: libc::msghdr = unsafe { mem::zeroed() };
406 let mut iovec: libc::iovec = unsafe { mem::zeroed() };
407 let mut cmsgs = cmsg::Aligned([0u8; cmsg::LEN]);
408 let dst_addr = socket2::SockAddr::from(transmit.destination);
409 prepare_msg(
410 transmit,
411 &dst_addr,
412 &mut msg_hdr,
413 &mut iovec,
414 &mut cmsgs,
415 encode_src_ip,
416 state.sendmsg_einval(),
417 );
418
419 loop {
420 let n = unsafe { libc::sendmsg(io.as_raw_fd(), &msg_hdr, 0) };
421
422 if n >= 0 {
423 return Ok(());
424 }
425
426 let e = io::Error::last_os_error();
427 match e.kind() {
428 io::ErrorKind::Interrupted => continue,
430 io::ErrorKind::WouldBlock => return Err(e),
431 _ => {
432 #[cfg(any(target_os = "linux", target_os = "android"))]
436 if let Some(libc::EIO) | Some(libc::EINVAL) = e.raw_os_error() {
437 if state.max_gso_segments().get() > 1 {
440 crate::log::info!(
441 "`libc::sendmsg` failed with {e}; halting segmentation offload"
442 );
443 state.max_gso_segments.store(1, Ordering::Relaxed);
444 }
445 }
446
447 if e.raw_os_error() == Some(libc::EINVAL) && !state.sendmsg_einval() {
450 state.set_sendmsg_einval();
451 prepare_msg(
452 transmit,
453 &dst_addr,
454 &mut msg_hdr,
455 &mut iovec,
456 &mut cmsgs,
457 encode_src_ip,
458 state.sendmsg_einval(),
459 );
460 continue;
461 }
462
463 return Err(e);
464 }
465 }
466 }
467}
468
469#[cfg(any(target_os = "openbsd", target_os = "netbsd", apple_slow))]
470fn send(state: &UdpSocketState, io: SockRef<'_>, transmit: &Transmit<'_>) -> io::Result<()> {
471 send_single(state, io, transmit)
472}
473
474#[cfg(any(target_os = "openbsd", target_os = "netbsd", apple))]
475#[cfg_attr(apple_fast, allow(dead_code))] pub(crate) fn send_single(
477 state: &UdpSocketState,
478 io: SockRef<'_>,
479 transmit: &Transmit<'_>,
480) -> io::Result<()> {
481 let mut hdr: libc::msghdr = unsafe { mem::zeroed() };
482 let mut iov: libc::iovec = unsafe { mem::zeroed() };
483 let mut ctrl = cmsg::Aligned([0u8; cmsg::LEN]);
484 let addr = socket2::SockAddr::from(transmit.destination);
485 prepare_msg(
486 transmit,
487 &addr,
488 &mut hdr,
489 &mut iov,
490 &mut ctrl,
491 cfg!(apple) || cfg!(target_os = "openbsd") || cfg!(target_os = "netbsd"),
492 state.sendmsg_einval(),
493 );
494 retry_if_interrupted(|| unsafe { libc::sendmsg(io.as_raw_fd(), &hdr, 0) })?;
495 Ok(())
496}
497
498#[cfg(not(any(
500 apple,
501 target_os = "openbsd",
502 target_os = "netbsd",
503 target_os = "dragonfly",
504 target_os = "redox",
505 solarish
506)))]
507fn recv_via_recvmmsg(
508 io: SockRef<'_>,
509 bufs: &mut [IoSliceMut<'_>],
510 meta: &mut [RecvMeta],
511) -> io::Result<usize> {
512 let mut names = [MaybeUninit::<libc::sockaddr_storage>::uninit(); BATCH_SIZE];
513 let mut ctrls = [cmsg::Aligned(MaybeUninit::<[u8; cmsg::LEN]>::uninit()); BATCH_SIZE];
514 let mut hdrs = unsafe { mem::zeroed::<[libc::mmsghdr; BATCH_SIZE]>() };
515 let max_msg_count = bufs.len().min(BATCH_SIZE);
516 for i in 0..max_msg_count {
517 prepare_recv(
518 &mut bufs[i],
519 &mut names[i],
520 &mut ctrls[i],
521 &mut hdrs[i].msg_hdr,
522 );
523 }
524 let msg_count = retry_if_interrupted(|| unsafe {
525 libc::recvmmsg(
526 io.as_raw_fd(),
527 hdrs.as_mut_ptr(),
528 bufs.len().min(BATCH_SIZE) as _,
529 0,
530 ptr::null_mut::<libc::timespec>(),
531 ) as isize
532 })?;
533 for i in 0..(msg_count as usize) {
534 meta[i] = decode_recv(&names[i], &hdrs[i].msg_hdr, hdrs[i].msg_len as usize)?;
535 }
536 Ok(msg_count as usize)
537}
538
539#[cfg(any(
540 target_os = "openbsd",
541 target_os = "netbsd",
542 target_os = "dragonfly",
543 target_os = "redox",
544 solarish,
545 apple
546))]
547#[cfg_attr(apple_fast, allow(dead_code))] pub(crate) fn recv_single(
549 io: SockRef<'_>,
550 bufs: &mut [IoSliceMut<'_>],
551 meta: &mut [RecvMeta],
552) -> io::Result<usize> {
553 let mut name = MaybeUninit::<libc::sockaddr_storage>::uninit();
554 let mut ctrl = cmsg::Aligned(MaybeUninit::<[u8; cmsg::LEN]>::uninit());
555 let mut hdr = unsafe { mem::zeroed::<libc::msghdr>() };
556 prepare_recv(&mut bufs[0], &mut name, &mut ctrl, &mut hdr);
557 let n = loop {
558 let n = unsafe { libc::recvmsg(io.as_raw_fd(), &mut hdr, 0) };
559
560 if hdr.msg_flags & libc::MSG_TRUNC != 0 {
561 continue;
562 }
563
564 if n >= 0 {
565 break n;
566 }
567
568 let e = io::Error::last_os_error();
569 match e.kind() {
570 io::ErrorKind::Interrupted => continue,
572 _ => return Err(e),
573 }
574 };
575 meta[0] = decode_recv(&name, &hdr, n as usize)?;
576 Ok(1)
577}
578
579#[cfg_attr(apple_fast, allow(dead_code))] fn prepare_msg(
581 transmit: &Transmit<'_>,
582 dst_addr: &socket2::SockAddr,
583 hdr: &mut libc::msghdr,
584 iov: &mut libc::iovec,
585 ctrl: &mut cmsg::Aligned<[u8; cmsg::LEN]>,
586 #[allow(unused_variables)] encode_src_ip: bool,
588 sendmsg_einval: bool,
589) {
590 iov.iov_base = transmit.contents.as_ptr() as *const _ as *mut _;
591 iov.iov_len = transmit.contents.len();
592
593 let name = dst_addr.as_ptr() as *mut libc::c_void;
599 let namelen = dst_addr.len();
600 hdr.msg_name = name as *mut _;
601 hdr.msg_namelen = namelen;
602 hdr.msg_iov = iov;
603 hdr.msg_iovlen = 1;
604
605 hdr.msg_control = ctrl.0.as_mut_ptr() as _;
606 hdr.msg_controllen = cmsg::LEN as _;
607 let mut encoder = unsafe { cmsg::Encoder::new(hdr) };
608 let ecn = transmit.ecn.map_or(0, |x| x as libc::c_int);
609 let is_ipv4 = transmit.destination.is_ipv4()
611 || matches!(transmit.destination.ip(), IpAddr::V6(addr) if addr.to_ipv4_mapped().is_some());
612 if is_ipv4 {
613 if !sendmsg_einval {
614 #[cfg(not(target_os = "netbsd"))]
615 {
616 encoder.push(libc::IPPROTO_IP, libc::IP_TOS, ecn as IpTosTy);
617 }
618 }
619 } else {
620 #[cfg(not(target_os = "redox"))]
621 encoder.push(libc::IPPROTO_IPV6, libc::IPV6_TCLASS, ecn);
622 }
623
624 #[cfg(not(apple_fast))]
628 if let Some(segment_size) = transmit.effective_segment_size() {
629 gso::set_segment_size(&mut encoder, segment_size as u16);
630 }
631
632 if let Some(ip) = &transmit.src_ip {
633 match ip {
634 IpAddr::V4(v4) => {
635 #[cfg(any(target_os = "linux", target_os = "android"))]
636 {
637 let pktinfo = libc::in_pktinfo {
638 ipi_ifindex: 0,
639 ipi_spec_dst: libc::in_addr {
640 s_addr: u32::from_ne_bytes(v4.octets()),
641 },
642 ipi_addr: libc::in_addr { s_addr: 0 },
643 };
644 encoder.push(libc::IPPROTO_IP, libc::IP_PKTINFO, pktinfo);
645 }
646 #[cfg(any(bsd, apple, solarish))]
647 {
648 if encode_src_ip {
649 let addr = libc::in_addr {
650 s_addr: u32::from_ne_bytes(v4.octets()),
651 };
652 encoder.push(libc::IPPROTO_IP, libc::IP_RECVDSTADDR, addr);
653 }
654 }
655 }
656 #[cfg(target_os = "redox")]
657 IpAddr::V6(_) => {}
658 #[cfg(not(target_os = "redox"))]
659 IpAddr::V6(v6) => {
660 let pktinfo = libc::in6_pktinfo {
661 ipi6_ifindex: 0,
662 ipi6_addr: libc::in6_addr {
663 s6_addr: v6.octets(),
664 },
665 };
666 encoder.push(libc::IPPROTO_IPV6, libc::IPV6_PKTINFO, pktinfo);
667 }
668 }
669 }
670
671 encoder.finish();
672}
673
674#[cfg_attr(apple_fast, allow(dead_code))] fn prepare_recv(
676 buf: &mut IoSliceMut<'_>,
677 name: &mut MaybeUninit<libc::sockaddr_storage>,
678 ctrl: &mut cmsg::Aligned<MaybeUninit<[u8; cmsg::LEN]>>,
679 hdr: &mut libc::msghdr,
680) {
681 hdr.msg_name = name.as_mut_ptr() as _;
682 hdr.msg_namelen = size_of::<libc::sockaddr_storage>() as _;
683 hdr.msg_iov = buf as *mut IoSliceMut<'_> as *mut libc::iovec;
684 hdr.msg_iovlen = 1;
685 hdr.msg_control = ctrl.0.as_mut_ptr() as _;
686 hdr.msg_controllen = cmsg::LEN as _;
687 hdr.msg_flags = 0;
688}
689
690pub(crate) fn decode_recv<M: cmsg::MsgHdr<ControlMessage = libc::cmsghdr>>(
691 name: &MaybeUninit<libc::sockaddr_storage>,
692 hdr: &M,
693 len: usize,
694) -> io::Result<RecvMeta> {
695 let name = unsafe { name.assume_init() };
696 let mut ctrl = ControlMetadata {
697 ecn_bits: 0,
698 dst_ip: None,
699 interface_index: None,
700 stride: len,
701 timestamp: None,
702 };
703
704 let cmsg_iter = unsafe { cmsg::Iter::new(hdr) };
705 for cmsg in cmsg_iter {
706 ctrl.decode(cmsg);
707 }
708
709 Ok(RecvMeta {
710 len,
711 stride: ctrl.stride,
712 addr: decode_socket_addr(&name)?,
713 ecn: EcnCodepoint::from_bits(ctrl.ecn_bits),
714 dst_ip: ctrl.dst_ip,
715 interface_index: ctrl.interface_index,
716 timestamp: ctrl.timestamp,
717 })
718}
719
720struct ControlMetadata {
722 ecn_bits: u8,
723 dst_ip: Option<IpAddr>,
724 interface_index: Option<u32>,
725 stride: usize,
726 timestamp: Option<Duration>,
727}
728
729impl ControlMetadata {
730 fn decode(&mut self, cmsg: &libc::cmsghdr) {
732 match (cmsg.cmsg_level, cmsg.cmsg_type) {
733 (libc::IPPROTO_IP, libc::IP_TOS) => unsafe {
734 self.ecn_bits = cmsg::decode::<u8, libc::cmsghdr>(cmsg);
735 },
736 #[cfg(not(any(
738 target_os = "openbsd",
739 target_os = "netbsd",
740 target_os = "dragonfly",
741 solarish
742 )))]
743 (libc::IPPROTO_IP, libc::IP_RECVTOS) => unsafe {
744 self.ecn_bits = cmsg::decode::<u8, libc::cmsghdr>(cmsg);
745 },
746 #[cfg(not(target_os = "redox",))]
747 (libc::IPPROTO_IPV6, libc::IPV6_TCLASS) => unsafe {
748 #[allow(clippy::unnecessary_cast)] if cfg!(apple)
752 && cmsg.cmsg_len as usize == libc::CMSG_LEN(size_of::<u8>() as _) as usize
753 {
754 self.ecn_bits = cmsg::decode::<u8, libc::cmsghdr>(cmsg);
755 } else {
756 self.ecn_bits = cmsg::decode::<libc::c_int, libc::cmsghdr>(cmsg) as u8;
757 }
758 },
759 #[cfg(any(target_os = "linux", target_os = "android"))]
760 (libc::IPPROTO_IP, libc::IP_PKTINFO) => {
761 let pktinfo = unsafe { cmsg::decode::<libc::in_pktinfo, libc::cmsghdr>(cmsg) };
762 self.dst_ip = Some(IpAddr::V4(Ipv4Addr::from(
763 pktinfo.ipi_addr.s_addr.to_ne_bytes(),
764 )));
765 self.interface_index = Some(pktinfo.ipi_ifindex as u32);
766 }
767 #[cfg(any(bsd, apple))]
768 (libc::IPPROTO_IP, libc::IP_RECVDSTADDR) => {
769 let in_addr = unsafe { cmsg::decode::<libc::in_addr, libc::cmsghdr>(cmsg) };
770 self.dst_ip = Some(IpAddr::V4(Ipv4Addr::from(in_addr.s_addr.to_ne_bytes())));
771 }
772 #[cfg(not(target_os = "redox",))]
773 (libc::IPPROTO_IPV6, libc::IPV6_PKTINFO) => {
774 let pktinfo = unsafe { cmsg::decode::<libc::in6_pktinfo, libc::cmsghdr>(cmsg) };
775 self.dst_ip = Some(IpAddr::V6(Ipv6Addr::from(pktinfo.ipi6_addr.s6_addr)));
776 #[cfg_attr(not(target_os = "android"), expect(clippy::unnecessary_cast))]
777 {
778 self.interface_index = Some(pktinfo.ipi6_ifindex as u32);
779 }
780 }
781 #[cfg(any(target_os = "linux", target_os = "android"))]
782 (libc::SOL_UDP, libc::UDP_GRO) => unsafe {
783 self.stride = cmsg::decode::<libc::c_int, libc::cmsghdr>(cmsg) as usize;
784 },
785 #[cfg(any(target_os = "linux", target_os = "android"))]
786 (libc::SOL_SOCKET, libc::SCM_TIMESTAMPNS) => {
787 let ts = unsafe { cmsg::decode::<libc::timespec, libc::cmsghdr>(cmsg) };
788 let secs = u64::try_from(ts.tv_sec).unwrap_or(0);
789 let nsecs = u32::try_from(ts.tv_nsec).unwrap_or(0);
790 self.timestamp = Some(Duration::new(secs, nsecs));
791 }
792 _ => {}
793 }
794 }
795}
796
797pub(crate) fn decode_socket_addr(name: &libc::sockaddr_storage) -> io::Result<SocketAddr> {
799 match libc::c_int::from(name.ss_family) {
800 libc::AF_INET => {
801 let addr: &libc::sockaddr_in =
803 unsafe { &*(name as *const _ as *const libc::sockaddr_in) };
804 Ok(SocketAddr::V4(SocketAddrV4::new(
805 Ipv4Addr::from(addr.sin_addr.s_addr.to_ne_bytes()),
806 u16::from_be(addr.sin_port),
807 )))
808 }
809 libc::AF_INET6 => {
810 let addr: &libc::sockaddr_in6 =
812 unsafe { &*(name as *const _ as *const libc::sockaddr_in6) };
813 Ok(SocketAddr::V6(SocketAddrV6::new(
814 Ipv6Addr::from(addr.sin6_addr.s6_addr),
815 u16::from_be(addr.sin6_port),
816 addr.sin6_flowinfo,
817 addr.sin6_scope_id,
818 )))
819 }
820 f => Err(io::Error::other(format!(
821 "expected AF_INET or AF_INET6, got {f}"
822 ))),
823 }
824}
825
826#[cfg(not(apple_slow))]
827pub(crate) const BATCH_SIZE: usize = 32;
829
830#[cfg(apple_slow)]
831pub(crate) const BATCH_SIZE: usize = 1;
832
833#[cfg(not(any(target_os = "linux", target_os = "android")))]
839mod gso {
840 use super::*;
841
842 pub(super) fn max_gso_segments(_socket: &impl AsRawFd) -> usize {
843 1
844 }
845
846 #[cfg_attr(apple_fast, allow(dead_code))] pub(super) fn set_segment_size(
848 #[cfg(not(apple_fast))] _encoder: &mut cmsg::Encoder<'_, libc::msghdr>,
849 #[cfg(apple_fast)] _encoder: &mut cmsg::Encoder<'_, msghdr_x>,
850 _segment_size: u16,
851 ) {
852 }
853}
854
855#[cfg(target_os = "freebsd")]
856type IpTosTy = libc::c_uchar;
857#[cfg(not(any(target_os = "freebsd", target_os = "netbsd")))]
858pub(crate) type IpTosTy = libc::c_int;
859
860fn set_socket_option_supported(
865 socket: &impl AsRawFd,
866 level: libc::c_int,
867 name: libc::c_int,
868 value: libc::c_int,
869) -> io::Result<bool> {
870 match set_socket_option(socket, level, name, value) {
871 Ok(()) => Ok(true),
872 Err(err) if err.raw_os_error() == Some(libc::ENOPROTOOPT) => Ok(false),
873 Err(err) if err.raw_os_error() == Some(libc::EOPNOTSUPP) => Ok(false),
874 Err(err) => Err(err),
875 }
876}
877
878pub(crate) fn set_socket_option(
879 socket: &impl AsRawFd,
880 level: libc::c_int,
881 name: libc::c_int,
882 value: libc::c_int,
883) -> io::Result<()> {
884 let rc = unsafe {
885 libc::setsockopt(
886 socket.as_raw_fd(),
887 level,
888 name,
889 &value as *const _ as _,
890 size_of_val(&value) as _,
891 )
892 };
893
894 match rc == 0 {
895 true => Ok(()),
896 false => Err(io::Error::last_os_error()),
897 }
898}
899
900const OPTION_ON: libc::c_int = 1;
901
902pub(crate) fn retry_if_interrupted(mut f: impl FnMut() -> isize) -> io::Result<isize> {
906 loop {
907 let n = f();
908 if n >= 0 {
909 return Ok(n);
910 }
911 let e = io::Error::last_os_error();
912 if e.kind() != io::ErrorKind::Interrupted {
913 return Err(e);
914 }
915 }
916}