1
0
mirror of https://github.com/coturn/coturn.git synced 2026-10-24 00:08:57 +03:00

56 Commits

Author SHA1 Message Date
Pavel Punsky
c6cd2106b6 Enable stateless nonce by default (#2036)
## What

Makes `--stateless-nonce` the default, and closes two paths that created
a
per-packet session for unauthenticated UDP traffic even when it was
enabled.


## Behaviour changes for operators

1. **The default challenge nonce goes from 16 to 24 characters.** RFC
8489
requires clients to treat NONCE as an opaque string of up to 128
characters,
so compliant clients are unaffected. `--stateless-nonce=false` restores
the
   stored random nonce and the 16-character challenge.
2. **The default signing key is ephemeral**, so a restart costs each
client one
`438` re-auth round-trip, as does a retry that lands on a different
instance
behind a load balancer. `--stateless-nonce-secret` shares the key across
a
fleet and avoids both. (This is not a regression — today a retry landing
on
another instance finds no session either — but it becomes the default
story.)


## Validation

Run in a container (Linux, isolated network namespace):

- build clean; 17/17 unit tests
- 11 example suites with zero FAIL lines: `run_tests`, `_conf`,
  `_stateless_nonce`, `_mobile`, `_dtls_default`, `_multiplex_peer`,
  `_stateless_binding`, `_ratelimit_401`, `_mobility_quota`, `_expiry`,
  `_mobility_resume_flood`
- both fuzzing smoke targets (`ASan 0`, `ASan 1`)
- `clang-format-15` clean; mandoc lint unchanged at 104 pre-existing
warnings
2026-08-04 21:08:28 -07:00
Pavel Punsky
3c5b2615fd Merge commit from fork
* Hold mobility allocation quota across first-stage close

When --mobility is enabled, shutdown_client_connection() released the
user and total allocation quota (and the bandwidth reservation) before
the first-stage mobility close, which keeps the allocation, relay
socket, session, and mobility ticket alive for a later resume. An
authenticated client could therefore create a mobility-enabled
allocation over TCP, disconnect, and immediately re-allocate, holding
many live relay allocations past --user-quota=1 and --total-quota=1
until the relay port pool was exhausted (508). No request was rejected
by either quota.

Keep the quota and bandwidth charge held for as long as the allocation
remains valid and resumable: release it only in the final second-stage
teardown, which the allocation-timeout and forced-shutdown paths always
reach.

A mobility resume then has to reuse the existing allocation's single
quota unit instead of acquiring a second one for the temporary resuming
session, which would otherwise reject legitimate resumes under strict
quotas. copy_auth_parameters() gains an acquire_quota flag: the resume
path in handle_turn_refresh adopts the owner's credentials without
charging the resuming session, and mobile_complete_transition keeps the
charge on the surviving session.

Add examples/run_tests_mobility_quota.sh and its raw-STUN driver
examples/scripts/mobility_quota_flood.py: under --user-quota=1
--total-quota=1 --mobility the flood is admitted exactly once and the
next allocation is rejected with 486, while a fresh-5-tuple resume still
succeeds. The test fails on the vulnerable server (admits many, hits
508) and passes on the fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Update ns_turn_server.c

* copy_auth_parameters: make acquire_quota a bool

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 20:12:52 -07:00
Pavel Punsky
ce26268da8 Start the DTLS listeners only when --dtls is given (#2026)
## What

The DTLS client listeners are no longer started by default. They come up
only
when `--dtls` is given.

Until now DTLS came up automatically whenever usable certificates were
configured, so every deployment that wanted TLS also exposed a DTLS
handshake
path it had never asked for. That path is the most expensive
unauthenticated
entry point the server has: it builds an `SSL` object, an `ioa_socket`
and a
session *before* the DTLS cookie is verified. Deployments that do not
serve
DTLS clients were paying for that surface silently.

## Compatibility

`--no-dtls` keeps its meaning, including `--no-dtls=false` to ask for
DTLS, so
an existing configuration resolves to exactly the listeners it asked
for. Both
spellings now log a deprecation warning naming `--dtls`:

```
CONFIG: --no-dtls is deprecated and now redundant: DTLS listeners are not started unless --dtls is given
CONFIG: --no-dtls=false is deprecated: use --dtls to start the DTLS listeners
```

**This is a behaviour change for anyone who relied on the old default.**
A
deployment that configured certificates and passed no DTLS flag loses
its DTLS
listeners on upgrade. The only signal such an operator gets is a startup
line,
emitted when the certificates are usable but DTLS is off:

```
DTLS listeners are not started; use --dtls to start them
```

If the project would rather have a transition release — default
unchanged, but
logging that it is about to change — that is a small edit to this
branch; say
so in review.

## How

`turn_params` carries the state as `dtls` rather than the previous
`no_dtls`,
so the listener and netengine tests read as "DTLS is on" instead of
double
negatives, and `openssl_setup()`'s two-flag guards lose their De Morgan
wrapper (`!(no_tls && no_dtls)` becomes `!no_tls || dtls`). The telnet
CLI and
web admin listings consequently report `dtls` instead of `no-dtls`,
matching
the flag an operator actually sets.

Documentation follows in `README.md`, `README.turnserver`, the built-in
help
and `examples/etc/turnserver.conf`. The `--pkey` help entry had a
sentence
truncated mid-clause; the rewrite fixes it.

## Tests

New `examples/run_tests_dtls_default.sh` pins four cases:

- no flag: no DTLS context, the hint line is logged, and a
`turnutils_uclient -S`
  session cannot complete;
- `--dtls`: context created, DTLS client relays end to end;
- `--no-dtls`: warns, stays off;
- `--no-dtls=false`: warns, starts DTLS, client relays.

It is wired into all four workflows, so this default is now covered on
both
Linux and macOS. Example scripts that only ever wanted DTLS off drop the
now-redundant flag; the ones that drive DTLS clients pass `--dtls`.

Also run green: `ctest` (18/18 macOS, 17/17 Linux) and, on both
platforms where
applicable, `run_tests.sh`, `run_tests_conf.sh`,
`run_tests_multiplex_peer.sh`,
`run_tests_stateless_nonce.sh`, `run_tests_stateless_binding.sh`,
`run_tests_ratelimit_401.sh`, `run_tests_mobile.sh`,
`run_tests_rfc5780.sh`,
`run_tests_expiry.sh`; fuzzing smoke tests (ASan, both targets); and the
packaged Debian image build plus its Bats suite (19 tests, 0 failures,
2 skipped).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 20:39:55 -07:00
Pavel Punsky
0c5ddc5481 Worktree stateless nonce mi fastpath (#2025)
What — the fast path fell through on the mere presence of
MESSAGE-INTEGRITY, so 20 garbage bytes put a spoofed flood back on the
session-creating path. Framed honestly as ~19 KB of alloc+zeroing per
~100-byte packet rather than an OOM, since close_after_auth_challenge
already bounded steady-state memory. Headline number: 200 forged-MI
packets → 200 sessions before, 0 after, all still answered.
How, part 1 — nonce validated in the listener; 400 / 437 / 441 / 438
answered there in check_stun_auth's order; wrong-length MI treated as
missing; a verifying nonce still falls through because the credential
lookup can be async. Plus the --stun-only side improvement.
How, part 2 — rejections charged to the existing
--unauthorized-ratelimit bucket, with the reasoning for not dropping
them: RFC 8489 §9.2.5 needs the 438 for stale-nonce recovery, and after
part 1 that legitimate retry lands on this very path. Includes the
amplification measurements (438 under 1:1 vs 401 at 3.6–5.4:1) to
explain why this caps a vector rather than closing an amplification
hole.
Wire compatibility — nine shapes dumped attribute-by-attribute against a
pre-change build, identical.
Tests — the new probe script and its assertions, the two rate-limit
cases, and the full green matrix including fuzz smoke and the Docker
image Bats run.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 22:57:23 -07:00
Pavel Punsky
0fb7fbade1 Binding stateless fastpath (#2024)
## What

Answer a plain STUN Binding request from an unknown UDP source in the
listener, instead of building a child socket and a session to reach
`handle_turn_binding()`.

That function derives its entire response from the request bytes and the
socket's own addresses — nothing in it needs session state. But reaching
it costs an `ioa_socket` plus an ~18KB `ts_ur_super_session`, and the
to-be-allocated timer holds both for 60 seconds. So one spoofed Binding
request per source buys ~22KB of server memory for a minute, in the
**default configuration**, with no authentication and no flag involved.

| | RSS growth, 3000 Binding requests from distinct source ports |
|---|---|
| master | **+66,720 KB** |
| this branch | **+304 KB** |

All requests are still answered, byte for byte.

## How

`udp_stateless_binding_fast_path()` in `dtls_listener.c`, called from
`handle_udp_packet()` for a source with no child socket yet. It rebuilds
the reply attribute for attribute:

- success response with XOR-MAPPED-ADDRESS (plus MAPPED-ADDRESS under
`--stun-backward-compatibility`, plus RESPONSE-ORIGIN/OTHER-ADDRESS
under `--rfc5780`), or `420` with the same UNKNOWN-ATTRIBUTES list;
- then SOFTWARE and FINGERPRINT on the same conditions as the session
path;
- and the same Prometheus binding counters (`stun_report_binding()`
already ignores its session argument).

The attribute walk mirrors `handle_turn_binding()`'s exactly, including
`SKIP_ATTRIBUTES`, so ICE-style requests carrying
USERNAME/PRIORITY/MESSAGE-INTEGRITY/FINGERPRINT are accepted rather than
flagged as unknown.

Requests the listener cannot answer identically still take the session
path:

- `--secure-stun` — BINDING is an authenticated method there, which is
`check_stun_auth()`'s business;
- CHANGE-REQUEST / RESPONSE-PORT / PADDING — the RFC 5780 probes leave
from an alternate socket that only the relay owns;
- `--log-binding` — its per-session log lines can only be written by the
relay, so the old path stays available for debugging.

Under `--no-stun` the request is dropped where the relay would have
ignored it anyway — same silence on the wire, minus the session it used
to leave behind.

`MAX_NUMBER_OF_UNKNOWN_ATTRS` moves from `ns_turn_server.c` to
`ns_turn_server.h` so both call sites share one definition.

## Wire compatibility

A byte-level dump of the reply (message type plus every attribute TLV)
was compared between a master build and this one for ten request shapes
— bare Binding, ICE-style, unknown comprehension-required attributes,
unknown comprehension-optional attribute, CHANGE-REQUEST with and
without bits set, RESPONSE-PORT, PADDING, and an attribute placed after
MESSAGE-INTEGRITY — in default configuration and with
`--software-attribute --fingerprint --stun-backward-compatibility`.
**Identical in every case.**

## Tests

New `examples/run_tests_stateless_binding.sh` with
`examples/scripts/stateless_binding_probe.py`:

- wire shapes, including that the reflexive port in XOR-MAPPED-ADDRESS
is the client's own and that the 420 echoes the offending attributes;
- `--no-stun` stays silent, `--secure-stun` still challenges with 401;
- a 2000-request flood asserting RSS growth stays under the threshold.

The suite discriminates: against a master binary the memory case fails
with `Binding flood grew RSS by 43632KB ... sessions are being created
again`, while every wire assertion passes on both builds.

Also run green: `ctest` (18/18 macOS, 17/17 Linux),
`turnutils_rfc5769check`, and `run_tests.sh`, `run_tests_conf.sh`,
`run_tests_mobile.sh`, `run_tests_multiplex_peer.sh`,
`run_tests_stateless_nonce.sh`, `run_tests_ratelimit_401.sh`,
`run_tests_rfc5780.sh` — the last one being the important one here,
since it exercises both the plain Binding response that now comes from
the listener and the CHANGE-REQUEST probes that must still reach the
relay.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 22:16:45 -07:00
Pavel Punsky
a97f1924bb Merge commit from fork
Under --mobility a resume does not hard-switch the allocation onto the
new client path; it opens a dual-5-tuple transition and links the
resuming session to the allocation via a single backlink
(mobile_pending_resume). An authenticated user could resume one
allocation repeatedly from fresh 5-tuples without completing any
handoff: each resume disarmed the new session's un-allocated watchdog
and overwrote that single backlink, orphaning the previous pending
session. Orphans were unreachable by the transition-deadline sweep and
owned no allocation timer, so they stayed allocated indefinitely,
letting one user grow server memory without bound. --user-quota did not
stop it, since pending sessions hold no quota unit.

Enforce one pending resume per allocation:

- mobile_begin_transition() fully tears down the existing pending
  session before opening a new one. It runs in REFRESH packet context,
  so the shutdown is synchronous and also clears the allocation's
  backlink via shutdown's transition-unlink path.
- mobile_abort_transition() re-arms the disarmed watchdog so an
  abandoned pending left after the transition deadline is reaped from
  timer context (safe during the session-map sweep, which only
  schedules the timer).
- mobile_complete_transition() promotes only when the session is still
  the allocation's current pending resume, so a superseded pending can
  neither clear live transition state nor steal a socket move.

Add examples/run_tests_mobility_resume_flood.sh (+ raw-STUN driver
scripts/mobility_resume_flood.py): it floods chained resumes and asserts
every superseded pending is torn down. It passes on the fixed server and
fails on the unpatched one.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 22:39:45 -07:00
Pavel Punsky
0177bcd373 Update version to 4.8.0 (#1791)
Update version to 4.8.0 
Set new release version to 4.8.0

Contains security fixes and highly recommended to upgrade
2026-01-05 17:35:27 -08:00
Pavel Punsky
678996a529 Update version to 4.7.0 (#1691)
Set new release version to 4.7.0
Updating minor version due to some breaking changes in options to enable
more secure/robust configuration without additional flags (or relying on
recommended conf file which people seem to skip during updates)
2025-05-30 14:13:59 -07:00
Pavel Punsky
b6e53ca4c9 Update version to 4.6.3 (#1609)
preparing for new tag 4.6.3

---------

Co-authored-by: tyranron <tyranron@gmail.com>
2024-12-11 10:13:38 -08:00
eakraly
86a8f659fb Fix rpm version scripts (#1556)
Fix #1521
2024-10-04 10:28:11 -07:00
vuittont60
da6bc6b0c4 Fix typos (#1345) 2024-01-15 18:31:16 -08:00
Pavel Punsky
73c14d6b10 Replace references to non-existent pdf file with links (#1002)
Fixes #800
2022-09-29 11:09:10 -07:00
Mészáros Mihály
8fbe513300 Fix typo (---allow-loopback-peers) 2021-05-07 21:25:40 +02:00
Mészáros Mihály
3b3c5849cc bump version 2020-12-07 15:26:00 +01:00
Mészáros Mihály
6d605d353f Bump version 2020-06-24 17:05:18 +02:00
Ilya Kisleyko
283d7c5749 tell tar to not include the metadata into release 2020-06-09 18:30:30 +00:00
Mészáros Mihály
2b2f116c2d Bump version 2020-04-30 20:41:17 +02:00
Mészáros Mihály
7d1c1e0f89 Add new test certs 2020-04-28 09:26:31 +00:00
Mészáros Mihály
8b46733fa1 Add relase script pack.sh 2019-01-17 07:50:47 +01:00
MÉSZÁROS Mihály
e2717e0fae tidy 2018-11-05 14:15:36 +00:00
MÉSZÁROS Mihály
78dbad44cd Add cli-password to test scripts 2018-11-05 10:56:43 +00:00
Mészáros Mihály
8a60754d70 Change loopback defaults 2018-09-12 10:30:07 +02:00
Mészáros Mihály
82ca50ebb2 tidy and small improvments
* rename long-term-key to auth-key
 * add warning for auth key and token lifetime expiry or missmatch
 * tidy sample script
2016-09-07 15:28:27 +02:00
Mészáros Mihály
9bd3f0ee9c tidy example script 2016-09-07 15:28:27 +02:00
Oleg Moskalenko
9124a167ed read_timeout support 2016-08-27 17:09:05 -07:00
mom040267
576f07fa84 default scripts fixed 2015-05-05 06:45:47 +00:00
mom040267
4bf655207f script fixed 2015-05-05 06:27:29 +00:00
mom040267
6291ba6702 working on oauth 2015-04-20 04:40:24 +00:00
mom040267
4d5f0cbc03 script fixed 2015-04-03 07:02:55 +00:00
mom040267
948a61d250 SCTP fixes. 2015-04-03 06:54:10 +00:00
mom040267
c0f2ca8cea working on sctp 2015-03-18 05:12:05 +00:00
mom040267
7d5ab2472a working on SCTP 2015-03-15 21:24:36 +00:00
mom040267
e8b18b4586 REFRESH adjusted for the new SSODA specs 2015-02-01 08:59:30 +00:00
mom040267
caff982f26 minor fixes 2015-01-31 07:20:41 +00:00
mom040267
5540622642 docs change 2015-01-25 08:18:41 +00:00
mom040267
e86bd85acb oauth restored 2015-01-21 06:38:23 +00:00
mom040267
4b2be8adae third-party authetorization option is mandatory with oauth 2015-01-20 17:05:58 +00:00
mom040267
237b3baaa7 short-term credentials removed in the TURN server 2015-01-11 06:28:58 +00:00
mom040267
5cd0d33c31 1) working on https; 2) SSLv2 support removed. 2015-01-10 08:25:29 +00:00
mom040267
01e2364a7d docs updated 2014-11-21 08:14:45 +00:00
mom040267
68f28329fc working on sqlite 2014-11-20 04:13:49 +00:00
mom040267
221f014a6c working on sqlite install 2014-11-20 02:56:53 +00:00
mom040267
46392bf738 short-term credentials script changed to use sqlite 2014-11-18 05:54:46 +00:00
mom040267
b25a32f10c sqlite support, draft implementation 2014-11-17 09:38:53 +00:00
mom040267
04f354d563 memory cleaning 2014-10-11 06:49:35 +00:00
mom040267
9ddf00a4c2 quota and server name ixes 2014-09-29 02:11:27 +00:00
mom040267
bb8ccd8a91 oauth support added to DB scripts 2014-09-27 05:45:35 +00:00
mom040267
7b444a1441 server-name parameter. 2014-09-17 06:50:35 +00:00
mom040267
fe19f4dee0 oauth db fixes 2014-08-28 07:49:33 +00:00
mom040267
70d8f5d803 new mysql ssl script added 2014-08-17 06:50:20 +00:00