diff --git a/.gitignore b/.gitignore index 659d10c9..3dd83e34 100644 --- a/.gitignore +++ b/.gitignore @@ -26,8 +26,8 @@ out/ #NDK src/main/obj src/main/libs -src/main/assets/armeabi -src/main/assets/armeabi-v7a -src/main/assets/x86 -src/main/assets/api-16/ +src/main/assets/armeabi-v7a/* +!src/main/assets/armeabi-v7a/kcptun +src/main/assets/x86/* +!src/main/assets/x86/kcptun jni/Application.mk diff --git a/build.sbt b/build.sbt index 19f12b0f..40b00afb 100644 --- a/build.sbt +++ b/build.sbt @@ -55,6 +55,8 @@ proguardCache in Android := Seq() proguardOptions in Android ++= Seq( "-keep class com.github.shadowsocks.System { *; }", + "-dontwarn com.google.android.gms.**", + "-keep class com.google.android.gms.**", "-dontnote com.google.android.gms.internal.**", "-dontnote com.j256.ormlite.**", "-dontnote me.dm7.barcodescannerview.**", diff --git a/build.sh b/build.sh index 513c0323..7bd7c34f 100755 --- a/build.sh +++ b/build.sh @@ -11,12 +11,10 @@ try pushd src/main try $ANDROID_NDK_HOME/ndk-build -j8 # copy executables -rm -rf assets/armeabi-v7a -rm -rf assets/x86 -mkdir -p assets/armeabi-v7a -mkdir -p assets/x86 for app in pdnsd redsocks ss-local ss-tunnel tun2socks do + rm -f assets/armeabi-v7a/$app + rm -f assets/x86/$app try mv libs/armeabi-v7a/$app assets/armeabi-v7a/ try mv libs/x86/$app assets/x86/ done diff --git a/project/plugins.sbt b/project/plugins.sbt index 649f9d2c..3e14dab1 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ -addSbtPlugin("org.scala-android" % "sbt-android" % "1.6.8") +addSbtPlugin("org.scala-android" % "sbt-android" % "1.6.10") addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.10") diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index caf790ef..83f78b3d 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="142" + android:versionName="3.0.0"> diff --git a/src/main/assets/armeabi-v7a/kcptun b/src/main/assets/armeabi-v7a/kcptun new file mode 100755 index 00000000..fe5a7fbc Binary files /dev/null and b/src/main/assets/armeabi-v7a/kcptun differ diff --git a/src/main/assets/x86/kcptun b/src/main/assets/x86/kcptun new file mode 100755 index 00000000..77a6db39 Binary files /dev/null and b/src/main/assets/x86/kcptun differ diff --git a/src/main/jni/shadowsocks-libev b/src/main/jni/shadowsocks-libev index d4aabcb4..84cc1097 160000 --- a/src/main/jni/shadowsocks-libev +++ b/src/main/jni/shadowsocks-libev @@ -1 +1 @@ -Subproject commit d4aabcb40b4bcbca352269a1474a19de691950d0 +Subproject commit 84cc109743e656b549dc4e8a9572b6063563852c diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index 98a5222a..9a51b96d 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -134,4 +134,11 @@ Отправлено: Получено: + + + Включить Shadowsocks над KCPTUN + KCP порт + %d (KCPTUN омер порта удалённого сервера) + KCP параметры + Командная строка KCPTUN diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 6309eb9a..40252f06 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -134,4 +134,11 @@ 发送: 接收: + + 启用 KCP 协议 + KCP 端口 + %d(远端 KCPTUN 服务器的端口号) + KCP 参数 + KCPTUN 的命令行参数 + diff --git a/src/main/res/values-zh/strings.xml b/src/main/res/values-zh/strings.xml index 94af6526..76bd9b08 100644 --- a/src/main/res/values-zh/strings.xml +++ b/src/main/res/values-zh/strings.xml @@ -136,4 +136,11 @@ 傳送: 接收: + + 啟用 KCP 協定 + KCP 連接埠 + %d(遠端 KCPTUN 伺服器的連接埠號碼) + KCP 參數 + KCPTUN 的命令行參數 + diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 8c472f3b..d13dc92a 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -139,4 +139,12 @@ Sent: Received: + + + KCP + Enable Shadowsocks over KCP + KCP Port + %d (port number of the remote KCPTUN server) + KCP Parameters (CLI) + The command line of KCPTUN diff --git a/src/main/res/xml/pref_all.xml b/src/main/res/xml/pref_all.xml index c6689417..6c85273b 100644 --- a/src/main/res/xml/pref_all.xml +++ b/src/main/res/xml/pref_all.xml @@ -77,6 +77,31 @@ + + + + + + + + + + { p.println(conf) }) @@ -109,10 +116,23 @@ class ShadowsocksNatService extends BaseService { sslocalProcess = new GuardedProcess(cmd).start() } - def startTunnel() { + def startKcptunDaemon() { + + val cmd = new ArrayBuffer[String] + cmd += (getApplicationInfo.dataDir + "/kcptun" + , "-r", profile.host + ":" + profile.kcpPort + , "-l", "127.0.0.1:" + (profile.localPort + 90) + , profile.kcpcli) + + if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) + + kcptunProcess = new GuardedProcess(cmd.mkString(" ").split(" ").toSeq).start() + } + + def startDNSTunnel() { if (profile.udpdns) { val conf = ConfigUtils - .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, 8153, + .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort + 53, profile.password, profile.method, 10) Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf"))(p => { p.println(conf) @@ -123,11 +143,10 @@ class ShadowsocksNatService extends BaseService { , "-t" , "10" , "-b" , "127.0.0.1" , "-L" , "8.8.8.8:53" + , "-l" , (profile.localPort + 53).toString , "-P" , getApplicationInfo.dataDir , "-c" , getApplicationInfo.dataDir + "/ss-tunnel-nat.conf") - cmd += ("-l" , "8153") - if (profile.auth) cmd += "-A" if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) @@ -135,18 +154,23 @@ class ShadowsocksNatService extends BaseService { sstunnelProcess = new GuardedProcess(cmd).start() } else { - val conf = ConfigUtils - .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, 8163, + val conf = if (profile.kcp) { + ConfigUtils + .SHADOWSOCKS.formatLocal(Locale.ENGLISH, "127.0.0.1", profile.localPort + 90, + profile.localPort + 63, profile.password, profile.method, 10) + } else { + ConfigUtils + .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort + 63, profile.password, profile.method, 10) + } Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf"))(p => { p.println(conf) }) val cmdBuf = new ArrayBuffer[String] cmdBuf += (getApplicationInfo.dataDir + "/ss-tunnel" - , "-u" , "-t" , "10" , "-b" , "127.0.0.1" - , "-l" , "8163" + , "-l" , (profile.localPort + 63).toString , "-L" , "8.8.8.8:53" , "-P", getApplicationInfo.dataDir , "-c" , getApplicationInfo.dataDir + "/ss-tunnel-nat.conf") @@ -163,10 +187,10 @@ class ShadowsocksNatService extends BaseService { val conf = if (profile.route == Route.BYPASS_CHN || profile.route == Route.BYPASS_LAN_CHN) { ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, - "127.0.0.1", 8153, getBlackList, 8163, "") + "127.0.0.1", profile.localPort + 53, getBlackList, profile.localPort + 63, "") } else { ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, - "127.0.0.1", 8153, 8163, "") + "127.0.0.1", profile.localPort + 53, profile.localPort + 63, "") } Utils.printToFile(new File(getApplicationInfo.dataDir + "/pdnsd-nat.conf"))(p => { @@ -194,10 +218,13 @@ class ShadowsocksNatService extends BaseService { /** Called when the activity is first created. */ def handleConnection { - startTunnel() - if (!profile.udpdns) startDnsDaemon() + startDNSTunnel() startRedsocksDaemon() startShadowsocksDaemon() + + if (!profile.udpdns) startDnsDaemon() + if (profile.kcp) startKcptunDaemon() + setupIptables() } @@ -212,6 +239,10 @@ class ShadowsocksNatService extends BaseService { } def killProcesses() { + if (kcptunProcess != null) { + kcptunProcess.destroy() + kcptunProcess = null + } if (sslocalProcess != null) { sslocalProcess.destroy() sslocalProcess = null @@ -247,7 +278,8 @@ class ShadowsocksNatService extends BaseService { 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:8153") + 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) diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala index cfa2c781..324b7855 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala @@ -18,7 +18,7 @@ object ShadowsocksSettings { // Constants private final val TAG = "ShadowsocksSettings" private val PROXY_PREFS = Array(Key.name, Key.host, Key.remotePort, Key.localPort, Key.password, Key.method, - Key.auth) + Key.auth, Key.kcp, Key.kcpPort, Key.kcpcli) private val FEATURE_PREFS = Array(Key.route, Key.proxyApps, Key.udpdns, Key.ipv6) // Helper functions @@ -36,8 +36,10 @@ object ShadowsocksSettings { } def updateSummaryEditTextPreference(pref: Preference, value: String) { - pref.setSummary(value) - pref.asInstanceOf[SummaryEditTextPreference].setText(value) + if (value != null) { + pref.setSummary(value) + pref.asInstanceOf[SummaryEditTextPreference].setText(value) + } } def updateSwitchPreference(pref: Preference, value: Boolean) { @@ -57,6 +59,9 @@ object ShadowsocksSettings { case Key.udpdns => updateSwitchPreference(pref, profile.udpdns) case Key.auth => updateSwitchPreference(pref, profile.auth) case Key.ipv6 => updateSwitchPreference(pref, profile.ipv6) + case Key.kcp => updateSwitchPreference(pref, profile.kcp) + case Key.kcpPort => updateNumberPickerPreference(pref, profile.kcpPort) + case Key.kcpcli => updateSummaryEditTextPreference(pref, profile.kcpcli) } } } @@ -103,6 +108,19 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan app.profileManager.updateProfile(profile) }) + findPreference(Key.kcp).setOnPreferenceChangeListener((_, value) => { + profile.kcp = value.asInstanceOf[Boolean] + app.profileManager.updateProfile(profile) + }) + findPreference(Key.kcpPort).setOnPreferenceChangeListener((_, value) => { + profile.kcpPort = value.asInstanceOf[Int] + app.profileManager.updateProfile(profile) + }) + findPreference(Key.kcpcli).setOnPreferenceChangeListener((_, value) => { + profile.kcpcli = value.asInstanceOf[String] + app.profileManager.updateProfile(profile) + }) + isProxyApps = findPreference(Key.proxyApps).asInstanceOf[SwitchPreference] isProxyApps.setOnPreferenceClickListener(_ => { startActivity(new Intent(activity, classOf[AppManager])) diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index 213e313a..dbb254e0 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -63,6 +63,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { var vpnThread: ShadowsocksVpnThread = _ private var notification: ShadowsocksNotification = _ + var kcptunProcess: Process = _ var sslocalProcess: Process = _ var sstunnelProcess: Process = _ var pdnsdProcess: Process = _ @@ -109,6 +110,10 @@ class ShadowsocksVpnService extends VpnService with BaseService { } def killProcesses() { + if (kcptunProcess != null) { + kcptunProcess.destroy() + kcptunProcess = null + } if (sslocalProcess != null) { sslocalProcess.destroy() sslocalProcess = null @@ -177,18 +182,65 @@ class ShadowsocksVpnService extends VpnService with BaseService { /** Called when the activity is first created. */ def handleConnection: Boolean = { - startShadowsocksDaemon() + if (!profile.udpdns) { startDnsDaemon() startDnsTunnel() } + startShadowsocksDaemon() + + if (profile.udpdns) { + startShadowsocksUDPDaemon() + } + val fd = startVpn() - sendFd(fd) + val ret = sendFd(fd) + + if (ret && profile.kcp) { + startKcptunDaemon() + } + + ret + } + + def startKcptunDaemon() { + val cmd = new ArrayBuffer[String] + cmd += (getApplicationInfo.dataDir + "/kcptun" + , "-r", profile.host + ":" + profile.kcpPort + , "-l", "127.0.0.1:" + (profile.localPort + 90) + , "--path", getApplicationInfo.dataDir + "/protect_path" + , profile.kcpcli) + + if (BuildConfig.DEBUG) + Log.d(TAG, cmd.mkString(" ")) + + kcptunProcess = new GuardedProcess(cmd.mkString(" ").split(" ").toSeq).start() + } + + def startShadowsocksUDPDaemon() { + val conf = ConfigUtils + .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort, + profile.password, profile.method, 600) + Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-local-udp-vpn.conf"))(p => { + p.println(conf) + }) + + val cmd = new ArrayBuffer[String] + cmd += (getApplicationInfo.dataDir + "/ss-local", "-V", "-U" + , "-b", "127.0.0.1" + , "-t", "600" + , "-P", getApplicationInfo.dataDir + , "-c", getApplicationInfo.dataDir + "/ss-local-udp-vpn.conf") + + if (profile.auth) cmd += "-A" + + if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) + + sstunnelProcess = new GuardedProcess(cmd).start() } def startShadowsocksDaemon() { - if (profile.route != Route.ALL) { val acl: Array[Array[String]] = profile.route match { case Route.BYPASS_LAN => Array(getResources.getStringArray(R.array.private_route)) @@ -201,15 +253,21 @@ class ShadowsocksVpnService extends VpnService with BaseService { }) } - val conf = ConfigUtils + val conf = if (profile.kcp) { + ConfigUtils + .SHADOWSOCKS.formatLocal(Locale.ENGLISH, "127.0.0.1", profile.localPort + 90, profile.localPort, + profile.password, profile.method, 600) + } else { + ConfigUtils .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort, profile.password, profile.method, 600) + } Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-local-vpn.conf"))(p => { p.println(conf) }) val cmd = new ArrayBuffer[String] - cmd += (getApplicationInfo.dataDir + "/ss-local", "-V", "-u" + cmd += (getApplicationInfo.dataDir + "/ss-local", "-V" , "-b", "127.0.0.1" , "-t", "600" , "-P", getApplicationInfo.dataDir @@ -228,19 +286,23 @@ class ShadowsocksVpnService extends VpnService with BaseService { } def startDnsTunnel() = { - val conf = ConfigUtils - .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, 8163, + val conf = if (profile.kcp) { + ConfigUtils + .SHADOWSOCKS.formatLocal(Locale.ENGLISH, "127.0.0.1", profile.localPort + 90, profile.localPort + 63, profile.password, profile.method, 10) + } else { + ConfigUtils + .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort + 63, + profile.password, profile.method, 10) + } Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf"))(p => { p.println(conf) }) val cmd = new ArrayBuffer[String] cmd += (getApplicationInfo.dataDir + "/ss-tunnel" , "-V" - , "-u" , "-t", "10" , "-b", "127.0.0.1" - , "-l", "8163" , "-L", "8.8.8.8:53" , "-P", getApplicationInfo.dataDir , "-c", getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf") @@ -257,10 +319,10 @@ class ShadowsocksVpnService extends VpnService with BaseService { val conf = { if (profile.route == Route.BYPASS_CHN || profile.route == Route.BYPASS_LAN_CHN) { ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, - "0.0.0.0", 8153, getBlackList, 8163, ipv6) + "0.0.0.0", profile.localPort + 53, getBlackList, profile.localPort + 63, ipv6) } else { ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, - "0.0.0.0", 8153, 8163, ipv6) + "0.0.0.0", profile.localPort + 53, profile.localPort + 63, ipv6) } } Utils.printToFile(new File(getApplicationInfo.dataDir + "/pdnsd-vpn.conf"))(p => { @@ -354,7 +416,8 @@ class ShadowsocksVpnService extends VpnService with BaseService { if (profile.udpdns) cmd += " --enable-udprelay" else - cmd += " --dnsgw %s:8153".formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1")) + cmd += " --dnsgw %s:%d".formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"), + profile.localPort + 53) if (BuildConfig.DEBUG) Log.d(TAG, cmd) diff --git a/src/main/scala/com/github/shadowsocks/database/DBHelper.scala b/src/main/scala/com/github/shadowsocks/database/DBHelper.scala index d513fe58..acaefebf 100644 --- a/src/main/scala/com/github/shadowsocks/database/DBHelper.scala +++ b/src/main/scala/com/github/shadowsocks/database/DBHelper.scala @@ -64,7 +64,7 @@ object DBHelper { } class DBHelper(val context: Context) - extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 16) { + extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 18) { import DBHelper._ lazy val profileDao: Dao[Profile, Int] = getDao(classOf[Profile]) @@ -125,6 +125,15 @@ class DBHelper(val context: Context) if (oldVersion < 16) { profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'bypass-lan-china' WHERE route = 'bypass-china'") } + + if (oldVersion < 17) { + profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN kcp SMALLINT;") + profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN kcpcli VARCHAR;") + } + + if (oldVersion < 18) { + profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN kcpPort INTEGER;") + } } } } diff --git a/src/main/scala/com/github/shadowsocks/database/Profile.scala b/src/main/scala/com/github/shadowsocks/database/Profile.scala index 19570fd3..07dc1b55 100644 --- a/src/main/scala/com/github/shadowsocks/database/Profile.scala +++ b/src/main/scala/com/github/shadowsocks/database/Profile.scala @@ -99,6 +99,15 @@ class Profile { @DatabaseField var userOrder: Long = _ + @DatabaseField + var kcp: Boolean = false + + @DatabaseField + var kcpPort: Int = 8399 + + @DatabaseField + var kcpcli: String = "" + override def toString = "ss://" + Base64.encodeToString("%s%s:%s@%s:%d".formatLocal(Locale.ENGLISH, method, if (auth) "-auth" else "", password, host, remotePort).getBytes, Base64.NO_PADDING | Base64.NO_WRAP) diff --git a/src/main/scala/com/github/shadowsocks/utils/Constants.scala b/src/main/scala/com/github/shadowsocks/utils/Constants.scala index ef784116..f3211440 100644 --- a/src/main/scala/com/github/shadowsocks/utils/Constants.scala +++ b/src/main/scala/com/github/shadowsocks/utils/Constants.scala @@ -45,6 +45,7 @@ object Executable { val SS_LOCAL = "ss-local" val SS_TUNNEL = "ss-tunnel" val TUN2SOCKS = "tun2socks" + val KCPTUN = "kcptun" } object ConfigUtils { @@ -166,6 +167,10 @@ object Key { val localPort = "localPortNum" val profileTip = "profileTip" + + val kcp = "kcp" + val kcpPort = "kcpPort" + val kcpcli = "kcpcli" } object State {