netbirdio/netbird · error
set filter: %w
Error message
set filter: %w
What it means
Returned by the uspfilter Manager constructor (client/firewall/uspfilter/filter.go:341) when iface.SetFilter(m) fails. WGIface.SetFilter (client/iface/iface.go:250) errors with 'userspace packet filtering not handled on this device' when tun.FilteredDevice() is nil - i.e. the interface is not a wireguard-go userspace device (kernel WireGuard mode, or a device wrapper without packet filtering). uspfilter filtering only works when packets traverse the in-process device, so the constructor aborts (after closing the fragment tracker).
Source
Thrown at client/firewall/uspfilter/filter.go:341
return nil, fmt.Errorf("update local IPs: %w", err)
}
m.fragments = newFragmentTracker(m.logger)
if disableConntrack {
log.Info("conntrack is disabled")
} else {
m.udpTracker = conntrack.NewUDPTracker(conntrack.DefaultUDPTimeout, m.logger, flowLogger)
m.icmpTracker = conntrack.NewICMPTracker(conntrack.DefaultICMPTimeout, m.logger, flowLogger)
m.tcpTracker = conntrack.NewTCPTracker(conntrack.DefaultTCPTimeout, m.logger, flowLogger)
}
if m.netstack && m.localForwarding {
if err := m.initForwarder(); err != nil {
log.Errorf("failed to initialize forwarder: %v", err)
}
}
if err := iface.SetFilter(m); err != nil {
m.fragments.Close()
return nil, fmt.Errorf("set filter: %w", err)
}
return m, nil
}
// blockInvalidRouted installs drop rules for traffic to the wg overlay that
// arrives via the routing path. v4 and v6 are independent: a v6 install
// failure leaves v4 protection in place (and vice versa) so the returned
// slice always contains whatever was successfully installed, even on error.
// Callers must persist the slice so DisableRouting can clean partial state.
func (m *Manager) blockInvalidRouted(iface common.IFaceMapper) ([]firewall.Rule, error) {
wgPrefix := iface.Address().Network
log.Debugf("blocking invalid routed traffic for %s", wgPrefix)
sources := []netip.Prefix{netip.PrefixFrom(netip.IPv4Unspecified(), 0)}
v6Net := iface.Address().IPv6Net
if v6Net.IsValid() {
sources = append(sources, netip.PrefixFrom(netip.IPv6Unspecified(), 0))
}View on GitHub (pinned to 93e97f4bf1)
Solutions
- Only construct the uspfilter manager when the interface is a userspace/netstack device; select nftables/iptables or WFP backends in kernel mode
- Check wgIface.GetWGDevice() / FilteredDevice() for nil before creating the manager and fall back to the platform's kernel firewall
- Initialize the FilteredDevice before the firewall manager in the engine bring-up sequence
- On error, prefer failing ACL setup loudly over silently running without filters
Example fix
// before
if err := iface.SetFilter(m); err != nil {
m.fragments.Close()
return nil, fmt.Errorf("set filter: %w", err)
}
// after - choose the backend by device capability before constructing
if iface.GetWGDevice() == nil {
return nil, fmt.Errorf("uspfilter requires a userspace device")
}
if err := iface.SetFilter(m); err != nil {
m.fragments.Close()
return nil, fmt.Errorf("set filter: %w", err)
} Defensive patterns
Strategy: type-guard
Validate before calling
if iface.GetWGDevice() == nil {
return fmt.Errorf("uspfilter requires a userspace device; select the kernel firewall backend instead")
}
fw, err := uspfilter.Create(iface, ...) Type guard
func supportsUserspaceFilter(wg common.IFaceMapper) bool {
return wg.GetWGDevice() != nil && wg.FilteredDevice() != nil
} Try / catch
fw, err := uspfilter.Create(iface, ...)
if err != nil {
if strings.Contains(err.Error(), "set filter") {
fw, err = nftables.Create(iface, ...) // kernel backend fallback
if err != nil {
return err
}
} else {
return err
}
} Prevention
- Select the firewall backend from the interface mode (kernel vs userspace) at engine init
- Do not force uspfilter via flags on kernel WireGuard setups
- Ensure FilteredDevice is wired before ACL initialization in new tun implementations
When it happens
Trigger: Building the uspfilter firewall manager while the agent runs in kernel WireGuard mode on Linux (wintun/kernel interfaces expose no FilteredDevice); netstack-less configurations; a tun implementation that does not wire FilteredDevice before SetFilter is called.
Common situations: Forcing uspfilter via flags/env on a kernel-mode setup; platform selection code choosing the wrong firewall backend (uspfilter instead of nftables/iptables) in kernel mode; embedded builds where the device wrapper lacks the filter hook.
Related errors
- add IP to ipset: %w
- failed to check rule: %w
- rule already exists
- failed to delete rule: %s, %v: %w
- add peer filtering for %s: %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/f6baab3a2ee5413e.
Report an issue: GitHub.