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
- Make DisableRouting idempotent: treat 'rule not found' from deleteRouteRule as success for that rule
- If the error persists, call manager Reset to rebuild uspfilter state cleanly instead of retrying DisableRouting on stale rules
- Keep Enable/DisableRouting strictly serialized under the manager mutex and never clear blockRules on a path that did not attempt deletion
- 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
- Make teardown idempotent: missing rules during deletion are success
- Never call DisableRouting concurrently with Reset
- If stale rules persist, manager Reset rebuilds uspfilter state wholesale instead of retrying deletes
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.