tests: rework the actually useful tests to live in the integration crate
Rust CI / cargo fmt (push) Successful in 17s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 34s
Rust CI / cargo clippy (push) Successful in 35s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 8s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 10s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Successful in 1m14s
Rust CI / cargo doc (push) Successful in 23s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 29s
Supply chain / cargo-deny advisories (push) Successful in 9s
Supply chain / cargo-deny bans licenses sources (push) Successful in 5s
Supply chain / actionlint (push) Successful in 3s
Netem Interop / netem interop (push) Successful in 1m11s

This commit is contained in:
Nils Andreas Svee committed 2026-10-03 13:59:38 +07:00
1 parent 74e7532965
commit 48a50d7f3a
101 files changed
+3636 -15159

No files matched your search

@@ -181,448 +181,3 @@ pub(crate) fn try_recv_error_queue_record(fd: RawFd) -> io::Result<Option<ErrorQ
Err(errno) => Err(io::Error::from(errno)),
}
}
#[cfg(test)]
mod tests {
use std::{
net::{SocketAddr, UdpSocket},
os::fd::AsRawFd,
time::{Duration, Instant, UNIX_EPOCH},
};
use nix::sys::{
socket::{setsockopt, sockopt},
time::TimeSpec,
};
use super::*;
fn extended_error(
errno: u32,
origin: u8,
info: u32,
data: u32,
) -> nix::libc::sock_extended_err {
nix::libc::sock_extended_err {
ee_errno: errno,
ee_origin: origin,
ee_type: 0,
ee_code: 0,
ee_pad: 0,
ee_info: info,
ee_data: data,
}
}
fn timestamps_with_system(system: TimeSpec) -> Timestamps {
Timestamps {
system,
hw_trans: TimeSpec::new(0, 0),
hw_raw: TimeSpec::new(0, 0),
}
}
fn send_timestamp_error(id: u32) -> ControlMessageOwned {
ControlMessageOwned::Ipv4RecvErr(
extended_error(
nix::libc::ENOMSG as u32,
nix::libc::SO_EE_ORIGIN_TIMESTAMPING,
SCM_TSTAMP_SND,
id,
),
None,
)
}
#[test]
fn classifies_valid_send_timestamp_completion() {
let cmsgs = vec![
send_timestamp_error(7),
ControlMessageOwned::ScmTimestampsns(timestamps_with_system(TimeSpec::new(1, 2))),
];
let record = classify(cmsgs.into_iter());
assert_eq!(
record,
ErrorQueueRecord::TxTimestamp {
id: 7,
timestamp: UNIX_EPOCH + Duration::new(1, 2),
}
);
}
#[test]
fn classifies_timestamp_origin_without_timestamp_as_malformed() {
let cmsgs = vec![send_timestamp_error(3)];
assert_eq!(
classify(cmsgs.into_iter()),
ErrorQueueRecord::MalformedOrUnsupportedTimestamp
);
}
#[test]
fn classifies_timestamp_origin_with_invalid_timespec_as_malformed() {
let cmsgs = vec![
send_timestamp_error(3),
ControlMessageOwned::ScmTimestampsns(timestamps_with_system(TimeSpec::new(-1, 0))),
];
assert_eq!(
classify(cmsgs.into_iter()),
ErrorQueueRecord::MalformedOrUnsupportedTimestamp
);
}
#[test]
fn classifies_non_timestamp_extended_error_as_socket_error() {
let cmsgs = vec![ControlMessageOwned::Ipv4RecvErr(
extended_error(
nix::libc::ECONNREFUSED as u32,
nix::libc::SO_EE_ORIGIN_ICMP,
0,
0,
),
None,
)];
assert_eq!(
classify(cmsgs.into_iter()),
ErrorQueueRecord::SocketError {
errno: nix::libc::ECONNREFUSED,
origin: nix::libc::SO_EE_ORIGIN_ICMP,
}
);
}
#[test]
fn classifies_timestamping_origin_with_wrong_completion_kind_as_malformed() {
// Same origin byte as a send-timestamp completion, but a different
// `ee_info` (e.g. SCM_TSTAMP_SCHED/ACK, which this client never
// requests). A timestamping-origin record must never become a
// fatal SocketError merely because it reports a completion kind we
// did not ask for; it stays timing metadata.
let cmsgs = vec![ControlMessageOwned::Ipv4RecvErr(
extended_error(
nix::libc::ENOMSG as u32,
nix::libc::SO_EE_ORIGIN_TIMESTAMPING,
1,
0,
),
None,
)];
assert_eq!(
classify(cmsgs.into_iter()),
ErrorQueueRecord::MalformedOrUnsupportedTimestamp
);
}
#[test]
fn classifies_timestamping_origin_with_unexpected_errno_as_malformed() {
// Origin is the sole discriminator: even an origin==TIMESTAMPING
// record with a surprising errno (the kernel always reports ENOMSG
// for this origin, but classification must not assume that) is
// still timing metadata, never a real socket error.
let cmsgs = vec![ControlMessageOwned::Ipv4RecvErr(
extended_error(
nix::libc::ECONNREFUSED as u32,
nix::libc::SO_EE_ORIGIN_TIMESTAMPING,
SCM_TSTAMP_SND,
0,
),
None,
)];
assert_eq!(
classify(cmsgs.into_iter()),
ErrorQueueRecord::MalformedOrUnsupportedTimestamp
);
}
#[test]
fn ignores_records_without_any_extended_error() {
let cmsgs = vec![ControlMessageOwned::Ipv4Tos(0)];
assert_eq!(classify(cmsgs.into_iter()), ErrorQueueRecord::Ignored);
}
#[test]
fn unknown_cmsgs_do_not_affect_classification() {
let cmsgs = vec![
ControlMessageOwned::Ipv4Tos(0),
send_timestamp_error(4),
ControlMessageOwned::ScmTimestampsns(timestamps_with_system(TimeSpec::new(9, 0))),
];
assert_eq!(
classify(cmsgs.into_iter()),
ErrorQueueRecord::TxTimestamp {
id: 4,
timestamp: UNIX_EPOCH + Duration::new(9, 0),
}
);
}
#[test]
fn cmsg_order_does_not_affect_classification() {
let forward = classify(
vec![
send_timestamp_error(9),
ControlMessageOwned::ScmTimestampsns(timestamps_with_system(TimeSpec::new(5, 0))),
]
.into_iter(),
);
let reversed = classify(
vec![
ControlMessageOwned::ScmTimestampsns(timestamps_with_system(TimeSpec::new(5, 0))),
send_timestamp_error(9),
]
.into_iter(),
);
assert_eq!(forward, reversed);
}
/// Bounded poll for one error-queue record, since software TX timestamp
/// delivery is asynchronous even on loopback. Never sleeps hoping
/// delivery "probably" happened; it keeps polling until either a
/// record appears or the overall deadline passes.
fn poll_for_record(fd: RawFd, overall_timeout: Duration) -> Option<ErrorQueueRecord> {
let deadline = Instant::now() + overall_timeout;
loop {
if let Some(record) = try_recv_error_queue_record(fd).unwrap() {
return Some(record);
}
if Instant::now() >= deadline {
return None;
}
std::thread::yield_now();
}
}
fn tx_timestamped_loopback_socket() -> Option<(UdpSocket, UdpSocket)> {
let socket = UdpSocket::bind(SocketAddr::from(([127, 0, 0, 1], 0))).unwrap();
// Held for the caller's lifetime: if this peer socket were dropped,
// its port would close and subsequent sends would fail with
// ECONNREFUSED once the kernel reports ICMP port-unreachable.
let peer = UdpSocket::bind(SocketAddr::from(([127, 0, 0, 1], 0))).unwrap();
socket.connect(peer.local_addr().unwrap()).unwrap();
match setsockopt(&socket, sockopt::Timestamping, &TX_TIMESTAMPING_FLAGS) {
Ok(()) => Some((socket, peer)),
Err(error) => {
eprintln!(
"skipping TX timestamping smoke test: kernel/container denied \
SO_TIMESTAMPING TX flags: {error}"
);
None
}
}
}
#[test]
fn automatic_opt_id_increments_sequentially_across_separate_sends() {
let Some((socket, _peer)) = tx_timestamped_loopback_socket() else {
return;
};
let fd = socket.as_raw_fd();
// Under heavy parallel test load, loopback UDP sends can
// occasionally surface a stale ICMP-derived socket error unrelated
// to this test's own sockets. That is an environmental condition,
// not a claim this test makes about kernel TX timestamp behavior,
// so it is a skip rather than a failure.
if let Err(error) = socket.send(b"first") {
eprintln!("skipping automatic OPT_ID assertion: first send failed: {error}");
return;
}
let Some(first) = poll_for_record(fd, Duration::from_secs(2)) else {
eprintln!(
"skipping automatic OPT_ID assertion: no TX timestamp record was \
delivered within the poll deadline"
);
return;
};
assert!(
matches!(first, ErrorQueueRecord::TxTimestamp { id: 0, .. }),
"expected first send to report id 0, got {first:?}"
);
if let Err(error) = socket.send(b"second") {
eprintln!("skipping automatic OPT_ID assertion: second send failed: {error}");
return;
}
let Some(second) = poll_for_record(fd, Duration::from_secs(2)) else {
eprintln!(
"skipping automatic OPT_ID assertion: no TX timestamp record was \
delivered for the second send within the poll deadline"
);
return;
};
assert!(
matches!(second, ErrorQueueRecord::TxTimestamp { id: 1, .. }),
"expected second send to report id 1, got {second:?}"
);
}
#[test]
fn rx_only_sends_before_tx_enable_do_not_consume_the_later_tx_id_namespace() {
let socket = UdpSocket::bind(SocketAddr::from(([127, 0, 0, 1], 0))).unwrap();
let peer = UdpSocket::bind(SocketAddr::from(([127, 0, 0, 1], 0))).unwrap();
socket.connect(peer.local_addr().unwrap()).unwrap();
setsockopt(
&socket,
sockopt::Timestamping,
&super::super::RX_TIMESTAMPING_FLAGS,
)
.unwrap();
// Ordinary pre-enable sends: TX_SOFTWARE is not yet requested, so
// none of these may leave anything on the error queue for the
// later-enabled OPT_ID counter to have skipped past.
for _ in 0..3 {
if let Err(error) = socket.send(b"pre-enable") {
eprintln!("skipping pre-enable send/RX-only smoke test: {error}");
return;
}
}
assert_eq!(
try_recv_error_queue_record(socket.as_raw_fd()).unwrap(),
None,
"RX-only sends must never populate MSG_ERRQUEUE"
);
// This mirrors production: RX-only is configured first (e.g. at
// connect time), and only later, after a successful Open, is the
// socket upgraded to TX_TIMESTAMPING_FLAGS.
if setsockopt(&socket, sockopt::Timestamping, &TX_TIMESTAMPING_FLAGS).is_err() {
eprintln!(
"skipping pre-enable send/RX-only smoke test: kernel/container denied \
SO_TIMESTAMPING TX flags"
);
return;
}
if let Err(error) = socket.send(b"first-timestamped") {
eprintln!(
"skipping pre-enable send/RX-only smoke test: post-upgrade send failed: {error}"
);
return;
}
let Some(record) = poll_for_record(socket.as_raw_fd(), Duration::from_secs(2)) else {
eprintln!(
"skipping pre-enable send/RX-only smoke test: no TX timestamp record was \
delivered within the poll deadline"
);
return;
};
assert!(
matches!(record, ErrorQueueRecord::TxTimestamp { id: 0, .. }),
"expected the first send after upgrade to report id 0 (pre-enable sends must not \
have consumed part of the namespace), got {record:?}"
);
}
#[test]
fn normal_rx_scm_timestamping_still_works_after_tx_flags_are_enabled() {
let Some((socket, peer)) = tx_timestamped_loopback_socket() else {
return;
};
peer.send_to(b"reply", socket.local_addr().unwrap())
.unwrap();
let mut buf = [0_u8; 16];
let Ok(datagram) = super::super::recv_datagram(&socket, &mut buf) else {
eprintln!("skipping post-TX-enable RX smoke test: receive failed");
return;
};
assert_eq!(&buf[..datagram.len], b"reply");
let Some(timestamp) = datagram.meta.kernel_rx_timestamp else {
eprintln!(
"skipping post-TX-enable RX smoke test: kernel did not provide SCM_TIMESTAMPING"
);
return;
};
assert!(timestamp.duration_since(std::time::UNIX_EPOCH).unwrap() > Duration::ZERO);
}
#[test]
fn tx_timestamp_and_normal_reply_streams_do_not_interfere() {
let Some((socket, peer)) = tx_timestamped_loopback_socket() else {
return;
};
let fd = socket.as_raw_fd();
if let Err(error) = socket.send(b"ping") {
eprintln!("skipping TX/RX separation smoke test: ping send failed: {error}");
return;
}
let mut incoming = [0_u8; 16];
let Ok((len, from)) = peer.recv_from(&mut incoming) else {
eprintln!("skipping TX/RX separation smoke test: peer never observed ping");
return;
};
assert_eq!(&incoming[..len], b"ping");
peer.send_to(b"pong", from).unwrap();
// Reading the normal payload must not require, wait for, or be
// disturbed by draining MSG_ERRQUEUE first.
let mut reply_buf = [0_u8; 16];
let Ok(datagram) = super::super::recv_datagram(&socket, &mut reply_buf) else {
eprintln!("skipping TX/RX separation smoke test: normal receive failed");
return;
};
assert_eq!(&reply_buf[..datagram.len], b"pong");
// The TX timestamp for "ping" is still independently available on
// the error queue, unconsumed by the normal receive above.
let Some(record) = poll_for_record(fd, Duration::from_secs(2)) else {
eprintln!("skipping TX/RX separation smoke test: no TX timestamp record arrived");
return;
};
assert!(matches!(
record,
ErrorQueueRecord::TxTimestamp { id: 0, .. }
));
}
#[test]
fn tx_timestamp_correlation_does_not_depend_on_original_payload() {
let Some((socket, _peer)) = tx_timestamped_loopback_socket() else {
return;
};
// OPT_TSONLY notifications never carry the original payload; a
// larger payload must classify identically to the zero-length one
// used elsewhere, since correlation is by `id` alone.
let payload = vec![0x5A_u8; 512];
if let Err(error) = socket.send(&payload) {
eprintln!("skipping OPT_TSONLY payload-independence smoke test: send failed: {error}");
return;
}
let Some(record) = poll_for_record(socket.as_raw_fd(), Duration::from_secs(2)) else {
eprintln!(
"skipping OPT_TSONLY payload-independence smoke test: no TX timestamp record \
arrived"
);
return;
};
assert!(matches!(
record,
ErrorQueueRecord::TxTimestamp { id: 0, .. }
));
}
// NOTE: There is no live test for whether a failed nonblocking `sendmsg`
// consumes an automatic OPT_ID. Forcing a reliable, non-flaky `WouldBlock`
// on a connected loopback UDP socket (for example through socket-buffer
// saturation) has no deterministic OS-level mechanism in this environment,
// and the repository's testing policy forbids sleep/saturation-based
// flakiness. The correlation is intentionally best effort: a kernel ID can
// theoretically be consumed by a send that later fails. The deterministic
// adapter test covers only the local half: an injected `WouldBlock` does
// not advance the local wire-sequence counter. Any later unmatched kernel
// timestamp is discarded and the userspace `sent_at` remains the fallback.
}