@@ -5,29 +5,41 @@ import android.net.LinkProperties
5
5
import android.net.NetworkCapabilities
6
6
import android.net.NetworkRequest
7
7
import co.touchlab.kermit.Logger
8
+ import java.net.DatagramSocket
9
+ import java.net.Inet4Address
10
+ import java.net.Inet6Address
8
11
import java.net.InetAddress
9
12
import kotlin.collections.ArrayList
10
13
import kotlinx.coroutines.CoroutineScope
14
+ import kotlinx.coroutines.Dispatchers
11
15
import kotlinx.coroutines.channels.Channel
12
16
import kotlinx.coroutines.flow.Flow
13
17
import kotlinx.coroutines.flow.MutableStateFlow
14
18
import kotlinx.coroutines.flow.SharingStarted
15
19
import kotlinx.coroutines.flow.StateFlow
20
+ import kotlinx.coroutines.flow.combine
21
+ import kotlinx.coroutines.flow.distinctUntilChanged
16
22
import kotlinx.coroutines.flow.map
17
23
import kotlinx.coroutines.flow.merge
18
24
import kotlinx.coroutines.flow.onEach
19
25
import kotlinx.coroutines.flow.receiveAsFlow
20
26
import kotlinx.coroutines.flow.scan
21
27
import kotlinx.coroutines.flow.stateIn
22
28
import kotlinx.coroutines.launch
29
+ import kotlinx.coroutines.plus
30
+ import net.mullvad.talpid.model.Connectivity
23
31
import net.mullvad.talpid.model.NetworkState
32
+ import net.mullvad.talpid.util.IPAvailabilityUtils
24
33
import net.mullvad.talpid.util.NetworkEvent
25
34
import net.mullvad.talpid.util.RawNetworkState
26
35
import net.mullvad.talpid.util.defaultRawNetworkStateFlow
27
36
import net.mullvad.talpid.util.networkEvents
28
37
29
- class ConnectivityListener (private val connectivityManager : ConnectivityManager ) {
30
- private lateinit var _isConnected : StateFlow <Boolean >
38
+ class ConnectivityListener (
39
+ val connectivityManager : ConnectivityManager ,
40
+ val protect : (socket: DatagramSocket ) -> Boolean ,
41
+ ) {
42
+ private lateinit var _isConnected : StateFlow <Connectivity >
31
43
// Used by JNI
32
44
val isConnected
33
45
get() = _isConnected .value
@@ -58,12 +70,49 @@ class ConnectivityListener(private val connectivityManager: ConnectivityManager)
58
70
}
59
71
60
72
_isConnected =
61
- hasInternetCapability()
62
- .onEach { notifyConnectivityChange(it) }
73
+ combine(connectivityManager.defaultRawNetworkStateFlow(), hasInternetCapability()) {
74
+ rawNetworkState,
75
+ hasInternetCapability: Boolean ->
76
+ if (hasInternetCapability) {
77
+ if (rawNetworkState.isUnderlyingNetwork()) {
78
+ // If the default network is not a VPN we can check the addresses
79
+ // directly
80
+ Connectivity .Status (
81
+ ipv4 =
82
+ rawNetworkState?.linkProperties?.routes?.any {
83
+ it.destination.address is Inet4Address
84
+ } == true ,
85
+ ipv6 =
86
+ rawNetworkState?.linkProperties?.routes?.any {
87
+ it.destination.address is Inet6Address
88
+ } == true ,
89
+ )
90
+ } else {
91
+ // If the default network is a VPN we need to use a socket to check
92
+ // the underlying network
93
+ Connectivity .Status (
94
+ IPAvailabilityUtils .isIPv4Available(protect = { protect(it) }),
95
+ IPAvailabilityUtils .isIPv6Available(protect = { protect(it) }),
96
+ )
97
+ }
98
+ // If we have internet, but both IPv4 and IPv6 are not available, we
99
+ // assume something is wrong and instead will return presume online.
100
+ .takeUnless { ! it.ipv4 && ! it.ipv6 } ? : Connectivity .PresumeOnline
101
+ } else {
102
+ Connectivity .Status (false , false )
103
+ }
104
+ }
105
+ .distinctUntilChanged()
106
+ .onEach {
107
+ when (it) {
108
+ Connectivity .PresumeOnline -> notifyConnectivityChange(true , true )
109
+ is Connectivity .Status -> notifyConnectivityChange(it.ipv4, it.ipv6)
110
+ }
111
+ }
63
112
.stateIn(
64
- scope,
113
+ scope + Dispatchers . IO ,
65
114
SharingStarted .Eagerly ,
66
- true , // Assume we have internet until we know otherwise
115
+ Connectivity . PresumeOnline , // Assume we have internet until we know otherwise
67
116
)
68
117
}
69
118
@@ -112,14 +161,17 @@ class ConnectivityListener(private val connectivityManager: ConnectivityManager)
112
161
private fun NetworkCapabilities?.hasInternetCapability (): Boolean =
113
162
this ?.hasCapability(NetworkCapabilities .NET_CAPABILITY_INTERNET ) == true
114
163
164
+ private fun RawNetworkState?.isUnderlyingNetwork (): Boolean =
165
+ this ?.networkCapabilities?.hasCapability(NetworkCapabilities .NET_CAPABILITY_NOT_VPN ) == true
166
+
115
167
private fun RawNetworkState.toNetworkState (): NetworkState =
116
168
NetworkState (
117
169
network.networkHandle,
118
170
linkProperties?.routes,
119
171
linkProperties?.dnsServersWithoutFallback(),
120
172
)
121
173
122
- private external fun notifyConnectivityChange (isConnected : Boolean )
174
+ private external fun notifyConnectivityChange (isIPv4 : Boolean , isIPv6 : Boolean )
123
175
124
176
private external fun notifyDefaultNetworkChange (networkState : NetworkState ? )
125
177
}
0 commit comments