diff --git a/core/src/main/cpp/main.c b/core/src/main/cpp/main.c
index 0a2c8b51..2ad3f717 100644
--- a/core/src/main/cpp/main.c
+++ b/core/src/main/cpp/main.c
@@ -111,18 +111,20 @@ Java_com_github_kr328_clash_core_bridge_Bridge_nativeNotifyInstalledAppChanged(J
JNIEXPORT void JNICALL
Java_com_github_kr328_clash_core_bridge_Bridge_nativeStartTun(JNIEnv *env, jobject thiz,
jint fd,
+ jstring stack,
jstring gateway,
jstring portal,
jstring dns,
jobject cb) {
TRACE_METHOD();
+ scoped_string _stack = get_string(stack);
scoped_string _gateway = get_string(gateway);
scoped_string _portal = get_string(portal);
scoped_string _dns = get_string(dns);
jobject _interface = new_global(cb);
- startTun(fd, _gateway, _portal, _dns, _interface);
+ startTun(fd, _stack, _gateway, _portal, _dns, _interface);
}
JNIEXPORT void JNICALL
diff --git a/core/src/main/golang/native/tun.go b/core/src/main/golang/native/tun.go
index b09d6f65..860a1fdc 100644
--- a/core/src/main/golang/native/tun.go
+++ b/core/src/main/golang/native/tun.go
@@ -64,7 +64,7 @@ func (t *remoteTun) close() {
}
//export startTun
-func startTun(fd C.int, gateway, portal, dns C.c_string, callback unsafe.Pointer) C.int {
+func startTun(fd C.int, stack, gateway, portal, dns C.c_string, callback unsafe.Pointer) C.int {
rTunLock.Lock()
defer rTunLock.Unlock()
@@ -74,6 +74,7 @@ func startTun(fd C.int, gateway, portal, dns C.c_string, callback unsafe.Pointer
}
f := int(fd)
+ s := C.GoString(stack)
g := C.GoString(gateway)
p := C.GoString(portal)
d := C.GoString(dns)
@@ -82,7 +83,7 @@ func startTun(fd C.int, gateway, portal, dns C.c_string, callback unsafe.Pointer
app.ApplyTunContext(remote.markSocket, remote.querySocketUid)
- closer, err := tun.Start(f, g, p, d)
+ closer, err := tun.Start(f, s, g, p, d)
if err != nil {
remote.close()
diff --git a/core/src/main/golang/native/tun/tun.go b/core/src/main/golang/native/tun/tun.go
index 9686ea6f..d686589f 100644
--- a/core/src/main/golang/native/tun/tun.go
+++ b/core/src/main/golang/native/tun/tun.go
@@ -14,8 +14,13 @@ import (
"github.com/metacubex/mihomo/tunnel"
)
-func Start(fd int, gateway, portal, dns string) (io.Closer, error) {
- log.Debugln("TUN: fd = %d, gateway = %s, portal = %s, dns = %s", fd, gateway, portal, dns)
+func Start(fd int, stack, gateway, portal, dns string) (io.Closer, error) {
+ log.Debugln("TUN: fd = %d, stack = %s, gateway = %s, portal = %s, dns = %s", fd, stack, gateway, portal, dns)
+
+ tunStack, ok := C.StackTypeMapping[strings.ToLower(stack)]
+ if !ok {
+ tunStack = C.TunSystem
+ }
var prefix4 []netip.Prefix
var prefix6 []netip.Prefix
@@ -49,7 +54,7 @@ func Start(fd int, gateway, portal, dns string) (io.Closer, error) {
options := LC.Tun{
Enable: true,
Device: sing_tun.InterfaceName,
- Stack: C.TunSystem,
+ Stack: tunStack,
DNSHijack: dnsHijack,
Inet4Address: prefix4,
Inet6Address: prefix6,
diff --git a/core/src/main/java/com/github/kr328/clash/core/Clash.kt b/core/src/main/java/com/github/kr328/clash/core/Clash.kt
index 37096215..a58dc41f 100644
--- a/core/src/main/java/com/github/kr328/clash/core/Clash.kt
+++ b/core/src/main/java/com/github/kr328/clash/core/Clash.kt
@@ -64,13 +64,14 @@ object Clash {
fun startTun(
fd: Int,
+ stack: String,
gateway: String,
portal: String,
dns: String,
markSocket: (Int) -> Boolean,
querySocketUid: (protocol: Int, source: InetSocketAddress, target: InetSocketAddress) -> Int
) {
- Bridge.nativeStartTun(fd, gateway, portal, dns, object : TunInterface {
+ Bridge.nativeStartTun(fd, stack, gateway, portal, dns, object : TunInterface {
override fun markSocket(fd: Int) {
markSocket(fd)
}
diff --git a/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.kt b/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.kt
index 09fa052b..8aa26fb4 100644
--- a/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.kt
+++ b/core/src/main/java/com/github/kr328/clash/core/bridge/Bridge.kt
@@ -19,7 +19,7 @@ object Bridge {
external fun nativeNotifyDnsChanged(dnsList: String)
external fun nativeNotifyTimeZoneChanged(name: String, offset: Int)
external fun nativeNotifyInstalledAppChanged(uidList: String)
- external fun nativeStartTun(fd: Int, gateway: String, portal: String, dns: String, cb: TunInterface)
+ external fun nativeStartTun(fd: Int, stack: String, gateway: String, portal: String, dns: String, cb: TunInterface)
external fun nativeStopTun()
external fun nativeStartHttp(listenAt: String): String?
external fun nativeStopHttp()
diff --git a/design/src/main/java/com/github/kr328/clash/design/NetworkSettingsDesign.kt b/design/src/main/java/com/github/kr328/clash/design/NetworkSettingsDesign.kt
index d2af3ded..2195840b 100644
--- a/design/src/main/java/com/github/kr328/clash/design/NetworkSettingsDesign.kt
+++ b/design/src/main/java/com/github/kr328/clash/design/NetworkSettingsDesign.kt
@@ -93,6 +93,22 @@ class NetworkSettingsDesign(
)
}
+ selectableList(
+ value = srvStore::tunStackMode,
+ values = arrayOf(
+ "system",
+ "gvisor",
+ "mixed"
+ ),
+ valuesText = arrayOf(
+ R.string.tun_stack_system,
+ R.string.tun_stack_gvisor,
+ R.string.tun_stack_mixed
+ ),
+ title = R.string.tun_stack_mode,
+ configure = vpnDependencies::add,
+ )
+
selectableList(
value = srvStore::accessControlMode,
values = AccessControlMode.values(),
diff --git a/design/src/main/res/values/strings.xml b/design/src/main/res/values/strings.xml
index 0be678d2..b1987c7c 100644
--- a/design/src/main/res/values/strings.xml
+++ b/design/src/main/res/values/strings.xml
@@ -232,6 +232,11 @@
Always Dark
Always Light
+ Stack Mode
+ System Stack
+ Gvisor Stack
+ Mixed Stack
+
Allow all apps
Allow selected apps
Deny selected apps
diff --git a/service/src/main/java/com/github/kr328/clash/service/TunService.kt b/service/src/main/java/com/github/kr328/clash/service/TunService.kt
index abb50f7c..8550f8ab 100644
--- a/service/src/main/java/com/github/kr328/clash/service/TunService.kt
+++ b/service/src/main/java/com/github/kr328/clash/service/TunService.kt
@@ -218,6 +218,7 @@ class TunService : VpnService(), CoroutineScope by CoroutineScope(Dispatchers.De
TunModule.TunDevice(
fd = establish()?.detachFd()
?: throw NullPointerException("Establish VPN rejected by system"),
+ stack = store.tunStackMode,
gateway = "$TUN_GATEWAY/$TUN_SUBNET_PREFIX" + if (store.allowIpv6) ",$TUN_GATEWAY6/$TUN_SUBNET_PREFIX6" else "",
portal = TUN_PORTAL + if (store.allowIpv6) ",$TUN_PORTAL6" else "",
dns = if (store.dnsHijacking) NET_ANY else (TUN_DNS + if (store.allowIpv6) ",$TUN_DNS6" else ""),
diff --git a/service/src/main/java/com/github/kr328/clash/service/clash/module/TunModule.kt b/service/src/main/java/com/github/kr328/clash/service/clash/module/TunModule.kt
index e2b38f59..84197d32 100644
--- a/service/src/main/java/com/github/kr328/clash/service/clash/module/TunModule.kt
+++ b/service/src/main/java/com/github/kr328/clash/service/clash/module/TunModule.kt
@@ -15,6 +15,7 @@ import java.security.SecureRandom
class TunModule(private val vpn: VpnService) : Module(vpn) {
data class TunDevice(
val fd: Int,
+ var stack: String,
val gateway: String,
val portal: String,
val dns: String,
@@ -56,6 +57,7 @@ class TunModule(private val vpn: VpnService) : Module(vpn) {
fun attach(device: TunDevice) {
Clash.startTun(
fd = device.fd,
+ stack = device.stack,
gateway = device.gateway,
portal = device.portal,
dns = device.dns,
diff --git a/service/src/main/java/com/github/kr328/clash/service/store/ServiceStore.kt b/service/src/main/java/com/github/kr328/clash/service/store/ServiceStore.kt
index 474f1fac..d361848f 100644
--- a/service/src/main/java/com/github/kr328/clash/service/store/ServiceStore.kt
+++ b/service/src/main/java/com/github/kr328/clash/service/store/ServiceStore.kt
@@ -56,6 +56,11 @@ class ServiceStore(context: Context) {
defaultValue = false
)
+ var tunStackMode by store.string(
+ key = "tun_stack_mode",
+ defaultValue = "system"
+ )
+
var dynamicNotification by store.boolean(
key = "dynamic_notification",
defaultValue = true