## Problem
Since 4.7.0, `--stun-backward-compatibility` no longer works. Legacy
STUN clients (RFC 3489, pre-magic-cookie) receive no response even when
the option is explicitly enabled.
## Root Cause
Commit 4cc076d renamed `no_stun_backward_compatibility` (negative logic)
to `stun_backward_compatibility` (positive logic). Two call sites were
not updated correctly:
1. **`src/server/ns_turn_server.c`**: the
`old_stun_is_command_message_str` branch in the TCP/stream socket
handler kept `!` on the renamed variable, inverting the condition. Old
STUN was processed only when backward compat was *off*.
2. **`src/apps/relay/dtls_listener.c`**: the UDP/DTLS early packet
validation block never included an `old_stun_is_command_message_str`
check, so old STUN packets were always classified as invalid regardless
of the backward compat flag.
## Fix
- **`ns_turn_server.c`**: Remove the stray `!` negation restoring
correct condition semantics.
- **`dtls_listener.c`**: Add the missing old-STUN branch in the UDP
packet validation block, guarded by
`turn_params.stun_backward_compatibility`.
based on the ideas originally developed in [this
pr](https://github.com/coturn/coturn/pull/1535) by @WHYHD
---------
Co-authored-by: Pavel Punsky <eakraly@users.noreply.github.com>
A rewrite and reformat of the PostgreSQL.md file:
- Applies some minor formatting to the document.
- Applies consistency between the 'turn' user and 'coturn' database
names.
- Adds some warnings about issues I encountered during my own
installation process (public schema error)
- Adds a minor example of restarting a service with SystemD
We use Coturn in production at [mesibo](https://mesibo.com) and
identified a gap in dbdriver: there's no callback to capture
post-session metrics like duration, bandwidth consumed, timestamps, etc.
We've made the following changes, which could be useful for Coturn.
Would appreciate your consideration. These changes are tested in
production environments at mesibo.
## Changes for reporting usage
Added a `report_usage` callback to the database driver interface,
invoked at session termination. This allows drivers to capture and
persist detailed usage statistics in their preferred storage (MySQL,
PostgreSQL, etc.).
### 1. Database Driver Interface
**File:** `src/apps/relay/dbdrivers/dbdriver.h`
```c
typedef struct _turn_dbdriver_t {
// ... existing callbacks
void (*report_usage)(void *);
} turn_dbdriver_t;
```
### 2. Session Termination Hook
**File:** `src/apps/relay/ns_ioalib_engine_impl.c`
```c
void turn_report_session_usage(void *session, int force_invalid) {
// ... existing code
if(force_invalid) {
const turn_dbdriver_t * dbd = get_dbdriver();
if(dbd->report_usage)
dbd->report_usage(session);
}
// ... existing code
ss->received_packets = 0;
...
}
```
---------
Co-authored-by: Pavel Punsky <eakraly@users.noreply.github.com>
cli interface is ON by default which creates a security risk (even
though requires a password) and recommended to be disabled.
Instead of just recommendation, this PR disables CLI by default and now
requires an explicit flag to enable it
If using old configuration or cli arguments to turnserver - it will log
an error message about `--no-cli` being deprecated while doing nothing
(already disabled). This log line will be removed in the future
Disable the messages by default - they can be re-enabled using
`--include-reason-string` option
As a result of not sending reason string (which is optional by standard
and provide debugging information for the actual numeric error code)
response message size can be decreased by up to NNN bytes.
Memory allocation performance optimizations:
- Replaced a heap-allocated buffer with a stack-allocated one in
generate_cookie():
- Optimized buffer reuse in the UDP server's packet receive loop -
reduces unnecessary allocations/frees in the hot path of the UDP batch
receive loop (especially during DDoS)
The AES_encrypt, AES_set_encrypt_key, CRYPTO_ctr128_encrypt, and
SSL_CTX_use_RSAPrivateKey_file functions are deprecated in OpenSSL 3.0+
and produce compiler warnings.Replace deprecated low-level OpenSSL
AES/CRYPTO functions with the modern EVP (Envelope) API, and remove the
deprecated SSL_CTX_use_RSAPrivateKey_file fallback.
Changes
encrypt_aes_128 — Replaced AES_set_encrypt_key + CRYPTO_ctr128_encrypt
with EVP_EncryptInit_ex / EVP_EncryptUpdate / EVP_EncryptFinal_ex using
EVP_aes_128_ctr(). Added proper error handling (context cleanup on
failure), input length bounds checking, and enlarged the total buffer
from 256 to 1024 bytes to match the output buffer. The IV was corrected
from 8 to 16 bytes (as required by AES-CTR).
decrypt_aes_128 — Same migration from CRYPTO_ctr128_encrypt to
EVP_DecryptInit_ex / EVP_DecryptUpdate / EVP_DecryptFinal_ex. Added
proper cleanup of both the EVP context and the encryptedText allocation
on every error path. Retained the existing bounds check on newTotalSize.
Output is now explicitly null-terminated using the actual decrypted
length (outlen + final_len).
set_ctx (TLS context setup) — Removed the SSL_CTX_use_RSAPrivateKey_file
fallback that was nested inside the SSL_CTX_use_PrivateKey_file failure
path. SSL_CTX_use_PrivateKey_file already handles RSA keys, so the
RSA-specific fallback was redundant and used a function deprecated since
OpenSSL 3.0.
## Issue
strcpy(smethod, s) with no size check. Callers pass fixed buffers (e.g.
32 bytes); if API were misused with a smaller
buffer, or s were ever longer, this could overflow.
## Fix
Use strncpy with a fixed maximum (32), then null-terminate,
so at most 32 bytes are written regardless of caller buffer size.
## Issue
Multiple changes in this PR related to address printing (with and
without port)
- Change buffer size to be 64 (enough to hold IPv6 - 46, and port - 5,
and formatting "[ip]:port")
- Align buffer size across all usages (were 65, 129, 256, 257, 1025).
Even 65 is bad - takes extra cache line.
- Change argument to `addr_to_string_no_port`/`addr_to_string` to be of
type char inasted of uint8_t (double converted)
- Eliminate extra buffer in `addr_to_string_no_port`
- Defensively terminate string with null in addr_to_string`
## Explanations
- `addr_to_string_no_port` rely on `inet_ntop` to convert address to
null terminated string
- `addr_to_string` with port==0 rely on `inet_ntop`, otherwise null
terminate at the end of the buffer of size MAX_IOA_ADDR_STRING
## Issue
strncpy(realm/pwd, ..., STUN_MAX_*_SIZE) does not append a null. When DB
value length >= size, the buffer was
unterminated, leading to potential reads past buffer in later code.
## Fix
Explicitly set realm[STUN_MAX_REALM_SIZE] and pwd[STUN_MAX_PWD_SIZE] to
'\0' after each strncpy.
## Issue
strcpy(data, data_http) copied into the network buffer
with no size check. If buffer allocation or layout changed, this
could overflow.
## Fix
Use ioa_network_buffer_get_capacity(), copy at most that many
bytes with memcpy, explicitly null-terminate, and set size accordingly.
## Issue
strcat(last, (char *)outdata) then strcpy(out, last) with last[1024].
outdata is not null-terminated (decryption output),
allowing reads past buffer and possible overflow of last.
## Fix
Use bounded memcpy with explicit null termination, limiting copy to
remaining space in last and sizeof(outdata). Check malloc before strcpy.
Co-authored-by: Cursor <cursoragent@cursor.com>
## Issue
strncpy(realm/pwd, ...) without null termination
when source is long left buffers unterminated.
## Fix
Set realm[STUN_MAX_REALM_SIZE] and pwd[STUN_MAX_PWD_SIZE]
to '\0' after each strncpy.
**Issue**:
strcat(last, (char *)outdata) was used with a fixed
buffer last[1024]. outdata is decryption output that is not
null-terminated (CRYPTO_ctr128_encrypt writes newTotalSize bytes). This
could read past outdata and/or overflow last if decrypted size grew.
**Fix**:
Replace with bounded copy using memcpy and explicit null
termination, limiting bytes copied to remaining space in last and to
sizeof(outdata).
This returns the code to the state before #1279 that made turn_random() less secure and introduced more secure version with urn_random_number() (which is actually the same as turn_random() before the change)
Add CLI flag to enable early packet validation
Drop packets that do not pass basic STUN command/channel or DTLS parsing
Before this change, no validation on packets were done and they were
passed (through libevent queue) to relay thread pool. Relay thread
would, for a new source, allocate a new SS (18KB) which will only be
released after 1s of no traffic, and then do the validation.
So with old code, invalid packet would have extra:
- Queuing
- Processing on a different thread
- Memory allocation of 18KB
Assuming DDoS attack is spoofing IPs it reduces processing capacity
dramatically.
Testing possible by:
```
hping3 -2 -p 3478 -d 2 -rand-source --flood turn_ip
```
which floods `turn_ip:3478` with packets of size 2 from random sources.
Size 2 is especially bad case - the packet is obviously invalid (too
short) but still goes through a long process of queuing, thread
switching, memory allocation and only then validation (and then memory
cleanup etc). In worst cases, memory is never cleaned up because sources
repeat.
Some places in code do not have access to the buffer size which result
in crash which can be seen in tests
This PR removes the call to `set_ioa_socket_buf_size` from those places
(which is redundant anyway)
# Description
Replace the hardcoded buffer sizes inside coturn to make them
configurable for different use cases (low bitrate use cases can save
memory and high bitrate use case can avoid congestion) - based on #1089
Add this feature in both sides (listener and relay connections).
# Tests
For now it is only the automated CI tests.
Confirmed with debugger that buffer sizes are set according to the
arguments.