From c3fc887742a369483d78f4b66c41607b58919896 Mon Sep 17 00:00:00 2001 From: Jesse Wilson Date: Wed, 12 Aug 2026 10:31:24 -0400 Subject: [PATCH] Handle unsupported AliasMode records --- .../internal/dns/-StateMachineDnsCall.kt | 40 +++++++--- .../internal/dns/StateMachineDnsCallTest.kt | 79 +++++++++++++++++++ .../internal/dns/StateMachineDnsCallTester.kt | 39 +++++---- 3 files changed, 129 insertions(+), 29 deletions(-) diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/dns/-StateMachineDnsCall.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/dns/-StateMachineDnsCall.kt index ff3a3249339c..76cbce079088 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/dns/-StateMachineDnsCall.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/dns/-StateMachineDnsCall.kt @@ -105,8 +105,8 @@ class StateMachineDnsCall( return } - val queries = - questions.map { question -> + val questionToQuery = + questions.associateWith { question -> queryFactory.newQuery(question) } @@ -114,17 +114,18 @@ class StateMachineDnsCall( State.Running( canceled = false, callback = callback, - runningQueries = queries, + runningQueries = questionToQuery.values.toList(), ) if (!state.compareAndSet(previous, next)) continue // Lost a race, retry. - for (query in queries) { + for ((question, query) in questionToQuery) { query.enqueue( callback = object : DnsQuery.Callback { override fun onResponse(dnsResponse: DnsMessage) { updateStateAndCallCallbacks( + question = question, completedQuery = query, dnsResponse = dnsResponse, ) @@ -160,6 +161,7 @@ class StateMachineDnsCall( } private fun updateStateAndCallCallbacks( + question: Question, completedQuery: DnsQuery, dnsResponse: DnsMessage, ) { @@ -177,10 +179,21 @@ class StateMachineDnsCall( ) } - val dnsRecords = - resourceRecords.map { resourceRecord -> - when (resourceRecord) { - is ResourceRecord.Https -> { + val dnsRecords: List = + when (question.type) { + TYPE_HTTPS -> { + resourceRecords.mapNotNull { resourceRecord -> + // Discard resource records that don't fit the query. + if (resourceRecord !is ResourceRecord.Https) return@mapNotNull null + + // OkHttp doesn't yet implement AliasMode resource records. If any AliasMode record is + // returned, we must ignore ALL returned resource records. + if (resourceRecord.priority == 0) { + return updateStateAndCallCallbacks( + completedQuery = completedQuery, + ) + } + Dns.Record.ServiceMetadata( hostname = resourceRecord.targetName.takeIf { it != "" } ?: request.hostname, alpnIds = @@ -196,14 +209,23 @@ class StateMachineDnsCall( echConfigList = resourceRecord.echConfigList, ) } + } + + TYPE_A, TYPE_AAAA -> { + resourceRecords.mapNotNull { resourceRecord -> + // Discard resource records that don't fit the query. + if (resourceRecord !is ResourceRecord.IpAddress) return@mapNotNull null - is ResourceRecord.IpAddress -> { Dns.Record.IpAddress( hostname = request.hostname, address = resourceRecord.address, ) } } + + else -> { + error("unexpected question type") + } } updateStateAndCallCallbacks( diff --git a/okhttp/src/commonTest/kotlin/okhttp3/internal/dns/StateMachineDnsCallTest.kt b/okhttp/src/commonTest/kotlin/okhttp3/internal/dns/StateMachineDnsCallTest.kt index 3b03495249f4..7a750eb30c77 100644 --- a/okhttp/src/commonTest/kotlin/okhttp3/internal/dns/StateMachineDnsCallTest.kt +++ b/okhttp/src/commonTest/kotlin/okhttp3/internal/dns/StateMachineDnsCallTest.kt @@ -1047,4 +1047,83 @@ class StateMachineDnsCallTest { assertThat(cache.networkCount).isEqualTo(3) assertThat(cache.hitCount).isEqualTo(0) } + + @Test + fun `alias mode records are ignored`() = + testStateMachineDnsCall { + val call = newCall(request = Dns.Request(hostname = "lysine.dev")) + call.enqueue() + + // Priority 0 means 'AliasMode'. We must ignore all SvcParams in AliasMode. + val query0 = queryFactory.takeQuery("lysine.dev", TYPE_HTTPS) + query0.respond( + ResourceRecord.Https( + timeToLive = 300, + name = "lysine.dev", + priority = 0, + alpnIds = listOf("h2"), + ), + ) + queryFactory.respondToQuery( + hostname = "lysine.dev", + type = TYPE_AAAA, + addresses = blueIpv6s, + ) + queryFactory.respondToQuery( + hostname = "lysine.dev", + type = TYPE_A, + addresses = blueIpv4s, + ) + + call.takeOnRecordsIpAddresses( + last = false, + addresses = blueIpv6s, + ) + call.takeOnRecordsIpAddresses( + last = true, + addresses = blueIpv4s, + ) + } + + @Test + fun `service mode records are ignored if any alias mode record is present`() = + testStateMachineDnsCall { + val call = newCall(request = Dns.Request(hostname = "lysine.dev")) + call.enqueue() + + // Priority 0 means 'AliasMode'. We must ignore all SvcParams in AliasMode. + val query0 = queryFactory.takeQuery("lysine.dev", TYPE_HTTPS) + query0.respond( + ResourceRecord.Https( + timeToLive = 300, + name = "lysine.dev", + priority = 0, + ), + ResourceRecord.Https( + timeToLive = 300, + name = "lysine.dev", + priority = 1, + alpnIds = listOf("h2"), + ), + ) + queryFactory.respondToQuery( + hostname = "lysine.dev", + type = TYPE_AAAA, + addresses = blueIpv6s, + ) + queryFactory.respondToQuery( + hostname = "lysine.dev", + type = TYPE_A, + addresses = blueIpv4s, + ) + + call.takeOnRecordsIpAddresses( + last = false, + addresses = blueIpv6s, + ) + call.takeOnRecordsIpAddresses( + last = true, + addresses = blueIpv4s, + ) + } } diff --git a/okhttp/src/commonTest/kotlin/okhttp3/internal/dns/StateMachineDnsCallTester.kt b/okhttp/src/commonTest/kotlin/okhttp3/internal/dns/StateMachineDnsCallTester.kt index 87770346fb41..51a62071a8c7 100644 --- a/okhttp/src/commonTest/kotlin/okhttp3/internal/dns/StateMachineDnsCallTester.kt +++ b/okhttp/src/commonTest/kotlin/okhttp3/internal/dns/StateMachineDnsCallTester.kt @@ -282,18 +282,12 @@ class StateMachineDnsCallTester internal constructor() { alpnIds: List? = null, echConfigList: ByteString? = null, ) { - callback.onResponse( - DnsMessage.response( - questions = listOf(question), - answers = - listOf( - ResourceRecord.Https( - name = question.name, - timeToLive = timeToLive.inWholeSeconds.toInt(), - alpnIds = alpnIds, - echConfigList = echConfigList, - ), - ), + respond( + ResourceRecord.Https( + name = question.name, + timeToLive = timeToLive.inWholeSeconds.toInt(), + alpnIds = alpnIds, + echConfigList = echConfigList, ), ) } @@ -308,17 +302,22 @@ class StateMachineDnsCallTester internal constructor() { timeToLive: Duration = 300.seconds, addresses: List = listOf(), ) { + val resourceRecords = + addresses.map { address -> + ResourceRecord.IpAddress( + name = question.name, + timeToLive = timeToLive.inWholeSeconds.toInt(), + address = address, + ) + } + respond(*resourceRecords.toTypedArray()) + } + + fun respond(vararg resourceRecords: ResourceRecord) { callback.onResponse( DnsMessage.response( questions = listOf(question), - answers = - addresses.map { address -> - ResourceRecord.IpAddress( - name = question.name, - timeToLive = timeToLive.inWholeSeconds.toInt(), - address = address, - ) - }, + answers = resourceRecords.toList(), ), ) }