feat: add selectable allocation-based privacy relay

This commit is contained in:
Alexander Sellite 2026-09-01 10:21:34 -04:00
parent e979da9e76
commit 46de16db00
14 changed files with 1067 additions and 43 deletions

View file

@ -1,16 +1,17 @@
# Discovery API v1
The API stores public room advertisements as short-lived leases and coordinates direct UDP hole
punching. It does not proxy or relay gameplay traffic. straywild continues to connect to the
returned host and UDP port through ENet.
The API stores public room advertisements as short-lived leases and coordinates UDP hole punching.
Player hosts explicitly advertise either direct or relay routing; dedicated servers must advertise
direct routing. Public room and social-presence responses never contain a host address or port.
Relay-enabled join responses contain only the relay endpoint.
All request and response bodies use `application/json`. Production clients must use HTTPS.
## `GET /health`
Returns service status, package version, configured build revision, and the number of active,
unexpired room leases. Deployments should set `straywild_DISCOVERY_BUILD_REVISION` to the exact
Git commit they are running. The package version follows the coordinated straywild release tag.
Returns service status, package version, configured build revision, active room count, whether the
relay is enabled, and the active relay allocation count. Deployments should set
`straywild_DISCOVERY_BUILD_REVISION` to the exact Git commit they are running.
## `GET /v1/rooms`
@ -19,7 +20,9 @@ Lists active rooms. Optional exact-match filters:
- `game_version`
- `protocol_version`
The response contains `rooms` and the server's `ttl_seconds`.
The response contains `rooms` and the server's `ttl_seconds`. Each room includes metadata plus
either `connection_mode: "relay"` with `ip_privacy: "relayed"`, or an explicitly configured
`direct` / `peer_visible` pair. It intentionally omits `address` and `port`.
## `POST /v1/rooms`
@ -32,13 +35,18 @@ trusting an address supplied by the game client.
"port": 7777,
"current_players": 0,
"max_players": 8,
"game_version": "0.20.2-alpha",
"protocol_version": 12
"game_version": "0.20.3-alpha",
"protocol_version": 12,
"host_kind": "player",
"connection_mode": "relay"
}
```
`current_players` may be zero for an empty dedicated server. A player-hosted
room normally includes its host in this count.
room normally includes its host in this count. `host_kind` accepts `player` or
`dedicated`. Player rooms may request `direct` or `relay`; dedicated rooms must
request `direct`. A dedicated relay advertisement, or a relay advertisement
when relay service is unavailable, is rejected with `400 invalid_room`.
The `201` response includes the room, a secret `lease_token`, and an endpoint verification token.
The host sends the verification token to the UDP rendezvous from its bound ENet socket. Rooms are
@ -69,8 +77,18 @@ but TTL expiry remains authoritative for crashes and lost connectivity.
## `POST /v1/rooms/{room_id}/join-attempts`
Creates a short-lived traversal token for a public room. The joining game sends that token to the
UDP rendezvous from the same ENet socket used for the gameplay connection.
Creates a short-lived join capability for a public room. For a relay route, the joining game sends
the capability to its allocated relay from the same ENet socket used for gameplay. The allocation
then accepts traffic only from that authenticated client socket and the verified host endpoint.
For an explicitly configured direct route, the capability retains the older rendezvous behavior.
The `201` response also contains a short-lived `route` with `transport`, `address`, `port`, and
`ip_privacy`. For a relay room, these are the relay's public address and unique allocation port;
the host endpoint is never returned. For a direct room, this is the only unauthenticated response
that discloses the verified host endpoint, and it is issued only after an explicit Join. Responses
are marked `Cache-Control: no-store`. Join issuance is limited per source address, per room,
globally, and by concurrent pending count. A relay allocation failure returns
`503 relay_unavailable`; it never falls back to direct.
## `GET /v1/rooms/{room_id}/join-attempts`
@ -109,10 +127,24 @@ live straywild session.
## Trust boundary
- The UDP rendezvous observation is authoritative for a room's public address and port.
- The UDP rendezvous observation is authoritative for a room's private join address and port.
- Public browse, presence, and invitation payloads never contain endpoints.
- Endpoint-bearing join routes are short-lived and must never be shown, logged, or persisted by
clients. Relay routes contain no peer endpoint.
- `X-Forwarded-For` is honored only when the immediate peer belongs to an explicitly configured
trusted proxy CIDR.
- Lease tokens authorize update and deletion but are never included in public listings.
- Friend capability channels are short-lived and reveal neither identity fingerprints nor a full
social graph to the service.
- The service is an ephemeral directory, not a source of gameplay or friendship authority.
- The service emits no HTTP request/access log. It does not log raw addresses, ports,
address-derived pseudonyms, request paths tied to rooms, names, payloads, headers, tokens,
authentication material, or friendship capabilities. Unexpected request failures produce only
a generic operational error without peer or request metadata.
- Peer endpoints exist only in volatile room, join, abuse-limit, and relay state for as long as
needed to provide the service. They are never written to an application log or database.
- A production reverse proxy must have access logging disabled for all discovery routes. Default
proxy logs commonly contain raw client addresses and violate this service's privacy policy.
- The relay necessarily holds peer endpoints in volatile memory while an allocation is active.
Allocations require a capability from the joining ENet socket, accept host traffic only from the
verified endpoint, enforce traffic budgets, and expire when idle.