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?
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({
url: '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>
You can test your servers here...
https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
Test your TURN and STUN servers here
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.
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));
If you are open for a commercial tool for high load testing of turn servers like 100,000 virtual users or 1 million virtual users, you may like to check Load Multiplier tool (www.loadmultiplier.com). Also a specific page how to test coturn server is covered here.
Please note that either Load Multiplier tool will simulate a dummy signalling plane (to exchange the SDP between clients). And you can test STUN, TURN and ICE with SRTP / DTLS media.
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.