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.
|
- No statistics, CLI or presentation concerns.
|
||||||
- `SocketAddr` belongs here: endpoint identity is session state. It does not
|
- `SocketAddr` belongs here: endpoint identity is session state. It does not
|
||||||
belong in `irtt-proto`.
|
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
|
## Core versus runtime
|
||||||
|
|
||||||
|
|||||||
@@ -245,14 +245,30 @@ impl ServerCore {
|
|||||||
|
|
||||||
/// Whether two source endpoints identify the same session endpoint.
|
/// Whether two source endpoints identify the same session endpoint.
|
||||||
///
|
///
|
||||||
/// Identity is the address family, the IP address, the UDP source port and —
|
/// The address family, the IP address and the UDP source port are established
|
||||||
/// for IPv6 — the scope identifier. That is the whole list, so this is not
|
/// identity components: a request from a different port, address or family is
|
||||||
/// simply `==`: [`SocketAddrV6`] equality also covers the flow label, which
|
/// dropped and leaves its session live.
|
||||||
/// 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
|
/// The remaining two fields of [`SocketAddrV6`] are decided here rather than
|
||||||
/// when asked to, so admitting it into identity could strand a session that its
|
/// observed, which is why this is not simply `==`.
|
||||||
/// 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 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
|
/// [`SocketAddrV6`]: std::net::SocketAddrV6
|
||||||
fn same_endpoint(session: SocketAddr, peer: SocketAddr) -> bool {
|
fn same_endpoint(session: SocketAddr, peer: SocketAddr) -> bool {
|
||||||
|
|||||||
@@ -27,11 +27,17 @@ impl Session {
|
|||||||
|
|
||||||
/// The exact source endpoint the open request arrived from.
|
/// The exact source endpoint the open request arrived from.
|
||||||
///
|
///
|
||||||
/// A session is bound to this endpoint, not merely to its address: address
|
/// A session is bound to this endpoint, not merely to its address: the
|
||||||
/// family, address, UDP source port and — for IPv6 — the scope identifier
|
/// address family, the address and the UDP source port all form part of its
|
||||||
/// all form part of its identity. Close processing requires both a token
|
/// identity, as — by `irtt-rs` policy rather than observed behavior — does
|
||||||
/// match and an exact endpoint match before removing a session, and echo
|
/// the IPv6 scope identifier. Close processing requires both a token match
|
||||||
/// processing will require the same.
|
/// 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 {
|
pub(crate) fn peer(&self) -> SocketAddr {
|
||||||
self.peer
|
self.peer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,8 +91,11 @@ fn an_echo_after_a_close_is_a_stale_token() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_close_from_any_other_endpoint_leaves_the_session_live() {
|
fn a_close_from_any_other_endpoint_leaves_the_session_live() {
|
||||||
// Endpoint identity is exact: family, address, UDP source port and IPv6
|
// Possessing the token is not authority to close. The first four pairs
|
||||||
// scope all count. Possessing the token is not authority to close.
|
// 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 [
|
for (bound, foreign) in [
|
||||||
("198.51.100.7:41234", "198.51.100.7:41235"),
|
("198.51.100.7:41234", "198.51.100.7:41235"),
|
||||||
("198.51.100.7:41234", "198.51.100.9:41234"),
|
("198.51.100.7:41234", "198.51.100.9:41234"),
|
||||||
|
|||||||
Reference in new issue
Block a user