chore: retarget upstream portal-tunnel v2.6.4 #116

Merged
ada merged 15 commits from chore/retarget-v2.6.4 into master 2026-10-15 07:53:34 +00:00
Collaborator

Retarget the Rust relay from upstream portal-tunnel v2.6.3 to v2.6.4 (tag commit 0a3b9c06236581be9451ea7f7bd83140d0eeb155). Tunnel protocol 10 and discovery protocol 9 are unchanged.

Ported relay contract (v2.6.4)

  • Register SIWE challenges bind the domain and /sdk/register URI to the validated request Host. v2.6.4 clients regenerate the message from the relay URL they dialed and reject any mismatch. The message format is pinned to vectors from upstream FormatSIWEMessage.
  • Overlay reverse endpoints carry the signed gateway descriptor, and direct endpoints do not carry one, as v2.6.4 clients require.
  • Lease bounds: TTL above ten minutes is rejected (official clients request 120 s), lease metadata and register-challenge bodies (64 KiB) are bounded, register challenges are capped globally, and rejected registrations release their ports.
  • Discovery: public-IP and local-only host policy, bounded descriptors, public HTTPS relay URLs only, and GET /discovery responses capped at 1 MiB.
  • Transport hardening: UDP flows and datagram sizes are bounded per lease, QUIC backhaul authentication is bounded per source and globally, public ingress and lease bridges are bounded, revocation cancels in-flight bridges, and control request bodies have an absolute read deadline.
  • Static frontend files stay within the frontend root.

Release metadata

  • Workspace version 2.6.4.
  • Browser WASM/JS assets are retargeted to v2.6.4 and checksum-pinned from the official checksums.txt.
  • The image tag gate accepts the v2.6.4 core.

Not ported (documented in docs/unsupported-features.md)

  • IVNP v0.1.1 pin: the helper keeps the v2.6.0 revision. Overlay stays opt-in and is not deployed.
  • Static relay cache upload admission: the Rust relay rejects CACHE_ENABLED=true, so there is no cache endpoint to bound.
  • Partial matches (QUIC accounting point, discovery dial-timeout split, HTTP/1 control-body handling) are described in the support matrix.

Verification

  • cargo fmt --check
  • cargo clippy --workspace --all-targets --locked -- -D warnings
  • cargo test --workspace --locked
  • bash scripts/test-container-image-tag.sh
  • Public live gate to follow on the exact published dev image (v2.6.4, v2.6.3, and v2.6.1 official clients, plus v2.6.4 browser/WASM).
Retarget the Rust relay from upstream portal-tunnel v2.6.3 to v2.6.4 (tag commit `0a3b9c06236581be9451ea7f7bd83140d0eeb155`). Tunnel protocol 10 and discovery protocol 9 are unchanged. ## Ported relay contract (v2.6.4) - Register SIWE challenges bind the domain and `/sdk/register` URI to the validated request `Host`. v2.6.4 clients regenerate the message from the relay URL they dialed and reject any mismatch. The message format is pinned to vectors from upstream `FormatSIWEMessage`. - Overlay reverse endpoints carry the signed gateway descriptor, and direct endpoints do not carry one, as v2.6.4 clients require. - Lease bounds: TTL above ten minutes is rejected (official clients request 120 s), lease metadata and register-challenge bodies (64 KiB) are bounded, register challenges are capped globally, and rejected registrations release their ports. - Discovery: public-IP and local-only host policy, bounded descriptors, public HTTPS relay URLs only, and `GET /discovery` responses capped at 1 MiB. - Transport hardening: UDP flows and datagram sizes are bounded per lease, QUIC backhaul authentication is bounded per source and globally, public ingress and lease bridges are bounded, revocation cancels in-flight bridges, and control request bodies have an absolute read deadline. - Static frontend files stay within the frontend root. ## Release metadata - Workspace version 2.6.4. - Browser WASM/JS assets are retargeted to v2.6.4 and checksum-pinned from the official `checksums.txt`. - The image tag gate accepts the v2.6.4 core. ## Not ported (documented in docs/unsupported-features.md) - IVNP v0.1.1 pin: the helper keeps the v2.6.0 revision. Overlay stays opt-in and is not deployed. - Static relay cache upload admission: the Rust relay rejects `CACHE_ENABLED=true`, so there is no cache endpoint to bound. - Partial matches (QUIC accounting point, discovery dial-timeout split, HTTP/1 control-body handling) are described in the support matrix. ## Verification - `cargo fmt --check` - `cargo clippy --workspace --all-targets --locked -- -D warnings` - `cargo test --workspace --locked` - `bash scripts/test-container-image-tag.sh` - Public live gate to follow on the exact published dev image (v2.6.4, v2.6.3, and v2.6.1 official clients, plus v2.6.4 browser/WASM).
ada added 15 commits 2026-10-15 07:48:50 +00:00
Port utils.IsPublicIP and utils.IsLocalOnlyRelayHost from upstream
v2.6.4 with table tests whose expected values were generated by running
the pinned upstream Go functions.
Port the relay-side discovery hardening from upstream v2.6.4:

- requirePublicRelayURL: discovered and announced relay URLs must be
  https with a host, no userinfo, not local-only, and any IP literal must
  be public. Gossiped descriptors failing the policy are dropped unless
  they are the polled target itself and an explicitly configured
  bootstrap. Announces now reject private and documentation literals.
- Non-bootstrap discovery dials resolve DNS and keep only public IPs,
  failing when none remain; untrusted clients use no proxy. Discovery
  clients (untrusted, bootstrap and pinned local) reject redirects.
- Descriptor URL/destination/signature byte bounds are checked before
  any other normalization or base64 work, and the URL is re-checked
  after normalization.
- Fetched discovery responses are capped at 1 MiB and at 1025 relays /
  1024 incompatible entries. Incompatible-relay visibility is bounded at
  1024 entries (oldest evicted, ties on the smaller URL) and labels over
  128 bytes are recorded as empty.
- Discovery responses list the serving relay first, as upstream
  Descriptors(self) has done since v2.6.3, so byte-capped prefixes keep
  the authoritative descriptor.
Port upstream writeDiscoveryResponse: cap relays at 1025, encode the
full envelope, and when it exceeds 1 MiB omit incompatible_relays and
binary-search the largest relay prefix that fits. The serving relay
stays at index zero; if even it cannot fit, the request fails with an
internal error.
Port the relay-observable UDP datagram bounds from portal-tunnel v2.6.4:

- Cap live flows per lease at 1024 and admit new flows through a
  64/s, burst-128 token bucket (x/time/rate semantics). At capacity or
  when admission is denied, packets from new senders are dropped; live
  flows are never evicted.
- Allocate flow IDs skipping IDs still live after wrap; zero is valid.
- Drop public UDP payloads above 1238 bytes whole (65535-byte read
  buffer, no prefix forwarding) and drop backhaul frames whose payload
  exceeds 1238 bytes instead of writing them to the public socket.
- Admit flows only while a live backhaul is bound; forget a fresh flow
  whose first send fails while keeping its admission token spent.
- Rate-limit drop warnings to about one per second.
- Clear flow tables on close and refuse flows afterwards.
- Require QUIC DATAGRAM support from the backhaul peer and start QUIC
  at a 1280-byte initial/minimum MTU so the maximum payload fits.

Wire format is unchanged.
Port the v2.6.4 QUIC backhaul pending-authentication bounds:

- At most 64 connections may be in control authentication at once,
  and at most 4 per source prefix (/32 IPv4 after unmapping, /64 IPv6).
  Over-limit connections are closed with code 1 and reason
  "backhaul authentication busy".
- Accepting the control stream, reading the control JSON, and writing
  the response share one 3 s budget. On expiry the connection is closed
  ("backhaul authentication canceled"), which also interrupts a peer
  stalled mid-message. Slots are released when authentication succeeds,
  fails, or times out.
- A failed success response now closes the connection.
- Handler tasks live in a JoinSet, so stopping the listener drops every
  connection still authenticating.

Slots are taken after the QUIC handshake, as with quic-go's
Listener.Accept, rather than at quinn's Incoming: an unvalidated source
address could be spoofed to exhaust another source's slots. The
handshake is separately bounded by quic-go's default 5 s handshake
idle timeout.
Requested lease TTLs above ten minutes now fail with invalid_request
instead of being clamped (#637); official clients request 120 seconds.
Lease metadata is bounded at challenge, register, and renew, retained
SIWE domain/URI lengths are capped, and outstanding register challenges
are limited to 256 globally in addition to 32 per source (#604).
Upstream v2.6.4 (#623) clients that request overlay reject an overlay
reverse endpoint without a signed gateway descriptor and require its URL
to resolve as the gateway URL with the /sdk/connect path. Direct
endpoints keep omitting the field; older clients ignore it.
Upstream v2.6.4 clients run ValidateRegisterSIWEChallenge before signing:
they regenerate the message from the relay URL they dialed and require
its domain and /sdk/register URI to match byte for byte. Derive both from
the validated request Host header, as upstream does, instead of the
configured portal URL, and pin the message format to vectors produced by
the upstream v2.6.4 FormatSIWEMessage.
A registration rejected after allocating a UDP or TCP port (bind failure
or reverse-endpoint failure) no longer leaves that port reserved for the
rejected identity (#609). Fresh ports return to the tail of the free
queue, a reclaimed reservation is restored without extending its expiry,
only the current owner may roll back, and released ports are reused in
queue order rather than sorted order.
Port upstream v2.6.4 #603 and #633 relay behavior:
- at most 128 public sockets await ClientHello inspection and 1024 tenant
  connections (SNI tenants and raw TCP lease ports) are pending or
  active; root API handoff never consumes the tenant budget;
- each lease admits at most 128 pending or active bridges;
- lease close (unregister, expiry, replacement) and a routing deny, ban,
  or approval revocation cancel the lease's pending and active bridges;
  re-allowing an identity admits only new connections;
- request bodies have an absolute 30-second read deadline (400), and a
  GET /sdk/connect that carries a body is rejected before any upgrade.
Upstream decodes POST /sdk/register/challenge with a 64 KiB limit and
answers 413 invalid_request "request body too large"; registration
request bodies from official clients are far below this bound.
Bump the workspace core to 2.6.4 (lockfile updated for portal-relay
only), keep tunnel protocol 10 and discovery protocol 9 as upstream does,
and retarget the release image tag fixtures so the former 2.6.3 core is
rejected.
Upstream v2.6.4 (#606) stops static routes from following symlinks
outside their root. Resolve each requested file and serve it only when
the canonical path stays under the canonical app root; symlinks inside
the root keep working.
Record the v2.6.4 client contract changes (SIWE challenge validation,
overlay gateway descriptor), the ported relay hardening, and what is
deliberately not ported or only partially matched, while keeping
v2.6.3+rs.1 as the current production release until the live gate.
build(image): pin upstream v2.6.4 browser WASM/JS assets
All checks were successful
Rust CI / Format, lint, and test (pull_request) Successful in 3m31s
Rust CI / Build and start linux/arm64 image (no push) (pull_request) Successful in 1m8s
01721ddb84
ada merged commit 1de6279f7a into master 2026-10-15 07:53:34 +00:00
ada deleted branch chore/retarget-v2.6.4 2026-10-15 07:53:34 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
gofix/portal-tunnel-rs!116
No description provided.