From 93084f0ed08a5aaba355179821a833ff6ea46cc5 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 6 Dec 2015 18:49:48 +0800 Subject: [PATCH] Ask for ROOT only when it's needed --- src/main/res/values-zh/strings.xml | 9 ++-- src/main/res/values/strings.xml | 9 ++-- src/main/res/xml/pref_all.xml | 10 +++-- .../com/github/shadowsocks/AppManager.scala | 4 +- .../com/github/shadowsocks/Shadowsocks.scala | 43 ++++++++----------- .../shadowsocks/ShadowsocksApplication.scala | 5 +-- .../shadowsocks/ShadowsocksNatService.scala | 20 ++++----- .../shadowsocks/ShadowsocksSettings.scala | 17 +++----- .../shadowsocks/ShadowsocksVpnService.scala | 6 +-- .../com/github/shadowsocks/package.scala | 13 ++++++ .../preferences/NatSwitchPreference.scala | 27 ------------ .../github/shadowsocks/utils/Console.scala | 14 ++---- .../com/github/shadowsocks/utils/Utils.scala | 17 ++++---- 13 files changed, 82 insertions(+), 112 deletions(-) create mode 100644 src/main/scala/com/github/shadowsocks/package.scala delete mode 100644 src/main/scala/com/github/shadowsocks/preferences/NatSwitchPreference.scala diff --git a/src/main/res/values-zh/strings.xml b/src/main/res/values-zh/strings.xml index c3910a87..b79e5088 100755 --- a/src/main/res/values-zh/strings.xml +++ b/src/main/res/values-zh/strings.xml @@ -34,6 +34,8 @@ 随系统启动后台服务 后台服务已开始运行。 无法连接远程服务器 + NAT 模式需要 ROOT 权限 + 切换到 VPN 模式 停止服务 正在关闭…… 后台服务启动失败:%s @@ -52,14 +54,15 @@ 关于 影梭(shadowsocks)%s 刷新 DNS 缓存 - 需要 ROOT 权限 + 只需在 NAT 模式下使用 + 只需在 NAT 模式下使用,需要 ROOT 权限 + 在没有 ROOT 权限的情况下无法刷新 DNS 缓存 刷新中…… 二维码 添加配置文件 NAT 模式 (仅限调试) - 从 VPN 模式切换为 NAT 模式 - NAT 模式已禁用,需要 ROOT 权限 + 从 VPN 模式切换为 NAT 模式,需要 ROOT 权限 UDP 转发 由远程服务器转发 UDP 协议的数据包 diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 7c154a9f..d3d7696f 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -10,8 +10,7 @@ Profile Switch to another profile or add new profiles NAT mode (deprecated) - Use NAT mode instead of VPN mode - NAT mode is disabled without root + Use NAT mode instead of VPN mode. Requires ROOT permission. Network Traffic Sent: \t\t\t\t\t%3$s\t↑\t%1$s/s\nReceived: \t%4$s\t↓\t%2$s/s \n%1$s↑\t%2$s↓ @@ -61,6 +60,8 @@ Shadowsocks started. Failed to connect the remote server + NAT mode requires ROOT permission + Switch to VPN mode Stop the service Shutting down… %s @@ -84,7 +85,9 @@ Shadowsocks %s QR Code Flush DNS cache - Requires ROOT permission + Only useful in NAT mode + Only useful in NAT mode. Requires ROOT permission. + Flush DNS cache cannot proceed without ROOT permission Flushing Add Profile Export to Clipboard diff --git a/src/main/res/xml/pref_all.xml b/src/main/res/xml/pref_all.xml index 20f23e2a..c5d7a3b5 100644 --- a/src/main/res/xml/pref_all.xml +++ b/src/main/res/xml/pref_all.xml @@ -83,12 +83,14 @@ android:summary="@string/auto_connect_summary" android:title="@string/auto_connect"/> - + - + diff --git a/src/main/scala/com/github/shadowsocks/AppManager.scala b/src/main/scala/com/github/shadowsocks/AppManager.scala index d08e8acb..81c17db3 100644 --- a/src/main/scala/com/github/shadowsocks/AppManager.scala +++ b/src/main/scala/com/github/shadowsocks/AppManager.scala @@ -58,8 +58,6 @@ import com.github.shadowsocks.utils.{Key, Utils} import scala.collection.JavaConversions._ import scala.collection.mutable -import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.Future import scala.language.implicitConversions object AppManager { @@ -249,7 +247,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC } def loadAppsAsync() { - Future { + ThrowableFuture { while (!appsLoading) loadApps() appsLoading = false handler.post(() => { diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index 37895f30..881c999e 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -65,8 +65,6 @@ import com.github.shadowsocks.utils._ import com.google.android.gms.ads.{AdRequest, AdSize, AdView} import scala.collection.mutable.ArrayBuffer -import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.Future object Typefaces { def get(c: Context, assetPath: String): Typeface = { @@ -182,8 +180,13 @@ class Shadowsocks handler.postDelayed(() => fabProgressCircle.hide(), 1000) fab.setEnabled(true) changeSwitch(checked = false) - if (m != null) Snackbar.make(findViewById(android.R.id.content), - getString(R.string.vpn_error).formatLocal(Locale.ENGLISH, m), Snackbar.LENGTH_LONG).show + if (m != null) { + val snackbar = Snackbar.make(findViewById(android.R.id.content), + getString(R.string.vpn_error).formatLocal(Locale.ENGLISH, m), Snackbar.LENGTH_LONG) + if (m == getString(R.string.nat_no_root)) snackbar.setAction(R.string.switch_to_vpn, + (_ => preferences.natSwitch.setChecked(false)): View.OnClickListener) + snackbar.show + } setPreferenceEnabled(enabled = true) case State.STOPPING => fab.setBackgroundTintList(greyTint) @@ -317,10 +320,10 @@ class Shadowsocks cmd.append("chmod 666 %s%s-vpn.pid".formatLocal(Locale.ENGLISH, Path.BASE, task)) } - if (!ShadowsocksApplication.isVpnEnabled) { - Console.runRootCommand(cmd.toArray) - } else { + if (ShadowsocksApplication.isVpnEnabled) { Console.runCommand(cmd.toArray) + } else { + Console.runRootCommand(cmd.toArray) } cmd.clear() @@ -341,8 +344,7 @@ class Shadowsocks cmd.append("rm -f %s%s-vpn.pid".formatLocal(Locale.ENGLISH, Path.BASE, task)) cmd.append("rm -f %s%s-vpn.conf".formatLocal(Locale.ENGLISH, Path.BASE, task)) } - Console.runCommand(cmd.toArray) - if (!ShadowsocksApplication.isVpnEnabled) { + if (ShadowsocksApplication.isVpnEnabled) Console.runCommand(cmd.toArray) else { Console.runRootCommand(cmd.toArray) Console.runRootCommand(Utils.getIptables + " -t nat -F OUTPUT") } @@ -361,7 +363,7 @@ class Shadowsocks } def prepareStartService() { - Future { + ThrowableFuture { if (ShadowsocksApplication.isVpnEnabled) { val intent = VpnService.prepare(this) if (intent != null) { @@ -534,18 +536,6 @@ class Shadowsocks handler.removeCallbacksAndMessages(null) } - def copyToSystem() { - val ab = new ArrayBuffer[String] - ab.append("mount -o rw,remount -t yaffs2 /dev/block/mtdblock3 /system") - for (executable <- Shadowsocks.EXECUTABLES) { - ab.append("cp %s%s /system/bin/".formatLocal(Locale.ENGLISH, Path.BASE, executable)) - ab.append("chmod 755 /system/bin/" + executable) - ab.append("chown root:shell /system/bin/" + executable) - } - ab.append("mount -o ro,remount -t yaffs2 /dev/block/mtdblock3 /system") - Console.runRootCommand(ab.toArray) - } - def install() { copyAssets(System.getABI) @@ -565,7 +555,7 @@ class Shadowsocks def recovery() { serviceStop() val h = showProgress(R.string.recovering) - Future { + ThrowableFuture { reset() h.sendEmptyMessage(0) } @@ -573,8 +563,9 @@ class Shadowsocks def flushDnsCache() { val h = showProgress(R.string.flushing) - Future { - Utils.toggleAirplaneMode(getBaseContext) + ThrowableFuture { + if (!Utils.toggleAirplaneMode(getBaseContext)) h.post(() => Snackbar.make(findViewById(android.R.id.content), + R.string.flush_dnscache_no_root, Snackbar.LENGTH_LONG).show) h.sendEmptyMessage(0) } } @@ -595,7 +586,7 @@ class Shadowsocks def checkText(key: String): Boolean = { val text = ShadowsocksApplication.settings.getString(key, "") if (text != null && text.length > 0) return true - Snackbar.make(findViewById(android.R.id.content), getString(R.string.proxy_empty), Snackbar.LENGTH_LONG).show + Snackbar.make(findViewById(android.R.id.content), R.string.proxy_empty, Snackbar.LENGTH_LONG).show false } diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala index 7bd76fe6..07eb2a60 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala @@ -46,7 +46,7 @@ import android.app.Application import android.content.pm.PackageManager import android.preference.PreferenceManager import com.github.shadowsocks.database.{DBHelper, ProfileManager} -import com.github.shadowsocks.utils.{Console, Key, Utils} +import com.github.shadowsocks.utils.{Key, Utils} import com.google.android.gms.analytics.{GoogleAnalytics, HitBuilders} import com.google.android.gms.common.api.ResultCallback import com.google.android.gms.tagmanager.{ContainerHolder, TagManager} @@ -60,8 +60,7 @@ object ShadowsocksApplication { lazy val settings = PreferenceManager.getDefaultSharedPreferences(instance) lazy val profileManager = new ProfileManager(settings, dbHelper) - val isRoot = Console.isRoot - def isVpnEnabled = !(isRoot && settings.getBoolean(Key.isNAT, !Utils.isLollipopOrAbove)) + def isVpnEnabled = !settings.getBoolean(Key.isNAT, false) def getVersionName = try { instance.getPackageManager.getPackageInfo(instance.getPackageName, 0).versionName diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala index 865cc586..26374ffc 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala @@ -55,8 +55,6 @@ import com.github.shadowsocks.utils._ import scala.collection.JavaConversions._ import scala.collection.mutable.ArrayBuffer -import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.Future class ShadowsocksNatService extends BaseService { @@ -144,7 +142,7 @@ class ShadowsocksNatService extends BaseService { }) Console.runRootCommand(cmdBuf.toArray) } else { - Console.runRootCommand(Array("ndc resolver flushdefaultif", "ndc resolver flushif wlan0")) + Console.runRootCommand("ndc resolver flushdefaultif", "ndc resolver flushif wlan0") } } @@ -384,12 +382,17 @@ class ShadowsocksNatService extends BaseService { } } } - Console.runRootCommand(init_sb.toArray) - Console.runRootCommand(http_sb.toArray) + Console.runRootCommand((init_sb ++ http_sb).toArray) } override def startRunner(config: Config) { + changeState(State.CONNECTING) + if (!Console.isRoot) { + changeState(State.STOPPED, getString(R.string.nat_no_root)) + return + } + super.startRunner(config) // register close receiver @@ -404,10 +407,7 @@ class ShadowsocksNatService extends BaseService { ShadowsocksApplication.track(TAG, "start") - changeState(State.CONNECTING) - - Future { - + ThrowableFuture { if (config.proxy == "198.199.101.152") { val holder = ShadowsocksApplication.containerHolder try { @@ -465,7 +465,7 @@ class ShadowsocksNatService extends BaseService { closeReceiver = null } - notification.destroy() + if (notification != null) notification.destroy() ShadowsocksApplication.track(TAG, "stop") diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala index b63c7d81..cc67d043 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala @@ -14,6 +14,7 @@ import com.github.shadowsocks.utils.Key // TODO: Move related logic here class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChangeListener { private lazy val activity = getActivity.asInstanceOf[Shadowsocks] + lazy val natSwitch = findPreference(Key.isNAT).asInstanceOf[SwitchPreference] private var isProxyApps: SwitchPreference = _ @@ -35,10 +36,8 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan }) val flush = findPreference("flush_dnscache") - if (Build.VERSION.SDK_INT >= 17 && !ShadowsocksApplication.isRoot) { - flush.setEnabled(false) - flush.setSummary(R.string.flush_dnscache_summary_disabled) - } else flush.setOnPreferenceClickListener((preference: Preference) => { + if (Build.VERSION.SDK_INT < 17) flush.setSummary(R.string.flush_dnscache_summary) + flush.setOnPreferenceClickListener(_ => { ShadowsocksApplication.track(Shadowsocks.TAG, "flush_dnscache") activity.flushDnsCache() true @@ -73,12 +72,10 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan } def onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) = key match { - case Key.isNAT => if (ShadowsocksApplication.isRoot && activity != null) { - activity.handler.post(() => { - activity.deattachService - activity.attachService - }) - } + case Key.isNAT => activity.handler.post(() => { + activity.deattachService + activity.attachService + }) case _ => } } diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index 3140baaf..ac832422 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -55,8 +55,6 @@ import org.apache.commons.net.util.SubnetUtils import scala.collection.JavaConversions._ import scala.collection.mutable.ArrayBuffer -import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.Future class ShadowsocksVpnService extends VpnService with BaseService { val TAG = "ShadowsocksVpnService" @@ -122,7 +120,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { vpnThread = null } - notification.destroy() + if (notification != null) notification.destroy() // channge the state changeState(State.STOPPING) @@ -213,7 +211,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { changeState(State.CONNECTING) - Future { + ThrowableFuture { if (config.proxy == "198.199.101.152") { val holder = ShadowsocksApplication.containerHolder try { diff --git a/src/main/scala/com/github/shadowsocks/package.scala b/src/main/scala/com/github/shadowsocks/package.scala new file mode 100644 index 00000000..a6db148d --- /dev/null +++ b/src/main/scala/com/github/shadowsocks/package.scala @@ -0,0 +1,13 @@ +package com.github + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.Future + +/** + * @author Mygod + */ +package object shadowsocks { + def ThrowableFuture[T](f: => T) = Future(f) onFailure { + case e: Throwable => e.printStackTrace() + } +} diff --git a/src/main/scala/com/github/shadowsocks/preferences/NatSwitchPreference.scala b/src/main/scala/com/github/shadowsocks/preferences/NatSwitchPreference.scala deleted file mode 100644 index 4471c329..00000000 --- a/src/main/scala/com/github/shadowsocks/preferences/NatSwitchPreference.scala +++ /dev/null @@ -1,27 +0,0 @@ -package com.github.shadowsocks.preferences - -import android.content.Context -import android.content.res.TypedArray -import android.preference.SwitchPreference -import android.util.AttributeSet -import com.github.shadowsocks.{R, ShadowsocksApplication} - -/** - * TODO: No animations? - * @author Mygod - */ -final class NatSwitchPreference(context: Context, attrs: AttributeSet) extends SwitchPreference(context, attrs) { - private var disabled = false - - override def isEnabled = super.isEnabled && !disabled - override def setEnabled(b: Boolean) = super.setEnabled(b && !disabled) - - if (!ShadowsocksApplication.isRoot) { - disabled = true - setEnabled(false) - setSummary(R.string.nat_summary_no_root) - } - - override def onGetDefaultValue(a: TypedArray, index: Int) = - (!ShadowsocksApplication.isVpnEnabled).asInstanceOf[AnyRef] -} diff --git a/src/main/scala/com/github/shadowsocks/utils/Console.scala b/src/main/scala/com/github/shadowsocks/utils/Console.scala index 60edd8f7..042dc9ff 100644 --- a/src/main/scala/com/github/shadowsocks/utils/Console.scala +++ b/src/main/scala/com/github/shadowsocks/utils/Console.scala @@ -75,17 +75,11 @@ object Console { shell.close() } - def runRootCommand(command: String): String = runRootCommand(Array(command)) - def runRootCommand(command: String, context: String): String = runRootCommand(Array(command), context) - def runRootCommand(commands: Array[String]): String = runRootCommand(commands, "u:r:init_shell:s0") - - def runRootCommand(commands: Array[String], context: String): String = { - if (!isRoot) { - return null - } - val shell = openRootShell(context) + def runRootCommand(commands: String*): String = runRootCommand(commands.toArray) + def runRootCommand(commands: Array[String]): String = { + val shell = openRootShell("u:r:init_shell:s0") val sb = new StringBuilder - shell.addCommand(commands, 0, ((commandCode: Int, exitCode: Int, output: util.List[String]) => { + shell.addCommand(commands, 0, ((_, exitCode, output) => { if (exitCode < 0) { shell.close() } else { diff --git a/src/main/scala/com/github/shadowsocks/utils/Utils.scala b/src/main/scala/com/github/shadowsocks/utils/Utils.scala index f0f7301d..3754efaa 100644 --- a/src/main/scala/com/github/shadowsocks/utils/Utils.scala +++ b/src/main/scala/com/github/shadowsocks/utils/Utils.scala @@ -140,12 +140,14 @@ object Utils { } // Blocked > 3 seconds - def toggleAirplaneMode(context: Context) { - if (ShadowsocksApplication.isRoot) { - Console.runRootCommand(Array("ndc resolver flushdefaultif", "ndc resolver flushif wlan0")) + def toggleAirplaneMode(context: Context) = { + if (Console.isRoot) { + Console.runRootCommand("ndc resolver flushdefaultif", "ndc resolver flushif wlan0") + true } else if (Build.VERSION.SDK_INT < 17) { toggleBelowApiLevel17(context) - } + true + } else false } //noinspection ScalaDeprecation @@ -305,8 +307,7 @@ object Utils { var compatible: Boolean = false var version: Boolean = false - val command = Array(iptables + " --version", iptables + " -L -t nat -n") - val lines = Console.runRootCommand(command) + val lines = Console.runRootCommand(iptables + " --version", iptables + " -L -t nat -n") if (lines == null) return if (lines.contains("OUTPUT")) { @@ -384,9 +385,7 @@ object Utils { hasRedirectSupport = 1 val command = Utils.getIptables + " -t nat -A OUTPUT -p udp --dport 54 -j REDIRECT --to 8154" - val lines = Console.runRootCommand(command) - - Console.runRootCommand(command.replace("-A", "-D")) + val lines = Console.runRootCommand(command, command.replace("-A", "-D")) if (lines == null) return if (lines.contains("No chain/target/match")) { hasRedirectSupport = 0