netbirdio/netbird · warning
rule already exists
Error message
rule already exists
What it means
Not a syscall failure: iptablesClient.Exists returned true, meaning a rule with exactly these specs (including the -m set --match-set clause) is already programmed in NETBIRD-ACL-INPUT, so the manager refuses a duplicate. It typically surfaces when kernel rules survived an unclean agent shutdown while the in-memory ipsetStore did not, so the code takes the create-set path and then discovers the leftover filter rule, or when the same peer/rule is applied twice without deduplication.
Source
Thrown at client/firewall/iptables/acl_linux.go:155
}
}
if err := m.createIPSet(ipsetName); err != nil {
return nil, fmt.Errorf("create ipset: %w", err)
}
if err := m.addToIPSet(ipsetName, ip); err != nil {
return nil, fmt.Errorf("add IP to ipset: %w", err)
}
ipList := newIpList(ip.String())
m.ipsetStore.addIpList(ipsetName, ipList)
}
ok, err := m.iptablesClient.Exists(tableFilter, chain, specs...)
if err != nil {
return nil, fmt.Errorf("failed to check rule: %w", err)
}
if ok {
return nil, fmt.Errorf("rule already exists")
}
// Insert DROP rules at the beginning, append ACCEPT rules at the end
if action == firewall.ActionDrop {
// Insert at the beginning of the chain (position 1)
err = m.iptablesClient.Insert(tableFilter, chain, 1, specs...)
} else {
err = m.iptablesClient.Append(tableFilter, chain, specs...)
}
if err != nil {
return nil, err
}
if err := m.iptablesClient.Append(tableMangle, chainRTPRE, mangleSpecs...); err != nil {
log.Errorf("failed to add mangle rule: %v", err)
mangleSpecs = nil
}
View on GitHub (pinned to 93e97f4bf1)
Solutions
- Call aclManager.Reset() (or Manager.Reset) before reprogramming rules after a restart, which is what init/cleanChains normally does; ensure that path ran.
- Deduplicate at the caller: track rule IDs and skip re-adding an unchanged rule.
- Treat Exists==true as idempotent success returning a Rule handle instead of an error.
Example fix
// before
if ok {
return nil, fmt.Errorf("rule already exists")
}
// after
if ok {
return []firewall.Rule{&Rule{
ruleID: uuid.New().String(),
ipsetName: ipsetName,
ip: ip.String(),
chain: chain,
specs: specs,
v6: m.v6,
}}, nil
} Defensive patterns
Strategy: validation
Validate before calling
// dedupe before adding: query the manager for the kernel rule the same way it does
if ok, err := iptClient.Exists("filter", "NETBIRD-ACL-INPUT", specs...); err == nil && ok {
// rule already programmed; skip AddPeerFiltering or adopt it instead of erroring
return existingRuleHandle()
} Try / catch
if err := mgr.AddPeerFiltering(...); err != nil {
if strings.Contains(err.Error(), "rule already exists") {
// benign duplicate: desired state already holds, log and continue
log.Warn("ACL rule already present, skipping")
err = nil
}
} Prevention
- Call Manager.Reset during startup/registration before applying a fresh network map so leftover kernel rules are cleared.
- Key applied rules by rule ID (or ipset+ip+proto+port+action) and skip re-application of unchanged entries.
- Treat 'rule already exists' as success in retry loops instead of escalating.
- Avoid invoking AddPeerFiltering twice for the same rule within one reconcile pass.
When it happens
Trigger: Agent crashed or was SIGKILLed so iptables rules persisted, then re-added on next start before Reset/cleanChains removed them; duplicate AddPeerFiltering calls for the same ipset, ip, port, and action; two ACL entries from the management network map collapsing to identical specs.
Common situations: Recovery flows after `kill -9` or power loss; network-map re-application where a peer's rule is resent unchanged; test harnesses that re-run AddPeerFiltering without resetting the manager.
Related errors
- failed to delete rule: %s, %v: %w
- add IP to ipset: %w
- failed to check rule: %w
- delete ip from ipset: %w
- list chains: %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/92d3b9e5668ac36d.
Report an issue: GitHub.