{"record":{"id":"8fa9fb074e7d8775","repo":"netbirdio/netbird","slug":"flush-delete-output-dnat-rule-w","errorCode":null,"errorMessage":"flush delete output DNAT rule: %w","messagePattern":"flush delete output DNAT rule: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/firewall/nftables/router_linux.go","lineNumber":2105,"sourceCode":"\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 {\n\t\t\treturn nil, fmt.Errorf(\"source: %w\", err)\n\t\t}\n\t\treturn exprs, nil","sourceCodeStart":2087,"sourceCodeEnd":2123,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/firewall/nftables/router_linux.go#L2087-L2123","documentation":"Returned by RemoveOutputDNAT (client/firewall/nftables/router_linux.go:2105) when the conn.Flush() following DelRule fails. DelRule only buffers the delete netlink message; Flush commits it, so this is the kernel's asynchronous verdict on the deletion: EPERM, ENOENT (rule handle already gone), or a netlink send failure (buffer full, socket closed).","triggerScenarios":"The same rule deleted externally between DelRule and Flush; netlink socket invalidated (network namespace teardown, daemon fork); too many buffered operations in one batch exceeding netlink buffer limits; missing capabilities.","commonSituations":"Container teardown where the network namespace is being destroyed concurrently; large batch removals of DNAT rules; agent shutdown racing with system nftables cleanup scripts.","solutions":["Retry the removal after a fresh refreshRulesMap when the kernel reports ENOENT - the rule is gone and the map just needs resyncing","Recreate the nftables manager if the socket is dead, then retry; the re-created table contains no stale DNAT rule so the removal goal is already met","Keep delete batches small or flush incrementally to avoid ENOBUFS on netlink","Verify the process retains CAP_NET_ADMIN for the whole daemon lifetime, not just at start"],"exampleFix":"// before\nif err := r.conn.Flush(); err != nil {\n    return fmt.Errorf(\"flush delete output DNAT rule: %w\", err)\n}\n// after\nif err := r.conn.Flush(); err != nil {\n    if isNotExistErr(err) {\n        delete(r.rules, ruleID)\n        return nil\n    }\n    return fmt.Errorf(\"flush delete output DNAT rule: %w\", err)\n}\ndelete(r.rules, ruleID)","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := router.RemoveOutputDNAT(...); err != nil {\n    switch {\n    case isNotExistErr(rootCause(err)):\n        return nil\n    case isPermissionErr(rootCause(err)):\n        return fmt.Errorf(\"need CAP_NET_ADMIN to remove DNAT: %w\", err)\n    default:\n        // socket may be dead; a recreated manager has no stale rule to remove\n        firewall.Reset()\n        return nil\n    }\n}","preventionTips":["Keep netlink batches small during mass teardown","Never assume DelRule success until the following Flush returns nil","On daemon shutdown, remove DNAT rules before dropping capabilities"],"tags":["nftables","netlink","cleanup","linux"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}