netbirdio/netbird · error

remove prerouting rule %s -> %s: %w

Error message

remove prerouting rule %s -> %s: %w

What it means

Returned by removeNatRule when nftables.Conn.DelRule fails to delete a cached prerouting NAT rule from the netbird routing chains. DelRule sends an NFT_MSG_DELRULE netlink request that executes immediately (unlike AddRule, which buffers until Flush), so this is a real kernel ACK error for the handle stored in r.rules. The message names the routing pair's source and destination networks.

Source

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

	ruleKey := firewall.GenKey(firewall.PreroutingFormat, pair)

	rule, exists := r.rules[ruleKey]
	if !exists {
		log.Debugf("prerouting rule %s not found", ruleKey)
		return nil
	}

	if rule.Handle == 0 {
		log.Warnf("prerouting rule %s has no handle, removing stale entry", ruleKey)
		if err := r.decrementSetCounter(rule); err != nil {
			log.Warnf("decrement set counter for stale rule %s: %v", ruleKey, err)
		}
		delete(r.rules, ruleKey)
		return nil
	}

	if err := r.conn.DelRule(rule); err != nil {
		return fmt.Errorf("remove prerouting rule %s -> %s: %w", pair.Source, pair.Destination, err)
	}

	log.Debugf("removed prerouting rule %s -> %s", pair.Source, pair.Destination)

	delete(r.rules, ruleKey)

	if err := r.decrementSetCounter(rule); err != nil {
		return fmt.Errorf("decrement set counter: %w", err)
	}

	return nil
}

// refreshRulesMap rebuilds the rule map from the kernel. This removes stale entries
// (e.g. from failed flushes) and updates handles for all existing rules.
func (r *router) refreshRulesMap() error {
	var merr *multierror.Error
	newRules := make(map[string]*nftables.Rule)

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Check 'sudo nft list ruleset' for the netbird prerouting rule: if it is already gone, the error is benign state skew.
  2. Treat ENOENT as success (errors.Is(err, unix.ENOENT)) since a missing rule is the desired end state.
  3. Ensure only one NetBird agent instance manages nftables on the host and stop external tooling from flushing its tables.
  4. Verify the daemon runs as root or with CAP_NET_ADMIN.
  5. Restart the agent to rebuild the rules map from the kernel if every removal fails.

Example fix

// before
if err := r.conn.DelRule(rule); err != nil {
    return fmt.Errorf("remove prerouting rule %s -> %s: %w", pair.Source, pair.Destination, err)
}

// after: a rule that is already gone satisfies the removal intent
if err := r.conn.DelRule(rule); err != nil {
    if errors.Is(err, unix.ENOENT) {
        log.Warnf("prerouting rule %s -> %s already absent", pair.Source, pair.Destination)
        delete(r.rules, ruleKey)
        return nil
    }
    return fmt.Errorf("remove prerouting rule %s -> %s: %w", pair.Source, pair.Destination, err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Before removing, confirm the rule still exists under its UserData key
func preroutingRuleExists(conn *nftables.Conn, table *nftables.Table, chains map[string]*nftables.Chain, key string) (bool, error) {
    for _, chain := range chains {
        rules, err := conn.GetRules(table, chain)
        if err != nil {
            return false, err
        }
        for _, rl := range rules {
            if string(rl.UserData) == key {
                return true, nil
            }
        }
    }
    return false, nil
}

Try / catch

if err := removeNat(pair); err != nil {
    if errors.Is(err, unix.ENOENT) {
        // rule already gone from the kernel: desired state reached
        return nil
    }
    return fmt.Errorf("remove prerouting rule %s -> %s: %w", pair.Source, pair.Destination, err)
}

Prevention

When it happens

Trigger: RemoveNatRule on a masquerading pair (or its inverse pair) where the cached rule handle no longer matches kernel state: the rule was already removed by an external tool, the netlink socket errored, or the process lacks CAP_NET_ADMIN.

Common situations: firewalld reload, 'nft flush ruleset', iptables-nft or a second agent instance wiped the netbird table between refreshRulesMap and DelRule; agent containerized without NET_ADMIN; host hardening scripts rewriting nftables under the agent.

Related errors


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