{"record":{"id":"a0157c9b2c5d8f1d","repo":"netbirdio/netbird","slug":"remove-prerouting-rule-s-s-w","errorCode":null,"errorMessage":"remove prerouting rule %s -> %s: %w","messagePattern":"remove prerouting rule (.+?) -> (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/firewall/nftables/router_linux.go","lineNumber":1514,"sourceCode":"\truleKey := firewall.GenKey(firewall.PreroutingFormat, pair)\n\n\trule, exists := r.rules[ruleKey]\n\tif !exists {\n\t\tlog.Debugf(\"prerouting rule %s not found\", ruleKey)\n\t\treturn nil\n\t}\n\n\tif rule.Handle == 0 {\n\t\tlog.Warnf(\"prerouting rule %s has no handle, removing stale entry\", ruleKey)\n\t\tif err := r.decrementSetCounter(rule); err != nil {\n\t\t\tlog.Warnf(\"decrement set counter for stale rule %s: %v\", ruleKey, err)\n\t\t}\n\t\tdelete(r.rules, ruleKey)\n\t\treturn nil\n\t}\n\n\tif err := r.conn.DelRule(rule); err != nil {\n\t\treturn fmt.Errorf(\"remove prerouting rule %s -> %s: %w\", pair.Source, pair.Destination, err)\n\t}\n\n\tlog.Debugf(\"removed prerouting rule %s -> %s\", pair.Source, pair.Destination)\n\n\tdelete(r.rules, ruleKey)\n\n\tif err := r.decrementSetCounter(rule); err != nil {\n\t\treturn fmt.Errorf(\"decrement set counter: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// refreshRulesMap rebuilds the rule map from the kernel. This removes stale entries\n// (e.g. from failed flushes) and updates handles for all existing rules.\nfunc (r *router) refreshRulesMap() error {\n\tvar merr *multierror.Error\n\tnewRules := make(map[string]*nftables.Rule)","sourceCodeStart":1496,"sourceCodeEnd":1532,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/firewall/nftables/router_linux.go#L1496-L1532","documentation":"Returned by removeNatRule when nftables.Conn.DelRule fails to delete a cached prerouting NAT rule from the netbird routing chains. DelRule sends an NFT_MSG_DELRULE netlink request that executes immediately (unlike AddRule, which buffers until Flush), so this is a real kernel ACK error for the handle stored in r.rules. The message names the routing pair's source and destination networks.","triggerScenarios":"RemoveNatRule on a masquerading pair (or its inverse pair) where the cached rule handle no longer matches kernel state: the rule was already removed by an external tool, the netlink socket errored, or the process lacks CAP_NET_ADMIN.","commonSituations":"firewalld reload, 'nft flush ruleset', iptables-nft or a second agent instance wiped the netbird table between refreshRulesMap and DelRule; agent containerized without NET_ADMIN; host hardening scripts rewriting nftables under the agent.","solutions":["Check 'sudo nft list ruleset' for the netbird prerouting rule: if it is already gone, the error is benign state skew.","Treat ENOENT as success (errors.Is(err, unix.ENOENT)) since a missing rule is the desired end state.","Ensure only one NetBird agent instance manages nftables on the host and stop external tooling from flushing its tables.","Verify the daemon runs as root or with CAP_NET_ADMIN.","Restart the agent to rebuild the rules map from the kernel if every removal fails."],"exampleFix":"// before\nif err := r.conn.DelRule(rule); err != nil {\n    return fmt.Errorf(\"remove prerouting rule %s -> %s: %w\", pair.Source, pair.Destination, err)\n}\n\n// after: a rule that is already gone satisfies the removal intent\nif err := r.conn.DelRule(rule); err != nil {\n    if errors.Is(err, unix.ENOENT) {\n        log.Warnf(\"prerouting rule %s -> %s already absent\", pair.Source, pair.Destination)\n        delete(r.rules, ruleKey)\n        return nil\n    }\n    return fmt.Errorf(\"remove prerouting rule %s -> %s: %w\", pair.Source, pair.Destination, err)\n}","handlingStrategy":"try-catch","validationCode":"// Before removing, confirm the rule still exists under its UserData key\nfunc preroutingRuleExists(conn *nftables.Conn, table *nftables.Table, chains map[string]*nftables.Chain, key string) (bool, error) {\n    for _, chain := range chains {\n        rules, err := conn.GetRules(table, chain)\n        if err != nil {\n            return false, err\n        }\n        for _, rl := range rules {\n            if string(rl.UserData) == key {\n                return true, nil\n            }\n        }\n    }\n    return false, nil\n}","typeGuard":null,"tryCatchPattern":"if err := removeNat(pair); err != nil {\n    if errors.Is(err, unix.ENOENT) {\n        // rule already gone from the kernel: desired state reached\n        return nil\n    }\n    return fmt.Errorf(\"remove prerouting rule %s -> %s: %w\", pair.Source, pair.Destination, err)\n}","preventionTips":["Run exactly one NetBird agent per host and keep external nftables tooling from touching the netbird tables.","Grant root or CAP_NET_ADMIN to the daemon.","Prefer APIs that refresh handles (refreshRulesMap-backed removals) over cached-handle deletes when orchestrating from outside.","Monitor logs for ENOENT clusters: they indicate a second writer on the ruleset."],"tags":["nftables","netlink","firewall","nat","linux"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}