1
0
Эх сурвалжийг харах

TUN inbound: Add iOS support (#5612)

And https://github.com/XTLS/Xray-core/pull/5612#issuecomment-3799070838
Evozi Team 3 долоо хоног өмнө
parent
commit
c3af657c0e

+ 37 - 1
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
+```

+ 30 - 1
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