netbirdio/netbird · error
add output DNAT rule: %w
Error message
add output DNAT rule: %w
What it means
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).
Source
Thrown at client/firewall/nftables/router_linux.go:2074
},
&expr.NAT{
Type: expr.NATTypeDestNAT,
Family: uint32(r.af.tableFamily),
RegAddrMin: 1,
RegProtoMin: 2,
},
)
dnatRule := &nftables.Rule{
Table: r.workTable,
Chain: r.chains[chainNameNATOutput],
Exprs: exprs,
UserData: []byte(ruleID),
}
r.conn.AddRule(dnatRule)
if err := r.conn.Flush(); err != nil {
return fmt.Errorf("add output DNAT rule: %w", err)
}
r.rules[ruleID] = dnatRule
return nil
}
// RemoveOutputDNAT removes an OUTPUT chain DNAT rule.
func (r *router) RemoveOutputDNAT(localAddr netip.Addr, protocol firewall.Protocol, originalPort, translatedPort uint16) error {
if err := r.refreshRulesMap(); err != nil {
return fmt.Errorf(refreshRulesMapError, err)
}
ruleID := fmt.Sprintf("output-dnat-%s-%s-%d-%d", localAddr.String(), protocol, originalPort, translatedPort)
rule, exists := r.rules[ruleID]
if !exists {
return nilView on GitHub (pinned to 93e97f4bf1)
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
Example fix
// before
if err := r.conn.Flush(); err != nil {
return fmt.Errorf("add output DNAT rule: %w", err)
}
// after - recreate dependent objects once when the kernel reports them missing
if err := r.conn.Flush(); err != nil {
if isNotExistErr(err) {
if rerr := r.ensureNATOutputChain(); rerr == nil {
r.conn.AddRule(dnatRule)
err = r.conn.Flush()
}
}
if err != nil {
return fmt.Errorf("add output DNAT rule: %w", err)
}
} Defensive patterns
Strategy: retry
Validate before calling
// before enabling DNAT, confirm nftables is writable as this user
cmd := exec.Command("nft", "list", "ruleset")
if err := cmd.Run(); err != nil {
return fmt.Errorf("nftables unavailable: %w", err)
} Try / catch
err := router.AddOutputDNAT(addr, proto, o, t)
if err != nil {
if isNetlinkNotExist(err) || isNetlinkBusy(err) {
firewall.Reset() // recreates table/chains
err = router.AddOutputDNAT(addr, proto, o, t)
}
if err != nil {
log.Errorf("output DNAT unavailable, local port forwarding disabled: %v", err)
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- delete output DNAT rule %s: %w
- create ipset: %w
- create ipset %s: %w
- add IP to ipset %s: %w
- decrement set counter: %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/24d282d989513d87.
Report an issue: GitHub.