From 8c664916f7b6718848eb43827b349472cfbe3213 Mon Sep 17 00:00:00 2001 From: Phoenix Kahlo Date: Fri, 8 Nov 2024 21:03:05 -0600 Subject: [PATCH 01/42] proto: Rename frame::Type to FrameType quic_proto::frame::Type should be publically re-exported. This commit renames the it to a name more befitting of a public type. --- quinn-proto/src/connection/mod.rs | 20 +-- quinn-proto/src/connection/streams/state.rs | 8 +- quinn-proto/src/frame.rs | 150 ++++++++++---------- quinn-proto/src/transport_error.rs | 2 +- 4 files changed, 90 insertions(+), 90 deletions(-) diff --git a/quinn-proto/src/connection/mod.rs b/quinn-proto/src/connection/mod.rs index e037e4486e..6986d63145 100644 --- a/quinn-proto/src/connection/mod.rs +++ b/quinn-proto/src/connection/mod.rs @@ -501,7 +501,7 @@ impl Connection { self, )?; trace!("validating previous path with PATH_CHALLENGE {:08x}", token); - buf.write(frame::Type::PATH_CHALLENGE); + buf.write(frame::FrameType::PATH_CHALLENGE); buf.write(token); self.stats.frame_tx.path_challenge += 1; @@ -883,7 +883,7 @@ impl Connection { // above. let mut builder = builder_storage.take().unwrap(); trace!("PATH_RESPONSE {:08x} (off-path)", token); - buf.write(frame::Type::PATH_RESPONSE); + buf.write(frame::FrameType::PATH_RESPONSE); buf.write(token); self.stats.frame_tx.path_response += 1; builder.pad_to(MIN_INITIAL_SIZE); @@ -979,12 +979,12 @@ impl Connection { )?; // We implement MTU probes as ping packets padded up to the probe size - buf.write(frame::Type::PING); + buf.write(frame::FrameType::PING); self.stats.frame_tx.ping += 1; // If supported by the peer, we want no delays to the probe's ACK if self.peer_supports_ack_frequency() { - buf.write(frame::Type::IMMEDIATE_ACK); + buf.write(frame::FrameType::IMMEDIATE_ACK); self.stats.frame_tx.immediate_ack += 1; } @@ -3037,7 +3037,7 @@ impl Connection { // HANDSHAKE_DONE if !is_0rtt && mem::replace(&mut space.pending.handshake_done, false) { - buf.write(frame::Type::HANDSHAKE_DONE); + buf.write(frame::FrameType::HANDSHAKE_DONE); sent.retransmits.get_or_create().handshake_done = true; // This is just a u8 counter and the frame is typically just sent once self.stats.frame_tx.handshake_done = @@ -3047,7 +3047,7 @@ impl Connection { // PING if mem::replace(&mut space.ping_pending, false) { trace!("PING"); - buf.write(frame::Type::PING); + buf.write(frame::FrameType::PING); sent.non_retransmits = true; self.stats.frame_tx.ping += 1; } @@ -3055,7 +3055,7 @@ impl Connection { // IMMEDIATE_ACK if mem::replace(&mut space.immediate_ack_pending, false) { trace!("IMMEDIATE_ACK"); - buf.write(frame::Type::IMMEDIATE_ACK); + buf.write(frame::FrameType::IMMEDIATE_ACK); sent.non_retransmits = true; self.stats.frame_tx.immediate_ack += 1; } @@ -3111,7 +3111,7 @@ impl Connection { sent.non_retransmits = true; sent.requires_padding = true; trace!("PATH_CHALLENGE {:08x}", token); - buf.write(frame::Type::PATH_CHALLENGE); + buf.write(frame::FrameType::PATH_CHALLENGE); buf.write(token); self.stats.frame_tx.path_challenge += 1; } @@ -3123,7 +3123,7 @@ impl Connection { sent.non_retransmits = true; sent.requires_padding = true; trace!("PATH_RESPONSE {:08x}", token); - buf.write(frame::Type::PATH_RESPONSE); + buf.write(frame::FrameType::PATH_RESPONSE); buf.write(token); self.stats.frame_tx.path_response += 1; } @@ -3210,7 +3210,7 @@ impl Connection { None => break, }; trace!(sequence = seq, "RETIRE_CONNECTION_ID"); - buf.write(frame::Type::RETIRE_CONNECTION_ID); + buf.write(frame::FrameType::RETIRE_CONNECTION_ID); buf.write_var(seq); sent.retransmits.get_or_create().retire_cids.push(seq); self.stats.frame_tx.retire_connection_id += 1; diff --git a/quinn-proto/src/connection/streams/state.rs b/quinn-proto/src/connection/streams/state.rs index faa3d55c22..a3f14b3b5f 100644 --- a/quinn-proto/src/connection/streams/state.rs +++ b/quinn-proto/src/connection/streams/state.rs @@ -478,7 +478,7 @@ impl StreamsState { } retransmits.get_or_create().max_data = true; - buf.write(frame::Type::MAX_DATA); + buf.write(frame::FrameType::MAX_DATA); buf.write(max); stats.max_data += 1; } @@ -508,7 +508,7 @@ impl StreamsState { rs.record_sent_max_stream_data(max); trace!(stream = %id, max = max, "MAX_STREAM_DATA"); - buf.write(frame::Type::MAX_STREAM_DATA); + buf.write(frame::FrameType::MAX_STREAM_DATA); buf.write(id); buf.write_var(max); stats.max_stream_data += 1; @@ -529,8 +529,8 @@ impl StreamsState { dir ); buf.write(match dir { - Dir::Uni => frame::Type::MAX_STREAMS_UNI, - Dir::Bi => frame::Type::MAX_STREAMS_BIDI, + Dir::Uni => frame::FrameType::MAX_STREAMS_UNI, + Dir::Bi => frame::FrameType::MAX_STREAMS_BIDI, }); buf.write_var(self.max_remote[dir as usize]); match dir { diff --git a/quinn-proto/src/frame.rs b/quinn-proto/src/frame.rs index 60b17eb78c..63cd9dc6bb 100644 --- a/quinn-proto/src/frame.rs +++ b/quinn-proto/src/frame.rs @@ -19,9 +19,9 @@ use crate::{ use arbitrary::Arbitrary; #[derive(Copy, Clone, Eq, PartialEq)] -pub struct Type(u64); +pub struct FrameType(u64); -impl Type { +impl FrameType { fn stream(self) -> Option { if STREAM_TYS.contains(&self.0) { Some(StreamInfo(self.0 as u8)) @@ -38,7 +38,7 @@ impl Type { } } -impl coding::Codec for Type { +impl coding::Codec for FrameType { fn decode(buf: &mut B) -> coding::Result { Ok(Self(buf.get_var()?)) } @@ -54,11 +54,11 @@ pub(crate) trait FrameStruct { macro_rules! frame_types { {$($name:ident = $val:expr,)*} => { - impl Type { - $(pub const $name: Type = Type($val);)* + impl FrameType { + $(pub const $name: FrameType = FrameType($val);)* } - impl fmt::Debug for Type { + impl fmt::Debug for FrameType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.0 { $($val => f.write_str(stringify!($name)),)* @@ -67,7 +67,7 @@ macro_rules! frame_types { } } - impl fmt::Display for Type { + impl fmt::Display for FrameType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.0 { $($val => f.write_str(stringify!($name)),)* @@ -166,25 +166,25 @@ pub(crate) enum Frame { } impl Frame { - pub(crate) fn ty(&self) -> Type { + pub(crate) fn ty(&self) -> FrameType { use self::Frame::*; match *self { - Padding => Type::PADDING, - ResetStream(_) => Type::RESET_STREAM, - Close(self::Close::Connection(_)) => Type::CONNECTION_CLOSE, - Close(self::Close::Application(_)) => Type::APPLICATION_CLOSE, - MaxData(_) => Type::MAX_DATA, - MaxStreamData { .. } => Type::MAX_STREAM_DATA, - MaxStreams { dir: Dir::Bi, .. } => Type::MAX_STREAMS_BIDI, - MaxStreams { dir: Dir::Uni, .. } => Type::MAX_STREAMS_UNI, - Ping => Type::PING, - DataBlocked { .. } => Type::DATA_BLOCKED, - StreamDataBlocked { .. } => Type::STREAM_DATA_BLOCKED, - StreamsBlocked { dir: Dir::Bi, .. } => Type::STREAMS_BLOCKED_BIDI, - StreamsBlocked { dir: Dir::Uni, .. } => Type::STREAMS_BLOCKED_UNI, - StopSending { .. } => Type::STOP_SENDING, - RetireConnectionId { .. } => Type::RETIRE_CONNECTION_ID, - Ack(_) => Type::ACK, + Padding => FrameType::PADDING, + ResetStream(_) => FrameType::RESET_STREAM, + Close(self::Close::Connection(_)) => FrameType::CONNECTION_CLOSE, + Close(self::Close::Application(_)) => FrameType::APPLICATION_CLOSE, + MaxData(_) => FrameType::MAX_DATA, + MaxStreamData { .. } => FrameType::MAX_STREAM_DATA, + MaxStreams { dir: Dir::Bi, .. } => FrameType::MAX_STREAMS_BIDI, + MaxStreams { dir: Dir::Uni, .. } => FrameType::MAX_STREAMS_UNI, + Ping => FrameType::PING, + DataBlocked { .. } => FrameType::DATA_BLOCKED, + StreamDataBlocked { .. } => FrameType::STREAM_DATA_BLOCKED, + StreamsBlocked { dir: Dir::Bi, .. } => FrameType::STREAMS_BLOCKED_BIDI, + StreamsBlocked { dir: Dir::Uni, .. } => FrameType::STREAMS_BLOCKED_UNI, + StopSending { .. } => FrameType::STOP_SENDING, + RetireConnectionId { .. } => FrameType::RETIRE_CONNECTION_ID, + Ack(_) => FrameType::ACK, Stream(ref x) => { let mut ty = *STREAM_TYS.start(); if x.fin { @@ -193,17 +193,17 @@ impl Frame { if x.offset != 0 { ty |= 0x04; } - Type(ty) + FrameType(ty) } - PathChallenge(_) => Type::PATH_CHALLENGE, - PathResponse(_) => Type::PATH_RESPONSE, - NewConnectionId { .. } => Type::NEW_CONNECTION_ID, - Crypto(_) => Type::CRYPTO, - NewToken { .. } => Type::NEW_TOKEN, - Datagram(_) => Type(*DATAGRAM_TYS.start()), - AckFrequency(_) => Type::ACK_FREQUENCY, - ImmediateAck => Type::IMMEDIATE_ACK, - HandshakeDone => Type::HANDSHAKE_DONE, + PathChallenge(_) => FrameType::PATH_CHALLENGE, + PathResponse(_) => FrameType::PATH_RESPONSE, + NewConnectionId { .. } => FrameType::NEW_CONNECTION_ID, + Crypto(_) => FrameType::CRYPTO, + NewToken { .. } => FrameType::NEW_TOKEN, + Datagram(_) => FrameType(*DATAGRAM_TYS.start()), + AckFrequency(_) => FrameType::ACK_FREQUENCY, + ImmediateAck => FrameType::IMMEDIATE_ACK, + HandshakeDone => FrameType::HANDSHAKE_DONE, } } @@ -253,7 +253,7 @@ pub struct ConnectionClose { /// Class of error as encoded in the specification pub error_code: TransportErrorCode, /// Type of frame that caused the close - pub frame_type: Option, + pub frame_type: Option, /// Human-readable reason for the close pub reason: Bytes, } @@ -285,7 +285,7 @@ impl FrameStruct for ConnectionClose { impl ConnectionClose { pub(crate) fn encode(&self, out: &mut W, max_len: usize) { - out.write(Type::CONNECTION_CLOSE); // 1 byte + out.write(FrameType::CONNECTION_CLOSE); // 1 byte out.write(self.error_code); // <= 8 bytes let ty = self.frame_type.map_or(0, |x| x.0); out.write_var(ty); // <= 8 bytes @@ -328,7 +328,7 @@ impl FrameStruct for ApplicationClose { impl ApplicationClose { pub(crate) fn encode(&self, out: &mut W, max_len: usize) { - out.write(Type::APPLICATION_CLOSE); // 1 byte + out.write(FrameType::APPLICATION_CLOSE); // 1 byte out.write(self.error_code); // <= 8 bytes let max_len = max_len - 3 - VarInt::from_u64(self.reason.len() as u64).unwrap().size(); let actual_len = self.reason.len().min(max_len); @@ -388,9 +388,9 @@ impl Ack { let largest = first.end - 1; let first_size = first.end - first.start; buf.write(if ecn.is_some() { - Type::ACK_ECN + FrameType::ACK_ECN } else { - Type::ACK + FrameType::ACK }); buf.write_var(largest); buf.write_var(delay); @@ -517,7 +517,7 @@ impl Crypto { pub(crate) const SIZE_BOUND: usize = 17; pub(crate) fn encode(&self, out: &mut W) { - out.write(Type::CRYPTO); + out.write(FrameType::CRYPTO); out.write_var(self.offset); out.write_var(self.data.len() as u64); out.put_slice(&self.data); @@ -527,7 +527,7 @@ impl Crypto { pub(crate) struct Iter { // TODO: ditch io::Cursor after bytes 0.5 bytes: io::Cursor, - last_ty: Option, + last_ty: Option, } impl Iter { @@ -558,68 +558,68 @@ impl Iter { } fn try_next(&mut self) -> Result { - let ty = self.bytes.get::()?; + let ty = self.bytes.get::()?; self.last_ty = Some(ty); Ok(match ty { - Type::PADDING => Frame::Padding, - Type::RESET_STREAM => Frame::ResetStream(ResetStream { + FrameType::PADDING => Frame::Padding, + FrameType::RESET_STREAM => Frame::ResetStream(ResetStream { id: self.bytes.get()?, error_code: self.bytes.get()?, final_offset: self.bytes.get()?, }), - Type::CONNECTION_CLOSE => Frame::Close(Close::Connection(ConnectionClose { + FrameType::CONNECTION_CLOSE => Frame::Close(Close::Connection(ConnectionClose { error_code: self.bytes.get()?, frame_type: { let x = self.bytes.get_var()?; if x == 0 { None } else { - Some(Type(x)) + Some(FrameType(x)) } }, reason: self.take_len()?, })), - Type::APPLICATION_CLOSE => Frame::Close(Close::Application(ApplicationClose { + FrameType::APPLICATION_CLOSE => Frame::Close(Close::Application(ApplicationClose { error_code: self.bytes.get()?, reason: self.take_len()?, })), - Type::MAX_DATA => Frame::MaxData(self.bytes.get()?), - Type::MAX_STREAM_DATA => Frame::MaxStreamData { + FrameType::MAX_DATA => Frame::MaxData(self.bytes.get()?), + FrameType::MAX_STREAM_DATA => Frame::MaxStreamData { id: self.bytes.get()?, offset: self.bytes.get_var()?, }, - Type::MAX_STREAMS_BIDI => Frame::MaxStreams { + FrameType::MAX_STREAMS_BIDI => Frame::MaxStreams { dir: Dir::Bi, count: self.bytes.get_var()?, }, - Type::MAX_STREAMS_UNI => Frame::MaxStreams { + FrameType::MAX_STREAMS_UNI => Frame::MaxStreams { dir: Dir::Uni, count: self.bytes.get_var()?, }, - Type::PING => Frame::Ping, - Type::DATA_BLOCKED => Frame::DataBlocked { + FrameType::PING => Frame::Ping, + FrameType::DATA_BLOCKED => Frame::DataBlocked { offset: self.bytes.get_var()?, }, - Type::STREAM_DATA_BLOCKED => Frame::StreamDataBlocked { + FrameType::STREAM_DATA_BLOCKED => Frame::StreamDataBlocked { id: self.bytes.get()?, offset: self.bytes.get_var()?, }, - Type::STREAMS_BLOCKED_BIDI => Frame::StreamsBlocked { + FrameType::STREAMS_BLOCKED_BIDI => Frame::StreamsBlocked { dir: Dir::Bi, limit: self.bytes.get_var()?, }, - Type::STREAMS_BLOCKED_UNI => Frame::StreamsBlocked { + FrameType::STREAMS_BLOCKED_UNI => Frame::StreamsBlocked { dir: Dir::Uni, limit: self.bytes.get_var()?, }, - Type::STOP_SENDING => Frame::StopSending(StopSending { + FrameType::STOP_SENDING => Frame::StopSending(StopSending { id: self.bytes.get()?, error_code: self.bytes.get()?, }), - Type::RETIRE_CONNECTION_ID => Frame::RetireConnectionId { + FrameType::RETIRE_CONNECTION_ID => Frame::RetireConnectionId { sequence: self.bytes.get_var()?, }, - Type::ACK | Type::ACK_ECN => { + FrameType::ACK | FrameType::ACK_ECN => { let largest = self.bytes.get_var()?; let delay = self.bytes.get_var()?; let extra_blocks = self.bytes.get_var()? as usize; @@ -630,7 +630,7 @@ impl Iter { delay, largest, additional: self.bytes.get_ref().slice(start..end), - ecn: if ty != Type::ACK_ECN { + ecn: if ty != FrameType::ACK_ECN { None } else { Some(EcnCounts { @@ -641,9 +641,9 @@ impl Iter { }, }) } - Type::PATH_CHALLENGE => Frame::PathChallenge(self.bytes.get()?), - Type::PATH_RESPONSE => Frame::PathResponse(self.bytes.get()?), - Type::NEW_CONNECTION_ID => { + FrameType::PATH_CHALLENGE => Frame::PathChallenge(self.bytes.get()?), + FrameType::PATH_RESPONSE => Frame::PathResponse(self.bytes.get()?), + FrameType::NEW_CONNECTION_ID => { let sequence = self.bytes.get_var()?; let retire_prior_to = self.bytes.get_var()?; if retire_prior_to > sequence { @@ -671,21 +671,21 @@ impl Iter { reset_token: reset_token.into(), }) } - Type::CRYPTO => Frame::Crypto(Crypto { + FrameType::CRYPTO => Frame::Crypto(Crypto { offset: self.bytes.get_var()?, data: self.take_len()?, }), - Type::NEW_TOKEN => Frame::NewToken { + FrameType::NEW_TOKEN => Frame::NewToken { token: self.take_len()?, }, - Type::HANDSHAKE_DONE => Frame::HandshakeDone, - Type::ACK_FREQUENCY => Frame::AckFrequency(AckFrequency { + FrameType::HANDSHAKE_DONE => Frame::HandshakeDone, + FrameType::ACK_FREQUENCY => Frame::AckFrequency(AckFrequency { sequence: self.bytes.get()?, ack_eliciting_threshold: self.bytes.get()?, request_max_ack_delay: self.bytes.get()?, reordering_threshold: self.bytes.get()?, }), - Type::IMMEDIATE_ACK => Frame::ImmediateAck, + FrameType::IMMEDIATE_ACK => Frame::ImmediateAck, _ => { if let Some(s) = ty.stream() { Frame::Stream(Stream { @@ -743,7 +743,7 @@ impl Iterator for Iter { #[derive(Debug)] pub(crate) struct InvalidFrame { - pub(crate) ty: Option, + pub(crate) ty: Option, pub(crate) reason: &'static str, } @@ -833,7 +833,7 @@ impl FrameStruct for ResetStream { impl ResetStream { pub(crate) fn encode(&self, out: &mut W) { - out.write(Type::RESET_STREAM); // 1 byte + out.write(FrameType::RESET_STREAM); // 1 byte out.write(self.id); // <= 8 bytes out.write(self.error_code); // <= 8 bytes out.write(self.final_offset); // <= 8 bytes @@ -852,7 +852,7 @@ impl FrameStruct for StopSending { impl StopSending { pub(crate) fn encode(&self, out: &mut W) { - out.write(Type::STOP_SENDING); // 1 byte + out.write(FrameType::STOP_SENDING); // 1 byte out.write(self.id); // <= 8 bytes out.write(self.error_code) // <= 8 bytes } @@ -868,7 +868,7 @@ pub(crate) struct NewConnectionId { impl NewConnectionId { pub(crate) fn encode(&self, out: &mut W) { - out.write(Type::NEW_CONNECTION_ID); + out.write(FrameType::NEW_CONNECTION_ID); out.write_var(self.sequence); out.write_var(self.retire_prior_to); out.write(self.id.len() as u8); @@ -893,7 +893,7 @@ impl FrameStruct for Datagram { impl Datagram { pub(crate) fn encode(&self, length: bool, out: &mut Vec) { - out.write(Type(*DATAGRAM_TYS.start() | u64::from(length))); // 1 byte + out.write(FrameType(*DATAGRAM_TYS.start() | u64::from(length))); // 1 byte if length { // Safe to unwrap because we check length sanity before queueing datagrams out.write(VarInt::from_u64(self.data.len() as u64).unwrap()); // <= 8 bytes @@ -920,7 +920,7 @@ pub(crate) struct AckFrequency { impl AckFrequency { pub(crate) fn encode(&self, buf: &mut W) { - buf.write(Type::ACK_FREQUENCY); + buf.write(FrameType::ACK_FREQUENCY); buf.write(self.sequence); buf.write(self.ack_eliciting_threshold); buf.write(self.request_max_ack_delay); @@ -990,7 +990,7 @@ mod test { #[test] fn immediate_ack_coding() { let mut buf = Vec::new(); - Type::IMMEDIATE_ACK.encode(&mut buf); + FrameType::IMMEDIATE_ACK.encode(&mut buf); let frames = frames(buf); assert_eq!(frames.len(), 1); assert_matches!(&frames[0], Frame::ImmediateAck); diff --git a/quinn-proto/src/transport_error.rs b/quinn-proto/src/transport_error.rs index 5007f7713e..047cd0acc1 100644 --- a/quinn-proto/src/transport_error.rs +++ b/quinn-proto/src/transport_error.rs @@ -13,7 +13,7 @@ pub struct Error { /// Type of error pub code: Code, /// Frame type that triggered the error - pub frame: Option, + pub frame: Option, /// Human-readable explanation of the reason pub reason: String, } From 7944e0fabcffe9c0d14f00d8eaa147f94f5970c7 Mon Sep 17 00:00:00 2001 From: Phoenix Kahlo Date: Fri, 8 Nov 2024 21:06:14 -0600 Subject: [PATCH 02/42] proto: Fix missing re-exports --- quinn-proto/src/frame.rs | 3 ++- quinn-proto/src/lib.rs | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/quinn-proto/src/frame.rs b/quinn-proto/src/frame.rs index 63cd9dc6bb..0bc7f34ad4 100644 --- a/quinn-proto/src/frame.rs +++ b/quinn-proto/src/frame.rs @@ -18,6 +18,7 @@ use crate::{ #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; +/// A QUIC frame type #[derive(Copy, Clone, Eq, PartialEq)] pub struct FrameType(u64); @@ -55,7 +56,7 @@ pub(crate) trait FrameStruct { macro_rules! frame_types { {$($name:ident = $val:expr,)*} => { impl FrameType { - $(pub const $name: FrameType = FrameType($val);)* + $(pub(crate) const $name: FrameType = FrameType($val);)* } impl fmt::Debug for FrameType { diff --git a/quinn-proto/src/lib.rs b/quinn-proto/src/lib.rs index 458b826bbc..79eddc827a 100644 --- a/quinn-proto/src/lib.rs +++ b/quinn-proto/src/lib.rs @@ -46,6 +46,9 @@ pub use crate::connection::{ WriteError, Written, }; +#[cfg(feature = "rustls")] +pub use rustls; + mod config; pub use config::{ AckFrequencyConfig, ClientConfig, ConfigError, EndpointConfig, IdleTimeout, MtuDiscoveryConfig, @@ -56,7 +59,7 @@ pub mod crypto; mod frame; use crate::frame::Frame; -pub use crate::frame::{ApplicationClose, ConnectionClose, Datagram}; +pub use crate::frame::{ApplicationClose, ConnectionClose, Datagram, FrameType}; mod endpoint; pub use crate::endpoint::{ From eebccff309cb342c2faac3ea875ca81734685821 Mon Sep 17 00:00:00 2001 From: Phoenix Kahlo Date: Fri, 8 Nov 2024 21:08:15 -0600 Subject: [PATCH 03/42] quinn: Fix missing re-exports --- quinn/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/quinn/src/lib.rs b/quinn/src/lib.rs index 31e13677f8..84a3821f68 100644 --- a/quinn/src/lib.rs +++ b/quinn/src/lib.rs @@ -63,8 +63,10 @@ mod work_limiter; pub use proto::{ congestion, crypto, AckFrequencyConfig, ApplicationClose, Chunk, ClientConfig, ClosedStream, - ConfigError, ConnectError, ConnectionClose, ConnectionError, ConnectionStats, EndpointConfig, - IdleTimeout, MtuDiscoveryConfig, ServerConfig, StreamId, Transmit, TransportConfig, VarInt, + ConfigError, ConnectError, ConnectionClose, ConnectionError, ConnectionId, + ConnectionIdGenerator, ConnectionStats, Dir, EcnCodepoint, EndpointConfig, FrameStats, + FrameType, IdleTimeout, MtuDiscoveryConfig, PathStats, ServerConfig, Side, StreamId, Transmit, + TransportConfig, TransportErrorCode, UdpStats, VarInt, VarIntBoundsExceeded, Written, }; #[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] pub use rustls; From 2a8b9044cc1a7108b63ff42746023bfbfec334bb Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 11 Nov 2024 11:45:52 +0100 Subject: [PATCH 04/42] proto: bump version to 0.11.9 --- quinn-proto/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quinn-proto/Cargo.toml b/quinn-proto/Cargo.toml index 6267ffe741..617c41081c 100644 --- a/quinn-proto/Cargo.toml +++ b/quinn-proto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "quinn-proto" -version = "0.11.8" +version = "0.11.9" edition.workspace = true rust-version.workspace = true license.workspace = true From ca1f2ef9fe87f90f1ca108a00fb60a41cef367bc Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 11 Nov 2024 11:47:18 +0100 Subject: [PATCH 05/42] Revert "crypto: expose negotiated_cipher_suite in the hadshake data" Temporarily revert a5d9bd1154b7644ff22b75191a89db9687546fdb because it introduced semver-incompatible change. Will submit as a PR again after merging. --- quinn-proto/src/crypto/rustls.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/quinn-proto/src/crypto/rustls.rs b/quinn-proto/src/crypto/rustls.rs index 02d26a1c60..d60740bb30 100644 --- a/quinn-proto/src/crypto/rustls.rs +++ b/quinn-proto/src/crypto/rustls.rs @@ -64,11 +64,6 @@ impl crypto::Session for TlsSession { Connection::Client(_) => None, Connection::Server(ref session) => session.server_name().map(|x| x.into()), }, - negotiated_cipher_suite: self - .inner - .negotiated_cipher_suite() - .expect("cipher is negotiated") - .suite(), })) } @@ -261,8 +256,6 @@ pub struct HandshakeData { /// /// Always `None` for outgoing connections pub server_name: Option, - /// The ciphersuite negotiated with the peer - pub negotiated_cipher_suite: CipherSuite, } /// A QUIC-compatible TLS client configuration From 66546ddd5aee10672e31bb166e57891a13863171 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 11 Nov 2024 11:50:40 +0100 Subject: [PATCH 06/42] quinn: bump version to 0.11.6 --- quinn/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quinn/Cargo.toml b/quinn/Cargo.toml index d1829e85b8..4b020a34ee 100644 --- a/quinn/Cargo.toml +++ b/quinn/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "quinn" -version = "0.11.5" +version = "0.11.6" license.workspace = true repository.workspace = true description = "Versatile QUIC transport protocol implementation" From bb4df4d35c84281ba25b7d5ac0e6e5f35279d94c Mon Sep 17 00:00:00 2001 From: Delta 4 <156182500+delta4chat@users.noreply.github.com> Date: Tue, 12 Nov 2024 19:02:13 +0800 Subject: [PATCH 07/42] fixes https://github.com/quinn-rs/quinn/issues/2033 --- quinn-udp/src/unix.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/quinn-udp/src/unix.rs b/quinn-udp/src/unix.rs index 252e6f719f..b8ff6e5929 100644 --- a/quinn-udp/src/unix.rs +++ b/quinn-udp/src/unix.rs @@ -445,7 +445,12 @@ fn send(state: &UdpSocketState, io: SockRef<'_>, transmit: &Transmit<'_>) -> io: Ok(()) } -#[cfg(not(any(apple, target_os = "openbsd", target_os = "solaris")))] +#[cfg(not(any( + apple, + target_os = "openbsd", + target_os = "netbsd", + target_os = "solaris" +)))] fn recv(io: SockRef<'_>, bufs: &mut [IoSliceMut<'_>], meta: &mut [RecvMeta]) -> io::Result { let mut names = [MaybeUninit::::uninit(); BATCH_SIZE]; let mut ctrls = [cmsg::Aligned(MaybeUninit::<[u8; CMSG_LEN]>::uninit()); BATCH_SIZE]; From d23e4e494f7446e21184bf58acd17a861ae73bba Mon Sep 17 00:00:00 2001 From: Delta 4 <156182500+delta4chat@users.noreply.github.com> Date: Tue, 12 Nov 2024 19:54:41 +0800 Subject: [PATCH 08/42] CI: add test for netbsd --- .github/workflows/rust.yml | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index cecfae37a0..e9af883275 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -26,11 +26,40 @@ jobs: pkg install -y curl curl https://sh.rustup.rs -sSf --output rustup.sh sh rustup.sh -y --profile minimal --default-toolchain stable - echo "~~~~ rustc --version ~~~~" - $HOME/.cargo/bin/rustc --version - echo "~~~~ freebsd-version ~~~~" + run: | + export PATH="$HOME/.cargo/bin:$PATH" + echo "===== rustc --version =====" + rustc --version + echo "===== freebsd-version =====" freebsd-version - run: $HOME/.cargo/bin/cargo build --all-targets && $HOME/.cargo/bin/cargo test && $HOME/.cargo/bin/cargo test --manifest-path fuzz/Cargo.toml && $HOME/.cargo/bin/cargo test -p quinn-udp --benches + + cargo build --all-targets && cargo test && cargo test --manifest-path fuzz/Cargo.toml && cargo test -p quinn-udp --benches + + test-netbsd: + name: test on netbsd + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: test on netbsd + uses: vmactions/netbsd-vm@v1 + with: + usesh: true + mem: 4096 + copyback: false + prepare: | + export PATH="/usr/sbin:/sbin:$PATH" + pkg_add curl + curl https://sh.rustup.rs -sSf --output rustup.sh + sh rustup.sh -y --profile minimal --default-toolchain stable + run: | + export PATH="$HOME/.cargo/bin:$PATH" + echo "===== rustc --version =====" + rustc --version + echo "===== uname -a =====" + uname -a + + cargo build --all-targets && cargo test && cargo test --manifest-path fuzz/Cargo.toml && cargo test -p quinn-udp --benches + test: strategy: matrix: From a9cee2102ea5bfb5b2c50c35304333fb9fe4a3bf Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 13 Nov 2024 17:02:29 +0100 Subject: [PATCH 09/42] Move docs.rs metadata to the bottom --- quinn-proto/Cargo.toml | 6 +++--- quinn-udp/Cargo.toml | 6 +++--- quinn/Cargo.toml | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/quinn-proto/Cargo.toml b/quinn-proto/Cargo.toml index 617c41081c..d2224cdda4 100644 --- a/quinn-proto/Cargo.toml +++ b/quinn-proto/Cargo.toml @@ -10,9 +10,6 @@ keywords.workspace = true categories.workspace = true workspace = ".." -[package.metadata.docs.rs] -all-features = true - [features] default = ["rustls-ring", "log"] aws-lc-rs = ["dep:aws-lc-rs", "aws-lc-rs?/aws-lc-sys", "aws-lc-rs?/prebuilt-nasm"] @@ -66,3 +63,6 @@ wasm-bindgen-test = { workspace = true } [lints.rust] # https://rust-fuzz.github.io/book/cargo-fuzz/guide.html#cfgfuzzing unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } + +[package.metadata.docs.rs] +all-features = true diff --git a/quinn-udp/Cargo.toml b/quinn-udp/Cargo.toml index 795de3ef72..8d5f056ce7 100644 --- a/quinn-udp/Cargo.toml +++ b/quinn-udp/Cargo.toml @@ -10,9 +10,6 @@ keywords.workspace = true categories.workspace = true workspace = ".." -[package.metadata.docs.rs] -all-features = true - [features] default = ["tracing", "log"] # Configure `tracing` to log events via `log` if no `tracing` subscriber exists. @@ -45,3 +42,6 @@ bench = false [[bench]] name = "throughput" harness = false + +[package.metadata.docs.rs] +all-features = true diff --git a/quinn/Cargo.toml b/quinn/Cargo.toml index 4b020a34ee..33879db03c 100644 --- a/quinn/Cargo.toml +++ b/quinn/Cargo.toml @@ -11,9 +11,6 @@ workspace = ".." edition.workspace = true rust-version.workspace = true -[package.metadata.docs.rs] -all-features = true - [features] default = ["log", "platform-verifier", "runtime-tokio", "rustls-ring"] # Enables `Endpoint::client` and `Endpoint::server` conveniences @@ -96,3 +93,6 @@ required-features = ["rustls-ring"] name = "bench" harness = false required-features = ["rustls-ring"] + +[package.metadata.docs.rs] +all-features = true From 37355ec5e7da09435e99d4a35df7ffd70d410061 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 13 Nov 2024 17:05:48 +0100 Subject: [PATCH 10/42] quinn: avoid FIPS in docs.rs builds --- quinn-proto/Cargo.toml | 3 ++- quinn/Cargo.toml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/quinn-proto/Cargo.toml b/quinn-proto/Cargo.toml index d2224cdda4..902b4a8467 100644 --- a/quinn-proto/Cargo.toml +++ b/quinn-proto/Cargo.toml @@ -65,4 +65,5 @@ wasm-bindgen-test = { workspace = true } unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } [package.metadata.docs.rs] -all-features = true +# all non-default features except fips (cannot build on docs.rs environment) +features = ["rustls-aws-lc-rs", "rustls-ring", "platform-verifier", "log", "rustls-log"] diff --git a/quinn/Cargo.toml b/quinn/Cargo.toml index 33879db03c..7dfc4b033f 100644 --- a/quinn/Cargo.toml +++ b/quinn/Cargo.toml @@ -95,4 +95,5 @@ harness = false required-features = ["rustls-ring"] [package.metadata.docs.rs] -all-features = true +# all non-default features except fips (cannot build on docs.rs environment) +features = ["lock_tracking", "rustls-aws-lc-rs", "rustls-ring", "runtime-tokio", "runtime-async-std", "runtime-smol", "log", "rustls-log"] From 7fa7dc220d90fa50ee0e73ae8971c99a7a3b95a4 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 13 Nov 2024 17:06:40 +0100 Subject: [PATCH 11/42] Bump versions to prepare release --- quinn-proto/Cargo.toml | 2 +- quinn/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/quinn-proto/Cargo.toml b/quinn-proto/Cargo.toml index 902b4a8467..8a91c14dff 100644 --- a/quinn-proto/Cargo.toml +++ b/quinn-proto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "quinn-proto" -version = "0.11.9" +version = "0.11.10" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/quinn/Cargo.toml b/quinn/Cargo.toml index 7dfc4b033f..a061520d7c 100644 --- a/quinn/Cargo.toml +++ b/quinn/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "quinn" -version = "0.11.6" +version = "0.11.7" license.workspace = true repository.workspace = true description = "Versatile QUIC transport protocol implementation" From a55c1141e96809a94fdafc131d51642c5444ed30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Mon, 11 Nov 2024 09:27:45 +0100 Subject: [PATCH 12/42] chore(ci): Remove workaround for broken `cc` version --- .github/workflows/rust.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index e9af883275..a2337566c9 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -121,11 +121,6 @@ jobs: - name: Install cargo binstall uses: cargo-bins/cargo-binstall@main - - # We need to downgrade cc to version 1.1.31 for ring Wasm compilation to work. - # See the upstream issue https://github.com/rust-lang/cc-rs/issues/1275 - - name: Use working `cc` version 1.1.31 - run: cargo update -p cc --precise 1.1.31 - name: build wasm32 tests (quinn-proto) run: cargo test -p quinn-proto --target wasm32-unknown-unknown --no-run From 2cd3b14216bcc77afc36f6132e694809626c4a12 Mon Sep 17 00:00:00 2001 From: Petr Sumbera Date: Wed, 13 Nov 2024 10:33:18 +0000 Subject: [PATCH 13/42] Add Solaris CI, plus disable failing Solaris tests for now (#1877) --- .github/workflows/rust.yml | 22 ++++++++++++++++++++++ quinn/src/tests.rs | 6 ++++++ 2 files changed, 28 insertions(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index a2337566c9..9da7d21432 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -60,6 +60,28 @@ jobs: cargo build --all-targets && cargo test && cargo test --manifest-path fuzz/Cargo.toml && cargo test -p quinn-udp --benches + test-solaris: + name: test on solaris + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: test on Solaris + uses: vmactions/solaris-vm@v1 + with: + release: "11.4-gcc" + usesh: true + mem: 4096 + copyback: false + prepare: | + source <(curl -s https://raw.githubusercontent.com/psumbera/solaris-rust/refs/heads/main/sh.rust-web-install) + echo "~~~~ rustc --version ~~~~" + rustc --version + echo "~~~~ Solaris-version ~~~~" + uname -a + run: | + export PATH=$HOME/.rust_solaris/bin:$PATH + cargo build --all-targets && cargo test && cargo test --manifest-path fuzz/Cargo.toml && cargo test -p quinn-udp --benches + test: strategy: matrix: diff --git a/quinn/src/tests.rs b/quinn/src/tests.rs index 021a1a650b..ea5a657130 100755 --- a/quinn/src/tests.rs +++ b/quinn/src/tests.rs @@ -387,6 +387,7 @@ async fn zero_rtt() { } #[test] +#[cfg_attr(target_os = "solaris", ignore = "Fails on Solaris")] fn echo_v6() { run_echo(EchoArgs { client_addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0), @@ -399,6 +400,7 @@ fn echo_v6() { } #[test] +#[cfg_attr(target_os = "solaris", ignore = "Sometimes hangs in poll() on Solaris")] fn echo_v4() { run_echo(EchoArgs { client_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0), @@ -411,6 +413,7 @@ fn echo_v4() { } #[test] +#[cfg_attr(target_os = "solaris", ignore = "Hangs in poll() on Solaris")] fn echo_dualstack() { run_echo(EchoArgs { client_addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0), @@ -423,6 +426,7 @@ fn echo_dualstack() { } #[test] +#[cfg_attr(target_os = "solaris", ignore = "Hangs in poll() on Solaris")] fn stress_receive_window() { run_echo(EchoArgs { client_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0), @@ -435,6 +439,7 @@ fn stress_receive_window() { } #[test] +#[cfg_attr(target_os = "solaris", ignore = "Hangs in poll() on Solaris")] fn stress_stream_receive_window() { // Note that there is no point in running this with too many streams, // since the window is only active within a stream. @@ -449,6 +454,7 @@ fn stress_stream_receive_window() { } #[test] +#[cfg_attr(target_os = "solaris", ignore = "Hangs in poll() on Solaris")] fn stress_both_windows() { run_echo(EchoArgs { client_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0), From 83e74fe3edcaaece3bde18703ded8809ebecfa01 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sat, 16 Nov 2024 13:17:24 -0800 Subject: [PATCH 14/42] Account for probe size limits before coalescing When a datagram is a loss probe, it must be at most 1200 bytes. This limit must be accounted for before we judge whether there's enough space to coalesce another packet. --- quinn-proto/src/connection/mod.rs | 26 +++++++++++++++++--- quinn-proto/src/connection/packet_builder.rs | 13 ++-------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/quinn-proto/src/connection/mod.rs b/quinn-proto/src/connection/mod.rs index 6986d63145..d6368ca1be 100644 --- a/quinn-proto/src/connection/mod.rs +++ b/quinn-proto/src/connection/mod.rs @@ -32,7 +32,7 @@ use crate::{ token::ResetToken, transport_parameters::TransportParameters, Dir, Duration, EndpointConfig, Frame, Instant, Side, StreamId, Transmit, TransportError, - TransportErrorCode, VarInt, MAX_CID_SIZE, MAX_STREAM_COUNT, MIN_INITIAL_SIZE, + TransportErrorCode, VarInt, INITIAL_MTU, MAX_CID_SIZE, MAX_STREAM_COUNT, MIN_INITIAL_SIZE, TIMER_GRANULARITY, }; @@ -692,13 +692,21 @@ impl Connection { // waste large amounts of bandwidth. The exact threshold is a bit arbitrary // and might benefit from further tuning, though there's no universally // optimal value. + // + // Additionally, if this datagram is a loss probe and `segment_size` is + // larger than `INITIAL_MTU`, then padding it to `segment_size` to continue + // the GSO batch would risk failure to recover from a reduction in path + // MTU. Loss probes are the only packets for which we might grow + // `buf_capacity` by less than `segment_size`. const MAX_PADDING: usize = 16; let packet_len_unpadded = cmp::max(builder.min_size, buf.len()) - datagram_start + builder.tag_len; - if packet_len_unpadded + MAX_PADDING < segment_size { + if packet_len_unpadded + MAX_PADDING < segment_size + || datagram_start + segment_size > buf_capacity + { trace!( - "GSO truncated by demand for {} padding bytes", + "GSO truncated by demand for {} padding bytes or loss probe", segment_size - packet_len_unpadded ); builder_storage = Some(builder); @@ -741,7 +749,17 @@ impl Connection { } // Allocate space for another datagram - buf_capacity += segment_size; + let next_datagram_size_limit = match self.spaces[space_id].loss_probes { + 0 => segment_size, + _ => { + self.spaces[space_id].loss_probes -= 1; + // Clamp the datagram to at most the minimum MTU to ensure that loss probes + // can get through and enable recovery even if the path MTU has shrank + // unexpectedly. + usize::from(INITIAL_MTU) + } + }; + buf_capacity += next_datagram_size_limit; if buf.capacity() < buf_capacity { // We reserve the maximum space for sending `max_datagrams` upfront // to avoid any reallocations if more datagrams have to be appended later on. diff --git a/quinn-proto/src/connection/packet_builder.rs b/quinn-proto/src/connection/packet_builder.rs index bc1cd7ffd3..868a8c7ca8 100644 --- a/quinn-proto/src/connection/packet_builder.rs +++ b/quinn-proto/src/connection/packet_builder.rs @@ -1,5 +1,3 @@ -use std::cmp; - use bytes::Bytes; use rand::Rng; use tracing::{trace, trace_span}; @@ -8,7 +6,7 @@ use super::{spaces::SentPacket, Connection, SentFrames}; use crate::{ frame::{self, Close}, packet::{Header, InitialHeader, LongType, PacketNumber, PartialEncode, SpaceId, FIXED_BIT}, - ConnectionId, Instant, TransportError, TransportErrorCode, INITIAL_MTU, + ConnectionId, Instant, TransportError, TransportErrorCode, }; pub(super) struct PacketBuilder { @@ -38,7 +36,7 @@ impl PacketBuilder { space_id: SpaceId, dst_cid: ConnectionId, buffer: &mut Vec, - mut buffer_capacity: usize, + buffer_capacity: usize, datagram_start: usize, ack_eliciting: bool, conn: &mut Connection, @@ -79,13 +77,6 @@ impl PacketBuilder { } let space = &mut conn.spaces[space_id]; - - if space.loss_probes != 0 { - space.loss_probes -= 1; - // Clamp the packet size to at most the minimum MTU to ensure that loss probes can get - // through and enable recovery even if the path MTU has shrank unexpectedly. - buffer_capacity = cmp::min(buffer_capacity, datagram_start + usize::from(INITIAL_MTU)); - } let exact_number = match space_id { SpaceId::Data => conn.packet_number_filter.allocate(&mut conn.rng, space), _ => space.get_tx_number(), From 5619b152bcb3f10d0eeaec75b4a80cd986a852b3 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sat, 16 Nov 2024 13:24:00 -0800 Subject: [PATCH 15/42] Fix typo --- quinn-proto/src/connection/spaces.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quinn-proto/src/connection/spaces.rs b/quinn-proto/src/connection/spaces.rs index a04fbd3d7a..a97eea24bd 100644 --- a/quinn-proto/src/connection/spaces.rs +++ b/quinn-proto/src/connection/spaces.rs @@ -104,7 +104,7 @@ impl PacketSpace { /// Queue data for a tail loss probe (or anti-amplification deadlock prevention) packet /// - /// Probes are sent similarly to normal packets when an expect ACK has not arrived. We never + /// Probes are sent similarly to normal packets when an expected ACK has not arrived. We never /// deem a packet lost until we receive an ACK that should have included it, but if a trailing /// run of packets (or their ACKs) are lost, this might not happen in a timely fashion. We send /// probe packets to force an ACK, and exempt them from congestion control to prevent a deadlock From b1bd28e9d4cd0124e3355ce106df85a74e8a0b61 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sat, 16 Nov 2024 13:59:42 -0800 Subject: [PATCH 16/42] Don't advertise fake GSO outside of apple_fast --- quinn-udp/src/unix.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/quinn-udp/src/unix.rs b/quinn-udp/src/unix.rs index b8ff6e5929..0432134a08 100644 --- a/quinn-udp/src/unix.rs +++ b/quinn-udp/src/unix.rs @@ -804,7 +804,14 @@ mod gso { use super::*; pub(super) fn max_gso_segments() -> usize { - BATCH_SIZE + #[cfg(apple_fast)] + { + BATCH_SIZE + } + #[cfg(not(apple_fast))] + { + 1 + } } pub(super) fn set_segment_size( From 46acdea438ad6f32f965cb84a93aa7f49f1d0e06 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sat, 16 Nov 2024 14:00:47 -0800 Subject: [PATCH 17/42] Enable GSO on Android --- quinn-udp/src/unix.rs | 16 +++++++++++----- quinn-udp/tests/tests.rs | 5 ++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/quinn-udp/src/unix.rs b/quinn-udp/src/unix.rs index 0432134a08..15ce2a4b32 100644 --- a/quinn-udp/src/unix.rs +++ b/quinn-udp/src/unix.rs @@ -318,7 +318,7 @@ fn send( // Some network adapters and drivers do not support GSO. Unfortunately, Linux // offers no easy way for us to detect this short of an EIO or sometimes EINVAL // when we try to actually send datagrams using it. - #[cfg(target_os = "linux")] + #[cfg(any(target_os = "linux", target_os = "android"))] if let Some(libc::EIO) | Some(libc::EINVAL) = e.raw_os_error() { // Prevent new transmits from being scheduled using GSO. Existing GSO transmits // may already be in the pipeline, so we need to tolerate additional failures. @@ -767,10 +767,16 @@ pub(crate) const BATCH_SIZE: usize = 32; #[cfg(apple_slow)] pub(crate) const BATCH_SIZE: usize = 1; -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "android"))] mod gso { use super::*; + #[cfg(not(target_os = "android"))] + const UDP_SEGMENT: libc::c_int = libc::UDP_SEGMENT; + #[cfg(target_os = "android")] + // TODO: Add this to libc + const UDP_SEGMENT: libc::c_int = 103; + /// Checks whether GSO support is available by setting the UDP_SEGMENT /// option on a socket pub(crate) fn max_gso_segments() -> usize { @@ -785,21 +791,21 @@ mod gso { // As defined in linux/udp.h // #define UDP_MAX_SEGMENTS (1 << 6UL) - match set_socket_option(&socket, libc::SOL_UDP, libc::UDP_SEGMENT, GSO_SIZE) { + match set_socket_option(&socket, libc::SOL_UDP, UDP_SEGMENT, GSO_SIZE) { Ok(()) => 64, Err(_) => 1, } } pub(crate) fn set_segment_size(encoder: &mut cmsg::Encoder, segment_size: u16) { - encoder.push(libc::SOL_UDP, libc::UDP_SEGMENT, segment_size); + encoder.push(libc::SOL_UDP, UDP_SEGMENT, segment_size); } } // On Apple platforms using the `sendmsg_x` call, UDP datagram segmentation is not // offloaded to the NIC or even the kernel, but instead done here in user space in // [`send`]) and then passed to the OS as individual `iovec`s (up to `BATCH_SIZE`). -#[cfg(not(target_os = "linux"))] +#[cfg(not(any(target_os = "linux", target_os = "android")))] mod gso { use super::*; diff --git a/quinn-udp/tests/tests.rs b/quinn-udp/tests/tests.rs index a0dd32fda2..53c5a1e49b 100644 --- a/quinn-udp/tests/tests.rs +++ b/quinn-udp/tests/tests.rs @@ -156,7 +156,10 @@ fn ecn_v4_mapped_v6() { } #[test] -#[cfg_attr(not(any(target_os = "linux", target_os = "windows")), ignore)] +#[cfg_attr( + not(any(target_os = "linux", target_os = "windows", target_os = "android")), + ignore +)] fn gso() { let send = UdpSocket::bind((Ipv6Addr::LOCALHOST, 0)) .or_else(|_| UdpSocket::bind((Ipv4Addr::LOCALHOST, 0))) From a694602cbd719cb1738bdaa0ca1fa72028827c44 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sat, 16 Nov 2024 21:16:44 -0800 Subject: [PATCH 18/42] Enable GRO on Android --- quinn-udp/src/unix.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/quinn-udp/src/unix.rs b/quinn-udp/src/unix.rs index 15ce2a4b32..cf3518173f 100644 --- a/quinn-udp/src/unix.rs +++ b/quinn-udp/src/unix.rs @@ -127,8 +127,7 @@ impl UdpSocketState { #[cfg(any(target_os = "linux", target_os = "android"))] { // opportunistically try to enable GRO. See gro::gro_segments(). - #[cfg(target_os = "linux")] - let _ = set_socket_option(&*io, libc::SOL_UDP, libc::UDP_GRO, OPTION_ON); + let _ = set_socket_option(&*io, libc::SOL_UDP, gro::UDP_GRO, OPTION_ON); // Forbid IPv4 fragmentation. Set even for IPv6 to account for IPv6 mapped IPv4 addresses. // Set `may_fragment` to `true` if this option is not supported on the platform. @@ -719,8 +718,8 @@ fn decode_recv( let pktinfo = unsafe { cmsg::decode::(cmsg) }; dst_ip = Some(IpAddr::V6(Ipv6Addr::from(pktinfo.ipi6_addr.s6_addr))); } - #[cfg(target_os = "linux")] - (libc::SOL_UDP, libc::UDP_GRO) => unsafe { + #[cfg(any(target_os = "linux", target_os = "android"))] + (libc::SOL_UDP, gro::UDP_GRO) => unsafe { stride = cmsg::decode::(cmsg) as usize; }, _ => {} @@ -828,10 +827,16 @@ mod gso { } } -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "android"))] mod gro { use super::*; + #[cfg(not(target_os = "android"))] + pub(crate) const UDP_GRO: libc::c_int = libc::UDP_GRO; + #[cfg(target_os = "android")] + // TODO: Add this to libc + pub(crate) const UDP_GRO: libc::c_int = 104; + pub(crate) fn gro_segments() -> usize { let socket = match std::net::UdpSocket::bind("[::]:0") .or_else(|_| std::net::UdpSocket::bind((Ipv4Addr::LOCALHOST, 0))) @@ -847,7 +852,7 @@ mod gro { // (get_max_udp_payload_size() * gro_segments()) is large enough to hold the largest GRO // list the kernel might potentially produce. See // https://github.com/quinn-rs/quinn/pull/1354. - match set_socket_option(&socket, libc::SOL_UDP, libc::UDP_GRO, OPTION_ON) { + match set_socket_option(&socket, libc::SOL_UDP, UDP_GRO, OPTION_ON) { Ok(()) => 64, Err(_) => 1, } @@ -895,7 +900,7 @@ fn set_socket_option( const OPTION_ON: libc::c_int = 1; -#[cfg(not(target_os = "linux"))] +#[cfg(not(any(target_os = "linux", target_os = "android")))] mod gro { pub(super) fn gro_segments() -> usize { 1 From 3a9d176a7a131a1f6d9472c1a23fccdcb1275b52 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 13:45:56 +0000 Subject: [PATCH 19/42] build(deps): bump codecov/codecov-action from 4 to 5 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4 to 5. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v4...v5) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 7fa7bcfdd4..83da040fd2 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -16,7 +16,7 @@ jobs: - uses: taiki-e/install-action@cargo-llvm-cov - run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} files: lcov.info From 98a1050dc61076e9084a4b05a140310db1d3c820 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 18 Nov 2024 15:16:57 +1100 Subject: [PATCH 20/42] Use `try_send` in tests Currently, the test suite uses `UdpSocketState::send`. This function never reports errors (apart from `io::ErrorKind::WouldBlock). Whilst this may make sense for production, in the tests we actually want to know whether we hit any IO errors. --- quinn-udp/tests/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quinn-udp/tests/tests.rs b/quinn-udp/tests/tests.rs index 53c5a1e49b..7169ed6ae8 100644 --- a/quinn-udp/tests/tests.rs +++ b/quinn-udp/tests/tests.rs @@ -193,7 +193,7 @@ fn test_send_recv(send: &Socket, recv: &Socket, transmit: Transmit) { // Reverse non-blocking flag set by `UdpSocketState` to make the test non-racy recv.set_nonblocking(false).unwrap(); - send_state.send(send.into(), &transmit).unwrap(); + send_state.try_send(send.into(), &transmit).unwrap(); let mut buf = [0; u16::MAX as usize]; let mut meta = RecvMeta::default(); From f8b8c5032e0db9d7dbc7c3452f09c7d1e2a4295d Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Mon, 18 Nov 2024 15:17:46 +0200 Subject: [PATCH 21/42] chore: Fix `cargo clippy` issues --- perf/src/bin/perf_client.rs | 2 +- perf/src/bin/perf_server.rs | 2 +- quinn-proto/src/connection/ack_frequency.rs | 4 +--- quinn-proto/src/connection/mod.rs | 16 ++++++---------- quinn-proto/src/connection/mtud.rs | 2 +- quinn-proto/src/connection/packet_crypto.rs | 2 +- quinn-proto/src/connection/spaces.rs | 2 +- quinn-proto/src/connection/streams/state.rs | 4 ++-- quinn-proto/src/connection/timer.rs | 2 +- quinn-proto/src/endpoint.rs | 6 +++--- quinn-proto/src/packet.rs | 2 +- quinn-proto/src/range_set/btree_range_set.rs | 2 +- quinn-proto/src/tests/util.rs | 4 ++-- quinn-proto/src/transport_parameters.rs | 7 +++---- quinn/examples/server.rs | 6 +++--- 15 files changed, 28 insertions(+), 35 deletions(-) diff --git a/perf/src/bin/perf_client.rs b/perf/src/bin/perf_client.rs index d081ae1799..a8ceeceec8 100644 --- a/perf/src/bin/perf_client.rs +++ b/perf/src/bin/perf_client.rs @@ -308,7 +308,7 @@ async fn request( let send_stream_stats = stream_stats.new_sender(&send, upload); - const DATA: [u8; 1024 * 1024] = [42; 1024 * 1024]; + static DATA: [u8; 1024 * 1024] = [42; 1024 * 1024]; while upload > 0 { let chunk_len = upload.min(DATA.len() as u64); send.write_chunk(Bytes::from_static(&DATA[..chunk_len as usize])) diff --git a/perf/src/bin/perf_server.rs b/perf/src/bin/perf_server.rs index 5d29701dbd..74b527acab 100644 --- a/perf/src/bin/perf_server.rs +++ b/perf/src/bin/perf_server.rs @@ -215,7 +215,7 @@ async fn drain_stream(mut stream: quinn::RecvStream) -> Result<()> { } async fn respond(mut bytes: u64, mut stream: quinn::SendStream) -> Result<()> { - const DATA: [u8; 1024 * 1024] = [42; 1024 * 1024]; + static DATA: [u8; 1024 * 1024] = [42; 1024 * 1024]; while bytes > 0 { let chunk_len = bytes.min(DATA.len() as u64); diff --git a/quinn-proto/src/connection/ack_frequency.rs b/quinn-proto/src/connection/ack_frequency.rs index 94b6805fbc..ab06e45ac8 100644 --- a/quinn-proto/src/connection/ack_frequency.rs +++ b/quinn-proto/src/connection/ack_frequency.rs @@ -121,9 +121,7 @@ impl AckFrequencyState { ) -> Result { if self .last_ack_frequency_frame - .map_or(false, |highest_sequence_nr| { - frame.sequence.into_inner() <= highest_sequence_nr - }) + .is_some_and(|highest_sequence_nr| frame.sequence.into_inner() <= highest_sequence_nr) { return Ok(false); } diff --git a/quinn-proto/src/connection/mod.rs b/quinn-proto/src/connection/mod.rs index d6368ca1be..babb0d7577 100644 --- a/quinn-proto/src/connection/mod.rs +++ b/quinn-proto/src/connection/mod.rs @@ -973,14 +973,10 @@ impl Connection { // Send MTU probe if necessary if buf.is_empty() && self.state.is_established() { let space_id = SpaceId::Data; - let probe_size = match self + let probe_size = self .path .mtud - .poll_transmit(now, self.packet_number_filter.peek(&self.spaces[space_id])) - { - Some(next_probe_size) => next_probe_size, - None => return None, - }; + .poll_transmit(now, self.packet_number_filter.peek(&self.spaces[space_id]))?; let buf_capacity = probe_size as usize; buf.reserve(buf_capacity); @@ -1655,7 +1651,7 @@ impl Connection { None if self .path .first_packet_after_rtt_sample - .map_or(false, |x| x < (pn_space, packet)) => + .is_some_and(|x| x < (pn_space, packet)) => { persistent_congestion_start = Some(info.time_sent); } @@ -2230,7 +2226,7 @@ impl Connection { let _guard = span.enter(); let is_duplicate = |n| self.spaces[packet.header.space()].dedup.insert(n); - if number.map_or(false, is_duplicate) { + if number.is_some_and(is_duplicate) { debug!("discarding possible duplicate packet"); return; } else if self.state.is_handshake() && packet.header.is_short() { @@ -3544,13 +3540,13 @@ impl Connection { || self .prev_path .as_ref() - .map_or(false, |(_, x)| x.challenge_pending) + .is_some_and(|(_, x)| x.challenge_pending) || !self.path_responses.is_empty() || self .datagrams .outgoing .front() - .map_or(false, |x| x.size(true) <= max_size) + .is_some_and(|x| x.size(true) <= max_size) } /// Update counters to account for a packet becoming acknowledged, lost, or abandoned diff --git a/quinn-proto/src/connection/mtud.rs b/quinn-proto/src/connection/mtud.rs index c221ab48dc..be1fe7eef1 100644 --- a/quinn-proto/src/connection/mtud.rs +++ b/quinn-proto/src/connection/mtud.rs @@ -417,7 +417,7 @@ impl BlackHoleDetector { let end_last_burst = self .current_loss_burst .as_ref() - .map_or(false, |current| pn - current.latest_non_probe != 1); + .is_some_and(|current| pn - current.latest_non_probe != 1); if end_last_burst { self.finish_loss_burst(); diff --git a/quinn-proto/src/connection/packet_crypto.rs b/quinn-proto/src/connection/packet_crypto.rs index ffb98ea8ad..0d3063aa91 100644 --- a/quinn-proto/src/connection/packet_crypto.rs +++ b/quinn-proto/src/connection/packet_crypto.rs @@ -131,7 +131,7 @@ pub(super) fn decrypt_packet_body( if crypto_update { // Validate incoming key update - if number <= rx_packet || prev_crypto.map_or(false, |x| x.update_unacked) { + if number <= rx_packet || prev_crypto.is_some_and(|x| x.update_unacked) { return Err(Some(TransportError::KEY_UPDATE_ERROR(""))); } } diff --git a/quinn-proto/src/connection/spaces.rs b/quinn-proto/src/connection/spaces.rs index a97eea24bd..ed58b51c1e 100644 --- a/quinn-proto/src/connection/spaces.rs +++ b/quinn-proto/src/connection/spaces.rs @@ -853,7 +853,7 @@ impl PacketNumberFilter { if space_id == SpaceId::Data && self .prev_skipped_packet_number - .map_or(false, |x| range.contains(&x)) + .is_some_and(|x| range.contains(&x)) { return Err(TransportError::PROTOCOL_VIOLATION("unsent packet acked")); } diff --git a/quinn-proto/src/connection/streams/state.rs b/quinn-proto/src/connection/streams/state.rs index a3f14b3b5f..90b853b7be 100644 --- a/quinn-proto/src/connection/streams/state.rs +++ b/quinn-proto/src/connection/streams/state.rs @@ -396,7 +396,7 @@ impl StreamsState { self.send .get(&stream.id) .and_then(|s| s.as_ref()) - .map_or(false, |s| !s.is_reset()) + .is_some_and(|s| !s.is_reset()) }) } @@ -406,7 +406,7 @@ impl StreamsState { .get(&id) .and_then(|s| s.as_ref()) .and_then(|s| s.as_open_recv()) - .map_or(false, |s| s.can_send_flow_control()) + .is_some_and(|s| s.can_send_flow_control()) } pub(in crate::connection) fn write_control_frames( diff --git a/quinn-proto/src/connection/timer.rs b/quinn-proto/src/connection/timer.rs index 1bf67e0232..566652d0da 100644 --- a/quinn-proto/src/connection/timer.rs +++ b/quinn-proto/src/connection/timer.rs @@ -60,6 +60,6 @@ impl TimerTable { } pub(super) fn is_expired(&self, timer: Timer, after: Instant) -> bool { - self.data[timer as usize].map_or(false, |x| x <= after) + self.data[timer as usize].is_some_and(|x| x <= after) } } diff --git a/quinn-proto/src/endpoint.rs b/quinn-proto/src/endpoint.rs index 6210aafe91..09819833c9 100644 --- a/quinn-proto/src/endpoint.rs +++ b/quinn-proto/src/endpoint.rs @@ -215,11 +215,11 @@ impl Endpoint { if incoming_buffer .total_bytes .checked_add(datagram_len as u64) - .map_or(false, |n| n <= config.incoming_buffer_size) + .is_some_and(|n| n <= config.incoming_buffer_size) && self .all_incoming_buffers_total_bytes .checked_add(datagram_len as u64) - .map_or(false, |n| n <= config.incoming_buffer_size_total) + .is_some_and(|n| n <= config.incoming_buffer_size_total) { incoming_buffer.datagrams.push(event); incoming_buffer.total_bytes += datagram_len as u64; @@ -334,7 +334,7 @@ impl Endpoint { ) -> Option { if self .last_stateless_reset - .map_or(false, |last| last + self.config.min_reset_interval > now) + .is_some_and(|last| last + self.config.min_reset_interval > now) { debug!("ignoring unexpected packet within minimum stateless reset interval"); return None; diff --git a/quinn-proto/src/packet.rs b/quinn-proto/src/packet.rs index fba0b40db2..1970b4679e 100644 --- a/quinn-proto/src/packet.rs +++ b/quinn-proto/src/packet.rs @@ -769,7 +769,7 @@ impl PacketNumber { // The following code calculates a candidate value and makes sure it's within the packet // number window. let candidate = (expected & !mask) | truncated; - if expected.checked_sub(hwin).map_or(false, |x| candidate <= x) { + if expected.checked_sub(hwin).is_some_and(|x| candidate <= x) { candidate + win } else if candidate > expected + hwin && candidate > win { candidate - win diff --git a/quinn-proto/src/range_set/btree_range_set.rs b/quinn-proto/src/range_set/btree_range_set.rs index 85a2c538ae..d8eb11de30 100644 --- a/quinn-proto/src/range_set/btree_range_set.rs +++ b/quinn-proto/src/range_set/btree_range_set.rs @@ -18,7 +18,7 @@ impl RangeSet { } pub fn contains(&self, x: u64) -> bool { - self.pred(x).map_or(false, |(_, end)| end > x) + self.pred(x).is_some_and(|(_, end)| end > x) } pub fn insert_one(&mut self, x: u64) -> bool { diff --git a/quinn-proto/src/tests/util.rs b/quinn-proto/src/tests/util.rs index 8cb47d30fe..7e927e2035 100644 --- a/quinn-proto/src/tests/util.rs +++ b/quinn-proto/src/tests/util.rs @@ -356,7 +356,7 @@ impl TestEndpoint { let buffer_size = self.endpoint.config().get_max_udp_payload_size() as usize; let mut buf = Vec::with_capacity(buffer_size); - while self.inbound.front().map_or(false, |x| x.0 <= now) { + while self.inbound.front().is_some_and(|x| x.0 <= now) { let (recv_time, ecn, packet) = self.inbound.pop_front().unwrap(); if let Some(event) = self .endpoint @@ -415,7 +415,7 @@ impl TestEndpoint { loop { let mut endpoint_events: Vec<(ConnectionHandle, EndpointEvent)> = vec![]; for (ch, conn) in self.connections.iter_mut() { - if self.timeout.map_or(false, |x| x <= now) { + if self.timeout.is_some_and(|x| x <= now) { self.timeout = None; conn.handle_timeout(now); } diff --git a/quinn-proto/src/transport_parameters.rs b/quinn-proto/src/transport_parameters.rs index c09c41f971..aa279d48f1 100644 --- a/quinn-proto/src/transport_parameters.rs +++ b/quinn-proto/src/transport_parameters.rs @@ -146,7 +146,7 @@ impl TransportParameters { initial_max_stream_data_uni: config.stream_receive_window, max_udp_payload_size: endpoint_config.max_udp_payload_size, max_idle_timeout: config.max_idle_timeout.unwrap_or(VarInt(0)), - disable_active_migration: server_config.map_or(false, |c| !c.migration), + disable_active_migration: server_config.is_some_and(|c| !c.migration), active_connection_id_limit: if cid_gen.cid_len() == 0 { 2 // i.e. default, i.e. unsent } else { @@ -446,7 +446,7 @@ impl TransportParameters { || params.initial_max_streams_bidi.0 > MAX_STREAM_COUNT || params.initial_max_streams_uni.0 > MAX_STREAM_COUNT // https://www.ietf.org/archive/id/draft-ietf-quic-ack-frequency-08.html#section-3-4 - || params.min_ack_delay.map_or(false, |min_ack_delay| { + || params.min_ack_delay.is_some_and(|min_ack_delay| { // min_ack_delay uses microseconds, whereas max_ack_delay uses milliseconds min_ack_delay.0 > params.max_ack_delay.0 * 1_000 }) @@ -458,8 +458,7 @@ impl TransportParameters { || params.stateless_reset_token.is_some())) // https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2-4.38.1 || params - .preferred_address - .map_or(false, |x| x.connection_id.is_empty()) + .preferred_address.is_some_and(|x| x.connection_id.is_empty()) { return Err(Error::IllegalValue); } diff --git a/quinn/examples/server.rs b/quinn/examples/server.rs index 5f9e6f35df..b6f63160e6 100644 --- a/quinn/examples/server.rs +++ b/quinn/examples/server.rs @@ -70,7 +70,7 @@ fn main() { async fn run(options: Opt) -> Result<()> { let (certs, key) = if let (Some(key_path), Some(cert_path)) = (&options.key, &options.cert) { let key = fs::read(key_path).context("failed to read private key")?; - let key = if key_path.extension().map_or(false, |x| x == "der") { + let key = if key_path.extension().is_some_and(|x| x == "der") { PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(key)) } else { rustls_pemfile::private_key(&mut &*key) @@ -78,7 +78,7 @@ async fn run(options: Opt) -> Result<()> { .ok_or_else(|| anyhow::Error::msg("no private keys found"))? }; let cert_chain = fs::read(cert_path).context("failed to read certificate chain")?; - let cert_chain = if cert_path.extension().map_or(false, |x| x == "der") { + let cert_chain = if cert_path.extension().is_some_and(|x| x == "der") { vec![CertificateDer::from(cert_chain)] } else { rustls_pemfile::certs(&mut &*cert_chain) @@ -140,7 +140,7 @@ async fn run(options: Opt) -> Result<()> { while let Some(conn) = endpoint.accept().await { if options .connection_limit - .map_or(false, |n| endpoint.open_connections() >= n) + .is_some_and(|n| endpoint.open_connections() >= n) { info!("refusing due to open connection limit"); conn.refuse(); From a16dcd27de41cac7df56dffdf9a56b8ccdc4d660 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 18 Nov 2024 15:06:19 +1100 Subject: [PATCH 22/42] Log GSO halt on info It is common to set up automated error reporting based on `ERROR` and potentially also `WARN` logs. Whilst GSO being unsupported is certainly something worthwhile logging, the `ERROR` log level seems a bit excessive and leads to unactionable errors reports. The system can still operate with `max_gso_segments == 1`. As such, this codepath "merely" indicates a state change in the system but not a fatal error. As such, logging this on INFO level seems more appropriate. --- quinn-udp/src/unix.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/quinn-udp/src/unix.rs b/quinn-udp/src/unix.rs index cf3518173f..4978fa4142 100644 --- a/quinn-udp/src/unix.rs +++ b/quinn-udp/src/unix.rs @@ -322,7 +322,9 @@ fn send( // Prevent new transmits from being scheduled using GSO. Existing GSO transmits // may already be in the pipeline, so we need to tolerate additional failures. if state.max_gso_segments() > 1 { - crate::log::error!("got transmit error, halting segmentation offload"); + crate::log::info!( + "`libc::sendmsg` failed with {e}; halting segmentation offload" + ); state .max_gso_segments .store(1, std::sync::atomic::Ordering::Relaxed); From 95a63d7cd214e8b3eb1a4d6fac59ad1261adeeff Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 18 Nov 2024 15:07:26 +1100 Subject: [PATCH 23/42] Don't log and return Logging AND returning errors is considered an anti-pattern because it leads to duplicate logs. --- quinn-udp/src/windows.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/quinn-udp/src/windows.rs b/quinn-udp/src/windows.rs index 886f455117..223ab0fdf6 100644 --- a/quinn-udp/src/windows.rs +++ b/quinn-udp/src/windows.rs @@ -14,7 +14,7 @@ use windows_sys::Win32::Networking::WinSock; use crate::{ cmsg::{self, CMsgHdr}, - log::{debug, error}, + log::debug, log_sendmsg_error, EcnCodepoint, RecvMeta, Transmit, UdpSockRef, IO_ERROR_LOG_INTERVAL, }; @@ -61,9 +61,10 @@ impl UdpSocketState { // We don't support old versions of Windows that do not enable access to `WSARecvMsg()` if WSARECVMSG_PTR.is_none() { - error!("network stack does not support WSARecvMsg function"); - - return Err(io::Error::from(io::ErrorKind::Unsupported)); + return Err(io::Error::new( + io::ErrorKind::Unsupported, + "network stack does not support WSARecvMsg function", + )); } if is_ipv4 { From c613edf3a31f7f1c28f16175ccc7061eb6a3991e Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 18 Nov 2024 16:13:03 +1100 Subject: [PATCH 24/42] Log failure to set `UDP_SEGMENT` option --- quinn-udp/src/unix.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/quinn-udp/src/unix.rs b/quinn-udp/src/unix.rs index 4978fa4142..02acdf0e0a 100644 --- a/quinn-udp/src/unix.rs +++ b/quinn-udp/src/unix.rs @@ -794,7 +794,13 @@ mod gso { // #define UDP_MAX_SEGMENTS (1 << 6UL) match set_socket_option(&socket, libc::SOL_UDP, UDP_SEGMENT, GSO_SIZE) { Ok(()) => 64, - Err(_) => 1, + Err(_e) => { + crate::log::debug!( + "failed to set `UDP_SEGMENT` socket option ({_e}); setting `max_gso_segments = 1`" + ); + + 1 + } } } From 9386cde871c750464073772409615e90344b80e9 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 18 Nov 2024 16:15:36 +1100 Subject: [PATCH 25/42] Only set `segment_size` if it is different from the transmit length --- quinn-udp/src/unix.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/quinn-udp/src/unix.rs b/quinn-udp/src/unix.rs index 02acdf0e0a..38b322e244 100644 --- a/quinn-udp/src/unix.rs +++ b/quinn-udp/src/unix.rs @@ -593,7 +593,12 @@ fn prepare_msg( encoder.push(libc::IPPROTO_IPV6, libc::IPV6_TCLASS, ecn); } - if let Some(segment_size) = transmit.segment_size { + // Only set the segment size if it is different from the size of the contents. + // Some network drivers don't like being told to do GSO even if there is effectively only a single segment. + if let Some(segment_size) = transmit + .segment_size + .filter(|segment_size| *segment_size != transmit.contents.len()) + { gso::set_segment_size(&mut encoder, segment_size as u16); } From b44294e4118a54d1033ff59a7675cf5191f36420 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 20 Nov 2024 12:57:47 +0100 Subject: [PATCH 26/42] Allow Unicode 3.0 license --- deny.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deny.toml b/deny.toml index 8998d62442..1fb8356f20 100644 --- a/deny.toml +++ b/deny.toml @@ -7,7 +7,7 @@ allow = [ "MIT", "MPL-2.0", "OpenSSL", - "Unicode-DFS-2016", + "Unicode-3.0", ] private = { ignore = true } From ff4663b69671cd8c68ba3487bb7d5f2884489f3f Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Tue, 19 Nov 2024 19:31:50 -0800 Subject: [PATCH 27/42] Preserve the time a new incoming packet was received at --- quinn-proto/src/endpoint.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/quinn-proto/src/endpoint.rs b/quinn-proto/src/endpoint.rs index 09819833c9..7cf0873933 100644 --- a/quinn-proto/src/endpoint.rs +++ b/quinn-proto/src/endpoint.rs @@ -284,7 +284,7 @@ impl Endpoint { return match first_decode.finish(Some(&*crypto.header.remote)) { Ok(packet) => { - self.handle_first_packet(addresses, ecn, packet, remaining, crypto, buf) + self.handle_first_packet(addresses, ecn, packet, remaining, crypto, buf, now) } Err(e) => { trace!("unable to decode initial packet: {}", e); @@ -482,6 +482,7 @@ impl Endpoint { rest: Option, crypto: Keys, buf: &mut Vec, + now: Instant, ) -> Option { if !packet.reserved_bits_valid() { debug!("dropping connection attempt with invalid reserved bits"); @@ -533,6 +534,7 @@ impl Endpoint { .insert_initial_incoming(header.dst_cid, incoming_idx); Some(DatagramEvent::NewConnection(Incoming { + received_at: now, addresses, ecn, packet: InitialPacket { @@ -644,7 +646,7 @@ impl Endpoint { src_cid, pref_addr_cid, incoming.addresses, - now, + incoming.received_at, tls, Some(server_config), transport_config, @@ -653,7 +655,7 @@ impl Endpoint { self.index.insert_initial(dst_cid, ch); match conn.handle_first_packet( - now, + incoming.received_at, incoming.addresses.remote, incoming.ecn, packet_number, @@ -1178,6 +1180,7 @@ pub enum DatagramEvent { /// An incoming connection for which the server has not yet begun its part of the handshake. pub struct Incoming { + received_at: Instant, addresses: FourTuple, ecn: Option, packet: InitialPacket, From df7f58e19b433f59e7a184e0cccc99be94fc230b Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Tue, 19 Nov 2024 19:33:56 -0800 Subject: [PATCH 28/42] Fail accept immediately on stale Incoming --- quinn-proto/src/endpoint.rs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/quinn-proto/src/endpoint.rs b/quinn-proto/src/endpoint.rs index 7cf0873933..fc652a8bc1 100644 --- a/quinn-proto/src/endpoint.rs +++ b/quinn-proto/src/endpoint.rs @@ -31,8 +31,8 @@ use crate::{ }, token::TokenDecodeError, transport_parameters::{PreferredAddress, TransportParameters}, - Instant, ResetToken, RetryToken, Side, SystemTime, Transmit, TransportConfig, TransportError, - INITIAL_MTU, MAX_CID_SIZE, MIN_INITIAL_SIZE, RESET_TOKEN_SIZE, + Duration, Instant, ResetToken, RetryToken, Side, SystemTime, Transmit, TransportConfig, + TransportError, INITIAL_MTU, MAX_CID_SIZE, MIN_INITIAL_SIZE, RESET_TOKEN_SIZE, }; /// The main entry point to the library @@ -571,6 +571,23 @@ impl Endpoint { version, .. } = incoming.packet.header; + let server_config = + server_config.unwrap_or_else(|| self.server_config.as_ref().unwrap().clone()); + + if server_config + .transport + .max_idle_timeout + .is_some_and(|timeout| { + incoming.received_at + Duration::from_millis(timeout.into()) <= now + }) + { + debug!("abandoning accept of stale initial"); + self.index.remove_initial(dst_cid); + return Err(AcceptError { + cause: ConnectionError::TimedOut, + response: None, + }); + } if self.cids_exhausted() { debug!("refusing connection"); @@ -588,9 +605,6 @@ impl Endpoint { }); } - let server_config = - server_config.unwrap_or_else(|| self.server_config.as_ref().unwrap().clone()); - if incoming .crypto .packet From a4c886c38a6e78916f683c01043b37b6d3a597cf Mon Sep 17 00:00:00 2001 From: Phoenix Kahlo Date: Wed, 20 Nov 2024 19:14:18 -0600 Subject: [PATCH 29/42] docs: Correct MSRV in README --- .github/workflows/rust.yml | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9da7d21432..edc0ce4865 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -163,6 +163,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + # Note that we must also update the README when changing the MSRV - uses: dtolnay/rust-toolchain@1.70.0 - uses: Swatinem/rust-cache@v2 - run: cargo check --lib --all-features -p quinn-udp -p quinn-proto -p quinn diff --git a/README.md b/README.md index b82f9c4fd4..d93c872838 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The project was founded by [Dirkjan Ochtman](https://github.com/djc) and [rustls][rustls] and [*ring*][ring] - Application-layer datagrams for small, unreliable messages - Future-based async API -- Minimum supported Rust version of 1.66 +- Minimum supported Rust version of 1.70 ## Overview From 2edf192511873a52093dd57b9e70eb4b27c442cd Mon Sep 17 00:00:00 2001 From: Yury Yarashevich Date: Wed, 20 Nov 2024 12:01:40 +0100 Subject: [PATCH 30/42] #2057: Use randomly generated GREASE transport parameter. --- quinn-proto/src/endpoint.rs | 2 + quinn-proto/src/transport_parameters.rs | 135 +++++++++++++++++++++++- 2 files changed, 134 insertions(+), 3 deletions(-) diff --git a/quinn-proto/src/endpoint.rs b/quinn-proto/src/endpoint.rs index fc652a8bc1..bd18b011aa 100644 --- a/quinn-proto/src/endpoint.rs +++ b/quinn-proto/src/endpoint.rs @@ -411,6 +411,7 @@ impl Endpoint { self.local_cid_generator.as_ref(), loc_cid, None, + &mut self.rng, ); let tls = config .crypto @@ -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); diff --git a/quinn-proto/src/transport_parameters.rs b/quinn-proto/src/transport_parameters.rs index aa279d48f1..381968059c 100644 --- a/quinn-proto/src/transport_parameters.rs +++ b/quinn-proto/src/transport_parameters.rs @@ -12,6 +12,7 @@ use std::{ }; use bytes::{Buf, BufMut}; +use rand::{Rng as _, RngCore}; use thiserror::Error; use crate::{ @@ -99,6 +100,10 @@ macro_rules! make_struct { pub(crate) stateless_reset_token: Option, /// The server's preferred address for communication after handshake completion pub(crate) preferred_address: Option, + /// The randomly generated reserved transport parameter to sustain future extensibility + /// of transport parameter extensions. + /// When present, it is included during serialization but ignored during deserialization. + pub(crate) grease_transport_parameter: Option, } // We deliberately don't implement the `Default` trait, since that would be public, and @@ -120,6 +125,7 @@ macro_rules! make_struct { retry_src_cid: None, stateless_reset_token: None, preferred_address: None, + grease_transport_parameter: None, } } } @@ -135,6 +141,7 @@ impl TransportParameters { cid_gen: &dyn ConnectionIdGenerator, initial_src_cid: ConnectionId, server_config: Option<&ServerConfig>, + rng: &mut impl RngCore, ) -> Self { Self { initial_src_cid: Some(initial_src_cid), @@ -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() } } @@ -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); @@ -467,6 +475,81 @@ impl TransportParameters { } } +/// A reserved transport parameter. +/// +/// It has an identifier of the form 31 * N + 27 for the integer value of N. +/// Such identifiers are reserved to exercise the requirement that unknown transport parameters be ignored. +/// The reserved transport parameter has no semantics and can carry arbitrary values. +/// It may be included in transport parameters sent to the peer, and should be ignored when received. +/// +/// See spec: +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(crate) struct ReservedTransportParameter { + /// The reserved identifier of the transport parameter + id: VarInt, + + /// Buffer to store the parameter payload + payload: [u8; Self::MAX_PAYLOAD_LEN], + + /// The number of bytes to include in the wire format from the `payload` buffer + payload_len: usize, +} + +impl ReservedTransportParameter { + /// Generates a transport parameter with a random payload and a reserved ID. + /// + /// The implementation is inspired by quic-go and quiche: + /// 1. + /// 2. + fn random(rng: &mut impl RngCore) -> 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(&self, w: &mut impl BufMut) { + w.write_var(self.id.0); + w.write_var(self.payload_len as u64); + w.put_slice(&self.payload[..self.payload_len]); + } + + /// Generates 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 must be ignored by peers. + /// See: and + fn generate_reserved_id(rng: &mut impl RngCore) -> 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)", + ) + } + + /// The maximum length of the payload to include as the parameter payload. + /// This value is not a specification-imposed limit but is chosen to match + /// the limit used by other implementations of QUIC, e.g., quic-go and quiche. + const MAX_PAYLOAD_LEN: usize = 16; +} + fn decode_cid(len: usize, value: &mut Option, r: &mut impl Buf) -> Result<(), Error> { if len > MAX_CID_SIZE || value.is_some() || r.remaining() < len { return Err(Error::Malformed); @@ -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)] From 81f4d6b0361cd6d11a3f6af73b41882920c190fd Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 25 Nov 2024 10:48:41 +0100 Subject: [PATCH 31/42] Bump MSRV to 1.71 --- .github/workflows/rust.yml | 3 +-- Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index edc0ce4865..7a3b22cd62 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -164,7 +164,7 @@ jobs: steps: - uses: actions/checkout@v4 # Note that we must also update the README when changing the MSRV - - uses: dtolnay/rust-toolchain@1.70.0 + - uses: dtolnay/rust-toolchain@1.71.0 - uses: Swatinem/rust-cache@v2 - run: cargo check --lib --all-features -p quinn-udp -p quinn-proto -p quinn @@ -271,4 +271,3 @@ jobs: - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@cargo-hack - run: cargo hack check --feature-powerset --optional-deps --no-dev-deps --ignore-unknown-features --ignore-private --group-features runtime-async-std,async-io,async-std --group-features runtime-smol,async-io,smol --skip "${{env.SKIP_FEATURES}}" - diff --git a/Cargo.toml b/Cargo.toml index e8e3f54127..b78d81eaa4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ default-members = ["quinn", "quinn-proto", "quinn-udp", "bench", "perf"] resolver = "2" [workspace.package] -rust-version = "1.70.0" +rust-version = "1.71" edition = "2021" license = "MIT OR Apache-2.0" repository = "https://github.com/quinn-rs/quinn" diff --git a/README.md b/README.md index d93c872838..23af951dfd 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The project was founded by [Dirkjan Ochtman](https://github.com/djc) and [rustls][rustls] and [*ring*][ring] - Application-layer datagrams for small, unreliable messages - Future-based async API -- Minimum supported Rust version of 1.70 +- Minimum supported Rust version of 1.71 ## Overview From e20d4caca4152e24f6fbca287f3bc161cdc4b26e Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 25 Nov 2024 10:46:43 +0100 Subject: [PATCH 32/42] Add CODEOWNERS --- .github/CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..754b6880db --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +* @djc @Ralith +/quinn-udp @djc @Ralith @mxinden From 1c463ab5b46d549c4e2b76fbaad9ddf50bac46bc Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 25 Nov 2024 10:44:35 +0100 Subject: [PATCH 33/42] proto: split config module --- quinn-proto/src/config/mod.rs | 501 ++++++++++++++++++ .../src/{config.rs => config/transport.rs} | 500 +---------------- 2 files changed, 503 insertions(+), 498 deletions(-) create mode 100644 quinn-proto/src/config/mod.rs rename quinn-proto/src/{config.rs => config/transport.rs} (59%) diff --git a/quinn-proto/src/config/mod.rs b/quinn-proto/src/config/mod.rs new file mode 100644 index 0000000000..3ec56f5715 --- /dev/null +++ b/quinn-proto/src/config/mod.rs @@ -0,0 +1,501 @@ +use std::{ + fmt, + net::{SocketAddrV4, SocketAddrV6}, + num::TryFromIntError, + sync::Arc, +}; + +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +use rustls::client::WebPkiServerVerifier; +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +use rustls::pki_types::{CertificateDer, PrivateKeyDer}; +use thiserror::Error; + +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +use crate::crypto::rustls::{configured_provider, QuicServerConfig}; +use crate::{ + cid_generator::{ConnectionIdGenerator, HashedConnectionIdGenerator}, + crypto::{self, HandshakeTokenKey, HmacKey}, + shared::ConnectionId, + Duration, RandomConnectionIdGenerator, VarInt, VarIntBoundsExceeded, + DEFAULT_SUPPORTED_VERSIONS, MAX_CID_SIZE, +}; + +mod transport; +pub use transport::{AckFrequencyConfig, IdleTimeout, MtuDiscoveryConfig, TransportConfig}; + +/// Global configuration for the endpoint, affecting all connections +/// +/// Default values should be suitable for most internet applications. +#[derive(Clone)] +pub struct EndpointConfig { + pub(crate) reset_key: Arc, + pub(crate) max_udp_payload_size: VarInt, + /// CID generator factory + /// + /// Create a cid generator for local cid in Endpoint struct + pub(crate) connection_id_generator_factory: + Arc Box + Send + Sync>, + pub(crate) supported_versions: Vec, + pub(crate) grease_quic_bit: bool, + /// Minimum interval between outgoing stateless reset packets + pub(crate) min_reset_interval: Duration, + /// Optional seed to be used internally for random number generation + pub(crate) rng_seed: Option<[u8; 32]>, +} + +impl EndpointConfig { + /// Create a default config with a particular `reset_key` + pub fn new(reset_key: Arc) -> Self { + let cid_factory = + || -> Box { Box::::default() }; + Self { + reset_key, + max_udp_payload_size: (1500u32 - 28).into(), // Ethernet MTU minus IP + UDP headers + connection_id_generator_factory: Arc::new(cid_factory), + supported_versions: DEFAULT_SUPPORTED_VERSIONS.to_vec(), + grease_quic_bit: true, + min_reset_interval: Duration::from_millis(20), + rng_seed: None, + } + } + + /// Supply a custom connection ID generator factory + /// + /// Called once by each `Endpoint` constructed from this configuration to obtain the CID + /// generator which will be used to generate the CIDs used for incoming packets on all + /// connections involving that `Endpoint`. A custom CID generator allows applications to embed + /// information in local connection IDs, e.g. to support stateless packet-level load balancers. + /// + /// Defaults to [`HashedConnectionIdGenerator`]. + pub fn cid_generator Box + Send + Sync + 'static>( + &mut self, + factory: F, + ) -> &mut Self { + self.connection_id_generator_factory = Arc::new(factory); + self + } + + /// Private key used to send authenticated connection resets to peers who were + /// communicating with a previous instance of this endpoint. + pub fn reset_key(&mut self, key: Arc) -> &mut Self { + self.reset_key = key; + self + } + + /// Maximum UDP payload size accepted from peers (excluding UDP and IP overhead). + /// + /// Must be greater or equal than 1200. + /// + /// Defaults to 1472, which is the largest UDP payload that can be transmitted in the typical + /// 1500 byte Ethernet MTU. Deployments on links with larger MTUs (e.g. loopback or Ethernet + /// with jumbo frames) can raise this to improve performance at the cost of a linear increase in + /// datagram receive buffer size. + pub fn max_udp_payload_size(&mut self, value: u16) -> Result<&mut Self, ConfigError> { + if !(1200..=65_527).contains(&value) { + return Err(ConfigError::OutOfBounds); + } + + self.max_udp_payload_size = value.into(); + Ok(self) + } + + /// Get the current value of `max_udp_payload_size` + /// + /// While most parameters don't need to be readable, this must be exposed to allow higher-level + /// layers, e.g. the `quinn` crate, to determine how large a receive buffer to allocate to + /// support an externally-defined `EndpointConfig`. + /// + /// While `get_` accessors are typically unidiomatic in Rust, we favor concision for setters, + /// which will be used far more heavily. + #[doc(hidden)] + pub fn get_max_udp_payload_size(&self) -> u64 { + self.max_udp_payload_size.into() + } + + /// Override supported QUIC versions + pub fn supported_versions(&mut self, supported_versions: Vec) -> &mut Self { + self.supported_versions = supported_versions; + self + } + + /// Whether to accept QUIC packets containing any value for the fixed bit + /// + /// Enabled by default. Helps protect against protocol ossification and makes traffic less + /// identifiable to observers. Disable if helping observers identify this traffic as QUIC is + /// desired. + pub fn grease_quic_bit(&mut self, value: bool) -> &mut Self { + self.grease_quic_bit = value; + self + } + + /// Minimum interval between outgoing stateless reset packets + /// + /// Defaults to 20ms. Limits the impact of attacks which flood an endpoint with garbage packets, + /// e.g. [ISAKMP/IKE amplification]. Larger values provide a stronger defense, but may delay + /// detection of some error conditions by clients. Using a [`ConnectionIdGenerator`] with a low + /// rate of false positives in [`validate`](ConnectionIdGenerator::validate) reduces the risk + /// incurred by a small minimum reset interval. + /// + /// [ISAKMP/IKE + /// amplification]: https://bughunters.google.com/blog/5960150648750080/preventing-cross-service-udp-loops-in-quic#isakmp-ike-amplification-vs-quic + pub fn min_reset_interval(&mut self, value: Duration) -> &mut Self { + self.min_reset_interval = value; + self + } + + /// Optional seed to be used internally for random number generation + /// + /// By default, quinn will initialize an endpoint's rng using a platform entropy source. + /// However, you can seed the rng yourself through this method (e.g. if you need to run quinn + /// deterministically or if you are using quinn in an environment that doesn't have a source of + /// entropy available). + pub fn rng_seed(&mut self, seed: Option<[u8; 32]>) -> &mut Self { + self.rng_seed = seed; + self + } +} + +impl fmt::Debug for EndpointConfig { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("EndpointConfig") + .field("reset_key", &"[ elided ]") + .field("max_udp_payload_size", &self.max_udp_payload_size) + .field("cid_generator_factory", &"[ elided ]") + .field("supported_versions", &self.supported_versions) + .field("grease_quic_bit", &self.grease_quic_bit) + .field("rng_seed", &self.rng_seed) + .finish() + } +} + +#[cfg(any(feature = "aws-lc-rs", feature = "ring"))] +impl Default for EndpointConfig { + fn default() -> Self { + #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] + use aws_lc_rs::hmac; + use rand::RngCore; + #[cfg(feature = "ring")] + use ring::hmac; + + let mut reset_key = [0; 64]; + rand::thread_rng().fill_bytes(&mut reset_key); + + Self::new(Arc::new(hmac::Key::new(hmac::HMAC_SHA256, &reset_key))) + } +} + +/// Parameters governing incoming connections +/// +/// Default values should be suitable for most internet applications. +#[derive(Clone)] +pub struct ServerConfig { + /// Transport configuration to use for incoming connections + pub transport: Arc, + + /// TLS configuration used for incoming connections. + /// + /// Must be set to use TLS 1.3 only. + pub crypto: Arc, + + /// Used to generate one-time AEAD keys to protect handshake tokens + pub(crate) token_key: Arc, + + /// Microseconds after a stateless retry token was issued for which it's considered valid. + pub(crate) retry_token_lifetime: Duration, + + /// Whether to allow clients to migrate to new addresses + /// + /// Improves behavior for clients that move between different internet connections or suffer NAT + /// rebinding. Enabled by default. + pub(crate) migration: bool, + + pub(crate) preferred_address_v4: Option, + pub(crate) preferred_address_v6: Option, + + pub(crate) max_incoming: usize, + pub(crate) incoming_buffer_size: u64, + pub(crate) incoming_buffer_size_total: u64, +} + +impl ServerConfig { + /// Create a default config with a particular handshake token key + pub fn new( + crypto: Arc, + token_key: Arc, + ) -> Self { + Self { + transport: Arc::new(TransportConfig::default()), + crypto, + + token_key, + retry_token_lifetime: Duration::from_secs(15), + + migration: true, + + preferred_address_v4: None, + preferred_address_v6: None, + + max_incoming: 1 << 16, + incoming_buffer_size: 10 << 20, + incoming_buffer_size_total: 100 << 20, + } + } + + /// Set a custom [`TransportConfig`] + pub fn transport_config(&mut self, transport: Arc) -> &mut Self { + self.transport = transport; + self + } + + /// Private key used to authenticate data included in handshake tokens. + pub fn token_key(&mut self, value: Arc) -> &mut Self { + self.token_key = value; + self + } + + /// Duration after a stateless retry token was issued for which it's considered valid. + pub fn retry_token_lifetime(&mut self, value: Duration) -> &mut Self { + self.retry_token_lifetime = value; + self + } + + /// Whether to allow clients to migrate to new addresses + /// + /// Improves behavior for clients that move between different internet connections or suffer NAT + /// rebinding. Enabled by default. + pub fn migration(&mut self, value: bool) -> &mut Self { + self.migration = value; + self + } + + /// The preferred IPv4 address that will be communicated to clients during handshaking. + /// If the client is able to reach this address, it will switch to it. + pub fn preferred_address_v4(&mut self, address: Option) -> &mut Self { + self.preferred_address_v4 = address; + self + } + + /// The preferred IPv6 address that will be communicated to clients during handshaking. + /// If the client is able to reach this address, it will switch to it. + pub fn preferred_address_v6(&mut self, address: Option) -> &mut Self { + self.preferred_address_v6 = address; + self + } + + /// Maximum number of [`Incoming`][crate::Incoming] to allow to exist at a time + /// + /// An [`Incoming`][crate::Incoming] comes into existence when an incoming connection attempt + /// is received and stops existing when the application either accepts it or otherwise disposes + /// of it. While this limit is reached, new incoming connection attempts are immediately + /// refused. Larger values have greater worst-case memory consumption, but accommodate greater + /// application latency in handling incoming connection attempts. + /// + /// The default value is set to 65536. With a typical Ethernet MTU of 1500 bytes, this limits + /// memory consumption from this to under 100 MiB--a generous amount that still prevents memory + /// exhaustion in most contexts. + pub fn max_incoming(&mut self, max_incoming: usize) -> &mut Self { + self.max_incoming = max_incoming; + self + } + + /// Maximum number of received bytes to buffer for each [`Incoming`][crate::Incoming] + /// + /// An [`Incoming`][crate::Incoming] comes into existence when an incoming connection attempt + /// is received and stops existing when the application either accepts it or otherwise disposes + /// of it. This limit governs only packets received within that period, and does not include + /// the first packet. Packets received in excess of this limit are dropped, which may cause + /// 0-RTT or handshake data to have to be retransmitted. + /// + /// The default value is set to 10 MiB--an amount such that in most situations a client would + /// not transmit that much 0-RTT data faster than the server handles the corresponding + /// [`Incoming`][crate::Incoming]. + pub fn incoming_buffer_size(&mut self, incoming_buffer_size: u64) -> &mut Self { + self.incoming_buffer_size = incoming_buffer_size; + self + } + + /// Maximum number of received bytes to buffer for all [`Incoming`][crate::Incoming] + /// collectively + /// + /// An [`Incoming`][crate::Incoming] comes into existence when an incoming connection attempt + /// is received and stops existing when the application either accepts it or otherwise disposes + /// of it. This limit governs only packets received within that period, and does not include + /// the first packet. Packets received in excess of this limit are dropped, which may cause + /// 0-RTT or handshake data to have to be retransmitted. + /// + /// The default value is set to 100 MiB--a generous amount that still prevents memory + /// exhaustion in most contexts. + pub fn incoming_buffer_size_total(&mut self, incoming_buffer_size_total: u64) -> &mut Self { + self.incoming_buffer_size_total = incoming_buffer_size_total; + self + } +} + +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +impl ServerConfig { + /// Create a server config with the given certificate chain to be presented to clients + /// + /// Uses a randomized handshake token key. + pub fn with_single_cert( + cert_chain: Vec>, + key: PrivateKeyDer<'static>, + ) -> Result { + Ok(Self::with_crypto(Arc::new(QuicServerConfig::new( + cert_chain, key, + )?))) + } +} + +#[cfg(any(feature = "aws-lc-rs", feature = "ring"))] +impl ServerConfig { + /// Create a server config with the given [`crypto::ServerConfig`] + /// + /// Uses a randomized handshake token key. + pub fn with_crypto(crypto: Arc) -> Self { + #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] + use aws_lc_rs::hkdf; + use rand::RngCore; + #[cfg(feature = "ring")] + use ring::hkdf; + + let rng = &mut rand::thread_rng(); + let mut master_key = [0u8; 64]; + rng.fill_bytes(&mut master_key); + let master_key = hkdf::Salt::new(hkdf::HKDF_SHA256, &[]).extract(&master_key); + + Self::new(crypto, Arc::new(master_key)) + } +} + +impl fmt::Debug for ServerConfig { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("ServerConfig") + .field("transport", &self.transport) + .field("crypto", &"ServerConfig { elided }") + .field("token_key", &"[ elided ]") + .field("retry_token_lifetime", &self.retry_token_lifetime) + .field("migration", &self.migration) + .field("preferred_address_v4", &self.preferred_address_v4) + .field("preferred_address_v6", &self.preferred_address_v6) + .field("max_incoming", &self.max_incoming) + .field("incoming_buffer_size", &self.incoming_buffer_size) + .field( + "incoming_buffer_size_total", + &self.incoming_buffer_size_total, + ) + .finish() + } +} + +/// Configuration for outgoing connections +/// +/// Default values should be suitable for most internet applications. +#[derive(Clone)] +#[non_exhaustive] +pub struct ClientConfig { + /// Transport configuration to use + pub(crate) transport: Arc, + + /// Cryptographic configuration to use + pub(crate) crypto: Arc, + + /// Provider that populates the destination connection ID of Initial Packets + pub(crate) initial_dst_cid_provider: Arc ConnectionId + Send + Sync>, + + /// QUIC protocol version to use + pub(crate) version: u32, +} + +impl ClientConfig { + /// Create a default config with a particular cryptographic config + pub fn new(crypto: Arc) -> Self { + Self { + transport: Default::default(), + crypto, + initial_dst_cid_provider: Arc::new(|| { + RandomConnectionIdGenerator::new(MAX_CID_SIZE).generate_cid() + }), + version: 1, + } + } + + /// Configure how to populate the destination CID of the initial packet when attempting to + /// establish a new connection. + /// + /// By default, it's populated with random bytes with reasonable length, so unless you have + /// a good reason, you do not need to change it. + /// + /// When prefer to override the default, please note that the generated connection ID MUST be + /// at least 8 bytes long and unpredictable, as per section 7.2 of RFC 9000. + pub fn initial_dst_cid_provider( + &mut self, + initial_dst_cid_provider: Arc ConnectionId + Send + Sync>, + ) -> &mut Self { + self.initial_dst_cid_provider = initial_dst_cid_provider; + self + } + + /// Set a custom [`TransportConfig`] + pub fn transport_config(&mut self, transport: Arc) -> &mut Self { + self.transport = transport; + self + } + + /// Set the QUIC version to use + pub fn version(&mut self, version: u32) -> &mut Self { + self.version = version; + self + } +} + +#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] +impl ClientConfig { + /// Create a client configuration that trusts the platform's native roots + #[cfg(feature = "platform-verifier")] + pub fn with_platform_verifier() -> Self { + Self::new(Arc::new(crypto::rustls::QuicClientConfig::new(Arc::new( + rustls_platform_verifier::Verifier::new(), + )))) + } + + /// Create a client configuration that trusts specified trust anchors + pub fn with_root_certificates( + roots: Arc, + ) -> Result { + Ok(Self::new(Arc::new(crypto::rustls::QuicClientConfig::new( + WebPkiServerVerifier::builder_with_provider(roots, configured_provider()).build()?, + )))) + } +} + +impl fmt::Debug for ClientConfig { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("ClientConfig") + .field("transport", &self.transport) + .field("crypto", &"ClientConfig { elided }") + .field("version", &self.version) + .finish_non_exhaustive() + } +} + +/// Errors in the configuration of an endpoint +#[derive(Debug, Error, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum ConfigError { + /// Value exceeds supported bounds + #[error("value exceeds supported bounds")] + OutOfBounds, +} + +impl From for ConfigError { + fn from(_: TryFromIntError) -> Self { + Self::OutOfBounds + } +} + +impl From for ConfigError { + fn from(_: VarIntBoundsExceeded) -> Self { + Self::OutOfBounds + } +} diff --git a/quinn-proto/src/config.rs b/quinn-proto/src/config/transport.rs similarity index 59% rename from quinn-proto/src/config.rs rename to quinn-proto/src/config/transport.rs index 571f998dda..d3fa86d752 100644 --- a/quinn-proto/src/config.rs +++ b/quinn-proto/src/config/transport.rs @@ -1,26 +1,6 @@ -use std::{ - fmt, - net::{SocketAddrV4, SocketAddrV6}, - num::TryFromIntError, - sync::Arc, -}; +use std::{fmt, sync::Arc}; -#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] -use rustls::client::WebPkiServerVerifier; -#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] -use rustls::pki_types::{CertificateDer, PrivateKeyDer}; -use thiserror::Error; - -#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] -use crate::crypto::rustls::{configured_provider, QuicServerConfig}; -use crate::{ - cid_generator::{ConnectionIdGenerator, HashedConnectionIdGenerator}, - congestion, - crypto::{self, HandshakeTokenKey, HmacKey}, - shared::ConnectionId, - Duration, RandomConnectionIdGenerator, VarInt, VarIntBoundsExceeded, - DEFAULT_SUPPORTED_VERSIONS, INITIAL_MTU, MAX_CID_SIZE, MAX_UDP_PAYLOAD, -}; +use crate::{congestion, Duration, VarInt, VarIntBoundsExceeded, INITIAL_MTU, MAX_UDP_PAYLOAD}; /// Parameters governing the core QUIC state machine /// @@ -630,482 +610,6 @@ impl Default for MtuDiscoveryConfig { } } -/// Global configuration for the endpoint, affecting all connections -/// -/// Default values should be suitable for most internet applications. -#[derive(Clone)] -pub struct EndpointConfig { - pub(crate) reset_key: Arc, - pub(crate) max_udp_payload_size: VarInt, - /// CID generator factory - /// - /// Create a cid generator for local cid in Endpoint struct - pub(crate) connection_id_generator_factory: - Arc Box + Send + Sync>, - pub(crate) supported_versions: Vec, - pub(crate) grease_quic_bit: bool, - /// Minimum interval between outgoing stateless reset packets - pub(crate) min_reset_interval: Duration, - /// Optional seed to be used internally for random number generation - pub(crate) rng_seed: Option<[u8; 32]>, -} - -impl EndpointConfig { - /// Create a default config with a particular `reset_key` - pub fn new(reset_key: Arc) -> Self { - let cid_factory = - || -> Box { Box::::default() }; - Self { - reset_key, - max_udp_payload_size: (1500u32 - 28).into(), // Ethernet MTU minus IP + UDP headers - connection_id_generator_factory: Arc::new(cid_factory), - supported_versions: DEFAULT_SUPPORTED_VERSIONS.to_vec(), - grease_quic_bit: true, - min_reset_interval: Duration::from_millis(20), - rng_seed: None, - } - } - - /// Supply a custom connection ID generator factory - /// - /// Called once by each `Endpoint` constructed from this configuration to obtain the CID - /// generator which will be used to generate the CIDs used for incoming packets on all - /// connections involving that `Endpoint`. A custom CID generator allows applications to embed - /// information in local connection IDs, e.g. to support stateless packet-level load balancers. - /// - /// Defaults to [`HashedConnectionIdGenerator`]. - pub fn cid_generator Box + Send + Sync + 'static>( - &mut self, - factory: F, - ) -> &mut Self { - self.connection_id_generator_factory = Arc::new(factory); - self - } - - /// Private key used to send authenticated connection resets to peers who were - /// communicating with a previous instance of this endpoint. - pub fn reset_key(&mut self, key: Arc) -> &mut Self { - self.reset_key = key; - self - } - - /// Maximum UDP payload size accepted from peers (excluding UDP and IP overhead). - /// - /// Must be greater or equal than 1200. - /// - /// Defaults to 1472, which is the largest UDP payload that can be transmitted in the typical - /// 1500 byte Ethernet MTU. Deployments on links with larger MTUs (e.g. loopback or Ethernet - /// with jumbo frames) can raise this to improve performance at the cost of a linear increase in - /// datagram receive buffer size. - pub fn max_udp_payload_size(&mut self, value: u16) -> Result<&mut Self, ConfigError> { - if !(1200..=65_527).contains(&value) { - return Err(ConfigError::OutOfBounds); - } - - self.max_udp_payload_size = value.into(); - Ok(self) - } - - /// Get the current value of `max_udp_payload_size` - /// - /// While most parameters don't need to be readable, this must be exposed to allow higher-level - /// layers, e.g. the `quinn` crate, to determine how large a receive buffer to allocate to - /// support an externally-defined `EndpointConfig`. - /// - /// While `get_` accessors are typically unidiomatic in Rust, we favor concision for setters, - /// which will be used far more heavily. - #[doc(hidden)] - pub fn get_max_udp_payload_size(&self) -> u64 { - self.max_udp_payload_size.into() - } - - /// Override supported QUIC versions - pub fn supported_versions(&mut self, supported_versions: Vec) -> &mut Self { - self.supported_versions = supported_versions; - self - } - - /// Whether to accept QUIC packets containing any value for the fixed bit - /// - /// Enabled by default. Helps protect against protocol ossification and makes traffic less - /// identifiable to observers. Disable if helping observers identify this traffic as QUIC is - /// desired. - pub fn grease_quic_bit(&mut self, value: bool) -> &mut Self { - self.grease_quic_bit = value; - self - } - - /// Minimum interval between outgoing stateless reset packets - /// - /// Defaults to 20ms. Limits the impact of attacks which flood an endpoint with garbage packets, - /// e.g. [ISAKMP/IKE amplification]. Larger values provide a stronger defense, but may delay - /// detection of some error conditions by clients. Using a [`ConnectionIdGenerator`] with a low - /// rate of false positives in [`validate`](ConnectionIdGenerator::validate) reduces the risk - /// incurred by a small minimum reset interval. - /// - /// [ISAKMP/IKE - /// amplification]: https://bughunters.google.com/blog/5960150648750080/preventing-cross-service-udp-loops-in-quic#isakmp-ike-amplification-vs-quic - pub fn min_reset_interval(&mut self, value: Duration) -> &mut Self { - self.min_reset_interval = value; - self - } - - /// Optional seed to be used internally for random number generation - /// - /// By default, quinn will initialize an endpoint's rng using a platform entropy source. - /// However, you can seed the rng yourself through this method (e.g. if you need to run quinn - /// deterministically or if you are using quinn in an environment that doesn't have a source of - /// entropy available). - pub fn rng_seed(&mut self, seed: Option<[u8; 32]>) -> &mut Self { - self.rng_seed = seed; - self - } -} - -impl fmt::Debug for EndpointConfig { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.debug_struct("EndpointConfig") - .field("reset_key", &"[ elided ]") - .field("max_udp_payload_size", &self.max_udp_payload_size) - .field("cid_generator_factory", &"[ elided ]") - .field("supported_versions", &self.supported_versions) - .field("grease_quic_bit", &self.grease_quic_bit) - .field("rng_seed", &self.rng_seed) - .finish() - } -} - -#[cfg(any(feature = "aws-lc-rs", feature = "ring"))] -impl Default for EndpointConfig { - fn default() -> Self { - #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] - use aws_lc_rs::hmac; - use rand::RngCore; - #[cfg(feature = "ring")] - use ring::hmac; - - let mut reset_key = [0; 64]; - rand::thread_rng().fill_bytes(&mut reset_key); - - Self::new(Arc::new(hmac::Key::new(hmac::HMAC_SHA256, &reset_key))) - } -} - -/// Parameters governing incoming connections -/// -/// Default values should be suitable for most internet applications. -#[derive(Clone)] -pub struct ServerConfig { - /// Transport configuration to use for incoming connections - pub transport: Arc, - - /// TLS configuration used for incoming connections. - /// - /// Must be set to use TLS 1.3 only. - pub crypto: Arc, - - /// Used to generate one-time AEAD keys to protect handshake tokens - pub(crate) token_key: Arc, - - /// Microseconds after a stateless retry token was issued for which it's considered valid. - pub(crate) retry_token_lifetime: Duration, - - /// Whether to allow clients to migrate to new addresses - /// - /// Improves behavior for clients that move between different internet connections or suffer NAT - /// rebinding. Enabled by default. - pub(crate) migration: bool, - - pub(crate) preferred_address_v4: Option, - pub(crate) preferred_address_v6: Option, - - pub(crate) max_incoming: usize, - pub(crate) incoming_buffer_size: u64, - pub(crate) incoming_buffer_size_total: u64, -} - -impl ServerConfig { - /// Create a default config with a particular handshake token key - pub fn new( - crypto: Arc, - token_key: Arc, - ) -> Self { - Self { - transport: Arc::new(TransportConfig::default()), - crypto, - - token_key, - retry_token_lifetime: Duration::from_secs(15), - - migration: true, - - preferred_address_v4: None, - preferred_address_v6: None, - - max_incoming: 1 << 16, - incoming_buffer_size: 10 << 20, - incoming_buffer_size_total: 100 << 20, - } - } - - /// Set a custom [`TransportConfig`] - pub fn transport_config(&mut self, transport: Arc) -> &mut Self { - self.transport = transport; - self - } - - /// Private key used to authenticate data included in handshake tokens. - pub fn token_key(&mut self, value: Arc) -> &mut Self { - self.token_key = value; - self - } - - /// Duration after a stateless retry token was issued for which it's considered valid. - pub fn retry_token_lifetime(&mut self, value: Duration) -> &mut Self { - self.retry_token_lifetime = value; - self - } - - /// Whether to allow clients to migrate to new addresses - /// - /// Improves behavior for clients that move between different internet connections or suffer NAT - /// rebinding. Enabled by default. - pub fn migration(&mut self, value: bool) -> &mut Self { - self.migration = value; - self - } - - /// The preferred IPv4 address that will be communicated to clients during handshaking. - /// If the client is able to reach this address, it will switch to it. - pub fn preferred_address_v4(&mut self, address: Option) -> &mut Self { - self.preferred_address_v4 = address; - self - } - - /// The preferred IPv6 address that will be communicated to clients during handshaking. - /// If the client is able to reach this address, it will switch to it. - pub fn preferred_address_v6(&mut self, address: Option) -> &mut Self { - self.preferred_address_v6 = address; - self - } - - /// Maximum number of [`Incoming`][crate::Incoming] to allow to exist at a time - /// - /// An [`Incoming`][crate::Incoming] comes into existence when an incoming connection attempt - /// is received and stops existing when the application either accepts it or otherwise disposes - /// of it. While this limit is reached, new incoming connection attempts are immediately - /// refused. Larger values have greater worst-case memory consumption, but accommodate greater - /// application latency in handling incoming connection attempts. - /// - /// The default value is set to 65536. With a typical Ethernet MTU of 1500 bytes, this limits - /// memory consumption from this to under 100 MiB--a generous amount that still prevents memory - /// exhaustion in most contexts. - pub fn max_incoming(&mut self, max_incoming: usize) -> &mut Self { - self.max_incoming = max_incoming; - self - } - - /// Maximum number of received bytes to buffer for each [`Incoming`][crate::Incoming] - /// - /// An [`Incoming`][crate::Incoming] comes into existence when an incoming connection attempt - /// is received and stops existing when the application either accepts it or otherwise disposes - /// of it. This limit governs only packets received within that period, and does not include - /// the first packet. Packets received in excess of this limit are dropped, which may cause - /// 0-RTT or handshake data to have to be retransmitted. - /// - /// The default value is set to 10 MiB--an amount such that in most situations a client would - /// not transmit that much 0-RTT data faster than the server handles the corresponding - /// [`Incoming`][crate::Incoming]. - pub fn incoming_buffer_size(&mut self, incoming_buffer_size: u64) -> &mut Self { - self.incoming_buffer_size = incoming_buffer_size; - self - } - - /// Maximum number of received bytes to buffer for all [`Incoming`][crate::Incoming] - /// collectively - /// - /// An [`Incoming`][crate::Incoming] comes into existence when an incoming connection attempt - /// is received and stops existing when the application either accepts it or otherwise disposes - /// of it. This limit governs only packets received within that period, and does not include - /// the first packet. Packets received in excess of this limit are dropped, which may cause - /// 0-RTT or handshake data to have to be retransmitted. - /// - /// The default value is set to 100 MiB--a generous amount that still prevents memory - /// exhaustion in most contexts. - pub fn incoming_buffer_size_total(&mut self, incoming_buffer_size_total: u64) -> &mut Self { - self.incoming_buffer_size_total = incoming_buffer_size_total; - self - } -} - -#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] -impl ServerConfig { - /// Create a server config with the given certificate chain to be presented to clients - /// - /// Uses a randomized handshake token key. - pub fn with_single_cert( - cert_chain: Vec>, - key: PrivateKeyDer<'static>, - ) -> Result { - Ok(Self::with_crypto(Arc::new(QuicServerConfig::new( - cert_chain, key, - )?))) - } -} - -#[cfg(any(feature = "aws-lc-rs", feature = "ring"))] -impl ServerConfig { - /// Create a server config with the given [`crypto::ServerConfig`] - /// - /// Uses a randomized handshake token key. - pub fn with_crypto(crypto: Arc) -> Self { - #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))] - use aws_lc_rs::hkdf; - use rand::RngCore; - #[cfg(feature = "ring")] - use ring::hkdf; - - let rng = &mut rand::thread_rng(); - let mut master_key = [0u8; 64]; - rng.fill_bytes(&mut master_key); - let master_key = hkdf::Salt::new(hkdf::HKDF_SHA256, &[]).extract(&master_key); - - Self::new(crypto, Arc::new(master_key)) - } -} - -impl fmt::Debug for ServerConfig { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.debug_struct("ServerConfig") - .field("transport", &self.transport) - .field("crypto", &"ServerConfig { elided }") - .field("token_key", &"[ elided ]") - .field("retry_token_lifetime", &self.retry_token_lifetime) - .field("migration", &self.migration) - .field("preferred_address_v4", &self.preferred_address_v4) - .field("preferred_address_v6", &self.preferred_address_v6) - .field("max_incoming", &self.max_incoming) - .field("incoming_buffer_size", &self.incoming_buffer_size) - .field( - "incoming_buffer_size_total", - &self.incoming_buffer_size_total, - ) - .finish() - } -} - -/// Configuration for outgoing connections -/// -/// Default values should be suitable for most internet applications. -#[derive(Clone)] -#[non_exhaustive] -pub struct ClientConfig { - /// Transport configuration to use - pub(crate) transport: Arc, - - /// Cryptographic configuration to use - pub(crate) crypto: Arc, - - /// Provider that populates the destination connection ID of Initial Packets - pub(crate) initial_dst_cid_provider: Arc ConnectionId + Send + Sync>, - - /// QUIC protocol version to use - pub(crate) version: u32, -} - -impl ClientConfig { - /// Create a default config with a particular cryptographic config - pub fn new(crypto: Arc) -> Self { - Self { - transport: Default::default(), - crypto, - initial_dst_cid_provider: Arc::new(|| { - RandomConnectionIdGenerator::new(MAX_CID_SIZE).generate_cid() - }), - version: 1, - } - } - - /// Configure how to populate the destination CID of the initial packet when attempting to - /// establish a new connection. - /// - /// By default, it's populated with random bytes with reasonable length, so unless you have - /// a good reason, you do not need to change it. - /// - /// When prefer to override the default, please note that the generated connection ID MUST be - /// at least 8 bytes long and unpredictable, as per section 7.2 of RFC 9000. - pub fn initial_dst_cid_provider( - &mut self, - initial_dst_cid_provider: Arc ConnectionId + Send + Sync>, - ) -> &mut Self { - self.initial_dst_cid_provider = initial_dst_cid_provider; - self - } - - /// Set a custom [`TransportConfig`] - pub fn transport_config(&mut self, transport: Arc) -> &mut Self { - self.transport = transport; - self - } - - /// Set the QUIC version to use - pub fn version(&mut self, version: u32) -> &mut Self { - self.version = version; - self - } -} - -#[cfg(any(feature = "rustls-aws-lc-rs", feature = "rustls-ring"))] -impl ClientConfig { - /// Create a client configuration that trusts the platform's native roots - #[cfg(feature = "platform-verifier")] - pub fn with_platform_verifier() -> Self { - Self::new(Arc::new(crypto::rustls::QuicClientConfig::new(Arc::new( - rustls_platform_verifier::Verifier::new(), - )))) - } - - /// Create a client configuration that trusts specified trust anchors - pub fn with_root_certificates( - roots: Arc, - ) -> Result { - Ok(Self::new(Arc::new(crypto::rustls::QuicClientConfig::new( - WebPkiServerVerifier::builder_with_provider(roots, configured_provider()).build()?, - )))) - } -} - -impl fmt::Debug for ClientConfig { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.debug_struct("ClientConfig") - .field("transport", &self.transport) - .field("crypto", &"ClientConfig { elided }") - .field("version", &self.version) - .finish_non_exhaustive() - } -} - -/// Errors in the configuration of an endpoint -#[derive(Debug, Error, Clone, PartialEq, Eq)] -#[non_exhaustive] -pub enum ConfigError { - /// Value exceeds supported bounds - #[error("value exceeds supported bounds")] - OutOfBounds, -} - -impl From for ConfigError { - fn from(_: TryFromIntError) -> Self { - Self::OutOfBounds - } -} - -impl From for ConfigError { - fn from(_: VarIntBoundsExceeded) -> Self { - Self::OutOfBounds - } -} - /// Maximum duration of inactivity to accept before timing out the connection. /// /// This wraps an underlying [`VarInt`], representing the duration in milliseconds. Values can be From af4f29b8455590652c559fce1e923363ce8fae5a Mon Sep 17 00:00:00 2001 From: Yury Yarashevich Date: Tue, 26 Nov 2024 10:28:42 +0100 Subject: [PATCH 34/42] #2057: Extract known transport parameter IDs into enum. --- quinn-proto/src/transport_parameters.rs | 144 +++++++++++++++++++----- 1 file changed, 117 insertions(+), 27 deletions(-) diff --git a/quinn-proto/src/transport_parameters.rs b/quinn-proto/src/transport_parameters.rs index 381968059c..60a0dc9fd3 100644 --- a/quinn-proto/src/transport_parameters.rs +++ b/quinn-proto/src/transport_parameters.rs @@ -35,37 +35,37 @@ macro_rules! apply_params { $macro! { // #[doc] name (id) = default, /// Milliseconds, disabled if zero - max_idle_timeout(0x0001) = 0, + max_idle_timeout(MaxIdleTimeout) = 0, /// Limits the size of UDP payloads that the endpoint is willing to receive - max_udp_payload_size(0x0003) = 65527, + max_udp_payload_size(MaxUdpPayloadSize) = 65527, /// Initial value for the maximum amount of data that can be sent on the connection - initial_max_data(0x0004) = 0, + initial_max_data(InitialMaxData) = 0, /// Initial flow control limit for locally-initiated bidirectional streams - initial_max_stream_data_bidi_local(0x0005) = 0, + initial_max_stream_data_bidi_local(InitialMaxStreamDataBidiLocal) = 0, /// Initial flow control limit for peer-initiated bidirectional streams - initial_max_stream_data_bidi_remote(0x0006) = 0, + initial_max_stream_data_bidi_remote(InitialMaxStreamDataBidiRemote) = 0, /// Initial flow control limit for unidirectional streams - initial_max_stream_data_uni(0x0007) = 0, + initial_max_stream_data_uni(InitialMaxStreamDataUni) = 0, /// Initial maximum number of bidirectional streams the peer may initiate - initial_max_streams_bidi(0x0008) = 0, + initial_max_streams_bidi(InitialMaxStreamsBidi) = 0, /// Initial maximum number of unidirectional streams the peer may initiate - initial_max_streams_uni(0x0009) = 0, + initial_max_streams_uni(InitialMaxStreamsUni) = 0, /// Exponent used to decode the ACK Delay field in the ACK frame - ack_delay_exponent(0x000a) = 3, + ack_delay_exponent(AckDelayExponent) = 3, /// Maximum amount of time in milliseconds by which the endpoint will delay sending /// acknowledgments - max_ack_delay(0x000b) = 25, + max_ack_delay(MaxAckDelay) = 25, /// Maximum number of connection IDs from the peer that an endpoint is willing to store - active_connection_id_limit(0x000e) = 2, + active_connection_id_limit(ActiveConnectionIdLimit) = 2, } }; } macro_rules! make_struct { - {$($(#[$doc:meta])* $name:ident ($code:expr) = $default:expr,)*} => { + {$($(#[$doc:meta])* $name:ident ($id:ident) = $default:expr,)*} => { /// Transport parameters used to negotiate connection-level preferences between peers #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct TransportParameters { @@ -296,10 +296,10 @@ impl TransportParameters { /// Encode `TransportParameters` into buffer pub fn write(&self, w: &mut W) { macro_rules! write_params { - {$($(#[$doc:meta])* $name:ident ($code:expr) = $default:expr,)*} => { + {$($(#[$doc:meta])* $name:ident ($id:ident) = $default:expr,)*} => { $( if self.$name.0 != $default { - w.write_var($code); + w.write_var(TransportParameterId::$id as u64); w.write(VarInt::try_from(self.$name.size()).unwrap()); w.write(self.$name); } @@ -366,7 +366,7 @@ impl TransportParameters { // State to check for duplicate transport parameters. macro_rules! param_state { - {$($(#[$doc:meta])* $name:ident ($code:expr) = $default:expr,)*} => {{ + {$($(#[$doc:meta])* $name:ident ($id:ident) = $default:expr,)*} => {{ struct ParamState { $($name: bool,)* } @@ -385,10 +385,17 @@ impl TransportParameters { return Err(Error::Malformed); } let len = len as usize; + let Ok(id) = TransportParameterId::try_from(id) else { + // unknown transport parameters are ignored + r.advance(len); + continue; + }; match id { - 0x00 => decode_cid(len, &mut params.original_dst_cid, r)?, - 0x02 => { + TransportParameterId::OriginalDestinationConnectionId => { + decode_cid(len, &mut params.original_dst_cid, r)? + } + TransportParameterId::StatelessResetToken => { if len != 16 || params.stateless_reset_token.is_some() { return Err(Error::Malformed); } @@ -396,42 +403,48 @@ impl TransportParameters { r.copy_to_slice(&mut tok); params.stateless_reset_token = Some(tok.into()); } - 0x0c => { + TransportParameterId::DisableActiveMigration => { if len != 0 || params.disable_active_migration { return Err(Error::Malformed); } params.disable_active_migration = true; } - 0x0d => { + TransportParameterId::PreferredAddress => { if params.preferred_address.is_some() { return Err(Error::Malformed); } params.preferred_address = Some(PreferredAddress::read(&mut r.take(len))?); } - 0x0f => decode_cid(len, &mut params.initial_src_cid, r)?, - 0x10 => decode_cid(len, &mut params.retry_src_cid, r)?, - 0x20 => { + TransportParameterId::InitialSourceConnectionId => { + decode_cid(len, &mut params.initial_src_cid, r)? + } + TransportParameterId::RetrySourceConnectionId => { + decode_cid(len, &mut params.retry_src_cid, r)? + } + TransportParameterId::MaxDatagramFrameSize => { if len > 8 || params.max_datagram_frame_size.is_some() { return Err(Error::Malformed); } params.max_datagram_frame_size = Some(r.get().unwrap()); } - 0x2ab2 => match len { + TransportParameterId::GreaseQuicBit => match len { 0 => params.grease_quic_bit = true, _ => return Err(Error::Malformed), }, - 0xff04de1b => params.min_ack_delay = Some(r.get().unwrap()), + TransportParameterId::MinAckDelayDraft07 => { + params.min_ack_delay = Some(r.get().unwrap()) + } _ => { macro_rules! parse { - {$($(#[$doc:meta])* $name:ident ($code:expr) = $default:expr,)*} => { + {$($(#[$doc:meta])* $name:ident ($id:ident) = $default:expr,)*} => { match id { - $($code => { + $(TransportParameterId::$id => { let value = r.get::()?; if len != value.size() || got.$name { return Err(Error::Malformed); } params.$name = value.into(); got.$name = true; })* - _ => r.advance(len as usize), + _ => r.advance(len), } } } @@ -550,6 +563,83 @@ impl ReservedTransportParameter { const MAX_PAYLOAD_LEN: usize = 16; } +#[repr(u64)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum TransportParameterId { + // https://www.rfc-editor.org/rfc/rfc9000.html#iana-tp-table + OriginalDestinationConnectionId = 0x00, + MaxIdleTimeout = 0x01, + StatelessResetToken = 0x02, + MaxUdpPayloadSize = 0x03, + InitialMaxData = 0x04, + InitialMaxStreamDataBidiLocal = 0x05, + InitialMaxStreamDataBidiRemote = 0x06, + InitialMaxStreamDataUni = 0x07, + InitialMaxStreamsBidi = 0x08, + InitialMaxStreamsUni = 0x09, + AckDelayExponent = 0x0A, + MaxAckDelay = 0x0B, + DisableActiveMigration = 0x0C, + PreferredAddress = 0x0D, + ActiveConnectionIdLimit = 0x0E, + InitialSourceConnectionId = 0x0F, + RetrySourceConnectionId = 0x10, + + // Smallest possible ID of reserved transport parameter https://datatracker.ietf.org/doc/html/rfc9000#section-22.3 + ReservedTransportParameter = 0x1B, + + // https://www.rfc-editor.org/rfc/rfc9221.html#section-3 + MaxDatagramFrameSize = 0x20, + + // https://www.rfc-editor.org/rfc/rfc9287.html#section-3 + GreaseQuicBit = 0x2AB2, + + // https://datatracker.ietf.org/doc/html/draft-ietf-quic-ack-frequency#section-10.1 + MinAckDelayDraft07 = 0xFF04DE1B, +} + +impl std::cmp::PartialEq for TransportParameterId { + fn eq(&self, other: &u64) -> bool { + *other == (*self as u64) + } +} + +impl TryFrom for TransportParameterId { + type Error = (); + + fn try_from(value: u64) -> Result { + let param = match value { + id if Self::MaxIdleTimeout == id => Self::MaxIdleTimeout, + id if Self::MaxUdpPayloadSize == id => Self::MaxUdpPayloadSize, + id if Self::InitialMaxData == id => Self::InitialMaxData, + id if Self::InitialMaxStreamDataBidiLocal == id => Self::InitialMaxStreamDataBidiLocal, + id if Self::InitialMaxStreamDataBidiRemote == id => { + Self::InitialMaxStreamDataBidiRemote + } + id if Self::InitialMaxStreamDataUni == id => Self::InitialMaxStreamDataUni, + id if Self::InitialMaxStreamsBidi == id => Self::InitialMaxStreamsBidi, + id if Self::InitialMaxStreamsUni == id => Self::InitialMaxStreamsUni, + id if Self::AckDelayExponent == id => Self::AckDelayExponent, + id if Self::MaxAckDelay == id => Self::MaxAckDelay, + id if Self::ActiveConnectionIdLimit == id => Self::ActiveConnectionIdLimit, + id if Self::ReservedTransportParameter == id => Self::ReservedTransportParameter, + id if Self::StatelessResetToken == id => Self::StatelessResetToken, + id if Self::DisableActiveMigration == id => Self::DisableActiveMigration, + id if Self::MaxDatagramFrameSize == id => Self::MaxDatagramFrameSize, + id if Self::PreferredAddress == id => Self::PreferredAddress, + id if Self::OriginalDestinationConnectionId == id => { + Self::OriginalDestinationConnectionId + } + id if Self::InitialSourceConnectionId == id => Self::InitialSourceConnectionId, + id if Self::RetrySourceConnectionId == id => Self::RetrySourceConnectionId, + id if Self::GreaseQuicBit == id => Self::GreaseQuicBit, + id if Self::MinAckDelayDraft07 == id => Self::MinAckDelayDraft07, + _ => return Err(()), + }; + Ok(param) + } +} + fn decode_cid(len: usize, value: &mut Option, r: &mut impl Buf) -> Result<(), Error> { if len > MAX_CID_SIZE || value.is_some() || r.remaining() < len { return Err(Error::Malformed); From f18890960d7911739b5ed9402e85e8f8ad02b834 Mon Sep 17 00:00:00 2001 From: Yury Yarashevich Date: Wed, 27 Nov 2024 14:25:40 +0100 Subject: [PATCH 35/42] #2057: Write transport parameters in random order. --- quinn-proto/src/transport_parameters.rs | 191 ++++++++++++++++-------- 1 file changed, 131 insertions(+), 60 deletions(-) diff --git a/quinn-proto/src/transport_parameters.rs b/quinn-proto/src/transport_parameters.rs index 60a0dc9fd3..ccf378d4a7 100644 --- a/quinn-proto/src/transport_parameters.rs +++ b/quinn-proto/src/transport_parameters.rs @@ -12,7 +12,7 @@ use std::{ }; use bytes::{Buf, BufMut}; -use rand::{Rng as _, RngCore}; +use rand::{seq::SliceRandom as _, Rng as _, RngCore}; use thiserror::Error; use crate::{ @@ -104,6 +104,12 @@ macro_rules! make_struct { /// of transport parameter extensions. /// When present, it is included during serialization but ignored during deserialization. pub(crate) grease_transport_parameter: Option, + + /// Defines the order in which transport parameters are serialized. + /// + /// This field is initialized only for outgoing `TransportParameters` instances and + /// is set to `None` for `TransportParameters` received from a peer. + pub(crate) write_order: Option<[u8; TransportParameterId::SUPPORTED.len()]>, } // We deliberately don't implement the `Default` trait, since that would be public, and @@ -126,6 +132,7 @@ macro_rules! make_struct { stateless_reset_token: None, preferred_address: None, grease_transport_parameter: None, + write_order: None, } } } @@ -168,6 +175,11 @@ impl TransportParameters { VarInt::from_u64(u64::try_from(TIMER_GRANULARITY.as_micros()).unwrap()).unwrap(), ), grease_transport_parameter: Some(ReservedTransportParameter::random(rng)), + write_order: Some({ + let mut order = std::array::from_fn(|i| i as u8); + order.shuffle(rng); + order + }), ..Self::default() } } @@ -295,68 +307,100 @@ impl From for Error { impl TransportParameters { /// Encode `TransportParameters` into buffer pub fn write(&self, w: &mut W) { - macro_rules! write_params { - {$($(#[$doc:meta])* $name:ident ($id:ident) = $default:expr,)*} => { - $( - if self.$name.0 != $default { - w.write_var(TransportParameterId::$id as u64); - w.write(VarInt::try_from(self.$name.size()).unwrap()); - w.write(self.$name); + for idx in self + .write_order + .as_ref() + .unwrap_or(&std::array::from_fn(|i| i as u8)) + { + let id = TransportParameterId::SUPPORTED[*idx as usize]; + match id { + TransportParameterId::ReservedTransportParameter => { + if let Some(param) = self.grease_transport_parameter { + param.write(w); } - )* - } - } - apply_params!(write_params); - - if let Some(param) = self.grease_transport_parameter { - param.write(w); - } - - if let Some(ref x) = self.stateless_reset_token { - w.write_var(0x02); - w.write_var(16); - w.put_slice(x); - } - - if self.disable_active_migration { - w.write_var(0x0c); - w.write_var(0); - } - - if let Some(x) = self.max_datagram_frame_size { - w.write_var(0x20); - w.write_var(x.size() as u64); - w.write(x); - } - - if let Some(ref x) = self.preferred_address { - w.write_var(0x000d); - w.write_var(x.wire_size() as u64); - x.write(w); - } - - for &(tag, cid) in &[ - (0x00, &self.original_dst_cid), - (0x0f, &self.initial_src_cid), - (0x10, &self.retry_src_cid), - ] { - if let Some(ref cid) = *cid { - w.write_var(tag); - w.write_var(cid.len() as u64); - w.put_slice(cid); + } + TransportParameterId::StatelessResetToken => { + if let Some(ref x) = self.stateless_reset_token { + w.write_var(id as u64); + w.write_var(16); + w.put_slice(x); + } + } + TransportParameterId::DisableActiveMigration => { + if self.disable_active_migration { + w.write_var(id as u64); + w.write_var(0); + } + } + TransportParameterId::MaxDatagramFrameSize => { + if let Some(x) = self.max_datagram_frame_size { + w.write_var(id as u64); + w.write_var(x.size() as u64); + w.write(x); + } + } + TransportParameterId::PreferredAddress => { + if let Some(ref x) = self.preferred_address { + w.write_var(id as u64); + w.write_var(x.wire_size() as u64); + x.write(w); + } + } + TransportParameterId::OriginalDestinationConnectionId => { + if let Some(ref cid) = self.original_dst_cid { + w.write_var(id as u64); + w.write_var(cid.len() as u64); + w.put_slice(cid); + } + } + TransportParameterId::InitialSourceConnectionId => { + if let Some(ref cid) = self.initial_src_cid { + w.write_var(id as u64); + w.write_var(cid.len() as u64); + w.put_slice(cid); + } + } + TransportParameterId::RetrySourceConnectionId => { + if let Some(ref cid) = self.retry_src_cid { + w.write_var(id as u64); + w.write_var(cid.len() as u64); + w.put_slice(cid); + } + } + TransportParameterId::GreaseQuicBit => { + if self.grease_quic_bit { + w.write_var(id as u64); + w.write_var(0); + } + } + TransportParameterId::MinAckDelayDraft07 => { + if let Some(x) = self.min_ack_delay { + w.write_var(id as u64); + w.write_var(x.size() as u64); + w.write(x); + } + } + id => { + macro_rules! write_params { + {$($(#[$doc:meta])* $name:ident ($id:ident) = $default:expr,)*} => { + match id { + $(TransportParameterId::$id => { + if self.$name.0 != $default { + w.write_var(id as u64); + w.write(VarInt::try_from(self.$name.size()).unwrap()); + w.write(self.$name); + } + })*, + _ => { + unimplemented!("Missing implementation of write for transport parameter with code {id:?}"); + } + } + } + } + apply_params!(write_params); + } } } - - if self.grease_quic_bit { - w.write_var(0x2ab2); - w.write_var(0); - } - - if let Some(x) = self.min_ack_delay { - w.write_var(0xff04de1b); - w.write_var(x.size() as u64); - w.write(x); - } } /// Decode `TransportParameters` from buffer @@ -598,6 +642,33 @@ pub(crate) enum TransportParameterId { MinAckDelayDraft07 = 0xFF04DE1B, } +impl TransportParameterId { + /// Array with all supported transport parameter IDs + const SUPPORTED: [Self; 21] = [ + Self::MaxIdleTimeout, + Self::MaxUdpPayloadSize, + Self::InitialMaxData, + Self::InitialMaxStreamDataBidiLocal, + Self::InitialMaxStreamDataBidiRemote, + Self::InitialMaxStreamDataUni, + Self::InitialMaxStreamsBidi, + Self::InitialMaxStreamsUni, + Self::AckDelayExponent, + Self::MaxAckDelay, + Self::ActiveConnectionIdLimit, + Self::ReservedTransportParameter, + Self::StatelessResetToken, + Self::DisableActiveMigration, + Self::MaxDatagramFrameSize, + Self::PreferredAddress, + Self::OriginalDestinationConnectionId, + Self::InitialSourceConnectionId, + Self::RetrySourceConnectionId, + Self::GreaseQuicBit, + Self::MinAckDelayDraft07, + ]; +} + impl std::cmp::PartialEq for TransportParameterId { fn eq(&self, other: &u64) -> bool { *other == (*self as u64) From e318cc4a80436fd9fa19c02886d682c49efca185 Mon Sep 17 00:00:00 2001 From: Asakura Mizu Date: Wed, 20 Nov 2024 16:14:36 +0800 Subject: [PATCH 36/42] feat(quinn-udp): support illumos --- .github/workflows/rust.yml | 18 ++++++++++++++++++ quinn-udp/build.rs | 6 ++++++ quinn-udp/src/unix.rs | 31 ++++++++++--------------------- quinn-udp/tests/tests.rs | 8 ++++---- quinn/src/tests.rs | 5 ++++- 5 files changed, 42 insertions(+), 26 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 7a3b22cd62..552fae963c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -82,6 +82,24 @@ jobs: export PATH=$HOME/.rust_solaris/bin:$PATH cargo build --all-targets && cargo test && cargo test --manifest-path fuzz/Cargo.toml && cargo test -p quinn-udp --benches + test-illumos: + name: test on illumos + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: test on Illumos + uses: vmactions/omnios-vm@v1 + with: + usesh: true + mem: 4096 + copyback: false + prepare: | + pkg install gcc14 curl pkg-config glib2 + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal + run: | + . "$HOME/.cargo/env" + cargo build --all-targets && cargo test && cargo test --manifest-path fuzz/Cargo.toml && cargo test -p quinn-udp --benches + test: strategy: matrix: diff --git a/quinn-udp/build.rs b/quinn-udp/build.rs index ef89ff70fb..d9893ed948 100644 --- a/quinn-udp/build.rs +++ b/quinn-udp/build.rs @@ -19,6 +19,12 @@ fn main() { target_os = "netbsd" ) }, + solarish: { + any( + target_os = "solaris", + target_os = "illumos" + ) + }, // Convenience aliases apple_fast: { all(apple, feature = "fast-apple-datapath") }, apple_slow: { all(apple, not(feature = "fast-apple-datapath")) }, diff --git a/quinn-udp/src/unix.rs b/quinn-udp/src/unix.rs index 38b322e244..1b35152974 100644 --- a/quinn-udp/src/unix.rs +++ b/quinn-udp/src/unix.rs @@ -1,4 +1,4 @@ -#[cfg(not(any(apple, target_os = "openbsd", target_os = "solaris")))] +#[cfg(not(any(apple, target_os = "openbsd", solarish)))] use std::ptr; use std::{ io::{self, IoSliceMut}, @@ -15,8 +15,7 @@ use std::{ use socket2::SockRef; use super::{ - cmsg, log::debug, log_sendmsg_error, EcnCodepoint, RecvMeta, Transmit, UdpSockRef, - IO_ERROR_LOG_INTERVAL, + cmsg, log_sendmsg_error, EcnCodepoint, RecvMeta, Transmit, UdpSockRef, IO_ERROR_LOG_INTERVAL, }; // Adapted from https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/bsd/sys/socket_private.h @@ -91,7 +90,7 @@ impl UdpSocketState { || cfg!(bsd) || cfg!(apple) || cfg!(target_os = "android") - || cfg!(target_os = "solaris") + || cfg!(solarish) { cmsg_platform_space += unsafe { libc::CMSG_SPACE(mem::size_of::() as _) as usize }; @@ -114,12 +113,12 @@ impl UdpSocketState { // mac and ios do not support IP_RECVTOS on dual-stack sockets :( // older macos versions also don't have the flag and will error out if we don't ignore it - #[cfg(not(any(target_os = "openbsd", target_os = "netbsd", target_os = "solaris")))] + #[cfg(not(any(target_os = "openbsd", target_os = "netbsd", solarish)))] if is_ipv4 || !io.only_v6()? { if let Err(_err) = set_socket_option(&*io, libc::IPPROTO_IP, libc::IP_RECVTOS, OPTION_ON) { - debug!("Ignoring error setting IP_RECVTOS on socket: {_err:?}"); + crate::log::debug!("Ignoring error setting IP_RECVTOS on socket: {_err:?}"); } } @@ -162,7 +161,7 @@ impl UdpSocketState { )?; } } - #[cfg(any(bsd, apple, target_os = "solaris"))] + #[cfg(any(bsd, apple, solarish))] // IP_RECVDSTADDR == IP_SENDSRCADDR on FreeBSD // macOS uses only IP_RECVDSTADDR, no IP_SENDSRCADDR on macOS (the same on Solaris) // macOS also supports IP_PKTINFO @@ -446,12 +445,7 @@ fn send(state: &UdpSocketState, io: SockRef<'_>, transmit: &Transmit<'_>) -> io: Ok(()) } -#[cfg(not(any( - apple, - target_os = "openbsd", - target_os = "netbsd", - target_os = "solaris" -)))] +#[cfg(not(any(apple, target_os = "openbsd", target_os = "netbsd", solarish)))] fn recv(io: SockRef<'_>, bufs: &mut [IoSliceMut<'_>], meta: &mut [RecvMeta]) -> io::Result { let mut names = [MaybeUninit::::uninit(); BATCH_SIZE]; let mut ctrls = [cmsg::Aligned(MaybeUninit::<[u8; CMSG_LEN]>::uninit()); BATCH_SIZE]; @@ -518,12 +512,7 @@ fn recv(io: SockRef<'_>, bufs: &mut [IoSliceMut<'_>], meta: &mut [RecvMeta]) -> Ok(msg_count as usize) } -#[cfg(any( - target_os = "openbsd", - target_os = "netbsd", - target_os = "solaris", - apple_slow -))] +#[cfg(any(target_os = "openbsd", target_os = "netbsd", solarish, apple_slow))] fn recv(io: SockRef<'_>, bufs: &mut [IoSliceMut<'_>], meta: &mut [RecvMeta]) -> io::Result { let mut name = MaybeUninit::::uninit(); let mut ctrl = cmsg::Aligned(MaybeUninit::<[u8; CMSG_LEN]>::uninit()); @@ -616,7 +605,7 @@ fn prepare_msg( }; encoder.push(libc::IPPROTO_IP, libc::IP_PKTINFO, pktinfo); } - #[cfg(any(bsd, apple, target_os = "solaris"))] + #[cfg(any(bsd, apple, solarish))] { if encode_src_ip { let addr = libc::in_addr { @@ -693,7 +682,7 @@ fn decode_recv( ecn_bits = cmsg::decode::(cmsg); }, // FreeBSD uses IP_RECVTOS here, and we can be liberal because cmsgs are opt-in. - #[cfg(not(any(target_os = "openbsd", target_os = "netbsd", target_os = "solaris")))] + #[cfg(not(any(target_os = "openbsd", target_os = "netbsd", solarish)))] (libc::IPPROTO_IP, libc::IP_RECVTOS) => unsafe { ecn_bits = cmsg::decode::(cmsg); }, diff --git a/quinn-udp/tests/tests.rs b/quinn-udp/tests/tests.rs index 7169ed6ae8..0ed020961c 100644 --- a/quinn-udp/tests/tests.rs +++ b/quinn-udp/tests/tests.rs @@ -1,4 +1,4 @@ -#[cfg(not(any(target_os = "openbsd", target_os = "netbsd", target_os = "solaris")))] +#[cfg(not(any(target_os = "openbsd", target_os = "netbsd", solarish)))] use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6}; use std::{ io::IoSliceMut, @@ -51,7 +51,7 @@ fn ecn_v6() { } #[test] -#[cfg(not(any(target_os = "openbsd", target_os = "netbsd", target_os = "solaris")))] +#[cfg(not(any(target_os = "openbsd", target_os = "netbsd", solarish)))] fn ecn_v4() { let send = Socket::from(UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)).unwrap()); let recv = Socket::from(UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)).unwrap()); @@ -71,7 +71,7 @@ fn ecn_v4() { } #[test] -#[cfg(not(any(target_os = "openbsd", target_os = "netbsd", target_os = "solaris")))] +#[cfg(not(any(target_os = "openbsd", target_os = "netbsd", solarish)))] fn ecn_v6_dualstack() { let recv = socket2::Socket::new( socket2::Domain::IPV6, @@ -117,7 +117,7 @@ fn ecn_v6_dualstack() { } #[test] -#[cfg(not(any(target_os = "openbsd", target_os = "netbsd", target_os = "solaris")))] +#[cfg(not(any(target_os = "openbsd", target_os = "netbsd", solarish)))] fn ecn_v4_mapped_v6() { let send = socket2::Socket::new( socket2::Domain::IPV6, diff --git a/quinn/src/tests.rs b/quinn/src/tests.rs index ea5a657130..8887af1c2e 100755 --- a/quinn/src/tests.rs +++ b/quinn/src/tests.rs @@ -387,7 +387,10 @@ async fn zero_rtt() { } #[test] -#[cfg_attr(target_os = "solaris", ignore = "Fails on Solaris")] +#[cfg_attr( + any(target_os = "solaris", target_os = "illumos"), + ignore = "Fails on Solaris and Illumos" +)] fn echo_v6() { run_echo(EchoArgs { client_addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0), From 7551282bdcffcf6ed57887d4eb41ffb2a4d88143 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 29 Nov 2024 11:48:34 +0100 Subject: [PATCH 37/42] fix(udp): use IPV6_PMTUDISC_PROBE instead of IP_PMTUDISC_PROBE on v6 Both resolve to `3`, thus not an actual bug. --- quinn-udp/src/unix.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quinn-udp/src/unix.rs b/quinn-udp/src/unix.rs index 1b35152974..adc7d8d348 100644 --- a/quinn-udp/src/unix.rs +++ b/quinn-udp/src/unix.rs @@ -145,7 +145,7 @@ impl UdpSocketState { &*io, libc::IPPROTO_IPV6, libc::IPV6_MTU_DISCOVER, - libc::IP_PMTUDISC_PROBE, + libc::IPV6_PMTUDISC_PROBE, )?; } } From 53e13f2eb9f536713a82107d72175d800709d6fd Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 29 Nov 2024 15:38:09 +0100 Subject: [PATCH 38/42] fix(udp): propagate error on apple_fast With https://github.com/quinn-rs/quinn/pull/2017, the concrete `send` implementations per platform are supposed to propagate `io::Error`s. Those errors are then eiter logged and dropped in `UdpSocketState::send` or further propagated in `UdpSocketState::try_send`. The `fast_apple` `send` implementation added in https://github.com/quinn-rs/quinn/pull/1993 does not follow this pattern. This commit adjusts the `fast_apple` implementation accordingly. --- quinn-udp/src/unix.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/quinn-udp/src/unix.rs b/quinn-udp/src/unix.rs index adc7d8d348..9258d0db82 100644 --- a/quinn-udp/src/unix.rs +++ b/quinn-udp/src/unix.rs @@ -394,16 +394,10 @@ fn send(state: &UdpSocketState, io: SockRef<'_>, transmit: &Transmit<'_>) -> io: } io::ErrorKind::WouldBlock => return Err(e), _ => { - // Other errors are ignored, since they will usually be handled - // by higher level retransmits and timeouts. - // - PermissionDenied errors have been observed due to iptable rules. - // Those are not fatal errors, since the - // configuration can be dynamically changed. - // - Destination unreachable errors have been observed for other // - EMSGSIZE is expected for MTU probes. Future work might be able to avoid // these by automatically clamping the MTUD upper bound to the interface MTU. if e.raw_os_error() != Some(libc::EMSGSIZE) { - log_sendmsg_error(&state.last_send_error, e, transmit); + return Err(e); } } } From 31a0440009afd5a7e29101410aa9d3da2d1f8077 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Fri, 29 Nov 2024 15:51:46 +0100 Subject: [PATCH 39/42] fix(udp): retry on ErrorKind::Interrupted On Linux and Android, `quinn-udp` will retry a send on `ErrorKind::Interrupted`: https://github.com/quinn-rs/quinn/blob/e318cc4a80436fd9fa19c02886d682c49efca185/quinn-udp/src/unix.rs#L305-L312 Do the same on all other unix platforms. --- quinn-udp/src/unix.rs | 65 +++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/quinn-udp/src/unix.rs b/quinn-udp/src/unix.rs index 9258d0db82..55bd210596 100644 --- a/quinn-udp/src/unix.rs +++ b/quinn-udp/src/unix.rs @@ -383,25 +383,27 @@ fn send(state: &UdpSocketState, io: SockRef<'_>, transmit: &Transmit<'_>) -> io: hdrs[i].msg_datalen = chunk.len(); cnt += 1; } - let n = unsafe { sendmsg_x(io.as_raw_fd(), hdrs.as_ptr(), cnt as u32, 0) }; - if n >= 0 { - return Ok(()); - } - let e = io::Error::last_os_error(); - match e.kind() { - io::ErrorKind::Interrupted => { - // Retry the transmission - } - io::ErrorKind::WouldBlock => return Err(e), - _ => { - // - EMSGSIZE is expected for MTU probes. Future work might be able to avoid - // these by automatically clamping the MTUD upper bound to the interface MTU. - if e.raw_os_error() != Some(libc::EMSGSIZE) { - return Err(e); + loop { + let n = unsafe { sendmsg_x(io.as_raw_fd(), hdrs.as_ptr(), cnt as u32, 0) }; + if n == -1 { + let e = io::Error::last_os_error(); + match e.kind() { + io::ErrorKind::Interrupted => { + // Retry the transmission + continue; + } + io::ErrorKind::WouldBlock => return Err(e), + _ => { + // - EMSGSIZE is expected for MTU probes. Future work might be able to avoid + // these by automatically clamping the MTUD upper bound to the interface MTU. + if e.raw_os_error() != Some(libc::EMSGSIZE) { + return Err(e); + } + } } } + return Ok(()); } - Ok(()) } #[cfg(any(target_os = "openbsd", target_os = "netbsd", apple_slow))] @@ -419,24 +421,27 @@ fn send(state: &UdpSocketState, io: SockRef<'_>, transmit: &Transmit<'_>) -> io: cfg!(apple) || cfg!(target_os = "openbsd") || cfg!(target_os = "netbsd"), state.sendmsg_einval(), ); - let n = unsafe { libc::sendmsg(io.as_raw_fd(), &hdr, 0) }; - if n == -1 { - let e = io::Error::last_os_error(); - match e.kind() { - io::ErrorKind::Interrupted => { - // Retry the transmission - } - io::ErrorKind::WouldBlock => return Err(e), - _ => { - // - EMSGSIZE is expected for MTU probes. Future work might be able to avoid - // these by automatically clamping the MTUD upper bound to the interface MTU. - if e.raw_os_error() != Some(libc::EMSGSIZE) { - return Err(e); + loop { + let n = unsafe { libc::sendmsg(io.as_raw_fd(), &hdr, 0) }; + if n == -1 { + let e = io::Error::last_os_error(); + match e.kind() { + io::ErrorKind::Interrupted => { + // Retry the transmission + continue; + } + io::ErrorKind::WouldBlock => return Err(e), + _ => { + // - EMSGSIZE is expected for MTU probes. Future work might be able to avoid + // these by automatically clamping the MTUD upper bound to the interface MTU. + if e.raw_os_error() != Some(libc::EMSGSIZE) { + return Err(e); + } } } } + return Ok(()); } - Ok(()) } #[cfg(not(any(apple, target_os = "openbsd", target_os = "netbsd", solarish)))] From 728a335454343e19d2cb01556ba7c15df232d1e2 Mon Sep 17 00:00:00 2001 From: BigWingBeat Date: Fri, 6 Dec 2024 14:58:52 +0000 Subject: [PATCH 40/42] Remove redundant nonblocking config on async-std --- quinn/src/runtime/async_io.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quinn/src/runtime/async_io.rs b/quinn/src/runtime/async_io.rs index 3eb2c4f21d..63c12ec2d0 100644 --- a/quinn/src/runtime/async_io.rs +++ b/quinn/src/runtime/async_io.rs @@ -89,7 +89,7 @@ impl UdpSocket { fn new(sock: std::net::UdpSocket) -> io::Result { Ok(Self { inner: udp::UdpSocketState::new((&sock).into())?, - io: Async::new(sock)?, + io: Async::new_nonblocking(sock)?, }) } } From 7260987c91aa4fd9135b7eba3082f0be5cd9e8e6 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 28 Nov 2024 11:54:19 +0100 Subject: [PATCH 41/42] fix(udp): do not enable URO on Windows on ARM On Windows on ARM with Windows Subsystem for Linux (WSL) `WSA_RECVMSG` does not return the segment size of coalesced UDP datagrams. See https://github.com/quinn-rs/quinn/issues/2041 for details. While lacking a fix for the root cause, don't enable URO, i.e. don't coalesce UDP datagrams on Windows on ARM. --- Cargo.toml | 2 +- quinn-udp/src/windows.rs | 71 +++++++++++++++++++++++++++++++++------- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b78d81eaa4..71f5668c9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ tracing-subscriber = { version = "0.3.0", default-features = false, features = [ url = "2" wasm-bindgen-test = { version = "0.3.45" } web-time = "1" -windows-sys = { version = ">=0.52, <=0.59", features = ["Win32_Foundation", "Win32_System_IO", "Win32_Networking_WinSock"] } +windows-sys = { version = ">=0.52, <=0.59", features = ["Win32_Foundation", "Win32_System_IO", "Win32_Networking_WinSock", "Win32_System_SystemInformation", "Win32_System_Threading"] } [profile.bench] debug = true diff --git a/quinn-udp/src/windows.rs b/quinn-udp/src/windows.rs index 223ab0fdf6..02717b7713 100644 --- a/quinn-udp/src/windows.rs +++ b/quinn-udp/src/windows.rs @@ -10,7 +10,13 @@ use std::{ use libc::{c_int, c_uint}; use once_cell::sync::Lazy; -use windows_sys::Win32::Networking::WinSock; +use windows_sys::Win32::{ + Networking::WinSock, + System::{ + SystemInformation::IMAGE_FILE_MACHINE_ARM64, + Threading::{GetCurrentProcess, IsWow64Process2}, + }, +}; use crate::{ cmsg::{self, CMsgHdr}, @@ -107,16 +113,33 @@ impl UdpSocketState { )?; } - // Opportunistically try to enable GRO - _ = set_socket_option( - &*socket.0, - WinSock::IPPROTO_UDP, - WinSock::UDP_RECV_MAX_COALESCED_SIZE, - // u32 per - // https://learn.microsoft.com/en-us/windows/win32/winsock/ipproto-udp-socket-options. - // Choice of 2^16 - 1 inspired by msquic. - u16::MAX as u32, - ); + match &*IS_WINDOWS_ON_ARM { + Ok(true) => { + // Bug on Windows on ARM, not receiving `UDP_COALESCED_INFO` `CMSG` + // when _Virtual Machine Platform_ feature enabled. See + // for details. + debug!("detected Windows on ARM host thus not enabling URO") + } + Ok(false) => { + // Opportunistically try to enable URO + let result = set_socket_option( + &*socket.0, + WinSock::IPPROTO_UDP, + WinSock::UDP_RECV_MAX_COALESCED_SIZE, + // u32 per + // https://learn.microsoft.com/en-us/windows/win32/winsock/ipproto-udp-socket-options. + // Choice of 2^16 - 1 inspired by msquic. + u16::MAX as u32, + ); + + if let Err(_e) = result { + debug!("failed to enable URO: {_e}"); + } + } + Err(_e) => { + debug!("failed to detect host system thus not enabling URO: {_e}"); + } + } let now = Instant::now(); Ok(Self { @@ -470,3 +493,29 @@ static MAX_GSO_SEGMENTS: Lazy = Lazy::new(|| { Err(_) => 1, } }); + +/// Evaluates to [`Ok(true)`] if executed either directly on Windows on ARM, or +/// on an emulator which itself executes on Windows on ARM. +/// +/// See +/// +/// for details. +static IS_WINDOWS_ON_ARM: Lazy> = Lazy::new(|| { + let mut process_machine: u16 = 0; + let mut native_machine: u16 = 0; + + let result = unsafe { + IsWow64Process2( + GetCurrentProcess(), + &mut process_machine as *mut u16, + &mut native_machine as *mut u16, + ) + }; + + match result { + // See + // . + 0 => Err(io::Error::last_os_error()), + _ => Ok(native_machine == IMAGE_FILE_MACHINE_ARM64), + } +}); From 204b14792b5e92eb2c43cdb1ff05426412ff4466 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Thu, 28 Nov 2024 12:32:54 +0100 Subject: [PATCH 42/42] chore(udp): increase crate patch version to v0.5.8 --- quinn-udp/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quinn-udp/Cargo.toml b/quinn-udp/Cargo.toml index 8d5f056ce7..6a1a25e774 100644 --- a/quinn-udp/Cargo.toml +++ b/quinn-udp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "quinn-udp" -version = "0.5.7" +version = "0.5.8" edition.workspace = true rust-version.workspace = true license.workspace = true