ICE Candidate Gathering & Filtering: Architecture, Configuration & Debugging

Real-time connectivity relies on deterministic ICE candidate generation, strict filtering, and synchronized exchange. This guide provides a step-by-step implementation path for production WebRTC deployments, with explicit handling for browser constraints and network fallbacks.

Step 1: Map Candidate Discovery Phases

The ICE agent systematically probes local interfaces and external servers to build a connectivity matrix. Within the broader WebRTC Protocol Stack & Signaling Servers, ICE operates as the negotiation layer that resolves NAT topology and firewall constraints before media flows.

Implementation Flow:

Step 2: Apply Filtering & Priority Algorithms

Not all discovered paths are viable. The ICE specification uses a strict priority formula: (2^24 * type_pref) + (2^8 * local_pref) + (2^0 * component_id). You must enforce transport policies before signaling begins.

Configuration & Constraints:

Browser Limits & Fallbacks: Firefox enforces strict ICE TCP toggles (media.peerconnection.ice.tcp). Safari restricts non-standard UDP ports. Always configure TURN on standard ports (3478/443) and implement automatic fallback to TCP/TLS when UDP connectivity fails.

Step 3: Synchronize Signaling Exchange

Filtered candidates must be transmitted asynchronously without blocking the SDP Offer/Answer Lifecycle. A robust WebSocket Signaling Implementation ensures out-of-order delivery and state transitions are handled gracefully.

Event Handling Pattern:

pc.onicecandidate = (event) => {
 if (event.candidate) {
 // Transmit incrementally (Trickle ICE)
 signalingChannel.send(JSON.stringify({
 type: 'candidate',
 candidate: event.candidate.candidate,
 sdpMid: event.candidate.sdpMid,
 sdpMLineIndex: event.candidate.sdpMLineIndex
 }));
 } else {
 console.log('ICE gathering complete.');
 }
};

Queue Management: Buffer candidates if the remote SDP hasn’t been applied. Apply pending candidates immediately upon setRemoteDescription() resolution to prevent parsing errors.

Step 4: Select Trickle vs. Bulk Gathering

Streaming candidates (trickle) reduces Time-to-First-Frame but increases signaling complexity. Waiting for iceGatheringState === 'complete' (bulk) simplifies state machines but adds 1–3 seconds of latency. Review Best practices for ICE candidate trickle vs bulk gathering to align with your scale requirements.

State Transition Monitoring:

Step 5: Production Debugging & Connectivity Workflows

Diagnose failures by intercepting generation events and correlating STUN/TURN responses with media state.

Troubleshooting Checklist:

  1. Monitor Errors: Attach onicecandidateerror to catch authentication failures, unreachable endpoints, or blocked ports.
  2. Parse Stats: Use getStats() to correlate local-candidate/remote-candidate reports with RTT and packet loss.
  3. Validate TURN Rotation: Ensure credential expiry aligns with session duration. Trigger iceRestart() proactively before expiry.
  4. Packet Capture: Run Wireshark with stun || turn filters to verify NAT mapping and relay allocation.

Debugging Snippet:

pc.onicecandidateerror = (e) => {
 console.error(`ICE Error [${e.errorCode}] on ${e.url}`);
 // Implement exponential backoff retry or alert monitoring
};

async function auditIceStats() {
 const stats = await pc.getStats();
 stats.forEach(r => {
 if (r.type.includes('-candidate')) {
 console.log(`${r.type}: ${r.address} (${r.protocol})`);
 }
 });
}

Network Fallbacks & Common Pitfalls

FAQ

Q: How do I force WebRTC to use only TURN relays for compliance? A: Set iceTransportPolicy: 'relay' in RTCPeerConnection config. This bypasses host and srflx candidates, routing all traffic through your TURN infrastructure.

Q: Why does media latency spike despite successful signaling? A: ICE is likely stuck in gathering or failing candidate pair validation. Verify UDP 3478/19302 reachability, check firewall rules, and pre-warm candidates using iceCandidatePoolSize: 10.

Q: What triggers icecandidateerror and how should I handle it? A: Fires on STUN/TURN allocation failure, auth rejection, or port blockage. Log the payload, implement exponential backoff, and trigger pc.restartIce() if the connection degrades.