blob: 51a871dd8a0690826fd42333a15cd7511a381f0c [file]
/*
* Copyright (C) 2024 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 android.net.ip
import android.net.MacAddress
import android.net.ip.ConnectivityPacketTracker.Dependencies
import android.os.Handler
import android.os.HandlerThread
import android.system.ErrnoException
import android.system.Os
import android.system.OsConstants.AF_UNIX
import android.system.OsConstants.SOCK_NONBLOCK
import android.system.OsConstants.SOCK_STREAM
import android.util.LocalLog
import androidx.test.filters.SmallTest
import com.android.net.module.util.HexDump
import com.android.net.module.util.InterfaceParams
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.waitForIdle
import java.io.FileDescriptor
import java.io.InterruptedIOException
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
import kotlin.test.assertEquals
import libcore.io.IoUtils
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.doReturn
import org.mockito.MockitoAnnotations
/**
* Test for ConnectivityPacketTracker.
*/
@SmallTest
@DevSdkIgnoreRunner.MonitorThreadLeak
class ConnectivityPacketTrackerTest {
companion object {
private const val TIMEOUT_MS: Long = 10000
private const val SLEEP_TIMEOUT_MS: Long = 500
private const val TEST_MAX_CAPTURE_PKT_SIZE: Int = 100
private const val TAG = "ConnectivityPacketTrackerTest"
}
private val loInterfaceParams = InterfaceParams.getByName("lo")
private val ifParams =
InterfaceParams(
"lo",
loInterfaceParams.index,
MacAddress.fromBytes(byteArrayOf(2, 3, 4, 5, 6, 7)),
loInterfaceParams.defaultMtu
)
private val writeSocket = FileDescriptor()
private val handlerThread by lazy {
HandlerThread("$TAG-handler-thread").apply { start() }
}
private val handler by lazy { Handler(handlerThread.looper) }
@Mock private lateinit var mDependencies: Dependencies
@Mock private lateinit var localLog: LocalLog
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
val readSocket = FileDescriptor()
Os.socketpair(AF_UNIX, SOCK_STREAM or SOCK_NONBLOCK, 0, writeSocket, readSocket)
doReturn(readSocket).`when`(mDependencies).createPacketReaderSocket(anyInt())
doReturn(TEST_MAX_CAPTURE_PKT_SIZE).`when`(mDependencies).maxCapturePktSize
}
@After
fun tearDown() {
IoUtils.closeQuietly(writeSocket)
handler.waitForIdle(10000)
Mockito.framework().clearInlineMocks()
handlerThread.quitSafely()
handlerThread.join()
}
@Test
fun testCapturePacket() {
val packetTracker = getConnectivityPacketTracker()
// Using scapy to generate ARP request packet:
// eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06")
// arp = ARP()
// pkt = eth/arp
val arpPkt = """
010203040506000102030405080600010800060400015c857e3c74e1c0a8012200000000000000000000
""".replace("\\s+".toRegex(), "").trim().uppercase()
val arpPktByteArray = HexDump.hexStringToByteArray(arpPkt)
assertEquals(0, getCapturePacketTypeCount(packetTracker))
assertEquals(0, getMatchedPacketCount(packetTracker, arpPkt))
// start capture packet
setCapture(packetTracker, true)
for (i in 1..5) {
pretendPacketReceive(arpPktByteArray)
Thread.sleep(SLEEP_TIMEOUT_MS)
}
assertEquals(1, getCapturePacketTypeCount(packetTracker))
assertEquals(5, getMatchedPacketCount(packetTracker, arpPkt))
// stop capture packet
setCapture(packetTracker, false)
assertEquals(0, getCapturePacketTypeCount(packetTracker))
assertEquals(0, getMatchedPacketCount(packetTracker, arpPkt))
}
@Test
fun testMaxCapturePacketSize() {
doReturn(3).`when`(mDependencies).maxCapturePktSize
val packetTracker = getConnectivityPacketTracker(mDependencies)
// Using scapy to generate ARP request packet:
// eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06")
// arp = ARP()
// pkt = eth/arp
val arpPkt = """
010203040506000102030405080600010800060400015c857e3c74e1c0a8012200000000000000000000
""".replace("\\s+".toRegex(), "").trim().uppercase()
val arpPktByteArray = HexDump.hexStringToByteArray(arpPkt)
// start capture packet
setCapture(packetTracker, true)
val pktCnt = 5
val pktList = ArrayList<String>()
for (i in 0..<pktCnt) {
// modify the original packet's last byte
val modPkt = arpPktByteArray.copyOf()
modPkt[modPkt.size - 1] = i.toByte()
pretendPacketReceive(modPkt)
pktList.add(HexDump.toHexString(modPkt))
Thread.sleep(SLEEP_TIMEOUT_MS)
}
// The old packets are evicted due to LruCache size
pktList.take(2).forEach {
assertEquals(0, getMatchedPacketCount(packetTracker, it))
}
pktList.drop(2).forEach {
assertEquals(1, getMatchedPacketCount(packetTracker, it))
}
assertEquals(mDependencies.maxCapturePktSize, getCapturePacketTypeCount(packetTracker))
}
@Throws(InterruptedIOException::class, ErrnoException::class)
private fun pretendPacketReceive(packet: ByteArray) {
Os.write(writeSocket, packet, 0, packet.size)
}
private fun getConnectivityPacketTracker(
dependencies: Dependencies = mDependencies
): ConnectivityPacketTracker {
val result = CompletableFuture<ConnectivityPacketTracker>()
handler.post {
try {
val tracker = ConnectivityPacketTracker(handler, ifParams, localLog, dependencies)
tracker.start(TAG)
result.complete(tracker)
} catch (e: Exception) {
result.completeExceptionally(e)
}
}
return result.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
}
private fun setCapture(
packetTracker: ConnectivityPacketTracker,
isCapturing: Boolean
) {
val result = CompletableFuture<Unit>()
handler.post {
try {
packetTracker.setCapture(isCapturing)
result.complete(Unit)
} catch (e: Exception) {
result.completeExceptionally(e)
}
}
result.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
}
private fun getMatchedPacketCount(
packetTracker: ConnectivityPacketTracker,
packet: String
): Int {
val result = CompletableFuture<Int>()
handler.post {
try {
result.complete(packetTracker.getMatchedPacketCount(packet))
} catch (e: Exception) {
result.completeExceptionally(e)
}
}
return result.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
}
private fun getCapturePacketTypeCount(
packetTracker: ConnectivityPacketTracker
): Int {
val result = CompletableFuture<Int>()
handler.post {
try {
val totalCnt = packetTracker.capturePacketTypeCount
result.complete(totalCnt)
} catch (e: Exception) {
result.completeExceptionally(e)
}
}
return result.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
}
}