Skip to content

Commit 2edf192

Browse files
mstyuradjc
authored andcommitted
#2057: Use randomly generated GREASE transport parameter.
1 parent a4c886c commit 2edf192

File tree

2 files changed

+134
-3
lines changed

2 files changed

+134
-3
lines changed

quinn-proto/src/endpoint.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ impl Endpoint {
411411
self.local_cid_generator.as_ref(),
412412
loc_cid,
413413
None,
414+
&mut self.rng,
414415
);
415416
let tls = config
416417
.crypto
@@ -632,6 +633,7 @@ impl Endpoint {
632633
self.local_cid_generator.as_ref(),
633634
loc_cid,
634635
Some(&server_config),
636+
&mut self.rng,
635637
);
636638
params.stateless_reset_token = Some(ResetToken::new(&*self.config.reset_key, &loc_cid));
637639
params.original_dst_cid = Some(incoming.orig_dst_cid);

quinn-proto/src/transport_parameters.rs

Lines changed: 132 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use std::{
1212
};
1313

1414
use bytes::{Buf, BufMut};
15+
use rand::{Rng as _, RngCore};
1516
use thiserror::Error;
1617

1718
use crate::{
@@ -99,6 +100,10 @@ macro_rules! make_struct {
99100
pub(crate) stateless_reset_token: Option<ResetToken>,
100101
/// The server's preferred address for communication after handshake completion
101102
pub(crate) preferred_address: Option<PreferredAddress>,
103+
/// The randomly generated reserved transport parameter to sustain future extensibility
104+
/// of transport parameter extensions.
105+
/// When present, it is included during serialization but ignored during deserialization.
106+
pub(crate) grease_transport_parameter: Option<ReservedTransportParameter>,
102107
}
103108

104109
// We deliberately don't implement the `Default` trait, since that would be public, and
@@ -120,6 +125,7 @@ macro_rules! make_struct {
120125
retry_src_cid: None,
121126
stateless_reset_token: None,
122127
preferred_address: None,
128+
grease_transport_parameter: None,
123129
}
124130
}
125131
}
@@ -135,6 +141,7 @@ impl TransportParameters {
135141
cid_gen: &dyn ConnectionIdGenerator,
136142
initial_src_cid: ConnectionId,
137143
server_config: Option<&ServerConfig>,
144+
rng: &mut impl RngCore,
138145
) -> Self {
139146
Self {
140147
initial_src_cid: Some(initial_src_cid),
@@ -160,6 +167,7 @@ impl TransportParameters {
160167
min_ack_delay: Some(
161168
VarInt::from_u64(u64::try_from(TIMER_GRANULARITY.as_micros()).unwrap()).unwrap(),
162169
),
170+
grease_transport_parameter: Some(ReservedTransportParameter::random(rng)),
163171
..Self::default()
164172
}
165173
}
@@ -300,9 +308,9 @@ impl TransportParameters {
300308
}
301309
apply_params!(write_params);
302310

303-
// Add a reserved parameter to keep people on their toes
304-
w.write_var(31 * 5 + 27);
305-
w.write_var(0);
311+
if let Some(param) = self.grease_transport_parameter {
312+
param.write(w);
313+
}
306314

307315
if let Some(ref x) = self.stateless_reset_token {
308316
w.write_var(0x02);
@@ -467,6 +475,81 @@ impl TransportParameters {
467475
}
468476
}
469477

478+
/// A reserved transport parameter.
479+
///
480+
/// It has an identifier of the form 31 * N + 27 for the integer value of N.
481+
/// Such identifiers are reserved to exercise the requirement that unknown transport parameters be ignored.
482+
/// The reserved transport parameter has no semantics and can carry arbitrary values.
483+
/// It may be included in transport parameters sent to the peer, and should be ignored when received.
484+
///
485+
/// See spec: <https://www.rfc-editor.org/rfc/rfc9000.html#section-18.1>
486+
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
487+
pub(crate) struct ReservedTransportParameter {
488+
/// The reserved identifier of the transport parameter
489+
id: VarInt,
490+
491+
/// Buffer to store the parameter payload
492+
payload: [u8; Self::MAX_PAYLOAD_LEN],
493+
494+
/// The number of bytes to include in the wire format from the `payload` buffer
495+
payload_len: usize,
496+
}
497+
498+
impl ReservedTransportParameter {
499+
/// Generates a transport parameter with a random payload and a reserved ID.
500+
///
501+
/// The implementation is inspired by quic-go and quiche:
502+
/// 1. <https://github.com/quic-go/quic-go/blob/3e0a67b2476e1819752f04d75968de042b197b56/internal/wire/transport_parameters.go#L338-L344>
503+
/// 2. <https://github.com/google/quiche/blob/cb1090b20c40e2f0815107857324e99acf6ec567/quiche/quic/core/crypto/transport_parameters.cc#L843-L860>
504+
fn random(rng: &mut impl RngCore) -> Self {
505+
let id = Self::generate_reserved_id(rng);
506+
507+
let payload_len = rng.gen_range(0..Self::MAX_PAYLOAD_LEN);
508+
509+
let payload = {
510+
let mut slice = [0u8; Self::MAX_PAYLOAD_LEN];
511+
rng.fill_bytes(&mut slice[..payload_len]);
512+
slice
513+
};
514+
515+
Self {
516+
id,
517+
payload,
518+
payload_len,
519+
}
520+
}
521+
522+
fn write(&self, w: &mut impl BufMut) {
523+
w.write_var(self.id.0);
524+
w.write_var(self.payload_len as u64);
525+
w.put_slice(&self.payload[..self.payload_len]);
526+
}
527+
528+
/// Generates a random reserved identifier of the form `31 * N + 27`, as required by RFC 9000.
529+
/// Reserved transport parameter identifiers are used to test compliance with the requirement
530+
/// that unknown transport parameters must be ignored by peers.
531+
/// See: <https://www.rfc-editor.org/rfc/rfc9000.html#section-18.1> and <https://www.rfc-editor.org/rfc/rfc9000.html#section-22.3>
532+
fn generate_reserved_id(rng: &mut impl RngCore) -> VarInt {
533+
let id = {
534+
let rand = rng.gen_range(0u64..(1 << 62) - 27);
535+
let n = rand / 31;
536+
31 * n + 27
537+
};
538+
debug_assert!(
539+
id % 31 == 27,
540+
"generated id does not have the form of 31 * N + 27"
541+
);
542+
VarInt::from_u64(id).expect(
543+
"generated id does fit into range of allowed transport parameter IDs: [0; 2^62)",
544+
)
545+
}
546+
547+
/// The maximum length of the payload to include as the parameter payload.
548+
/// This value is not a specification-imposed limit but is chosen to match
549+
/// the limit used by other implementations of QUIC, e.g., quic-go and quiche.
550+
const MAX_PAYLOAD_LEN: usize = 16;
551+
}
552+
470553
fn decode_cid(len: usize, value: &mut Option<ConnectionId>, r: &mut impl Buf) -> Result<(), Error> {
471554
if len > MAX_CID_SIZE || value.is_some() || r.remaining() < len {
472555
return Err(Error::Malformed);
@@ -507,6 +590,52 @@ mod test {
507590
);
508591
}
509592

593+
#[test]
594+
fn reserved_transport_parameter_generate_reserved_id() {
595+
use rand::rngs::mock::StepRng;
596+
let mut rngs = [
597+
StepRng::new(0, 1),
598+
StepRng::new(1, 1),
599+
StepRng::new(27, 1),
600+
StepRng::new(31, 1),
601+
StepRng::new(u32::MAX as u64, 1),
602+
StepRng::new(u32::MAX as u64 - 1, 1),
603+
StepRng::new(u32::MAX as u64 + 1, 1),
604+
StepRng::new(u32::MAX as u64 - 27, 1),
605+
StepRng::new(u32::MAX as u64 + 27, 1),
606+
StepRng::new(u32::MAX as u64 - 31, 1),
607+
StepRng::new(u32::MAX as u64 + 31, 1),
608+
StepRng::new(u64::MAX, 1),
609+
StepRng::new(u64::MAX - 1, 1),
610+
StepRng::new(u64::MAX - 27, 1),
611+
StepRng::new(u64::MAX - 31, 1),
612+
StepRng::new(1 << 62, 1),
613+
StepRng::new((1 << 62) - 1, 1),
614+
StepRng::new((1 << 62) + 1, 1),
615+
StepRng::new((1 << 62) - 27, 1),
616+
StepRng::new((1 << 62) + 27, 1),
617+
StepRng::new((1 << 62) - 31, 1),
618+
StepRng::new((1 << 62) + 31, 1),
619+
];
620+
for rng in &mut rngs {
621+
let id = ReservedTransportParameter::generate_reserved_id(rng);
622+
assert!(id.0 % 31 == 27)
623+
}
624+
}
625+
626+
#[test]
627+
fn reserved_transport_parameter_ignored_when_read() {
628+
let mut buf = Vec::new();
629+
let reserved_parameter = ReservedTransportParameter::random(&mut rand::thread_rng());
630+
assert!(reserved_parameter.payload_len < ReservedTransportParameter::MAX_PAYLOAD_LEN);
631+
assert!(reserved_parameter.id.0 % 31 == 27);
632+
633+
reserved_parameter.write(&mut buf);
634+
assert!(!buf.is_empty());
635+
let read_params = TransportParameters::read(Side::Server, &mut buf.as_slice()).unwrap();
636+
assert_eq!(read_params, TransportParameters::default());
637+
}
638+
510639
#[test]
511640
fn read_semantic_validation() {
512641
#[allow(clippy::type_complexity)]

0 commit comments

Comments
 (0)