Setting up a TURN server with Node: Production-ready
In this guide, you’ll learn how to set up a TURN relaying server and how to configure it to use with Node.JS for a production app.
The problem
If you’re familiar with WebRTC and P2P (peer-to-peer) connection, then you should know about its’ limitations.
One of the biggest hurdles in setting up a direct P2P connection is the existence of NATs (Network Address Translation).
As the name suggests, NAT basically converts one IP address to another. For example, when you’re behind a router, you have an internal IP associated with you, and then when it passes through the router you have another IP address that is considered public.
So what’s the problem?
When you’re connecting to the internet behind a NAT, your device only has knowledge of the IP associated with it (e.g. assigned IP from a router). However, other devices from around the internet only know your external IP (e.g. your router’s IP).
This causes problems when you connect P2P, as your device has no knowledge of its’ own external IP.
The solution
There are 2 ways of dealing with this:
Remember when I said that devices (e.g. your laptop) don’t know of their external IP address when behind a NAT? Well, STUN provides you with exactly that. When you make a request to a STUN server, it replies with your public IP address. This way you are now capable of connecting through P2P. That’s all!
STUN works well when the NAT works the same way for every connection. However, there are situations where a device is behind a symmetric NAT. In this case, connections to the “outside” always change, so you can’t define a specific IP and port to map your device.
To overcome this, a workaround is to simply relay all the communication through a server. With TURN, P2P is not so peer-to-peer (not at all, actually), as the communication is now going through a server instead.
Installing coTURN
Great, so now you know 2 of the fallacies of P2P, so how can you fix it in your case?
coTURN is an open-source implementation that lets you create a STUN and TURN server in one, without much hassle.
Let’s say you spin up a Debian (or Ubuntu) server on Digitalocean, AWS, GCloud, etc… In this case, you should first open the required ports:
80: TCP (if you need SSL)
443: TCP (if you need SSL)
3478: TCP/UDP
5349: TCP/UDP
Since managing the firewall depends on the provider you’re using, it’s out of the scope of this guide. If you’re having trouble, reply in the comments and I’ll help you out.
Once you’re done, all you need is to SSH into your server and install with the following commands:
sudo apt-get -y update
sudo apt-get -y install coturn
turnserver
That’s it! That’s all you need. You can check if it’s running by executing:
sudo systemctl status coturn
Which should show:
coturn.service - coTURN STUN/TURN Server
Loaded: loaded (/lib/systemd/system/coturn.service; enabled; ....)
Active: active (running) since Mon 2020-08-03 19:06:01 UTC; 3 days ago
If you want to execute coTURN as a service, edit the file /etc/default/coturn and add the following line to it:
TURNSERVER_ENABLED=1
Configuring coTURN
Although coTURN is now installed and running, there are a few things you should configure to:
- Enhance the security of your server
- Prevent other apps from using your TURN server
- Set up SSL
To set up SSL, please go to certbot.eff.org and follow the interactive tutorial on setting up Let’s Encrypt on your server. We’ll use the certificates generated by certbot later.
We should also install fail2ban, which is a simple but effective extension that protects your server against unwanted requests. Let’s do it by running the command:
sudo apt install fail2ban
Now, on Debian / Ubuntu, the configuration file for coTURN can be found in /etc/turnserver.conf
If you check its’ contents, you’ll see it’s well documented for all you need, but you usually want to have a configuration like follows:
fingerprint
use-auth-secret
static-auth-secret=[SECRET] #change this
realm=[YOUR DOMAIN] #change this with e.g. example.com
total-quota=100
syslog
no-multicast-peers
cert=/etc/letsencrypt/live/[YOUR DOMAIN]/fullchain.pem #certificate from ssl
pkey=/etc/letsencrypt/live/[YOUR DOMAIN]/privkey.pem #certificate from ssl
If your server is also behind a NAT (e.g if you’re using AWS), you need to add 3 more options
listening-ip=[INTERNAL IP ADDRESS] #change this
relay-ip=[INTERNAL IP ADDRESS] #change this
external-ip=[EXTERNAL IP ADDRESS]/[INTERNAL IP ADDRESS] #change this
In many other configurations, you see using lt-cred-mech instead of use-auth-secret.
In WebRTC, you need to share the user and password with the client for it to be able to connect with the server.
With lt-cred-mech, you’ll set a password that is directly shared with the user. This is easy to set up and good for testing, but not recommended for production.
With use-auth-secret, you’ll set a secret that generates other temporary passwords based on cryptography. This is crucial for any production app because it’ll prevent malicious activity like stealing your server to relay their own communications.
Now, it’s important that you change static-auth-secret with something secure. You’ll need this to generate the credentials for your backend server. To generate a secure password in Ubuntu / Debian, run the following command:
openssl rand -base64 32
After you finish configuring, restart coTURN like this:
sudo systemctl restart coturn
And you’re done with the TURN server!
Generating temporary credentials in Node.JS
Now you have your TURN server working and properly configured. If you already set up a WebRTC connection in your frontend application, then the configuration params for RTCPeerConnection would be like like the following:
{
iceServers: [
{
urls: ['stun:yourturn.server.com']
},
{
urls: ['turn:yourturn.server.com'],
username: '????',
credential: '????'
}
],
sdpSemantics: 'unified-plan'
}
The username and credential in ???? are what we’ll be working to generate in this section, for a production-ready setup.
To generate the keys in Node.JS, use this code:
const crypto = require("crypto");
const secret = "YOUR_SECRET_KEY" // Replace this with the key from static-auth-secret
function generateTurnKey() {
// The username is a timestamp that represents the expiration date of this credential
// In this case, it's valid for 12 hours (change the '12' to how many hours you want)
const username = (Date.now() / 1000 + 12 * 3600).toString();
// Now create the corresponding credential based on the secret
const hmac = crypto.createHmac("sha1", secret);
hmac.setEncoding("base64");
hmac.write(username);
hmac.end();
const credential = hmac.read();
return {
username,
credential,
};
};
console.log(generateTurnKey());
Now all you need is to send the generated values to the client (e.g. with Socket.io) and replace the values ‘????’ at the beginning of this section with what’s generated in the function above.
Testing the TURN server
To test your TURN server connectivity, open this link: Trickle ICE.
In “STUN or TURN URI” field type “turn:your.turnserver.com”
Now, run the function that generates the temporary in Node.JS, copy the “username” value, and paste it into “TURN username”. Do the same with “credential” and paste it in “TURN password”. Now press “Gather candidates”.
It should show up a list of candidates without errors if it’s working properly.
You can also try setting a wrong username or password on purpose to see if you get “Unauthorized” in the response.
If it does, then everything is working properly. Congrats!
If you have any questions, please comment below and I’ll be answering as much as I can. Happy TURNing!