netbirdio/netbird · error
flush delete inbound DNAT rule: %w
Error message
flush delete inbound DNAT rule: %w
What it means
RemoveInboundDNAT commits the queued rule deletion with conn.Flush(); 'flush delete inbound DNAT rule' wraps that commit failure. The map entry is deleted only after a successful flush, so on failure the entry survives and a retry re-attempts the whole removal.
Source
Thrown at client/firewall/nftables/router_linux.go:1977
ruleID := fmt.Sprintf("inbound-dnat-%s-%s-%d-%d", localAddr.String(), protocol, originalPort, translatedPort)
rule, exists := r.rules[ruleID]
if !exists {
return nil
}
if rule.Handle == 0 {
log.Warnf("inbound DNAT rule %s has no handle, removing stale entry", ruleID)
delete(r.rules, ruleID)
return nil
}
if err := r.conn.DelRule(rule); err != nil {
return fmt.Errorf("delete inbound DNAT rule %s: %w", ruleID, err)
}
if err := r.conn.Flush(); err != nil {
return fmt.Errorf("flush delete inbound DNAT rule: %w", err)
}
delete(r.rules, ruleID)
return nil
}
// ensureNATOutputChain lazily creates the OUTPUT NAT chain on first use.
func (r *router) ensureNATOutputChain() error {
if _, exists := r.chains[chainNameNATOutput]; exists {
return nil
}
r.chains[chainNameNATOutput] = r.conn.AddChain(&nftables.Chain{
Name: chainNameNATOutput,
Table: r.workTable,
Hooknum: nftables.ChainHookOutput,
Priority: nftables.ChainPriorityNATDest,
Type: nftables.ChainTypeNAT,View on GitHub (pinned to 93e97f4bf1)
Solutions
- Read the wrapped errno to separate privilege issues from state skew.
- Retry RemoveInboundDNAT; the surviving map entry makes it idempotent.
- Confirm with 'sudo nft list chain <table> netbird-rt-redirect' whether the rule is actually gone despite the error.
- Check for seccomp or LSM policies blocking netlink sendmsg.
Example fix
// before
if err := r.conn.Flush(); err != nil {
return fmt.Errorf("flush delete inbound DNAT rule: %w", err)
}
// after: downgrade ENOENT (rule already gone) and keep the entry only for real failures
if err := r.conn.Flush(); err != nil {
if errors.Is(err, unix.ENOENT) {
log.Warnf("inbound DNAT delete flush: rule already absent")
} else {
return fmt.Errorf("flush delete inbound DNAT rule: %w", err)
}
} Defensive patterns
Strategy: retry
Try / catch
if err := r.conn.Flush(); err != nil {
if errors.Is(err, unix.ENOENT) {
log.Warnf("inbound DNAT delete flush: rule already absent")
delete(r.rules, ruleID)
return nil
}
return fmt.Errorf("flush delete inbound DNAT rule: %w", err)
} Prevention
- Keep the map delete after the flush so failed commits remain retryable, mirroring RemoveInboundDNAT.
- Verify with 'nft list chain' after failed delete flushes to distinguish real rules from stale handles.
- Ensure netlink sendmsg is permitted in container security profiles.
When it happens
Trigger: The kernel rejects the delete inside the batch (stale handle, ENOENT), netlink exchange failure at commit time, or missing CAP_NET_ADMIN.
Common situations: Ruleset churn between DelRule and commit; netlink pressure on hosts with many rules; unprivileged environments.
Related errors
- add inbound DNAT rule: %w
- flush rules: %w
- delete dnat rule: %w
- delete inbound DNAT rule %s: %w
- add output DNAT rule: %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/77e91e10e45390bf.
Report an issue: GitHub.