What is a Candidate?
A candidate is defined by theCandidate interface in candidate.go:22:
Candidate Types
ICE defines four candidate types (fromcandidatetype.go:12-18):
Host Candidates
Host candidates represent direct addresses from the local machine’s network interfaces.Host candidates are the most efficient connection type as they enable direct peer-to-peer communication without intermediary servers.
- Gathered from local network interfaces (WiFi, Ethernet, VPN, etc.)
- Highest priority (126) for preference
- No additional infrastructure required
- May be private IPs (192.168.x.x, 10.x.x.x) behind NAT
candidate_host.go:32-70
Server Reflexive Candidates
Server reflexive (srflx) candidates represent the public IP address and port as seen by a STUN server. Characteristics:- Discovered by sending STUN requests to a STUN server
- Shows how the local endpoint appears from outside the NAT
- Priority: 100
- Includes related address (the local host candidate that was used)
- Agent sends STUN Binding Request to STUN server
- STUN server responds with XOR-MAPPED-ADDRESS
- This address becomes a server reflexive candidate
candidate_server_reflexive.go:30-68
Gathering: gather.go:735-824
Peer Reflexive Candidates
Peer reflexive (prflx) candidates are discovered during connectivity checks, not during gathering. Characteristics:- Learned when receiving binding requests from unexpected addresses
- Priority: 110 (higher than server reflexive!)
- Occurs when peers are behind symmetric NATs
- Automatically created by the agent
agent.go:1562-1589, when an inbound binding request arrives from an unknown address:
candidate_peer_reflexive.go:32-66
Peer reflexive candidates have higher priority than server reflexive because they represent the actual path being used for connectivity checks, which may differ from the path through the STUN server.
Relay Candidates
Relay candidates represent addresses allocated on TURN (Traversal Using Relays around NAT) servers. Characteristics:- Obtained by allocating a relay address on a TURN server
- Priority: 0 (lowest - used as last resort)
- All media flows through the relay server
- Most reliable but highest latency and server cost
- Supports UDP, TCP, TLS, and DTLS transport
candidate_relay.go:11-18):
candidate_relay.go:45-130
Gathering: gather.go:827-1047
Candidate Priority
Type Preferences
Fromcandidatetype.go:44-57, RFC 5245 recommended preferences:
Priority Calculation
The full priority formula combines:- Type preference (126, 110, 100, or 0)
- Local preference (65535 by default)
- Component ID (1 for RTP, 2 for RTCP)
- Host candidates are always preferred over reflexive
- Reflexive are preferred over relay
- Within the same type, local preference distinguishes interfaces
- RTP components are slightly preferred over RTCP
Candidate Pair Priority
When forming pairs, the priority is calculated fromcandidatepair.go:92-129:
Candidate Gathering
The Gathering Process
Gathering is initiated withGatherCandidates() from gather.go:51-77:
Gathering Stages
Implemented ingather.go:196-220:
Gathering host candidates
Gathering host candidates
From
gather.go:245-428, host gathering:- Enumerate network interfaces using the configured filters
- For each interface address:
- Apply IP filters (skip loopback unless configured)
- Apply interface filters (skip Docker, VMs, etc.)
- Apply location tracking filters (skip IPv6 link-local)
- Create UDP or TCP socket on available port
- Apply NAT 1:1 mappings if configured
- Create CandidateHost for each valid address
- Add to local candidates and emit via OnCandidate
Gathering server reflexive candidates
Gathering server reflexive candidates
From
gather.go:735-824, srflx gathering:- For each STUN URL:
- Open UDP socket
- Send STUN Binding Request
- Wait for response with timeout (default 5s)
- Extract XOR-MAPPED-ADDRESS
- Create CandidateServerReflexive
- Associate with local candidate as related address
- Add to local candidates and emit
Gathering relay candidates
Gathering relay candidates
From
gather.go:827-1047, relay gathering:- For each TURN URL:
- Connect to TURN server (UDP, TCP, TLS, or DTLS)
- Authenticate with username/password
- Send TURN Allocate request
- Receive allocated relay address
- Create CandidateRelay
- Maintain TURN client for the allocation lifetime
- Add to local candidates and emit
Continual Gathering
Pion ICE supports continual gathering (monitoring for network changes):gather.go:89-112, continual gathering:
- Performs initial gathering
- Monitors network interfaces every 2 seconds (configurable)
- Detects new IP addresses
- Automatically gathers candidates for new interfaces
- Continues until agent is closed
Candidate Selection
Forming Candidate Pairs
The agent forms pairs from local and remote candidates (fromagent.go:860-868):
The Checklist
Pairs go through states (fromcandidatepair_state.go:9-26):
Selection Algorithm
Fromagent.go:827-858, finding the best valid pair:
Nomination
The controlling agent nominates the selected pair:Candidate String Format
Candidates are serialized following RFC 5245:Best Practices
Filter candidates carefully when behind corporate firewalls. Some organizations block UDP or restrict ports, making TCP or relay candidates necessary.
Optimizing gathering time
Optimizing gathering time
To reduce gathering time:Be careful not to sacrifice connectivity for speed.
Handling symmetric NATs
Handling symmetric NATs
Symmetric NATs allocate different mappings for different destinations. This makes server reflexive candidates ineffective.Indicators:
- STUN server shows one port
- Remote peer sees different port
- Connection only works with relay
Related Topics
- ICE Protocol - Understanding the overall ICE process
- Agents - Managing the ICE agent lifecycle
- Connectivity - How candidates are tested and selected