Merge "Revert "[ST03] Add test dns server for integration tests""

This commit is contained in:
Treehugger Robot
2022-11-16 05:13:03 +00:00
committed by Gerrit Code Review
3 changed files with 0 additions and 356 deletions

View File

@@ -1,65 +0,0 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.testutils
import android.net.DnsResolver.CLASS_IN
import com.android.net.module.util.DnsPacket
import com.android.net.module.util.DnsPacket.ANSECTION
import java.net.InetAddress
import java.util.concurrent.ConcurrentHashMap
const val DEFAULT_TTL_S = 5L
/**
* Helper class to store the mapping of DNS queries.
*
* DnsAnswerProvider is built atop a ConcurrentHashMap and as such it provides the same
* guarantees as ConcurrentHashMap between writing and reading elements. Specifically :
* - Setting an answer happens-before reading the same answer.
* - Callers can read and write concurrently from DnsAnswerProvider and expect no
* ConcurrentModificationException.
* Freshness of the answers depends on ordering of the threads ; if callers need a
* freshness guarantee, they need to provide the happens-before relationship from a
* write that they want to observe to the read that they need to be observed.
*/
class DnsAnswerProvider {
private val mDnsKeyToRecords = ConcurrentHashMap<String, List<DnsPacket.DnsRecord>>()
/**
* Get answer for the specified hostname.
*
* @param query the target hostname.
* @param type type of record, could be A or AAAA.
*
* @return list of [DnsPacket.DnsRecord] associated to the query. Empty if no record matches.
*/
fun getAnswer(query: String, type: Int) = mDnsKeyToRecords[query]
.orEmpty().filter { it.nsType == type }
/** Set answer for the specified {@code query}.
*
* @param query the target hostname
* @param addresses [List<InetAddress>] which could be used to generate multiple A or AAAA
* RRs with the corresponding addresses.
*/
fun setAnswer(query: String, hosts: List<InetAddress>) = mDnsKeyToRecords.put(query, hosts.map {
DnsPacket.DnsRecord.makeAOrAAAARecord(ANSECTION, query, CLASS_IN, DEFAULT_TTL_S, it)
})
fun clearAnswer(query: String) = mDnsKeyToRecords.remove(query)
fun clearAll() = mDnsKeyToRecords.clear()
}

View File

@@ -1,169 +0,0 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.testutils
import android.net.Network
import android.util.Log
import com.android.internal.annotations.GuardedBy
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE
import com.android.net.module.util.DnsPacket
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.SocketAddress
import java.net.SocketException
import java.util.ArrayList
private const val TAG = "TestDnsServer"
private const val VDBG = true
@VisibleForTesting(visibility = PRIVATE)
const val MAX_BUF_SIZE = 8192
/**
* A simple implementation of Dns Server that can be bound on specific address and Network.
*
* The caller should use start() to make the server start a new thread to receive DNS queries
* on the bound address, [isAlive] to check status, and stop() for stopping.
* The server allows user to manipulate the records to be answered through
* [setAnswer] at runtime.
*
* This server runs on its own thread. Please make sure writing the query to the socket
* happens-after using [setAnswer] to guarantee the correct answer is returned. If possible,
* use [setAnswer] before calling [start] for simplicity.
*/
class TestDnsServer(network: Network, addr: InetSocketAddress) {
enum class Status {
NOT_STARTED, STARTED, STOPPED
}
@GuardedBy("thread")
private var status: Status = Status.NOT_STARTED
private val thread = ReceivingThread()
private val socket = DatagramSocket(addr).also { network.bindSocket(it) }
private val ansProvider = DnsAnswerProvider()
// The buffer to store the received packet. They are being reused for
// efficiency and it's fine because they are only ever accessed
// on the server thread in a sequential manner.
private val buffer = ByteArray(MAX_BUF_SIZE)
private val packet = DatagramPacket(buffer, buffer.size)
fun setAnswer(hostname: String, answer: List<InetAddress>) =
ansProvider.setAnswer(hostname, answer)
private fun processPacket() {
// Blocking read and try construct a DnsQueryPacket object.
socket.receive(packet)
val q = DnsQueryPacket(packet.data)
handleDnsQuery(q, packet.socketAddress)
}
// TODO: Add support to reply some error with a DNS reply packet with failure RCODE.
private fun handleDnsQuery(q: DnsQueryPacket, src: SocketAddress) {
val queryRecords = q.queryRecords
if (queryRecords.size != 1) {
throw IllegalArgumentException(
"Expected one dns query record but got ${queryRecords.size}"
)
}
val answerRecords = queryRecords[0].let { ansProvider.getAnswer(it.dName, it.nsType) }
if (VDBG) {
Log.v(TAG, "handleDnsPacket: " +
queryRecords.map { "${it.dName},${it.nsType}" }.joinToString() +
" ansCount=${answerRecords.size} socketAddress=$src")
}
val bytes = q.getAnswerPacket(answerRecords).bytes
val reply = DatagramPacket(bytes, bytes.size, src)
socket.send(reply)
}
fun start() {
synchronized(thread) {
if (status != Status.NOT_STARTED) {
throw IllegalStateException("unexpected status: $status")
}
thread.start()
status = Status.STARTED
}
}
fun stop() {
synchronized(thread) {
if (status != Status.STARTED) {
throw IllegalStateException("unexpected status: $status")
}
socket.close()
thread.interrupt()
thread.join()
status = Status.STOPPED
}
}
val isAlive get() = thread.isAlive
val port get() = socket.localPort
inner class ReceivingThread : Thread() {
override fun run() {
Log.i(TAG, "starting addr={${socket.localSocketAddress}}")
while (!interrupted() && !socket.isClosed) {
try {
processPacket()
} catch (e: InterruptedException) {
// The caller terminated the server, exit.
break
} catch (e: SocketException) {
// The caller terminated the server, exit.
break
}
}
Log.i(TAG, "exiting socket={$socket}")
}
}
@VisibleForTesting(visibility = PRIVATE)
class DnsQueryPacket : DnsPacket {
constructor(data: ByteArray) : super(data)
constructor(header: DnsHeader, qd: List<DnsRecord>, an: List<DnsRecord>) :
super(header, qd, an)
init {
if (mHeader.isResponse) {
throw ParseException("Not a query packet")
}
}
val queryRecords: List<DnsRecord>
get() = mRecords[QDSECTION]
fun getAnswerPacket(ar: List<DnsRecord>): DnsAnswerPacket {
// Set QR bit of flag to 1 for response packet according to RFC 1035 section 4.1.1.
val flags = 1 shl 15
val qr = ArrayList(mRecords[QDSECTION])
// Copy the query packet header id to the answer packet as RFC 1035 section 4.1.1.
val header = DnsHeader(mHeader.id, flags, qr.size, ar.size)
return DnsAnswerPacket(header, qr, ar)
}
}
class DnsAnswerPacket : DnsPacket {
constructor(header: DnsHeader, qr: List<DnsRecord>, ar: List<DnsRecord>) :
super(header, qr, ar)
@VisibleForTesting(visibility = PRIVATE)
constructor(bytes: ByteArray) : super(bytes)
}
}