diff --git a/okhttp-dnsoverhttps/src/main/kotlin/okhttp3/dnsoverhttps/DnsOverHttps.kt b/okhttp-dnsoverhttps/src/main/kotlin/okhttp3/dnsoverhttps/DnsOverHttps.kt index 78120e32fc32..e78d50dd40dc 100644 --- a/okhttp-dnsoverhttps/src/main/kotlin/okhttp3/dnsoverhttps/DnsOverHttps.kt +++ b/okhttp-dnsoverhttps/src/main/kotlin/okhttp3/dnsoverhttps/DnsOverHttps.kt @@ -15,28 +15,23 @@ */ package okhttp3.dnsoverhttps -import java.io.IOException import java.net.InetAddress import java.net.UnknownHostException -import java.util.concurrent.CountDownLatch import okhttp3.Call -import okhttp3.Callback import okhttp3.Dns import okhttp3.HttpUrl import okhttp3.MediaType import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request -import okhttp3.Response import okhttp3.dnsoverhttps.internal.DnsMessage import okhttp3.dnsoverhttps.internal.DnsOverHttpsCall import okhttp3.dnsoverhttps.internal.QueryRequestBody -import okhttp3.dnsoverhttps.internal.ResourceRecord import okhttp3.dnsoverhttps.internal.TYPE_A import okhttp3.dnsoverhttps.internal.TYPE_AAAA import okhttp3.dnsoverhttps.internal.TYPE_HTTPS import okhttp3.dnsoverhttps.internal.asQueryParameter -import okhttp3.dnsoverhttps.internal.decodeResponse +import okhttp3.internal.dns.execute import okhttp3.internal.publicsuffix.PublicSuffixDatabase /** @@ -96,101 +91,21 @@ class DnsOverHttps internal constructor( @Throws(UnknownHostException::class) override fun lookup(hostname: String): List { - val validationException = validate(hostname) - if (validationException != null) throw validationException - - val calls = callsList(hostname, inetAddressesOnly = true) - - val failures = ArrayList(3) - val results = ArrayList(5) - executeRequests(calls, results, failures) - - return results.ifEmpty { - throwBestFailure(hostname, failures) - } - } - - private fun executeRequests( - networkRequests: List, - responses: MutableList, - failures: MutableList, - ) { - val latch = CountDownLatch(networkRequests.size) - - for (call in networkRequests) { - call.enqueue( - object : Callback { - override fun onFailure( - call: Call, - e: IOException, - ) { - synchronized(failures) { - failures.add(e) - } - latch.countDown() - } - - override fun onResponse( - call: Call, - response: Response, - ) { - processResponse(response, responses, failures) - latch.countDown() - } - }, + val withoutServiceMetadata = + DnsOverHttps( + client = client, + url = url, + includeIPv6 = includeIPv6, + includeServiceMetadata = false, + post = post, + resolvePrivateAddresses = resolvePrivateAddresses, + resolvePublicAddresses = resolvePublicAddresses, ) - } - - try { - latch.await() - } catch (e: InterruptedException) { - failures.add(e) - } - } - - private fun processResponse( - response: Response, - results: MutableList, - failures: MutableList, - ) { - try { - val addresses = - decodeResponse(response) - .filterIsInstance() - .map { it.address } - synchronized(results) { - results.addAll(addresses) - } - } catch (e: IOException) { - synchronized(failures) { - failures.add(e) - } - } - } - - @Throws(UnknownHostException::class) - private fun throwBestFailure( - hostname: String, - failures: List, - ): List { - if (failures.isEmpty()) { - throw UnknownHostException(hostname) - } - - val failure = failures[0] - - if (failure is UnknownHostException) { - throw failure - } - - val unknownHostException = UnknownHostException(hostname) - unknownHostException.initCause(failure) - - for (i in 1 until failures.size) { - unknownHostException.addSuppressed(failures[i]) - } - - throw unknownHostException + val call = withoutServiceMetadata.newCall(Dns.Request(hostname)) + val records = call.execute() + return records + .filterIsInstance() + .map { it.address } } internal fun createCall( diff --git a/okhttp-dnsoverhttps/src/test/java/okhttp3/dnsoverhttps/DnsOverHttpsTest.kt b/okhttp-dnsoverhttps/src/test/java/okhttp3/dnsoverhttps/DnsOverHttpsTest.kt index d32de35bf55b..968b2cb5d890 100644 --- a/okhttp-dnsoverhttps/src/test/java/okhttp3/dnsoverhttps/DnsOverHttpsTest.kt +++ b/okhttp-dnsoverhttps/src/test/java/okhttp3/dnsoverhttps/DnsOverHttpsTest.kt @@ -85,7 +85,7 @@ import org.junit.jupiter.api.extension.RegisterExtension @Tag("Slowish") @Burst class DnsOverHttpsTest( - private val entryPoint: EntryPoint = EntryPoint.NewCall, + private val entryPoint: EntryPoint = EntryPoint.Lookup, ) { @RegisterExtension val platform = PlatformRule() @@ -180,7 +180,32 @@ class DnsOverHttpsTest( } @Test - fun failure() { + fun lookupDoesNotRequestServiceMetadata() { + assumeTrue(entryPoint == EntryPoint.Lookup) + + server["lysine.dev"] = + listOf( + InetAddress.getByName("10.20.30.40"), + InetAddress.getByName("1:2::3:4"), + ) + dns = buildLocalhost(bootstrapClient, includeIPv6 = true, includeServiceMetadata = true) + val result = dns(entryPoint, "lysine.dev") + assertThat(result).containsExactly( + address("1:2::3:4"), + address("10.20.30.40"), + ) + + val (_, dnsRequest1) = server.takeRequest() as DnsOverHttpsRequest + assertThat(dnsRequest1).isEqualTo(queryRequest("lysine.dev", TYPE_AAAA)) + + val (_, dnsRequest2) = server.takeRequest() as DnsOverHttpsRequest + assertThat(dnsRequest2).isEqualTo(queryRequest("lysine.dev", TYPE_A)) + + assertThat(server.pollRequest()).isNull() + } + + @Test + fun failsBecauseNoRecords() { assertFailsWith { dns(entryPoint, "lysine.dev") } @@ -190,6 +215,30 @@ class DnsOverHttpsTest( .isEqualTo(queryRequest("lysine.dev", TYPE_A)) } + @Test + fun lookupReturnsNormallyIfIpv4FailsAndIpv6Succeeds() { + assumeTrue(entryPoint == EntryPoint.Lookup) + + dns = buildLocalhost(bootstrapClient, includeIPv6 = true) + server["lysine.dev"] = listOf(InetAddress.getByName("11:22::33:44")) + server.sequenceIndexToOverride[1] = overrideResponse("") + + val results = dns(entryPoint, "lysine.dev") + assertThat(results).containsExactly(InetAddress.getByName("11:22::33:44")) + } + + @Test + fun lookupReturnsNormallyIfIpv6FailsAndIpv6Succeeds() { + assumeTrue(entryPoint == EntryPoint.Lookup) + + dns = buildLocalhost(bootstrapClient, includeIPv6 = true) + server["lysine.dev"] = listOf(InetAddress.getByName("10.20.30.40")) + server.sequenceIndexToOverride[0] = overrideResponse("") + + val results = dns(entryPoint, "lysine.dev") + assertThat(results).containsExactly(InetAddress.getByName("10.20.30.40")) + } + @Test fun resolveForbiddenPrivateDomain() { dns = buildLocalhost(bootstrapClient, resolvePrivateAddresses = false) diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/dns/-ExecuteDns.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/dns/-ExecuteDns.kt index c71f81b29f0c..4b83adab2bff 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/dns/-ExecuteDns.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/dns/-ExecuteDns.kt @@ -17,6 +17,8 @@ package okhttp3.internal.dns +import java.io.IOException +import java.net.UnknownHostException import java.util.concurrent.LinkedBlockingQueue import okhttp3.Dns import okhttp3.internal.OkHttpInternalApi @@ -24,10 +26,14 @@ import okhttp3.internal.OkHttpInternalApi /** * Call our asynchronous API synchronously. * + * This returns normally if at least one [Dns.Record.IpAddress] is returned. Partial failures are + * silently ignored. + * * This is intended to be transitional only; doing it this way needlessly blocks the caller's * thread. */ @OkHttpInternalApi +@Throws(UnknownHostException::class) fun Dns.Call.execute(): List { val queue = LinkedBlockingQueue>>() @@ -42,15 +48,24 @@ fun Dns.Call.execute(): List { ) { allRecords += records if (last) { - queue.put(Result.success(allRecords)) + complete() } } override fun onFailure( call: Dns.Call, - e: okio.IOException, + e: IOException, ) { - queue.put(Result.failure(e)) + complete(e) + } + + private fun complete(e: IOException? = null) { + val result = + when { + allRecords.any { it is Dns.Record.IpAddress } -> Result.success(allRecords) + else -> Result.failure(e ?: UnknownHostException()) + } + queue.put(result) } }, ) diff --git a/okhttp/src/jvmMain/java9/module-info.java b/okhttp/src/jvmMain/java9/module-info.java index be85fc417f13..bb08c78336ee 100644 --- a/okhttp/src/jvmMain/java9/module-info.java +++ b/okhttp/src/jvmMain/java9/module-info.java @@ -7,6 +7,7 @@ exports okhttp3.internal to okhttp3.logging, okhttp3.sse, okhttp3.java.net.cookiejar, okhttp3.dnsoverhttps, mockwebserver3, okhttp3.mockwebserver, mockwebserver3.junit5, okhttp3.coroutines, okhttp3.tls; exports okhttp3.internal.concurrent to mockwebserver3, okhttp3.mockwebserver; exports okhttp3.internal.connection to mockwebserver3, okhttp3.mockwebserver, okhttp3.logging; + exports okhttp3.internal.dns to okhttp3.dnsoverhttps; exports okhttp3.internal.http to okhttp3.logging, okhttp3.brotli, mockwebserver3; exports okhttp3.internal.http2 to mockwebserver3, okhttp3.mockwebserver; exports okhttp3.internal.platform to okhttp3.logging, okhttp3.java.net.cookiejar, okhttp3.dnsoverhttps, mockwebserver3, okhttp3.mockwebserver, okhttp3.tls;