slackhq/nebula · error

ErrUnknownState

ErrUnknownState

Error message

nebula state is invalid

What it means

ErrUnknownState in control.go is returned by Start() when the Control's state is not one of the recognized states in the switch (ready/stopping/stopped/started fall-through default). It indicates the internal state machine reached an invalid or uninitialized value.

Source

Thrown at control.go:30

	"github.com/slackhq/nebula/cert"
	"github.com/slackhq/nebula/header"
	"github.com/slackhq/nebula/overlay"
)

type RunState int

const (
	StateUnknown RunState = iota
	StateReady
	StateStarted
	StateStopping
	StateStopped
)

var ErrAlreadyStarted = errors.New("nebula is already started")
var ErrAlreadyStopped = errors.New("nebula cannot be restarted")
var ErrUnknownState = errors.New("nebula state is invalid")

// Every interaction here needs to take extra care to copy memory and not return or use arguments "as is" when touching
// core. This means copying IP objects, slices, de-referencing pointers and taking the actual value, etc

type controlEach func(h *HostInfo)

type controlHostLister interface {
	QueryVpnAddr(vpnAddr netip.Addr) *HostInfo
	ForEachIndex(each controlEach)
	ForEachVpnAddr(each controlEach)
	GetPreferredRanges() []netip.Prefix
}

type Control struct {
	stateLock sync.Mutex
	state     RunState

	f                      *Interface

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Ensure the Control is created via its constructor so StateReady is set before Start.
  2. Serialize Start/Stop calls with a mutex or single goroutine to avoid corrupting the state field.
  3. Log/inspect c.State() when this error appears to identify the invalid value.
  4. Report or fix the state transition bug if a legitimately new state bypasses the switch.

Example fix

// before
c := &control.Control{} // zero value
c.Start() // ErrUnknownState

// after
c := control.New(lightHouse, ..., config)
c.Start()
Defensive patterns

Strategy: validation

Validate before calling

if c.State() != control.StateReady {
    return fmt.Errorf("control in unexpected state %v; expected StateReady before Start", c.State())
}

Type guard

func stateIsValid(s control.State) bool {
    return s >= control.StateReady && s <= control.StateStopped
}

Try / catch

if err := c.Start(); err != nil {
    if errors.Is(err, control.ErrUnknownState) {
        log.Fatalf("control state machine invalid (state=%v); rebuild instance", c.State())
    }
    return err
}

Prevention

When it happens

Trigger: Start() invoked when c.state holds an unexpected value (control.go:85 default branch) — e.g. an uninitialized Control or a state value corrupted by concurrent mutation without the proper locking.

Common situations: Using a zero-value Control without proper construction; data races on the state field from concurrent Start/Stop calls in custom code; version mismatches where a new state was added but Start's switch is stale.

Related errors


AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03). Data as JSON: /api/errors/34fcc3807e425a6a. Report an issue: GitHub.