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

  1. Only construct the uspfilter manager when the interface is a userspace/netstack device; select nftables/iptables or WFP backends in kernel mode
  2. Check wgIface.GetWGDevice() / FilteredDevice() for nil before creating the manager and fall back to the platform's kernel firewall
  3. Initialize the FilteredDevice before the firewall manager in the engine bring-up sequence
  4. 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

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


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