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
- On OpenBSD, use the device-name path: set tun.dev to a valid device (e.g. tun0) so newTun is used instead.
- Remove any fd-passing configuration that triggers newTunFromFd on OpenBSD.
- 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.
- 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
- On OpenBSD always configure tun.dev with a valid device name (tun0, tun1, ...).
- Do not use fd-passing / privilege-separation tun setups on OpenBSD builds.
- Gate fd-based tun code paths behind runtime.GOOS checks.
- Match device names against the tun[0-9]+ pattern the library requires.
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
- newTunFromFd not supported in NetBSD
- a device name in the format of /dev/tunN must be specified
- error closing tun file: %w
- unable to determine IP version from packet
- failed to set tun address %s: %s
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/d5a29646db147ab4.
Report an issue: GitHub.