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
- Ensure the previous Forwarder's cancel() and goroutine shutdown complete before creating a new one (join on a done channel)
- Guard the constructor entry with the engine's lifecycle mutex so New and Stop cannot interleave
- Retry construction once after teardown completes if the error indicates an unknown NIC
- 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
- Never run forwarder.New concurrently with Close/Stop of the previous instance
- Cancel and wait for the old forwarder's context before constructing a new stack
- Add a startup self-test that constructs and tears down the forwarder to surface lifecycle bugs early
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
- set promiscuous mode: %s
- add IPv6 protocol address: %s
- creating default subnet: %w
- creating default v6 subnet: %w
- panic: %v
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/4429810639194575.
Report an issue: GitHub.