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
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:
101 files changed
+3636
-15159
No files matched your search
@@ -256,189 +256,3 @@ fn unsupported() -> io::Error {
|
||||
"reply source-address selection is unsupported on this target",
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Sends `payload` to a fresh explicit-address listener and returns what the
|
||||
/// transport layer decided about it, received into a `capacity`-byte buffer.
|
||||
///
|
||||
/// The buffer is deliberately tiny: the full-buffer rule is written against
|
||||
/// the caller's capacity, so it is provable without a 65,536-byte datagram.
|
||||
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);
|
||||
}
|
||||
|
||||
/// An exactly-full receive is ambiguous, not obviously fine: `recv_from`
|
||||
/// reports no truncation flag, so this datagram is indistinguishable from a
|
||||
/// larger one cut down to the same capacity. Both are dropped.
|
||||
#[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());
|
||||
}
|
||||
|
||||
/// What matters is that an oversized datagram never reaches the core, not
|
||||
/// how the platform says so: Unix truncates it to the buffer, which the
|
||||
/// full-buffer rule then drops, while Windows fails the receive outright
|
||||
/// with `WSAEMSGSIZE`. Asserting the shared guarantee keeps this test true
|
||||
/// on both.
|
||||
#[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(_))));
|
||||
}
|
||||
|
||||
/// A listener that never asked for kernel timestamps receives normally and
|
||||
/// reports none — the field is optional in the plainest sense, and its
|
||||
/// absence is not a defect to be worked around later.
|
||||
#[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);
|
||||
}
|
||||
|
||||
/// Linux-only coverage of the two receive paths, which is where the
|
||||
/// timestamp actually comes from.
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux {
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Sends `payload` from loopback to `listener` and receives it once.
|
||||
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)
|
||||
}
|
||||
|
||||
/// Reports the timestamp, or says why it is skipping the assertion
|
||||
/// rather than inventing one. Loopback under a normal kernel supplies
|
||||
/// it; a container or emulator that does not must not fail the run.
|
||||
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"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// The explicit-address listener's new `recvmsg` path: it must keep
|
||||
/// reporting exactly what `recv_from` did, and additionally see the
|
||||
/// timestamp `recv_from` had no way to retrieve.
|
||||
#[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");
|
||||
}
|
||||
|
||||
/// The regression this whole change risks: asking for a second control
|
||||
/// message must not crowd out the first. A wildcard listener with both
|
||||
/// enabled has to recover its destination *and* its timestamp from one
|
||||
/// datagram, with no `MSG_CTRUNC` drop and no dependence on the order
|
||||
/// the kernel returns them in.
|
||||
#[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");
|
||||
}
|
||||
|
||||
/// The timestamp is not a precondition for either listener: a wildcard
|
||||
/// one that never enabled it still recovers the destination its reply
|
||||
/// depends on and serves the request.
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user