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

  1. Gate the call on the interface mode: only program in-process filters for userspace/netstack interfaces
  2. Use the firewall manager for kernel-mode filtering
  3. Check for the nil filtered device before calling (the same condition the error tests)
  4. 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

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


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