Snap for 12086388 from a2e716d90f7355141ee07415e71b37eb490274b7 to mainline-media-release Change-Id: I8d35725103120af45c97f9fcb690b172ac2a2a9f
diff --git a/Android.bp b/Android.bp index ab24060..1228079 100644 --- a/Android.bp +++ b/Android.bp
@@ -14,32 +14,6 @@ // limitations under the License. // -// The network stack can be compiled using system_current (non-finalized) SDK, or finalized system_X -// SDK. There is also a variant that uses system_current SDK and runs in the system process -// (InProcessNetworkStack). The following structure is used to create the build rules: -// -// NetworkStackAndroidLibraryDefaults <-- common defaults for android libs -// / \ -// +NetworkStackApiStableShims --> / \ <-- +NetworkStackApiCurrentShims -// +NetworkStackReleaseApiLevel / \ +NetworkStackDevApiLevel -// +jarjar apishim.api[latest].* / \ -// to apishim.* / \ -// / \ -// / \ -// / \ android libs w/ all code -// / <- +module src/ -> \ (also used in unit tests) -// / \ | -// NetworkStackApiStableLib NetworkStackApiCurrentLib <--* -// | | -// | <-- +NetworkStackAppDefaults --> | -// | (APK build params) | -// | | -// | <-- +NetworkStackReleaseApiLevel | <-- +NetworkStackDevApiLevel -// | | -// | | -// NetworkStackApiStable NetworkStack, InProcessNetworkStack, <-- APKs -// TestNetworkStack - // Common defaults to define SDK level package { default_team: "trendy_team_fwk_core_networking", @@ -56,24 +30,6 @@ enabled: true, } -// This is a placeholder comment to avoid merge conflicts -// as the above target may have different "enabled" values -// depending on the branch - -java_defaults { - name: "NetworkStackDevApiLevel", - min_sdk_version: "30", - sdk_version: "module_current", - libs: [ - "framework-configinfrastructure", - "framework-connectivity", - "framework-connectivity-t", - "framework-statsd", - "framework-tethering", - "framework-wifi", - ], -} - // Common defaults for NetworkStack integration tests, root tests and coverage tests // to keep tests always running against the same target sdk version with NetworkStack. java_defaults { @@ -264,7 +220,7 @@ name: "NetworkStackApiCurrentShims", defaults: [ "NetworkStackShimsDefaults", - "NetworkStackDevApiLevel", + "NetworkStackReleaseApiLevel", "ConnectivityNextEnableDefaults", ], static_libs: [ @@ -345,7 +301,7 @@ android_library { name: "NetworkStackApiCurrentLib", defaults: [ - "NetworkStackDevApiLevel", + "NetworkStackReleaseApiLevel", "NetworkStackAndroidLibraryDefaults", "ConnectivityNextEnableDefaults", ], @@ -486,7 +442,7 @@ name: "InProcessNetworkStack", defaults: [ "NetworkStackAppDefaults", - "NetworkStackDevApiLevel", + "NetworkStackReleaseApiLevel", "ConnectivityNextEnableDefaults", ], static_libs: ["NetworkStackApiCurrentLib"], @@ -509,7 +465,7 @@ name: "NetworkStackNextManifestBase", defaults: [ "NetworkStackAppDefaults", - "NetworkStackDevApiLevel", + "NetworkStackReleaseApiLevel", "ConnectivityNextEnableDefaults", ], static_libs: ["NetworkStackApiCurrentLib"], @@ -521,7 +477,7 @@ name: "NetworkStackNext", defaults: [ "NetworkStackAppDefaults", - "NetworkStackDevApiLevel", + "NetworkStackReleaseApiLevel", "ConnectivityNextEnableDefaults", ], static_libs: ["NetworkStackNextManifestBase"],
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml index 32d423b..22591f0 100644 --- a/res/values-el/strings.xml +++ b/res/values-el/strings.xml
@@ -22,5 +22,5 @@ <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Εμφανίζονται ειδοποιήσεις για να υποδείξουν ότι το δίκτυο διαθέτει σελίδα πληροφοριών για τον τόπο."</string> <string name="connected" msgid="4563643884927480998">"Συνδέθηκε"</string> <string name="tap_for_info" msgid="6849746325626883711">"Συνδέθηκε/Πατήστε για προβολή του ιστοτόπου"</string> - <string name="application_label" msgid="1322847171305285454">"Διαχειριστής δικτύου"</string> + <string name="application_label" msgid="1322847171305285454">"Διαχείριση δικτύου"</string> </resources>
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java index f6c6ac7..dd55165 100644 --- a/src/android/net/apf/ApfFilter.java +++ b/src/android/net/apf/ApfFilter.java
@@ -265,7 +265,6 @@ private static final boolean DBG = true; private static final boolean VDBG = false; - private final int mApfVersionSupported; private final int mApfRamSize; private final int mMaximumApfProgramSize; private final int mInstallableProgramSizeClamp; @@ -274,6 +273,8 @@ private final TokenBucket mTokenBucket; @VisibleForTesting + public final int mApfVersionSupported; + @VisibleForTesting @NonNull public final byte[] mHardwareAddress; @VisibleForTesting @@ -611,7 +612,7 @@ // in an SSID. This is limited to APFv3 devices because this large write triggers // a crash on some older devices (b/78905546). if (hasDataAccess(mApfVersionSupported)) { - byte[] zeroes = new byte[mMaximumApfProgramSize]; + byte[] zeroes = new byte[mApfRamSize]; if (!mIpClientCallback.installPacketFilter(zeroes)) { sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE); }
diff --git a/tests/unit/src/android/net/apf/ApfFilterTest.kt b/tests/unit/src/android/net/apf/ApfFilterTest.kt new file mode 100644 index 0000000..d9bc6ab --- /dev/null +++ b/tests/unit/src/android/net/apf/ApfFilterTest.kt
@@ -0,0 +1,1195 @@ +/* + * 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.apf + +import android.content.Context +import android.net.LinkAddress +import android.net.LinkProperties +import android.net.MacAddress +import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_REQUEST_REPLIED +import android.net.apf.ApfCounterTracker.Counter.DROPPED_ETHERTYPE_NOT_ALLOWED +import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_NON_DHCP4 +import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_INVALID +import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_OTHER_HOST +import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_REPLIED_NON_DAD +import android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_REQUEST +import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4 +import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4_FROM_DHCPV4_SERVER +import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_ICMP +import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_DAD +import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_ADDRESS +import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_SLLA_OPTION +import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_TENTATIVE +import android.net.apf.ApfFilter.Dependencies +import android.net.apf.ApfTestHelpers.Companion.verifyProgramRun +import android.net.apf.BaseApfGenerator.APF_VERSION_3 +import android.net.apf.BaseApfGenerator.APF_VERSION_6 +import android.net.ip.IpClient.IpClientCallbacksWrapper +import android.os.Build +import android.system.OsConstants.IFA_F_TENTATIVE +import androidx.test.filters.SmallTest +import com.android.internal.annotations.GuardedBy +import com.android.net.module.util.HexDump +import com.android.net.module.util.InterfaceParams +import com.android.net.module.util.NetworkStackConstants.ARP_ETHER_IPV4_LEN +import com.android.net.module.util.NetworkStackConstants.ARP_REPLY +import com.android.net.module.util.NetworkStackConstants.ARP_REQUEST +import com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN +import com.android.net.module.util.NetworkStackConstants.ICMPV6_NA_HEADER_LEN +import com.android.net.module.util.NetworkStackConstants.ICMPV6_NS_HEADER_LEN +import com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN +import com.android.net.module.util.arp.ArpPacket +import com.android.networkstack.metrics.NetworkQuirkMetrics +import com.android.networkstack.packets.NeighborAdvertisement +import com.android.networkstack.packets.NeighborSolicitation +import com.android.networkstack.util.NetworkStackUtils +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo +import com.android.testutils.DevSdkIgnoreRunner +import com.android.testutils.quitResources +import java.net.Inet6Address +import java.net.InetAddress +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.any +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.doAnswer +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations +import org.mockito.invocation.InvocationOnMock + +/** + * Test for APF filter. + */ +@DevSdkIgnoreRunner.MonitorThreadLeak +@RunWith(DevSdkIgnoreRunner::class) +@SmallTest +class ApfFilterTest { + companion object { + private const val THREAD_QUIT_MAX_RETRY_COUNT = 3 + } + + @get:Rule + val ignoreRule = DevSdkIgnoreRule() + + @Mock + private lateinit var context: Context + + @Mock private lateinit var metrics: NetworkQuirkMetrics + + @Mock private lateinit var dependencies: Dependencies + + @Mock private lateinit var ipClientCallback: IpClientCallbacksWrapper + + @GuardedBy("mApfFilterCreated") + private val mApfFilterCreated = ArrayList<AndroidPacketFilter>() + 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 hostIpv4Address = byteArrayOf(10, 0, 0, 1) + private val senderIpv4Address = byteArrayOf(10, 0, 0, 2) + private val arpBroadcastMacAddress = intArrayOf(0xff, 0xff, 0xff, 0xff, 0xff, 0xff) + .map { it.toByte() }.toByteArray() + private val senderMacAddress = intArrayOf(0x02, 0x22, 0x33, 0x44, 0x55, 0x66) + .map { it.toByte() }.toByteArray() + private val senderIpv6Address = + // 2001::200:1a:1122:3344 + intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x02, 0, 0, 0x1a, 0x11, 0x22, 0x33, 0x44) + .map{ it.toByte() }.toByteArray() + private val hostIpv6Addresses = listOf( + // 2001::200:1a:3344:1122 + intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x02, 0, 0, 0x1a, 0x33, 0x44, 0x11, 0x22) + .map{ it.toByte() }.toByteArray(), + // 2001::100:1b:4455:6677 + intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x01, 0, 0, 0x1b, 0x44, 0x55, 0x66, 0x77) + .map{ it.toByte() }.toByteArray() + ) + private val hostIpv6TentativeAddresses = listOf( + // 2001::200:1a:1234:5678 + intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x02, 0, 0, 0x1a, 0x12, 0x34, 0x56, 0x78) + .map{ it.toByte() }.toByteArray(), + // 2001::100:1b:1234:5678 + intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x01, 0, 0, 0x1b, 0x12, 0x34, 0x56, 0x78) + .map{ it.toByte() }.toByteArray() + ) + private val hostAnycast6Addresses = listOf( + // 2001::100:1b:aabb:ccdd + intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x01, 0, 0, 0x1b, 0xaa, 0xbb, 0xcc, 0xdd) + .map{ it.toByte() }.toByteArray() + ) + private val hostMulticastMacAddresses = listOf( + // 33:33:00:00:00:01 + intArrayOf(0x33, 0x33, 0, 0, 0, 1).map { it.toByte() }.toByteArray(), + // 33:33:ff:44:11:22 + intArrayOf(0x33, 0x33, 0xff, 0x44, 0x11, 0x22).map { it.toByte() }.toByteArray(), + // 33:33:ff:55:66:77 + intArrayOf(0x33, 0x33, 0xff, 0x55, 0x66, 0x77).map { it.toByte() }.toByteArray(), + // 33:33:ff:bb:cc:dd + intArrayOf(0x33, 0x33, 0xff, 0xbb, 0xcc, 0xdd).map { it.toByte() }.toByteArray(), + ) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + // mock anycast6 address from /proc/net/anycast6 + `when`(dependencies.getAnycast6Addresses(any())).thenReturn(hostAnycast6Addresses) + + // mock ether multicast mac address from /proc/net/dev_mcast + `when`(dependencies.getEtherMulticastAddresses(any())).thenReturn(hostMulticastMacAddresses) + + // mock nd traffic class from /proc/sys/net/ipv6/conf/{ifname}/ndisc_tclass + `when`(dependencies.getNdTrafficClass(any())).thenReturn(0) + doAnswer { invocation: InvocationOnMock -> + synchronized(mApfFilterCreated) { + mApfFilterCreated.add(invocation.getArgument(0)) + } + }.`when`(dependencies).onApfFilterCreated(any()) + } + + private fun shutdownApfFilters() { + quitResources(THREAD_QUIT_MAX_RETRY_COUNT, { + synchronized(mApfFilterCreated) { + val ret = ArrayList(mApfFilterCreated) + mApfFilterCreated.clear() + return@quitResources ret + } + }, { apf: AndroidPacketFilter -> + apf.shutdown() + }) + + synchronized(mApfFilterCreated) { + assertEquals( + 0, + mApfFilterCreated.size.toLong(), + "ApfFilters did not fully shutdown." + ) + } + } + + @After + fun tearDown() { + shutdownApfFilters() + Mockito.framework().clearInlineMocks() + ApfJniUtils.resetTransmittedPacketMemory() + } + + private fun getDefaultConfig(apfVersion: Int = APF_VERSION_6): ApfFilter.ApfConfiguration { + val config = ApfFilter.ApfConfiguration() + config.apfVersionSupported = apfVersion + // 4K is the highly recommended value in APFv6 for vendor + config.apfRamSize = 4096 + config.multicastFilter = false + config.ieee802_3Filter = false + config.ethTypeBlackList = IntArray(0) + config.shouldHandleArpOffload = true + config.shouldHandleNdOffload = true + return config + } + + private fun getApfFilter( + apfCfg: ApfFilter.ApfConfiguration = getDefaultConfig(APF_VERSION_6) + ): ApfFilter { + return ApfFilter( + context, + apfCfg, + ifParams, + ipClientCallback, + metrics, + dependencies + ) + } + + private fun doTestEtherTypeAllowListFilter(apfFilter: ApfFilter) { + val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + verify(ipClientCallback, times(2)).installPacketFilter(programCaptor.capture()) + val program = programCaptor.allValues.last() + + // Using scapy to generate IPv4 mDNS packet: + // eth = Ether(src="E8:9F:80:66:60:BB", dst="01:00:5E:00:00:FB") + // ip = IP(src="192.168.1.1") + // udp = UDP(sport=5353, dport=5353) + // dns = DNS(qd=DNSQR(qtype="PTR", qname="a.local")) + // p = eth/ip/udp/dns + val mdnsPkt = "01005e0000fbe89f806660bb080045000035000100004011d812c0a80101e00000f" + + "b14e914e900214d970000010000010000000000000161056c6f63616c00000c0001" + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(mdnsPkt), + PASSED_IPV4 + ) + + // Using scapy to generate RA packet: + // eth = Ether(src="E8:9F:80:66:60:BB", dst="33:33:00:00:00:01") + // ip6 = IPv6(src="fe80::1", dst="ff02::1") + // icmp6 = ICMPv6ND_RA(routerlifetime=3600, retranstimer=3600) + // p = eth/ip6/icmp6 + val raPkt = "333300000001e89f806660bb86dd6000000000103afffe800000000000000000000000" + + "000001ff0200000000000000000000000000018600600700080e100000000000000e10" + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(raPkt), + PASSED_IPV6_ICMP + ) + + // Using scapy to generate ethernet packet with type 0x88A2: + // p = Ether(type=0x88A2)/Raw(load="01") + val ethPkt = "ffffffffffff047bcb463fb588a23031" + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(ethPkt), + DROPPED_ETHERTYPE_NOT_ALLOWED + ) + } + + private fun generateNsPacket( + srcMac: ByteArray, + dstMac: ByteArray, + srcIp: ByteArray, + dstIp: ByteArray, + target: ByteArray, + ): ByteArray { + val nsPacketBuf = NeighborSolicitation.build( + MacAddress.fromBytes(srcMac), + MacAddress.fromBytes(dstMac), + InetAddress.getByAddress(srcIp) as Inet6Address, + InetAddress.getByAddress(dstIp) as Inet6Address, + InetAddress.getByAddress(target) as Inet6Address + ) + + val nsPacket = ByteArray( + ETHER_HEADER_LEN + IPV6_HEADER_LEN + ICMPV6_NS_HEADER_LEN + 8 // option length + ) + nsPacketBuf.get(nsPacket) + return nsPacket + } + + private fun generateNaPacket( + srcMac: ByteArray, + dstMac: ByteArray, + srcIp: ByteArray, + dstIp: ByteArray, + flags: Int, + target: ByteArray, + ): ByteArray { + val naPacketBuf = NeighborAdvertisement.build( + MacAddress.fromBytes(srcMac), + MacAddress.fromBytes(dstMac), + InetAddress.getByAddress(srcIp) as Inet6Address, + InetAddress.getByAddress(dstIp) as Inet6Address, + flags, + InetAddress.getByAddress(target) as Inet6Address + ) + val naPacket = ByteArray( + ETHER_HEADER_LEN + IPV6_HEADER_LEN + ICMPV6_NA_HEADER_LEN + 8 // lla option length + ) + + naPacketBuf.get(naPacket) + return naPacket + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + fun testV4EtherTypeAllowListFilter() { + val apfFilter = getApfFilter(getDefaultConfig(APF_VERSION_3)) + doTestEtherTypeAllowListFilter(apfFilter) + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + fun testV6EtherTypeAllowListFilter() { + val apfFilter = getApfFilter(getDefaultConfig(APF_VERSION_6)) + doTestEtherTypeAllowListFilter(apfFilter) + } + + @Test + fun testIPv4PacketFilterOnV6OnlyNetwork() { + val apfFilter = getApfFilter() + apfFilter.updateClatInterfaceState(true) + val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture()) + val program = programCaptor.allValues.last() + + // Using scapy to generate IPv4 mDNS packet: + // eth = Ether(src="E8:9F:80:66:60:BB", dst="01:00:5E:00:00:FB") + // ip = IP(src="192.168.1.1") + // udp = UDP(sport=5353, dport=5353) + // dns = DNS(qd=DNSQR(qtype="PTR", qname="a.local")) + // p = eth/ip/udp/dns + val mdnsPkt = "01005e0000fbe89f806660bb080045000035000100004011d812c0a80101e00000f" + + "b14e914e900214d970000010000010000000000000161056c6f63616c00000c0001" + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(mdnsPkt), + DROPPED_IPV4_NON_DHCP4 + ) + + // Using scapy to generate DHCP4 offer packet: + // ether = Ether(src='00:11:22:33:44:55', dst='ff:ff:ff:ff:ff:ff') + // ip = IP(src='192.168.1.1', dst='255.255.255.255') + // udp = UDP(sport=67, dport=68) + // bootp = BOOTP(op=2, + // yiaddr='192.168.1.100', + // siaddr='192.168.1.1', + // chaddr=b'\x00\x11\x22\x33\x44\x55') + // dhcp_options = [('message-type', 'offer'), + // ('server_id', '192.168.1.1'), + // ('subnet_mask', '255.255.255.0'), + // ('router', '192.168.1.1'), + // ('lease_time', 86400), + // ('name_server', '8.8.8.8'), + // 'end'] + // dhcp = DHCP(options=dhcp_options) + // dhcp_offer_packet = ether/ip/udp/bootp/dhcp + val dhcp4Pkt = + "ffffffffffff00112233445508004500012e000100004011b815c0a80101ffffffff0043" + + "0044011a5ffc02010600000000000000000000000000c0a80164c0a80101000000000011" + + "223344550000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000638253633501023604c0" + + "a801010104ffffff000304c0a80101330400015180060408080808ff" + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(dhcp4Pkt), + PASSED_IPV4_FROM_DHCPV4_SERVER + ) + + // Using scapy to generate DHCP4 offer packet: + // eth = Ether(src="E8:9F:80:66:60:BB", dst="01:00:5E:00:00:FB") + // ip = IP(src="192.168.1.10", dst="192.168.1.20") # IPv4 + // udp = UDP(sport=12345, dport=53) + // dns = DNS(qd=DNSQR(qtype="PTR", qname="a.local")) + // pkt = eth / ip / udp / dns + // fragments = fragment(pkt, fragsize=30) + // fragments[1] + val fragmentedUdpPkt = + "01005e0000fbe89f806660bb08004500001d000100034011f75dc0a8010ac0a8" + + "01146f63616c00000c0001" + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(fragmentedUdpPkt), + DROPPED_IPV4_NON_DHCP4 + ) + } + + // The APFv6 code path is only turned on in V+ + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @Test + fun testArpTransmit() { + val apfFilter = getApfFilter() + verify(ipClientCallback, times(2)).installPacketFilter(any()) + val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) + val lp = LinkProperties() + lp.addLinkAddress(linkAddress) + apfFilter.setLinkProperties(lp) + val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture()) + val program = programCaptor.value + val receivedArpPacketBuf = ArpPacket.buildArpPacket( + arpBroadcastMacAddress, + senderMacAddress, + hostIpv4Address, + HexDump.hexStringToByteArray("000000000000"), + senderIpv4Address, + ARP_REQUEST.toShort() + ) + val receivedArpPacket = ByteArray(ARP_ETHER_IPV4_LEN) + receivedArpPacketBuf.get(receivedArpPacket) + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + receivedArpPacket, + DROPPED_ARP_REQUEST_REPLIED + ) + + val transmittedPacket = ApfJniUtils.getTransmittedPacket() + val expectedArpReplyBuf = ArpPacket.buildArpPacket( + senderMacAddress, + apfFilter.mHardwareAddress, + senderIpv4Address, + senderMacAddress, + hostIpv4Address, + ARP_REPLY.toShort() + ) + val expectedArpReplyPacket = ByteArray(ARP_ETHER_IPV4_LEN) + expectedArpReplyBuf.get(expectedArpReplyPacket) + assertContentEquals( + expectedArpReplyPacket + ByteArray(18) { 0 }, + transmittedPacket + ) + } + + @Test + fun testArpOffloadDisabled() { + val apfConfig = getDefaultConfig() + apfConfig.shouldHandleArpOffload = false + val apfFilter = getApfFilter(apfConfig) + verify(ipClientCallback, times(2)).installPacketFilter(any()) + val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) + val lp = LinkProperties() + lp.addLinkAddress(linkAddress) + apfFilter.setLinkProperties(lp) + val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture()) + val program = programCaptor.value + val receivedArpPacketBuf = ArpPacket.buildArpPacket( + arpBroadcastMacAddress, + senderMacAddress, + hostIpv4Address, + HexDump.hexStringToByteArray("000000000000"), + senderIpv4Address, + ARP_REQUEST.toShort() + ) + val receivedArpPacket = ByteArray(ARP_ETHER_IPV4_LEN) + receivedArpPacketBuf.get(receivedArpPacket) + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + receivedArpPacket, + PASSED_ARP_REQUEST + ) + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + fun testNsFilterNoIPv6() { + `when`(dependencies.getAnycast6Addresses(any())).thenReturn(listOf()) + val apfFilter = getApfFilter() + // validate NS packet check when there is no IPv6 address + val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + verify(ipClientCallback, times(2)).installPacketFilter(programCaptor.capture()) + val program = programCaptor.allValues.last() + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // pkt = eth/ip6/icmp6 + val nsPkt = "01020304050600010203040586DD6000000000183AFF200100000000000" + + "00200001A1122334420010000000000000200001A334411228700452900" + + "00000020010000000000000200001A33441122" + // when there is no IPv6 addresses -> pass NS packet + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(nsPkt), + PASSED_IPV6_NS_NO_ADDRESS + ) + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + fun testNsFilter() { + val apfFilter = getApfFilter() + verify(ipClientCallback, times(2)).installPacketFilter(any()) + + val lp = LinkProperties() + for (addr in hostIpv6Addresses) { + lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) + } + + for (addr in hostIpv6TentativeAddresses) { + lp.addLinkAddress( + LinkAddress( + InetAddress.getByAddress(addr), + 64, + IFA_F_TENTATIVE, + 0 + ) + ) + } + + apfFilter.setLinkProperties(lp) + verify(ipClientCallback, times(3)).installPacketFilter(any()) + apfFilter.updateClatInterfaceState(true) + val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + verify(ipClientCallback, times(4)).installPacketFilter(programCaptor.capture()) + val program = programCaptor.value + + // validate Ethernet dst address check + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="00:05:04:03:02:01") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptDstLLAddr(lladdr="00:01:02:03:04:05") + // pkt = eth/ip6/icmp6/icmp6_opt + val nonHostDstMacNsPkt = + "00050403020100010203040586DD6000000000203AFF2001000000000000" + + "0200001A1122334420010000000000000200001A3344112287003D170000" + + "000020010000000000000200001A334411220201000102030405" + // invalid unicast ether dst -> pass + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(nonHostDstMacNsPkt), + DROPPED_IPV6_NS_OTHER_HOST + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="33:33:ff:03:02:01") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptDstLLAddr(lladdr="00:01:02:03:04:05") + // pkt = eth/ip6/icmp6/icmp6_opt + val nonMcastDstMacNsPkt = "3333FF03020100010203040586DD6000000000203AFF20010000000000" + + "000200001A1122334420010000000000000200001A3344112287003D17" + + "0000000020010000000000000200001A334411220201000102030405" + // mcast dst mac is not one of solicited mcast mac derived from one of device's ip -> pass + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(nonMcastDstMacNsPkt), + DROPPED_IPV6_NS_OTHER_HOST + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="33:33:ff:44:11:22") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") + // pkt = eth/ip6/icmp6/icmp6_opt + val hostMcastDstMacNsPkt = + "3333FF44112200010203040586DD6000000000203AFF20010000000000" + + "000200001A1122334420010000000000000200001A3344112287003E17" + + "0000000020010000000000000200001A334411220101000102030405" + // mcast dst mac is one of solicited mcast mac derived from one of device's ip + // -> drop and replied + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(hostMcastDstMacNsPkt), + DROPPED_IPV6_NS_REPLIED_NON_DAD + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="FF:FF:FF:FF:FF:FF") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") + // pkt = eth/ip6/icmp6/icmp6_opt + val broadcastNsPkt = + "FFFFFFFFFFFF00010203040586DD6000000000203AFF200100000000000002000" + + "01A1122334420010000000000000200001A3344112287003E1700000000200100" + + "00000000000200001A334411220101000102030405" + // mcast dst mac is broadcast address -> drop and replied + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(broadcastNsPkt), + DROPPED_IPV6_NS_REPLIED_NON_DAD + ) + + // validate IPv6 dst address check + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") + // pkt = eth/ip6/icmp6/icmp6_opt + val validHostDstIpNsPkt = + "02030405060700010203040586DD6000000000203AFF200100000000000" + + "00200001A1122334420010000000000000200001A3344112287003E1700" + + "00000020010000000000000200001A334411220101000102030405" + // dst ip is one of device's ip -> drop and replied + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(validHostDstIpNsPkt), + DROPPED_IPV6_NS_REPLIED_NON_DAD + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::100:1b:aabb:ccdd", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::100:1b:aabb:ccdd") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") + // pkt = eth/ip6/icmp6/icmp6_opt + val validHostAnycastDstIpNsPkt = + "02030405060700010203040586DD6000000000203AFF20010000" + + "000000000200001A1122334420010000000000000100001BAABB" + + "CCDD8700D9AE0000000020010000000000000100001BAABBCCDD" + + "0101000102030405" + // dst ip is device's anycast address -> drop and replied + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(validHostAnycastDstIpNsPkt), + DROPPED_IPV6_NS_REPLIED_NON_DAD + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:4444:5555", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") + // pkt = eth/ip6/icmp6/icmp6_opt + val nonHostUcastDstIpNsPkt = + "02030405060700010203040586DD6000000000203AFF2001000000000" + + "0000200001A1122334420010000000000000200001A444455558700E8" + + "E30000000020010000000000000200001A334411220101000102030405" + // unicast dst ip is not one of device's ip -> pass + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(nonHostUcastDstIpNsPkt), + DROPPED_IPV6_NS_OTHER_HOST + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="ff02::1:ff44:1133", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") + // pkt = eth/ip6/icmp6/icmp6_opt + val nonHostMcastDstIpNsPkt = + "02030405060700010203040586DD6000000000203AFF2001000000000" + + "0000200001A11223344FF0200000000000000000001FF441133870095" + + "1C0000000020010000000000000200001A334411220101000102030405" + // mcast dst ip is not one of solicited mcast ip derived from one of device's ip -> pass + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(nonHostMcastDstIpNsPkt), + DROPPED_IPV6_NS_OTHER_HOST + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="ff02::1:ff44:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") + // pkt = eth/ip6/icmp6/icmp6_opt + val hostMcastDstIpNsPkt = + "02030405060700010203040586DD6000000000203AFF2001000000000000" + + "0200001A11223344FF0200000000000000000001FF4411228700952D0000" + + "000020010000000000000200001A334411220101000102030405" + // mcast dst ip is one of solicited mcast ip derived from one of device's ip + // -> drop and replied + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(hostMcastDstIpNsPkt), + DROPPED_IPV6_NS_REPLIED_NON_DAD + ) + + // validate IPv6 NS payload check + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255, plen=20) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") + // pkt = eth/ip6/icmp6/icmp6_opt + val shortNsPkt = + "02030405060700010203040586DD6000000000143AFF20010000000000000200001A1" + + "122334420010000000000000200001A3344112287003B140000000020010000000000" + + "000200001A334411220101010203040506" + // payload len < 24 -> drop + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(shortNsPkt), + DROPPED_IPV6_NS_INVALID + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:4444:5555") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") + // pkt = eth/ip6/icmp6/icmp6_opt + val otherHostNsPkt = + "02030405060700010203040586DD6000000000203AFF200100000000000002000" + + "01A1122334420010000000000000200001A334411228700E5E000000000200100" + + "00000000000200001A444455550101010203040506" + // target ip is not one of device's ip -> drop + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(otherHostNsPkt), + DROPPED_IPV6_NS_OTHER_HOST + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=20) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") + // pkt = eth/ip6/icmp6/icmp6_opt + val invalidHoplimitNsPkt = + "02030405060700010203040586DD6000000000203A14200100000000000" + + "00200001A1122334420010000000000000200001A3344112287003B1400" + + "00000020010000000000000200001A334411220101010203040506" + // hoplimit is not 255 -> drop + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(invalidHoplimitNsPkt), + DROPPED_IPV6_NS_INVALID + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122", code=5) + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") + // pkt = eth/ip6/icmp6/icmp6_opt + val invalidIcmpCodeNsPkt = + "02030405060700010203040586DD6000000000203AFF200100000000000" + + "00200001A1122334420010000000000000200001A3344112287053B0F00" + + "00000020010000000000000200001A334411220101010203040506" + // icmp6 code is not 0 -> drop + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(invalidIcmpCodeNsPkt), + DROPPED_IPV6_NS_INVALID + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:1234:5678") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") + // pkt = eth/ip6/icmp6/icmp6_opt + val tentativeTargetIpNsPkt = + "02030405060700010203040586DD6000000000203AFF200100000000" + + "00000200001A1122334420010000000000000200001A334411228700" + + "16CE0000000020010000000000000200001A123456780101010203040506" + // target ip is one of tentative address -> pass + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(tentativeTargetIpNsPkt), + PASSED_IPV6_NS_TENTATIVE + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1c:2255:6666") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") + // pkt = eth/ip6/icmp6/icmp6_opt + val invalidTargetIpNsPkt = + "02030405060700010203040586DD6000000000203AFF200100000000000" + + "00200001A1122334420010000000000000200001A334411228700F6BC00" + + "00000020010000000000000200001C225566660101010203040506" + // target ip is none of {non-tentative, anycast} -> drop + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(invalidTargetIpNsPkt), + DROPPED_IPV6_NS_OTHER_HOST + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="::", dst="ff02::1:ff44:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptDstLLAddr(lladdr="02:03:04:05:06:07") + // pkt = eth/ip6/icmp6/icmp6_opt + val dadNsPkt = + "02030405060700010203040586DD6000000000203AFF000000000000000000000000000" + + "00000FF0200000000000000000001FF4411228700F4A800000000200100000000000002" + + "00001A334411220201020304050607" + // DAD NS request -> pass + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(dadNsPkt), + PASSED_IPV6_NS_DAD + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // pkt = eth/ip6/icmp6 + val noOptionNsPkt = + "02030405060700010203040586DD6000000000183AFF2001000000000000020000" + + "1A1122334420010000000000000200001A33441122870045290000000020010000" + + "000000000200001A33441122" + // payload len < 32 -> pass + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(noOptionNsPkt), + PASSED_IPV6_NS_NO_SLLA_OPTION + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="ff01::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") + // pkt = eth/ip6/icmp6/icmp6_opt + val nonDadMcastSrcIpPkt = + "02030405060700010203040586DD6000000000203AFFFF01000000000000" + + "0200001A1122334420010000000000000200001A3344112287005C130000" + + "000020010000000000000200001A334411220101010203040506" + // non-DAD src IPv6 is FF::/8 -> drop + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(nonDadMcastSrcIpPkt), + DROPPED_IPV6_NS_INVALID + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="0001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") + // pkt = eth/ip6/icmp6/icmp6_opt + val nonDadLoopbackSrcIpPkt = + "02030405060700010203040586DD6000000000203AFF0001000000000" + + "0000200001A1122334420010000000000000200001A3344112287005B" + + "140000000020010000000000000200001A334411220101010203040506" + // non-DAD src IPv6 is 00::/8 -> drop + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(nonDadLoopbackSrcIpPkt), + DROPPED_IPV6_NS_INVALID + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt1 = ICMPv6NDOptDstLLAddr(lladdr="01:02:03:04:05:06") + // icmp6_opt2 = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") + // pkt = eth/ip6/icmp6/icmp6_opt1/icmp6_opt2 + val sllaNotFirstOptionNsPkt = + "02030405060700010203040586DD6000000000283AFF200100000000" + + "00000200001A1122334420010000000000000200001A334411228700" + + "2FFF0000000020010000000000000200001A33441122020101020304" + + "05060101010203040506" + // non-DAD with multiple options, SLLA in 2nd option -> pass + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(sllaNotFirstOptionNsPkt), + PASSED_IPV6_NS_NO_SLLA_OPTION + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptDstLLAddr(lladdr="01:02:03:04:05:06") + // pkt = eth/ip6/icmp6/icmp6_opt + val noSllaOptionNsPkt = + "02030405060700010203040586DD6000000000203AFF200100000000000002" + + "00001A1122334420010000000000000200001A3344112287003A1400000000" + + "20010000000000000200001A334411220201010203040506" + // non-DAD with one option but not SLLA -> pass + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(noSllaOptionNsPkt), + PASSED_IPV6_NS_NO_SLLA_OPTION + ) + + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") + // pkt = eth/ip6/icmp6/icmp6_opt + val mcastMacSllaOptionNsPkt = + "02030405060700010203040586DD6000000000203AFF200100000000" + + "00000200001A1122334420010000000000000200001A334411228700" + + "3B140000000020010000000000000200001A33441122010101020304" + + "0506" + // non-DAD, SLLA is multicast MAC -> drop + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(mcastMacSllaOptionNsPkt), + DROPPED_IPV6_NS_INVALID + ) + } + + // The APFv6 code path is only turned on in V+ + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @Test + fun testNaTransmit() { + val apfFilter = getApfFilter() + val lp = LinkProperties() + for (addr in hostIpv6Addresses) { + lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) + } + + apfFilter.setLinkProperties(lp) + val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture()) + val program = programCaptor.allValues.last() + val validIpv6Addresses = hostIpv6Addresses + hostAnycast6Addresses + for (addr in validIpv6Addresses) { + // unicast solicited NS request + val receivedUcastNsPacket = generateNsPacket( + senderMacAddress, + apfFilter.mHardwareAddress, + senderIpv6Address, + addr, + addr + ) + + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + receivedUcastNsPacket, + DROPPED_IPV6_NS_REPLIED_NON_DAD + ) + + val transmittedUcastPacket = ApfJniUtils.getTransmittedPacket() + val expectedUcastNaPacket = generateNaPacket( + apfFilter.mHardwareAddress, + senderMacAddress, + addr, + senderIpv6Address, + 0xe0000000.toInt(), // R=1, S=1, O=1 + addr + ) + + assertContentEquals( + expectedUcastNaPacket, + transmittedUcastPacket + ) + + val solicitedMcastAddr = NetworkStackUtils.ipv6AddressToSolicitedNodeMulticast( + InetAddress.getByAddress(addr) as Inet6Address + )!! + val mcastDa = NetworkStackUtils.ipv6MulticastToEthernetMulticast(solicitedMcastAddr) + .toByteArray() + + // multicast solicited NS request + var receivedMcastNsPacket = generateNsPacket( + senderMacAddress, + mcastDa, + senderIpv6Address, + solicitedMcastAddr.address, + addr + ) + + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + receivedMcastNsPacket, + DROPPED_IPV6_NS_REPLIED_NON_DAD + ) + + val transmittedMcastPacket = ApfJniUtils.getTransmittedPacket() + val expectedMcastNaPacket = generateNaPacket( + apfFilter.mHardwareAddress, + senderMacAddress, + addr, + senderIpv6Address, + 0xe0000000.toInt(), // R=1, S=1, O=1 + addr + ) + + assertContentEquals( + expectedMcastNaPacket, + transmittedMcastPacket + ) + } + } + + // The APFv6 code path is only turned on in V+ + @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @Test + fun testNaTransmitWithTclass() { + // mock nd traffic class from /proc/sys/net/ipv6/conf/{ifname}/ndisc_tclass to 20 + `when`(dependencies.getNdTrafficClass(any())).thenReturn(20) + val apfFilter = getApfFilter() + val lp = LinkProperties() + for (addr in hostIpv6Addresses) { + lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) + } + apfFilter.setLinkProperties(lp) + val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture()) + val program = programCaptor.allValues.last() + // Using scapy to generate IPv6 NS packet: + // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") + // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="ff02::1:ff44:1122", hlim=255, tc=20) + // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") + // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") + // pkt = eth/ip6/icmp6/icmp6_opt + val hostMcastDstIpNsPkt = + "02030405060700010203040586DD6140000000203AFF2001000000000000" + + "0200001A11223344FF0200000000000000000001FF4411228700952D0000" + + "000020010000000000000200001A334411220101000102030405" + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + HexDump.hexStringToByteArray(hostMcastDstIpNsPkt), + DROPPED_IPV6_NS_REPLIED_NON_DAD + ) + + val transmitPkt = ApfJniUtils.getTransmittedPacket() + // Using scapy to generate IPv6 NA packet: + // eth = Ether(src="02:03:04:05:06:07", dst="00:01:02:03:04:05") + // ip6 = IPv6(src="2001::200:1a:3344:1122", dst="2001::200:1a:1122:3344", hlim=255, tc=20) + // icmp6 = ICMPv6ND_NA(tgt="2001::200:1a:3344:1122", R=1, S=1, O=1) + // icmp6_opt = ICMPv6NDOptDstLLAddr(lladdr="02:03:04:05:06:07") + // pkt = eth/ip6/icmp6/icmp6_opt + val expectedNaPacket = + "00010203040502030405060786DD6140000000203AFF2001000000000000020" + + "0001A3344112220010000000000000200001A1122334488005610E000000020" + + "010000000000000200001A334411220201020304050607" + assertContentEquals( + HexDump.hexStringToByteArray(expectedNaPacket), + transmitPkt + ) + } + + @Test + fun testNdOffloadDisabled() { + val apfConfig = getDefaultConfig() + apfConfig.shouldHandleNdOffload = false + val apfFilter = getApfFilter(apfConfig) + val lp = LinkProperties() + for (addr in hostIpv6Addresses) { + lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) + } + + apfFilter.setLinkProperties(lp) + val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture()) + val program = programCaptor.allValues.last() + val validIpv6Addresses = hostIpv6Addresses + hostAnycast6Addresses + for (addr in validIpv6Addresses) { + // unicast solicited NS request + val receivedUcastNsPacket = generateNsPacket( + senderMacAddress, + apfFilter.mHardwareAddress, + senderIpv6Address, + addr, + addr + ) + + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + receivedUcastNsPacket, + PASSED_IPV6_ICMP + ) + + val solicitedMcastAddr = NetworkStackUtils.ipv6AddressToSolicitedNodeMulticast( + InetAddress.getByAddress(addr) as Inet6Address + )!! + val mcastDa = NetworkStackUtils.ipv6MulticastToEthernetMulticast(solicitedMcastAddr) + .toByteArray() + + // multicast solicited NS request + var receivedMcastNsPacket = generateNsPacket( + senderMacAddress, + mcastDa, + senderIpv6Address, + solicitedMcastAddr.address, + addr + ) + + verifyProgramRun( + apfFilter.mApfVersionSupported, + program, + receivedMcastNsPacket, + PASSED_IPV6_ICMP + ) + } + } + + @Test + fun testApfProgramUpdate() { + val apfFilter = getApfFilter() + verify(ipClientCallback, times(2)).installPacketFilter(any()) + // add IPv4 address, expect to have apf program update + val lp = LinkProperties() + val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) + lp.addLinkAddress(linkAddress) + apfFilter.setLinkProperties(lp) + verify(ipClientCallback, times(3)).installPacketFilter(any()) + + // add the same IPv4 address, expect to have no apf program update + apfFilter.setLinkProperties(lp) + verify(ipClientCallback, times(3)).installPacketFilter(any()) + + // add IPv6 addresses, expect to have apf program update + for (addr in hostIpv6Addresses) { + lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) + } + + apfFilter.setLinkProperties(lp) + verify(ipClientCallback, times(4)).installPacketFilter(any()) + + // add the same IPv6 addresses, expect to have no apf program update + apfFilter.setLinkProperties(lp) + verify(ipClientCallback, times(4)).installPacketFilter(any()) + + // add more tentative IPv6 addresses, expect to have apf program update + for (addr in hostIpv6TentativeAddresses) { + lp.addLinkAddress( + LinkAddress( + InetAddress.getByAddress(addr), + 64, + IFA_F_TENTATIVE, + 0 + ) + ) + } + + apfFilter.setLinkProperties(lp) + verify(ipClientCallback, times(5)).installPacketFilter(any()) + + // add the same IPv6 addresses, expect to have no apf program update + apfFilter.setLinkProperties(lp) + verify(ipClientCallback, times(5)).installPacketFilter(any()) + } + + @Test + fun testApfFilterInitializationCleanUpTheApfMemoryRegion() { + val apfFilter = getApfFilter() + val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) + verify(ipClientCallback, times(2)).installPacketFilter(programCaptor.capture()) + val program = programCaptor.allValues.first() + assertContentEquals(ByteArray(4096) { 0 }, program) + } +}
diff --git a/tests/unit/src/android/net/apf/ApfGeneratorTest.kt b/tests/unit/src/android/net/apf/ApfGeneratorTest.kt new file mode 100644 index 0000000..1364af5 --- /dev/null +++ b/tests/unit/src/android/net/apf/ApfGeneratorTest.kt
@@ -0,0 +1,1637 @@ +/* + * Copyright (C) 2023 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.apf + +import android.net.apf.ApfCounterTracker.Counter +import android.net.apf.ApfCounterTracker.Counter.CORRUPT_DNS_PACKET +import android.net.apf.ApfCounterTracker.Counter.DROPPED_ETHERTYPE_NOT_ALLOWED +import android.net.apf.ApfCounterTracker.Counter.DROPPED_ETH_BROADCAST +import android.net.apf.ApfCounterTracker.Counter.PASSED_ALLOCATE_FAILURE +import android.net.apf.ApfCounterTracker.Counter.PASSED_ARP +import android.net.apf.ApfCounterTracker.Counter.PASSED_TRANSMIT_FAILURE +import android.net.apf.ApfCounterTracker.Counter.TOTAL_PACKETS +import android.net.apf.ApfTestHelpers.Companion.decodeCountersIntoMap +import android.net.apf.ApfTestHelpers.Companion.verifyProgramRun +import android.net.apf.ApfTestUtils.DROP +import android.net.apf.ApfTestUtils.MIN_PKT_SIZE +import android.net.apf.ApfTestUtils.PASS +import android.net.apf.ApfTestUtils.assertDrop +import android.net.apf.ApfTestUtils.assertPass +import android.net.apf.ApfTestUtils.assertVerdict +import android.net.apf.BaseApfGenerator.APF_VERSION_2 +import android.net.apf.BaseApfGenerator.APF_VERSION_3 +import android.net.apf.BaseApfGenerator.APF_VERSION_6 +import android.net.apf.BaseApfGenerator.DROP_LABEL +import android.net.apf.BaseApfGenerator.IllegalInstructionException +import android.net.apf.BaseApfGenerator.MemorySlot +import android.net.apf.BaseApfGenerator.PASS_LABEL +import android.net.apf.BaseApfGenerator.Register.R0 +import android.net.apf.BaseApfGenerator.Register.R1 +import androidx.test.filters.SmallTest +import com.android.net.module.util.HexDump +import com.android.net.module.util.Struct +import com.android.net.module.util.structs.EthernetHeader +import com.android.net.module.util.structs.Ipv4Header +import com.android.net.module.util.structs.UdpHeader +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRunner +import java.nio.ByteBuffer +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.times + +const val ETH_HLEN = 14 +const val IPV4_HLEN = 20 +const val IPPROTO_UDP = 17 + +/** + * Tests for APF generator instructions. + */ +@RunWith(DevSdkIgnoreRunner::class) +@SmallTest +class ApfGeneratorTest { + + @get:Rule val ignoreRule = DevSdkIgnoreRule() + + private val ramSize = 2048 + private val clampSize = 2048 + + private val testPacket = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) + + @Test + fun testDataInstructionMustComeFirst() { + var gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + gen.addAllocateR0() + assertFailsWith<IllegalInstructionException> { gen.addData(ByteArray(3) { 0x01 }) } + } + + @Test + fun testApfInstructionEncodingSizeCheck() { + var gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + assertFailsWith<IllegalArgumentException> { + ApfV6Generator(ByteArray(65536) { 0x01 }, APF_VERSION_6, ramSize, clampSize) + } + assertFailsWith<IllegalArgumentException> { gen.addAllocate(65536) } + assertFailsWith<IllegalArgumentException> { gen.addAllocate(-1) } + assertFailsWith<IllegalArgumentException> { gen.addDataCopy(-1, 1) } + assertFailsWith<IllegalArgumentException> { gen.addPacketCopy(-1, 1) } + assertFailsWith<IllegalArgumentException> { gen.addDataCopy(1, 256) } + assertFailsWith<IllegalArgumentException> { gen.addPacketCopy(1, 256) } + assertFailsWith<IllegalArgumentException> { gen.addDataCopy(1, -1) } + assertFailsWith<IllegalArgumentException> { gen.addPacketCopy(1, -1) } + assertFailsWith<IllegalArgumentException> { gen.addPacketCopyFromR0(256) } + assertFailsWith<IllegalArgumentException> { gen.addDataCopyFromR0(256) } + assertFailsWith<IllegalArgumentException> { gen.addPacketCopyFromR0(-1) } + assertFailsWith<IllegalArgumentException> { gen.addDataCopyFromR0(-1) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(1, 'A'.code.toByte(), 0, 0), + 256, + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(1, 'a'.code.toByte(), 0, 0), + 0x0c, + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(1, '.'.code.toByte(), 0, 0), + 0x0c, + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(0, 0), + 0xc0, + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(1, 'A'.code.toByte()), + 0xc0, + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(64) + ByteArray(64) { 'A'.code.toByte() } + byteArrayOf(0, 0), + 0xc0, + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0), + 0xc0, + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()), + 0xc0, + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(1, 'A'.code.toByte(), 0, 0), + 256, + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(1, 'a'.code.toByte(), 0, 0), + 0x0c, + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(1, '.'.code.toByte(), 0, 0), + 0x0c, + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(0, 0), + 0xc0, + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(1, 'A'.code.toByte()), + 0xc0, + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(64) + ByteArray(64) { 'A'.code.toByte() } + byteArrayOf(0, 0), + 0xc0, + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0), + 0xc0, + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()), + 0xc0, + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(1, 'a'.code.toByte(), 0, 0), + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(1, '.'.code.toByte(), 0, 0), + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(0, 0), + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(1, 'A'.code.toByte()), + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(64) + ByteArray(64) { 'A'.code.toByte() } + byteArrayOf(0, 0), + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0), + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()), + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(1, 'a'.code.toByte(), 0, 0), + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(1, '.'.code.toByte(), 0, 0), + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(0, 0), + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(1, 'A'.code.toByte()), + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(64) + ByteArray(64) { 'A'.code.toByte() } + byteArrayOf(0, 0), + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0), + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( + byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()), + ApfV4Generator.DROP_LABEL + ) } + assertFailsWith<IllegalArgumentException> { + gen.addJumpIfBytesAtR0Equal(ByteArray(2048) { 1 }, DROP_LABEL) + } + assertFailsWith<IllegalArgumentException> { + gen.addJumpIfBytesAtR0NotEqual(ByteArray(2048) { 1 }, DROP_LABEL) + } + assertFailsWith<IllegalArgumentException> { gen.addCountAndDrop(PASSED_ARP) } + assertFailsWith<IllegalArgumentException> { gen.addCountAndPass(DROPPED_ETH_BROADCAST) } + assertFailsWith<IllegalArgumentException> { + gen.addCountAndDropIfR0Equals(3, PASSED_ARP) + } + assertFailsWith<IllegalArgumentException> { + gen.addCountAndPassIfR0Equals(3, DROPPED_ETH_BROADCAST) + } + assertFailsWith<IllegalArgumentException> { + gen.addCountAndDropIfR0NotEquals(3, PASSED_ARP) + } + assertFailsWith<IllegalArgumentException> { + gen.addCountAndPassIfR0NotEquals(3, DROPPED_ETH_BROADCAST) + } + assertFailsWith<IllegalArgumentException> { + gen.addCountAndDropIfR0LessThan(3, PASSED_ARP) + } + assertFailsWith<IllegalArgumentException> { + gen.addCountAndPassIfR0LessThan(3, DROPPED_ETH_BROADCAST) + } + assertFailsWith<IllegalArgumentException> { + gen.addCountAndDropIfR0GreaterThan(3, PASSED_ARP) + } + assertFailsWith<IllegalArgumentException> { + gen.addCountAndPassIfR0GreaterThan(3, DROPPED_ETH_BROADCAST) + } + assertFailsWith<IllegalArgumentException> { + gen.addCountAndDropIfBytesAtR0NotEqual(byteArrayOf(1), PASSED_ARP) + } + assertFailsWith<IllegalArgumentException> { + gen.addCountAndDropIfBytesAtR0Equal(byteArrayOf(1), PASSED_ARP) + } + assertFailsWith<IllegalArgumentException> { + gen.addCountAndPassIfBytesAtR0Equal(byteArrayOf(1), DROPPED_ETH_BROADCAST) + } + assertFailsWith<IllegalArgumentException> { + gen.addCountAndDropIfR0AnyBitsSet(3, PASSED_ARP) + } + assertFailsWith<IllegalArgumentException> { + gen.addCountAndPassIfR0AnyBitsSet(3, DROPPED_ETH_BROADCAST) + } + assertFailsWith<IllegalArgumentException> { + gen.addCountAndDropIfR0IsOneOf(setOf(3), PASSED_ARP) + } + assertFailsWith<IllegalArgumentException> { + gen.addCountAndPassIfR0IsOneOf(setOf(3), DROPPED_ETH_BROADCAST) + } + assertFailsWith<IllegalArgumentException> { + gen.addCountAndDropIfR0IsNoneOf(setOf(3), PASSED_ARP) + } + assertFailsWith<IllegalArgumentException> { + gen.addCountAndPassIfR0IsNoneOf(setOf(3), DROPPED_ETH_BROADCAST) + } + assertFailsWith<IllegalArgumentException> { + gen.addCountAndDropIfBytesAtR0EqualsAnyOf(listOf(byteArrayOf(1)), PASSED_ARP) + } + assertFailsWith<IllegalArgumentException> { + gen.addCountAndPassIfBytesAtR0EqualsAnyOf(listOf(byteArrayOf(1)), DROPPED_ETH_BROADCAST) + } + assertFailsWith<IllegalArgumentException> { + gen.addCountAndDropIfBytesAtR0EqualsNoneOf(listOf(byteArrayOf(1)), PASSED_ARP) + } + assertFailsWith<IllegalArgumentException> { + gen.addCountAndPassIfBytesAtR0EqualsNoneOf( + listOf(byteArrayOf(1)), + DROPPED_ETH_BROADCAST + ) + } + assertFailsWith<IllegalArgumentException> { + gen.addWrite32(byteArrayOf()) + } + assertFailsWith<IllegalArgumentException> { + gen.addJumpIfOneOf(R0, setOf(), PASS_LABEL) + } + assertFailsWith<IllegalArgumentException> { + gen.addJumpIfOneOf(R0, setOf(-1, 1), PASS_LABEL) + } + assertFailsWith<IllegalArgumentException> { + gen.addJumpIfOneOf(R0, setOf(4294967296L, 1), PASS_LABEL) + } + assertFailsWith<IllegalArgumentException> { + gen.addJumpIfOneOf(R0, List(34) { (it + 1).toLong() }.toSet(), PASS_LABEL) + } + assertFailsWith<IllegalArgumentException> { + gen.addJumpIfBytesAtR0EqualsAnyOf(listOf(ByteArray(2048) { 1 }), PASS_LABEL ) + } + assertFailsWith<IllegalArgumentException> { + gen.addJumpIfBytesAtR0EqualsAnyOf( + listOf(byteArrayOf(1), byteArrayOf(1, 2)), + PASS_LABEL + ) + } + + val v4gen = ApfV4Generator(APF_VERSION_3, ramSize, clampSize) + assertFailsWith<IllegalArgumentException> { v4gen.addCountAndDrop(PASSED_ARP) } + assertFailsWith<IllegalArgumentException> { v4gen.addCountAndPass(DROPPED_ETH_BROADCAST) } + assertFailsWith<IllegalArgumentException> { + v4gen.addCountAndDropIfR0Equals(3, PASSED_ARP) + } + assertFailsWith<IllegalArgumentException> { + v4gen.addCountAndPassIfR0Equals(3, DROPPED_ETH_BROADCAST) + } + assertFailsWith<IllegalArgumentException> { + v4gen.addCountAndDropIfR0NotEquals(3, PASSED_ARP) + } + assertFailsWith<IllegalArgumentException> { + v4gen.addCountAndPassIfR0NotEquals(3, DROPPED_ETH_BROADCAST) + } + assertFailsWith<IllegalArgumentException> { + v4gen.addCountAndDropIfBytesAtR0Equal(byteArrayOf(1), PASSED_ARP) + } + assertFailsWith<IllegalArgumentException> { + v4gen.addCountAndPassIfBytesAtR0Equal(byteArrayOf(1), DROPPED_ETH_BROADCAST) + } + assertFailsWith<IllegalArgumentException> { + v4gen.addCountAndDropIfR0LessThan(3, PASSED_ARP) + } + assertFailsWith<IllegalArgumentException> { + v4gen.addCountAndPassIfR0LessThan(3, DROPPED_ETH_BROADCAST) + } + assertFailsWith<IllegalArgumentException> { + v4gen.addCountAndDropIfR0GreaterThan(3, PASSED_ARP) + } + assertFailsWith<IllegalArgumentException> { + v4gen.addCountAndPassIfR0GreaterThan(3, DROPPED_ETH_BROADCAST) + } + assertFailsWith<IllegalArgumentException> { + v4gen.addCountAndDropIfBytesAtR0NotEqual(byteArrayOf(1), PASSED_ARP) + } + assertFailsWith<IllegalArgumentException> { + v4gen.addCountAndDropIfR0AnyBitsSet(3, PASSED_ARP) + } + assertFailsWith<IllegalArgumentException> { + v4gen.addCountAndPassIfR0AnyBitsSet(3, DROPPED_ETH_BROADCAST) + } + assertFailsWith<IllegalArgumentException> { + v4gen.addCountAndDropIfR0IsOneOf(setOf(3), PASSED_ARP) + } + assertFailsWith<IllegalArgumentException> { + v4gen.addCountAndPassIfR0IsOneOf(setOf(3), DROPPED_ETH_BROADCAST) + } + assertFailsWith<IllegalArgumentException> { + v4gen.addCountAndDropIfR0IsNoneOf(setOf(3), PASSED_ARP) + } + assertFailsWith<IllegalArgumentException> { + v4gen.addCountAndPassIfR0IsNoneOf(setOf(3), DROPPED_ETH_BROADCAST) + } + assertFailsWith<IllegalArgumentException> { + v4gen.addCountAndDropIfBytesAtR0EqualsAnyOf(listOf(byteArrayOf(1)), PASSED_ARP) + } + assertFailsWith<IllegalArgumentException> { + v4gen.addCountAndPassIfBytesAtR0EqualsAnyOf( + listOf(byteArrayOf(1)), + DROPPED_ETH_BROADCAST + ) + } + assertFailsWith<IllegalArgumentException> { + v4gen.addCountAndDropIfBytesAtR0EqualsNoneOf(listOf(byteArrayOf(1)), PASSED_ARP) + } + assertFailsWith<IllegalArgumentException> { + v4gen.addCountAndPassIfBytesAtR0EqualsNoneOf( + listOf(byteArrayOf(1)), + DROPPED_ETH_BROADCAST + ) + } + } + + @Test + fun testValidateDnsNames() { + // '%' is a valid label character in mDNS subtype + // byte == 0xff means it is a '*' wildcard, which is a valid encoding. + val program = ApfV6Generator(ramSize, ramSize, clampSize).addJumpIfPktAtR0ContainDnsQ( + byteArrayOf(1, '%'.code.toByte(), 0, 0), + 1, + DROP_LABEL + ).addJumpIfPktAtR0ContainDnsA( + byteArrayOf(0xff.toByte(), 1, 'B'.code.toByte(), 0, 0), + DROP_LABEL + ).generate() + } + + @Test + fun testApfInstructionsEncoding() { + val v4gen = ApfV4Generator(APF_VERSION_2, ramSize, clampSize) + v4gen.addPass() + var program = v4gen.generate() + // encoding PASS opcode: opcode=0, imm_len=0, R=0 + assertContentEquals( + byteArrayOf(encodeInstruction(opcode = 0, immLength = 0, register = 0)), + program + ) + assertContentEquals( + listOf("0: pass"), + ApfJniUtils.disassembleApf(program).map { it.trim() } + ) + + var gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + gen.addDrop() + program = gen.generate().skipDataAndDebug() + // encoding DROP opcode: opcode=0, imm_len=0, R=1 + assertContentEquals( + byteArrayOf(encodeInstruction(opcode = 0, immLength = 0, register = 1)), + program + ) + assertContentEquals( + listOf("0: drop"), + ApfJniUtils.disassembleApf(program).map { it.trim() } + ) + + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + gen.addCountAndPass(129) + program = gen.generate().skipDataAndDebug() + // encoding COUNT(PASS) opcode: opcode=0, imm_len=size_of(imm), R=0, imm=counterNumber + assertContentEquals( + byteArrayOf( + encodeInstruction(opcode = 0, immLength = 1, register = 0), + 0x81.toByte() + ), + program + ) + assertContentEquals( + listOf("0: pass counter=129"), + ApfJniUtils.disassembleApf(program).map { it.trim() } + ) + + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + gen.addCountAndDrop(1000) + program = gen.generate().skipDataAndDebug() + // encoding COUNT(DROP) opcode: opcode=0, imm_len=size_of(imm), R=1, imm=counterNumber + assertContentEquals( + byteArrayOf( + encodeInstruction(opcode = 0, immLength = 2, register = 1), + 0x03, + 0xe8.toByte() + ), + program + ) + assertContentEquals( + listOf("0: drop counter=1000"), + ApfJniUtils.disassembleApf(program).map { it.trim() } + ) + + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + gen.addCountAndPass(PASSED_ARP) + program = gen.generate().skipDataAndDebug() + // encoding COUNT(PASS) opcode: opcode=0, imm_len=size_of(imm), R=0, imm=counterNumber + assertContentEquals( + byteArrayOf( + encodeInstruction(opcode = 0, immLength = 1, register = 0), + PASSED_ARP.value().toByte() + ), + program + ) + assertContentEquals( + listOf("0: pass counter=10"), + ApfJniUtils.disassembleApf(program).map { it.trim() } + ) + + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + gen.addCountAndDrop(DROPPED_ETHERTYPE_NOT_ALLOWED) + program = gen.generate().skipDataAndDebug() + // encoding COUNT(DROP) opcode: opcode=0, imm_len=size_of(imm), R=1, imm=counterNumber + assertContentEquals( + byteArrayOf( + encodeInstruction(opcode = 0, immLength = 1, register = 1), + DROPPED_ETHERTYPE_NOT_ALLOWED.value().toByte() + ), + program + ) + assertContentEquals( + listOf("0: drop counter=46"), + ApfJniUtils.disassembleApf(program).map { it.trim() } + ) + + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + gen.addAllocateR0() + gen.addAllocate(1500) + program = gen.generate().skipDataAndDebug() + // encoding ALLOC opcode: opcode=21(EXT opcode number), imm=36(TRANS opcode number). + // R=0 means length stored in R0. R=1 means the length stored in imm1. + assertContentEquals( + byteArrayOf( + encodeInstruction(opcode = 21, immLength = 1, register = 0), + 36, + encodeInstruction(opcode = 21, immLength = 1, register = 1), + 36, + 0x05, + 0xDC.toByte() + ), + program + ) + assertContentEquals(listOf( + "0: allocate r0", + "2: allocate 1500" + ), ApfJniUtils.disassembleApf(program).map { it.trim() }) + + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + gen.addTransmitWithoutChecksum() + gen.addTransmitL4(30, 40, 50, 256, true) + program = gen.generate().skipDataAndDebug() + // encoding TRANSMIT opcode: opcode=21(EXT opcode number), + // imm=37(TRANSMIT opcode number), + assertContentEquals(byteArrayOf( + encodeInstruction(opcode = 21, immLength = 1, register = 0), + 37, 255.toByte(), 255.toByte(), + encodeInstruction(opcode = 21, immLength = 1, register = 1), 37, 30, 40, 50, 1, 0 + ), program) + assertContentEquals(listOf( + "0: transmit ip_ofs=255", + "4: transmitudp ip_ofs=30, csum_ofs=40, csum_start=50, partial_csum=0x0100", + ), ApfJniUtils.disassembleApf(program).map { it.trim() }) + + val largeByteArray = ByteArray(256) { 0x01 } + gen = ApfV6Generator(largeByteArray, APF_VERSION_6, ramSize, clampSize) + program = gen.generate() + assertContentEquals( + byteArrayOf( + encodeInstruction(opcode = 14, immLength = 2, register = 1), 1, 0 + ) + largeByteArray + byteArrayOf( + encodeInstruction(opcode = 21, immLength = 1, register = 0), 48, 6, 13 + ), + program + ) + assertContentEquals( + listOf( + "0: data 256, " + "01".repeat(256), + "259: debugbuf size=1549" + ), + ApfJniUtils.disassembleApf(program).map { it.trim() } + ) + + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + gen.addWriteU8(0x01) + gen.addWriteU16(0x0102) + gen.addWriteU32(0x01020304) + gen.addWriteU8(0x00) + gen.addWriteU8(0x80) + gen.addWriteU16(0x0000) + gen.addWriteU16(0x8000) + gen.addWriteU32(0x00000000) + gen.addWriteU32(0x80000000) + gen.addWrite32(-2) + gen.addWrite32(byteArrayOf(0xff.toByte(), 0xfe.toByte(), 0xfd.toByte(), 0xfc.toByte())) + program = gen.generate().skipDataAndDebug() + assertContentEquals(byteArrayOf( + encodeInstruction(24, 1, 0), 0x01, + encodeInstruction(24, 2, 0), 0x01, 0x02, + encodeInstruction(24, 4, 0), 0x01, 0x02, 0x03, 0x04, + encodeInstruction(24, 1, 0), 0x00, + encodeInstruction(24, 1, 0), 0x80.toByte(), + encodeInstruction(24, 2, 0), 0x00, 0x00, + encodeInstruction(24, 2, 0), 0x80.toByte(), 0x00, + encodeInstruction(24, 4, 0), 0x00, 0x00, 0x00, 0x00, + encodeInstruction(24, 4, 0), 0x80.toByte(), 0x00, 0x00, 0x00, + encodeInstruction(24, 4, 0), 0xff.toByte(), 0xff.toByte(), + 0xff.toByte(), 0xfe.toByte(), + encodeInstruction(24, 4, 0), 0xff.toByte(), 0xfe.toByte(), + 0xfd.toByte(), 0xfc.toByte()), program) + assertContentEquals(listOf( + "0: write 0x01", + "2: write 0x0102", + "5: write 0x01020304", + "10: write 0x00", + "12: write 0x80", + "14: write 0x0000", + "17: write 0x8000", + "20: write 0x00000000", + "25: write 0x80000000", + "30: write 0xfffffffe", + "35: write 0xfffefdfc" + ), ApfJniUtils.disassembleApf(program).map { it.trim() }) + + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + gen.addWriteU8(R0) + gen.addWriteU16(R0) + gen.addWriteU32(R0) + gen.addWriteU8(R1) + gen.addWriteU16(R1) + gen.addWriteU32(R1) + program = gen.generate().skipDataAndDebug() + assertContentEquals(byteArrayOf( + encodeInstruction(21, 1, 0), 38, + encodeInstruction(21, 1, 0), 39, + encodeInstruction(21, 1, 0), 40, + encodeInstruction(21, 1, 1), 38, + encodeInstruction(21, 1, 1), 39, + encodeInstruction(21, 1, 1), 40 + ), program) + assertContentEquals(listOf( + "0: ewrite1 r0", + "2: ewrite2 r0", + "4: ewrite4 r0", + "6: ewrite1 r1", + "8: ewrite2 r1", + "10: ewrite4 r1" + ), ApfJniUtils.disassembleApf(program).map { it.trim() }) + + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + gen.addDataCopy(0, 10) + gen.addDataCopy(1, 5) + gen.addPacketCopy(1000, 255) + program = gen.generate().skipDataAndDebug() + assertContentEquals(byteArrayOf( + encodeInstruction(25, 0, 1), 10, + encodeInstruction(25, 1, 1), 1, 5, + encodeInstruction(25, 2, 0), + 0x03.toByte(), 0xe8.toByte(), 0xff.toByte(), + ), program) + assertContentEquals(listOf( + "0: datacopy src=0, len=10", + "2: datacopy src=1, len=5", + "5: pktcopy src=1000, len=255" + ), ApfJniUtils.disassembleApf(program).map { it.trim() }) + + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + gen.addDataCopyFromR0(5) + gen.addPacketCopyFromR0(5) + gen.addDataCopyFromR0LenR1() + gen.addPacketCopyFromR0LenR1() + program = gen.generate().skipDataAndDebug() + assertContentEquals(byteArrayOf( + encodeInstruction(21, 1, 1), 41, 5, + encodeInstruction(21, 1, 0), 41, 5, + encodeInstruction(21, 1, 1), 42, + encodeInstruction(21, 1, 0), 42, + ), program) + assertContentEquals(listOf( + "0: edatacopy src=r0, len=5", + "3: epktcopy src=r0, len=5", + "6: edatacopy src=r0, len=r1", + "8: epktcopy src=r0, len=r1" + ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) + + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + gen.addJumpIfBytesAtR0Equal(byteArrayOf('a'.code.toByte()), ApfV4Generator.DROP_LABEL) + program = gen.generate().skipDataAndDebug() + assertContentEquals(byteArrayOf( + encodeInstruction(opcode = 20, immLength = 1, register = 1), + 1, + 1, + 'a'.code.toByte() + ), program) + assertContentEquals(listOf( + "0: jbseq r0, 0x1, DROP, 61" + ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) + + val qnames = byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0, 0) + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + gen.addJumpIfPktAtR0DoesNotContainDnsQ(qnames, 0x0c, ApfV4Generator.DROP_LABEL) + gen.addJumpIfPktAtR0ContainDnsQ(qnames, 0x0c, ApfV4Generator.DROP_LABEL) + program = gen.generate().skipDataAndDebug() + assertContentEquals(byteArrayOf( + encodeInstruction(21, 1, 0), 43, 11, 0x0c.toByte(), + ) + qnames + byteArrayOf( + encodeInstruction(21, 1, 1), 43, 1, 0x0c.toByte(), + ) + qnames, program) + assertContentEquals(listOf( + "0: jdnsqne r0, DROP, 12, (1)A(1)B(0)(0)", + "10: jdnsqeq r0, DROP, 12, (1)A(1)B(0)(0)" + ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) + + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + gen.addJumpIfPktAtR0DoesNotContainDnsQSafe(qnames, 0x0c, ApfV4Generator.DROP_LABEL) + gen.addJumpIfPktAtR0ContainDnsQSafe(qnames, 0x0c, ApfV4Generator.DROP_LABEL) + program = gen.generate().skipDataAndDebug() + assertContentEquals(byteArrayOf( + encodeInstruction(21, 1, 0), 45, 11, 0x0c.toByte(), + ) + qnames + byteArrayOf( + encodeInstruction(21, 1, 1), 45, 1, 0x0c.toByte(), + ) + qnames, program) + assertContentEquals(listOf( + "0: jdnsqnesafe r0, DROP, 12, (1)A(1)B(0)(0)", + "10: jdnsqeqsafe r0, DROP, 12, (1)A(1)B(0)(0)" + ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) + + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + gen.addJumpIfPktAtR0DoesNotContainDnsA(qnames, ApfV4Generator.DROP_LABEL) + gen.addJumpIfPktAtR0ContainDnsA(qnames, ApfV4Generator.DROP_LABEL) + program = gen.generate().skipDataAndDebug() + assertContentEquals(byteArrayOf( + encodeInstruction(21, 1, 0), 44, 10, + ) + qnames + byteArrayOf( + encodeInstruction(21, 1, 1), 44, 1, + ) + qnames, program) + assertContentEquals(listOf( + "0: jdnsane r0, DROP, (1)A(1)B(0)(0)", + "9: jdnsaeq r0, DROP, (1)A(1)B(0)(0)" + ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) + + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + gen.addJumpIfPktAtR0DoesNotContainDnsASafe(qnames, ApfV4Generator.DROP_LABEL) + gen.addJumpIfPktAtR0ContainDnsASafe(qnames, ApfV4Generator.DROP_LABEL) + program = gen.generate().skipDataAndDebug() + assertContentEquals(byteArrayOf( + encodeInstruction(21, 1, 0), 46, 10, + ) + qnames + byteArrayOf( + encodeInstruction(21, 1, 1), 46, 1, + ) + qnames, program) + assertContentEquals(listOf( + "0: jdnsanesafe r0, DROP, (1)A(1)B(0)(0)", + "9: jdnsaeqsafe r0, DROP, (1)A(1)B(0)(0)" + ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) + + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + gen.addJumpIfOneOf(R1, List(32) { (it + 1).toLong() }.toSet(), DROP_LABEL) + gen.addJumpIfOneOf(R0, setOf(0, 257, 65536), DROP_LABEL) + gen.addJumpIfNoneOf(R0, setOf(1, 2, 3), DROP_LABEL) + program = gen.generate().skipDataAndDebug() + assertContentEquals(byteArrayOf( + encodeInstruction(21, 1, 1), 47, 24, -16, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, 32, + encodeInstruction(21, 1, 0), 47, 8, 14, 0, 0, 0, 0, 0, 0, + 1, 1, 0, 1, 0, 0, + encodeInstruction(21, 1, 0), 47, 1, 9, 1, 2, 3 + ), program) + + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + gen.addJumpIfOneOf(R0, setOf(0, 128, 256, 65536), DROP_LABEL) + gen.addJumpIfNoneOf(R1, setOf(0, 128, 256, 65536), DROP_LABEL) + program = gen.generate().skipDataAndDebug() + assertContentEquals(listOf( + "0: joneof r0, DROP, { 0, 128, 256, 65536 }", + "20: jnoneof r1, DROP, { 0, 128, 256, 65536 }" + ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) + + gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + gen.addJumpIfBytesAtR0EqualsAnyOf(listOf(byteArrayOf(1, 2), byteArrayOf(3, 4)), DROP_LABEL) + gen.addJumpIfBytesAtR0EqualNoneOf(listOf(byteArrayOf(1, 2), byteArrayOf(3, 4)), DROP_LABEL) + gen.addJumpIfBytesAtR0EqualNoneOf(listOf(byteArrayOf(1, 1), byteArrayOf(1, 1)), DROP_LABEL) + program = gen.generate().skipDataAndDebug() + assertContentEquals(byteArrayOf( + encodeInstruction(opcode = 20, immLength = 2, register = 1), + 0, 15, 8, 2, 1, 2, 3, 4, + encodeInstruction(opcode = 20, immLength = 2, register = 0), + 0, 6, 8, 2, 1, 2, 3, 4, + encodeInstruction(opcode = 20, immLength = 1, register = 0), + 1, 2, 1, 1 + ), program) + assertContentEquals(listOf( + "0: jbseq r0, 0x2, DROP, { 0102, 0304 }", + "9: jbsne r0, 0x2, DROP, { 0102, 0304 }", + "18: jbsne r0, 0x2, DROP, 0101" + ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) + } + + @Test + fun testWriteToTxBuffer() { + var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addAllocate(14) + .addWriteU8(0x01) + .addWriteU16(0x0203) + .addWriteU32(0x04050607) + .addWrite32(-2) + .addWrite32(byteArrayOf(0xff.toByte(), 0xfe.toByte(), 0xfd.toByte(), 0xfc.toByte())) + .addLoadImmediate(R0, 1) + .addWriteU8(R0) + .addLoadImmediate(R0, 0x0203) + .addWriteU16(R0) + .addLoadImmediate(R1, 0x04050607) + .addWriteU32(R1) + .addTransmitWithoutChecksum() + .generate() + assertPass(APF_VERSION_6, program, ByteArray(MIN_PKT_SIZE)) + assertContentEquals( + byteArrayOf( + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xff.toByte(), + 0xff.toByte(), 0xff.toByte(), 0xfe.toByte(), 0xff.toByte(), 0xfe.toByte(), + 0xfd.toByte(), 0xfc.toByte(), 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07), + ApfJniUtils.getTransmittedPacket() + ) + } + + @Test + fun testCopyToTxBuffer() { + var program = ApfV6Generator(byteArrayOf(33, 34, 35), APF_VERSION_6, ramSize, clampSize) + .addAllocate(14) + .addDataCopy(3, 2) // arg1=src, arg2=len + .addDataCopy(5, 1) // arg1=src, arg2=len + .addPacketCopy(0, 1) // arg1=src, arg2=len + .addPacketCopy(1, 3) // arg1=src, arg2=len + .addLoadImmediate(R0, 3) // data copy offset + .addDataCopyFromR0(2) // len + .addLoadImmediate(R0, 5) // data copy offset + .addLoadImmediate(R1, 1) // len + .addDataCopyFromR0LenR1() + .addLoadImmediate(R0, 0) // packet copy offset + .addPacketCopyFromR0(1) // len + .addLoadImmediate(R0, 1) // packet copy offset + .addLoadImmediate(R1, 3) // len + .addPacketCopyFromR0LenR1() + .addTransmitWithoutChecksum() + .generate() + assertPass(APF_VERSION_6, program, testPacket) + assertContentEquals( + byteArrayOf(33, 34, 35, 1, 2, 3, 4, 33, 34, 35, 1, 2, 3, 4), + ApfJniUtils.getTransmittedPacket() + ) + } + + @Test + fun testCopyContentToTxBuffer() { + val program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addAllocate(18) + .addDataCopy(HexDump.hexStringToByteArray("112233445566")) + .addDataCopy(HexDump.hexStringToByteArray("223344")) + .addDataCopy(HexDump.hexStringToByteArray("778899")) + .addDataCopy(HexDump.hexStringToByteArray("112233445566")) + .addTransmitWithoutChecksum() + .generate() + assertContentEquals(listOf( + "0: data 9, 112233445566778899", + "12: debugbuf size=1776", + "16: allocate 18", + "20: datacopy src=3, len=6", + "23: datacopy src=4, len=3", + "26: datacopy src=9, len=3", + "29: datacopy src=3, len=6", + "32: transmit ip_ofs=255" + ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) + assertPass(APF_VERSION_6, program, testPacket) + val transmitPkt = HexDump.toHexString(ApfJniUtils.getTransmittedPacket()) + assertEquals("112233445566223344778899112233445566", transmitPkt) + } + + @Test + fun testPassDrop() { + var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addDrop() + .addPass() + .generate() + assertDrop(APF_VERSION_6, program, testPacket) + + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addCountAndDrop(Counter.DROPPED_ETH_BROADCAST) + .generate() + verifyProgramRun(APF_VERSION_6, program, testPacket, DROPPED_ETH_BROADCAST) + + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addCountAndPass(Counter.PASSED_ARP) + .generate() + verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP) + } + + @Test + fun testLoadStoreCounter() { + doTestLoadStoreCounter ( + { mutableMapOf() }, + { ApfV4Generator(APF_VERSION_3, ramSize, clampSize) } + ) + doTestLoadStoreCounter ( + { mutableMapOf(TOTAL_PACKETS to 1) }, + { ApfV6Generator(APF_VERSION_6, ramSize, clampSize) } + ) + } + + private fun doTestLoadStoreCounter( + getInitialMap: () -> MutableMap<Counter, Long>, + getGenerator: () -> ApfV4GeneratorBase<*> + ) { + val program = getGenerator() + .addIncrementCounter(PASSED_ARP, 2) + .addPass() + .generate() + var dataRegion = ByteArray(Counter.totalSize()) { 0 } + assertVerdict(APF_VERSION_6, PASS, program, testPacket, dataRegion) + var counterMap = decodeCountersIntoMap(dataRegion) + var expectedMap = getInitialMap() + expectedMap[PASSED_ARP] = 2 + assertEquals(expectedMap, counterMap) + } + + @Test + fun testV4CountAndPassDropCompareR0() { + doTestCountAndPassDropCompareR0( + getGenerator = { ApfV4Generator(APF_VERSION_3, ramSize, clampSize) }, + incTotal = false + ) + } + + @Test + fun testV6CountAndPassDropCompareR0() { + doTestCountAndPassDropCompareR0( + getGenerator = { ApfV6Generator(APF_VERSION_6, ramSize, clampSize) }, + incTotal = true + ) + } + + private fun doTestCountAndPassDropCompareR0( + getGenerator: () -> ApfV4GeneratorBase<*>, + incTotal: Boolean + ) { + var program = getGenerator() + .addLoadImmediate(R0, 123) + .addCountAndDropIfR0Equals(123, Counter.DROPPED_ETH_BROADCAST) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun( + APF_VERSION_6, + program, + testPacket, + DROPPED_ETH_BROADCAST, + incTotal = incTotal + ) + + program = getGenerator() + .addLoadImmediate(R0, 123) + .addCountAndPassIfR0Equals(123, Counter.PASSED_ARP) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) + + program = getGenerator() + .addLoadImmediate(R0, 123) + .addCountAndDropIfR0NotEquals(124, Counter.DROPPED_ETH_BROADCAST) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun( + APF_VERSION_6, + program, + testPacket, + DROPPED_ETH_BROADCAST, + incTotal = incTotal + ) + + program = getGenerator() + .addLoadImmediate(R0, 123) + .addCountAndPassIfR0NotEquals(124, Counter.PASSED_ARP) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) + + program = getGenerator() + .addLoadImmediate(R0, 123) + .addCountAndDropIfR0LessThan(124, Counter.DROPPED_ETH_BROADCAST) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun( + APF_VERSION_6, + program, + testPacket, + DROPPED_ETH_BROADCAST, + incTotal = incTotal + ) + + program = getGenerator() + .addLoadImmediate(R0, 123) + .addCountAndPassIfR0LessThan(124, Counter.PASSED_ARP) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) + + program = getGenerator() + .addLoadImmediate(R0, 123) + .addCountAndDropIfR0GreaterThan(122, Counter.DROPPED_ETH_BROADCAST) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun( + APF_VERSION_6, + program, + testPacket, + DROPPED_ETH_BROADCAST, + incTotal = incTotal + ) + + program = getGenerator() + .addLoadImmediate(R0, 123) + .addCountAndPassIfR0GreaterThan(122, Counter.PASSED_ARP) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) + + program = getGenerator() + .addLoadImmediate(R0, 1) + .addCountAndDropIfBytesAtR0NotEqual( + byteArrayOf(5, 5), DROPPED_ETH_BROADCAST) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun( + APF_VERSION_6, + program, + testPacket, + DROPPED_ETH_BROADCAST, + incTotal = incTotal + ) + + program = getGenerator() + .addLoadImmediate(R0, 1) + .addCountAndPassIfBytesAtR0NotEqual( + byteArrayOf(5, 5), PASSED_ARP) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) + + program = getGenerator() + .addLoadImmediate(R0, 1) + .addCountAndDropIfR0AnyBitsSet(0xffff, DROPPED_ETH_BROADCAST) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun( + APF_VERSION_6, + program, + testPacket, + DROPPED_ETH_BROADCAST, + incTotal = incTotal + ) + + program = getGenerator() + .addLoadImmediate(R0, 1) + .addCountAndPassIfR0AnyBitsSet(0xffff, PASSED_ARP) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) + + program = getGenerator() + .addLoadImmediate(R0, 123) + .addCountAndDropIfR0IsOneOf(setOf(123), DROPPED_ETH_BROADCAST) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun( + APF_VERSION_6, + program, + testPacket, + DROPPED_ETH_BROADCAST, + incTotal = incTotal + ) + + program = getGenerator() + .addLoadImmediate(R0, 123) + .addCountAndPassIfR0IsOneOf(setOf(123), PASSED_ARP) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) + + program = getGenerator() + .addLoadImmediate(R0, 123) + .addCountAndDropIfR0IsNoneOf(setOf(124), DROPPED_ETH_BROADCAST) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun( + APF_VERSION_6, + program, + testPacket, + DROPPED_ETH_BROADCAST, + incTotal = incTotal + ) + + program = getGenerator() + .addLoadImmediate(R0, 123) + .addCountAndPassIfR0IsNoneOf(setOf(124), PASSED_ARP) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) + + program = getGenerator() + .addLoadImmediate(R0, 123) + .addCountAndDropIfR0IsOneOf(setOf(123, 124), DROPPED_ETH_BROADCAST) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun( + APF_VERSION_6, + program, + testPacket, + DROPPED_ETH_BROADCAST, + incTotal = incTotal + ) + + program = getGenerator() + .addLoadImmediate(R0, 123) + .addCountAndPassIfR0IsOneOf(setOf(123, 124), PASSED_ARP) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) + + program = getGenerator() + .addLoadImmediate(R0, 123) + .addCountAndDropIfR0IsNoneOf(setOf(122, 124), DROPPED_ETH_BROADCAST) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun( + APF_VERSION_6, + program, + testPacket, + DROPPED_ETH_BROADCAST, + incTotal = incTotal + ) + + program = getGenerator() + .addLoadImmediate(R0, 123) + .addCountAndPassIfR0IsNoneOf(setOf(122, 124), PASSED_ARP) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) + + program = getGenerator() + .addLoadImmediate(R0, 0) + .addCountAndDropIfBytesAtR0EqualsAnyOf( + listOf(byteArrayOf(1, 2), byteArrayOf(3, 4)), + DROPPED_ETH_BROADCAST + ) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun( + APF_VERSION_6, + program, + testPacket, + DROPPED_ETH_BROADCAST, + incTotal = incTotal + ) + + program = getGenerator() + .addLoadImmediate(R0, 0) + .addCountAndPassIfBytesAtR0EqualsAnyOf( + listOf(byteArrayOf(1, 2), byteArrayOf(3, 4)), + PASSED_ARP + ) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) + + program = getGenerator() + .addLoadImmediate(R0, 0) + .addCountAndDropIfBytesAtR0EqualsNoneOf( + listOf(byteArrayOf(1, 3), byteArrayOf(3, 4)), + DROPPED_ETH_BROADCAST + ) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun( + APF_VERSION_6, + program, + testPacket, + DROPPED_ETH_BROADCAST, + incTotal = incTotal + ) + + program = getGenerator() + .addLoadImmediate(R0, 0) + .addCountAndPassIfBytesAtR0EqualsNoneOf( + listOf(byteArrayOf(1, 3), byteArrayOf(3, 4)), + PASSED_ARP + ) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) + + program = getGenerator() + .addLoadImmediate(R0, 1) + .addCountAndDropIfBytesAtR0Equal( + byteArrayOf(2, 3), DROPPED_ETH_BROADCAST) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun( + APF_VERSION_6, + program, + testPacket, + DROPPED_ETH_BROADCAST, + incTotal = incTotal + ) + + program = getGenerator() + .addLoadImmediate(R0, 1) + .addCountAndPassIfBytesAtR0Equal( + byteArrayOf(2, 3), PASSED_ARP) + .addPass() + .addCountTrampoline() + .generate() + verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) + } + + @Test + fun testV4CountAndPassDrop() { + var program = ApfV4Generator(APF_VERSION_3, ramSize, clampSize) + .addCountAndDrop(Counter.DROPPED_ETH_BROADCAST) + .addCountTrampoline() + .generate() + verifyProgramRun( + APF_VERSION_6, + program, + testPacket, + DROPPED_ETH_BROADCAST, + incTotal = false + ) + + program = ApfV4Generator(APF_VERSION_3, ramSize, clampSize) + .addCountAndPass(Counter.PASSED_ARP) + .addCountTrampoline() + .generate() + verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = false) + } + + @Test + fun testV2CountAndPassDrop() { + var program = ApfV4Generator(APF_VERSION_2, ramSize, clampSize) + .addCountAndDrop(Counter.DROPPED_ETH_BROADCAST) + .addCountTrampoline() + .generate() + var dataRegion = ByteArray(Counter.totalSize()) { 0 } + assertVerdict(APF_VERSION_6, DROP, program, testPacket, dataRegion) + assertContentEquals(ByteArray(Counter.totalSize()) { 0 }, dataRegion) + + program = ApfV4Generator(APF_VERSION_2, ramSize, clampSize) + .addCountAndPass(PASSED_ARP) + .addCountTrampoline() + .generate() + dataRegion = ByteArray(Counter.totalSize()) { 0 } + assertVerdict(APF_VERSION_6, PASS, program, testPacket, dataRegion) + assertContentEquals(ByteArray(Counter.totalSize()) { 0 }, dataRegion) + } + + @Test + fun testAllocateFailure() { + val program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + // allocate size: 65535 > sizeof(apf_test_buffer): 1514, trigger allocate failure. + .addAllocate(65535) + .addDrop() + .generate() + verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ALLOCATE_FAILURE) + } + + @Test + fun testTransmitFailure() { + val program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addAllocate(14) + // len: 13 is less than ETH_HLEN, trigger transmit failure. + .addLoadImmediate(R0, 13) + .addStoreToMemory(MemorySlot.TX_BUFFER_OUTPUT_POINTER, R0) + .addTransmitWithoutChecksum() + .addDrop() + .generate() + verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_TRANSMIT_FAILURE) + } + + @Test + fun testTransmitL4() { + val etherIpv4UdpPacket = intArrayOf( + 0x01, 0x00, 0x5e, 0x00, 0x00, 0xfb, + 0x38, 0xca, 0x84, 0xb7, 0x7f, 0x16, + 0x08, 0x00, // end of ethernet header + 0x45, + 0x04, + 0x00, 0x3f, + 0x43, 0xcd, + 0x40, 0x00, + 0xff, + 0x11, + 0x00, 0x00, // ipv4 checksum set to 0 + 0xc0, 0xa8, 0x01, 0x03, + 0xe0, 0x00, 0x00, 0xfb, // end of ipv4 header + 0x14, 0xe9, + 0x14, 0xe9, + 0x00, 0x2b, + 0x00, 0x2b, // end of udp header. udp checksum set to udp (header + payload) size + 0x00, 0x00, 0x84, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x62, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, + 0x00, 0x00, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x04, 0xc0, 0xa8, 0x01, + 0x09, + ).map { it.toByte() }.toByteArray() + val program = ApfV6Generator(etherIpv4UdpPacket, APF_VERSION_6, ramSize, clampSize) + .addAllocate(etherIpv4UdpPacket.size) + .addDataCopy(3, etherIpv4UdpPacket.size) // arg1=src, arg2=len + .addTransmitL4( + ETH_HLEN, // ipOfs, + ETH_HLEN + IPV4_HLEN + 6, // csumOfs + ETH_HLEN + IPV4_HLEN - 8, // csumStart + IPPROTO_UDP, // partialCsum + true // isUdp + ) + .generate() + assertPass(APF_VERSION_6, program, testPacket) + val txBuf = ByteBuffer.wrap(ApfJniUtils.getTransmittedPacket()) + Struct.parse(EthernetHeader::class.java, txBuf) + val ipv4Hdr = Struct.parse(Ipv4Header::class.java, txBuf) + val udpHdr = Struct.parse(UdpHeader::class.java, txBuf) + assertEquals(0x9535.toShort(), ipv4Hdr.checksum) + assertEquals(0xa73d.toShort(), udpHdr.checksum) + } + + @Test + fun testDnsQuestionMatch() { + // needles = { A, B.LOCAL } + val needlesMatch = intArrayOf( + 0x01, 'A'.code, + 0x00, + 0x01, 'B'.code, + 0x05, 'L'.code, 'O'.code, 'C'.code, 'A'.code, 'L'.code, + 0x00, + 0x00 + ).map { it.toByte() }.toByteArray() + val udpPayload = intArrayOf( + 0x00, 0x00, 0x00, 0x00, // tid = 0x00, flags = 0x00, + 0x00, 0x02, // qdcount = 2 + 0x00, 0x00, // ancount = 0 + 0x00, 0x00, // nscount = 0 + 0x00, 0x00, // arcount = 0 + 0x01, 'a'.code, + 0x01, 'b'.code, + 0x05, 'l'.code, 'o'.code, 'c'.code, 'a'.code, 'l'.code, + 0x00, // qname1 = a.b.local + 0x00, 0x01, 0x00, 0x01, // type = A, class = 0x0001 + 0xc0, 0x0e, // qname2 = b.local (name compression) + 0x00, 0x01, 0x00, 0x01 // type = A, class = 0x0001 + ).map { it.toByte() }.toByteArray() + + var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addLoadImmediate(R0, 0) + .addJumpIfPktAtR0ContainDnsQ(needlesMatch, 0x01, DROP_LABEL) // arg2=qtype + .addPass() + .generate() + assertDrop(APF_VERSION_6, program, udpPayload) + + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addLoadImmediate(R0, 0) + .addJumpIfPktAtR0ContainDnsQSafe(needlesMatch, 0x01, DROP_LABEL) + .addPass() + .generate() + assertDrop(APF_VERSION_6, program, udpPayload) + + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addLoadImmediate(R0, 0) + .addJumpIfPktAtR0DoesNotContainDnsQ(needlesMatch, 0x01, DROP_LABEL) // arg2=qtype + .addPass() + .generate() + assertPass(APF_VERSION_6, program, udpPayload) + + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addLoadImmediate(R0, 0) + .addJumpIfPktAtR0DoesNotContainDnsQSafe(needlesMatch, 0x01, DROP_LABEL) // arg2=qtype + .addPass() + .generate() + assertPass(APF_VERSION_6, program, udpPayload) + + val badUdpPayload = intArrayOf( + 0x00, 0x00, 0x00, 0x00, // tid = 0x00, flags = 0x00, + 0x00, 0x02, // qdcount = 2 + 0x00, 0x00, // ancount = 0 + 0x00, 0x00, // nscount = 0 + 0x00, 0x00, // arcount = 0 + 0x01, 'a'.code, + 0x01, 'b'.code, + 0x05, 'l'.code, 'o'.code, 'c'.code, 'a'.code, 'l'.code, + 0x00, // qname1 = a.b.local + 0x00, 0x01, 0x00, 0x01, // type = A, class = 0x0001 + 0xc0, 0x1b, // corrupted pointer cause infinite loop + 0x00, 0x01, 0x00, 0x01 // type = A, class = 0x0001 + ).map { it.toByte() }.toByteArray() + + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addLoadImmediate(R0, 0) + .addJumpIfPktAtR0ContainDnsQ(needlesMatch, 0x01, DROP_LABEL) // arg2=qtype + .addPass() + .generate() + verifyProgramRun(APF_VERSION_6, program, badUdpPayload, CORRUPT_DNS_PACKET, result = DROP) + + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addLoadImmediate(R0, 0) + .addJumpIfPktAtR0ContainDnsQSafe(needlesMatch, 0x01, DROP_LABEL) // arg2=qtype + .addPass() + .generate() + verifyProgramRun(APF_VERSION_6, program, badUdpPayload, CORRUPT_DNS_PACKET, result = PASS) + } + + @Test + fun testDnsAnswerMatch() { + // needles = { A, B.LOCAL } + val needlesMatch = intArrayOf( + 0x01, 'A'.code, + 0x00, + 0x01, 'B'.code, + 0x05, 'L'.code, 'O'.code, 'C'.code, 'A'.code, 'L'.code, + 0x00, + 0x00 + ).map { it.toByte() }.toByteArray() + + val udpPayload = intArrayOf( + 0x00, 0x00, 0x84, 0x00, // tid = 0x00, flags = 0x8400, + 0x00, 0x00, // qdcount = 0 + 0x00, 0x02, // ancount = 2 + 0x00, 0x00, // nscount = 0 + 0x00, 0x00, // arcount = 0 + 0x01, 'a'.code, + 0x01, 'b'.code, + 0x05, 'l'.code, 'o'.code, 'c'.code, 'a'.code, 'l'.code, + 0x00, // name1 = a.b.local + 0x00, 0x01, 0x80, 0x01, // type = A, class = 0x8001 + 0x00, 0x00, 0x00, 0x78, // ttl = 120 + 0x00, 0x04, 0xc0, 0xa8, 0x01, 0x09, // rdlengh = 4, rdata = 192.168.1.9 + 0xc0, 0x0e, // name2 = b.local (name compression) + 0x00, 0x01, 0x80, 0x01, // type = A, class = 0x8001 + 0x00, 0x00, 0x00, 0x78, // ttl = 120 + 0x00, 0x04, 0xc0, 0xa8, 0x01, 0x09 // rdlengh = 4, rdata = 192.168.1.9 + ).map { it.toByte() }.toByteArray() + + var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addLoadImmediate(R0, 0) + .addJumpIfPktAtR0ContainDnsA(needlesMatch, DROP_LABEL) + .addPass() + .generate() + assertDrop(APF_VERSION_6, program, udpPayload) + + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addLoadImmediate(R0, 0) + .addJumpIfPktAtR0ContainDnsASafe(needlesMatch, DROP_LABEL) + .addPass() + .generate() + assertDrop(APF_VERSION_6, program, udpPayload) + + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addLoadImmediate(R0, 0) + .addJumpIfPktAtR0DoesNotContainDnsA(needlesMatch, DROP_LABEL) + .addPass() + .generate() + assertPass(APF_VERSION_6, program, udpPayload) + + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addLoadImmediate(R0, 0) + .addJumpIfPktAtR0DoesNotContainDnsASafe(needlesMatch, DROP_LABEL) + .addPass() + .generate() + assertPass(APF_VERSION_6, program, udpPayload) + + val badUdpPayload = intArrayOf( + 0x00, 0x00, 0x84, 0x00, // tid = 0x00, flags = 0x8400, + 0x00, 0x00, // qdcount = 0 + 0x00, 0x02, // ancount = 2 + 0x00, 0x00, // nscount = 0 + 0x00, 0x00, // arcount = 0 + 0x01, 'a'.code, + 0x01, 'b'.code, + 0x05, 'l'.code, 'o'.code, 'c'.code, 'a'.code, 'l'.code, + 0x00, // name1 = a.b.local + 0x00, 0x01, 0x80, 0x01, // type = A, class = 0x8001 + 0x00, 0x00, 0x00, 0x78, // ttl = 120 + 0x00, 0x04, 0xc0, 0xa8, 0x01, 0x09, // rdlengh = 4, rdata = 192.168.1.9 + 0xc0, 0x25, // corrupted pointer cause infinite loop + 0x00, 0x01, 0x80, 0x01, // type = A, class = 0x8001 + 0x00, 0x00, 0x00, 0x78, // ttl = 120 + 0x00, 0x04, 0xc0, 0xa8, 0x01, 0x09 // rdlengh = 4, rdata = 192.168.1.9 + ).map { it.toByte() }.toByteArray() + + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addLoadImmediate(R0, 0) + .addJumpIfPktAtR0ContainDnsA(needlesMatch, DROP_LABEL) + .addPass() + .generate() + verifyProgramRun(APF_VERSION_6, program, badUdpPayload, CORRUPT_DNS_PACKET, result = DROP) + + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addLoadImmediate(R0, 0) + .addJumpIfPktAtR0ContainDnsASafe(needlesMatch, DROP_LABEL) + .addPass() + .generate() + verifyProgramRun(APF_VERSION_6, program, badUdpPayload, CORRUPT_DNS_PACKET, result = PASS) + } + + @Test + fun testGetCounterValue() { + val counterBytes = intArrayOf(0xff, 0, 0, 0, 0x78, 0x56, 0x34, 0x12) + .map { it.toByte() }.toByteArray() + assertEquals(0xff, ApfCounterTracker.getCounterValue(counterBytes, Counter.TOTAL_PACKETS)) + } + + @Test + fun testJumpMultipleByteSequencesMatch() { + var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addLoadImmediate(R0, 0) + .addJumpIfBytesAtR0EqualsAnyOf( + listOf(byteArrayOf(1, 2, 3), byteArrayOf(6, 5, 4)), + DROP_LABEL + ) + .addPass() + .generate() + assertDrop(APF_VERSION_6, program, testPacket) + + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addLoadImmediate(R0, 2) + .addJumpIfBytesAtR0EqualsAnyOf( + listOf(byteArrayOf(1, 2, 3), byteArrayOf(6, 5, 4)), + DROP_LABEL + ) + .addPass() + .generate() + assertPass(APF_VERSION_6, program, testPacket) + + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addLoadImmediate(R0, 1) + .addJumpIfBytesAtR0EqualNoneOf( + listOf(byteArrayOf(1, 2, 3), byteArrayOf(6, 5, 4)), + DROP_LABEL + ) + .addPass() + .generate() + assertDrop(APF_VERSION_6, program, testPacket) + + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addLoadImmediate(R0, 0) + .addJumpIfBytesAtR0EqualNoneOf( + listOf(byteArrayOf(1, 2, 3), byteArrayOf(6, 5, 4)), + DROP_LABEL + ) + .addPass() + .generate() + assertPass(APF_VERSION_6, program, testPacket) + } + + @Test + fun testJumpOneOf() { + var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addLoadImmediate(R0, 255) + .addJumpIfOneOf(R0, setOf(1, 2, 3, 128, 255), DROP_LABEL) + .addPass() + .generate() + assertDrop(APF_VERSION_6, program, testPacket) + + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addLoadImmediate(R0, 254) + .addJumpIfOneOf(R0, setOf(1, 2, 3, 128, 255), DROP_LABEL) + .addPass() + .generate() + assertPass(APF_VERSION_6, program, testPacket) + + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addLoadImmediate(R0, 254) + .addJumpIfNoneOf(R0, setOf(1, 2, 3, 128, 255), DROP_LABEL) + .addPass() + .generate() + assertDrop(APF_VERSION_6, program, testPacket) + + program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addLoadImmediate(R0, 255) + .addJumpIfNoneOf(R0, setOf(1, 2, 3, 128, 255), DROP_LABEL) + .addPass() + .generate() + assertPass(APF_VERSION_6, program, testPacket) + } + + @Test + fun testDebugBuffer() { + val program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) + .addLoad8(R0, 255) + .generate() + val dataRegion = ByteArray(ramSize - program.size) { 0 } + + assertVerdict(APF_VERSION_6, PASS, program, testPacket, dataRegion) + // offset 3 in the data region should contain if the interpreter is APFv6 mode or not + assertEquals(1, dataRegion[3]) + } + + private fun encodeInstruction(opcode: Int, immLength: Int, register: Int): Byte { + val immLengthEncoding = if (immLength == 4) 3 else immLength + return opcode.shl(3).or(immLengthEncoding.shl(1)).or(register).toByte() + } + + private fun ByteArray.skipDataAndDebug(): ByteArray { + assertEquals( + listOf( + encodeInstruction(14, 2, 1), + 0, + 0, + encodeInstruction(21, 1, 0), + 48 + // the actual exception buffer size is not checked here. + ), + this.take(5) + ) + return this.drop(7).toByteArray() + } +}
diff --git a/tests/unit/src/android/net/apf/ApfNewTest.kt b/tests/unit/src/android/net/apf/ApfNewTest.kt deleted file mode 100644 index f016c75..0000000 --- a/tests/unit/src/android/net/apf/ApfNewTest.kt +++ /dev/null
@@ -1,2808 +0,0 @@ -/* - * Copyright (C) 2023 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.apf - -import android.content.Context -import android.net.LinkAddress -import android.net.LinkProperties -import android.net.MacAddress -import android.net.apf.ApfCounterTracker.Counter -import android.net.apf.ApfCounterTracker.Counter.APF_PROGRAM_ID -import android.net.apf.ApfCounterTracker.Counter.APF_VERSION -import android.net.apf.ApfCounterTracker.Counter.CORRUPT_DNS_PACKET -import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_REQUEST_REPLIED -import android.net.apf.ApfCounterTracker.Counter.DROPPED_ETHERTYPE_NOT_ALLOWED -import android.net.apf.ApfCounterTracker.Counter.DROPPED_ETH_BROADCAST -import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_NON_DHCP4 -import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_INVALID -import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_OTHER_HOST -import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_REPLIED_NON_DAD -import android.net.apf.ApfCounterTracker.Counter.PASSED_ALLOCATE_FAILURE -import android.net.apf.ApfCounterTracker.Counter.PASSED_ARP -import android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_REQUEST -import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4 -import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4_FROM_DHCPV4_SERVER -import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_ICMP -import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_DAD -import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_ADDRESS -import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_SLLA_OPTION -import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_TENTATIVE -import android.net.apf.ApfCounterTracker.Counter.PASSED_TRANSMIT_FAILURE -import android.net.apf.ApfCounterTracker.Counter.TOTAL_PACKETS -import android.net.apf.ApfFilter.Dependencies -import android.net.apf.ApfTestUtils.DROP -import android.net.apf.ApfTestUtils.MIN_PKT_SIZE -import android.net.apf.ApfTestUtils.PASS -import android.net.apf.ApfTestUtils.assertDrop -import android.net.apf.ApfTestUtils.assertPass -import android.net.apf.ApfTestUtils.assertVerdict -import android.net.apf.BaseApfGenerator.APF_VERSION_2 -import android.net.apf.BaseApfGenerator.APF_VERSION_3 -import android.net.apf.BaseApfGenerator.APF_VERSION_6 -import android.net.apf.BaseApfGenerator.DROP_LABEL -import android.net.apf.BaseApfGenerator.IllegalInstructionException -import android.net.apf.BaseApfGenerator.MemorySlot -import android.net.apf.BaseApfGenerator.PASS_LABEL -import android.net.apf.BaseApfGenerator.Register.R0 -import android.net.apf.BaseApfGenerator.Register.R1 -import android.net.ip.IpClient.IpClientCallbacksWrapper -import android.os.Build -import android.system.OsConstants.IFA_F_TENTATIVE -import androidx.test.filters.SmallTest -import com.android.net.module.util.HexDump -import com.android.net.module.util.InterfaceParams -import com.android.net.module.util.NetworkStackConstants.ARP_ETHER_IPV4_LEN -import com.android.net.module.util.NetworkStackConstants.ARP_REPLY -import com.android.net.module.util.NetworkStackConstants.ARP_REQUEST -import com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN -import com.android.net.module.util.NetworkStackConstants.ICMPV6_NA_HEADER_LEN -import com.android.net.module.util.NetworkStackConstants.ICMPV6_NS_HEADER_LEN -import com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN -import com.android.net.module.util.Struct -import com.android.net.module.util.arp.ArpPacket -import com.android.net.module.util.structs.EthernetHeader -import com.android.net.module.util.structs.Ipv4Header -import com.android.net.module.util.structs.UdpHeader -import com.android.networkstack.metrics.NetworkQuirkMetrics -import com.android.networkstack.packets.NeighborAdvertisement -import com.android.networkstack.packets.NeighborSolicitation -import com.android.networkstack.util.NetworkStackUtils -import com.android.testutils.DevSdkIgnoreRule -import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo -import com.android.testutils.DevSdkIgnoreRunner -import java.net.Inet6Address -import java.net.InetAddress -import java.nio.ByteBuffer -import kotlin.test.assertContentEquals -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers.any -import org.mockito.Mock -import org.mockito.Mockito -import org.mockito.Mockito.times -import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` -import org.mockito.MockitoAnnotations - -const val ETH_HLEN = 14 -const val IPV4_HLEN = 20 -const val IPPROTO_UDP = 17 - -/** - * Tests for APF instructions. - */ -@RunWith(DevSdkIgnoreRunner::class) -@SmallTest -class ApfNewTest { - - @get:Rule val ignoreRule = DevSdkIgnoreRule() - - @Mock private lateinit var context: Context - - @Mock private lateinit var metrics: NetworkQuirkMetrics - - @Mock private lateinit var dependencies: Dependencies - - @Mock private lateinit var ipClientCallback: IpClientCallbacksWrapper - - private val ramSize = 2048 - private val clampSize = 2048 - - 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 testPacket = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) - private val hostIpv4Address = byteArrayOf(10, 0, 0, 1) - private val senderIpv4Address = byteArrayOf(10, 0, 0, 2) - private val arpBroadcastMacAddress = intArrayOf(0xff, 0xff, 0xff, 0xff, 0xff, 0xff) - .map { it.toByte() }.toByteArray() - private val senderMacAddress = intArrayOf(0x02, 0x22, 0x33, 0x44, 0x55, 0x66) - .map { it.toByte() }.toByteArray() - private val senderIpv6Address = - // 2001::200:1a:1122:3344 - intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x02, 0, 0, 0x1a, 0x11, 0x22, 0x33, 0x44) - .map{ it.toByte() }.toByteArray() - private val hostIpv6Addresses = listOf( - // 2001::200:1a:3344:1122 - intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x02, 0, 0, 0x1a, 0x33, 0x44, 0x11, 0x22) - .map{ it.toByte() }.toByteArray(), - // 2001::100:1b:4455:6677 - intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x01, 0, 0, 0x1b, 0x44, 0x55, 0x66, 0x77) - .map{ it.toByte() }.toByteArray() - ) - private val hostIpv6TentativeAddresses = listOf( - // 2001::200:1a:1234:5678 - intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x02, 0, 0, 0x1a, 0x12, 0x34, 0x56, 0x78) - .map{ it.toByte() }.toByteArray(), - // 2001::100:1b:1234:5678 - intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x01, 0, 0, 0x1b, 0x12, 0x34, 0x56, 0x78) - .map{ it.toByte() }.toByteArray() - ) - private val hostAnycast6Addresses = listOf( - // 2001::100:1b:aabb:ccdd - intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x01, 0, 0, 0x1b, 0xaa, 0xbb, 0xcc, 0xdd) - .map{ it.toByte() }.toByteArray() - ) - private val hostMulticastMacAddresses = listOf( - // 33:33:00:00:00:01 - intArrayOf(0x33, 0x33, 0, 0, 0, 1).map { it.toByte() }.toByteArray(), - // 33:33:ff:44:11:22 - intArrayOf(0x33, 0x33, 0xff, 0x44, 0x11, 0x22).map { it.toByte() }.toByteArray(), - // 33:33:ff:55:66:77 - intArrayOf(0x33, 0x33, 0xff, 0x55, 0x66, 0x77).map { it.toByte() }.toByteArray(), - // 33:33:ff:bb:cc:dd - intArrayOf(0x33, 0x33, 0xff, 0xbb, 0xcc, 0xdd).map { it.toByte() }.toByteArray(), - ) - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - // mock anycast6 address from /proc/net/anycast6 - `when`(dependencies.getAnycast6Addresses(any())).thenReturn(hostAnycast6Addresses) - - // mock ether multicast mac address from /proc/net/dev_mcast - `when`(dependencies.getEtherMulticastAddresses(any())).thenReturn(hostMulticastMacAddresses) - - // mock nd traffic class from /proc/sys/net/ipv6/conf/{ifname}/ndisc_tclass - `when`(dependencies.getNdTrafficClass(any())).thenReturn(0) - } - - @After - fun tearDown() { - Mockito.framework().clearInlineMocks() - ApfJniUtils.resetTransmittedPacketMemory() - } - - @Test - fun testDataInstructionMustComeFirst() { - var gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - gen.addAllocateR0() - assertFailsWith<IllegalInstructionException> { gen.addData(ByteArray(3) { 0x01 }) } - } - - @Test - fun testApfInstructionEncodingSizeCheck() { - var gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - assertFailsWith<IllegalArgumentException> { - ApfV6Generator(ByteArray(65536) { 0x01 }, APF_VERSION_6, ramSize, clampSize) - } - assertFailsWith<IllegalArgumentException> { gen.addAllocate(65536) } - assertFailsWith<IllegalArgumentException> { gen.addAllocate(-1) } - assertFailsWith<IllegalArgumentException> { gen.addDataCopy(-1, 1) } - assertFailsWith<IllegalArgumentException> { gen.addPacketCopy(-1, 1) } - assertFailsWith<IllegalArgumentException> { gen.addDataCopy(1, 256) } - assertFailsWith<IllegalArgumentException> { gen.addPacketCopy(1, 256) } - assertFailsWith<IllegalArgumentException> { gen.addDataCopy(1, -1) } - assertFailsWith<IllegalArgumentException> { gen.addPacketCopy(1, -1) } - assertFailsWith<IllegalArgumentException> { gen.addPacketCopyFromR0(256) } - assertFailsWith<IllegalArgumentException> { gen.addDataCopyFromR0(256) } - assertFailsWith<IllegalArgumentException> { gen.addPacketCopyFromR0(-1) } - assertFailsWith<IllegalArgumentException> { gen.addDataCopyFromR0(-1) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( - byteArrayOf(1, 'A'.code.toByte(), 0, 0), - 256, - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( - byteArrayOf(1, 'a'.code.toByte(), 0, 0), - 0x0c, - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( - byteArrayOf(1, '.'.code.toByte(), 0, 0), - 0x0c, - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( - byteArrayOf(0, 0), - 0xc0, - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( - byteArrayOf(1, 'A'.code.toByte()), - 0xc0, - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( - byteArrayOf(64) + ByteArray(64) { 'A'.code.toByte() } + byteArrayOf(0, 0), - 0xc0, - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( - byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0), - 0xc0, - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsQ( - byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()), - 0xc0, - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( - byteArrayOf(1, 'A'.code.toByte(), 0, 0), - 256, - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( - byteArrayOf(1, 'a'.code.toByte(), 0, 0), - 0x0c, - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( - byteArrayOf(1, '.'.code.toByte(), 0, 0), - 0x0c, - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( - byteArrayOf(0, 0), - 0xc0, - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( - byteArrayOf(1, 'A'.code.toByte()), - 0xc0, - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( - byteArrayOf(64) + ByteArray(64) { 'A'.code.toByte() } + byteArrayOf(0, 0), - 0xc0, - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( - byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0), - 0xc0, - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsQ( - byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()), - 0xc0, - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( - byteArrayOf(1, 'a'.code.toByte(), 0, 0), - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( - byteArrayOf(1, '.'.code.toByte(), 0, 0), - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( - byteArrayOf(0, 0), - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( - byteArrayOf(1, 'A'.code.toByte()), - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( - byteArrayOf(64) + ByteArray(64) { 'A'.code.toByte() } + byteArrayOf(0, 0), - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( - byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0), - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0DoesNotContainDnsA( - byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()), - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( - byteArrayOf(1, 'a'.code.toByte(), 0, 0), - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( - byteArrayOf(1, '.'.code.toByte(), 0, 0), - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( - byteArrayOf(0, 0), - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( - byteArrayOf(1, 'A'.code.toByte()), - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( - byteArrayOf(64) + ByteArray(64) { 'A'.code.toByte() } + byteArrayOf(0, 0), - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( - byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0), - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { gen.addJumpIfPktAtR0ContainDnsA( - byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte()), - ApfV4Generator.DROP_LABEL - ) } - assertFailsWith<IllegalArgumentException> { - gen.addJumpIfBytesAtR0Equal(ByteArray(2048) { 1 }, DROP_LABEL) - } - assertFailsWith<IllegalArgumentException> { - gen.addJumpIfBytesAtR0NotEqual(ByteArray(2048) { 1 }, DROP_LABEL) - } - assertFailsWith<IllegalArgumentException> { gen.addCountAndDrop(PASSED_ARP) } - assertFailsWith<IllegalArgumentException> { gen.addCountAndPass(DROPPED_ETH_BROADCAST) } - assertFailsWith<IllegalArgumentException> { - gen.addCountAndDropIfR0Equals(3, PASSED_ARP) - } - assertFailsWith<IllegalArgumentException> { - gen.addCountAndPassIfR0Equals(3, DROPPED_ETH_BROADCAST) - } - assertFailsWith<IllegalArgumentException> { - gen.addCountAndDropIfR0NotEquals(3, PASSED_ARP) - } - assertFailsWith<IllegalArgumentException> { - gen.addCountAndPassIfR0NotEquals(3, DROPPED_ETH_BROADCAST) - } - assertFailsWith<IllegalArgumentException> { - gen.addCountAndDropIfR0LessThan(3, PASSED_ARP) - } - assertFailsWith<IllegalArgumentException> { - gen.addCountAndPassIfR0LessThan(3, DROPPED_ETH_BROADCAST) - } - assertFailsWith<IllegalArgumentException> { - gen.addCountAndDropIfR0GreaterThan(3, PASSED_ARP) - } - assertFailsWith<IllegalArgumentException> { - gen.addCountAndPassIfR0GreaterThan(3, DROPPED_ETH_BROADCAST) - } - assertFailsWith<IllegalArgumentException> { - gen.addCountAndDropIfBytesAtR0NotEqual(byteArrayOf(1), PASSED_ARP) - } - assertFailsWith<IllegalArgumentException> { - gen.addCountAndDropIfBytesAtR0Equal(byteArrayOf(1), PASSED_ARP) - } - assertFailsWith<IllegalArgumentException> { - gen.addCountAndPassIfBytesAtR0Equal(byteArrayOf(1), DROPPED_ETH_BROADCAST) - } - assertFailsWith<IllegalArgumentException> { - gen.addCountAndDropIfR0AnyBitsSet(3, PASSED_ARP) - } - assertFailsWith<IllegalArgumentException> { - gen.addCountAndPassIfR0AnyBitsSet(3, DROPPED_ETH_BROADCAST) - } - assertFailsWith<IllegalArgumentException> { - gen.addCountAndDropIfR0IsOneOf(setOf(3), PASSED_ARP) - } - assertFailsWith<IllegalArgumentException> { - gen.addCountAndPassIfR0IsOneOf(setOf(3), DROPPED_ETH_BROADCAST) - } - assertFailsWith<IllegalArgumentException> { - gen.addCountAndDropIfR0IsNoneOf(setOf(3), PASSED_ARP) - } - assertFailsWith<IllegalArgumentException> { - gen.addCountAndPassIfR0IsNoneOf(setOf(3), DROPPED_ETH_BROADCAST) - } - assertFailsWith<IllegalArgumentException> { - gen.addCountAndDropIfBytesAtR0EqualsAnyOf(listOf(byteArrayOf(1)), PASSED_ARP) - } - assertFailsWith<IllegalArgumentException> { - gen.addCountAndPassIfBytesAtR0EqualsAnyOf(listOf(byteArrayOf(1)), DROPPED_ETH_BROADCAST) - } - assertFailsWith<IllegalArgumentException> { - gen.addCountAndDropIfBytesAtR0EqualsNoneOf(listOf(byteArrayOf(1)), PASSED_ARP) - } - assertFailsWith<IllegalArgumentException> { - gen.addCountAndPassIfBytesAtR0EqualsNoneOf( - listOf(byteArrayOf(1)), - DROPPED_ETH_BROADCAST - ) - } - assertFailsWith<IllegalArgumentException> { - gen.addWrite32(byteArrayOf()) - } - assertFailsWith<IllegalArgumentException> { - gen.addJumpIfOneOf(R0, setOf(), PASS_LABEL) - } - assertFailsWith<IllegalArgumentException> { - gen.addJumpIfOneOf(R0, setOf(-1, 1), PASS_LABEL) - } - assertFailsWith<IllegalArgumentException> { - gen.addJumpIfOneOf(R0, setOf(4294967296L, 1), PASS_LABEL) - } - assertFailsWith<IllegalArgumentException> { - gen.addJumpIfOneOf(R0, List(34) { (it + 1).toLong() }.toSet(), PASS_LABEL) - } - assertFailsWith<IllegalArgumentException> { - gen.addJumpIfBytesAtR0EqualsAnyOf(listOf(ByteArray(2048) { 1 }), PASS_LABEL ) - } - assertFailsWith<IllegalArgumentException> { - gen.addJumpIfBytesAtR0EqualsAnyOf( - listOf(byteArrayOf(1), byteArrayOf(1, 2)), - PASS_LABEL - ) - } - - val v4gen = ApfV4Generator(APF_VERSION_3, ramSize, clampSize) - assertFailsWith<IllegalArgumentException> { v4gen.addCountAndDrop(PASSED_ARP) } - assertFailsWith<IllegalArgumentException> { v4gen.addCountAndPass(DROPPED_ETH_BROADCAST) } - assertFailsWith<IllegalArgumentException> { - v4gen.addCountAndDropIfR0Equals(3, PASSED_ARP) - } - assertFailsWith<IllegalArgumentException> { - v4gen.addCountAndPassIfR0Equals(3, DROPPED_ETH_BROADCAST) - } - assertFailsWith<IllegalArgumentException> { - v4gen.addCountAndDropIfR0NotEquals(3, PASSED_ARP) - } - assertFailsWith<IllegalArgumentException> { - v4gen.addCountAndPassIfR0NotEquals(3, DROPPED_ETH_BROADCAST) - } - assertFailsWith<IllegalArgumentException> { - v4gen.addCountAndDropIfBytesAtR0Equal(byteArrayOf(1), PASSED_ARP) - } - assertFailsWith<IllegalArgumentException> { - v4gen.addCountAndPassIfBytesAtR0Equal(byteArrayOf(1), DROPPED_ETH_BROADCAST) - } - assertFailsWith<IllegalArgumentException> { - v4gen.addCountAndDropIfR0LessThan(3, PASSED_ARP) - } - assertFailsWith<IllegalArgumentException> { - v4gen.addCountAndPassIfR0LessThan(3, DROPPED_ETH_BROADCAST) - } - assertFailsWith<IllegalArgumentException> { - v4gen.addCountAndDropIfR0GreaterThan(3, PASSED_ARP) - } - assertFailsWith<IllegalArgumentException> { - v4gen.addCountAndPassIfR0GreaterThan(3, DROPPED_ETH_BROADCAST) - } - assertFailsWith<IllegalArgumentException> { - v4gen.addCountAndDropIfBytesAtR0NotEqual(byteArrayOf(1), PASSED_ARP) - } - assertFailsWith<IllegalArgumentException> { - v4gen.addCountAndDropIfR0AnyBitsSet(3, PASSED_ARP) - } - assertFailsWith<IllegalArgumentException> { - v4gen.addCountAndPassIfR0AnyBitsSet(3, DROPPED_ETH_BROADCAST) - } - assertFailsWith<IllegalArgumentException> { - v4gen.addCountAndDropIfR0IsOneOf(setOf(3), PASSED_ARP) - } - assertFailsWith<IllegalArgumentException> { - v4gen.addCountAndPassIfR0IsOneOf(setOf(3), DROPPED_ETH_BROADCAST) - } - assertFailsWith<IllegalArgumentException> { - v4gen.addCountAndDropIfR0IsNoneOf(setOf(3), PASSED_ARP) - } - assertFailsWith<IllegalArgumentException> { - v4gen.addCountAndPassIfR0IsNoneOf(setOf(3), DROPPED_ETH_BROADCAST) - } - assertFailsWith<IllegalArgumentException> { - v4gen.addCountAndDropIfBytesAtR0EqualsAnyOf(listOf(byteArrayOf(1)), PASSED_ARP) - } - assertFailsWith<IllegalArgumentException> { - v4gen.addCountAndPassIfBytesAtR0EqualsAnyOf( - listOf(byteArrayOf(1)), - DROPPED_ETH_BROADCAST - ) - } - assertFailsWith<IllegalArgumentException> { - v4gen.addCountAndDropIfBytesAtR0EqualsNoneOf(listOf(byteArrayOf(1)), PASSED_ARP) - } - assertFailsWith<IllegalArgumentException> { - v4gen.addCountAndPassIfBytesAtR0EqualsNoneOf( - listOf(byteArrayOf(1)), - DROPPED_ETH_BROADCAST - ) - } - } - - @Test - fun testValidateDnsNames() { - // '%' is a valid label character in mDNS subtype - // byte == 0xff means it is a '*' wildcard, which is a valid encoding. - val program = ApfV6Generator(ramSize, ramSize, clampSize).addJumpIfPktAtR0ContainDnsQ( - byteArrayOf(1, '%'.code.toByte(), 0, 0), - 1, - DROP_LABEL - ).addJumpIfPktAtR0ContainDnsA( - byteArrayOf(0xff.toByte(), 1, 'B'.code.toByte(), 0, 0), - DROP_LABEL - ).generate() - } - - @Test - fun testApfInstructionsEncoding() { - val v4gen = ApfV4Generator(APF_VERSION_2, ramSize, clampSize) - v4gen.addPass() - var program = v4gen.generate() - // encoding PASS opcode: opcode=0, imm_len=0, R=0 - assertContentEquals( - byteArrayOf(encodeInstruction(opcode = 0, immLength = 0, register = 0)), - program - ) - assertContentEquals( - listOf("0: pass"), - ApfJniUtils.disassembleApf(program).map { it.trim() } - ) - - var gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - gen.addDrop() - program = gen.generate().skipDataAndDebug() - // encoding DROP opcode: opcode=0, imm_len=0, R=1 - assertContentEquals( - byteArrayOf(encodeInstruction(opcode = 0, immLength = 0, register = 1)), - program - ) - assertContentEquals( - listOf("0: drop"), - ApfJniUtils.disassembleApf(program).map { it.trim() } - ) - - gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - gen.addCountAndPass(129) - program = gen.generate().skipDataAndDebug() - // encoding COUNT(PASS) opcode: opcode=0, imm_len=size_of(imm), R=0, imm=counterNumber - assertContentEquals( - byteArrayOf( - encodeInstruction(opcode = 0, immLength = 1, register = 0), - 0x81.toByte() - ), - program - ) - assertContentEquals( - listOf("0: pass counter=129"), - ApfJniUtils.disassembleApf(program).map { it.trim() } - ) - - gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - gen.addCountAndDrop(1000) - program = gen.generate().skipDataAndDebug() - // encoding COUNT(DROP) opcode: opcode=0, imm_len=size_of(imm), R=1, imm=counterNumber - assertContentEquals( - byteArrayOf( - encodeInstruction(opcode = 0, immLength = 2, register = 1), - 0x03, - 0xe8.toByte() - ), - program - ) - assertContentEquals( - listOf("0: drop counter=1000"), - ApfJniUtils.disassembleApf(program).map { it.trim() } - ) - - gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - gen.addCountAndPass(PASSED_ARP) - program = gen.generate().skipDataAndDebug() - // encoding COUNT(PASS) opcode: opcode=0, imm_len=size_of(imm), R=0, imm=counterNumber - assertContentEquals( - byteArrayOf( - encodeInstruction(opcode = 0, immLength = 1, register = 0), - PASSED_ARP.value().toByte() - ), - program - ) - assertContentEquals( - listOf("0: pass counter=10"), - ApfJniUtils.disassembleApf(program).map { it.trim() } - ) - - gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - gen.addCountAndDrop(DROPPED_ETHERTYPE_NOT_ALLOWED) - program = gen.generate().skipDataAndDebug() - // encoding COUNT(DROP) opcode: opcode=0, imm_len=size_of(imm), R=1, imm=counterNumber - assertContentEquals( - byteArrayOf( - encodeInstruction(opcode = 0, immLength = 1, register = 1), - DROPPED_ETHERTYPE_NOT_ALLOWED.value().toByte() - ), - program - ) - assertContentEquals( - listOf("0: drop counter=46"), - ApfJniUtils.disassembleApf(program).map { it.trim() } - ) - - gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - gen.addAllocateR0() - gen.addAllocate(1500) - program = gen.generate().skipDataAndDebug() - // encoding ALLOC opcode: opcode=21(EXT opcode number), imm=36(TRANS opcode number). - // R=0 means length stored in R0. R=1 means the length stored in imm1. - assertContentEquals( - byteArrayOf( - encodeInstruction(opcode = 21, immLength = 1, register = 0), - 36, - encodeInstruction(opcode = 21, immLength = 1, register = 1), - 36, - 0x05, - 0xDC.toByte() - ), - program - ) - assertContentEquals(listOf( - "0: allocate r0", - "2: allocate 1500" - ), ApfJniUtils.disassembleApf(program).map { it.trim() }) - - gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - gen.addTransmitWithoutChecksum() - gen.addTransmitL4(30, 40, 50, 256, true) - program = gen.generate().skipDataAndDebug() - // encoding TRANSMIT opcode: opcode=21(EXT opcode number), - // imm=37(TRANSMIT opcode number), - assertContentEquals(byteArrayOf( - encodeInstruction(opcode = 21, immLength = 1, register = 0), - 37, 255.toByte(), 255.toByte(), - encodeInstruction(opcode = 21, immLength = 1, register = 1), 37, 30, 40, 50, 1, 0 - ), program) - assertContentEquals(listOf( - "0: transmit ip_ofs=255", - "4: transmitudp ip_ofs=30, csum_ofs=40, csum_start=50, partial_csum=0x0100", - ), ApfJniUtils.disassembleApf(program).map { it.trim() }) - - val largeByteArray = ByteArray(256) { 0x01 } - gen = ApfV6Generator(largeByteArray, APF_VERSION_6, ramSize, clampSize) - program = gen.generate() - assertContentEquals( - byteArrayOf( - encodeInstruction(opcode = 14, immLength = 2, register = 1), 1, 0 - ) + largeByteArray + byteArrayOf( - encodeInstruction(opcode = 21, immLength = 1, register = 0), 48, 6, 13 - ), - program - ) - assertContentEquals( - listOf( - "0: data 256, " + "01".repeat(256), - "259: debugbuf size=1549" - ), - ApfJniUtils.disassembleApf(program).map { it.trim() } - ) - - gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - gen.addWriteU8(0x01) - gen.addWriteU16(0x0102) - gen.addWriteU32(0x01020304) - gen.addWriteU8(0x00) - gen.addWriteU8(0x80) - gen.addWriteU16(0x0000) - gen.addWriteU16(0x8000) - gen.addWriteU32(0x00000000) - gen.addWriteU32(0x80000000) - gen.addWrite32(-2) - gen.addWrite32(byteArrayOf(0xff.toByte(), 0xfe.toByte(), 0xfd.toByte(), 0xfc.toByte())) - program = gen.generate().skipDataAndDebug() - assertContentEquals(byteArrayOf( - encodeInstruction(24, 1, 0), 0x01, - encodeInstruction(24, 2, 0), 0x01, 0x02, - encodeInstruction(24, 4, 0), 0x01, 0x02, 0x03, 0x04, - encodeInstruction(24, 1, 0), 0x00, - encodeInstruction(24, 1, 0), 0x80.toByte(), - encodeInstruction(24, 2, 0), 0x00, 0x00, - encodeInstruction(24, 2, 0), 0x80.toByte(), 0x00, - encodeInstruction(24, 4, 0), 0x00, 0x00, 0x00, 0x00, - encodeInstruction(24, 4, 0), 0x80.toByte(), 0x00, 0x00, 0x00, - encodeInstruction(24, 4, 0), 0xff.toByte(), 0xff.toByte(), - 0xff.toByte(), 0xfe.toByte(), - encodeInstruction(24, 4, 0), 0xff.toByte(), 0xfe.toByte(), - 0xfd.toByte(), 0xfc.toByte()), program) - assertContentEquals(listOf( - "0: write 0x01", - "2: write 0x0102", - "5: write 0x01020304", - "10: write 0x00", - "12: write 0x80", - "14: write 0x0000", - "17: write 0x8000", - "20: write 0x00000000", - "25: write 0x80000000", - "30: write 0xfffffffe", - "35: write 0xfffefdfc" - ), ApfJniUtils.disassembleApf(program).map { it.trim() }) - - gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - gen.addWriteU8(R0) - gen.addWriteU16(R0) - gen.addWriteU32(R0) - gen.addWriteU8(R1) - gen.addWriteU16(R1) - gen.addWriteU32(R1) - program = gen.generate().skipDataAndDebug() - assertContentEquals(byteArrayOf( - encodeInstruction(21, 1, 0), 38, - encodeInstruction(21, 1, 0), 39, - encodeInstruction(21, 1, 0), 40, - encodeInstruction(21, 1, 1), 38, - encodeInstruction(21, 1, 1), 39, - encodeInstruction(21, 1, 1), 40 - ), program) - assertContentEquals(listOf( - "0: ewrite1 r0", - "2: ewrite2 r0", - "4: ewrite4 r0", - "6: ewrite1 r1", - "8: ewrite2 r1", - "10: ewrite4 r1" - ), ApfJniUtils.disassembleApf(program).map { it.trim() }) - - gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - gen.addDataCopy(0, 10) - gen.addDataCopy(1, 5) - gen.addPacketCopy(1000, 255) - program = gen.generate().skipDataAndDebug() - assertContentEquals(byteArrayOf( - encodeInstruction(25, 0, 1), 10, - encodeInstruction(25, 1, 1), 1, 5, - encodeInstruction(25, 2, 0), - 0x03.toByte(), 0xe8.toByte(), 0xff.toByte(), - ), program) - assertContentEquals(listOf( - "0: datacopy src=0, len=10", - "2: datacopy src=1, len=5", - "5: pktcopy src=1000, len=255" - ), ApfJniUtils.disassembleApf(program).map { it.trim() }) - - gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - gen.addDataCopyFromR0(5) - gen.addPacketCopyFromR0(5) - gen.addDataCopyFromR0LenR1() - gen.addPacketCopyFromR0LenR1() - program = gen.generate().skipDataAndDebug() - assertContentEquals(byteArrayOf( - encodeInstruction(21, 1, 1), 41, 5, - encodeInstruction(21, 1, 0), 41, 5, - encodeInstruction(21, 1, 1), 42, - encodeInstruction(21, 1, 0), 42, - ), program) - assertContentEquals(listOf( - "0: edatacopy src=r0, len=5", - "3: epktcopy src=r0, len=5", - "6: edatacopy src=r0, len=r1", - "8: epktcopy src=r0, len=r1" - ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) - - gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - gen.addJumpIfBytesAtR0Equal(byteArrayOf('a'.code.toByte()), ApfV4Generator.DROP_LABEL) - program = gen.generate().skipDataAndDebug() - assertContentEquals(byteArrayOf( - encodeInstruction(opcode = 20, immLength = 1, register = 1), - 1, - 1, - 'a'.code.toByte() - ), program) - assertContentEquals(listOf( - "0: jbseq r0, 0x1, DROP, 61" - ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) - - val qnames = byteArrayOf(1, 'A'.code.toByte(), 1, 'B'.code.toByte(), 0, 0) - gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - gen.addJumpIfPktAtR0DoesNotContainDnsQ(qnames, 0x0c, ApfV4Generator.DROP_LABEL) - gen.addJumpIfPktAtR0ContainDnsQ(qnames, 0x0c, ApfV4Generator.DROP_LABEL) - program = gen.generate().skipDataAndDebug() - assertContentEquals(byteArrayOf( - encodeInstruction(21, 1, 0), 43, 11, 0x0c.toByte(), - ) + qnames + byteArrayOf( - encodeInstruction(21, 1, 1), 43, 1, 0x0c.toByte(), - ) + qnames, program) - assertContentEquals(listOf( - "0: jdnsqne r0, DROP, 12, (1)A(1)B(0)(0)", - "10: jdnsqeq r0, DROP, 12, (1)A(1)B(0)(0)" - ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) - - gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - gen.addJumpIfPktAtR0DoesNotContainDnsQSafe(qnames, 0x0c, ApfV4Generator.DROP_LABEL) - gen.addJumpIfPktAtR0ContainDnsQSafe(qnames, 0x0c, ApfV4Generator.DROP_LABEL) - program = gen.generate().skipDataAndDebug() - assertContentEquals(byteArrayOf( - encodeInstruction(21, 1, 0), 45, 11, 0x0c.toByte(), - ) + qnames + byteArrayOf( - encodeInstruction(21, 1, 1), 45, 1, 0x0c.toByte(), - ) + qnames, program) - assertContentEquals(listOf( - "0: jdnsqnesafe r0, DROP, 12, (1)A(1)B(0)(0)", - "10: jdnsqeqsafe r0, DROP, 12, (1)A(1)B(0)(0)" - ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) - - gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - gen.addJumpIfPktAtR0DoesNotContainDnsA(qnames, ApfV4Generator.DROP_LABEL) - gen.addJumpIfPktAtR0ContainDnsA(qnames, ApfV4Generator.DROP_LABEL) - program = gen.generate().skipDataAndDebug() - assertContentEquals(byteArrayOf( - encodeInstruction(21, 1, 0), 44, 10, - ) + qnames + byteArrayOf( - encodeInstruction(21, 1, 1), 44, 1, - ) + qnames, program) - assertContentEquals(listOf( - "0: jdnsane r0, DROP, (1)A(1)B(0)(0)", - "9: jdnsaeq r0, DROP, (1)A(1)B(0)(0)" - ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) - - gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - gen.addJumpIfPktAtR0DoesNotContainDnsASafe(qnames, ApfV4Generator.DROP_LABEL) - gen.addJumpIfPktAtR0ContainDnsASafe(qnames, ApfV4Generator.DROP_LABEL) - program = gen.generate().skipDataAndDebug() - assertContentEquals(byteArrayOf( - encodeInstruction(21, 1, 0), 46, 10, - ) + qnames + byteArrayOf( - encodeInstruction(21, 1, 1), 46, 1, - ) + qnames, program) - assertContentEquals(listOf( - "0: jdnsanesafe r0, DROP, (1)A(1)B(0)(0)", - "9: jdnsaeqsafe r0, DROP, (1)A(1)B(0)(0)" - ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) - - gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - gen.addJumpIfOneOf(R1, List(32) { (it + 1).toLong() }.toSet(), DROP_LABEL) - gen.addJumpIfOneOf(R0, setOf(0, 257, 65536), DROP_LABEL) - gen.addJumpIfNoneOf(R0, setOf(1, 2, 3), DROP_LABEL) - program = gen.generate().skipDataAndDebug() - assertContentEquals(byteArrayOf( - encodeInstruction(21, 1, 1), 47, 24, -16, 1, 2, 3, 4, 5, 6, - 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, - 29, 30, 31, 32, - encodeInstruction(21, 1, 0), 47, 8, 14, 0, 0, 0, 0, 0, 0, - 1, 1, 0, 1, 0, 0, - encodeInstruction(21, 1, 0), 47, 1, 9, 1, 2, 3 - ), program) - - gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - gen.addJumpIfOneOf(R0, setOf(0, 128, 256, 65536), DROP_LABEL) - gen.addJumpIfNoneOf(R1, setOf(0, 128, 256, 65536), DROP_LABEL) - program = gen.generate().skipDataAndDebug() - assertContentEquals(listOf( - "0: joneof r0, DROP, { 0, 128, 256, 65536 }", - "20: jnoneof r1, DROP, { 0, 128, 256, 65536 }" - ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) - - gen = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - gen.addJumpIfBytesAtR0EqualsAnyOf(listOf(byteArrayOf(1, 2), byteArrayOf(3, 4)), DROP_LABEL) - gen.addJumpIfBytesAtR0EqualNoneOf(listOf(byteArrayOf(1, 2), byteArrayOf(3, 4)), DROP_LABEL) - gen.addJumpIfBytesAtR0EqualNoneOf(listOf(byteArrayOf(1, 1), byteArrayOf(1, 1)), DROP_LABEL) - program = gen.generate().skipDataAndDebug() - assertContentEquals(byteArrayOf( - encodeInstruction(opcode = 20, immLength = 2, register = 1), - 0, 15, 8, 2, 1, 2, 3, 4, - encodeInstruction(opcode = 20, immLength = 2, register = 0), - 0, 6, 8, 2, 1, 2, 3, 4, - encodeInstruction(opcode = 20, immLength = 1, register = 0), - 1, 2, 1, 1 - ), program) - assertContentEquals(listOf( - "0: jbseq r0, 0x2, DROP, { 0102, 0304 }", - "9: jbsne r0, 0x2, DROP, { 0102, 0304 }", - "18: jbsne r0, 0x2, DROP, 0101" - ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) - } - - @Test - fun testWriteToTxBuffer() { - var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addAllocate(14) - .addWriteU8(0x01) - .addWriteU16(0x0203) - .addWriteU32(0x04050607) - .addWrite32(-2) - .addWrite32(byteArrayOf(0xff.toByte(), 0xfe.toByte(), 0xfd.toByte(), 0xfc.toByte())) - .addLoadImmediate(R0, 1) - .addWriteU8(R0) - .addLoadImmediate(R0, 0x0203) - .addWriteU16(R0) - .addLoadImmediate(R1, 0x04050607) - .addWriteU32(R1) - .addTransmitWithoutChecksum() - .generate() - assertPass(APF_VERSION_6, program, ByteArray(MIN_PKT_SIZE)) - assertContentEquals( - byteArrayOf( - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0xff.toByte(), - 0xff.toByte(), 0xff.toByte(), 0xfe.toByte(), 0xff.toByte(), 0xfe.toByte(), - 0xfd.toByte(), 0xfc.toByte(), 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07), - ApfJniUtils.getTransmittedPacket() - ) - } - - @Test - fun testCopyToTxBuffer() { - var program = ApfV6Generator(byteArrayOf(33, 34, 35), APF_VERSION_6, ramSize, clampSize) - .addAllocate(14) - .addDataCopy(3, 2) // arg1=src, arg2=len - .addDataCopy(5, 1) // arg1=src, arg2=len - .addPacketCopy(0, 1) // arg1=src, arg2=len - .addPacketCopy(1, 3) // arg1=src, arg2=len - .addLoadImmediate(R0, 3) // data copy offset - .addDataCopyFromR0(2) // len - .addLoadImmediate(R0, 5) // data copy offset - .addLoadImmediate(R1, 1) // len - .addDataCopyFromR0LenR1() - .addLoadImmediate(R0, 0) // packet copy offset - .addPacketCopyFromR0(1) // len - .addLoadImmediate(R0, 1) // packet copy offset - .addLoadImmediate(R1, 3) // len - .addPacketCopyFromR0LenR1() - .addTransmitWithoutChecksum() - .generate() - assertPass(APF_VERSION_6, program, testPacket) - assertContentEquals( - byteArrayOf(33, 34, 35, 1, 2, 3, 4, 33, 34, 35, 1, 2, 3, 4), - ApfJniUtils.getTransmittedPacket() - ) - } - - @Test - fun testCopyContentToTxBuffer() { - val program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addAllocate(18) - .addDataCopy(HexDump.hexStringToByteArray("112233445566")) - .addDataCopy(HexDump.hexStringToByteArray("223344")) - .addDataCopy(HexDump.hexStringToByteArray("778899")) - .addDataCopy(HexDump.hexStringToByteArray("112233445566")) - .addTransmitWithoutChecksum() - .generate() - assertContentEquals(listOf( - "0: data 9, 112233445566778899", - "12: debugbuf size=1776", - "16: allocate 18", - "20: datacopy src=3, len=6", - "23: datacopy src=4, len=3", - "26: datacopy src=9, len=3", - "29: datacopy src=3, len=6", - "32: transmit ip_ofs=255" - ), ApfJniUtils.disassembleApf(program).map{ it.trim() }) - assertPass(APF_VERSION_6, program, testPacket) - val transmitPkt = HexDump.toHexString(ApfJniUtils.getTransmittedPacket()) - assertEquals("112233445566223344778899112233445566", transmitPkt) - } - - @Test - fun testPassDrop() { - var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addDrop() - .addPass() - .generate() - assertDrop(APF_VERSION_6, program, testPacket) - - program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addCountAndDrop(Counter.DROPPED_ETH_BROADCAST) - .generate() - verifyProgramRun(APF_VERSION_6, program, testPacket, DROPPED_ETH_BROADCAST) - - program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addCountAndPass(Counter.PASSED_ARP) - .generate() - verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP) - } - - @Test - fun testLoadStoreCounter() { - doTestLoadStoreCounter ( - { mutableMapOf() }, - { ApfV4Generator(APF_VERSION_3, ramSize, clampSize) } - ) - doTestLoadStoreCounter ( - { mutableMapOf(TOTAL_PACKETS to 1) }, - { ApfV6Generator(APF_VERSION_6, ramSize, clampSize) } - ) - } - - private fun doTestLoadStoreCounter( - getInitialMap: () -> MutableMap<Counter, Long>, - getGenerator: () -> ApfV4GeneratorBase<*> - ) { - val program = getGenerator() - .addIncrementCounter(PASSED_ARP, 2) - .addPass() - .generate() - var dataRegion = ByteArray(Counter.totalSize()) { 0 } - assertVerdict(APF_VERSION_6, PASS, program, testPacket, dataRegion) - var counterMap = decodeCountersIntoMap(dataRegion) - var expectedMap = getInitialMap() - expectedMap[PASSED_ARP] = 2 - assertEquals(expectedMap, counterMap) - } - - @Test - fun testV4CountAndPassDropCompareR0() { - doTestCountAndPassDropCompareR0( - getGenerator = { ApfV4Generator(APF_VERSION_3, ramSize, clampSize) }, - incTotal = false - ) - } - - @Test - fun testV6CountAndPassDropCompareR0() { - doTestCountAndPassDropCompareR0( - getGenerator = { ApfV6Generator(APF_VERSION_6, ramSize, clampSize) }, - incTotal = true - ) - } - - private fun doTestCountAndPassDropCompareR0( - getGenerator: () -> ApfV4GeneratorBase<*>, - incTotal: Boolean - ) { - var program = getGenerator() - .addLoadImmediate(R0, 123) - .addCountAndDropIfR0Equals(123, Counter.DROPPED_ETH_BROADCAST) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun( - APF_VERSION_6, - program, - testPacket, - DROPPED_ETH_BROADCAST, - incTotal = incTotal - ) - - program = getGenerator() - .addLoadImmediate(R0, 123) - .addCountAndPassIfR0Equals(123, Counter.PASSED_ARP) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) - - program = getGenerator() - .addLoadImmediate(R0, 123) - .addCountAndDropIfR0NotEquals(124, Counter.DROPPED_ETH_BROADCAST) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun( - APF_VERSION_6, - program, - testPacket, - DROPPED_ETH_BROADCAST, - incTotal = incTotal - ) - - program = getGenerator() - .addLoadImmediate(R0, 123) - .addCountAndPassIfR0NotEquals(124, Counter.PASSED_ARP) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) - - program = getGenerator() - .addLoadImmediate(R0, 123) - .addCountAndDropIfR0LessThan(124, Counter.DROPPED_ETH_BROADCAST) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun( - APF_VERSION_6, - program, - testPacket, - DROPPED_ETH_BROADCAST, - incTotal = incTotal - ) - - program = getGenerator() - .addLoadImmediate(R0, 123) - .addCountAndPassIfR0LessThan(124, Counter.PASSED_ARP) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) - - program = getGenerator() - .addLoadImmediate(R0, 123) - .addCountAndDropIfR0GreaterThan(122, Counter.DROPPED_ETH_BROADCAST) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun( - APF_VERSION_6, - program, - testPacket, - DROPPED_ETH_BROADCAST, - incTotal = incTotal - ) - - program = getGenerator() - .addLoadImmediate(R0, 123) - .addCountAndPassIfR0GreaterThan(122, Counter.PASSED_ARP) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) - - program = getGenerator() - .addLoadImmediate(R0, 1) - .addCountAndDropIfBytesAtR0NotEqual( - byteArrayOf(5, 5), DROPPED_ETH_BROADCAST) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun( - APF_VERSION_6, - program, - testPacket, - DROPPED_ETH_BROADCAST, - incTotal = incTotal - ) - - program = getGenerator() - .addLoadImmediate(R0, 1) - .addCountAndPassIfBytesAtR0NotEqual( - byteArrayOf(5, 5), PASSED_ARP) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) - - program = getGenerator() - .addLoadImmediate(R0, 1) - .addCountAndDropIfR0AnyBitsSet(0xffff, DROPPED_ETH_BROADCAST) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun( - APF_VERSION_6, - program, - testPacket, - DROPPED_ETH_BROADCAST, - incTotal = incTotal - ) - - program = getGenerator() - .addLoadImmediate(R0, 1) - .addCountAndPassIfR0AnyBitsSet(0xffff, PASSED_ARP) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) - - program = getGenerator() - .addLoadImmediate(R0, 123) - .addCountAndDropIfR0IsOneOf(setOf(123), DROPPED_ETH_BROADCAST) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun( - APF_VERSION_6, - program, - testPacket, - DROPPED_ETH_BROADCAST, - incTotal = incTotal - ) - - program = getGenerator() - .addLoadImmediate(R0, 123) - .addCountAndPassIfR0IsOneOf(setOf(123), PASSED_ARP) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) - - program = getGenerator() - .addLoadImmediate(R0, 123) - .addCountAndDropIfR0IsNoneOf(setOf(124), DROPPED_ETH_BROADCAST) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun( - APF_VERSION_6, - program, - testPacket, - DROPPED_ETH_BROADCAST, - incTotal = incTotal - ) - - program = getGenerator() - .addLoadImmediate(R0, 123) - .addCountAndPassIfR0IsNoneOf(setOf(124), PASSED_ARP) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) - - program = getGenerator() - .addLoadImmediate(R0, 123) - .addCountAndDropIfR0IsOneOf(setOf(123, 124), DROPPED_ETH_BROADCAST) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun( - APF_VERSION_6, - program, - testPacket, - DROPPED_ETH_BROADCAST, - incTotal = incTotal - ) - - program = getGenerator() - .addLoadImmediate(R0, 123) - .addCountAndPassIfR0IsOneOf(setOf(123, 124), PASSED_ARP) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) - - program = getGenerator() - .addLoadImmediate(R0, 123) - .addCountAndDropIfR0IsNoneOf(setOf(122, 124), DROPPED_ETH_BROADCAST) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun( - APF_VERSION_6, - program, - testPacket, - DROPPED_ETH_BROADCAST, - incTotal = incTotal - ) - - program = getGenerator() - .addLoadImmediate(R0, 123) - .addCountAndPassIfR0IsNoneOf(setOf(122, 124), PASSED_ARP) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) - - program = getGenerator() - .addLoadImmediate(R0, 0) - .addCountAndDropIfBytesAtR0EqualsAnyOf( - listOf(byteArrayOf(1, 2), byteArrayOf(3, 4)), - DROPPED_ETH_BROADCAST - ) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun( - APF_VERSION_6, - program, - testPacket, - DROPPED_ETH_BROADCAST, - incTotal = incTotal - ) - - program = getGenerator() - .addLoadImmediate(R0, 0) - .addCountAndPassIfBytesAtR0EqualsAnyOf( - listOf(byteArrayOf(1, 2), byteArrayOf(3, 4)), - PASSED_ARP - ) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) - - program = getGenerator() - .addLoadImmediate(R0, 0) - .addCountAndDropIfBytesAtR0EqualsNoneOf( - listOf(byteArrayOf(1, 3), byteArrayOf(3, 4)), - DROPPED_ETH_BROADCAST - ) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun( - APF_VERSION_6, - program, - testPacket, - DROPPED_ETH_BROADCAST, - incTotal = incTotal - ) - - program = getGenerator() - .addLoadImmediate(R0, 0) - .addCountAndPassIfBytesAtR0EqualsNoneOf( - listOf(byteArrayOf(1, 3), byteArrayOf(3, 4)), - PASSED_ARP - ) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) - - program = getGenerator() - .addLoadImmediate(R0, 1) - .addCountAndDropIfBytesAtR0Equal( - byteArrayOf(2, 3), DROPPED_ETH_BROADCAST) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun( - APF_VERSION_6, - program, - testPacket, - DROPPED_ETH_BROADCAST, - incTotal = incTotal - ) - - program = getGenerator() - .addLoadImmediate(R0, 1) - .addCountAndPassIfBytesAtR0Equal( - byteArrayOf(2, 3), PASSED_ARP) - .addPass() - .addCountTrampoline() - .generate() - verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal) - } - - private fun doTestEtherTypeAllowListFilter(apfVersion: Int) { - val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) - val apfFilter = - ApfFilter( - context, - getDefaultConfig(apfVersion), - ifParams, - ipClientCallback, - metrics, - dependencies - ) - verify(ipClientCallback, times(2)).installPacketFilter(programCaptor.capture()) - val program = programCaptor.allValues.last() - - // Using scapy to generate IPv4 mDNS packet: - // eth = Ether(src="E8:9F:80:66:60:BB", dst="01:00:5E:00:00:FB") - // ip = IP(src="192.168.1.1") - // udp = UDP(sport=5353, dport=5353) - // dns = DNS(qd=DNSQR(qtype="PTR", qname="a.local")) - // p = eth/ip/udp/dns - val mdnsPkt = "01005e0000fbe89f806660bb080045000035000100004011d812c0a80101e00000f" + - "b14e914e900214d970000010000010000000000000161056c6f63616c00000c0001" - verifyProgramRun(APF_VERSION_6, program, HexDump.hexStringToByteArray(mdnsPkt), PASSED_IPV4) - - // Using scapy to generate RA packet: - // eth = Ether(src="E8:9F:80:66:60:BB", dst="33:33:00:00:00:01") - // ip6 = IPv6(src="fe80::1", dst="ff02::1") - // icmp6 = ICMPv6ND_RA(routerlifetime=3600, retranstimer=3600) - // p = eth/ip6/icmp6 - val raPkt = "333300000001e89f806660bb86dd6000000000103afffe800000000000000000000000" + - "000001ff0200000000000000000000000000018600600700080e100000000000000e10" - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(raPkt), - PASSED_IPV6_ICMP - ) - - // Using scapy to generate ethernet packet with type 0x88A2: - // p = Ether(type=0x88A2)/Raw(load="01") - val ethPkt = "ffffffffffff047bcb463fb588a23031" - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(ethPkt), - DROPPED_ETHERTYPE_NOT_ALLOWED - ) - - apfFilter.shutdown() - } - - @Test - @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - fun testV4EtherTypeAllowListFilter() { - doTestEtherTypeAllowListFilter(APF_VERSION_3) - } - - @Test - @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - fun testV6EtherTypeAllowListFilter() { - doTestEtherTypeAllowListFilter(APF_VERSION_6) - } - - @Test - fun testV4CountAndPassDrop() { - var program = ApfV4Generator(APF_VERSION_3, ramSize, clampSize) - .addCountAndDrop(Counter.DROPPED_ETH_BROADCAST) - .addCountTrampoline() - .generate() - verifyProgramRun( - APF_VERSION_6, - program, - testPacket, - DROPPED_ETH_BROADCAST, - incTotal = false - ) - - program = ApfV4Generator(APF_VERSION_3, ramSize, clampSize) - .addCountAndPass(Counter.PASSED_ARP) - .addCountTrampoline() - .generate() - verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = false) - } - - @Test - fun testV2CountAndPassDrop() { - var program = ApfV4Generator(APF_VERSION_2, ramSize, clampSize) - .addCountAndDrop(Counter.DROPPED_ETH_BROADCAST) - .addCountTrampoline() - .generate() - var dataRegion = ByteArray(Counter.totalSize()) { 0 } - assertVerdict(APF_VERSION_6, DROP, program, testPacket, dataRegion) - assertContentEquals(ByteArray(Counter.totalSize()) { 0 }, dataRegion) - - program = ApfV4Generator(APF_VERSION_2, ramSize, clampSize) - .addCountAndPass(PASSED_ARP) - .addCountTrampoline() - .generate() - dataRegion = ByteArray(Counter.totalSize()) { 0 } - assertVerdict(APF_VERSION_6, PASS, program, testPacket, dataRegion) - assertContentEquals(ByteArray(Counter.totalSize()) { 0 }, dataRegion) - } - - @Test - fun testAllocateFailure() { - val program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - // allocate size: 65535 > sizeof(apf_test_buffer): 1514, trigger allocate failure. - .addAllocate(65535) - .addDrop() - .generate() - verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ALLOCATE_FAILURE) - } - - @Test - fun testTransmitFailure() { - val program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addAllocate(14) - // len: 13 is less than ETH_HLEN, trigger transmit failure. - .addLoadImmediate(R0, 13) - .addStoreToMemory(MemorySlot.TX_BUFFER_OUTPUT_POINTER, R0) - .addTransmitWithoutChecksum() - .addDrop() - .generate() - verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_TRANSMIT_FAILURE) - } - - @Test - fun testTransmitL4() { - val etherIpv4UdpPacket = intArrayOf( - 0x01, 0x00, 0x5e, 0x00, 0x00, 0xfb, - 0x38, 0xca, 0x84, 0xb7, 0x7f, 0x16, - 0x08, 0x00, // end of ethernet header - 0x45, - 0x04, - 0x00, 0x3f, - 0x43, 0xcd, - 0x40, 0x00, - 0xff, - 0x11, - 0x00, 0x00, // ipv4 checksum set to 0 - 0xc0, 0xa8, 0x01, 0x03, - 0xe0, 0x00, 0x00, 0xfb, // end of ipv4 header - 0x14, 0xe9, - 0x14, 0xe9, - 0x00, 0x2b, - 0x00, 0x2b, // end of udp header. udp checksum set to udp (header + payload) size - 0x00, 0x00, 0x84, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x62, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, - 0x00, 0x00, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x04, 0xc0, 0xa8, 0x01, - 0x09, - ).map { it.toByte() }.toByteArray() - val program = ApfV6Generator(etherIpv4UdpPacket, APF_VERSION_6, ramSize, clampSize) - .addAllocate(etherIpv4UdpPacket.size) - .addDataCopy(3, etherIpv4UdpPacket.size) // arg1=src, arg2=len - .addTransmitL4( - ETH_HLEN, // ipOfs, - ETH_HLEN + IPV4_HLEN + 6, // csumOfs - ETH_HLEN + IPV4_HLEN - 8, // csumStart - IPPROTO_UDP, // partialCsum - true // isUdp - ) - .generate() - assertPass(APF_VERSION_6, program, testPacket) - val txBuf = ByteBuffer.wrap(ApfJniUtils.getTransmittedPacket()) - Struct.parse(EthernetHeader::class.java, txBuf) - val ipv4Hdr = Struct.parse(Ipv4Header::class.java, txBuf) - val udpHdr = Struct.parse(UdpHeader::class.java, txBuf) - assertEquals(0x9535.toShort(), ipv4Hdr.checksum) - assertEquals(0xa73d.toShort(), udpHdr.checksum) - } - - @Test - fun testDnsQuestionMatch() { - // needles = { A, B.LOCAL } - val needlesMatch = intArrayOf( - 0x01, 'A'.code, - 0x00, - 0x01, 'B'.code, - 0x05, 'L'.code, 'O'.code, 'C'.code, 'A'.code, 'L'.code, - 0x00, - 0x00 - ).map { it.toByte() }.toByteArray() - val udpPayload = intArrayOf( - 0x00, 0x00, 0x00, 0x00, // tid = 0x00, flags = 0x00, - 0x00, 0x02, // qdcount = 2 - 0x00, 0x00, // ancount = 0 - 0x00, 0x00, // nscount = 0 - 0x00, 0x00, // arcount = 0 - 0x01, 'a'.code, - 0x01, 'b'.code, - 0x05, 'l'.code, 'o'.code, 'c'.code, 'a'.code, 'l'.code, - 0x00, // qname1 = a.b.local - 0x00, 0x01, 0x00, 0x01, // type = A, class = 0x0001 - 0xc0, 0x0e, // qname2 = b.local (name compression) - 0x00, 0x01, 0x00, 0x01 // type = A, class = 0x0001 - ).map { it.toByte() }.toByteArray() - - var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addLoadImmediate(R0, 0) - .addJumpIfPktAtR0ContainDnsQ(needlesMatch, 0x01, DROP_LABEL) // arg2=qtype - .addPass() - .generate() - assertDrop(APF_VERSION_6, program, udpPayload) - - program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addLoadImmediate(R0, 0) - .addJumpIfPktAtR0ContainDnsQSafe(needlesMatch, 0x01, DROP_LABEL) - .addPass() - .generate() - assertDrop(APF_VERSION_6, program, udpPayload) - - program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addLoadImmediate(R0, 0) - .addJumpIfPktAtR0DoesNotContainDnsQ(needlesMatch, 0x01, DROP_LABEL) // arg2=qtype - .addPass() - .generate() - assertPass(APF_VERSION_6, program, udpPayload) - - program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addLoadImmediate(R0, 0) - .addJumpIfPktAtR0DoesNotContainDnsQSafe(needlesMatch, 0x01, DROP_LABEL) // arg2=qtype - .addPass() - .generate() - assertPass(APF_VERSION_6, program, udpPayload) - - val badUdpPayload = intArrayOf( - 0x00, 0x00, 0x00, 0x00, // tid = 0x00, flags = 0x00, - 0x00, 0x02, // qdcount = 2 - 0x00, 0x00, // ancount = 0 - 0x00, 0x00, // nscount = 0 - 0x00, 0x00, // arcount = 0 - 0x01, 'a'.code, - 0x01, 'b'.code, - 0x05, 'l'.code, 'o'.code, 'c'.code, 'a'.code, 'l'.code, - 0x00, // qname1 = a.b.local - 0x00, 0x01, 0x00, 0x01, // type = A, class = 0x0001 - 0xc0, 0x1b, // corrupted pointer cause infinite loop - 0x00, 0x01, 0x00, 0x01 // type = A, class = 0x0001 - ).map { it.toByte() }.toByteArray() - - program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addLoadImmediate(R0, 0) - .addJumpIfPktAtR0ContainDnsQ(needlesMatch, 0x01, DROP_LABEL) // arg2=qtype - .addPass() - .generate() - verifyProgramRun(APF_VERSION_6, program, badUdpPayload, CORRUPT_DNS_PACKET, result = DROP) - - program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addLoadImmediate(R0, 0) - .addJumpIfPktAtR0ContainDnsQSafe(needlesMatch, 0x01, DROP_LABEL) // arg2=qtype - .addPass() - .generate() - verifyProgramRun(APF_VERSION_6, program, badUdpPayload, CORRUPT_DNS_PACKET, result = PASS) - } - - @Test - fun testDnsAnswerMatch() { - // needles = { A, B.LOCAL } - val needlesMatch = intArrayOf( - 0x01, 'A'.code, - 0x00, - 0x01, 'B'.code, - 0x05, 'L'.code, 'O'.code, 'C'.code, 'A'.code, 'L'.code, - 0x00, - 0x00 - ).map { it.toByte() }.toByteArray() - - val udpPayload = intArrayOf( - 0x00, 0x00, 0x84, 0x00, // tid = 0x00, flags = 0x8400, - 0x00, 0x00, // qdcount = 0 - 0x00, 0x02, // ancount = 2 - 0x00, 0x00, // nscount = 0 - 0x00, 0x00, // arcount = 0 - 0x01, 'a'.code, - 0x01, 'b'.code, - 0x05, 'l'.code, 'o'.code, 'c'.code, 'a'.code, 'l'.code, - 0x00, // name1 = a.b.local - 0x00, 0x01, 0x80, 0x01, // type = A, class = 0x8001 - 0x00, 0x00, 0x00, 0x78, // ttl = 120 - 0x00, 0x04, 0xc0, 0xa8, 0x01, 0x09, // rdlengh = 4, rdata = 192.168.1.9 - 0xc0, 0x0e, // name2 = b.local (name compression) - 0x00, 0x01, 0x80, 0x01, // type = A, class = 0x8001 - 0x00, 0x00, 0x00, 0x78, // ttl = 120 - 0x00, 0x04, 0xc0, 0xa8, 0x01, 0x09 // rdlengh = 4, rdata = 192.168.1.9 - ).map { it.toByte() }.toByteArray() - - var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addLoadImmediate(R0, 0) - .addJumpIfPktAtR0ContainDnsA(needlesMatch, DROP_LABEL) - .addPass() - .generate() - assertDrop(APF_VERSION_6, program, udpPayload) - - program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addLoadImmediate(R0, 0) - .addJumpIfPktAtR0ContainDnsASafe(needlesMatch, DROP_LABEL) - .addPass() - .generate() - assertDrop(APF_VERSION_6, program, udpPayload) - - program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addLoadImmediate(R0, 0) - .addJumpIfPktAtR0DoesNotContainDnsA(needlesMatch, DROP_LABEL) - .addPass() - .generate() - assertPass(APF_VERSION_6, program, udpPayload) - - program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addLoadImmediate(R0, 0) - .addJumpIfPktAtR0DoesNotContainDnsASafe(needlesMatch, DROP_LABEL) - .addPass() - .generate() - assertPass(APF_VERSION_6, program, udpPayload) - - val badUdpPayload = intArrayOf( - 0x00, 0x00, 0x84, 0x00, // tid = 0x00, flags = 0x8400, - 0x00, 0x00, // qdcount = 0 - 0x00, 0x02, // ancount = 2 - 0x00, 0x00, // nscount = 0 - 0x00, 0x00, // arcount = 0 - 0x01, 'a'.code, - 0x01, 'b'.code, - 0x05, 'l'.code, 'o'.code, 'c'.code, 'a'.code, 'l'.code, - 0x00, // name1 = a.b.local - 0x00, 0x01, 0x80, 0x01, // type = A, class = 0x8001 - 0x00, 0x00, 0x00, 0x78, // ttl = 120 - 0x00, 0x04, 0xc0, 0xa8, 0x01, 0x09, // rdlengh = 4, rdata = 192.168.1.9 - 0xc0, 0x25, // corrupted pointer cause infinite loop - 0x00, 0x01, 0x80, 0x01, // type = A, class = 0x8001 - 0x00, 0x00, 0x00, 0x78, // ttl = 120 - 0x00, 0x04, 0xc0, 0xa8, 0x01, 0x09 // rdlengh = 4, rdata = 192.168.1.9 - ).map { it.toByte() }.toByteArray() - - program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addLoadImmediate(R0, 0) - .addJumpIfPktAtR0ContainDnsA(needlesMatch, DROP_LABEL) - .addPass() - .generate() - verifyProgramRun(APF_VERSION_6, program, badUdpPayload, CORRUPT_DNS_PACKET, result = DROP) - - program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addLoadImmediate(R0, 0) - .addJumpIfPktAtR0ContainDnsASafe(needlesMatch, DROP_LABEL) - .addPass() - .generate() - verifyProgramRun(APF_VERSION_6, program, badUdpPayload, CORRUPT_DNS_PACKET, result = PASS) - } - - @Test - fun testGetCounterValue() { - val counterBytes = intArrayOf(0xff, 0, 0, 0, 0x78, 0x56, 0x34, 0x12) - .map { it.toByte() }.toByteArray() - assertEquals(0xff, ApfCounterTracker.getCounterValue(counterBytes, Counter.TOTAL_PACKETS)) - } - - @Test - fun testJumpMultipleByteSequencesMatch() { - var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addLoadImmediate(R0, 0) - .addJumpIfBytesAtR0EqualsAnyOf( - listOf(byteArrayOf(1, 2, 3), byteArrayOf(6, 5, 4)), - DROP_LABEL - ) - .addPass() - .generate() - assertDrop(APF_VERSION_6, program, testPacket) - - program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addLoadImmediate(R0, 2) - .addJumpIfBytesAtR0EqualsAnyOf( - listOf(byteArrayOf(1, 2, 3), byteArrayOf(6, 5, 4)), - DROP_LABEL - ) - .addPass() - .generate() - assertPass(APF_VERSION_6, program, testPacket) - - program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addLoadImmediate(R0, 1) - .addJumpIfBytesAtR0EqualNoneOf( - listOf(byteArrayOf(1, 2, 3), byteArrayOf(6, 5, 4)), - DROP_LABEL - ) - .addPass() - .generate() - assertDrop(APF_VERSION_6, program, testPacket) - - program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addLoadImmediate(R0, 0) - .addJumpIfBytesAtR0EqualNoneOf( - listOf(byteArrayOf(1, 2, 3), byteArrayOf(6, 5, 4)), - DROP_LABEL - ) - .addPass() - .generate() - assertPass(APF_VERSION_6, program, testPacket) - } - - @Test - fun testJumpOneOf() { - var program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addLoadImmediate(R0, 255) - .addJumpIfOneOf(R0, setOf(1, 2, 3, 128, 255), DROP_LABEL) - .addPass() - .generate() - assertDrop(APF_VERSION_6, program, testPacket) - - program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addLoadImmediate(R0, 254) - .addJumpIfOneOf(R0, setOf(1, 2, 3, 128, 255), DROP_LABEL) - .addPass() - .generate() - assertPass(APF_VERSION_6, program, testPacket) - - program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addLoadImmediate(R0, 254) - .addJumpIfNoneOf(R0, setOf(1, 2, 3, 128, 255), DROP_LABEL) - .addPass() - .generate() - assertDrop(APF_VERSION_6, program, testPacket) - - program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addLoadImmediate(R0, 255) - .addJumpIfNoneOf(R0, setOf(1, 2, 3, 128, 255), DROP_LABEL) - .addPass() - .generate() - assertPass(APF_VERSION_6, program, testPacket) - } - - @Test - fun testDebugBuffer() { - val program = ApfV6Generator(APF_VERSION_6, ramSize, clampSize) - .addLoad8(R0, 255) - .generate() - val dataRegion = ByteArray(ramSize - program.size) { 0 } - - assertVerdict(APF_VERSION_6, PASS, program, testPacket, dataRegion) - // offset 3 in the data region should contain if the interpreter is APFv6 mode or not - assertEquals(1, dataRegion[3]) - } - - @Test - fun testIPv4PacketFilterOnV6OnlyNetwork() { - val apfFilter = - ApfFilter( - context, - getDefaultConfig(), - ifParams, - ipClientCallback, - metrics, - dependencies - ) - apfFilter.updateClatInterfaceState(true) - val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) - verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture()) - val program = programCaptor.allValues.last() - - // Using scapy to generate IPv4 mDNS packet: - // eth = Ether(src="E8:9F:80:66:60:BB", dst="01:00:5E:00:00:FB") - // ip = IP(src="192.168.1.1") - // udp = UDP(sport=5353, dport=5353) - // dns = DNS(qd=DNSQR(qtype="PTR", qname="a.local")) - // p = eth/ip/udp/dns - val mdnsPkt = "01005e0000fbe89f806660bb080045000035000100004011d812c0a80101e00000f" + - "b14e914e900214d970000010000010000000000000161056c6f63616c00000c0001" - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(mdnsPkt), - DROPPED_IPV4_NON_DHCP4 - ) - - // Using scapy to generate DHCP4 offer packet: - // ether = Ether(src='00:11:22:33:44:55', dst='ff:ff:ff:ff:ff:ff') - // ip = IP(src='192.168.1.1', dst='255.255.255.255') - // udp = UDP(sport=67, dport=68) - // bootp = BOOTP(op=2, - // yiaddr='192.168.1.100', - // siaddr='192.168.1.1', - // chaddr=b'\x00\x11\x22\x33\x44\x55') - // dhcp_options = [('message-type', 'offer'), - // ('server_id', '192.168.1.1'), - // ('subnet_mask', '255.255.255.0'), - // ('router', '192.168.1.1'), - // ('lease_time', 86400), - // ('name_server', '8.8.8.8'), - // 'end'] - // dhcp = DHCP(options=dhcp_options) - // dhcp_offer_packet = ether/ip/udp/bootp/dhcp - val dhcp4Pkt = "ffffffffffff00112233445508004500012e000100004011b815c0a80101ffffffff0043" + - "0044011a5ffc02010600000000000000000000000000c0a80164c0a80101000000000011" + - "223344550000000000000000000000000000000000000000000000000000000000000000" + - "000000000000000000000000000000000000000000000000000000000000000000000000" + - "000000000000000000000000000000000000000000000000000000000000000000000000" + - "000000000000000000000000000000000000000000000000000000000000000000000000" + - "000000000000000000000000000000000000000000000000000000000000000000000000" + - "0000000000000000000000000000000000000000000000000000638253633501023604c0" + - "a801010104ffffff000304c0a80101330400015180060408080808ff" - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(dhcp4Pkt), - PASSED_IPV4_FROM_DHCPV4_SERVER - ) - - // Using scapy to generate DHCP4 offer packet: - // eth = Ether(src="E8:9F:80:66:60:BB", dst="01:00:5E:00:00:FB") - // ip = IP(src="192.168.1.10", dst="192.168.1.20") # IPv4 - // udp = UDP(sport=12345, dport=53) - // dns = DNS(qd=DNSQR(qtype="PTR", qname="a.local")) - // pkt = eth / ip / udp / dns - // fragments = fragment(pkt, fragsize=30) - // fragments[1] - val fragmentedUdpPkt = "01005e0000fbe89f806660bb08004500001d000100034011f75dc0a8010ac0a8" + - "01146f63616c00000c0001" - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(fragmentedUdpPkt), - DROPPED_IPV4_NON_DHCP4 - ) - apfFilter.shutdown() - } - - // The APFv6 code path is only turned on in V+ - @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - @Test - fun testArpTransmit() { - val apfFilter = - ApfFilter( - context, - getDefaultConfig(), - ifParams, - ipClientCallback, - metrics, - dependencies - ) - verify(ipClientCallback, times(2)).installPacketFilter(any()) - val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) - val lp = LinkProperties() - lp.addLinkAddress(linkAddress) - apfFilter.setLinkProperties(lp) - val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) - verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture()) - val program = programCaptor.value - val receivedArpPacketBuf = ArpPacket.buildArpPacket( - arpBroadcastMacAddress, - senderMacAddress, - hostIpv4Address, - HexDump.hexStringToByteArray("000000000000"), - senderIpv4Address, - ARP_REQUEST.toShort() - ) - val receivedArpPacket = ByteArray(ARP_ETHER_IPV4_LEN) - receivedArpPacketBuf.get(receivedArpPacket) - verifyProgramRun(APF_VERSION_6, program, receivedArpPacket, DROPPED_ARP_REQUEST_REPLIED) - - val transmittedPacket = ApfJniUtils.getTransmittedPacket() - val expectedArpReplyBuf = ArpPacket.buildArpPacket( - senderMacAddress, - apfFilter.mHardwareAddress, - senderIpv4Address, - senderMacAddress, - hostIpv4Address, - ARP_REPLY.toShort() - ) - val expectedArpReplyPacket = ByteArray(ARP_ETHER_IPV4_LEN) - expectedArpReplyBuf.get(expectedArpReplyPacket) - assertContentEquals( - expectedArpReplyPacket + ByteArray(18) {0}, - transmittedPacket - ) - apfFilter.shutdown() - } - - @Test - fun testArpOffloadDisabled() { - val apfConfig = getDefaultConfig() - apfConfig.shouldHandleArpOffload = false - val apfFilter = - ApfFilter( - context, - apfConfig, - ifParams, - ipClientCallback, - metrics, - dependencies - ) - verify(ipClientCallback, times(2)).installPacketFilter(any()) - val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) - val lp = LinkProperties() - lp.addLinkAddress(linkAddress) - apfFilter.setLinkProperties(lp) - val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) - verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture()) - val program = programCaptor.value - val receivedArpPacketBuf = ArpPacket.buildArpPacket( - arpBroadcastMacAddress, - senderMacAddress, - hostIpv4Address, - HexDump.hexStringToByteArray("000000000000"), - senderIpv4Address, - ARP_REQUEST.toShort() - ) - val receivedArpPacket = ByteArray(ARP_ETHER_IPV4_LEN) - receivedArpPacketBuf.get(receivedArpPacket) - verifyProgramRun(APF_VERSION_6, program, receivedArpPacket, PASSED_ARP_REQUEST) - apfFilter.shutdown() - } - - @Test - @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - fun testNsFilterNoIPv6() { - `when`(dependencies.getAnycast6Addresses(any())).thenReturn(listOf()) - val apfFilter = - ApfFilter( - context, - getDefaultConfig(), - ifParams, - ipClientCallback, - metrics, - dependencies - ) - - // validate NS packet check when there is no IPv6 address - val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) - verify(ipClientCallback, times(2)).installPacketFilter(programCaptor.capture()) - val program = programCaptor.allValues.last() - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // pkt = eth/ip6/icmp6 - val nsPkt = "01020304050600010203040586DD6000000000183AFF200100000000000" + - "00200001A1122334420010000000000000200001A334411228700452900" + - "00000020010000000000000200001A33441122" - // when there is no IPv6 addresses -> pass NS packet - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(nsPkt), - PASSED_IPV6_NS_NO_ADDRESS - ) - - apfFilter.shutdown() - } - - @Test - @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - fun testNsFilter() { - val apfFilter = - ApfFilter( - context, - getDefaultConfig(), - ifParams, - ipClientCallback, - metrics, - dependencies - ) - verify(ipClientCallback, times(2)).installPacketFilter(any()) - - val lp = LinkProperties() - for (addr in hostIpv6Addresses) { - lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) - } - - for (addr in hostIpv6TentativeAddresses) { - lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64, IFA_F_TENTATIVE, 0)) - } - - apfFilter.setLinkProperties(lp) - verify(ipClientCallback, times(3)).installPacketFilter(any()) - apfFilter.updateClatInterfaceState(true) - val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) - verify(ipClientCallback, times(4)).installPacketFilter(programCaptor.capture()) - val program = programCaptor.value - - // validate Ethernet dst address check - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="00:05:04:03:02:01") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // icmp6_opt = ICMPv6NDOptDstLLAddr(lladdr="00:01:02:03:04:05") - // pkt = eth/ip6/icmp6/icmp6_opt - val nonHostDstMacNsPkt = "00050403020100010203040586DD6000000000203AFF2001000000000000" + - "0200001A1122334420010000000000000200001A3344112287003D170000" + - "000020010000000000000200001A334411220201000102030405" - // invalid unicast ether dst -> pass - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(nonHostDstMacNsPkt), - DROPPED_IPV6_NS_OTHER_HOST - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="33:33:ff:03:02:01") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // icmp6_opt = ICMPv6NDOptDstLLAddr(lladdr="00:01:02:03:04:05") - // pkt = eth/ip6/icmp6/icmp6_opt - val nonMcastDstMacNsPkt = "3333FF03020100010203040586DD6000000000203AFF20010000000000" + - "000200001A1122334420010000000000000200001A3344112287003D17" + - "0000000020010000000000000200001A334411220201000102030405" - // mcast dst mac is not one of solicited mcast mac derived from one of device's ip -> pass - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(nonMcastDstMacNsPkt), - DROPPED_IPV6_NS_OTHER_HOST - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="33:33:ff:44:11:22") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") - // pkt = eth/ip6/icmp6/icmp6_opt - val hostMcastDstMacNsPkt = "3333FF44112200010203040586DD6000000000203AFF20010000000000" + - "000200001A1122334420010000000000000200001A3344112287003E17" + - "0000000020010000000000000200001A334411220101000102030405" - // mcast dst mac is one of solicited mcast mac derived from one of device's ip - // -> drop and replied - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(hostMcastDstMacNsPkt), - DROPPED_IPV6_NS_REPLIED_NON_DAD - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="FF:FF:FF:FF:FF:FF") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") - // pkt = eth/ip6/icmp6/icmp6_opt - val broadcastNsPkt = "FFFFFFFFFFFF00010203040586DD6000000000203AFF200100000000000002000" + - "01A1122334420010000000000000200001A3344112287003E1700000000200100" + - "00000000000200001A334411220101000102030405" - // mcast dst mac is broadcast address -> drop and replied - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(broadcastNsPkt), - DROPPED_IPV6_NS_REPLIED_NON_DAD - ) - - // validate IPv6 dst address check - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") - // pkt = eth/ip6/icmp6/icmp6_opt - val validHostDstIpNsPkt = "02030405060700010203040586DD6000000000203AFF200100000000000" + - "00200001A1122334420010000000000000200001A3344112287003E1700" + - "00000020010000000000000200001A334411220101000102030405" - // dst ip is one of device's ip -> drop and replied - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(validHostDstIpNsPkt), - DROPPED_IPV6_NS_REPLIED_NON_DAD - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::100:1b:aabb:ccdd", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::100:1b:aabb:ccdd") - // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") - // pkt = eth/ip6/icmp6/icmp6_opt - val validHostAnycastDstIpNsPkt = "02030405060700010203040586DD6000000000203AFF20010000" + - "000000000200001A1122334420010000000000000100001BAABB" + - "CCDD8700D9AE0000000020010000000000000100001BAABBCCDD" + - "0101000102030405" - // dst ip is device's anycast address -> drop and replied - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(validHostAnycastDstIpNsPkt), - DROPPED_IPV6_NS_REPLIED_NON_DAD - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:4444:5555", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") - // pkt = eth/ip6/icmp6/icmp6_opt - val nonHostUcastDstIpNsPkt = "02030405060700010203040586DD6000000000203AFF2001000000000" + - "0000200001A1122334420010000000000000200001A444455558700E8" + - "E30000000020010000000000000200001A334411220101000102030405" - // unicast dst ip is not one of device's ip -> pass - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(nonHostUcastDstIpNsPkt), - DROPPED_IPV6_NS_OTHER_HOST - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="ff02::1:ff44:1133", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") - // pkt = eth/ip6/icmp6/icmp6_opt - val nonHostMcastDstIpNsPkt = "02030405060700010203040586DD6000000000203AFF2001000000000" + - "0000200001A11223344FF0200000000000000000001FF441133870095" + - "1C0000000020010000000000000200001A334411220101000102030405" - // mcast dst ip is not one of solicited mcast ip derived from one of device's ip -> pass - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(nonHostMcastDstIpNsPkt), - DROPPED_IPV6_NS_OTHER_HOST - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="ff02::1:ff44:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") - // pkt = eth/ip6/icmp6/icmp6_opt - val hostMcastDstIpNsPkt = "02030405060700010203040586DD6000000000203AFF2001000000000000" + - "0200001A11223344FF0200000000000000000001FF4411228700952D0000" + - "000020010000000000000200001A334411220101000102030405" - // mcast dst ip is one of solicited mcast ip derived from one of device's ip - // -> drop and replied - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(hostMcastDstIpNsPkt), - DROPPED_IPV6_NS_REPLIED_NON_DAD - ) - - // validate IPv6 NS payload check - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255, plen=20) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") - // pkt = eth/ip6/icmp6/icmp6_opt - val shortNsPkt = "02030405060700010203040586DD6000000000143AFF20010000000000000200001A1" + - "122334420010000000000000200001A3344112287003B140000000020010000000000" + - "000200001A334411220101010203040506" - // payload len < 24 -> drop - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(shortNsPkt), - DROPPED_IPV6_NS_INVALID - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:4444:5555") - // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") - // pkt = eth/ip6/icmp6/icmp6_opt - val otherHostNsPkt = "02030405060700010203040586DD6000000000203AFF200100000000000002000" + - "01A1122334420010000000000000200001A334411228700E5E000000000200100" + - "00000000000200001A444455550101010203040506" - // target ip is not one of device's ip -> drop - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(otherHostNsPkt), - DROPPED_IPV6_NS_OTHER_HOST - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=20) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") - // pkt = eth/ip6/icmp6/icmp6_opt - val invalidHoplimitNsPkt = "02030405060700010203040586DD6000000000203A14200100000000000" + - "00200001A1122334420010000000000000200001A3344112287003B1400" + - "00000020010000000000000200001A334411220101010203040506" - // hoplimit is not 255 -> drop - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(invalidHoplimitNsPkt), - DROPPED_IPV6_NS_INVALID - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122", code=5) - // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") - // pkt = eth/ip6/icmp6/icmp6_opt - val invalidIcmpCodeNsPkt = "02030405060700010203040586DD6000000000203AFF200100000000000" + - "00200001A1122334420010000000000000200001A3344112287053B0F00" + - "00000020010000000000000200001A334411220101010203040506" - // icmp6 code is not 0 -> drop - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(invalidIcmpCodeNsPkt), - DROPPED_IPV6_NS_INVALID - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:1234:5678") - // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") - // pkt = eth/ip6/icmp6/icmp6_opt - val tentativeTargetIpNsPkt = "02030405060700010203040586DD6000000000203AFF200100000000" + - "00000200001A1122334420010000000000000200001A334411228700" + - "16CE0000000020010000000000000200001A123456780101010203040506" - // target ip is one of tentative address -> pass - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(tentativeTargetIpNsPkt), - PASSED_IPV6_NS_TENTATIVE - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1c:2255:6666") - // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") - // pkt = eth/ip6/icmp6/icmp6_opt - val invalidTargetIpNsPkt = "02030405060700010203040586DD6000000000203AFF200100000000000" + - "00200001A1122334420010000000000000200001A334411228700F6BC00" + - "00000020010000000000000200001C225566660101010203040506" - // target ip is none of {non-tentative, anycast} -> drop - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(invalidTargetIpNsPkt), - DROPPED_IPV6_NS_OTHER_HOST - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="::", dst="ff02::1:ff44:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // icmp6_opt = ICMPv6NDOptDstLLAddr(lladdr="02:03:04:05:06:07") - // pkt = eth/ip6/icmp6/icmp6_opt - val dadNsPkt = "02030405060700010203040586DD6000000000203AFF000000000000000000000000000" + - "00000FF0200000000000000000001FF4411228700F4A800000000200100000000000002" + - "00001A334411220201020304050607" - // DAD NS request -> pass - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(dadNsPkt), - PASSED_IPV6_NS_DAD - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // pkt = eth/ip6/icmp6 - val noOptionNsPkt = "02030405060700010203040586DD6000000000183AFF2001000000000000020000" + - "1A1122334420010000000000000200001A33441122870045290000000020010000" + - "000000000200001A33441122" - // payload len < 32 -> pass - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(noOptionNsPkt), - PASSED_IPV6_NS_NO_SLLA_OPTION - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="ff01::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") - // pkt = eth/ip6/icmp6/icmp6_opt - val nonDadMcastSrcIpPkt = "02030405060700010203040586DD6000000000203AFFFF01000000000000" + - "0200001A1122334420010000000000000200001A3344112287005C130000" + - "000020010000000000000200001A334411220101010203040506" - // non-DAD src IPv6 is FF::/8 -> drop - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(nonDadMcastSrcIpPkt), - DROPPED_IPV6_NS_INVALID - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="0001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") - // pkt = eth/ip6/icmp6/icmp6_opt - val nonDadLoopbackSrcIpPkt = "02030405060700010203040586DD6000000000203AFF0001000000000" + - "0000200001A1122334420010000000000000200001A3344112287005B" + - "140000000020010000000000000200001A334411220101010203040506" - // non-DAD src IPv6 is 00::/8 -> drop - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(nonDadLoopbackSrcIpPkt), - DROPPED_IPV6_NS_INVALID - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // icmp6_opt1 = ICMPv6NDOptDstLLAddr(lladdr="01:02:03:04:05:06") - // icmp6_opt2 = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") - // pkt = eth/ip6/icmp6/icmp6_opt1/icmp6_opt2 - val sllaNotFirstOptionNsPkt = "02030405060700010203040586DD6000000000283AFF200100000000" + - "00000200001A1122334420010000000000000200001A334411228700" + - "2FFF0000000020010000000000000200001A33441122020101020304" + - "05060101010203040506" - // non-DAD with multiple options, SLLA in 2nd option -> pass - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(sllaNotFirstOptionNsPkt), - PASSED_IPV6_NS_NO_SLLA_OPTION - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // icmp6_opt = ICMPv6NDOptDstLLAddr(lladdr="01:02:03:04:05:06") - // pkt = eth/ip6/icmp6/icmp6_opt - val noSllaOptionNsPkt = "02030405060700010203040586DD6000000000203AFF200100000000000002" + - "00001A1122334420010000000000000200001A3344112287003A1400000000" + - "20010000000000000200001A334411220201010203040506" - // non-DAD with one option but not SLLA -> pass - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(noSllaOptionNsPkt), - PASSED_IPV6_NS_NO_SLLA_OPTION - ) - - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="01:02:03:04:05:06") - // pkt = eth/ip6/icmp6/icmp6_opt - val mcastMacSllaOptionNsPkt = "02030405060700010203040586DD6000000000203AFF200100000000" + - "00000200001A1122334420010000000000000200001A334411228700" + - "3B140000000020010000000000000200001A33441122010101020304" + - "0506" - // non-DAD, SLLA is multicast MAC -> drop - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(mcastMacSllaOptionNsPkt), - DROPPED_IPV6_NS_INVALID - ) - apfFilter.shutdown() - } - - // The APFv6 code path is only turned on in V+ - @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - @Test - fun testNaTransmit() { - val apfFilter = - ApfFilter( - context, - getDefaultConfig(), - ifParams, - ipClientCallback, - metrics, - dependencies - ) - val lp = LinkProperties() - for (addr in hostIpv6Addresses) { - lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) - } - - apfFilter.setLinkProperties(lp) - val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) - verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture()) - val program = programCaptor.allValues.last() - val validIpv6Addresses = hostIpv6Addresses + hostAnycast6Addresses - for (addr in validIpv6Addresses) { - // unicast solicited NS request - val receivedUcastNsPacket = generateNsPacket( - senderMacAddress, - apfFilter.mHardwareAddress, - senderIpv6Address, - addr, - addr - ) - - verifyProgramRun( - APF_VERSION_6, - program, - receivedUcastNsPacket, - DROPPED_IPV6_NS_REPLIED_NON_DAD - ) - - val transmittedUcastPacket = ApfJniUtils.getTransmittedPacket() - val expectedUcastNaPacket = generateNaPacket( - apfFilter.mHardwareAddress, - senderMacAddress, - addr, - senderIpv6Address, - 0xe0000000.toInt(), // R=1, S=1, O=1 - addr - ) - - assertContentEquals( - expectedUcastNaPacket, - transmittedUcastPacket - ) - - val solicitedMcastAddr = NetworkStackUtils.ipv6AddressToSolicitedNodeMulticast( - InetAddress.getByAddress(addr) as Inet6Address - )!! - val mcastDa = NetworkStackUtils.ipv6MulticastToEthernetMulticast(solicitedMcastAddr) - .toByteArray() - - // multicast solicited NS request - var receivedMcastNsPacket = generateNsPacket( - senderMacAddress, - mcastDa, - senderIpv6Address, - solicitedMcastAddr.address, - addr - ) - - verifyProgramRun( - APF_VERSION_6, - program, - receivedMcastNsPacket, - DROPPED_IPV6_NS_REPLIED_NON_DAD - ) - - val transmittedMcastPacket = ApfJniUtils.getTransmittedPacket() - val expectedMcastNaPacket = generateNaPacket( - apfFilter.mHardwareAddress, - senderMacAddress, - addr, - senderIpv6Address, - 0xe0000000.toInt(), // R=1, S=1, O=1 - addr - ) - - assertContentEquals( - expectedMcastNaPacket, - transmittedMcastPacket - ) - } - - apfFilter.shutdown() - } - - // The APFv6 code path is only turned on in V+ - @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - @Test - fun testNaTransmitWithTclass() { - // mock nd traffic class from /proc/sys/net/ipv6/conf/{ifname}/ndisc_tclass to 20 - `when`(dependencies.getNdTrafficClass(any())).thenReturn(20) - val apfFilter = - ApfFilter( - context, - getDefaultConfig(), - ifParams, - ipClientCallback, - metrics, - dependencies - ) - - val lp = LinkProperties() - for (addr in hostIpv6Addresses) { - lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) - } - apfFilter.setLinkProperties(lp) - val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) - verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture()) - val program = programCaptor.allValues.last() - // Using scapy to generate IPv6 NS packet: - // eth = Ether(src="00:01:02:03:04:05", dst="02:03:04:05:06:07") - // ip6 = IPv6(src="2001::200:1a:1122:3344", dst="ff02::1:ff44:1122", hlim=255, tc=20) - // icmp6 = ICMPv6ND_NS(tgt="2001::200:1a:3344:1122") - // icmp6_opt = ICMPv6NDOptSrcLLAddr(lladdr="00:01:02:03:04:05") - // pkt = eth/ip6/icmp6/icmp6_opt - val hostMcastDstIpNsPkt = "02030405060700010203040586DD6140000000203AFF2001000000000000" + - "0200001A11223344FF0200000000000000000001FF4411228700952D0000" + - "000020010000000000000200001A334411220101000102030405" - verifyProgramRun( - APF_VERSION_6, - program, - HexDump.hexStringToByteArray(hostMcastDstIpNsPkt), - DROPPED_IPV6_NS_REPLIED_NON_DAD - ) - - val transmitPkt = ApfJniUtils.getTransmittedPacket() - // Using scapy to generate IPv6 NA packet: - // eth = Ether(src="02:03:04:05:06:07", dst="00:01:02:03:04:05") - // ip6 = IPv6(src="2001::200:1a:3344:1122", dst="2001::200:1a:1122:3344", hlim=255, tc=20) - // icmp6 = ICMPv6ND_NA(tgt="2001::200:1a:3344:1122", R=1, S=1, O=1) - // icmp6_opt = ICMPv6NDOptDstLLAddr(lladdr="02:03:04:05:06:07") - // pkt = eth/ip6/icmp6/icmp6_opt - val expectedNaPacket = "00010203040502030405060786DD6140000000203AFF2001000000000000020" + - "0001A3344112220010000000000000200001A1122334488005610E000000020" + - "010000000000000200001A334411220201020304050607" - assertContentEquals( - HexDump.hexStringToByteArray(expectedNaPacket), - transmitPkt - ) - } - - @Test - fun testNdOffloadDisabled() { - val apfConfig = getDefaultConfig() - apfConfig.shouldHandleNdOffload = false - val apfFilter = - ApfFilter( - context, - apfConfig, - ifParams, - ipClientCallback, - metrics, - dependencies - ) - val lp = LinkProperties() - for (addr in hostIpv6Addresses) { - lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) - } - - apfFilter.setLinkProperties(lp) - val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) - verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture()) - val program = programCaptor.allValues.last() - val validIpv6Addresses = hostIpv6Addresses + hostAnycast6Addresses - for (addr in validIpv6Addresses) { - // unicast solicited NS request - val receivedUcastNsPacket = generateNsPacket( - senderMacAddress, - apfFilter.mHardwareAddress, - senderIpv6Address, - addr, - addr - ) - - verifyProgramRun( - APF_VERSION_6, - program, - receivedUcastNsPacket, - PASSED_IPV6_ICMP - ) - - val solicitedMcastAddr = NetworkStackUtils.ipv6AddressToSolicitedNodeMulticast( - InetAddress.getByAddress(addr) as Inet6Address - )!! - val mcastDa = NetworkStackUtils.ipv6MulticastToEthernetMulticast(solicitedMcastAddr) - .toByteArray() - - // multicast solicited NS request - var receivedMcastNsPacket = generateNsPacket( - senderMacAddress, - mcastDa, - senderIpv6Address, - solicitedMcastAddr.address, - addr - ) - - verifyProgramRun( - APF_VERSION_6, - program, - receivedMcastNsPacket, - PASSED_IPV6_ICMP - ) - } - apfFilter.shutdown() - } - - @Test - fun testApfProgramUpdate() { - val apfFilter = - ApfFilter( - context, - getDefaultConfig(), - ifParams, - ipClientCallback, - metrics, - dependencies - ) - - verify(ipClientCallback, times(2)).installPacketFilter(any()) - // add IPv4 address, expect to have apf program update - val lp = LinkProperties() - val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) - lp.addLinkAddress(linkAddress) - apfFilter.setLinkProperties(lp) - verify(ipClientCallback, times(3)).installPacketFilter(any()) - - // add the same IPv4 address, expect to have no apf program update - apfFilter.setLinkProperties(lp) - verify(ipClientCallback, times(3)).installPacketFilter(any()) - - // add IPv6 addresses, expect to have apf program update - for (addr in hostIpv6Addresses) { - lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) - } - - apfFilter.setLinkProperties(lp) - verify(ipClientCallback, times(4)).installPacketFilter(any()) - - // add the same IPv6 addresses, expect to have no apf program update - apfFilter.setLinkProperties(lp) - verify(ipClientCallback, times(4)).installPacketFilter(any()) - - // add more tentative IPv6 addresses, expect to have apf program update - for (addr in hostIpv6TentativeAddresses) { - lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64, IFA_F_TENTATIVE, 0)) - } - - apfFilter.setLinkProperties(lp) - verify(ipClientCallback, times(5)).installPacketFilter(any()) - - // add the same IPv6 addresses, expect to have no apf program update - apfFilter.setLinkProperties(lp) - verify(ipClientCallback, times(5)).installPacketFilter(any()) - apfFilter.shutdown() - } - - private fun verifyProgramRun( - version: Int, - program: ByteArray, - pkt: ByteArray, - targetCnt: Counter, - cntMap: MutableMap<Counter, Long> = mutableMapOf(), - dataRegion: ByteArray = ByteArray(Counter.totalSize()) { 0 }, - incTotal: Boolean = true, - result: Int = if (targetCnt.name.startsWith("PASSED")) PASS else DROP - ) { - assertVerdict(version, result, program, pkt, dataRegion) - cntMap[targetCnt] = cntMap.getOrDefault(targetCnt, 0) + 1 - if (incTotal) { - cntMap[TOTAL_PACKETS] = cntMap.getOrDefault(TOTAL_PACKETS, 0) + 1 - } - val errMsg = "Counter is not increased properly. To debug: \n" + - " apf_run --program ${HexDump.toHexString(program)} " + - "--packet ${HexDump.toHexString(pkt)} " + - "--data ${HexDump.toHexString(dataRegion)} --age 0 " + - "${if (version == APF_VERSION_6) "--v6" else "" } --trace | less \n" - assertEquals(cntMap, decodeCountersIntoMap(dataRegion), errMsg) - } - - private fun decodeCountersIntoMap(counterBytes: ByteArray): Map<Counter, Long> { - val counters = Counter::class.java.enumConstants - val ret = HashMap<Counter, Long>() - val skippedCounters = setOf(APF_PROGRAM_ID, APF_VERSION) - // starting from index 2 to skip the endianness mark - for (c in listOf(*counters).subList(2, counters.size)) { - if (c in skippedCounters) continue - val value = ApfCounterTracker.getCounterValue(counterBytes, c) - if (value != 0L) { - ret[c] = value - } - } - return ret - } - - private fun encodeInstruction(opcode: Int, immLength: Int, register: Int): Byte { - val immLengthEncoding = if (immLength == 4) 3 else immLength - return opcode.shl(3).or(immLengthEncoding.shl(1)).or(register).toByte() - } - - private fun ByteArray.skipDataAndDebug(): ByteArray { - assertEquals( - listOf( - encodeInstruction(14, 2, 1), - 0, - 0, - encodeInstruction(21, 1, 0), - 48 - // the actual exception buffer size is not checked here. - ), - this.take(5) - ) - return this.drop(7).toByteArray() - } - - private fun getDefaultConfig(apfVersion: Int = APF_VERSION_6): ApfFilter.ApfConfiguration { - val config = ApfFilter.ApfConfiguration() - config.apfVersionSupported = apfVersion - config.apfRamSize = 4096 - config.multicastFilter = false - config.ieee802_3Filter = false - config.ethTypeBlackList = IntArray(0) - config.shouldHandleArpOffload = true - config.shouldHandleNdOffload = true - return config - } - - private fun generateNsPacket( - srcMac: ByteArray, - dstMac: ByteArray, - srcIp: ByteArray, - dstIp: ByteArray, - target: ByteArray, - ): ByteArray { - val nsPacketBuf = NeighborSolicitation.build( - MacAddress.fromBytes(srcMac), - MacAddress.fromBytes(dstMac), - InetAddress.getByAddress(srcIp) as Inet6Address, - InetAddress.getByAddress(dstIp) as Inet6Address, - InetAddress.getByAddress(target) as Inet6Address - ) - - val nsPacket = ByteArray( - ETHER_HEADER_LEN + IPV6_HEADER_LEN + ICMPV6_NS_HEADER_LEN + 8 // option length - ) - nsPacketBuf.get(nsPacket) - return nsPacket - } - - private fun generateNaPacket( - srcMac: ByteArray, - dstMac: ByteArray, - srcIp: ByteArray, - dstIp: ByteArray, - flags: Int, - target: ByteArray, - ): ByteArray { - val naPacketBuf = NeighborAdvertisement.build( - MacAddress.fromBytes(srcMac), - MacAddress.fromBytes(dstMac), - InetAddress.getByAddress(srcIp) as Inet6Address, - InetAddress.getByAddress(dstIp) as Inet6Address, - flags, - InetAddress.getByAddress(target) as Inet6Address - ) - val naPacket = ByteArray( - ETHER_HEADER_LEN + IPV6_HEADER_LEN + ICMPV6_NA_HEADER_LEN + 8 // lla option length - ) - - naPacketBuf.get(naPacket) - return naPacket - } -}
diff --git a/tests/unit/src/android/net/apf/ApfTestHelpers.kt b/tests/unit/src/android/net/apf/ApfTestHelpers.kt new file mode 100644 index 0000000..b67ec05 --- /dev/null +++ b/tests/unit/src/android/net/apf/ApfTestHelpers.kt
@@ -0,0 +1,93 @@ +/* + * 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.apf + +import android.net.apf.ApfCounterTracker.Counter +import android.net.apf.ApfCounterTracker.Counter.APF_PROGRAM_ID +import android.net.apf.ApfCounterTracker.Counter.APF_VERSION +import android.net.apf.ApfCounterTracker.Counter.TOTAL_PACKETS +import android.net.apf.ApfTestUtils.DROP +import android.net.apf.ApfTestUtils.PASS +import android.net.apf.ApfTestUtils.assertVerdict +import android.net.apf.BaseApfGenerator.APF_VERSION_6 +import android.net.ip.IpClient +import com.android.net.module.util.HexDump +import kotlin.test.assertEquals +import org.mockito.ArgumentCaptor +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.timeout +import org.mockito.Mockito.verify + +class ApfTestHelpers private constructor() { + companion object { + const val TIMEOUT_MS: Long = 1000 + fun verifyProgramRun( + version: Int, + program: ByteArray, + pkt: ByteArray, + targetCnt: Counter, + cntMap: MutableMap<Counter, Long> = mutableMapOf(), + dataRegion: ByteArray = ByteArray(Counter.totalSize()) { 0 }, + incTotal: Boolean = true, + result: Int = if (targetCnt.name.startsWith("PASSED")) PASS else DROP + ) { + assertVerdict(version, result, program, pkt, dataRegion) + cntMap[targetCnt] = cntMap.getOrDefault(targetCnt, 0) + 1 + if (incTotal) { + cntMap[TOTAL_PACKETS] = cntMap.getOrDefault(TOTAL_PACKETS, 0) + 1 + } + val errMsg = "Counter is not increased properly. To debug: \n" + + " apf_run --program ${HexDump.toHexString(program)} " + + "--packet ${HexDump.toHexString(pkt)} " + + "--data ${HexDump.toHexString(dataRegion)} --age 0 " + + "${if (version == APF_VERSION_6) "--v6" else "" } --trace | less \n" + assertEquals(cntMap, decodeCountersIntoMap(dataRegion), errMsg) + } + + fun decodeCountersIntoMap(counterBytes: ByteArray): Map<Counter, Long> { + val counters = Counter::class.java.enumConstants + val ret = HashMap<Counter, Long>() + val skippedCounters = setOf(APF_PROGRAM_ID, APF_VERSION) + // starting from index 2 to skip the endianness mark + if (counters != null) { + for (c in listOf(*counters).subList(2, counters.size)) { + if (c in skippedCounters) continue + val value = ApfCounterTracker.getCounterValue(counterBytes, c) + if (value != 0L) { + ret[c] = value + } + } + } + return ret + } + + fun consumeInstalledProgram( + ipClientCb: IpClient.IpClientCallbacksWrapper, + installCnt: Int + ): ByteArray { + val programCaptor = ArgumentCaptor.forClass( + ByteArray::class.java + ) + + verify(ipClientCb, timeout(TIMEOUT_MS).times(installCnt)).installPacketFilter( + programCaptor.capture() + ) + + clearInvocations<Any>(ipClientCb) + return programCaptor.value + } + } +}