slackhq/nebula · error

INetwork.GetCategory: %s

Error message

INetwork.GetCategory: %s

What it means

This error wraps a failed HRESULT from INetwork::GetCategory, which reads the NLM_NETWORK_CATEGORY (public/private/domain) of a Windows network. It is the core query used to map the OS category onto Nebula's networkCategory during tun setup. A failing HRESULT means the COM call failed rather than the category being unknown.

Source

Thrown at overlay/network_category_windows.go:233

	IsConnected                uintptr
	GetConnectivity            uintptr
	GetCategory                uintptr
	SetCategory                uintptr
}

type iNetwork struct{ Vtbl *iNetworkVtbl }

func (n *iNetwork) Release() {
	syscall.SyscallN(n.Vtbl.Release, uintptr(unsafe.Pointer(n)))
}

func (n *iNetwork) GetCategory() (networkCategory, error) {
	var c networkCategory
	r1, _, _ := syscall.SyscallN(n.Vtbl.GetCategory,
		uintptr(unsafe.Pointer(n)), uintptr(unsafe.Pointer(&c)),
	)
	if hr := hresult(r1); hr.failed() {
		return 0, fmt.Errorf("INetwork.GetCategory: %s", hr)
	}
	return c, nil
}

func (n *iNetwork) SetCategory(c networkCategory) error {
	r1, _, _ := syscall.SyscallN(n.Vtbl.SetCategory,
		uintptr(unsafe.Pointer(n)), uintptr(int32(c)),
	)
	if hr := hresult(r1); hr.failed() {
		return fmt.Errorf("INetwork.SetCategory: %s", hr)
	}
	return nil
}

// coInit initializes COM for the current OS thread. The returned function must
// be deferred to balance a successful init. RPC_E_CHANGED_MODE means COM is
// already initialized in a different mode on this thread, which is still fine
// for our calls but we must not Uninitialize in that case.

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Retry the classification once with a freshly obtained INetwork pointer
  2. Verify the Network List Service (netprofm) is running and healthy
  3. Keep the COM object usage on the thread where COM was initialized
  4. Parse the hresult in the message for the specific COM failure and degrade gracefully (leave category unset) when classification is best-effort

Example fix

// before
cat, err := net.GetCategory()
if err != nil { return err }
// after
cat, err := net.GetCategory()
if err != nil {
    l.WithError(err).Warn("unable to read network category; leaving category unset")
    return 0, false, nil
}
Defensive patterns

Strategy: retry

Validate before calling

// preflight service availability
func nlmReady() bool {
    state, err := svcQuery("netprofm")
    return err == nil && state == "RUNNING"
}

Type guard

func isGetCategoryError(err error) bool {
    return err != nil && strings.HasPrefix(err.Error(), "INetwork.GetCategory:")
}

Try / catch

cat, err := net.GetCategory()
if err != nil {
    time.Sleep(250 * time.Millisecond)
    if n2, rerr := refreshNetwork(); rerr == nil {
        cat, err = n2.GetCategory()
    }
    if err != nil {
        log.Warnf("category read failed, leaving unset: %v", err)
        return 0, false, nil
    }
}

Prevention

When it happens

Trigger: Calling GetCategory() on an iNetwork pointer obtained via GetNetwork() while classifying the tunnel adapter, when the COM vtbl call returns a failed HRESULT.

Common situations: Transient NLM service failure during tunnel startup; stale INetwork pointer after the network profile changed or was deleted; COM apartment/threading violations in service contexts.

Related errors


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