docs(server): record IPv6 zone identity as project policy
Endpoint identity documented the IPv6 scope identifier as an established component alongside family, address and port. The server specification does list the zone, 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. Presenting the choice as verified compatibility overstated what is known, which the crate's own guidance forbids. Say what it is instead: a deliberate policy taken under a recorded unknown. 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, and the failure it risks is a scoped client having to reopen. The excluded flow label is labelled the same way, and `irtt-server` guidance now carries the rule so the echo slice reuses this comparison rather than inventing a second one. Behavior is unchanged; this is documentation and a test comment.
This commit is contained in:
4 files changed
+48
-15
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