378 lines
12 KiB
Text
378 lines
12 KiB
Text
# Setting up TURN/STUN
|
|
|
|
[TURN][turn] and [STUN][stun] are used as components 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. This guide assumes that you are using docker compose for deployment.
|
|
|
|
:::tip
|
|
You can find help setting up TURN/STUN in our MatrixRTC room - [#matrixrtc:continuwuity.org](https://matrix.to/#/%23matrixrtc%3Acontinuwuity.org)
|
|
:::
|
|
|
|
[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. Set up your 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. Configuring coturn
|
|
|
|
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 certificate and 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 must be 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** is used here for better performance and reduced configuration complexity (see [coturn's 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
|
|
|
|
Create a `docker-compose.yml` file as follows and run `docker compose up -d`:
|
|
|
|
```yaml
|
|
services:
|
|
coturn:
|
|
container_name: coturn
|
|
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
|
|
```
|
|
|
|
### 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][starstruck-mxtoken2].
|
|
|
|
[starstruck-mxtoken2]: https://starstruck.systems/matrix/tools/mxtoken2
|
|
|
|
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:3478?transport=tcp",
|
|
"turn:coturn.example.com:3478?transport=udp",
|
|
"turn:coturn.example.com:5349?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. Once all the URIs and credentials have been added, 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
|
|
|
|
To gather debug logs while troubleshooting coturn, add `verbose` to your `coturn.conf`. You can then view these logs with `docker compose logs --follow coturn`.
|
|
|
|
### 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)
|
|
- Verify via logs that coturn is exposed on the correct addresses and interfaces
|
|
- 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
|
|
- Wrong IP address advertised
|
|
- This may be caused by coturn not recognizing its public-facing IP correctly. You can configure `external-ip=<desired-public-ip>` to fix this issue.
|
|
|
|
### 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
|
|
|
|
### TURN-over-TLS on 443
|
|
|
|
TURN servers are generally reachable on their default ports, as configured above. However, there are situations where clients may be unable to use nonstandard ports or UDP connections, such as in highly restrictive networks. In such cases, a TURN-over-TLS server on port 443 could relay traffic for the clients.
|
|
|
|
However, port 443 is usually used by other HTTPS services. Therefore, one would need to multiplex both TURN-over-TLS and HTTPS on these ports, and filter packets to them via SNI routing.
|
|
|
|
Below are examples to multiplex coturn and [LiveKit](./livekit.mdx) on port 443, using [Caddy-L4][caddy-l4] on the host system.
|
|
|
|
<details>
|
|
|
|
<summary>Caddyfile with TLS passthrough</summary>
|
|
|
|
This Caddyfile:
|
|
|
|
- Route `turn.example.com` to the TURN-over-TLS port for coturn (port 5349) 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 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 block to obtain certs for turn.example.com
|
|
https://turn.example.com {
|
|
respond "OK" 200
|
|
}
|
|
```
|
|
|
|
**Note**: the setup will disable TURN-over-TLS functionality on port 5349/tcp.
|
|
|
|
</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](./livekit#additional-turn-over-tls-on-443-configuration).
|
|
|
|
[caddy-l4]: https://github.com/mholt/caddy-l4
|
|
|
|
### Unsafe TURN setups (not recommended)
|
|
|
|
<details>
|
|
|
|
<summary>Using static credentials</summary>
|
|
|
|
:::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](#2-configuration).
|
|
:::
|
|
|
|
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"
|
|
```
|
|
|
|
</details>
|