feat(server): handle client close #75
No files matched your search
@@ -25,6 +25,14 @@ material does not establish a semantic, state the ambiguity instead of guessing.
|
||||
- No statistics, CLI or presentation concerns.
|
||||
- `SocketAddr` belongs here: endpoint identity is session state. It does not
|
||||
belong in `irtt-proto`.
|
||||
- Endpoint identity is compared field by field, not with `SocketAddr` equality.
|
||||
Family, address and port are established components. The IPv6 flow label is
|
||||
excluded because it identifies no endpoint; the IPv6 zone is included as a
|
||||
deliberate project policy, because the specification records multi-zone
|
||||
behavior as untested and two peers sharing a link-local address in different
|
||||
zones are genuinely different peers. Reuse that one comparison rather than
|
||||
writing a second identity rule, and keep both choices labelled as policy
|
||||
rather than as verified compatibility.
|
||||
|
||||
## Core versus runtime
|
||||
|
||||
|
||||
@@ -245,14 +245,30 @@ impl ServerCore {
|
||||
|
||||
/// Whether two source endpoints identify the same session endpoint.
|
||||
///
|
||||
/// Identity is the address family, the IP address, the UDP source port and —
|
||||
/// for IPv6 — the scope identifier. That is the whole list, so this is not
|
||||
/// simply `==`: [`SocketAddrV6`] equality also covers the flow label, which
|
||||
/// identifies no endpoint. It is routing metadata that a sender or a forwarding
|
||||
/// path may legitimately vary within one flow, and a receiver reports it only
|
||||
/// when asked to, so admitting it into identity could strand a session that its
|
||||
/// own client can then never close. It grants nothing in exchange: the token,
|
||||
/// address, port and scope are what an off-path attacker would have to guess.
|
||||
/// The address family, the IP address and the UDP source port are established
|
||||
/// identity components: a request from a different port, address or family is
|
||||
/// dropped and leaves its session live.
|
||||
///
|
||||
/// The remaining two fields of [`SocketAddrV6`] are decided here rather than
|
||||
/// observed, which is why this is not simply `==`.
|
||||
///
|
||||
/// The flow label is excluded. It identifies no endpoint — it is routing
|
||||
/// metadata a sender or forwarding path may legitimately vary within one flow,
|
||||
/// and a receiver reports it only when asked to — so admitting it could strand
|
||||
/// a session its own client can then never close. It grants nothing in
|
||||
/// exchange: the token, address, port and scope are what an off-path attacker
|
||||
/// would have to guess.
|
||||
///
|
||||
/// The scope identifier is included, and that is **`irtt-rs` policy, not
|
||||
/// verified compatibility behavior**. The server specification lists the IPv6
|
||||
/// zone as an identity component, but its remaining-unknowns section records
|
||||
/// that multi-zone behavior could not be tested on a single-host platform, so
|
||||
/// no evidence settles how a reference server compares scoped addresses. Two
|
||||
/// peers reachable at the same link-local address in different zones are
|
||||
/// genuinely different peers, so treating the zone as identity is the
|
||||
/// conservative reading: the failure it prevents is one peer closing another's
|
||||
/// session, while the failure it risks is a scoped client having to reopen.
|
||||
/// Revisit if evidence ever settles the question.
|
||||
///
|
||||
/// [`SocketAddrV6`]: std::net::SocketAddrV6
|
||||
fn same_endpoint(session: SocketAddr, peer: SocketAddr) -> bool {
|
||||
|
||||
@@ -27,11 +27,17 @@ impl Session {
|
||||
|
||||
/// The exact source endpoint the open request arrived from.
|
||||
///
|
||||
/// A session is bound to this endpoint, not merely to its address: address
|
||||
/// family, address, UDP source port and — for IPv6 — the scope identifier
|
||||
/// all form part of its identity. Close processing requires both a token
|
||||
/// match and an exact endpoint match before removing a session, and echo
|
||||
/// processing will require the same.
|
||||
/// A session is bound to this endpoint, not merely to its address: the
|
||||
/// address family, the address and the UDP source port all form part of its
|
||||
/// identity, as — by `irtt-rs` policy rather than observed behavior — does
|
||||
/// the IPv6 scope identifier. Close processing requires both a token match
|
||||
/// and an endpoint match before removing a session, and echo processing
|
||||
/// will require the same; `same_endpoint` in `core` is where the comparison
|
||||
/// and its reasoning live.
|
||||
///
|
||||
/// The address is stored exactly as received, including the flow label that
|
||||
/// identity ignores, so a reply goes back to the endpoint the request
|
||||
/// actually came from.
|
||||
pub(crate) fn peer(&self) -> SocketAddr {
|
||||
self.peer
|
||||
}
|
||||
|
||||
@@ -91,8 +91,11 @@ fn an_echo_after_a_close_is_a_stale_token() {
|
||||
|
||||
#[test]
|
||||
fn a_close_from_any_other_endpoint_leaves_the_session_live() {
|
||||
// Endpoint identity is exact: family, address, UDP source port and IPv6
|
||||
// scope all count. Possessing the token is not authority to close.
|
||||
// Possessing the token is not authority to close. The first four pairs
|
||||
// differ in an established identity component; the last differs only in
|
||||
// IPv6 zone, which is `irtt-rs` policy rather than observed behavior — see
|
||||
// `same_endpoint`. It is pinned so the choice cannot drift silently, not
|
||||
// because evidence settled it.
|
||||
for (bound, foreign) in [
|
||||
("198.51.100.7:41234", "198.51.100.7:41235"),
|
||||
("198.51.100.7:41234", "198.51.100.9:41234"),
|
||||
|
||||
Reference in new issue
Block a user