2
2
// SPDX-License-Identifier: BSD-3-Clause
3
3
package com.tailscale.ipn
4
4
5
- import android.content.Context
6
5
import android.net.ConnectivityManager
7
6
import android.net.LinkProperties
8
7
import android.net.Network
9
8
import android.net.NetworkCapabilities
10
- import android.net.NetworkRequest
11
9
import android.util.Log
12
10
import libtailscale.Libtailscale
13
11
import java.net.InetAddress
14
- import java.net.NetworkInterface
15
12
16
13
object NetworkChangeCallback {
17
14
18
- // requestNetwork attempts to find the best network that matches the passed NetworkRequest. It is
19
- // possible that this might return an unusuable network, eg a captive portal.
15
+ private const val TAG = " NetworkChangeCallback"
16
+
17
+ // Cache LinkProperties and NetworkCapabilities since synchronous ConnectivityManager calls are
18
+ // prone to races.
19
+ // Since there is no guarantee for which update might come first, maybe update DNS configs on
20
+ // both.
21
+ val networkCapabilitiesCache = mutableMapOf<Network , NetworkCapabilities >()
22
+ val linkPropertiesCache = mutableMapOf<Network , LinkProperties >()
23
+
24
+ // requestDefaultNetworkCallback receives notifications about the default network. Listen for
25
+ // changes to the capabilities, which are guaranteed to come after a network becomes available per
26
+ // https://developer.android.com/reference/android/net/ConnectivityManager.NetworkCallback#onAvailable(android.net.Network),
27
+ // in order to filter on non-VPN networks.
20
28
fun monitorDnsChanges (connectivityManager : ConnectivityManager , dns : DnsConfig ) {
21
- val networkConnectivityRequest =
22
- NetworkRequest .Builder ()
23
- .addCapability(NetworkCapabilities .NET_CAPABILITY_INTERNET )
24
- .addCapability(NetworkCapabilities .NET_CAPABILITY_NOT_VPN )
25
- .build()
26
-
27
- connectivityManager.registerNetworkCallback(
28
- networkConnectivityRequest,
29
+
30
+ connectivityManager.registerDefaultNetworkCallback(
29
31
object : ConnectivityManager .NetworkCallback () {
30
- override fun onAvailable (network : Network ) {
31
- super .onAvailable(network)
32
-
33
- val sb = StringBuilder ()
34
- val linkProperties: LinkProperties ? = connectivityManager.getLinkProperties(network)
35
- val dnsList: MutableList <InetAddress > = linkProperties?.dnsServers ? : mutableListOf ()
36
- for (ip in dnsList) {
37
- sb.append(ip.hostAddress).append(" " )
38
- }
39
- val searchDomains: String? = linkProperties?.domains
40
- if (searchDomains != null ) {
41
- sb.append(" \n " )
42
- sb.append(searchDomains)
32
+ override fun onCapabilitiesChanged (network : Network , capabilities : NetworkCapabilities ) {
33
+ super .onCapabilitiesChanged(network, capabilities)
34
+ networkCapabilitiesCache[network] = capabilities
35
+ val linkProperties = linkPropertiesCache[network]
36
+ if (linkProperties != null &&
37
+ capabilities.hasCapability(NetworkCapabilities .NET_CAPABILITY_NOT_VPN ) &&
38
+ capabilities.hasCapability(NetworkCapabilities .NET_CAPABILITY_INTERNET )) {
39
+ maybeUpdateDNSConfig(linkProperties, dns)
40
+ } else {
41
+ if (! capabilities.hasCapability(NetworkCapabilities .NET_CAPABILITY_NOT_VPN ) ||
42
+ ! capabilities.hasCapability(NetworkCapabilities .NET_CAPABILITY_INTERNET )) {
43
+ Log .d(
44
+ TAG ,
45
+ " Capabilities changed for network $network .toString(), but not updating DNS config because either this is a VPN network or non-internet network" )
46
+ } else {
47
+ Log .d(
48
+ TAG ,
49
+ " Capabilities changed for network $network .toString(), but not updating DNS config, because the LinkProperties hasn't been gotten yet" )
50
+ }
43
51
}
52
+ }
44
53
45
- if (dns.updateDNSFromNetwork(sb.toString())) {
46
- Libtailscale .onDNSConfigChanged(linkProperties?.interfaceName)
54
+ override fun onLinkPropertiesChanged (network : Network , linkProperties : LinkProperties ) {
55
+ super .onLinkPropertiesChanged(network, linkProperties)
56
+ linkPropertiesCache[network] = linkProperties
57
+ val capabilities = networkCapabilitiesCache[network]
58
+ if (capabilities != null &&
59
+ capabilities.hasCapability(NetworkCapabilities .NET_CAPABILITY_NOT_VPN ) &&
60
+ capabilities.hasCapability(NetworkCapabilities .NET_CAPABILITY_INTERNET )) {
61
+ maybeUpdateDNSConfig(linkProperties, dns)
62
+ } else {
63
+ if (capabilities == null ) {
64
+ Log .d(
65
+ TAG ,
66
+ " Capabilities changed for network $network .toString(), but not updating DNS config because capabilities haven't been gotten for this network yet" )
67
+ } else {
68
+ Log .d(
69
+ TAG ,
70
+ " Capabilities changed for network $network .toString(), but not updating DNS config, because this is a VPN network or non-Internet network" )
71
+ }
47
72
}
48
73
}
49
74
@@ -55,4 +80,20 @@ object NetworkChangeCallback {
55
80
}
56
81
})
57
82
}
83
+
84
+ fun maybeUpdateDNSConfig (linkProperties : LinkProperties , dns : DnsConfig ) {
85
+ val sb = StringBuilder ()
86
+ val dnsList: MutableList <InetAddress > = linkProperties.dnsServers ? : mutableListOf ()
87
+ for (ip in dnsList) {
88
+ sb.append(ip.hostAddress).append(" " )
89
+ }
90
+ val searchDomains: String? = linkProperties.domains
91
+ if (searchDomains != null ) {
92
+ sb.append(" \n " )
93
+ sb.append(searchDomains)
94
+ }
95
+ if (dns.updateDNSFromNetwork(sb.toString())) {
96
+ Libtailscale .onDNSConfigChanged(linkProperties.interfaceName)
97
+ }
98
+ }
58
99
}
0 commit comments