netbirdio/netbird · error

set spoofing: %s

Error message

set spoofing: %s

What it means

Returned when the gVisor stack refuses SetSpoofing(nicID, true). Spoofing mode lets the NIC reply with arbitrary source addresses, which this forwarder needs because it answers on behalf of overlay peers. The stack errors when the NIC no longer exists or, on some gVisor versions, when the NIC was not created with capabilities allowing spoofing to be toggled after the fact.

Source

Thrown at client/firewall/uspfilter/forwarder/forwarder.go:132

		tcpip.MaskFromBytes([]byte{0, 0, 0, 0}),
	)
	if err != nil {
		return nil, fmt.Errorf("creating default subnet: %w", err)
	}

	defaultSubnetV6, err := tcpip.NewSubnet(
		tcpip.AddrFrom16([16]byte{}),
		tcpip.MaskFromBytes(make([]byte, 16)),
	)
	if err != nil {
		return nil, fmt.Errorf("creating default v6 subnet: %w", err)
	}

	if err := s.SetPromiscuousMode(nicID, true); err != nil {
		return nil, fmt.Errorf("set promiscuous mode: %s", err)
	}
	if err := s.SetSpoofing(nicID, true); err != nil {
		return nil, fmt.Errorf("set spoofing: %s", err)
	}

	s.SetRouteTable([]tcpip.Route{
		{Destination: defaultSubnet, NIC: nicID},
		{Destination: defaultSubnetV6, NIC: nicID},
	})

	ctx, cancel := context.WithCancel(context.Background())
	f := &Forwarder{
		logger:        logger,
		flowLogger:    flowLogger,
		stack:         s,
		endpoint:      endpoint,
		udpForwarder:  newUDPForwarder(mtu, logger, flowLogger),
		ctx:           ctx,
		cancel:        cancel,
		netstack:      netstack,
		ip:            tcpip.AddrFrom4(iface.Address().IP.As4()),

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Ensure the previous Forwarder's cancel() and goroutine shutdown complete before creating a new one (join on a done channel)
  2. Guard the constructor entry with the engine's lifecycle mutex so New and Stop cannot interleave
  3. Retry construction once after teardown completes if the error indicates an unknown NIC
  4. Inspect the wrapped %s error text to distinguish unknown-NIC races from genuine gVisor capability failures
Defensive patterns

Strategy: try-catch

Validate before calling

if iface == nil || iface.GetWGDevice() == nil {
    return errors.New("interface/device not ready")
}

Type guard

func canBuildForwarder(iface common.IFaceMapper) bool {
    return iface != nil && iface.GetWGDevice() != nil
}

Try / catch

f, err := forwarder.New(...)
if err != nil && strings.Contains(err.Error(), "set spoofing") {
    // NIC teardown race: wait for Stop to finish, then retry New once
}

Prevention

When it happens

Trigger: The NIC created at the top of forwarder.New was removed concurrently (engine teardown racing initialization), or the endpoint was closed between CreateNIC and SetSpoofing; a repeated New() against a half-torn-down stack also triggers it.

Common situations: Fast up/down or reconnection cycles (common on laptops changing networks, or mobile apps backgrounding) where Close() races the constructor; a crash in a prior lifecycle leaving the stack in a bad state.

Related errors


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