Sfoglia il codice sorgente

Add auto mtu / shadowsocks ReducedIvHeadEntropy option

世界 3 anni fa
parent
commit
b37130a45a

+ 2 - 0
app/src/main/java/io/nekohasekai/sagernet/Constants.kt

@@ -93,6 +93,7 @@ object Key {
     const val TUN_IMPLEMENTATION = "tunImplementation"
     const val ENABLE_PCAP = "enablePcap"
     const val MTU = "mtu"
+    const val USE_UPSTREAM_INTERFACE_MTU = "useUpstreamInterfaceMTU"
 
     const val APP_TRAFFIC_STATISTICS = "appTrafficStatistics"
     const val PROFILE_TRAFFIC_STATISTICS = "profileTrafficStatistics"
@@ -158,6 +159,7 @@ object Key {
     const val SERVER_LOCAL_ADDRESS = "serverLocalAddress"
     const val SERVER_INSECURE_CONCURRENCY = "serverInsecureConcurrency"
     const val SERVER_MTU = "serverMTU"
+    const val SERVER_REDUCED_IV_HEAD_ENTROPY = "serverReducedIvHeadEntropy"
 
     const val BALANCER_TYPE = "balancerType"
     const val BALANCER_GROUP = "balancerGroup"

+ 3 - 7
app/src/main/java/io/nekohasekai/sagernet/SagerNet.kt

@@ -243,7 +243,7 @@ class SagerNet : Application(),
             }
         }
 
-        fun reloadNetworkType(network: Network?) {
+        fun reloadNetwork(network: Network?) {
             val capabilities = connectivity.getNetworkCapabilities(network) ?: return
             val networkType = when {
                 capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> "wifi"
@@ -253,12 +253,9 @@ class SagerNet : Application(),
                 else -> "data"
             }
             Libcore.setNetworkType(networkType)
-        }
-
-        fun reloadSSID(network: Network?) {
             var ssid: String? = null
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
-                when (val transportInfo = connectivity.getNetworkCapabilities(network)?.transportInfo) {
+                when (val transportInfo = capabilities.transportInfo) {
                     is WifiInfo -> {
                         ssid = transportInfo.ssid
                     }
@@ -267,8 +264,7 @@ class SagerNet : Application(),
                 val wifiInfo = wifi.connectionInfo
                 ssid = wifiInfo?.ssid
             }
-            ssid = ssid?.trim { it == '"' } ?: ""
-            Libcore.setWifiSSID(ssid)
+            Libcore.setWifiSSID(ssid?.trim { it == '"' } ?: "")
         }
 
         fun startService() = ContextCompat.startForegroundService(

+ 1 - 2
app/src/main/java/io/nekohasekai/sagernet/bg/ProxyService.kt

@@ -36,8 +36,7 @@ class ProxyService : Service(), BaseService.Interface {
         ServiceNotification(this, profileName, "service-proxy", true)
 
     override suspend fun preInit() = DefaultNetworkListener.start(this) {
-        SagerNet.reloadSSID(it)
-        SagerNet.reloadNetworkType(it)
+        SagerNet.reloadNetwork(it)
     }
 
     @Suppress("EXPERIMENTAL_API_USAGE")

+ 80 - 11
app/src/main/java/io/nekohasekai/sagernet/bg/VpnService.kt

@@ -33,9 +33,7 @@ import android.os.CancellationSignal
 import android.os.ParcelFileDescriptor
 import android.system.ErrnoException
 import android.system.Os
-import android.system.OsConstants
 import android.util.Log
-import androidx.annotation.RequiresApi
 import cn.hutool.core.lang.Validator
 import io.nekohasekai.sagernet.*
 import io.nekohasekai.sagernet.database.DataStore
@@ -55,7 +53,7 @@ import libcore.*
 import java.io.FileDescriptor
 import java.io.IOException
 import java.net.InetAddress
-import java.net.InetSocketAddress
+import java.net.NetworkInterface
 import java.net.UnknownHostException
 import kotlin.coroutines.suspendCoroutine
 import android.net.VpnService as BaseVpnService
@@ -148,13 +146,25 @@ class VpnService : BaseVpnService(),
         return Service.START_NOT_STICKY
     }
 
+    var upstreamInterfaceMTU = 0
     override suspend fun preInit() = DefaultNetworkListener.start(this) {
         underlyingNetwork = it
-        SagerNet.reloadSSID(it)
-        SagerNet.reloadNetworkType(it)
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
-            SagerNet.connectivity.getLinkProperties(it)?.interfaceName?.also { interfaceName ->
-                Libcore.bindNetworkName(interfaceName)
+        SagerNet.reloadNetwork(it)
+        SagerNet.connectivity.getLinkProperties(it)?.also { link ->
+            var mtu = 0
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+                mtu = link.mtu
+            }
+            if (mtu == 0) {
+                mtu = NetworkInterface.getByName(link.interfaceName).mtu
+            }
+            if (upstreamInterfaceMTU != mtu) {
+                upstreamInterfaceMTU = mtu
+                Logs.d("Updated upstream network MTU: $upstreamInterfaceMTU")
+                if (data.state.canStop) forceLoad()
+            }
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+                Libcore.bindNetworkName(link.interfaceName)
             }
         }
     }
@@ -164,13 +174,72 @@ class VpnService : BaseVpnService(),
         override fun getLocalizedMessage() = getString(R.string.reboot_required)
     }
 
+    fun getMTU(network: Network): Int {
+        var mtu = 0
+        SagerNet.connectivity.getLinkProperties(network)?.also { link ->
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+                mtu = link.mtu
+            }
+            if (mtu == 0) {
+                mtu = NetworkInterface.getByName(link.interfaceName).mtu
+            }
+        }
+        return mtu
+    }
+
+    fun getActiveNetworkUnder23(): Network? {
+        val activeInfo = SagerNet.connectivity.activeNetworkInfo ?: return null
+        for (network in SagerNet.connectivity.allNetworks) {
+            val info = SagerNet.connectivity.getNetworkInfo(network) ?: continue
+            if (info.type != activeInfo.type) continue
+            if (info.isConnected != activeInfo.isConnected) continue
+            if (info.isAvailable != activeInfo.isAvailable) continue
+            return network
+        }
+        return null
+    }
+
     private fun startVpn() {
         instance = this
 
+        var mtuFinal = 0
+        val useUpstreamInterfaceMTU = DataStore.useUpstreamInterfaceMTU
+        if (useUpstreamInterfaceMTU) {
+            if (upstreamInterfaceMTU > 0) {
+                mtuFinal = upstreamInterfaceMTU
+                Logs.d("Use MTU of upstream network: $upstreamInterfaceMTU")
+            } else {
+                val network = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                    SagerNet.connectivity.activeNetwork
+                } else {
+                    getActiveNetworkUnder23()
+                }
+                if (network != null) {
+                    try {
+                        mtuFinal = getMTU(network)
+                        upstreamInterfaceMTU = mtuFinal
+                        Logs.d("Use MTU of upstream network: $mtuFinal")
+                    } catch (e: Exception) {
+                        Logs.w("Failed to get MTU of current network", e)
+                    }
+                } else {
+                    Logs.d("Failed to get current network")
+                }
+                if (mtuFinal == 0) {
+                    mtuFinal = DataStore.mtu
+                }
+            }
+        } else {
+            upstreamInterfaceMTU = DataStore.mtu
+            Logs.d("Use MTU: $upstreamInterfaceMTU")
+        }
+
         val profile = data.proxy!!.profile
         val builder = Builder().setConfigureIntent(SagerNet.configureIntent(this))
             .setSession(profile.displayName())
-            .setMtu(DataStore.mtu)
+        if (!useUpstreamInterfaceMTU) {
+            builder.setMtu(mtuFinal)
+        }
         val ipv6Mode = DataStore.ipv6Mode
 
         builder.addAddress(PRIVATE_VLAN4_CLIENT, 30)
@@ -263,15 +332,15 @@ class VpnService : BaseVpnService(),
         }
 
         metered = DataStore.meteredNetwork
-        active = true   // possible race condition here?
         if (Build.VERSION.SDK_INT >= 29) builder.setMetered(metered)
 
         conn = builder.establish() ?: throw NullConnectionException()
+        active = true   // possible race condition here?
 
         val config = TunConfig().apply {
             fileDescriptor = conn.fd
             protect = needIncludeSelf
-            mtu = DataStore.mtu
+            mtu = mtuFinal
             v2Ray = data.proxy!!.v2rayPoint
             gateway4 = PRIVATE_VLAN4_GATEWAY
             gateway6 = PRIVATE_VLAN6_GATEWAY

+ 3 - 0
app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt

@@ -202,6 +202,8 @@ object DataStore : OnPreferenceDataStoreChangeListener {
     var alwaysShowAddress by configurationStore.boolean(Key.ALWAYS_SHOW_ADDRESS)
 
     var tunImplementation by configurationStore.stringToInt(Key.TUN_IMPLEMENTATION) { TunImplementation.GVISOR }
+
+    var useUpstreamInterfaceMTU by configurationStore.boolean(Key.USE_UPSTREAM_INTERFACE_MTU) { true }
     var mtu by configurationStore.stringToInt(Key.MTU) { VpnService.DEFAULT_MTU }
 
     var appTrafficStatistics by configurationStore.boolean(Key.APP_TRAFFIC_STATISTICS)
@@ -266,6 +268,7 @@ object DataStore : OnPreferenceDataStoreChangeListener {
     var serverLocalAddress by profileCacheStore.string(Key.SERVER_LOCAL_ADDRESS)
     var serverInsecureConcurrency by profileCacheStore.stringToInt(Key.SERVER_INSECURE_CONCURRENCY)
     var serverMTU by profileCacheStore.stringToInt(Key.SERVER_MTU)
+    var serverReducedIvHeadEntropy by profileCacheStore.boolean(Key.SERVER_REDUCED_IV_HEAD_ENTROPY)
 
     var balancerType by profileCacheStore.stringToInt(Key.BALANCER_TYPE)
     var balancerGroup by profileCacheStore.stringToLong(Key.BALANCER_GROUP)

+ 1 - 0
app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt

@@ -707,6 +707,7 @@ fun buildV2RayConfig(
                                                     is ShadowsocksBean -> {
                                                         method = bean.method
                                                         password = bean.password
+                                                        experimentReducedIvHeadEntropy = bean.experimentReducedIvHeadEntropy
                                                     }
                                                     is ShadowsocksRBean -> {
                                                         method = bean.method

+ 8 - 1
app/src/main/java/io/nekohasekai/sagernet/fmt/shadowsocks/ShadowsocksBean.java

@@ -36,6 +36,8 @@ public class ShadowsocksBean extends AbstractBean {
     public String password;
     public String plugin;
 
+    public Boolean experimentReducedIvHeadEntropy;
+
     @Override
     public void initializeDefaultValues() {
         super.initializeDefaultValues();
@@ -44,15 +46,17 @@ public class ShadowsocksBean extends AbstractBean {
         if (method == null) method = "";
         if (password == null) password = "";
         if (plugin == null) plugin = "";
+        if (experimentReducedIvHeadEntropy == null) experimentReducedIvHeadEntropy = false;
     }
 
     @Override
     public void serialize(ByteBufferOutput output) {
-        output.writeInt(0);
+        output.writeInt(1);
         super.serialize(output);
         output.writeString(method);
         output.writeString(password);
         output.writeString(plugin);
+        output.writeBoolean(experimentReducedIvHeadEntropy);
     }
 
     @Override
@@ -62,6 +66,9 @@ public class ShadowsocksBean extends AbstractBean {
         method = input.readString();
         password = input.readString();
         plugin = input.readString();
+        if (version >= 1) {
+            experimentReducedIvHeadEntropy = input.readBoolean();
+        }
     }
 
     @NotNull

+ 1 - 0
app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/V2RayConfig.java

@@ -597,6 +597,7 @@ public class V2RayConfig {
             public String password;
             public Integer level;
             public String email;
+            public Boolean experimentReducedIvHeadEntropy;
 
         }
 

+ 6 - 4
app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt

@@ -215,9 +215,6 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
         val enablePcap = findPreference<SwitchPreference>(Key.ENABLE_PCAP)!!
         val providerRootCA = findPreference<SimpleMenuPreference>(Key.PROVIDER_ROOT_CA)!!
 
-        val mtu = findPreference<EditTextPreference>(Key.MTU)!!
-        mtu.setOnBindEditTextListener(EditTextPreferenceModifiers.Number)
-
         providerRootCA.setOnPreferenceChangeListener { _, newValue ->
             val useSystem = (newValue as String) == "${RootCAProvider.SYSTEM}"
             Libcore.updateSystemRoots(useSystem)
@@ -226,6 +223,11 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
             true
         }
 
+        val mtu = findPreference<EditTextPreference>(Key.MTU)!!
+        mtu.setOnBindEditTextListener(EditTextPreferenceModifiers.Number)
+
+        val useUpstreamInterfaceMTU = findPreference<SwitchPreference>(Key.USE_UPSTREAM_INTERFACE_MTU)!!
+
         speedInterval.onPreferenceChangeListener = reloadListener
         portSocks5.onPreferenceChangeListener = reloadListener
         portHttp.onPreferenceChangeListener = reloadListener
@@ -260,7 +262,7 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
         destinationOverride.onPreferenceChangeListener = reloadListener
         resolveDestination.onPreferenceChangeListener = reloadListener
         mtu.onPreferenceChangeListener = reloadListener
-
+        useUpstreamInterfaceMTU.onPreferenceChangeListener = reloadListener
 
         enablePcap.setOnPreferenceChangeListener { _, newValue ->
             if (newValue as Boolean) {

+ 2 - 1
app/src/main/java/io/nekohasekai/sagernet/ui/profile/ShadowsocksSettingsActivity.kt

@@ -69,6 +69,7 @@ class ShadowsocksSettingsActivity : ProfileSettingsActivity<ShadowsocksBean>(),
         DataStore.serverMethod = method
         DataStore.serverPassword = password
         DataStore.serverPlugin = plugin
+        DataStore.serverReducedIvHeadEntropy = experimentReducedIvHeadEntropy;
     }
 
     override fun ShadowsocksBean.serialize() {
@@ -78,7 +79,7 @@ class ShadowsocksSettingsActivity : ProfileSettingsActivity<ShadowsocksBean>(),
         method = DataStore.serverMethod
         password = DataStore.serverPassword
         plugin = DataStore.serverPlugin
-
+        experimentReducedIvHeadEntropy = DataStore.serverReducedIvHeadEntropy
     }
 
     override fun onAttachedToWindow() {

+ 4 - 0
app/src/main/res/values/strings.xml

@@ -504,5 +504,9 @@
 
     <string name="packet_encoding">Packet Encoding</string>
     <string name="mtu">MTU</string>
+    <string name="experimental_reduced_iv_head_entropy">This is GFWReport\'s proposal for a countermeasure for the random stream like protocol blocking behaviour observed on GFW.
+\n\nThis option remap the first 6 bytes of IV to printable characters, it\'s possible for anyone on the privileged network path to identify the protocol when enabled.</string>
+    <string name="auto_mtu">Auto MTU</string>
+    <string name="auto_mtu_summary">Use MTU of the upstream network interface</string>
 
 </resources>

+ 6 - 0
app/src/main/res/xml/global_preferences.xml

@@ -41,6 +41,12 @@
             app:key="tunImplementation"
             app:title="@string/tun_implementation"
             app:useSimpleSummaryProvider="true" />
+        <SwitchPreference
+            app:defaultValue="true"
+            app:icon="@drawable/ic_hardware_router"
+            app:key="useUpstreamInterfaceMTU"
+            app:summary="@string/auto_mtu_summary"
+            app:title="@string/auto_mtu" />
         <EditTextPreference
             app:icon="@drawable/baseline_public_24"
             app:key="mtu"

+ 12 - 5
app/src/main/res/xml/shadowsocks_preferences.xml

@@ -31,21 +31,28 @@
             app:title="@string/password" />
     </PreferenceCategory>
 
-    <PreferenceCategory
-        app:title="@string/plugin">
+    <PreferenceCategory app:title="@string/plugin">
 
         <com.github.shadowsocks.preference.PluginPreference
             app:key="serverPlugin"
             app:persistent="false"
             app:title="@string/plugin"
-            app:useSimpleSummaryProvider="true"/>
+            app:useSimpleSummaryProvider="true" />
         <EditTextPreference
-            app:key="serverPluginConfigure"
             app:icon="@drawable/ic_action_settings"
+            app:key="serverPluginConfigure"
             app:persistent="false"
             app:title="@string/plugin_configure"
-            app:useSimpleSummaryProvider="true"/>
+            app:useSimpleSummaryProvider="true" />
     </PreferenceCategory>
 
+    <PreferenceCategory
+        app:icon="@drawable/ic_baseline_grid_3x3_24"
+        app:title="@string/experimental_settings">
+        <com.takisoft.preferencex.SwitchPreferenceCompat
+            app:key="serverReducedIvHeadEntropy"
+            app:summary="@string/experimental_reduced_iv_head_entropy"
+            app:title="ReducedIvHeadEntropy" />
+    </PreferenceCategory>
 
 </PreferenceScreen>

+ 1 - 1
external/preferencex

@@ -1 +1 @@
-Subproject commit 2bdf5a06bc242f5d5f01aa66b88ea640c938f243
+Subproject commit 8bdb0c6ae44f378b073c6a1c850d03d729b70ff8

+ 1 - 1
external/v2ray-core

@@ -1 +1 @@
-Subproject commit 334013497f8801dce737392fd90a05e732e72361
+Subproject commit f8bbbd17c0cca7e8ce2d2ce77abc0ae58dcef626

+ 1 - 1
library/core

@@ -1 +1 @@
-Subproject commit 5ea677865136705ef4bc7c0888bb1d289ed5fc07
+Subproject commit 9f0ee5d2158640d1a9ca80c2970701db8bcf4fff