@ -2,48 +2,73 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-License-Identifier: BSD-3-Clause
package com.tailscale.ipn
package com.tailscale.ipn
import android.content.Context
import android.net.ConnectivityManager
import android.net.ConnectivityManager
import android.net.LinkProperties
import android.net.LinkProperties
import android.net.Network
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.util.Log
import android.util.Log
import libtailscale.Libtailscale
import libtailscale.Libtailscale
import java.net.InetAddress
import java.net.InetAddress
import java.net.NetworkInterface
object NetworkChangeCallback {
object NetworkChangeCallback {
// requestNetwork attempts to find the best network that matches the passed NetworkRequest. It is
private const val TAG = " NetworkChangeCallback "
// possible that this might return an unusuable network, eg a captive portal.
// Cache LinkProperties and NetworkCapabilities since synchronous ConnectivityManager calls are
// prone to races.
// Since there is no guarantee for which update might come first, maybe update DNS configs on
// both.
val networkCapabilitiesCache = mutableMapOf < Network , NetworkCapabilities > ( )
val linkPropertiesCache = mutableMapOf < Network , LinkProperties > ( )
// requestDefaultNetworkCallback receives notifications about the default network. Listen for
// changes to the capabilities, which are guaranteed to come after a network becomes available per
// https://developer.android.com/reference/android/net/ConnectivityManager.NetworkCallback#onAvailable(android.net.Network),
// in order to filter on non-VPN networks.
fun monitorDnsChanges ( connectivityManager : ConnectivityManager , dns : DnsConfig ) {
fun monitorDnsChanges ( connectivityManager : ConnectivityManager , dns : DnsConfig ) {
val networkConnectivityRequest =
NetworkRequest . Builder ( )
. addCapability ( NetworkCapabilities . NET _CAPABILITY _INTERNET )
. addCapability ( NetworkCapabilities . NET _CAPABILITY _NOT _VPN )
. build ( )
connectivityManager . registerNetworkCallback (
networkConnectivityRequest ,
object : ConnectivityManager . NetworkCallback ( ) {
override fun onAvailable ( network : Network ) {
super . onAvailable ( network )
val sb = StringBuilder ( )
connectivityManager . registerDefaultNetworkCallback (
val linkProperties : LinkProperties ? = connectivityManager . getLinkProperties ( network )
object : ConnectivityManager . NetworkCallback ( ) {
val dnsList : MutableList < InetAddress > = linkProperties ?. dnsServers ?: mutableListOf ( )
override fun onCapabilitiesChanged ( network : Network , capabilities : NetworkCapabilities ) {
for ( ip in dnsList ) {
super . onCapabilitiesChanged ( network , capabilities )
sb . append ( ip . hostAddress ) . append ( " " )
networkCapabilitiesCache [ network ] = capabilities
val linkProperties = linkPropertiesCache [ network ]
if ( linkProperties != null &&
capabilities . hasCapability ( NetworkCapabilities . NET _CAPABILITY _NOT _VPN ) &&
capabilities . hasCapability ( NetworkCapabilities . NET _CAPABILITY _INTERNET ) ) {
maybeUpdateDNSConfig ( linkProperties , dns )
} else {
if ( ! capabilities . hasCapability ( NetworkCapabilities . NET _CAPABILITY _NOT _VPN ) ||
! capabilities . hasCapability ( NetworkCapabilities . NET _CAPABILITY _INTERNET ) ) {
Log . d (
TAG ,
" Capabilities changed for network $network .toString(), but not updating DNS config because either this is a VPN network or non-internet network " )
} else {
Log . d (
TAG ,
" Capabilities changed for network $network .toString(), but not updating DNS config, because the LinkProperties hasn't been gotten yet " )
}
}
}
val searchDomains : String ? = linkProperties ?. domains
if ( searchDomains != null ) {
sb . append ( " \n " )
sb . append ( searchDomains )
}
}
if ( dns . updateDNSFromNetwork ( sb . toString ( ) ) ) {
override fun onLinkPropertiesChanged ( network : Network , linkProperties : LinkProperties ) {
Libtailscale . onDNSConfigChanged ( linkProperties ?. interfaceName )
super . onLinkPropertiesChanged ( network , linkProperties )
linkPropertiesCache [ network ] = linkProperties
val capabilities = networkCapabilitiesCache [ network ]
if ( capabilities != null &&
capabilities . hasCapability ( NetworkCapabilities . NET _CAPABILITY _NOT _VPN ) &&
capabilities . hasCapability ( NetworkCapabilities . NET _CAPABILITY _INTERNET ) ) {
maybeUpdateDNSConfig ( linkProperties , dns )
} else {
if ( capabilities == null ) {
Log . d (
TAG ,
" Capabilities changed for network $network .toString(), but not updating DNS config because capabilities haven't been gotten for this network yet " )
} else {
Log . d (
TAG ,
" Capabilities changed for network $network .toString(), but not updating DNS config, because this is a VPN network or non-Internet network " )
}
}
}
}
}
@ -55,4 +80,20 @@ object NetworkChangeCallback {
}
}
} )
} )
}
}
fun maybeUpdateDNSConfig ( linkProperties : LinkProperties , dns : DnsConfig ) {
val sb = StringBuilder ( )
val dnsList : MutableList < InetAddress > = linkProperties . dnsServers ?: mutableListOf ( )
for ( ip in dnsList ) {
sb . append ( ip . hostAddress ) . append ( " " )
}
val searchDomains : String ? = linkProperties . domains
if ( searchDomains != null ) {
sb . append ( " \n " )
sb . append ( searchDomains )
}
if ( dns . updateDNSFromNetwork ( sb . toString ( ) ) ) {
Libtailscale . onDNSConfigChanged ( linkProperties . interfaceName )
}
}
}
}