ICE Agent — ice_agent.h

Overview

xIceAgent is the central component of the xp2p module. It implements the full ICE (Interactive Connectivity Establishment) protocol as defined in RFC 8445, providing NAT traversal and peer-to-peer UDP connectivity.

The agent handles:

  • Candidate gathering — Enumerates local network interfaces (host candidates), queries STUN servers (server-reflexive candidates), and optionally allocates TURN relays (relay candidates).
  • Connectivity checks — Performs STUN Binding request/response exchanges on all candidate pairs to find working paths.
  • Nomination — Selects the best candidate pair for data transport (aggressive nomination in controlling mode).
  • Data transport — Sends and receives application data over the nominated pair, with TURN relay fallback via ChannelData framing.
  • Consent freshness — Periodically verifies the peer is still reachable (RFC 7675).
#include <xp2p/ice_agent.h>

States

The ICE agent progresses through the following states:

New → Gathering → Checking → Connected → Completed
                                ↘         ↗
                                 Failed
                                    ↓
                                  Closed
StateValueDescription
xIceState_New0Initial state, no activity yet
xIceState_Gathering1Gathering local candidates (host / srflx / relay)
xIceState_Checking2Performing connectivity checks on candidate pairs
xIceState_Connected3At least one valid pair found
xIceState_Completed4All checks done, nominated pair selected
xIceState_Failed5All checks failed, no valid pair
xIceState_Closed6Agent has been shut down

Roles

RoleValueDescription
xIceRole_Controlling0Initiates nomination (sends USE-CANDIDATE)
xIceRole_Controlled1Accepts nomination from the controlling agent

Configuration

struct xIceConf {
    xIceRole     role;           // Controlling or Controlled
    bool         enable_ipv6;    // Enable IPv6 candidates (default: false)

    const char  *stun_server;    // STUN server "host:port" (or NULL)
    const char  *turn_server;    // TURN server "host:port" (or NULL)
    const char  *turn_username;  // TURN long-term credential username
    const char  *turn_password;  // TURN long-term credential password

    xIceOnStateChange on_state_change;  // State change callback
    xIceOnCandidate   on_candidate;     // New candidate callback
    xIceOnData        on_data;          // Data received callback
    void             *ctx;              // Forwarded to all callbacks
};

Callbacks

xIceOnStateChange

typedef void (*xIceOnStateChange)(xIceAgent agent, xIceState state, void *arg);

Called when the agent transitions to a new state. Use this to detect when the connection is established (Connected / Completed) or has failed.

xIceOnCandidate

typedef void (*xIceOnCandidate)(xIceAgent agent, const char *candidate_sdp, void *arg);

Called when a new local candidate is gathered. The candidate_sdp is an SDP candidate line (e.g. "candidate:...") suitable for Trickle ICE. When candidate_sdp is NULL, gathering is complete (end-of-candidates signal).

xIceOnData

typedef void (*xIceOnData)(xIceAgent agent, const uint8_t *data, size_t len, void *arg);

Called when application data is received on the nominated pair. The data buffer is valid only for the duration of the callback.

API Reference

Lifecycle

FunctionDescription
xIceAgentCreate(loop, conf)Create a new ICE agent. Generates random ice-ufrag/ice-pwd. Returns NULL on failure.
xIceAgentDestroy(agent)Destroy the agent, close sockets, cancel timers. Safe to call with NULL.

Gathering

FunctionDescription
xIceAgentGather(agent)Start candidate gathering. Enumerates interfaces, sends STUN/TURN requests. Candidates reported via on_candidate.

SDP Exchange

FunctionDescription
xIceAgentCreateOffer(agent)Generate an SDP offer string. Caller must free() the result.
xIceAgentCreateAnswer(agent)Generate an SDP answer string. Caller must free() the result.
xIceAgentSetRemoteDescription(agent, sdp)Parse remote SDP (ice-ufrag, ice-pwd, candidates) and start connectivity checks.
xIceAgentAddRemoteCandidate(agent, sdp)Add a single remote candidate (Trickle ICE).

Data Transport

FunctionDescription
xIceAgentSend(agent, data, len)Send data through the nominated pair. Only valid in Connected or Completed state.

Candidate Types

TypePriority PrefDescription
host126Direct local interface address
srflx100Server-reflexive (public address from STUN)
prflx110Peer-reflexive (discovered during checks)
relay0TURN relay address

Priority is computed per RFC 8445 §5.1.2.1:

priority = (2^24) × type_pref + (2^8) × local_pref + (256 - component_id)

ICE Lifecycle Flow

sequenceDiagram
    participant App as Application
    participant A as Agent A (Controlling)
    participant B as Agent B (Controlled)
    participant STUN as STUN Server

    App->>A: xIceAgentCreate(loop, conf)
    App->>B: xIceAgentCreate(loop, conf)
    App->>A: xIceAgentGather()
    App->>B: xIceAgentGather()

    A->>STUN: STUN Binding Request
    B->>STUN: STUN Binding Request
    STUN-->>A: Binding Response (srflx addr)
    STUN-->>B: Binding Response (srflx addr)

    A-->>App: on_candidate(host), on_candidate(srflx), on_candidate(NULL)
    B-->>App: on_candidate(host), on_candidate(srflx), on_candidate(NULL)

    App->>A: offer = xIceAgentCreateOffer()
    App->>B: xIceAgentSetRemoteDescription(offer)
    App->>B: answer = xIceAgentCreateAnswer()
    App->>A: xIceAgentSetRemoteDescription(answer)

    A->>B: STUN Binding Request (connectivity check)
    B-->>A: Binding Response
    A->>B: STUN Binding Request + USE-CANDIDATE

    A-->>App: on_state_change(Connected)
    B-->>App: on_state_change(Connected)

    App->>A: xIceAgentSend("Hello!")
    A->>B: UDP data
    B-->>App: on_data("Hello!")

Example — Loopback Echo

The examples/ice_echo.c demo creates two agents in the same process, exchanges SDP, and echoes data:

# Default (host candidates only, no STUN)
./build/ice_echo

# With STUN server
./build/ice_echo -s stun.l.google.com:19302

# Filter to only use server-reflexive candidates
./build/ice_echo -s stun.l.google.com:19302 -f srflx

# Enable IPv6 candidate gathering
./build/ice_echo -6

Command-Line Options

FlagDescription
-s host:portSTUN server address (default: stun.l.google.com:19302). Pass -s "" to disable.
-f typeFilter candidates by type (host, srflx, relay). Default: keep all.
-6Enable IPv6 candidate gathering (disabled by default).

Protocol Constants

ConstantValueDescription
XICE_GATHER_TIMEOUT_MS5000Candidate gathering timeout
XICE_CHECK_TIMEOUT_MS10000Connectivity check timeout
XICE_CHECK_PACING_MS50Check pacing interval
XICE_CONSENT_INTERVAL_MS15000Consent freshness interval (RFC 7675)
XICE_MAX_CANDIDATES32Max candidates per agent
XICE_MAX_PAIRS128Max candidate pairs
XSTUN_INITIAL_RTO_MS500Initial STUN retransmission timeout
XSTUN_MAX_RETRANSMITS7Max STUN retransmissions