Refine #816 and error handling in startRunner
This commit is contained in:
@@ -39,6 +39,7 @@
|
||||
|
||||
package com.github.shadowsocks
|
||||
|
||||
import java.io.IOException
|
||||
import java.util.{Timer, TimerTask}
|
||||
|
||||
import android.app.Service
|
||||
@@ -48,7 +49,6 @@ import android.os.{Handler, RemoteCallbackList}
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
|
||||
import com.github.kevinsawicki.http.HttpRequest
|
||||
import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback}
|
||||
import com.github.shadowsocks.utils._
|
||||
@@ -60,6 +60,9 @@ trait BaseService extends Service {
|
||||
@volatile private var state = State.STOPPED
|
||||
@volatile protected var profile: Profile = _
|
||||
|
||||
case class NameNotResolvedException() extends IOException
|
||||
case class KcpcliParseException(cause: Throwable) extends Exception(cause)
|
||||
|
||||
var timer: Timer = _
|
||||
var trafficMonitorThread: TrafficMonitorThread = _
|
||||
|
||||
@@ -136,7 +139,7 @@ trait BaseService extends Service {
|
||||
false
|
||||
} else true
|
||||
|
||||
def connect() = if (profile.host == "198.199.101.152") try {
|
||||
def connect() = if (profile.host == "198.199.101.152") {
|
||||
val holder = app.containerHolder
|
||||
val container = holder.getContainer
|
||||
val url = container.getString("proxy_url")
|
||||
@@ -153,8 +156,6 @@ trait BaseService extends Service {
|
||||
profile.remotePort = proxy(1).trim.toInt
|
||||
profile.password = proxy(2).trim
|
||||
profile.method = proxy(3).trim
|
||||
} catch {
|
||||
case ex: Exception => stopRunner(true, getString(R.string.service_failed))
|
||||
}
|
||||
|
||||
def startRunner(profile: Profile) {
|
||||
@@ -188,7 +189,15 @@ trait BaseService extends Service {
|
||||
|
||||
if (profile.isMethodUnsafe) handler.post(() => Toast.makeText(this, R.string.method_unsafe, Toast.LENGTH_LONG).show)
|
||||
|
||||
Utils.ThrowableFuture(connect)
|
||||
Utils.ThrowableFuture(try connect catch {
|
||||
case _: NameNotResolvedException => stopRunner(true, getString(R.string.invalid_server))
|
||||
case exc: KcpcliParseException =>
|
||||
stopRunner(true, getString(R.string.service_failed) + ": " + exc.cause.getMessage)
|
||||
case exc: Throwable =>
|
||||
stopRunner(true, getString(R.string.service_failed) + ": " + exc.getMessage)
|
||||
exc.printStackTrace()
|
||||
app.track(exc)
|
||||
})
|
||||
}
|
||||
|
||||
def stopRunner(stopService: Boolean, msg: String = null) {
|
||||
|
||||
@@ -97,8 +97,7 @@ class ShadowsocksNatService extends BaseService {
|
||||
p.println(conf)
|
||||
})
|
||||
|
||||
val cmd = new ArrayBuffer[String]
|
||||
cmd += (getApplicationInfo.dataDir + "/ss-local"
|
||||
val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/ss-local"
|
||||
, "-b" , "127.0.0.1"
|
||||
, "-t" , "600"
|
||||
, "-P", getApplicationInfo.dataDir
|
||||
@@ -120,15 +119,16 @@ class ShadowsocksNatService extends BaseService {
|
||||
def startKcptunDaemon() {
|
||||
if (profile.kcpcli == null) profile.kcpcli = ""
|
||||
|
||||
val cmd = new ArrayBuffer[String]
|
||||
cmd += (getApplicationInfo.dataDir + "/kcptun"
|
||||
val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/kcptun"
|
||||
, "-r", profile.host + ":" + profile.kcpPort
|
||||
, "-l", "127.0.0.1:" + (profile.localPort + 90)
|
||||
, profile.kcpcli.replaceAll("^\\s*([\"'])(.*)\\1\\s*$", "$2"))
|
||||
, "-l", "127.0.0.1:" + (profile.localPort + 90))
|
||||
try cmd ++= Utils.translateCommandline(profile.kcpcli) catch {
|
||||
case exc: Exception => throw KcpcliParseException(exc)
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
|
||||
|
||||
kcptunProcess = new GuardedProcess(cmd.mkString(" ").split("[ ]{1,}").toSeq).start()
|
||||
kcptunProcess = new GuardedProcess(cmd).start()
|
||||
}
|
||||
|
||||
def startDNSTunnel() {
|
||||
@@ -139,8 +139,7 @@ class ShadowsocksNatService extends BaseService {
|
||||
Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf"))(p => {
|
||||
p.println(conf)
|
||||
})
|
||||
val cmd = new ArrayBuffer[String]
|
||||
cmd += (getApplicationInfo.dataDir + "/ss-tunnel"
|
||||
val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/ss-tunnel"
|
||||
, "-u"
|
||||
, "-t" , "10"
|
||||
, "-b" , "127.0.0.1"
|
||||
@@ -168,8 +167,7 @@ class ShadowsocksNatService extends BaseService {
|
||||
Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf"))(p => {
|
||||
p.println(conf)
|
||||
})
|
||||
val cmdBuf = new ArrayBuffer[String]
|
||||
cmdBuf += (getApplicationInfo.dataDir + "/ss-tunnel"
|
||||
val cmdBuf = ArrayBuffer[String](getApplicationInfo.dataDir + "/ss-tunnel"
|
||||
, "-t" , "10"
|
||||
, "-b" , "127.0.0.1"
|
||||
, "-l" , (profile.localPort + 63).toString
|
||||
@@ -198,27 +196,26 @@ class ShadowsocksNatService extends BaseService {
|
||||
Utils.printToFile(new File(getApplicationInfo.dataDir + "/pdnsd-nat.conf"))(p => {
|
||||
p.println(conf)
|
||||
})
|
||||
val cmd = getApplicationInfo.dataDir + "/pdnsd -c " + getApplicationInfo.dataDir + "/pdnsd-nat.conf"
|
||||
val cmd = Array(getApplicationInfo.dataDir + "/pdnsd", "-c", getApplicationInfo.dataDir + "/pdnsd-nat.conf")
|
||||
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd)
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
|
||||
|
||||
pdnsdProcess = new GuardedProcess(cmd.split(" ").toSeq).start()
|
||||
pdnsdProcess = new GuardedProcess(cmd).start()
|
||||
}
|
||||
|
||||
def startRedsocksDaemon() {
|
||||
val conf = ConfigUtils.REDSOCKS.formatLocal(Locale.ENGLISH, profile.localPort)
|
||||
val cmd = "%s/redsocks -c %s/redsocks-nat.conf"
|
||||
.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, getApplicationInfo.dataDir)
|
||||
val cmd = Array(getApplicationInfo.dataDir + "/redsocks", "-c", getApplicationInfo.dataDir + "/redsocks-nat.conf")
|
||||
Utils.printToFile(new File(getApplicationInfo.dataDir + "/redsocks-nat.conf"))(p => {
|
||||
p.println(conf)
|
||||
})
|
||||
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd)
|
||||
redsocksProcess = new GuardedProcess(cmd.split(" ").toSeq).start()
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
|
||||
redsocksProcess = new GuardedProcess(cmd).start()
|
||||
}
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
def handleConnection {
|
||||
def handleConnection() {
|
||||
|
||||
startDNSTunnel()
|
||||
startRedsocksDaemon()
|
||||
@@ -323,31 +320,19 @@ class ShadowsocksNatService extends BaseService {
|
||||
override def connect() {
|
||||
super.connect()
|
||||
|
||||
if (profile != null) {
|
||||
// Clean up
|
||||
killProcesses()
|
||||
|
||||
// Clean up
|
||||
killProcesses()
|
||||
|
||||
var resolved = false
|
||||
if (!Utils.isNumeric(profile.host)) {
|
||||
Utils.resolve(profile.host, enableIPv6 = true) match {
|
||||
case Some(a) =>
|
||||
profile.host = a
|
||||
resolved = true
|
||||
case None => resolved = false
|
||||
}
|
||||
} else {
|
||||
resolved = true
|
||||
}
|
||||
|
||||
if (!resolved) stopRunner(true, getString(R.string.invalid_server)) else {
|
||||
handleConnection
|
||||
// Set DNS
|
||||
su.addCommand(Utils.FLUSH_DNS)
|
||||
changeState(State.CONNECTED)
|
||||
notification = new ShadowsocksNotification(this, profile.name, true)
|
||||
}
|
||||
if (!Utils.isNumeric(profile.host)) Utils.resolve(profile.host, enableIPv6 = true) match {
|
||||
case Some(a) => profile.host = a
|
||||
case None => throw NameNotResolvedException()
|
||||
}
|
||||
|
||||
handleConnection()
|
||||
// Set DNS
|
||||
su.addCommand(Utils.FLUSH_DNS)
|
||||
changeState(State.CONNECTED)
|
||||
notification = new ShadowsocksNotification(this, profile.name, true)
|
||||
}
|
||||
|
||||
override def stopRunner(stopService: Boolean, msg: String = null) {
|
||||
|
||||
@@ -148,42 +148,28 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
override def connect() = {
|
||||
super.connect()
|
||||
|
||||
if (profile != null) {
|
||||
vpnThread = new ShadowsocksVpnThread(this)
|
||||
vpnThread.start()
|
||||
|
||||
vpnThread = new ShadowsocksVpnThread(this)
|
||||
vpnThread.start()
|
||||
// reset the context
|
||||
killProcesses()
|
||||
|
||||
// reset the context
|
||||
killProcesses()
|
||||
|
||||
// Resolve the server address
|
||||
var resolved: Boolean = false
|
||||
if (!Utils.isNumeric(profile.host)) {
|
||||
Utils.resolve(profile.host, enableIPv6 = true) match {
|
||||
case Some(addr) =>
|
||||
profile.host = addr
|
||||
resolved = true
|
||||
case None => resolved = false
|
||||
}
|
||||
} else {
|
||||
resolved = true
|
||||
}
|
||||
|
||||
if (!resolved) stopRunner(true, getString(R.string.invalid_server)) else if (handleConnection) {
|
||||
changeState(State.CONNECTED)
|
||||
notification = new ShadowsocksNotification(this, profile.name)
|
||||
} else {
|
||||
changeState(State.STOPPED, getString(R.string.service_failed))
|
||||
stopRunner(true)
|
||||
}
|
||||
// Resolve the server address
|
||||
if (!Utils.isNumeric(profile.host)) Utils.resolve(profile.host, enableIPv6 = true) match {
|
||||
case Some(addr) => profile.host = addr
|
||||
case None => throw NameNotResolvedException()
|
||||
}
|
||||
|
||||
handleConnection()
|
||||
changeState(State.CONNECTED)
|
||||
notification = new ShadowsocksNotification(this, profile.name)
|
||||
}
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
def handleConnection: Boolean = {
|
||||
def handleConnection() {
|
||||
|
||||
val fd = startVpn()
|
||||
if (!sendFd(fd)) return false
|
||||
if (!sendFd(fd)) throw new Exception("sendFd failed")
|
||||
|
||||
if (profile.kcp) {
|
||||
startKcptunDaemon()
|
||||
@@ -199,24 +185,23 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
startDnsDaemon()
|
||||
startDnsTunnel()
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
def startKcptunDaemon() {
|
||||
if (profile.kcpcli == null) profile.kcpcli = ""
|
||||
|
||||
val cmd = new ArrayBuffer[String]
|
||||
cmd += (getApplicationInfo.dataDir + "/kcptun"
|
||||
val cmd = ArrayBuffer(getApplicationInfo.dataDir + "/kcptun"
|
||||
, "-r", profile.host + ":" + profile.kcpPort
|
||||
, "-l", "127.0.0.1:" + (profile.localPort + 90)
|
||||
, "--path", getApplicationInfo.dataDir + "/protect_path"
|
||||
, profile.kcpcli.replaceAll("^\\s*([\"'])(.*)\\1\\s*$", "$2"))
|
||||
, "--path", getApplicationInfo.dataDir + "/protect_path")
|
||||
try cmd ++= Utils.translateCommandline(profile.kcpcli) catch {
|
||||
case exc: Exception => throw KcpcliParseException(exc)
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, cmd.mkString(" "))
|
||||
|
||||
kcptunProcess = new GuardedProcess(cmd.mkString(" ").split("[ ]{1,}").toSeq).start()
|
||||
kcptunProcess = new GuardedProcess(cmd).start()
|
||||
}
|
||||
|
||||
def startShadowsocksUDPDaemon() {
|
||||
@@ -227,8 +212,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
p.println(conf)
|
||||
})
|
||||
|
||||
val cmd = new ArrayBuffer[String]
|
||||
cmd += (getApplicationInfo.dataDir + "/ss-local", "-V", "-U"
|
||||
val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/ss-local", "-V", "-U"
|
||||
, "-b", "127.0.0.1"
|
||||
, "-t", "600"
|
||||
, "-P", getApplicationInfo.dataDir
|
||||
@@ -267,8 +251,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
p.println(conf)
|
||||
})
|
||||
|
||||
val cmd = new ArrayBuffer[String]
|
||||
cmd += (getApplicationInfo.dataDir + "/ss-local", "-V"
|
||||
val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/ss-local", "-V"
|
||||
, "-b", "127.0.0.1"
|
||||
, "-t", "600"
|
||||
, "-P", getApplicationInfo.dataDir
|
||||
@@ -303,8 +286,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf"))(p => {
|
||||
p.println(conf)
|
||||
})
|
||||
val cmd = new ArrayBuffer[String]
|
||||
cmd += (getApplicationInfo.dataDir + "/ss-tunnel"
|
||||
val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/ss-tunnel"
|
||||
, "-V"
|
||||
, "-t", "10"
|
||||
, "-b", "127.0.0.1"
|
||||
@@ -333,11 +315,11 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
Utils.printToFile(new File(getApplicationInfo.dataDir + "/pdnsd-vpn.conf"))(p => {
|
||||
p.println(conf)
|
||||
})
|
||||
val cmd = getApplicationInfo.dataDir + "/pdnsd -c " + getApplicationInfo.dataDir + "/pdnsd-vpn.conf"
|
||||
val cmd = Array(getApplicationInfo.dataDir + "/pdnsd", "-c", getApplicationInfo.dataDir + "/pdnsd-vpn.conf")
|
||||
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd)
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
|
||||
|
||||
pdnsdProcess = new GuardedProcess(cmd.split(" ").toSeq).start()
|
||||
pdnsdProcess = new GuardedProcess(cmd).start()
|
||||
}
|
||||
|
||||
def startVpn(): Int = {
|
||||
@@ -397,30 +379,27 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
|
||||
val fd = conn.getFd
|
||||
|
||||
var cmd = (getApplicationInfo.dataDir +
|
||||
"/tun2socks --netif-ipaddr %s "
|
||||
+ "--netif-netmask 255.255.255.0 "
|
||||
+ "--socks-server-addr 127.0.0.1:%d "
|
||||
+ "--tunfd %d "
|
||||
+ "--tunmtu %d "
|
||||
+ "--sock-path %s "
|
||||
+ "--loglevel 3")
|
||||
.formatLocal(Locale.ENGLISH,
|
||||
PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "2"),
|
||||
profile.localPort, fd, VPN_MTU, getApplicationInfo.dataDir + "/sock_path")
|
||||
var cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/tun2socks",
|
||||
"--netif-ipaddr", PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "2"),
|
||||
"--netif-netmask", "255.255.255.0",
|
||||
"--socks-server-addr", "127.0.0.1:" + profile.localPort,
|
||||
"--tunfd", fd.toString,
|
||||
"--tunmtu", VPN_MTU.toString,
|
||||
"--sock-path", getApplicationInfo.dataDir + "/sock_path",
|
||||
"--loglevel", "3")
|
||||
|
||||
if (profile.ipv6)
|
||||
cmd += " --netif-ip6addr " + PRIVATE_VLAN6.formatLocal(Locale.ENGLISH, "2")
|
||||
cmd += ("--netif-ip6addr", PRIVATE_VLAN6.formatLocal(Locale.ENGLISH, "2"))
|
||||
|
||||
if (profile.udpdns)
|
||||
cmd += " --enable-udprelay"
|
||||
cmd += "--enable-udprelay"
|
||||
else
|
||||
cmd += " --dnsgw %s:%d".formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"),
|
||||
profile.localPort + 53)
|
||||
cmd += ("--dnsgw", "%s:%d".formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"),
|
||||
profile.localPort + 53))
|
||||
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd)
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
|
||||
|
||||
tun2socksProcess = new GuardedProcess(cmd.split(" ").toSeq).start(() => sendFd(fd))
|
||||
tun2socksProcess = new GuardedProcess(cmd).start(() => sendFd(fd))
|
||||
|
||||
fd
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ package com.github.shadowsocks.utils
|
||||
import java.io.File
|
||||
import java.net._
|
||||
import java.security.MessageDigest
|
||||
import java.util.Scanner
|
||||
import java.util.{Scanner, StringTokenizer}
|
||||
|
||||
import android.animation.{Animator, AnimatorListenerAdapter}
|
||||
import android.content.pm.PackageManager
|
||||
@@ -58,6 +58,7 @@ import eu.chainfire.libsuperuser.Shell
|
||||
import org.xbill.DNS._
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
import scala.util.{Failure, Try}
|
||||
@@ -127,6 +128,48 @@ object Utils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Crack a command line.
|
||||
* Based on: https://github.com/apache/ant/blob/588ce1f/src/main/org/apache/tools/ant/types/Commandline.java#L471
|
||||
* @param toProcess the command line to process.
|
||||
* @return the command line broken into strings.
|
||||
* An empty or null toProcess parameter results in a zero sized ArrayBuffer.
|
||||
*/
|
||||
def translateCommandline(toProcess: String): ArrayBuffer[String] = {
|
||||
if (toProcess == null || toProcess.length == 0) return ArrayBuffer[String]()
|
||||
val tok = new StringTokenizer(toProcess, "\"' ", true)
|
||||
val result = ArrayBuffer[String]()
|
||||
val current = new StringBuilder()
|
||||
var quote = ' '
|
||||
var last = " "
|
||||
while (tok.hasMoreTokens) {
|
||||
val nextTok = tok.nextToken
|
||||
quote match {
|
||||
case '\'' => nextTok match {
|
||||
case "'" => quote = ' '
|
||||
case _ => current.append(nextTok)
|
||||
}
|
||||
case '"' => nextTok match {
|
||||
case "\"" => quote = ' '
|
||||
case _ => current.append(nextTok)
|
||||
}
|
||||
case _ => nextTok match {
|
||||
case "'" => quote = '\''
|
||||
case "\"" => quote = '"'
|
||||
case " " => if (last != " ") {
|
||||
result.append(current.toString)
|
||||
current.setLength(0)
|
||||
}
|
||||
case _ => current.append(nextTok)
|
||||
}
|
||||
}
|
||||
last = nextTok
|
||||
}
|
||||
if (current.nonEmpty) result.append(current.toString)
|
||||
if (quote == '\'' || quote == '"') throw new Exception("Unbalanced quotes in " + toProcess)
|
||||
result
|
||||
}
|
||||
|
||||
// Because /sys/class/net/* isn't accessible since API level 24
|
||||
final val FLUSH_DNS = "for if in /sys/class/net/*; do " +
|
||||
"if [ \"down\" != $(cat $if/operstate) ]; then " + // up or unknown
|
||||
|
||||
Reference in New Issue
Block a user