test: cover timing, overflow, and target authentication contracts

This commit is contained in:
Nils Andreas Svee committed 2026-10-03 13:47:09 +07:00
1 parent 56fe86f543
commit f2c2153cc0
4 files changed
+150 -3

No files matched your search

+30
View File
@@ -144,6 +144,36 @@ fn default_client_output_emits_a_packet_summary() {
}
}
#[cfg(feature = "client")]
#[test]
fn target_authentication_inherits_overrides_or_disables_the_global_key() {
use clap::Parser;
use irtt_app::cmd::client::args::ClientArgs;
let args = ClientArgs::try_parse_from([
"irtt-client",
"--hmac",
"global-test-key",
"inherit=127.0.0.1:2112",
"override=127.0.0.1:2113@hmac=target-test-key",
"disable=127.0.0.1:2114@hmac=",
])
.unwrap();
let setup = args.prepare().unwrap();
assert_eq!(
setup.managed_config().client.hmac_key.as_deref(),
Some(b"global-test-key".as_slice())
);
let targets = setup.managed_targets();
let authentication: Vec<_> = targets
.iter()
.map(|target| target.auth.as_ref().map(|auth| auth.hmac_key.as_deref()))
.collect();
assert_eq!(
authentication,
[None, Some(Some(b"target-test-key".as_slice())), Some(None)]
);
}
// A real authenticated server detects broken client argument-to-config mapping.
#[cfg(feature = "client")]
#[test]
+72
View File
@@ -1314,6 +1314,78 @@ mod tests {
}
}
// Kernel completion order relative to timeout cannot be forced reliably
// through a real socket. Drive both transitions through production methods
// and observe the selected endpoint on the emitted measurable LateReply.
#[test]
fn late_replies_use_tx_timestamps_attached_before_or_after_timeout() {
for attach_before_timeout in [true, false] {
let mut machine = opened();
let anchor = ClientTimestamp {
mono: Instant::now(),
wall: UNIX_EPOCH + Duration::from_secs(10),
};
let sent_at = ClientTimestamp {
mono: anchor.mono + Duration::from_millis(1),
wall: anchor.wall + Duration::from_millis(1),
};
let kernel_tx = anchor.wall + Duration::from_micros(500);
let probe = machine.prepare_probe().unwrap();
let preflight = machine.preflight_probe_commit(&probe).unwrap();
let commit = machine.finalize_probe_commit(preflight, anchor).unwrap();
let sent = machine.commit_probe_sent(commit, sent_at, probe.bytes.len());
if attach_before_timeout {
machine.record_kernel_tx_timestamp(sent.seq, kernel_tx);
}
let deadline = machine.next_probe_timeout_deadline().unwrap();
assert!(matches!(
machine.poll_timeouts_at(deadline).unwrap()[0],
ClientEvent::EchoLoss { seq: 0, .. }
));
if !attach_before_timeout {
machine.record_kernel_tx_timestamp(sent.seq, kernel_tx);
}
let events = machine
.process_echo_reply(
EchoReply {
flags: FLAG_REPLY,
token: 1,
sequence: sent.seq,
recv_count: None,
recv_window: None,
timestamps: TimestampFields {
recv_wall: Some(12_000_000_000),
..TimestampFields::default()
},
payload: vec![],
},
probe.bytes.len(),
ClientTimestamp {
mono: deadline + Duration::from_millis(1),
wall: anchor.wall + Duration::from_secs(10),
},
ReceiveMeta::default(),
)
.unwrap();
let ClientEvent::LateReply {
sent_at: Some(reported_send),
server_timing: Some(timing),
one_way: Some(one_way),
..
} = &events[0]
else {
panic!("expected measurable LateReply: {events:?}");
};
assert_eq!(*reported_send, sent_at);
let selected = i128::from(timing.receive_wall_ns.unwrap())
- one_way.client_to_server.unwrap().as_nanos();
assert_eq!(
selected, 10_000_500_000,
"kernel TX endpoint must survive timeout"
);
}
}
// A recoverable send failure that consumes an OPT_ID, followed by a
// plausible wrong-ID completion, cannot be induced reliably with public
// sockets. Exercise that failure boundary directly, without a socket hook
@@ -1,5 +1,45 @@
use super::*;
#[test]
fn probe_timeout_overflow_transmits_no_echo() {
let params = default_params();
let server = start_fake_server(move |socket, tx| {
socket
.set_read_timeout(Some(Duration::from_secs(2)))
.unwrap();
let (_, peer) = recv_request(&socket, &tx);
socket
.send_to(
&open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, &params, None),
peer,
)
.unwrap();
// CLOSE is a completion barrier: any incorrectly transmitted ECHO
// precedes it on this connected loopback socket.
let _ = recv_request(&socket, &tx);
});
let config = ClientConfig {
probe_timeout: Duration::MAX,
..default_test_config(server.addr)
};
let mut client = Client::connect(config).unwrap();
assert_open_started(client.open().unwrap());
assert!(matches!(
client.send_probe(),
Err(ClientError::DurationOverflow)
));
assert!(!client.has_pending_probes());
client.close().unwrap();
let packets: Vec<_> = server.rx.iter().collect();
assert_eq!(packets.len(), 2);
assert_eq!(
packets[1][3],
flags::FLAG_CLOSE,
"overflow must not transmit an ECHO"
);
server.join();
}
#[test]
fn poll_timeouts_emits_echo_loss() {
let params = default_params();
+8 -3
View File
@@ -132,9 +132,14 @@ fn assert_kernel_send_endpoint(event: &ClientEvent, before_send: SystemTime) {
"TX timestamp was not observed; userspace fallback is insufficient"
);
assert!(selected <= ns(received_at.wall));
assert!(
packet_meta.kernel_rx_timestamp.is_some(),
"Linux RX timestamp required"
let selected_receive =
i128::from(timing.send_wall_ns.unwrap()) + one_way.server_to_client.unwrap().as_nanos();
assert_eq!(
selected_receive,
ns(packet_meta
.kernel_rx_timestamp
.expect("Linux RX timestamp required")),
"downstream OWD must use the kernel receive endpoint"
);
assert_eq!(rtt.raw, received_at.mono.duration_since(sent_at.mono));
}