slackhq/nebula · error

newTun not supported in iOS

Error message

newTun not supported in iOS

What it means

newTun (overlay/tun_ios.go:34) is a stub on iOS: creating a TUN device directly is not supported because iOS requires the TUN file descriptor to be provided by the NetworkExtension framework. Calling newTun always returns this error unconditionally.

Source

Thrown at overlay/tun_ios.go:34

	"github.com/gaissmai/bart"
	"github.com/slackhq/nebula/config"
	"github.com/slackhq/nebula/overlay/tio"
	"github.com/slackhq/nebula/routing"
	"github.com/slackhq/nebula/util"
	"golang.org/x/sys/unix"
)

type tun struct {
	io.ReadWriteCloser
	vpnNetworks []netip.Prefix
	Routes      atomic.Pointer[[]Route]
	routeTree   atomic.Pointer[bart.Table[routing.Gateways]]
	l           *slog.Logger
}

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

func newTunFromFd(c *config.C, l *slog.Logger, deviceFd int, vpnNetworks []netip.Prefix) (*tun, error) {
	if err := unix.SetNonblock(deviceFd, true); err != nil {
		// We own the fd from the moment it is handed to us, same as the reload error path below
		_ = unix.Close(deviceFd)
		return nil, fmt.Errorf("failed to set the tun fd to non-blocking mode: %w", err)
	}

	file := os.NewFile(uintptr(deviceFd), "/dev/tun")
	t := &tun{
		vpnNetworks:     vpnNetworks,
		ReadWriteCloser: &tunReadCloser{f: file},
		l:               l,
	}

	err := t.reload(c, true)
	if err != nil {

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Obtain the TUN file descriptor from your NEPacketTunnelProvider and call newTunFromFd(c, l, deviceFd, vpnNetworks) instead of newTun.
  2. Ensure the iOS build tag is active so the correct overlay implementation is compiled in.
  3. If you expected TUN creation to work, confirm you are not accidentally building/running the iOS variant on another platform setup.

Example fix

// before
t, err := newTun(c, l, vpnNetworks, false)
// after
fd := packetFlow.FileDescriptor() // from NEPacketTunnelProvider
t, err := newTunFromFd(c, l, fd, vpnNetworks)
Defensive patterns

Strategy: fallback

Validate before calling

// iOS: only call newTunFromFd; detect the unsupported path first
if runtime.GOOS == "ios" {
	// must supply fd from NEPacketTunnelProvider
	fd := packetFlow.FileDescriptor()
	t, err = newTunFromFd(c, l, fd, vpnNetworks)
}

Type guard

func tunCreationSupported(goos string) bool {
	return goos != "ios"
}

Try / catch

t, err := newTun(c, l, vpnNetworks, false)
if err != nil && strings.Contains(err.Error(), "not supported in iOS") {
	// switch to newTunFromFd with NetworkExtension-provided fd
}

Prevention

When it happens

Trigger: Starting nebula on an iOS build via the default newTun entry point instead of newTunFromFd with an fd obtained from Packet Flow / NEPacketTunnelProvider.

Common situations: Embedding nebula in an iOS app but wiring the NetworkExtension incorrectly; using the standard Linux/BSD startup path on iOS; forgetting that iOS builds must supply the device fd from NEPacketTunnelProvider.

Related errors


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