| /* |
| * 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.NattKeepalivePacketDataParcelable |
| import android.net.TcpKeepalivePacketDataParcelable |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_NON_IPV4 |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_OTHER_HOST |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_REPLY_SPA_NO_HOST |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_REQUEST_REPLIED |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_UNKNOWN |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_ARP_V6_ONLY |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_ETHERTYPE_NOT_ALLOWED |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_GARP_REPLY |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_BROADCAST_ADDR |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_BROADCAST_NET |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_KEEPALIVE_ACK |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_L2_BROADCAST |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_MULTICAST |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_NATT_KEEPALIVE |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_NON_DHCP4 |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_TCP_PORT7_UNICAST |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MULTICAST_NA |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NON_ICMP_MULTICAST |
| 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_ETHER_OUR_SRC_MAC |
| import android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_BROADCAST_REPLY |
| import android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_REQUEST |
| import android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_UNICAST_REPLY |
| import android.net.apf.ApfCounterTracker.Counter.PASSED_DHCP |
| 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_IPV4_UNICAST |
| import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_ICMP |
| import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NON_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_MLD |
| import android.net.apf.ApfFilter.Dependencies |
| import android.net.apf.ApfTestHelpers.Companion.TIMEOUT_MS |
| import android.net.apf.ApfTestHelpers.Companion.consumeInstalledProgram |
| import android.net.apf.ApfTestHelpers.Companion.consumeTransmittedPackets |
| 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.net.nsd.NsdManager |
| import android.net.nsd.OffloadEngine |
| import android.net.nsd.OffloadServiceInfo |
| import android.os.Build |
| import android.os.Handler |
| import android.os.HandlerThread |
| import android.os.SystemClock |
| import android.system.Os |
| import android.system.OsConstants.AF_UNIX |
| import android.system.OsConstants.IFA_F_TENTATIVE |
| import android.system.OsConstants.SOCK_STREAM |
| 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 com.android.testutils.waitForIdle |
| import java.io.FileDescriptor |
| import java.net.Inet6Address |
| import java.net.InetAddress |
| import kotlin.test.assertContentEquals |
| import kotlin.test.assertEquals |
| import libcore.io.IoUtils |
| 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.ArgumentMatchers.anyInt |
| import org.mockito.ArgumentMatchers.anyLong |
| import org.mockito.ArgumentMatchers.eq |
| import org.mockito.Mock |
| import org.mockito.Mockito |
| import org.mockito.Mockito.doAnswer |
| import org.mockito.Mockito.doReturn |
| import org.mockito.Mockito.never |
| import org.mockito.Mockito.times |
| import org.mockito.Mockito.verify |
| 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 |
| private const val TAG = "ApfFilterTest" |
| } |
| |
| @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 |
| @Mock private lateinit var nsdManager: NsdManager |
| |
| @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(), |
| ) |
| |
| private val handlerThread by lazy { |
| HandlerThread("$TAG handler thread").apply { start() } |
| } |
| private val handler by lazy { Handler(handlerThread.looper) } |
| private var writerSocket = FileDescriptor() |
| |
| @Before |
| fun setUp() { |
| MockitoAnnotations.initMocks(this) |
| // mock anycast6 address from /proc/net/anycast6 |
| doReturn(hostAnycast6Addresses).`when`(dependencies).getAnycast6Addresses(any()) |
| |
| // mock ether multicast mac address from /proc/net/dev_mcast |
| doReturn(hostMulticastMacAddresses).`when`(dependencies).getEtherMulticastAddresses(any()) |
| |
| // mock nd traffic class from /proc/sys/net/ipv6/conf/{ifname}/ndisc_tclass |
| doReturn(0).`when`(dependencies).getNdTrafficClass(any()) |
| doAnswer { invocation: InvocationOnMock -> |
| synchronized(mApfFilterCreated) { |
| mApfFilterCreated.add(invocation.getArgument(0)) |
| } |
| }.`when`(dependencies).onApfFilterCreated(any()) |
| doReturn(SystemClock.elapsedRealtime()).`when`(dependencies).elapsedRealtime() |
| val readSocket = FileDescriptor() |
| Os.socketpair(AF_UNIX, SOCK_STREAM, 0, writerSocket, readSocket) |
| doReturn(readSocket).`when`(dependencies).createPacketReaderSocket(anyInt()) |
| doReturn(nsdManager).`when`(context).getSystemService(NsdManager::class.java) |
| } |
| |
| private fun shutdownApfFilters() { |
| quitResources(THREAD_QUIT_MAX_RETRY_COUNT, { |
| synchronized(mApfFilterCreated) { |
| val ret = ArrayList(mApfFilterCreated) |
| mApfFilterCreated.clear() |
| return@quitResources ret |
| } |
| }, { apf: AndroidPacketFilter -> |
| handler.post { apf.shutdown() } |
| }) |
| |
| synchronized(mApfFilterCreated) { |
| assertEquals( |
| 0, |
| mApfFilterCreated.size.toLong(), |
| "ApfFilters did not fully shutdown." |
| ) |
| } |
| } |
| |
| @After |
| fun tearDown() { |
| IoUtils.closeQuietly(writerSocket) |
| shutdownApfFilters() |
| handler.waitForIdle(TIMEOUT_MS) |
| Mockito.framework().clearInlineMocks() |
| ApfJniUtils.resetTransmittedPacketMemory() |
| handlerThread.quitSafely() |
| handlerThread.join() |
| } |
| |
| 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 { |
| lateinit var apfFilter: ApfFilter |
| handler.post { |
| apfFilter = ApfFilter( |
| handler, |
| context, |
| apfCfg, |
| ifParams, |
| ipClientCallback, |
| metrics, |
| dependencies |
| ) |
| } |
| handlerThread.waitForIdle(TIMEOUT_MS) |
| return apfFilter |
| } |
| |
| private fun doTestEtherTypeAllowListFilter(apfFilter: ApfFilter) { |
| val program = consumeInstalledProgram(ipClientCallback, installCnt = 2) |
| |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| 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 program = consumeInstalledProgram(ipClientCallback, installCnt = 3) |
| |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(mdnsPkt), |
| DROPPED_IPV4_NON_DHCP4 |
| ) |
| |
| // Using scapy to generate non UDP protocol 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', proto=12) |
| // pkt = ether/ip |
| val nonUdpPkt = """ |
| ffffffffffff00112233445508004500001400010000400cb934c0a80101ffffffff |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(nonUdpPkt), |
| DROPPED_IPV4_NON_DHCP4 |
| ) |
| |
| // Using scapy to generate fragmented UDP protocol 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', flags=1, frag=10, proto=17) |
| // pkt = ether/ip |
| val fragmentUdpPkt = """ |
| ffffffffffff0011223344550800450000140001200a40119925c0a80101ffffffff |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(fragmentUdpPkt), |
| DROPPED_IPV4_NON_DHCP4 |
| ) |
| |
| // Using scapy to generate destination port is not DHCP client port 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(dport=70) |
| // pkt = ether/ip/udp |
| val nonDhcpServerPkt = """ |
| ffffffffffff00112233445508004500001c000100004011b927c0a80101ffffffff0035004600083dba |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(nonDhcpServerPkt), |
| 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(dhcp4Pkt), |
| PASSED_IPV4_FROM_DHCPV4_SERVER |
| ) |
| |
| // Duplicate of dhcp4Pkt with DF flag set. |
| val dhcp4PktDf = """ |
| ffffffffffff00112233445508004500012e000140004011b815c0a80101ffffffff0043 |
| 0044011a5ffc02010600000000000000000000000000c0a80164c0a80101000000000011 |
| 223344550000000000000000000000000000000000000000000000000000000000000000 |
| 000000000000000000000000000000000000000000000000000000000000000000000000 |
| 000000000000000000000000000000000000000000000000000000000000000000000000 |
| 000000000000000000000000000000000000000000000000000000000000000000000000 |
| 000000000000000000000000000000000000000000000000000000000000000000000000 |
| 0000000000000000000000000000000000000000000000000000638253633501023604c0 |
| a801010104ffffff000304c0a80101330400015180060408080808ff |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(dhcp4PktDf), |
| 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(fragmentedUdpPkt), |
| DROPPED_IPV4_NON_DHCP4 |
| ) |
| } |
| |
| @Test |
| fun testLoopbackFilter() { |
| val apfConfig = getDefaultConfig() |
| val apfFilter = getApfFilter(apfConfig) |
| val program = consumeInstalledProgram(ipClientCallback, installCnt = 2) |
| // Using scapy to generate echo-ed broadcast packet: |
| // ether = Ether(src=${ifParams.macAddr}, dst='ff:ff:ff:ff:ff:ff') |
| // ip = IP(src='192.168.1.1', dst='255.255.255.255', proto=21) |
| // pkt = ether/ip |
| val nonDhcpBcastPkt = """ |
| ffffffffffff020304050607080045000014000100004015b92bc0a80101ffffffff |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(nonDhcpBcastPkt), |
| PASSED_ETHER_OUR_SRC_MAC |
| ) |
| } |
| |
| @Test |
| fun testIPv4MulticastPacketFilter() { |
| val apfConfig = getDefaultConfig() |
| apfConfig.multicastFilter = true |
| val apfFilter = getApfFilter(apfConfig) |
| consumeInstalledProgram(ipClientCallback, installCnt = 2) |
| val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) |
| val lp = LinkProperties() |
| lp.addLinkAddress(linkAddress) |
| apfFilter.setLinkProperties(lp) |
| val program = consumeInstalledProgram(ipClientCallback, installCnt = 1) |
| |
| // 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'\x02\x03\x04\x05\x06\x07') |
| // 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 |
| 0044011a5ffc02010600000000000000000000000000c0a80164c0a80101000000000203 |
| 040506070000000000000000000000000000000000000000000000000000000000000000 |
| 000000000000000000000000000000000000000000000000000000000000000000000000 |
| 000000000000000000000000000000000000000000000000000000000000000000000000 |
| 000000000000000000000000000000000000000000000000000000000000000000000000 |
| 000000000000000000000000000000000000000000000000000000000000000000000000 |
| 0000000000000000000000000000000000000000000000000000638253633501023604c0 |
| a801010104ffffff000304c0a80101330400015180060408080808ff |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(dhcp4Pkt), |
| PASSED_DHCP |
| ) |
| |
| // Using scapy to generate non DHCP multicast 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='224.0.0.1', proto=21) |
| // pkt = ether/ip |
| val nonDhcpMcastPkt = """ |
| ffffffffffff001122334455080045000014000100004015d929c0a80101e0000001 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(nonDhcpMcastPkt), |
| DROPPED_IPV4_MULTICAST |
| ) |
| |
| // Using scapy to generate non DHCP broadcast 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', proto=21) |
| // pkt = ether/ip |
| val nonDhcpBcastPkt = """ |
| ffffffffffff001122334455080045000014000100004015b92bc0a80101ffffffff |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(nonDhcpBcastPkt), |
| DROPPED_IPV4_BROADCAST_ADDR |
| ) |
| |
| // Using scapy to generate non DHCP subnet broadcast 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='10.0.0.255', proto=21) |
| // pkt = ether/ip |
| val nonDhcpNetBcastPkt = """ |
| ffffffffffff001122334455080045000014000100004015ae2cc0a801010a0000ff |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(nonDhcpNetBcastPkt), |
| DROPPED_IPV4_BROADCAST_NET |
| ) |
| |
| // Using scapy to generate non DHCP unicast packet: |
| // ether = Ether(src='00:11:22:33:44:55', dst='02:03:04:05:06:07') |
| // ip = IP(src='192.168.1.1', dst='192.168.1.2', proto=21) |
| // pkt = ether/ip |
| val nonDhcpUcastPkt = """ |
| 020304050607001122334455080045000014000100004015f780c0a80101c0a80102 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(nonDhcpUcastPkt), |
| PASSED_IPV4_UNICAST |
| ) |
| |
| // Using scapy to generate non DHCP unicast packet with broadcast ether destination: |
| // ether = Ether(src='00:11:22:33:44:55', dst='ff:ff:ff:ff:ff:ff') |
| // ip = IP(src='192.168.1.1', dst='192.168.1.2', proto=21) |
| // pkt = ether/ip |
| val nonDhcpUcastL2BcastPkt = """ |
| ffffffffffff001122334455080045000014000100004015f780c0a80101c0a80102 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(nonDhcpUcastL2BcastPkt), |
| DROPPED_IPV4_L2_BROADCAST |
| ) |
| } |
| |
| @Test |
| fun testArpFilterDropPktsOnV6OnlyNetwork() { |
| val apfFilter = getApfFilter() |
| consumeInstalledProgram(ipClientCallback, installCnt = 2) |
| apfFilter.updateClatInterfaceState(true) |
| val program = consumeInstalledProgram(ipClientCallback, installCnt = 1) |
| |
| // Drop ARP request packet when clat is enabled |
| // Using scapy to generate ARP request packet: |
| // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") |
| // arp = ARP() |
| // pkt = eth/arp |
| val arpPkt = """ |
| 010203040506000102030405080600010800060400015c857e3c74e1c0a8012200000000000000000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(arpPkt), |
| DROPPED_ARP_V6_ONLY |
| ) |
| } |
| |
| @Test |
| fun testIPv4TcpKeepaliveFilter() { |
| val srcAddr = byteArrayOf(10, 0, 0, 5) |
| val dstAddr = byteArrayOf(10, 0, 0, 6) |
| val srcPort = 12345 |
| val dstPort = 54321 |
| val seqNum = 2123456789 |
| val ackNum = 1234567890 |
| |
| // src: 10.0.0.5:12345 |
| // dst: 10.0.0.6:54321 |
| val parcel = TcpKeepalivePacketDataParcelable() |
| parcel.srcAddress = InetAddress.getByAddress(srcAddr).address |
| parcel.srcPort = srcPort |
| parcel.dstAddress = InetAddress.getByAddress(dstAddr).address |
| parcel.dstPort = dstPort |
| parcel.seq = seqNum |
| parcel.ack = ackNum |
| |
| val apfConfig = getDefaultConfig() |
| apfConfig.multicastFilter = true |
| apfConfig.ieee802_3Filter = true |
| val apfFilter = getApfFilter(apfConfig) |
| consumeInstalledProgram(ipClientCallback, installCnt = 2) |
| apfFilter.addTcpKeepalivePacketFilter(1, parcel) |
| var program = consumeInstalledProgram(ipClientCallback, installCnt = 1) |
| |
| // Drop IPv4 keepalive ack |
| // Using scapy to generate IPv4 TCP keepalive ack packet with seq + 1: |
| // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") |
| // ip = IP(src='10.0.0.6', dst='10.0.0.5') |
| // tcp = TCP(sport=54321, dport=12345, flags="A", seq=1234567890, ack=2123456790) |
| // pkt = eth/ip/tcp |
| val keepaliveAckPkt = """ |
| 01020304050600010203040508004500002800010000400666c50a0000060a000005d4313039499602d2 |
| 7e916116501020004b4f0000 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(keepaliveAckPkt), |
| DROPPED_IPV4_KEEPALIVE_ACK |
| ) |
| |
| // Pass IPv4 non-keepalive ack from the same source address |
| // Using scapy to generate IPv4 TCP non-keepalive ack from the same source address: |
| // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") |
| // ip = IP(src='10.0.0.6', dst='10.0.0.5') |
| // tcp = TCP(sport=54321, dport=12345, flags="A", seq=1234567990, ack=2123456789) |
| // pkt = eth/ip/tcp |
| val nonKeepaliveAckPkt1 = """ |
| 01020304050600010203040508004500002800010000400666c50a0000060a000005d431303949960336 |
| 7e916115501020004aec0000 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(nonKeepaliveAckPkt1), |
| PASSED_IPV4_UNICAST |
| ) |
| |
| // Pass IPv4 non-keepalive ack from the same source address |
| // Using scapy to generate IPv4 TCP non-keepalive ack from the same source address: |
| // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") |
| // ip = IP(src='10.0.0.6', dst='10.0.0.5') |
| // tcp = TCP(sport=54321, dport=12345, flags="A", seq=1234567890, ack=2123456790) |
| // payload = Raw(b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09') |
| // pkt = eth/ip/tcp/payload |
| val nonKeepaliveAckPkt2 = """ |
| 01020304050600010203040508004500003200010000400666bb0a0000060a000005d4313039499602d27 |
| e91611650102000372c000000010203040506070809 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(nonKeepaliveAckPkt2), |
| PASSED_IPV4_UNICAST |
| ) |
| |
| // Pass IPv4 keepalive ack from another address |
| // Using scapy to generate IPv4 TCP keepalive ack from another address: |
| // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") |
| // ip = IP(src='10.0.0.7', dst='10.0.0.5') |
| // tcp = TCP(sport=23456, dport=65432, flags="A", seq=2123456780, ack=1123456789) |
| // pkt = eth/ip/tcp |
| val otherSrcKeepaliveAck = """ |
| 01020304050600010203040508004500002800010000400666c40a0000070a0000055ba0ff987e91610c4 |
| 2f697155010200066e60000 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(otherSrcKeepaliveAck), |
| PASSED_IPV4_UNICAST |
| ) |
| |
| // test IPv4 packets when TCP keepalive filter is removed |
| apfFilter.removeKeepalivePacketFilter(1) |
| program = consumeInstalledProgram(ipClientCallback, installCnt = 1) |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(keepaliveAckPkt), |
| PASSED_IPV4_UNICAST |
| ) |
| |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(otherSrcKeepaliveAck), |
| PASSED_IPV4_UNICAST |
| ) |
| } |
| |
| @Test |
| fun testIPv4NattKeepaliveFilter() { |
| val srcAddr = byteArrayOf(10, 0, 0, 5) |
| val dstAddr = byteArrayOf(10, 0, 0, 6) |
| val srcPort = 1024 |
| val dstPort = 4500 |
| |
| // src: 10.0.0.5:1024 |
| // dst: 10.0.0.6:4500 |
| val parcel = NattKeepalivePacketDataParcelable() |
| parcel.srcAddress = InetAddress.getByAddress(srcAddr).address |
| parcel.srcPort = srcPort |
| parcel.dstAddress = InetAddress.getByAddress(dstAddr).address |
| parcel.dstPort = dstPort |
| |
| val apfConfig = getDefaultConfig() |
| apfConfig.multicastFilter = true |
| apfConfig.ieee802_3Filter = true |
| val apfFilter = getApfFilter(apfConfig) |
| consumeInstalledProgram(ipClientCallback, installCnt = 2) |
| apfFilter.addNattKeepalivePacketFilter(1, parcel) |
| val program = consumeInstalledProgram(ipClientCallback, installCnt = 1) |
| |
| // Drop IPv4 keepalive response packet |
| // Using scapy to generate IPv4 NAT-T keepalive ack packet with payload 0xff: |
| // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") |
| // ip = IP(src='10.0.0.6', dst='10.0.0.5') |
| // udp = UDP(sport=4500, dport=1024) |
| // payload = NAT_KEEPALIVE(nat_keepalive=0xff) |
| // pkt = eth/ip/udp/payload |
| val validNattPkt = """ |
| 01020304050600010203040508004500001d00010000401166c50a0000060a000005119404000009d73cff |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(validNattPkt), |
| DROPPED_IPV4_NATT_KEEPALIVE |
| ) |
| |
| // Pass IPv4 keepalive response packet with 0xfe payload |
| // Using scapy to generate IPv4 NAT-T keepalive ack packet with payload 0xfe: |
| // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") |
| // ip = IP(src='10.0.0.6', dst='10.0.0.5') |
| // udp = UDP(sport=4500, dport=1024) |
| // payload = NAT_KEEPALIVE(nat_keepalive=0xfe) |
| // pkt = eth/ip/udp/payload |
| val invalidNattPkt = """ |
| 01020304050600010203040508004500001d00010000401166c50a0000060a000005119404000009d83cfe |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(invalidNattPkt), |
| PASSED_IPV4_UNICAST |
| ) |
| |
| // Pass IPv4 non-keepalive response packet from the same source address |
| // Using scapy to generate IPv4 NAT-T keepalive ack packet with payload 0xfe: |
| // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") |
| // ip = IP(src='10.0.0.6', dst='10.0.0.5') |
| // udp = UDP(sport=4500, dport=1024) |
| // payload = Raw(b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09') |
| // pkt = eth/ip/udp/payload |
| val nonNattPkt = """ |
| 01020304050600010203040508004500002600010000401166bc0a0000060a000005119404000012c2120 |
| 0010203040506070809 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(nonNattPkt), |
| PASSED_IPV4_UNICAST |
| ) |
| |
| // Pass IPv4 non-keepalive response packet from other source address |
| // Using scapy to generate IPv4 NAT-T keepalive ack packet with payload 0xfe: |
| // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") |
| // ip = IP(src='10.0.0.7', dst='10.0.0.5') |
| // udp = UDP(sport=4500, dport=1024) |
| // payload = Raw(b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09') |
| // pkt = eth/ip/udp/payload |
| val otherSrcNonNattPkt = """ |
| 01020304050600010203040508004500002600010000401166bb0a0000070a000005119404000012c2110 |
| 0010203040506070809 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(otherSrcNonNattPkt), |
| PASSED_IPV4_UNICAST |
| ) |
| } |
| |
| @Test |
| fun testIPv4TcpPort7Filter() { |
| val apfFilter = getApfFilter() |
| val program = consumeInstalledProgram(ipClientCallback, installCnt = 2) |
| |
| // Drop IPv4 TCP port 7 packet |
| // Using scapy to generate IPv4 TCP port 7 packet: |
| // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") |
| // ip = IP(src='10.0.0.6', dst='10.0.0.5') |
| // tcp = TCP(dport=7) |
| // pkt = eth/ip/tcp |
| val tcpPort7Pkt = """ |
| 01020304050600010203040508004500002800010000400666c50a0000060a00000500140007000000000 |
| 0000000500220007bbd0000 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(tcpPort7Pkt), |
| DROPPED_IPV4_TCP_PORT7_UNICAST |
| ) |
| |
| // Pass IPv4 TCP initial fragment packet |
| // Using scapy to generate IPv4 TCP initial fragment packet: |
| // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") |
| // ip = IP(src='10.0.0.6', dst='10.0.0.5', flags=1, frag=0) |
| // tcp = TCP() |
| // pkt = eth/ip/tcp |
| val initialFragmentTcpPkt = """ |
| 01020304050600010203040508004500002800012000400646c50a0000060a00000500140050000000000 |
| 0000000500220007b740000 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(initialFragmentTcpPkt), |
| PASSED_IPV4 |
| ) |
| |
| // Pass IPv4 TCP fragment packet |
| // Using scapy to generate IPv4 TCP fragment packet: |
| // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") |
| // ip = IP(src='10.0.0.6', dst='10.0.0.5', flags=1, frag=100) |
| // tcp = TCP() |
| // pkt = eth/ip/tcp |
| val fragmentTcpPkt = """ |
| 01020304050600010203040508004500002800012064400646610a0000060a00000500140050000000000 |
| 0000000500220007b740000 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(fragmentTcpPkt), |
| PASSED_IPV4 |
| ) |
| } |
| |
| @Test |
| fun testIPv6MulticastPacketFilterInDozeMode() { |
| val apfConfig = getDefaultConfig() |
| apfConfig.multicastFilter = true |
| val apfFilter = getApfFilter(apfConfig) |
| consumeInstalledProgram(ipClientCallback, installCnt = 2) |
| val lp = LinkProperties() |
| for (addr in hostIpv6Addresses) { |
| lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) |
| } |
| apfFilter.setLinkProperties(lp) |
| apfFilter.setDozeMode(true) |
| val program = consumeInstalledProgram(ipClientCallback, installCnt = 2) |
| // Using scapy to generate non ICMPv6 sent to ff00::/8 (multicast prefix) 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="ff00::1", nh=59) |
| // pkt = eth/ip6 |
| val nonIcmpv6McastPkt = """ |
| ffffffffffff00112233445586dd6000000000003b4020010000000000000200001a11223344ff00000 |
| 0000000000000000000000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(nonIcmpv6McastPkt), |
| DROPPED_IPV6_NON_ICMP_MULTICAST |
| ) |
| |
| // Using scapy to generate ICMPv6 echo sent to ff00::/8 (multicast prefix) 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="ff00::1", hlim=255) |
| // icmp6 = ICMPv6EchoRequest() |
| // pkt = eth/ip6/icmp6 |
| val icmpv6EchoPkt = """ |
| 02030405060700010203040586dd6000000000083aff20010000000000000200001a11223344ff00000 |
| 000000000000000000000000180001a3a00000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(icmpv6EchoPkt), |
| DROPPED_IPV6_NON_ICMP_MULTICAST |
| ) |
| } |
| |
| @Test |
| fun testIPv6PacketFilter() { |
| val apfFilter = getApfFilter() |
| consumeInstalledProgram(ipClientCallback, installCnt = 2) |
| val lp = LinkProperties() |
| for (addr in hostIpv6Addresses) { |
| lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) |
| } |
| apfFilter.setLinkProperties(lp) |
| val program = consumeInstalledProgram(ipClientCallback, installCnt = 1) |
| // Using scapy to generate non ICMPv6 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", nh=59) |
| // pkt = eth/ip6 |
| val nonIcmpv6Pkt = """ |
| ffffffffffff00112233445586dd6000000000003b4020010000000000000200001a112233442001000 |
| 0000000000200001a33441122 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(nonIcmpv6Pkt), |
| PASSED_IPV6_NON_ICMP |
| ) |
| |
| // Using scapy to generate ICMPv6 NA sent to ff02::/120 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="ff02::1") |
| // icmp6 = ICMPv6ND_NA() |
| // pkt = eth/ip6/icmp6 |
| val icmpv6McastNaPkt = """ |
| 01020304050600010203040586dd6000000000183aff20010000000000000200001a11223344ff02000 |
| 000000000000000000000000188007227a000000000000000000000000000000000000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(icmpv6McastNaPkt), |
| DROPPED_IPV6_MULTICAST_NA |
| ) |
| |
| // Using scapy to generate IPv6 packet with hop-by-hop option: |
| // 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", nh=0) |
| // pkt = eth/ip6 |
| val ipv6WithHopByHopOptionPkt = """ |
| 01020304050600010203040586dd600000000000004020010000000000000200001a112233442001000 |
| 0000000000200001a33441122 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(ipv6WithHopByHopOptionPkt), |
| PASSED_MLD |
| ) |
| } |
| |
| @Test |
| fun testArpFilterDropPktsNoIPv4() { |
| val apfFilter = getApfFilter() |
| val program = consumeInstalledProgram(ipClientCallback, installCnt = 2) |
| |
| // Drop ARP request packet with invalid hw type |
| // Using scapy to generate ARP request packet with invalid hw type : |
| // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") |
| // arp = ARP(hwtype=3) |
| // pkt = eth/arp |
| val invalidHwTypePkt = """ |
| 01020304050600010203040508060003080000040001c0a8012200000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(invalidHwTypePkt), |
| DROPPED_ARP_NON_IPV4 |
| ) |
| |
| // Drop ARP request packet with invalid proto type |
| // Using scapy to generate ARP request packet with invalid proto type: |
| // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") |
| // arp = ARP(ptype=20) |
| // pkt = eth/arp |
| val invalidProtoTypePkt = """ |
| 010203040506000102030405080600010014060000015c857e3c74e1000000000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(invalidProtoTypePkt), |
| DROPPED_ARP_NON_IPV4 |
| ) |
| |
| // Drop ARP request packet with invalid hw len |
| // Using scapy to generate ARP request packet with invalid hw len: |
| // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") |
| // arp = ARP(hwlen=20) |
| // pkt = eth/arp |
| val invalidHwLenPkt = """ |
| 01020304050600010203040508060001080014040001000000000000000000000000 |
| 0000000000000000c0a8012200000000000000000000000000000000000000000000 |
| 0000 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(invalidHwLenPkt), |
| DROPPED_ARP_NON_IPV4 |
| ) |
| |
| // Drop ARP request packet with invalid proto len |
| // Using scapy to generate ARP request packet with invalid proto len: |
| // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") |
| // arp = ARP(plen=20) |
| // pkt = eth/arp |
| val invalidProtoLenPkt = """ |
| 010203040506000102030405080600010800061400015c857e3c74e1000000000000 |
| 00000000000000000000000000000000000000000000000000000000000000000000 |
| 000000000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(invalidProtoLenPkt), |
| DROPPED_ARP_NON_IPV4 |
| ) |
| |
| // Drop ARP request packet with invalid opcode |
| // Using scapy to generate ARP request packet with invalid opcode: |
| // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") |
| // arp = ARP(op=5) |
| // pkt = eth/arp |
| val invalidOpPkt = """ |
| 010203040506000102030405080600010800060400055c857e3c74e1c0a8012200000000000000000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(invalidOpPkt), |
| DROPPED_ARP_UNKNOWN |
| ) |
| |
| // Drop ARP reply packet with zero source protocol address |
| // Using scapy to generate ARP request packet with zero source protocol address: |
| // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") |
| // arp = ARP(op=2, psrc="0.0.0.0) |
| // pkt = eth/arp |
| val noHostArpReplyPkt = """ |
| 010203040506000102030405080600010800060400025c857e3c74e10000000000000000000000000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(noHostArpReplyPkt), |
| DROPPED_ARP_REPLY_SPA_NO_HOST |
| ) |
| |
| // Drop ARP reply packet with ethernet broadcast destination |
| // Using scapy to generate ARP reply packet with ethernet broadcast destination: |
| // eth = Ether(src="00:01:02:03:04:05", dst="FF:FF:FF:FF:FF:FF") |
| // arp = ARP(op=2, pdst="0.0.0.0") |
| // pkt = eth/arp |
| val garpReplyPkt = """ |
| ffffffffffff000102030405080600010800060400025c857e3c74e1c0a8012200000000000000000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(garpReplyPkt), |
| DROPPED_GARP_REPLY |
| ) |
| } |
| |
| @Test |
| fun testArpFilterPassPktsNoIPv4() { |
| val apfFilter = getApfFilter() |
| val program = consumeInstalledProgram(ipClientCallback, installCnt = 2) |
| // Pass non-broadcast ARP reply packet |
| // Using scapy to generate unicast ARP reply packet: |
| // eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06") |
| // arp = ARP(op=2, psrc="1.2.3.4") |
| // pkt = eth/arp |
| val nonBcastArpReplyPkt = """ |
| 010203040506000102030405080600010800060400025c857e3c74e10102030400000000000000000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(nonBcastArpReplyPkt), |
| PASSED_ARP_UNICAST_REPLY |
| ) |
| |
| // Pass ARP request packet if device doesn't have any IPv4 address |
| // Using scapy to generate ARP request packet: |
| // eth = Ether(src="00:01:02:03:04:05", dst="FF:FF:FF:FF:FF:FF") |
| // arp = ARP(op=1, pdst="1.2.3.4") |
| // pkt = eth/arp |
| val arpRequestPkt = """ |
| ffffffffffff000102030405080600010800060400015c857e3c74e1c0a8012200000000000001020304 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(arpRequestPkt), |
| PASSED_ARP_REQUEST |
| ) |
| } |
| |
| @Test |
| fun testArpFilterDropPktsWithIPv4() { |
| val apfFilter = getApfFilter() |
| consumeInstalledProgram(ipClientCallback, installCnt = 2) |
| val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) |
| val lp = LinkProperties() |
| lp.addLinkAddress(linkAddress) |
| apfFilter.setLinkProperties(lp) |
| val program = consumeInstalledProgram(ipClientCallback, installCnt = 1) |
| // Drop ARP reply packet is not for the device |
| // Using scapy to generate ARP reply packet not for the device: |
| // eth = Ether(src="00:01:02:03:04:05", dst="FF:FF:FF:FF:FF:FF") |
| // arp = ARP(op=2, pdst="1.2.3.4") |
| // pkt = eth/arp |
| val otherHostArpReplyPkt = """ |
| ffffffffffff000102030405080600010800060400025c857e3c74e1c0a8012200000000000001020304 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(otherHostArpReplyPkt), |
| DROPPED_ARP_OTHER_HOST |
| ) |
| |
| // Drop broadcast ARP request packet not for the device |
| // Using scapy to generate ARP broadcast request packet not for the device: |
| // eth = Ether(src="00:01:02:03:04:05", dst="FF:FF:FF:FF:FF:FF") |
| // arp = ARP(op=1, pdst="1.2.3.4") |
| // pkt = eth/arp |
| val otherHostArpRequestPkt = """ |
| ffffffffffff000102030405080600010800060400015c857e3c74e1c0a8012200000000000001020304 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(otherHostArpRequestPkt), |
| DROPPED_ARP_OTHER_HOST |
| ) |
| } |
| |
| @Test |
| fun testArpFilterPassPktsWithIPv4() { |
| val apfFilter = getApfFilter() |
| consumeInstalledProgram(ipClientCallback, installCnt = 2) |
| val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) |
| val lp = LinkProperties() |
| lp.addLinkAddress(linkAddress) |
| apfFilter.setLinkProperties(lp) |
| val program = consumeInstalledProgram(ipClientCallback, installCnt = 1) |
| |
| // Using scapy to generate ARP broadcast reply packet: |
| // eth = Ether(src="00:01:02:03:04:05", dst="FF:FF:FF:FF:FF:FF") |
| // arp = ARP(op=2, pdst="10.0.0.1") |
| // pkt = eth/arp |
| val bcastArpReplyPkt = """ |
| ffffffffffff000102030405080600010800060400025c857e3c74e1c0a801220000000000000a000001 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| APF_VERSION_6, |
| program, |
| HexDump.hexStringToByteArray(bcastArpReplyPkt), |
| PASSED_ARP_BROADCAST_REPLY |
| ) |
| } |
| |
| // The APFv6 code path is only turned on in V+ |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testArpTransmit() { |
| val apfFilter = getApfFilter() |
| consumeInstalledProgram(ipClientCallback, installCnt = 2) |
| val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) |
| val lp = LinkProperties() |
| lp.addLinkAddress(linkAddress) |
| apfFilter.setLinkProperties(lp) |
| val program = consumeInstalledProgram(ipClientCallback, installCnt = 1) |
| 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 transmittedPackets = consumeTransmittedPackets(1) |
| 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 }, |
| transmittedPackets[0] |
| ) |
| } |
| |
| @Test |
| fun testArpOffloadDisabled() { |
| val apfConfig = getDefaultConfig() |
| apfConfig.shouldHandleArpOffload = false |
| val apfFilter = getApfFilter(apfConfig) |
| consumeInstalledProgram(ipClientCallback, installCnt = 2) |
| val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) |
| val lp = LinkProperties() |
| lp.addLinkAddress(linkAddress) |
| apfFilter.setLinkProperties(lp) |
| val program = consumeInstalledProgram(ipClientCallback, installCnt = 1) |
| 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() { |
| doReturn(listOf<ByteArray>()).`when`(dependencies).getAnycast6Addresses(any()) |
| val apfFilter = getApfFilter() |
| // validate NS packet check when there is no IPv6 address |
| val program = consumeInstalledProgram(ipClientCallback, installCnt = 2) |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| // 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() |
| consumeInstalledProgram(ipClientCallback, installCnt = 2) |
| 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) |
| consumeInstalledProgram(ipClientCallback, installCnt = 1) |
| apfFilter.updateClatInterfaceState(true) |
| val program = consumeInstalledProgram(ipClientCallback, installCnt = 1) |
| |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| // 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 program = consumeInstalledProgram(ipClientCallback, installCnt = 3) |
| val validIpv6Addresses = hostIpv6Addresses + hostAnycast6Addresses |
| val expectPackets = mutableListOf<ByteArray>() |
| 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 expectedUcastNaPacket = generateNaPacket( |
| apfFilter.mHardwareAddress, |
| senderMacAddress, |
| addr, |
| senderIpv6Address, |
| 0xe0000000.toInt(), // R=1, S=1, O=1 |
| addr |
| ) |
| expectPackets.add(expectedUcastNaPacket) |
| |
| 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 expectedMcastNaPacket = generateNaPacket( |
| apfFilter.mHardwareAddress, |
| senderMacAddress, |
| addr, |
| senderIpv6Address, |
| 0xe0000000.toInt(), // R=1, S=1, O=1 |
| addr |
| ) |
| expectPackets.add(expectedMcastNaPacket) |
| } |
| |
| val transmitPackets = consumeTransmittedPackets(expectPackets.size) |
| for (i in transmitPackets.indices) { |
| assertContentEquals(expectPackets[i], transmitPackets[i]) |
| } |
| } |
| |
| // 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 |
| doReturn(20).`when`(dependencies).getNdTrafficClass(any()) |
| val apfFilter = getApfFilter() |
| val lp = LinkProperties() |
| for (addr in hostIpv6Addresses) { |
| lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) |
| } |
| apfFilter.setLinkProperties(lp) |
| val program = consumeInstalledProgram(ipClientCallback, installCnt = 3) |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(hostMcastDstIpNsPkt), |
| DROPPED_IPV6_NS_REPLIED_NON_DAD |
| ) |
| |
| val transmitPkts = consumeTransmittedPackets(1) |
| // 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 |
| """.replace("\\s+".toRegex(), "").trim() |
| assertContentEquals( |
| HexDump.hexStringToByteArray(expectedNaPacket), |
| transmitPkts[0] |
| ) |
| } |
| |
| @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 program = consumeInstalledProgram(ipClientCallback, installCnt = 3) |
| 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 |
| ) |
| } |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testRegisterOffloadEngine() { |
| val apfConfig = getDefaultConfig() |
| apfConfig.shouldHandleMdnsOffload = true |
| val apfFilter = getApfFilter(apfConfig) |
| val captor = ArgumentCaptor.forClass(OffloadEngine::class.java) |
| verify(nsdManager).registerOffloadEngine( |
| eq(ifParams.name), |
| anyLong(), |
| anyLong(), |
| any(), |
| captor.capture() |
| ) |
| val offloadEngine = captor.value |
| val info1 = OffloadServiceInfo( |
| OffloadServiceInfo.Key("TestServiceName", "_advertisertest._tcp"), |
| listOf(), |
| "Android_test.local", |
| byteArrayOf(0x01, 0x02, 0x03, 0x04), |
| 0, |
| OffloadEngine.OFFLOAD_TYPE_REPLY.toLong() |
| ) |
| val info2 = OffloadServiceInfo( |
| OffloadServiceInfo.Key("TestServiceName2", "_advertisertest._tcp"), |
| listOf(), |
| "Android_test.local", |
| byteArrayOf(0x01, 0x02, 0x03, 0x04), |
| 0, |
| OffloadEngine.OFFLOAD_TYPE_REPLY.toLong() |
| ) |
| val updatedInfo1 = OffloadServiceInfo( |
| OffloadServiceInfo.Key("TestServiceName", "_advertisertest._tcp"), |
| listOf(), |
| "Android_test.local", |
| byteArrayOf(), |
| 0, |
| OffloadEngine.OFFLOAD_TYPE_REPLY.toLong() |
| ) |
| handler.post { offloadEngine.onOffloadServiceUpdated(info1) } |
| handlerThread.waitForIdle(TIMEOUT_MS) |
| assertContentEquals(listOf(info1), apfFilter.mOffloadServiceInfos) |
| handler.post { offloadEngine.onOffloadServiceUpdated(info2) } |
| handlerThread.waitForIdle(TIMEOUT_MS) |
| assertContentEquals(listOf(info1, info2), apfFilter.mOffloadServiceInfos) |
| handler.post { offloadEngine.onOffloadServiceUpdated(updatedInfo1) } |
| handlerThread.waitForIdle(TIMEOUT_MS) |
| assertContentEquals(listOf(info2, updatedInfo1), apfFilter.mOffloadServiceInfos) |
| handler.post { offloadEngine.onOffloadServiceRemoved(updatedInfo1) } |
| handlerThread.waitForIdle(TIMEOUT_MS) |
| assertContentEquals(listOf(info2), apfFilter.mOffloadServiceInfos) |
| |
| handler.post { apfFilter.shutdown() } |
| handlerThread.waitForIdle(TIMEOUT_MS) |
| verify(nsdManager).unregisterOffloadEngine(any()) |
| } |
| |
| @Test |
| fun testApfProgramUpdate() { |
| val apfFilter = getApfFilter() |
| consumeInstalledProgram(ipClientCallback, installCnt = 2) |
| // 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) |
| consumeInstalledProgram(ipClientCallback, installCnt = 1) |
| |
| // add the same IPv4 address, expect to have no apf program update |
| apfFilter.setLinkProperties(lp) |
| verify(ipClientCallback, never()).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) |
| consumeInstalledProgram(ipClientCallback, installCnt = 1) |
| |
| // add the same IPv6 addresses, expect to have no apf program update |
| apfFilter.setLinkProperties(lp) |
| verify(ipClientCallback, never()).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) |
| consumeInstalledProgram(ipClientCallback, installCnt = 1) |
| |
| // add the same IPv6 addresses, expect to have no apf program update |
| apfFilter.setLinkProperties(lp) |
| verify(ipClientCallback, never()).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) |
| } |
| |
| @Test |
| fun testApfFilterResumeWillCleanUpTheApfMemoryRegion() { |
| val apfFilter = getApfFilter() |
| consumeInstalledProgram(ipClientCallback, installCnt = 2) |
| apfFilter.resume() |
| val program = consumeInstalledProgram(ipClientCallback, installCnt = 1) |
| assertContentEquals(ByteArray(4096) { 0 }, program) |
| } |
| } |