slackhq/nebula · error

INetwork.SetCategory: %s

Error message

INetwork.SetCategory: %s

What it means

This error wraps a failed HRESULT from INetwork::SetCategory, which writes a new NLM_NETWORK_CATEGORY (public/private/domain) for a Windows network. Nebula uses it to persist the configured tun.network_category onto the underlying OS network profile. A failing HRESULT means Windows refused the update — commonly due to insufficient privileges or service issues.

Source

Thrown at overlay/network_category_windows.go:243

}

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.
func coInit() (func(), error) {
	err := windows.CoInitializeEx(0, windows.COINIT_MULTITHREADED)
	if err == nil {
		return windows.CoUninitialize, nil
	}
	if e, ok := err.(syscall.Errno); ok {
		switch uint32(e) {
		case hrSFALSE:
			return windows.CoUninitialize, nil
		case hrRPCEChangedMode:

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Run the process elevated (or as LocalSystem) so it has permission to change network profiles
  2. Verify the Network List Service (netprofm) is running before startup
  3. Check the hresult in the message: E_ACCESSDENIED → privileges; service errors → restart netprofm
  4. Re-acquire the INetwork pointer via GetNetworkConnections/GetNetwork if the network profile changed before the write

Example fix

// before: ignoring why the write failed
if err := net.SetCategory(cat); err != nil { return err }
// after: distinguish privilege failures
if err := net.SetCategory(cat); err != nil {
    if strings.Contains(err.Error(), "Access is denied") {
        return fmt.Errorf("setting network category requires elevation: %w", err)
    }
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

// check write permission context before attempting SetCategory
func canSetCategory() error {
    if !isAdminOrLocalSystem() {
        return fmt.Errorf("changing network category requires elevation")
    }
    return nil
}

Type guard

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

Try / catch

err := net.SetCategory(cat)
if err != nil {
    log.Warnf("could not persist network category (running elevated?): %v", err)
    // continue startup; category persistence is secondary
}

Prevention

When it happens

Trigger: Calling SetCategory(c) during tun setup when applying the user's configured network_category, and the NLM COM vtbl call returns a failing HRESULT (e.g. E_ACCESSDENIED from a non-elevated process).

Common situations: Running Nebula as a non-admin service without rights to change network profiles; group policy locking network profile changes; NLM service stopped; stale INetwork pointer after profile changes.

Related errors


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