STUN/TURN server connectivity test

WebrtcStunTurn

Webrtc Problem Overview


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?

Webrtc Solutions


Solution 1 - Webrtc

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>

Solution 2 - Webrtc

Solution 3 - Webrtc

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.

Solution 4 - Webrtc

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.

Solution 5 - Webrtc

Test your TURN and STUN servers here

Solution 6 - Webrtc

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

Solution 7 - Webrtc

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

Solution 8 - Webrtc

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

Solution 9 - Webrtc

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmirazourView Question on Stackoverflow
Solution 1 - WebrtcmidoView Answer on Stackoverflow
Solution 2 - WebrtcgugaizView Answer on Stackoverflow
Solution 3 - WebrtcMoritzView Answer on Stackoverflow
Solution 4 - WebrtcYOGOView Answer on Stackoverflow
Solution 5 - WebrtcDevaroopView Answer on Stackoverflow
Solution 6 - WebrtcAlexander GoncharovView Answer on Stackoverflow
Solution 7 - WebrtcjmpView Answer on Stackoverflow
Solution 8 - WebrtcMindingView Answer on Stackoverflow
Solution 9 - WebrtcOctavian NaicuView Answer on Stackoverflow