test: cover timing, overflow, and target authentication contracts
This commit is contained in:
4 files changed
+150
-3
No files matched your search
@@ -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.
|
// A real authenticated server detects broken client argument-to-config mapping.
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -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
|
// A recoverable send failure that consumes an OPT_ID, followed by a
|
||||||
// plausible wrong-ID completion, cannot be induced reliably with public
|
// plausible wrong-ID completion, cannot be induced reliably with public
|
||||||
// sockets. Exercise that failure boundary directly, without a socket hook
|
// sockets. Exercise that failure boundary directly, without a socket hook
|
||||||
|
|||||||
@@ -1,5 +1,45 @@
|
|||||||
use super::*;
|
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, ¶ms, 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]
|
#[test]
|
||||||
fn poll_timeouts_emits_echo_loss() {
|
fn poll_timeouts_emits_echo_loss() {
|
||||||
let params = default_params();
|
let params = default_params();
|
||||||
|
|||||||
@@ -132,9 +132,14 @@ fn assert_kernel_send_endpoint(event: &ClientEvent, before_send: SystemTime) {
|
|||||||
"TX timestamp was not observed; userspace fallback is insufficient"
|
"TX timestamp was not observed; userspace fallback is insufficient"
|
||||||
);
|
);
|
||||||
assert!(selected <= ns(received_at.wall));
|
assert!(selected <= ns(received_at.wall));
|
||||||
assert!(
|
let selected_receive =
|
||||||
packet_meta.kernel_rx_timestamp.is_some(),
|
i128::from(timing.send_wall_ns.unwrap()) + one_way.server_to_client.unwrap().as_nanos();
|
||||||
"Linux RX timestamp required"
|
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));
|
assert_eq!(rtt.raw, received_at.mono.duration_since(sent_at.mono));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user