Shadowsocks protocol added

This commit is contained in:
aman 2022-04-26 23:49:20 +05:30
parent 59b4bf5267
commit 29656fb9a6
22 changed files with 791 additions and 664 deletions

View file

@ -77,7 +77,10 @@
<!-- extract android style --> <!-- extract android style -->
<meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/splashscreen"/> <meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/splashscreen"/>
</activity> </activity>
<service android:name=".VPNService" android:process=":QtOnlyProcess"> <service android:name=".VPNService" android:permission="android.permission.BIND_VPN_SERVICE" android:process=":QtOnlyProcess">
<intent-filter>
<action android:name="android.net.VpnService"/>
</intent-filter>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/> <meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/> <meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/> <meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>

View file

@ -47,32 +47,10 @@ dependencies {
implementation "androidx.security:security-crypto:1.1.0-alpha03" implementation "androidx.security:security-crypto:1.1.0-alpha03"
implementation "androidx.security:security-identity-credential:1.0.0-alpha02" 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-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" coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.0.10"
implementation project(path: ':shadowsocks') 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 { androidExtensions {

View file

@ -1 +0,0 @@
/build

View file

@ -3,6 +3,7 @@ allprojects {
repositories { repositories {
google() google()
jcenter() jcenter()
mavenCentral()
} }
} }
@ -40,14 +41,6 @@ android {
jvmTarget = '1.8' 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 { androidExtensions {
experimental = true experimental = true
@ -62,6 +55,8 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.30-M1" 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-android:1.3.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0"
implementation "androidx.core:core-ktx:1.2.0" implementation "androidx.core:core-ktx:1.2.0"
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"

View file

@ -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')"
]
}
}

View file

@ -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')"
]
}
}

View file

@ -30,48 +30,45 @@
android:name="com.google.android.backup.api_key" android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAI_zVxZthz2HDuz9toTvkYvL0L5GA-OjeUIfBeXg" /> android:value="AEdPqrEAAAAI_zVxZthz2HDuz9toTvkYvL0L5GA-OjeUIfBeXg" />
<service <!-- <service-->
android:name="org.amnezia.vpn.shadowsocks.core.bg.VpnService" <!-- android:name="org.amnezia.vpn.shadowsocks.core.bg.ShadowsocksVpnService"-->
android:directBootAware="true" <!-- android:directBootAware="true"-->
android:exported="false" <!-- android:exported="false"-->
android:label="@string/app_name" <!-- android:label="@string/app_name"-->
android:permission="android.permission.BIND_VPN_SERVICE" <!-- android:permission="android.permission.BIND_VPN_SERVICE"-->
android:process=":bg" <!-- android:process=":BG"-->
tools:targetApi="n"> <!-- tools:targetApi="n">-->
<intent-filter> <!-- <intent-filter>-->
<action android:name="android.net.VpnService" /> <!-- <action android:name="android.net.VpnService" />-->
</intent-filter> <!-- </intent-filter>-->
<meta-data <!-- </service>-->
android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON"
android:value="true" />
</service>
<service <!-- <service-->
android:name="org.amnezia.vpn.shadowsocks.core.bg.TransproxyService" <!-- android:name="org.amnezia.vpn.shadowsocks.core.bg.TransproxyService"-->
android:directBootAware="true" <!-- android:directBootAware="true"-->
android:exported="false" <!-- android:exported="false"-->
android:process=":bg" <!-- android:process=":QtOnlyProcess"-->
tools:targetApi="n" /> <!-- tools:targetApi="n" />-->
<service <!-- <service-->
android:name="org.amnezia.vpn.shadowsocks.core.bg.ProxyService" <!-- android:name="org.amnezia.vpn.shadowsocks.core.bg.ProxyService"-->
android:directBootAware="true" <!-- android:directBootAware="true"-->
android:exported="false" <!-- android:exported="false"-->
android:process=":bg" <!-- android:process=":QtOnlyProcess"-->
tools:targetApi="n" /> <!-- tools:targetApi="n" />-->
<activity <!-- <activity-->
android:name="org.amnezia.vpn.shadowsocks.core.VpnRequestActivity" <!-- android:name="org.amnezia.vpn.shadowsocks.core.VpnRequestActivity"-->
android:excludeFromRecents="true" <!-- android:excludeFromRecents="true"-->
android:launchMode="singleTask" <!-- android:launchMode="singleTask"-->
android:taskAffinity="" <!-- android:taskAffinity=""-->
android:theme="@style/Theme.AppCompat.Translucent" /> <!-- android:theme="@style/Theme.AppCompat.Translucent" />-->
<receiver <receiver
android:name="org.amnezia.vpn.shadowsocks.core.BootReceiver" android:name="org.amnezia.vpn.shadowsocks.core.BootReceiver"
android:directBootAware="true" android:directBootAware="true"
android:enabled="false" android:enabled="false"
android:process=":bg"> android:process=":QtOnlyProcess">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" /> <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
@ -87,48 +84,48 @@
<service <service
android:name="androidx.work.impl.background.systemalarm.SystemAlarmService" android:name="androidx.work.impl.background.systemalarm.SystemAlarmService"
android:directBootAware="true" android:directBootAware="true"
android:process=":bg" android:process=":QtOnlyProcess"
tools:replace="android:directBootAware" /> tools:replace="android:directBootAware" />
<service <service
android:name="androidx.work.impl.background.systemjob.SystemJobService" android:name="androidx.work.impl.background.systemjob.SystemJobService"
android:directBootAware="true" android:directBootAware="true"
android:process=":bg" android:process=":QtOnlyProcess"
tools:replace="android:directBootAware" /> tools:replace="android:directBootAware" />
<receiver <receiver
android:name="androidx.work.impl.utils.ForceStopRunnable$BroadcastReceiver" android:name="androidx.work.impl.utils.ForceStopRunnable$BroadcastReceiver"
android:directBootAware="true" android:directBootAware="true"
android:process=":bg" android:process=":QtOnlyProcess"
tools:replace="android:directBootAware" /> tools:replace="android:directBootAware" />
<receiver <receiver
android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryChargingProxy" android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryChargingProxy"
android:directBootAware="true" android:directBootAware="true"
android:process=":bg" android:process=":QtOnlyProcess"
tools:replace="android:directBootAware" /> tools:replace="android:directBootAware" />
<receiver <receiver
android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryNotLowProxy" android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryNotLowProxy"
android:directBootAware="true" android:directBootAware="true"
android:process=":bg" android:process=":QtOnlyProcess"
tools:replace="android:directBootAware" /> tools:replace="android:directBootAware" />
<receiver <receiver
android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$StorageNotLowProxy" android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$StorageNotLowProxy"
android:directBootAware="true" android:directBootAware="true"
android:process=":bg" android:process=":QtOnlyProcess"
tools:replace="android:directBootAware" /> tools:replace="android:directBootAware" />
<receiver <receiver
android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$NetworkStateProxy" android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$NetworkStateProxy"
android:directBootAware="true" android:directBootAware="true"
android:process=":bg" android:process=":QtOnlyProcess"
tools:replace="android:directBootAware" /> tools:replace="android:directBootAware" />
<receiver <receiver
android:name="androidx.work.impl.background.systemalarm.RescheduleReceiver" android:name="androidx.work.impl.background.systemalarm.RescheduleReceiver"
android:directBootAware="true" android:directBootAware="true"
android:process=":bg" android:process=":QtOnlyProcess"
tools:replace="android:directBootAware" /> tools:replace="android:directBootAware" />
<receiver <receiver
android:name="androidx.work.impl.background.systemalarm.ConstraintProxyUpdateReceiver" android:name="androidx.work.impl.background.systemalarm.ConstraintProxyUpdateReceiver"
android:directBootAware="true" android:directBootAware="true"
android:process=":bg" android:process=":QtOnlyProcess"
tools:replace="android:directBootAware" /> tools:replace="android:directBootAware" />
</application> </application>
</manifest> </manifest>

View file

@ -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.preference.DataStore
import org.amnezia.vpn.shadowsocks.core.utils.Key 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() { class VpnManager private constructor() {
var state = BaseService.State.Idle var state = BaseService.State.Idle
@ -80,31 +74,23 @@ class VpnManager private constructor() {
connect() connect()
} }
/*** fun run() {
* 开启或者关闭 自动判断
*/
fun run(activity:Activity) {
when { when {
state.canStop -> Core.stopService() state.canStop -> Core.stopService()
DataStore.serviceMode == Key.modeVpn -> { // DataStore.serviceMode == Key.modeVpn -> {
val intent = VpnService.prepare(activity) // val intent = VpnService.prepare(activity)
if (intent != null) activity.startActivityForResult(intent, REQUEST_CONNECT) // if (intent != null) activity.startActivityForResult(intent, REQUEST_CONNECT)
else onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null) // else onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null)
} // }
else -> Core.startService() else -> Core.startService()
} }
} }
/***
* 设置状态监听
*/
fun setOnStatusChangeListener(listener: OnStatusChangeListener) { fun setOnStatusChangeListener(listener: OnStatusChangeListener) {
this.listener = listener this.listener = listener
} }
/***
* application调用stop时调用
*/
fun onStop() { fun onStop() {
connection.bandwidthTimeout = 0 connection.bandwidthTimeout = 0
} }
@ -112,31 +98,23 @@ class VpnManager private constructor() {
fun onStart() { fun onStart() {
connection.bandwidthTimeout = 1000 connection.bandwidthTimeout = 1000
} }
/***
* activity调用onActivityResult时调用
*/
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when { when {
requestCode != REQUEST_CONNECT -> { requestCode != REQUEST_CONNECT -> {
} }
resultCode == Activity.RESULT_OK -> Core.startService() resultCode == Activity.RESULT_OK -> Core.startService()
else -> { else -> {
//无权限
} }
} }
} }
/***
* 改变当前状态
*/
private fun changeState(state: BaseService.State) { private fun changeState(state: BaseService.State) {
this.state = state this.state = state
this.listener?.onStatusChanged(state) this.listener?.onStatusChanged(state)
} }
/***
* 状态改变监听器
*/
interface OnStatusChangeListener { interface OnStatusChangeListener {
fun onStatusChanged(state: BaseService.State) fun onStatusChanged(state: BaseService.State)
@ -144,24 +122,24 @@ class VpnManager private constructor() {
} }
enum class Route(name: String) { enum class Route(name: String) {
//全部
ALL("all") ALL("all")
//绕过局域网地址
, ,
BY_PASS_LAN("bypass-lan") BY_PASS_LAN("bypass-lan")
//绕过中国大陆地址
, ,
BY_PASS_CHINA("bypass-china") BY_PASS_CHINA("bypass-china")
//绕过局域网和中国大陆地址
, ,
BY_PASS_LAN_CHINA("bypass-lan-china") BY_PASS_LAN_CHINA("bypass-lan-china")
//GFW列表
, ,
GFW_LIST("gfwlist") GFW_LIST("gfwlist")
//仅代理中国大陆地址
, ,
CHINA_LIST("china-list") CHINA_LIST("china-list")
//自定义规则
, ,
CUSTOM_RULES("custom-rules"); CUSTOM_RULES("custom-rules");

View file

@ -1,23 +1,3 @@
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package org.amnezia.vpn.shadowsocks.core.acl package org.amnezia.vpn.shadowsocks.core.acl
import android.content.Context import android.content.Context

View file

@ -1,23 +1,3 @@
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package org.amnezia.vpn.shadowsocks.core.acl package org.amnezia.vpn.shadowsocks.core.acl
import android.content.Context import android.content.Context

View file

@ -31,7 +31,7 @@ import android.os.RemoteException
import org.amnezia.vpn.shadowsocks.core.bg.BaseService import org.amnezia.vpn.shadowsocks.core.bg.BaseService
import org.amnezia.vpn.shadowsocks.core.bg.ProxyService import org.amnezia.vpn.shadowsocks.core.bg.ProxyService
import org.amnezia.vpn.shadowsocks.core.bg.TransproxyService 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.preference.DataStore
import org.amnezia.vpn.shadowsocks.core.utils.Action import org.amnezia.vpn.shadowsocks.core.utils.Action
import org.amnezia.vpn.shadowsocks.core.utils.Key import org.amnezia.vpn.shadowsocks.core.utils.Key
@ -45,7 +45,7 @@ class ShadowsocksConnection(private val handler: Handler = Handler(),
companion object { companion object {
val serviceClass get() = when (DataStore.serviceMode) { val serviceClass get() = when (DataStore.serviceMode) {
Key.modeProxy -> ProxyService::class Key.modeProxy -> ProxyService::class
Key.modeVpn -> VpnService::class Key.modeVpn -> ShadowsocksVpnService::class
Key.modeTransproxy -> TransproxyService::class Key.modeTransproxy -> TransproxyService::class
else -> throw UnknownError() else -> throw UnknownError()
}.java }.java

View file

@ -25,26 +25,25 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.os.* import android.os.*
import android.util.Log
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import kotlinx.coroutines.*
import org.amnezia.vpn.shadowsocks.core.Core import org.amnezia.vpn.shadowsocks.core.Core
import org.amnezia.vpn.shadowsocks.core.Core.app 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.IShadowsocksService
import org.amnezia.vpn.shadowsocks.core.aidl.IShadowsocksServiceCallback import org.amnezia.vpn.shadowsocks.core.aidl.IShadowsocksServiceCallback
import org.amnezia.vpn.shadowsocks.core.aidl.TrafficStats 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.plugin.PluginManager
import org.amnezia.vpn.shadowsocks.core.utils.Action import org.amnezia.vpn.shadowsocks.core.utils.Action
import org.amnezia.vpn.shadowsocks.core.utils.broadcastReceiver import org.amnezia.vpn.shadowsocks.core.utils.broadcastReceiver
import org.amnezia.vpn.shadowsocks.core.utils.printLog import org.amnezia.vpn.shadowsocks.core.utils.printLog
import org.amnezia.vpn.shadowsocks.core.utils.readableMessage import org.amnezia.vpn.shadowsocks.core.utils.readableMessage
import kotlinx.coroutines.*
import java.io.File import java.io.File
import java.net.BindException import java.net.BindException
import java.net.InetAddress import java.net.InetAddress
import java.net.URL import java.net.URL
import java.net.UnknownHostException import java.net.UnknownHostException
import java.util.*
/** /**
* This object uses WeakMap to simulate the effects of multi-inheritance. * 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 = "shadowsocks.conf"
const val CONFIG_FILE_UDP = "shadowsocks-udp.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 state = State.Stopped
var processes: GuardedProcessPool? = null var processes: GuardedProcessPool? = null
var proxy: ProxyInstance? = null var proxy: ProxyInstance? = null
var udpFallback: ProxyInstance? = null var udpFallback: ProxyInstance? = null
var notification: ServiceNotification? = null // var notification: ServiceNotification? = null
val closeReceiver = broadcastReceiver { _, intent -> val closeReceiver = broadcastReceiver { _, intent ->
when (intent.action) { when (intent.action) {
Action.RELOAD -> service.forceLoad() Action.RELOAD -> service.forceLoad()
@ -96,7 +95,8 @@ object BaseService {
stopListeningForBandwidth(callback ?: return) stopListeningForBandwidth(callback ?: return)
} }
} }
private val bandwidthListeners = mutableMapOf<IBinder, Long>() // the binder is the real identifier private val bandwidthListeners =
mutableMapOf<IBinder, Long>() // the binder is the real identifier
private val handler = Handler() private val handler = Handler()
override fun getState(): Int = (data?.state ?: State.Idle).ordinal override fun getState(): Int = (data?.state ?: State.Idle).ordinal
@ -119,14 +119,15 @@ object BaseService {
} }
private fun registerTimeout() { private fun registerTimeout() {
handler.postDelayed(this::onTimeout, bandwidthListeners.values.min() ?: return) handler.postDelayed(this::onTimeout, bandwidthListeners.values.minOrNull() ?: return)
} }
private fun onTimeout() { private fun onTimeout() {
val proxies = listOfNotNull(data?.proxy, data?.udpFallback) val proxies = listOfNotNull(data?.proxy, data?.udpFallback)
val stats = proxies val stats = proxies
.map { Pair(it.profile.id, it.trafficMonitor?.requestUpdate()) } .map { Pair(it.profile.id, it.trafficMonitor?.requestUpdate()) }
.filter { it.second != null } .filter { it.second != null }
.map { Triple(it.first, it.second!!.first, it.second!!.second) } .map { Triple(it.first, it.second!!.first, it.second!!.second) }
if (stats.any { it.third } && data?.state == State.Connected && bandwidthListeners.isNotEmpty()) { if (stats.any { it.third } && data?.state == State.Connected && bandwidthListeners.isNotEmpty()) {
val sum = stats.fold(TrafficStats()) { a, b -> a + b.second } val sum = stats.fold(TrafficStats()) { a, b -> a + b.second }
broadcast { item -> broadcast { item ->
@ -148,17 +149,21 @@ object BaseService {
val data = data val data = data
val proxy = data?.proxy ?: return val proxy = data?.proxy ?: return
proxy.trafficMonitor?.out.also { stats -> proxy.trafficMonitor?.out.also { stats ->
cb.trafficUpdated(proxy.profile.id, if (stats == null) sum else { cb.trafficUpdated(
sum += stats proxy.profile.id, if (stats == null) sum else {
stats sum += stats
}) stats
}
)
} }
data.udpFallback?.also { udpFallback -> data.udpFallback?.also { udpFallback ->
udpFallback.trafficMonitor?.out.also { stats -> udpFallback.trafficMonitor?.out.also { stats ->
cb.trafficUpdated(udpFallback.profile.id, if (stats == null) TrafficStats() else { cb.trafficUpdated(
sum += stats udpFallback.profile.id, if (stats == null) TrafficStats() else {
stats sum += stats
}) stats
}
)
} }
} }
cb.trafficUpdated(0, sum) cb.trafficUpdated(0, sum)
@ -197,15 +202,17 @@ object BaseService {
interface Interface { interface Interface {
val data: Data val data: Data
val tag: String 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() { fun forceLoad() {
val (profile, fallback) = Core.currentProfile 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() || 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)) stopRunner(false, (this as Context).getString(R.string.proxy_empty))
return return
} }
@ -221,17 +228,22 @@ object BaseService {
suspend fun startProcesses() { suspend fun startProcesses() {
val configRoot = (if (Build.VERSION.SDK_INT < 24 || app.getSystemService<UserManager>() val configRoot = (if (Build.VERSION.SDK_INT < 24 || app.getSystemService<UserManager>()
?.isUserUnlocked != false) app else Core.deviceStorage).noBackupFilesDir ?.isUserUnlocked != false
) app else Core.deviceStorage).noBackupFilesDir
val udpFallback = data.udpFallback val udpFallback = data.udpFallback
data.proxy!!.start(this, data.proxy!!.start(
File(Core.deviceStorage.noBackupFilesDir, "stat_main"), this,
File(configRoot, CONFIG_FILE), File(Core.deviceStorage.noBackupFilesDir, "stat_main"),
if (udpFallback == null) "-u" else null) File(configRoot, CONFIG_FILE),
if (udpFallback == null) "-u" else null
)
check(udpFallback?.pluginPath == null) { "UDP fallback cannot have plugins" } check(udpFallback?.pluginPath == null) { "UDP fallback cannot have plugins" }
udpFallback?.start(this, udpFallback?.start(
File(Core.deviceStorage.noBackupFilesDir, "stat_udp"), this,
File(configRoot, CONFIG_FILE_UDP), File(Core.deviceStorage.noBackupFilesDir, "stat_udp"),
"-U") File(configRoot, CONFIG_FILE_UDP),
"-U"
)
} }
fun startRunner() { fun startRunner() {
@ -264,8 +276,8 @@ object BaseService {
data.closeReceiverRegistered = false data.closeReceiverRegistered = false
} }
data.notification?.destroy() // data.notification?.destroy()
data.notification = null // data.notification = null
val ids = listOfNotNull(data.proxy, data.udpFallback).map { val ids = listOfNotNull(data.proxy, data.udpFallback).map {
it.shutdown(this) it.shutdown(this)
@ -280,30 +292,36 @@ object BaseService {
data.changeState(State.Stopped, msg) data.changeState(State.Stopped, msg)
// stop the service if nothing has bound to it // 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 resolver(host: String) = InetAddress.getAllByName(host)
suspend fun openConnection(url: URL) = url.openConnection() suspend fun openConnection(url: URL) = url.openConnection()
fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val data = data 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 val profilePair = Core.currentProfile
this as Context this as Context
if (profilePair == null) { if (profilePair == null) {
// gracefully shutdown: https://stackoverflow.com/q/47337857/2245107 // gracefully shutdown: https://stackoverflow.com/q/47337857/2245107
data.notification = createNotification("") // data.notification = createNotification("")
stopRunner(false, getString(R.string.profile_empty)) stopRunner(false, getString(R.string.profile_empty))
return Service.START_NOT_STICKY return Service.START_REDELIVER_INTENT
} }
val (profile, fallback) = profilePair val (profile, fallback) = profilePair
profile.name = profile.formattedName // save name for later queries profile.name = profile.formattedName // save name for later queries
val proxy = ProxyInstance(profile) val proxy = ProxyInstance(profile)
data.proxy = proxy 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) { if (!data.closeReceiverRegistered) {
registerReceiver(data.closeReceiver, IntentFilter().apply { registerReceiver(data.closeReceiver, IntentFilter().apply {
@ -314,7 +332,7 @@ object BaseService {
data.closeReceiverRegistered = true data.closeReceiverRegistered = true
} }
data.notification = createNotification(profile.formattedName) // data.notification = createNotification(profile.formattedName)
data.changeState(State.Connecting) data.changeState(State.Connecting)
data.connectingJob = GlobalScope.launch(Dispatchers.Main) { data.connectingJob = GlobalScope.launch(Dispatchers.Main) {
@ -340,16 +358,20 @@ object BaseService {
stopRunner(false, getString(R.string.invalid_server)) stopRunner(false, getString(R.string.invalid_server))
} catch (exc: Throwable) { } catch (exc: Throwable) {
if (exc !is PluginManager.PluginNotFoundException && if (exc !is PluginManager.PluginNotFoundException &&
exc !is BindException && exc !is BindException &&
exc !is VpnService.NullConnectionException) { exc !is ShadowsocksVpnService.NullConnectionException
) {
printLog(exc) printLog(exc)
} }
stopRunner(false, "${getString(R.string.service_failed)}: ${exc.readableMessage}") stopRunner(
false,
"${getString(R.string.service_failed)}: ${exc.readableMessage}"
)
} finally { } finally {
data.connectingJob = null data.connectingJob = null
} }
} }
return Service.START_NOT_STICKY return Service.START_REDELIVER_INTENT
} }
} }
} }

View file

@ -29,8 +29,8 @@ import android.content.Intent
class ProxyService : Service(), BaseService.Interface { class ProxyService : Service(), BaseService.Interface {
override val data = BaseService.Data(this) override val data = BaseService.Data(this)
override val tag: String get() = "ShadowsocksProxyService" override val tag: String get() = "ShadowsocksProxyService"
override fun createNotification(profileName: String): ServiceNotification = // override fun createNotification(profileName: String): ServiceNotification =
ServiceNotification(this, profileName, "service-proxy", true) // ServiceNotification(this, profileName, "service-proxy", true)
override fun onBind(intent: Intent) = super.onBind(intent) override fun onBind(intent: Intent) = super.onBind(intent)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int = override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int =

View file

@ -1,135 +1,145 @@
/******************************************************************************* ///*******************************************************************************
* * // * *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> * // * Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> * // * Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* * // * *
* This program is free software: you can redistribute it and/or modify * // * 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 * // * it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or * // * the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. * // * (at your option) any later version. *
* * // * *
* This program is distributed in the hope that it will be useful, * // * This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of * // * but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. * // * GNU General Public License for more details. *
* * // * *
* You should have received a copy of the GNU General Public License * // * You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. * // * along with this program. If not, see <http://www.gnu.org/licenses/>. *
* * // * *
*******************************************************************************/ // *******************************************************************************/
//
package org.amnezia.vpn.shadowsocks.core.bg //package org.amnezia.vpn.shadowsocks.core.bg
//
import android.app.KeyguardManager //import android.app.KeyguardManager
import android.app.NotificationManager //import android.app.NotificationManager
import android.app.PendingIntent //import android.app.PendingIntent
import android.app.Service //import android.app.Service
import android.content.Context //import android.content.Context
import android.content.Intent //import android.content.Intent
import android.content.IntentFilter //import android.content.IntentFilter
import android.os.Build //import android.os.Build
import android.os.PowerManager //import android.os.PowerManager
import android.text.format.Formatter //import android.text.format.Formatter
import androidx.core.app.NotificationCompat //import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat //import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService //import androidx.core.content.getSystemService
import org.amnezia.vpn.shadowsocks.core.Core //import org.amnezia.vpn.shadowsocks.core.Core
import org.amnezia.vpn.shadowsocks.core.aidl.IShadowsocksServiceCallback //import org.amnezia.vpn.shadowsocks.core.aidl.IShadowsocksServiceCallback
import org.amnezia.vpn.shadowsocks.core.aidl.TrafficStats //import org.amnezia.vpn.shadowsocks.core.aidl.TrafficStats
import org.amnezia.vpn.shadowsocks.core.R //import org.amnezia.vpn.shadowsocks.core.R
import org.amnezia.vpn.shadowsocks.core.utils.Action //import org.amnezia.vpn.shadowsocks.core.utils.Action
import org.amnezia.vpn.shadowsocks.core.utils.broadcastReceiver //import org.amnezia.vpn.shadowsocks.core.utils.broadcastReceiver
//
/** ///**
* Android < 8 VPN: always invisible because of VPN notification/icon // * Android < 8 VPN: always invisible because of VPN notification/icon
* Android < 8 other: only invisible in (possibly unsecure) lockscreen // * Android < 8 other: only invisible in (possibly unsecure) lockscreen
* Android 8+: always visible due to system limitations // * Android 8+: always visible due to system limitations
* (user can choose to hide the notification in secure lockscreen or anywhere) // * (user can choose to hide the notification in secure lockscreen or anywhere)
*/ // */
class ServiceNotification(private val service: BaseService.Interface, profileName: String, //class ServiceNotification(private val service: BaseService.Interface, profileName: String,
channel: String, private val visible: Boolean = false) { // channel: String, private val visible: Boolean = false) {
private val keyGuard = (service as Context).getSystemService<KeyguardManager>()!! // private val keyGuard = (service as Context).getSystemService<KeyguardManager>()!!
private val nm by lazy { (service as Context).getSystemService<NotificationManager>()!! } // private val nm by lazy { (service as Context).getSystemService<NotificationManager>()!! }
private val callback: IShadowsocksServiceCallback by lazy { // private val callback: IShadowsocksServiceCallback by lazy {
object : IShadowsocksServiceCallback.Stub() { // object : IShadowsocksServiceCallback.Stub() {
override fun stateChanged(state: Int, profileName: String?, msg: String?) { } // ignore // override fun stateChanged(state: Int, profileName: String?, msg: String?) {
override fun trafficUpdated(profileId: Long, stats: TrafficStats) { // when (state) {
if (profileId != 0L) return // BaseService.State.Connected.ordinal -> {
service as Context // builder.setContentText("VPN Connected")
val txr = service.getString(R.string.speed, Formatter.formatFileSize(service, stats.txRate)) // }
val rxr = service.getString(R.string.speed, Formatter.formatFileSize(service, stats.rxRate)) // BaseService.State.Stopped.ordinal -> {
builder.setContentText("$txr\t$rxr") // builder.setContentText("VPN Disconnected")
style.bigText(service.getString(R.string.stat_summary, txr, rxr, // }
Formatter.formatFileSize(service, stats.txTotal), // }
Formatter.formatFileSize(service, stats.rxTotal))) // } // ignore
show() // override fun trafficUpdated(profileId: Long, stats: TrafficStats) {
} //// if (profileId != 0L) return
override fun trafficPersisted(profileId: Long) { } //// 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))
private val lockReceiver = broadcastReceiver { _, intent -> update(intent.action) } //// builder.setContentText("$txr↑\t$rxr↓")
private var callbackRegistered = false //// style.bigText(service.getString(R.string.stat_summary, txr, rxr,
//// Formatter.formatFileSize(service, stats.txTotal),
private val builder = NotificationCompat.Builder(service as Context, channel) //// Formatter.formatFileSize(service, stats.rxTotal)))
.setWhen(0) //// show()
.setColor(ContextCompat.getColor(service, R.color.material_primary_500)) // }
.setTicker(service.getString(R.string.forward_success)) // override fun trafficPersisted(profileId: Long) { }
.setContentTitle(profileName) // }
.setContentIntent(Core.configureIntent(service)) // }
.setSmallIcon(R.drawable.ic_service_active) //// private val lockReceiver = broadcastReceiver { _, intent -> update(intent.action) }
private val style = NotificationCompat.BigTextStyle(builder).bigText("") // private var callbackRegistered = false
private var isVisible = true //
// private val builder = NotificationCompat.Builder(service as Context, channel)
init { // .setWhen(0)
service as Context // .setColor(ContextCompat.getColor(service, R.color.material_primary_500))
if (Build.VERSION.SDK_INT < 24) builder.addAction(R.drawable.ic_navigation_close, // .setTicker(service.getString(R.string.forward_success))
service.getString(R.string.stop), PendingIntent.getBroadcast(service, 0, Intent(Action.CLOSE), 0)) // .setContentTitle("AmneziaVPN -- testing")
update(if (service.getSystemService<PowerManager>()?.isInteractive != false) // .setContentIntent(Core.configureIntent(service))
Intent.ACTION_SCREEN_ON else Intent.ACTION_SCREEN_OFF, true) // .setSmallIcon(R.drawable.ic_amnezia_round)
service.registerReceiver(lockReceiver, IntentFilter().apply { // private val style = NotificationCompat.BigTextStyle(builder).bigText("")
addAction(Intent.ACTION_SCREEN_ON) // private var isVisible = true
addAction(Intent.ACTION_SCREEN_OFF) //
if (visible && Build.VERSION.SDK_INT < 26) addAction(Intent.ACTION_USER_PRESENT) // 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))
private fun update(action: String?, forceShow: Boolean = false) { //// update(if (service.getSystemService<PowerManager>()?.isInteractive != false)
if (forceShow || service.data.state == BaseService.State.Connected) when (action) { //// Intent.ACTION_SCREEN_ON else Intent.ACTION_SCREEN_OFF, true)
Intent.ACTION_SCREEN_OFF -> { //// service.registerReceiver(lockReceiver, IntentFilter().apply {
setVisible(false, forceShow) //// addAction(Intent.ACTION_SCREEN_ON)
unregisterCallback() // unregister callback to save battery //// addAction(Intent.ACTION_SCREEN_OFF)
} //// if (visible && Build.VERSION.SDK_INT < 26) addAction(Intent.ACTION_USER_PRESENT)
Intent.ACTION_SCREEN_ON -> { //// })
setVisible(visible && !keyGuard.isKeyguardLocked, forceShow) // }
service.data.binder.registerCallback(callback) //
service.data.binder.startListeningForBandwidth(callback, 1000) //// private fun update(action: String?, forceShow: Boolean = false) {
callbackRegistered = true //// if (forceShow || service.data.state == BaseService.State.Connected) when (action) {
} //// Intent.ACTION_SCREEN_OFF -> {
Intent.ACTION_USER_PRESENT -> setVisible(true, forceShow) //// setVisible(false, forceShow)
} //// unregisterCallback() // unregister callback to save battery
} //// }
//// Intent.ACTION_SCREEN_ON -> {
private fun unregisterCallback() { //// setVisible(visible && !keyGuard.isKeyguardLocked, forceShow)
if (callbackRegistered) { //// service.data.binder.registerCallback(callback)
service.data.binder.unregisterCallback(callback) //// service.data.binder.startListeningForBandwidth(callback, 1000)
callbackRegistered = false //// callbackRegistered = true
} //// }
} //// Intent.ACTION_USER_PRESENT -> setVisible(true, forceShow)
//// }
private fun setVisible(visible: Boolean, forceShow: Boolean = false) { //// }
if (isVisible != visible) { //
isVisible = visible // private fun unregisterCallback() {
builder.priority = if (visible) NotificationCompat.PRIORITY_LOW else NotificationCompat.PRIORITY_MIN // if (callbackRegistered) {
show() // service.data.binder.unregisterCallback(callback)
} else if (forceShow) show() // callbackRegistered = false
} // }
// }
private fun show() = (service as Service).startForeground(1, builder.build()) //
// private fun setVisible(visible: Boolean, forceShow: Boolean = false) {
fun destroy() { // if (isVisible != visible) {
(service as Service).unregisterReceiver(lockReceiver) // isVisible = visible
unregisterCallback() // builder.priority = if (visible) NotificationCompat.PRIORITY_LOW else NotificationCompat.PRIORITY_MIN
service.stopForeground(true) // show()
nm.cancel(1) // } 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)
// }
//}

View file

@ -30,6 +30,7 @@ import android.os.Build
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import android.system.ErrnoException import android.system.ErrnoException
import android.system.Os import android.system.Os
import android.util.Log
import org.amnezia.vpn.shadowsocks.core.Core import org.amnezia.vpn.shadowsocks.core.Core
import org.amnezia.vpn.shadowsocks.core.R import org.amnezia.vpn.shadowsocks.core.R
import org.amnezia.vpn.shadowsocks.core.VpnRequestActivity import org.amnezia.vpn.shadowsocks.core.VpnRequestActivity
@ -51,7 +52,7 @@ import java.net.URL
import java.util.* import java.util.*
import android.net.VpnService as BaseVpnService import android.net.VpnService as BaseVpnService
class VpnService : BaseVpnService(), LocalDnsService.Interface { open class ShadowsocksVpnService : BaseVpnService(), LocalDnsService.Interface {
companion object { companion object {
private const val VPN_MTU = 1500 private const val VPN_MTU = 1500
private const val PRIVATE_VLAN4_CLIENT = "172.19.0.1" 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 data = BaseService.Data(this)
override val tag: String get() = "ShadowsocksVpnService" 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 conn: ParcelFileDescriptor? = null
private var worker: ProtectWorker? = null private var worker: ProtectWorker? = null
@ -117,7 +120,9 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
else -> super<LocalDnsService.Interface>.onBind(intent) else -> super<LocalDnsService.Interface>.onBind(intent)
} }
override fun onRevoke() = stopRunner() override fun onRevoke() {
stopRunner()
}
override fun killProcesses(scope: CoroutineScope) { override fun killProcesses(scope: CoroutineScope) {
super.killProcesses(scope) super.killProcesses(scope)
@ -136,7 +141,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
} else return super<LocalDnsService.Interface>.onStartCommand(intent, flags, startId) } else return super<LocalDnsService.Interface>.onStartCommand(intent, flags, startId)
} }
stopRunner() stopRunner()
return Service.START_NOT_STICKY return Service.START_STICKY
} }
override suspend fun preInit() = DefaultNetworkListener.start(this) { underlyingNetwork = it } override suspend fun preInit() = DefaultNetworkListener.start(this) { underlyingNetwork = it }

View file

@ -29,15 +29,16 @@ import java.io.File
class TransproxyService : Service(), LocalDnsService.Interface { class TransproxyService : Service(), LocalDnsService.Interface {
override val data = BaseService.Data(this) override val data = BaseService.Data(this)
override val tag: String get() = "ShadowsocksTransproxyService" override val tag: String get() = "ShadowsocksTransproxyService"
override fun createNotification(profileName: String): ServiceNotification = // override fun createNotification(profileName: String): ServiceNotification =
ServiceNotification(this, profileName, "service-transproxy", true) // ServiceNotification(this, profileName, "service-transproxy", true)
override fun onBind(intent: Intent) = super.onBind(intent) override fun onBind(intent: Intent) = super.onBind(intent)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int = override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int =
super<LocalDnsService.Interface>.onStartCommand(intent, flags, startId) super<LocalDnsService.Interface>.onStartCommand(intent, flags, startId)
private fun startRedsocksDaemon() { private fun startRedsocksDaemon() {
File(Core.deviceStorage.noBackupFilesDir, "redsocks.conf").writeText("""base { File(Core.deviceStorage.noBackupFilesDir, "redsocks.conf").writeText(
"""base {
log_debug = off; log_debug = off;
log_info = off; log_info = off;
log = stderr; log = stderr;
@ -51,9 +52,15 @@ redsocks {
port = ${DataStore.portProxy}; port = ${DataStore.portProxy};
type = socks5; 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() { override suspend fun startProcesses() {

View file

@ -75,9 +75,9 @@ object Key {
} }
object Action { object Action {
const val SERVICE = "com.kyle.shadowsocks.SERVICE" const val SERVICE = "org.amnezia.vpn.shadowsocks.SERVICE"
const val CLOSE = "com.kyle.shadowsocks.CLOSE" const val CLOSE = "org.amnezia.vpn.shadowsocks.CLOSE"
const val RELOAD = "com.kyle.shadowsocks.RELOAD" 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"
} }

View file

@ -38,10 +38,10 @@ import androidx.core.os.bundleOf
* ... * ...
* &lt;application&gt; * &lt;application&gt;
* ... * ...
* &lt;provider android:name="com.kyle.shadowsocks.$PLUGIN_ID.BinaryProvider" * &lt;provider android:name="org.amnezia.vpn.shadowsocks.$PLUGIN_ID.BinaryProvider"
* android:authorities="com.kyle.shadowsocks.plugin.$PLUGIN_ID.BinaryProvider"&gt; * android:authorities="org.amnezia.vpn.shadowsocks.plugin.$PLUGIN_ID.BinaryProvider"&gt;
* &lt;intent-filter&gt; * &lt;intent-filter&gt;
* &lt;category android:name="com.kyle.shadowsocks.plugin.ACTION_NATIVE_PLUGIN" /&gt; * &lt;category android:name="org.amnezia.vpn.shadowsocks.plugin.ACTION_NATIVE_PLUGIN" /&gt;
* &lt;/intent-filter&gt; * &lt;/intent-filter&gt;
* &lt;/provider&gt; * &lt;/provider&gt;
* ... * ...

View file

@ -29,61 +29,61 @@ object PluginContract {
/** /**
* ContentProvider Action: Used for NativePluginProvider. * 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. * 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. * 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. * 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. * The lookup key for a string that provides the options as a string.
* *
* Example: "obfs=http;obfs-host=www.baidu.com" * 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. * The lookup key for a CharSequence that provides user relevant help message.
* *
* Example: "obfs=<http></http>|tls> Enable obfuscating: HTTP or TLS (Experimental). * Example: "obfs=<http></http>|tls> Enable obfuscating: HTTP or TLS (Experimental).
* obfs-host=<host_name> Hostname for obfuscating (Experimental)." * obfs-host=<host_name> Hostname for obfuscating (Experimental)."
* *
* Constant Value: "com.kyle.shadowsocks.plugin.EXTRA_HELP_MESSAGE" * Constant Value: "org.amnezia.vpn.shadowsocks.plugin.EXTRA_HELP_MESSAGE"
</host_name> */ </host_name> */
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. * 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. * 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" const val METHOD_GET_EXECUTABLE = "shadowsocks:getExecutable"
@ -114,5 +114,5 @@ object PluginContract {
/** /**
* The authority for general plugin actions. * The authority for general plugin actions.
*/ */
const val AUTHORITY = "com.kyle.shadowsocks" const val AUTHORITY = "org.amnezia.vpn.shadowsocks"
} }

File diff suppressed because one or more lines are too long

View file

@ -1,11 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:name="path"
android:fillColor="#fff"
android:pathData="M 21.25 2.28 L 17.55 18.55 L 9.26 15.89 L 16.58 7.16 L 6.83 15.37 L 0 12.8 L 21.25 2.28 ZM 9.45 17.56 L 12.09 18.41 L 9.46 22 L 9.45 17.56 Z" />
</vector>

View file

@ -6,18 +6,137 @@ package org.amnezia.vpn
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.content.pm.PackageManager
import android.os.IBinder import android.net.LocalSocket
import android.net.LocalSocketAddress
import android.net.Network
import android.net.ProxyInfo import android.net.ProxyInfo
import android.os.ParcelFileDescriptor import android.os.*
import android.system.ErrnoException
import android.system.Os
import android.system.OsConstants import android.system.OsConstants
import com.wireguard.android.util.SharedLibraryLoader import com.wireguard.android.util.SharedLibraryLoader
import com.wireguard.config.* import com.wireguard.config.*
import com.wireguard.crypto.Key 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 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 mBinder: VPNServiceBinder = VPNServiceBinder(this)
private var mConfig: JSONObject? = null private var mConfig: JSONObject? = null
private var mProtocol: String? = null private var mProtocol: String? = null
@ -26,7 +145,11 @@ class VPNService : android.net.VpnService() {
private var mbuilder: Builder = Builder() private var mbuilder: Builder = Builder()
private var mOpenVPNThreadv3: OpenVPNThreadv3? = null 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() { fun init() {
if (mAlreadyInitialised) { if (mAlreadyInitialised) {
@ -41,8 +164,16 @@ class VPNService : android.net.VpnService() {
mAlreadyInitialised = true 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 { override fun onUnbind(intent: Intent?): Boolean {
Log.v(tag, "Got Unbind request") Log.v(tag, "Aman: onUnbind....................")
if (!isUp) { if (!isUp) {
// If the Qt Client got closed while we were not connected // If the Qt Client got closed while we were not connected
// we do not need to stay as a foreground service. // 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 * EntryPoint for the Service, gets Called when AndroidController.cpp
* calles bindService. Returns the [VPNServiceBinder] so QT can send Requests to it. * calles bindService. Returns the [VPNServiceBinder] so QT can send Requests to it.
*/ */
override fun onBind(intent: Intent?): IBinder? { override fun onBind(intent: Intent): IBinder {
Log.v(tag, "Got Bind request") Log.v(tag, "Aman: onBind....................")
init() when (mProtocol) {
"shadowsocks" -> {
when (intent.action) {
SERVICE_INTERFACE -> super<BaseVpnService>.onBind(intent)
else -> super<LocalDnsService.Interface>.onBind(intent)
}
startTest()
}
else -> {
init()
}
}
return mBinder return mBinder
} }
@ -67,11 +210,16 @@ class VPNService : android.net.VpnService() {
* or from Booting the device and having "connect on boot" enabled. * or from Booting the device and having "connect on boot" enabled.
*/ */
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { 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() init()
intent?.let { intent?.let {
if (intent.getBooleanExtra("startOnly", false)) { if (!isUp && intent.getBooleanExtra("startOnly", false)) {
Log.i(tag, "Start only!") Log.i(tag, "Start only!")
return super.onStartCommand(intent, flags, startId) return START_REDELIVER_INTENT
// return super<LocalDnsService.Interface>.onStartCommand(intent, flags, startId)
} }
} }
// This start is from always-on // This start is from always-on
@ -81,18 +229,39 @@ class VPNService : android.net.VpnService() {
val lastConfString = prefs.getString("lastConf", "") val lastConfString = prefs.getString("lastConf", "")
if (lastConfString.isNullOrEmpty()) { if (lastConfString.isNullOrEmpty()) {
// We have nothing to connect to -> Exit // We have nothing to connect to -> Exit
Log.e(tag,"VPN service was triggered without defining a Server or having a tunnel") Log.e(tag, "VPN service was triggered without defining a Server or having a tunnel")
return super.onStartCommand(intent, flags, startId) return super<android.net.VpnService>.onStartCommand(intent, flags, startId)
} }
this.mConfig = JSONObject(lastConfString) 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<LocalDnsService.Interface>.onStartCommand(intent, flags, startId)
}
}
stopRunner()
}
return START_REDELIVER_INTENT
} }
// Invoked when the application is revoked. // Invoked when the application is revoked.
// At this moment, the VPN interface is already deactivated by the system. // At this moment, the VPN interface is already deactivated by the system.
override fun onRevoke() { override fun onRevoke() {
Log.v(tag, "Aman: onRevoke....................")
this.turnOff() this.turnOff()
super.onRevoke() super.onRevoke()
} }
@ -145,6 +314,7 @@ class VPNService : android.net.VpnService() {
} }
fun turnOn(json: JSONObject?): Int { fun turnOn(json: JSONObject?): Int {
Log.v(tag, "Aman: turnOn....................")
if (!checkPermissions()) { if (!checkPermissions()) {
Log.e(tag, "turn on was called without no permissions present!") Log.e(tag, "turn on was called without no permissions present!")
isUp = false isUp = false
@ -152,23 +322,31 @@ class VPNService : android.net.VpnService() {
} }
Log.i(tag, "Permission okay") Log.i(tag, "Permission okay")
mConfig = json!! mConfig = json!!
Log.i(tag, "Config: " + mConfig) Log.i(tag, "Config: $mConfig")
mProtocol = mConfig!!.getString("protocol") mProtocol = mConfig!!.getString("protocol")
Log.i(tag, "Protocol: " + mProtocol) Log.i(tag, "Protocol: $mProtocol")
when (mProtocol) { when (mProtocol) {
"openvpn" -> startOpenVpn() "openvpn" -> {
"wireguard" -> startWireGuard() startOpenVpn()
"shadowsocks" -> startShadowsocks() }
"wireguard" -> {
startWireGuard()
}
"shadowsocks" -> {
startShadowsocks()
startTest()
}
else -> { else -> {
Log.e(tag, "No protocol") Log.e(tag, "No protocol")
return 0 return 0
} }
} }
NotificationUtil.show(this) // Go foreground NotificationUtil.show(this)
return 1 return 1
} }
fun establish(): ParcelFileDescriptor? { fun establish(): ParcelFileDescriptor? {
Log.v(tag, "Aman: establish....................")
mbuilder.allowFamily(OsConstants.AF_INET) mbuilder.allowFamily(OsConstants.AF_INET)
mbuilder.allowFamily(OsConstants.AF_INET6) mbuilder.allowFamily(OsConstants.AF_INET6)
@ -208,7 +386,9 @@ class VPNService : android.net.VpnService() {
fun addHttpProxy(host: String, port: Int): Boolean { fun addHttpProxy(host: String, port: Int): Boolean {
val proxyInfo = ProxyInfo.buildDirectProxy(host, port) val proxyInfo = ProxyInfo.buildDirectProxy(host, port)
Log.v(tag, "mbuilder.addHttpProxy($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 return true
} }
@ -218,19 +398,22 @@ class VPNService : android.net.VpnService() {
} }
fun turnOff() { fun turnOff() {
Log.v(tag, "Try to disable tunnel") Log.v(tag, "Aman: turnOff....................")
when (mProtocol) { when (mProtocol) {
"wireguard" -> wgTurnOff(currentTunnelHandle) "wireguard" -> wgTurnOff(currentTunnelHandle)
"openvpn" -> ovpnTurnOff() "openvpn" -> ovpnTurnOff()
"shadowsocks" -> {
stopRunner(false)
stopTest()
}
else -> { else -> {
Log.e(tag, "No protocol") Log.e(tag, "No protocol")
} }
} }
currentTunnelHandle = -1 currentTunnelHandle = -1
stopForeground(true) stopForeground(true)
isUp = false isUp = false
stopSelf(); stopSelf()
} }
@ -366,11 +549,89 @@ class VPNService : android.net.VpnService() {
private fun startShadowsocks() { private fun startShadowsocks() {
Log.e(tag, "startShadowsocks method enters") Log.e(tag, "startShadowsocks method enters")
if(mConfig != null) { if (mConfig != null) {
try { try {
Log.e(tag, "Config: $mConfig")
} catch(e: Exception) { ProfileManager.clear()
Log.e(tag, "Error in startShadowsocks: $e") val profile = Profile()
// val iter: Iterator<String> = 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 { } else {
Log.e(tag, "Invalid config file!!") Log.e(tag, "Invalid config file!!")
@ -386,19 +647,20 @@ class VPNService : android.net.VpnService() {
private fun startWireGuard() { private fun startWireGuard() {
val wireguard_conf = buildWireugardConfig(mConfig!!) val wireguard_conf = buildWireugardConfig(mConfig!!)
Log.i(tag, "startWireGuard: wireguard_conf : $wireguard_conf")
if (currentTunnelHandle != -1) { if (currentTunnelHandle != -1) {
Log.e(tag, "Tunnel already up") Log.e(tag, "Tunnel already up")
// Turn the tunnel down because this might be a switch // Turn the tunnel down because this might be a switch
wgTurnOff(currentTunnelHandle) wgTurnOff(currentTunnelHandle)
} }
val wgConfig: String = wireguard_conf!!.toWgUserspaceString() val wgConfig: String = wireguard_conf.toWgUserspaceString()
val builder = Builder() val builder = Builder()
setupBuilder(wireguard_conf, builder) setupBuilder(wireguard_conf, builder)
builder.setSession("avpn0") builder.setSession("Amnezia")
builder.establish().use { tun -> builder.establish().use { tun ->
if (tun == null) return if (tun == null) return
Log.i(tag, "Go backend " + wgVersion()) Log.i(tag, "Go backend " + wgVersion())
currentTunnelHandle = wgTurnOn("avpn0", tun.detachFd(), wgConfig) currentTunnelHandle = wgTurnOn("Amnezia", tun.detachFd(), wgConfig)
} }
if (currentTunnelHandle < 0) { if (currentTunnelHandle < 0) {
Log.e(tag, "Activation Error Code -> $currentTunnelHandle") Log.e(tag, "Activation Error Code -> $currentTunnelHandle")
@ -417,31 +679,163 @@ class VPNService : android.net.VpnService() {
.apply() .apply()
} }
companion object { override suspend fun startProcesses() {
@JvmStatic worker = ProtectWorker().apply { start() }
fun startService(c: Context) { try {
c.applicationContext.startService( Log.i(tag, "startProcesses: ------------------1")
Intent(c.applicationContext, VPNService::class.java).apply { super.startProcesses()
putExtra("startOnly", true) 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)
}
} }