slackhq/nebula · error

newTunFromFd not supported in openbsd

Error message

newTunFromFd not supported in openbsd

What it means

On OpenBSD, creating a tun device from an existing file descriptor is not implemented; newTunFromFd is a stub that always returns this error. The library only supports opening tun devices by device name (via newTun) on this platform. Any caller or config path that resolves to newTunFromFd on OpenBSD will fail immediately.

Source

Thrown at overlay/tun_openbsd.go:65

	Name [unix.IFNAMSIZ]byte
	data int
}

type tun struct {
	Device      string
	vpnNetworks []netip.Prefix
	MTU         int
	Routes      atomic.Pointer[[]Route]
	routeTree   atomic.Pointer[bart.Table[routing.Gateways]]
	l           *slog.Logger
	f           *os.File
	fd          int
}

var deviceNameRE = regexp.MustCompile(`^tun[0-9]+$`)

func newTunFromFd(_ *config.C, _ *slog.Logger, _ int, _ []netip.Prefix) (*tun, error) {
	return nil, fmt.Errorf("newTunFromFd not supported in openbsd")
}

func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, _ bool) (*tun, error) {
	// Try to open tun device
	var err error
	deviceName := c.GetString("tun.dev", "")
	if deviceName == "" {
		return nil, fmt.Errorf("a device name in the format of /dev/tunN must be specified")
	}
	if !deviceNameRE.MatchString(deviceName) {
		return nil, fmt.Errorf("a device name in the format of /dev/tunN must be specified")
	}

	fd, err := unix.Open("/dev/"+deviceName, os.O_RDWR, 0)
	if err != nil {
		return nil, err
	}

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. On OpenBSD, use the device-name path: set tun.dev to a valid device (e.g. tun0) so newTun is used instead.
  2. Remove any fd-passing configuration that triggers newTunFromFd on OpenBSD.
  3. If fd inheritance is required, pre-open /dev/tunN in the parent and pass the device name to the child instead of the raw fd.
  4. Check for a newer library version that may implement fd-based tun creation on OpenBSD.

Example fix

// before (config triggering fd path)
 tun:
   fd: 7
// after (OpenBSD: use device name)
 tun:
   dev: tun0
Defensive patterns

Strategy: fallback

Validate before calling

// Choose the right constructor per platform before calling
func openTun(c *config.C, l *slog.Logger, fd int, networks []netip.Prefix) (*tun, error) {
    if fd >= 0 && runtime.GOOS == "openbsd" {
        return nil, fmt.Errorf("fd-based tun is unsupported on openbsd; set tun.dev instead")
    }
    if fd >= 0 {
        return newTunFromFd(c, l, fd, networks)
    }
    return newTun(c, l, networks, false)
}

Try / catch

t, err := openTun(cfg, log, fd, networks)
if err != nil && strings.Contains(err.Error(), "not supported in openbsd") {
    // Fallback: open by device name
    devName := cfg.GetString("tun.dev", "tun0")
    return fmt.Errorf("newTunFromFd unavailable; configure tun.dev (e.g. %s) instead: %w", devName, err)
}

Prevention

When it happens

Trigger: Calling newTunFromFd directly, or using a config/injection path (e.g. passing an inherited tun fd) that routes to newTunFromFd on an OpenBSD build.

Common situations: Portable deployments that pass an already-open tun fd from a parent process (common with privilege-separation designs) running on OpenBSD; copied Linux configuration files that set tun file-descriptor options.

Related errors


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