feat: Implement graceful shutdown#91
Conversation
7d7f3e8 to
77bf722
Compare
algesten
left a comment
There was a problem hiding this comment.
Thanks for looking at this!
A couple of comments and questions. I also see we have some unrelated code cleanup, like moving code around and removing white space, and a change to &mut OsRng. We can keep that in PR, but you need to force push and make "clean" commits I can rebase at the end.
9820a7a to
ebb9d22
Compare
algesten
left a comment
There was a problem hiding this comment.
Thanks!
Much improved with the client/server having the state logic.
This read through I got stuck on insert_incoming and the amount of logic being done there. I see dtls13 has set some pattern that I don't quite feel is right.
82fbb62 to
563ed6a
Compare
HMBSbige
left a comment
There was a problem hiding this comment.
I've extracted the per-record processing out of insert_incoming into dedicated methods:
- dtls12:
extract_alerts()— epoch-aware alert handling + post-close_notify filtering - dtls13:
extract_control_records()— ACK/Alert/CCS + close_notify sequence filtering
insert_incoming is back to capacity-check + dispatch in both versions.
The into_records/from_records decompose-recompose pattern is still there though. I considered moving it into incoming.rs alongside RecordDecrypt, but alert processing needs engine state (peer_encryption_enabled, buffers_free, close_notify_received/close_notify_sequence) which doesn't fit the stateless parsing layer.
If you have a different direction in mind for eliminating the pattern, I'm happy to follow your lead.
|
Thanks for extracting
Roughly: // Extend (or rename) RecordDecrypt. The engine already implements it, so
// engine state (peer_encryption_enabled, buffers_free, close_notify_received, …)
// stays where it belongs — no split.
pub trait RecordHandler: RecordDecrypt /* or fold the methods in */ {
/// Called after a record is parsed and (if applicable) decrypted,
/// before it is added to Records. Return Ok(false) to drop the
/// record; its buffer goes back to the pool via Record::into_buffer.
fn classify(&mut self, record: &Record) -> Result<bool, Error>;
}In let record = match Record::parse(slice, decrypt, cs)? {
Some(r) => r,
None => continue, // already-handled cases: replay, bad header, …
};
if !decrypt.classify(&record)? {
// Control record (Alert / CCS / ACK) — engine consumed it.
continue;
}
records.try_push(record).map_err(|_| Error::TooManyRecords)?;Engine impl then houses what impl RecordHandler for Engine {
fn classify(&mut self, record: &Record) -> Result<bool, Error> {
match record.record().content_type {
ContentType::Alert => { /* epoch-aware alert handling, return Ok(false) */ }
// dtls13 also: Ack => process_ack; ChangeCipherSpec => discard
ContentType::ApplicationData if self.close_notify_received => Ok(false),
_ => Ok(true),
}
}
}Net effect:
One real tradeoff: |
- Remove unnecessary `&mut` on `OsRng` (rust_crypto kx_group) - Replace `if let Err(_)` with idiomatic `.is_err()` (auto cross_matrix) - Inline `self.last_now` and remove extra blank lines in Server::poll_output - Move helper functions before `#[cfg(test)]` per file ordering convention
563ed6a to
10cbdea
Compare
9e44576 to
62628c3
Compare
Add close_notify support for both DTLS 1.2 and DTLS 1.3, implementing
graceful connection shutdown per RFC 5246 §7.2.1 and RFC 9147 §5.10.
DTLS 1.2: close() sends close_notify and transitions to Closed state.
Receiving close_notify triggers a reciprocal alert and discards pending
writes. No half-close support (full close only).
DTLS 1.3: close() sends close_notify and enters HalfClosedLocal state
where the read half remains open. Receiving close_notify while
half-closed transitions to Closed. Incoming KeyUpdate messages are
still processed (recv keys updated) but no outgoing records are sent.
Engine tracks close_notify at the record layer (filtering app data
after the alert sequence), while client/server handle connection state
and Output::CloseNotify emission.
Error::ConnectionClosed replaces SecurityError("connection closed") for
send_application_data on closed connections.
62628c3 to
8ff6ef4
Compare
|
The signature is |
algesten
left a comment
There was a problem hiding this comment.
Thanks for taking it all the way!
Closes #72
Code cleanup (commit 1)
&mutonOsRngin rust_crypto kx_groupif let Err(_)with idiomatic.is_err()in auto cross_matrix testsself.last_nowand remove extra blank lines inServer::poll_output(dtls12)#[cfg(test)]per file ordering convention (dtls12 server)close_notify implementation (commit 2)
Dtls::close()API for graceful connection shutdown viaclose_notifyalertOutput::CloseNotifyvariant to signal peer-initiated closure to the applicationError::ConnectionClosedfor send attempts on closed connectionsclose_notifyper RFC 5246 §7.2.1HalfClosedLocal(write-closed, read-open) per RFC 8446 §6.1; receiver filters records by sequence number per RFC 9147 §5.10Output::CloseNotifyemissioncomplete_dtls12_handshake,setup_connected_12_pair,complete_dtls13_handshake,setup_connected_13_pair) from edge.rs into common.rs and consolidate importsOutput::CloseNotifydocumentation and example