STUN/TURN using Coturn - Corporate FW solution ** HERE'S HOW **

So after a very long journey with 0 experience setting up STUN/TURN server and a great success I decided to write this guide to shorten the path for those who need this solution.

How does it work
Background (as far as I got it)
Corporate networks block any type of communications other than ports 80 and 443 unless they trust your app and whitelist it.
Since you can’t expect every corporation IT to whitelist your app this solution was created.

A relay server called TURN server to connect to the firewalled endpoint via 443 (or another per your configuration) TCP and to the JVB (in this case) via port 10000 UDP.

On this guide I’ll be explaining how to set-up an external TURN server using CoTURN (open source) in an Ubuntu environment. I used 20.04.

What you’ll need:

  1. Ubuntu server (I used 20.04) - get a good hardware for your installation, you’ll need it if you have many firewalled corporate users.
  2. Your server’s internet-facing public IP
  3. A domain for SSL connection
  4. SSL certificate buy one or use Let’s Encrypt free 3 months one.

Let’s begin.

STEP A - set up your server and install CoTURN
Get an Ubuntu server and set it’s connectivity and OS. get a good hardware for your installation, you’ll need it if you have many firewalled corporate users. To my impression you can configure just one TURN server per Jicofo//Prosody instance. Meaning 1 TURN server per Jitsi Shard.

I might be wrong on this - I never tried configuring more than one per shard.

Make sure it’s up-to-date using the command:

sudo apt-get -y update

Install the CoTURN package

sudo apt-get install coturn

Then you’ll need to verify CoTURN isn’t running. Type

systemctl stop coturn

Enable your CoTURN server

nano /etc/default/coturn

Be sure that the content of the file has the properly uncommented, like this

TURNSERVER_ENABLED=1

Save changes and exit the nano editor.

Next stop - STEP B - Configuration of your CoTURN server

First… we always play safe. Backup your out-of-the-box config file

mv /etc/turnserver.conf /etc/turnserver.conf.original

Now, simply jump right in to business

nano /etc/turnserver.conf

Copy and paste to following config

# /etc/turnserver.conf

# STUN server port is 3478 for UDP and TCP, and 5349 for TLS.
# Allow connection on the UDP port 3478
#listening-port=3478
# and 5349 for TLS (secure)
tls-listening-port=443
no-multicast-peers
no-cli
no-loopback-peers
no-tcp-relay
no-tcp
no-tlsv1
no-tlsv1_1
no-tlsv1_2
no-sslv3
keep-address-family

# Require authentication
fingerprint
lt-cred-mech

# We will use the longterm authentication mechanism, but if
# you want to use the auth-secret mechanism, comment lt-cred-mech and 
# uncomment use-auth-secret
# Check: https://github.com/coturn/coturn/issues/180#issuecomment-364363272
#The static auth secret needs to be changed, in this tutorial
# we'll generate a token using OpenSSL
# use-auth-secret
# static-auth-secret=replace-this-secret
# ----
# If you decide to use use-auth-secret, After saving the changes, change the auth-secret using the following command:
# sed -i "s/replace-this-secret/$(openssl rand -hex 32)/" /etc/turnserver.conf
# This will replace the replace-this-secret text on the file with the generated token using openssl. 

# Specify the server name and the realm that will be used
# if is your first time configuring, just use the domain as name
server-name=yourdomain.com
realm=yourdomain.com

# Important: 
# Create a test user if you want
# You can remove this user after testing
user=user:whateverpassword

total-quota=100
stale-nonce=600

# Path to the SSL certificate and private key. In this example we will use
# the letsencrypt generated certificate files.
cert=/usr/local/psa/var/modules/letsencrypt/etc/live/domain.com/fullchain.pem
pkey=/usr/local/psa/var/modules/letsencrypt/etc/live/domain.com/privkey.pem

# Specify the allowed OpenSSL cipher list for TLS/DTLS connections
cipher-list="ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384"

# Specify the process user and group
proc-user=turnserver
proc-group=turnserver
no-sslv3
no-tlsv1
no-tlsv1_1
no-tlsv1_2

# jitsi-meet coturn relay disable config. Do not modify this line
denied-peer-ip=0.0.0.0-0.255.255.255
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=100.64.0.0-100.127.255.255
denied-peer-ip=127.0.0.0-127.255.255.255
denied-peer-ip=169.254.0.0-169.254.255.255
denied-peer-ip=127.0.0.0-127.255.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.0.0.0-192.0.0.255
denied-peer-ip=192.0.2.0-192.0.2.255
denied-peer-ip=192.88.99.0-192.88.99.255
denied-peer-ip=192.168.0.0-192.168.255.255
denied-peer-ip=198.18.0.0-198.19.255.255
denied-peer-ip=198.51.100.0-198.51.100.255
denied-peer-ip=203.0.113.0-203.0.113.255
denied-peer-ip=240.0.0.0-255.255.255.255
denied-peer-ip=::1
denied-peer-ip=64:ff9b::-64:ff9b::ffff:ffff
denied-peer-ip=::ffff:0.0.0.0-::ffff:255.255.255.255
denied-peer-ip=100::-100::ffff:ffff:ffff:ffff
denied-peer-ip=2001::-2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=2002::-2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff

syslog

STEP C - Set up your domain
For the SSL certificate you must have an A record on your domain that points to your server’s IP.
You’ll also have to set-up SRV records.
Buy your domain and set-up the DNS with an A record that points to the server’s public IP address.
Set up your A record to point to your server IP.

turn.yourdomain.com

Set up your SRV records like shown in the images below. I’m attaching images from Godaddy’s console for clarity. It was my first time setting up SRV records manually.
STUN SRV record

TURN SRV record

TURNS SRV record

STEP D - Get your domain SSL certificate
Get a SSL certificate from let’s encrypt // Zero SSL // or a company of your choice.
Once you have your certificate and private key copy and paste them into the cert.pem and privkey.pem you have in your config. Make sure to get the certificate with the full chain.
In this example

/usr/local/psa/var/modules/letsencrypt/etc/live/ourcodeworld.com/fullchain.pem
/usr/local/psa/var/modules/letsencrypt/etc/live/ourcodeworld.com/privkey.pem

STEP E - Turn your CoTURN server on and test
Turn your server on

systemctl start coturn

Check the process to verify it’s active

systemctl status coturn

You should get this output

There’s an awesome online tool that allows you to check the functionality of STUN/TURN servers. This tool is Trickle ICE, a WebRTC page project that tests the trickle ICE functionality in a regular WebRTC implementation. It creates a PeerConnection with the specified ICEServers (which will contain the information of our recently implemented server), and then starts candidate gathering for a session with a single audio stream. As candidates are gathered, they are displayed in the text box below, along with an indication when candidate gathering is complete.

To get started open the tool website in a new browser tab and start filling the ICE servers form. In the form you will need to provide the STUN or TURN URI respectively with the credentials (only for the TURN server) like this:

We need to verify the connection on port 443.
Go to Trickle ICE and fill the fields similar to this:

Important
If you’re getting an error with binding port 443 run the following commands

sudo rm /lib/systemd/system/coturn.service
sudo systemctl daemon-reload
service coturn restart

STEP F - Configuring Prosody - the fun begins

Open Prosody configuration

nano /etc/prosody/conf.d/yourdomain.com.cfg.lua


In the first few lines you’ll find the following

external_service_secret = "SECRET";
external_services = {
     { type = "stun", host = "turn.yourdomain.com", port = 3478 },
     { type = "turn", host = "turn.yourdomain.com", port = 3478, transport = "udp", secret = true, ttl = 86400, algorithm = "turn" },
     { type = "turns", host = "turn.yourdomain.com", port = 443, transport = "tcp", secret = true, ttl = 86400, algorithm = "turn" }
};

First, you’ll need to set up a auth-secret on the CoTURN server and configure it on prosody to enable the connection.

Go back to your CoTURN server and edit the config file

nano /etc/turnserver.conf

Remove the # sign before the line

# static-auth-secret=replace-this-secret

Set up a secret key as you see fit. Make it complex.
The line should look like this

static-auth-secret=some-complicated-secret

Restart CoTURN

service coturn restart

Now go to the prosody configuration and replace SECRET with your some-complicated-secret

external_service_secret = "some-complicated-secret";

Next, you’ll need to configure the “turns” line and set the the port to 443 and TCP. You can remove the turn line as we want to connection to be secured using “turns”

external_service_secret = "SECRET";
external_services = {
     { type = "stun", host = "turn.yourdomain.com", port = 3478 },
     { type = "turns", host = "turn.yourdomain.com", port = 443, transport = "tcp", secret = true, ttl = 86400, algorithm = "turn" }
};

Now save and exit the file and Restart prosody.

service prosody restart 

This is it. to view CoTURN logs type

 tail -n 500 -f /var/log/syslog

You’re all set. You can test the connection by blocking port 10,000 UDP on your local machine firewall and try join a meeting. You should see the logs populate on the CoTURN logs.

1 Like

Mind that this removes turn that uses a SSL connection. There are certain firewalls that allow only secure links over 443 and this configuration will not work there.

Im not sure thats correct since it goes through the tls listening port.

With “turn” coturn was not reachable at all. There was no incoming connection.

On other guides i found online the show the use of tls over 443 with “turn” not “turns”.

So essentially the URL will be

turn:turn.yourdomain.com

I understand “turn” ans “turns” equivalent to “http” and “https” but again. There was not even a connection attempt shown on the server with “turns”.


This is on meet.jit.si no idea why it would behave differently in your browser …

turns works for me.

FYI the coturn configuration is very tortuous and has implicit options that are not easy to follow. For example, if you include certificates in your config, coturn thinks that as you want tls, you want tcp and ignores the no-tcp option that becomes a no-operation (and you don’t get any warning in the log to boot). There could be other traps that I’m not (thankfully) aware because I never tripped them but could concern your particular case.

Me neither. Just glad it finally works…

Can you share your config?

This is a working config

# jitsi-meet coturn config. Do not modify this line
use-auth-secret
keep-address-family
static-auth-secret=Qtslk8DZTlCfNagi
realm=jitsi.mydomain.corp
cert=/etc/jitsi/meet/jitsi.mydomain.corp.crt
pkey=/etc/jitsi/meet/jitsi.mydomain.corp.key
no-multicast-peers
no-cli
no-loopback-peers
no-tcp-relay
no-tcp
listening-port=3478
tls-listening-port=5349
no-tlsv1
no-tlsv1_1
# https://ssl-config.mozilla.org/#server=haproxy&version=2.1&config=intermediate&openssl=1.1.0g&guideline=5.4
cipher-list=ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
# jitsi-meet coturn relay disable config. Do not modify this line
denied-peer-ip=0.0.0.0-0.255.255.255
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=100.64.0.0-100.127.255.255
denied-peer-ip=127.0.0.0-127.255.255.255
denied-peer-ip=169.254.0.0-169.254.255.255
denied-peer-ip=127.0.0.0-127.255.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.0.0.0-192.0.0.255
denied-peer-ip=192.0.2.0-192.0.2.255
denied-peer-ip=192.88.99.0-192.88.99.255
denied-peer-ip=192.168.0.0-192.168.255.255
denied-peer-ip=198.18.0.0-198.19.255.255
denied-peer-ip=198.51.100.0-198.51.100.255
denied-peer-ip=203.0.113.0-203.0.113.255
denied-peer-ip=240.0.0.0-255.255.255.255
denied-peer-ip=::1
denied-peer-ip=64:ff9b::-64:ff9b::ffff:ffff
denied-peer-ip=::ffff:0.0.0.0-::ffff:255.255.255.255
denied-peer-ip=100::-100::ffff:ffff:ffff:ffff
denied-peer-ip=2001::-2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=2002::-2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff
syslog

listening-ip=172.22.22.14
allowed-peer-ip=172.22.22.14
no-udp

I’m first to admit I was wrong. You were right - it wasn’t SSL. Thank @damencho

We did have a certificate issue where Chrome couldn’t read it properly. We used this online tool to capture the entire chain in a format that was accepted using our certificate main domain.
https://whatsmychaincert.com/

All works now :grinning:

Guide corrected.

Hello @rn1984,
In first, thank you for your share.
I try to implemente your configuration. it work fine for 2 or more participants but for the participants behind a restricted network, that don’t work.
They go through by coturn service but for the client more restricted nothing.
I don’t have either the status of the connection for this client.
I see the error below in coturn log.

JVB 2022-03-16 18:38:17.939 INFO: [115] [confId=47819d12d582a780 gid=24747 stats_id=Jaylen-2Ex conf_name=itryit@conference.meet.domain.com ufrag=22nv61fua0bh0v epId=9ca3ada3 local_ufrag=22nv61fua0bh0v] Agent.checkListStatesUpdated#1927: ICE state is FAILED

JVB 2022-03-16 18:38:18.040 INFO: [114] [confId=47819d12d582a780 gid=24747 stats_id=Jaylen-2Ex conf_name=itryit@conference.meet.domain.com ufrag=22nv61fua0bh0v epId=9ca3ada3 local_ufrag=22nv61fua0bh0v] ConnectivityCheckClient.processTimeout#880: timeout for pair: 74.3.54.1:10000/udp/host -> 86.48.35.55:54404/udp/prflx (stream-9ca3ada3.RTP), failing.

JVB 2022-03-16 18:38:18.536 INFO: [67] [confId=47819d12d582a780 gid=24747 stats_id=Jaylen-2Ex conf_name=itryit@conference.meet.domain.com ufrag=59sg01fua0c2ei epId=9ca3ada3 local_ufrag=59sg01fua0c2ei] Agent.triggerCheck#1739: Add peer CandidatePair with new reflexive address to checkList: CandidatePair (State=Frozen Priority=7962116751041232895):

        LocalCandidate=candidate:2 1 udp 2130706431 74.3.54.1 10000 typ host

        RemoteCandidate=candidate:10000 1 udp 1853824767 86.48.35.55 61123 typ prflx

JVB 2022-03-16 18:38:18.698 WARNING: [109] [confId=47819d12d582a780 gid=24747 stats_id=Jaylen-2Ex conf_name=itryit@conference.meet.domain.com ufrag=59sg01fua0c2ei epId=9ca3ada3 local_ufrag=59sg01fua0c2ei] ConnectivityCheckClient.startCheckForPair#374: Failed to send BINDING-REQUEST(0x1)[attrib.count=6 len=92 tranID=0x4A0C06947F01A46CBD939D04]

java.lang.IllegalArgumentException: No socket found for 74.3.54.1:10000/udp->192.168.1.179:61123/udp

I think it’s because JVB try to response to this client with UDP port and i don’t know why…
Can you help me for that?
Could you share me one link of your instance for try it please?
Thank you in advance.

Yeah we just tested today and same result.
** I think **, @damencho and @gpatel-fr please correct me if I’m wrong, this might happen if the bridges are separated servers from Jicofo and are set with their IP address instead of their domain URL.

Maybe that’s why the ketstore SSL certificate is not loaded which causes the connection to drop on high-security networks?

What do you guys think?

These people and their reverse proxy, ugh! :dizzy_face:

Not sure what is this result actually. The OP (other poster) says it’s a coturn log while it’s a jvb log actually. So there is no reason to be sure that coturn works in this case, while you said it was working for you. The OP says that it does not work for ‘restricted network’, while if it works for you it can only be through ‘restricted networks’ else there is no point for the system to use coturn. So additional information needed for me. Does coturn work or not ? if partially (?), in which case ?

As of the external JVB I’m not sure that public DNS names are absolutely necessary, since direct DTLS traffic (port 10000) don’t use public certificates, and Colibri traffic is usually proxied through Nginx and as such is crypted (if not protected…) by the HTTPS certificate of the web server.

Yep about DTLS. In their case, it is coturn directly being connected, as I understand, and coturn is using its own certificate.

Yeah, jvb connection is behind coturn when using turn/turns. And what the firewall will see if connection is established is a normal https connection between client and coturn, which will encapsulate the media.

Thank you both. Some clarifications:
A. same result - we have a client the logs in from a bank’s network. They’re able to join the meeting but no video//audio and they’re getting bridge channel closed error on their console which ends up kicking them off the meeting.

B. When that client joined there was no movement on CoTurn, Nothing, no connection.

C. When we asked for logs, they sent us that their system identifies our bridge IP and blocks the 10000 connection under a security policy. **Important to mention that client was able to join a meet.jit.si without a hinge.

D. On the test I conducted I blocked the 10000 UDP port using my computer’s FW and then I saw immediate connection on CoTurn and was able to join.

I hope this gives some insight and you guys can help us solve this issue. Tough 3 weeks tackling this.

Did you use the telnet interface to check that your coturn is working as you think it does when you test with your computers having the port 10000 blocked ?

telnet localhost 5766
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
TURN Server
Coturn-4.5.1.1 'dan Eider'

Type '?' for help
Enter password: 
qwerty7
> ps

    1) id=000000000000000001, user <1647552710>:
      realm: meet.example.com
      started 20 secs ago
      expiring in 580 secs
      client protocol TCP, relay protocol UDP
      client addr 10.11.0.1:33290, server addr 10.11.0.247:3478
      relay addr 10.11.0.247:49729
      fingerprints enforced: OFF
      mobile: OFF
      usage: rp=2, rb=140, sp=1, sb=116
       rate: r=0, s=0, total=0 (bytes per sec)

    2) id=002000000000000001, user <1647552675>:
      realm: meet.example.com
      started 20 secs ago
      expiring in 580 secs
      client protocol TCP, relay protocol UDP
      client addr 10.11.0.1:33286, server addr 10.11.0.247:3478
      relay addr 10.11.0.247:54588
      fingerprints enforced: OFF
      mobile: OFF
      usage: rp=1142, rb=252157, sp=906, sb=208708
       rate: r=19396, s=16054, total=35450 (bytes per sec)
      peers:
          10.11.0.247
          10.11.0.247:10001

  Total sessions: 2

Note that this test server is proxied, you should see real public IP addresses instead (and of course port 5766 should never be exposed to the Internet).

I wasn’t able to connect on 5766 to local host.
I see the session connection on the syslog the server produces.

Looks like this

turnserver: 23984: session 008000000000000019: usage: realm=<mydomain.com>, username=<1647565808>, rp=1352, rb=976617, sp=696, sb=632460
turnserver: 24106: IPv4. tcp or tls connected to: {My IP}:59037
turnserver: 24106: session 011000000000000018: new, realm=<mydomain.com>, username=<1647566226>, lifetime=600, cipher=TLS_AES_256_GCM_SHA384, method=UNKNOWN
turnserver: 24106: session 011000000000000018: realm <mydomain.com> user <1647566226>: incoming packet ALLOCATE processed, success

The only suspicious line I noticed is this

turnserver: 24106: session 002000000000000017: realm <mydomain.com> user <>: incoming packet message processed, error 401: Unauthorized

Other than that I was able to see all stream from the same 10000 UDP firewalled computer. I opened several tabs.

Hum, unauthorized means wrong secret maybe.

enabling the telnet interface is not done by default, since it’s a security risk if the port 5766 is not firewalled.