diff --git a/.gitmodules b/.gitmodules index 8e93b3ff..8c1d844a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,10 +5,6 @@ path = src/main/jni/badvpn url = https://github.com/shadowsocks/badvpn.git branch = shadowsocks-android -[submodule "src/main/jni/iptables"] - path = src/main/jni/iptables - url = https://github.com/shadowsocks/iptables.git - branch = shadowsocks-android [submodule "src/main/jni/libancillary"] path = src/main/jni/libancillary url = https://github.com/shadowsocks/libancillary.git diff --git a/build.sbt b/build.sbt index 25808fdf..eedbba28 100644 --- a/build.sbt +++ b/build.sbt @@ -33,7 +33,7 @@ useSupportVectors libraryDependencies ++= Seq( "dnsjava" % "dnsjava" % "2.1.7", "com.github.kevinsawicki" % "http-request" % "6.0", - "eu.chainfire" % "libsuperuser" % "1.0.0.201602271131", + "eu.chainfire" % "libsuperuser" % "1.0.0.201607041850", "net.glxn.qrgen" % "android" % "2.0", "com.google.android.gms" % "play-services-ads" % "9.2.0", "com.google.android.gms" % "play-services-analytics" % "9.2.0", diff --git a/src/main/jni/Android.mk b/src/main/jni/Android.mk index 7c53ab1c..46cca351 100755 --- a/src/main/jni/Android.mk +++ b/src/main/jni/Android.mk @@ -552,14 +552,5 @@ openssl_subdirs := $(addprefix $(LOCAL_PATH)/openssl/,$(addsuffix /Android.mk, \ )) include $(openssl_subdirs) -# Iptables -# LOCAL_PATH := $(ROOT_PATH) -# iptables_subdirs := $(addprefix $(LOCAL_PATH)/iptables/,$(addsuffix /Android.mk, \ -# iptables \ -# extensions \ -# libiptc \ -# )) -# include $(iptables_subdirs) - # Import cpufeatures $(call import-module,android/cpufeatures) diff --git a/src/main/jni/iptables b/src/main/jni/iptables deleted file mode 160000 index 67560ab1..00000000 --- a/src/main/jni/iptables +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 67560ab12c1ff3413f971f9f1ba59ff28cb052ce diff --git a/src/main/scala/com/github/shadowsocks/BaseService.scala b/src/main/scala/com/github/shadowsocks/BaseService.scala index eccec110..d2cea36d 100644 --- a/src/main/scala/com/github/shadowsocks/BaseService.scala +++ b/src/main/scala/com/github/shadowsocks/BaseService.scala @@ -142,9 +142,7 @@ trait BaseService extends Service { profile.password = proxy(2).trim profile.method = proxy(3).trim } catch { - case ex: Exception => - changeState(State.STOPPED, getString(R.string.service_failed)) - stopRunner(true) + case ex: Exception => stopRunner(true, getString(R.string.service_failed)) } def startRunner(profile: Profile) { @@ -171,7 +169,7 @@ trait BaseService extends Service { Utils.ThrowableFuture(connect) } - def stopRunner(stopService: Boolean) { + def stopRunner(stopService: Boolean, msg: String = null) { // clean up recevier if (closeReceiverRegistered) { unregisterReceiver(closeReceiver) @@ -188,7 +186,7 @@ trait BaseService extends Service { } // change the state - changeState(State.STOPPED) + changeState(State.STOPPED, msg) // stop the service if nothing has bound to it if (stopService) stopSelf() @@ -244,7 +242,7 @@ trait BaseService extends Service { protected def changeState(s: Int, msg: String = null) { val handler = new Handler(getMainLooper) - handler.post(() => if (state != s) { + handler.post(() => if (state != s || msg != null) { if (callbacksCount > 0) { val n = callbacks.beginBroadcast() for (i <- 0 until n) { diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index 6f6d1f39..930e2c91 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -65,6 +65,7 @@ import com.github.shadowsocks.utils.CloseUtils._ import com.github.shadowsocks.utils._ import com.github.shadowsocks.ShadowsocksApplication.app import com.google.android.gms.ads.{AdRequest, AdSize, AdView} +import eu.chainfire.libsuperuser.Shell import scala.collection.mutable.ArrayBuffer @@ -110,7 +111,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext { // Services private val callback = new IShadowsocksServiceCallback.Stub { def stateChanged(s: Int, m: String) { - handler.post(() => if (state != s) { + handler.post(() => { s match { case State.CONNECTING => fab.setBackgroundTintList(greyTint) @@ -286,34 +287,17 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext { val cmd = new ArrayBuffer[String]() for (task <- Array("ss-local", "ss-tunnel", "pdnsd", "redsocks", "tun2socks")) { - cmd.append("chmod 666 %s/%s-nat.pid".formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task)) - cmd.append("chmod 666 %s/%s-vpn.pid".formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task)) - } - - if (app.isNatEnabled) Console.runRootCommand(cmd.toArray) else Console.runCommand(cmd.toArray) - - cmd.clear() - - for (task <- Array("ss-local", "ss-tunnel", "pdnsd", "redsocks", "tun2socks")) { - try { - val pid_nat = scala.io.Source.fromFile(getApplicationInfo.dataDir + "/" + task + "-nat.pid").mkString.trim.toInt - val pid_vpn = scala.io.Source.fromFile(getApplicationInfo.dataDir + "/" + task + "-vpn.pid").mkString.trim.toInt - cmd.append("kill -9 %d".formatLocal(Locale.ENGLISH, pid_nat)) - cmd.append("kill -9 %d".formatLocal(Locale.ENGLISH, pid_vpn)) - Process.killProcess(pid_nat) - Process.killProcess(pid_vpn) - } catch { - case e: Throwable => // Ignore - } - cmd.append("rm -f %s/%s-nat.pid".formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task)) - cmd.append("rm -f %s/%s-nat.conf".formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task)) - cmd.append("rm -f %s/%s-vpn.pid".formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task)) - cmd.append("rm -f %s/%s-vpn.conf".formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task)) + cmd.append("killall %s".formatLocal(Locale.ENGLISH, task)) + cmd.append("rm -f %1$s/%2$s-nat.conf %1$s/%2$s-vpn.conf" + .formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task)) } if (app.isNatEnabled) { - Console.runRootCommand(cmd.toArray) - Console.runRootCommand(Utils.iptables + " -t nat -F OUTPUT") - } else Console.runCommand(cmd.toArray) + cmd.append("iptables -t nat -F OUTPUT") + cmd.append("echo done") + val result = Shell.SU.run(cmd.toArray) + if (result != null && !result.isEmpty) return // fallback to SH + } + Shell.SH.run(cmd.toArray) } def cancelStart() { @@ -535,7 +519,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext { for (executable <- EXECUTABLES) { ab.append("chmod 755 " + getApplicationInfo.dataDir + "/" + executable) } - Console.runCommand(ab.toArray) + Shell.SH.run(ab.toArray) } def recovery() { diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala index f213f3aa..40b1b36a 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala @@ -50,6 +50,7 @@ import android.util.Log import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.database.Profile import com.github.shadowsocks.utils._ +import eu.chainfire.libsuperuser.Shell import scala.collection.JavaConversions._ import scala.collection.mutable.ArrayBuffer @@ -58,8 +59,7 @@ class ShadowsocksNatService extends BaseService { val TAG = "ShadowsocksNatService" - val CMD_IPTABLES_RETURN = " -t nat -A OUTPUT -p tcp -d 0.0.0.0 -j RETURN" - val CMD_IPTABLES_DNAT_ADD_SOCKS = " -t nat -A OUTPUT -p tcp " + + val CMD_IPTABLES_DNAT_ADD_SOCKS = "iptables -t nat -A OUTPUT -p tcp " + "-j DNAT --to-destination 127.0.0.1:8123" private var notification: ShadowsocksNotification = _ @@ -69,6 +69,7 @@ class ShadowsocksNatService extends BaseService { var sstunnelProcess: Process = _ var redsocksProcess: Process = _ var pdnsdProcess: Process = _ + var su: Shell.Interactive = _ def startShadowsocksDaemon() { if (profile.route != Route.ALL) { @@ -191,7 +192,7 @@ class ShadowsocksNatService extends BaseService { } /** Called when the activity is first created. */ - def handleConnection: Boolean = { + def handleConnection { startTunnel() if (!profile.udpdns) startDnsDaemon() @@ -199,7 +200,6 @@ class ShadowsocksNatService extends BaseService { startShadowsocksDaemon() setupIptables() - true } def onBind(intent: Intent): IBinder = { @@ -229,7 +229,7 @@ class ShadowsocksNatService extends BaseService { pdnsdProcess = null } - Console.runRootCommand(Utils.iptables + " -t nat -F OUTPUT") + su.addCommand("iptables -t nat -F OUTPUT") } def setupIptables() = { @@ -237,9 +237,9 @@ class ShadowsocksNatService extends BaseService { val http_sb = new ArrayBuffer[String] init_sb.append("ulimit -n 4096") - init_sb.append(Utils.iptables + " -t nat -F OUTPUT") + init_sb.append("iptables -t nat -F OUTPUT") - val cmd_bypass = Utils.iptables + CMD_IPTABLES_RETURN + val cmd_bypass = "iptables -t nat -A OUTPUT -p tcp -d 0.0.0.0 -j RETURN" if (!InetAddress.getByName(profile.host.toUpperCase).isInstanceOf[Inet6Address]) { init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-d " + profile.host)) } @@ -247,18 +247,17 @@ class ShadowsocksNatService extends BaseService { init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-m owner --uid-owner " + myUid)) init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "--dport 53")) - init_sb.append(Utils.iptables - + " -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:8153") + init_sb.append("iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:8153") if (!profile.proxyApps || profile.bypass) { - http_sb.append(Utils.iptables + CMD_IPTABLES_DNAT_ADD_SOCKS) + http_sb.append(CMD_IPTABLES_DNAT_ADD_SOCKS) } if (profile.proxyApps) { val uidMap = getPackageManager.getInstalledApplications(0).map(ai => ai.packageName -> ai.uid).toMap for (pn <- profile.individual.split('\n')) uidMap.get(pn) match { case Some(uid) => if (!profile.bypass) { - http_sb.append((Utils.iptables + CMD_IPTABLES_DNAT_ADD_SOCKS) + http_sb.append(CMD_IPTABLES_DNAT_ADD_SOCKS .replace("-t nat", "-t nat -m owner --uid-owner " + uid)) } else { init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "-m owner --uid-owner " + uid)) @@ -266,31 +265,38 @@ class ShadowsocksNatService extends BaseService { case _ => // probably removed package, ignore } } - Console.runRootCommand((init_sb ++ http_sb).toArray) + su.addCommand((init_sb ++ http_sb).toArray) } - override def startRunner(profile: Profile) { - if (!Console.isRoot) { - changeState(State.STOPPED, getString(R.string.nat_no_root)) - stopRunner(true) - return + override def startRunner(profile: Profile) = if (su == null) { + su = new Shell.Builder().useSU().setWantSTDERR(true).setWatchdogTimeout(10).open((_, exitCode, _) => + if (exitCode == 0) super.startRunner(profile) else { + Log.wtf(TAG, "libsuperuser#55 has been fixed. Please remove the redundant code.") + su.close() + su = null + super.stopRunner(true, getString(R.string.nat_no_root)) + }) + su.waitForIdle() + if (!su.isRunning) { + su.close() + su = null + super.stopRunner(true, getString(R.string.nat_no_root)) } - super.startRunner(profile) } override def connect() { super.connect() - if (this.profile != null) { + if (profile != null) { // Clean up killProcesses() - var resolved: Boolean = false - if (!Utils.isNumeric(this.profile.host)) { - Utils.resolve(this.profile.host, enableIPv6 = true) match { + var resolved = false + if (!Utils.isNumeric(profile.host)) { + Utils.resolve(profile.host, enableIPv6 = true) match { case Some(a) => - this.profile.host = a + profile.host = a resolved = true case None => resolved = false } @@ -298,33 +304,31 @@ class ShadowsocksNatService extends BaseService { resolved = true } - if (!resolved) { - changeState(State.STOPPED, getString(R.string.invalid_server)) - stopRunner(true) - } else if (handleConnection) { + if (!resolved) stopRunner(true, getString(R.string.invalid_server)) else { + handleConnection // Set DNS - Utils.flushDns() + su.addCommand(Utils.FLUSH_DNS) changeState(State.CONNECTED) notification = new ShadowsocksNotification(this, profile.name, true) - } else { - changeState(State.STOPPED, getString(R.string.service_failed)) - stopRunner(true) } } } - override def stopRunner(stopService: Boolean) { + override def stopRunner(stopService: Boolean, msg: String = null) { + + if (notification != null) notification.destroy() // channge the state changeState(State.STOPPING) - if (notification != null) notification.destroy() - app.track(TAG, "stop") // reset NAT killProcesses() - super.stopRunner(stopService) + su.close() + su = null + + super.stopRunner(stopService, msg) } } diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index b7a38c51..213e313a 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -82,7 +82,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { stopRunner(true) } - override def stopRunner(stopService: Boolean) { + override def stopRunner(stopService: Boolean, msg: String = null) { if (vpnThread != null) { vpnThread.stopThread() @@ -105,7 +105,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { conn = null } - super.stopRunner(stopService) + super.stopRunner(stopService, msg) } def killProcesses() { @@ -165,10 +165,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { resolved = true } - if (!resolved) { - changeState(State.STOPPED, getString(R.string.invalid_server)) - stopRunner(true) - } else if (handleConnection) { + if (!resolved) stopRunner(true, getString(R.string.invalid_server)) else if (handleConnection) { changeState(State.CONNECTED) notification = new ShadowsocksNotification(this, profile.name) } else { diff --git a/src/main/scala/com/github/shadowsocks/utils/Console.scala b/src/main/scala/com/github/shadowsocks/utils/Console.scala deleted file mode 100644 index 27cdd6f9..00000000 --- a/src/main/scala/com/github/shadowsocks/utils/Console.scala +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Shadowsocks - A shadowsocks client for Android - * Copyright (C) 2014 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - * ___====-_ _-====___ - * _--^^^#####// \\#####^^^--_ - * _-^##########// ( ) \\##########^-_ - * -############// |\^^/| \\############- - * _/############// (@::@) \\############\_ - * /#############(( \\// ))#############\ - * -###############\\ (oo) //###############- - * -#################\\ / VV \ //#################- - * -###################\\/ \//###################- - * _#/|##########/\######( /\ )######/\##########|\#_ - * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| - * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' - * ` ` ` ` / | | | | \ ' ' ' ' - * ( | | | | ) - * __\ | | | | /__ - * (vvv(VVV)(VVV)vvv) - * - * HERE BE DRAGONS - * - */ - -package com.github.shadowsocks.utils - -import java.util - -import eu.chainfire.libsuperuser.Shell -import eu.chainfire.libsuperuser.Shell.{Builder, SU} - -object Console { - - private def openShell(): Shell.Interactive = { - val builder = new Builder() - builder - .useSH() - .setWatchdogTimeout(10) - .open() - } - - private def openRootShell(context: String): Shell.Interactive = { - val builder = new Builder() - builder - .setShell(SU.shell(0, context)) - .setWantSTDERR(true) - .setWatchdogTimeout(10) - .open() - } - - def runCommand(commands: Array[String]) { - val shell = openShell() - shell.addCommand(commands, 0, ((commandCode: Int, exitCode: Int, output: util.List[String]) => - if (exitCode < 0) shell.close()): Shell.OnCommandResultListener) - shell.waitForIdle() - shell.close() - } - - 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, ((_, exitCode, output) => { - if (exitCode < 0) { - shell.close() - } else { - import scala.collection.JavaConversions._ - output.foreach(line => sb.append(line).append('\n')) - } - }): Shell.OnCommandResultListener) - if (shell.waitForIdle()) { - shell.close() - sb.toString() - } - else { - shell.close() - null - } - } - - def isRoot: Boolean = SU.available() -} diff --git a/src/main/scala/com/github/shadowsocks/utils/Utils.scala b/src/main/scala/com/github/shadowsocks/utils/Utils.scala index ecdf46f9..1b2abdcd 100644 --- a/src/main/scala/com/github/shadowsocks/utils/Utils.scala +++ b/src/main/scala/com/github/shadowsocks/utils/Utils.scala @@ -53,6 +53,7 @@ import android.view.{Gravity, View, Window} import android.widget.Toast import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.{BuildConfig, ShadowsocksRunnerService} +import eu.chainfire.libsuperuser.Shell import org.xbill.DNS._ import scala.concurrent.ExecutionContext.Implicits.global @@ -62,18 +63,6 @@ import scala.util.{Failure, Try} object Utils { private val TAG = "Shadowsocks" - /** - * Check the system's iptables - * Default to use the app's iptables - */ - private val DEFAULT_IPTABLES = "/system/bin/iptables" - private val ALTERNATIVE_IPTABLES = "iptables" - lazy val iptables = if (Console.isRoot) { - val lines = Console.runRootCommand(DEFAULT_IPTABLES + " --version", DEFAULT_IPTABLES + " -L -t nat -n") - if (Console.isRoot && lines != null && !lines.contains("OUTPUT") || !lines.contains("v1.4.")) ALTERNATIVE_IPTABLES - else DEFAULT_IPTABLES - } else DEFAULT_IPTABLES - def isLollipopOrAbove: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP def getSignature(context: Context): String = { @@ -130,18 +119,16 @@ object Utils { } // Because /sys/class/net/* isn't accessible since API level 24 - def flushDns() = Console.runRootCommand("for if in /sys/class/net/*; do " + + final val FLUSH_DNS = "for if in /sys/class/net/*; do " + "if [ \"down\" != $(cat $if/operstate) ]; then " + // up or unknown "ndc resolver flushif ${if##*/}; " + "fi " + - "done") + "done; echo done" // Blocked > 3 seconds def toggleAirplaneMode(context: Context) = { - if (Console.isRoot) { - flushDns() - true - } else if (Build.VERSION.SDK_INT < 17) { + val result = Shell.SU.run(FLUSH_DNS) + if (result != null && !result.isEmpty) true else if (Build.VERSION.SDK_INT < 17) { toggleBelowApiLevel17(context) true } else false