Skip to main content
Pion ICE supports Multicast DNS (mDNS) for local network discovery, allowing peers to exchange ICE candidates using .local hostnames instead of IP addresses. This enhances privacy by preventing IP address exposure in certain scenarios.

MulticastDNS Modes

ICE agents can operate in three different mDNS modes:
mdns.go
type MulticastDNSMode byte

const (
    // MulticastDNSModeDisabled means remote mDNS candidates will be discarded, 
    // and local host candidates will use IPs.
    MulticastDNSModeDisabled MulticastDNSMode = iota + 1

    // MulticastDNSModeQueryOnly means remote mDNS candidates will be accepted, 
    // and local host candidates will use IPs.
    MulticastDNSModeQueryOnly

    // MulticastDNSModeQueryAndGather means remote mDNS candidates will be accepted,
    // and local host candidates will use mDNS.
    MulticastDNSModeQueryAndGather
)

Disabled Mode

In this mode, mDNS is completely disabled:
  • Local candidates use IP addresses
  • Remote .local candidates are discarded with a warning
agent, err := ice.NewAgent(&ice.AgentConfig{
    MulticastDNSMode: ice.MulticastDNSModeDisabled,
})
If a remote peer sends mDNS candidates while your agent is in MulticastDNSModeDisabled, those candidates will be ignored and you may fail to establish connectivity.

Query Only Mode

The agent can resolve remote .local addresses but publishes IP addresses for its own candidates:
agent, err := ice.NewAgent(&ice.AgentConfig{
    MulticastDNSMode: ice.MulticastDNSModeQueryOnly,
})
Use this mode when:
  • You want to accept connections from privacy-conscious peers using mDNS
  • Your application doesn’t require IP address privacy
  • You want maximum compatibility (IP addresses work everywhere)

Query and Gather Mode

Full mDNS support - both generate and resolve .local addresses:
agent, err := ice.NewAgent(&ice.AgentConfig{
    MulticastDNSMode: ice.MulticastDNSModeQueryAndGather,
})
Use this mode when:
  • Privacy is important (hide IP addresses)
  • Peers are on the same local network
  • Both sides support mDNS
This is the most privacy-preserving mode but requires both peers to support mDNS and be on networks that allow multicast traffic.

mDNS Name Generation

When using MulticastDNSModeQueryAndGather, Pion ICE generates a unique hostname following the mDNS ICE candidates specification:
mdns.go
func generateMulticastDNSName() (string, error) {
    // The unique name MUST consist of a version 4 UUID as defined in [RFC4122], 
    // followed by ".local".
    u, err := uuid.NewRandom()
    return u.String() + ".local", err
}
Example generated name:
550e8400-e29b-41d4-a716-446655440000.local
The UUID-based naming ensures uniqueness across all peers on the local network and prevents hostname collisions.

Network Configuration

The mDNS server can be configured for specific network interfaces:
agent, err := ice.NewAgent(&ice.AgentConfig{
    MulticastDNSMode: ice.MulticastDNSModeQueryAndGather,
    Interfaces:       myInterfaces,        // Specific interfaces
    IncludeLoopback:  true,                // Include loopback (127.0.0.1)
    LocalAddress:     net.ParseIP("..."), // Bind to specific address
})

IPv4 and IPv6 Support

mDNS operates on both IPv4 and IPv6:
  • IPv4: 224.0.0.251:5353 (defined in mdns.DefaultAddressIPv4)
  • IPv6: [ff02::fb]:5353 (defined in mdns.DefaultAddressIPv6)
The agent automatically enables the appropriate IP versions based on your NetworkTypes configuration:
mdns.go
var useV4, useV6 bool
for _, nt := range networkTypes {
    if nt.IsIPv4() {
        useV4 = true
    }
    if nt.IsIPv6() {
        useV6 = true
    }
}

Candidate Resolution

When a remote candidate with a .local address is added, the agent automatically resolves it:
agent.go
// If we have a mDNS Candidate lets fully resolve it before adding it locally
if cand.Type() == CandidateTypeHost && strings.HasSuffix(cand.Address(), ".local") {
    if a.mDNSMode == MulticastDNSModeDisabled {
        a.log.Warnf("Remote mDNS candidate added, but mDNS is disabled: (%s)", cand.Address())
        return nil
    }
    go a.resolveAndAddMulticastCandidate(hostCandidate)
}
Resolution happens asynchronously in a goroutine to avoid blocking the agent’s main loop.

Error Handling

If mDNS server initialization fails, Pion ICE automatically falls back to disabled mode:
mdns.go
if mdns4Err != nil && mdns6Err != nil {
    // If ICE fails to start MulticastDNS server just warn the user and continue
    log.Errorf("Failed to enable mDNS, continuing in mDNS disabled mode")
    return nil, MulticastDNSModeDisabled, nil
}
Common reasons for mDNS failure:
  • Port 5353 is already in use
  • Insufficient permissions to bind to multicast addresses
  • Multicast traffic is blocked by firewall
  • Network interface doesn’t support multicast
If mDNS initialization fails, the agent continues with IP-based candidates. Monitor your logs for mDNS errors to ensure expected behavior.

Use Cases

Local Development

mDNS is perfect for development environments where peers are on the same network:
// Development agent with full mDNS support
agent, err := ice.NewAgent(&ice.AgentConfig{
    MulticastDNSMode: ice.MulticastDNSModeQueryAndGather,
    IncludeLoopback:  true, // Allow connections to localhost
})

Privacy-Preserving Applications

Applications that prioritize user privacy can use mDNS to avoid exposing IP addresses in signaling:
agent, err := ice.NewAgent(&ice.AgentConfig{
    MulticastDNSMode: ice.MulticastDNSModeQueryAndGather,
})

// Candidates will look like:
// a=candidate:1 1 udp 2130706431 550e8400-e29b-41d4-a716-446655440000.local 54321 typ host

IoT and Local Device Discovery

IoT devices on a local network can discover each other using mDNS without requiring external STUN/TURN servers:
agent, err := ice.NewAgent(&ice.AgentConfig{
    MulticastDNSMode: ice.MulticastDNSModeQueryAndGather,
    Interfaces:       []string{"eth0"}, // Specific IoT network interface
})

Mixed Environments

For maximum compatibility, use query-only mode to support both traditional IP and mDNS peers:
agent, err := ice.NewAgent(&ice.AgentConfig{
    MulticastDNSMode: ice.MulticastDNSModeQueryOnly,
})
// Publishes: IP addresses
// Accepts: Both IP addresses and .local names

SDP Representation

mDNS candidates appear in SDP with .local hostnames:
a=candidate:1 1 udp 2130706431 550e8400-e29b-41d4-a716-446655440000.local 54321 typ host
Compare with traditional IP-based candidate:
a=candidate:1 1 udp 2130706431 192.168.1.100 54321 typ host

Network Requirements

Allow UDP traffic on port 5353 for multicast addresses:
  • IPv4: 224.0.0.251
  • IPv6: ff02::fb
Many corporate networks block multicast traffic. Test connectivity in your target environment.
Ensure your router/switch supports:
  • IGMP (Internet Group Management Protocol) for IPv4 multicast
  • MLD (Multicast Listener Discovery) for IPv6 multicast
Some routers disable multicast forwarding by default.
All major operating systems support mDNS:
  • Linux: Avahi daemon (usually pre-installed)
  • macOS: Bonjour (built-in)
  • Windows: Bonjour service (may need installation)
  • iOS/Android: Native support

Debugging

Enable debug logging to troubleshoot mDNS issues:
loggerFactory := logging.NewDefaultLoggerFactory()
loggerFactory.DefaultLogLevel = logging.LogLevelTrace

agent, err := ice.NewAgent(&ice.AgentConfig{
    MulticastDNSMode: ice.MulticastDNSModeQueryAndGather,
    LoggerFactory:    loggerFactory,
})
Look for messages like:
  • Failed to enable mDNS over IPv4/IPv6
  • Remote mDNS candidate added, but mDNS is disabled
  • Failed to resolve .local address

Reference

  • mDNS Modes: mdns.go:18 - MulticastDNSMode enum
  • Name Generation: mdns.go:33 - generateMulticastDNSName function
  • Server Creation: mdns.go:42 - createMulticastDNS function
  • Candidate Resolution: agent.go:955 - mDNS candidate handling
  • Specification: draft-ietf-rtcweb-mdns-ice-candidates

Build docs developers (and LLMs) love