{"record":{"id":"92d3b9e5668ac36d","repo":"netbirdio/netbird","slug":"rule-already-exists","errorCode":null,"errorMessage":"rule already exists","messagePattern":"rule already exists","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"client/firewall/iptables/acl_linux.go","lineNumber":155,"sourceCode":"\t\t\t}\n\t\t}\n\t\tif err := m.createIPSet(ipsetName); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"create ipset: %w\", err)\n\t\t}\n\t\tif err := m.addToIPSet(ipsetName, ip); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"add IP to ipset: %w\", err)\n\t\t}\n\n\t\tipList := newIpList(ip.String())\n\t\tm.ipsetStore.addIpList(ipsetName, ipList)\n\t}\n\n\tok, err := m.iptablesClient.Exists(tableFilter, chain, specs...)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to check rule: %w\", err)\n\t}\n\tif ok {\n\t\treturn nil, fmt.Errorf(\"rule already exists\")\n\t}\n\n\t// Insert DROP rules at the beginning, append ACCEPT rules at the end\n\tif action == firewall.ActionDrop {\n\t\t// Insert at the beginning of the chain (position 1)\n\t\terr = m.iptablesClient.Insert(tableFilter, chain, 1, specs...)\n\t} else {\n\t\terr = m.iptablesClient.Append(tableFilter, chain, specs...)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := m.iptablesClient.Append(tableMangle, chainRTPRE, mangleSpecs...); err != nil {\n\t\tlog.Errorf(\"failed to add mangle rule: %v\", err)\n\t\tmangleSpecs = nil\n\t}\n","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/firewall/iptables/acl_linux.go#L137-L173","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nif ok {\n    return nil, fmt.Errorf(\"rule already exists\")\n}\n\n// after\nif ok {\n    return []firewall.Rule{&Rule{\n        ruleID:    uuid.New().String(),\n        ipsetName: ipsetName,\n        ip:        ip.String(),\n        chain:     chain,\n        specs:     specs,\n        v6:        m.v6,\n    }}, nil\n}","handlingStrategy":"validation","validationCode":"// dedupe before adding: query the manager for the kernel rule the same way it does\nif ok, err := iptClient.Exists(\"filter\", \"NETBIRD-ACL-INPUT\", specs...); err == nil && ok {\n    // rule already programmed; skip AddPeerFiltering or adopt it instead of erroring\n    return existingRuleHandle()\n}","typeGuard":null,"tryCatchPattern":"if err := mgr.AddPeerFiltering(...); err != nil {\n    if strings.Contains(err.Error(), \"rule already exists\") {\n        // benign duplicate: desired state already holds, log and continue\n        log.Warn(\"ACL rule already present, skipping\")\n        err = nil\n    }\n}","preventionTips":["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."],"tags":["go","linux","iptables","firewall","acl","idempotency","netbird"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}