slackhq/nebula · error

newTunFromFd not supported in Darwin

Error message

newTunFromFd not supported in Darwin

What it means

On Darwin, nebula cannot adopt an externally supplied TUN file descriptor; the utun control socket must be created by nebula itself via newTun. newTunFromFd is therefore a stub returning this error. Any attempt to construct the TUN from a caller-provided fd on macOS fails unconditionally.

Source

Thrown at overlay/tun_darwin.go:154

	c.RegisterReloadCallback(func(c *config.C) {
		err := t.reload(c, false)
		if err != nil {
			util.LogWithContextIfNeeded("failed to reload tun device", err, t.l)
		}
	})

	return t, nil
}

func (t *tun) deviceBytes() (o [16]byte) {
	for i, c := range t.Device {
		o[i] = byte(c)
	}
	return
}

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

func (t *tun) Close() error {
	if t.f != nil {
		return t.f.Close()
	}
	return nil
}

func (t *tun) Activate() error {
	devName := t.deviceBytes()

	s, err := unix.Socket(
		unix.AF_INET,
		unix.SOCK_DGRAM,
		unix.IPPROTO_IP,
	)
	if err != nil {

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. On darwin, call tun.New (newTun) and let nebula create its own utun device instead of NewFromFd.
  2. Branch on runtime.GOOS: use NewFromFd only for android, New for darwin/linux/bsd.
  3. If the fd must come from outside the process, route through a NetworkExtension or restructure so nebula owns TUN creation.

Example fix

// before
tunDev, err := tun.NewFromFd(c, l, fd, vpnNetworks) // fails on darwin
// after
if runtime.GOOS == "darwin" {
    tunDev, err = tun.New(c, l, vpnNetworks, false)
} else {
    tunDev, err = tun.NewFromFd(c, l, fd, vpnNetworks)
}
Defensive patterns

Strategy: fallback

Validate before calling

if runtime.GOOS == "darwin" && useProvidedFd {
    return errors.New("darwin cannot adopt external TUN fds; use tun.New")
}

Try / catch

t, err := tun.NewFromFd(c, l, fd, vpnNetworks)
if err != nil && strings.Contains(err.Error(), "not supported in Darwin") {
    t, err = tun.New(c, l, vpnNetworks, false) // nebula creates its own utun
}

Prevention

When it happens

Trigger: Calling tun.NewFromFd (newTunFromFd) on a darwin build — e.g. porting the Android-style fd handoff code path to macOS.

Common situations: Sharing embedding code between Android and macOS that passes VpnService-established fds; building an iOS/macOS Catalyst app that reuses the Android fd plumbing; tests invoking NewFromFd on darwin.

Related errors


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