9

Scenario: You would like to know if TURN server is being used for a particular call and which one from the array of TURN servers you provided during PeerConnection creation, is being used. Right now there are two options:

  • Wireshark: But when you are behind a corporate proxy and TURN server is outside that, wireshark would show the Proxy IP as the destination.( also not the mention the inconvenience of running it in the background)
  • Going through the stats page and finding out, chrome --> chrome://webrtc-internals and Firefox --> about:webrtc

I would like to use a alternative to the above two, programmatically determine this so I do not have to leave my application page.

mido
  • 24,198
  • 15
  • 92
  • 117
  • https://github.com/webrtc/apprtc/pull/99 shows you how to figure out the type of TURN server used (udp, tcp, tls) -- it doesn't work with Firefox but that's mostly an issue of the apprtc sample being slightly behind. – Philipp Hancke Aug 25 '15 at 21:42

2 Answers2

6

Update: I've updated the example to follow the latest spec, with maplike getStats.

The following approach follows the specification and currently only works in Firefox, because Chrome implements getStats() incorrectly at the moment. Hopefully, a version of the adapter.js polyfill should be available soon that will make this work in Chrome as well.

When you run this fiddle in Firefox, you'll see:

checking
connected
Does not use TURN

This is because the example provides both a STUN and a TURN server. But when I modify the config to use TURN only with iceTransportPolicy: "relay", I see:

checking
connected
Uses TURN server: 10.252.73.50

Note that the turn server I use is behind a VPN, so it won't work for you, but feel free to modify the fiddle with your own server (just don't save it unless you want the info to become public!)

While I haven't tested with more than one turn server, as you can see the IP address shown matches the turn server configured, so it should be possible to tell which server is used using this approach.

// Turn server is on Mozilla's VPN.
var cfg = { iceTransportPolicy: "all", // set to "relay" to force TURN.
            iceServers: [{ urls: "stun:stun.l.google.com:19302" },
                         { urls: "turn:10.252.73.50",
                           username:"webrtc", credential:"firefox" }] };
var pc1 = new RTCPeerConnection(cfg), pc2 = new RTCPeerConnection(cfg);

pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
pc2.oniceconnectionstatechange = () => log(pc2.iceConnectionState);
pc2.onaddstream = e => v2.srcObject = e.stream;

var findSelected = stats =>
  [...stats.values()].find(s => s.type == "candidate-pair" && s.selected);

var start = () => navigator.mediaDevices.getUserMedia({ video: true })
  .then(stream => pc1.addStream(v1.srcObject = stream))
  .then(() => pc1.createOffer()).then(d => pc1.setLocalDescription(d))
  .then(() => pc2.setRemoteDescription(pc1.localDescription))
  .then(() => pc2.createAnswer()).then(d => pc2.setLocalDescription(d))
  .then(() => pc1.setRemoteDescription(pc2.localDescription))
  .then(() => waitUntil(() => pc1.getStats().then(s => findSelected(s))))
  .then(() => pc1.getStats())
  .then(stats => {
    var candidate = stats.get(findSelected(stats).localCandidateId);
    if (candidate.candidateType == "relayed") {
      log("Uses TURN server: " + candidate.ipAddress);
    } else {
      log("Does not use TURN (uses " + candidate.candidateType + ").");
    }
  })
  .catch(log);

var waitUntil = f => Promise.resolve(f())
  .then(done => done || wait(200).then(() => waitUntil(f)));

var wait = ms => new Promise(resolve => setTimeout(resolve, ms));
var log = msg => div.innerHTML += msg +"<br>";
var failed = e => log(e +", line "+ e.lineNumber);
<video id="v1" width="108" height="81" autoplay></video>
<video id="v2" width="108" height="81" autoplay></video><br>
<button onclick="start()">Start!</button><br><div id="div"></div>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
jib
  • 40,579
  • 17
  • 100
  • 158
  • made a mistake of assuming that you always need to provide a mediatrack to receive stats, thanks for clearing that up. – mido Aug 26 '15 at 01:38
  • for me the problem is, sometimes certain ports become unresponsive in TURN server( run on an EC2 instance), so I run them on multiple ports, so though we can obtain IP from rtc stats, the port would still be a mystery – mido Aug 26 '15 at 01:41
  • @mido22 `candidate.portNumber` should give you the port number. See [here](http://w3c.github.io/webrtc-stats/#idl-def-RTCIceCandidateAttributes). – jib Aug 26 '15 at 01:59
  • yup, but that port number through what peer is connected to TURN, not the port on which TURN is running. I generally resort to checking TURN server logs... – mido Aug 26 '15 at 02:10
  • 1
    Ah, I see. `candidate.addressSourceUrl` would be the right answer then, except it is not implemented yet in Firefox unfortunately. – jib Aug 26 '15 at 02:39
  • Property "selected" of RTCIceCandidatePairStats is not mentioned here https://www.w3.org/TR/webrtc-stats/#dom-rtcicecandidatepairstats so this will never work in other browsers than Firefox. – mikep May 14 '18 at 08:17
  • candidateType seems to have changed as well to "LocalCandidateType" and "RemoteCandidateType". I assume they have merged the candidates to a single stat. – radalin Aug 18 '20 at 15:24
4

I wrote and tested the below piece of code, works in latest versions of both firefox and chrome, getConnectionDetails returns a promise which resolves to connection details:

function getConnectionDetails(peerConnection){


  var connectionDetails = {};   // the final result object.

  if(window.chrome){  // checking if chrome

    var reqFields = [   'googLocalAddress',
                        'googLocalCandidateType',   
                        'googRemoteAddress',
                        'googRemoteCandidateType'
                    ];
    return new Promise(function(resolve, reject){
      peerConnection.getStats(function(stats){
        var filtered = stats.result().filter(function(e){return e.id.indexOf('Conn-audio')==0 && e.stat('googActiveConnection')=='true'})[0];
        if(!filtered) return reject('Something is wrong...');
        reqFields.forEach(function(e){connectionDetails[e.replace('goog', '')] = filtered.stat(e)});
        resolve(connectionDetails);
      });
    });

  }else{  // assuming it is firefox
    return peerConnection.getStats(null).then(function(stats){
        var selectedCandidatePair = stats[Object.keys(stats).filter(function(key){return stats[key].selected})[0]]
          , localICE = stats[selectedCandidatePair.localCandidateId]
          , remoteICE = stats[selectedCandidatePair.remoteCandidateId];
        connectionDetails.LocalAddress = [localICE.ipAddress, localICE.portNumber].join(':');
        connectionDetails.RemoteAddress = [remoteICE.ipAddress, remoteICE.portNumber].join(':');
        connectionDetails.LocalCandidateType = localICE.candidateType;
        connectionDetails.RemoteCandidateType = remoteICE.candidateType;
        return connectionDetails;
    });

  }
}

I would like point out one thing, all these three methods fail in one scenario: two turn servers running from same machine on different ports, only reliable way I found was looking at the turn server logs.

mido
  • 24,198
  • 15
  • 92
  • 117
  • Thanks for this! I'm finding that about 25% of the time there are no "Conn-audio" entries in the results, even though the ICE connection state is "completed". Any idea why that might be? – Rob Agar Jun 28 '16 at 09:28
  • @RobAgar not sure what is causing that issue, try asking jib or hancke, they have better understanding – mido Jun 28 '16 at 16:40