{"record":{"id":"6f8a9b49da0b833a","repo":"netbirdio/netbird","slug":"delete-dnat-rule-w-6f8a9b","errorCode":null,"errorMessage":"delete dnat rule: %w","messagePattern":"delete dnat rule: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/firewall/nftables/router_linux.go","lineNumber":1816,"sourceCode":"\tif err := r.refreshRulesMap(); err != nil {\n\t\treturn fmt.Errorf(refreshRulesMapError, err)\n\t}\n\n\t_, hadDNAT := r.rules[ruleKey+dnatSuffix]\n\t_, hadSNAT := r.rules[ruleKey+snatSuffix]\n\tif !hadDNAT && !hadSNAT {\n\t\treturn nil\n\t}\n\n\tvar merr *multierror.Error\n\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 {","sourceCodeStart":1798,"sourceCodeEnd":1834,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/firewall/nftables/router_linux.go#L1798-L1834","documentation":"DeleteDNATRule issues an immediate NFT_MSG_DELRULE for the cached _dnat rule (the redirect in netbird-rt-redirect). This error wraps the kernel's negative ACK and is accumulated into a multierror together with the snat deletion and flush; on failure the map entries survive and the forwarding reference is kept for a later retry.","triggerScenarios":"The cached handle is stale because the rule was already removed externally (ENOENT is the common case), the netlink socket fails, or the process lacks CAP_NET_ADMIN.","commonSituations":"External ruleset flushes between refreshRulesMap and DelRule; concurrent deletions of the same forward rule; containerized agents without NET_ADMIN.","solutions":["Confirm with 'sudo nft list chain <table> netbird-rt-redirect' whether the rule is already gone; ENOENT then means success.","Retry DeleteDNATRule: refreshRulesMap re-reads handles and the second attempt deletes what remains.","Ensure the agent has root or CAP_NET_ADMIN.","Stop external tooling from rewriting the netbird tables while routes exist."],"exampleFix":"// before\n} else if err := r.conn.DelRule(dnatRule); err != nil {\n    merr = multierror.Append(merr, fmt.Errorf(\"delete dnat rule: %w\", err))\n}\n\n// after: a vanished rule satisfies the deletion intent\n} else if err := r.conn.DelRule(dnatRule); err != nil {\n    if errors.Is(err, unix.ENOENT) {\n        log.Warnf(\"dnat rule %s already absent\", ruleKey+dnatSuffix)\n    } else {\n        merr = multierror.Append(merr, fmt.Errorf(\"delete dnat rule: %w\", err))\n    }\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"} else if err := r.conn.DelRule(dnatRule); err != nil {\n    if errors.Is(err, unix.ENOENT) {\n        log.Warnf(\"dnat rule %s already absent\", ruleKey+dnatSuffix)\n    } else {\n        merr = multierror.Append(merr, fmt.Errorf(\"delete dnat rule: %w\", err))\n    }\n}","preventionTips":["Rely on DeleteDNATRule's built-in refresh rather than caching handles across long periods.","Prevent concurrent deletions of the same forward rule from orchestrators.","Treat ENOENT on delete paths as success when reconcilers converge on 'rule absent'."],"tags":["nftables","netlink","dnat","firewall"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}