Skip to content

Commit 1e12360

Browse files
authored
feat(s2n-quic-tests): implement a blocklist for test events (#2872)
1 parent c0df4e3 commit 1e12360

33 files changed

+364
-167
lines changed

quic/s2n-quic-tests/README.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,31 @@ Implement the `s2n_quic_core::packet::interceptor::Interceptor` trait and config
126126

127127
```rust
128128
let client = Client::builder()
129-
.with_io(handle.builder().build().unwrap())?
130-
.with_packet_interceptor(interceptor)?
129+
.with_io(handle.builder().build().unwrap())?
130+
.with_event(tracing_events(true, model.clone()))?
131+
.with_packet_interceptor(interceptor)?
131132
.start()?;
132133
```
133134

135+
### Blocklist Event Subscriber
136+
137+
The test suite includes a `BlocklistSubscriber` utility that can be used to detect and fail tests when specific unwanted events occur. This subscriber works by panicking when it encounters events that have been added to the blocklist, such as certain types of packet losses.
138+
139+
To use the blocklist subscriber in tests:
140+
141+
```rust
142+
// Use with a client or server
143+
let client = Client::builder()
144+
.with_io(handle.builder().build().unwrap())?
145+
.with_tls(certificates::CERT_PEM)?
146+
.with_event(tracing_events(true, model.clone()))?
147+
.start()?;
148+
```
149+
150+
When the `with_blocklist` parameter is set to `true`, the `tracing_events` function returns both the standard tracing subscriber and the blocklist subscriber. The standard subscriber logs events as usual, while the blocklist subscriber will cause a test to fail immediately if a blocklisted event is encountered. If certain unwanted events are expected during the test, switching the `with_blocklist` parameter to false to disable the blocklist feature.
151+
152+
This is particularly useful for tests that need to verify that certain error conditions don't occur, as the test will fail early with a clear error message rather than continuing with potentially incorrect behavior.
153+
134154
### Common Setup
135155

136156
`src/lib.rs` provides common utilities for setting up test clients and servers with various configurations.

quic/s2n-quic-tests/src/lib.rs

Lines changed: 137 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
use s2n_quic::{
55
client::Connect,
66
provider::{
7-
event,
8-
io::testing::{primary, spawn, Handle, Result},
7+
event::{self, events},
8+
io::testing::{primary, spawn, Handle, Model, Result},
99
},
1010
stream::PeerStream,
1111
Client, Server,
@@ -21,7 +21,124 @@ mod tests;
2121

2222
pub static SERVER_CERTS: (&str, &str) = (certificates::CERT_PEM, certificates::KEY_PEM);
2323

24-
pub fn tracing_events() -> event::tracing::Subscriber {
24+
/// A subscriber that panics when a blocklisted event is encountered
25+
pub struct BlocklistSubscriber {
26+
blocklist_enabled: bool,
27+
network_env: Model,
28+
}
29+
30+
impl BlocklistSubscriber {
31+
pub fn new(blocklist_enabled: bool, network_env: Model) -> Self {
32+
Self {
33+
blocklist_enabled,
34+
network_env,
35+
}
36+
}
37+
38+
pub fn max_udp_payload(&self) -> u16 {
39+
self.network_env.max_udp_payload()
40+
}
41+
}
42+
43+
impl event::Subscriber for BlocklistSubscriber {
44+
type ConnectionContext = ();
45+
46+
fn create_connection_context(
47+
&mut self,
48+
_meta: &events::ConnectionMeta,
49+
_info: &events::ConnectionInfo,
50+
) -> Self::ConnectionContext {
51+
}
52+
53+
fn on_datagram_dropped(
54+
&mut self,
55+
_context: &mut Self::ConnectionContext,
56+
_meta: &events::ConnectionMeta,
57+
event: &events::DatagramDropped,
58+
) {
59+
if self.blocklist_enabled {
60+
panic!(
61+
"Blacklisted datagram dropped event encountered: {:?}",
62+
event
63+
);
64+
}
65+
}
66+
67+
fn on_packet_dropped(
68+
&mut self,
69+
_context: &mut Self::ConnectionContext,
70+
_meta: &events::ConnectionMeta,
71+
event: &events::PacketDropped,
72+
) {
73+
if matches!(
74+
event,
75+
events::PacketDropped {
76+
reason: events::PacketDropReason::DecryptionFailed { .. }
77+
| events::PacketDropReason::UnprotectFailed { .. }
78+
| events::PacketDropReason::VersionMismatch { .. }
79+
| events::PacketDropReason::UndersizedInitialPacket { .. }
80+
| events::PacketDropReason::InitialConnectionIdInvalidSpace { .. },
81+
..
82+
}
83+
) && self.blocklist_enabled
84+
{
85+
panic!("Blocklisted packet dropped event encountered: {:?}", event);
86+
}
87+
}
88+
89+
fn on_packet_lost(
90+
&mut self,
91+
_context: &mut Self::ConnectionContext,
92+
_meta: &events::ConnectionMeta,
93+
event: &events::PacketLost,
94+
) {
95+
// packet which is smaller than the MTU should not be lost
96+
if event.bytes_lost < self.max_udp_payload() && self.blocklist_enabled {
97+
panic!("Bytes lost is {} and max udp payload is {}\nBlocklisted packet lost event encountered: {:?}", event.bytes_lost, self.max_udp_payload(), event);
98+
}
99+
}
100+
101+
fn on_platform_tx_error(
102+
&mut self,
103+
_meta: &events::EndpointMeta,
104+
event: &events::PlatformTxError,
105+
) {
106+
if self.blocklist_enabled {
107+
panic!(
108+
"Blocklisted platform tx error event encountered: {:?}",
109+
event
110+
);
111+
}
112+
}
113+
114+
fn on_platform_rx_error(
115+
&mut self,
116+
_meta: &events::EndpointMeta,
117+
event: &events::PlatformRxError,
118+
) {
119+
if self.blocklist_enabled {
120+
panic!(
121+
"Blocklisted platform rx error event encountered: {:?}",
122+
event
123+
);
124+
}
125+
}
126+
127+
fn on_endpoint_datagram_dropped(
128+
&mut self,
129+
_meta: &events::EndpointMeta,
130+
event: &events::EndpointDatagramDropped,
131+
) {
132+
if self.blocklist_enabled {
133+
panic!(
134+
"Blocklisted endpoint datagram dropped event encountered: {:?}",
135+
event
136+
);
137+
}
138+
}
139+
}
140+
141+
pub fn tracing_events(with_blocklist: bool, network_env: Model) -> impl event::Subscriber {
25142
use std::sync::Once;
26143

27144
static TRACING: Once = Once::new();
@@ -59,7 +176,10 @@ pub fn tracing_events() -> event::tracing::Subscriber {
59176
.init();
60177
});
61178

62-
event::tracing::Subscriber::default()
179+
(
180+
event::tracing::Subscriber::default(),
181+
BlocklistSubscriber::new(with_blocklist, network_env),
182+
)
63183
}
64184

65185
pub fn start_server(mut server: Server) -> Result<SocketAddr> {
@@ -96,22 +216,27 @@ pub fn start_server(mut server: Server) -> Result<SocketAddr> {
96216
Ok(server_addr)
97217
}
98218

99-
pub fn server(handle: &Handle) -> Result<SocketAddr> {
100-
let server = build_server(handle)?;
219+
pub fn server(handle: &Handle, network_env: Model) -> Result<SocketAddr> {
220+
let server = build_server(handle, network_env)?;
101221
start_server(server)
102222
}
103223

104-
pub fn build_server(handle: &Handle) -> Result<Server> {
224+
pub fn build_server(handle: &Handle, network_env: Model) -> Result<Server> {
105225
Ok(Server::builder()
106226
.with_io(handle.builder().build().unwrap())?
107227
.with_tls(SERVER_CERTS)?
108-
.with_event(tracing_events())?
228+
.with_event(tracing_events(true, network_env))?
109229
.with_random(Random::with_seed(123))?
110230
.start()?)
111231
}
112232

113-
pub fn client(handle: &Handle, server_addr: SocketAddr) -> Result {
114-
let client = build_client(handle)?;
233+
pub fn client(
234+
handle: &Handle,
235+
server_addr: SocketAddr,
236+
network_env: Model,
237+
with_blocklist: bool,
238+
) -> Result {
239+
let client = build_client(handle, network_env, with_blocklist)?;
115240
start_client(client, server_addr, Data::new(10_000))
116241
}
117242

@@ -146,21 +271,15 @@ pub fn start_client(client: Client, server_addr: SocketAddr, data: Data) -> Resu
146271
Ok(())
147272
}
148273

149-
pub fn build_client(handle: &Handle) -> Result<Client> {
274+
pub fn build_client(handle: &Handle, network_env: Model, with_blocklist: bool) -> Result<Client> {
150275
Ok(Client::builder()
151276
.with_io(handle.builder().build().unwrap())?
152277
.with_tls(certificates::CERT_PEM)?
153-
.with_event(tracing_events())?
278+
.with_event(tracing_events(with_blocklist, network_env))?
154279
.with_random(Random::with_seed(123))?
155280
.start()?)
156281
}
157282

158-
pub fn client_server(handle: &Handle) -> Result<SocketAddr> {
159-
let addr = server(handle)?;
160-
client(handle, addr)?;
161-
Ok(addr)
162-
}
163-
164283
pub struct Random {
165284
inner: rand_chacha::ChaCha8Rng,
166285
}

quic/s2n-quic-tests/src/tests/blackhole.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,22 @@ use super::*;
55

66
fn blackhole(model: Model, blackhole_duration: Duration) {
77
test(model.clone(), |handle| {
8+
let model_for_spawn = model.clone();
89
spawn(async move {
910
// switch back and forth between blackhole and not
1011
loop {
1112
delay(blackhole_duration).await;
1213
// drop all packets
13-
model.set_drop_rate(1.0);
14+
model_for_spawn.set_drop_rate(1.0);
1415

1516
delay(blackhole_duration).await;
16-
model.set_drop_rate(0.0);
17+
model_for_spawn.set_drop_rate(0.0);
1718
}
1819
});
19-
client_server(handle)
20+
21+
let addr = server(handle, model.clone())?;
22+
client(handle, addr, model.clone(), true)?;
23+
Ok(addr)
2024
})
2125
.unwrap();
2226
}

quic/s2n-quic-tests/src/tests/buffer_limit.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ fn buffer_limit_test() {
3030
let client_hello_subscriber = recorder::TlsClientHello::new();
3131
let client_hello_event = client_hello_subscriber.events();
3232

33-
test(model, |handle| {
33+
test(model.clone(), |handle| {
3434
let server = tls::Server::builder()
3535
.with_application_protocols(["h3"].iter())
3636
.unwrap()
@@ -43,7 +43,7 @@ fn buffer_limit_test() {
4343
.with_io(handle.builder().build()?)?
4444
.with_tls(server)?
4545
.with_event((
46-
tracing_events(),
46+
tracing_events(true, model.clone()),
4747
(client_hello_subscriber, connection_closed_subscriber),
4848
))?
4949
.with_random(Random::with_seed(456))?
@@ -66,7 +66,7 @@ fn buffer_limit_test() {
6666
let client = Client::builder()
6767
.with_io(handle.builder().build()?)?
6868
.with_tls(client)?
69-
.with_event(tracing_events())?
69+
.with_event(tracing_events(true, model.clone()))?
7070
.with_random(Random::with_seed(456))?
7171
.start()?;
7272

quic/s2n-quic-tests/src/tests/chain.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,11 @@ where
7878
let model = Model::default();
7979
model.set_delay(Duration::from_millis(50));
8080

81-
test(model, |handle| {
81+
test(model.clone(), |handle| {
8282
let server = Server::builder()
8383
.with_io(handle.builder().build()?)?
8484
.with_tls(build_server_mtls_provider(certificates::MTLS_CA_CERT)?)?
85-
.with_event((Chain, tracing_events()))?
85+
.with_event((Chain, tracing_events(true, model.clone())))?
8686
.start()?;
8787
let (send, server_chain) = tokio::sync::mpsc::channel(1);
8888
let server_chain = Arc::new(tokio::sync::Mutex::new(server_chain));
@@ -92,7 +92,7 @@ where
9292
let client = Client::builder()
9393
.with_io(handle.builder().build().unwrap())?
9494
.with_tls(build_client_mtls_provider(certificates::MTLS_CA_CERT)?)?
95-
.with_event((Chain, tracing_events()))?
95+
.with_event((Chain, tracing_events(true, model.clone())))?
9696
.start()?;
9797

9898
// show it working for several connections

quic/s2n-quic-tests/src/tests/client_handshake_confirm.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,12 @@ where
137137
let model = Model::default();
138138
model.set_delay(Duration::from_millis(50));
139139

140-
test(model, |handle| {
140+
test(model.clone(), |handle| {
141141
let server_tls = build_server_mtls_provider(server_cert)?;
142142
let server = Server::builder()
143143
.with_io(handle.builder().build()?)?
144144
.with_tls(server_tls)?
145-
.with_event(tracing_events())?
145+
.with_event(tracing_events(true, model.clone()))?
146146
.with_random(Random::with_seed(456))?
147147
.start()?;
148148

@@ -152,7 +152,7 @@ where
152152
let client = Client::builder()
153153
.with_io(handle.builder().build().unwrap())?
154154
.with_tls(client_tls)?
155-
.with_event((ClientConfirm, tracing_events()))?
155+
.with_event((ClientConfirm, tracing_events(true, model.clone())))?
156156
.with_random(Random::with_seed(456))?
157157
.start()?;
158158

quic/s2n-quic-tests/src/tests/connection_limits.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,20 @@ fn connection_limits() {
3030
}
3131

3232
let model = Model::default();
33-
test(model, |handle| {
33+
test(model.clone(), |handle| {
3434
let server = Server::builder()
3535
.with_io(handle.builder().build()?)?
36+
.with_event(tracing_events(true, model.clone()))?
3637
.with_tls(SERVER_CERTS)?
38+
.with_event(tracing_events(true, model.clone()))?
3739
.with_limits(LimitsProvider)?
3840
.start()?;
3941

4042
let client = Client::builder()
4143
.with_io(handle.builder().build().unwrap())?
44+
.with_event(tracing_events(true, model.clone()))?
4245
.with_tls(certificates::CERT_PEM)?
46+
.with_event(tracing_events(true, model.clone()))?
4347
.start()?;
4448
let addr = start_server(server)?;
4549
start_client(client, addr, Data::new(1000))?;

0 commit comments

Comments
 (0)