ICE candidate gathering

🎻 February 2021 fiddle

Want to learn more about WebRTC?

Look us up here for the WebRTC Fiddle of the Month, created… once a month.

Or just enroll to one of our excellent WebRTC training courses.



Tsahi: Hi, everyone, and welcome to the WebRTC Fiddle of the Month. This time we’re going to talk about ICE candidates gathering. This is something that we see many people get confused about. So we want to try and put some order in that yet again from a slightly different angle.

Now, what happens in a WebRTC call, is that I need to be able to connect to you. In order to be able to connect to you, there needs to be well… A connection. And that goes over IP networks and IP networks are finicky, especially when we’ve got firewalls and NAT devices along the way. WebRTC uses a mechanism called ICE to to gather different types of candidates, and then it will start negotiating to see which of these candidates stick and allow me to reach out to you in that call.

OK, now, before I let Philipp talk and let me share, let me show you what I mean real quickly. We’ve got three mechanisms in place – three types of addresses. It is a bit more complicated and we’ll see that. But essentially:

I want to reach someone – the first address that I can use or want to use with is local address, the host IP address. If I can reach out to him in that address, then I’ve got a direct link going to him.

The second one is an address that I received from a STUN server. A STUN server will simply tell me what public IP U se. Our guy here will go to the STUN server, ask it for his public IP address. It will get the address and this will be considered the server reflexive address for him.

And now if someone goes to that address, the NAT device is going to simply pass that to me.

Philipp: Not quite. What happens there is that you are sending a message to the STUN server. Which means you’re sending a packet from the inside of your firewall to the STUN server and then the packet from the output that goes to that port can pass.

Tsahi: Yes. It creates a kind of a pinhole.

The third option when this doesn’t work is to use a TURN server, which is a full relay. That means that I’m going to a TURN server. I’m allocating an actual address on the TURN server that can be used to relay data to me. And then it looks something like this.

We’ve got different kinds of addresses and we’re going to need to gather them first in order to be able to connect a call. So Philipp, to you.

Philipp: Yes, so we have a small jsFiddle, as usual, and it is pretty simple. We have a form here for inputting the TURN servers with username and the credentials. So it’s very similar to the Web page and it does exactly the same. And then we have the start button, which will start the process and we have a log here for showing the output.

So we have that part here, which creates the RTC ICE server object, an array of objects. And that gets passed through the RTC peer connection constructor here. Then we have the callbacks on the peer connection. We could also write them using the addevent listener pattern, which is slightly better in production because you can’t have anyone accidentally unset your onicecandidate.

We have two things here: (1) onicecandidate, which is called for every candidate that is gathered. (2) onicegatheringstatechange, to check for state changes.

We then create a data channel and we create an offer and then we call setlocaldescription.

We do that, because it’s only when you call setlocaldescription the whole process of ICE gathering actually starts.

Tsahi: So the ICE candidates are actually being gathered before even send the offer to the other side, the actual message.

Philipp: Yes, I mean, this is the whole thing about trickle ICE: You can easily wait for all candidates to be gathered before you’re sending a single message to the other side or you just send your initial offer without any candidates to the other side, and then just as candidates come in on your end, you send them to the other side. Yes.

Tsahi: And we don’t want to wait because waiting takes time and time means connection. Time will increase.

Philipp: Yes, OK. For example, if you’re waiting for candidates to gather, that takes two round trips to the TURN server. And if you have multiple interfaces, it will also time out. It might take up to 15 seconds and that’s a long time to wait. OK, so everybody, this is basically so we’re using a Twilio TURN server, which is A good way to show things.

If I click the start button, it will start gathering candidates and the first thing you’ll notice is the candidate stage change “gathering”, which comes from the gathering state change.

And that way we know, OK, things have started now. Then if we look at the timestamps, you can see that we’re gathering host candidates pretty quickly.

Tsahi: And those candidates are my own local IP addresses.

Philipp: Yes, it’s one IPv6 here and one IPv4 we don’t have the mDNS .local because I have given jsFiddle system permission to access my camera, which means I get IP addresses instead of mDNS things.

OK. And then what we get here – a server reflexive candidate from the TURN server.

Then we also have a TCP candidate here. Both for IPv6 and IPv4, and you can see the port is 9, which means the other side is just going to ignore that candidate because you can’t connect anything on port 6. Also, it’s an active candidate. So it’s only saying, OK, I support ICE over TCP, but why do we even send you a candidate that you can’t use anyway? Yes, because a browser can’t open TCP ports and listen on them.

And then we have another gathering state change to “complete”, which again comes from this function here, and then we have a candidate null here, which is basically the old way to say, OK, we’ll be done with gathering. This old stuff, you need to update your code at some point. It’s not terribly important, but be aware of it. Also, in Firefox, you have a candidate which has an empty string as the candidate, which is a per media section way of signaling the same thing that you are done with gathering all the candidates.

Tsahi: So the best approach would be to sit on the ice gathering state change and wait for it to be complete.

Philipp: Yes. Then you can signal that to the other side and then the other side does the same process with its answer and setlocaldescription and send you the candidates back.

Tsahi: So what we’ve seen is we create a connection. We put the ICE servers in it: STUN and TURN. Then we let ICE gather the candidates. The way it does that is by going to the local host and going to the STUN and TURN servers, collect these addresses and provide them back through the onicecandidate messages. We need then to take these candidates and somehow send them to the other side so they will know about these candidates. And when that process is done, we reach the onicegatheringstatechange with complete.

OK, so thank you for this, Philip, and see you next month!