Skip to content

feat(server): add FSEND_ENABLE_RELAY (pairing + STUN only mode) - #86

Merged
polius merged 2 commits into
mainfrom
feat/relay-forwarding-toggle
Jun 25, 2026
Merged

polius merged 2 commits into
mainfrom
feat/relay-forwarding-toggle

Conversation

@polius

@polius polius commented Jun 25, 2026

Copy link
Copy Markdown
Owner

What

Adds FSEND_ENABLE_RELAY (default true). Setting it false runs the server in stun-only mode: it still answers STUN so peers can hole-punch a direct path, but carries no file data. For operators who want to help peers connect without paying for — or being liable for — relayed traffic.

Default behaviour is unchanged (byte-identical to today); no protocol break.

Why

In the current architecture one UDP socket does three jobs: STUN binding, STUN-address delivery (via the allocate response), and byte forwarding. So a naive "disable the relay" that tears down the listener would also kill hole-punching, leaving same-LAN + public-IP peers only. This change decouples forwarding from STUN: STUN/pairing stay, only data forwarding is gated.

How

Two enforcement points:

  • Relay teeth (internal/relay): ServerConfig.DisableForwarding (negative-framed so the zero value forwards). A one-line gate in handle drops data datagrams after the STUN dispatch — bytes never move even for a non-compliant/old client. Forwarding() exposes the mode to the signaling layer.
  • Client signal (internal/server + cmd/fsend): allocate response gains forwarding_disabled (omitempty, so old servers round-trip as false = forwarding-capable). The client keeps using RelayAddr for STUN but skips the relay dial and fails fast with a clear ErrConnectFailed instead of timing out on a relay that would only drop its packets. Applied at all three consumer sites (sender fallback, receiver fallback, --mode=relay).

The UDP listener always binds (STUN needs it); only forwarding is gated. Bearer-token gate on allocate is unchanged. A startup log line announces stun-only mode.

Behaviour matrix

client → server result
new ↔ stun-only fast, clean "relay forwarding disabled" error
old ↔ stun-only still tries the relay dial → slow QUIC timeout (degraded but safe)
any ↔ old server field absent → false → unchanged

Tests (all pass, -race clean)

  • TestForwardingDisabled — data dropped while STUN still answers (the core guarantee, one test)
  • TestForwardingEnabledByDefault — default forwards
  • TestRelayAllocate_ForwardingDisabledFlag — response flag mirrors the mode; RelayAddr always set
  • TestAllocAndDialRelay_ForwardingDisabled — client fails cleanly without dialing
  • TestServerEnvBool — envBool parses and rejects typos loudly (matches envInt/envBytes)

Verified locally: go build, go vet, gofmt -l, full suite incl. -race. (golangci-lint couldn't run locally — its binary targets go1.25 vs the repo's 1.26.4; CI covers it.)

Docs

docs/self-hosting.md: config row + a "Pairing-only mode" section spelling out the trade-off (symmetric-NAT transfers fail rather than relay; UDP still binds for STUN). fsend server --help updated.

Note

Second commit is an unrelated pre-existing doc fix (FSEND_MAX_RELAY_BYTES_PER_SESSION default was documented as 1GB; the real default is 100MB). Kept separate so it can be dropped/cherry-picked independently.

🤖 Generated with Claude Code

Pol Alzina and others added 2 commits June 25, 2026 16:58
By default the server still forwards data (unchanged). Setting
FSEND_ENABLE_RELAY=false puts it in stun-only mode: it keeps answering
STUN so peers can hole-punch a direct path, but carries no file data —
useful for operators who want to help peers connect without paying for
or being liable for relayed traffic.

Two enforcement points:
- Relay: DisableForwarding drops data datagrams (STUN path untouched),
  so bytes never move even for a non-compliant client. Forwarding()
  exposes the mode to the signaling layer.
- Allocate response: forwarding_disabled (omitempty, so old servers
  round-trip as false) tells the client to use RelayAddr for STUN but
  skip the relay fallback and fail fast with a clear error instead of
  dialing a relay that would only drop its datagrams.

The UDP listener always binds (STUN needs it); only forwarding is gated.

Tests: relay drops data while STUN still answers; allocate response flag
mirrors the mode; client fails cleanly when forwarding is disabled;
envBool parsing rejects typos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The self-hosting table listed the per-session relay cap default as 1GB,
but the code, ServerConfig.Default(), and `fsend server --help` all use
100MB. Correct the default and align the plain-byte-count example
(100000000) with the decimal units the table documents.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@polius
polius merged commit 28d3675 into main Jun 25, 2026
20 checks passed
@polius
polius deleted the feat/relay-forwarding-toggle branch June 25, 2026 15:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant