diff --git a/src/main/java/com/github/shadowsocks/GuardedProcess.scala b/src/main/java/com/github/shadowsocks/GuardedProcess.scala new file mode 100644 index 00000000..ce6c5469 --- /dev/null +++ b/src/main/java/com/github/shadowsocks/GuardedProcess.scala @@ -0,0 +1,147 @@ +/* + * Shadowsocks - A shadowsocks client for Android + * Copyright (C) 2015 + * + * 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 + +import android.util.Log +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream +import java.util.concurrent.CountDownLatch +import java.util.concurrent.atomic.AtomicReference + +import collection.JavaConversions._ + +object GuardedProcess { + private val TAG: String = classOf[GuardedProcess].getSimpleName + + @throws(classOf[IOException]) + private def startProcess(cmd: Seq[String]): Process = { + new ProcessBuilder(cmd).redirectErrorStream(true).start + } +} + +class GuardedProcess extends Process { + private var guardThread: Thread = null + @volatile + private var isDestroyed: Boolean = false + @volatile + private var process: Process = null + + @throws(classOf[InterruptedException]) + @throws(classOf[IOException]) + def this(cmd: Seq[String], onRestartCallback: Runnable = null) { + this() + initThread(cmd, onRestartCallback) + } + + def initThread(cmd: Seq[String], onRestartCallback: Runnable): Unit = { + val atomicIoException: AtomicReference[IOException] = new AtomicReference[IOException](null) + val countDownLatch: CountDownLatch = new CountDownLatch(1) + guardThread = new Thread(new Runnable() { + override def run(): Unit = { + try { + while (!isDestroyed) { + Log.i(GuardedProcess.TAG, "start process: " + cmd) + val startTime: Long = java.lang.System.currentTimeMillis + process = GuardedProcess.startProcess(cmd) + if (onRestartCallback != null && countDownLatch.getCount <= 0) { + onRestartCallback.run() + } + countDownLatch.countDown() + process.waitFor + if (java.lang.System.currentTimeMillis - startTime < 1000) { + Log.w(GuardedProcess.TAG, "process exit too fast, stop guard: " + cmd) + return + } + } + } + catch { + case ignored: InterruptedException => + Log.i(GuardedProcess.TAG, "thread interrupt, destroy process: " + cmd) + process.destroy() + case e: IOException => + atomicIoException.compareAndSet(null, e) + } finally { + countDownLatch.countDown() + } + } + }, "GuardThread-" + cmd) + guardThread.start() + countDownLatch.await() + val ioException: IOException = atomicIoException.get + if (ioException != null) { + throw ioException + } + } + + def destroy() { + isDestroyed = true + guardThread.interrupt() + process.destroy() + try { + guardThread.join() + } + catch { + case ignored: InterruptedException => + } + } + + def exitValue: Int = { + throw new UnsupportedOperationException + } + + def getErrorStream: InputStream = { + throw new UnsupportedOperationException + } + + def getInputStream: InputStream = { + throw new UnsupportedOperationException + } + + def getOutputStream: OutputStream = { + throw new UnsupportedOperationException + } + + @throws(classOf[InterruptedException]) + def waitFor: Int = { + guardThread.join() + 0 + } +} \ No newline at end of file diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala index 24887bb0..b7d9d308 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala @@ -196,10 +196,7 @@ class ShadowsocksNatService extends BaseService { } if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) - sslocalProcess = new ProcessBuilder() - .command(cmd) - .redirectErrorStream(true) - .start() + sslocalProcess = new GuardedProcess(cmd) } def startTunnel() { @@ -225,10 +222,7 @@ class ShadowsocksNatService extends BaseService { if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) - sstunnelProcess = new ProcessBuilder() - .command(cmd) - .redirectErrorStream(true) - .start() + sstunnelProcess = new GuardedProcess(cmd) } else { val conf = ConfigUtils @@ -251,10 +245,7 @@ class ShadowsocksNatService extends BaseService { if (BuildConfig.DEBUG) Log.d(TAG, cmdBuf.mkString(" ")) - sstunnelProcess = new ProcessBuilder() - .command(cmdBuf) - .redirectErrorStream(true) - .start() + sstunnelProcess = new GuardedProcess(cmdBuf) } } @@ -277,10 +268,7 @@ class ShadowsocksNatService extends BaseService { if (BuildConfig.DEBUG) Log.d(TAG, cmd) - pdnsdProcess = new ProcessBuilder() - .command(cmd.split(" ").toSeq) - .redirectErrorStream(true) - .start() + pdnsdProcess = new GuardedProcess(cmd.split(" ").toSeq) } def getVersionName: String = { @@ -304,10 +292,7 @@ class ShadowsocksNatService extends BaseService { }) if (BuildConfig.DEBUG) Log.d(TAG, cmd) - redsocksProcess = new ProcessBuilder() - .command(cmd.split(" ").toSeq) - .redirectErrorStream(true) - .start() + redsocksProcess = new GuardedProcess(cmd.split(" ").toSeq) } /** Called when the activity is first created. */ diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index dd82b756..06639770 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -256,18 +256,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { } val fd = startVpn() - - if (fd != -1) { - var tries = 1 - while (tries < 5) { - Thread.sleep(1000 * tries) - if (System.sendfd(fd, getApplicationInfo.dataDir + "/sock_path") != -1) { - return true - } - tries += 1 - } - } - false + sendFd(fd) } def startShadowsocksDaemon() { @@ -307,10 +296,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) - sslocalProcess = new ProcessBuilder() - .command(cmd) - .redirectErrorStream(true) - .start() + sslocalProcess = new GuardedProcess(cmd) } def startDnsTunnel() = { @@ -335,10 +321,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) - sstunnelProcess = new ProcessBuilder() - .command(cmd) - .redirectErrorStream(true) - .start() + sstunnelProcess = new GuardedProcess(cmd) } def startDnsDaemon() { @@ -361,10 +344,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { if (BuildConfig.DEBUG) Log.d(TAG, cmd) - pdnsdProcess = new ProcessBuilder() - .command(cmd.split(" ").toSeq) - .redirectErrorStream(true) - .start() + pdnsdProcess = new GuardedProcess(cmd.split(" ").toSeq) } override def getContext = getBaseContext @@ -454,14 +434,29 @@ class ShadowsocksVpnService extends VpnService with BaseService { if (BuildConfig.DEBUG) Log.d(TAG, cmd) - tun2socksProcess = new ProcessBuilder() - .command(cmd.split(" ").toSeq) - .redirectErrorStream(true) - .start() + tun2socksProcess = new GuardedProcess(cmd.split(" ").toSeq, new Runnable { + override def run(): Unit = { + sendFd(fd) + } + }) fd } + def sendFd(fd: Int): Boolean = { + if (fd != -1) { + var tries = 1 + while (tries < 5) { + Thread.sleep(1000 * tries) + if (System.sendfd(fd, getApplicationInfo.dataDir + "/sock_path") != -1) { + return true + } + tries += 1 + } + } + false + } + override def getTag = TAG override def getServiceMode = Mode.VPN