Skip to content

Commit 61cfc7a

Browse files
fix(s2n-quic-dc): handle possible secret control packet correctly (#2280)
1 parent f568f26 commit 61cfc7a

File tree

3 files changed

+191
-38
lines changed

3 files changed

+191
-38
lines changed

quic/s2n-quic-core/src/dc/testing.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,23 @@ use crate::{
99
varint::VarInt,
1010
};
1111
use core::time::Duration;
12+
use std::sync::{
13+
atomic::{AtomicU8, Ordering},
14+
Arc,
15+
};
1216

1317
pub struct MockDcEndpoint {
1418
stateless_reset_tokens: Vec<stateless_reset::Token>,
19+
pub on_possible_secret_control_packet_count: Arc<AtomicU8>,
20+
pub on_possible_secret_control_packet: fn() -> bool,
1521
}
1622

1723
impl MockDcEndpoint {
1824
pub fn new(tokens: &[stateless_reset::Token]) -> Self {
1925
Self {
2026
stateless_reset_tokens: tokens.to_vec(),
27+
on_possible_secret_control_packet_count: Arc::new(AtomicU8::default()),
28+
on_possible_secret_control_packet: || false,
2129
}
2230
}
2331
}
@@ -45,7 +53,9 @@ impl dc::Endpoint for MockDcEndpoint {
4553
_datagram_info: &DatagramInfo,
4654
_payload: &mut [u8],
4755
) -> bool {
48-
false
56+
self.on_possible_secret_control_packet_count
57+
.fetch_add(1, Ordering::Relaxed);
58+
(self.on_possible_secret_control_packet)()
4959
}
5060
}
5161

quic/s2n-quic-transport/src/endpoint/mod.rs

+14-9
Original file line numberDiff line numberDiff line change
@@ -458,14 +458,6 @@ impl<Cfg: Config> Endpoint<Cfg> {
458458
endpoint_context.connection_id_format,
459459
) {
460460
(packet, remaining)
461-
} else if Cfg::DcEndpoint::ENABLED
462-
&& endpoint_context
463-
.dc
464-
.on_possible_secret_control_packet(&dc::DatagramInfo::new(&remote_address), payload)
465-
{
466-
// This was a DC secret control packet, so we don't need to proceed
467-
// with checking for a stateless reset
468-
return;
469461
} else {
470462
//= https://www.rfc-editor.org/rfc/rfc9000#section-5.2.2
471463
//# Servers MUST drop incoming packets under all other circumstances.
@@ -761,12 +753,25 @@ impl<Cfg: Config> Endpoint<Cfg> {
761753
}
762754
}
763755
(_, packet) => {
756+
let is_short_header_packet = matches!(packet, ProtectedPacket::Short(_));
757+
758+
if Cfg::DcEndpoint::ENABLED
759+
&& is_short_header_packet // dc packets are short header packets
760+
&& endpoint_context.dc.on_possible_secret_control_packet(
761+
&dc::DatagramInfo::new(&remote_address),
762+
payload,
763+
)
764+
{
765+
// This was a DC secret control packet, so we don't need to proceed
766+
// with checking for a stateless reset
767+
return;
768+
}
769+
764770
publisher.on_endpoint_datagram_dropped(event::builder::EndpointDatagramDropped {
765771
len: payload_len as u16,
766772
reason: event::builder::DatagramDropReason::UnknownDestinationConnectionId,
767773
});
768774

769-
let is_short_header_packet = matches!(packet, ProtectedPacket::Short(_));
770775
//= https://www.rfc-editor.org/rfc/rfc9000#section-10.3.1
771776
//# Endpoints MAY skip this check if any packet from a datagram is
772777
//# successfully processed. However, the comparison MUST be performed

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

+166-28
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,22 @@ use crate::{
1010
server,
1111
server::ServerProviders,
1212
};
13+
use s2n_codec::DecoderBufferMut;
1314
use s2n_quic_core::{
1415
crypto::tls,
1516
dc::testing::MockDcEndpoint,
16-
event::{api::DcState, Timestamp},
17+
event::{
18+
api::{DatagramDropReason, DcState, EndpointDatagramDropped, EndpointMeta, Subject},
19+
Timestamp,
20+
},
1721
frame::ConnectionClose,
22+
packet::interceptor::{Datagram, Interceptor},
1823
stateless_reset,
1924
stateless_reset::token::testing::{TEST_TOKEN_1, TEST_TOKEN_2},
2025
transport,
2126
varint::VarInt,
2227
};
28+
use std::sync::atomic::Ordering;
2329

2430
const SERVER_TOKENS: [stateless_reset::Token; 1] = [TEST_TOKEN_1];
2531
const CLIENT_TOKENS: [stateless_reset::Token; 1] = [TEST_TOKEN_2];
@@ -63,7 +69,9 @@ fn dc_handshake_self_test() -> Result<()> {
6369
.with_tls(certificates::CERT_PEM)?
6470
.with_dc(MockDcEndpoint::new(&CLIENT_TOKENS))?;
6571

66-
self_test(server, client, None, None)
72+
self_test(server, client, None, None)?;
73+
74+
Ok(())
6775
}
6876

6977
// Client Server
@@ -106,7 +114,9 @@ fn dc_mtls_handshake_self_test() -> Result<()> {
106114
.with_tls(client_tls)?
107115
.with_dc(MockDcEndpoint::new(&SERVER_TOKENS))?;
108116

109-
self_test(server, client, None, None)
117+
self_test(server, client, None, None)?;
118+
119+
Ok(())
110120
}
111121

112122
#[test]
@@ -133,7 +143,9 @@ fn dc_mtls_handshake_auth_failure_self_test() -> Result<()> {
133143
}
134144
.into();
135145

136-
self_test(server, client, Some(expected_client_error), None)
146+
self_test(server, client, Some(expected_client_error), None)?;
147+
148+
Ok(())
137149
}
138150

139151
// Client Server
@@ -173,7 +185,9 @@ fn dc_mtls_handshake_server_not_supported_self_test() -> Result<()> {
173185
"peer does not support specified dc versions",
174186
)),
175187
Some(expected_server_error),
176-
)
188+
)?;
189+
190+
Ok(())
177191
}
178192

179193
// Client Server
@@ -218,22 +232,81 @@ fn dc_mtls_handshake_client_not_supported_self_test() -> Result<()> {
218232
Some(connection::Error::invalid_configuration(
219233
"peer does not support specified dc versions",
220234
)),
221-
)
235+
)?;
236+
237+
Ok(())
238+
}
239+
240+
#[test]
241+
fn dc_secret_control_packet() -> Result<()> {
242+
dc_possible_secret_control_packet(|| true)
243+
}
244+
245+
#[test]
246+
fn dc_not_secret_control_packet() -> Result<()> {
247+
dc_possible_secret_control_packet(|| false)
248+
}
249+
250+
fn dc_possible_secret_control_packet(
251+
on_possible_secret_control_packet: fn() -> bool,
252+
) -> Result<()> {
253+
let server_tls = build_server_mtls_provider(certificates::MTLS_CA_CERT)?;
254+
let server = Server::builder()
255+
.with_tls(server_tls)?
256+
.with_dc(MockDcEndpoint::new(&SERVER_TOKENS))?;
257+
258+
let client_tls = build_client_mtls_provider(certificates::MTLS_CA_CERT)?;
259+
let mut dc_endpoint = MockDcEndpoint::new(&CLIENT_TOKENS);
260+
dc_endpoint.on_possible_secret_control_packet = on_possible_secret_control_packet;
261+
let on_possible_secret_control_packet_count =
262+
dc_endpoint.on_possible_secret_control_packet_count.clone();
263+
264+
let client = Client::builder()
265+
.with_tls(client_tls)?
266+
.with_dc(dc_endpoint)?
267+
.with_packet_interceptor(RandomShort::default())?;
268+
269+
let (client_events, _server_events) = self_test(server, client, None, None)?;
270+
271+
assert_eq!(
272+
1,
273+
on_possible_secret_control_packet_count.load(Ordering::Relaxed)
274+
);
275+
276+
let client_datagram_drops = client_events
277+
.endpoint_datagram_dropped_events
278+
.lock()
279+
.unwrap();
280+
281+
if on_possible_secret_control_packet() {
282+
// No datagrams should be recorded as dropped because MockDcEndpoint::on_possible_secret_control_packet
283+
// returned true, indicating the given datagram was a secret control packet
284+
assert_eq!(0, client_datagram_drops.len());
285+
} else {
286+
// The datagram was not a secret control packet, so it is dropped
287+
assert_eq!(1, client_datagram_drops.len());
288+
assert!(matches!(
289+
client_datagram_drops[0].reason,
290+
DatagramDropReason::UnknownDestinationConnectionId { .. }
291+
));
292+
}
293+
294+
Ok(())
222295
}
223296

224297
fn self_test<S: ServerProviders, C: ClientProviders>(
225298
server: server::Builder<S>,
226299
client: client::Builder<C>,
227300
expected_client_error: Option<connection::Error>,
228301
expected_server_error: Option<connection::Error>,
229-
) -> Result<()> {
302+
) -> Result<(DcRecorder, DcRecorder)> {
230303
let model = Model::default();
231304
let rtt = Duration::from_millis(100);
232305
model.set_delay(rtt / 2);
233306

234-
let server_subscriber = DcStateChanged::new();
307+
let server_subscriber = DcRecorder::new();
235308
let server_events = server_subscriber.clone();
236-
let client_subscriber = DcStateChanged::new();
309+
let client_subscriber = DcRecorder::new();
237310
let client_events = client_subscriber.clone();
238311

239312
test(model, |handle| {
@@ -284,7 +357,11 @@ fn self_test<S: ServerProviders, C: ClientProviders>(
284357
}
285358
} else {
286359
assert!(result.is_ok());
287-
let client_events = client_events.events().lock().unwrap().clone();
360+
let client_events = client_events
361+
.dc_state_changed_events()
362+
.lock()
363+
.unwrap()
364+
.clone();
288365
assert_dc_complete(&client_events);
289366
// wait briefly so the ack for the `DC_STATELESS_RESET_TOKENS` frame from the server is sent
290367
// before the client closes the connection. This is only necessary to confirm the `dc::State`
@@ -298,33 +375,55 @@ fn self_test<S: ServerProviders, C: ClientProviders>(
298375
.unwrap();
299376

300377
if expected_client_error.is_some() || expected_server_error.is_some() {
301-
return Ok(());
378+
return Ok((client_events, server_events));
302379
}
303380

304-
let server_events = server_events.events().lock().unwrap().clone();
305-
let client_events = client_events.events().lock().unwrap().clone();
381+
let server_dc_state_changed_events = server_events
382+
.dc_state_changed_events()
383+
.lock()
384+
.unwrap()
385+
.clone();
386+
let client_dc_state_changed_events = client_events
387+
.dc_state_changed_events()
388+
.lock()
389+
.unwrap()
390+
.clone();
306391

307-
assert_dc_complete(&server_events);
308-
assert_dc_complete(&client_events);
392+
assert_dc_complete(&server_dc_state_changed_events);
393+
assert_dc_complete(&client_dc_state_changed_events);
309394

310395
// Server path secrets are ready in 1.5 RTTs measured from the start of the test, since it takes
311396
// .5 RTT for the Initial from the client to reach the server
312397
assert_eq!(
313398
// remove floating point division error
314399
Duration::from_millis(rtt.mul_f32(1.5).as_millis() as u64),
315-
server_events[1].timestamp.duration_since_start()
400+
server_dc_state_changed_events[1]
401+
.timestamp
402+
.duration_since_start()
403+
);
404+
assert_eq!(
405+
rtt,
406+
client_dc_state_changed_events[1]
407+
.timestamp
408+
.duration_since_start()
316409
);
317-
assert_eq!(rtt, client_events[1].timestamp.duration_since_start());
318410

319411
// Server completes in 2.5 RTTs measured from the start of the test, since it takes .5 RTT
320412
// for the Initial from the client to reach the server
321413
assert_eq!(
322414
rtt.mul_f32(2.5),
323-
server_events[2].timestamp.duration_since_start()
415+
server_dc_state_changed_events[2]
416+
.timestamp
417+
.duration_since_start()
418+
);
419+
assert_eq!(
420+
rtt * 2,
421+
client_dc_state_changed_events[2]
422+
.timestamp
423+
.duration_since_start()
324424
);
325-
assert_eq!(rtt * 2, client_events[2].timestamp.duration_since_start());
326425

327-
Ok(())
426+
Ok((client_events, server_events))
328427
}
329428

330429
fn assert_dc_complete(events: &[DcStateChangedEvent]) {
@@ -358,21 +457,22 @@ struct DcStateChangedEvent {
358457
}
359458

360459
#[derive(Clone, Default)]
361-
struct DcStateChanged {
362-
pub events: Arc<Mutex<Vec<DcStateChangedEvent>>>,
460+
struct DcRecorder {
461+
pub dc_state_changed_events: Arc<Mutex<Vec<DcStateChangedEvent>>>,
462+
pub endpoint_datagram_dropped_events: Arc<Mutex<Vec<EndpointDatagramDropped>>>,
363463
}
364-
impl DcStateChanged {
464+
impl DcRecorder {
365465
pub fn new() -> Self {
366466
Self::default()
367467
}
368468

369-
pub fn events(&self) -> Arc<Mutex<Vec<DcStateChangedEvent>>> {
370-
self.events.clone()
469+
pub fn dc_state_changed_events(&self) -> Arc<Mutex<Vec<DcStateChangedEvent>>> {
470+
self.dc_state_changed_events.clone()
371471
}
372472
}
373473

374-
impl events::Subscriber for DcStateChanged {
375-
type ConnectionContext = DcStateChanged;
474+
impl events::Subscriber for DcRecorder {
475+
type ConnectionContext = DcRecorder;
376476

377477
fn create_connection_context(
378478
&mut self,
@@ -394,7 +494,45 @@ impl events::Subscriber for DcStateChanged {
394494
state: event.state.clone(),
395495
});
396496
};
397-
let mut buffer = context.events.lock().unwrap();
497+
let mut buffer = context.dc_state_changed_events.lock().unwrap();
398498
store(event, &mut buffer);
399499
}
500+
501+
fn on_endpoint_datagram_dropped(
502+
&mut self,
503+
_meta: &EndpointMeta,
504+
event: &EndpointDatagramDropped,
505+
) {
506+
self.endpoint_datagram_dropped_events
507+
.lock()
508+
.unwrap()
509+
.push(event.clone());
510+
}
511+
}
512+
513+
/// Replace the first short packet payload with a randomized short packet
514+
#[derive(Default)]
515+
struct RandomShort(bool);
516+
517+
impl Interceptor for RandomShort {
518+
#[inline]
519+
fn intercept_rx_datagram<'a>(
520+
&mut self,
521+
_subject: &Subject,
522+
_datagram: &Datagram,
523+
payload: DecoderBufferMut<'a>,
524+
) -> DecoderBufferMut<'a> {
525+
let payload = payload.into_less_safe_slice();
526+
527+
if let 0b0100u8..=0b0111u8 = payload[0] >> 4 {
528+
if !self.0 {
529+
// randomize everything after the short header tag
530+
rand::fill_bytes(&mut payload[1..]);
531+
// only change the first short packet
532+
self.0 = true;
533+
}
534+
}
535+
536+
DecoderBufferMut::new(payload)
537+
}
400538
}

0 commit comments

Comments
 (0)