Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
com.ccwangluo.accelerator
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
sheteng
com.ccwangluo.accelerator
Commits
2e289597
Commit
2e289597
authored
Jan 24, 2019
by
Mygod
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor using coroutines
parent
c0ad5841
Changes
14
Show whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
240 additions
and
270 deletions
+240
-270
build.gradle
build.gradle
+1
-0
core/build.gradle
core/build.gradle
+3
-1
core/src/main/java/com/github/shadowsocks/Core.kt
core/src/main/java/com/github/shadowsocks/Core.kt
+3
-1
core/src/main/java/com/github/shadowsocks/bg/BaseService.kt
core/src/main/java/com/github/shadowsocks/bg/BaseService.kt
+53
-49
core/src/main/java/com/github/shadowsocks/bg/GuardedProcessPool.kt
...main/java/com/github/shadowsocks/bg/GuardedProcessPool.kt
+70
-101
core/src/main/java/com/github/shadowsocks/bg/LocalDnsService.kt
...rc/main/java/com/github/shadowsocks/bg/LocalDnsService.kt
+3
-3
core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt
.../src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt
+16
-21
core/src/main/java/com/github/shadowsocks/bg/TransproxyService.kt
.../main/java/com/github/shadowsocks/bg/TransproxyService.kt
+6
-6
core/src/main/java/com/github/shadowsocks/bg/VpnService.kt
core/src/main/java/com/github/shadowsocks/bg/VpnService.kt
+8
-8
core/src/main/java/com/github/shadowsocks/utils/HttpsTest.kt
core/src/main/java/com/github/shadowsocks/utils/HttpsTest.kt
+33
-27
core/src/main/java/com/github/shadowsocks/utils/TcpFastOpen.kt
...src/main/java/com/github/shadowsocks/utils/TcpFastOpen.kt
+3
-1
core/src/main/java/com/github/shadowsocks/utils/Utils.kt
core/src/main/java/com/github/shadowsocks/utils/Utils.kt
+0
-11
mobile/src/main/java/com/github/shadowsocks/AppManager.kt
mobile/src/main/java/com/github/shadowsocks/AppManager.kt
+38
-40
mobile/src/main/res/layout/layout_apps.xml
mobile/src/main/res/layout/layout_apps.xml
+3
-1
No files found.
build.gradle
View file @
2e289597
...
...
@@ -5,6 +5,7 @@ apply plugin: 'com.github.ben-manes.versions'
buildscript
{
ext
{
kotlinVersion
=
'1.3.11'
kotlinCoroutinesVersion
=
'1.1.0'
minSdkVersion
=
21
sdkVersion
=
28
compileSdkVersion
=
28
...
...
core/build.gradle
View file @
2e289597
...
...
@@ -62,13 +62,15 @@ dependencies {
api
"android.arch.work:work-runtime-ktx:$workVersion"
api
"androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
api
"androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
api
"androidx.preference:preference:1.0.0"
api
'androidx.preference:preference:1.0.0'
api
"androidx.room:room-runtime:$roomVersion"
api
'com.crashlytics.sdk.android:crashlytics:2.9.8'
api
'com.google.firebase:firebase-config:16.1.3'
api
'com.google.firebase:firebase-core:16.0.6'
api
'com.squareup.okhttp3:okhttp:3.12.1'
api
"com.takisoft.preferencex:preferencex:$preferencexVersion"
api
"org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutinesVersion"
api
"org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesVersion"
kapt
"androidx.lifecycle:lifecycle-compiler:$lifecycleVersion"
kapt
"androidx.room:room-compiler:$roomVersion"
testImplementation
"androidx.arch.core:core-testing:$lifecycleVersion"
...
...
core/src/main/java/com/github/shadowsocks/Core.kt
View file @
2e289597
...
...
@@ -51,6 +51,8 @@ import com.github.shadowsocks.utils.*
import
com.google.firebase.FirebaseApp
import
com.google.firebase.analytics.FirebaseAnalytics
import
io.fabric.sdk.android.Fabric
import
kotlinx.coroutines.CoroutineScope
import
kotlinx.coroutines.Dispatchers
import
java.io.File
import
java.io.IOException
import
kotlin.reflect.KClass
...
...
@@ -106,7 +108,7 @@ object Core {
// handle data restored/crash
if
(
Build
.
VERSION
.
SDK_INT
>=
24
&&
DataStore
.
directBootAware
&&
app
.
getSystemService
<
UserManager
>()
?.
isUserUnlocked
==
true
)
DirectBoot
.
flushTrafficStats
()
if
(
DataStore
.
tcpFastOpen
&&
!
TcpFastOpen
.
sendEnabled
)
TcpFastOpen
.
enable
Async
()
if
(
DataStore
.
tcpFastOpen
&&
!
TcpFastOpen
.
sendEnabled
)
TcpFastOpen
.
enable
Timeout
()
if
(
DataStore
.
publicStore
.
getLong
(
Key
.
assetUpdateTime
,
-
1
)
!=
packageInfo
.
lastUpdateTime
)
{
val
assetManager
=
app
.
assets
for
(
dir
in
arrayOf
(
"acl"
,
"overture"
))
...
...
core/src/main/java/com/github/shadowsocks/bg/BaseService.kt
View file @
2e289597
...
...
@@ -39,8 +39,8 @@ import com.github.shadowsocks.plugin.PluginManager
import
com.github.shadowsocks.utils.Action
import
com.github.shadowsocks.utils.broadcastReceiver
import
com.github.shadowsocks.utils.printLog
import
com.github.shadowsocks.utils.thread
import
com.google.firebase.analytics.FirebaseAnalytics
import
kotlinx.coroutines.*
import
java.io.File
import
java.net.UnknownHostException
import
java.util.*
...
...
@@ -62,16 +62,16 @@ object BaseService {
const
val
CONFIG_FILE_UDP
=
"shadowsocks-udp.conf"
class
Data
internal
constructor
(
private
val
service
:
Interface
)
{
@Volatile
var
state
=
STOPPED
va
l
processes
=
GuardedProcessPool
()
@Volatile
var
proxy
:
ProxyInstance
?
=
null
@Volatile
var
udpFallback
:
ProxyInstance
?
=
null
var
state
=
STOPPED
va
r
processes
:
GuardedProcessPool
?
=
null
var
proxy
:
ProxyInstance
?
=
null
var
udpFallback
:
ProxyInstance
?
=
null
var
notification
:
ServiceNotification
?
=
null
val
closeReceiver
=
broadcastReceiver
{
_
,
intent
->
when
(
intent
.
action
)
{
Action
.
RELOAD
->
service
.
forceLoad
()
else
->
service
.
stopRunner
(
true
)
else
->
service
.
stopRunner
()
}
}
var
closeReceiverRegistered
=
false
...
...
@@ -101,12 +101,13 @@ object BaseService {
}
private
fun
broadcast
(
work
:
(
IShadowsocksServiceCallback
)
->
Unit
)
{
val
n
=
callbacks
.
beginBroadcast
()
for
(
i
in
0
until
n
)
try
{
work
(
callbacks
.
getBroadcastItem
(
i
))
repeat
(
callbacks
.
beginBroadcast
())
{
try
{
work
(
callbacks
.
getBroadcastItem
(
it
))
}
catch
(
e
:
Exception
)
{
printLog
(
e
)
}
}
callbacks
.
finishBroadcast
()
}
...
...
@@ -193,26 +194,23 @@ object BaseService {
fun
forceLoad
()
{
val
(
profile
,
fallback
)
=
Core
.
currentProfile
?:
return
stopRunner
(
tru
e
,
(
this
as
Context
).
getString
(
R
.
string
.
profile_empty
))
?:
return
stopRunner
(
fals
e
,
(
this
as
Context
).
getString
(
R
.
string
.
profile_empty
))
if
(
profile
.
host
.
isEmpty
()
||
profile
.
password
.
isEmpty
()
||
fallback
!=
null
&&
(
fallback
.
host
.
isEmpty
()
||
fallback
.
password
.
isEmpty
()))
{
stopRunner
(
tru
e
,
(
this
as
Context
).
getString
(
R
.
string
.
proxy_empty
))
stopRunner
(
fals
e
,
(
this
as
Context
).
getString
(
R
.
string
.
proxy_empty
))
return
}
val
s
=
data
.
state
when
(
s
)
{
STOPPED
->
startRunner
()
CONNECTED
->
{
stopRunner
(
false
)
startRunner
()
}
CONNECTED
->
stopRunner
(
true
)
else
->
Crashlytics
.
log
(
Log
.
WARN
,
tag
,
"Illegal state when invoking use: $s"
)
}
}
fun
buildAdditionalArguments
(
cmd
:
ArrayList
<
String
>):
ArrayList
<
String
>
=
cmd
fun
startNative
Processes
()
{
suspend
fun
start
Processes
()
{
val
configRoot
=
(
if
(
Build
.
VERSION
.
SDK_INT
<
24
||
app
.
getSystemService
<
UserManager
>()
?.
isUserUnlocked
!=
false
)
app
else
Core
.
deviceStorage
).
noBackupFilesDir
val
udpFallback
=
data
.
udpFallback
...
...
@@ -233,19 +231,24 @@ object BaseService {
else
startService
(
Intent
(
this
,
javaClass
))
}
fun
killProcesses
()
=
data
.
processes
.
killAll
()
suspend
fun
killProcesses
()
{
data
.
processes
?.
run
{
close
()
data
.
processes
=
null
}
}
fun
stopRunner
(
stopService
:
Boolean
,
msg
:
String
?
=
null
)
{
fun
stopRunner
(
restart
:
Boolean
=
false
,
msg
:
String
?
=
null
)
{
// channge the state
val
data
=
data
data
.
changeState
(
STOPPING
)
GlobalScope
.
launch
(
Dispatchers
.
Main
,
CoroutineStart
.
UNDISPATCHED
)
{
Core
.
analytics
.
logEvent
(
"stop"
,
bundleOf
(
Pair
(
FirebaseAnalytics
.
Param
.
METHOD
,
tag
)))
killProcesses
()
// clean up recevier
this
as
Service
this
@Interface
as
Service
val
data
=
data
if
(
data
.
closeReceiverRegistered
)
{
unregisterReceiver
(
data
.
closeReceiver
)
data
.
closeReceiverRegistered
=
false
...
...
@@ -265,7 +268,8 @@ object BaseService {
data
.
changeState
(
STOPPED
,
msg
)
// stop the service if nothing has bound to it
if
(
stopService
)
stopSelf
()
if
(
restart
)
startRunner
()
else
stopSelf
()
}
}
fun
onStartCommand
(
intent
:
Intent
?,
flags
:
Int
,
startId
:
Int
):
Int
{
...
...
@@ -276,7 +280,7 @@ object BaseService {
if
(
profilePair
==
null
)
{
// gracefully shutdown: https://stackoverflow.com/q/47337857/2245107
data
.
notification
=
createNotification
(
""
)
stopRunner
(
tru
e
,
getString
(
R
.
string
.
profile_empty
))
stopRunner
(
fals
e
,
getString
(
R
.
string
.
profile_empty
))
return
Service
.
START_NOT_STICKY
}
val
(
profile
,
fallback
)
=
profilePair
...
...
@@ -299,14 +303,14 @@ object BaseService {
data
.
changeState
(
CONNECTING
)
thread
(
"$tag-Connecting"
)
{
GlobalScope
.
launch
(
Dispatchers
.
Main
)
{
try
{
proxy
.
init
()
data
.
udpFallback
?.
init
()
// Clean up
killProcesses
()
startNativeProcesses
()
killProcesses
()
data
.
processes
=
GuardedProcessPool
()
startProcesses
()
proxy
.
scheduleUpdate
()
data
.
udpFallback
?.
scheduleUpdate
()
...
...
@@ -314,12 +318,12 @@ object BaseService {
data
.
changeState
(
CONNECTED
)
}
catch
(
_
:
UnknownHostException
)
{
stopRunner
(
tru
e
,
getString
(
R
.
string
.
invalid_server
))
stopRunner
(
fals
e
,
getString
(
R
.
string
.
invalid_server
))
}
catch
(
exc
:
Throwable
)
{
if
(
exc
!
is
PluginManager
.
PluginNotFoundException
&&
exc
!
is
VpnService
.
NullConnectionException
)
{
printLog
(
exc
)
}
stopRunner
(
tru
e
,
"${getString(R.string.service_failed)}: ${exc.localizedMessage}"
)
stopRunner
(
fals
e
,
"${getString(R.string.service_failed)}: ${exc.localizedMessage}"
)
}
}
return
Service
.
START_NOT_STICKY
...
...
core/src/main/java/com/github/shadowsocks/bg/GuardedProcessPool.kt
View file @
2e289597
...
...
@@ -26,76 +26,69 @@ import android.system.ErrnoException
import
android.system.Os
import
android.system.OsConstants
import
android.util.Log
import
androidx.annotation.MainThread
import
com.crashlytics.android.Crashlytics
import
com.github.shadowsocks.Core
import
com.github.shadowsocks.utils.Commandline
import
com.github.shadowsocks.utils.thread
import
kotlinx.coroutines.*
import
kotlinx.coroutines.channels.Channel
import
kotlinx.coroutines.selects.select
import
java.io.File
import
java.io.IOException
import
java.io.InputStream
import
java.util.concurrent.ArrayBlockingQueue
import
java.util.concurrent.atomic.AtomicReference
import
java.util.concurrent.TimeUnit
import
kotlin.concurrent.thread
class
GuardedProcessPool
{
companion
object
Dummy
:
IOException
(
"Oopsie the developer has made a no-no"
)
{
class
GuardedProcessPool
:
CoroutineScope
{
companion
object
{
private
const
val
TAG
=
"GuardedProcessPool"
private
val
ProcessImpl
by
lazy
{
Class
.
forName
(
"java.lang.ProcessManager\$ProcessImpl"
)
}
private
val
pid
by
lazy
{
ProcessImpl
.
getDeclaredField
(
"pid"
).
apply
{
isAccessible
=
true
}
}
private
val
exitValueMutex
by
lazy
{
ProcessImpl
.
getDeclaredField
(
"exitValueMutex"
).
apply
{
isAccessible
=
true
}
private
val
pid
by
lazy
{
Class
.
forName
(
"java.lang.ProcessManager\$ProcessImpl"
).
getDeclaredField
(
"pid"
).
apply
{
isAccessible
=
true
}
}
}
private
inner
class
Guard
(
private
val
cmd
:
List
<
String
>,
private
val
onRestartCallback
:
(()
->
Unit
)?)
{
val
cmdName
=
File
(
cmd
.
first
()).
nameWithoutExtension
val
excQueue
=
ArrayBlockingQueue
<
IOException
>(
1
)
// ArrayBlockingQueue doesn't want null
private
var
pushed
=
false
private
inner
class
Guard
(
private
val
cmd
:
List
<
String
>)
{
val
abortChannel
=
Channel
<
Unit
>()
private
val
exitChannel
=
Channel
<
Int
>()
private
val
cmdName
=
File
(
cmd
.
first
()).
nameWithoutExtension
private
var
startTime
:
Long
=
-
1
private
lateinit
var
process
:
Process
private
fun
streamLogger
(
input
:
InputStream
,
logger
:
(
String
,
String
)
->
Int
)
=
thread
(
"StreamLogger-$cmdName"
)
{
try
{
private
fun
streamLogger
(
input
:
InputStream
,
logger
:
(
String
,
String
)
->
Int
)
=
try
{
input
.
bufferedReader
().
forEachLine
{
logger
(
TAG
,
it
)
}
}
catch
(
_
:
IOException
)
{
}
// ignore
}
private
fun
pushException
(
ioException
:
IOException
?)
{
if
(
pushed
)
return
excQueue
.
put
(
ioException
?:
Dummy
)
pushed
=
true
fun
start
()
{
startTime
=
SystemClock
.
elapsedRealtime
()
process
=
ProcessBuilder
(
cmd
).
directory
(
Core
.
deviceStorage
.
noBackupFilesDir
).
start
()
}
fun
looper
(
host
:
HashSet
<
Thread
>
)
{
var
process
:
Process
?
=
null
suspend
fun
looper
(
onRestartCallback
:
(()
->
Unit
)?
)
{
var
running
=
true
try
{
var
callback
:
(()
->
Unit
)?
=
null
while
(
guardThreads
.
get
()
===
host
)
{
Crashlytics
.
log
(
Log
.
DEBUG
,
TAG
,
"start process: "
+
Commandline
.
toString
(
cmd
))
val
startTime
=
SystemClock
.
elapsedRealtime
()
process
=
ProcessBuilder
(
cmd
)
.
redirectErrorStream
(
true
)
.
directory
(
Core
.
deviceStorage
.
noBackupFilesDir
)
.
start
()
while
(
true
)
{
thread
(
name
=
"stderr-$cmdName"
)
{
streamLogger
(
process
.
errorStream
,
Log
::
e
)
}
thread
(
name
=
"stdout-$cmdName"
)
{
runBlocking
{
streamLogger
(
process
.
inputStream
,
Log
::
i
)
streamLogger
(
process
.
errorStream
,
Log
::
e
)
if
(
callback
==
null
)
callback
=
onRestartCallback
else
callback
()
pushException
(
null
)
process
.
waitFor
()
exitChannel
.
send
(
process
.
waitFor
())
// this thread also acts as a daemon thread for waitFor
}
}
if
(
select
{
abortChannel
.
onReceive
{
true
}
// prefer abort to save work
exitChannel
.
onReceive
{
false
}
})
break
running
=
false
if
(
SystemClock
.
elapsedRealtime
()
-
startTime
<
1000
)
{
Crashlytics
.
log
(
Log
.
WARN
,
TAG
,
"process exit too fast, stop guard: $cmdName"
)
break
}
Crashlytics
.
log
(
Log
.
DEBUG
,
TAG
,
"restart process: "
+
Commandline
.
toString
(
cmd
))
start
()
running
=
true
onRestartCallback
?.
invoke
()
}
}
catch
(
_
:
InterruptedException
)
{
Crashlytics
.
log
(
Log
.
DEBUG
,
TAG
,
"thread interrupt, destroy process: $cmdName"
)
}
catch
(
e
:
IOException
)
{
pushException
(
e
)
}
finally
{
if
(
process
!=
null
)
{
if
(
!
running
)
return
// process already exited, nothing to be done
if
(
Build
.
VERSION
.
SDK_INT
<
24
)
{
val
pid
=
pid
.
get
(
process
)
as
Int
try
{
...
...
@@ -103,58 +96,34 @@ class GuardedProcessPool {
}
catch
(
e
:
ErrnoException
)
{
if
(
e
.
errno
!=
OsConstants
.
ESRCH
)
throw
e
}
val
mutex
=
exitValueMutex
.
get
(
process
)
as
Object
synchronized
(
mutex
)
{
try
{
process
.
exitValue
()
}
catch
(
e
:
IllegalThreadStateException
)
{
mutex
.
wait
(
500
)
if
(
withTimeoutOrNull
(
500
)
{
exitChannel
.
receive
()
}
!=
null
)
return
}
}
}
process
.
destroy
()
// kill the process
if
(
Build
.
VERSION
.
SDK_INT
>=
26
)
{
val
isKilled
=
process
.
waitFor
(
1L
,
TimeUnit
.
SECONDS
)
// wait for 1 second
if
(!
isKilled
)
{
if
(
withTimeoutOrNull
(
1000
)
{
exitChannel
.
receive
()
}
!=
null
)
return
process
.
destroyForcibly
()
// Force to kill the process if it's still alive
}
}
process
.
waitFor
()
// ensure the process is destroyed
}
pushException
(
null
)
exitChannel
.
receive
()
}
}
}
/**
* This is an indication of which thread pool is being active.
* Reading/writing this collection still needs an additional lock to prevent concurrent modification.
*/
private
val
guardThreads
=
AtomicReference
<
HashSet
<
Thread
>>(
HashSet
())
private
val
supervisor
=
SupervisorJob
()
override
val
coroutineContext
get
()
=
Dispatchers
.
Main
+
super
visor
private
val
guards
=
ArrayList
<
Guard
>()
fun
start
(
cmd
:
List
<
String
>,
onRestartCallback
:
(()
->
Unit
)?
=
null
):
GuardedProcessPool
{
val
guard
=
Guard
(
cmd
,
onRestartCallback
)
val
guardThreads
=
guardThreads
.
get
()
synchronized
(
guardThreads
)
{
guardThreads
.
add
(
thread
(
"GuardThread-${guard.cmdName}"
)
{
guard
.
looper
(
guardThreads
)
})
}
val
ioException
=
guard
.
excQueue
.
take
()
if
(
ioException
!==
Dummy
)
throw
ioException
return
this
@MainThread
suspend
fun
start
(
cmd
:
List
<
String
>,
onRestartCallback
:
(()
->
Unit
)?
=
null
)
{
Crashlytics
.
log
(
Log
.
DEBUG
,
TAG
,
"start process: "
+
Commandline
.
toString
(
cmd
))
val
guard
=
Guard
(
cmd
)
guard
.
start
()
guards
+=
guard
launch
(
start
=
CoroutineStart
.
UNDISPATCHED
)
{
guard
.
looper
(
onRestartCallback
)
}
}
fun
killAll
()
{
val
guardThreads
=
guardThreads
.
getAndSet
(
HashSet
())
synchronized
(
guardThreads
)
{
guardThreads
.
forEach
{
it
.
interrupt
()
}
try
{
guardThreads
.
forEach
{
it
.
join
()
}
}
catch
(
_
:
InterruptedException
)
{
}
}
@MainThread
suspend
fun
close
()
{
guards
.
forEach
{
it
.
abortChannel
.
send
(
Unit
)
}
super
visor
.
children
.
forEach
{
it
.
join
()
}
}
}
core/src/main/java/com/github/shadowsocks/bg/LocalDnsService.kt
View file @
2e289597
...
...
@@ -32,8 +32,8 @@ import org.json.JSONObject
object
LocalDnsService
{
interface
Interface
:
BaseService
.
Interface
{
override
fun
startNative
Processes
()
{
super
.
start
Native
Processes
()
override
suspend
fun
start
Processes
()
{
super
.
startProcesses
()
val
data
=
data
val
profile
=
data
.
proxy
!!
.
profile
...
...
@@ -86,7 +86,7 @@ object LocalDnsService {
})
}
if
(!
profile
.
udpdns
)
data
.
processes
.
start
(
buildAdditionalArguments
(
arrayListOf
(
if
(!
profile
.
udpdns
)
data
.
processes
!!
.
start
(
buildAdditionalArguments
(
arrayListOf
(
File
(
app
.
applicationInfo
.
nativeLibraryDir
,
Executable
.
OVERTURE
).
absolutePath
,
"-c"
,
buildOvertureConfig
(
"overture.conf"
))))
}
...
...
core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt
View file @
2e289597
...
...
@@ -30,7 +30,12 @@ import com.github.shadowsocks.database.ProfileManager
import
com.github.shadowsocks.plugin.PluginConfiguration
import
com.github.shadowsocks.plugin.PluginManager
import
com.github.shadowsocks.preference.DataStore
import
com.github.shadowsocks.utils.*
import
com.github.shadowsocks.utils.DirectBoot
import
com.github.shadowsocks.utils.parseNumericAddress
import
com.github.shadowsocks.utils.signaturesCompat
import
kotlinx.coroutines.Dispatchers
import
kotlinx.coroutines.withContext
import
kotlinx.coroutines.withTimeout
import
okhttp3.FormBody
import
okhttp3.OkHttpClient
import
okhttp3.Request
...
...
@@ -39,7 +44,6 @@ import java.io.IOException
import
java.net.InetAddress
import
java.net.UnknownHostException
import
java.security.MessageDigest
import
java.util.concurrent.TimeUnit
/**
* This class sets up environment for ss-local.
...
...
@@ -50,13 +54,9 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
private
val
plugin
=
PluginConfiguration
(
profile
.
plugin
?:
""
).
selectedOptions
val
pluginPath
by
lazy
{
PluginManager
.
init
(
plugin
)
}
fun
init
()
{
suspend
fun
init
()
{
if
(
profile
.
host
==
"198.199.101.152"
)
{
val
client
=
OkHttpClient
.
Builder
()
.
connectTimeout
(
10
,
TimeUnit
.
SECONDS
)
.
writeTimeout
(
10
,
TimeUnit
.
SECONDS
)
.
readTimeout
(
30
,
TimeUnit
.
SECONDS
)
.
build
()
val
client
=
OkHttpClient
.
Builder
().
build
()
val
mdg
=
MessageDigest
.
getInstance
(
"SHA-1"
)
mdg
.
update
(
Core
.
packageInfo
.
signaturesCompat
.
first
().
toByteArray
())
val
requestBody
=
FormBody
.
Builder
()
...
...
@@ -67,8 +67,9 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
.
post
(
requestBody
)
.
build
()
val
proxies
=
client
.
newCall
(
request
).
execute
()
.
body
()
!!
.
string
().
split
(
'|'
).
toMutableList
()
val
proxies
=
withTimeout
(
30_000
)
{
withContext
(
Dispatchers
.
IO
)
{
client
.
newCall
(
request
).
execute
().
body
()
!!
.
string
()
}
}.
split
(
'|'
).
toMutableList
()
proxies
.
shuffle
()
val
proxy
=
proxies
.
first
().
split
(
':'
)
profile
.
host
=
proxy
[
0
].
trim
()
...
...
@@ -80,22 +81,16 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
if
(
route
==
Acl
.
CUSTOM_RULES
)
Acl
.
save
(
Acl
.
CUSTOM_RULES
,
Acl
.
customRules
.
flatten
(
10
))
// it's hard to resolve DNS on a specific interface so we'll do it here
if
(
profile
.
host
.
parseNumericAddress
()
==
null
)
{
thread
(
"ProxyInstance-resolve"
)
{
// A WAR fix for Huawei devices that UnknownHostException cannot be caught correctly
try
{
profile
.
host
=
InetAddress
.
getByName
(
profile
.
host
).
hostAddress
?:
""
}
catch
(
_
:
UnknownHostException
)
{
}
}.
join
(
10
*
1000
)
if
(
profile
.
host
.
parseNumericAddress
()
==
null
)
throw
UnknownHostException
()
}
if
(
profile
.
host
.
parseNumericAddress
()
==
null
)
profile
.
host
=
withTimeout
(
10_000
)
{
withContext
(
Dispatchers
.
IO
)
{
InetAddress
.
getByName
(
profile
.
host
).
hostAddress
}
}
?:
throw
UnknownHostException
()
}
/**
* Sensitive shadowsocks configuration file requires extra protection. It may be stored in encrypted storage or
* device storage, depending on which is currently available.
*/
fun
start
(
service
:
BaseService
.
Interface
,
stat
:
File
,
configFile
:
File
,
extraFlag
:
String
?
=
null
)
{
suspend
fun
start
(
service
:
BaseService
.
Interface
,
stat
:
File
,
configFile
:
File
,
extraFlag
:
String
?
=
null
)
{
trafficMonitor
=
TrafficMonitor
(
stat
)
this
.
configFile
=
configFile
...
...
@@ -122,7 +117,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
if
(
DataStore
.
tcpFastOpen
)
cmd
+=
"--fast-open"
service
.
data
.
processes
.
start
(
cmd
)
service
.
data
.
processes
!!
.
start
(
cmd
)
}
fun
scheduleUpdate
()
{
...
...
core/src/main/java/com/github/shadowsocks/bg/TransproxyService.kt
View file @
2e289597
...
...
@@ -36,7 +36,7 @@ class TransproxyService : Service(), LocalDnsService.Interface {
override
fun
onStartCommand
(
intent
:
Intent
?,
flags
:
Int
,
startId
:
Int
):
Int
=
super
<
LocalDnsService
.
Interface
>.
onStartCommand
(
intent
,
flags
,
startId
)
private
fun
startDNSTunnel
()
{
private
suspend
fun
startDNSTunnel
()
{
val
proxy
=
data
.
proxy
!!
val
cmd
=
arrayListOf
(
File
(
applicationInfo
.
nativeLibraryDir
,
Executable
.
SS_TUNNEL
).
absolutePath
,
"-t"
,
"10"
,
...
...
@@ -47,10 +47,10 @@ class TransproxyService : Service(), LocalDnsService.Interface {
// config is already built by BaseService.Interface
"-c"
,
(
data
.
udpFallback
?:
proxy
).
configFile
!!
.
absolutePath
)
if
(
DataStore
.
tcpFastOpen
)
cmd
+=
"--fast-open"
data
.
processes
.
start
(
cmd
)
data
.
processes
!!
.
start
(
cmd
)
}
private
fun
startRedsocksDaemon
()
{
private
suspend
fun
startRedsocksDaemon
()
{
File
(
Core
.
deviceStorage
.
noBackupFilesDir
,
"redsocks.conf"
).
writeText
(
"""base {
log_debug = off;
log_info = off;
...
...
@@ -66,13 +66,13 @@ redsocks {
type = socks5;
}
"""
)
data
.
processes
.
start
(
listOf
(
data
.
processes
!!
.
start
(
listOf
(
File
(
applicationInfo
.
nativeLibraryDir
,
Executable
.
REDSOCKS
).
absolutePath
,
"-c"
,
"redsocks.conf"
))
}
override
fun
startNative
Processes
()
{
override
suspend
fun
start
Processes
()
{
startRedsocksDaemon
()
super
.
start
Native
Processes
()
super
.
startProcesses
()
if
(
data
.
proxy
!!
.
profile
.
udpdns
)
startDNSTunnel
()
}
...
...
core/src/main/java/com/github/shadowsocks/bg/VpnService.kt
View file @
2e289597
...
...
@@ -139,9 +139,9 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
else
->
super
<
LocalDnsService
.
Interface
>.
onBind
(
intent
)
}
override
fun
onRevoke
()
=
stopRunner
(
true
)
override
fun
onRevoke
()
=
stopRunner
()
override
fun
killProcesses
()
{
override
suspend
fun
killProcesses
()
{
if
(
listeningForDefaultNetwork
)
{
connectivity
.
unregisterNetworkCallback
(
defaultNetworkCallback
)
listeningForDefaultNetwork
=
false
...
...
@@ -159,16 +159,16 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
startActivity
(
Intent
(
this
,
VpnRequestActivity
::
class
.
java
)
.
addFlags
(
Intent
.
FLAG_ACTIVITY_NEW_TASK
))
else
return
super
<
LocalDnsService
.
Interface
>.
onStartCommand
(
intent
,
flags
,
startId
)
stopRunner
(
true
)
stopRunner
()
return
Service
.
START_NOT_STICKY
}
override
fun
startNative
Processes
()
{
override
suspend
fun
start
Processes
()
{
val
worker
=
ProtectWorker
()
worker
.
start
()
this
.
worker
=
worker
super
.
start
Native
Processes
()
super
.
startProcesses
()
sendFd
(
startVpn
())
}
...
...
@@ -178,7 +178,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
return
cmd
}
private
fun
startVpn
():
FileDescriptor
{
private
suspend
fun
startVpn
():
FileDescriptor
{
val
profile
=
data
.
proxy
!!
.
profile
val
builder
=
Builder
()
.
setConfigureIntent
(
Core
.
configureIntent
(
this
))
...
...
@@ -247,11 +247,11 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
cmd
+=
"--dnsgw"
cmd
+=
"127.0.0.1:${DataStore.portLocalDns}"
}
data
.
processes
.
start
(
cmd
,
onRestartCallback
=
{
data
.
processes
!!
.
start
(
cmd
,
onRestartCallback
=
{
try
{
sendFd
(
conn
.
fileDescriptor
)
}
catch
(
e
:
ErrnoException
)
{
stopRunner
(
tru
e
,
e
.
message
)
stopRunner
(
fals
e
,
e
.
message
)
}
})
return
conn
.
fileDescriptor
...
...
core/src/main/java/com/github/shadowsocks/utils/HttpsTest.kt
View file @
2e289597
...
...
@@ -26,9 +26,9 @@ import androidx.lifecycle.ViewModel
import
com.github.shadowsocks.Core
import
com.github.shadowsocks.Core.app
import
com.github.shadowsocks.acl.Acl
import
com.github.shadowsocks.bg.BaseService
import
com.github.shadowsocks.core.R
import
com.github.shadowsocks.preference.DataStore
import
kotlinx.coroutines.*
import
java.io.IOException
import
java.net.HttpURLConnection
import
java.net.InetSocketAddress
...
...
@@ -72,14 +72,12 @@ class HttpsTest : ViewModel() {
}
}
private
var
testCount
=
0
private
var
running
:
Pair
<
HttpURLConnection
,
Job
>?
=
null
val
status
=
MutableLiveData
<
Status
>().
apply
{
value
=
Status
.
Idle
}
fun
testConnection
()
{
++
testCount
cancelTest
()
status
.
value
=
Status
.
Testing
val
id
=
testCount
// it would change by other code
thread
(
"ConnectionTest"
)
{
val
url
=
URL
(
"https"
,
when
(
Core
.
currentProfile
!!
.
first
.
route
)
{
Acl
.
CHINALIST
->
"www.qualcomm.cn"
else
->
"www.google.com"
...
...
@@ -90,7 +88,9 @@ class HttpsTest : ViewModel() {
conn
.
setRequestProperty
(
"Connection"
,
"close"
)
conn
.
instanceFollowRedirects
=
false
conn
.
useCaches
=
false
val
result
=
try
{
running
=
conn
to
GlobalScope
.
launch
(
Dispatchers
.
Main
,
CoroutineStart
.
UNDISPATCHED
)
{
status
.
value
=
withContext
(
Dispatchers
.
IO
)
{
try
{
val
start
=
SystemClock
.
elapsedRealtime
()
val
code
=
conn
.
responseCode
val
elapsed
=
SystemClock
.
elapsedRealtime
()
-
start
...
...
@@ -101,12 +101,18 @@ class HttpsTest : ViewModel() {
}
finally
{
conn
.
disconnect
()
}
if
(
testCount
==
id
)
status
.
postValue
(
result
)
}
}
}
private
fun
cancelTest
()
=
running
?.
let
{
(
conn
,
job
)
->
job
.
cancel
()
// ensure job is cancelled before interrupting
conn
.
disconnect
()
running
=
null
}
fun
invalidate
()
{
++
testCount
cancelTest
()
status
.
value
=
Status
.
Idle
}
}
core/src/main/java/com/github/shadowsocks/utils/TcpFastOpen.kt
View file @
2e289597
...
...
@@ -20,6 +20,8 @@
package
com.github.shadowsocks.utils
import
kotlinx.coroutines.runBlocking
import
kotlinx.coroutines.withTimeoutOrNull
import
java.io.File
import
java.io.IOException
...
...
@@ -58,5 +60,5 @@ object TcpFastOpen {
e
.
localizedMessage
}
}
fun
enable
Async
()
=
thread
(
"TcpFastOpen"
)
{
enable
()
}.
join
(
1000
)
fun
enable
Timeout
()
=
runBlocking
{
withTimeoutOrNull
(
1000
)
{
enable
()
}
}
}
core/src/main/java/com/github/shadowsocks/utils/Utils.kt
View file @
2e289597
...
...
@@ -54,17 +54,6 @@ fun broadcastReceiver(callback: (Context, Intent) -> Unit): BroadcastReceiver =
override
fun
onReceive
(
context
:
Context
,
intent
:
Intent
)
=
callback
(
context
,
intent
)
}
/**
* Wrapper for kotlin.concurrent.thread that tracks uncaught exceptions.
*/
fun
thread
(
name
:
String
?
=
null
,
start
:
Boolean
=
true
,
isDaemon
:
Boolean
=
false
,
contextClassLoader
:
ClassLoader
?
=
null
,
priority
:
Int
=
-
1
,
block
:
()
->
Unit
):
Thread
{
val
thread
=
kotlin
.
concurrent
.
thread
(
false
,
isDaemon
,
contextClassLoader
,
name
,
priority
,
block
)
thread
.
setUncaughtExceptionHandler
{
_
,
t
->
printLog
(
t
)
}
if
(
start
)
thread
.
start
()
return
thread
}
val
URLConnection
.
responseLength
:
Long
get
()
=
if
(
Build
.
VERSION
.
SDK_INT
>=
24
)
contentLengthLong
else
contentLength
.
toLong
()
...
...
mobile/src/main/java/com/github/shadowsocks/AppManager.kt
View file @
2e289597
...
...
@@ -32,10 +32,10 @@ import android.content.pm.PackageInfo
import
android.content.pm.PackageManager
import
android.graphics.drawable.Drawable
import
android.os.Bundle
import
android.os.Handler
import
android.view.*
import
android.widget.ImageView
import
android.widget.Switch
import
androidx.annotation.UiThread
import
androidx.appcompat.app.AppCompatActivity
import
androidx.appcompat.widget.Toolbar
import
androidx.core.content.getSystemService
...
...
@@ -47,9 +47,8 @@ import com.github.shadowsocks.database.ProfileManager
import
com.github.shadowsocks.preference.DataStore
import
com.github.shadowsocks.utils.DirectBoot
import
com.github.shadowsocks.utils.Key
import
com.github.shadowsocks.utils.thread
import
com.google.android.material.snackbar.Snackbar
import
java.util.concurrent.atomic.AtomicBoolean
import
kotlinx.coroutines.*
class
AppManager
:
AppCompatActivity
()
{
companion
object
{
...
...
@@ -58,13 +57,13 @@ class AppManager : AppCompatActivity() {
private
var
receiver
:
BroadcastReceiver
?
=
null
private
var
cachedApps
:
List
<
PackageInfo
>?
=
null
private
fun
getApps
(
pm
:
PackageManager
)
=
synchronized
(
AppManager
)
{
private
suspend
fun
getApps
(
pm
:
PackageManager
)
=
synchronized
(
AppManager
)
{
if
(
receiver
==
null
)
receiver
=
Core
.
listenForPackageChanges
{
synchronized
(
AppManager
)
{
receiver
=
null
cachedApps
=
null
}
AppManager
.
instance
?.
re
loadApps
()
AppManager
.
instance
?.
loadApps
()
}
// Labels and icons can change on configuration (locale, etc.) changes, therefore they are not cached.
val
cachedApps
=
cachedApps
?:
pm
.
getInstalledPackages
(
PackageManager
.
GET_PERMISSIONS
)
...
...
@@ -72,7 +71,10 @@ class AppManager : AppCompatActivity() {
it
.
requestedPermissions
?.
contains
(
Manifest
.
permission
.
INTERNET
)
?:
false
}
this
.
cachedApps
=
cachedApps
cachedApps
}.
map
{
ProxiedApp
(
pm
,
it
.
applicationInfo
,
it
.
packageName
)
}
}.
map
{
yield
()
ProxiedApp
(
pm
,
it
.
applicationInfo
,
it
.
packageName
)
}
}
private
class
ProxiedApp
(
private
val
pm
:
PackageManager
,
private
val
appInfo
:
ApplicationInfo
,
...
...
@@ -106,17 +108,15 @@ class AppManager : AppCompatActivity() {
proxiedApps
.
add
(
item
.
packageName
)
check
.
isChecked
=
true
}
if
(!
appsLoading
.
get
())
{
DataStore
.
individual
=
proxiedApps
.
joinToString
(
"\n"
)
DataStore
.
dirty
=
true
}
}
}
private
inner
class
AppsAdapter
:
RecyclerView
.
Adapter
<
AppViewHolder
>()
{
private
var
apps
=
listOf
<
ProxiedApp
>()
fun
reload
()
{
suspend
fun
reload
()
{
apps
=
getApps
(
packageManager
)
.
sortedWith
(
compareBy
({
!
proxiedApps
.
contains
(
it
.
packageName
)
},
{
it
.
name
.
toString
()
}))
}
...
...
@@ -132,38 +132,36 @@ class AppManager : AppCompatActivity() {
private
lateinit
var
bypassSwitch
:
Switch
private
lateinit
var
appListView
:
RecyclerView
private
lateinit
var
loadingView
:
View
private
val
appsLoading
=
AtomicBoolean
()
private
val
handler
=
Handler
()
private
val
clipboard
by
lazy
{
getSystemService
<
ClipboardManager
>()
!!
}
private
var
loader
:
Job
?
=
null
private
val
shortAnimTime
by
lazy
{
resources
.
getInteger
(
android
.
R
.
integer
.
config_shortAnimTime
).
toLong
()
}
private
fun
View
.
crossFadeFrom
(
other
:
View
)
{
clearAnimation
()
other
.
clearAnimation
()
if
(
visibility
==
View
.
VISIBLE
&&
other
.
visibility
==
View
.
GONE
)
return
alpha
=
0F
visibility
=
View
.
VISIBLE
animate
().
alpha
(
1F
).
duration
=
shortAnimTime
other
.
animate
().
alpha
(
0F
).
setListener
(
object
:
AnimatorListenerAdapter
()
{
override
fun
onAnimationEnd
(
animation
:
Animator
)
{
other
.
visibility
=
View
.
GONE
}
}).
duration
=
shortAnimTime
}
private
fun
initProxiedApps
(
str
:
String
=
DataStore
.
individual
)
{
proxiedApps
=
str
.
split
(
'\n'
).
toHashSet
()
}
private
fun
reloadApps
()
{
if
(!
appsLoading
.
compareAndSet
(
true
,
false
))
loadAppsAsync
()
}
private
fun
loadAppsAsync
()
{
if
(!
appsLoading
.
compareAndSet
(
false
,
true
))
return
appListView
.
visibility
=
View
.
GONE
loadingView
.
visibility
=
View
.
VISIBLE
thread
(
"AppManager-loader"
)
{
@UiThread
private
fun
loadApps
()
{
loader
?.
cancel
()
loader
=
GlobalScope
.
launch
(
Dispatchers
.
Main
,
CoroutineStart
.
UNDISPATCHED
)
{
loadingView
.
crossFadeFrom
(
appListView
)
val
adapter
=
appListView
.
adapter
as
AppsAdapter
do
{
appsLoading
.
set
(
true
)
adapter
.
reload
()
}
while
(!
appsLoading
.
compareAndSet
(
true
,
false
))
handler
.
post
{
withContext
(
Dispatchers
.
IO
)
{
adapter
.
reload
()
}
adapter
.
notifyDataSetChanged
()
val
shortAnimTime
=
resources
.
getInteger
(
android
.
R
.
integer
.
config_shortAnimTime
)
appListView
.
alpha
=
0F
appListView
.
visibility
=
View
.
VISIBLE
appListView
.
animate
().
alpha
(
1F
).
duration
=
shortAnimTime
.
toLong
()
loadingView
.
animate
().
alpha
(
0F
).
setListener
(
object
:
AnimatorListenerAdapter
()
{
override
fun
onAnimationEnd
(
animation
:
Animator
)
{
loadingView
.
visibility
=
View
.
GONE
}
}).
duration
=
shortAnimTime
.
toLong
()
}
appListView
.
crossFadeFrom
(
loadingView
)
}
}
...
...
@@ -199,7 +197,7 @@ class AppManager : AppCompatActivity() {
appListView
.
adapter
=
AppsAdapter
()
instance
=
this
loadApps
Async
()
loadApps
()
}
override
fun
onCreateOptionsMenu
(
menu
:
Menu
?):
Boolean
{
...
...
@@ -239,7 +237,7 @@ class AppManager : AppCompatActivity() {
DataStore
.
dirty
=
true
Snackbar
.
make
(
appListView
,
R
.
string
.
action_import_msg
,
Snackbar
.
LENGTH_LONG
).
show
()
initProxiedApps
(
apps
)
re
loadApps
()
loadApps
()
return
true
}
catch
(
_
:
IllegalArgumentException
)
{
}
}
...
...
@@ -255,7 +253,7 @@ class AppManager : AppCompatActivity() {
override
fun
onDestroy
()
{
instance
=
null
handler
.
removeCallbacksAndMessages
(
null
)
loader
?.
cancel
(
)
super
.
onDestroy
()
}
}
mobile/src/main/res/layout/layout_apps.xml
View file @
2e289597
...
...
@@ -55,11 +55,13 @@
android:layout_width=
"fill_parent"
android:layout_height=
"fill_parent"
android:id=
"@+id/list"
android:visibility=
"gone"
app:fastScrollEnabled=
"true"
app:fastScrollHorizontalThumbDrawable=
"@drawable/fastscroll_thumb"
app:fastScrollHorizontalTrackDrawable=
"@drawable/fastscroll_line"
app:fastScrollVerticalThumbDrawable=
"@drawable/fastscroll_thumb"
app:fastScrollVerticalTrackDrawable=
"@drawable/fastscroll_line"
tools:listitem=
"@layout/layout_apps_item"
/>
tools:listitem=
"@layout/layout_apps_item"
tools:visibility=
"visible"
/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</LinearLayout>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment