{"record":{"id":"bd04464a96ddf5e6","repo":"netbirdio/netbird","slug":"delete-output-dnat-rule-s-w","errorCode":null,"errorMessage":"delete output DNAT rule %s: %w","messagePattern":"delete output DNAT rule (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/firewall/nftables/router_linux.go","lineNumber":2102,"sourceCode":"\tif err := r.refreshRulesMap(); err != nil {\n\t\treturn fmt.Errorf(refreshRulesMapError, err)\n\t}\n\n\truleID := fmt.Sprintf(\"output-dnat-%s-%s-%d-%d\", localAddr.String(), protocol, originalPort, translatedPort)\n\n\trule, exists := r.rules[ruleID]\n\tif !exists {\n\t\treturn nil\n\t}\n\n\tif rule.Handle == 0 {\n\t\tlog.Warnf(\"output DNAT rule %s has no handle, removing stale entry\", ruleID)\n\t\tdelete(r.rules, ruleID)\n\t\treturn nil\n\t}\n\n\tif err := r.conn.DelRule(rule); err != nil {\n\t\treturn fmt.Errorf(\"delete output DNAT rule %s: %w\", ruleID, err)\n\t}\n\tif err := r.conn.Flush(); err != nil {\n\t\treturn fmt.Errorf(\"flush delete output DNAT rule: %w\", err)\n\t}\n\tdelete(r.rules, ruleID)\n\n\treturn nil\n}\n\n// applyNetwork generates nftables expressions for networks (CIDR) or sets\nfunc (r *router) applyNetwork(\n\tnetwork firewall.Network,\n\tsetPrefixes []netip.Prefix,\n\tisSource bool,\n) ([]expr.Any, error) {\n\tif network.IsSet() {\n\t\texprs, err := r.getIpSet(network.Set, setPrefixes, isSource)\n\t\tif err != nil {","sourceCodeStart":2084,"sourceCodeEnd":2120,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/firewall/nftables/router_linux.go#L2084-L2120","documentation":"Raised in RemoveOutputDNAT (client/firewall/nftables/router_linux.go:2102) when r.conn.DelRule(rule) rejects the delete request. By this point the rule was found in r.rules with a non-zero Handle, so the usual causes are ENOENT (the rule was deleted in the kernel between refreshRulesMap and this call) and EPERM (no CAP_NET_ADMIN), plus EINVAL for a handle that is stale or belongs to a different table generation.","triggerScenarios":"Concurrent nft client deletes the same rule right after the refresh; two agent goroutines racing to remove the same output-dnat id; running unprivileged; netlink socket reused across table recreation so handles point at dead objects.","commonSituations":"Duplicate RemoveOutputDNAT calls for the same localAddr/protocol/port tuple (e.g. listener teardown racing reconnect); manual 'nft delete rule' by an admin; nftables library version changing handle semantics.","solutions":["Treat ENOENT from DelRule as success - the desired end state (rule gone) already exists","Serialize Add/RemoveOutputDNAT behind the router mutex so a second remover sees !exists after the first completes","Confirm the daemon holds CAP_NET_ADMIN (systemd AmbientCapabilities=CAP_NET_ADMIN or root)","If handles are stale (table recreated), call refreshRulesMap again or recreate the manager, then retry once"],"exampleFix":"// before\nif err := r.conn.DelRule(rule); err != nil {\n    return fmt.Errorf(\"delete output DNAT rule %s: %w\", ruleID, err)\n}\n// after - a rule that is already gone satisfies the removal\nif err := r.conn.DelRule(rule); err != nil {\n    if isNotExistErr(err) {\n        delete(r.rules, ruleID)\n        return nil\n    }\n    return fmt.Errorf(\"delete output DNAT rule %s: %w\", ruleID, err)\n}","handlingStrategy":"try-catch","validationCode":"// Only attempt delete when the id is currently tracked\nif !router.HasOutputDNAT(localAddr, protocol, originalPort) {\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := router.RemoveOutputDNAT(localAddr, proto, o, t); err != nil {\n    if isNotExistErr(rootCause(err)) {\n        return nil // already gone: goal state reached\n    }\n    return fmt.Errorf(\"teardown output DNAT: %w\", err)\n}","preventionTips":["Serialize add/remove of the same DNAT tuple behind one lock","Treat ENOENT on delete as success in teardown paths","Re-sync via refreshRulesMap before retrying after external nftables changes"],"tags":["nftables","netlink","dnat","cleanup","linux"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}