Files
continuwuity/docs/calls/turn.mdx
T

405 lines
13 KiB
Plaintext

# Setting up TURN/STUN
[TURN][turn] and [STUN][stun] are used as a component in many calling systems. Matrix uses them directly for legacy calls and indirectly for MatrixRTC via Livekit.
Continuwuity recommends using [Coturn][coturn] as your TURN/STUN server, which is available as a Docker image or a distro package.
[turn]: https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT
[stun]: https://en.wikipedia.org/wiki/STUN
[coturn]: https://github.com/coturn/coturn
## Installing Coturn
### 1. Domain
Coturn should live on its own domain or subdomain. In this guide we use `coturn.example.com` - this should be replaced with a domain you control.
### 2. Configuration
Create a configuration file called `coturn.conf` containing:
```ini
use-auth-secret
static-auth-secret=<a secret key>
realm=coturn.example.com
```
:::tip Tip: Generate a long, secure secret with the following command
```bash
pwgen -s 64 1
```
:::
If you want to enable TURN-over-TLS (TURNS), add the appropriate cert/key paths to your `coturn.conf` by adding the following lines:
```ini
cert=/etc/letsencrypt/coturn.example.com.crt
pkey=/etc/letsencrypt/coturn.example.com.key
```
The cert and key must be encoded in PEM format and are readable by the coturn user.
Check out the [`turnserver.conf`][turnserver-conf] example for all coturn options.
[turnserver-conf]: https://github.com/coturn/coturn/blob/master/examples/etc/turnserver.conf
### 3. Running the coturn container
Next, we will start the Coturn container with the [official image][coturn-image]. **Host networking mode** will be used, as it is better for performance and reduces configuration complexity (see [Coturn Docker docs][coturn-docker-docs] for rationale).
[coturn-image]: https://hub.docker.com/r/coturn/coturn
[coturn-docker-docs]: https://github.com/coturn/coturn/blob/master/docker/coturn/README.md#why-so-many-ports-opened
#### With Docker Compose (recommended)
Create a `docker-compose.yml` file as follows and run `docker compose up -d`:
```yaml
version: '3'
services:
turn:
container_name: coturn-server
image: docker.io/coturn/coturn
restart: unless-stopped
network_mode: "host"
volumes:
- ./coturn.conf:/etc/coturn/turnserver.conf
# replace this with actual paths to your certificates
- /path/to/certs:/etc/letsencrypt
```
#### With Docker
```bash
docker run -d --network=host \
-v $(pwd)/coturn.conf:/etc/coturn/turnserver.conf \
-v /path/to/certs:/etc/letsencrypt # replace this with actual paths to your certificates
coturn/coturn
```
### 4. Opening ports
By default, coturn uses the following ports:
- `3478` (UDP/TCP): Standard TURN/STUN port
- `5349` (UDP/TCP): TURN/STUN over TLS
- `49152-65535` (UDP): Media relay ports
You will need to allow them through your firewall. If you use UFW, the commands are:
```bash
ufw allow 3478/udp
ufw allow 3478/tcp
ufw allow 5349/tcp
ufw allow 5349/udp
ufw allow 49152-65535/udp
```
::: tip For LiveKit users
If you're also running LiveKit, you'll need configure non-overlapping port ranges to avoid port conflicts:
```ini
# In coturn.conf
min-port=50201
max-port=65535
```
```bash
# With ufw
ufw allow 50201:65535/udp
```
This leaves ports `50100-50200` available for LiveKit's default configuration.
:::
### 5. Security Recommendations
For Coturn hardening and security best practices, see Synapse's [Coturn documentation][synapse-coturn-guide],
which includes important firewall and access control recommendations.
[synapse-coturn-guide]: https://element-hq.github.io/synapse/latest/setup/turn/coturn.html#configuration
## Configuring Continuwuity
Once your TURN server is running, configure Continuwuity to provide credentials to clients. Add the following to your Continuwuity configuration file:
```toml
# TURN URIs that clients should connect to
turn_uris = [
"turn:coturn.example.com:3478?transport=udp",
"turn:coturn.example.com:3478?transport=tcp",
# Add this if you're using TURN-over-TLS (note the `turns:` prefix)
"turns:coturn.example.com:5349?transport=tcp"
]
# Shared secret for generating credentials (must match coturn's static-auth-secret)
turn_secret = "<your coturn static-auth-secret>"
# Optional: Read secret from a file instead (takes priority over turn_secret)
# turn_secret_file = "/etc/continuwuity/.turn_secret"
# TTL for generated credentials in seconds (default: 86400 = 24 hours)
turn_ttl = 10800
```
Restart Continuwuity, and the new changes should now be applied.
## Testing Your TURN Server
### Testing Credentials
Get an access token for your current login session. These can be found in your client's settings or obtained via [this website](https://timedout.uk/mxtoken.html).
Then, using that token, verify that Continuwuity is correctly serving TURN credentials to clients:
```bash
curl "https://matrix.example.com/_matrix/client/r0/voip/turnServer" \
-H "Authorization: Bearer <your_client_token>" | jq
```
You should receive a response like this:
```json
{
"username": "1752792167:@jade:example.com",
"password": "KjlDlawdPbU9mvP4bhdV/2c/h65=",
"uris": [
"turns:coturn.example.com?transport=tcp",
"turn:coturn.example.com?transport=udp",
"turn:coturn.example.com?transport=tcp"
],
"ttl": 86400
}
```
### Testing Connectivity
Open the [Trickle ICE][trickle-ice] testing page in a browser and then:
1. Copy the URIs and credentials from the response above
2. Paste them into the Trickle ICE testing tool, and click on "Add server"
3. When finished adding all URIs and credentials, click "Gather candidates"
If you see `relay` candidates in the results, your TURN/STUN server is working correctly! You should now be able to place and receive legacy calls.
[trickle-ice]: https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
## Troubleshooting
### Errors with Trickle ICE
- `code=701` - the TURN server is not reachable
- Verify firewall rules allow the necessary ports (3478, 5349, and your media port range)
- Check that DNS resolves correctly for your TURN domain
- `code=401` - unauthorized credentials
- Ensure your `turn_secret` matches coturn's `static-auth-secret`
- Ensure the credentials you obtained from the Testing steps has not expired yet. You can adjust `turn_ttl` in your Continuwuity configuration to increase this, or simply re-request a new one
### 404 when calling the turnServer endpoint
This is the correct response when no TURN servers are configured, as per [MSC4166][msc4166]. Verify that your `turn_uris` is not empty in your Continuwuity config and try again.
[msc4166]: https://github.com/matrix-org/matrix-spec-proposals/pull/4166
## Related Documentation
- [MatrixRTC/LiveKit Setup](./livekit.mdx) - Configure group calling with LiveKit
- [Coturn GitHub][coturn] - Official coturn repository
- [`turnserver.conf`][turnserver-conf] - Coturn TURN SERVER configuration file with full options
- [Synapse TURN Guide][synapse-turn-guide] - TURN server guide for Synapse
- [Synapse Coturn Guide][synapse-coturn-guide] - Coturn-specific guide for Synapse, with important security recommendations
[synapse-turn-guide]: https://element-hq.github.io/synapse/latest/turn-howto.html
## Appendix
### Using Eturnal
If you instead prefer to use [eturnal][eturnal], you can refer to the guides below:
- [Community guidance][eturnal-continuwuity] on using eturnal with Legacy Calls, LiveKit calls, and more
- Synapse's [eturnal documentation][synapse-eturnal-guide]
[eturnal]: https://eturnal.net
[eturnal-continuwuity]: https://muoi.me/~stratself/articles/an-eturnal-to-rule-them-all/
[synapse-eturnal-guide]: https://element-hq.github.io/synapse/latest/setup/turn/eturnal.html
### TURNS-over-443
In very restrictive networks where UDP traffic and non-standard ports are disallowed, normal TURN servers are not reachable. To establish connectivity in these scenarios, a common solution is to host a TURN-over-TLS server on port 443, and allow clients to connect to it like any other web traffic.
However, port 443 is usually utilized by other HTTPS services. Therefore, one would need to **multiplex** both the TURN and the HTTPS services, and filter packets to them via **SNI routing**. A layer-4 load balancer such as caddy-l4 can serve this function.
Below are Caddyfile examples to multiplex both the TURNS service and LiveKit on port 443. It requires the [caddy-l4][caddy-l4] plugin to be compiled with your Caddy binary.
<details>
<summary>Caddyfile with TLS passthrough</summary>
This Caddyfile:
- Route `turn.example.com` to the TURNS port for Coturn without TLS termination, and
- Route `livekit.example.com` to the [LiveKit services](./livekit.mdx) with TLS termination by Caddy
Please note that all traffic from Coturn's perspective will be coming from caddy-l4's IP now.
```
{
servers {
listener_wrappers {
# intercept packets meant for the TURN domain first
# before forwarding other packets to "normal" HTTP listeners
layer4 {
@turn {
tls {
sni turn.example.com
}
}
route @turn {
proxy {
upstream 127.0.0.1:5349 # forward to normal TURNS port
}
}
}
tls
}
}
}
# livekit stuff
https://livekit.example.com {
@lk-jwt-service path /healthz /get_token /sfu/get
route @lk-jwt-service {
reverse_proxy 127.0.0.1:8081
}
reverse_proxy http://127.0.0.1:7880
}
```
</details>
<details>
<summary>Caddyfile with TLS termination and PROXY protocol forwarding</summary>
This setup:
- Terminates TLS for `turn.example.com`,
- Tag the decrypted packets with PROXY protocol, and route it to coturn's `tcp-proxy-port`
- Route `livekit.example.com` to the [LiveKit services](./livekit.mdx) with TLS termination by Caddy
It allows coturn to see real client IPs, but the TLS handling is done on Caddy's side.
First, enable coturn's PROXY-protocol accepting port by adding this:
```ini
# in coturn.conf
tcp-proxy-port=5555
```
Then, in the Caddyfile:
```
{
servers {
listener_wrappers {
# intercept packets meant for the TURN domain first
# before forwarding other packets to "normal" HTTP listeners
layer4 {
@turn {
tls {
sni turn.example.com
}
}
route @turn {
tls # terminate TLS for the turn.example.com packets
proxy {
# then, proxy them to tcp-proxy-port and enable PROXY protocol version 2
upstream 127.0.0.1:5555
proxy_protocol v2
}
}
}
tls
}
}
}
# livekit stuff
https://livekit.example.com {
@lk-jwt-service path /healthz /get_token /sfu/get
route @lk-jwt-service {
reverse_proxy 127.0.0.1:8081
}
reverse_proxy http://127.0.0.1:7880
}
# placeholder server block to obtain certs for turn.example.com
https://turn.example.com {
respond "OK" 200
}
```
</details>
After configuration and spin-up, the destination `turns:turn.example.com:443?transport=tcp` should work with Trickle ICE tests. You can now advertise it as an address in your `turn_uris` as well as LiveKit.
[caddy-l4]: https://github.com/mholt/caddy-l4
### Unsafe TURN setups (not recommended)
These TURN setups are available, but **not recommended** due to security issues. They are only included for completeness.
<details>
<summary>Using static credentials</summary>
If you prefer static username/password credentials instead of shared secrets:
```ini
# In coturn.conf
# Comment out options to use a secret
# use-auth-secret
# static-auth-secret=<a secret key>
# Define a username-password pair
user=your_username:your_password
```
```toml
# In continuwuity.toml
turn_uris = [
"turn:coturn.example.com?transport=udp",
"turn:coturn.example.com?transport=tcp"
]
turn_username = "your_username"
turn_password = "your_password"
```
:::caution
Static credentials are less secure than shared secrets because they don't expire and must be configured in coturn separately. It is strongly advised you use shared secret authentication.
:::
</details>
<details>
<summary>Allowing guest access</summary>
By default, TURN credentials require client authentication. To allow unauthenticated access:
```toml
turn_allow_guests = true
```
:::caution
This is not recommended as it allows unauthenticated users to access your TURN server, potentially enabling abuse by bots. All major Matrix clients that support legacy calls *also* support authenticated TURN access.
:::
</details>