diff --git a/.gitmodules b/.gitmodules index ca93a282..09136bb1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,6 +9,14 @@ path = mobile/src/main/jni/libancillary url = https://github.com/shadowsocks/libancillary.git branch = shadowsocks-android +[submodule "mobile/src/main/jni/libevent"] + path = mobile/src/main/jni/libevent + url = https://github.com/shadowsocks/libevent.git + branch = shadowsocks-android +[submodule "mobile/src/main/jni/redsocks"] + path = mobile/src/main/jni/redsocks + url = https://github.com/shadowsocks/redsocks.git + branch = shadowsocks-android [submodule "mobile/src/main/jni/mbedtls"] path = mobile/src/main/jni/mbedtls url = https://github.com/ARMmbed/mbedtls diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index c3ef3499..fbbe3bff 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -12,7 +12,7 @@ android:required="false"/> - diff --git a/mobile/src/main/jni/Android.mk b/mobile/src/main/jni/Android.mk index 303c88cc..ccc3b473 100755 --- a/mobile/src/main/jni/Android.mk +++ b/mobile/src/main/jni/Android.mk @@ -78,6 +78,32 @@ LOCAL_SRC_FILES := $(addprefix libsodium/src/libsodium/,$(SODIUM_SOURCE)) include $(BUILD_STATIC_LIBRARY) +######################################################## +## libevent +######################################################## + +include $(CLEAR_VARS) + +LIBEVENT_SOURCES := \ + buffer.c \ + bufferevent.c bufferevent_filter.c \ + bufferevent_pair.c bufferevent_ratelim.c \ + bufferevent_sock.c epoll.c \ + epoll_sub.c evdns.c event.c \ + event_tagging.c evmap.c \ + evrpc.c evthread.c \ + evthread_pthread.c evutil.c \ + evutil_rand.c http.c \ + listener.c log.c poll.c \ + select.c signal.c strlcpy.c + +LOCAL_MODULE := event +LOCAL_SRC_FILES := $(addprefix libevent/, $(LIBEVENT_SOURCES)) +LOCAL_CFLAGS := -O2 -D_EVENT_HAVE_ARC4RANDOM -I$(LOCAL_PATH)/libevent \ + -I$(LOCAL_PATH)/libevent/include \ + +include $(BUILD_STATIC_LIBRARY) + ######################################################## ## libancillary ######################################################## @@ -174,6 +200,28 @@ LOCAL_SRC_FILES := \ include $(BUILD_STATIC_LIBRARY) +######################################################## +## redsocks +######################################################## + +include $(CLEAR_VARS) + +REDSOCKS_SOURCES := base.c http-connect.c \ + log.c md5.c socks5.c \ + base64.c http-auth.c http-relay.c main.c \ + parser.c redsocks.c socks4.c utils.c + +LOCAL_STATIC_LIBRARIES := libevent + +LOCAL_MODULE := redsocks +LOCAL_SRC_FILES := $(addprefix redsocks/, $(REDSOCKS_SOURCES)) +LOCAL_CFLAGS := -O2 -std=gnu99 -DUSE_IPTABLES \ + -I$(LOCAL_PATH)/redsocks \ + -I$(LOCAL_PATH)/libevent/include \ + -I$(LOCAL_PATH)/libevent + +include $(BUILD_SHARED_EXECUTABLE) + ######################################################## ## shadowsocks-libev local ######################################################## diff --git a/mobile/src/main/jni/libevent b/mobile/src/main/jni/libevent new file mode 160000 index 00000000..359ca847 --- /dev/null +++ b/mobile/src/main/jni/libevent @@ -0,0 +1 @@ +Subproject commit 359ca847a649b9c318f9217c0755484d98ecb779 diff --git a/mobile/src/main/jni/redsocks b/mobile/src/main/jni/redsocks new file mode 160000 index 00000000..274334f1 --- /dev/null +++ b/mobile/src/main/jni/redsocks @@ -0,0 +1 @@ +Subproject commit 274334f14839431ae003774d99c3d1de337afff4 diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index ed44e500..09e252b8 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -7,8 +7,8 @@ Profile Switch to another profile or add new profiles - SOCKS5 mode - Enable SOCKS5 proxy mode instead of VPN mode to work with AFWall+ or Orbot + NAT mode (deprecated) + Use NAT mode instead of VPN mode. Requires ROOT permission. Remote DNS Sent: \t\t\t\t\t%3$s\t↑\t%1$s/s\nReceived: \t%4$s\t↓\t%2$s/s %1$s↑\t%2$s↓ @@ -43,7 +43,7 @@ GFW List China List Per-App Proxy - Set proxy for selected apps + Set proxy for selected apps, requires NAT mode under Android 4.x Set proxy for selected apps On Bypass Mode @@ -57,10 +57,12 @@ VPN Service - SOCKS5 Service + NAT Service Shadowsocks started. Invalid server name Failed to connect the remote server + WARNING: NAT mode has been deprecated since Android 5.0 + NAT mode requires ROOT permission Switch to VPN mode Stop Shutting down… @@ -122,7 +124,7 @@ Received: Connecting… Connected, tap to check connection - Connected + Connected Not connected diff --git a/mobile/src/main/res/xml/pref_global.xml b/mobile/src/main/res/xml/pref_global.xml index d6df49f7..7d54d225 100644 --- a/mobile/src/main/res/xml/pref_global.xml +++ b/mobile/src/main/res/xml/pref_global.xml @@ -8,6 +8,6 @@ android:summary="@string/tcp_fastopen_summary" android:title="TCP Fast Open"/> + android:title="@string/nat" + android:summary="@string/nat_summary"/> diff --git a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala index 5bef0c68..9450aac6 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala @@ -117,7 +117,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe if (state == State.CONNECTING) fabProgressCircle.beginFinalAnimation() else fabProgressCircle.postDelayed(hideCircle, 1000) fab.setImageResource(R.drawable.ic_start_connected) - statusText.setText(if (app.isLocalEnabled) R.string.local_connected else R.string.vpn_connected) + statusText.setText(if (app.isNatEnabled) R.string.nat_connected else R.string.vpn_connected) case State.STOPPING => fab.setImageResource(R.drawable.ic_start_busy) if (state == State.CONNECTED) fabProgressCircle.show() // ignore for stopped @@ -128,6 +128,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe if (m != null) { val snackbar = Snackbar.make(findViewById(R.id.snackbar), getString(R.string.vpn_error).formatLocal(Locale.ENGLISH, m), Snackbar.LENGTH_LONG) + if (m == getString(R.string.nat_no_root)) addDisableNatToSnackbar(snackbar) snackbar.show() Log.e(TAG, "Error to start VPN service: " + m) } @@ -156,9 +157,16 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe override def onServiceConnected() { changeState(bgService.getState) + if (Build.VERSION.SDK_INT >= 21 && app.isNatEnabled) { + val snackbar = Snackbar.make(findViewById(R.id.snackbar), R.string.nat_deprecated, Snackbar.LENGTH_LONG) + addDisableNatToSnackbar(snackbar) + snackbar.show() + } } override def onServiceDisconnected(): Unit = changeState(State.IDLE) + private def addDisableNatToSnackbar(snackbar: Snackbar) = snackbar.setAction(R.string.switch_to_vpn, (_ => + if (state == State.STOPPED) app.dataStore.isNAT = false): View.OnClickListener) override def binderDied(): Unit = handler.post(() => { detachService() @@ -292,7 +300,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe fab = findViewById(R.id.fab).asInstanceOf[FloatingActionButton] fabProgressCircle = findViewById(R.id.fabProgressCircle).asInstanceOf[FABProgressCircle] fab.setOnClickListener(_ => if (state == State.CONNECTED) Utils.stopSsService(this) else Utils.ThrowableFuture { - if (app.isLocalEnabled) Utils.startSsService(this) else { + if (app.isNatEnabled) Utils.startSsService(this) else { val intent = VpnService.prepare(this) if (intent != null) startActivityForResult(intent, REQUEST_CONNECT) else handler.post(() => onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null)) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala index 1f304e6d..16cdf902 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala @@ -66,7 +66,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe findPreference(Key.password).setSummary("\u2022" * 32) } isProxyApps = findPreference(Key.proxyApps).asInstanceOf[SwitchPreference] - isProxyApps.setEnabled(Utils.isLollipopOrAbove || app.isLocalEnabled) + isProxyApps.setEnabled(Utils.isLollipopOrAbove || app.isNatEnabled) isProxyApps.setOnPreferenceClickListener(_ => { startActivity(new Intent(getActivity, classOf[AppManager])) isProxyApps.setChecked(true) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala b/mobile/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala index 4f4ab15c..f588ff2c 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala @@ -85,7 +85,7 @@ trait ServiceBoundContext extends Context with IBinder.DeathRecipient { protected def attachService(callback: IShadowsocksServiceCallback.Stub = null) { this.callback = callback if (bgService == null) { - val s = if (app.isLocalEnabled) classOf[ShadowsocksLocalService] else classOf[ShadowsocksVpnService] + val s = if (app.isNatEnabled) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService] val intent = new Intent(this, s) intent.setAction(Action.SERVICE) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala index 296e320d..d8419e0c 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala @@ -66,8 +66,8 @@ class ShadowsocksApplication extends Application { lazy val profileManager = new ProfileManager(dbHelper) lazy val dataStore = new OrmLitePreferenceDataStore(dbHelper) - def isLocalEnabled: Boolean = dataStore.isNAT - def isVpnEnabled: Boolean = !isLocalEnabled + def isNatEnabled: Boolean = dataStore.isNAT + def isVpnEnabled: Boolean = !isNatEnabled // send event def track(category: String, action: String): Unit = tracker.send(new HitBuilders.EventBuilder() @@ -156,7 +156,7 @@ class ShadowsocksApplication extends Application { if (Build.VERSION.SDK_INT >= 26) getSystemService(classOf[NotificationManager]).createNotificationChannels(List( new NotificationChannel("service-vpn", getText(R.string.service_vpn), NotificationManager.IMPORTANCE_MIN), - new NotificationChannel("service-local", getText(R.string.service_local), NotificationManager.IMPORTANCE_LOW) + new NotificationChannel("service-nat", getText(R.string.service_nat), NotificationManager.IMPORTANCE_LOW) )) } @@ -165,9 +165,15 @@ class ShadowsocksApplication extends Application { for (task <- Executable.EXECUTABLES) { cmd.append("killall lib%s.so".formatLocal(Locale.ENGLISH, task)) - cmd.append("rm -f %1$s/%2$s-local.conf %1$s/%2$s-vpn.conf" + cmd.append("rm -f %1$s/%2$s-nat.conf %1$s/%2$s-vpn.conf" .formatLocal(Locale.ENGLISH, getFilesDir.getAbsolutePath, task)) } + if (app.isNatEnabled) { + cmd.append("iptables -t nat -F OUTPUT") + cmd.append("echo done") + val result = Shell.SU.run(cmd.toArray) + if (result != null && !result.isEmpty) return // fallback to SH + } Shell.SH.run(cmd.toArray) } diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksLocalService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksLocalService.scala deleted file mode 100644 index 248cd5e6..00000000 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksLocalService.scala +++ /dev/null @@ -1,121 +0,0 @@ -/*******************************************************************************/ -/* */ -/* Copyright (C) 2017 by Max Lv */ -/* Copyright (C) 2017 by Mygod Studio */ -/* */ -/* 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 . */ -/* */ -/*******************************************************************************/ - -package com.github.shadowsocks - -import java.io.File -import java.net.{Inet6Address, InetAddress} -import java.util.Locale - -import android.app.Service -import android.content._ -import android.os._ -import android.util.Log -import com.github.shadowsocks.ShadowsocksApplication.app -import com.github.shadowsocks.acl.{Acl, AclSyncJob} -import com.github.shadowsocks.database.Profile -import com.github.shadowsocks.utils._ - -import scala.collection.JavaConversions._ -import scala.collection.mutable.ArrayBuffer - -class ShadowsocksLocalService extends BaseService { - - val TAG = "ShadowsocksLocalService" - - var sslocalProcess: GuardedProcess = _ - - def startShadowsocksDaemon() { - val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so", - "-b", "127.0.0.1", - "-l", profile.localPort.toString, - "-t", "600", - "-c", buildShadowsocksConfig("ss-local-local.conf")) - - if (TcpFastOpen.sendEnabled) cmd += "--fast-open" - - if (profile.route != Acl.ALL) { - cmd += "--acl" - cmd += Acl.getFile(profile.route match { - case Acl.CUSTOM_RULES => Acl.CUSTOM_RULES_FLATTENED - case route => route - }).getAbsolutePath - } - - sslocalProcess = new GuardedProcess(cmd: _*).start() - } - - /** Called when the activity is first created. */ - def handleConnection() { - - startShadowsocksDaemon() - - } - - def onBind(intent: Intent): IBinder = { - Log.d(TAG, "onBind") - if (Action.SERVICE == intent.getAction) { - binder - } else { - null - } - } - - def killProcesses() { - if (sslocalProcess != null) { - sslocalProcess.destroy() - sslocalProcess = null - } - } - - override def connect() { - super.connect() - - // Clean up - killProcesses() - - if (!Utils.isNumeric(profile.host)) Utils.resolve(profile.host, enableIPv6 = true) match { - case Some(a) => profile.host = a - case None => throw NameNotResolvedException() - } - - handleConnection() - - if (profile.route != Acl.ALL && profile.route != Acl.CUSTOM_RULES) - AclSyncJob.schedule(profile.route) - - changeState(State.CONNECTED) - } - - override def createNotification() = new ShadowsocksNotification(this, profile.name, "service-local", true) - - override def stopRunner(stopService: Boolean, msg: String = null) { - - // channge the state - changeState(State.STOPPING) - - app.track(TAG, "stop") - - // reset NAT - killProcesses() - - super.stopRunner(stopService, msg) - } -} diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala new file mode 100644 index 00000000..dd915eb3 --- /dev/null +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala @@ -0,0 +1,231 @@ +/*******************************************************************************/ +/* */ +/* Copyright (C) 2017 by Max Lv */ +/* Copyright (C) 2017 by Mygod Studio */ +/* */ +/* 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 . */ +/* */ +/*******************************************************************************/ + +package com.github.shadowsocks + +import java.io.File +import java.net.{Inet6Address, InetAddress} +import java.util.Locale + +import android.app.Service +import android.content._ +import android.os._ +import android.util.Log +import com.github.shadowsocks.ShadowsocksApplication.app +import com.github.shadowsocks.acl.{Acl, AclSyncJob} +import com.github.shadowsocks.database.Profile +import com.github.shadowsocks.utils._ +import eu.chainfire.libsuperuser.Shell + +import scala.collection.JavaConversions._ +import scala.collection.mutable.ArrayBuffer + +class ShadowsocksNatService extends BaseService { + + val TAG = "ShadowsocksNatService" + + val CMD_IPTABLES_DNAT_ADD_SOCKS = + "iptables -t nat -A OUTPUT -p tcp -j DNAT --to-destination 127.0.0.1:8123" + + val myUid: Int = android.os.Process.myUid() + + var sslocalProcess: GuardedProcess = _ + var sstunnelProcess: GuardedProcess = _ + var redsocksProcess: GuardedProcess = _ + var overtureProcess: GuardedProcess = _ + var su: Shell.Interactive = _ + + def startShadowsocksDaemon() { + val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so", + "-b", "127.0.0.1", + "-l", profile.localPort.toString, + "-t", "600", + "-c", buildShadowsocksConfig("ss-local-nat.conf")) + + if (TcpFastOpen.sendEnabled) cmd += "--fast-open" + + if (profile.route != Acl.ALL) { + cmd += "--acl" + cmd += Acl.getFile(profile.route match { + case Acl.CUSTOM_RULES => Acl.CUSTOM_RULES_FLATTENED + case route => route + }).getAbsolutePath + } + + sslocalProcess = new GuardedProcess(cmd: _*).start() + } + + def startDNSTunnel() { + val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-tunnel.so", + "-t", "10", + "-b", "127.0.0.1", + "-l", (profile.localPort + 63).toString, + "-L", profile.remoteDns.split(",").head.trim + ":53", + "-c", buildShadowsocksConfig("ss-tunnel-nat.conf")) + + if (profile.udpdns) cmd.append("-u") + + sstunnelProcess = new GuardedProcess(cmd: _*).start() + } + + def startDnsDaemon() { + overtureProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/liboverture.so", + "-c", buildOvertureConfig("overture-nat.conf")) + .start() + } + + def startRedsocksDaemon() { + IOUtils.writeString(new File(getFilesDir, "redsocks-nat.conf"), + ConfigUtils.REDSOCKS.formatLocal(Locale.ENGLISH, profile.localPort)) + redsocksProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/libredsocks.so", + "-c", "redsocks-nat.conf").start() + } + + /** Called when the activity is first created. */ + def handleConnection() { + + startRedsocksDaemon() + startShadowsocksDaemon() + + if (!profile.udpdns) + startDnsDaemon() + else + startDNSTunnel() + + setupIptables() + + } + + def onBind(intent: Intent): IBinder = { + Log.d(TAG, "onBind") + if (Action.SERVICE == intent.getAction) { + binder + } else { + null + } + } + + def killProcesses() { + if (sslocalProcess != null) { + sslocalProcess.destroy() + sslocalProcess = null + } + if (sstunnelProcess != null) { + sstunnelProcess.destroy() + sstunnelProcess = null + } + if (redsocksProcess != null) { + redsocksProcess.destroy() + redsocksProcess = null + } + if (overtureProcess != null) { + overtureProcess.destroy() + overtureProcess = null + } + + su.addCommand("iptables -t nat -F OUTPUT") + } + + def setupIptables() { + val init_sb = new ArrayBuffer[String] + val http_sb = new ArrayBuffer[String] + + init_sb.append("ulimit -n 4096") + init_sb.append("iptables -t nat -F OUTPUT") + + val cmd_bypass = "iptables -t nat -A OUTPUT -p tcp -d 0.0.0.0 -j RETURN" + if (!InetAddress.getByName(profile.host.toUpperCase).isInstanceOf[Inet6Address]) { + init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-d " + profile.host)) + } + init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-d 127.0.0.1")) + init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-m owner --uid-owner " + myUid)) + init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "--dport 53")) + + init_sb.append("iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:" + + (profile.localPort + 53)) + + if (!profile.proxyApps || profile.bypass) { + http_sb.append(CMD_IPTABLES_DNAT_ADD_SOCKS) + } + if (profile.proxyApps) { + val uidMap = getPackageManager.getInstalledApplications(0).map(ai => ai.packageName -> ai.uid).toMap + for (pn <- profile.individual.split('\n')) uidMap.get(pn) match { + case Some(uid) => + if (!profile.bypass) { + http_sb.append(CMD_IPTABLES_DNAT_ADD_SOCKS + .replace("-t nat", "-t nat -m owner --uid-owner " + uid)) + } else { + init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "-m owner --uid-owner " + uid)) + } + case _ => // probably removed package, ignore + } + } + su.addCommand((init_sb ++ http_sb).toArray) + } + + override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = if (su == null) { + su = new Shell.Builder().useSU().setWantSTDERR(true).setWatchdogTimeout(10).open((_, exitCode, _) => + if (exitCode == 0) super.onStartCommand(intent, flags, startId) else { + if (su != null) { + su.close() + su = null + } + super.stopRunner(stopService = true, getString(R.string.nat_no_root)) + }) + Service.START_NOT_STICKY + } else super.onStartCommand(intent, flags, startId) + + override def connect() { + super.connect() + + // Clean up + killProcesses() + + if (!Utils.isNumeric(profile.host)) Utils.resolve(profile.host, enableIPv6 = true) match { + case Some(a) => profile.host = a + case None => throw NameNotResolvedException() + } + + handleConnection() + + if (profile.route != Acl.ALL && profile.route != Acl.CUSTOM_RULES) + AclSyncJob.schedule(profile.route) + + changeState(State.CONNECTED) + } + + override def createNotification() = new ShadowsocksNotification(this, profile.name, "service-nat", true) + + override def stopRunner(stopService: Boolean, msg: String = null) { + + // channge the state + changeState(State.STOPPING) + + app.track(TAG, "stop") + + // reset NAT + killProcesses() + + su.close() + su = null + + super.stopRunner(stopService, msg) + } +} diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala index a1c5a9cf..9fd86e8e 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala @@ -46,7 +46,7 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext { } def startBackgroundService() { - if (app.isLocalEnabled) { + if (app.isNatEnabled) { Utils.startSsService(this) finish() } else { diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala index 349ccc58..eae40a19 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala @@ -33,7 +33,7 @@ import android.view.View.MeasureSpec import android.view.{Gravity, View, Window} import android.widget.Toast import com.github.shadowsocks.ShadowsocksApplication.app -import com.github.shadowsocks.{BuildConfig, ShadowsocksLocalService, ShadowsocksVpnService} +import com.github.shadowsocks.{BuildConfig, ShadowsocksNatService, ShadowsocksVpnService} import org.xbill.DNS._ import scala.collection.JavaConversions._ @@ -138,7 +138,7 @@ object Utils { def startSsService(context: Context) { val intent = - new Intent(context, if (app.dataStore.isNAT) classOf[ShadowsocksLocalService] else classOf[ShadowsocksVpnService]) + new Intent(context, if (app.dataStore.isNAT) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService]) if (Build.VERSION.SDK_INT >= 26) context.startForegroundService(intent) else context.startService(intent) } def reloadSsService(context: Context): Unit = context.sendBroadcast(new Intent(Action.RELOAD))