Another distuptive WebRTC experiment in Chrome to become reality.
I’ve had clients approaching me in the past month or two with questions about a new type of address cropping up in as ICE candidates. As it so happens, these new candidates have caused some broken experiences.
In this article, I’ll try to untangle how local ICE candidates work, what is mDNS, how it is used in WebRTC, why it breaks WebRTC and how this could have been handled better.
How local ICE candidates work in WebRTC?
Before we go into mDNS, let’s start with understanding why we’re headed there with WebRTC.
When trying to connect a session over WebRTC, there are 3 types of addresses that a WebRTC client tries to negotiate:
- Local IP addresses
- Public IP addresses, found through STUN servers
- Public IP addresses, allocated on TURN servers
During the ICE negotiation process, your browser (or app) will contact its configured STUN and TURN server, asking them for addresses. It will also check with the operating system what local IP addresses it has in its disposal.
Why do we need a local IP address?
If both machines that need to connect to each other using WebRTC sit within the same private network, then there’s no need for the communication to leave the local network either.
Why do we need a public IP address through STUN?
If the machines are on different networks, then by punching a hole through the NAT/firewall, we might be able to use the public IP address that gets allocated to our machine to communicate with the remote peer.
Why do we need a public IP address on a TURN server?
If all else fails, then we need to relay our media through a “third party”. That third party is a TURN server.
Local IP addresses as a privacy risk
That part of sharing local IP addresses? Can really improve things in getting calls connected.
It is also something that is widely used and common in VoIP services. The difference though is that VoIP services that aren’t WebRTC and don’t run in the browsers are a bit harder to hack or abuse. They need to be installed first.
WebRTC gives web developers “superpowers” in knowing your local IP address. That scares privacy advocates who see this is as a breach of privacy and even gave it the name “WebRTC Leak”.
A few things about that:
- Any application running on your device knows your IP address and can report it back to someone
- People using VPNs assume the VPNs takes care of that (browsers do offer mechanisms to remove local IP addresses), but they sometimes fail to add WebRTC support properly
- There is no security risk here. Just privacy risk – leaking a local IP address. How much risk does that entail? I don’t really know
Is WebRTC being abused to harvest local IP addresses?
Yes, we have known that problem ever since the NY Times used a webrtc-based script to gather IP addresses back in 2015. “WebRTC IP leak” is one most common search terms (SEO hacking at its best).
Luckily for us, Google is collecting anonymous usage statistics from Chrome, making the information available through a public chromestatus metrics site. We can use that to see what percentage of the page loads WebRTC is used. The numbers are quite… big:
RTCPeerConnection calls on % of Chrome page loads (see here)
Currently, 8% of page loads create a RTCPeerConnection. 8%. That is quite a bit. We can see two large increases, one in early 2018 when 4% of pageloads used RTCPeerConnection and then another jump in November to 8%.
Now that just means RTCPeerConnection is used. In order to gather local IPs the setLocalDescription call is required. There are statistics for this one as well:
setLocalDescription calls on % of Chrome page loads (see here)
The numbers here are significantly lower than for the constructor. This means a lot of peer connections are constructed but not used. It is somewhat unclear why this happens. We can see a really big increase in November 2018 to 4%, at about the same time that PTC calls jumped to 7-8%. While it makes no sense, this is what we have to work with.
Now, WebRTC could be used legitimately to establish a peer-to-peer connection. For that we need both setLocalDescription and setRemoteDescription and we have statistics for the latter as well:
setRemoteDescription calls on % of Chrome page loads (see here)
Since the big jump in late 2017 (which is explained by a different way of gathering data) the usage of setRemoteDescription hovers between 0.03% and 0.04% of pageloads. That’s close to 1% of the pages a peer connection is actually created on.
We can get another idea about how popular WebRTC is from the getUserMedia statistics:
getUserMedia calls on % of Chrome page loads (see here)
This is consistently around 0.05% of pageloads. A bit more than RTCPeerConnection being used to actually open a session (that setRemoteDescription graph) but there are use-cases such as taking a photo which do not require WebRTC.
Here’s what we’ve arrived with, assuming the metrics collection of chromestats reflects real use behavior. We have 0.04% of pageloads compared to 4%. Assuming the numbers and math is correct, then a considerable percentage of the RTCCPeerConnections are potentially used for a purpose other than what WebRTC was designed for. That is a problem that needs to be solved.
* credits and thanks to Philipp Hancke for assisting in collecting and analyzing the chromestats metrics
What is mDNS?
Switching to a different topic before we go back to WebRTC leaks and local IP addresses.
mDNS stands for Multicast DNS. it is defined in IETF RFC 6762.
mDNS is meant to deal with having names for machines on local networks without needing to register them on DNS servers. This is especially useful when there are no DNS servers you can control – think of a home with a couple of devices who need to interact locally without going to the internet – Chromecast and network printers are some good examples. What we want is something lightweight that requires no administration to make that magic work.
And how does it work exactly? In a similar fashion to DNS itself, just without any global registration – no DNS server.
At its basic approach, when a machine wants to know the IP address within the local network of a device with a given name (lets say tsahi-laptop), it will send out an mDNS query on a known multicast IP address (exact address and stuff can be found in the spec) with a request to find “tsahi-laptop.local”. There’s a separate registration mechanism whereby devices can register their mDNS names on the local network by announcing it within the local network.
Since the request is sent over a multicast address, all machines within the local network receive it. The machine with that name (probably my laptop, assuming it supports mDNS and is discoverable in the local network), will return back with its IP address, doing that also over multicast.
That means that all machines in the local network heard the response and can now cache that fact – what is the IP address on the local network for a machine called tsahi-laptop.
How is mDNS used in WebRTC?
Back to that WebRTC leak and how mDNS can help us.
Why do we need local IP addresses? So that sessions that need to take place in a local network don’t need to use public IP addresses. This makes routing a lot simpler and efficient in such cases.
But we also don’t want to share these local IP addresses with the Java Script application running in the browser. That would be considered a breach of privacy.
Which is why mDNS was suggested as a solution. There It is a new IETF draft known as draft-ietf-rtcweb-mdns-ice-candidates-03. The authors behind it? Developers at both Apple and Google.
The reason for it? Fixing the longstanding complaint about WebRTC leaking out IP addresses. From its abstract:
WebRTC applications collect ICE candidates as part of the process of creating peer-to-peer connections. To maximize the probability of a direct peer-to-peer connection, client private IP addresses are included in this candidate collection. However, disclosure of these addresses has privacy implications. This document describes a way to share local IP addresses with other clients while preserving client privacy. This is achieved by concealing IP addresses with dynamically generated Multicast DNS (mDNS) names.
How does this work?
Assuming WebRTC needs to share a local IP address which it deduces is private, it will use an mDNS address for it instead. If there is no mDNS address for it, it will generate and register a random one with the local network. That random mDNS name will then be used as a replacement of the local IP address in all SDP and ICE message negotiations.
- The local IP address isn’t exposed to the Java Script code of the application. The receiver of such an mDNS address can perform a lookup on his local network and deduce the local IP address from there only if the device is within the same local network
- A positive side effect is that now, the local IP address isn’t exposed to media, signaling and other servers either. Just the mDNS name is known to them. This reduces the level of trust needed to connect two devices via WebRTC even further
Why this breaks WebRTC applications?
Here’s the rub though. mDNS breaks some WebRTC implementations.
mDNS is supposed to be innocuous:
- It uses a top-level domain name of its own (.local) that shouldn’t be used elsewhere anyway
- mDNS is sent over multicast, on its own dedicated IP and port, so it is limited to its own closed world
- If the mDNS name (tsahi-laptop.local) is processed by a DNS server, it just won’t find it and that will be the end of it
- It doesn’t leave the world of the local network
- It is shared in places where one wants to share DNS names
With WebRTC though, mDNS names are shared instead of IP addresses. And they are sent over the public network, inside a protocol that expects to receive only IP addresses and not DNS names.
The result? Questions like this recent one on discuss-webrtc:
Weird address format in c= line from browser
I am getting an offer SDP from browser with a connection line as such:
c=IN IP4 3db1cebd-e606-4dc1-b561-e0af5b4fd327.local
This is causing trouble in a webrtc server that we have since the parser is bad (it is expecting a normal ipv4 address format)[…]
This isn’t a singular occurrence. I’ve had multiple clients approach me with similar complaints.
What happens here, and in many other cases, is that the IP addresses that are expected to be in SDP messages are replaced with mDNS names – instead of x.x.x.x:yyyy the servers receive <random-ugly-something>.local and the parsing of that information is totally different.
This applies to all types of media servers – the common SFU media server used for group video calls, gateways to other systems, PBX products, recording servers, etc.
Some of these have been updated to support mDNS addresses inside ICE candidates already. Others probably haven’t, like the recent one above. But more importantly, many of the deployments made that don’t want, need or care to upgrade their server software so frequently are now broken as well, and should be upgraded.
Could Google have handled this better?
In January, Google announced on discuss-webrtc this new experiment. More importantly, it stated that:
No application code is affected by this feature, so there are no actions for developers with regard to this experiment.
Within a week, it got this in a reply:
As it stands right now, most ICE libraries will fail to parse a session description with FQDN in the candidate address and will fail to negotiate.
More importantly, current experiment does not work with anything except Chrome due to c= line population error. It would break on the basic session setup with Firefox. I would assume at least some testing should be attempted before releasing something as “experiment” to the public. I understand the purpose of this experiment, but since it was released without testing, all we got as a result are guaranteed failures whenever it is enabled.
The interesting discussion that ensued for some reason focused on how people interpret the various DNS and ICE related standards and does libnice (an open source implementation of ICE) breaks or doesn’t break due to mDNS.
But it failed to encompass the much bigger issue – developers were somehow expected to write their code in a way that won’t break the introduction of mDNS in WebRTC – without even being aware that this is going to happen at some point in the future.
Ignoring that fact, Google has been running mDNS as an experiment for a few Chrome releases already. As an experiment, two things were decided:
- It runs almost “randomly” on Chrome browsers of users without any real control of the user or the service that this is happening (not something automated and obvious at least)
- It was added only when local IP addresses had to be shared and no permission for the camera or microphone were asked for (receive only scenarios)
The bigger issue here is that many view only solutions of WebRTC are developed and deployed by people who aren’t “in the know” when it comes to WebRTC. They know the standard, they may know how to implement with it, but most times, they don’t roam the discuss-webrtc mailing list and their names and faces aren’t known within the tight knit of the WebRTC community. They have no voice in front of those that make such decisions.
In that same thread discussion, Google also shared the following statement:
FWIW, we are also considering to add an option to let user force this feature on regardless of getUserMedia permissions.
Mind you – that statement was a one liner inside a forum discussion thread, from a person who didn’t identify in his message with a title or the fact that he speaks for Google and is a decision maker.
Which is the reason I sat down to write this article.
mDNS is GREAT. AWESOME. Really. It is simple, elegant and gets the job done than any other solution people would come up with. But it is a breaking change. And that is a fact that seems to be lost to Google for some reason.
By enforcing mDNS addresses on all local IP addresses (which is a very good thing to do), Chrome will undoubtedly break a lot of services out there. Most of them might be small, and not part of the small majority of the billion-minutes club.
Google needs to be a lot more transparent and public about such a change. This is by no means a singular case.
Just digging into what mDNS is, how it affects WebRTC negotiation and what might break took me time. The initial messages about an mDNS experiment are just not enough to get people to do anything about it. Google did a way better job with their explanation about the migration from Plan B to Unified Plan as well as the ensuing changes in getStats().
My main worry is that this type of transparency doesn’t happen as part of a planned rollout program. It is done ad-hoc with each initiative finding its own creative solution to convey the changes to the ecosystem.
This just isn’t enough.
WebRTC is huge today. Many businesses rely on it. It should be treated as the mission critical system that developers who use it see in it.
It is time for Google to step up its game here and put the mechanisms in place for that.
What should you do as a developer?
First? Go check if mDNS breaks your app. You can enable this functionality on chrome://flags/#enable-webrtc-hide-local-ips-with-mdns
In the long run? My best suggestion would be to follow messages coming out of Google in discuss-webrtc about their implementation of WebRTC. To actively read them. Read the replies and discussions that take place around them. To understand what they mean. And to engage in that conversation instead of silently reading the threads.
Test your applications on the beta and Canary releases of Chrome. Collect WebRTC behavior related metrics from your deployment to find unexpected changes there.
Apart from that? Nothing much you can do.
As for mDNS, it is a great improvement. I’ll be adding a snippet explanation about it to my WebRTC Tools course, something new that will be added next month to the WebRTC Course. Stay tuned!