62

I am trying to figure out how to test whether a STUN/TURN server is alive and properly responding to connections. Ideally this test would be performed from an external machine, just in case the STUN/TURN machine is down for this case should also be reported by the connectivity test.

Has anyone looked into this case in the past? What solutions would be recommended?

mirazour
  • 723
  • 1
  • 6
  • 7

10 Answers10

57

Edit: A nice implementation in github.io taken from comment to another answer( choose "relay" in IceTransports value):

Test TURN Server


following Benjamin Trent's advice, I wrote the below code to test TURN server's connectivity, works on both firefox n chrome:

function checkTURNServer(turnConfig, timeout){ 

  return new Promise(function(resolve, reject){

    setTimeout(function(){
        if(promiseResolved) return;
        resolve(false);
        promiseResolved = true;
    }, timeout || 5000);

    var promiseResolved = false
      , myPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection   //compatibility for firefox and chrome
      , pc = new myPeerConnection({iceServers:[turnConfig]})
      , noop = function(){};
    pc.createDataChannel("");    //create a bogus data channel
    pc.createOffer(function(sdp){
      if(sdp.sdp.indexOf('typ relay') > -1){ // sometimes sdp contains the ice candidates...
        promiseResolved = true;
        resolve(true);
      }
      pc.setLocalDescription(sdp, noop, noop);
    }, noop);    // create offer and set local description
    pc.onicecandidate = function(ice){  //listen for candidate events
      if(promiseResolved || !ice || !ice.candidate || !ice.candidate.candidate || !(ice.candidate.candidate.indexOf('typ relay')>-1))  return;
      promiseResolved = true;
      resolve(true);
    };
  });   
}

example usage:

checkTURNServer({
    urls: 'turn:127.0.0.1',
    username: 'test',
    credential: 'test'
}).then(function(bool){
    console.log('is TURN server active? ', bool? 'yes':'no');
}).catch(console.error.bind(console));

You can run the below snippet to check:

var res = id('result');

id('button').onclick = function(){
 res.innerHTML = 'Checking TURN Server...';
  var url = 'turn:'+id('url').value+':'+id('port').value,
    useUDP = id('udp').checked;
  url +='?transport=' + (useUDP ? 'udp': 'tcp');
  checkTURNServer({
      urls: url,
      username: id('name').value, 
      credential: id('pass').value
  }, id('time').value).then(function(bool){
    if(bool)
         res.innerHTML = 'Yep, the TURN server works...';
      else
         throw new Error('Doesn\'t work');
  }).catch(function(e){
    console.log(e);
     res.innerHTML = 'TURN server does not work.';
  });
};


function checkTURNServer(turnConfig, timeout){ 
 console.log('turnConfig: ', turnConfig);
  return new Promise(function(resolve, reject){

    setTimeout(function(){
        if(promiseResolved) return;
        resolve(false);
        promiseResolved = true;
    }, timeout || 5000);

    var promiseResolved = false
      , myPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection   //compatibility for firefox and chrome
      , pc = new myPeerConnection({iceServers:[turnConfig]})
      , noop = function(){};
    pc.createDataChannel("");    //create a bogus data channel
    pc.createOffer(function(sdp){
      if(sdp.sdp.indexOf('typ relay') > -1){ // sometimes sdp contains the ice candidates...
        promiseResolved = true;
        resolve(true);
      }
      pc.setLocalDescription(sdp, noop, noop);
    }, noop);    // create offer and set local description
    pc.onicecandidate = function(ice){  //listen for candidate events
      if(promiseResolved || !ice || !ice.candidate || !ice.candidate.candidate || !(ice.candidate.candidate.indexOf('typ relay')>-1))  return;
      promiseResolved = true;
      resolve(true);
    };
  });   
}


function id(val){
 return document.getElementById(val);
}
#url{
  width: 250px;
}
#port{
  width: 70px;
}
<h1>
 Test TURN server
</h1>
<div>
TURN URL: <input id='url' placeholder='example.com  or  xxx.yyy.rrr.ddd'  />
Port: <input type='number' value='3478' id='port' placeholder='enter a port number' />
</div>
<div>
Transport: <input type="radio" name="transport" id="tcp" value="tcp" /> TCP
<input type="radio" name="transport" id="udp" value="udp" checked/>UDP
</div>

<div>
Username: <input id="name" placeholder="turn username" />
</div>
<div>
password: <input id="pass" placeholder="turn password" />
</div>

<div>
checking Timeout: <input type='number'  id="time" placeholder="wait time  before checking timeout" value=5000 />
</div>
<div>
<button id='button'>
Check TURN Server
</button>
</div>

<h4 id='result'></h4>
x-yuri
  • 16,722
  • 15
  • 114
  • 161
mido
  • 24,198
  • 15
  • 92
  • 117
  • 4
    What should be the output of gather candidate to make sure that stun or turn server is working. https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/ – wrufesh Apr 27 '18 at 17:42
  • 3
    **Do not user `test` as username** as shown in the example. Apparently there is some `test` user already implemented behind the scenes within the browsers, hence it is burnt and if you try it for your tests, things fail for no apparent reason. I found no documentation about this behavior, though, but after using a different name, everything suddenly started to work .. – Tino May 23 '18 at 11:33
  • @mido could you please answer my question here -> https://stackoverflow.com/questions/60946697/coturn-aws-ec2-problems-running – anivader Mar 31 '20 at 08:58
19

You can test your servers here...

https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/

jamix
  • 5,484
  • 5
  • 26
  • 35
gugaiz
  • 534
  • 4
  • 12
  • 1
    This is great! Is there a way though to implement this as an in-house solution? I would like to have a daemon that periodically checks that the server is alive, instead of doing it manually. – mirazour Feb 27 '15 at 22:24
  • You can see the code [here](https://github.com/Temasys/Google-WebRTC-Samples/tree/master/samples/web/content/peerconnection/trickle-ice) – gugaiz Feb 28 '15 at 02:27
  • Highly suggested to get the most recent version from github, as linked by @gugaiz. The temasys.com link seems to be outdated and doesn't work for me in Firefox. – Michael Franzl Feb 02 '16 at 11:31
  • What should the results of a successful test show? Sorry getting confused by their instructions on the page – mding5692 Jul 26 '19 at 04:21
  • @mirazour check my answer to automate the server checking – YOGO Aug 17 '19 at 16:47
13

The earlier answers work with TURN if you use a username:password authentication mechanism for your coturn server. However, as is the case for BigBlueButton and others using static-auth-secret as seen in /etc/turnserver.conf, it is not possible to use https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/.

use-auth-secret
static-auth-secret=XXXX

One way to still test your TURN server is to install turnutils_uclient with sudo apt install coturn or your respective package manager. You can then subsequently test it with (replace XXXX and turn.example.com):

turnutils_uclient -T -W XXXX turn.example.com

This should result in the following output (redacted IP addresses 192.168.0.2 as internal client address and 1.2.3.4 as the server address):

0: IPv4. Connected from: 192.168.0.2:50988
0: IPv4. Connected to: 1.2.3.4:3478
0: allocate sent
0: allocate response received: 
0: allocate sent
0: allocate response received: 
0: success
0: IPv4. Received relay addr: 1.2.3.4:56365
....
4: Total transmit time is 4
4: Total lost packets 0 (0.000000%), total send dropped 0 (0.000000%)
4: Average round trip delay 32.500000 ms; min = 15 ms, max = 56 ms
4: Average jitter 12.600000 ms; min = 0 ms, max = 41 ms

On your TURN server, this should be mirrored in /var/log/coturn.log.

Moritz
  • 2,987
  • 3
  • 21
  • 34
  • 4
    Also there is turnutils_stunclient for stun: `turnutils_stunclient -p 3478 stunserver.com` – sparse Dec 23 '20 at 05:02
  • Without reading the RfCs, it seems to be possible to compute a temporarily valid user name based on the current time and the static secret: `secret=$(awk -F "=" '/^static-auth-secret/ {print $2}' /etc/turnserver.conf) && time=$(date +%s) && expiry=3600 && username=$(( $time + $expiry )) && echo username:$username && echo "password : $(echo -n $username | openssl dgst -binary -sha1 -hmac $secret | openssl base64)"` Thanks to https://blog.wydler.eu/2020/04/04/eigener-turn-server-fuer-jitsi-meet-bereitstellen/ for this script. (Adjusted to work with commented-out static-auth-secret rows in conf) – Gunter Ohrner May 21 '21 at 16:26
  • Jupp, BigBlueButton also released a [script](https://docs.bigbluebutton.org/2.2/setup-turn-server.html#test-you-turn-server) for that purpose. It allows you to use the [Trickle ICE](https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/) posted in another answer. – Moritz May 23 '21 at 12:41
5

If you want to check the stun server constantly you can execute this command with cron :

stunserver=stun1.l.google.com;stunport=19302;listenport=20000;echo -ne "\x00\x01\x00\x00YOGO\x59\x4f\x47\x4fSTACFLOW" | nc -u -p $listenport $stunserver $stunport -w 0;timeout 1 nc -l -u $listenport | head -c 32 | tail -c 4 | hexdump -e '/1 "%u" "."' | grep -o ".*[^.]" && echo yes-no-problem || mail -s "Error in Tun server:$stunserver:$stunport" root@localhost <<< 'Error in Tun server'

Replace root@localhost with your email to get the report.

stunserver=stun1.l.google.com;
stunport=19302;
listenport=20000; # Change freely this port if not available

Add it to cron and execute it every minute.

YOGO
  • 531
  • 4
  • 5
  • Also include port: `stunserver=stun.l.google.com;stunport=19302;listenport=20000;echo -ne "\x00\x01\x00\x000000000000000000" | nc -u -p $listenport $stunserver $stunport -w 0;timeout 1 nc -l -u $listenport | head -c 32 | tail -c 6 | hexdump -e '1/2 "%u:" 4/1 "%u."'` – wddd Aug 04 '23 at 09:11
2

Test your TURN and STUN servers here

Devaroop
  • 7,900
  • 2
  • 37
  • 34
  • I can test turn server over there but i am not sure whether stun server is working or not Can you please suggest any way to test stun server as well – Hassan Tariq Jul 25 '17 at 09:25
1

Version of @mido function to check the TURN and the STUN server both (original rejects stun-servers):

function checkTurnOrStun(turnConfig, timeout){ 
  return new Promise(function(resolve, reject){

    setTimeout(function(){
        if(promiseResolved){
            if (promiseResolved == 'STUN') resolve('STUN');
            return;
        }
        resolve(false);
        promiseResolved = true;
    }, timeout || 5000);

    var promiseResolved = false
      , myPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection   //compatibility for firefox and chrome
      , pc = new myPeerConnection({iceServers:[turnConfig]})
      , noop = function(){};
    pc.createDataChannel("");    //create a bogus data channel
    pc.createOffer(function(sdp){
      if(sdp.sdp.indexOf('typ relay') > -1){ // sometimes sdp contains the ice candidates...
        promiseResolved = 'TURN'; 
        resolve(true);
      }
      pc.setLocalDescription(sdp, noop, noop);
    }, noop);    // create offer and set local description
    pc.onicecandidate = function(ice){  //listen for candidate events
      if( !ice || !ice.candidate || !ice.candidate.candidate)  return;
      if (ice.candidate.candidate.indexOf('typ relay')!=-1) { promiseResolved = 'TURN'; resolve('TURN'); }
      else if (!promiseResolved && (ice.candidate.candidate.indexOf('typ prflx')!=-1 || ice.candidate.candidate.indexOf('typ srflx')!=-1)){
          promiseResolved = 'STUN';
        if (turnConfig.url.indexOf('turn:')!==0) resolve('STUN');
      }
      else return;
    };
  });   
}

checkTurnOrStun({"url": "stun:stunserver.org"}).then(function(result){
    console.log(
    result ? 'YES, Server active as '+result : 'NO, server not active');
}).catch(console.error.bind(console));

checkTurnOrStun({
            url: 'turn:numb.viagenie.ca',
            credential: 'muazkh',
            username: 'webrtc@live.com'
}).then(function(result){
    console.log(
    result ? 'YES, Server active as '+result : 'NO, server not active');
}).catch(console.error.bind(console));
Alexander Goncharov
  • 1,572
  • 17
  • 20
  • even if stun server offline its shows that you have turn server on, but i dont – jmp May 19 '20 at 23:12
  • @waza123 If the turn server is not available say wrong credentials it reverts to 'active as STUN' if offline it will say: "NO, turn server not active" – user2677034 Nov 15 '21 at 23:26
0

npm i stun

const stun = require('stun');

stun.request('stun.l.google.com:19302', (err, res) => {
  if (err) {
    console.error(err);
  } else {
    const { address } = res.getXorAddress();
    console.log('your ip', address);
  }
});
jmp
  • 2,456
  • 3
  • 30
  • 47
0

Here's a cleaned up and modernized version of @mido's answer to check if the server is reachable. This is especially useful for private networks where an online tests fails.

It also prints the types of the candidates to the console.

Function

const checkTURNServer = (turnConfig, timeout = 5000) => {
    return new Promise(async (resolve, reject) => {
        const pc = new RTCPeerConnection({iceServers: [turnConfig]});
        let promiseResolved = false;
        // Stop waiting after X milliseconds and display the result
        setTimeout(() => {
            if(promiseResolved)
                return;
            promiseResolved = true;
            resolve(false);
        }, timeout);
        // Create a bogus data channel
        pc.createDataChannel('');
        // Listen for candidates
        pc.onicecandidate = ice => {
            if(promiseResolved || ice === null || ice.candidate === null)
                return;
            console.log(ice.candidate.type);
            if(ice.candidate.type === 'relay') {
                promiseResolved = true;
                resolve(true);
            }
        };
        // Create offer and set local description
        const offer = await pc.createOffer();
        await pc.setLocalDescription(offer);
    });
};

Usage

checkTURNServer({
    urls: ['turn:' + location.host + ':3478', 'turns:' + location.host + ':5349'],
    username: "1604441890:myUser",
    credential: "myPassword",
    credentialType: 'password'
}).then(
    active => console.log('Is the TURN server active?', active ? 'Yes :)' : 'Not yet...keep trying ;)')
).catch(
    e => console.error(e)
);
Minding
  • 1,383
  • 1
  • 17
  • 29
0

TURN server Test This is the great website which test TURN server by relaying a video stream through it you just have to specify the username and password

Usama
  • 159
  • 1
  • 13
-1

You could set up a 3rd party monitoring service (we use Monitis) or even your own machine to PING the server every minute from 1 or more locations. However this will only tell you if the server is reachable and not necessarily if the TURN/STUN application server still accepts & responds to TURN/STUN packets.

A server side monitoring library for STUN/TURN would make a great GitHub project.

octavn
  • 3,154
  • 32
  • 49