{"record":{"id":"40d818721f8f8116","repo":"netbirdio/netbird","slug":"remove-allowed-ip-s-on-interface-s-w","errorCode":null,"errorMessage":"remove allowed IP %s on interface %s: %w","messagePattern":"remove allowed IP (.+?) on interface (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/iface/configurer/kernel_unix.go","lineNumber":212,"sourceCode":"\t\tif existingAllowedIP.String() == ipNet.String() {\n\t\t\tnewAllowedIPs = append(existingPeer.AllowedIPs[:i], existingPeer.AllowedIPs[i+1:]...) //nolint:gocritic\n\t\t\tbreak\n\t\t}\n\t}\n\n\tpeer := wgtypes.PeerConfig{\n\t\tPublicKey:         peerKeyParsed,\n\t\tUpdateOnly:        true,\n\t\tReplaceAllowedIPs: true,\n\t\tAllowedIPs:        newAllowedIPs,\n\t}\n\n\tconfig := wgtypes.Config{\n\t\tPeers: []wgtypes.PeerConfig{peer},\n\t}\n\terr = c.configure(config)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"remove allowed IP %s on interface %s: %w\", allowedIP, c.deviceName, err)\n\t}\n\treturn nil\n}\n\nfunc (c *KernelConfigurer) getPeer(ifaceName, peerPubKey string) (wgtypes.Peer, error) {\n\twg, err := wgctrl.New()\n\tif err != nil {\n\t\treturn wgtypes.Peer{}, fmt.Errorf(\"wgctl: %w\", err)\n\t}\n\tdefer func() {\n\t\terr = wg.Close()\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"Got error while closing wgctl: %v\", err)\n\t\t}\n\t}()\n\n\twgDevice, err := wg.Device(ifaceName)\n\tif err != nil {","sourceCodeStart":194,"sourceCodeEnd":230,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/iface/configurer/kernel_unix.go#L194-L230","documentation":"RemoveAllowedIP rebuilt the peer's allowed-IP list without the target prefix and asked the kernel to replace the peer's allowed IPs (ReplaceAllowedIPs:true, UpdateOnly:true), but wg.ConfigureDevice returned an error. Because the whole list is swapped in one netlink operation, one invalid surviving entry, a vanished peer or interface, or a permission problem aborts the removal. The wrapped netlink error names the concrete cause.","triggerScenarios":"Peer removed concurrently between getPeer and configure (UpdateOnly:true then hits a missing peer); device deleted mid-call; an allowed-IP entry in the existing list rejected by netlink validation; EPERM without CAP_NET_ADMIN.","commonSituations":"Two goroutines reconfiguring the same peer during a network-map roll; interface teardown racing a route removal; running without root in a test harness that created the device earlier.","solutions":["Read the wrapped error: ENOENT means peer/interface vanished (safe to ignore), EINVAL means a bad allowed IP, EPERM means privileges","Serialize peer reconfigurations with a per-peer or per-interface mutex to avoid remove/rebuild races","Re-check with FullStats and retry once if the peer still exists","Run with CAP_NET_ADMIN and confirm the device with wg show"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := kernelCfg.RemoveAllowedIP(peerKey, prefix); err != nil {\n\tif errors.Is(err, os.ErrNotExist) {\n\t\t// peer/interface vanished mid-operation; safe to treat as removed\n\t\treturn nil\n\t}\n\tif errors.Is(err, os.ErrPermission) {\n\t\t// escalate: daemon lost privileges, do not retry\n\t\treturn err\n\t}\n\t// EINVAL-style netlink rejection: dump current peers and investigate\n\treturn err\n}","preventionTips":["Serialize per-peer reconfiguration with a mutex to avoid rebuild races","Re-read peer state and retry once when the wrapped error indicates a vanished peer","Validate allowed-IP prefixes with netip.ParsePrefix before they ever reach the device","Run with CAP_NET_ADMIN so EPERM never masquerades as a config bug"],"tags":["wireguard","wgctrl","netlink","concurrency","peer-management"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}