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

  1. Retry the operation (RemoveNatRule then AddNatRule) to clear the half-installed pair
  2. Verify modules and chain as for the forward rule: iptables -t mangle -S NETBIRD-RT-PRE
  3. Check for concurrent iptables users holding the lock
  4. 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

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


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/ea29a8d1ed509c9b. Report an issue: GitHub.