impl PreferredAddress { /// Make a new preferred address configuration. /// /// # Panics /// /// If neither address is provided, or if either address is of the wrong type. #[must_use] pubfn new(v4: Option<SocketAddrV4>, v6: Option<SocketAddrV6>) -> Self {
assert!(v4.is_some() || v6.is_some()); iflet Some(a) = v4 {
assert!(!a.ip().is_unspecified());
assert_ne!(a.port(), 0);
} iflet Some(a) = v6 {
assert!(!a.ip().is_unspecified());
assert_ne!(a.port(), 0);
} Self { v4, v6 }
}
/// A generic version of `new()` for testing. /// # Panics /// When the addresses are the wrong type. #[must_use] #[cfg(test)] pubfn new_any(v4: Option<std::net::SocketAddr>, v6: Option<std::net::SocketAddr>) -> Self { use std::net::SocketAddr;
let v4 = v4.map(|v4| { let SocketAddr::V4(v4) = v4 else {
panic!("not v4");
};
v4
}); let v6 = v6.map(|v6| { let SocketAddr::V6(v6) = v6 else {
panic!("not v6");
};
v6
}); Self::new(v4, v6)
}
fn decode_preferred_address(d: &mut Decoder) -> Res<Self> { // IPv4 address (maybe) let v4ip = Ipv4Addr::from(<[u8; 4]>::try_from(d.decode(4).ok_or(Error::NoMoreData)?)?); let v4port = d.decode_uint::<u16>().ok_or(Error::NoMoreData)?; // Can't have non-zero IP and zero port, or vice versa. if v4ip.is_unspecified() ^ (v4port == 0) { return Err(Error::TransportParameter);
} let v4 = if v4port == 0 {
None
} else {
Some(SocketAddrV4::new(v4ip, v4port))
};
// IPv6 address (mostly the same as v4) let v6ip = Ipv6Addr::from(<[u8; 16]>::try_from(
d.decode(16).ok_or(Error::NoMoreData)?,
)?); let v6port = d.decode_uint().ok_or(Error::NoMoreData)?; if v6ip.is_unspecified() ^ (v6port == 0) { return Err(Error::TransportParameter);
} let v6 = if v6port == 0 {
None
} else {
Some(SocketAddrV6::new(v6ip, v6port, 0, 0))
}; // Need either v4 or v6 to be present. if v4.is_none() && v6.is_none() { return Err(Error::TransportParameter);
}
// Connection ID (non-zero length) let cid = ConnectionId::from(d.decode_vec(1).ok_or(Error::NoMoreData)?); if cid.is_empty() || cid.len() > ConnectionId::MAX_LEN { return Err(Error::TransportParameter);
}
// Stateless reset token let srt = Srt::try_from(d).map_err(|_| Error::TransportParameter)?;
Ok(Self::PreferredAddress { v4, v6, cid, srt })
}
fn decode_versions(dec: &mut Decoder) -> Res<Self> { fn dv(dec: &mut Decoder) -> Res<version::Wire> { let v = dec
.decode_uint::<version::Wire>()
.ok_or(Error::NoMoreData)?; if v == 0 {
Err(Error::TransportParameter)
} else {
Ok(v)
}
}
let current = dv(dec)?; // This rounding down is OK because `decode` checks for left over data. let count = dec.remaining() / 4; letmut other = Vec::with_capacity(count); for _ in0..count {
other.push(dv(dec)?);
}
Ok(Self::Versions { current, other })
}
fn decode(dec: &mut Decoder) -> Res<Option<(TransportParameterId, Self)>> { let tp = dec.decode_varint().ok_or(Error::NoMoreData)?; let content = dec.decode_vvec().ok_or(Error::NoMoreData)?;
qtrace!("TP {tp:x} length {:x}", content.len()); let tp = match tp.try_into() {
Ok(tp) => tp,
Err(Error::UnknownTransportParameter) => return Ok(None), // Skip
Err(e) => return Err(e),
}; letmut d = Decoder::from(content); let value = match tp {
TransportParameterId::OriginalDestinationConnectionId
| TransportParameterId::InitialSourceConnectionId
| TransportParameterId::RetrySourceConnectionId => { Self::Bytes(d.decode_remainder().to_vec())
}
TransportParameterId::StatelessResetToken => { if d.remaining() != 16 { return Err(Error::TransportParameter);
} Self::Bytes(d.decode_remainder().to_vec())
}
TransportParameterId::IdleTimeout
| TransportParameterId::InitialMaxData
| TransportParameterId::InitialMaxStreamDataBidiLocal
| TransportParameterId::InitialMaxStreamDataBidiRemote
| TransportParameterId::InitialMaxStreamDataUni
| TransportParameterId::MaxAckDelay
| TransportParameterId::MaxDatagramFrameSize => match d.decode_varint() {
Some(v) => Self::Integer(v),
None => return Err(Error::TransportParameter),
},
TransportParameterId::InitialMaxStreamsBidi
| TransportParameterId::InitialMaxStreamsUni => match d.decode_varint() {
Some(v) if v <= (1 << 60) => Self::Integer(v),
_ => return Err(Error::StreamLimit),
},
TransportParameterId::MaxUdpPayloadSize => match d.decode_varint() {
Some(v) if v >= MIN_INITIAL_PACKET_SIZE.try_into()? => Self::Integer(v),
_ => return Err(Error::TransportParameter),
},
TransportParameterId::AckDelayExponent => match d.decode_varint() {
Some(v) if v <= 20 => Self::Integer(v),
_ => return Err(Error::TransportParameter),
},
TransportParameterId::ActiveConnectionIdLimit => match d.decode_varint() {
Some(v) if v >= 2 => Self::Integer(v),
_ => return Err(Error::TransportParameter),
},
TransportParameterId::DisableMigration
| TransportParameterId::GreaseQuicBit
| TransportParameterId::Scone => Self::Empty,
TransportParameterId::PreferredAddress => Self::decode_preferred_address(&='color:red'>mut d)?,
TransportParameterId::MinAckDelay => match d.decode_varint() {
Some(v) if v < (1 << 24) => Self::Integer(v),
_ => return Err(Error::TransportParameter),
},
TransportParameterId::VersionInformation => Self::decode_versions(&mut d)?, #[cfg(test)]
TransportParameterId::TestTransportParameter => { Self::Bytes(d.decode_remainder().to_vec())
}
}; if d.remaining() > 0 { return Err(Error::TooMuchData);
}
qtrace!("TP decoded; type {tp} val {value:?}");
Ok(Some((tp, value)))
}
}
/// Decode is a static function that parses transport parameters /// using the provided decoder. /// /// # Errors /// When the transport parameters are malformed. pubfn decode(d: &mut Decoder) -> Res<Self> { #[cfg(feature = "build-fuzzing-corpus")]
neqo_common::write_item_to_fuzzing_corpus("tparams", d.as_ref());
// Get an integer type or a default. /// # Panics /// When the transport parameter isn't recognized as being an integer. #[must_use] pubfn get_integer(&self, tp: TransportParameterId) -> u64 { let default = match tp {
TransportParameterId::IdleTimeout
| TransportParameterId::InitialMaxData
| TransportParameterId::InitialMaxStreamDataBidiLocal
| TransportParameterId::InitialMaxStreamDataBidiRemote
| TransportParameterId::InitialMaxStreamDataUni
| TransportParameterId::InitialMaxStreamsBidi
| TransportParameterId::InitialMaxStreamsUni
| TransportParameterId::MinAckDelay
| TransportParameterId::MaxDatagramFrameSize => 0,
TransportParameterId::MaxUdpPayloadSize => 65527,
TransportParameterId::AckDelayExponent => 3,
TransportParameterId::MaxAckDelay => DEFAULT_REMOTE_ACK_DELAY
.as_millis()
.try_into()
.expect("default remote ack delay in ms can't overflow u64"),
TransportParameterId::ActiveConnectionIdLimit => 2,
_ => panic!("Transport parameter not known or not an Integer"),
}; matchself.params[tp] {
None => default,
Some(TransportParameter::Integer(x)) => x,
_ => panic!("Internal error"),
}
}
// Set an integer type or a default. /// # Panics /// When the transport parameter isn't recognized as being an integer. pubfn set_integer(&mutself, tp: TransportParameterId, value: u64) { match tp {
TransportParameterId::IdleTimeout
| TransportParameterId::InitialMaxData
| TransportParameterId::InitialMaxStreamDataBidiLocal
| TransportParameterId::InitialMaxStreamDataBidiRemote
| TransportParameterId::InitialMaxStreamDataUni
| TransportParameterId::InitialMaxStreamsBidi
| TransportParameterId::InitialMaxStreamsUni
| TransportParameterId::MaxUdpPayloadSize
| TransportParameterId::AckDelayExponent
| TransportParameterId::MaxAckDelay
| TransportParameterId::ActiveConnectionIdLimit
| TransportParameterId::MinAckDelay
| TransportParameterId::MaxDatagramFrameSize => { self.set(tp, TransportParameter::Integer(value));
}
_ => panic!("Transport parameter not known"),
}
}
/// # Panics /// When the transport parameter isn't recognized as containing bytes. #[must_use] pubfn get_bytes(&self, tp: TransportParameterId) -> Option<&[u8]> { match tp {
TransportParameterId::OriginalDestinationConnectionId
| TransportParameterId::InitialSourceConnectionId
| TransportParameterId::RetrySourceConnectionId
| TransportParameterId::StatelessResetToken => {}
_ => panic!("Transport parameter not known or not type bytes"),
}
/// # Panics /// When the transport parameter isn't recognized as containing bytes. pubfn set_bytes(&mutself, tp: TransportParameterId, value: Vec<u8>) { match tp {
TransportParameterId::OriginalDestinationConnectionId
| TransportParameterId::InitialSourceConnectionId
| TransportParameterId::RetrySourceConnectionId
| TransportParameterId::StatelessResetToken => { self.set(tp, TransportParameter::Bytes(value));
}
_ => panic!("Transport parameter not known or not type bytes"),
}
}
/// # Panics /// When the transport parameter isn't recognized as being empty. pubfn set_empty(&mutself, tp: TransportParameterId) { match tp {
TransportParameterId::DisableMigration
| TransportParameterId::GreaseQuicBit
| TransportParameterId::Scone => { self.set(tp, TransportParameter::Empty);
}
_ => panic!("Transport parameter not known or not type empty"),
}
}
/// Set version information. pubfn set_versions(&mutself, role: Role, versions: &version::Config) { letmut other: Vec<u32> = Vec::with_capacity(versions.all().len() + 1); let grease = u32::from_ne_bytes(random::<4>()) & 0xf0f0_f0f0 | 0x0a0a_0a0a;
other.push(grease); for &v in versions.all() { if role == Role::Client && !versions.initial().is_compatible(v) { continue;
}
other.push(v.wire_version());
} let current = versions.initial().wire_version(); self.set(
TransportParameterId::VersionInformation,
TransportParameter::Versions { current, other },
);
}
/// # Panics /// When the indicated transport parameter is present but NOT empty. /// This should not happen if the parsing code in `TransportParameter::decode` is correct. #[must_use] pubfn get_empty(&self, tipe: TransportParameterId) -> bool { matchself.params[tipe] {
None => false,
Some(TransportParameter::Empty) => true,
_ => panic!("Internal error"),
}
}
/// Return true if the remembered transport parameters are OK for 0-RTT. /// Generally this means that any value that is currently in effect is greater than /// or equal to the promised value. pub(crate) fn ok_for_0rtt(&self, remembered: &Self) -> bool { for (k, v_rem) in &remembered.params { // Skip checks for these, which don't affect 0-RTT. if v_rem.is_none()
|| matches!(
k,
TransportParameterId::OriginalDestinationConnectionId
| TransportParameterId::InitialSourceConnectionId
| TransportParameterId::RetrySourceConnectionId
| TransportParameterId::StatelessResetToken
| TransportParameterId::IdleTimeout
| TransportParameterId::AckDelayExponent
| TransportParameterId::MaxAckDelay
| TransportParameterId::ActiveConnectionIdLimit
| TransportParameterId::PreferredAddress
| TransportParameterId::Scone
)
{ continue;
}
let ok = self.params[k]
.as_ref()
.is_some_and(|v_self| match (v_self, v_rem) {
(
TransportParameter::Integer(i_self),
Some(TransportParameter::Integer(i_rem)),
) => { if k == TransportParameterId::MinAckDelay { // MIN_ACK_DELAY is backwards: // it can only be reduced safely.
*i_self <= *i_rem
} else {
*i_self >= *i_rem
}
}
(TransportParameter::Empty, Some(TransportParameter::Empty)) => true,
(
TransportParameter::Versions {
current: v_self, ..
},
Some(TransportParameter::Versions { current: v_rem, .. }),
) => v_self == v_rem,
_ => false,
}); if !ok { returnfalse;
}
} true
}
/// When resuming, the version is set based on the ticket. /// That needs to be done to override the default choice from configuration. pubfn set_version(&mutself, version: Version) {
debug_assert_eq!(self.role, Role::Client); self.versions.set_initial(version); self.local.set_versions(self.role, &self.versions);
}
/// # Panics /// When this function is called before the peer has provided transport parameters. /// Do not call this function if you are not also able to send data. #[must_use] pubfn remote(&self) -> &TransportParameters { match (self.remote_handshake(), self.remote_0rtt()) {
(Some(tp), _) | (_, Some(tp)) => tp,
_ => panic!("no transport parameters from peer"),
}
}
/// Get the version as set (or as determined by a compatible upgrade). #[must_use] pubconstfn version(&self) -> Version { self.versions.initial()
}
fn compatible_upgrade(&mutself, remote_tp: &TransportParameters) -> Res<()> { ifself.version_selected { // A second call to this is only possible on the server, // if we need to send a TLS HelloRetryRequest.
debug_assert_eq!(self.role, Role::Server); return Ok(());
}
ifself.role == Role::Client { let chosen = Version::try_from(current)?; ifself.versions.compatible().any(|&v| v == chosen) { self.version_selected = true;
Ok(())
} else {
qinfo!( "Chosen version {current:x} is not compatible with initial version {:x}", self.versions.initial().wire_version(),
);
Err(Error::TransportParameter)
}
} else { if current != self.versions.initial().wire_version() {
qinfo!( "Current version {current:x} != own version {:x}", self.versions.initial().wire_version(),
); return Err(Error::TransportParameter);
}
/// Filter to retain only those transport parameters that are necessary for an outer /// `ClientHello`. /// /// We don't need the connection for long if we are forced into an ECH fallback, /// we only need it around long enough to get a fresh ECH config; and no data is exchanged. /// /// However, we do need to ensure that the connection attempt works. /// That motivates the inclusion of version negotiation and connection ID parameters, /// which would break the connection if they were dropped. /// /// Also, we include any parameters that might affect the configuration of the connection /// in ways that might adversely affect operation. /// That saves us from having to swap in a different configuration (i.e., /// `ConnectionParameters`) if the outer `ClientHello` is used. /// These probably aren't strictly necessary, even then: /// * ACK-related parameters only affect RTT estimation, which won't matter much; and /// * UDP datagram sizes will look like a path MTU restriction. /// /// There is no privacy harm to including them as the values are fixed (maximum ACK delay) /// or not set (ACK delay exponent and UDP payload size) in our code. constfn filter_ch_outer(tp: TransportParameterId, _v: Option<&TransportParameter>) -> bool {
matches!(
tp,
TransportParameterId::OriginalDestinationConnectionId
| TransportParameterId::StatelessResetToken
| TransportParameterId::InitialSourceConnectionId
| TransportParameterId::RetrySourceConnectionId
| TransportParameterId::VersionInformation
| TransportParameterId::AckDelayExponent
| TransportParameterId::MaxAckDelay
| TransportParameterId::MaxUdpPayloadSize
)
}
}
/// This takes a `TransportParameter::PreferredAddress` that has been mutilated. /// It then encodes it, working from the knowledge that the `encode` function /// doesn't care about validity, and decodes it. The result should be failure. fn assert_invalid_spa(spa: &TransportParameter) { letmut enc = Encoder::default();
spa.encode(&mut enc, PreferredAddress);
assert_eq!(
TransportParameter::decode(&mut enc.as_decoder()).unwrap_err(),
Error::TransportParameter
);
}
/// This is for those rare mutations that are acceptable. fn assert_valid_spa(spa: &TransportParameter) { letmut enc = Encoder::default();
spa.encode(&mut enc, PreferredAddress); letmut dec = enc.as_decoder(); let (id, decoded) = TransportParameter::decode(&mut dec).unwrap().unwrap();
assert_eq!(id, PreferredAddress);
assert_eq!(&decoded, spa);
}
#[test] fn preferred_address_zero_address() { // Either port being zero is bad.
assert_invalid_spa(&mutate_spa(|v4, _, _| {
v4.as_mut().unwrap().set_port(0);
}));
assert_invalid_spa(&mutate_spa(|_, v6, _| {
v6.as_mut().unwrap().set_port(0);
})); // Either IP being zero is bad.
assert_invalid_spa(&mutate_spa(|v4, _, _| {
v4.as_mut().unwrap().set_ip(Ipv4Addr::from(0));
}));
assert_invalid_spa(&mutate_spa(|_, v6, _| {
v6.as_mut().unwrap().set_ip(Ipv6Addr::from(0));
})); // Either address being absent is OK.
assert_valid_spa(&mutate_spa(|v4, _, _| {
*v4 = None;
}));
assert_valid_spa(&mutate_spa(|_, v6, _| {
*v6 = None;
})); // Both addresses being absent is bad.
assert_invalid_spa(&mutate_spa(|v4, v6, _| {
*v4 = None;
*v6 = None;
}));
}
letmut tps_a = TransportParameters::default(); for i in INTEGER_KEYS {
tps_a.set(*i, TransportParameter::Integer(12));
}
let tps_b = tps_a.clone();
assert!(tps_a.ok_for_0rtt(&tps_b));
assert!(tps_b.ok_for_0rtt(&tps_a));
// For each integer key, choose a new value that will be accepted. for i in INTEGER_KEYS { letmut tps_b = tps_a.clone(); // Set a safe new value; reducing MIN_ACK_DELAY instead. let safe_value = if *i == MinAckDelay { 11 } else { 13 };
tps_b.set(*i, TransportParameter::Integer(safe_value)); // If the new value is not safe relative to the remembered value, // then we can't attempt 0-RTT with these parameters.
assert!(!tps_a.ok_for_0rtt(&tps_b)); // The opposite situation is fine.
assert!(tps_b.ok_for_0rtt(&tps_a));
}
// Drop integer values and check that that is OK. for i in INTEGER_KEYS { letmut tps_b = tps_a.clone();
tps_b.remove(*i); // A value that is missing from what is remembered is OK.
assert!(tps_a.ok_for_0rtt(&tps_b)); // A value that is remembered, but not current is not OK.
assert!(!tps_b.ok_for_0rtt(&tps_a));
}
}
/// `ACTIVE_CONNECTION_ID_LIMIT` can't be less than 2. #[test] fn active_connection_id_limit_min_2() { letmut tps = TransportParameters::default();
// Intentionally set an invalid value for the ACTIVE_CONNECTION_ID_LIMIT transport // parameter.
tps.params[ActiveConnectionIdLimit] = Some(TransportParameter::Integer(1));
// When decoding a set of transport parameters with an invalid ACTIVE_CONNECTION_ID_LIMIT // the result should be an error. let invalid_decode_result = TransportParameters::decode(&mut enc.as_decoder());
assert!(invalid_decode_result.is_err());
}
letmut remembered = TransportParameters::default(); // It's OK to not remember having versions.
assert!(current.ok_for_0rtt(&remembered)); // But it is bad in the opposite direction.
assert!(!remembered.ok_for_0rtt(¤t));
// If the version matches, it's OK to use 0-RTT.
remembered.set(
VersionInformation,
TransportParameter::Versions {
current: Version::Version1.wire_version(),
other: vec![0x5a6a_7a8a, 0x9aaa_baca],
},
);
assert!(current.ok_for_0rtt(&remembered));
assert!(remembered.ok_for_0rtt(¤t));
// An apparent "upgrade" is still cause to reject 0-RTT.
remembered.set(
VersionInformation,
TransportParameter::Versions {
current: Version::Version1.wire_version() + 1,
other: vec![],
},
);
assert!(!current.ok_for_0rtt(&remembered));
assert!(!remembered.ok_for_0rtt(¤t));
}
#[test] fn max_udp_payload_size_boundary() { let min = crate::packet::MIN_INITIAL_PACKET_SIZE as u64;
assert!(decode_tp_integer(MaxUdpPayloadSize, min).is_ok());
assert!(decode_tp_integer(MaxUdpPayloadSize, min - 1).is_err());
}
#[test] fn min_ack_delay_boundary() { // Just below the limit is valid.
assert!(decode_tp_integer(MinAckDelay, (1 << 24) - 1).is_ok()); // At the limit (not strictly less than) is an error.
assert!(decode_tp_integer(MinAckDelay, 1 << 24).is_err());
}
#[test] fn trailing_data_rejected() { // Encode a valid TP, then append an extra byte. letmut enc = Encoder::default();
TransportParameter::Integer(42).encode(&mut enc, IdleTimeout); // Corrupt the vvec length to include an extra zero byte. letmut raw: Vec<u8> = enc.into();
raw[1] += 1; // Increase vvec length by 1 (second byte is the length).
raw.push(0xff); // Extra byte. let err = TransportParameter::decode(&mut Decoder::from(&raw[..])).unwrap_err();
assert_eq!(err, Error::TooMuchData);
}
#[test] fn preferred_address_max_len_cid() { // A preferred address with exactly MAX_LEN-byte CID should be valid. let cid = ConnectionId::from(&[0xab; ConnectionId::MAX_LEN]); let spa = mutate_spa(|_, _, cid_out| *cid_out = cid);
assert_valid_spa(&spa);
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.