netbirdio/netbird · error
add inverse nat rule: %w
Error message
add inverse nat rule: %w
What it means
Returned by router.AddNatRule when addNatRule(firewall.GetInversePair(pair)) fails. The inverse rule uses '! -i <wg-interface>' plus conntrack NEW and sets the return-traffic mark (PreroutingFwmarkMasqueradeReturn) in NETBIRD-RT-PRE so replies leaving the overlay get masqueraded. This is the second half of the rule pair; if it fails after the forward rule succeeded, the pair is left half-installed.
Source
Thrown at client/firewall/iptables/router_linux.go:283
// AddNatRule inserts an iptables rule pair into the nat chain
func (r *router) AddNatRule(pair firewall.RouterPair) error {
if r.legacyManagement {
log.Warnf("This peer is connected to a NetBird Management service with an older version. Allowing all traffic for %s", pair.Destination)
if err := r.addLegacyRouteRule(pair); err != nil {
return fmt.Errorf("add legacy routing rule: %w", err)
}
}
if !pair.Masquerade {
return nil
}
if err := r.addNatRule(pair); err != nil {
return fmt.Errorf("add nat rule: %w", err)
}
if err := r.addNatRule(firewall.GetInversePair(pair)); err != nil {
return fmt.Errorf("add inverse nat rule: %w", err)
}
r.updateState()
return nil
}
// RemoveNatRule removes an iptables rule pair from forwarding and nat chains
func (r *router) RemoveNatRule(pair firewall.RouterPair) error {
if pair.Masquerade {
if err := r.removeNatRule(pair); err != nil {
return fmt.Errorf("remove nat rule: %w", err)
}
if err := r.removeNatRule(firewall.GetInversePair(pair)); err != nil {
return fmt.Errorf("remove inverse nat rule: %w", err)
}
}View on GitHub (pinned to 93e97f4bf1)
Solutions
- Retry the operation (RemoveNatRule then AddNatRule) to clear the half-installed pair
- Verify modules and chain as for the forward rule: iptables -t mangle -S NETBIRD-RT-PRE
- Check for concurrent iptables users holding the lock
- If it persists, gather 'sudo iptables-save' output and the agent debug log for the inner error
Defensive patterns
Strategy: retry
Try / catch
if err := r.addNatRule(firewall.GetInversePair(pair)); err != nil {
// pair is half-installed: remove the forward leg before surfacing the error
_ = r.removeNatRule(pair)
return fmt.Errorf("add inverse nat rule: %w", err)
} Prevention
- Make add/remove paths idempotent so retries converge
- Log the inner 'error while adding marking rule' text to catch module errors
- Free the xtables lock before retry bursts
When it happens
Trigger: Same masquerade path as the forward rule, immediately after it succeeds. Fails on missing NETBIRD-RT-PRE chain, missing xt_conntrack/xt_mark/xt_set modules, or iptables backend errors. Note the code has a TODO: rollback of the forward rule/ipset counter does not happen on this branch.
Common situations: Transient xtables lock contention hitting the second Insert; iptables nft backend rejecting the inverted interface match after nftables ruleset changes underneath; module unload between the two inserts.
Related errors
- add nat rule: %w
- remove nat rule: %w
- remove inverse nat rule: %w
- add static nat rules: %w
- add outbound masquerade rule: %v
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/ea29a8d1ed509c9b.
Report an issue: GitHub.