@@ -4,6 +4,7 @@ import android.net.ConnectivityManager
4
4
import android.net.LinkProperties
5
5
import android.net.Network
6
6
import android.net.NetworkCapabilities
7
+ import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
7
8
import android.net.NetworkRequest
8
9
import co.touchlab.kermit.Logger
9
10
import java.net.DatagramSocket
@@ -16,6 +17,7 @@ import kotlinx.coroutines.flow.SharingStarted
16
17
import kotlinx.coroutines.flow.StateFlow
17
18
import kotlinx.coroutines.flow.combine
18
19
import kotlinx.coroutines.flow.distinctUntilChanged
20
+ import kotlinx.coroutines.flow.filterIsInstance
19
21
import kotlinx.coroutines.flow.map
20
22
import kotlinx.coroutines.flow.onEach
21
23
import kotlinx.coroutines.flow.scan
@@ -25,13 +27,14 @@ import net.mullvad.talpid.model.Connectivity
25
27
import net.mullvad.talpid.model.NetworkState
26
28
import net.mullvad.talpid.util.IPAvailabilityUtils
27
29
import net.mullvad.talpid.util.NetworkEvent
30
+ import net.mullvad.talpid.util.NetworkEvent.CapabilitiesChanged
28
31
import net.mullvad.talpid.util.RawNetworkState
29
32
import net.mullvad.talpid.util.defaultRawNetworkStateFlow
30
33
import net.mullvad.talpid.util.networkEvents
31
34
32
35
class ConnectivityListener (
33
36
val connectivityManager : ConnectivityManager ,
34
- val protect : (socket: DatagramSocket ) -> Unit ,
37
+ val protect : (socket: DatagramSocket ) -> Boolean ,
35
38
) {
36
39
private lateinit var _isConnected : StateFlow <Connectivity >
37
40
// Used by JNI
@@ -60,22 +63,55 @@ class ConnectivityListener(
60
63
.stateIn(scope, SharingStarted .Eagerly , null )
61
64
62
65
_isConnected =
63
- combine(_currentNetworkState , hasInternetCapability()) { linkPropertiesChanged: NetworkState ? ,
66
+ combine(
67
+ _currentNetworkState ,
68
+ connectivityManager
69
+ .defaultRawNetworkStateFlow()
70
+ .filterIsInstance<CapabilitiesChanged >(),
71
+ hasInternetCapability(),
72
+ ) {
73
+ currentNetworkState: NetworkState ? ,
74
+ capabilitiesChanged: CapabilitiesChanged ,
64
75
hasInternetCapability: Boolean ->
65
76
if (hasInternetCapability) {
66
- Connectivity .Status (
67
- IPAvailabilityUtils .isIPv4Available(protect = { protect(it) }),
68
- IPAvailabilityUtils .isIPv6Available(protect = { protect(it) }),
69
- )
77
+ if (
78
+ capabilitiesChanged.networkCapabilities.hasCapability(
79
+ NET_CAPABILITY_NOT_VPN
80
+ )
81
+ ) {
82
+ // If the default network is not a VPN we can check the addresses
83
+ // directly
84
+ Connectivity .Status (
85
+ ipv4 =
86
+ currentNetworkState?.routes?.any {
87
+ ! it.destination.isIpv6
88
+ } == true ,
89
+ ipv6 =
90
+ currentNetworkState?.routes?.any {
91
+ it.destination.isIpv6
92
+ } == true ,
93
+ )
94
+ } else {
95
+ // If the default network is a VPN we need to use a socket to check
96
+ // the underlying network
97
+ Connectivity .Status (
98
+ IPAvailabilityUtils .isIPv4Available(protect = { protect(it) }),
99
+ IPAvailabilityUtils .isIPv6Available(protect = { protect(it) }),
100
+ )
101
+ }
70
102
// If we have internet, but both IPv4 and IPv6 are not available, we
71
- // assume something is wrong and instead
72
- // will return both as available since this is the previous behavior.
73
- .takeUnless { ! it.ipv4 && ! it.ipv6 } ? : Connectivity .Status (true , true )
103
+ // assume something is wrong and instead will return presume online.
104
+ .takeUnless { ! it.ipv4 && ! it.ipv6 } ? : Connectivity .PresumeOnline
74
105
} else {
75
106
Connectivity .Status (false , false )
76
107
}
77
108
}
78
- .onEach { notifyConnectivityChange(it.ipv4, it.ipv6) }
109
+ .onEach {
110
+ when (it) {
111
+ Connectivity .PresumeOnline -> notifyConnectivityChange(true , true )
112
+ is Connectivity .Status -> notifyConnectivityChange(it.ipv4, it.ipv6)
113
+ }
114
+ }
79
115
.stateIn(
80
116
scope + Dispatchers .IO ,
81
117
SharingStarted .Eagerly ,
0 commit comments