Skip to main content
This guide walks you through creating a complete ICE connection between two peers. We’ll build a “ping-pong” application where two ICE agents establish a connection and exchange messages.
This example demonstrates local peer-to-peer communication. Both agents run on the same machine, but the same principles apply to remote connections.

Overview

Establishing an ICE connection involves:
  1. Creating ICE agents on both sides
  2. Gathering local candidates (network addresses)
  3. Exchanging credentials and candidates with the remote peer
  4. Performing connectivity checks
  5. Using the established connection to send/receive data

Complete Example

Here’s a full working example that establishes an ICE connection between two peers:
1

Create the ICE agents

Each peer creates an Agent with network configuration:
WithNetworkTypes restricts which network protocols to use. Options include:
  • NetworkTypeUDP4 - IPv4 UDP (most common)
  • NetworkTypeUDP6 - IPv6 UDP
  • NetworkTypeTCP4 - IPv4 TCP
  • NetworkTypeTCP6 - IPv6 TCP
2

Set up event handlers

Register handlers for candidates and connection state changes:
OnCandidate is called with nil when candidate gathering completes.
3

Exchange ICE credentials

Get local credentials and share them with the remote peer:
The username fragment (ufrag) and password (pwd) are used to authenticate connectivity checks.
4

Gather candidates

Start gathering network candidates:
This discovers all available network paths (local addresses, STUN reflexive addresses, TURN relay addresses).
5

Add remote candidates

As you receive candidates from the remote peer, add them to your agent:
6

Establish the connection

One peer acts as “controlling” and dials, the other “accepts”:
One peer must be controlling and the other controlled. Both cannot be the same role.
7

Send and receive data

Once connected, use the ice.Conn like any net.Conn:

Full Working Example

Here’s a complete ping-pong example that demonstrates the entire flow. This example uses HTTP for signaling (exchanging credentials and candidates):
Run the example: Open two terminals and run the program with and without the -controlling flag. Press Enter in both when ready, and watch the connection establish!

Key Concepts Explained

ICE Candidates

Candidates represent possible network paths:
  • Host candidates: Your local IP addresses and ports
  • Server reflexive candidates: Your public IP (discovered via STUN)
  • Relay candidates: TURN server addresses (for difficult NAT scenarios)

Controlling vs Controlled

ICE requires one peer to be “controlling” (initiates nomination) and the other “controlled” (responds). This is separate from client/server - either peer can be controlling.

Connection States

Watch for these states during connection:

Adding STUN/TURN Servers

For connections across the internet, add STUN/TURN servers:
For relay (TURN) support:
TURN servers require authentication. Configure credentials in the URI or use WithTURNCredentials option.

Continual Gathering

For applications that need to adapt to network changes (like mobile apps), enable continual gathering:
This monitors network interfaces and gathers new candidates when networks change.

Next Steps

Agent Configuration

Learn about all available agent options

Candidate Types

Deep dive into ICE candidates

NAT Traversal

Configure STUN and TURN servers

Examples

Explore more code examples