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

  1. Only call GetICEMux after the interface is confirmed up (BindUpdate/Open completed and the device reports a listen port)
  2. Treat the error as retryable: wait for the interface-ready event or poll with a deadline instead of failing the caller's flow
  3. Re-check after Close: if the error follows a teardown, propagate shutdown rather than retrying
  4. 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

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


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/4b54f84047f79eb8. Report an issue: GitHub.