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
134 lines
4.8 KiB
Rust
134 lines
4.8 KiB
Rust
//! Observe the emitted IP byte, independently of protocol negotiation.
|
|
#![cfg(target_os = "linux")]
|
|
|
|
use std::{io::IoSliceMut, net::UdpSocket as StdSocket, os::fd::AsRawFd, time::Duration};
|
|
|
|
use irtt_proto::{
|
|
decode_echo_reply, decode_open_reply, encode_request, OpenReply, Params, RequestToEncode,
|
|
FLAG_CLOSE,
|
|
};
|
|
use irtt_server::{Server, ServerConfig};
|
|
use nix::sys::socket::{recvmsg, ControlMessageOwned, MsgFlags};
|
|
use tokio::{net::UdpSocket, sync::oneshot, time::timeout};
|
|
|
|
async fn exchange(client: &UdpSocket, request: &[u8], expected: u8) -> Vec<u8> {
|
|
client.send(request).await.unwrap();
|
|
loop {
|
|
client.readable().await.unwrap();
|
|
let packet = client.try_io(tokio::io::Interest::READABLE, || {
|
|
let mut bytes = [0; 2048];
|
|
let mut control = nix::cmsg_space!(u8);
|
|
let mut iov = [IoSliceMut::new(&mut bytes)];
|
|
let msg = recvmsg::<()>(
|
|
client.as_raw_fd(),
|
|
&mut iov,
|
|
Some(&mut control),
|
|
MsgFlags::MSG_DONTWAIT,
|
|
)?;
|
|
let size = msg.bytes;
|
|
let class = msg.cmsgs().unwrap().find_map(|message| match message {
|
|
ControlMessageOwned::Ipv4Tos(class) => Some(class),
|
|
_ => None,
|
|
});
|
|
assert_eq!(class, Some(expected));
|
|
Ok(bytes[..size].to_vec())
|
|
});
|
|
match packet {
|
|
Ok(packet) => return packet,
|
|
Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {}
|
|
Err(error) => panic!("receive failed: {error}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn echo(opened: &OpenReply, sequence: u32) -> Vec<u8> {
|
|
encode_request(
|
|
RequestToEncode::Echo {
|
|
token: opened.token,
|
|
sequence,
|
|
params: &opened.params,
|
|
payload: &[],
|
|
},
|
|
None,
|
|
)
|
|
.unwrap()
|
|
}
|
|
|
|
#[tokio::test(flavor = "current_thread")]
|
|
async fn every_open_echo_and_server_close_applies_its_own_raw_traffic_class() {
|
|
timeout(Duration::from_secs(8), async {
|
|
let maximum = Duration::from_millis(1);
|
|
let mut server = Server::bind(
|
|
"127.0.0.1:0".parse().unwrap(),
|
|
ServerConfig::default()
|
|
.with_min_send_interval(Duration::ZERO)
|
|
.with_idle_timeout(Duration::MAX)
|
|
.with_max_test_duration(maximum),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
let addr = server.local_addr().unwrap();
|
|
let (shutdown, stopped) = oneshot::channel();
|
|
let task = tokio::spawn(async move {
|
|
server
|
|
.run(async {
|
|
let _ = stopped.await;
|
|
})
|
|
.await
|
|
});
|
|
let socket = StdSocket::bind("127.0.0.1:0").unwrap();
|
|
socket2::SockRef::from(&socket)
|
|
.set_recv_tos_v4(true)
|
|
.unwrap();
|
|
socket.set_nonblocking(true).unwrap();
|
|
let client = UdpSocket::from_std(socket).unwrap();
|
|
client.connect(addr).await.unwrap();
|
|
let mut sessions = Vec::new();
|
|
// Each new OPEN follows a prior ECHO. The first raw byte also includes
|
|
// ECN bits: neither a codepoint shift nor masking would preserve it.
|
|
for (requested, class) in [(185, 185), (32, 32), (0, 0), (256, 0), (-1, 0)] {
|
|
let params = Params {
|
|
dscp: requested,
|
|
..Params::with_protocol_defaults()
|
|
};
|
|
let request = encode_request(
|
|
RequestToEncode::Open {
|
|
params: ¶ms,
|
|
no_test: false,
|
|
},
|
|
None,
|
|
)
|
|
.unwrap();
|
|
let packet = exchange(&client, &request, 0).await;
|
|
let opened = decode_open_reply(&packet, None).unwrap();
|
|
assert_eq!(opened.params.dscp, requested);
|
|
let packet = exchange(&client, &echo(&opened, 0), class).await;
|
|
assert_eq!(
|
|
decode_echo_reply(&packet, &opened.params, None)
|
|
.unwrap()
|
|
.flags
|
|
& FLAG_CLOSE,
|
|
0
|
|
);
|
|
sessions.push((opened, class));
|
|
}
|
|
// Every session has now served its first echo. Waiting past all duration
|
|
// deadlines is safe under oversleep; idle expiry cannot intervene.
|
|
tokio::time::sleep(maximum + Duration::from_secs(2)).await;
|
|
for (opened, class) in sessions {
|
|
let packet = exchange(&client, &echo(&opened, 1), class).await;
|
|
assert_ne!(
|
|
decode_echo_reply(&packet, &opened.params, None)
|
|
.unwrap()
|
|
.flags
|
|
& FLAG_CLOSE,
|
|
0
|
|
);
|
|
}
|
|
shutdown.send(()).unwrap();
|
|
task.await.unwrap().unwrap();
|
|
})
|
|
.await
|
|
.expect("marked runtime exchanges did not complete");
|
|
}
|