feat: implement authenticated NAT mediator (#11)
quality-gate / quality (push) Successful in 59s
quality-gate / quality (push) Successful in 59s
Closes #11
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
# UDP presence contract v1
|
||||
# UDP presence and NAT-punch contract v1
|
||||
|
||||
Tracking: #4
|
||||
Tracking: #4, #11
|
||||
|
||||
The UDP mediator accepts a single bounded presence envelope from a host or
|
||||
client. It associates the authenticated mediation handle with the packet's
|
||||
observed public source endpoint and the sender's reported local endpoint. It
|
||||
does not carry gameplay packets.
|
||||
The UDP mediator accepts the frozen bounded presence envelope below and native
|
||||
LiteNetLib NAT-introduction requests. Both forms associate an authenticated
|
||||
mediation handle with the packet's observed public source endpoint and the
|
||||
sender's reported local endpoint. Neither form carries gameplay packets.
|
||||
|
||||
All multi-byte integers use network byte order. UUID bytes use the canonical
|
||||
RFC 4122 textual order (the byte pairs from the 32 hexadecimal digits), not the
|
||||
@@ -49,5 +49,48 @@ Capabilities are short-lived, single-purpose, scoped to one mediation handle,
|
||||
and compared without exposing them in logs. A valid-looking packet does not
|
||||
prove authorization until the capability is checked. Invalid packets receive
|
||||
no UDP response, preventing the mediator from becoming an amplification oracle.
|
||||
Replay, expiry, pairing, and rate-limit policy are defined by later mediator
|
||||
issues; the v1 envelope deliberately leaves no unbounded or reflected payload.
|
||||
For the frozen envelope, `HostPresence` is resolved against either the listing's
|
||||
host-presence capability or an attempt's host-role capability. `ClientPresence`
|
||||
is resolved only against the attempt's client-role capability. Handles are
|
||||
globally distinct in the active store, so this does not permit role confusion.
|
||||
|
||||
## Native LiteNetLib request token
|
||||
|
||||
A game using LiteNetLib sends `NatPunchModule.SendNatIntroduceRequest` from its
|
||||
gameplay `NetManager`. The `additionalInfo` value is produced by
|
||||
`NatPunchRequestTokenCodec` and is exactly 192 ASCII characters:
|
||||
|
||||
```text
|
||||
rv1:<role>:<32 lowercase handle hex>:<43-character capability><dot padding>
|
||||
```
|
||||
|
||||
`role` is `p` for listing host-presence refresh, `h` for the host side of a join
|
||||
attempt, or `c` for its client side. Padding is canonical and leaves the token
|
||||
below LiteNetLib's 256-character ceiling. Its fixed size also ensures that the
|
||||
two authenticated introduction responses remain within the 2.0 response-byte
|
||||
budget. Tokens with a wrong length, role, handle, capability, or padding receive
|
||||
no response.
|
||||
|
||||
The mediator runs LiteNetLib in bounded manual-poll mode. Its packet layer admits
|
||||
only the pinned native `NatIntroduceRequest` frame, consumes every inbound frame
|
||||
before LiteNetLib can act on it, and uses `NatPunchModule` only to emit authorized
|
||||
introductions. Native and frozen v1 inputs reach the same atomic role/capability
|
||||
checks. Only the packet source is
|
||||
used as the public endpoint. A claimed private candidate is retained only when
|
||||
it is private unicast, matches the observed address family, and both authorized
|
||||
peers were observed behind the same public address; otherwise the observed
|
||||
public endpoint is substituted. IPv4 punching is required. IPv6 sources must be
|
||||
observed global unicast and both roles must use IPv6; IPv6 NAT traversal remains
|
||||
best-effort rather than a v1 release requirement.
|
||||
|
||||
The second valid contribution atomically consumes the introduction and starts
|
||||
the connection-ticket lifetime. `NatIntroduce` is called once with the distinct
|
||||
43-character connection ticket. Reordered and exact duplicate requests are
|
||||
idempotent. Endpoint substitution, cross-role use, stale host presence, expired
|
||||
or cancelled attempts, malformed packets, and gameplay payloads produce no
|
||||
introduction and create no mediator queue or endpoint state.
|
||||
|
||||
Frozen envelopes may refresh listing presence over IPv6 because that operation
|
||||
has no response. IPv6 attempt contributions must use the fixed-size native token;
|
||||
the shorter frozen IPv6 envelope cannot fund two IPv6 introduction frames within
|
||||
the 2.0 response-byte ceiling and is therefore dropped without response.
|
||||
|
||||
Reference in New Issue
Block a user