Skip to content

Commit

Permalink
quinn-rs#2057: Use randomly generated GREASE transport parameter.
Browse files Browse the repository at this point in the history
  • Loading branch information
mstyura committed Nov 21, 2024
1 parent a4c886c commit 86a6a35
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 4 deletions.
2 changes: 2 additions & 0 deletions quinn-proto/src/endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ impl Endpoint {
self.local_cid_generator.as_ref(),
loc_cid,
None,
&mut self.rng,
);
let tls = config
.crypto
Expand Down Expand Up @@ -632,6 +633,7 @@ impl Endpoint {
self.local_cid_generator.as_ref(),
loc_cid,
Some(&server_config),
&mut self.rng,
);
params.stateless_reset_token = Some(ResetToken::new(&*self.config.reset_key, &loc_cid));
params.original_dst_cid = Some(incoming.orig_dst_cid);
Expand Down
137 changes: 133 additions & 4 deletions quinn-proto/src/transport_parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use std::{
};

use bytes::{Buf, BufMut};
use rand::Rng as _;
use thiserror::Error;

use crate::{
Expand Down Expand Up @@ -99,6 +100,10 @@ macro_rules! make_struct {
pub(crate) stateless_reset_token: Option<ResetToken>,
/// The server's preferred address for communication after handshake completion
pub(crate) preferred_address: Option<PreferredAddress>,
/// The randomly generated reserved transport parameter to sustain future extensibility
/// of transport parameter extension.
/// When present included during serialization, bug ignored during deserialization
pub(crate) grease_transport_parameter: Option<ReservedTransportParameter>,
}

// We deliberately don't implement the `Default` trait, since that would be public, and
Expand All @@ -120,6 +125,7 @@ macro_rules! make_struct {
retry_src_cid: None,
stateless_reset_token: None,
preferred_address: None,
grease_transport_parameter: None,
}
}
}
Expand All @@ -129,12 +135,13 @@ macro_rules! make_struct {
apply_params!(make_struct);

impl TransportParameters {
pub(crate) fn new(
pub(crate) fn new<R: rand::RngCore>(
config: &TransportConfig,
endpoint_config: &EndpointConfig,
cid_gen: &dyn ConnectionIdGenerator,
initial_src_cid: ConnectionId,
server_config: Option<&ServerConfig>,
rng: &mut R,
) -> Self {
Self {
initial_src_cid: Some(initial_src_cid),
Expand All @@ -160,6 +167,7 @@ impl TransportParameters {
min_ack_delay: Some(
VarInt::from_u64(u64::try_from(TIMER_GRANULARITY.as_micros()).unwrap()).unwrap(),
),
grease_transport_parameter: Some(ReservedTransportParameter::random(rng)),
..Self::default()
}
}
Expand Down Expand Up @@ -300,9 +308,9 @@ impl TransportParameters {
}
apply_params!(write_params);

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

if let Some(ref x) = self.stateless_reset_token {
w.write_var(0x02);
Expand Down Expand Up @@ -467,6 +475,81 @@ impl TransportParameters {
}
}

/// A reserved transport parameter.
///
/// It has identifier of the form 31 * N + 27 for integer value of N.
/// Such identifiers reserved to exercise the requirement that unknown transport parameters be ignored.
/// The reserved transport parameter have no semantics and can carry arbitrary values.
/// It may be included into quic_transport_parameters TLS extensions when sent and should be ignored when received.
///
/// See spec: https://www.rfc-editor.org/rfc/rfc9000.html#section-18.1
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub(crate) struct ReservedTransportParameter {
/// The reserved identifier of transport parameter
id: VarInt,

/// Buffer to store parameter payload
payload: [u8; Self::MAX_PAYLOAD_LEN],

/// The number of bytes to include into wire format from the `payload` buffer
payload_len: usize,
}

impl ReservedTransportParameter {
/// The maximum length of payload to include as parameter payload.
/// This value is not a specification-imposed limit and picked such that
/// it matches the limit by other implementations of quic, e.g. quic-go & quiche.
const MAX_PAYLOAD_LEN: usize = 16;

// Generate a random reserved identifier of the form `31 * N + 27` as required by RFC 9000.
// Reserved transport parameter identifiers are used to test compliance with the requirement
// that unknown transport parameters be ignored by peers.
// See: https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2
fn generate_reserved_id<R: rand::RngCore>(rng: &mut R) -> VarInt {
let id = {
let rand = rng.gen_range(0u64..(1 << 62) - 27);
let n = rand / 31;
31 * n + 27
};
debug_assert!(
id % 31 == 27,
"generated id does not have the form of 31 * N + 27"
);
VarInt::from_u64(id).expect(
"generated id does fit into range of allowed transport parameter IDs: [0; 2^62)",
)
}

/// Generate transport parameter with random payload and reserved id.
///
/// Implementation is inspired by quic-go & quiche:
/// [1] https://github.com/quic-go/quic-go/blob/3e0a67b2476e1819752f04d75968de042b197b56/internal/wire/transport_parameters.go#L338-L344
/// [2] https://github.com/google/quiche/blob/cb1090b20c40e2f0815107857324e99acf6ec567/quiche/quic/core/crypto/transport_parameters.cc#L843-L860
fn random<R: rand::RngCore>(rng: &mut R) -> Self {
let id = Self::generate_reserved_id(rng);

let payload_len = rng.gen_range(0..Self::MAX_PAYLOAD_LEN);

let payload = {
let mut slice = [0u8; Self::MAX_PAYLOAD_LEN];
rng.fill_bytes(&mut slice[..payload_len]);
slice
};

Self {
id,
payload,
payload_len,
}
}

fn write<W: BufMut>(&self, w: &mut W) {
w.write_var(self.id.0);
w.write_var(self.payload_len as u64);
w.put_slice(&self.payload[..self.payload_len]);
}
}

fn decode_cid(len: usize, value: &mut Option<ConnectionId>, r: &mut impl Buf) -> Result<(), Error> {
if len > MAX_CID_SIZE || value.is_some() || r.remaining() < len {
return Err(Error::Malformed);
Expand Down Expand Up @@ -507,6 +590,52 @@ mod test {
);
}

#[test]
fn reserved_transport_parameter_generate_reserved_id() {
use rand::rngs::mock::StepRng;
let mut rngs = [
StepRng::new(0, 1),
StepRng::new(1, 1),
StepRng::new(27, 1),
StepRng::new(31, 1),
StepRng::new(u32::MAX as u64, 1),
StepRng::new(u32::MAX as u64 - 1, 1),
StepRng::new(u32::MAX as u64 + 1, 1),
StepRng::new(u32::MAX as u64 - 27, 1),
StepRng::new(u32::MAX as u64 + 27, 1),
StepRng::new(u32::MAX as u64 - 31, 1),
StepRng::new(u32::MAX as u64 + 31, 1),
StepRng::new(u64::MAX, 1),
StepRng::new(u64::MAX - 1, 1),
StepRng::new(u64::MAX - 27, 1),
StepRng::new(u64::MAX - 31, 1),
StepRng::new(1 << 62, 1),
StepRng::new((1 << 62) - 1, 1),
StepRng::new((1 << 62) + 1, 1),
StepRng::new((1 << 62) - 27, 1),
StepRng::new((1 << 62) + 27, 1),
StepRng::new((1 << 62) - 31, 1),
StepRng::new((1 << 62) + 31, 1),
];
for rng in &mut rngs {
let id = ReservedTransportParameter::generate_reserved_id(rng);
assert!(id.0 % 31 == 27)
}
}

#[test]
fn reserved_transport_parameter_ignored_when_read() {
let mut buf = Vec::new();
let reserved_parameter = ReservedTransportParameter::random(&mut rand::thread_rng());
assert!(reserved_parameter.payload_len < ReservedTransportParameter::MAX_PAYLOAD_LEN);
assert!(reserved_parameter.id.0 % 31 == 27);

reserved_parameter.write(&mut buf);
assert!(!buf.is_empty());
let read_params = TransportParameters::read(Side::Server, &mut buf.as_slice()).unwrap();
assert_eq!(read_params, TransportParameters::default());
}

#[test]
fn read_semantic_validation() {
#[allow(clippy::type_complexity)]
Expand Down

0 comments on commit 86a6a35

Please sign in to comment.