1
0
mirror of https://github.com/coturn/coturn.git synced 2026-10-24 00:08:57 +03:00
Files
coturn/docs/Prometheus.md
Pavel Punsky 47ce433cde Add per-source rate-limiting of UDP 401 Unauthorized responses (#1957)
TURN/STUN long-term auth answers the first unauthenticated request with
a 401 Unauthorized carrying REALM/NONCE. On UDP, where source addresses
are spoofable and the 401 is larger than the request that triggers it,
the server becomes a reflection/amplification vector: an attacker forges
a victim's source IP and bounces amplified 401s at it.

This adds an opt-in mitigation that caps how many UDP 401 responses the
server emits toward any single source IP per second. Past the cap the
server stays silent, denying both the reflection and the amplification.
Authenticated relay traffic is untouched — only requests that reach the
401 branch spend budget.

Flags (off by default):
  --unauthorized-ratelimit            Enable the mitigation (UDP only).
--unauthorized-ratelimit-rps=<n> Max 401 responses per source IP per
second. Default 10.

Implementation:
- Lock-free, fixed 4096-bucket direct-mapped table (~80 KiB, no
malloc/free, no lock, no list scan on the hot path); the source IP
appears only as a 32-bit tag, never as a pointer. The port is stripped
from the key so an attacker cannot evade the limit by rotating the
source port. A fixed 1-second window backs the per-second limit.
- Colliding addresses share the live bucket budget instead of receiving
a fresh allowance, so hash collisions can never expand reflected output.
- Drop and collision diagnostics are each latched to one log line per
(bucket, window) to avoid log amplification.
- Portable 32-bit atomics over C11 <stdatomic.h> with an MSVC
Interlocked* fallback; the on/off flag and the per-second threshold are
pointers into turn_params so every relay thread sees live CLI values
without per-thread copies.

Observability (Prometheus):
- turn_unauthenticated_401_{requests,responses,dropped_responses}
- turn_ratelimit_{hash_collisions,occupied_buckets,total_buckets}

Tests & docs:
- Unity unit test (tests/test_ratelimit.c) covering live-bucket
collisions, occupancy tracking, and the collision counter.
- End-to-end positive/negative system test (run_tests_ratelimit_401.sh)
and a Prometheus counter test (run_tests_prom.sh).
- A 401-response-flood load harness under examples/loadtest/.
- docs/401-ratelimit.md design note, plus man page, README, Prometheus
docs, and the example/Docker turnserver.conf entries.
2026-06-21 14:25:09 -07:00

4.3 KiB

Prometheus setup

coturn exposes a Prometheus metrics endpoint (default :9641/metrics) when started with --prometheus.

Vendored Prometheus client

coturn ships a minimal, self-contained Prometheus client under src/prometheus and builds it straight from those sources.

The vendored client implements the slice of the API coturn uses — counters, gauges, a single default registry, and the text-exposition serializer. Histograms, summaries, custom registries and a bundled HTTP handler are out of scope; coturn serves the endpoint with its own libmicrohttpd handler in src/apps/relay/prom_server.c.

The exporter's only external dependency is libmicrohttpd.

Install libmicrohttpd

Ubuntu / Debian

sudo apt install libmicrohttpd-dev

macOS (Homebrew)

brew install libmicrohttpd

From source

libmicrohttpd — download from https://git.gnunet.org/libmicrohttpd.git

git clone https://git.gnunet.org/libmicrohttpd.git
cd libmicrohttpd
./autogen.sh
./configure --prefix=`pwd`/install --disable-doc --disable-examples \
    --disable-tools
make install

Build

The exporter is always compiled in; libmicrohttpd is a required build dependency (provided by vcpkg on Windows). The runtime --prometheus flag controls whether it is started. To point CMake at a non-standard libmicrohttpd install prefix, set MicroHTTPD_ROOT:

cmake -S . -B build -DMicroHTTPD_ROOT=/path/to/libmicrohttpd/install
cmake --build build

The exporter is always compiled in (libmicrohttpd is a required build dependency); the runtime --prometheus flag controls whether it is started.

HTTPS (optional)

By default the metrics endpoint is served over plain HTTP. To serve it over HTTPS instead, pass --prometheus-tls (requires libmicrohttpd built with TLS support). --prometheus-tls implies --prometheus, so it enables the exporter on its own — there is no need to pass both. The certificate and key default to the server's own --cert / --pkey; override them with --prometheus-cert / --prometheus-key:

# Reuse the server's TLS material:
turnserver --prometheus-tls --cert server.pem --pkey server.key

# Or use a dedicated certificate for the metrics endpoint:
turnserver --prometheus-tls \
    --prometheus-cert metrics.pem --prometheus-key metrics.key

Then scrape with https://<host>:9641/metrics. If --prometheus-tls is set but no certificate/key can be loaded, the exporter logs an error and does not start.

UDP 401 mitigation metrics

When the optional UDP 401 response rate limit is enabled, use:

unauthorized-ratelimit
unauthorized-ratelimit-rps=10
prometheus

The rate limit is disabled by default. It applies only to UDP 401 Unauthorized responses, which can otherwise be used in spoofed-source reflection attacks.

When Prometheus is enabled, the exporter provides the following counters for this path whether or not mitigation is enabled. The dropped response counter increments only when unauthorized-ratelimit suppresses responses.

Metric Type Meaning
turn_unauthenticated_401_requests counter UDP requests requiring a 401 Unauthorized response.
turn_unauthenticated_401_responses counter UDP 401 Unauthorized responses emitted.
turn_unauthenticated_401_dropped_responses counter UDP 401 responses suppressed by the rate limit.

The rate-limit hash table is also instrumented. These values are computed lazily when Prometheus scrapes /metrics, so they add no cost to the hot path:

Metric Type Meaning
turn_ratelimit_hash_collisions counter Requests whose source hashed to a bucket already owned by a different live address (distinct sources sharing a budget — the mitigation's false-positive surface).
turn_ratelimit_occupied_buckets gauge Buckets currently holding a live (non-expired) window.
turn_ratelimit_total_buckets gauge Table capacity in buckets.

turn_ratelimit_occupied_buckets / turn_ratelimit_total_buckets is the table load factor; a sustained high ratio or a climbing turn_ratelimit_hash_collisions rate means distinct attackers are colliding into shared buckets. See docs/401-ratelimit.md for the full design.