Commit 25bd5347 authored by Mygod's avatar Mygod

Refine #816 and error handling in startRunner

parent d2a527ed
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
package com.github.shadowsocks package com.github.shadowsocks
import java.io.IOException
import java.util.{Timer, TimerTask} import java.util.{Timer, TimerTask}
import android.app.Service import android.app.Service
...@@ -48,7 +49,6 @@ import android.os.{Handler, RemoteCallbackList} ...@@ -48,7 +49,6 @@ import android.os.{Handler, RemoteCallbackList}
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import com.github.kevinsawicki.http.HttpRequest import com.github.kevinsawicki.http.HttpRequest
import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback} import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback}
import com.github.shadowsocks.utils._ import com.github.shadowsocks.utils._
...@@ -60,6 +60,9 @@ trait BaseService extends Service { ...@@ -60,6 +60,9 @@ trait BaseService extends Service {
@volatile private var state = State.STOPPED @volatile private var state = State.STOPPED
@volatile protected var profile: Profile = _ @volatile protected var profile: Profile = _
case class NameNotResolvedException() extends IOException
case class KcpcliParseException(cause: Throwable) extends Exception(cause)
var timer: Timer = _ var timer: Timer = _
var trafficMonitorThread: TrafficMonitorThread = _ var trafficMonitorThread: TrafficMonitorThread = _
...@@ -136,7 +139,7 @@ trait BaseService extends Service { ...@@ -136,7 +139,7 @@ trait BaseService extends Service {
false false
} else true } 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 holder = app.containerHolder
val container = holder.getContainer val container = holder.getContainer
val url = container.getString("proxy_url") val url = container.getString("proxy_url")
...@@ -153,8 +156,6 @@ trait BaseService extends Service { ...@@ -153,8 +156,6 @@ trait BaseService extends Service {
profile.remotePort = proxy(1).trim.toInt profile.remotePort = proxy(1).trim.toInt
profile.password = proxy(2).trim profile.password = proxy(2).trim
profile.method = proxy(3).trim profile.method = proxy(3).trim
} catch {
case ex: Exception => stopRunner(true, getString(R.string.service_failed))
} }
def startRunner(profile: Profile) { def startRunner(profile: Profile) {
...@@ -188,7 +189,15 @@ trait BaseService extends Service { ...@@ -188,7 +189,15 @@ trait BaseService extends Service {
if (profile.isMethodUnsafe) handler.post(() => Toast.makeText(this, R.string.method_unsafe, Toast.LENGTH_LONG).show) 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) { def stopRunner(stopService: Boolean, msg: String = null) {
......
...@@ -97,8 +97,7 @@ class ShadowsocksNatService extends BaseService { ...@@ -97,8 +97,7 @@ class ShadowsocksNatService extends BaseService {
p.println(conf) p.println(conf)
}) })
val cmd = new ArrayBuffer[String] val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/ss-local"
cmd += (getApplicationInfo.dataDir + "/ss-local"
, "-b" , "127.0.0.1" , "-b" , "127.0.0.1"
, "-t" , "600" , "-t" , "600"
, "-P", getApplicationInfo.dataDir , "-P", getApplicationInfo.dataDir
...@@ -120,15 +119,16 @@ class ShadowsocksNatService extends BaseService { ...@@ -120,15 +119,16 @@ class ShadowsocksNatService extends BaseService {
def startKcptunDaemon() { def startKcptunDaemon() {
if (profile.kcpcli == null) profile.kcpcli = "" if (profile.kcpcli == null) profile.kcpcli = ""
val cmd = new ArrayBuffer[String] val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/kcptun"
cmd += (getApplicationInfo.dataDir + "/kcptun"
, "-r", profile.host + ":" + profile.kcpPort , "-r", profile.host + ":" + profile.kcpPort
, "-l", "127.0.0.1:" + (profile.localPort + 90) , "-l", "127.0.0.1:" + (profile.localPort + 90))
, profile.kcpcli.replaceAll("^\\s*([\"'])(.*)\\1\\s*$", "$2")) try cmd ++= Utils.translateCommandline(profile.kcpcli) catch {
case exc: Exception => throw KcpcliParseException(exc)
}
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
kcptunProcess = new GuardedProcess(cmd.mkString(" ").split("[ ]{1,}").toSeq).start() kcptunProcess = new GuardedProcess(cmd).start()
} }
def startDNSTunnel() { def startDNSTunnel() {
...@@ -139,8 +139,7 @@ class ShadowsocksNatService extends BaseService { ...@@ -139,8 +139,7 @@ class ShadowsocksNatService extends BaseService {
Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf"))(p => { Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf"))(p => {
p.println(conf) p.println(conf)
}) })
val cmd = new ArrayBuffer[String] val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/ss-tunnel"
cmd += (getApplicationInfo.dataDir + "/ss-tunnel"
, "-u" , "-u"
, "-t" , "10" , "-t" , "10"
, "-b" , "127.0.0.1" , "-b" , "127.0.0.1"
...@@ -168,8 +167,7 @@ class ShadowsocksNatService extends BaseService { ...@@ -168,8 +167,7 @@ class ShadowsocksNatService extends BaseService {
Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf"))(p => { Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf"))(p => {
p.println(conf) p.println(conf)
}) })
val cmdBuf = new ArrayBuffer[String] val cmdBuf = ArrayBuffer[String](getApplicationInfo.dataDir + "/ss-tunnel"
cmdBuf += (getApplicationInfo.dataDir + "/ss-tunnel"
, "-t" , "10" , "-t" , "10"
, "-b" , "127.0.0.1" , "-b" , "127.0.0.1"
, "-l" , (profile.localPort + 63).toString , "-l" , (profile.localPort + 63).toString
...@@ -198,27 +196,26 @@ class ShadowsocksNatService extends BaseService { ...@@ -198,27 +196,26 @@ class ShadowsocksNatService extends BaseService {
Utils.printToFile(new File(getApplicationInfo.dataDir + "/pdnsd-nat.conf"))(p => { Utils.printToFile(new File(getApplicationInfo.dataDir + "/pdnsd-nat.conf"))(p => {
p.println(conf) 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() { def startRedsocksDaemon() {
val conf = ConfigUtils.REDSOCKS.formatLocal(Locale.ENGLISH, profile.localPort) val conf = ConfigUtils.REDSOCKS.formatLocal(Locale.ENGLISH, profile.localPort)
val cmd = "%s/redsocks -c %s/redsocks-nat.conf" val cmd = Array(getApplicationInfo.dataDir + "/redsocks", "-c", getApplicationInfo.dataDir + "/redsocks-nat.conf")
.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, getApplicationInfo.dataDir)
Utils.printToFile(new File(getApplicationInfo.dataDir + "/redsocks-nat.conf"))(p => { Utils.printToFile(new File(getApplicationInfo.dataDir + "/redsocks-nat.conf"))(p => {
p.println(conf) p.println(conf)
}) })
if (BuildConfig.DEBUG) Log.d(TAG, cmd) if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
redsocksProcess = new GuardedProcess(cmd.split(" ").toSeq).start() redsocksProcess = new GuardedProcess(cmd).start()
} }
/** Called when the activity is first created. */ /** Called when the activity is first created. */
def handleConnection { def handleConnection() {
startDNSTunnel() startDNSTunnel()
startRedsocksDaemon() startRedsocksDaemon()
...@@ -323,31 +320,19 @@ class ShadowsocksNatService extends BaseService { ...@@ -323,31 +320,19 @@ class ShadowsocksNatService extends BaseService {
override def connect() { override def connect() {
super.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 { if (!Utils.isNumeric(profile.host)) Utils.resolve(profile.host, enableIPv6 = true) match {
handleConnection case Some(a) => profile.host = a
// Set DNS case None => throw NameNotResolvedException()
su.addCommand(Utils.FLUSH_DNS)
changeState(State.CONNECTED)
notification = new ShadowsocksNotification(this, profile.name, true)
}
} }
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) { override def stopRunner(stopService: Boolean, msg: String = null) {
......
...@@ -148,42 +148,28 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -148,42 +148,28 @@ class ShadowsocksVpnService extends VpnService with BaseService {
override def connect() = { override def connect() = {
super.connect() super.connect()
if (profile != null) { vpnThread = new ShadowsocksVpnThread(this)
vpnThread.start()
vpnThread = new ShadowsocksVpnThread(this)
vpnThread.start()
// 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) { // reset the context
changeState(State.CONNECTED) killProcesses()
notification = new ShadowsocksNotification(this, profile.name)
} else { // Resolve the server address
changeState(State.STOPPED, getString(R.string.service_failed)) if (!Utils.isNumeric(profile.host)) Utils.resolve(profile.host, enableIPv6 = true) match {
stopRunner(true) 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. */ /** Called when the activity is first created. */
def handleConnection: Boolean = { def handleConnection() {
val fd = startVpn() val fd = startVpn()
if (!sendFd(fd)) return false if (!sendFd(fd)) throw new Exception("sendFd failed")
if (profile.kcp) { if (profile.kcp) {
startKcptunDaemon() startKcptunDaemon()
...@@ -199,24 +185,23 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -199,24 +185,23 @@ class ShadowsocksVpnService extends VpnService with BaseService {
startDnsDaemon() startDnsDaemon()
startDnsTunnel() startDnsTunnel()
} }
true
} }
def startKcptunDaemon() { def startKcptunDaemon() {
if (profile.kcpcli == null) profile.kcpcli = "" if (profile.kcpcli == null) profile.kcpcli = ""
val cmd = new ArrayBuffer[String] val cmd = ArrayBuffer(getApplicationInfo.dataDir + "/kcptun"
cmd += (getApplicationInfo.dataDir + "/kcptun"
, "-r", profile.host + ":" + profile.kcpPort , "-r", profile.host + ":" + profile.kcpPort
, "-l", "127.0.0.1:" + (profile.localPort + 90) , "-l", "127.0.0.1:" + (profile.localPort + 90)
, "--path", getApplicationInfo.dataDir + "/protect_path" , "--path", getApplicationInfo.dataDir + "/protect_path")
, profile.kcpcli.replaceAll("^\\s*([\"'])(.*)\\1\\s*$", "$2")) try cmd ++= Utils.translateCommandline(profile.kcpcli) catch {
case exc: Exception => throw KcpcliParseException(exc)
}
if (BuildConfig.DEBUG) if (BuildConfig.DEBUG)
Log.d(TAG, cmd.mkString(" ")) Log.d(TAG, cmd.mkString(" "))
kcptunProcess = new GuardedProcess(cmd.mkString(" ").split("[ ]{1,}").toSeq).start() kcptunProcess = new GuardedProcess(cmd).start()
} }
def startShadowsocksUDPDaemon() { def startShadowsocksUDPDaemon() {
...@@ -227,8 +212,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -227,8 +212,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
p.println(conf) p.println(conf)
}) })
val cmd = new ArrayBuffer[String] val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/ss-local", "-V", "-U"
cmd += (getApplicationInfo.dataDir + "/ss-local", "-V", "-U"
, "-b", "127.0.0.1" , "-b", "127.0.0.1"
, "-t", "600" , "-t", "600"
, "-P", getApplicationInfo.dataDir , "-P", getApplicationInfo.dataDir
...@@ -267,8 +251,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -267,8 +251,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
p.println(conf) p.println(conf)
}) })
val cmd = new ArrayBuffer[String] val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/ss-local", "-V"
cmd += (getApplicationInfo.dataDir + "/ss-local", "-V"
, "-b", "127.0.0.1" , "-b", "127.0.0.1"
, "-t", "600" , "-t", "600"
, "-P", getApplicationInfo.dataDir , "-P", getApplicationInfo.dataDir
...@@ -303,8 +286,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -303,8 +286,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf"))(p => { Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf"))(p => {
p.println(conf) p.println(conf)
}) })
val cmd = new ArrayBuffer[String] val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/ss-tunnel"
cmd += (getApplicationInfo.dataDir + "/ss-tunnel"
, "-V" , "-V"
, "-t", "10" , "-t", "10"
, "-b", "127.0.0.1" , "-b", "127.0.0.1"
...@@ -333,11 +315,11 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -333,11 +315,11 @@ class ShadowsocksVpnService extends VpnService with BaseService {
Utils.printToFile(new File(getApplicationInfo.dataDir + "/pdnsd-vpn.conf"))(p => { Utils.printToFile(new File(getApplicationInfo.dataDir + "/pdnsd-vpn.conf"))(p => {
p.println(conf) 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 = { def startVpn(): Int = {
...@@ -397,30 +379,27 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -397,30 +379,27 @@ class ShadowsocksVpnService extends VpnService with BaseService {
val fd = conn.getFd val fd = conn.getFd
var cmd = (getApplicationInfo.dataDir + var cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/tun2socks",
"/tun2socks --netif-ipaddr %s " "--netif-ipaddr", PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "2"),
+ "--netif-netmask 255.255.255.0 " "--netif-netmask", "255.255.255.0",
+ "--socks-server-addr 127.0.0.1:%d " "--socks-server-addr", "127.0.0.1:" + profile.localPort,
+ "--tunfd %d " "--tunfd", fd.toString,
+ "--tunmtu %d " "--tunmtu", VPN_MTU.toString,
+ "--sock-path %s " "--sock-path", getApplicationInfo.dataDir + "/sock_path",
+ "--loglevel 3") "--loglevel", "3")
.formatLocal(Locale.ENGLISH,
PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "2"),
profile.localPort, fd, VPN_MTU, getApplicationInfo.dataDir + "/sock_path")
if (profile.ipv6) if (profile.ipv6)
cmd += " --netif-ip6addr " + PRIVATE_VLAN6.formatLocal(Locale.ENGLISH, "2") cmd += ("--netif-ip6addr", PRIVATE_VLAN6.formatLocal(Locale.ENGLISH, "2"))
if (profile.udpdns) if (profile.udpdns)
cmd += " --enable-udprelay" cmd += "--enable-udprelay"
else else
cmd += " --dnsgw %s:%d".formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"), cmd += ("--dnsgw", "%s:%d".formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"),
profile.localPort + 53) 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 fd
} }
......
...@@ -41,7 +41,7 @@ package com.github.shadowsocks.utils ...@@ -41,7 +41,7 @@ package com.github.shadowsocks.utils
import java.io.File import java.io.File
import java.net._ import java.net._
import java.security.MessageDigest import java.security.MessageDigest
import java.util.Scanner import java.util.{Scanner, StringTokenizer}
import android.animation.{Animator, AnimatorListenerAdapter} import android.animation.{Animator, AnimatorListenerAdapter}
import android.content.pm.PackageManager import android.content.pm.PackageManager
...@@ -58,6 +58,7 @@ import eu.chainfire.libsuperuser.Shell ...@@ -58,6 +58,7 @@ import eu.chainfire.libsuperuser.Shell
import org.xbill.DNS._ import org.xbill.DNS._
import scala.collection.JavaConversions._ import scala.collection.JavaConversions._
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future import scala.concurrent.Future
import scala.util.{Failure, Try} import scala.util.{Failure, Try}
...@@ -127,6 +128,48 @@ object Utils { ...@@ -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 // Because /sys/class/net/* isn't accessible since API level 24
final val FLUSH_DNS = "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 "if [ \"down\" != $(cat $if/operstate) ]; then " + // up or unknown
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment