use std::{
io::{self, IoSliceMut},
net::{SocketAddr, UdpSocket},
os::fd::{AsFd, AsRawFd, RawFd},
time::{Duration, SystemTime, UNIX_EPOCH},
};
use nix::sys::{
socket::{
cmsg_space, recvmsg, setsockopt, sockopt, ControlMessageOwned, MsgFlags, RecvMsg,
TimestampingFlag, Timestamps,
},
time::TimeSpec,
};
use crate::{metadata::ReceiveMeta, receive::ReceivedDatagram, timing::ClientTimestamp};
pub(crate) mod error_queue;
use error_queue::ErrorQueueRecord;
pub(crate) const MAX_TX_TIMESTAMP_RECORDS_PER_DRAIN: usize = 32;
pub(crate) fn try_enable_tx_timestamping<S: AsFd>(socket: &S) -> bool {
if setsockopt(
socket,
sockopt::Timestamping,
&error_queue::TX_TIMESTAMPING_FLAGS,
)
.is_ok()
{
return true;
}
let _ = setsockopt(socket, sockopt::Timestamping, &RX_TIMESTAMPING_FLAGS);
false
}
pub(crate) fn drain_tx_timestamps<S: AsRawFd>(
socket: &S,
mut on_timestamp: impl FnMut(u32, SystemTime),
) -> io::Result<()> {
let fd = socket.as_raw_fd();
for _ in 0..MAX_TX_TIMESTAMP_RECORDS_PER_DRAIN {
match error_queue::try_recv_error_queue_record(fd)? {
None => return Ok(()),
Some(ErrorQueueRecord::TxTimestamp { id, timestamp }) => on_timestamp(id, timestamp),
Some(ErrorQueueRecord::MalformedOrUnsupportedTimestamp | ErrorQueueRecord::Ignored) => {
}
Some(ErrorQueueRecord::SocketError { errno, .. }) => {
return Err(io::Error::from_raw_os_error(errno));
}
}
}
Ok(())
}
const RX_TIMESTAMPING_FLAGS: TimestampingFlag = TimestampingFlag::SOF_TIMESTAMPING_RX_SOFTWARE
.union(TimestampingFlag::SOF_TIMESTAMPING_SOFTWARE);
const CONTROL_LEN: usize = cmsg_space::<Timestamps>() + cmsg_space::<i32>();
pub(crate) fn configure_receive_metadata(socket: &UdpSocket, remote: SocketAddr) -> io::Result<()> {
setsockopt(socket, sockopt::Timestamping, &RX_TIMESTAMPING_FLAGS)?;
let socket = socket2::SockRef::from(socket);
if remote.is_ipv4() {
socket.set_recv_tos_v4(true)
} else {
socket.set_recv_tclass_v6(true)
}
}
pub(crate) fn recv_datagram(
socket: &UdpSocket,
buf: &mut [u8],
) -> Result<ReceivedDatagram, io::Error> {
recv_datagram_fd(socket.as_raw_fd(), buf, MsgFlags::empty())
}
#[cfg(feature = "tokio")]
pub(crate) fn try_recv_tokio_datagram(
socket: &tokio::net::UdpSocket,
buf: &mut [u8],
) -> Result<ReceivedDatagram, io::Error> {
socket.try_io(tokio::io::Interest::READABLE, || {
recv_datagram_fd(socket.as_raw_fd(), buf, MsgFlags::MSG_DONTWAIT)
})
}
fn recv_datagram_fd(
socket_fd: RawFd,
buf: &mut [u8],
flags: MsgFlags,
) -> Result<ReceivedDatagram, io::Error> {
let mut control = ControlBuffer([0; CONTROL_LEN]);
let mut iov = [IoSliceMut::new(buf)];
let msg = recvmsg::<()>(socket_fd, &mut iov, Some(&mut control.0), flags)?;
let received_at = ClientTimestamp::now();
Ok(ReceivedDatagram {
len: msg.bytes,
received_at,
meta: receive_meta(&msg),
})
}
fn receive_meta<S>(msg: &RecvMsg<'_, '_, S>) -> ReceiveMeta {
let mut meta = ReceiveMeta::default();
let Ok(cmsgs) = msg.cmsgs() else {
return meta;
};
for cmsg in cmsgs {
match cmsg {
ControlMessageOwned::ScmTimestampsns(timestamps) => {
meta.kernel_rx_timestamp = system_time_from_timespec(timestamps.system);
}
ControlMessageOwned::Ipv4Tos(tos) => {
meta.traffic_class = Some(tos);
}
ControlMessageOwned::Ipv6TClass(traffic_class) => {
meta.traffic_class = u8::try_from(traffic_class).ok();
}
_ => {}
}
}
meta
}
fn system_time_from_timespec(timespec: TimeSpec) -> Option<SystemTime> {
let seconds = u64::try_from(timespec.tv_sec()).ok()?;
let nanos = u32::try_from(timespec.tv_nsec()).ok()?;
if nanos >= 1_000_000_000 {
return None;
}
UNIX_EPOCH.checked_add(Duration::new(seconds, nanos))
}
#[repr(align(8))]
struct ControlBuffer([u8; CONTROL_LEN]);