Repository navigation
feat(relay): add per-day egress budget (Denial-of-Wallet cap) - #101
Merged
Merged
Conversation
Add FSEND_RELAY_MAX_BYTES_PER_DAY: a server-wide cap on relay bytes per UTC day. Default 0 = unlimited. Once spent, the relay refuses new transfers and drops in-flight ones until 00:00 UTC. Bounds total egress cost, which per-IP and per-session limits cannot: a distributed abuser sidesteps per-IP caps, and a per-session cap only limits one transfer. Enforcement has two paths. New transfers fail fast at allocate time (deterministic, both peers get the reason). A budget that trips mid-transfer evicts via the same path as the per-session cap. Both surface as E037 with a descriptive message on sender and receiver. Every rate-limit error (E017/E023/E037) now ends with a self-hosting link so users know they can run their own server. Rename two env vars for consistency (breaking, pre-1.0): - FSEND_PAIRING_ADDR -> FSEND_SERVER_ADDR - FSEND_SERVER_MAX_SESSIONS_PER_IP_PER_MIN -> ..._PER_MINUTE Metrics gain budget_max_bytes_per_day, budget_bytes_today, and transfers_budget_capped_total. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The two budget gauges already report the actionable state: budget_bytes_today >= budget_max_bytes_per_day means the breaker tripped. The counter only tallied mid-flight evictions (not up-front refusals), so it was a partial, easily-misread signal on top of gauges that answer the question exactly. Keep it simple. The per-session transfers_capped_total stays: that cap has no gauge, so its counter is the only signal. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop budget_max_bytes_per_day from /v1/metrics. It's the operator's own config, it's the only limit value the metrics exposed (per-IP and per-session caps expose only counts), and on an open server it handed a Denial-of-Wallet abuser the exact target. Keep budget_bytes_today — live usage, the operator's early warning. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Group the config table, startup summary, and CLI help the same way: server-wide (log, auth), then the pairing control plane (its listener + session limits), then the relay data plane (its listener + byte limits). Previously FSEND_SERVER_ADDR sat after the session limits, so the table didn't read plane-by-plane. Also fix the startup-example alignment to match the actual output. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The env-var rename missed scripts/preview/demo.sh, which set a custom port via FSEND_PAIRING_ADDR — now ignored, so the server bound :8080 while the client dialed :8799 and the demo broke. Point it at FSEND_SERVER_ADDR. Also add comments: the both-peers allocate asymmetry (only the first caller sees the budget refusal; the second converges via the in-flight breaker) and the per-IP-count retention when a budget-exhausted session is left to PairedTTL. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Prove the lazy-once contract: the relay token is minted on the first peer's allocate and reused for the second, so the second peer never re-invokes Allocate. The fake relay is rigged to report the budget spent on any second Allocate; the test asserts it never fires (allocCalls==1, no budget_exhausted, shared token). This is the asymmetry that lets a budget spent mid-pairing converge via the in-flight breaker. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Spell out what FSEND_RELAY_MAX_BYTES_PER_DAY meters: every forwarded datagram, once, in both directions (payload one way, ACKs/control the other) = outbound bytes, matching how egress is billed. Note STUN and direct/LAN transfers aren't counted, and that a host billing inbound too sees ~2x. Applied to the config table, the budget_bytes_today metric row, and the CLI help. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Previous wording over-explained "both directions" and a 2x inbound caveat, which read as if the budget double-counts. It doesn't: each byte is counted once when forwarded, so a 1 MB relayed file ≈ 1 MB of budget. State that plainly and drop the confusing bits. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
FSEND_RELAY_MAX_BYTES_PER_DAY: a server-wide cap on relay bytes per UTC day.Default
0= unlimited. Once spent, the relay refuses new transfers and drops in-flight ones until 00:00 UTC.Why
Per-IP and per-session limits can't bound total cost:
A daily budget is the only knob that bounds cumulative egress spend. This is the Denial-of-Wallet backstop.
How it works
Two enforcement paths:
Both surface as E037 with a descriptive message, on sender and receiver.
Descriptive errors
Every rate-limit error now ends with a self-hosting link so users know they can run their own server:
Renames (breaking, pre-1.0)
FSEND_PAIRING_ADDR->FSEND_SERVER_ADDRFSEND_SERVER_MAX_SESSIONS_PER_IP_PER_MIN->..._PER_MINUTEMetrics
One new field on
/v1/metrics:budget_bytes_today— bytes forwarded so far in the current UTC day. Approaching your configuredFSEND_RELAY_MAX_BYTES_PER_DAYis the early warning; reaching it means the breaker tripped.The configured limit is deliberately not exposed: it's the operator's own config, and on an open server it would hand a Denial-of-Wallet abuser the exact target.
Docs
Reordered the config (table, startup summary, CLI help) by plane: server-wide, pairing/control plane, relay/data plane.
Validation
Unit tests,
-race, andgofmt/vetall clean.Also validated end-to-end with the real binary, driving two processes through the relay (
--mode relay):Notes
0(unlimited), as requested.🤖 Generated with Claude Code