{"record":{"id":"4b54f84047f79eb8","repo":"netbirdio/netbird","slug":"icebind-has-not-been-initialized-yet","errorCode":null,"errorMessage":"ICEBind has not been initialized yet","messagePattern":"ICEBind has not been initialized yet","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"client/iface/bind/ice_bind.go","lineNumber":133,"sourceCode":"\ts.muUDPMux.Lock()\n\ts.ipv4Conn = nil\n\ts.ipv6Conn = nil\n\ts.udpMux = nil\n\ts.muUDPMux.Unlock()\n\n\treturn s.StdNetBind.Close()\n}\n\nfunc (s *ICEBind) ActivityRecorder() *ActivityRecorder {\n\treturn s.activityRecorder\n}\n\n// GetICEMux returns the ICE UDPMux that was created and used by ICEBind\nfunc (s *ICEBind) GetICEMux() (*udpmux.UniversalUDPMuxDefault, error) {\n\ts.muUDPMux.Lock()\n\tdefer s.muUDPMux.Unlock()\n\tif s.udpMux == nil {\n\t\treturn nil, fmt.Errorf(\"ICEBind has not been initialized yet\")\n\t}\n\n\treturn s.udpMux, nil\n}\n\nfunc (b *ICEBind) SetEndpoint(fakeIP netip.Addr, conn net.Conn) {\n\tb.endpointsMu.Lock()\n\tb.endpoints[fakeIP] = conn\n\tb.endpointsMu.Unlock()\n}\n\nfunc (b *ICEBind) RemoveEndpoint(fakeIP netip.Addr) {\n\tb.endpointsMu.Lock()\n\tdefer b.endpointsMu.Unlock()\n\n\tdelete(b.endpoints, fakeIP)\n}\n","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/iface/bind/ice_bind.go#L115-L151","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nmux, err := b.GetICEMux()\nif err != nil { return err }\n\n// after\nvar mux *udpmux.UniversalUDPMuxDefault\nerr := retry.Do(func() error {\n    var e error\n    mux, e = b.GetICEMux()\n    return e\n}, retry.WithDelay(100*time.Millisecond), retry.WithMaxDelay(5*time.Second))\nif err != nil { return err }","handlingStrategy":"retry","validationCode":"// only query the mux after the bind is open (device has a port)\nif b.IsClosed() { // or track bind state at your layer\n    return errors.New(\"bind not open\")\n}\nmux, err := b.GetICEMux()\nif err != nil { return err }","typeGuard":"func (s *ICEBind) muxReady() bool {\n    s.muUDPMux.Lock()\n    defer s.muUDPMux.Unlock()\n    return s.udpMux != nil\n}","tryCatchPattern":"deadline := time.Now().Add(5 * time.Second)\nfor {\n    mux, err := b.GetICEMux()\n    if err == nil {\n        return mux, nil\n    }\n    if !strings.Contains(err.Error(), \"not been initialized\") || time.Now().After(deadline) {\n        return nil, err\n    }\n    time.Sleep(100 * time.Millisecond) // wait for Open/BindUpdate\n}","preventionTips":["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"],"tags":["go","netbird","ice","pion","wireguard","lifecycle"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}