Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 20 additions & 18 deletions library/src/main/java/com/nextcloud/common/DNSCache.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
* Nextcloud Android Library
*
* SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-License-Identifier: MIT
*/
package com.nextcloud.common

import android.os.Build
import androidx.annotation.VisibleForTesting
import com.nextcloud.android.lib.core.Clock
import com.nextcloud.android.lib.core.ClockImpl
Expand All @@ -15,6 +17,7 @@ import java.net.Inet4Address
import java.net.Inet6Address
import java.net.InetAddress
import java.net.UnknownHostException
import java.util.concurrent.ConcurrentHashMap

/**
* DNS Cache which prefers IPv6 unless otherwise specified
Expand All @@ -24,12 +27,15 @@ object DNSCache {

// 30 seconds is the Java default. Let's keep it.
@VisibleForTesting
@Volatile
var ttlMillis: Long = DEFAULT_TTL

@VisibleForTesting
@Volatile
var clock: Clock = ClockImpl()

@VisibleForTesting
@Volatile
var dns: Dns = Dns.SYSTEM

data class DNSInfo(
Expand All @@ -40,49 +46,47 @@ object DNSCache {
fun isExpired(): Boolean = clock.currentTimeMillis - timestamp > ttlMillis
}

private val cache: MutableMap<String, DNSInfo> = HashMap()
private val cache: ConcurrentHashMap<String, DNSInfo> = ConcurrentHashMap()

@Throws(UnknownHostException::class)
@Synchronized
@JvmStatic
fun lookup(hostname: String): List<InetAddress> {
val entry = cache[hostname]
if (entry?.addresses?.isNotEmpty() == true && !entry.isExpired()) {
return entry.addresses
}
val preferIPV4 =
when (entry) {
null -> false
else -> entry.preferIPV4
}

val addresses = dns.lookup(hostname).toMutableList()
if (addresses.isEmpty()) {
throw UnknownHostException("Unknown host $hostname")
}
val sortedAddresses = sortAddresses(addresses, preferIPV4)

val newEntry = DNSInfo(sortedAddresses, preferIPV4)
cache[hostname] = newEntry
val preferIPV4 = entry?.preferIPV4 ?: false
val sortedAddresses = sortAddresses(addresses, preferIPV4)
cache[hostname] = DNSInfo(sortedAddresses, preferIPV4)

return sortedAddresses
}

/**
* Set IP version preference for a hostname, and re-sort addresses if needed
*/
@Synchronized
@JvmStatic
fun setIPVersionPreference(
hostname: String,
preferIPV4: Boolean
) {
val entry = cache[hostname]
if (entry != null) {
val addresses = sortAddresses(entry.addresses, preferIPV4)
cache[hostname] = DNSInfo(addresses, preferIPV4)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
cache.compute(hostname) { _, old ->
val addresses =
old?.addresses?.let {
sortAddresses(it, preferIPV4)
} ?: emptyList()
DNSInfo(addresses, preferIPV4)
}
} else {
cache[hostname] = DNSInfo(emptyList(), preferIPV4)
val addresses = cache[hostname]?.addresses?.let { sortAddresses(it, preferIPV4) } ?: emptyList()
cache[hostname] = DNSInfo(addresses, preferIPV4)
}
}

Expand All @@ -92,7 +96,6 @@ object DNSCache {
* - The first address is an IPv6 address
* - There are IPv4 addresses available too
*/
@Synchronized
@JvmStatic
fun isIPV6First(hostname: String): Boolean {
val firstV6 = cache[hostname]?.addresses?.firstOrNull() is Inet6Address
Expand All @@ -103,7 +106,6 @@ object DNSCache {
/**
* Clears the cache
*/
@Synchronized
@JvmStatic
fun clear() {
cache.clear()
Expand Down
Loading