Commit 2677cee4 authored by chenhuaqing's avatar chenhuaqing

code refactor

parent 116dee96
......@@ -7,6 +7,14 @@ data class NetState(
var accNetDelay: Int,
//丢包率
var lossRate: String
var lossRate: String,
var pingWifiLatency: Int = -1,
var pingWifiTotal: Int = -1,
var pingWifiLoss: Int = -1,
var pingCellularLatency: Int = -1,
var pingCellularTotal: Int = -1,
var pingCellularLoss: Int = -1,
)
\ No newline at end of file
......@@ -5,10 +5,11 @@ import androidx.lifecycle.ViewModelProvider
import androidx.viewpager.widget.ViewPager
import com.ccwangluo.accelerator.databinding.FragmentBtnNavBinding
import com.ccwangluo.accelerator.ui.home.AccelertorFragment
import com.ccwangluo.accelerator.ui.home.AcceleratorFragment
import com.ccwangluo.accelerator.ui.home.AccelertorViewModel
import com.ccwangluo.accelerator.ui.home.MineFragment
import com.ccwangluo.accelerator.ui.home.NewsFragment
import com.ccwangluo.accelerator.utils.AcceleratorUtils
import com.ccwangluo.accelerator.utils.LoginUtils
import com.ccwangluo.accelerator.utils.datareport.DataRePortUtils
import com.google.android.material.bottomnavigation.BottomNavigationView
......@@ -22,12 +23,12 @@ import com.xuexiang.xutil.common.CollectionUtils
@Page(name = "main")
class BottomNavigationFragment : XPageFragment(), ViewPager.OnPageChangeListener,
BottomNavigationView.OnNavigationItemSelectedListener {
private lateinit var accelertorViewModel: AccelertorViewModel
private lateinit var acceleratorViewModel: AccelertorViewModel
lateinit var binding: FragmentBtnNavBinding
override fun inflateView(inflater: LayoutInflater?, container: ViewGroup?): View {
binding = FragmentBtnNavBinding.inflate(inflater!!, container, false)
accelertorViewModel =
acceleratorViewModel =
ViewModelProvider(requireActivity()).get(AccelertorViewModel::class.java)
return binding.root
}
......@@ -39,7 +40,7 @@ class BottomNavigationFragment : XPageFragment(), ViewPager.OnPageChangeListener
var titles = arrayOf("加速", "资讯", "我的")
var fragments = arrayOf(
AccelertorFragment.newInstance(),
AcceleratorFragment.newInstance(),
NewsFragment.newInstance(),
MineFragment.newInstance()
)
......@@ -55,6 +56,8 @@ class BottomNavigationFragment : XPageFragment(), ViewPager.OnPageChangeListener
binding.viewPager.setOffscreenPageLimit(titles.size - 1);
binding.viewPager.adapter = adapter
binding.viewPager.setNoScroll(true)
AcceleratorUtils.initConnect(fragments[0] as AcceleratorFragment)
}
override fun initListeners() {
......@@ -78,7 +81,7 @@ class BottomNavigationFragment : XPageFragment(), ViewPager.OnPageChangeListener
LoginUtils.token?.let {
LoginUtils.getUserProfile(this) {
it?.let {
accelertorViewModel.userProfile.postValue(it)
acceleratorViewModel.userProfile.postValue(it)
}
}
}
......
......@@ -45,14 +45,14 @@ class MainActivity() : XPageActivity(), TencentLocationListener {
}
}
private lateinit var accelertorViewModel: AccelertorViewModel
private lateinit var acceleratorViewModel: AccelertorViewModel
private var mSensorEventListener: SensorEventListener? = null
private var mSensorManager: SensorManager? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
accelertorViewModel =
acceleratorViewModel =
ViewModelProvider(this).get(AccelertorViewModel::class.java)
// app 启动
if (SettingSPUtils.lastAPPOpenReportTime == 0L ||
......@@ -83,14 +83,14 @@ class MainActivity() : XPageActivity(), TencentLocationListener {
getPermission()
}
AcceleratorUtils.initConnect(this, connect)
AcceleratorUtils.initContext(this, connect)
mSensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
mSensorEventListener = JCVideoPlayer.JCAutoFullscreenListener()
DataRePortUtils.report("st_app_open")
}
fun getPermission() {
private fun getPermission() {
PermissionUtils.permission(Manifest.permission.READ_PHONE_STATE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
......
......@@ -12,6 +12,7 @@ import android.view.animation.RotateAnimation
import androidx.lifecycle.ViewModelProvider
import com.ccwangluo.accelerator.R
import com.ccwangluo.accelerator.databinding.FragmentQuickBinding
import com.ccwangluo.accelerator.model.NetState
import com.ccwangluo.accelerator.ui.dialog.CommonDialog
import com.ccwangluo.accelerator.ui.dialog.MemberDialog
import com.ccwangluo.accelerator.ui.web.CommonWebViewFragment
......@@ -30,11 +31,10 @@ import com.xuexiang.xpage.utils.TitleBar
import com.xuexiang.xui.widget.progress.CircleProgressView
import com.xuexiang.xutil.app.AppUtils
class AccelertorFragment : XPageFragment() {
class AcceleratorFragment : XPageFragment() {
private lateinit var binding: FragmentQuickBinding
private lateinit var accelertorViewModel: AccelertorViewModel
private var acc_timer = 0L
private lateinit var acceleratorViewModel: AccelertorViewModel
private var accTimer = 0L
override fun initTitleBar(): TitleBar? {
//不使用@Page标注的一定要注意覆盖这个方法
......@@ -43,16 +43,25 @@ class AccelertorFragment : XPageFragment() {
var state: BaseService.State? = null
fun updateNetState(netState: NetState) {
acceleratorViewModel.netState.postValue(netState)
accTimer = AcceleratorUtils.getAccTime()
}
fun changeState(state: BaseService.State) {
acceleratorViewModel.accState.postValue(state)
}
override fun inflateView(inflater: LayoutInflater?, container: ViewGroup?): View {
binding = FragmentQuickBinding.inflate(inflater!!, container, false)
accelertorViewModel =
acceleratorViewModel =
ViewModelProvider(requireActivity()).get(AccelertorViewModel::class.java)
return binding.root
}
override fun initViews() {
accelertorViewModel.netState.observe(this, {
acceleratorViewModel.netState.observe(this, {
if (it.preNetDelay != -1) { //有网络
binding.preDelay1.setText(it.preNetDelay.toString())
......@@ -99,8 +108,8 @@ class AccelertorFragment : XPageFragment() {
binding.activeNetwork.setText(SysUtils.getActiveNet())
})
accelertorViewModel.accState.observe(this, {
binding.accStateTx.setText(SysUtils.getAccTimeString(acc_timer))
acceleratorViewModel.accState.observe(this, {
binding.accStateTx.setText(SysUtils.getAccTimeString(accTimer))
state = it
freshData(it)
if (SettingSPUtils.isSmartAccOpen) {
......@@ -114,7 +123,7 @@ class AccelertorFragment : XPageFragment() {
}
})
accelertorViewModel.userProfile.observe(this, {
acceleratorViewModel.userProfile.observe(this, {
if (it.hasUnreadNotice) {
binding.iconNoticeBadge.visibility = View.VISIBLE
} else {
......@@ -136,7 +145,7 @@ class AccelertorFragment : XPageFragment() {
binding.progressArea.visibility = View.GONE
binding.btnQuick.isEnabled = true
binding.progressViewCircleSmall.progress = 0F
AcceleratorUtils.startAccelerator(this@AccelertorFragment)
AcceleratorUtils.startAccelerator(this@AcceleratorFragment)
}
})
......@@ -196,7 +205,7 @@ class AccelertorFragment : XPageFragment() {
}
}
binding.btnQuick.setOnClickListener {
accelertorViewModel.accState.value?.let {
acceleratorViewModel.accState.value?.let {
if (it.canStop) {
context?.let {
CommonDialog(it).setTitle("加速停止确认")
......@@ -252,7 +261,7 @@ class AccelertorFragment : XPageFragment() {
}
}
//用户是否有使用时长
LoginUtils.getUserHasTimer(this@AccelertorFragment) {
LoginUtils.getUserHasTimer(this@AcceleratorFragment) {
if (it) {
binding.btnQuick.isEnabled = false
startAnim()
......@@ -261,7 +270,7 @@ class AccelertorFragment : XPageFragment() {
"st_speed_result",
mapOf("result" to 0, "cause" to 3)
)
MemberDialog(this@AccelertorFragment).show<MemberDialog>()
MemberDialog(this@AcceleratorFragment).show<MemberDialog>()
}
}
} else {
......@@ -375,7 +384,7 @@ class AccelertorFragment : XPageFragment() {
LoginUtils.token?.let {
LoginUtils.getUserProfile(this) {
it?.let {
accelertorViewModel.userProfile.postValue(it)
acceleratorViewModel.userProfile.postValue(it)
}
}
}
......@@ -384,7 +393,7 @@ class AccelertorFragment : XPageFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AcceleratorUtils.registerVipExpireListener {
MemberDialog(this@AccelertorFragment).show<MemberDialog>()
MemberDialog(this@AcceleratorFragment).show<MemberDialog>()
}
}
......@@ -396,18 +405,18 @@ class AccelertorFragment : XPageFragment() {
override fun onStart() {
super.onStart()
AcceleratorUtils.registerStateListener { state, l, net ->
net?.let {
accelertorViewModel.netState.postValue(net)
}
acc_timer = l
accelertorViewModel.accState.postValue(state)
}
// AcceleratorUtils.registerStateListener { state, l, net ->
// net?.let {
// acceleratorViewModel.netState.postValue(net)
// }
// accTimer = l
// acceleratorViewModel.accState.postValue(state)
// }
}
override fun onStop() {
super.onStop()
AcceleratorUtils.unRegisterStateListener()
// AcceleratorUtils.unRegisterStateListener()
}
override fun onResume() {
......@@ -462,9 +471,8 @@ class AccelertorFragment : XPageFragment() {
}
companion object {
fun newInstance(): AccelertorFragment {
val fragment = AccelertorFragment()
return fragment
fun newInstance(): AcceleratorFragment {
return AcceleratorFragment()
}
}
......
......@@ -4,17 +4,20 @@ import android.annotation.SuppressLint
import android.content.Context
import android.os.RemoteException
import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.ccwangluo.accelerator.model.*
import com.ccnet.acc.NetworkAdapter
import com.ccwangluo.accelerator.model.Game
import com.ccwangluo.accelerator.model.GameInfo
import com.ccwangluo.accelerator.model.NetState
import com.ccwangluo.accelerator.ui.dialog.CommonDialog
import com.ccwangluo.accelerator.ui.home.AcceleratorFragment
import com.ccwangluo.accelerator.utils.datareport.DataRePortUtils
import com.ccwangluo.accelerator.utils.http.HttpGo
import com.github.shadowsocks.Core
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.aidl.IShadowsocksService
import com.github.shadowsocks.aidl.ShadowsocksConnection
import com.github.shadowsocks.aidl.TrafficStats
import com.github.shadowsocks.bg.AuthManager
import com.github.shadowsocks.bg.AuthManager.ACC_TIME
import com.github.shadowsocks.bg.BaseService
......@@ -22,9 +25,16 @@ import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.preference.DataStore
import com.hjq.toast.ToastUtils
import com.xuexiang.xpage.base.XPageActivity
import com.xuexiang.xpage.base.XPageFragment
import com.xuexiang.xutil.common.StringUtils
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.net.InetAddress
import java.util.concurrent.TimeUnit
/**
......@@ -37,22 +47,67 @@ object AcceleratorUtils {
private lateinit var connect: ActivityResultLauncher<Void?>
private lateinit var context: AppCompatActivity
private lateinit var context: XPageActivity
private lateinit var acceleratorFragment: AcceleratorFragment
// service
var state = BaseService.State.Idle
private set
//游戏
var game: GameInfo? = null
var stateListener: (BaseService.State, Long, NetState?) -> Unit = { state, l, net ->
private val stateListener: (BaseService.State) -> Unit = { state ->
if (state == BaseService.State.Connected) {
game?.let {
val gameServerAndPort = it.boosterServer.split(":")
DataStore.pingAccHost = gameServerAndPort[0]
DataStore.pingAccPort = gameServerAndPort[1].toInt()
context.lifecycleScope.launch(Dispatchers.IO) {
while (AcceleratorUtils.state == BaseService.State.Connected) {
netState.preNetDelay =
NetUtils.tcpPing(gameServerAndPort[0], gameServerAndPort[1].toInt())
.toInt()
// val delay =
// NetUtils.tcpPing(serverPort[0], serverPort[1].toInt(), DataStore.portProxy)
// .toInt()
// netState.accNetDelay = delay
LoginUtils.getUserHasTimer { haveTime ->
if (!haveTime) { //没有加速时长关闭服务
stopAccelerator()
}
}
netState.accNetDelay =
if (netState.accNetDelay > netState.preNetDelay && netState.preNetDelay != -1) {
netState.preNetDelay
} else {
netState.accNetDelay
}
netState.lossRate = if (netState.accNetDelay == -1) "100" else "0"
acceleratorFragment.updateNetState(netState)
delay(1000)
}
}
}
} else {
netState.accNetDelay = -1
netState.lossRate = if (netState.preNetDelay == -1) "100" else "0"
acceleratorFragment.updateNetState(netState)
}
acceleratorFragment.changeState(state)
}
var vipExpireListener: () -> Unit = { ->
var vipExpireListener: () -> Unit = { ->
}
var netState = NetState(-1, -1, "100")
fun getGameList(xPageFragment: XPageFragment, callback: (Boolean) -> Unit) {
game?.let {
callback(true)
......@@ -64,8 +119,8 @@ object AcceleratorUtils {
withContext(Dispatchers.Main) {
callback(true)
}
game?.let {
AuthManager.gameId = it.id.toString()
game?.let { gameInfo ->
AuthManager.gameId = gameInfo.id.toString()
}
}
}
......@@ -73,44 +128,26 @@ object AcceleratorUtils {
}
fun initConnect(context: AppCompatActivity, connect: ActivityResultLauncher<Void?>) {
fun initContext(context: XPageActivity, connect: ActivityResultLauncher<Void?>) {
this.context = context
this.connect = connect
connection.connect(context, shadowsocksConnection)
NetworkAdapter.initNetworks(context)
ProfileManager.ensureNotEmpty()
context.lifecycleScope.launch(Dispatchers.IO) {
NetUtils.init()
while (true) {
game?.let {
var net = NetState(-1, -1, "100")
if (state == BaseService.State.Connected) {
val split = it.boosterServer.split(":")
net.preNetDelay = NetUtils.tcpPing(split[0], split[1].toInt()).toInt()
val delay =
NetUtils.tcpPing(split[0], split[1].toInt(), DataStore.portProxy)
.toInt()
net.accNetDelay = delay
LoginUtils.getUserHasTimer {
if (!it) { //没有加速时长关闭服务
stopAccelerator()
}
}
net.accNetDelay =
if (net.accNetDelay > net.preNetDelay && net.preNetDelay != -1) net.preNetDelay else net.accNetDelay
net.lossRate = if (net.accNetDelay == -1) "100" else "0"
} else {
net.accNetDelay = -1
net.lossRate = if (net.preNetDelay == -1) "100" else "0"
}
withContext(Dispatchers.Main) {
stateListener(state, getAccTime(), net)
}
delay(1000)
}
testLatency()
delay(TimeUnit.SECONDS.toMillis(DataStore.pingIntervalSec.toLong()))
}
}
}
fun initConnect(acceleratorFragment: AcceleratorFragment) {
this.acceleratorFragment = acceleratorFragment
connection.connect(context, shadowsocksConnection)
}
fun checkVpn() {
if (AuthManager.Last_ACC_TIme == 0L || System.currentTimeMillis() - AuthManager.Last_ACC_TIme > 2 * 3600 * 1000) {
return
......@@ -131,37 +168,67 @@ object AcceleratorUtils {
fun startAccelerator(xPageFragment: XPageFragment) {
game?.let {
initAuthProfile(xPageFragment)
val serverPort = it.boosterServer.split(":")
DataStore.pingAccHost = serverPort[0]
DataStore.pingAccPort = serverPort[1].toInt()
// context.lifecycleScope.launch(Dispatchers.IO) {
// while (true) {
// val net = NetState(-1, -1, "100")
// if (state == BaseService.State.Connected) {
// net.preNetDelay =
// NetUtils.tcpPing(serverPort[0], serverPort[1].toInt()).toInt()
// val delay =
// NetUtils.tcpPing(
// serverPort[0],
// serverPort[1].toInt(),
// DataStore.portProxy
// )
// .toInt()
// net.accNetDelay = delay
// LoginUtils.getUserHasTimer {
// if (!it) { //没有加速时长关闭服务
// stopAccelerator()
// }
// }
// net.accNetDelay =
// if (net.accNetDelay > net.preNetDelay && net.preNetDelay != -1) net.preNetDelay else net.accNetDelay
// net.lossRate = if (net.accNetDelay == -1) "100" else "0"
// } else {
// net.accNetDelay = -1
// net.lossRate = if (net.preNetDelay == -1) "100" else "0"
// }
// withContext(Dispatchers.Main) {
// stateListener(state, getAccTime(), net)
// }
// delay(1000)
// }
// }
}
}
fun initAuthProfile(xPageFragment: XPageFragment) {
private fun initAuthProfile(xPageFragment: XPageFragment) {
xPageFragment.lifecycleScope.launch(Dispatchers.IO) {
val profileId = 1L
val profile = ProfileManager.getProfile(profileId) ?: Profile()
profile.proxyApps = true
val proxyGames = mutableSetOf(
"com.sec.android.app.sbrowser",
"com.nexon.maplem.global",
"jp.co.cygames.umamusume",
"com.ea.gp.apexlegendsmobilefps",
"jp.konami.duellinks",
"com.riotgames.league.wildrift",
"com.garena.game.codm",
"com.google.android.configupdater",
"com.miHoYo.GenshinImpact",
"com.rekoo.pubgm",
"com.pubg.krmobile",
"com.tgc.sky.android",
"com.YoStarJP.Arknights",
// "com.android.chrome",
"com.example.myapplication",
"com.riotgames.league.wildrifttw",
"com.baidu.browser.apps"
"com.activision.callofduty.shooter",
"com.pubg.krmobile",
)
game?.boosterPackageName?.split(",")?.forEach {
if (!StringUtils.isEmpty(it)) {
proxyGames.add(it)
}
}
// proxyGames.addAll(profile.individual.split("\n"))
game?.boosterPackageName?.split(",")?.forEach {
if (!StringUtils.isEmpty(it)) {
proxyGames.add(it)
}
}
profile.individual = proxyGames.joinToString("\n")
profile.route = Acl.BYPASS_LAN
profile.password = "barfoo!"
......@@ -175,27 +242,27 @@ object AcceleratorUtils {
}
fun registerStateListener(listener: (BaseService.State, Long, NetState?) -> Unit) {
stateListener = listener
stateListener(state, getAccTime(), null)
}
fun unRegisterStateListener() {
stateListener = { state, l, net ->
}
}
// fun registerStateListener(listener: (BaseService.State, Long, NetState?) -> Unit) {
// stateListener = listener
// stateListener(state, getAccTime(), null)
// }
//
// fun unRegisterStateListener() {
// stateListener = { state, l, net ->
//
// }
// }
fun registerVipExpireListener(listener: () -> Unit) {
vipExpireListener = listener
}
fun unRegisterVipExpireListener() {
vipExpireListener = { ->
vipExpireListener = { ->
}
}
fun stopAccelerator() {
Core.stopService()
......@@ -204,35 +271,43 @@ object AcceleratorUtils {
private val shadowsocksConnection = object : ShadowsocksConnection.Callback {
override fun stateChanged(state: BaseService.State, profileName: String?, msg: String?) {
AcceleratorUtils.state = state
stateListener(state, getAccTime(), null)
stateListener(state)
if (state == BaseService.State.Stopped && !msg.isNullOrBlank()) {
DataRePortUtils.report("st_speed_result", mapOf("result" to 0, "cause" to 3.toString()))
if("vip到期".equals(msg)||(msg!=null&&msg.startsWith("vip已到期"))){
DataRePortUtils.report(
"st_speed_result",
mapOf("result" to 0, "cause" to 3.toString())
)
if ("vip到期".equals(msg) || (msg != null && msg.startsWith("vip已到期"))) {
vipExpireListener()
}else{
} else {
stopAccelerator()
ToastUtils.show(msg)
}
}
if (state == BaseService.State.Connected && getAccTime() < 100){
if (state == BaseService.State.Connected && getAccTime() < 100) {
DataRePortUtils.report("st_speed_result", mapOf("result" to 1))
}
}
override fun onServiceConnected(service: IShadowsocksService) {
try {
state = BaseService.State.values()[service.state]
state = try {
BaseService.State.values()[service.state]
} catch (_: RemoteException) {
state = BaseService.State.Idle
BaseService.State.Idle
}
stateListener(state, getAccTime(), null)
stateListener(state)
}
override fun onBinderDied() {
connection.disconnect(context)
connection.connect(context, this)
}
override fun trafficUpdated(profileId: Long, stats: TrafficStats) {
netState.accNetDelay = stats.latency
// acceleratorFragment.updateTraffic(stats)
}
}
fun getAccTime(): Long {
......@@ -252,4 +327,43 @@ object AcceleratorUtils {
connection.disconnect(context)
}
private fun testLatency() {
val networkAutoSwitch = false
if (networkAutoSwitch) {
val wifiLatency: Long = NetworkAdapter.wifiNetwork?.let { wifi ->
try {
val pingAddress = wifi.getByName(DataStore.pingNetReachableHost) // DataStore.pingAccHost
val start = System.currentTimeMillis()
pingAddress.isReachable(DataStore.pingTimeoutMillis)
System.currentTimeMillis() - start
} catch (_: Exception) {
-1L
}
} ?: -1
val cellularLatency: Long = NetworkAdapter.cellularNetwork?.let { wifi ->
try {
val pingAddress = wifi.getByName(DataStore.pingNetReachableHost) // DataStore.pingAccHost
val start = System.currentTimeMillis()
pingAddress.isReachable(DataStore.pingTimeoutMillis)
System.currentTimeMillis() - start
} catch (_: Exception) {
-1L
}
} ?: -1
Timber.d("wifi latency: $wifiLatency, cellular latency: $cellularLatency")
} else {
val currentNetworkLatency = try {
val defaultNetAddress = InetAddress.getByName(DataStore.pingNetReachableHost) // DataStore.pingAccHost
val start = System.currentTimeMillis()
defaultNetAddress.isReachable(DataStore.pingTimeoutMillis)
System.currentTimeMillis() - start
} catch (_: Exception) {
-1L
}
Timber.d("network latency: $currentNetworkLatency")
}
}
}
\ No newline at end of file
package com.ccwangluo.accelerator.utils
import android.net.Network
import com.github.shadowsocks.net.DefaultNetworkListener
import kotlinx.coroutines.isActive
import timber.log.Timber
import java.io.IOException
import java.net.InetSocketAddress
import java.net.Proxy
import java.net.Socket
import java.net.UnknownHostException
import kotlin.coroutines.coroutineContext
object NetUtils {
@Volatile
private var underlyingNetwork: Network? = null
suspend fun init() {
DefaultNetworkListener.start(this) {
underlyingNetwork = it
}
}
suspend fun tcpPing(url: String, port: Int, proxyPort: Int = 0): Long {
var time = -1L
for (k in 0 until 2) {
......@@ -25,12 +35,16 @@ object NetUtils {
return time
}
private fun socketConnectTime(url: String, port: Int, proxyPort: Int): Long {
private suspend fun socketConnectTime(url: String, port: Int, proxyPort: Int): Long {
try {
val socket = if (proxyPort == 0) {
Socket()
} else {
Socket(Proxy(Proxy.Type.SOCKS, InetSocketAddress("127.0.0.1", proxyPort)))
val socket = Socket()
DefaultNetworkListener.get().bindSocket(socket)
socket
// Socket(Proxy(Proxy.Type.SOCKS, InetSocketAddress("127.0.0.1", proxyPort)))
}
val start = System.currentTimeMillis()
socket.connect(InetSocketAddress(url, port))
......
{
"formatVersion": 1,
"database": {
"version": 30,
"identityHash": "c6293b758903bd4b8084f9d2f0ce9763",
"entities": [
{
"tableName": "Profile",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `host` TEXT NOT NULL, `remotePort` INTEGER NOT NULL, `password` TEXT NOT NULL, `token` TEXT, `method` TEXT NOT NULL, `authTimeout` INTEGER NOT NULL, `beatsInterval` INTEGER NOT NULL, `retryLimit` INTEGER NOT NULL, `route` TEXT NOT NULL, `remoteDns` TEXT NOT NULL, `proxyApps` INTEGER NOT NULL, `bypass` INTEGER NOT NULL, `udpdns` INTEGER NOT NULL, `ipv6` INTEGER NOT NULL, `metered` INTEGER NOT NULL, `individual` TEXT NOT NULL, `plugin` TEXT, `udpFallback` INTEGER, `subscription` INTEGER NOT NULL, `tx` INTEGER NOT NULL, `rx` INTEGER NOT NULL, `userOrder` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "remotePort",
"columnName": "remotePort",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "password",
"columnName": "password",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "token",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "method",
"columnName": "method",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "authTimeout",
"columnName": "authTimeout",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "beatsInterval",
"columnName": "beatsInterval",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "retryLimit",
"columnName": "retryLimit",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "route",
"columnName": "route",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "remoteDns",
"columnName": "remoteDns",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "proxyApps",
"columnName": "proxyApps",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "bypass",
"columnName": "bypass",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "udpdns",
"columnName": "udpdns",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ipv6",
"columnName": "ipv6",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "metered",
"columnName": "metered",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "individual",
"columnName": "individual",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "plugin",
"columnName": "plugin",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "udpFallback",
"columnName": "udpFallback",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "subscription",
"columnName": "subscription",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "tx",
"columnName": "tx",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "rx",
"columnName": "rx",
"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, `valueType` INTEGER NOT NULL, `value` BLOB NOT NULL, PRIMARY KEY(`key`))",
"fields": [
{
"fieldPath": "key",
"columnName": "key",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "valueType",
"columnName": "valueType",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "value",
"columnName": "value",
"affinity": "BLOB",
"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, 'c6293b758903bd4b8084f9d2f0ce9763')"
]
}
}
\ No newline at end of file
......@@ -10,4 +10,5 @@ interface IShadowsocksService {
void startListeningForBandwidth(in IShadowsocksServiceCallback cb, long timeout);
oneway void stopListeningForBandwidth(in IShadowsocksServiceCallback cb);
oneway void unregisterCallback(in IShadowsocksServiceCallback cb);
oneway void bindNetwork(int networkMode);
}
package com.ccnet.acc
import android.content.Context
import com.github.shadowsocks.database.Profile
import timber.log.Timber
import java.io.File
object AccVpn {
private const val configFilename = "config.conf"
fun configFile(context: Context): File {
return File(context.filesDir, configFilename)
}
fun writeConfig(context: Context, tunFd: Int, profile: Profile) {
val configContent = """
[General]
loglevel = trace
dns-server = 223.5.5.5
tun-fd = ${tunFd}
[Proxy]
Direct = direct
SS = ss, ${profile.host}, ${profile.remotePort}, encrypt-method=chacha20-ietf-poly1305, password=123456, auth_timeout=${profile.authTimeout}, beats_interval=${profile.beatsInterval}, retry_limit=${profile.retryLimit}, token=${profile.token}
[Rule]
FINAL, SS
"""
val configFile = configFile(context)
Timber.d("writeConfig ${configFile.absolutePath}")
configFile.writeText(configContent)
}
}
\ No newline at end of file
package com.ccnet.acc
class AccVpnService {
companion object {
private val INSTANCE = AccVpnService()
val instance: AccVpnService
get() {
return INSTANCE
}
}
init {
System.loadLibrary("ccnetacc")
}
external fun runAcc(
configPath: String,
protectPath: String,
networkClass: String,
callback: String
)
external fun testLatency(
proxyHost: String,
proxyPort: Int,
testHost: String,
intervalSec: Int,
timeoutMillis: Int,
callback: TestLatencyCallback
): Int
external fun stopAcc()
}
interface TestLatencyCallback {
fun onLatency(latency: Int)
}
\ No newline at end of file
package com.ccnet.acc
import android.annotation.SuppressLint
import android.content.Context
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import timber.log.Timber
import java.io.FileDescriptor
import java.io.IOException
import java.lang.reflect.Field
object NetworkAdapter {
val JNI_CLAZZ_NAME = NetworkAdapter::class.java.name.replace(".", "/")
var wifiNetwork: Network? = null
var cellularNetwork: Network? = null
private var bindNetwork: Network? = null
private val FD_FIELD: Field
val wifiRequest: NetworkRequest = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
val cellularRequest: NetworkRequest = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
fun initNetworks(context: Context) {
connectivityManager(context).requestNetwork(wifiRequest, object : NetworkCallback() {
override fun onAvailable(network: Network) {
wifiNetwork = network
}
override fun onLost(network: Network) {
wifiNetwork = null
}
})
connectivityManager(context).requestNetwork(cellularRequest, object : NetworkCallback() {
override fun onAvailable(network: Network) {
cellularNetwork = network
}
override fun onLost(network: Network) {
cellularNetwork = null
}
})
}
fun useWifi(context: Context) {
connectivityManager(context).requestNetwork(wifiRequest, object : NetworkCallback() {
override fun onAvailable(network: Network) {
bindNetwork = network
}
override fun onLost(network: Network) {
bindNetwork = null
}
})
}
fun useCellular(context: Context) {
connectivityManager(context).requestNetwork(cellularRequest, object : NetworkCallback() {
override fun onAvailable(network: Network) {
bindNetwork = network
}
override fun onLost(network: Network) {
bindNetwork = null
}
})
}
fun useSystem() {
bindNetwork = null
}
private fun connectivityManager(context: Context): ConnectivityManager {
return context.getSystemService(ConnectivityManager::class.java)
}
@JvmStatic
fun bindSocket(rawFd: Int) {
val bindNetwork = bindNetwork ?: return
val fd = FileDescriptor()
try {
FD_FIELD[fd] = rawFd
NetworkAdapter.bindNetwork!!.bindSocket(fd)
Timber.d("bind socket success")
} catch (e: IllegalAccessException) {
// ignore
Timber.e("bind socket failed $e")
} catch (e: IOException) {
Timber.e("bind socket failed $e")
}
}
init {
try {
@SuppressLint("DiscouragedPrivateApi")
FD_FIELD = FileDescriptor::class.java.getDeclaredField("descriptor")
} catch (e: NoSuchFieldException) {
throw RuntimeException(e)
}
FD_FIELD.isAccessible = true
}
}
\ No newline at end of file
package com.ccnet.acc.aidl
enum class NetworkMode(val code: Int) {
WIFI(0),
CELLULAR(1),
SYSTEM(2),
}
\ No newline at end of file
package com.ccnet.acc.common
object Constants {
const val CONFIG_PREFERENCE = "config_preference"
const val ACC_CONFIG_KEY = "acc_config_key"
}
\ No newline at end of file
package com.ccnet.acc.native
interface NativeCallback {
enum class ErrorCode(val code: Int) {
AuthFailed(1001),
NetworkBindSocketFailed(1002),
NetworkError(1003),
UnknownError(1004),
;
companion object {
private val VALUES = values().associateBy { it.code }
fun from(code: Int): ErrorCode {
return VALUES[code] ?: UnknownError
}
}
}
fun onStart()
fun onError(code: ErrorCode, error: String)
fun onFatal(code: ErrorCode, error: String)
}
\ No newline at end of file
package com.ccnet.acc.native
object VpnNativeDispatcher {
val JNI_CLAZZ_NAME = VpnNativeDispatcher::class.java.name.replace(".", "/")
private val NATIVE_CALLBACKS: MutableMap<String, NativeCallback> = mutableMapOf()
@JvmStatic
fun registerCallback(key: String, listener: NativeCallback) {
NATIVE_CALLBACKS[key] = listener
}
@JvmStatic
private fun onStart() {
for (eventListener in NATIVE_CALLBACKS.values) {
eventListener.onStart()
}
}
@JvmStatic
fun onError(code: Int, error: String) {
for (eventListener in NATIVE_CALLBACKS.values) {
eventListener.onError(NativeCallback.ErrorCode.from(code), error)
}
}
@JvmStatic
fun onFatal(code: Int, error: String) {
for (eventListener in NATIVE_CALLBACKS.values) {
eventListener.onFatal(NativeCallback.ErrorCode.from(code), error)
}
}
}
\ No newline at end of file
......@@ -41,12 +41,6 @@ import kotlinx.coroutines.launch
class ShadowsocksConnection(private var listenForDeath: Boolean = false) : ServiceConnection, IBinder.DeathRecipient {
companion object {
val serviceClass get() = VpnService::class.java
// when (DataStore.serviceMode) {
// Key.modeProxy -> ProxyService::class
// Key.modeVpn -> VpnService::class
// Key.modeTransproxy -> TransproxyService::class
// else -> throw UnknownError()
// }.java
}
interface Callback {
......
......@@ -25,15 +25,19 @@ import kotlinx.parcelize.Parcelize
@Parcelize
data class TrafficStats(
// Bytes per second
var txRate: Long = 0L,
var rxRate: Long = 0L,
// Bytes per second
var txRate: Long = 0L,
var rxRate: Long = 0L,
// Bytes for the current session
var txTotal: Long = 0L,
var rxTotal: Long = 0L
// Bytes for the current session
var txTotal: Long = 0L,
var rxTotal: Long = 0L,
// round trip latency
var latency: Int = 0,
) : Parcelable {
operator fun plus(other: TrafficStats) = TrafficStats(
txRate + other.txRate, rxRate + other.rxRate,
txTotal + other.txTotal, rxTotal + other.rxTotal)
txRate + other.txRate, rxRate + other.rxRate,
txTotal + other.txTotal, rxTotal + other.rxTotal, other.latency
)
}
package com.github.shadowsocks.bg
import android.os.Build
import android.util.Log
import com.ccnet.acc.AccVpn
import com.github.shadowsocks.Core
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.http.*
import com.github.shadowsocks.preference.DataStore
import com.google.gson.Gson
import com.google.gson.JsonStreamParser
import com.google.gson.reflect.TypeToken
......@@ -94,6 +95,9 @@ object AuthManager {
profile.host = lineConfig.host
profile.token = lineConfig.token
profile.remotePort = lineConfig.port
DataStore.pingProxyServer = profile.host
DataStore.pingProxyPort = profile.remotePort + 1
Timber.d("start vpn with games: ${profile.individual}, profileId: ${profile.id}, route: ${profile.route}")
ProfileManager.updateProfile(profile)
} else if (lineConfigResult.code == 1001) {
......@@ -129,6 +133,10 @@ object AuthManager {
proxyInstance.profile.token = refreshTokenResult.unwrapData().token
configJson.addProperty("token", proxyInstance.profile.token)
configFile.writeText(configJson.toString())
data.tunFd?.let {
AccVpn.writeConfig(Core.app, it, proxyInstance.profile)
}
return@let
} else if (refreshTokenResult.code == 1001) {
failedCount=0
throw AuthException(refreshTokenResult.error!!)
......@@ -157,7 +165,7 @@ object AuthManager {
): ApiResult<T> {
val body: RequestBody = gson.toJson(params).toString().toRequestBody(jsonType)
val request = Request.Builder()
.url(HttpConfig.baseUrl + url)
.url(if (url.startsWith("http")) url else HttpConfig.baseUrl + url)
.post(body)
token?.let {
request.addHeader("Authorization", it)
......
......@@ -28,6 +28,10 @@ import android.os.Build
import android.os.IBinder
import android.os.RemoteCallbackList
import android.os.RemoteException
import com.ccnet.acc.NetworkAdapter
import com.ccnet.acc.aidl.NetworkMode
import com.ccnet.acc.native.NativeCallback
import com.ccnet.acc.native.VpnNativeDispatcher
import com.github.shadowsocks.BootReceiver
import com.github.shadowsocks.Core
import com.github.shadowsocks.Core.app
......@@ -43,7 +47,6 @@ import com.github.shadowsocks.utils.broadcastReceiver
import com.github.shadowsocks.utils.readableMessage
import kotlinx.coroutines.*
import timber.log.Timber
import java.io.File
import java.io.IOException
import java.net.URL
......@@ -74,6 +77,7 @@ object BaseService {
var proxy: ProxyInstance? = null
var udpFallback: ProxyInstance? = null
var localDns: LocalDnsWorker? = null
var tunFd: Int? = null
var notification: ServiceNotification? = null
val closeReceiver = broadcastReceiver { _, intent ->
......@@ -192,6 +196,14 @@ object BaseService {
callbacks.unregister(cb)
}
override fun bindNetwork(networkMode: Int) {
when (networkMode) {
NetworkMode.WIFI.code -> NetworkAdapter.useWifi(app)
NetworkMode.CELLULAR.code -> NetworkAdapter.useCellular(app)
NetworkMode.SYSTEM.code -> NetworkAdapter.useSystem()
}
}
fun stateChanged(s: State, msg: String?) = launch {
val profileName = profileName
broadcast { it.stateChanged(s.ordinal, profileName, msg) }
......@@ -229,20 +241,20 @@ object BaseService {
val isVpnService get() = false
suspend fun startProcesses() {
val context = if (Build.VERSION.SDK_INT < 24 || Core.user.isUserUnlocked) app else Core.deviceStorage
val configRoot = context.noBackupFilesDir
val udpFallback = data.udpFallback
data.proxy!!.start(this,
File(Core.deviceStorage.noBackupFilesDir, "stat_main"),
File(configRoot, CONFIG_FILE),
if (udpFallback == null && data.proxy?.plugin == null) "tcp_and_udp" else "tcp_only")
if (udpFallback?.plugin != null) throw ExpectedExceptionWrapper(IllegalStateException(
"UDP fallback cannot have plugins"))
udpFallback?.start(this,
File(Core.deviceStorage.noBackupFilesDir, "stat_udp"),
File(configRoot, CONFIG_FILE_UDP),
"udp_only", false)
data.localDns = LocalDnsWorker(this::rawResolver).apply { start() }
// val context = if (Build.VERSION.SDK_INT < 24 || Core.user.isUserUnlocked) app else Core.deviceStorage
// val configRoot = context.noBackupFilesDir
// val udpFallback = data.udpFallback
// data.proxy!!.start(this,
// File(Core.deviceStorage.noBackupFilesDir, "stat_main"),
// File(configRoot, CONFIG_FILE),
// if (udpFallback == null && data.proxy?.plugin == null) "tcp_and_udp" else "tcp_only")
// if (udpFallback?.plugin != null) throw ExpectedExceptionWrapper(IllegalStateException(
// "UDP fallback cannot have plugins"))
// udpFallback?.start(this,
// File(Core.deviceStorage.noBackupFilesDir, "stat_udp"),
// File(configRoot, CONFIG_FILE_UDP),
// "udp_only", false)
// data.localDns = LocalDnsWorker(this::rawResolver).apply { start() }
}
fun startRunner() {
......@@ -346,7 +358,8 @@ object BaseService {
data.changeState(State.Connecting)
data.connectingJob = GlobalScope.launch(Dispatchers.Main) {
try {
Executable.killAll() // clean up old processes
AuthManager.initAuthProfile(profile)
// Executable.killAll() // clean up old processes
preInit()
if (profile.route == Acl.CUSTOM_RULES) try {
withContext(Dispatchers.IO) {
......@@ -362,12 +375,28 @@ object BaseService {
Timber.w(it)
stopRunner(false, it.readableMessage)
}
startProcesses()
VpnNativeDispatcher.registerCallback(this.javaClass.name,
object : NativeCallback {
override fun onStart() {
data.changeState(State.Connected)
}
data.proxy!!.scheduleUpdate()
data.udpFallback?.scheduleUpdate()
override fun onError(code: NativeCallback.ErrorCode, error: String) {
}
override fun onFatal(code: NativeCallback.ErrorCode, error: String) {
Timber.e("base service onFatal $code, $error")
if (code == NativeCallback.ErrorCode.NetworkError) {
stopRunner(true, error)
} else {
stopRunner(false, error)
}
}
})
startProcesses()
data.changeState(State.Connected)
// data.proxy!!.scheduleUpdate()
// data.udpFallback?.scheduleUpdate()
} catch (_: CancellationException) {
// if the job was cancelled, it is canceller's responsibility to call stopRunner
} catch (exc: Throwable) {
......
......@@ -52,7 +52,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
}
}
private var configFile: File? = null
var trafficMonitor: TrafficMonitor? = null
var trafficMonitor: TrafficMonitor? = TrafficMonitor(null)
val plugin by lazy { PluginManager.init(PluginConfiguration(profile.plugin ?: "")) }
/**
......@@ -120,6 +120,8 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
Timber.d("start vpn service with acl ${Acl.getFile(route).readText()}")
}
// DataStore.outboundBindInterface = "rmnet_data1"
if (DataStore.outboundBindInterface.isNotBlank()) {
cmd += "--outbound-bind-interface"
cmd += DataStore.outboundBindInterface
......
......@@ -22,38 +22,83 @@ package com.github.shadowsocks.bg
import android.net.LocalSocket
import android.os.SystemClock
import com.ccnet.acc.AccVpnService
import com.ccnet.acc.TestLatencyCallback
import com.github.shadowsocks.aidl.TrafficStats
import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.net.LocalSocketListener
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot
import kotlinx.coroutines.CoroutineScope
import timber.log.Timber
import java.io.File
import java.io.IOException
import java.nio.ByteBuffer
import java.nio.ByteOrder
class TrafficMonitor(statFile: File) {
val thread = object : LocalSocketListener("TrafficMonitor-" + statFile.name, statFile) {
private val buffer = ByteArray(16)
private val stat = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN)
override fun acceptInternal(socket: LocalSocket) {
when (val read = socket.inputStream.read(buffer)) {
-1 -> return
16 -> { }
else -> throw IOException("Unexpected traffic stat length $read")
class TrafficMonitor(statFile: File?) {
interface DisposableTasker {
fun shutdown(scope: CoroutineScope)
}
val thread: DisposableTasker = if (statFile != null) {
object : LocalSocketListener("TrafficMonitor-" + statFile.name, statFile),
DisposableTasker {
private val buffer = ByteArray(16)
private val stat = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN)
override fun acceptInternal(socket: LocalSocket) {
when (val read = socket.inputStream.read(buffer)) {
-1 -> return
16 -> {
}
else -> throw IOException("Unexpected traffic stat length $read")
}
val tx = stat.getLong(0)
val rx = stat.getLong(8)
if (current.txTotal != tx) {
current.txTotal = tx
dirty = true
}
if (current.rxTotal != rx) {
current.rxTotal = rx
dirty = true
}
}
val tx = stat.getLong(0)
val rx = stat.getLong(8)
if (current.txTotal != tx) {
current.txTotal = tx
dirty = true
}.apply { start() }
} else {
object : Thread("TrafficMonitor-NativePing"), DisposableTasker {
@Volatile
private var running = true
override fun run() {
Timber.d("TrafficMonitor-NativePing prepare test ${DataStore.pingAccHost}:${DataStore.pingAccPort}")
while (running) {
val resultCode = AccVpnService.instance.testLatency(
DataStore.pingProxyServer,
DataStore.pingProxyPort,
DataStore.pingAccHost,
DataStore.pingIntervalSec,
DataStore.pingTimeoutMillis,
object : TestLatencyCallback {
override fun onLatency(latency: Int) {
Timber.d("TrafficMonitor-NativeTcpPing test ${current.latency}")
current.latency = latency
dirty = true
}
}
)
if (resultCode != 0) {
Timber.d("TrafficMonitor-NativeTcpPing exited with $resultCode")
}
sleep(DataStore.pingIntervalSec.toLong())
}
}
if (current.rxTotal != rx) {
current.rxTotal = rx
dirty = true
override fun shutdown(scope: CoroutineScope) {
running = false
}
}
}.apply { start() }
}.apply { start() }
}
val current = TrafficStats()
var out = TrafficStats()
......
......@@ -23,6 +23,7 @@ package com.github.shadowsocks.bg
import android.app.Service
import android.content.Intent
import android.content.pm.PackageManager
import android.net.LocalServerSocket
import android.net.LocalSocket
import android.net.LocalSocketAddress
import android.net.Network
......@@ -31,25 +32,27 @@ import android.os.ParcelFileDescriptor
import android.system.ErrnoException
import android.system.Os
import android.system.OsConstants
import com.ccnet.acc.AccVpn
import com.ccnet.acc.AccVpnService
import com.ccnet.acc.NetworkAdapter
import com.ccnet.acc.native.VpnNativeDispatcher
import com.github.shadowsocks.Core
import com.github.shadowsocks.VpnRequestActivity
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.core.R
import com.github.shadowsocks.net.ConcurrentLocalSocketListener
import com.github.shadowsocks.net.DefaultNetworkListener
import com.github.shadowsocks.net.DnsResolverCompat
import com.github.shadowsocks.net.Subnet
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.int
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import timber.log.Timber
import java.io.File
import java.io.FileDescriptor
import java.io.IOException
import java.net.URL
import java.nio.ByteBuffer
import kotlin.concurrent.thread
import android.net.VpnService as BaseVpnService
class VpnService : BaseVpnService(), BaseService.Interface {
......@@ -59,18 +62,22 @@ class VpnService : BaseVpnService(), BaseService.Interface {
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"
private const val PUBLIC_DNS_SERVER = "1.1.1.1"
private fun <T> FileDescriptor.use(block: (FileDescriptor) -> T) = try {
block(this)
} finally {
try {
Os.close(this)
} catch (_: ErrnoException) { }
} catch (_: ErrnoException) {
}
}
}
private inner class ProtectWorker : ConcurrentLocalSocketListener("ShadowsocksVpnThread",
File(Core.deviceStorage.noBackupFilesDir, "protect_path")) {
private inner class ProtectWorker : ConcurrentLocalSocketListener(
"ShadowsocksVpnThread",
File(Core.deviceStorage.noBackupFilesDir, "protect_path")
) {
override fun acceptInternal(socket: LocalSocket) {
if (socket.inputStream.read() == -1) return
val success = socket.ancillaryFileDescriptors!!.single()!!.use { fd ->
......@@ -91,7 +98,8 @@ class VpnService : BaseVpnService(), BaseService.Interface {
}
try {
socket.outputStream.write(if (success) 0 else 1)
} catch (_: IOException) { } // ignore connection early close
} catch (_: IOException) {
} // ignore connection early close
}
}
......@@ -102,21 +110,28 @@ class VpnService : BaseVpnService(), BaseService.Interface {
override val data = BaseService.Data(this)
override val tag: String get() = "ShadowsocksVpnService"
override fun createNotification(profileName: String): ServiceNotification =
ServiceNotification(this, profileName, "service-vpn")
ServiceNotification(this, profileName, "service-vpn")
private var conn: ParcelFileDescriptor? = null
private var worker: ProtectWorker? = null
private var protectJob: Job? = null
private var active = false
private var metered = false
@Volatile
private var underlyingNetwork: Network? = null
set(value) {
field = value
if (active) 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) }
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
)
}
override fun onBind(intent: Intent) = when (intent.action) {
SERVICE_INTERFACE -> super<BaseVpnService>.onBind(intent)
......@@ -127,10 +142,13 @@ class VpnService : BaseVpnService(), BaseService.Interface {
override fun killProcesses(scope: CoroutineScope) {
super.killProcesses(scope)
AccVpnService.instance.stopAcc()
active = false
scope.launch { DefaultNetworkListener.stop(this) }
worker?.shutdown(scope)
worker = null
protectJob?.cancel()
protectJob = null
conn?.close()
conn = null
}
......@@ -138,7 +156,12 @@ class VpnService : BaseVpnService(), BaseService.Interface {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (DataStore.serviceMode == Key.modeVpn) {
if (prepare(this) != null) {
startActivity(Intent(this, VpnRequestActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
startActivity(
Intent(
this,
VpnRequestActivity::class.java
).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
} else {
return super<BaseService.Interface>.onStartCommand(intent, flags, startId)
}
......@@ -149,60 +172,64 @@ class VpnService : BaseVpnService(), BaseService.Interface {
override suspend fun preInit() = DefaultNetworkListener.start(this) { underlyingNetwork = it }
override suspend fun rawResolver(query: ByteArray) =
// no need to listen for network here as this is only used for forwarding local DNS queries.
// retries should be attempted by client.
DnsResolverCompat.resolveRaw(underlyingNetwork ?: throw IOException("no network"), query)
// no need to listen for network here as this is only used for forwarding local DNS queries.
// retries should be attempted by client.
DnsResolverCompat.resolveRaw(underlyingNetwork ?: throw IOException("no network"), query)
override suspend fun openConnection(url: URL) = DefaultNetworkListener.get().openConnection(url)
override suspend fun startProcesses() {
worker = ProtectWorker().apply { start() }
super.startProcesses()
sendFd(startVpn())
// worker = ProtectWorker().apply { start() }
// super.startProcesses()
startVpn()
// sendFd(startVpn())
}
override val isVpnService get() = true
private suspend fun startVpn(): FileDescriptor {
private fun startVpn(): FileDescriptor {
val profile = data.proxy!!.profile
val builder = Builder()
.setConfigureIntent(Core.configureIntent(this))
.setSession(profile.formattedName)
.setMtu(VPN_MTU)
.addAddress(PRIVATE_VLAN4_CLIENT, 30)
.addDnsServer(PRIVATE_VLAN4_ROUTER)
.setConfigureIntent(Core.configureIntent(this))
.setSession(profile.formattedName)
.setMtu(VPN_MTU)
.addAddress(PRIVATE_VLAN4_CLIENT, 30)
.addDnsServer(PUBLIC_DNS_SERVER)
if (profile.ipv6) builder.addAddress(PRIVATE_VLAN6_CLIENT, 126)
if (profile.proxyApps) {
val me = packageName
profile.individual.split('\n')
.filter { it != me }
.forEach {
try {
if (profile.bypass) builder.addDisallowedApplication(it)
else builder.addAllowedApplication(it)
} catch (ex: PackageManager.NameNotFoundException) {
Timber.w(ex)
}
.filter { it != me }
.forEach {
try {
if (profile.bypass) builder.addDisallowedApplication(it)
else builder.addAllowedApplication(it)
} catch (ex: PackageManager.NameNotFoundException) {
Timber.w(ex)
}
}
// if (!profile.bypass) builder.addAllowedApplication(me)
}
when (profile.route) {
Acl.ALL, Acl.BYPASS_CHN, Acl.CUSTOM_RULES -> {
builder.addRoute("0.0.0.0", 0)
if (profile.ipv6) builder.addRoute("::", 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)
// https://issuetracker.google.com/issues/149636790
if (profile.ipv6) builder.addRoute("2000::", 3)
}
}
// when (profile.route) {
// Acl.ALL, Acl.BYPASS_CHN, Acl.CUSTOM_RULES -> {
// builder.addRoute("0.0.0.0", 0)
// if (profile.ipv6) builder.addRoute("::", 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)
// // https://issuetracker.google.com/issues/149636790
// if (profile.ipv6) builder.addRoute("2000::", 3)
// }
// }
builder.addRoute("0.0.0.0", 0)
if (profile.ipv6) builder.addRoute("::", 0)
metered = profile.metered
active = true // possible race condition here?
......@@ -211,46 +238,93 @@ class VpnService : BaseVpnService(), BaseService.Interface {
val conn = builder.establish() ?: throw NullConnectionException()
this.conn = conn
data.tunFd = conn.fd
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")
if (profile.ipv6) {
cmd += "--netif-ip6addr"
cmd += PRIVATE_VLAN6_ROUTER
}
cmd += "--enable-udprelay"
data.processes!!.start(cmdSupplier = { cmd }, onRestartCallback = {
try {
sendFd(conn.fileDescriptor)
} catch (e: ErrnoException) {
stopRunner(false, e.message)
val protectPath =
File.createTempFile("leaf_vpn_socket_protect", ".sock", cacheDir).absolutePath
protectJob = GlobalScope.launch(Dispatchers.IO) {
val localSocket = LocalSocket()
localSocket.bind(
LocalSocketAddress(
protectPath,
LocalSocketAddress.Namespace.FILESYSTEM
)
)
val socket = LocalServerSocket(localSocket.fileDescriptor)
val buffer = ByteBuffer.allocate(4)
while (true) {
val stream = socket.accept()
buffer.clear()
val n = stream.inputStream.read(buffer.array())
if (n == 4) {
val fd = buffer.int
if (!super.protect(fd)) {
println("protect failed")
}
buffer.clear()
buffer.putInt(0)
} else {
buffer.clear()
buffer.putInt(1)
}
stream.outputStream.write(buffer.array())
}
})
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
AccVpn.writeConfig(this, conn.fd, profile)
thread(start = true) {
AccVpnService.instance.runAcc(
AccVpn.configFile(this).absolutePath,
protectPath,
NetworkAdapter.JNI_CLAZZ_NAME,
VpnNativeDispatcher.JNI_CLAZZ_NAME,
)
}
// 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")
// if (profile.ipv6) {
// cmd += "--netif-ip6addr"
// cmd += PRIVATE_VLAN6_ROUTER
// }
// cmd += "--enable-udprelay"
// data.processes!!.start(cmdSupplier = { cmd }, onRestartCallback = {
// try {
// sendFd(conn.fileDescriptor)
// } catch (e: ErrnoException) {
// stopRunner(false, e.message)
// }
// })
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
// }
// }
override fun onDestroy() {
super.onDestroy()
data.binder.close()
......
......@@ -6,6 +6,9 @@ object HttpConfig {
//val baseUrl = "http://10.16.1.98:9002"
// val baseUrl = "https://test-cc-tt-api.orangenet.org.cn" //测试
val baseUrl = "https://cc-tt-front.srccwl.com" //生产
// val baseUrl = "http://192.168.137.25:9002"
// val baseUrl = "https://test-cc-tt-api.orangenet.org.cn" //测试
// val baseUrl = "https://cc-tt-front.srccwl.com" //生产
/**
......
......@@ -92,7 +92,8 @@ object DefaultNetworkListener {
// NB: this runs in ConnectivityThread, and this behavior cannot be changed until API 26
private object Callback : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) = runBlocking { networkActor.send(NetworkMessage.Put(network)) }
override fun onAvailable(network: Network) = runBlocking {
networkActor.send(NetworkMessage.Put(network)) }
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
// it's a good idea to refresh capabilities
runBlocking { networkActor.send(NetworkMessage.Update(network)) }
......
......@@ -110,4 +110,25 @@ object DataStore : OnPreferenceDataStoreChangeListener {
var outboundBindInterface: String
get() = privateStore.getString(Key.outboundBindInterface) ?: ""
set(value) = privateStore.putString(Key.outboundBindInterface, value)
var pingProxyServer: String
get() = privateStore.getString(Key.pingProxyServer) ?: ""
set(value) = privateStore.putString(Key.pingProxyServer, value)
var pingProxyPort: Int
get() = privateStore.getInt(Key.pingProxyPort) ?: 0
set(value) = privateStore.putInt(Key.pingProxyPort, value)
var pingIntervalSec: Int
get() = privateStore.getInt(Key.pingIntervalSec) ?: 3
set(value) = privateStore.putInt(Key.pingIntervalSec, value)
var pingTimeoutMillis: Int
get() = privateStore.getInt(Key.pingTimeoutMillis) ?: 200
set(value) = privateStore.putInt(Key.pingTimeoutMillis, value)
var pingAccHost: String
get() = privateStore.getString(Key.pingAccHost) ?: ""
set(value) = privateStore.putString(Key.pingAccHost, value)
var pingAccPort: Int
get() = privateStore.getInt(Key.pingAccPort) ?: 80
set(value) = privateStore.putInt(Key.pingAccPort, value)
var pingNetReachableHost: String
get() = privateStore.getString(Key.pingNetReachableHost) ?: "baidu.com"
set(value) = privateStore.putString(Key.pingNetReachableHost, value)
}
......@@ -71,6 +71,14 @@ object Key {
const val assetUpdateTime = "assetUpdateTime"
const val pingProxyServer = "pingProxyServer"
const val pingProxyPort = "pingProxyPort"
const val pingIntervalSec = "pingIntervalSec"
const val pingTimeoutMillis = "pingTimeoutMillis"
const val pingAccHost = "pingAccHost"
const val pingAccPort = "pingAccPort"
const val pingNetReachableHost = "pingNetReachableHost"
// TV specific values
const val controlStats = "control.stats"
const val controlImport = "control.import"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment