netbirdio/netbird · warning

delete block rule: %w

Error message

delete block rule: %w

What it means

Accumulated into a multierror by Manager.DisableRouting (client/firewall/uspfilter/filter.go:1793) when m.deleteRouteRule(rule) fails for one of the persisted blockRules (installed by blockInvalidRouted). These are in-memory uspfilter rules, so deletion failing indicates the rule was already removed (stale blockRules after a Reset), the manager's rule set was replaced, or the rule ID lookup misses. Remaining rules are still attempted, m.blockRules is set to nil regardless, and the forwarder is stopped (unless netstack+localForwarding).

Source

Thrown at client/firewall/uspfilter/filter.go:1793

	return nil
}

func (m *Manager) DisableRouting() error {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	fwder := m.forwarder.Load()
	if fwder == nil {
		return nil
	}

	m.routingEnabled.Store(false)
	m.nativeRouter.Store(false)

	var merr *multierror.Error
	for _, rule := range m.blockRules {
		if err := m.deleteRouteRule(rule); err != nil {
			merr = multierror.Append(merr, fmt.Errorf("delete block rule: %w", err))
		}
	}
	m.blockRules = nil

	if m.netstack && m.localForwarding {
		return nberrors.FormatErrorOrNil(merr)
	}

	fwder.Stop()
	m.forwarder.Store(nil)

	log.Debug("forwarder stopped")

	return nberrors.FormatErrorOrNil(merr)
}

// RegisterNetstackService registers a service as listening on the netstack for the given protocol and port
func (m *Manager) RegisterNetstackService(protocol nftypes.Protocol, port uint16) {

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Make DisableRouting idempotent: treat 'rule not found' from deleteRouteRule as success for that rule
  2. If the error persists, call manager Reset to rebuild uspfilter state cleanly instead of retrying DisableRouting on stale rules
  3. Keep Enable/DisableRouting strictly serialized under the manager mutex and never clear blockRules on a path that did not attempt deletion
  4. Inspect the multierror entries to see which rule IDs failed and re-run once after state settles

Example fix

// before
for _, rule := range m.blockRules {
    if err := m.deleteRouteRule(rule); err != nil {
        merr = multierror.Append(merr, fmt.Errorf("delete block rule: %w", err))
    }
}
// after - tolerate already-removed rules during teardown
for _, rule := range m.blockRules {
    if err := m.deleteRouteRule(rule); err != nil && !errors.Is(err, firewall.ErrRuleNotFound) {
        merr = multierror.Append(merr, fmt.Errorf("delete block rule %s: %w", rule.ID(), err))
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

if !fw.RoutingEnabled() {
    return nil // nothing was installed; skip teardown
}

Try / catch

if err := fw.DisableRouting(); err != nil {
    for _, e := range unwrapMulti(err) {
        if strings.Contains(e.Error(), "delete block rule") {
            log.Warnf("block rule possibly stale: %v", e)
            continue
        }
        return err
    }
    return nil
}

Prevention

When it happens

Trigger: DisableRouting called twice; EnableRouting failed earlier leaving partial blockRules that reference rules Reset already cleared; concurrent ACL rebuild swapping the rule set under DisableRouting.

Common situations: Agent shutdown after a failed EnableRouting; engine reconfiguration calling Disable then Enable rapidly; crash-recovery paths where blockRules were deserialized from stale state.

Related errors


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