netbirdio/netbird · warning
ICEBind has not been initialized yet
Error message
ICEBind has not been initialized yet
What it means
GetICEMux returns the pion UniversalUDPMuxDefault that ICEBind creates, but that mux only exists after wireguard-go calls the receiver-creation hook during StdNetBind.Open (i.e. after a BindUpdate brings the interface up). Close() nils udpMux again, so the mux is absent both before the first Open and after a Close. The check under muUDPMux returns this error instead of a nil pointer, forcing callers to handle the not-yet-initialized case explicitly.
Source
Thrown at client/iface/bind/ice_bind.go:133
s.muUDPMux.Lock()
s.ipv4Conn = nil
s.ipv6Conn = nil
s.udpMux = nil
s.muUDPMux.Unlock()
return s.StdNetBind.Close()
}
func (s *ICEBind) ActivityRecorder() *ActivityRecorder {
return s.activityRecorder
}
// GetICEMux returns the ICE UDPMux that was created and used by ICEBind
func (s *ICEBind) GetICEMux() (*udpmux.UniversalUDPMuxDefault, error) {
s.muUDPMux.Lock()
defer s.muUDPMux.Unlock()
if s.udpMux == nil {
return nil, fmt.Errorf("ICEBind has not been initialized yet")
}
return s.udpMux, nil
}
func (b *ICEBind) SetEndpoint(fakeIP netip.Addr, conn net.Conn) {
b.endpointsMu.Lock()
b.endpoints[fakeIP] = conn
b.endpointsMu.Unlock()
}
func (b *ICEBind) RemoveEndpoint(fakeIP netip.Addr) {
b.endpointsMu.Lock()
defer b.endpointsMu.Unlock()
delete(b.endpoints, fakeIP)
}
View on GitHub (pinned to 93e97f4bf1)
Solutions
- Only call GetICEMux after the interface is confirmed up (BindUpdate/Open completed and the device reports a listen port)
- Treat the error as retryable: wait for the interface-ready event or poll with a deadline instead of failing the caller's flow
- Re-check after Close: if the error follows a teardown, propagate shutdown rather than retrying
- In tests, drive a full Open before asserting on the mux
Example fix
// before
mux, err := b.GetICEMux()
if err != nil { return err }
// after
var mux *udpmux.UniversalUDPMuxDefault
err := retry.Do(func() error {
var e error
mux, e = b.GetICEMux()
return e
}, retry.WithDelay(100*time.Millisecond), retry.WithMaxDelay(5*time.Second))
if err != nil { return err } Defensive patterns
Strategy: retry
Validate before calling
// only query the mux after the bind is open (device has a port)
if b.IsClosed() { // or track bind state at your layer
return errors.New("bind not open")
}
mux, err := b.GetICEMux()
if err != nil { return err } Type guard
func (s *ICEBind) muxReady() bool {
s.muUDPMux.Lock()
defer s.muUDPMux.Unlock()
return s.udpMux != nil
} Try / catch
deadline := time.Now().Add(5 * time.Second)
for {
mux, err := b.GetICEMux()
if err == nil {
return mux, nil
}
if !strings.Contains(err.Error(), "not been initialized") || time.Now().After(deadline) {
return nil, err
}
time.Sleep(100 * time.Millisecond) // wait for Open/BindUpdate
} Prevention
- Query the mux only after the interface-up event, not after engine construction
- Treat this error as retryable-but-bounded: after Close it never becomes ready again
- Wire ICE candidate gathering to start on bind-open callbacks rather than on startup order
When it happens
Trigger: Calling GetICEMux before the WireGuard device has been opened (before netbird up completes the bind); calling it after Close/BindUpdate teardown when udpMux was reset to nil; a query racing an ongoing Close so the mux disappears between the nil check and the return.
Common situations: Connection-manager or ICE agent code that starts gathering candidates as soon as the engine object exists, before the interface bind finished; shutdown paths where the signal to stop ICE and the interface Close interleave; tests that construct ICEBind without ever calling Open.
Related errors
- service is not up
- client not initialized
- engine not started
- received error "%w" while configuring interface %s with port
- received error "%w" while updating peer on interface %s with
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/4b54f84047f79eb8.
Report an issue: GitHub.