fix(relay): bounded resources for traffic spikes

- Reject envelopes for addresses without a device (404) instead of
  queueing them for 30 days; random addresses were the cheapest way
  to fill the relay's disk.
- Per-sender hourly quota on stored envelopes (count and bytes, 429)
  and a count cap on each recipient's queue next to the byte cap.
- Per-account attachment quota: bytes per day and bytes stored within
  the TTL, kept in memory only (no account-to-blob link is stored).
- Disk watermarks: below a configurable share of free space the relay
  refuses new attachments, then new offline envelopes (507), before
  Postgres runs out of space.
- WebSocket buffers 4 KiB read / unbuffered write: ~16.6 KiB per
  connection instead of ~137 KiB (10 500 connections measured at
  ~180 MiB including the DB pool).
- reqwest with HTTP/2: APNs only accepts HTTP/2, so no iOS wake-up
  could ever be delivered. APNs registration with only a VoIP token
  is stored instead of deleted.
- All limits configurable via environment (limits.rs). Defaults keep
  previous behaviour except registration per IP (50 + 1/10 s, for
  carrier-grade NAT) guarded by a global cap (40/s), separate Tor
  buckets (SEPOT_TOR_PEERS) and a rate limit on opening WebSockets.
  SEPOT_REGISTRATION_PAUSED=1 pauses new registrations (503).
- Envelopes whose encoding exceeds the queue column limit are 400
  before they count against the sender quota.

Rate limiting moves from tower_governor to governor directly, so Tor
and clearnet can use different quotas on the same route.
This commit is contained in:
Julius Joska
2026-10-23 11:04:33 +02:00
parent 830d6290ca
commit 5222c301ff
12 changed files with 1181 additions and 325 deletions
+15 -132
View File
@@ -633,16 +633,6 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "forwarded-header-value"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9"
dependencies = [
"nonempty",
"thiserror 1.0.69",
]
[[package]]
name = "futures-channel"
version = "0.3.32"
@@ -1059,19 +1049,6 @@ dependencies = [
"webpki-roots 1.0.8",
]
[[package]]
name = "hyper-timeout"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0"
dependencies = [
"hyper",
"hyper-util",
"pin-project-lite",
"tokio",
"tower-service",
]
[[package]]
name = "hyper-util"
version = "0.1.20"
@@ -1440,7 +1417,7 @@ dependencies = [
"rand 0.9.4",
"sha2",
"subtle",
"thiserror 2.0.18",
"thiserror",
"uuid",
"x25519-dalek",
"zerocopy",
@@ -1488,7 +1465,7 @@ dependencies = [
"signal-crypto",
"spqr",
"subtle",
"thiserror 2.0.18",
"thiserror",
"uuid",
"zerocopy",
]
@@ -1590,12 +1567,6 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
[[package]]
name = "nonempty"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7"
[[package]]
name = "nonzero_ext"
version = "0.3.0"
@@ -1788,26 +1759,6 @@ dependencies = [
"indexmap",
]
[[package]]
name = "pin-project"
version = "1.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pin-project-lite"
version = "0.2.17"
@@ -2071,7 +2022,7 @@ dependencies = [
"rustc-hash",
"rustls",
"socket2",
"thiserror 2.0.18",
"thiserror",
"tokio",
"tracing",
"web-time",
@@ -2092,7 +2043,7 @@ dependencies = [
"rustls",
"rustls-pki-types",
"slab",
"thiserror 2.0.18",
"thiserror",
"tinyvec",
"tracing",
"web-time",
@@ -2277,6 +2228,7 @@ dependencies = [
"base64",
"bytes",
"futures-core",
"h2",
"http",
"http-body",
"http-body-util",
@@ -2445,6 +2397,7 @@ dependencies = [
"chrono",
"dashmap",
"futures-util",
"governor",
"hex",
"hmac",
"ipnet",
@@ -2463,7 +2416,6 @@ dependencies = [
"tokio",
"tokio-util",
"tower-http 0.7.0",
"tower_governor",
"tracing",
"tracing-subscriber",
]
@@ -2603,7 +2555,7 @@ dependencies = [
"sha1",
"sha2",
"subtle",
"thiserror 2.0.18",
"thiserror",
"zeroize",
]
@@ -2635,7 +2587,7 @@ checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d"
dependencies = [
"num-bigint",
"num-traits",
"thiserror 2.0.18",
"thiserror",
"time",
]
@@ -2718,7 +2670,7 @@ dependencies = [
"rand_core 0.9.5",
"sha2",
"sorted-vec",
"thiserror 2.0.18",
"thiserror",
]
[[package]]
@@ -2763,7 +2715,7 @@ dependencies = [
"serde_json",
"sha2",
"smallvec",
"thiserror 2.0.18",
"thiserror",
"tokio",
"tokio-stream",
"tracing",
@@ -2847,7 +2799,7 @@ dependencies = [
"smallvec",
"sqlx-core",
"stringprep",
"thiserror 2.0.18",
"thiserror",
"tracing",
"whoami",
]
@@ -2885,7 +2837,7 @@ dependencies = [
"smallvec",
"sqlx-core",
"stringprep",
"thiserror 2.0.18",
"thiserror",
"tracing",
"whoami",
]
@@ -2910,7 +2862,7 @@ dependencies = [
"serde",
"serde_urlencoded",
"sqlx-core",
"thiserror 2.0.18",
"thiserror",
"tracing",
"url",
]
@@ -2982,33 +2934,13 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl 1.0.69",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl 2.0.18",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
"thiserror-impl",
]
[[package]]
@@ -3190,35 +3122,6 @@ dependencies = [
"winnow",
]
[[package]]
name = "tonic"
version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef"
dependencies = [
"async-trait",
"axum",
"base64",
"bytes",
"h2",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-timeout",
"hyper-util",
"percent-encoding",
"pin-project",
"socket2",
"sync_wrapper",
"tokio",
"tokio-stream",
"tower",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower"
version = "0.5.3"
@@ -3227,12 +3130,9 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
dependencies = [
"futures-core",
"futures-util",
"indexmap",
"pin-project-lite",
"slab",
"sync_wrapper",
"tokio",
"tokio-util",
"tower-layer",
"tower-service",
"tracing",
@@ -3285,23 +3185,6 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tower_governor"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44de9b94d849d3c46e06a883d72d408c2de6403367b39df2b1c9d9e7b6736fe6"
dependencies = [
"axum",
"forwarded-header-value",
"governor",
"http",
"pin-project",
"thiserror 2.0.18",
"tonic",
"tower",
"tracing",
]
[[package]]
name = "tracing"
version = "0.1.44"
@@ -3383,7 +3266,7 @@ dependencies = [
"log",
"rand 0.9.4",
"sha1",
"thiserror 2.0.18",
"thiserror",
]
[[package]]