{"record":{"id":"24d282d989513d87","repo":"netbirdio/netbird","slug":"add-output-dnat-rule-w-24d282","errorCode":null,"errorMessage":"add output DNAT rule: %w","messagePattern":"add output DNAT rule: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/firewall/nftables/router_linux.go","lineNumber":2074,"sourceCode":"\t\t},\n\t\t&expr.NAT{\n\t\t\tType:        expr.NATTypeDestNAT,\n\t\t\tFamily:      uint32(r.af.tableFamily),\n\t\t\tRegAddrMin:  1,\n\t\t\tRegProtoMin: 2,\n\t\t},\n\t)\n\n\tdnatRule := &nftables.Rule{\n\t\tTable:    r.workTable,\n\t\tChain:    r.chains[chainNameNATOutput],\n\t\tExprs:    exprs,\n\t\tUserData: []byte(ruleID),\n\t}\n\tr.conn.AddRule(dnatRule)\n\n\tif err := r.conn.Flush(); err != nil {\n\t\treturn fmt.Errorf(\"add output DNAT rule: %w\", err)\n\t}\n\n\tr.rules[ruleID] = dnatRule\n\n\treturn nil\n}\n\n// RemoveOutputDNAT removes an OUTPUT chain DNAT rule.\nfunc (r *router) RemoveOutputDNAT(localAddr netip.Addr, protocol firewall.Protocol, originalPort, translatedPort uint16) error {\n\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","sourceCodeStart":2056,"sourceCodeEnd":2092,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/firewall/nftables/router_linux.go#L2056-L2092","documentation":"Returned by router.AddOutputDNAT (client/firewall/nftables/router_linux.go:2074) when r.conn.Flush() fails after AddRule queued the OUTPUT-chain DNAT rule. Flush submits all buffered netlink messages to the kernel at once, so the error is the kernel's verdict on the rule: EPERM without CAP_NET_ADMIN, ENOENT when the target table/chain vanished between creation and flush, EINVAL for malformed expressions, or ENODEV/EOPNOTSUPP when the nftables kernel module is unavailable (old kernels, restricted containers).","triggerScenarios":"Running the agent unprivileged or in a container without NET_ADMIN; another nftables client (firewalld, docker, a manual 'nft flush ruleset') deleting the netbird table between ensureNATOutputChain and Flush; an nftables/expr version mismatch producing an expression the kernel rejects.","commonSituations":"Agent started without root or in a Docker/LXC environment lacking nftables support; parallel firewall tooling flushing the ruleset; kernel built without CONFIG_NF_TABLES; upgrading the nftables Go library to a version emitting syntax the running kernel rejects.","solutions":["Run the agent as root (or with CAP_NET_ADMIN) in an environment that exposes nftables; verify with 'nft list ruleset' as the same user","Check for concurrent nftables managers (firewalld, docker) flushing the table, and prefer a dedicated netbird chain/table policy","Recreate the nftables manager (firewall factory Reset/Create) and retry AddOutputDNAT once, since a vanished table is recreated by ensureNATOutputChain","Inspect the exact errno via strace on the netlink sendmsg to distinguish permission (EPERM) from missing-objects (ENOENT) errors"],"exampleFix":"// before\nif err := r.conn.Flush(); err != nil {\n    return fmt.Errorf(\"add output DNAT rule: %w\", err)\n}\n// after - recreate dependent objects once when the kernel reports them missing\nif err := r.conn.Flush(); err != nil {\n    if isNotExistErr(err) {\n        if rerr := r.ensureNATOutputChain(); rerr == nil {\n            r.conn.AddRule(dnatRule)\n            err = r.conn.Flush()\n        }\n    }\n    if err != nil {\n        return fmt.Errorf(\"add output DNAT rule: %w\", err)\n    }\n}","handlingStrategy":"retry","validationCode":"// before enabling DNAT, confirm nftables is writable as this user\ncmd := exec.Command(\"nft\", \"list\", \"ruleset\")\nif err := cmd.Run(); err != nil {\n    return fmt.Errorf(\"nftables unavailable: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"err := router.AddOutputDNAT(addr, proto, o, t)\nif err != nil {\n    if isNetlinkNotExist(err) || isNetlinkBusy(err) {\n        firewall.Reset() // recreates table/chains\n        err = router.AddOutputDNAT(addr, proto, o, t)\n    }\n    if err != nil {\n        log.Errorf(\"output DNAT unavailable, local port forwarding disabled: %v\", err)\n    }\n}","preventionTips":["Run the agent with CAP_NET_ADMIN for its whole lifetime","Avoid running external nftables flushes against the netbird table while the agent is up","Treat DNAT add failure as feature degradation (skip port forwarding), not agent-fatal"],"tags":["nftables","netlink","dnat","permissions","linux"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}