diff --git a/contrib/mobile/build b/contrib/mobile/build
index 3c7b1d1..3f6b9bf 100755
--- a/contrib/mobile/build
+++ b/contrib/mobile/build
@@ -37,7 +37,7 @@ if [ $IOS ]; then
   echo "Building framework for iOS"
   go get golang.org/x/mobile/bind
   gomobile bind \
-    -target ios -tags mobile -o Yggdrasil.xcframework \
+    -target ios,macos -tags mobile -o Yggdrasil.xcframework \
     -ldflags="$LDFLAGS $STRIP" -gcflags="$GCFLAGS" \
     ./contrib/mobile ./src/config;
 fi
diff --git a/contrib/mobile/mobile.go b/contrib/mobile/mobile.go
index 6b03609..4c1a8ef 100644
--- a/contrib/mobile/mobile.go
+++ b/contrib/mobile/mobile.go
@@ -5,6 +5,7 @@ import (
 	"encoding/json"
 	"net"
 	"regexp"
+	"runtime/debug"
 
 	"github.com/gologme/log"
 
@@ -13,6 +14,7 @@ import (
 	"github.com/yggdrasil-network/yggdrasil-go/src/core"
 	"github.com/yggdrasil-network/yggdrasil-go/src/ipv6rwc"
 	"github.com/yggdrasil-network/yggdrasil-go/src/multicast"
+	"github.com/yggdrasil-network/yggdrasil-go/src/tun"
 	"github.com/yggdrasil-network/yggdrasil-go/src/version"
 
 	_ "golang.org/x/mobile/bind"
@@ -28,7 +30,9 @@ type Yggdrasil struct {
 	iprwc     *ipv6rwc.ReadWriteCloser
 	config    *config.NodeConfig
 	multicast *multicast.Multicast
+	tun       *tun.TunAdapter // optional
 	log       MobileLogger
+	logger    *log.Logger
 }
 
 // StartAutoconfigure starts a node with a randomly generated config
@@ -39,6 +43,8 @@ func (m *Yggdrasil) StartAutoconfigure() error {
 // StartJSON starts a node with the given JSON config. You can get JSON config
 // (rather than HJSON) by using the GenerateConfigJSON() function
 func (m *Yggdrasil) StartJSON(configjson []byte) error {
+  debug.SetMemoryLimit(1024 * 1024 * 40)
+  
 	logger := log.New(m.log, "", 0)
 	logger.EnableLevel("error")
 	logger.EnableLevel("warn")
@@ -88,9 +94,9 @@ func (m *Yggdrasil) StartJSON(configjson []byte) error {
 				Priority: uint8(intf.Priority),
 			})
 		}
-		m.multicast, err = multicast.New(m.core, logger, options...)
+		m.multicast, err = multicast.New(m.core, m.logger, options...)
 		if err != nil {
-			logger.Errorln("An error occurred starting multicast:", err)
+			m.logger.Errorln("An error occurred starting multicast:", err)
 		}
 	}
 
@@ -153,6 +159,11 @@ func (m *Yggdrasil) Stop() error {
 	if err := m.multicast.Stop(); err != nil {
 		return err
 	}
+	if m.tun != nil {
+		if err := m.tun.Stop(); err != nil {
+			return err
+		}
+	}
 	m.core.Stop()
 	return nil
 }
diff --git a/contrib/mobile/mobile_ios.go b/contrib/mobile/mobile_ios.go
index fedee2d..c7747ea 100644
--- a/contrib/mobile/mobile_ios.go
+++ b/contrib/mobile/mobile_ios.go
@@ -15,6 +15,8 @@ void Log(const char *text) {
 import "C"
 import (
 	"unsafe"
+
+	"github.com/yggdrasil-network/yggdrasil-go/src/tun"
 )
 
 type MobileLogger struct {
@@ -26,3 +28,13 @@ func (nsl MobileLogger) Write(p []byte) (n int, err error) {
 	C.Log(cstr)
 	return len(p), nil
 }
+
+func (m *Yggdrasil) TakeOverTUN(fd int32) error {
+	options := []tun.SetupOption{
+		tun.FileDescriptor(fd),
+		tun.InterfaceMTU(m.iprwc.MTU()),
+	}
+	var err error
+	m.tun, err = tun.New(m.iprwc, m.logger, options...)
+	return err
+}
diff --git a/src/tun/options.go b/src/tun/options.go
index 7be7921..58d3d80 100644
--- a/src/tun/options.go
+++ b/src/tun/options.go
@@ -6,6 +6,8 @@ func (m *TunAdapter) _applyOption(opt SetupOption) {
 		m.config.name = v
 	case InterfaceMTU:
 		m.config.mtu = v
+	case FileDescriptor:
+		m.config.fd = int32(v)
 	}
 }
 
@@ -15,6 +17,8 @@ type SetupOption interface {
 
 type InterfaceName string
 type InterfaceMTU uint64
+type FileDescriptor int32
 
-func (a InterfaceName) isSetupOption() {}
-func (a InterfaceMTU) isSetupOption()  {}
+func (a InterfaceName) isSetupOption()  {}
+func (a InterfaceMTU) isSetupOption()   {}
+func (a FileDescriptor) isSetupOption() {}
diff --git a/src/tun/tun.go b/src/tun/tun.go
index 7964ab9..cca3af5 100644
--- a/src/tun/tun.go
+++ b/src/tun/tun.go
@@ -44,6 +44,7 @@ type TunAdapter struct {
 	isOpen      bool
 	isEnabled   bool // Used by the writer to drop sessionTraffic if not enabled
 	config      struct {
+    fd   int32
 		name InterfaceName
 		mtu  InterfaceMTU
 	}
@@ -126,7 +127,13 @@ func (tun *TunAdapter) _start() error {
 	if tun.rwc.MaxMTU() < mtu {
 		mtu = tun.rwc.MaxMTU()
 	}
-	if err := tun.setup(string(tun.config.name), addr, mtu); err != nil {
+	var err error
+	if tun.config.fd > 0 {
+		err = tun.setupFD(tun.config.fd, addr, mtu)
+	} else {
+		err = tun.setup(string(tun.config.name), addr, mtu)
+	}
+	if err != nil {
 		return err
 	}
 	if tun.MTU() != mtu {
diff --git a/src/tun/tun_bsd.go b/src/tun/tun_bsd.go
index 9a8f70c..3910cce 100644
--- a/src/tun/tun_bsd.go
+++ b/src/tun/tun_bsd.go
@@ -5,6 +5,7 @@ package tun
 
 import (
 	"encoding/binary"
+	"fmt"
 	"os/exec"
 	"strconv"
 	"strings"
@@ -88,6 +89,11 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
 	return tun.setupAddress(addr)
 }
 
+// Configures the "utun" adapter from an existing file descriptor.
+func (tun *TunAdapter) setupFD(fd int32, addr string, mtu uint64) error {
+	return fmt.Errorf("setup via FD not supported on this platform")
+}
+
 func (tun *TunAdapter) setupAddress(addr string) error {
 	var sfd int
 	var err error
diff --git a/src/tun/tun_darwin.go b/src/tun/tun_darwin.go
index 29cfe95..b8e32a2 100644
--- a/src/tun/tun_darwin.go
+++ b/src/tun/tun_darwin.go
@@ -1,5 +1,5 @@
-//go:build !mobile
-// +build !mobile
+//go:build darwin || ios
+// +build darwin ios
 
 package tun
 
@@ -7,6 +7,7 @@ package tun
 
 import (
 	"encoding/binary"
+	"os"
 	"strconv"
 	"strings"
 	"unsafe"
@@ -34,6 +35,31 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
 	return tun.setupAddress(addr)
 }
 
+// Configures the "utun" adapter from an existing file descriptor.
+func (tun *TunAdapter) setupFD(fd int32, addr string, mtu uint64) error {
+	dfd, err := unix.Dup(int(fd))
+	if err != nil {
+		return err
+	}
+	err = unix.SetNonblock(dfd, true)
+	if err != nil {
+		unix.Close(dfd)
+		return err
+	}
+	iface, err := wgtun.CreateTUNFromFile(os.NewFile(uintptr(dfd), "/dev/tun"), 0)
+	if err != nil {
+		unix.Close(dfd)
+		return err
+	}
+	tun.iface = iface
+	if m, err := iface.MTU(); err == nil {
+		tun.mtu = getSupportedMTU(uint64(m))
+	} else {
+		tun.mtu = 0
+	}
+	return nil // tun.setupAddress(addr)
+}
+
 const (
 	darwin_SIOCAIFADDR_IN6       = 2155899162 // netinet6/in6_var.h
 	darwin_IN6_IFF_NODAD         = 0x0020     // netinet6/in6_var.h
diff --git a/src/tun/tun_linux.go b/src/tun/tun_linux.go
index 1e42b7b..16deb8e 100644
--- a/src/tun/tun_linux.go
+++ b/src/tun/tun_linux.go
@@ -6,6 +6,8 @@ package tun
 // The linux platform specific tun parts
 
 import (
+	"fmt"
+
 	"github.com/vishvananda/netlink"
 	wgtun "golang.zx2c4.com/wireguard/tun"
 )
@@ -28,6 +30,11 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
 	return tun.setupAddress(addr)
 }
 
+// Configures the "utun" adapter from an existing file descriptor.
+func (tun *TunAdapter) setupFD(fd int32, addr string, mtu uint64) error {
+	return fmt.Errorf("setup via FD not supported on this platform")
+}
+
 // Configures the TUN adapter with the correct IPv6 address and MTU. Netlink
 // is used to do this, so there is not a hard requirement on "ip" or "ifconfig"
 // to exist on the system, but this will fail if Netlink is not present in the
diff --git a/src/tun/tun_other.go b/src/tun/tun_other.go
index c618d83..dd33708 100644
--- a/src/tun/tun_other.go
+++ b/src/tun/tun_other.go
@@ -7,6 +7,8 @@ package tun
 // If your platform supports tun devices, you could try configuring it manually
 
 import (
+	"fmt"
+
 	wgtun "golang.zx2c4.com/wireguard/tun"
 )
 
@@ -25,6 +27,11 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
 	return tun.setupAddress(addr)
 }
 
+// Configures the "utun" adapter from an existing file descriptor.
+func (tun *TunAdapter) setupFD(fd int32, addr string, mtu uint64) error {
+	return fmt.Errorf("setup via FD not supported on this platform")
+}
+
 // We don't know how to set the IPv6 address on an unknown platform, therefore
 // write about it to stdout and don't try to do anything further.
 func (tun *TunAdapter) setupAddress(addr string) error {
diff --git a/src/tun/tun_windows.go b/src/tun/tun_windows.go
index 1a8aa1f..b3bb0c7 100644
--- a/src/tun/tun_windows.go
+++ b/src/tun/tun_windows.go
@@ -6,6 +6,7 @@ package tun
 import (
 	"bytes"
 	"errors"
+	"fmt"
 	"log"
 	"net"
 
@@ -50,6 +51,11 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
 	})
 }
 
+// Configures the "utun" adapter from an existing file descriptor.
+func (tun *TunAdapter) setupFD(fd int32, addr string, mtu uint64) error {
+	return fmt.Errorf("setup via FD not supported on this platform")
+}
+
 // Sets the MTU of the TUN adapter.
 func (tun *TunAdapter) setupMTU(mtu uint64) error {
 	if tun.iface == nil || tun.Name() == "" {