use std::{io, net::SocketAddr, time::SystemTime};
use tokio::net::UdpSocket;
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
mod ancillary;
pub(crate) const SUPPORTED: bool = cfg!(any(
target_os = "linux",
target_os = "macos",
target_os = "freebsd"
));
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ReplySource {
Bound,
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
V4 { address: std::net::Ipv4Addr },
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
V6 {
address: std::net::Ipv6Addr,
interface_index: u32,
},
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct ReceivedDatagram {
pub(crate) len: usize,
pub(crate) peer: SocketAddr,
pub(crate) reply_source: ReplySource,
pub(crate) kernel_rx_timestamp: Option<SystemTime>,
}
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
pub(crate) fn configure_destination_metadata(socket: &UdpSocket, is_ipv4: bool) -> io::Result<()> {
ancillary::configure_destination_metadata(socket, is_ipv4)
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "freebsd")))]
pub(crate) fn configure_destination_metadata(
_socket: &UdpSocket,
_is_ipv4: bool,
) -> io::Result<()> {
Err(unsupported())
}
#[cfg(target_os = "linux")]
pub(crate) fn try_configure_kernel_rx_timestamp(socket: &UdpSocket) -> bool {
ancillary::try_configure_kernel_rx_timestamp(socket)
}
#[cfg(not(target_os = "linux"))]
pub(crate) fn try_configure_kernel_rx_timestamp(_socket: &UdpSocket) -> bool {
false
}
pub(crate) async fn receive(
socket: &UdpSocket,
buffer: &mut [u8],
select_reply_source: bool,
) -> io::Result<Option<ReceivedDatagram>> {
let capacity = buffer.len();
let received = if select_reply_source {
receive_with_destination(socket, buffer).await?
} else {
receive_bound(socket, buffer).await?
};
Ok(received.filter(|datagram| datagram.len < capacity))
}
#[cfg(target_os = "linux")]
async fn receive_bound(
socket: &UdpSocket,
buffer: &mut [u8],
) -> io::Result<Option<ReceivedDatagram>> {
ancillary::receive_bound(socket, buffer).await
}
#[cfg(not(target_os = "linux"))]
async fn receive_bound(
socket: &UdpSocket,
buffer: &mut [u8],
) -> io::Result<Option<ReceivedDatagram>> {
let (len, peer) = socket.recv_from(buffer).await?;
Ok(Some(ReceivedDatagram {
len,
peer,
reply_source: ReplySource::Bound,
kernel_rx_timestamp: None,
}))
}
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
async fn receive_with_destination(
socket: &UdpSocket,
buffer: &mut [u8],
) -> io::Result<Option<ReceivedDatagram>> {
ancillary::receive_with_destination(socket, buffer).await
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "freebsd")))]
async fn receive_with_destination(
_socket: &UdpSocket,
_buffer: &mut [u8],
) -> io::Result<Option<ReceivedDatagram>> {
Err(unsupported())
}
pub(crate) async fn send(
socket: &UdpSocket,
bytes: &[u8],
peer: SocketAddr,
reply_source: ReplySource,
) -> io::Result<usize> {
match reply_source {
ReplySource::Bound => socket.send_to(bytes, peer).await,
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd"))]
selected => ancillary::send_from_source(socket, bytes, peer, selected).await,
}
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "freebsd")))]
fn unsupported() -> io::Error {
io::Error::new(
io::ErrorKind::Unsupported,
"reply source-address selection is unsupported on this target",
)
}
#[cfg(test)]
mod tests {
use std::net::Ipv4Addr;
use super::*;
async fn receive_one(
capacity: usize,
payload: &[u8],
) -> (io::Result<Option<ReceivedDatagram>>, Vec<u8>) {
let listener = UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)).await.unwrap();
let sender = UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)).await.unwrap();
sender
.send_to(payload, listener.local_addr().unwrap())
.await
.unwrap();
let mut buffer = vec![0; capacity];
let received = receive(&listener, &mut buffer, false).await;
(received, buffer)
}
#[tokio::test(flavor = "current_thread")]
async fn a_datagram_shorter_than_the_buffer_reaches_the_core_intact() {
let payload = b"irtt";
let (received, buffer) = receive_one(16, payload).await;
let received = received
.unwrap()
.expect("a datagram that cannot have been truncated is accepted");
assert_eq!(received.len, payload.len());
assert_eq!(received.peer.ip(), Ipv4Addr::LOCALHOST);
assert_eq!(received.reply_source, ReplySource::Bound);
assert_eq!(&buffer[..received.len], payload);
}
#[tokio::test(flavor = "current_thread")]
async fn a_datagram_filling_the_buffer_is_dropped_as_possibly_truncated() {
let (received, _) = receive_one(4, b"irtt").await;
assert!(received.unwrap().is_none());
}
#[tokio::test(flavor = "current_thread")]
async fn a_datagram_larger_than_the_buffer_never_reaches_the_core() {
let (received, _) = receive_one(4, b"irtt-rs").await;
assert!(!matches!(received, Ok(Some(_))));
}
#[tokio::test(flavor = "current_thread")]
async fn a_datagram_with_no_kernel_timestamp_is_served_all_the_same() {
let (received, _) = receive_one(16, b"irtt").await;
let received = received.unwrap().expect("an untimestamped datagram serves");
assert_eq!(received.reply_source, ReplySource::Bound);
assert_eq!(received.kernel_rx_timestamp, None);
}
#[cfg(target_os = "linux")]
mod linux {
use std::net::SocketAddr;
use super::*;
async fn exchange(
listener: &UdpSocket,
select_reply_source: bool,
) -> (Option<ReceivedDatagram>, Vec<u8>) {
let payload = b"irtt";
let destination =
SocketAddr::from((Ipv4Addr::LOCALHOST, listener.local_addr().unwrap().port()));
let sender = UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)).await.unwrap();
sender.send_to(payload, destination).await.unwrap();
let mut buffer = vec![0; 64];
let received = receive(listener, &mut buffer, select_reply_source)
.await
.unwrap();
(received, buffer)
}
fn timestamp_or_skip(received: &ReceivedDatagram, what: &str) {
match received.kernel_rx_timestamp {
Some(timestamp) => assert!(
timestamp.duration_since(std::time::UNIX_EPOCH).is_ok(),
"a kernel receive timestamp is a wall-clock instant"
),
None => eprintln!(
"skipping {what} kernel timestamp assertion: \
kernel did not provide SCM_TIMESTAMPNS"
),
}
}
#[tokio::test(flavor = "current_thread")]
async fn an_explicit_listener_receives_a_datagram_and_its_kernel_timestamp() {
let listener = UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)).await.unwrap();
assert!(
try_configure_kernel_rx_timestamp(&listener),
"Linux loopback accepts SO_TIMESTAMPNS"
);
let (received, buffer) = exchange(&listener, false).await;
let received = received.expect("an explicit listener serves a short datagram");
assert_eq!(received.len, 4);
assert_eq!(&buffer[..received.len], b"irtt");
assert_eq!(received.peer.ip(), Ipv4Addr::LOCALHOST);
assert_eq!(received.reply_source, ReplySource::Bound);
timestamp_or_skip(&received, "explicit");
}
#[tokio::test(flavor = "current_thread")]
async fn a_wildcard_listener_recovers_its_destination_and_timestamp_together() {
let listener = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0)).await.unwrap();
configure_destination_metadata(&listener, true).unwrap();
assert!(try_configure_kernel_rx_timestamp(&listener));
let (received, buffer) = exchange(&listener, true).await;
let received = received.expect(
"a wildcard listener asking for two control messages still serves the datagram",
);
assert_eq!(received.len, 4);
assert_eq!(&buffer[..received.len], b"irtt");
assert_eq!(received.peer.ip(), Ipv4Addr::LOCALHOST);
assert_eq!(
received.reply_source,
ReplySource::V4 {
address: Ipv4Addr::LOCALHOST
},
"the required destination survives the added timestamp request"
);
timestamp_or_skip(&received, "wildcard");
}
#[tokio::test(flavor = "current_thread")]
async fn a_wildcard_listener_without_timestamps_still_recovers_its_destination() {
let listener = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0)).await.unwrap();
configure_destination_metadata(&listener, true).unwrap();
let (received, _) = exchange(&listener, true).await;
let received = received.expect("no timestamp requested, and none needed");
assert_eq!(
received.reply_source,
ReplySource::V4 {
address: Ipv4Addr::LOCALHOST
}
);
assert_eq!(received.kernel_rx_timestamp, None);
}
}
}