@@ -6,15 +6,20 @@ import android.net.Network
6
6
import android.net.NetworkCapabilities
7
7
import android.net.NetworkRequest
8
8
import co.touchlab.kermit.Logger
9
+ import java.net.DatagramSocket
10
+ import java.net.Inet4Address
11
+ import java.net.Inet6Address
9
12
import java.net.InetAddress
10
13
import kotlin.collections.ArrayList
11
14
import kotlin.time.Duration.Companion.seconds
12
15
import kotlinx.coroutines.CoroutineScope
16
+ import kotlinx.coroutines.Dispatchers
13
17
import kotlinx.coroutines.channels.Channel
14
18
import kotlinx.coroutines.flow.Flow
15
19
import kotlinx.coroutines.flow.MutableStateFlow
16
20
import kotlinx.coroutines.flow.SharingStarted
17
21
import kotlinx.coroutines.flow.StateFlow
22
+ import kotlinx.coroutines.flow.combine
18
23
import kotlinx.coroutines.flow.distinctUntilChanged
19
24
import kotlinx.coroutines.flow.filter
20
25
import kotlinx.coroutines.flow.map
@@ -25,15 +30,21 @@ import kotlinx.coroutines.flow.receiveAsFlow
25
30
import kotlinx.coroutines.flow.scan
26
31
import kotlinx.coroutines.flow.stateIn
27
32
import kotlinx.coroutines.launch
33
+ import kotlinx.coroutines.plus
28
34
import net.mullvad.mullvadvpn.lib.common.util.debounceFirst
35
+ import net.mullvad.talpid.model.Connectivity
29
36
import net.mullvad.talpid.model.NetworkState
37
+ import net.mullvad.talpid.util.IPAvailabilityUtils
30
38
import net.mullvad.talpid.util.NetworkEvent
31
39
import net.mullvad.talpid.util.RawNetworkState
32
40
import net.mullvad.talpid.util.defaultRawNetworkStateFlow
33
41
import net.mullvad.talpid.util.networkEvents
34
42
35
- class ConnectivityListener (private val connectivityManager : ConnectivityManager ) {
36
- private lateinit var _isConnected : StateFlow <Boolean >
43
+ class ConnectivityListener (
44
+ val connectivityManager : ConnectivityManager ,
45
+ val protect : (socket: DatagramSocket ) -> Boolean ,
46
+ ) {
47
+ private lateinit var _isConnected : StateFlow <Connectivity >
37
48
// Used by JNI
38
49
val isConnected
39
50
get() = _isConnected .value
@@ -64,12 +75,49 @@ class ConnectivityListener(private val connectivityManager: ConnectivityManager)
64
75
}
65
76
66
77
_isConnected =
67
- hasInternetConnectivity()
68
- .onEach { notifyConnectivityChange(it) }
78
+ combine(connectivityManager.defaultRawNetworkStateFlow(), hasInternetConnectivity()) {
79
+ rawNetworkState,
80
+ hasInternetCapability: Boolean ->
81
+ if (hasInternetCapability) {
82
+ if (rawNetworkState.isNotVpn()) {
83
+ // If the default network is not a VPN we can check the addresses
84
+ // directly
85
+ Connectivity .Status (
86
+ ipv4 =
87
+ rawNetworkState?.linkProperties?.routes?.any {
88
+ it.destination.address is Inet4Address
89
+ } == true ,
90
+ ipv6 =
91
+ rawNetworkState?.linkProperties?.routes?.any {
92
+ it.destination.address is Inet6Address
93
+ } == true ,
94
+ )
95
+ } else {
96
+ // If the default network is a VPN we need to use a socket to check
97
+ // the underlying network
98
+ Connectivity .Status (
99
+ IPAvailabilityUtils .isIPv4Available(protect = { protect(it) }),
100
+ IPAvailabilityUtils .isIPv6Available(protect = { protect(it) }),
101
+ )
102
+ }
103
+ // If we have internet, but both IPv4 and IPv6 are not available, we
104
+ // assume something is wrong and instead will return presume online.
105
+ .takeUnless { ! it.ipv4 && ! it.ipv6 } ? : Connectivity .PresumeOnline
106
+ } else {
107
+ Connectivity .Status (false , false )
108
+ }
109
+ }
110
+ .distinctUntilChanged()
111
+ .onEach {
112
+ when (it) {
113
+ Connectivity .PresumeOnline -> notifyConnectivityChange(true , true )
114
+ is Connectivity .Status -> notifyConnectivityChange(it.ipv4, it.ipv6)
115
+ }
116
+ }
69
117
.stateIn(
70
- scope,
118
+ scope + Dispatchers . IO ,
71
119
SharingStarted .Eagerly ,
72
- true , // Assume we have internet until we know otherwise
120
+ Connectivity . PresumeOnline , // Assume we have internet until we know otherwise
73
121
)
74
122
}
75
123
@@ -142,14 +190,17 @@ class ConnectivityListener(private val connectivityManager: ConnectivityManager)
142
190
}
143
191
.toSet()
144
192
193
+ private fun RawNetworkState?.isNotVpn (): Boolean =
194
+ this ?.networkCapabilities?.hasCapability(NetworkCapabilities .NET_CAPABILITY_NOT_VPN ) == true
195
+
145
196
private fun RawNetworkState.toNetworkState (): NetworkState =
146
197
NetworkState (
147
198
network.networkHandle,
148
199
linkProperties?.routes,
149
200
linkProperties?.dnsServersWithoutFallback(),
150
201
)
151
202
152
- private external fun notifyConnectivityChange (isConnected : Boolean )
203
+ private external fun notifyConnectivityChange (isIPv4 : Boolean , isIPv6 : Boolean )
153
204
154
205
private external fun notifyDefaultNetworkChange (networkState : NetworkState ? )
155
206
}
0 commit comments