mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-08-14 15:40:02 +00:00
405 lines
13 KiB
Plaintext
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>
|