WebRTC ICE Candidates and Network Traversal
This article provides an overview of Interactive Connectivity Establishment (ICE) candidates and explains how JavaScript manages network traversal to establish peer-to-peer (P2P) connections in WebRTC. It covers the challenges posed by NATs and firewalls, the distinct types of ICE candidates, the roles of STUN and TURN servers, and the step-by-step JavaScript implementation required to gather, signal, and negotiate connection paths between remote browsers.
The Network Traversal Problem in WebRTC
WebRTC enables real-time audio, video, and data communication
directly between browsers. However, most consumer devices do not have a
public, static IP address. Instead, they reside behind Network Address
Translation (NAT) devices and firewalls. NAT assigns private IP
addresses (such as 192.168.x.x or 10.x.x.x) to
local devices and translates them to a single public IP address when
communicating with the internet.
Because two devices behind different NATs cannot directly see each other’s private IP addresses, direct communication fails without an intermediary mechanism to discover public routing paths and bypass firewall restrictions.
What Are ICE Candidates?
An ICE (Interactive Connectivity Establishment) candidate is a network configuration profile that describes a potential method and path for communicating with a peer. Each candidate contains vital networking information, including:
- IP address
- Port number
- Transport protocol (UDP or TCP)
- Candidate type
- Priority level
During the connection phase, WebRTC gathers multiple candidates from both peers to test all possible combinations and select the most efficient, viable path.
Types of ICE Candidates
- Host Candidates: Represent the local IP address and port directly assigned to the device’s network interfaces (Wi-Fi or Ethernet). These work only if both peers are on the same local network.
- Server Reflexive (srflx) Candidates: Represent the public IP address and port allocated to the device by its NAT. These are discovered using a STUN (Session Traversal Utilities for NAT) server.
- Relay Candidates: Represent an allocated address on an external TURN (Traversal Using Relays around NAT) server. If symmetric NATs or strict firewalls block direct communication, media and data packets are relayed through this third-party server.
- Peer Reflexive (prflx) Candidates: Discovered dynamically during ICE connectivity checks when an incoming packet arrives from an address different from known host or reflexive candidates.
How JavaScript Handles ICE in WebRTC
JavaScript utilizes the RTCPeerConnection API to
automate network traversal. The browser handles the low-level discovery
and connectivity testing, while JavaScript coordinates signaling via the
application’s signaling server (typically implemented with
WebSockets).
1. Configuring ICE Servers
When instantiating the RTCPeerConnection, JavaScript
provides an array of STUN and TURN server URLs.
const configuration = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: 'turn:turn.example.com:3478',
username: 'user',
credential: 'password'
}
]
};
const peerConnection = new RTCPeerConnection(configuration);2. Gathering ICE Candidates
Once local session descriptions (Offer/Answer) are set via
setLocalDescription(), the browser begins the ICE gathering
process automatically. As it discovers potential endpoints, it triggers
the icecandidate event on the
RTCPeerConnection instance.
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
// Send the candidate to the remote peer via your signaling server
signalingServer.send(JSON.stringify({
type: 'candidate',
candidate: event.candidate
}));
} else {
// All ICE candidates have been gathered
console.log('ICE gathering complete');
}
};3. Signaling Candidates
WebSockets, HTTP requests, or any message-passing layer transmit the serialized ICE candidates to the remote peer. This process is known as Trickle ICE, where candidates are transmitted incrementally as they are found rather than waiting for all candidates to be collected, drastically reducing connection setup time.
4. Adding Remote Candidates
When the remote peer receives candidate data from the signaling
channel, it registers the candidate using the
addIceCandidate() method.
signalingServer.onmessage = async (message) => {
const data = JSON.parse(message.data);
if (data.type === 'candidate') {
try {
await peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate));
} catch (error) {
console.error('Error adding received ICE candidate', error);
}
}
};5. Connectivity Checks and Path Selection
Under the hood, WebRTC pairs each local candidate with each remote
candidate to form candidate pairs. It systematically tests each pair
with STUN binding requests. The browser selects the highest-priority
pair that successfully establishes two-way communication (preferring
Host, then Server Reflexive, and falling back to Relay), transitioning
the connection state to connected.