From 29656fb9a6d9e93db25a8c1cbe05d770bc3a882f Mon Sep 17 00:00:00 2001 From: aman Date: Tue, 26 Apr 2022 23:49:20 +0530 Subject: [PATCH] Shadowsocks protocol added --- client/android/AndroidManifest.xml | 5 +- client/android/build.gradle | 26 +- client/android/shadowsocks/.gitignore | 1 - client/android/shadowsocks/build.gradle | 11 +- .../29.json | 174 ------ .../4.json | 46 -- .../shadowsocks/src/main/AndroidManifest.xml | 83 ++- .../vpn/shadowsocks/core/VpnManager.kt | 54 +- .../amnezia/vpn/shadowsocks/core/acl/Acl.kt | 20 - .../vpn/shadowsocks/core/acl/AclSyncer.kt | 20 - .../core/aidl/ShadowsocksConnection.kt | 4 +- .../vpn/shadowsocks/core/bg/BaseService.kt | 112 ++-- .../vpn/shadowsocks/core/bg/ProxyService.kt | 4 +- .../core/bg/ServiceNotification.kt | 280 +++++----- ...VpnService.kt => ShadowsocksVpnService.kt} | 15 +- .../shadowsocks/core/bg/TransproxyService.kt | 21 +- .../vpn/shadowsocks/core/utils/Constants.kt | 8 +- .../core/widget/NativePluginProvider.kt | 6 +- .../shadowsocks/core/widget/PluginContract.kt | 36 +- .../main/res/drawable/ic_amnezia_round.xml | 10 + .../main/res/drawable/ic_service_active.xml | 11 - .../android/src/org/amnezia/vpn/VPNService.kt | 508 ++++++++++++++++-- 22 files changed, 791 insertions(+), 664 deletions(-) delete mode 100644 client/android/shadowsocks/.gitignore delete mode 100644 client/android/shadowsocks/schemas/org.amnezia.vpn.shadowsocks.core.database.PrivateDatabase/29.json delete mode 100644 client/android/shadowsocks/schemas/org.amnezia.vpn.shadowsocks.core.database.PublicDatabase/4.json rename client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/{VpnService.kt => ShadowsocksVpnService.kt} (96%) create mode 100644 client/android/shadowsocks/src/main/res/drawable/ic_amnezia_round.xml delete mode 100644 client/android/shadowsocks/src/main/res/drawable/ic_service_active.xml diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml index d0cfc058..78d575da 100644 --- a/client/android/AndroidManifest.xml +++ b/client/android/AndroidManifest.xml @@ -77,7 +77,10 @@ - + + + + diff --git a/client/android/build.gradle b/client/android/build.gradle index bc5322f7..001dc4cb 100644 --- a/client/android/build.gradle +++ b/client/android/build.gradle @@ -47,32 +47,10 @@ dependencies { implementation "androidx.security:security-crypto:1.1.0-alpha03" implementation "androidx.security:security-identity-credential:1.0.0-alpha02" implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0" coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.0.10" implementation project(path: ':shadowsocks') - //ss - // implementation 'androidx.lifecycle:lifecycle-common-java8:2.4.0' - // implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - // implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0" - // //implementation "androidx.core:core-ktx:1.2.0" - // implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" - // implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0" - // implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:2.4.0" - // implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" - // implementation "androidx.room:room-runtime:2.2.5" // runtime - // implementation "androidx.preference:preference:1.1.0" - // implementation "androidx.work:work-runtime-ktx:2.3.4" - // implementation "androidx.browser:browser:1.3.0-alpha01" - // implementation "androidx.constraintlayout:constraintlayout:1.1.3" - // implementation "com.google.android.material:material:1.2.0-alpha05" - // implementation "com.google.code.gson:gson:2.8.5" - // implementation "dnsjava:dnsjava:2.1.9" - // implementation "org.connectbot.jsocks:jsocks:1.0.0" - // implementation "com.afollestad.material-dialogs:core:2.6.0" - // implementation 'com.takisoft.preferencex:preferencex:1.1.0' - // implementation 'com.android.support:multidex:1.0.0' - // api 'org.connectbot.jsocks:jsocks:1.0.0' - // annotationProcessor "androidx.room:room-compiler:2.2.5" - // annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.4.0" } androidExtensions { diff --git a/client/android/shadowsocks/.gitignore b/client/android/shadowsocks/.gitignore deleted file mode 100644 index 796b96d1..00000000 --- a/client/android/shadowsocks/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/client/android/shadowsocks/build.gradle b/client/android/shadowsocks/build.gradle index f3446189..d35daa11 100644 --- a/client/android/shadowsocks/build.gradle +++ b/client/android/shadowsocks/build.gradle @@ -3,6 +3,7 @@ allprojects { repositories { google() jcenter() + mavenCentral() } } @@ -40,14 +41,6 @@ android { jvmTarget = '1.8' } } -//publish { -// userOrg = 'kyle' //bintray注册的用户名 -// groupId = 'com.kyle' //compile引用时的第1部分groupId -// artifactId = 'shadowsocks' //compile引用时的第2部分项目名 -// publishVersion = '1.0.1' //compile引用时的第3部分版本号 -// desc = 'This is a shadowsocks library ' -// website = 'https://github.com/zhengKyles/shadowsocksDemo' -//} androidExtensions { experimental = true @@ -62,6 +55,8 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.30-M1" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0" + implementation "androidx.core:core-ktx:1.2.0" implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" diff --git a/client/android/shadowsocks/schemas/org.amnezia.vpn.shadowsocks.core.database.PrivateDatabase/29.json b/client/android/shadowsocks/schemas/org.amnezia.vpn.shadowsocks.core.database.PrivateDatabase/29.json deleted file mode 100644 index f9a4300e..00000000 --- a/client/android/shadowsocks/schemas/org.amnezia.vpn.shadowsocks.core.database.PrivateDatabase/29.json +++ /dev/null @@ -1,174 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 29, - "identityHash": "b60ecca4d684ffe73173478bffd50a17", - "entities": [ - { - "tableName": "Profile", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bypass` INTEGER NOT NULL, `host` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `individual` TEXT NOT NULL, `ipv6` INTEGER NOT NULL, `metered` INTEGER NOT NULL, `method` TEXT NOT NULL, `name` TEXT, `password` TEXT NOT NULL, `plugin` TEXT, `proxyApps` INTEGER NOT NULL, `remoteDns` TEXT NOT NULL, `remotePort` INTEGER NOT NULL, `route` TEXT NOT NULL, `rx` INTEGER NOT NULL, `tx` INTEGER NOT NULL, `udpFallback` INTEGER, `udpdns` INTEGER NOT NULL, `userOrder` INTEGER NOT NULL)", - "fields": [ - { - "fieldPath": "bypass", - "columnName": "bypass", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "host", - "columnName": "host", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "id", - "columnName": "id", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "individual", - "columnName": "individual", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "ipv6", - "columnName": "ipv6", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "metered", - "columnName": "metered", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "method", - "columnName": "method", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "password", - "columnName": "password", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "plugin", - "columnName": "plugin", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "proxyApps", - "columnName": "proxyApps", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "remoteDns", - "columnName": "remoteDns", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "remotePort", - "columnName": "remotePort", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "route", - "columnName": "route", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "rx", - "columnName": "rx", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "tx", - "columnName": "tx", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "udpFallback", - "columnName": "udpFallback", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "udpdns", - "columnName": "udpdns", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "userOrder", - "columnName": "userOrder", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": true - }, - "indices": [], - "foreignKeys": [] - }, - { - "tableName": "KeyValuePair", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` BLOB NOT NULL, `valueType` INTEGER NOT NULL, PRIMARY KEY(`key`))", - "fields": [ - { - "fieldPath": "key", - "columnName": "key", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "value", - "columnName": "value", - "affinity": "BLOB", - "notNull": true - }, - { - "fieldPath": "valueType", - "columnName": "valueType", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "key" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [] - } - ], - "views": [], - "setupQueries": [ - "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b60ecca4d684ffe73173478bffd50a17')" - ] - } -} \ No newline at end of file diff --git a/client/android/shadowsocks/schemas/org.amnezia.vpn.shadowsocks.core.database.PublicDatabase/4.json b/client/android/shadowsocks/schemas/org.amnezia.vpn.shadowsocks.core.database.PublicDatabase/4.json deleted file mode 100644 index b15900b6..00000000 --- a/client/android/shadowsocks/schemas/org.amnezia.vpn.shadowsocks.core.database.PublicDatabase/4.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 4, - "identityHash": "f1aab1fb633378621635c344dbc8ac7b", - "entities": [ - { - "tableName": "KeyValuePair", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` BLOB NOT NULL, `valueType` INTEGER NOT NULL, PRIMARY KEY(`key`))", - "fields": [ - { - "fieldPath": "key", - "columnName": "key", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "value", - "columnName": "value", - "affinity": "BLOB", - "notNull": true - }, - { - "fieldPath": "valueType", - "columnName": "valueType", - "affinity": "INTEGER", - "notNull": true - } - ], - "primaryKey": { - "columnNames": [ - "key" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [] - } - ], - "views": [], - "setupQueries": [ - "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f1aab1fb633378621635c344dbc8ac7b')" - ] - } -} \ No newline at end of file diff --git a/client/android/shadowsocks/src/main/AndroidManifest.xml b/client/android/shadowsocks/src/main/AndroidManifest.xml index e98b9f4b..9769ab7a 100644 --- a/client/android/shadowsocks/src/main/AndroidManifest.xml +++ b/client/android/shadowsocks/src/main/AndroidManifest.xml @@ -30,48 +30,45 @@ android:name="com.google.android.backup.api_key" android:value="AEdPqrEAAAAI_zVxZthz2HDuz9toTvkYvL0L5GA-OjeUIfBeXg" /> - - - - - - + + + + + + + + + + + + - + + + + + + - + + + + + + - + + + + + + + android:process=":QtOnlyProcess"> @@ -87,48 +84,48 @@ diff --git a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/VpnManager.kt b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/VpnManager.kt index 044ba002..b9429544 100644 --- a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/VpnManager.kt +++ b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/VpnManager.kt @@ -14,12 +14,6 @@ import org.amnezia.vpn.shadowsocks.core.bg.BaseService import org.amnezia.vpn.shadowsocks.core.preference.DataStore import org.amnezia.vpn.shadowsocks.core.utils.Key -/** - * @author : kyle - * e-mail : 1239878682@qq.com - * @date : 2019/5/14 16:54 - * 看了我的代码,感动了吗? - */ class VpnManager private constructor() { var state = BaseService.State.Idle @@ -80,31 +74,23 @@ class VpnManager private constructor() { connect() } - /*** - * 开启或者关闭 自动判断 - */ - fun run(activity:Activity) { + fun run() { when { state.canStop -> Core.stopService() - DataStore.serviceMode == Key.modeVpn -> { - val intent = VpnService.prepare(activity) - if (intent != null) activity.startActivityForResult(intent, REQUEST_CONNECT) - else onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null) - } +// DataStore.serviceMode == Key.modeVpn -> { +// val intent = VpnService.prepare(activity) +// if (intent != null) activity.startActivityForResult(intent, REQUEST_CONNECT) +// else onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null) +// } else -> Core.startService() } } - /*** - * 设置状态监听 - */ + fun setOnStatusChangeListener(listener: OnStatusChangeListener) { this.listener = listener } - /*** - * application调用stop时调用 - */ fun onStop() { connection.bandwidthTimeout = 0 } @@ -112,31 +98,23 @@ class VpnManager private constructor() { fun onStart() { connection.bandwidthTimeout = 1000 } - /*** - * activity调用onActivityResult时调用 - */ + fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { when { requestCode != REQUEST_CONNECT -> { } resultCode == Activity.RESULT_OK -> Core.startService() else -> { - //无权限 + } } } - /*** - * 改变当前状态 - */ private fun changeState(state: BaseService.State) { this.state = state this.listener?.onStatusChanged(state) } - /*** - * 状态改变监听器 - */ interface OnStatusChangeListener { fun onStatusChanged(state: BaseService.State) @@ -144,24 +122,24 @@ class VpnManager private constructor() { } enum class Route(name: String) { - //全部 + ALL("all") - //绕过局域网地址 + , BY_PASS_LAN("bypass-lan") - //绕过中国大陆地址 + , BY_PASS_CHINA("bypass-china") - //绕过局域网和中国大陆地址 + , BY_PASS_LAN_CHINA("bypass-lan-china") - //GFW列表 + , GFW_LIST("gfwlist") - //仅代理中国大陆地址 + , CHINA_LIST("china-list") - //自定义规则 + , CUSTOM_RULES("custom-rules"); diff --git a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/acl/Acl.kt b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/acl/Acl.kt index 2b0623b5..0bae1fac 100644 --- a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/acl/Acl.kt +++ b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/acl/Acl.kt @@ -1,23 +1,3 @@ -/******************************************************************************* - * * - * 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 org.amnezia.vpn.shadowsocks.core.acl import android.content.Context diff --git a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/acl/AclSyncer.kt b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/acl/AclSyncer.kt index f3214356..b42a1063 100644 --- a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/acl/AclSyncer.kt +++ b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/acl/AclSyncer.kt @@ -1,23 +1,3 @@ -/******************************************************************************* - * * - * 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 org.amnezia.vpn.shadowsocks.core.acl import android.content.Context diff --git a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/aidl/ShadowsocksConnection.kt b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/aidl/ShadowsocksConnection.kt index a411effc..bf728bcf 100644 --- a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/aidl/ShadowsocksConnection.kt +++ b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/aidl/ShadowsocksConnection.kt @@ -31,7 +31,7 @@ import android.os.RemoteException import org.amnezia.vpn.shadowsocks.core.bg.BaseService import org.amnezia.vpn.shadowsocks.core.bg.ProxyService import org.amnezia.vpn.shadowsocks.core.bg.TransproxyService -import org.amnezia.vpn.shadowsocks.core.bg.VpnService +import org.amnezia.vpn.shadowsocks.core.bg.ShadowsocksVpnService import org.amnezia.vpn.shadowsocks.core.preference.DataStore import org.amnezia.vpn.shadowsocks.core.utils.Action import org.amnezia.vpn.shadowsocks.core.utils.Key @@ -45,7 +45,7 @@ class ShadowsocksConnection(private val handler: Handler = Handler(), companion object { val serviceClass get() = when (DataStore.serviceMode) { Key.modeProxy -> ProxyService::class - Key.modeVpn -> VpnService::class + Key.modeVpn -> ShadowsocksVpnService::class Key.modeTransproxy -> TransproxyService::class else -> throw UnknownError() }.java diff --git a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/BaseService.kt b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/BaseService.kt index 2be043b3..8e44f37f 100644 --- a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/BaseService.kt +++ b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/BaseService.kt @@ -25,26 +25,25 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.os.* +import android.util.Log import androidx.core.content.getSystemService - +import kotlinx.coroutines.* import org.amnezia.vpn.shadowsocks.core.Core import org.amnezia.vpn.shadowsocks.core.Core.app +import org.amnezia.vpn.shadowsocks.core.R import org.amnezia.vpn.shadowsocks.core.aidl.IShadowsocksService import org.amnezia.vpn.shadowsocks.core.aidl.IShadowsocksServiceCallback import org.amnezia.vpn.shadowsocks.core.aidl.TrafficStats -import org.amnezia.vpn.shadowsocks.core.R import org.amnezia.vpn.shadowsocks.core.plugin.PluginManager import org.amnezia.vpn.shadowsocks.core.utils.Action import org.amnezia.vpn.shadowsocks.core.utils.broadcastReceiver import org.amnezia.vpn.shadowsocks.core.utils.printLog import org.amnezia.vpn.shadowsocks.core.utils.readableMessage -import kotlinx.coroutines.* import java.io.File import java.net.BindException import java.net.InetAddress import java.net.URL import java.net.UnknownHostException -import java.util.* /** * This object uses WeakMap to simulate the effects of multi-inheritance. @@ -64,13 +63,13 @@ object BaseService { const val CONFIG_FILE = "shadowsocks.conf" const val CONFIG_FILE_UDP = "shadowsocks-udp.conf" - class Data internal constructor(private val service: Interface) { + class Data(private val service: Interface) { var state = State.Stopped var processes: GuardedProcessPool? = null var proxy: ProxyInstance? = null var udpFallback: ProxyInstance? = null - var notification: ServiceNotification? = null +// var notification: ServiceNotification? = null val closeReceiver = broadcastReceiver { _, intent -> when (intent.action) { Action.RELOAD -> service.forceLoad() @@ -96,7 +95,8 @@ object BaseService { stopListeningForBandwidth(callback ?: return) } } - private val bandwidthListeners = mutableMapOf() // the binder is the real identifier + private val bandwidthListeners = + mutableMapOf() // the binder is the real identifier private val handler = Handler() override fun getState(): Int = (data?.state ?: State.Idle).ordinal @@ -119,14 +119,15 @@ object BaseService { } private fun registerTimeout() { - handler.postDelayed(this::onTimeout, bandwidthListeners.values.min() ?: return) + handler.postDelayed(this::onTimeout, bandwidthListeners.values.minOrNull() ?: return) } + private fun onTimeout() { val proxies = listOfNotNull(data?.proxy, data?.udpFallback) val stats = proxies - .map { Pair(it.profile.id, it.trafficMonitor?.requestUpdate()) } - .filter { it.second != null } - .map { Triple(it.first, it.second!!.first, it.second!!.second) } + .map { Pair(it.profile.id, it.trafficMonitor?.requestUpdate()) } + .filter { it.second != null } + .map { Triple(it.first, it.second!!.first, it.second!!.second) } if (stats.any { it.third } && data?.state == State.Connected && bandwidthListeners.isNotEmpty()) { val sum = stats.fold(TrafficStats()) { a, b -> a + b.second } broadcast { item -> @@ -148,17 +149,21 @@ object BaseService { val data = data val proxy = data?.proxy ?: return proxy.trafficMonitor?.out.also { stats -> - cb.trafficUpdated(proxy.profile.id, if (stats == null) sum else { - sum += stats - stats - }) + cb.trafficUpdated( + proxy.profile.id, if (stats == null) sum else { + sum += stats + stats + } + ) } data.udpFallback?.also { udpFallback -> udpFallback.trafficMonitor?.out.also { stats -> - cb.trafficUpdated(udpFallback.profile.id, if (stats == null) TrafficStats() else { - sum += stats - stats - }) + cb.trafficUpdated( + udpFallback.profile.id, if (stats == null) TrafficStats() else { + sum += stats + stats + } + ) } } cb.trafficUpdated(0, sum) @@ -197,15 +202,17 @@ object BaseService { interface Interface { val data: Data val tag: String - fun createNotification(profileName: String): ServiceNotification +// fun createNotification(profileName: String): ServiceNotification - fun onBind(intent: Intent): IBinder? = if (intent.action == Action.SERVICE) data.binder else null + fun onBind(intent: Intent): IBinder? = + if (intent.action == Action.SERVICE) data.binder else null fun forceLoad() { val (profile, fallback) = Core.currentProfile - ?: return stopRunner(false, (this as Context).getString(R.string.profile_empty)) + ?: return stopRunner(false, (this as Context).getString(R.string.profile_empty)) if (profile.host.isEmpty() || profile.password.isEmpty() || - fallback != null && (fallback.host.isEmpty() || fallback.password.isEmpty())) { + fallback != null && (fallback.host.isEmpty() || fallback.password.isEmpty()) + ) { stopRunner(false, (this as Context).getString(R.string.proxy_empty)) return } @@ -221,17 +228,22 @@ object BaseService { suspend fun startProcesses() { val configRoot = (if (Build.VERSION.SDK_INT < 24 || app.getSystemService() - ?.isUserUnlocked != false) app else Core.deviceStorage).noBackupFilesDir + ?.isUserUnlocked != false + ) app else Core.deviceStorage).noBackupFilesDir val udpFallback = data.udpFallback - data.proxy!!.start(this, - File(Core.deviceStorage.noBackupFilesDir, "stat_main"), - File(configRoot, CONFIG_FILE), - if (udpFallback == null) "-u" else null) + data.proxy!!.start( + this, + File(Core.deviceStorage.noBackupFilesDir, "stat_main"), + File(configRoot, CONFIG_FILE), + if (udpFallback == null) "-u" else null + ) check(udpFallback?.pluginPath == null) { "UDP fallback cannot have plugins" } - udpFallback?.start(this, - File(Core.deviceStorage.noBackupFilesDir, "stat_udp"), - File(configRoot, CONFIG_FILE_UDP), - "-U") + udpFallback?.start( + this, + File(Core.deviceStorage.noBackupFilesDir, "stat_udp"), + File(configRoot, CONFIG_FILE_UDP), + "-U" + ) } fun startRunner() { @@ -264,8 +276,8 @@ object BaseService { data.closeReceiverRegistered = false } - data.notification?.destroy() - data.notification = null +// data.notification?.destroy() +// data.notification = null val ids = listOfNotNull(data.proxy, data.udpFallback).map { it.shutdown(this) @@ -280,30 +292,36 @@ object BaseService { data.changeState(State.Stopped, msg) // stop the service if nothing has bound to it - if (restart) startRunner() else stopSelf() + if (restart) { + startRunner() + } else { + Log.d("Aman", "Stop Self BaseService-------") +// stopSelf() + } } } - suspend fun preInit() { } + suspend fun preInit() {} suspend fun resolver(host: String) = InetAddress.getAllByName(host) suspend fun openConnection(url: URL) = url.openConnection() fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { val data = data - if (data.state != State.Stopped) return Service.START_NOT_STICKY + if (data.state != State.Stopped) return Service.START_REDELIVER_INTENT val profilePair = Core.currentProfile this as Context if (profilePair == null) { // gracefully shutdown: https://stackoverflow.com/q/47337857/2245107 - data.notification = createNotification("") +// data.notification = createNotification("") stopRunner(false, getString(R.string.profile_empty)) - return Service.START_NOT_STICKY + return Service.START_REDELIVER_INTENT } val (profile, fallback) = profilePair profile.name = profile.formattedName // save name for later queries val proxy = ProxyInstance(profile) data.proxy = proxy - data.udpFallback = if (fallback == null) null else ProxyInstance(fallback, profile.route) + data.udpFallback = + if (fallback == null) null else ProxyInstance(fallback, profile.route) if (!data.closeReceiverRegistered) { registerReceiver(data.closeReceiver, IntentFilter().apply { @@ -314,7 +332,7 @@ object BaseService { data.closeReceiverRegistered = true } - data.notification = createNotification(profile.formattedName) +// data.notification = createNotification(profile.formattedName) data.changeState(State.Connecting) data.connectingJob = GlobalScope.launch(Dispatchers.Main) { @@ -340,16 +358,20 @@ object BaseService { stopRunner(false, getString(R.string.invalid_server)) } catch (exc: Throwable) { if (exc !is PluginManager.PluginNotFoundException && - exc !is BindException && - exc !is VpnService.NullConnectionException) { + exc !is BindException && + exc !is ShadowsocksVpnService.NullConnectionException + ) { printLog(exc) } - stopRunner(false, "${getString(R.string.service_failed)}: ${exc.readableMessage}") + stopRunner( + false, + "${getString(R.string.service_failed)}: ${exc.readableMessage}" + ) } finally { data.connectingJob = null } } - return Service.START_NOT_STICKY + return Service.START_REDELIVER_INTENT } } } diff --git a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/ProxyService.kt b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/ProxyService.kt index ea07f30c..4f4372ef 100644 --- a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/ProxyService.kt +++ b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/ProxyService.kt @@ -29,8 +29,8 @@ import android.content.Intent class ProxyService : Service(), BaseService.Interface { override val data = BaseService.Data(this) override val tag: String get() = "ShadowsocksProxyService" - override fun createNotification(profileName: String): ServiceNotification = - ServiceNotification(this, profileName, "service-proxy", true) +// override fun createNotification(profileName: String): ServiceNotification = +// ServiceNotification(this, profileName, "service-proxy", true) override fun onBind(intent: Intent) = super.onBind(intent) override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int = diff --git a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/ServiceNotification.kt b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/ServiceNotification.kt index 26df36ec..7a4ca1b3 100644 --- a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/ServiceNotification.kt +++ b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/ServiceNotification.kt @@ -1,135 +1,145 @@ -/******************************************************************************* - * * - * 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 org.amnezia.vpn.shadowsocks.core.bg - -import android.app.KeyguardManager -import android.app.NotificationManager -import android.app.PendingIntent -import android.app.Service -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.os.Build -import android.os.PowerManager -import android.text.format.Formatter -import androidx.core.app.NotificationCompat -import androidx.core.content.ContextCompat -import androidx.core.content.getSystemService -import org.amnezia.vpn.shadowsocks.core.Core -import org.amnezia.vpn.shadowsocks.core.aidl.IShadowsocksServiceCallback -import org.amnezia.vpn.shadowsocks.core.aidl.TrafficStats -import org.amnezia.vpn.shadowsocks.core.R -import org.amnezia.vpn.shadowsocks.core.utils.Action -import org.amnezia.vpn.shadowsocks.core.utils.broadcastReceiver - -/** - * Android < 8 VPN: always invisible because of VPN notification/icon - * Android < 8 other: only invisible in (possibly unsecure) lockscreen - * Android 8+: always visible due to system limitations - * (user can choose to hide the notification in secure lockscreen or anywhere) - */ -class ServiceNotification(private val service: BaseService.Interface, profileName: String, - channel: String, private val visible: Boolean = false) { - private val keyGuard = (service as Context).getSystemService()!! - private val nm by lazy { (service as Context).getSystemService()!! } - private val callback: IShadowsocksServiceCallback by lazy { - object : IShadowsocksServiceCallback.Stub() { - override fun stateChanged(state: Int, profileName: String?, msg: String?) { } // ignore - override fun trafficUpdated(profileId: Long, stats: TrafficStats) { - if (profileId != 0L) return - service as Context - val txr = service.getString(R.string.speed, Formatter.formatFileSize(service, stats.txRate)) - val rxr = service.getString(R.string.speed, Formatter.formatFileSize(service, stats.rxRate)) - builder.setContentText("$txr↑\t$rxr↓") - style.bigText(service.getString(R.string.stat_summary, txr, rxr, - Formatter.formatFileSize(service, stats.txTotal), - Formatter.formatFileSize(service, stats.rxTotal))) - show() - } - override fun trafficPersisted(profileId: Long) { } - } - } - private val lockReceiver = broadcastReceiver { _, intent -> update(intent.action) } - private var callbackRegistered = false - - private val builder = NotificationCompat.Builder(service as Context, channel) - .setWhen(0) - .setColor(ContextCompat.getColor(service, R.color.material_primary_500)) - .setTicker(service.getString(R.string.forward_success)) - .setContentTitle(profileName) - .setContentIntent(Core.configureIntent(service)) - .setSmallIcon(R.drawable.ic_service_active) - private val style = NotificationCompat.BigTextStyle(builder).bigText("") - private var isVisible = true - - init { - service as Context - if (Build.VERSION.SDK_INT < 24) builder.addAction(R.drawable.ic_navigation_close, - service.getString(R.string.stop), PendingIntent.getBroadcast(service, 0, Intent(Action.CLOSE), 0)) - update(if (service.getSystemService()?.isInteractive != false) - Intent.ACTION_SCREEN_ON else Intent.ACTION_SCREEN_OFF, true) - service.registerReceiver(lockReceiver, IntentFilter().apply { - addAction(Intent.ACTION_SCREEN_ON) - addAction(Intent.ACTION_SCREEN_OFF) - if (visible && Build.VERSION.SDK_INT < 26) addAction(Intent.ACTION_USER_PRESENT) - }) - } - - private fun update(action: String?, forceShow: Boolean = false) { - if (forceShow || service.data.state == BaseService.State.Connected) when (action) { - Intent.ACTION_SCREEN_OFF -> { - setVisible(false, forceShow) - unregisterCallback() // unregister callback to save battery - } - Intent.ACTION_SCREEN_ON -> { - setVisible(visible && !keyGuard.isKeyguardLocked, forceShow) - service.data.binder.registerCallback(callback) - service.data.binder.startListeningForBandwidth(callback, 1000) - callbackRegistered = true - } - Intent.ACTION_USER_PRESENT -> setVisible(true, forceShow) - } - } - - private fun unregisterCallback() { - if (callbackRegistered) { - service.data.binder.unregisterCallback(callback) - callbackRegistered = false - } - } - - private fun setVisible(visible: Boolean, forceShow: Boolean = false) { - if (isVisible != visible) { - isVisible = visible - builder.priority = if (visible) NotificationCompat.PRIORITY_LOW else NotificationCompat.PRIORITY_MIN - show() - } else if (forceShow) show() - } - - private fun show() = (service as Service).startForeground(1, builder.build()) - - fun destroy() { - (service as Service).unregisterReceiver(lockReceiver) - unregisterCallback() - service.stopForeground(true) - nm.cancel(1) - } -} +///******************************************************************************* +// * * +// * 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 org.amnezia.vpn.shadowsocks.core.bg +// +//import android.app.KeyguardManager +//import android.app.NotificationManager +//import android.app.PendingIntent +//import android.app.Service +//import android.content.Context +//import android.content.Intent +//import android.content.IntentFilter +//import android.os.Build +//import android.os.PowerManager +//import android.text.format.Formatter +//import androidx.core.app.NotificationCompat +//import androidx.core.content.ContextCompat +//import androidx.core.content.getSystemService +//import org.amnezia.vpn.shadowsocks.core.Core +//import org.amnezia.vpn.shadowsocks.core.aidl.IShadowsocksServiceCallback +//import org.amnezia.vpn.shadowsocks.core.aidl.TrafficStats +//import org.amnezia.vpn.shadowsocks.core.R +//import org.amnezia.vpn.shadowsocks.core.utils.Action +//import org.amnezia.vpn.shadowsocks.core.utils.broadcastReceiver +// +///** +// * Android < 8 VPN: always invisible because of VPN notification/icon +// * Android < 8 other: only invisible in (possibly unsecure) lockscreen +// * Android 8+: always visible due to system limitations +// * (user can choose to hide the notification in secure lockscreen or anywhere) +// */ +//class ServiceNotification(private val service: BaseService.Interface, profileName: String, +// channel: String, private val visible: Boolean = false) { +// private val keyGuard = (service as Context).getSystemService()!! +// private val nm by lazy { (service as Context).getSystemService()!! } +// private val callback: IShadowsocksServiceCallback by lazy { +// object : IShadowsocksServiceCallback.Stub() { +// override fun stateChanged(state: Int, profileName: String?, msg: String?) { +// when (state) { +// BaseService.State.Connected.ordinal -> { +// builder.setContentText("VPN Connected") +// } +// BaseService.State.Stopped.ordinal -> { +// builder.setContentText("VPN Disconnected") +// } +// } +// } // ignore +// override fun trafficUpdated(profileId: Long, stats: TrafficStats) { +//// if (profileId != 0L) return +//// service as Context +//// val txr = service.getString(R.string.speed, Formatter.formatFileSize(service, stats.txRate)) +//// val rxr = service.getString(R.string.speed, Formatter.formatFileSize(service, stats.rxRate)) +//// builder.setContentText("$txr↑\t$rxr↓") +//// style.bigText(service.getString(R.string.stat_summary, txr, rxr, +//// Formatter.formatFileSize(service, stats.txTotal), +//// Formatter.formatFileSize(service, stats.rxTotal))) +//// show() +// } +// override fun trafficPersisted(profileId: Long) { } +// } +// } +//// private val lockReceiver = broadcastReceiver { _, intent -> update(intent.action) } +// private var callbackRegistered = false +// +// private val builder = NotificationCompat.Builder(service as Context, channel) +// .setWhen(0) +// .setColor(ContextCompat.getColor(service, R.color.material_primary_500)) +// .setTicker(service.getString(R.string.forward_success)) +// .setContentTitle("AmneziaVPN -- testing") +// .setContentIntent(Core.configureIntent(service)) +// .setSmallIcon(R.drawable.ic_amnezia_round) +// private val style = NotificationCompat.BigTextStyle(builder).bigText("") +// private var isVisible = true +// +// init { +// service as Context +//// if (Build.VERSION.SDK_INT < 24) builder.addAction(R.drawable.ic_navigation_close, +//// service.getString(R.string.stop), PendingIntent.getBroadcast(service, 0, Intent(Action.CLOSE), 0)) +//// update(if (service.getSystemService()?.isInteractive != false) +//// Intent.ACTION_SCREEN_ON else Intent.ACTION_SCREEN_OFF, true) +//// service.registerReceiver(lockReceiver, IntentFilter().apply { +//// addAction(Intent.ACTION_SCREEN_ON) +//// addAction(Intent.ACTION_SCREEN_OFF) +//// if (visible && Build.VERSION.SDK_INT < 26) addAction(Intent.ACTION_USER_PRESENT) +//// }) +// } +// +//// private fun update(action: String?, forceShow: Boolean = false) { +//// if (forceShow || service.data.state == BaseService.State.Connected) when (action) { +//// Intent.ACTION_SCREEN_OFF -> { +//// setVisible(false, forceShow) +//// unregisterCallback() // unregister callback to save battery +//// } +//// Intent.ACTION_SCREEN_ON -> { +//// setVisible(visible && !keyGuard.isKeyguardLocked, forceShow) +//// service.data.binder.registerCallback(callback) +//// service.data.binder.startListeningForBandwidth(callback, 1000) +//// callbackRegistered = true +//// } +//// Intent.ACTION_USER_PRESENT -> setVisible(true, forceShow) +//// } +//// } +// +// private fun unregisterCallback() { +// if (callbackRegistered) { +// service.data.binder.unregisterCallback(callback) +// callbackRegistered = false +// } +// } +// +// private fun setVisible(visible: Boolean, forceShow: Boolean = false) { +// if (isVisible != visible) { +// isVisible = visible +// builder.priority = if (visible) NotificationCompat.PRIORITY_LOW else NotificationCompat.PRIORITY_MIN +// show() +// } else if (forceShow) show() +// } +// +// +// private fun show() = (service as Service).startForeground(1337, builder.build()) +// +// fun destroy() { +//// (service as Service).unregisterReceiver(lockReceiver) +// unregisterCallback() +//// service.stopForeground(true) +// nm.cancel(1337) +// } +//} diff --git a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/VpnService.kt b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/ShadowsocksVpnService.kt similarity index 96% rename from client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/VpnService.kt rename to client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/ShadowsocksVpnService.kt index dbe64ef6..403bb1a3 100644 --- a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/VpnService.kt +++ b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/ShadowsocksVpnService.kt @@ -30,6 +30,7 @@ import android.os.Build import android.os.ParcelFileDescriptor import android.system.ErrnoException import android.system.Os +import android.util.Log import org.amnezia.vpn.shadowsocks.core.Core import org.amnezia.vpn.shadowsocks.core.R import org.amnezia.vpn.shadowsocks.core.VpnRequestActivity @@ -51,7 +52,7 @@ import java.net.URL import java.util.* import android.net.VpnService as BaseVpnService -class VpnService : BaseVpnService(), LocalDnsService.Interface { +open class ShadowsocksVpnService : BaseVpnService(), LocalDnsService.Interface { companion object { private const val VPN_MTU = 1500 private const val PRIVATE_VLAN4_CLIENT = "172.19.0.1" @@ -95,8 +96,10 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { override val data = BaseService.Data(this) override val tag: String get() = "ShadowsocksVpnService" - override fun createNotification(profileName: String): ServiceNotification = - ServiceNotification(this, profileName, "service-vpn") + + val NOTIFICATION_CHANNEL_ID = "com.amnezia.vpnNotification" +// override fun createNotification(profileName: String): ServiceNotification = +// ServiceNotification(this, profileName, NOTIFICATION_CHANNEL_ID) private var conn: ParcelFileDescriptor? = null private var worker: ProtectWorker? = null @@ -117,7 +120,9 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { else -> super.onBind(intent) } - override fun onRevoke() = stopRunner() + override fun onRevoke() { + stopRunner() + } override fun killProcesses(scope: CoroutineScope) { super.killProcesses(scope) @@ -136,7 +141,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { } else return super.onStartCommand(intent, flags, startId) } stopRunner() - return Service.START_NOT_STICKY + return Service.START_STICKY } override suspend fun preInit() = DefaultNetworkListener.start(this) { underlyingNetwork = it } diff --git a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/TransproxyService.kt b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/TransproxyService.kt index 9d5e7bdf..1da2d3f2 100644 --- a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/TransproxyService.kt +++ b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/bg/TransproxyService.kt @@ -29,15 +29,16 @@ import java.io.File class TransproxyService : Service(), LocalDnsService.Interface { override val data = BaseService.Data(this) override val tag: String get() = "ShadowsocksTransproxyService" - override fun createNotification(profileName: String): ServiceNotification = - ServiceNotification(this, profileName, "service-transproxy", true) +// override fun createNotification(profileName: String): ServiceNotification = +// ServiceNotification(this, profileName, "service-transproxy", true) override fun onBind(intent: Intent) = super.onBind(intent) override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int = - super.onStartCommand(intent, flags, startId) + super.onStartCommand(intent, flags, startId) private fun startRedsocksDaemon() { - File(Core.deviceStorage.noBackupFilesDir, "redsocks.conf").writeText("""base { + File(Core.deviceStorage.noBackupFilesDir, "redsocks.conf").writeText( + """base { log_debug = off; log_info = off; log = stderr; @@ -51,9 +52,15 @@ redsocks { port = ${DataStore.portProxy}; type = socks5; } -""") - data.processes!!.start(listOf( - File(applicationInfo.nativeLibraryDir, Executable.REDSOCKS).absolutePath, "-c", "redsocks.conf")) +""" + ) + data.processes!!.start( + listOf( + File(applicationInfo.nativeLibraryDir, Executable.REDSOCKS).absolutePath, + "-c", + "redsocks.conf" + ) + ) } override suspend fun startProcesses() { diff --git a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/utils/Constants.kt b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/utils/Constants.kt index 1c3b700d..53203af4 100644 --- a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/utils/Constants.kt +++ b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/utils/Constants.kt @@ -75,9 +75,9 @@ object Key { } object Action { - const val SERVICE = "com.kyle.shadowsocks.SERVICE" - const val CLOSE = "com.kyle.shadowsocks.CLOSE" - const val RELOAD = "com.kyle.shadowsocks.RELOAD" + const val SERVICE = "org.amnezia.vpn.shadowsocks.SERVICE" + const val CLOSE = "org.amnezia.vpn.shadowsocks.CLOSE" + const val RELOAD = "org.amnezia.vpn.shadowsocks.RELOAD" - const val EXTRA_PROFILE_ID = "com.kyle.shadowsocks.EXTRA_PROFILE_ID" + const val EXTRA_PROFILE_ID = "org.amnezia.vpn.shadowsocks.EXTRA_PROFILE_ID" } diff --git a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/widget/NativePluginProvider.kt b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/widget/NativePluginProvider.kt index 4d7a4423..3fd80cc7 100644 --- a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/widget/NativePluginProvider.kt +++ b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/widget/NativePluginProvider.kt @@ -38,10 +38,10 @@ import androidx.core.os.bundleOf * ... * <application> * ... - * <provider android:name="com.kyle.shadowsocks.$PLUGIN_ID.BinaryProvider" - * android:authorities="com.kyle.shadowsocks.plugin.$PLUGIN_ID.BinaryProvider"> + * <provider android:name="org.amnezia.vpn.shadowsocks.$PLUGIN_ID.BinaryProvider" + * android:authorities="org.amnezia.vpn.shadowsocks.plugin.$PLUGIN_ID.BinaryProvider"> * <intent-filter> - * <category android:name="com.kyle.shadowsocks.plugin.ACTION_NATIVE_PLUGIN" /> + * <category android:name="org.amnezia.vpn.shadowsocks.plugin.ACTION_NATIVE_PLUGIN" /> * </intent-filter> * </provider> * ... diff --git a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/widget/PluginContract.kt b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/widget/PluginContract.kt index fd980810..418e5086 100644 --- a/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/widget/PluginContract.kt +++ b/client/android/shadowsocks/src/main/java/org/amnezia/vpn/shadowsocks/core/widget/PluginContract.kt @@ -29,61 +29,61 @@ object PluginContract { /** * ContentProvider Action: Used for NativePluginProvider. * - * Constant Value: "com.kyle.shadowsocks.plugin.ACTION_NATIVE_PLUGIN" + * Constant Value: "org.amnezia.vpn.shadowsocks.plugin.ACTION_NATIVE_PLUGIN" */ - const val ACTION_NATIVE_PLUGIN = "com.kyle.shadowsocks.plugin.ACTION_NATIVE_PLUGIN" + const val ACTION_NATIVE_PLUGIN = "org.amnezia.vpn.shadowsocks.plugin.ACTION_NATIVE_PLUGIN" /** * Activity Action: Used for ConfigurationActivity. * - * Constant Value: "com.kyle.shadowsocks.plugin.ACTION_CONFIGURE" + * Constant Value: "org.amnezia.vpn.shadowsocks.plugin.ACTION_CONFIGURE" */ - const val ACTION_CONFIGURE = "com.kyle.shadowsocks.plugin.ACTION_CONFIGURE" + const val ACTION_CONFIGURE = "org.amnezia.vpn.shadowsocks.plugin.ACTION_CONFIGURE" /** * Activity Action: Used for HelpActivity or HelpCallback. * - * Constant Value: "com.kyle.shadowsocks.plugin.ACTION_HELP" + * Constant Value: "org.amnezia.vpn.shadowsocks.plugin.ACTION_HELP" */ - const val ACTION_HELP = "com.kyle.shadowsocks.plugin.ACTION_HELP" + const val ACTION_HELP = "org.amnezia.vpn.shadowsocks.plugin.ACTION_HELP" /** * The lookup key for a string that provides the plugin entry binary. * - * Example: "/data/data/com.kyle.shadowsocks.plugin.obfs_local/lib/libobfs-local.so" + * Example: "/data/data/org.amnezia.vpn.shadowsocks.plugin.obfs_local/lib/libobfs-local.so" * - * Constant Value: "com.kyle.shadowsocks.plugin.EXTRA_ENTRY" + * Constant Value: "org.amnezia.vpn.shadowsocks.plugin.EXTRA_ENTRY" */ - const val EXTRA_ENTRY = "com.kyle.shadowsocks.plugin.EXTRA_ENTRY" + const val EXTRA_ENTRY = "org.amnezia.vpn.shadowsocks.plugin.EXTRA_ENTRY" /** * The lookup key for a string that provides the options as a string. * * Example: "obfs=http;obfs-host=www.baidu.com" * - * Constant Value: "com.kyle.shadowsocks.plugin.EXTRA_OPTIONS" + * Constant Value: "org.amnezia.vpn.shadowsocks.plugin.EXTRA_OPTIONS" */ - const val EXTRA_OPTIONS = "com.kyle.shadowsocks.plugin.EXTRA_OPTIONS" + const val EXTRA_OPTIONS = "org.amnezia.vpn.shadowsocks.plugin.EXTRA_OPTIONS" /** * The lookup key for a CharSequence that provides user relevant help message. * * Example: "obfs=|tls> Enable obfuscating: HTTP or TLS (Experimental). * obfs-host= Hostname for obfuscating (Experimental)." * - * Constant Value: "com.kyle.shadowsocks.plugin.EXTRA_HELP_MESSAGE" + * Constant Value: "org.amnezia.vpn.shadowsocks.plugin.EXTRA_HELP_MESSAGE" */ - const val EXTRA_HELP_MESSAGE = "com.kyle.shadowsocks.plugin.EXTRA_HELP_MESSAGE" + const val EXTRA_HELP_MESSAGE = "org.amnezia.vpn.shadowsocks.plugin.EXTRA_HELP_MESSAGE" /** * The metadata key to retrieve plugin id. Required for plugins. * - * Constant Value: "com.kyle.shadowsocks.plugin.id" + * Constant Value: "org.amnezia.vpn.shadowsocks.plugin.id" */ - const val METADATA_KEY_ID = "com.kyle.shadowsocks.plugin.id" + const val METADATA_KEY_ID = "org.amnezia.vpn.shadowsocks.plugin.id" /** * The metadata key to retrieve default configuration. Default value is empty. * - * Constant Value: "com.kyle.shadowsocks.plugin.default_config" + * Constant Value: "org.amnezia.vpn.shadowsocks.plugin.default_config" */ - const val METADATA_KEY_DEFAULT_CONFIG = "com.kyle.shadowsocks.plugin.default_config" + const val METADATA_KEY_DEFAULT_CONFIG = "org.amnezia.vpn.shadowsocks.plugin.default_config" const val METHOD_GET_EXECUTABLE = "shadowsocks:getExecutable" @@ -114,5 +114,5 @@ object PluginContract { /** * The authority for general plugin actions. */ - const val AUTHORITY = "com.kyle.shadowsocks" + const val AUTHORITY = "org.amnezia.vpn.shadowsocks" } diff --git a/client/android/shadowsocks/src/main/res/drawable/ic_amnezia_round.xml b/client/android/shadowsocks/src/main/res/drawable/ic_amnezia_round.xml new file mode 100644 index 00000000..32df5ca4 --- /dev/null +++ b/client/android/shadowsocks/src/main/res/drawable/ic_amnezia_round.xml @@ -0,0 +1,10 @@ + + + diff --git a/client/android/shadowsocks/src/main/res/drawable/ic_service_active.xml b/client/android/shadowsocks/src/main/res/drawable/ic_service_active.xml deleted file mode 100644 index 33062676..00000000 --- a/client/android/shadowsocks/src/main/res/drawable/ic_service_active.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - diff --git a/client/android/src/org/amnezia/vpn/VPNService.kt b/client/android/src/org/amnezia/vpn/VPNService.kt index 27c604bc..f8ed3999 100644 --- a/client/android/src/org/amnezia/vpn/VPNService.kt +++ b/client/android/src/org/amnezia/vpn/VPNService.kt @@ -6,18 +6,137 @@ package org.amnezia.vpn import android.content.Context import android.content.Intent -import android.os.Build -import android.os.IBinder +import android.content.pm.PackageManager +import android.net.LocalSocket +import android.net.LocalSocketAddress +import android.net.Network import android.net.ProxyInfo -import android.os.ParcelFileDescriptor +import android.os.* +import android.system.ErrnoException +import android.system.Os import android.system.OsConstants import com.wireguard.android.util.SharedLibraryLoader import com.wireguard.config.* import com.wireguard.crypto.Key +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.amnezia.vpn.shadowsocks.core.Core +import org.amnezia.vpn.shadowsocks.core.R +import org.amnezia.vpn.shadowsocks.core.VpnRequestActivity +import org.amnezia.vpn.shadowsocks.core.acl.Acl +import org.amnezia.vpn.shadowsocks.core.bg.* +import org.amnezia.vpn.shadowsocks.core.database.Profile +import org.amnezia.vpn.shadowsocks.core.database.ProfileManager +import org.amnezia.vpn.shadowsocks.core.net.ConcurrentLocalSocketListener +import org.amnezia.vpn.shadowsocks.core.net.DefaultNetworkListener +import org.amnezia.vpn.shadowsocks.core.net.Subnet +import org.amnezia.vpn.shadowsocks.core.preference.DataStore +import org.amnezia.vpn.shadowsocks.core.utils.Key.modeVpn +import org.amnezia.vpn.shadowsocks.core.utils.printLog import org.json.JSONObject +import java.io.Closeable +import java.io.File +import java.io.FileDescriptor +import java.io.IOException +import android.net.VpnService as BaseVpnService + + +class VPNService : BaseVpnService(), LocalDnsService.Interface { + + override val data = BaseService.Data(this) + override val tag: String get() = "VPNService" +// override fun createNotification(profileName: String): ServiceNotification = +// ServiceNotification(this, profileName, "service-vpn") + + private var conn: ParcelFileDescriptor? = null + private var worker: ProtectWorker? = null + private var active = false + private var metered = false + private var underlyingNetwork: Network? = null + set(value) { + field = value + if (active && Build.VERSION.SDK_INT >= 22) setUnderlyingNetworks(underlyingNetworks) + } + private val underlyingNetworks + get() = + // clearing underlyingNetworks makes Android 9+ consider the network to be metered + if (Build.VERSION.SDK_INT >= 28 && metered) null else underlyingNetwork?.let { + arrayOf( + it + ) + } + + val handler = Handler(Looper.getMainLooper()) + var runnable: Runnable = object : Runnable { + override fun run() { + if (mProtocol.equals("shadowsocks", true)) { + Log.e(tag, "run: -----------------: ${data.state}") + when (data.state) { + BaseService.State.Connected -> { + currentTunnelHandle = 1 + isUp = true + } + BaseService.State.Stopped -> { + currentTunnelHandle = -1 + isUp = false + } + else -> { + + } + } + } + handler.postDelayed(this, 1000L) //wait 4 sec and run again + } + } + + fun stopTest() { + handler.removeCallbacks(runnable) + } + + fun startTest() { + handler.postDelayed(runnable, 0) //wait 0 ms and run + } + + companion object { + private const val VPN_MTU = 1500 + private const val PRIVATE_VLAN4_CLIENT = "172.19.0.1" + private const val PRIVATE_VLAN4_ROUTER = "172.19.0.2" + private const val PRIVATE_VLAN6_CLIENT = "fdfe:dcba:9876::1" + private const val PRIVATE_VLAN6_ROUTER = "fdfe:dcba:9876::2" + + /** + * https://android.googlesource.com/platform/prebuilts/runtime/+/94fec32/appcompat/hiddenapi-light-greylist.txt#9466 + */ + private val getInt = FileDescriptor::class.java.getDeclaredMethod("getInt$") + + @JvmStatic + fun startService(c: Context) { + c.applicationContext.startService( + Intent(c.applicationContext, VPNService::class.java).apply { + putExtra("startOnly", true) + }) + } + + @JvmStatic + private external fun wgGetConfig(handle: Int): String? + + @JvmStatic + private external fun wgGetSocketV4(handle: Int): Int + + @JvmStatic + private external fun wgGetSocketV6(handle: Int): Int + + @JvmStatic + private external fun wgTurnOff(handle: Int) + + @JvmStatic + private external fun wgTurnOn(ifName: String, tunFd: Int, settings: String): Int + + @JvmStatic + private external fun wgVersion(): String? + } -class VPNService : android.net.VpnService() { - private val tag = "VPNService" private var mBinder: VPNServiceBinder = VPNServiceBinder(this) private var mConfig: JSONObject? = null private var mProtocol: String? = null @@ -26,7 +145,11 @@ class VPNService : android.net.VpnService() { private var mbuilder: Builder = Builder() private var mOpenVPNThreadv3: OpenVPNThreadv3? = null - private var currentTunnelHandle = -1 + var currentTunnelHandle = -1 + + private var intent: Intent? = null + private var flags = 0 + private var startId = 0 fun init() { if (mAlreadyInitialised) { @@ -41,8 +164,16 @@ class VPNService : android.net.VpnService() { mAlreadyInitialised = true } + override fun onCreate() { + super.onCreate() +// Log.v(tag, "Aman: onCreate....................") +// Log.v(tag, "Aman: onCreate....................") +// Log.v(tag, "Aman: onCreate....................") +// NotificationUtil.show(this) // Go foreground + } + override fun onUnbind(intent: Intent?): Boolean { - Log.v(tag, "Got Unbind request") + Log.v(tag, "Aman: onUnbind....................") if (!isUp) { // If the Qt Client got closed while we were not connected // we do not need to stay as a foreground service. @@ -55,9 +186,21 @@ class VPNService : android.net.VpnService() { * EntryPoint for the Service, gets Called when AndroidController.cpp * calles bindService. Returns the [VPNServiceBinder] so QT can send Requests to it. */ - override fun onBind(intent: Intent?): IBinder? { - Log.v(tag, "Got Bind request") - init() + override fun onBind(intent: Intent): IBinder { + Log.v(tag, "Aman: onBind....................") + when (mProtocol) { + "shadowsocks" -> { + when (intent.action) { + SERVICE_INTERFACE -> super.onBind(intent) + else -> super.onBind(intent) + } + startTest() + } + else -> { + init() + } + } + return mBinder } @@ -67,11 +210,16 @@ class VPNService : android.net.VpnService() { * or from Booting the device and having "connect on boot" enabled. */ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + Log.v(tag, "Aman: onStartCommand....................") + this.intent = intent + this.flags = flags + this.startId = startId init() intent?.let { - if (intent.getBooleanExtra("startOnly", false)) { + if (!isUp && intent.getBooleanExtra("startOnly", false)) { Log.i(tag, "Start only!") - return super.onStartCommand(intent, flags, startId) + return START_REDELIVER_INTENT +// return super.onStartCommand(intent, flags, startId) } } // This start is from always-on @@ -81,18 +229,39 @@ class VPNService : android.net.VpnService() { val lastConfString = prefs.getString("lastConf", "") if (lastConfString.isNullOrEmpty()) { // We have nothing to connect to -> Exit - Log.e(tag,"VPN service was triggered without defining a Server or having a tunnel") - return super.onStartCommand(intent, flags, startId) + Log.e(tag, "VPN service was triggered without defining a Server or having a tunnel") + return super.onStartCommand(intent, flags, startId) } this.mConfig = JSONObject(lastConfString) } - return super.onStartCommand(intent, flags, startId) + mProtocol = mConfig!!.getString("protocol") + Log.e(tag, "mProtocol: $mProtocol") + if (mProtocol.equals("shadowsocks", true)) { + if (DataStore.serviceMode == modeVpn) { + if (prepare(this) != null) { + startActivity( + Intent( + this, + VpnRequestActivity::class.java + ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ) + } else { + Log.e(tag, "Else part enter") +// service?.startListeningForBandwidth(serviceCallback, 1000) + Log.e(tag, "test") + return super.onStartCommand(intent, flags, startId) + } + } + stopRunner() + } + return START_REDELIVER_INTENT } // Invoked when the application is revoked. // At this moment, the VPN interface is already deactivated by the system. override fun onRevoke() { + Log.v(tag, "Aman: onRevoke....................") this.turnOff() super.onRevoke() } @@ -145,6 +314,7 @@ class VPNService : android.net.VpnService() { } fun turnOn(json: JSONObject?): Int { + Log.v(tag, "Aman: turnOn....................") if (!checkPermissions()) { Log.e(tag, "turn on was called without no permissions present!") isUp = false @@ -152,23 +322,31 @@ class VPNService : android.net.VpnService() { } Log.i(tag, "Permission okay") mConfig = json!! - Log.i(tag, "Config: " + mConfig) + Log.i(tag, "Config: $mConfig") mProtocol = mConfig!!.getString("protocol") - Log.i(tag, "Protocol: " + mProtocol) + Log.i(tag, "Protocol: $mProtocol") when (mProtocol) { - "openvpn" -> startOpenVpn() - "wireguard" -> startWireGuard() - "shadowsocks" -> startShadowsocks() + "openvpn" -> { + startOpenVpn() + } + "wireguard" -> { + startWireGuard() + } + "shadowsocks" -> { + startShadowsocks() + startTest() + } else -> { Log.e(tag, "No protocol") return 0 } } - NotificationUtil.show(this) // Go foreground + NotificationUtil.show(this) return 1 } fun establish(): ParcelFileDescriptor? { + Log.v(tag, "Aman: establish....................") mbuilder.allowFamily(OsConstants.AF_INET) mbuilder.allowFamily(OsConstants.AF_INET6) @@ -208,7 +386,9 @@ class VPNService : android.net.VpnService() { fun addHttpProxy(host: String, port: Int): Boolean { val proxyInfo = ProxyInfo.buildDirectProxy(host, port) Log.v(tag, "mbuilder.addHttpProxy($host, $port)") - mbuilder.setHttpProxy(proxyInfo) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + mbuilder.setHttpProxy(proxyInfo) + } return true } @@ -218,19 +398,22 @@ class VPNService : android.net.VpnService() { } fun turnOff() { - Log.v(tag, "Try to disable tunnel") + Log.v(tag, "Aman: turnOff....................") when (mProtocol) { "wireguard" -> wgTurnOff(currentTunnelHandle) "openvpn" -> ovpnTurnOff() + "shadowsocks" -> { + stopRunner(false) + stopTest() + } else -> { Log.e(tag, "No protocol") } } currentTunnelHandle = -1 stopForeground(true) - isUp = false - stopSelf(); + stopSelf() } @@ -366,11 +549,89 @@ class VPNService : android.net.VpnService() { private fun startShadowsocks() { Log.e(tag, "startShadowsocks method enters") - if(mConfig != null) { - try { + if (mConfig != null) { + try { + Log.e(tag, "Config: $mConfig") - } catch(e: Exception) { - Log.e(tag, "Error in startShadowsocks: $e") + ProfileManager.clear() + val profile = Profile() +// val iter: Iterator = mConfig!!.keys() +// while (iter.hasNext()) { +// val key = iter.next() +// try { +// val value: Any = mConfig!!.get(key) +// Log.i(tag, "startShadowsocks: $key : $value") +// } catch (e: JSONException) { +// // Something went wrong! +// } +// } + + val shadowsocksConfig = mConfig?.getJSONObject("shadowsocks_config_data") + + if (shadowsocksConfig?.has("name") == true) { + profile.name = shadowsocksConfig.getString("name") + } else { + profile.name = "amnezia" + } + if (shadowsocksConfig?.has("method") == true) { + profile.method = shadowsocksConfig.getString("method").toString() + } + if (shadowsocksConfig?.has("server") == true) { + profile.host = shadowsocksConfig.getString("server").toString() + } + if (shadowsocksConfig?.has("password") == true) { + profile.password = shadowsocksConfig.getString("password").toString() + } + if (shadowsocksConfig?.has("server_port") == true) { + profile.remotePort = shadowsocksConfig.getInt("server_port") + } +// if(mConfig?.has("local_port") == true) { +// profile. = mConfig?.getInt("local_port") +// } +// profile.name = "amnezia" +// profile.method = "chacha20-ietf-poly1305" +// profile.host = "de01-ss.sshocean.net" +// profile.password = "ZTZhN" +// profile.remotePort = 8388 + + profile.proxyApps = false + profile.bypass = false + profile.metered = false + profile.dirty = false + profile.ipv6 = true + + DataStore.profileId = ProfileManager.createProfile(profile).id + val switchProfile = Core.switchProfile(DataStore.profileId) + Log.i(tag, "startShadowsocks: SwitchProfile: $switchProfile") + intent?.putExtra("startOnly", false) + onStartCommand( + intent, + flags, + startId + ) +// startRunner() +// VpnManager.getInstance().run() +// VpnManager.getInstance() +// .setOnStatusChangeListener(object : VpnManager.OnStatusChangeListener { +// override fun onStatusChanged(state: BaseService.State) { +// when (state) { +// BaseService.State.Connected -> { +// isUp = true +// } +// BaseService.State.Stopped -> { +// isUp = false +// } +// else -> {} +// } +// } +// +// override fun onTrafficUpdated(profileId: Long, stats: TrafficStats) { +// +// } +// }) +//// Core.startService() + } catch (e: Exception) { + Log.e(tag, "Error in startShadowsocks: $e") } } else { Log.e(tag, "Invalid config file!!") @@ -386,19 +647,20 @@ class VPNService : android.net.VpnService() { private fun startWireGuard() { val wireguard_conf = buildWireugardConfig(mConfig!!) + Log.i(tag, "startWireGuard: wireguard_conf : $wireguard_conf") if (currentTunnelHandle != -1) { Log.e(tag, "Tunnel already up") // Turn the tunnel down because this might be a switch wgTurnOff(currentTunnelHandle) } - val wgConfig: String = wireguard_conf!!.toWgUserspaceString() + val wgConfig: String = wireguard_conf.toWgUserspaceString() val builder = Builder() setupBuilder(wireguard_conf, builder) - builder.setSession("avpn0") + builder.setSession("Amnezia") builder.establish().use { tun -> if (tun == null) return Log.i(tag, "Go backend " + wgVersion()) - currentTunnelHandle = wgTurnOn("avpn0", tun.detachFd(), wgConfig) + currentTunnelHandle = wgTurnOn("Amnezia", tun.detachFd(), wgConfig) } if (currentTunnelHandle < 0) { Log.e(tag, "Activation Error Code -> $currentTunnelHandle") @@ -417,31 +679,163 @@ class VPNService : android.net.VpnService() { .apply() } - companion object { - @JvmStatic - fun startService(c: Context) { - c.applicationContext.startService( - Intent(c.applicationContext, VPNService::class.java).apply { - putExtra("startOnly", true) - }) + override suspend fun startProcesses() { + worker = ProtectWorker().apply { start() } + try { + Log.i(tag, "startProcesses: ------------------1") + super.startProcesses() + Log.i(tag, "startProcesses: ------------------2") + sendFd(startVpn()) + Log.i(tag, "startProcesses: ------------------3") + } catch (e: Exception) { + e.printStackTrace() } - - @JvmStatic - private external fun wgGetConfig(handle: Int): String? - - @JvmStatic - private external fun wgGetSocketV4(handle: Int): Int - - @JvmStatic - private external fun wgGetSocketV6(handle: Int): Int - - @JvmStatic - private external fun wgTurnOff(handle: Int) - - @JvmStatic - private external fun wgTurnOn(ifName: String, tunFd: Int, settings: String): Int - - @JvmStatic - private external fun wgVersion(): String? } + + override fun killProcesses(scope: CoroutineScope) { + super.killProcesses(scope) + active = false + scope.launch { DefaultNetworkListener.stop(this) } + worker?.shutdown(scope) + worker = null + conn?.close() + conn = null + } + + private suspend fun startVpn(): FileDescriptor { + val profile = data.proxy!!.profile + Log.i(tag, "startVpn: -----------------------1") + val builder = Builder() + .setConfigureIntent(Core.configureIntent(this)) + .setSession(profile.formattedName) + .setMtu(VPN_MTU) + .addAddress(PRIVATE_VLAN4_CLIENT, 30) + .addDnsServer(PRIVATE_VLAN4_ROUTER) + Log.i(tag, "startVpn: -----------------------2") + if (profile.ipv6) { + builder.addAddress(PRIVATE_VLAN6_CLIENT, 126) + builder.addRoute("::", 0) + } + Log.i(tag, "startVpn: -----------------------3") + val me = packageName + if (profile.proxyApps) { + profile.individual.split('\n') + .filter { it != me } + .forEach { + try { + if (profile.bypass) builder.addDisallowedApplication(it) + else builder.addAllowedApplication(it) + } catch (ex: PackageManager.NameNotFoundException) { + printLog(ex) + } + } + if (profile.bypass) { + builder.addDisallowedApplication(me) + } + } else { + builder.addDisallowedApplication(me) + } + Log.i(tag, "startVpn: -----------------------4") + when (profile.route) { + Acl.ALL, Acl.BYPASS_CHN, Acl.CUSTOM_RULES -> builder.addRoute("0.0.0.0", 0) + else -> { + resources.getStringArray(R.array.bypass_private_route).forEach { + val subnet = Subnet.fromString(it)!! + builder.addRoute(subnet.address.hostAddress, subnet.prefixSize) + } + builder.addRoute(PRIVATE_VLAN4_ROUTER, 32) + } + } + Log.i(tag, "startVpn: -----------------------5") + metered = profile.metered + active = true // possible race condition here? + Log.i(tag, "startVpn: -----------------------6") + builder.setUnderlyingNetworks(underlyingNetworks) + Log.i(tag, "startVpn: -----------------------7") + val conn = builder.establish() ?: throw NullConnectionException() + Log.i(tag, "startVpn: -----------------------8") + this.conn = conn + Log.i(tag, "startVpn: -----------------------9") + val cmd = arrayListOf( + File(applicationInfo.nativeLibraryDir, Executable.TUN2SOCKS).absolutePath, + "--netif-ipaddr", PRIVATE_VLAN4_ROUTER, + "--socks-server-addr", "${DataStore.listenAddress}:${DataStore.portProxy}", + "--tunmtu", VPN_MTU.toString(), + "--sock-path", "sock_path", + "--dnsgw", "127.0.0.1:${DataStore.portLocalDns}", + "--loglevel", "warning" + ) + Log.i(tag, "startVpn: -----------------------10") + if (profile.ipv6) { + cmd += "--netif-ip6addr" + cmd += PRIVATE_VLAN6_ROUTER + } + Log.i(tag, "startVpn: -----------------------11") + cmd += "--enable-udprelay" + Log.i(tag, "startVpn: -----------------------12") + data.processes!!.start(cmd, onRestartCallback = { + try { + sendFd(conn.fileDescriptor) + } catch (e: ErrnoException) { + e.printStackTrace() + stopRunner(false, e.message) + } + }) + Log.i(tag, "startVpn: -----------------------13") + return conn.fileDescriptor + } + + private suspend fun sendFd(fd: FileDescriptor) { + var tries = 0 + val path = File(Core.deviceStorage.noBackupFilesDir, "sock_path").absolutePath + while (true) try { + delay(50L shl tries) + LocalSocket().use { localSocket -> + localSocket.connect( + LocalSocketAddress( + path, + LocalSocketAddress.Namespace.FILESYSTEM + ) + ) + localSocket.setFileDescriptorsForSend(arrayOf(fd)) + localSocket.outputStream.write(42) + } + return + } catch (e: IOException) { + if (tries > 5) throw e + tries += 1 + } + } + + + private inner class ProtectWorker : ConcurrentLocalSocketListener( + "ShadowsocksVpnThread", + File(Core.deviceStorage.noBackupFilesDir, "protect_path") + ) { + override fun acceptInternal(socket: LocalSocket) { + socket.inputStream.read() + val fd = socket.ancillaryFileDescriptors!!.single()!! + CloseableFd(fd).use { + socket.outputStream.write(if (underlyingNetwork.let { network -> + if (network != null && Build.VERSION.SDK_INT >= 23) try { + network.bindSocket(fd) + true + } catch (e: IOException) { + // suppress ENONET (Machine is not on the network) + if ((e.cause as? ErrnoException)?.errno != 64) printLog(e) + false + } else protect(getInt.invoke(fd) as Int) + }) 0 else 1) + } + } + } + + inner class NullConnectionException : NullPointerException() { + override fun getLocalizedMessage() = getString(R.string.reboot_required) + } + + class CloseableFd(val fd: FileDescriptor) : Closeable { + override fun close() = Os.close(fd) + } + }