netbirdio/netbird · error

add inverse nat rule: %w

Error message

add inverse nat rule: %w

What it means

Returned by AddNatRule (router_linux.go:699) when the second addNatRule call — for firewall.GetInversePair(pair), the rule that marks return traffic with PreroutingFwmarkMasqueradeReturn so postrouting masquerades replies exiting the overlay interface — fails. Causes are identical in shape to 691 (set creation for the inverted source/destination, or prerouting rule replacement). By this point the forward rule is already queued on the conn buffer, so failing here leaves an asymmetric state unless the caller clears it; the pair is only committed or rolled back together at the Flush on line 704.

Source

Thrown at client/firewall/nftables/router_linux.go:700

func (r *router) AddNatRule(pair firewall.RouterPair) error {
	if err := r.refreshRulesMap(); err != nil {
		return fmt.Errorf(refreshRulesMapError, err)
	}

	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 {
		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)
		}
	}

	if err := r.conn.Flush(); err != nil {
		r.rollbackRules(pair)
		return fmt.Errorf("insert rules for %s: %w", pair.Destination, err)
	}

	return nil
}

// rollbackRules cleans up unflushed rules and their set counters after a flush failure.
func (r *router) rollbackRules(pair firewall.RouterPair) {
	keys := []string{
		firewall.GenKey(firewall.ForwardingFormat, pair),
		firewall.GenKey(firewall.PreroutingFormat, pair),
		firewall.GenKey(firewall.PreroutingFormat, firewall.GetInversePair(pair)),
	}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Treat 691 and 692 identically: fix the wrapped apply/remove error, then re-invoke AddNatRule which replaces both rules atomically at its final Flush.
  2. Call refreshRulesMap before AddNatRule (it does this itself at line 683) so the inverse rule key maps to a live handle and removal succeeds.
  3. Ensure the connection buffer is clean after failure so the queued forward rule is not orphaned.
  4. Restart the agent if stale map entries keep reproducing the failure.

Example fix

// before
if err := r.addNatRule(firewall.GetInversePair(pair)); err != nil {
    return fmt.Errorf("add inverse nat rule: %w", err)
}

// after
if err := r.addNatRule(firewall.GetInversePair(pair)); err != nil {
    // forward rule is queued but not committed; roll it back so the pair stays symmetric
    r.rollbackRules(pair)
    return fmt.Errorf("add inverse nat rule: %w", err)
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate the inverse pair too — it shares the same networks, swapped
inv := firewall.GetInversePair(pair)
if err := validatePairNetworks(inv); err != nil {
    return fmt.Errorf("inverse pair invalid: %w", err)
}

Type guard

func isNatRuleRetryable(err error) bool {
	return isErrno(err, unix.EEXIST, unix.ENOENT, unix.EAGAIN)
}

Try / catch

if err := r.addNatRule(firewall.GetInversePair(pair)); err != nil {
    r.rollbackRules(pair) // forward rule is queued; drop it so the pair stays symmetric
    if isNatRuleRetryable(err) {
        return r.AddNatRule(pair)
    }
    return fmt.Errorf("add inverse nat rule: %w", err)
}

Prevention

When it happens

Trigger: Re-applying a masqueraded route where the inverse pair's rule key already exists with a stale handle (removeNatRule fails), or the inverse pair's set (same prefixes, swapped roles) failing EEXIST/EINVAL during creation.

Common situations: Idempotent re-application of NAT routes during network-map refreshes; peers that previously experienced a failed flush leaving handle-less prerouting entries; large bidirectional ranges.

Related errors


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