netbirdio/netbird · warning
userspace packet filtering not handled on this device
Error message
userspace packet filtering not handled on this device
What it means
SetFilter() programs packet filtering inside the userspace (wireguard-go) data path, which only exists when the interface uses a userspace/netstack TUN whose FilteredDevice() is non-nil. Kernel-mode devices (TunKernelDevice) return nil from FilteredDevice(), so calling SetFilter there returns this error: kernel-mode filtering belongs to the OS firewall (nftables/iptables/pf/WFP), not to the in-process device.
Source
Thrown at client/iface/iface.go:255
if err := w.waitUntilRemoved(); err != nil {
log.Warnf("failed to remove WireGuard interface %s: %v", w.Name(), err)
if err := w.Destroy(); err != nil {
result = multierror.Append(result, fmt.Errorf("failed to remove WireGuard interface %s: %w", w.Name(), err))
return errors.FormatErrorOrNil(result)
}
log.Infof("interface %s successfully removed", w.Name())
}
return errors.FormatErrorOrNil(result)
}
// SetFilter sets packet filters for the userspace implementation
func (w *WGIface) SetFilter(filter device.PacketFilter) error {
w.mu.Lock()
defer w.mu.Unlock()
if w.tun.FilteredDevice() == nil {
return fmt.Errorf("userspace packet filtering not handled on this device")
}
w.filter = filter
w.tun.FilteredDevice().SetFilter(filter)
return nil
}
// GetFilter returns packet filter used by interface if it uses userspace device implementation
func (w *WGIface) GetFilter() device.PacketFilter {
w.mu.Lock()
defer w.mu.Unlock()
return w.filter
}
// GetDevice to interact with raw device (with filtering)
func (w *WGIface) GetDevice() *device.FilteredDevice {View on GitHub (pinned to 93e97f4bf1)
Solutions
- Gate the call on the interface mode: only program in-process filters for userspace/netstack interfaces
- Use the firewall manager for kernel-mode filtering
- Check for the nil filtered device before calling (the same condition the error tests)
- Switch the interface to userspace mode if in-process filtering is required on that platform
Example fix
// before
if err := wgi.SetFilter(filter); err != nil {
return err // blows up on kernel-mode interfaces
}
// after: kernel mode relies on the OS firewall
if !kernelMode {
if err := wgi.SetFilter(filter); err != nil {
return err
}
}
// kernel mode: apply equivalent rules via the firewall manager Defensive patterns
Strategy: type-guard
Type guard
// SetFilter only succeeds when a userspace filtered device exists;
// kernel-mode interfaces must filter via the OS firewall instead.
func supportsInProcessFilter(ifaceMode string) bool {
return ifaceMode == "userspace" || ifaceMode == "netstack"
} Try / catch
if err := wgi.SetFilter(f); err != nil {
if strings.Contains(err.Error(), "not handled on this device") {
// kernel mode: apply the same policy via the firewall manager
return firewallMgr.Apply(f)
}
return err
} Prevention
- Know the interface mode before calling userspace-only APIs
- Route kernel-mode filtering through the OS firewall backends
- Centralize platform capability checks instead of scattering them at call sites
When it happens
Trigger: Calling WGIface.SetFilter() on an interface created in kernel mode (Linux with kernel WireGuard), or from code paths that assume the userspace data path (packet-filter DNS hook, capture support) running against a kernel device.
Common situations: Linux agent with kernel WireGuard enabled; platform integrations reusing SetFilter unconditionally; tests with fake TUN devices whose FilteredDevice() returns nil.
Related errors
- allow netbird v4 interface traffic: %w
- allow netbird v6 interface traffic: %w
- output DNAT not supported without native firewall
- management client is not initialised
- profile not found
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/ba6a7b355bc6416b.
Report an issue: GitHub.