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))