Skip to content

Commit e7c8744

Browse files
committed
rewrite dns notify mechanism
1 parent b2ba3cd commit e7c8744

File tree

1 file changed

+103
-76
lines changed

1 file changed

+103
-76
lines changed

service/src/main/java/com/github/kr328/clash/service/clash/module/NetworkObserveModule.kt

Lines changed: 103 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -6,123 +6,150 @@ import android.os.Build
66
import androidx.core.content.getSystemService
77
import com.github.kr328.clash.common.log.Log
88
import com.github.kr328.clash.core.Clash
9-
import com.github.kr328.clash.service.util.resolveDns
9+
import com.github.kr328.clash.service.util.asSocketAddressText
1010
import kotlinx.coroutines.NonCancellable
1111
import kotlinx.coroutines.channels.Channel
12-
import kotlinx.coroutines.channels.trySendBlocking
12+
import kotlinx.coroutines.selects.select
1313
import kotlinx.coroutines.withContext
14+
import java.net.InetAddress
15+
import java.util.concurrent.ConcurrentHashMap
1416

15-
class NetworkObserveModule(service: Service) : Module<Network?>(service) {
16-
private data class Action(val type: Type, val network: Network) {
17-
enum class Type { Available, Lost, Changed }
18-
}
19-
17+
class NetworkObserveModule(service: Service) : Module<Network>(service) {
2018
private val connectivity = service.getSystemService<ConnectivityManager>()!!
21-
private val actions = Channel<Action>(Channel.UNLIMITED)
19+
private val networks: Channel<Network> = Channel(Channel.UNLIMITED)
2220
private val request = NetworkRequest.Builder().apply {
2321
addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
2422
addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
23+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
24+
addCapability(NetworkCapabilities.NET_CAPABILITY_FOREGROUND)
25+
}
2526
addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
2627
}.build()
28+
29+
private data class NetworkInfo(
30+
@Volatile var losingMs: Long = 0,
31+
@Volatile var dnsList: List<InetAddress> = emptyList()
32+
) {
33+
fun isAvailable(): Boolean = losingMs < System.currentTimeMillis()
34+
}
35+
36+
private val networkInfos = ConcurrentHashMap<Network, NetworkInfo>()
37+
38+
@Volatile
39+
private var curDnsList = emptyList<String>()
40+
2741
private val callback = object : ConnectivityManager.NetworkCallback() {
2842
override fun onAvailable(network: Network) {
29-
actions.trySendBlocking(Action(Action.Type.Available, network))
43+
Log.i("NetworkObserve onAvailable network=$network")
44+
networkInfos[network] = NetworkInfo()
45+
}
46+
47+
override fun onLosing(network: Network, maxMsToLive: Int) {
48+
Log.i("NetworkObserve onLosing network=$network")
49+
networkInfos[network]?.losingMs = System.currentTimeMillis() + maxMsToLive
50+
notifyDnsChange()
51+
52+
networks.trySend(network)
3053
}
3154

3255
override fun onLost(network: Network) {
33-
actions.trySendBlocking(Action(Action.Type.Lost, network))
56+
Log.i("NetworkObserve onLost network=$network")
57+
networkInfos.remove(network)
58+
notifyDnsChange()
59+
60+
networks.trySend(network)
3461
}
3562

3663
override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
37-
actions.trySendBlocking(Action(Action.Type.Changed, network))
64+
Log.i("NetworkObserve onLinkPropertiesChanged network=$network $linkProperties")
65+
networkInfos[network]?.dnsList = linkProperties.dnsServers
66+
notifyDnsChange()
67+
68+
networks.trySend(network)
69+
}
70+
71+
override fun onUnavailable() {
72+
Log.i("NetworkObserve onUnavailable")
3873
}
3974
}
4075

41-
override suspend fun run() {
42-
try {
76+
private fun register(): Boolean {
77+
Log.i("NetworkObserve start register")
78+
return try {
4379
connectivity.registerNetworkCallback(request, callback)
80+
81+
true
4482
} catch (e: Exception) {
45-
Log.w("Observe network failed: $e", e)
83+
Log.w("NetworkObserve register failed", e)
4684

47-
return
85+
false
4886
}
87+
}
4988

89+
private fun unregister(): Boolean {
90+
Log.i("NetworkObserve start unregister")
5091
try {
51-
val networks = mutableSetOf<Network>()
52-
53-
while (true) {
54-
val action = actions.receive()
55-
56-
val resolveDefault = when (action.type) {
57-
Action.Type.Available -> {
58-
networks.add(action.network)
92+
connectivity.unregisterNetworkCallback(callback)
93+
} catch (e: Exception) {
94+
Log.w("NetworkObserve unregister failed", e)
95+
}
5996

60-
true
61-
}
62-
Action.Type.Lost -> {
63-
networks.remove(action.network)
97+
return false
98+
}
6499

65-
true
66-
}
67-
Action.Type.Changed -> {
68-
false
69-
}
70-
}
100+
private fun networkToInt(entry: Map.Entry<Network, NetworkInfo>): Int {
101+
val capabilities = connectivity.getNetworkCapabilities(entry.key)
102+
// calculate priority based on transport type, available state
103+
// lower value means higher priority
104+
// wifi > ethernet > usb tethering > bluetooth tethering > cellular > satellite > other
105+
return when {
106+
capabilities == null -> 100
107+
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) -> 90
108+
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> 0
109+
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> 1
110+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_USB) -> 2
111+
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> 3
112+
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> 4
113+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_SATELLITE) -> 5
114+
// TRANSPORT_LOWPAN / TRANSPORT_THREAD / TRANSPORT_WIFI_AWARE are not for general internet access, which will not set as default route.
115+
else -> 6
116+
} + (if (entry.value.isAvailable()) 0 else 10)
117+
}
71118

72-
val dns = networks.flatMap { network ->
73-
connectivity?.resolveDns(network) ?: emptyList()
74-
}
119+
private fun notifyDnsChange() {
120+
val dnsList = (networkInfos.asSequence().minByOrNull { networkToInt(it) }?.value?.dnsList
121+
?: emptyList()).map { x -> x.asSocketAddressText(53) }
122+
val prevDnsList = curDnsList
123+
if (dnsList.isNotEmpty() && prevDnsList != dnsList) {
124+
Log.i("notifyDnsChange $prevDnsList -> $dnsList")
125+
curDnsList = dnsList
126+
Clash.notifyDnsChanged(dnsList)
127+
}
128+
}
75129

76-
Clash.notifyDnsChanged(dns)
130+
override suspend fun run() {
131+
register()
77132

78-
Log.d("DNS: $dns")
133+
try {
134+
while (true) {
135+
val quit = select {
136+
networks.onReceive {
137+
enqueueEvent(it)
79138

80-
if (resolveDefault) {
81-
val network = networks.maxByOrNull { net ->
82-
connectivity.getNetworkCapabilities(net)?.let { cap ->
83-
TRANSPORT_PRIORITY.indexOfFirst { cap.hasTransport(it) }
84-
} ?: -1
139+
false
85140
}
86-
87-
enqueueEvent(network)
88-
89-
Log.d("Network: $network of $networks")
141+
}
142+
if (quit) {
143+
return
90144
}
91145
}
92146
} finally {
93147
withContext(NonCancellable) {
94-
enqueueEvent(null)
148+
unregister()
95149

150+
Log.i("NetworkObserve dns = []")
96151
Clash.notifyDnsChanged(emptyList())
97-
98-
runCatching {
99-
connectivity.unregisterNetworkCallback(callback)
100-
}
101152
}
102153
}
103154
}
104-
105-
companion object {
106-
private val TRANSPORT_PRIORITY = sequence {
107-
yield(NetworkCapabilities.TRANSPORT_CELLULAR)
108-
109-
if (Build.VERSION.SDK_INT >= 27) {
110-
yield(NetworkCapabilities.TRANSPORT_LOWPAN)
111-
}
112-
113-
yield(NetworkCapabilities.TRANSPORT_BLUETOOTH)
114-
115-
if (Build.VERSION.SDK_INT >= 26) {
116-
yield(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
117-
}
118-
119-
yield(NetworkCapabilities.TRANSPORT_WIFI)
120-
121-
if (Build.VERSION.SDK_INT >= 31) {
122-
yield(NetworkCapabilities.TRANSPORT_USB)
123-
}
124-
125-
yield(NetworkCapabilities.TRANSPORT_ETHERNET)
126-
}.toList()
127-
}
128155
}

0 commit comments

Comments
 (0)