> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/pion/ice/llms.txt
> Use this file to discover all available pages before exploring further.

# ICE Candidates

> Understanding candidate types, priority, gathering, and selection in ICE

ICE candidates represent potential network endpoints for establishing peer-to-peer connections. Each candidate describes a transport address (IP and port) that can be used for communication.

## What is a Candidate?

A candidate is defined by the `Candidate` interface in `candidate.go:22`:

```go theme={null}
type Candidate interface {
    Foundation() string
    ID() string
    Component() uint16
    
    NetworkType() NetworkType
    Address() string
    Port() int
    Priority() uint32
    
    Type() CandidateType
    TCPType() TCPType
    
    RelatedAddress() *CandidateRelatedAddress
    LastReceived() time.Time
    LastSent() time.Time
    
    // Extension attributes (RFC 5245)
    Extensions() []CandidateExtension
    GetExtension(key string) (CandidateExtension, bool)
    AddExtension(CandidateExtension) error
}
```

## Candidate Types

ICE defines four candidate types (from `candidatetype.go:12-18`):

```go theme={null}
const (
    CandidateTypeUnspecified CandidateType = iota
    CandidateTypeHost
    CandidateTypeServerReflexive
    CandidateTypePeerReflexive
    CandidateTypeRelay
)
```

### Host Candidates

Host candidates represent direct addresses from the local machine's network interfaces.

<Info>
  Host candidates are the most efficient connection type as they enable direct peer-to-peer communication without intermediary servers.
</Info>

**Characteristics:**

* 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

**Creation:**

```go theme={null}
candidate, err := ice.NewCandidateHost(&ice.CandidateHostConfig{
    Network:   "udp",
    Address:   "192.168.1.100",
    Port:      54321,
    Component: ice.ComponentRTP,
})
```

**Implementation:** `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)

**How it works:**

1. Agent sends STUN Binding Request to STUN server
2. STUN server responds with XOR-MAPPED-ADDRESS
3. This address becomes a server reflexive candidate

```go theme={null}
candidate, err := ice.NewCandidateServerReflexive(&ice.CandidateServerReflexiveConfig{
    Network:   "udp",
    Address:   "203.0.113.42",  // Public IP from STUN
    Port:      54321,
    Component: ice.ComponentRTP,
    RelAddr:   "192.168.1.100",  // Local IP that was mapped
    RelPort:   54321,
})
```

**Implementation:** `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

**When created:**

From `agent.go:1562-1589`, when an inbound binding request arrives from an unknown address:

```go theme={null}
if remoteCandidate == nil {
    // Create peer reflexive candidate
    prflxCandidate, err := ice.NewCandidatePeerReflexive(&ice.CandidatePeerReflexiveConfig{
        Network:   networkType.String(),
        Address:   ip.String(),
        Port:      port,
        Component: local.Component(),
    })
    
    a.addRemoteCandidate(prflxCandidate)
}
```

**Implementation:** `candidate_peer_reflexive.go:32-66`

<Note>
  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.
</Note>

### 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

**Relay protocols** (from `candidate_relay.go:11-18`):

```go theme={null}
const (
    preferenceRelayTLS  = 0  // turn:server:port?transport=tcp over TLS
    preferenceRelayTCP  = 1  // turn:server:port?transport=tcp
    preferenceRelayDTLS = 2  // turns:server:port (DTLS over UDP)
    preferenceRelayUDP  = 3  // turn:server:port?transport=udp
)
```

**Creation:**

```go theme={null}
candidate, err := ice.NewCandidateRelay(&ice.CandidateRelayConfig{
    Network:       "udp",
    Address:       "198.51.100.1",  // TURN relay address
    Port:          60000,
    Component:     ice.ComponentRTP,
    RelAddr:       "192.168.1.100", // Local address
    RelPort:       54321,
    RelayProtocol: "udp",
})
```

**Implementation:** `candidate_relay.go:45-130`

**Gathering:** `gather.go:827-1047`

## Candidate Priority

### Type Preferences

From `candidatetype.go:44-57`, RFC 5245 recommended preferences:

```go theme={null}
func (c CandidateType) Preference() uint16 {
    switch c {
    case CandidateTypeHost:
        return 126  // Highest - direct connection
    case CandidateTypePeerReflexive:
        return 110
    case CandidateTypeServerReflexive:
        return 100
    case CandidateTypeRelay:
        return 0    // Lowest - relayed connection
    }
}
```

### 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)

```go theme={null}
priority = (2^24)*(type preference) + 
           (2^8)*(local preference) + 
           (2^0)*(256 - component ID)
```

This means:

* 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 from `candidatepair.go:92-129`:

```go theme={null}
// RFC 5245 - 5.7.2
// pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0)
// G = controlling agent's candidate priority
// D = controlled agent's candidate priority

func (p *CandidatePair) priority() uint64 {
    var g, d uint32
    if p.iceRoleControlling {
        g = p.Local.Priority()
        d = p.Remote.Priority()
    } else {
        g = p.Remote.Priority()
        d = p.Local.Priority()
    }
    
    return (1<<32-1)*min(g, d) + 2*max(g, d) + cmp(g, d)
}
```

## Candidate Gathering

### The Gathering Process

Gathering is initiated with `GatherCandidates()` from `gather.go:51-77`:

```go theme={null}
err := agent.GatherCandidates()

// Candidates are delivered via the OnCandidate callback:
agent.OnCandidate(func(c ice.Candidate) {
    if c == nil {
        // Gathering complete
    } else {
        // New candidate available
        sendToRemotePeer(c.Marshal())
    }
})
```

### Gathering Stages

Implemented in `gather.go:196-220`:

```go theme={null}
func (a *Agent) gatherCandidatesInternal(ctx context.Context) {
    var wg sync.WaitGroup
    for _, t := range a.candidateTypes {
        switch t {
        case CandidateTypeHost:
            wg.Add(1)
            go func() {
                a.gatherCandidatesLocal(ctx, a.networkTypes)
                wg.Done()
            }()
            
        case CandidateTypeServerReflexive:
            a.gatherServerReflexiveCandidates(ctx, &wg)
            
        case CandidateTypeRelay:
            wg.Add(1)
            go func() {
                a.gatherCandidatesRelay(ctx, a.urls)
                wg.Done()
            }()
        }
    }
    wg.Wait()
}
```

<Accordion title="Gathering host candidates">
  From `gather.go:245-428`, host gathering:

  1. **Enumerate network interfaces** using the configured filters
  2. **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
  3. **Apply NAT 1:1 mappings** if configured
  4. **Create CandidateHost** for each valid address
  5. **Add to local candidates** and emit via OnCandidate

  Special handling for mDNS:

  ```go theme={null}
  if a.mDNSMode == MulticastDNSModeQueryAndGather {
      address = a.mDNSName // Use .local address instead of IP
  }
  ```
</Accordion>

<Accordion title="Gathering server reflexive candidates">
  From `gather.go:735-824`, srflx gathering:

  1. **For each STUN URL:**
     * Open UDP socket
     * Send STUN Binding Request
     * Wait for response with timeout (default 5s)
     * Extract XOR-MAPPED-ADDRESS
  2. **Create CandidateServerReflexive**
  3. **Associate with local candidate** as related address
  4. **Add to local candidates** and emit

  Parallel requests:

  ```go theme={null}
  for _, url := range urls {
      wg.Add(1)
      go func(url stun.URI) {
          defer wg.Done()
          xorAddr, err := stunx.GetXORMappedAddr(conn, serverAddr, timeout)
          // Create candidate from xorAddr
      }(url)
  }
  ```
</Accordion>

<Accordion title="Gathering relay candidates">
  From `gather.go:827-1047`, relay gathering:

  1. **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
  2. **Create CandidateRelay**
  3. **Maintain TURN client** for the allocation lifetime
  4. **Add to local candidates** and emit

  Protocol matrix:

  ```go theme={null}
  switch {
  case url.Proto == ProtoTypeUDP && url.Scheme == SchemeTypeTURN:
      // UDP TURN
  case url.Proto == ProtoTypeTCP && url.Scheme == SchemeTypeTURN:
      // TCP TURN
  case url.Proto == ProtoTypeUDP && url.Scheme == SchemeTypeTURNS:
      // DTLS TURN
  case url.Proto == ProtoTypeTCP && url.Scheme == SchemeTypeTURNS:
      // TLS TURN
  }
  ```
</Accordion>

### Continual Gathering

Pion ICE supports continual gathering (monitoring for network changes):

```go theme={null}
agent, err := ice.NewAgentWithOptions(
    ice.WithContinualGatheringPolicy(ice.GatherContinually),
)
```

From `gather.go:89-112`, continual gathering:

1. Performs initial gathering
2. Monitors network interfaces every 2 seconds (configurable)
3. Detects new IP addresses
4. Automatically gathers candidates for new interfaces
5. Continues until agent is closed

## Candidate Selection

### Forming Candidate Pairs

The agent forms pairs from local and remote candidates (from `agent.go:860-868`):

```go theme={null}
func (a *Agent) addPair(local, remote Candidate) *CandidatePair {
    a.nextPairID++
    p := newCandidatePair(local, remote, a.isControlling.Load())
    p.id = a.nextPairID
    a.checklist = append(a.checklist, p)
    a.pairsByID[p.id] = p
    return p
}
```

### The Checklist

Pairs go through states (from `candidatepair_state.go:9-26`):

```go theme={null}
const (
    CandidatePairStateWaiting      // Check not performed
    CandidatePairStateInProgress   // Check sent, waiting for response
    CandidatePairStateFailed       // Check failed or timed out
    CandidatePairStateSucceeded    // Check succeeded
)
```

### Selection Algorithm

From `agent.go:827-858`, finding the best valid pair:

```go theme={null}
func (a *Agent) getBestValidCandidatePair() *CandidatePair {
    var best *CandidatePair
    for _, p := range a.checklist {
        if p.state != CandidatePairStateSucceeded {
            continue
        }
        
        if best == nil {
            best = p
        } else if best.priority() < p.priority() {
            best = p  // Higher priority wins
        }
    }
    return best
}
```

### Nomination

The controlling agent nominates the selected pair:

```go theme={null}
// Controlling agent sends binding request with USE-CANDIDATE
msg, _ := stun.Build(
    stun.BindingRequest,
    stun.NewUsername(remoteUfrag + ":" + localUfrag),
    UseCandidate(),  // This flag nominates the pair
    AttrControlling(tieBreaker),
    stun.NewShortTermIntegrity(remotePwd),
    stun.Fingerprint,
)
```

## Candidate String Format

Candidates are serialized following RFC 5245:

```
candidate:<foundation> <component> <protocol> <priority> <address> <port> typ <type> [raddr <rel-addr>] [rport <rel-port>]
```

Examples:

```text theme={null}
# Host candidate
candidate:1 1 UDP 2130706431 192.168.1.100 54321 typ host

# Server reflexive
candidate:2 1 UDP 1694498815 203.0.113.42 54321 typ srflx raddr 192.168.1.100 rport 54321

# Relay candidate
candidate:3 1 UDP 16777215 198.51.100.1 60000 typ relay raddr 192.168.1.100 rport 54321
```

## Best Practices

<Tip>
  **Always gather multiple candidate types** for the best chance of connectivity. Even if you have a public IP, gathering relay candidates provides a fallback for restrictive networks.
</Tip>

<Note>
  **Filter candidates carefully** when behind corporate firewalls. Some organizations block UDP or restrict ports, making TCP or relay candidates necessary.
</Note>

<Accordion title="Optimizing gathering time">
  To reduce gathering time:

  ```go theme={null}
  agent, err := ice.NewAgentWithOptions(
      // Reduce STUN timeout
      ice.WithSTUNGatherTimeout(3 * time.Second),
      
      // Only gather needed types
      ice.WithCandidateTypes([]ice.CandidateType{
          ice.CandidateTypeHost,
          ice.CandidateTypeServerReflexive,
      }),
      
      // Limit network types
      ice.WithNetworkTypes([]ice.NetworkType{
          ice.NetworkTypeUDP4,
      }),
  )
  ```

  Be careful not to sacrifice connectivity for speed.
</Accordion>

<Accordion title="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

  Solution: Peer reflexive candidates (automatically created) or relay candidates.
</Accordion>

## Related Topics

* [ICE Protocol](/concepts/ice-protocol) - Understanding the overall ICE process
* [Agents](/concepts/agents) - Managing the ICE agent lifecycle
* [Connectivity](/concepts/connectivity) - How candidates are tested and selected
