mirazour
mirazour

Reputation: 793

STUN/TURN server connectivity test

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?

Upvotes: 66

Views: 127521

Answers (8)

Ali Asgher Lakkadshaw
Ali Asgher Lakkadshaw

Reputation: 11

To check whether a TURN server is working or not you can use the turn server testing tool.

This testing tool lets you test between TURN server only, STUN server only or both TURN and STUN servers

  1. Using the TURN server test: https://www.metered.ca/turn-server-testing

Upvotes: 1

Minding
Minding

Reputation: 1414

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)
);

Upvotes: 0

Moritz
Moritz

Reputation: 3737

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.

Upvotes: 17

Mr. Smit
Mr. Smit

Reputation: 2532

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);
  }
});

Upvotes: 0

mido
mido

Reputation: 25054

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>

Upvotes: 60

Alexander Goncharov
Alexander Goncharov

Reputation: 1662

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: '[email protected]'
}).then(function(result){
    console.log(
    result ? 'YES, Server active as '+result : 'NO, server not active');
}).catch(console.error.bind(console));

Upvotes: 1

YOGO
YOGO

Reputation: 551

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.

Upvotes: 6

octavn
octavn

Reputation: 3285

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.

Upvotes: -1

Related Questions