{"record":{"id":"ee0f47d9a1bc0fd7","repo":"netbirdio/netbird","slug":"delete-snat-rule-w-ee0f47","errorCode":null,"errorMessage":"delete snat rule: %w","messagePattern":"delete snat rule: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/firewall/nftables/router_linux.go","lineNumber":1827,"sourceCode":"\tvar needsFlush bool\n\n\tif dnatRule, exists := r.rules[ruleKey+dnatSuffix]; exists {\n\t\tif dnatRule.Handle == 0 {\n\t\t\tlog.Warnf(\"dnat rule %s has no handle, removing stale entry\", ruleKey+dnatSuffix)\n\t\t\tdelete(r.rules, ruleKey+dnatSuffix)\n\t\t} else if err := r.conn.DelRule(dnatRule); err != nil {\n\t\t\tmerr = multierror.Append(merr, fmt.Errorf(\"delete dnat rule: %w\", err))\n\t\t} else {\n\t\t\tneedsFlush = true\n\t\t}\n\t}\n\n\tif masqRule, exists := r.rules[ruleKey+snatSuffix]; exists {\n\t\tif masqRule.Handle == 0 {\n\t\t\tlog.Warnf(\"snat rule %s has no handle, removing stale entry\", ruleKey+snatSuffix)\n\t\t\tdelete(r.rules, ruleKey+snatSuffix)\n\t\t} else if err := r.conn.DelRule(masqRule); err != nil {\n\t\t\tmerr = multierror.Append(merr, fmt.Errorf(\"delete snat rule: %w\", err))\n\t\t} else {\n\t\t\tneedsFlush = true\n\t\t}\n\t}\n\n\tif needsFlush {\n\t\tif err := r.conn.Flush(); err != nil {\n\t\t\tmerr = multierror.Append(merr, fmt.Errorf(flushError, err))\n\t\t}\n\t}\n\n\t// Release the refcount only once the rules are gone from the kernel. On\n\t// failure (including the refreshRulesMap error above) the rules and their\n\t// map entries remain, keeping forwarding on until a retry removes them.\n\tif merr == nil {\n\t\tdelete(r.rules, ruleKey+dnatSuffix)\n\t\tdelete(r.rules, ruleKey+snatSuffix)\n","sourceCodeStart":1809,"sourceCodeEnd":1845,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/firewall/nftables/router_linux.go#L1809-L1845","documentation":"DeleteDNATRule issues an immediate NFT_MSG_DELRULE for the cached _snat rule (the masquerade rule in netbird-rt-postrouting). This error wraps the kernel's negative ACK, is appended to the same multierror as the dnat deletion and flush, and on failure the map entry and forwarding reference are intentionally kept so a retry completes the teardown.","triggerScenarios":"Stale handle (the masquerade rule was removed externally, ENOENT), netlink socket failure, or missing CAP_NET_ADMIN.","commonSituations":"External nftables rewrites racing the deletion; the dnat rule deletion succeeded but the snat half failed halfway through teardown; unprivileged containers.","solutions":["Check 'sudo nft list chain <table> netbird-rt-postrouting' for the masquerade rule; if absent, ENOENT is benign.","Retry DeleteDNATRule so refreshRulesMap picks up current handles for both halves.","Verify root/CAP_NET_ADMIN.","After a successful retry, confirm forwarding was released (the refcount drop happens only when merr is nil)."],"exampleFix":"// before\n} else if err := r.conn.DelRule(masqRule); err != nil {\n    merr = multierror.Append(merr, fmt.Errorf(\"delete snat rule: %w\", err))\n}\n\n// after: tolerate an externally-removed masquerade rule\n} else if err := r.conn.DelRule(masqRule); err != nil {\n    if errors.Is(err, unix.ENOENT) {\n        log.Warnf(\"snat rule %s already absent\", ruleKey+snatSuffix)\n    } else {\n        merr = multierror.Append(merr, fmt.Errorf(\"delete snat rule: %w\", err))\n    }\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"} else if err := r.conn.DelRule(masqRule); err != nil {\n    if errors.Is(err, unix.ENOENT) {\n        log.Warnf(\"snat rule %s already absent\", ruleKey+snatSuffix)\n    } else {\n        merr = multierror.Append(merr, fmt.Errorf(\"delete snat rule: %w\", err))\n    }\n}","preventionTips":["Delete dnat and snat halves through the same DeleteDNATRule call so refreshes stay consistent.","After teardown, verify forwarding was released; a kept reference means one half failed.","Keep external tools away from netbird-rt-postrouting while routes are configured."],"tags":["nftables","netlink","snat","masquerade"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}