{"record":{"id":"986645c3be02fc02","repo":"netbirdio/netbird","slug":"panic-v","errorCode":null,"errorMessage":"panic: %v","messagePattern":"panic: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/firewall/uspfilter/localip.go","lineNumber":66,"sourceCode":"\t\t}\n\n\t\tparsed, ok := netip.AddrFromSlice(ip)\n\t\tif !ok {\n\t\t\tlog.Warnf(\"invalid IP address %s in interface %s\", ip.String(), iface.Name)\n\t\t\tcontinue\n\t\t}\n\n\t\tparsed = parsed.Unmap()\n\t\tips[parsed] = struct{}{}\n\t\t*addresses = append(*addresses, parsed)\n\t}\n}\n\n// UpdateLocalIPs rebuilds the local IP snapshot and swaps it in atomically.\nfunc (m *localIPManager) UpdateLocalIPs(iface common.IFaceMapper) (err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terr = fmt.Errorf(\"panic: %v\", r)\n\t\t}\n\t}()\n\n\tips := make(map[netip.Addr]struct{})\n\tvar addresses []netip.Addr\n\n\t// loopback\n\tips[netip.AddrFrom4([4]byte{127, 0, 0, 1})] = struct{}{}\n\tips[netip.IPv6Loopback()] = struct{}{}\n\n\tif iface != nil {\n\t\tip := iface.Address().IP\n\t\tips[ip] = struct{}{}\n\t\taddresses = append(addresses, ip)\n\t\tif v6 := iface.Address().IPv6; v6.IsValid() {\n\t\t\tips[v6] = struct{}{}\n\t\t\taddresses = append(addresses, v6)\n\t\t}","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/firewall/uspfilter/localip.go#L48-L84","documentation":"UpdateLocalIPs recovers any panic in the local-IP snapshot rebuild and converts it to a regular error string. The recover guard exists because the function reads live interface state (iface.Address(), net.Interfaces, per-interface Addrs) that can mutate concurrently when the overlay interface is being reconfigured or torn down mid-enumeration. The %v formatting flattens the panic value, so the original stack trace is lost, making the cause hard to pinpoint from the message alone.","triggerScenarios":"A panic inside iface.Address() when the address mapper is being swapped concurrently; a panic while iterating interfaces that disappear mid-iteration on platforms with volatile network state; theoretically any nil-dereference on the IFaceMapper implementation provided by a different package version.","commonSituations":"Interface reconfiguration races (address change, v6 enable/disable toggling) while the periodic local-IP refresh runs; VPN or container interfaces appearing/vanishing during enumeration on macOS/Windows; version skew between the uspfilter package and a custom IFaceMapper implementation.","solutions":["Reproduce with the panic's runtime stack: temporarily log debug.Stack() in the recover block to capture where it originated","Serialize calls: do not invoke UpdateLocalIPs concurrently with interface address changes; hook it after the address swap completes","If using a custom common.IFaceMapper, make its Address() method return a value copy safe against concurrent mutation","Upgrade NetBird if the panic originates inside shipped iface code, and report the captured stack"],"exampleFix":"// before\ndefer func() {\n    if r := recover(); r != nil {\n        err = fmt.Errorf(\"panic: %v\", r)\n    }\n}()\n\n// after\ndefer func() {\n    if r := recover(); r != nil {\n        err = fmt.Errorf(\"panic: %v\\n%s\", r, debug.Stack())\n    }\n}()","handlingStrategy":"fallback","validationCode":"// keep the previous snapshot when a refresh fails\nif err := m.localIPs.UpdateLocalIPs(iface); err != nil {\n    log.Warnf(\"local IP refresh failed, keeping previous snapshot: %v\", err)\n}","typeGuard":"func (m *localIPManager) hasSnapshot() bool {\n    return m.snapshot.Load() != nil\n}","tryCatchPattern":"// the recover is inside the library; callers just handle the error\nif err := m.UpdateLocalIPs(iface); err != nil {\n    if strings.HasPrefix(err.Error(), \"panic:\") {\n        log.Warnf(\"snapshot refresh panicked; retaining last-known IPs: %v\", err)\n        return // previous snapshot still serves IsLocalIP\n    }\n    return err\n}","preventionTips":["Treat UpdateLocalIPs as best-effort: never fail the caller's flow on it","Serialize refreshes with interface address changes to shrink the race window","Capture debug.Stack() in your own recover blocks so panics stay diagnosable","Never pass an IFaceMapper implementation whose Address() can panic on concurrent reads"],"tags":["go","netbird","panic","race","firewall","diagnostics"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}