Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions quic/s2n-quic-core/src/connection/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,15 @@ pub trait Generator {
fn lifetime(&self) -> Option<core::time::Duration> {
None
}

/// If true (default), the connection ID used during the the handshake
/// will be requested to be retired following confirmation of the handshake
/// completing. This reduces linkability between information exchanged
/// during and after the handshake.
#[inline]
fn rotate_handshake_connection_id(&self) -> bool {
true
}
}

#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
Expand Down
23 changes: 19 additions & 4 deletions quic/s2n-quic-core/src/event/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,10 @@ pub mod api {
stream_limit: u64,
},
#[non_exhaustive]
NewConnectionId {},
NewConnectionId {
sequence_number: u64,
retire_prior_to: u64,
},
#[non_exhaustive]
RetireConnectionId {},
#[non_exhaustive]
Expand Down Expand Up @@ -1478,7 +1481,10 @@ pub mod api {
impl<'a> IntoEvent<builder::Frame> for &crate::frame::NewConnectionId<'a> {
#[inline]
fn into_event(self) -> builder::Frame {
builder::Frame::NewConnectionId {}
builder::Frame::NewConnectionId {
sequence_number: self.sequence_number.as_u64(),
retire_prior_to: self.retire_prior_to.as_u64(),
}
}
}
impl IntoEvent<builder::Frame> for &crate::frame::RetireConnectionId {
Expand Down Expand Up @@ -2743,7 +2749,10 @@ pub mod builder {
stream_type: StreamType,
stream_limit: u64,
},
NewConnectionId,
NewConnectionId {
sequence_number: u64,
retire_prior_to: u64,
},
RetireConnectionId,
PathChallenge,
PathResponse,
Expand Down Expand Up @@ -2831,7 +2840,13 @@ pub mod builder {
stream_type: stream_type.into_event(),
stream_limit: stream_limit.into_event(),
},
Self::NewConnectionId => NewConnectionId {},
Self::NewConnectionId {
sequence_number,
retire_prior_to,
} => NewConnectionId {
sequence_number: sequence_number.into_event(),
retire_prior_to: retire_prior_to.into_event(),
},
Self::RetireConnectionId => RetireConnectionId {},
Self::PathChallenge => PathChallenge {},
Self::PathResponse => PathResponse {},
Expand Down
10 changes: 8 additions & 2 deletions quic/s2n-quic-events/events/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,10 @@ enum Frame {
stream_type: StreamType,
stream_limit: u64,
},
NewConnectionId,
NewConnectionId {
sequence_number: u64,
retire_prior_to: u64,
},
RetireConnectionId,
PathChallenge,
PathResponse,
Expand Down Expand Up @@ -487,7 +490,10 @@ impl IntoEvent<builder::Frame> for &crate::frame::StreamsBlocked {
impl<'a> IntoEvent<builder::Frame> for &crate::frame::NewConnectionId<'a> {
#[inline]
fn into_event(self) -> builder::Frame {
builder::Frame::NewConnectionId {}
builder::Frame::NewConnectionId {
sequence_number: self.sequence_number.as_u64(),
retire_prior_to: self.retire_prior_to.as_u64(),
}
}
}

Expand Down
20 changes: 16 additions & 4 deletions quic/s2n-quic-transport/src/connection/connection_id_mapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,13 +319,15 @@ impl ConnectionIdMapper {
initial_connection_id: &connection::LocalId,
initial_connection_id_expiration_time: Option<Timestamp>,
local_stateless_reset_token: stateless_reset::Token,
rotate_handshake_connection_id: bool,
) -> LocalIdRegistry {
LocalIdRegistry::new(
internal_id,
self.state.clone(),
initial_connection_id,
initial_connection_id_expiration_time,
local_stateless_reset_token,
rotate_handshake_connection_id,
)
}

Expand All @@ -338,8 +340,13 @@ impl ConnectionIdMapper {
&mut self,
internal_id: InternalConnectionId,
initial_connection_id: connection::PeerId,
rotate_handshake_connection_id: bool,
) -> PeerIdRegistry {
let mut registry = PeerIdRegistry::new(internal_id, self.state.clone());
let mut registry = PeerIdRegistry::new(
internal_id,
self.state.clone(),
rotate_handshake_connection_id,
);

registry.register_initial_connection_id(initial_connection_id);
registry
Expand All @@ -353,8 +360,13 @@ impl ConnectionIdMapper {
pub fn create_client_peer_id_registry(
&mut self,
internal_id: InternalConnectionId,
rotate_handshake_connection_id: bool,
) -> PeerIdRegistry {
PeerIdRegistry::new(internal_id, self.state.clone())
PeerIdRegistry::new(
internal_id,
self.state.clone(),
rotate_handshake_connection_id,
)
}
}

Expand All @@ -371,7 +383,7 @@ mod tests {
let internal_id = InternalConnectionIdGenerator::new().generate_id();
let peer_id = id(b"id01");

let mut registry = mapper.create_client_peer_id_registry(internal_id);
let mut registry = mapper.create_client_peer_id_registry(internal_id, true);
registry.register_initial_connection_id(peer_id);
registry.register_initial_stateless_reset_token(TEST_TOKEN_1);

Expand All @@ -388,7 +400,7 @@ mod tests {
mapper.remove_internal_connection_id_by_stateless_reset_token(&TEST_TOKEN_2)
);

let mut registry = mapper.create_client_peer_id_registry(internal_id);
let mut registry = mapper.create_client_peer_id_registry(internal_id, true);
registry.register_initial_connection_id(peer_id);
registry.register_initial_stateless_reset_token(TEST_TOKEN_3);

Expand Down
17 changes: 16 additions & 1 deletion quic/s2n-quic-transport/src/connection/local_id_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ pub struct LocalIdRegistry {
transmission_interest: Memo<transmission::Interest, RegisteredIds>,
/// Memoized query to track the number of active CIDs
active_id_count: Memo<u8, RegisteredIds>,
/// If true, the connection ID used during the the handshake will be requested
/// to be retired following confirmation of the handshake completing.
rotate_handshake_connection_id: bool,
}

type RegisteredIds = SmallVec<[LocalIdInfo; NR_STATIC_REGISTRABLE_IDS]>;
Expand Down Expand Up @@ -237,6 +240,7 @@ impl LocalIdRegistry {
handshake_connection_id: &connection::LocalId,
handshake_connection_id_expiration_time: Option<Timestamp>,
stateless_reset_token: stateless_reset::Token,
rotate_handshake_connection_id: bool,
) -> Self {
let mut registry = Self {
internal_id,
Expand Down Expand Up @@ -279,6 +283,7 @@ impl LocalIdRegistry {
}
count
}),
rotate_handshake_connection_id,
};

let _ = registry.register_connection_id(
Expand Down Expand Up @@ -615,8 +620,18 @@ impl LocalIdRegistry {
self.check_consistency();
}

/// Invoked when the completion of the handshake has been confirmed
///
/// If `rotate_handshake_connection_id` is enabled, the peer will be
/// requested to retire the connection id used during the handshake.
pub fn on_handshake_confirmed(&mut self) {
if self.rotate_handshake_connection_id {
self.retire_handshake_connection_id()
}
}

/// Requests the peer to retire the connection id used during the handshake
pub fn retire_handshake_connection_id(&mut self) {
fn retire_handshake_connection_id(&mut self) {
if let Some(handshake_id_info) = self
.registered_ids
.iter_mut()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ fn mapper(
&handshake_id,
handshake_id_expiration_time,
token,
true,
);
(mapper, registry)
}
Expand Down Expand Up @@ -152,12 +153,14 @@ fn connection_mapper_test() {
&ext_id_1,
Some(handshake_id_expiration_time),
TEST_TOKEN_1,
true,
);
let mut reg2 = mapper.create_local_id_registry(
id2,
&ext_id_3,
Some(handshake_id_expiration_time),
TEST_TOKEN_3,
true,
);

reg1.set_active_connection_id_limit(3);
Expand Down Expand Up @@ -497,7 +500,7 @@ fn endpoint_may_exceed_limit_temporarily() {
assert!(reg1
.register_connection_id(&ext_id_2, Some(now + EXPIRATION_BUFFER), TEST_TOKEN_2)
.is_ok());
reg1.retire_handshake_connection_id();
reg1.on_handshake_confirmed();
reg1.on_timeout(now + EXPIRATION_BUFFER);

// We can register another ID because the retire_prior_to field retires old IDs
Expand Down Expand Up @@ -807,7 +810,7 @@ fn on_timeout() {
// Timer set for the handshake connection ID
assert_eq!(1, reg1.armed_timer_count());

reg1.retire_handshake_connection_id();
reg1.on_handshake_confirmed();

// Too early, no timer is ready
reg1.on_timeout(now);
Expand Down Expand Up @@ -859,14 +862,15 @@ fn on_timeout() {
}

#[test]
fn retire_handshake_connection_id() {
fn on_handshake_confirmed_rotate_handshake_connection_id_enabled() {
let ext_id_1 = id(b"id01");
let ext_id_2 = id(b"id02");
let ext_id_3 = id(b"id03");

let now = time::now();
let handshake_expiration = now + Duration::from_secs(60);
let (_, mut reg1) = mapper(ext_id_1, Some(handshake_expiration), TEST_TOKEN_1);
assert!(reg1.rotate_handshake_connection_id);

reg1.set_active_connection_id_limit(3);

Expand All @@ -877,7 +881,7 @@ fn retire_handshake_connection_id() {
.register_connection_id(&ext_id_3, None, TEST_TOKEN_3)
.is_ok());

reg1.retire_handshake_connection_id();
reg1.on_handshake_confirmed();

assert_eq!(3, reg1.registered_ids.iter().count());

Expand All @@ -893,8 +897,8 @@ fn retire_handshake_connection_id() {
}
}

// Calling retire_handshake_connection_id again does nothing
reg1.retire_handshake_connection_id();
// Calling on_handshake_confirmed again does nothing
reg1.on_handshake_confirmed();

assert_eq!(3, reg1.registered_ids.iter().count());

Expand Down Expand Up @@ -923,3 +927,32 @@ fn retire_handshake_connection_id() {
}
}
}

#[test]
fn on_handshake_confirmed_rotate_handshake_connection_id_disabled() {
let ext_id_1 = id(b"id01");
let ext_id_2 = id(b"id02");
let ext_id_3 = id(b"id03");

let (_, mut reg1) = mapper(ext_id_1, None, TEST_TOKEN_1);
// Disable rotating handshake connection ID
reg1.rotate_handshake_connection_id = false;

reg1.set_active_connection_id_limit(3);

assert!(reg1
.register_connection_id(&ext_id_2, None, TEST_TOKEN_2)
.is_ok());
assert!(reg1
.register_connection_id(&ext_id_3, None, TEST_TOKEN_3)
.is_ok());

reg1.on_handshake_confirmed();

assert_eq!(3, reg1.registered_ids.iter().count());

for id_info in reg1.registered_ids.iter() {
assert!(!id_info.is_retired());
assert_eq!(None, id_info.retirement_time);
}
}
23 changes: 19 additions & 4 deletions quic/s2n-quic-transport/src/connection/peer_id_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ pub struct PeerIdRegistry {
ack_interest: Memo<bool, RegisteredIds>,
/// Memoized query to track if there is any transmission interest
transmission_interest: Memo<transmission::Interest, RegisteredIds>,
/// If true, the connection ID used during the the handshake will be retired
/// when the peer sends a NEW_CONNECTION_ID frame.
rotate_handshake_connection_id: bool,
}

type RegisteredIds = SmallVec<[PeerIdInfo; NR_STATIC_REGISTRABLE_IDS]>;
Expand Down Expand Up @@ -247,6 +250,7 @@ impl PeerIdRegistry {
pub(crate) fn new(
internal_id: InternalConnectionId,
state: Arc<Mutex<ConnectionIdMapperState>>,
rotate_handshake_connection_id: bool,
) -> Self {
Self {
internal_id,
Expand All @@ -271,6 +275,7 @@ impl PeerIdRegistry {

interest
}),
rotate_handshake_connection_id,
}
}

Expand All @@ -283,15 +288,22 @@ impl PeerIdRegistry {
pub(crate) fn register_initial_connection_id(&mut self, peer_id: connection::PeerId) {
debug_assert!(self.is_empty());

let status = if self.rotate_handshake_connection_id {
// Start the initial PeerId in `InUsePendingNewConnectionId` so the ID used
// during the handshake is rotated as soon as the peer sends a new connection ID
PeerIdStatus::InUsePendingNewConnectionId
} else {
PeerIdStatus::InUse
};

self.registered_ids.push(PeerIdInfo {
id: peer_id,
//= https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1
//# The sequence number of the initial connection ID is 0.
sequence_number: 0,
stateless_reset_token: None,
// Start the initial PeerId in ActivePendingNewConnectionId so the ID used
// during the handshake is rotated as soon as the peer sends a new connection ID
status: PeerIdStatus::InUsePendingNewConnectionId,

status,
});

self.check_consistency();
Expand Down Expand Up @@ -692,7 +704,10 @@ pub mod testing {
let mut random_generator = random::testing::Generator(123);

let mut registry = ConnectionIdMapper::new(&mut random_generator, endpoint::Type::Server)
.create_client_peer_id_registry(InternalConnectionIdGenerator::new().generate_id());
.create_client_peer_id_registry(
InternalConnectionIdGenerator::new().generate_id(),
true,
);
registry.register_initial_connection_id(initial_id);
if let Some(token) = stateless_reset_token {
registry.register_initial_stateless_reset_token(token);
Expand Down
Loading