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
255 lines
8.9 KiB
Rust
255 lines
8.9 KiB
Rust
use super::*;
|
|
|
|
// Untrusted returned intervals must be rejected before managed scheduling,
|
|
// even when loose negotiation permits legitimate server restrictions.
|
|
#[test]
|
|
fn loose_open_rejects_nonpositive_returned_intervals() {
|
|
for interval_ns in [0, -1] {
|
|
let mut returned = default_params();
|
|
returned.interval_ns = interval_ns;
|
|
let server = open_success_server(returned);
|
|
let config = ClientConfig {
|
|
negotiation_policy: NegotiationPolicy::Loose,
|
|
..default_test_config(server.addr)
|
|
};
|
|
let mut client = Client::connect(config).unwrap();
|
|
assert!(
|
|
matches!(client.open(), Err(ClientError::NegotiationRejected { .. })),
|
|
"returned interval {interval_ns} must be rejected"
|
|
);
|
|
server.join();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn open_fails_when_already_open() {
|
|
let params = default_params();
|
|
let server = open_success_server(params);
|
|
let mut client = Client::connect(default_test_config(server.addr)).unwrap();
|
|
assert_open_started(client.open().unwrap());
|
|
assert!(matches!(client.open(), Err(ClientError::AlreadyOpen)));
|
|
server.join();
|
|
}
|
|
|
|
#[test]
|
|
fn open_retries_after_first_timeout() {
|
|
let server = start_fake_server(|socket, tx| {
|
|
let (first, _) = recv_request(&socket, &tx);
|
|
let (_, peer) = recv_request(&socket, &tx);
|
|
let params = Params::decode(&first[4..]).unwrap();
|
|
let reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, None);
|
|
assert_eq!(first[3] & FLAG_OPEN, FLAG_OPEN);
|
|
socket.send_to(&reply, peer).unwrap();
|
|
});
|
|
let config = ClientConfig {
|
|
open_timeouts: vec![Duration::from_millis(200), Duration::from_millis(500)],
|
|
..default_test_config(server.addr)
|
|
};
|
|
let mut client = Client::connect(config).unwrap();
|
|
let outcome = client.open().unwrap();
|
|
assert_open_started(outcome);
|
|
assert_eq!(server.rx.iter().take(2).count(), 2);
|
|
server.join();
|
|
}
|
|
|
|
#[test]
|
|
fn protocol_version_mismatch_fails() {
|
|
let mut config = default_test_config(SocketAddr::from(([127, 0, 0, 1], 1)));
|
|
config.negotiation_policy = NegotiationPolicy::Loose;
|
|
let mut params = default_params();
|
|
params.protocol_version = 2;
|
|
let server = open_success_server(params);
|
|
let mut client = Client::connect(default_test_config(server.addr)).unwrap();
|
|
assert!(matches!(
|
|
client.open(),
|
|
Err(ClientError::ProtocolVersionMismatch { received: 2, .. })
|
|
));
|
|
assert_eq!(server.rx.iter().take(1).count(), 1);
|
|
server.join();
|
|
}
|
|
|
|
#[test]
|
|
fn server_rejection_fails_in_normal_mode() {
|
|
let params = default_params();
|
|
let server = start_fake_server(move |socket, tx| {
|
|
let (_, peer) = recv_request(&socket, &tx);
|
|
let reply = open_reply(FLAG_OPEN | FLAG_REPLY | flags::FLAG_CLOSE, 0, ¶ms, None);
|
|
socket.send_to(&reply, peer).unwrap();
|
|
});
|
|
let mut client = Client::connect(default_test_config(server.addr)).unwrap();
|
|
assert!(matches!(client.open(), Err(ClientError::ServerRejected)));
|
|
assert_eq!(server.rx.iter().take(1).count(), 1);
|
|
server.join();
|
|
}
|
|
|
|
#[test]
|
|
fn ignored_datagrams_do_not_restart_the_attempt_deadline() {
|
|
let server = start_fake_server(move |socket, tx| {
|
|
let (_, peer) = recv_request(&socket, &tx);
|
|
thread::sleep(Duration::from_millis(150));
|
|
socket.send_to(&[0_u8], peer).unwrap();
|
|
thread::sleep(Duration::from_millis(150));
|
|
socket.send_to(&[0_u8], peer).unwrap();
|
|
let _ = recv_request(&socket, &tx);
|
|
});
|
|
let config = ClientConfig {
|
|
open_timeouts: vec![Duration::from_millis(250), Duration::from_millis(250)],
|
|
..default_test_config(server.addr)
|
|
};
|
|
let mut client = Client::connect(config).unwrap();
|
|
let started = Instant::now();
|
|
|
|
assert!(matches!(client.open(), Err(ClientError::OpenTimeout)));
|
|
let requests: Vec<_> = server.rx.iter().take(2).collect();
|
|
assert_eq!(requests.len(), 2);
|
|
assert!(
|
|
started.elapsed() < Duration::from_millis(700),
|
|
"ignored datagrams restarted an attempt deadline"
|
|
);
|
|
server.join();
|
|
}
|
|
|
|
#[test]
|
|
fn opening_deadline_overflow_occurs_before_send() {
|
|
let server = timeout_server(Duration::from_millis(250));
|
|
let config = ClientConfig {
|
|
open_timeouts: vec![Duration::MAX],
|
|
..default_test_config(server.addr)
|
|
};
|
|
let mut client = Client::connect(config).unwrap();
|
|
|
|
assert!(matches!(client.open(), Err(ClientError::DurationOverflow)));
|
|
assert!(server.rx.try_recv().is_err());
|
|
server.join();
|
|
}
|
|
|
|
#[test]
|
|
fn post_token_negotiation_failure_sends_cleanup_close() {
|
|
let mut returned = default_params();
|
|
returned.interval_ns += 1;
|
|
let server = start_fake_server(move |socket, tx| {
|
|
let (_, peer) = recv_request(&socket, &tx);
|
|
socket
|
|
.send_to(
|
|
&open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, &returned, None),
|
|
peer,
|
|
)
|
|
.unwrap();
|
|
let _ = recv_request(&socket, &tx);
|
|
});
|
|
let mut client = Client::connect(default_test_config(server.addr)).unwrap();
|
|
|
|
assert!(matches!(
|
|
client.open(),
|
|
Err(ClientError::NegotiationRejected { .. })
|
|
));
|
|
let packets: Vec<_> = server.rx.iter().take(2).collect();
|
|
assert_eq!(packets[1][3], flags::FLAG_CLOSE);
|
|
assert_eq!(
|
|
u64::from_le_bytes(packets[1][4..12].try_into().unwrap()),
|
|
TOKEN
|
|
);
|
|
server.join();
|
|
}
|
|
|
|
#[test]
|
|
fn open_timeout_after_all_timeouts() {
|
|
let server = timeout_server(Duration::from_millis(700));
|
|
let config = ClientConfig {
|
|
open_timeouts: vec![Duration::from_millis(200), Duration::from_millis(200)],
|
|
socket_config: irtt_client::SocketConfig {
|
|
recv_timeout: Some(Duration::from_millis(50)),
|
|
..Default::default()
|
|
},
|
|
..default_test_config(server.addr)
|
|
};
|
|
let mut client = Client::connect(config).unwrap();
|
|
assert!(matches!(client.open(), Err(ClientError::OpenTimeout)));
|
|
assert_eq!(server.rx.iter().take(2).count(), 2);
|
|
server.join();
|
|
}
|
|
|
|
#[test]
|
|
fn multiple_untrusted_datagrams_before_valid_reply_use_one_attempt() {
|
|
let params = default_params();
|
|
let server = start_fake_server(move |socket, tx| {
|
|
let (_, peer) = recv_request(&socket, &tx);
|
|
let valid = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, None);
|
|
let mut wrong_type = valid.clone();
|
|
wrong_type[3] = FLAG_REPLY;
|
|
let mut reserved_flags = valid.clone();
|
|
reserved_flags[3] |= 0x10;
|
|
let mut bad_magic = valid.clone();
|
|
bad_magic[0] ^= 0xff;
|
|
let unexpected_hmac =
|
|
open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, Some(b"unexpected"));
|
|
|
|
for packet in [
|
|
vec![0_u8],
|
|
bad_magic,
|
|
wrong_type,
|
|
reserved_flags,
|
|
unexpected_hmac,
|
|
valid,
|
|
] {
|
|
socket.send_to(&packet, peer).unwrap();
|
|
}
|
|
});
|
|
let mut client = Client::connect(default_test_config(server.addr)).unwrap();
|
|
|
|
assert_open_started(client.open().unwrap());
|
|
assert_eq!(server.rx.iter().take(1).count(), 1);
|
|
assert!(server.rx.try_recv().is_err());
|
|
|
|
server.join();
|
|
}
|
|
|
|
#[test]
|
|
fn only_ignored_open_datagrams_eventually_time_out() {
|
|
let server = start_fake_server(move |socket, tx| {
|
|
for _ in 0..2 {
|
|
let (_, peer) = recv_request(&socket, &tx);
|
|
socket.send_to(&[0_u8], peer).unwrap();
|
|
socket.send_to(&MAGIC, peer).unwrap();
|
|
}
|
|
thread::sleep(Duration::from_millis(300));
|
|
});
|
|
let config = ClientConfig {
|
|
open_timeouts: vec![Duration::from_millis(200), Duration::from_millis(200)],
|
|
socket_config: irtt_client::SocketConfig {
|
|
recv_timeout: Some(Duration::from_millis(50)),
|
|
..Default::default()
|
|
},
|
|
..default_test_config(server.addr)
|
|
};
|
|
let mut client = Client::connect(config).unwrap();
|
|
|
|
assert!(matches!(client.open(), Err(ClientError::OpenTimeout)));
|
|
assert_eq!(server.rx.iter().take(2).count(), 2);
|
|
assert!(server.rx.try_recv().is_err());
|
|
server.join();
|
|
}
|
|
|
|
#[test]
|
|
fn trusted_zero_token_normal_reply_is_terminal() {
|
|
let params = default_params();
|
|
let server = start_fake_server(move |socket, tx| {
|
|
let (_, peer) = recv_request(&socket, &tx);
|
|
// encode_open_reply rejects a zero token without FLAG_CLOSE, so this
|
|
// deliberately non-compliant reply is built by encoding a normal
|
|
// reply with a placeholder token and then zeroing the token field.
|
|
let mut reply = open_reply(FLAG_OPEN | FLAG_REPLY, TOKEN, ¶ms, None);
|
|
reply[HMAC_OFFSET..HMAC_OFFSET + 8].copy_from_slice(&0_u64.to_le_bytes());
|
|
socket.send_to(&reply, peer).unwrap();
|
|
});
|
|
let mut client = Client::connect(default_test_config(server.addr)).unwrap();
|
|
|
|
assert!(matches!(
|
|
client.open(),
|
|
Err(ClientError::Protocol(irtt_proto::ProtoError::ZeroToken))
|
|
));
|
|
assert_eq!(server.rx.iter().take(1).count(), 1);
|
|
|
|
server.join();
|
|
}
|