diff --git a/build.sh b/build.sh index 8c4b249a..513c0323 100755 --- a/build.sh +++ b/build.sh @@ -7,7 +7,7 @@ function try () { try pushd src/main # Build -try $ANDROID_NDK_HOME/ndk-build clean +#try $ANDROID_NDK_HOME/ndk-build clean try $ANDROID_NDK_HOME/ndk-build -j8 # copy executables diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index a20f1ca9..9ed06a27 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="109" + android:versionName="2.7.1"> diff --git a/src/main/java/com/github/shadowsocks/System.java b/src/main/java/com/github/shadowsocks/System.java index 0dd47f59..47b10ed6 100755 --- a/src/main/java/com/github/shadowsocks/System.java +++ b/src/main/java/com/github/shadowsocks/System.java @@ -46,4 +46,5 @@ public class System { public static native void exec(String cmd); public static native String getABI(); public static native int sendfd(int fd); + public static native void jniclose(int fd); } diff --git a/src/main/jni/Android.mk b/src/main/jni/Android.mk index 13fa8e6e..11a8038d 100755 --- a/src/main/jni/Android.mk +++ b/src/main/jni/Android.mk @@ -249,13 +249,14 @@ include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) -SHADOWSOCKS_SOURCES := local.c cache.c udprelay.c encrypt.c utils.c netutils.c json.c jconf.c acl.c +SHADOWSOCKS_SOURCES := local.c cache.c udprelay.c encrypt.c utils.c netutils.c json.c jconf.c acl.c android.c LOCAL_MODULE := ss-local LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/src/, $(SHADOWSOCKS_SOURCES)) LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DUDPRELAY_LOCAL \ -DUSE_CRYPTO_OPENSSL -DANDROID -DHAVE_CONFIG_H \ - -I$(LOCAL_PATH)/libev/ \ + -I$(LOCAL_PATH)/libev \ + -I$(LOCAL_PATH)/libancillary \ -I$(LOCAL_PATH)/openssl/include \ -I$(LOCAL_PATH)/shadowsocks-libev/libudns \ -I$(LOCAL_PATH)/shadowsocks-libev/libcork/include \ @@ -263,7 +264,7 @@ LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DUDPRELAY_LOCAL \ -I$(LOCAL_PATH)/shadowsocks-libev/libsodium/src/libsodium/include/sodium \ -I$(LOCAL_PATH)/shadowsocks-libev/libipset/include -LOCAL_STATIC_LIBRARIES := libev libcrypto libipset libcork libudns libsodium +LOCAL_STATIC_LIBRARIES := libev libcrypto libipset libcork libudns libsodium libancillary LOCAL_LDLIBS := -llog @@ -275,20 +276,21 @@ include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) -SHADOWSOCKS_SOURCES := tunnel.c cache.c udprelay.c encrypt.c utils.c netutils.c json.c jconf.c +SHADOWSOCKS_SOURCES := tunnel.c cache.c udprelay.c encrypt.c utils.c netutils.c json.c jconf.c android.c LOCAL_MODULE := ss-tunnel LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/src/, $(SHADOWSOCKS_SOURCES)) LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DUDPRELAY_LOCAL -DUDPRELAY_TUNNEL \ -DUSE_CRYPTO_OPENSSL -DANDROID -DHAVE_CONFIG_H -DSSTUNNEL_JNI \ - -I$(LOCAL_PATH)/libev/ \ + -I$(LOCAL_PATH)/libev \ + -I$(LOCAL_PATH)/libancillary \ -I$(LOCAL_PATH)/shadowsocks-libev/libudns \ -I$(LOCAL_PATH)/shadowsocks-libev/libcork/include \ -I$(LOCAL_PATH)/shadowsocks-libev/libsodium/src/libsodium/include \ -I$(LOCAL_PATH)/shadowsocks-libev/libsodium/src/libsodium/include/sodium \ -I$(LOCAL_PATH)/openssl/include -LOCAL_STATIC_LIBRARIES := libev libcrypto libsodium libcork libudns +LOCAL_STATIC_LIBRARIES := libev libcrypto libsodium libcork libudns libancillary LOCAL_LDLIBS := -llog diff --git a/src/main/jni/shadowsocks-libev b/src/main/jni/shadowsocks-libev index 2b1eef11..3af591c5 160000 --- a/src/main/jni/shadowsocks-libev +++ b/src/main/jni/shadowsocks-libev @@ -1 +1 @@ -Subproject commit 2b1eef11973de3f7380401fd20f937e84bc2b756 +Subproject commit 3af591c5657c1c2c30a69cab3ca0e5d1f4e3d084 diff --git a/src/main/jni/system.cpp b/src/main/jni/system.cpp index 8a44882d..39cfc1e4 100644 --- a/src/main/jni/system.cpp +++ b/src/main/jni/system.cpp @@ -43,6 +43,10 @@ void Java_com_github_shadowsocks_system_exec(JNIEnv *env, jobject thiz, jstring env->ReleaseStringUTFChars(cmd, str); } +void Java_com_github_shadowsocks_system_jniclose(JNIEnv *env, jobject thiz, jint fd) { + close(fd); +} + jint Java_com_github_shadowsocks_system_sendfd(JNIEnv *env, jobject thiz, jint tun_fd) { int fd; struct sockaddr_un addr; @@ -75,6 +79,8 @@ jint Java_com_github_shadowsocks_system_sendfd(JNIEnv *env, jobject thiz, jint t static const char *classPathName = "com/github/shadowsocks/System"; static JNINativeMethod method_table[] = { + { "jniclose", "(I)V", + (void*) Java_com_github_shadowsocks_system_jniclose }, { "sendfd", "(I)I", (void*) Java_com_github_shadowsocks_system_sendfd }, { "exec", "(Ljava/lang/String;)V", diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index 8eed9747..75dcd5c3 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -61,18 +61,16 @@ import scala.concurrent.ops._ class ShadowsocksVpnService extends VpnService with BaseService { + private lazy val application = getApplication.asInstanceOf[ShadowsocksApplication] val TAG = "ShadowsocksVpnService" - val VPN_MTU = 1500 val PRIVATE_VLAN = "26.26.26.%s" - var conn: ParcelFileDescriptor = null var notificationManager: NotificationManager = null var receiver: BroadcastReceiver = null var apps: Array[ProxiedApp] = null var config: Config = null - - private lazy val application = getApplication.asInstanceOf[ShadowsocksApplication] + var vpnThread: ShadowsocksVpnThread = null def isByass(net: SubnetUtils): Boolean = { val info = net.getInfo @@ -95,81 +93,71 @@ class ShadowsocksVpnService extends VpnService with BaseService { } } - def startShadowsocksDaemon() { - - if (Utils.isLollipopOrAbove && config.route != Route.ALL) { - val acl: Array[String] = config.route match { - case Route.BYPASS_LAN => getResources.getStringArray(R.array.private_route) - case Route.BYPASS_CHN => getResources.getStringArray(R.array.chn_route_full) - } - ConfigUtils.printToFile(new File(Path.BASE + "acl.list"))(p => { - acl.foreach(item => p.println(item)) - }) + override def onBind(intent: Intent): IBinder = { + val action = intent.getAction + if (VpnService.SERVICE_INTERFACE == action) { + return super.onBind(intent) + } else if (Action.SERVICE == action) { + return binder } - - val conf = ConfigUtils - .SHADOWSOCKS.formatLocal(Locale.ENGLISH, config.proxy, config.remotePort, config.localPort, - config.sitekey, config.encMethod, 10) - ConfigUtils.printToFile(new File(Path.BASE + "ss-local-vpn.conf"))(p => { - p.println(conf) - }) - - val cmd = new ArrayBuffer[String] - cmd +=(Path.BASE + "ss-local", "-u" - , "-b" , "127.0.0.1" - , "-t" , "600" - , "-c" , Path.BASE + "ss-local-vpn.conf" - , "-f" , Path.BASE + "ss-local-vpn.pid") - - if (Utils.isLollipopOrAbove && config.route != Route.ALL) { - cmd += "--acl" - cmd += (Path.BASE + "acl.list") - } - - if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) - Console.runCommand(cmd.mkString(" ")) + null } - def startDnsTunnel() = { - val conf = ConfigUtils - .SHADOWSOCKS.formatLocal(Locale.ENGLISH, config.proxy, config.remotePort, 8163, - config.sitekey, config.encMethod, 10) - ConfigUtils.printToFile(new File(Path.BASE + "ss-tunnel-vpn.conf"))(p => { - p.println(conf) - }) - val cmd = new ArrayBuffer[String] - cmd +=(Path.BASE + "ss-tunnel" - , "-u" - , "-t" , "10" - , "-b" , "127.0.0.1" - , "-l" , "8163" - , "-L" , "8.8.8.8:53" - , "-c" , Path.BASE + "ss-tunnel-vpn.conf" - , "-f" , Path.BASE + "ss-tunnel-vpn.pid") - - if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) - Console.runCommand(cmd.mkString(" ")) + override def onDestroy() { + super.onDestroy() + if (vpnThread != null) vpnThread.stopThread() } - def startDnsDaemon() { - val conf = { - if (config.route == Route.BYPASS_CHN) { - val reject = ConfigUtils.getRejectList(getContext, application) - val blackList = ConfigUtils.getBlackList(getContext, application) - ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "0.0.0.0", 8153, - Path.BASE + "pdnsd-vpn.pid", reject, blackList, 8163) - } else { - ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, "0.0.0.0", 8153, - Path.BASE + "pdnsd-vpn.pid", 8163) - } - } - ConfigUtils.printToFile(new File(Path.BASE + "pdnsd-vpn.conf"))(p => { - p.println(conf) - }) - val cmd = Path.BASE + "pdnsd -c " + Path.BASE + "pdnsd-vpn.conf" + override def onCreate() { + super.onCreate() - if (BuildConfig.DEBUG) Log.d(TAG, cmd) - Console.runCommand(cmd) + ConfigUtils.refresh(this) + + notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) + .asInstanceOf[NotificationManager] + + vpnThread = new ShadowsocksVpnThread(this) + vpnThread.start() + } + + override def onRevoke() { + stopRunner() + } + + override def stopRunner() { + + // channge the state + changeState(State.STOPPING) + + // send event + application.tracker.send(new HitBuilders.EventBuilder() + .setCategory(TAG) + .setAction("stop") + .setLabel(getVersionName) + .build()) + + // reset VPN + killProcesses() + + // close connections + if (conn != null) { + conn.close() + conn = null + } + + // stop the service if no callback registered + if (getCallbackCount == 0) { + stopSelf() + } + + // clean up the context + if (receiver != null) { + unregisterReceiver(receiver) + receiver = null + } + + // channge the state + changeState(State.STOPPED) } def getVersionName: String = { @@ -184,208 +172,6 @@ class ShadowsocksVpnService extends VpnService with BaseService { version } - def startVpn(): Int = { - - val builder = new Builder() - builder - .setSession(config.profileName) - .setMtu(VPN_MTU) - .addAddress(PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"), 24) - .addDnsServer("8.8.8.8") - - if (Utils.isLollipopOrAbove) { - - builder.allowFamily(android.system.OsConstants.AF_INET6) - - if (!config.isGlobalProxy) { - val apps = AppManager.getProxiedApps(this, config.proxiedAppString) - val pkgSet: mutable.HashSet[String] = new mutable.HashSet[String] - for (app <- apps) { - if (app.proxied) { - pkgSet.add(app.packageName) - } - } - for (pkg <- pkgSet) { - if (!config.isBypassApps) { - builder.addAllowedApplication(pkg) - } else { - builder.addDisallowedApplication(pkg) - } - } - - if (config.isBypassApps) { - builder.addDisallowedApplication(this.getPackageName) - } - } else { - builder.addDisallowedApplication(this.getPackageName) - } - } - - if (InetAddressUtils.isIPv6Address(config.proxy)) { - builder.addRoute("0.0.0.0", 0) - } else { - if (!Utils.isLollipopOrAbove) { - config.route match { - case Route.BYPASS_LAN => - for (i <- 1 to 223) { - if (i != 26 && i != 127) { - val addr = i.toString + ".0.0.0" - val cidr = addr + "/8" - val net = new SubnetUtils(cidr) - - if (!isByass(net) && !isPrivateA(i)) { - builder.addRoute(addr, 8) - } else { - for (j <- 0 to 255) { - val subAddr = i.toString + "." + j.toString + ".0.0" - val subCidr = subAddr + "/16" - val subNet = new SubnetUtils(subCidr) - if (!isByass(subNet) && !isPrivateB(i, j)) { - builder.addRoute(subAddr, 16) - } - } - } - } - } - case Route.BYPASS_CHN => - val list = { - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { - getResources.getStringArray(R.array.simple_route) - } else { - getResources.getStringArray(R.array.gfw_route) - } - } - list.foreach(cidr => { - val net = new SubnetUtils(cidr) - if (!isByass(net)) { - val addr = cidr.split('/') - builder.addRoute(addr(0), addr(1).toInt) - } - }) - case Route.ALL => - for (i <- 1 to 223) { - if (i != 26 && i != 127) { - val addr = i.toString + ".0.0.0" - val cidr = addr + "/8" - val net = new SubnetUtils(cidr) - - if (!isByass(net)) { - builder.addRoute(addr, 8) - } else { - for (j <- 0 to 255) { - val subAddr = i.toString + "." + j.toString + ".0.0" - val subCidr = subAddr + "/16" - val subNet = new SubnetUtils(subCidr) - if (!isByass(subNet)) { - builder.addRoute(subAddr, 16) - } - } - } - } - } - } - } else { - if (config.route == Route.ALL) { - builder.addRoute("0.0.0.0", 0) - } else { - val privateList = getResources.getStringArray(R.array.bypass_private_route) - privateList.foreach(cidr => { - val addr = cidr.split('/') - builder.addRoute(addr(0), addr(1).toInt) - }) - } - } - } - - builder.addRoute("8.8.0.0", 16) - - try { - conn = builder.establish() - } catch { - case ex: IllegalStateException => - changeState(State.STOPPED, ex.getMessage) - conn = null - case ex: Exception => conn = null - } - - if (conn == null) { - stopRunner() - return -1 - } - - val fd = conn.getFd - - var cmd = (Path.BASE + - "tun2socks --netif-ipaddr %s " - + "--netif-netmask 255.255.255.0 " - + "--socks-server-addr 127.0.0.1:%d " - + "--tunfd %d " - + "--tunmtu %d " - + "--loglevel 3 " - + "--pid %stun2socks-vpn.pid") - .formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "2"), config.localPort, fd, VPN_MTU, Path.BASE) - - if (config.isUdpDns) - cmd += " --enable-udprelay" - else - cmd += " --dnsgw %s:8153".formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1")) - - // if (Utils.isLollipopOrAbove) { - // cmd += " --fake-proc" - // } - - if (BuildConfig.DEBUG) Log.d(TAG, cmd) - - Console.runCommand(cmd) - - return fd; - } - - /** Called when the activity is first created. */ - def handleConnection: Boolean = { - startShadowsocksDaemon() - if (!config.isUdpDns) { - startDnsDaemon() - startDnsTunnel() - } - - val fd = startVpn() - - if (fd == -1) { - false - } else { - Thread.sleep(1000) - if (System.sendfd(fd) == -1) { - false - } else { - true - } - } - } - - override def onBind(intent: Intent): IBinder = { - val action = intent.getAction - if (VpnService.SERVICE_INTERFACE == action) { - return super.onBind(intent) - } else if (Action.SERVICE == action) { - return binder - } - null - } - - override def onCreate() { - super.onCreate() - - ConfigUtils.refresh(this) - - notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) - .asInstanceOf[NotificationManager] - } - - override def onRevoke() { - stopRunner() - } - def killProcesses() { for (task <- Array("ss-local", "ss-tunnel", "pdnsd", "tun2socks")) { try { @@ -471,40 +257,187 @@ class ShadowsocksVpnService extends VpnService with BaseService { } } - override def stopRunner() { - - // channge the state - changeState(State.STOPPING) - - // send event - application.tracker.send(new HitBuilders.EventBuilder() - .setCategory(TAG) - .setAction("stop") - .setLabel(getVersionName) - .build()) - - // reset VPN - killProcesses() - - // close connections - if (conn != null) { - conn.close() - conn = null + /** Called when the activity is first created. */ + def handleConnection: Boolean = { + startShadowsocksDaemon() + if (!config.isUdpDns) { + startDnsDaemon() + startDnsTunnel() } - // stop the service if no callback registered - if (getCallbackCount == 0) { - stopSelf() + val fd = startVpn() + if (fd == -1) { + false + } else { + Thread.sleep(1000) + if (System.sendfd(fd) == -1) { + false + } else { + true + } } - // clean up the context - if (receiver != null) { - unregisterReceiver(receiver) - receiver = null + } + + def startShadowsocksDaemon() { + + if (Utils.isLollipopOrAbove && config.route != Route.ALL) { + val acl: Array[String] = config.route match { + case Route.BYPASS_LAN => getResources.getStringArray(R.array.private_route) + case Route.BYPASS_CHN => getResources.getStringArray(R.array.chn_route_full) + } + ConfigUtils.printToFile(new File(Path.BASE + "acl.list"))(p => { + acl.foreach(item => p.println(item)) + }) } - // channge the state - changeState(State.STOPPED) + val conf = ConfigUtils + .SHADOWSOCKS.formatLocal(Locale.ENGLISH, config.proxy, config.remotePort, config.localPort, + config.sitekey, config.encMethod, 10) + ConfigUtils.printToFile(new File(Path.BASE + "ss-local-vpn.conf"))(p => { + p.println(conf) + }) + + val cmd = new ArrayBuffer[String] + cmd +=(Path.BASE + "ss-local", "-V", "-u" + , "-b", "127.0.0.1" + , "-t", "600" + , "-c", Path.BASE + "ss-local-vpn.conf" + , "-f", Path.BASE + "ss-local-vpn.pid") + + if (config.route != Route.ALL) { + cmd += "--acl" + cmd += (Path.BASE + "acl.list") + } + + if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) + Console.runCommand(cmd.mkString(" ")) + } + + def startDnsTunnel() = { + val conf = ConfigUtils + .SHADOWSOCKS.formatLocal(Locale.ENGLISH, config.proxy, config.remotePort, 8163, + config.sitekey, config.encMethod, 10) + ConfigUtils.printToFile(new File(Path.BASE + "ss-tunnel-vpn.conf"))(p => { + p.println(conf) + }) + val cmd = new ArrayBuffer[String] + cmd +=(Path.BASE + "ss-tunnel" + , "-V" + , "-u" + , "-t", "10" + , "-b", "127.0.0.1" + , "-l", "8163" + , "-L", "8.8.8.8:53" + , "-c", Path.BASE + "ss-tunnel-vpn.conf" + , "-f", Path.BASE + "ss-tunnel-vpn.pid") + + if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) + Console.runCommand(cmd.mkString(" ")) + } + + def startDnsDaemon() { + val conf = { + if (config.route == Route.BYPASS_CHN) { + val reject = ConfigUtils.getRejectList(getContext, application) + val blackList = ConfigUtils.getBlackList(getContext, application) + ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "0.0.0.0", 8153, + Path.BASE + "pdnsd-vpn.pid", reject, blackList, 8163) + } else { + ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, "0.0.0.0", 8153, + Path.BASE + "pdnsd-vpn.pid", 8163) + } + } + ConfigUtils.printToFile(new File(Path.BASE + "pdnsd-vpn.conf"))(p => { + p.println(conf) + }) + val cmd = Path.BASE + "pdnsd -c " + Path.BASE + "pdnsd-vpn.conf" + + if (BuildConfig.DEBUG) Log.d(TAG, cmd) + Console.runCommand(cmd) + } + + override def getContext = getBaseContext + + def startVpn(): Int = { + + val builder = new Builder() + builder + .setSession(config.profileName) + .setMtu(VPN_MTU) + .addAddress(PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"), 24) + .addDnsServer("8.8.8.8") + + if (Utils.isLollipopOrAbove) { + + builder.allowFamily(android.system.OsConstants.AF_INET6) + + if (!config.isGlobalProxy) { + val apps = AppManager.getProxiedApps(this, config.proxiedAppString) + val pkgSet: mutable.HashSet[String] = new mutable.HashSet[String] + for (app <- apps) { + if (app.proxied) { + pkgSet.add(app.packageName) + } + } + for (pkg <- pkgSet) { + if (!config.isBypassApps) { + builder.addAllowedApplication(pkg) + } else { + builder.addDisallowedApplication(pkg) + } + } + } + } + + if (config.route == Route.ALL) { + builder.addRoute("0.0.0.0", 0) + } else { + val privateList = getResources.getStringArray(R.array.bypass_private_route) + privateList.foreach(cidr => { + val addr = cidr.split('/') + builder.addRoute(addr(0), addr(1).toInt) + }) + } + + builder.addRoute("8.8.0.0", 16) + + try { + conn = builder.establish() + } catch { + case ex: IllegalStateException => + changeState(State.STOPPED, ex.getMessage) + conn = null + case ex: Exception => conn = null + } + + if (conn == null) { + stopRunner() + return -1 + } + + val fd = conn.getFd + + var cmd = (Path.BASE + + "tun2socks --netif-ipaddr %s " + + "--netif-netmask 255.255.255.0 " + + "--socks-server-addr 127.0.0.1:%d " + + "--tunfd %d " + + "--tunmtu %d " + + "--loglevel 3 " + + "--pid %stun2socks-vpn.pid") + .formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "2"), config.localPort, fd, VPN_MTU, Path.BASE) + + if (config.isUdpDns) + cmd += " --enable-udprelay" + else + cmd += " --dnsgw %s:8153".formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1")) + + if (BuildConfig.DEBUG) Log.d(TAG, cmd) + + Console.runCommand(cmd) + + return fd } override def stopBackgroundService() { @@ -514,6 +447,4 @@ class ShadowsocksVpnService extends VpnService with BaseService { override def getTag = TAG override def getServiceMode = Mode.VPN - - override def getContext = getBaseContext } diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala new file mode 100644 index 00000000..7eadaad5 --- /dev/null +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala @@ -0,0 +1,138 @@ +/* + * 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 java.io.{File, FileDescriptor, IOException} +import java.util.concurrent.Executors + +import android.net.{LocalServerSocket, LocalSocket, LocalSocketAddress} +import android.util.Log + +class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread { + + val TAG = "ShadowsocksVpnService" + val PATH = "/data/data/com.github.shadowsocks/protect_path" + + var isRunning: Boolean = true + var serverSocket: LocalServerSocket = null + + def stopThread() { + isRunning = false + if (serverSocket != null) { + serverSocket.close() + serverSocket = null + } + } + + override def run(): Unit = { + + try { + new File(PATH).delete() + } catch { + case _: Exception => // ignore + } + + try { + val localSocket = new LocalSocket + + localSocket.bind(new LocalSocketAddress(PATH, LocalSocketAddress.Namespace.FILESYSTEM)) + serverSocket = new LocalServerSocket(localSocket.getFileDescriptor) + } catch { + case e: IOException => + Log.e(TAG, "unable to bind", e) + vpnService.stopBackgroundService() + return + } + + val pool = Executors.newFixedThreadPool(4) + + while (isRunning) { + + try { + val socket = serverSocket.accept() + + pool.execute(new Runnable { + override def run() { + try { + val input = socket.getInputStream + val output = socket.getOutputStream + + input.read() + + val fds = socket.getAncillaryFileDescriptors + + if (fds.length > 0) { + var ret = false + + val getInt = classOf[FileDescriptor].getDeclaredMethod("getInt$") + val fd = getInt.invoke(fds(0)).asInstanceOf[Int] + ret = vpnService.protect(fd) + + // Trick to close file decriptor + System.jniclose(fd) + + if (ret) { + output.write(0) + } else { + output.write(1) + } + + input.close() + output.close() + } + } catch { + case e: Exception => + Log.e(TAG, "Error when protect socket", e) + } + + // close socket + try { + socket.close() + } catch { + case _: Exception => // ignore + } + + }}) + } catch { + case e: IOException => Log.e(TAG, "Error when accept socket", e) + } + } + } +}