feat(server): handle client close
Release the session a client close names. A close is bound to a session only when the token matches and the datagram arrived from that session's exact endpoint, so holding a token is not by itself authority to tear the session down: a close from a foreign source port, address, family or IPv6 scope leaves the session live, and one close cannot take down a sibling session the same peer opened separately. A token that is unknown, already closed or zero matches nothing and leaves the table untouched; zero is reserved for no-test replies and never issued. Nothing is sent either way. Protocol version 1 defines no acknowledgement for a client close, so a successful close returns the same `Ok(None)` as a rejected one and a delivered close is indistinguishable to the client from a lost one. A standalone `Reply|Close` datagram is tolerated by clients but has no demonstrated use, so none is encoded. Authentication still runs before the session is looked up. That keeps a bad MAC on a live token indistinguishable from an unknown token, and means a close that fails authentication — missing, wrongly keyed, corrupted, or unexpected on a server with no key — cannot remove a session. Trailing bytes after the token stay acceptable: the structural decoder already tolerates them and the server does not re-parse the request. The session table remains the only record of how full the server is, so a close frees capacity immediately and a refused open can succeed once one is released. Close draws no token and touches no other state; the endpoint lookup borrows the table only until the comparison is made, so the removal needs no clone. Echo remains ignored. The test that pinned close as a no-op is replaced by `tests/close.rs`, which covers removal, silence, repeated close, echo after close, endpoint binding, unknown/zero/stale tokens, authentication failure, trailing bytes, sibling sessions and capacity reuse; `unsupported.rs` keeps echo and structurally invalid traffic.
This commit is contained in:
8 files changed
+380
-54
No files matched your search
@@ -72,6 +72,31 @@ pub(crate) fn core_with_tokens(config: ServerConfig, tokens: ScriptedTokens) ->
|
||||
ServerCore::with_token_source(config, Box::new(tokens))
|
||||
}
|
||||
|
||||
/// An otherwise default core that authenticates iff `hmac_key` is set.
|
||||
///
|
||||
/// Most behavior has to hold identically with and without authentication, so
|
||||
/// tests loop over both and build the core from the key.
|
||||
pub(crate) fn core_for(hmac_key: Option<&[u8]>, tokens: ScriptedTokens) -> ServerCore {
|
||||
let mut config = ServerConfig::default();
|
||||
if let Some(key) = hmac_key {
|
||||
config = config.with_hmac_key(key);
|
||||
}
|
||||
core_with_tokens(config, tokens)
|
||||
}
|
||||
|
||||
/// Opens one ordinary session from `endpoint` and returns its token.
|
||||
pub(crate) fn open_session(
|
||||
core: &mut ServerCore,
|
||||
endpoint: SocketAddr,
|
||||
hmac_key: Option<&[u8]>,
|
||||
) -> u64 {
|
||||
let packet = core
|
||||
.handle_datagram(endpoint, &open_request(&client_params(), hmac_key))
|
||||
.unwrap()
|
||||
.expect("the session-creating open must be answered");
|
||||
expect_normal_open_reply(&packet, hmac_key).token
|
||||
}
|
||||
|
||||
/// Encodes a normal open request carrying `params`.
|
||||
pub(crate) fn open_request(params: &Params, hmac_key: Option<&[u8]>) -> Vec<u8> {
|
||||
encode_request(
|
||||
@@ -96,6 +121,29 @@ pub(crate) fn no_test_request(params: &Params, hmac_key: Option<&[u8]>) -> Vec<u
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Encodes a close request for `token`.
|
||||
pub(crate) fn close_request(token: u64, hmac_key: Option<&[u8]>) -> Vec<u8> {
|
||||
encode_request(RequestToEncode::Close { token }, hmac_key).unwrap()
|
||||
}
|
||||
|
||||
/// Encodes a close request carrying arbitrary bytes after the token.
|
||||
///
|
||||
/// The decoder tolerates them, and this is the shape that proves it. The MAC
|
||||
/// covers the whole datagram, so it is recomputed over the final form rather
|
||||
/// than left behind by the encoder's shorter packet.
|
||||
pub(crate) fn close_request_with_trailing(
|
||||
token: u64,
|
||||
trailing: &[u8],
|
||||
hmac_key: Option<&[u8]>,
|
||||
) -> Vec<u8> {
|
||||
let mut packet = close_request(token, hmac_key);
|
||||
packet.extend_from_slice(trailing);
|
||||
if let Some(key) = hmac_key {
|
||||
irtt_proto::compute_hmac_in_place(key, &mut packet, hmac_field_offset()).unwrap();
|
||||
}
|
||||
packet
|
||||
}
|
||||
|
||||
/// Builds an open request around a raw parameter payload.
|
||||
///
|
||||
/// Used for payloads a compliant encoder cannot produce, such as a truncated
|
||||
|
||||
Reference in new issue
Block a user