This guide references examples from the Pion ICE repository, explaining key patterns and use cases.
Available Examples
The Pion ICE library includes several examples demonstrating different features:
Ping-Pong Basic peer-to-peer connection with message exchange
Continual Gathering Dynamic candidate gathering with network monitoring
Automatic Renomination Automatic switching to better candidate pairs
NAT Rules Address rewriting for complex NAT scenarios
Ping-Pong Example
The ping-pong example demonstrates a basic ICE connection between two peers.
Key Patterns
Setting up the agent:
examples/ping-pong/main.go
iceAgent , err = ice . NewAgentWithOptions (
ice . WithNetworkTypes ([] ice . NetworkType { ice . NetworkTypeUDP4 }),
)
Handling candidates:
examples/ping-pong/main.go
err = iceAgent . OnCandidate ( func ( c ice . Candidate ) {
if c == nil {
return
}
// Send candidate to remote peer via signaling
_ , err = http . PostForm ( fmt . Sprintf ( "http://localhost: %d /remoteCandidate" , remoteHTTPPort ),
url . Values {
"candidate" : { c . Marshal ()},
})
})
Establishing connection:
examples/ping-pong/main.go
// Controlling agent
if isControlling {
conn , err = iceAgent . Dial ( context . TODO (), remoteUfrag , remotePwd )
} else {
conn , err = iceAgent . Accept ( context . TODO (), remoteUfrag , remotePwd )
}
Exchanging data:
examples/ping-pong/main.go
// Send messages
val , err := randutil . GenerateCryptoRandomString ( 15 , "abcdefghijklmnopqrstuvwxyz" )
if _ , err = conn . Write ([] byte ( val )); err != nil {
panic ( err )
}
// Receive messages
buf := make ([] byte , 1500 )
for {
n , err := conn . Read ( buf )
if err != nil {
panic ( err )
}
fmt . Printf ( "Received: ' %s ' \n " , string ( buf [: n ]))
}
Running the Example
# Terminal 1 - Controlled agent
go run examples/ping-pong/main.go
# Terminal 2 - Controlling agent
go run examples/ping-pong/main.go -controlling
Continual Gathering Example
Demonstrates dynamic candidate gathering with network interface monitoring.
Key Patterns
Configuring continual gathering:
examples/continual-gathering/main.go
agent , err := ice . NewAgentWithOptions (
ice . WithNetworkTypes ([] ice . NetworkType { ice . NetworkTypeUDP4 , ice . NetworkTypeUDP6 }),
ice . WithCandidateTypes ([] ice . CandidateType { ice . CandidateTypeHost }),
ice . WithContinualGatheringPolicy ( ice . GatherContinually ),
ice . WithNetworkMonitorInterval ( 2 * time . Second ),
)
Tracking candidates over time:
examples/continual-gathering/main.go
candidateCount := 0
candidateMap := make ( map [ string ] ice . Candidate )
err = agent . OnCandidate ( func ( candidate ice . Candidate ) {
if candidate == nil {
if policy == ice . GatherOnce {
fmt . Println ( "Gathering completed" )
}
return
}
candidateCount ++
candidateID := candidate . String ()
if _ , exists := candidateMap [ candidateID ]; ! exists {
candidateMap [ candidateID ] = candidate
fmt . Printf ( "[ %s ] Candidate # %d : %s \n " ,
time . Now (). Format ( "15:04:05" ), candidateCount , candidate )
}
})
Monitoring gathering state:
examples/continual-gathering/main.go
go func () {
ticker := time . NewTicker ( 5 * time . Second )
defer ticker . Stop ()
for range ticker . C {
state , _ := agent . GetGatheringState ()
localCandidates , _ := agent . GetLocalCandidates ()
fmt . Printf ( "[ %s ] Status: GatheringState= %s , Candidates= %d \n " ,
time . Now (). Format ( "15:04:05" ), state , len ( localCandidates ))
}
}()
Running the Example
# Gather once (default)
go run examples/continual-gathering/main.go -mode=once
# Gather continually with custom interval
go run examples/continual-gathering/main.go -mode=continually -interval=5s
Try connecting/disconnecting network interfaces while running with -mode=continually to see new candidates discovered automatically.
Automatic Renomination Example
Shows automatic switching to better candidate pairs based on network conditions.
Key Patterns
Enabling automatic renomination:
examples/automatic-renomination/main.go
if isControlling {
renominationInterval := 3 * time . Second
iceAgent , err = ice . NewAgentWithOptions (
ice . WithNetworkTypes ([] ice . NetworkType { ice . NetworkTypeUDP4 , ice . NetworkTypeUDP6 }),
ice . WithRenomination ( ice . DefaultNominationValueGenerator ()),
ice . WithAutomaticRenomination ( renominationInterval ),
)
}
Monitoring candidate pair changes:
examples/automatic-renomination/main.go
err = iceAgent . OnSelectedCandidatePairChange ( func ( local , remote ice . Candidate ) {
fmt . Println ( ">>> SELECTED CANDIDATE PAIR CHANGED <<<" )
fmt . Printf ( " Local: %s (type: %s ) \n " , local , local . Type ())
fmt . Printf ( " Remote: %s (type: %s ) \n " , remote , remote . Type ())
})
Tracking RTT:
examples/automatic-renomination/main.go
func getRTT () string {
if selectedLocalCandidateID == "" || selectedRemoteCandidateID == "" {
return "N/A"
}
stats := iceAgent . GetCandidatePairsStats ()
for _ , stat := range stats {
if stat . LocalCandidateID == selectedLocalCandidateID &&
stat . RemoteCandidateID == selectedRemoteCandidateID {
if stat . CurrentRoundTripTime > 0 {
return fmt . Sprintf ( " %.2f ms" , stat . CurrentRoundTripTime * 1000 )
}
}
}
return "N/A"
}
Debugging candidate pairs:
examples/automatic-renomination/main.go
go func () {
for {
time . Sleep ( 5 * time . Second )
fmt . Println ( "=== DEBUG: Candidate Pair States ===" )
stats := iceAgent . GetCandidatePairsStats ()
for _ , stat := range stats {
fmt . Printf ( " %s <-> %s \n " , stat . LocalCandidateID , stat . RemoteCandidateID )
fmt . Printf ( " State: %s , Nominated: %v \n " , stat . State , stat . Nominated )
if stat . CurrentRoundTripTime > 0 {
fmt . Printf ( " RTT: %.2f ms \n " , stat . CurrentRoundTripTime * 1000 )
}
}
}
}()
Running the Example
This example requires network namespaces to simulate network changes:
# See README in examples/automatic-renomination for setup instructions
cd examples/automatic-renomination
cat README.md
The example uses tc (traffic control) to add latency to specific interfaces, triggering automatic renomination to better paths.
NAT Rules Example
Demonstrates address rewriting for various NAT scenarios.
Key Patterns
Multi-network host rewriting:
examples/nat-rules/main.go
rules := [] ice . AddressRewriteRule {
// Blue network
{
External : [] string { publicBlue },
Local : localBlue ,
AsCandidateType : ice . CandidateTypeHost ,
Networks : [] ice . NetworkType { ice . NetworkTypeUDP4 , ice . NetworkTypeTCP4 },
},
// Green network
{
External : [] string { publicGreen },
Local : localGreen ,
AsCandidateType : ice . CandidateTypeHost ,
Networks : [] ice . NetworkType { ice . NetworkTypeUDP4 , ice . NetworkTypeTCP4 },
},
// Global fallback
{
External : [] string { globalFallback },
AsCandidateType : ice . CandidateTypeHost ,
},
}
Interface-scoped rules:
examples/nat-rules/main.go
rules := [] ice . AddressRewriteRule {
{
External : [] string { publicBlue },
Local : localBlue ,
Iface : "eth0" ,
AsCandidateType : ice . CandidateTypeHost ,
},
{
External : [] string { publicGreen },
Local : localGreen ,
Iface : "eth1" ,
AsCandidateType : ice . CandidateTypeHost ,
},
}
Server reflexive pool:
examples/nat-rules/main.go
rules := [] ice . AddressRewriteRule {
// Primary srflx
{
External : [] string { srflxPrimary },
Local : "0.0.0.0" ,
AsCandidateType : ice . CandidateTypeServerReflexive ,
Networks : [] ice . NetworkType { ice . NetworkTypeUDP4 },
},
// Secondary srflx catch-all
{
External : [] string { srflxSecondary },
AsCandidateType : ice . CandidateTypeServerReflexive ,
Networks : [] ice . NetworkType { ice . NetworkTypeUDP4 },
},
// Host override
{
External : [] string { hostPublic },
Local : hostLocal ,
AsCandidateType : ice . CandidateTypeHost ,
},
}
CIDR-scoped rules:
examples/nat-rules/main.go
rules := [] ice . AddressRewriteRule {
// Scoped to specific CIDR
{
External : [] string { scopedPublic },
Local : scopedLocal ,
CIDR : "10.30.0.0/24" ,
AsCandidateType : ice . CandidateTypeHost ,
},
// Catch-all for same CIDR
{
External : [] string { catchAll },
CIDR : "10.30.0.0/24" ,
AsCandidateType : ice . CandidateTypeHost ,
},
}
Running the Example
# List available scenarios
go run examples/nat-rules/main.go -list
# Run specific scenario
go run examples/nat-rules/main.go -scenario=multi-net
# Run all scenarios
go run examples/nat-rules/main.go -scenario=all
# Use with Docker Compose topology
cd examples/nat-rules
docker-compose up
The Docker Compose setup creates a realistic multi-homed environment with multiple networks and NAT gateways.
Common Patterns
Error Handling
All examples demonstrate proper error handling:
if err := agent . GatherCandidates (); err != nil {
panic ( err )
}
Resource Cleanup
Always close the agent when done:
defer func () {
if closeErr := agent . Close (); closeErr != nil {
log . Printf ( "Failed to close agent: %v " , closeErr )
}
}()
Signaling
Examples use HTTP for simplicity, but production should use WebSocket or other real-time protocols:
// Simple HTTP signaling (examples only)
_ , err = http . PostForm ( fmt . Sprintf ( "http://localhost: %d /remoteCandidate" , port ),
url . Values { "candidate" : { c . Marshal ()}})
Logging
Enable debug logging to understand behavior:
import " github.com/pion/logging "
loggerFactory := logging . NewDefaultLoggerFactory ()
loggerFactory . DefaultLogLevel = logging . LogLevelDebug
agent , err := ice . NewAgentWithOptions (
ice . WithLoggerFactory ( loggerFactory ),
)
Building and Running
All examples can be run directly:
# Clone repository
git clone https://github.com/pion/ice.git
cd ice
# Run any example
go run examples/ping-pong/main.go
go run examples/continual-gathering/main.go
go run examples/automatic-renomination/main.go
go run examples/nat-rules/main.go
Troubleshooting Examples
Ping-Pong Connection Fails
Ensure both instances are running
Check they’re using opposite controlling flags
Verify HTTP ports are available (9000, 9001)
Enable debug logging to see connection progress
Continual Gathering Shows No New Candidates
Try actually changing network interfaces (enable/disable WiFi)
Check the monitoring interval isn’t too long
Verify interface filters aren’t excluding new interfaces
Automatic Renomination Doesn’t Switch
Ensure both agents support renomination
Check that network conditions actually changed (use tc to add latency)
Verify renomination interval has elapsed
Review candidate pair statistics for RTT differences
NAT Rules Not Applied
Verify local addresses match exactly
Check rule precedence (more specific first)
Enable debug logging to see which rules match
Ensure external IPs are valid
Next Steps
Configuration Deep dive into agent configuration options
Gathering Learn about candidate gathering in detail
Connectivity Checks Understand connectivity check mechanics
GitHub Repository Browse the full source code and examples