use std::{marker::PhantomData, net::IpAddr};
use crate::socket::Timestamp;
#[cfg(target_os = "linux")]
const SCM_TIMESTAMPING_CMSG_SIZE: usize = control_message_space::<[libc::timespec; 3]>();
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
const SCM_TIMESTAMP_NS_CMSG_SIZE: usize = control_message_space::<libc::timespec>();
const SCM_TIMESTAMP_CMSG_SIZE: usize = control_message_space::<libc::timeval>();
#[cfg(target_os = "linux")]
const RECEIVERR_CMSG_SIZE: usize =
control_message_space::<(libc::sock_extended_err, libc::sockaddr_storage)>();
#[cfg(target_os = "linux")]
const IP_PKTINFO_CMSG_SIZE: usize = control_message_space::<libc::in_pktinfo>();
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
const IP_RECVDSTADDR_CMSG_SIZE: usize = control_message_space::<libc::in_addr>();
const IP6_PKTINFO_CMSG_SIZE: usize = control_message_space::<libc::in6_pktinfo>();
const fn max(a: usize, b: usize) -> usize {
if a > b {
a
} else {
b
}
}
#[cfg(target_os = "linux")]
pub(crate) const EXPECTED_MAX_CMSG_SIZE: usize =
max(
max(SCM_TIMESTAMPING_CMSG_SIZE, SCM_TIMESTAMP_NS_CMSG_SIZE),
SCM_TIMESTAMP_CMSG_SIZE,
) + max(IP_PKTINFO_CMSG_SIZE, IP6_PKTINFO_CMSG_SIZE)
+ RECEIVERR_CMSG_SIZE;
#[cfg(target_os = "freebsd")]
pub(crate) const EXPECTED_MAX_CMSG_SIZE: usize =
max(SCM_TIMESTAMP_NS_CMSG_SIZE, SCM_TIMESTAMP_CMSG_SIZE)
+ max(IP_RECVDSTADDR_CMSG_SIZE, IP6_PKTINFO_CMSG_SIZE);
#[cfg(target_os = "macos")]
pub(crate) const EXPECTED_MAX_CMSG_SIZE: usize =
SCM_TIMESTAMP_CMSG_SIZE + max(IP_RECVDSTADDR_CMSG_SIZE, IP6_PKTINFO_CMSG_SIZE);
#[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "macos")))]
pub(crate) const EXPECTED_MAX_CMSG_SIZE: usize = SCM_TIMESTAMP_CMSG_SIZE + IP6_PKTINFO_CMSG_SIZE;
const fn control_message_space<T>() -> usize {
(unsafe { libc::CMSG_SPACE((std::mem::size_of::<T>()) as _) }) as usize
}
pub(crate) enum MessageQueue {
Normal,
#[cfg(target_os = "linux")]
Error,
}
pub(crate) struct ControlMessageIterator<'a> {
mhdr: libc::msghdr,
next_msg: *const libc::cmsghdr,
phantom: PhantomData<&'a [u8]>,
}
impl ControlMessageIterator<'_> {
pub unsafe fn new(mhdr: libc::msghdr) -> Self {
let current_msg = if mhdr.msg_flags & libc::MSG_CTRUNC == 0 {
unsafe { libc::CMSG_FIRSTHDR(&mhdr) }
} else {
std::ptr::null()
};
Self {
mhdr,
next_msg: current_msg,
phantom: PhantomData,
}
}
}
pub(crate) enum ControlMessage {
Timestamping {
software: Option<Timestamp>,
hardware: Option<Timestamp>,
},
#[cfg(target_os = "linux")]
ReceiveError(libc::sock_extended_err),
DestinationIp(IpAddr),
Other(libc::cmsghdr),
}
#[cfg(target_os = "linux")]
const SCM_TIMESTAMP_NS: libc::c_int = libc::SCM_TIMESTAMPNS;
#[cfg(target_os = "freebsd")]
const SCM_TIMESTAMP_NS: libc::c_int = libc::SCM_REALTIME;
#[cfg(target_os = "linux")]
const PACKET_TX_TIMESTAMP: libc::c_int = 16;
impl Iterator for ControlMessageIterator<'_> {
type Item = ControlMessage;
fn next(&mut self) -> Option<Self::Item> {
let current_msg = unsafe { self.next_msg.as_ref() }?;
self.next_msg = unsafe { libc::CMSG_NXTHDR(&self.mhdr, self.next_msg) };
Some(match (current_msg.cmsg_level, current_msg.cmsg_type) {
#[cfg(target_os = "linux")]
(libc::SOL_SOCKET, libc::SCM_TIMESTAMPING) => {
let cmsg_data =
unsafe { libc::CMSG_DATA(current_msg) } as *const [libc::timespec; 3];
let [software, _, hardware] = unsafe { std::ptr::read_unaligned(cmsg_data) };
let hardware = if hardware.tv_sec != 0 || hardware.tv_nsec != 0 {
Some(Timestamp::from_timespec(hardware))
} else {
None
};
let software = if software.tv_sec != 0 || software.tv_nsec != 0 {
Some(Timestamp::from_timespec(software))
} else {
None
};
ControlMessage::Timestamping { software, hardware }
}
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
(libc::SOL_SOCKET, SCM_TIMESTAMP_NS) => {
let cmsg_data = unsafe { libc::CMSG_DATA(current_msg) } as *const libc::timespec;
let timespec = unsafe { std::ptr::read_unaligned(cmsg_data) };
ControlMessage::Timestamping {
software: Some(Timestamp::from_timespec(timespec)),
hardware: None,
}
}
(libc::SOL_SOCKET, libc::SCM_TIMESTAMP) => {
let cmsg_data = unsafe { libc::CMSG_DATA(current_msg) } as *const libc::timeval;
let timeval = unsafe { std::ptr::read_unaligned(cmsg_data) };
ControlMessage::Timestamping {
software: Some(Timestamp::from_timeval(timeval)),
hardware: None,
}
}
#[cfg(target_os = "linux")]
(libc::SOL_IP, libc::IP_RECVERR)
| (libc::SOL_IPV6, libc::IPV6_RECVERR)
| (libc::SOL_PACKET, PACKET_TX_TIMESTAMP) => {
let error = unsafe {
let ptr = libc::CMSG_DATA(current_msg) as *const libc::sock_extended_err;
std::ptr::read_unaligned(ptr)
};
ControlMessage::ReceiveError(error)
}
#[cfg(target_os = "linux")]
(libc::SOL_IP, libc::IP_PKTINFO) => {
let pktinfo = unsafe {
let ptr = libc::CMSG_DATA(current_msg) as *const libc::in_pktinfo;
std::ptr::read_unaligned(ptr)
};
ControlMessage::DestinationIp(
std::net::Ipv4Addr::from_bits(u32::from_be(pktinfo.ipi_addr.s_addr)).into(),
)
}
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
(libc::IPPROTO_IP, libc::IP_RECVDSTADDR) => {
let in_addr = unsafe {
let ptr = libc::CMSG_DATA(current_msg) as *const libc::in_addr;
std::ptr::read_unaligned(ptr)
};
ControlMessage::DestinationIp(
std::net::Ipv4Addr::from_bits(u32::from_be(in_addr.s_addr)).into(),
)
}
(libc::IPPROTO_IPV6, libc::IPV6_PKTINFO) => {
let pktinfo = unsafe {
let ptr = libc::CMSG_DATA(current_msg) as *const libc::in6_pktinfo;
std::ptr::read_unaligned(ptr)
};
ControlMessage::DestinationIp(
std::net::Ipv6Addr::from_bits(u128::from_be_bytes(pktinfo.ipi6_addr.s6_addr))
.into(),
)
}
_ => ControlMessage::Other(*current_msg),
})
}
}
pub(crate) fn zeroed_sockaddr_storage() -> libc::sockaddr_storage {
unsafe { std::mem::MaybeUninit::zeroed().assume_init() }
}
pub(crate) fn empty_msghdr() -> libc::msghdr {
unsafe { std::mem::MaybeUninit::<libc::msghdr>::zeroed().assume_init() }
}
pub(crate) fn empty_cmsghdr() -> libc::cmsghdr {
unsafe { std::mem::MaybeUninit::<libc::cmsghdr>::zeroed().assume_init() }
}