diff --git a/proxy/tun/README.md b/proxy/tun/README.md index ea5d799d1eaf..ca081f5a5b8b 100644 --- a/proxy/tun/README.md +++ b/proxy/tun/README.md @@ -4,7 +4,7 @@ TUN interface support bridges the gap between network layer 3 and layer 7, intro This functionality is targeted to assist applications/end devices that don't have proxy support, or can't run external applications (like Smart TV's). Making it possible to run Xray proxy right on network edge devices (routers) with support to route raw network traffic. \ Primary targets are Linux based router devices. Like most popular OpenWRT option. \ -Although support for Windows is also implemented (see below). +Support for Windows, macOS, Android and iOS is also implemented (see below). ## PLEASE READ FOLLOWING CAREFULLY @@ -194,3 +194,39 @@ sudo route add -inet6 -host 2606:4700:4700::1111 -iface utun10 sudo route add -inet6 -host 2606:4700:4700::1001 -iface utun10 ``` Important to remember that everything written above about Linux routing concept, also apply to Mac OS X. If you simply route default route through utun interface, that will result network loop and immediate network failure. + +## ANDROID SUPPORT + +Android uses the VpnService API which provides a TUN file descriptor to the application. + +Obtain the fd from VpnService: +```kotlin +val tunFd = vpnInterface.fd +``` + +Set the environment variable `xray.tun.fd` (or `XRAY_TUN_FD`) to the fd number before starting Xray. This can be done from Kotlin/Java or by exposing a Go function via gomobile bindings. + +Build using gomobile for Android library integration: +``` +gomobile bind -target=android +``` + +## iOS SUPPORT + +iOS uses the same utun packet format as macOS, but the file descriptor is provided by NetworkExtension. + +Obtain the fd from NetworkExtension: +```swift +var buf = [CChar](repeating: 0, count: Int(IFNAMSIZ)) +let utunPrefix = "utun".utf8CString.dropLast() +let tunFd = ((0 ... 1024).first { (_ fd: Int32) -> Bool in var len = socklen_t(buf.count) + return getsockopt(fd, 2, 2, &buf, &len) == 0 && buf.starts(with: utunPrefix) +}! +``` + +Set the environment variable `xray.tun.fd` (or `XRAY_TUN_FD`) to the fd number before starting Xray. This can be done from Swift/Objective-C or by exposing a Go function via gomobile bindings. + +Build using gomobile for iOS framework integration: +``` +gomobile bind -target=ios +``` \ No newline at end of file diff --git a/proxy/tun/tun_darwin.go b/proxy/tun/tun_darwin.go index a1b24deeb257..ad9f4783a18e 100644 --- a/proxy/tun/tun_darwin.go +++ b/proxy/tun/tun_darwin.go @@ -8,10 +8,12 @@ import ( "net" "net/netip" "os" + "strconv" "syscall" "unsafe" "github.com/xtls/xray-core/common/buf" + "github.com/xtls/xray-core/common/platform" "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/buffer" "gvisor.dev/gvisor/pkg/tcpip" @@ -38,6 +40,7 @@ func procyield(cycles uint32) type DarwinTun struct { tunFile *os.File options TunOptions + ownsFd bool // true for macOS (we created the fd), false for iOS (fd from system) } var _ Tun = (*DarwinTun)(nil) @@ -45,6 +48,27 @@ var _ GVisorTun = (*DarwinTun)(nil) var _ GVisorDevice = (*DarwinTun)(nil) func NewTun(options TunOptions) (Tun, error) { + // Check if fd is provided via environment (iOS mode) + fdStr := platform.NewEnvFlag(platform.TunFdKey).GetValue(func() string { return "" }) + if fdStr != "" { + // iOS: use provided fd from NetworkExtension + fd, err := strconv.Atoi(fdStr) + if err != nil { + return nil, err + } + + if err = unix.SetNonblock(fd, true); err != nil { + return nil, err + } + + return &DarwinTun{ + tunFile: os.NewFile(uintptr(fd), "utun"), + options: options, + ownsFd: false, + }, nil + } + + // macOS: create our own utun interface tunFile, err := open(options.Name) if err != nil { return nil, err @@ -59,6 +83,7 @@ func NewTun(options TunOptions) (Tun, error) { return &DarwinTun{ tunFile: tunFile, options: options, + ownsFd: true, }, nil } @@ -67,7 +92,11 @@ func (t *DarwinTun) Start() error { } func (t *DarwinTun) Close() error { - return t.tunFile.Close() + if t.ownsFd { + return t.tunFile.Close() + } + // iOS: don't close the fd, it's owned by NetworkExtension + return nil } // WritePacket implements GVisorDevice method to write one packet to the tun device