Update: iOS 15.1 (released on October 27th 2021) makes private relay work with WebRTC. You may still find the post below useful for background on WebRTC NAT/Firewall traversal.
Safari is getting a lot of criticism these days. This includes plenty about WebRTC. There are frequent grumblings about missing features and others like regressions (such as this from webrtcHacks author Das-Inge Aas) despite active community interest in the form of detailed bug reports. One of the underlying problems is Apple’s very long release cycle and opaque roadmap. This makes it hard to test in advance and report bugs. The other is that it is pretty unclear what makes it into a release and what does not. “iCloud Private Relay” turned out to be such a case. This feature is currently broken with WebRTC.
iCloud Private Relay is one of the new features in iOS 15 available with iCloud+. For some background reading, refer to this interview or this excellent support article from Apple for technical information. In a nutshell private relay is supposed to hide your IP address. Apple provides a proxy service that is only aware of your IP address and they forward the request on to yet-to-be-identified third-party content providers that actually establish the connection. Apple is aware of your IP address, but not what site you are looking at. The website provider doesn’t get your IP address, keeping you anonymous to them.
At least that is how it is supposed to work. We did some quick tests on the public iOS 15 release that was posted last week and discovered WebRTC’s Interactive Connectivity Establishment (ICE) process breaks Private Relay. We’ll show this in action in a moment, but first, let’s review ICE and what it matters.
WebRTC NAT and Firewall Traversal Recap
WebRTC is a peer-to-peer (p2p) protocol. Your camera, microphone, screen share, and data streams are sent directly from your web browser to another web browser. Peer-to-peer is used because a direct p2p connection is usually the fastest and low latency is critical for quick, interactive communications.
To establish a direct connection, the browsers need to know each other’s IP addresses. Network Address Translation (NAT) and firewalls often prevent this direct communication. That’s where Interactive Connectivity Establishment (ICE) comes in. ICE is a procedure for making sure this IP address information gets between you and the callee. One of the most common methods is a Session Traversal Using NATs or STUN server. Your browser pings the STUN server and it responds with the public IP it sees for you. Your browser then gets this data and uses it as part of the negotiation process.
When you make an RTCPeerConnection
, you should specify the iceServers
object as part of the configuration.
If you are new to this, see Chad’s Kranky Geek video for a more detailed explanation:
WebRTC IP leaks
Doesn’t that expose your private IP address? Yes, it does and that is by design. This topic comes up in WebRTC quite often and we’ve written about it often.
How often does this happen? We can actually make an approximation using the Chrome Platform Status tracker. To establish a WebRTC connection that actually sends something, the app needs to make both a setLocalDescription
and setRemoteDescription
call. Looking at the stats, the local description is called more often than the remote description:
Chrome stats: setLocalDescription | Chrome stats: setRemoteDescription |
There is a big difference here – 6% vs. 0.3%. Why would an app do a setLocalDescription
and not setRemoteDescription
? Well if you wanted to track a user’s local IP addresses and used that for fingerprinting you wouldn’t need the setRemoteDescription
. The only reason for this magnitude of difference is tracking, and you can often see this yourself by looking for sites you don’t recognize in webrtc-internals / about:webrtc.
mDNS to the rescue
This was not the desired intent of these API’s. To help resolve this issue, Multicast DNS (MDNS) candidates were introduced. When enabled, randomly generated addresses that use a UUID – i.e. eb93e835-73d6-4a13-b6f6-2be4f3dee256.local
are used instead of your IP addresses. These MDNS entries can be resolved on the local network only, still allowing for important use-cases like file transfer.
By the way – it was Apple folks who came up with the idea and it is great (also hey Googlers, mind updating the status of your Android implementation of that feature?).
Fippo did a whole talk on that one if you want more information:
Test Private Relay and WebRTC yourself
If there is an expectation of IP address privacy with private relay, how does Apple explain the following simple experiment:
Private Relay OFF
- Turn off Private Relay in your iOS 15 iCloud settings
- Go to https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
This is a pretty simple test that uses WebRTC to ask a STUN server for the device’s public IP address. This is done via UDP sockets and part of the core WebRTC functionality for NAT traversal. - Click the “Gather candidates” button at the bottom.
- We see three candidates in each case – two host addresses, obfuscated via MDNS, and a server reflexive (
srflx
) one that has been gathered via STUN.
Private Relay ON
- Now go back and turn On Private Relay and restart Safari.
- Open the same WebRTC samples page
- “Gather Candidates” again:
That srflx candidate from the STUN server is a problem – that’s your real IP address! Search for “what is my IP address” and you’ll see Private Relay is working there. It seems Apple forgot to proxy WebRTC STUN requests.
What’s Going on at Apple?
Was Apple aware of this? Reddit was a month ago. The Safari Tech Preview release notes as well.
mentioned a cryptic “WebRTC relay” functionality at the beginning of September. This pointed to two commits in the Webkit git repository:
These commits predate the Reddit discussion by a few weeks.
Under the hood what needs to happen is an integration between the socket abstraction libwebrtc uses and Safari so that when libwebrtc opens a UDP socket and sends a STUN packet to the server this gets relayed. We can find the commit for this as well, dating back to the end of June:
[Cocoa] Migrate WebRTC UDP socket handling to NW API
So how come the iOS 15 release shipped without proper integration between WebRTC and private relay?
Impact on WebRTC applications
To be fair, the feature is a public Beta. Perhaps Apple had issues with Private Relay and WebRTC services so they haven’t turned that on yet.
That is a real possibility. Remember WebRTC is supposed to be peer-to-peer as much as possible. For input-sensitive low-latency streaming, relaying traffic via not one but two entities (in separate organizations) seems like a terribly bad idea. We hope Apple’s relay servers have been thoroughly tested and preserve the standard properties of WebRTC and ICE like 5-tuples. They will also need to deal with streaming bitrates of tens of megabits per second without adding much latency or packet loss. If not, then WebRTC services may need to ask their users to turn off Private Relay when using WebRTC in Safari.
{“authors”: [“Philipp Hancke“, “chad hart“]}
Leave a Reply