diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/root/RootProxyManager.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/root/RootProxyManager.kt index 1aa6b0262..96c653d89 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/root/RootProxyManager.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/root/RootProxyManager.kt @@ -126,7 +126,7 @@ object RootProxyManager { val lanShare = forceLanShare || MmkvManager.decodeSettingsBool(AppConfig.PREF_ROOT_LAN_SHARING) val corePid = Process.myPid() - val config = buildHevConfig(socksUsername, socksPassword, port, ipv6) + val config = buildHevConfig(socksUsername, socksPassword, port, ipv6, SettingsManager.getVpnMtu(), lanShare) if (!writeHevConfig(cfgFile, config)) { return null } @@ -209,14 +209,28 @@ object RootProxyManager { * tun address when IPv6 is enabled; whether v6 actually flows in is decided separately by * the v6 route into [TABLE]. */ - private fun buildHevConfig(socksUsername: String?, socksPassword: String?, socksPort: Int, ipv6: Boolean): String { + internal fun buildHevConfig( + socksUsername: String?, + socksPassword: String?, + socksPort: Int, + ipv6: Boolean, + mtu: Int, + lanSharing: Boolean, + ): String { val v4 = AppConfig.ROOT_TUN_ADDR_V4.substringBefore("/") val v6 = AppConfig.ROOT_TUN_ADDR_V6.substringBefore("/") return buildString { appendLine("tunnel:") appendLine(" name: '$TUN'") - appendLine(" mtu: ${SettingsManager.getVpnMtu()}") + appendLine(" mtu: $mtu") appendLine(" multi-queue: true") + if (lanSharing) { + // VNET setup or TCP-GSO EINVAL on Android can leave IPv6 packets above the LAN MTU. + // Remove when both fallback forwarding tests pass with jumbo and TSO enabled. + appendLine(" offload:") + appendLine(" jumbo: false") + if (ipv6) appendLine(" tso: false") + } appendLine(" ipv4: '$v4'") if (ipv6) appendLine(" ipv6: '$v6'") appendLine("socks5:") diff --git a/V2rayNG/app/src/test/java/com/v2ray/ang/root/RootProxyManagerTest.kt b/V2rayNG/app/src/test/java/com/v2ray/ang/root/RootProxyManagerTest.kt index e34ec97a3..12e8a0c96 100644 --- a/V2rayNG/app/src/test/java/com/v2ray/ang/root/RootProxyManagerTest.kt +++ b/V2rayNG/app/src/test/java/com/v2ray/ang/root/RootProxyManagerTest.kt @@ -1,10 +1,43 @@ package com.v2ray.ang.root import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test class RootProxyManagerTest { + @Test + fun `IPv6 LAN forwarding disables jumbo and TSO`() { + val config = RootProxyManager.buildHevConfig(null, null, 10808, true, 1500, true) + + assertTrue(config.contains(" offload:\n jumbo: false\n")) + assertTrue(config.contains(" multi-queue: true\n")) + assertTrue(config.contains(" tso: false\n")) + assertFalse(config.contains("uso:")) + } + + @Test + fun `local root keeps default offloads and configured MTU`() { + val config = RootProxyManager.buildHevConfig(null, null, 10808, true, 8500, false) + + assertFalse(config.contains("offload:")) + assertTrue(config.contains(" mtu: 8500\n")) + assertTrue(config.contains(" ipv6:")) + } + + @Test + fun `LAN offload guard preserves IPv4 only and quoted authentication`() { + val config = RootProxyManager.buildHevConfig("user'name", "pa'ss", 10809, false, 1500, true) + + assertTrue(config.contains(" offload:\n jumbo: false\n")) + assertFalse(config.contains("tso:")) + assertFalse(config.contains(" ipv6:")) + assertTrue(config.contains(" port: 10809\n")) + assertTrue(config.contains(" username: 'user''name'\n")) + assertTrue(config.contains(" password: 'pa''ss'\n")) + } + @Test fun `single quotes are escaped in YAML scalars`() { assertEquals("'user''name'", "user'name".toSingleQuotedYamlScalar()) diff --git a/hev-socks5-tunnel b/hev-socks5-tunnel index d9dca26c7..c5584feca 160000 --- a/hev-socks5-tunnel +++ b/hev-socks5-tunnel @@ -1 +1 @@ -Subproject commit d9dca26c7ad0e494492244f0309e80ee583e739e +Subproject commit c5584feca8f6df70ee53eb2879e034b849a3d15a