Commit 25bd5347 authored by Mygod's avatar Mygod

Refine #816 and error handling in startRunner

parent d2a527ed
......@@ -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()
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
}
// Clean up
killProcesses()
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()
// 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
}
vpnThread = new ShadowsocksVpnThread(this)
vpnThread.start()
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)
}
// reset the context
killProcesses()
// 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
......
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