diff --git a/src/main/java/com/github/shadowsocks/GuardedProcess.java b/src/main/java/com/github/shadowsocks/GuardedProcess.java new file mode 100644 index 00000000..2323e719 --- /dev/null +++ b/src/main/java/com/github/shadowsocks/GuardedProcess.java @@ -0,0 +1,145 @@ +/* + * 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.lang.System; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; + +public class GuardedProcess extends Process { + private static final String TAG = GuardedProcess.class.getSimpleName(); + private final Thread guardThread; + private volatile boolean isDestroyed = false; + private volatile Process process = null; + + public GuardedProcess(String... cmd) throws InterruptedException, IOException { + this(Arrays.asList(cmd)); + } + + public GuardedProcess(final List cmd) throws InterruptedException, IOException { + this(cmd, null); + } + + public GuardedProcess(final List cmd, final Runnable onRestartCallback) throws InterruptedException, IOException { + final CountDownLatch countDownLatch = new CountDownLatch(1); + final AtomicReference atomicIoException = new AtomicReference(null); + guardThread = new Thread(new Runnable() { + @Override + public void run() { + try { + while (!isDestroyed) { + Log.i(TAG, "start process: " + cmd); + long startTime = System.currentTimeMillis(); + process = startProcess(cmd); + if (onRestartCallback != null && countDownLatch.getCount() <= 0) { + onRestartCallback.run(); + } + countDownLatch.countDown(); + process.waitFor(); + if (System.currentTimeMillis() - startTime < 1000) { + Log.w(TAG, "process exit too fast, stop guard: " + cmd); + break; + } + } + } catch (InterruptedException ignored) { + Log.i(TAG, "thread interrupt, destroy process: " + cmd); + process.destroy(); + } catch (IOException e) { + atomicIoException.compareAndSet(null, e); + } finally { + countDownLatch.countDown(); + } + } + }, "GuardThread-" + cmd); + guardThread.start(); + countDownLatch.await(); + IOException ioException = atomicIoException.get(); + if (ioException != null) { + throw ioException; + } + } + + private static Process startProcess(List cmd) throws IOException { + return new ProcessBuilder(cmd).redirectErrorStream(true).start(); + } + + public void destroy() { + isDestroyed = true; + guardThread.interrupt(); + process.destroy(); + try { + guardThread.join(); + } catch (InterruptedException ignored) { + } + } + + @Override + public int exitValue() { + throw new UnsupportedOperationException(); + } + + @Override + public InputStream getErrorStream() { + throw new UnsupportedOperationException(); + } + + @Override + public InputStream getInputStream() { + throw new UnsupportedOperationException(); + } + + @Override + public OutputStream getOutputStream() { + throw new UnsupportedOperationException(); + } + + @Override + public int waitFor() throws InterruptedException { + guardThread.join(); + return 0; + } +} 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