Skip to content

feat(s2n-quic-dc): Add UPS replay to path secret map#3089

Open
serdarhas wants to merge 1 commit into
aws:mainfrom
serdarhas:serdarsh/dc-ups-emission
Open

feat(s2n-quic-dc): Add UPS replay to path secret map#3089
serdarhas wants to merge 1 commit into
aws:mainfrom
serdarhas:serdarsh/dc-ups-emission

Conversation

@serdarhas

Copy link
Copy Markdown

Release Summary:

This feature is to add UnknownPathSecret (UPS) emission during startup of s2n-quic server to reset client-side path secret Entries

Description of changes:

Adds a new replay_unknown_path_secrets method to s2n-quic map, and adds a new builder option to inject an observer to move client persistence outside of s2n-quic.

Persistence is linked to the map cleaner loop where we can save snapshots and diffs to file through the observer. replay_unknown_path_secrets loops through a series of entries, constructs a UPS packet, and sends it to the client.

This feature relies on clients having should_evict_on_unknown_path_secret enabled.

Call-outs:

The implementation here pulls out the persistance logic for external libraries to deal with. This was chosen instead of the subscriber to de-couple persistence from the handshake hot-path, and to avoid having s2n-quic take on additional functionality to write to disk as a networking library.

Testing:

Unit tests + external library tests.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@serdarhas serdarhas closed this May 19, 2026
@serdarhas serdarhas force-pushed the serdarsh/dc-ups-emission branch from 7257b38 to 58dc96f Compare May 19, 2026 22:13
@serdarhas serdarhas reopened this May 19, 2026
@serdarhas serdarhas force-pushed the serdarsh/dc-ups-emission branch from c0fb146 to eb72730 Compare May 19, 2026 22:33
@mehnazyunus

mehnazyunus commented May 22, 2026

Copy link
Copy Markdown
Member

The implementation here pulls out the persistance logic for external libraries to deal with.

I'm confused, doesn't this PR implement the persistence logic?
Wasn't the subscriber also intended to have the persistence off the handshake hot path? Could you explain the decision here?

@serdarhas serdarhas marked this pull request as ready for review June 1, 2026 21:48
@serdarhas serdarhas requested review from a team as code owners June 1, 2026 21:48
let rehandshake_period = Duration::from_secs(3600 * 24);

let persistence = persistence_directory.and_then(|dir| {
match super::persistence::PersistenceStore::new(dir) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be enforcing that each Map uses a different directory for persistence?

Comment thread dc/s2n-quic-dc/src/path/secret/map.rs Outdated
C: 'static + time::Clock + Send + Sync,
S: event::Subscriber,
{
let signer = self.signer.unwrap_or_else(stateless_reset::Signer::random);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think falling back to the random Signer will work as expected (will not work across restarts, see

/// Note that this signer cannot be used across restarts and will result in an endpoint
). While the fallback scenario is probably unlikely, I think build here should enforce passing in a signer by returning a Result.

Comment thread dc/s2n-quic-dc/src/path/secret/map.rs Outdated
builder = builder.with_persistence_directory(dir);
}

let store = builder.build().unwrap();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, this should return a Result instead of unwrapping.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, just noticed that Map::new also unwraps, not sure if that should also be fixed or if there was some rationale.

match super::persistence::PersistenceStore::new(dir) {
Ok(store) => Some(store),
Err(e) => {
tracing::warn!("persistence: failed to initialize store: {:?}", e);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will we be adding events here for observability into failures? Not sure if that is planned as a follow up.

}

*last = current.keys().copied().collect();
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be fsync-ing the journal too?

@@ -0,0 +1,341 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can we follow the convention in the repo of having a named module file (persistence.rs, here) and only the test file in the module folder? See map/rehandshake for example.

@serdarhas serdarhas force-pushed the serdarsh/dc-ups-emission branch from eb72730 to 9bc18fb Compare June 2, 2026 22:03
Capture the live (credential_id, peer) set during the cleaner's existing
eviction walk and write it to a per-Map snapshot file. At startup, a
free-function replay() walks the persistence directory, dedupes by
credential_id, and emits a rate-limited UnknownPathSecret to each peer so
clients re-handshake before the server admits application traffic.

- persistence.rs: Writer (tmp + fsync + atomic rename, orphan-tmp sweep)
  and replay() primitive; per-Map peers-<pid>-<n>.txt plaintext format.
- cleaner.rs: capture retained entries inline in queue.retain every
  PERSISTENCE_CYCLES-th cycle; no extra map walk or lock.
- state.rs/store.rs/map.rs: thread peer_persistence_dir through the
  builder; test_run_cleaner hook for deterministic snapshot tests.

Replaces the earlier snapshot/journal spike with a single fsynced
snapshot file (no journal), a named module file (persistence.rs) per repo
convention, and per-Map file naming to avoid directory collisions.
@serdarhas serdarhas force-pushed the serdarsh/dc-ups-emission branch from 9bc18fb to 7f76965 Compare June 3, 2026 17:07
)
}

pub fn new_with_persistence<C, S>(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than creating a bunch of new methods for this, we should change it to just take a State::builder. Having a proliferation of one-off methods with extra arguments isn't going to scale.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants