Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d96e311e authored by Ricki Hirner's avatar Ricki Hirner
Browse files

Improve SRV record resolving (resolves bitfireAT/davx5#18)

- Android 8 to 10: use all DNS servers; prioritize resolvers of active connections
  (before: use only DNS servers of "active" connection, which may be a VPN connection without servers)
- Android 10+: use new DnsResolver API so that we don't need to know DNS servers anymore (also supports DoT etc.)
parent 952a10fb
Loading
Loading
Loading
Loading
+87 −0
Original line number Diff line number Diff line
package at.bitfire.davdroid

import android.net.DnsResolver
import android.os.Build
import androidx.annotation.RequiresApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
import org.xbill.DNS.Message
import org.xbill.DNS.Resolver
import org.xbill.DNS.ResolverListener
import org.xbill.DNS.TSIG
import java.util.*
import java.util.concurrent.CompletableFuture

@RequiresApi(Build.VERSION_CODES.Q)
class Android10Resolver: Resolver {

    private val executor = Dispatchers.IO.asExecutor()
    private val resolver = DnsResolver.getInstance()


    override fun send(query: Message): Message {
        val future = CompletableFuture<Message>()

        resolver.rawQuery(null, query.toWire(), DnsResolver.FLAG_EMPTY, executor, null, object: DnsResolver.Callback<ByteArray> {
            override fun onAnswer(rawAnswer: ByteArray, rcode: Int) {
                future.complete(Message((rawAnswer)))
            }

            override fun onError(error: DnsResolver.DnsException) {
                future.completeExceptionally(error)
            }
        })

        return future.get()
    }

    override fun sendAsync(query: Message, listener: ResolverListener): Any {
        val id = UUID.randomUUID()

        resolver.rawQuery(null, query.toWire(), DnsResolver.FLAG_EMPTY, executor, null, object: DnsResolver.Callback<ByteArray> {
            override fun onAnswer(rawAnswer: ByteArray, rcode: Int) {
                listener.receiveMessage(id, Message(rawAnswer))
            }

            override fun onError(error: DnsResolver.DnsException) {
                listener.handleException(id, error)
            }
        })

        return id
    }


    override fun setPort(port: Int) {
        // not applicable
    }

    override fun setTCP(flag: Boolean) {
        // not applicable
    }

    override fun setIgnoreTruncation(flag: Boolean) {
        // not applicable
    }

    override fun setEDNS(level: Int) {
        // not applicable
    }

    override fun setEDNS(level: Int, payloadSize: Int, flags: Int, options: MutableList<Any?>?) {
        // not applicable
    }

    override fun setTSIGKey(key: TSIG?) {
        // not applicable
    }

    override fun setTimeout(secs: Int, msecs: Int) {
        // not applicable
    }

    override fun setTimeout(secs: Int) {
        // not applicable
    }

}
 No newline at end of file
+35 −16
Original line number Diff line number Diff line
@@ -9,7 +9,6 @@
package at.bitfire.davdroid

import android.accounts.Account
import android.annotation.TargetApi
import android.content.ContentResolver
import android.content.Context
import android.net.ConnectivityManager
@@ -25,7 +24,9 @@ import okhttp3.HttpUrl
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaType
import org.xbill.DNS.*
import java.net.InetAddress
import java.util.*
import kotlin.collections.LinkedHashSet

/**
 * Some WebDAV and related network utility methods
@@ -61,26 +62,44 @@ object DavUtils {


    fun prepareLookup(context: Context, lookup: Lookup) {
        @TargetApi(Build.VERSION_CODES.O)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        if (Build.VERSION.SDK_INT >= 29) {
            /* Since Android 10, there's a native DnsResolver API that allows to send SRV queries without
               knowing which DNS serv0ers have to be used. DNS over TLS is now also supported. */
            Logger.log.fine("Using Android 10+ DnsResolver")
            lookup.setResolver(Android10Resolver())

        } else if (Build.VERSION.SDK_INT >= 26) {
            /* Since Android 8, the system properties net.dns1, net.dns2, ... are not available anymore.
               The current version of dnsjava relies on these properties to find the default name servers,
               so we have to add the servers explicitly (fortunately, there's an Android API to
               get the active DNS servers). */
               get the DNS servers of the network connections). */
           val dnsServers = LinkedList<InetAddress>()

            val connectivity = context.getSystemService<ConnectivityManager>()!!
            val activeLink = connectivity.getLinkProperties(connectivity.activeNetwork)
            if (activeLink != null) {
                // get DNS servers of active network link and set them for dnsjava so that it can send SRV queries
                val simpleResolvers = activeLink.dnsServers.map {
                    Logger.log.fine("Using DNS server ${it.hostAddress}")
                    val resolver = SimpleResolver()
                    resolver.setAddress(it)
                    resolver
            connectivity.allNetworks.forEach { network ->
                val active = connectivity.getNetworkInfo(network)?.isConnected ?: false
                connectivity.getLinkProperties(network)?.let { link ->
                    if (active)
                        // active connection, insert at top of list
                        dnsServers.addAll(0, link.dnsServers)
                    else
                        // inactive connection, insert at end of list
                        dnsServers.addAll(link.dnsServers)
                }
            }

            // fallback: add Quad9 DNS (9.9.9.9) in case that other DNS works
            dnsServers.add(InetAddress.getByAddress(byteArrayOf(9,9,9,9)))

            val uniqueDnsServers = LinkedHashSet<InetAddress>(dnsServers)
            val simpleResolvers = uniqueDnsServers.map { dns ->
                Logger.log.fine("Adding DNS server ${dns.hostAddress}")
                SimpleResolver().apply {
                    setAddress(dns)
                }
            }
            val resolver = ExtendedResolver(simpleResolvers.toTypedArray())
            lookup.setResolver(resolver)
            } else
                Logger.log.severe("Couldn't determine DNS servers, dnsjava queries (SRV/TXT records) won't work")
        }
    }

+2 −0
Original line number Diff line number Diff line
@@ -347,9 +347,11 @@ class DavResourceFinder(

        val query = "_${service.wellKnownName}s._tcp.$domain"
        log.fine("Looking up SRV records for $query")

        val srvLookup = Lookup(query, Type.SRV)
        DavUtils.prepareLookup(context, srvLookup)
        val srv = DavUtils.selectSRVRecord(srvLookup.run().orEmpty())

        if (srv != null) {
            // choose SRV record to use (query may return multiple SRV records)
            scheme = "https"