{"record":{"id":"e5fd8527a633e66e","repo":"netbirdio/netbird","slug":"insert-rules-for-s-w","errorCode":null,"errorMessage":"insert rules for %s: %w","messagePattern":"insert rules for (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/firewall/nftables/router_linux.go","lineNumber":706,"sourceCode":"\t\tlog.Warnf(\"This peer is connected to a NetBird Management service with an older version. Allowing all traffic for %s\", pair.Destination)\n\t\tif err := r.addLegacyRouteRule(pair); err != nil {\n\t\t\treturn fmt.Errorf(\"add legacy routing rule: %w\", err)\n\t\t}\n\t}\n\n\tif pair.Masquerade {\n\t\tif err := r.addNatRule(pair); err != nil {\n\t\t\treturn fmt.Errorf(\"add nat rule: %w\", err)\n\t\t}\n\n\t\tif err := r.addNatRule(firewall.GetInversePair(pair)); err != nil {\n\t\t\treturn fmt.Errorf(\"add inverse nat rule: %w\", err)\n\t\t}\n\t}\n\n\tif err := r.conn.Flush(); err != nil {\n\t\tr.rollbackRules(pair)\n\t\treturn fmt.Errorf(\"insert rules for %s: %w\", pair.Destination, err)\n\t}\n\n\treturn nil\n}\n\n// rollbackRules cleans up unflushed rules and their set counters after a flush failure.\nfunc (r *router) rollbackRules(pair firewall.RouterPair) {\n\tkeys := []string{\n\t\tfirewall.GenKey(firewall.ForwardingFormat, pair),\n\t\tfirewall.GenKey(firewall.PreroutingFormat, pair),\n\t\tfirewall.GenKey(firewall.PreroutingFormat, firewall.GetInversePair(pair)),\n\t}\n\tfor _, key := range keys {\n\t\trule, ok := r.rules[key]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif err := r.decrementSetCounter(rule); err != nil {","sourceCodeStart":688,"sourceCodeEnd":724,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/firewall/nftables/router_linux.go#L688-L724","documentation":"Returned by AddNatRule (router_linux.go:704-706) when the final Conn.Flush fails that would commit the legacy forwarding rule and/or the two mangle-prerouting mark rules for the pair. Before returning, rollbackRules (line 713) removes the three keys from r.rules and decrements their set counters, restoring local bookkeeping while the kernel (which applies a failed netlink batch atomically) typically keeps its prior state. Errnos seen here: EINVAL from a malformed expression, ENOENT when the target chain/table vanished (external flush), EPERM without CAP_NET_ADMIN.","triggerScenarios":"Committing a masqueraded route while the work table was flushed externally (ENOENT on netbird-rt-fwd / mangle chains); queueing a rule whose expressions fail kernel validation (EINVAL); running the agent without CAP_NET_ADMIN at rule-commit time.","commonSituations":"Route updates racing firewalld reloads; first route after agent start where table init partially failed (createContainers error was swallowed earlier); capability drops in hardened systemd units.","solutions":["Inspect the errno: EPERM -> fix capabilities; ENOENT -> recreate the work table via manager init, then retry; EINVAL -> capture the rule expressions in a bug report.","Retry AddNatRule after refreshRulesMap — rollbackRules already restored the map so the retry rebuilds cleanly.","Ensure table/chain creation (createContainers) succeeded before route rules are attempted.","Keep external nftables mutation away from the netbird table during updates."],"exampleFix":"// before\nif err := r.conn.Flush(); err != nil {\n    r.rollbackRules(pair)\n    return fmt.Errorf(\"insert rules for %s: %w\", pair.Destination, err)\n}\n\n// after\nif err := r.conn.Flush(); err != nil {\n    r.rollbackRules(pair)\n    if isErrno(err, unix.ENOENT) {\n        // chains were externally flushed; recreate containers and retry once\n        if cerr := r.createContainers(); cerr == nil {\n            return r.AddNatRule(pair)\n        }\n    }\n    return fmt.Errorf(\"insert rules for %s: %w\", pair.Destination, err)\n}","handlingStrategy":"fallback","validationCode":"// Pre-flight: the target chains must exist before committing rules\nfor _, name := range []string{chainNameManglePrerouting, chainNameRoutingFw} {\n    if r.chains[name] == nil {\n        return fmt.Errorf(\"chain %s missing; recreate containers first\", name)\n    }\n}","typeGuard":"func isChainMissingErr(err error) bool {\n\treturn isErrno(err, unix.ENOENT)\n}","tryCatchPattern":"if err := r.conn.Flush(); err != nil {\n    r.rollbackRules(pair)\n    if isChainMissingErr(err) {\n        if cerr := r.createContainers(); cerr == nil {\n            return r.AddNatRule(pair) // rebuilt chains, retry once\n        }\n    }\n    return fmt.Errorf(\"insert rules for %s: %w\", pair.Destination, err)\n}","preventionTips":["rollbackRules already restores local state on flush failure — keep calling it before any retry so maps stay consistent.","Verify chain/table existence after any external firewall event (firewalld reload) before programming rules.","Run the agent with a systemd unit that holds CAP_NET_ADMIN permanently; mid-flight drops surface as EPERM here.","Consider nftables batches atomic: a failed flush usually leaves kernel state untouched, so a clean retry is safe."],"tags":["go","nftables","netlink","nat","rollback","firewall","routing"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}