netbirdio/netbird · error
delete ip from ipset: %w
Error message
delete ip from ipset: %w
What it means
The netlink DEL of a peer IP from its ipset failed inside DeletePeerRule. The in-memory ips map guard above only proves the manager believes the element exists; the kernel set may disagree (element or whole set removed externally), which yields ErrNotExist from ipset-go. Other causes are family mismatch between the net.IP and the set, or missing CAP_NET_ADMIN.
Source
Thrown at client/firewall/iptables/acl_linux.go:205
}
// DeletePeerRule from the firewall by rule definition
func (m *aclManager) DeletePeerRule(rule firewall.Rule) error {
r, ok := rule.(*Rule)
if !ok {
return fmt.Errorf("invalid rule type")
}
shouldDestroyIpset := false
if ipsetList, ok := m.ipsetStore.ipset(r.ipsetName); ok {
// delete IP from ruleset IPs list and ipset
if _, ok := ipsetList.ips[r.ip]; ok {
ip := net.ParseIP(r.ip)
if ip == nil {
return fmt.Errorf("parse IP %s", r.ip)
}
if err := m.delFromIPSet(r.ipsetName, ip); err != nil {
return fmt.Errorf("delete ip from ipset: %w", err)
}
delete(ipsetList.ips, r.ip)
}
// if after delete, set still contains other IPs,
// no need to delete firewall rule and we should exit here
if len(ipsetList.ips) != 0 {
return nil
}
// we delete last IP from the set, that means we need to delete
// set itself and associated firewall rule too
m.ipsetStore.deleteIpset(r.ipsetName)
shouldDestroyIpset = true
}
if err := m.iptablesClient.Delete(tableName, r.chain, r.specs...); err != nil {
return fmt.Errorf("failed to delete rule: %s, %v: %w", r.chain, r.specs, err)View on GitHub (pinned to 93e97f4bf1)
Solutions
- Treat ErrNotExist as success (element already gone) and continue the delete flow, since the desired end state is achieved.
- Recreate/synchronize the set when the whole set is missing before retrying the DEL.
- Run with CAP_NET_ADMIN and validate the IP family against the manager's v6 flag.
- Avoid external mutation of NETBIRD ipsets; use the manager's Reset to reconcile.
Example fix
// before
if err := m.delFromIPSet(r.ipsetName, ip); err != nil {
return fmt.Errorf("delete ip from ipset: %w", err)
}
// after
if err := m.delFromIPSet(r.ipsetName, ip); err != nil {
if errors.Is(err, ipset.ErrElementNotExist) || errors.Is(err, ipset.ErrSetNotExist) {
log.Debugf("ip %s already absent from ipset %s", r.ip, r.ipsetName)
} else {
return fmt.Errorf("delete ip from ipset: %w", err)
}
} Defensive patterns
Strategy: try-catch
Validate before calling
// verify the element is really in the kernel set before issuing the DEL
if err := ipset.Test(name, &ipset.Entry{IP: ip, CIDR: cidr}); err != nil {
if errors.Is(err, ipset.ErrElementNotExist) || errors.Is(err, ipset.ErrSetNotExist) {
// already gone: skip the kernel DEL, still update local maps
}
} Try / catch
if err := mgr.DeletePeerRule(rule); err != nil {
if strings.Contains(err.Error(), "delete ip from ipset") &&
(errors.Is(err, ipset.ErrElementNotExist) || errors.Is(err, ipset.ErrSetNotExist)) {
// element/set already absent: desired state achieved, ignore
err = nil
}
} Prevention
- Treat ipset 'does not exist' outcomes on delete as success; the end state is what matters.
- Do not flush or destroy NETBIRD ipsets externally; if you must, run manager Reset right after.
- Keep the process privileged so netlink DEL is not EPERM.
- After external ipset changes, reconcile with Reset instead of incremental deletes.
When it happens
Trigger: DeletePeerRule after an external `ipset flush`/`destroy` removed the element or set while the manager's map still lists it; agent restart that repopulated ipsetStore from state but the kernel was reset; unprivileged execution; v4 IP passed to a v6 set's delete path.
Common situations: Hosts where operators periodically flush ipsets; crash/restart sequences that leave the memory map ahead of the kernel; cleanup code running after the network namespace changed.
Related errors
- delete IP from ipset %s: %w
- create ipset: %w
- add IP to ipset %s: %w
- add IP to ipset: %w
- rule already exists
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/7264b269219ba28d.
Report an issue: GitHub.