netbirdio/netbird · error
invalid rule type
Error message
invalid rule type
What it means
DeletePeerRule performs a checked type assertion of the firewall.Rule interface to the iptables package's concrete *Rule. Passing anything else, a rule from the nftables manager, a mock, or a typed-nil *Rule wrapped in the interface, fails the assertion and returns this error. It is a programming/API-contract error, not an environment problem: rules are only meaningful to the manager implementation that created them.
Source
Thrown at client/firewall/iptables/acl_linux.go:193
ruleID: uuid.New().String(),
specs: specs,
mangleSpecs: mangleSpecs,
ipsetName: ipsetName,
ip: ip.String(),
chain: chain,
v6: m.v6,
}
m.updateState()
return []firewall.Rule{rule}, nil
}
// DeletePeerRule from the firewall by rule definition
func (m *aclManager) DeletePeerRule(rule firewall.Rule) error {
r, ok := rule.(*Rule)
if !ok {
return fmt.Errorf("invalid rule type")
}
shouldDestroyIpset := false
if ipsetList, ok := m.ipsetStore.ipset(r.ipsetName); ok {
// delete IP from ruleset IPs list and ipset
if _, ok := ipsetList.ips[r.ip]; ok {
ip := net.ParseIP(r.ip)
if ip == nil {
return fmt.Errorf("parse IP %s", r.ip)
}
if err := m.delFromIPSet(r.ipsetName, ip); err != nil {
return fmt.Errorf("delete ip from ipset: %w", err)
}
delete(ipsetList.ips, r.ip)
}
// if after delete, set still contains other IPs,
// no need to delete firewall rule and we should exit hereView on GitHub (pinned to 93e97f4bf1)
Solutions
- Only pass rules returned by the same iptables Manager instance's AddPeerFiltering/AddRouteFiltering.
- Keep rules stored as their concrete type or re-fetch them from the manager instead of crossing implementations.
- In tests, construct rules via the real manager or export a constructor for fixtures.
- Add a type switch/guard at the call site to fail fast with context.
Example fix
// before
err := mgr.DeletePeerRule(rule) // rule came from another backend
// after
itr, ok := rule.(*iptables.Rule)
if !ok {
return fmt.Errorf("rule %T not created by the iptables manager", rule)
}
err := mgr.DeletePeerRule(itr) Defensive patterns
Strategy: type-guard
Validate before calling
if _, ok := rule.(*iptables.Rule); !ok {
return fmt.Errorf("refusing to delete rule of type %T through the iptables manager", rule)
} Type guard
func isIPTablesRule(r firewall.Rule) (*iptables.Rule, bool) {
ir, ok := r.(*iptables.Rule)
return ir, ok
} Try / catch
if err := mgr.DeletePeerRule(rule); err != nil {
if strings.Contains(err.Error(), "invalid rule type") {
// programmer error: rule came from another backend; drop it from tracking
log.Errorf("dropping foreign rule %T", rule)
}
} Prevention
- Store rules next to the manager that produced them; never move firewall.Rule values across implementations.
- Return rules to the exact same manager instance that created them, especially across fallback/swap logic.
- In tests, generate rules via the real manager instead of hand-building struct literals.
- Prefer deleting by rule ID/handle at the engine layer rather than passing opaque Rule objects around.
When it happens
Trigger: Forwarding a firewall.Rule obtained from a different backend (nftables/pf/WFP) into the iptables manager's DeletePeerRule; unit tests passing mocks or plain structs; storing rules as firewall.Rule across a manager recreation and deleting them from the new instance; deleting a nil *Rule via the interface.
Common situations: Firewall-manager fallback code that swaps implementations at runtime but reuses cached rules; test code exercising DeletePeerRule with hand-built rule values.
Related errors
- add IP to ipset: %w
- create ipset: %w
- failed to check rule: %w
- rule already exists
- delete ip from ipset: %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/e69bc8de29d66743.
Report an issue: GitHub.