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
- Obtain the TUN file descriptor from your NEPacketTunnelProvider and call newTunFromFd(c, l, deviceFd, vpnNetworks) instead of newTun.
- Ensure the iOS build tag is active so the correct overlay implementation is compiled in.
- 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
- On iOS, always source the TUN fd from NEPacketTunnelProvider and use newTunFromFd.
- Gate startup code paths by build tags/GOOS so the stub newTun is never reached on iOS.
- Document in the extension integration that direct TUN creation is unsupported.
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
- unable to determine IP version from packet
- newTunFromFd not supported in FreeBSD
- failed to set the tun fd to non-blocking mode: %w
- no inside interface (tun)
- must be using user device
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/6e17384c5f918309.
Report an issue: GitHub.