| /* |
| * 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.InetAddresses |
| import android.net.LinkAddress |
| import android.net.LinkProperties |
| import android.net.MacAddress |
| import android.net.NattKeepalivePacketDataParcelable |
| import android.net.TcpKeepalivePacketDataParcelable |
| import android.net.apf.ApfConstants.ETH_MULTICAST_MDNS_V4_MAC_ADDRESS |
| import android.net.apf.ApfConstants.ETH_MULTICAST_MDNS_V6_MAC_ADDRESS |
| 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_ETHER_OUR_SRC_MAC |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_GARP_REPLY |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_IGMP_INVALID |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_IGMP_REPORT |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_IGMP_V2_GENERAL_QUERY_REPLIED |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_IGMP_V3_GENERAL_QUERY_REPLIED |
| 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_ICMP_INVALID |
| 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_PING_REQUEST_REPLIED |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_TCP_PORT7_UNICAST |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_ICMP6_ECHO_REQUEST_INVALID |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_ICMP6_ECHO_REQUEST_REPLIED |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MLD_INVALID |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MLD_REPORT |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MLD_V1_GENERAL_QUERY_REPLIED |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MLD_V2_GENERAL_QUERY_REPLIED |
| 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.DROPPED_MDNS |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_MDNS_REPLIED |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_NON_UNICAST_TDLS |
| import android.net.apf.ApfCounterTracker.Counter.DROPPED_RA |
| 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_ETHER_OUR_SRC_MAC |
| 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_HOPOPTS |
| 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_MDNS |
| import android.net.apf.ApfCounterTracker.Counter.PASSED_NON_IP_UNICAST |
| import android.net.apf.ApfFilter.Dependencies |
| import android.net.apf.ApfTestHelpers.Companion.TIMEOUT_MS |
| import android.net.apf.BaseApfGenerator.APF_VERSION_3 |
| 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 android.util.Log |
| 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_ADDR_ALL_NODES_MULTICAST |
| import com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_NODE_LOCAL_ALL_NODES_MULTICAST |
| 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.networkstack.util.NetworkStackUtils.isAtLeast25Q2 |
| 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.tryTest |
| import com.android.testutils.visibleOnHandlerThread |
| import com.android.testutils.waitForIdle |
| import com.google.common.truth.Truth.assertThat |
| import java.io.FileDescriptor |
| import java.net.Inet4Address |
| import java.net.Inet6Address |
| import java.net.InetAddress |
| import kotlin.random.Random |
| import kotlin.test.assertContentEquals |
| import kotlin.test.assertEquals |
| import libcore.io.IoUtils |
| import org.junit.After |
| import org.junit.Assume.assumeTrue |
| import org.junit.Before |
| import org.junit.Rule |
| import org.junit.Test |
| import org.junit.runner.RunWith |
| import org.junit.runners.Parameterized |
| 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.clearInvocations |
| import org.mockito.Mockito.doAnswer |
| import org.mockito.Mockito.doReturn |
| import org.mockito.Mockito.mock |
| import org.mockito.Mockito.never |
| import org.mockito.Mockito.times |
| import org.mockito.Mockito.verify |
| import org.mockito.MockitoAnnotations |
| import org.mockito.invocation.InvocationOnMock |
| |
| open class FromU<Type>(val value: Type) |
| |
| /** |
| * 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 NO_CALLBACK_TIMEOUT_MS: Long = 500 |
| private const val TAG = "ApfFilterTest" |
| |
| @Parameterized.Parameters |
| @JvmStatic |
| fun data(): Iterable<Any?> { |
| return mutableListOf<Int?>( |
| ApfJniUtils.APF_INTERPRETER_VERSION_V6, |
| ApfJniUtils.APF_INTERPRETER_VERSION_NEXT |
| ) |
| } |
| } |
| |
| @get:Rule |
| val ignoreRule = DevSdkIgnoreRule() |
| |
| // Indicates which apfInterpreter to load. |
| @Parameterized.Parameter(0) |
| @JvmField |
| var apfInterpreterVersion: Int = ApfJniUtils.APF_INTERPRETER_VERSION_NEXT |
| |
| @Mock |
| private lateinit var context: Context |
| |
| @Mock private lateinit var metrics: NetworkQuirkMetrics |
| |
| @Mock private lateinit var dependencies: Dependencies |
| |
| @Mock private lateinit var apfController: ApfFilter.IApfController |
| @Mock private lateinit var nsdManager: NsdManager |
| |
| @GuardedBy("mApfFilterCreated") |
| private val mApfFilterCreated = ArrayList<ApfFilter>() |
| 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 hostLinkLocalIpv6Address = InetAddresses.parseNumericAddress("fe80::3") |
| 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(), |
| ETH_MULTICAST_MDNS_V4_MAC_ADDRESS, |
| ETH_MULTICAST_MDNS_V6_MAC_ADDRESS |
| ) |
| |
| // Using scapy to generate payload: |
| // answers = [ |
| // DNSRR(rrname="_googlecast._tcp.local", type="PTR", ttl=120, rdata="gambit-3cb56c6253638b3641e3d289013cc0ae._googlecast._tcp.local."), |
| // DNSRR(rrname="gambit-3cb56c6253638b3641e3d289013cc0ae._googlecast._tcp.local", type="SRV", ttl=120, rdata="0 0 8009 3cb56c62-5363-8b36-41e3-d289013cc0ae.local."), |
| // DNSRR(rrname="gambit-3cb56c6253638b3641e3d289013cc0ae._googlecast._tcp.local", type="TXT", ttl=120, rdata=' "id=3cb56c6253638b3641e3d289013cc0ae cd=8ECC37F6755390D005DFC02F8EC0D4FA rm=4ABD579644ACFCCF ve=05 md=gambit ic=/setup/icon.png fn=gambit a=264709 st=0 bs=FA8FFD2242A7 nf=1 rs= ',), |
| // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="A", ttl=120, rdata="100.89.85.228"), |
| // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="fe80:0000:0000:0000:0000:0000:0000:0003"), |
| // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="200a:0000:0000:0000:0000:0000:0000:0003"), |
| // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="200b:0000:0000:0000:0000:0000:0000:0003"), |
| // ] |
| // dns = dns_compress(DNS(qr=1, aa=1, rd=0, qd=None, an=answers)) |
| private val castOffloadPayload = """ |
| 0000840000000007000000000b5f676f6f676c6563617374045f746370056c6 |
| f63616c00000c000100000078002a2767616d6269742d336362353663363235 |
| 3336333862333634316533643238393031336363306165c00c01c0000021000 |
| 100000078003430203020383030392033636235366336322d353336332d3862 |
| 33362d343165332d6432383930313363633061652e6c6f63616c2e01c000001 |
| 000010000007800b3b2202269643d3363623536633632353336333862333634 |
| 3165336432383930313363633061652063643d3845434333374636373535333 |
| 93044303035444643303246384543304434464120726d3d3441424435373936 |
| 34344143464343462076653d3035206d643d67616d6269742069633d2f73657 |
| 475702f69636f6e2e706e6720666e3d67616d62697420613d32363437303920 |
| 73743d302062733d464138464644323234324137206e663d312072733d20284 |
| 16e64726f69645f663437616331306235386363346238386263336635653761 |
| 3831653539383732c01d00010001000000780004645955e4c157001c0001000 |
| 000780010fe800000000000000000000000000003c157001c00010000007800 |
| 10200a0000000000000000000000000003c157001c0001000000780010200b0 |
| 000000000000000000000000003 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| // Using scapy to generate payload: |
| // answers = [ |
| // DNSRR(rrname="_androidtvremote2._tcp.local", type="PTR", rdata="gambit._androidtvremote2._tcp.local", ttl=120), |
| // DNSRR(rrname="gambit._androidtvremote2._tcp.local", type="SRV", rdata="0 0 6466 Android_2570595cc11d4af4a4b7146b946eeb9e.local", ttl=120), |
| // DNSRR(rrname="gambit._androidtvremote2._tcp.local", type="TXT", rdata='''"bt=3C:4E:56:76:1E:E9"''', ttl=120), |
| // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="A", ttl=120, rdata="100.89.85.228"), |
| // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="fe80:0000:0000:0000:0000:0000:0000:0003"), |
| // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="200a:0000:0000:0000:0000:0000:0000:0003"), |
| // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="200b:0000:0000:0000:0000:0000:0000:0003"), |
| // ] |
| // dns = dns_compress(DNS(qr=1, aa=1, rd=0, qd=None, an=answers)) |
| val tvRemoteOffloadPayload = """ |
| 000084000000000700000000115f616e64726f6964747672656d6f746532045 |
| f746370056c6f63616c00000c00010000007800090667616d626974c00cc034 |
| 00210001000000780037302030203634363620416e64726f69645f323537303 |
| 53935636331316434616634613462373134366239343665656239652e6c6f63 |
| 616cc03400100001000000780017162262743d33433a34453a35363a37363a3 |
| 1453a45392228416e64726f69645f6634376163313062353863633462383862 |
| 633366356537613831653539383732c02300010001000000780004645955e4c |
| 0a3001c0001000000780010fe800000000000000000000000000003c0a3001c |
| 0001000000780010200a0000000000000000000000000003c0a3001c0001000 |
| 000780010200b0000000000000000000000000003 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| // answers = [ |
| // DNSRR(rrname="_airplay._tcp.local", type="PTR", rdata="gambit._airplay._tcp.local", ttl=120), |
| // DNSRR(rrname="gambit._airplay._tcp.local", type="SRV", rdata="0 0 6466 Android_2570595cc11d4af4a4b7146b946eeb9e.local", ttl=120), |
| // DNSRR(rrname="gambit._airplay._tcp.local", type="TXT", rdata='"deviceid=58:55:CA:1A:E2:88 features=0x39f7 model=AppleTV2,1 srcvers=130.14"', ttl=120), DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="A", ttl=120, rdata="100.89.85.228"), |
| // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="fe80:0000:0000:0000:0000:0000:0000:0003"), |
| // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="200a:0000:0000:0000:0000:0000:0000:0003"), |
| // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="200b:0000:0000:0000:0000:0000:0000:0003"), |
| // ] |
| // dns = dns_compress(DNS(qr=1, aa=1, rd=0, qd=None, an=answers)) |
| val airplayOffloadPayload = """ |
| 000084000000000700000000085f616972706c6179045f746370056c6f63616 |
| c00000c00010000007800090667616d626974c00cc02b002100010000007800 |
| 37302030203634363620416e64726f69645f323537303539356363313164346 |
| 16634613462373134366239343665656239652e6c6f63616cc02b0010000100 |
| 000078004d4c2264657669636569643d35383a35353a43413a31413a45323a3 |
| 8382066656174757265733d307833396637206d6f64656c3d4170706c655456 |
| 322c3120737263766572733d3133302e31342228416e64726f69645f6634376 |
| 163313062353863633462383862633366356537613831653539383732c01a00 |
| 010001000000780004645955e4c0d0001c0001000000780010fe80000000000 |
| 0000000000000000003c0d0001c0001000000780010200a0000000000000000 |
| 000000000003c0d0001c0001000000780010200b00000000000000000000000 |
| 00003 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| // answers = [ |
| // DNSRR(rrname="_raop._tcp.local", type="PTR", rdata="5855CA1AE288@gambit._raop._tcp.local", ttl=120), |
| // DNSRR(rrname="5855CA1AE288@gambit._raop._tcp.local", type="SRV", rdata="0 0 6466 Android_2570595cc11d4af4a4b7146b946eeb9e.local", ttl=120), |
| // DNSRR(rrname="5855CA1AE288@gambit._raop._tcp.local", type="TXT", rdata='"txtvers=1 ch=2 cn=0,1,2,3 da=true et=0,3,5 md=0,1,2 pw=false sv=false sr=44100 ss=16 tp=UDP vn=65537 vs=130.14 am=AppleTV2,1 sf=0x4"', ttl=120), |
| // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="A", ttl=120, rdata="100.89.85.228"), |
| // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="fe80:0000:0000:0000:0000:0000:0000:0003"), |
| // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="200a:0000:0000:0000:0000:0000:0000:0003"), |
| // DNSRR(rrname="Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", type="AAAA", ttl=120, rdata="200b:0000:0000:0000:0000:0000:0000:0003"), |
| // ] |
| // dns = dns_compress(DNS(qr=1, aa=1, rd=0, qd=None, an=answers)) |
| val raopOffloadPayload = """ |
| 000084000000000700000000055f72616f70045f746370056c6f63616c00000 |
| c0001000000780016133538353543413141453238384067616d626974c00cc0 |
| 2800210001000000780037302030203634363620416e64726f69645f3235373 |
| 0353935636331316434616634613462373134366239343665656239652e6c6f |
| 63616cc028001000010000007800868522747874766572733d312063683d322 |
| 0636e3d302c312c322c332064613d747275652065743d302c332c35206d643d |
| 302c312c322070773d66616c73652073763d66616c73652073723d343431303 |
| 02073733d31362074703d55445020766e3d36353533372076733d3133302e31 |
| 3420616d3d4170706c655456322c312073663d3078342228416e64726f69645 |
| f66343761633130623538636334623838626333663565376138316535393837 |
| 32c01700010001000000780004645955e4c113001c0001000000780010fe800 |
| 000000000000000000000000003c113001c0001000000780010200a00000000 |
| 00000000000000000003c113001c0001000000780010200b000000000000000 |
| 0000000000003 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| private val passthroughCastOffloadInfo by lazy { |
| FromU( |
| OffloadServiceInfo( |
| OffloadServiceInfo.Key( |
| "gambit-3cb56c6253638b3641e3d289013cc0ae", |
| "_googlecast._tcp" |
| ), |
| listOf(), |
| "Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", |
| null, |
| 0, |
| OffloadEngine.OFFLOAD_TYPE_REPLY.toLong() |
| ) |
| ) |
| } |
| |
| private val castOffloadInfo by lazy { |
| FromU( |
| OffloadServiceInfo( |
| OffloadServiceInfo.Key( |
| "gambit-3cb56c6253638b3641e3d289013cc0ae", |
| "_googlecast._tcp" |
| ), |
| listOf(), |
| "Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", |
| HexDump.hexStringToByteArray(castOffloadPayload), |
| 1, |
| OffloadEngine.OFFLOAD_TYPE_REPLY.toLong() |
| ) |
| ) |
| } |
| private val tvRemoteOffloadInfo by lazy { |
| FromU( |
| OffloadServiceInfo( |
| OffloadServiceInfo.Key("gambit", "_androidtvremote2._tcp"), |
| listOf(), |
| "Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", |
| HexDump.hexStringToByteArray(tvRemoteOffloadPayload), |
| 2, |
| OffloadEngine.OFFLOAD_TYPE_REPLY.toLong() |
| ) |
| ) |
| } |
| private val manySubtypeOffloadInfo by lazy { |
| FromU( |
| OffloadServiceInfo( |
| OffloadServiceInfo.Key("gambit", "_testsubtype._tcp"), |
| listOf("subtype1", "subtype2", "subtype3", "subtype4", "subtype5"), |
| "Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", |
| HexDump.hexStringToByteArray(castOffloadPayload), |
| 3, |
| OffloadEngine.OFFLOAD_TYPE_REPLY.toLong() |
| ) |
| ) |
| } |
| |
| private val airplayOffloadInfo by lazy { |
| FromU( |
| OffloadServiceInfo( |
| OffloadServiceInfo.Key("gambit", "_airplay._tcp"), |
| listOf(), |
| "Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", |
| HexDump.hexStringToByteArray(airplayOffloadPayload), |
| 4, |
| OffloadEngine.OFFLOAD_TYPE_REPLY.toLong() |
| ) |
| ) |
| } |
| |
| private val raopOffloadInfo by lazy { |
| FromU( |
| OffloadServiceInfo( |
| OffloadServiceInfo.Key("5855CA1AE288@gambit", "_raop._tcp"), |
| listOf(), |
| "Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", |
| HexDump.hexStringToByteArray(raopOffloadPayload), |
| 4, |
| OffloadEngine.OFFLOAD_TYPE_REPLY.toLong() |
| ) |
| |
| ) |
| } |
| private val counterTotalSize = ApfCounterTracker.Counter.totalSize() |
| |
| private val handlerThread by lazy { |
| HandlerThread("$TAG handler thread").apply { start() } |
| } |
| private val handler by lazy { Handler(handlerThread.looper) } |
| private lateinit var raReadSocket: FileDescriptor |
| private var raWriterSocket = FileDescriptor() |
| private var mcastWriteSocket = FileDescriptor() |
| private lateinit var apfTestHelpers: ApfTestHelpers |
| |
| @Before |
| fun setUp() { |
| apfTestHelpers = ApfTestHelpers(apfInterpreterVersion) |
| 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() |
| raReadSocket = FileDescriptor() |
| Os.socketpair(AF_UNIX, SOCK_STREAM, 0, raWriterSocket, raReadSocket) |
| doReturn(raReadSocket).`when`(dependencies).createPacketReaderSocket(anyInt()) |
| val mcastReadSocket = FileDescriptor() |
| Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mcastWriteSocket, mcastReadSocket) |
| doReturn(mcastReadSocket) |
| .`when`(dependencies).createEgressIgmpReportsReaderSocket(anyInt()) |
| doReturn(mcastReadSocket) |
| .`when`(dependencies).createEgressMulticastReportsReaderSocket(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: ApfFilter -> |
| handler.post { apf.shutdown() } |
| }) |
| |
| synchronized(mApfFilterCreated) { |
| assertEquals( |
| 0, |
| mApfFilterCreated.size.toLong(), |
| "ApfFilters did not fully shutdown." |
| ) |
| } |
| } |
| |
| @After |
| fun tearDown() { |
| IoUtils.closeQuietly(raWriterSocket) |
| IoUtils.closeQuietly(mcastWriteSocket) |
| shutdownApfFilters() |
| handler.waitForIdle(TIMEOUT_MS) |
| Mockito.framework().clearInlineMocks() |
| apfTestHelpers.resetTransmittedPacketMemory() |
| handlerThread.quitSafely() |
| handlerThread.join() |
| } |
| |
| private fun getDefaultConfig( |
| apfVersion: Int = apfInterpreterVersion |
| ): 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.handleArpOffload = true |
| config.handleNdOffload = true |
| return config |
| } |
| |
| private fun getApfFilter( |
| apfCfg: ApfFilter.ApfConfiguration = getDefaultConfig(apfInterpreterVersion) |
| ): ApfFilter { |
| lateinit var apfFilter: ApfFilter |
| handler.post { |
| apfFilter = ApfFilter( |
| handler, |
| context, |
| apfCfg, |
| ifParams, |
| apfController, |
| metrics, |
| dependencies |
| ) |
| } |
| handlerThread.waitForIdle(TIMEOUT_MS) |
| return apfFilter |
| } |
| |
| private fun getIgmpApfFilter(): ApfFilter { |
| val mcastAddrs = listOf( |
| InetAddress.getByName("224.0.0.1") as Inet4Address, |
| InetAddress.getByName("239.0.0.1") as Inet4Address, |
| InetAddress.getByName("239.0.0.2") as Inet4Address, |
| InetAddress.getByName("239.0.0.3") as Inet4Address |
| ) |
| val apfConfig = getDefaultConfig() |
| apfConfig.handleIgmpOffload = true |
| |
| // mock IPv4 multicast address from /proc/net/igmp |
| doReturn(mcastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any()) |
| val apfFilter = getApfFilter(apfConfig) |
| val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) |
| val lp = LinkProperties() |
| lp.addLinkAddress(linkAddress) |
| apfFilter.setLinkProperties(lp) |
| return apfFilter |
| } |
| |
| private fun doTestEtherTypeAllowListFilter(apfFilter: ApfFilter) { |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, 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() |
| apfTestHelpers.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() |
| apfTestHelpers.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" |
| apfTestHelpers.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 |
| } |
| |
| private fun updateIPv4MulticastAddrs(apfFilter: ApfFilter, mcastAddrs: List<Inet4Address>) { |
| doReturn(mcastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any()) |
| apfFilter.updateMulticastAddrs() |
| } |
| |
| private fun updateIPv6MulticastAddrs(apfFilter: ApfFilter, mcastAddrs: List<Inet6Address>) { |
| doReturn(mcastAddrs).`when`(dependencies).getIPv6MulticastAddresses(any()) |
| apfFilter.updateMulticastAddrs() |
| } |
| |
| @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(apfInterpreterVersion)) |
| doTestEtherTypeAllowListFilter(apfFilter) |
| } |
| |
| @Test |
| fun testIPv4PacketFilterOnV6OnlyNetwork() { |
| val apfFilter = getApfFilter() |
| apfFilter.updateClatInterfaceState(true) |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, 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() |
| apfTestHelpers.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() |
| apfTestHelpers.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() |
| apfTestHelpers.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() |
| apfTestHelpers.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() |
| apfTestHelpers.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() |
| apfTestHelpers.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() |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(fragmentedUdpPkt), |
| DROPPED_IPV4_NON_DHCP4 |
| ) |
| } |
| |
| @Test |
| fun testLoopbackFilter() { |
| val apfConfig = getDefaultConfig() |
| val apfFilter = getApfFilter(apfConfig) |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, 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() |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(nonDhcpBcastPkt), |
| if (isAtLeast25Q2()) DROPPED_ETHER_OUR_SRC_MAC else PASSED_ETHER_OUR_SRC_MAC |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testInvalidIgmpPacketDropped() { |
| val apfFilter = getIgmpApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| // Using scapy to generate invalid length IGMPv1 general query packet: |
| // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:00:00:01') |
| // ip = IP(src='10.0.0.2', dst='224.0.0.1', len=24, proto=2) |
| // payload = Raw(b'\x11\x00\xee\xff\x01\x02\x03\x04\x05\x06') |
| // pkt = ether/ip/payload |
| val payloadLen10Pkt = """ |
| 01005e00000100112233445508004500001800010000400290e00a000002e00000011100eeff010203040506 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(payloadLen10Pkt), |
| DROPPED_IGMP_INVALID |
| ) |
| |
| // Using scapy to generate invalid length IGMPv1 general query packet: |
| // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:00:00:01') |
| // ip = IP(src='10.0.0.2', dst='224.0.0.1', len=20, proto=2) |
| // payload = Raw(b'\x11\x00\xee\xff\x01\x02') |
| // pkt = ether/ip/payload |
| val payloadLen7Pkt = """ |
| 01005e00000100112233445508004500001400010000400290e40a000002e00000011100eeff010203 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(payloadLen7Pkt), |
| DROPPED_IGMP_INVALID |
| ) |
| |
| // Using scapy to generate invalid length IGMP general query which the destination IP is |
| // not 224.0.0.1: |
| // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:00:00:05') |
| // ip = IP(src='10.0.0.2', dst='224.0.0.5') |
| // igmp = IGMP(type=0x11, mrcode=0) |
| // pkt = ether/ip/igmp |
| val pktWithWrongDst = """ |
| 01005e00000300112233445508004500001c000100000102cfda0a000002e00000031100eeff00000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(pktWithWrongDst), |
| DROPPED_IGMP_INVALID |
| ) |
| |
| // Using scapy to generate invalid IGMP general query with wrong type: |
| // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:00:00:01') |
| // ip = IP(src='10.0.0.2', dst='224.0.0.1') |
| // igmp = IGMP(type=0x51, mrcode=0) |
| // pkt = ether/ip/igmp |
| val pktWithWrongType = """ |
| 01005e00000100112233445508004500001c000100000102cfdc0a000002e00000015100aeff00000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(pktWithWrongType), |
| DROPPED_IGMP_INVALID |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIgmpV1ReportDropped() { |
| val apfFilter = getIgmpApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| // Using scapy to generate IGMPv1 report packet: |
| // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:7f:00:01') |
| // ip = IP(src='10.0.0.2', dst='239.0.0.1') |
| // igmp = IGMP(type=0x12, mrcode=0, gaddr='239.0.0.1') |
| // pkt = ether/ip/igmp |
| val pkt = """ |
| 01005e7f000100112233445508004500001c000100000102c0dc0a000002ef0000011200fefdef000001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(pkt), |
| DROPPED_IGMP_REPORT |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIgmpV1GeneralQueryPassed() { |
| val apfFilter = getIgmpApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| // Using scapy to generate IGMPv1 general query packet: |
| // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:00:00:01') |
| // ip = IP(src='10.0.0.2', dst='224.0.0.1') |
| // igmp = IGMP(type=0x11, mrcode=0) |
| // pkt = ether/ip/igmp |
| val pkt = """ |
| 01005e00000100112233445508004500001c000100000102cfdc0a000002e00000011100eeff00000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(pkt), |
| PASSED_IPV4 |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIgmpV2ReportDropped() { |
| val apfFilter = getIgmpApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| // Using scapy to generate IGMPv2 report packet: |
| // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:7f:00:01') |
| // ip = IP(src='10.0.0.2', dst='239.0.0.1') |
| // igmp = IGMP(type=0x16, gaddr='239.0.0.1') |
| // pkt = ether/ip/igmp |
| val v2ReportPkt = """ |
| 01005e7f000100112233445508004500001c000100000102c0dc0a000002ef0000011614fae9ef000001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(v2ReportPkt), |
| DROPPED_IGMP_REPORT |
| ) |
| |
| // Using scapy to generate IGMPv2 leave packet: |
| // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:7f:00:01') |
| // ip = IP(src='10.0.0.2', dst='239.0.0.1') |
| // igmp = IGMP(type=0x17, gaddr='239.0.0.1') |
| // pkt = ether/ip/igmp |
| val v2LeaveReportPkt = """ |
| 01005e7f000100112233445508004500001c000100000102c0dc0a000002ef0000011714f9e9ef000001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(v2LeaveReportPkt), |
| DROPPED_IGMP_REPORT |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIgmpV2GeneralQueryReplied() { |
| val apfFilter = getIgmpApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| // Using scapy to generate IGMPv2 general query packet without router alert option: |
| // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:00:00:01') |
| // ip = IP(src='10.0.0.2', dst='224.0.0.1') |
| // igmp = IGMP(type=0x11) |
| // pkt = ether/ip/igmp |
| val pkt = """ |
| 01005e00000100112233445508004500001c000100000102cfdc0a000002e00000011114eeeb00000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(pkt), |
| DROPPED_IGMP_V2_GENERAL_QUERY_REPLIED |
| ) |
| |
| val igmpv2ReportPkts = setOf( |
| // ###[ Ethernet ]### |
| // dst = 01:00:5e:00:00:01 |
| // src = 02:03:04:05:06:07 |
| // type = IPv4 |
| // ###[ IP ]### |
| // version = 4 |
| // ihl = 6 |
| // tos = 0xc0 |
| // len = 32 |
| // id = 0 |
| // flags = DF |
| // frag = 0 |
| // ttl = 1 |
| // proto = igmp |
| // chksum = 0xeb15 |
| // src = 10.0.0.1 |
| // dst = 239.0.0.1 |
| // \options \ |
| // |###[ IP Option Router Alert ]### |
| // | copy_flag = 1 |
| // | optclass = control |
| // | option = router_alert |
| // | length = 4 |
| // | alert = router_shall_examine_packet |
| // ###[ IGMP ]### |
| // type = Version 2 - Membership Report |
| // mrcode = 0 |
| // chksum = 0xfafd |
| // gaddr = 239.0.0.1 |
| """ |
| 01005e000001020304050607080046c00020000040000102eb150a000001ef000001940400001600fafd |
| ef000001 |
| """.replace("\\s+".toRegex(), "").trim().uppercase(), |
| |
| // ###[ Ethernet ]### |
| // dst = 01:00:5e:00:00:02 |
| // src = 02:03:04:05:06:07 |
| // type = IPv4 |
| // ###[ IP ]### |
| // version = 4 |
| // ihl = 6 |
| // tos = 0xc0 |
| // len = 32 |
| // id = 0 |
| // flags = DF |
| // frag = 0 |
| // ttl = 1 |
| // proto = igmp |
| // chksum = 0xeb14 |
| // src = 10.0.0.1 |
| // dst = 239.0.0.2 |
| // \options \ |
| // |###[ IP Option Router Alert ]### |
| // | copy_flag = 1 |
| // | optclass = control |
| // | option = router_alert |
| // | length = 4 |
| // | alert = router_shall_examine_packet |
| // ###[ IGMP ]### |
| // type = Version 2 - Membership Report |
| // mrcode = 0 |
| // chksum = 0xfafc |
| // gaddr = 239.0.0.2 |
| """ |
| 01005e000002020304050607080046c00020000040000102eb140a000001ef000002940400001600fafc |
| ef000002 |
| """.replace("\\s+".toRegex(), "").trim().uppercase(), |
| // ###[ Ethernet ]### |
| // dst = 01:00:5e:00:00:03 |
| // src = 02:03:04:05:06:07 |
| // type = IPv4 |
| // ###[ IP ]### |
| // version = 4 |
| // ihl = 6 |
| // tos = 0xc0 |
| // len = 32 |
| // id = 0 |
| // flags = DF |
| // frag = 0 |
| // ttl = 1 |
| // proto = igmp |
| // chksum = 0xeb13 |
| // src = 10.0.0.1 |
| // dst = 239.0.0.3 |
| // \options \ |
| // |###[ IP Option Router Alert ]### |
| // | copy_flag = 1 |
| // | optclass = control |
| // | option = router_alert |
| // | length = 4 |
| // | alert = router_shall_examine_packet |
| // ###[ IGMP ]### |
| // type = Version 2 - Membership Report |
| // mrcode = 0 |
| // chksum = 0xfafb |
| // gaddr = 239.0.0.3 |
| """ |
| 01005e000003020304050607080046c00020000040000102eb130a000001ef000003940400001600fafb |
| ef000003 |
| """.replace("\\s+".toRegex(), "").trim().uppercase() |
| ) |
| |
| val transmitPackets = apfTestHelpers.getAllTransmittedPackets() |
| .map { HexDump.toHexString(it).uppercase() }.toSet() |
| assertEquals(igmpv2ReportPkts, transmitPackets) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIgmpV2GeneralQueryWithRouterAlertOptionReplied() { |
| val apfFilter = getIgmpApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| // Using scapy to generate IGMPv2 general query packet with router alert option: |
| // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:00:00:01') |
| // ip = IP(src='10.0.0.2', dst='224.0.0.1', options=[IPOption_Router_Alert()]) |
| // igmp = IGMP(type=0x11) |
| // pkt = ether/ip/igmp |
| val pkt = """ |
| 01005e0000010011223344550800460000200001000001023ad40a000002e0000001940400001114eeeb |
| 00000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(pkt), |
| DROPPED_IGMP_V2_GENERAL_QUERY_REPLIED |
| ) |
| |
| val igmpv2ReportPkts = setOf( |
| // ###[ Ethernet ]### |
| // dst = 01:00:5e:00:00:01 |
| // src = 02:03:04:05:06:07 |
| // type = IPv4 |
| // ###[ IP ]### |
| // version = 4 |
| // ihl = 6 |
| // tos = 0xc0 |
| // len = 32 |
| // id = 0 |
| // flags = DF |
| // frag = 0 |
| // ttl = 1 |
| // proto = igmp |
| // chksum = 0xeb15 |
| // src = 10.0.0.1 |
| // dst = 239.0.0.1 |
| // \options \ |
| // |###[ IP Option Router Alert ]### |
| // | copy_flag = 1 |
| // | optclass = control |
| // | option = router_alert |
| // | length = 4 |
| // | alert = router_shall_examine_packet |
| // ###[ IGMP ]### |
| // type = Version 2 - Membership Report |
| // mrcode = 0 |
| // chksum = 0xfafd |
| // gaddr = 239.0.0.1 |
| """ |
| 01005e000001020304050607080046c00020000040000102eb150a000001ef000001940400001600fafd |
| ef000001 |
| """.replace("\\s+".toRegex(), "").trim().uppercase(), |
| |
| // ###[ Ethernet ]### |
| // dst = 01:00:5e:00:00:02 |
| // src = 02:03:04:05:06:07 |
| // type = IPv4 |
| // ###[ IP ]### |
| // version = 4 |
| // ihl = 6 |
| // tos = 0xc0 |
| // len = 32 |
| // id = 0 |
| // flags = DF |
| // frag = 0 |
| // ttl = 1 |
| // proto = igmp |
| // chksum = 0xeb14 |
| // src = 10.0.0.1 |
| // dst = 239.0.0.2 |
| // \options \ |
| // |###[ IP Option Router Alert ]### |
| // | copy_flag = 1 |
| // | optclass = control |
| // | option = router_alert |
| // | length = 4 |
| // | alert = router_shall_examine_packet |
| // ###[ IGMP ]### |
| // type = Version 2 - Membership Report |
| // mrcode = 0 |
| // chksum = 0xfafc |
| // gaddr = 239.0.0.2 |
| """ |
| 01005e000002020304050607080046c00020000040000102eb140a000001ef000002940400001600fafc |
| ef000002 |
| """.replace("\\s+".toRegex(), "").trim().uppercase(), |
| |
| // ###[ Ethernet ]### |
| // dst = 01:00:5e:00:00:03 |
| // src = 02:03:04:05:06:07 |
| // type = IPv4 |
| // ###[ IP ]### |
| // version = 4 |
| // ihl = 6 |
| // tos = 0xc0 |
| // len = 32 |
| // id = 0 |
| // flags = DF |
| // frag = 0 |
| // ttl = 1 |
| // proto = igmp |
| // chksum = 0xeb13 |
| // src = 10.0.0.1 |
| // dst = 239.0.0.3 |
| // \options \ |
| // |###[ IP Option Router Alert ]### |
| // | copy_flag = 1 |
| // | optclass = control |
| // | option = router_alert |
| // | length = 4 |
| // | alert = router_shall_examine_packet |
| // ###[ IGMP ]### |
| // type = Version 2 - Membership Report |
| // mrcode = 0 |
| // chksum = 0xfafb |
| // gaddr = 239.0.0.3 |
| """ |
| 01005e000003020304050607080046c00020000040000102eb130a000001ef000003940400001600fafb |
| ef000003 |
| """.replace("\\s+".toRegex(), "").trim().uppercase() |
| ) |
| |
| val transmitPackets = apfTestHelpers.getAllTransmittedPackets() |
| .map { HexDump.toHexString(it).uppercase() }.toSet() |
| assertEquals(igmpv2ReportPkts, transmitPackets) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIgmpV2GroupSpecificQueryPassed() { |
| val apfFilter = getIgmpApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| // Using scapy to generate IGMPv2 group specific query packet without router alert option: |
| // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:7f:00:01') |
| // ip = IP(src='10.0.0.2', dst='239.0.0.1') |
| // igmp = IGMP(type=0x11, gaddr='239.0.0.1') |
| // pkt = ether/ip/igmp |
| val pkt = """ |
| 01005e7f000100112233445508004500001c000100000102c0dc0a000002ef0000011114ffe9ef000001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(pkt), |
| PASSED_IPV4 |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIgmpV3ReportDropped() { |
| val apfFilter = getIgmpApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| // Using scapy to generate IGMPv3 report packet without router alert option: |
| // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:00:00:16') |
| // ip = IP(src='10.0.0.2', dst='224.0.0.22') |
| // igmp = IGMPv3(type=0x22)/IGMPv3mr(records=[IGMPv3gr(rtype=2, maddr='239.0.0.1')]) |
| // pkt = ether/ip/igmp |
| val pkt = """ |
| 01005e000001001122334455080045c00024000100000102cf140a000002e00000012200ecfc000000 |
| 0102000000ef000001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(pkt), |
| DROPPED_IGMP_REPORT |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIgmpV3GeneralQueryReplied() { |
| val apfFilter = getIgmpApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| // Using scapy to generate IGMPv3 general query packet without router alert option: |
| // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:00:00:01') |
| // ip = IP(src='10.0.0.2', dst='224.0.0.1') |
| // igmp = IGMPv3(type=0x11)/IGMPv3mq() |
| // pkt = ether/ip/igmp |
| val pkt = """ |
| 01005e000001001122334455080045c00020000100000102cf180a000002e00000011114eeeb00000000 |
| 00000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(pkt), |
| DROPPED_IGMP_V3_GENERAL_QUERY_REPLIED |
| ) |
| |
| val transmittedIgmpv3Reports = apfTestHelpers.consumeTransmittedPackets(1) |
| |
| // ###[ Ethernet ]### |
| // dst = 01:00:5e:00:00:16 |
| // src = 02:03:04:05:06:07 |
| // type = IPv4 |
| // ###[ IP ]### |
| // version = 4 |
| // ihl = 6 |
| // tos = 0xc0 |
| // len = 56 |
| // id = 0 |
| // flags = DF |
| // frag = 0 |
| // ttl = 1 |
| // proto = igmp |
| // chksum = 0xf9e8 |
| // src = 10.0.0.1 |
| // dst = 224.0.0.22 |
| // \options \ |
| // |###[ IP Option Router Alert ]### |
| // | copy_flag = 1 |
| // | optclass = control |
| // | option = router_alert |
| // | length = 4 |
| // | alert = router_shall_examine_packet |
| // ###[ IGMPv3 ]### |
| // type = Version 3 Membership Report |
| // mrcode = 0 |
| // chksum = 0xaf4 |
| // ###[ IGMPv3mr ]### |
| // res2 = 0x0 |
| // numgrp = 3 |
| // \records \ |
| // |###[ IGMPv3gr ]### |
| // | rtype = Mode Is Exclude |
| // | auxdlen = 0 |
| // | numsrc = 0 |
| // | maddr = 239.0.0.1 |
| // | srcaddrs = [] |
| // |###[ IGMPv3gr ]### |
| // | rtype = Mode Is Exclude |
| // | auxdlen = 0 |
| // | numsrc = 0 |
| // | maddr = 239.0.0.2 |
| // | srcaddrs = [] |
| // |###[ IGMPv3gr ]### |
| // | rtype = Mode Is Exclude |
| // | auxdlen = 0 |
| // | numsrc = 0 |
| // | maddr = 239.0.0.3 |
| // | srcaddrs = [] |
| val igmpv3ReportPkt = """ |
| 01005e000016020304050607080046c00038000040000102f9e80a000001e00000169404000022000af40 |
| 000000302000000ef00000102000000ef00000202000000ef000003 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| assertContentEquals( |
| HexDump.hexStringToByteArray(igmpv3ReportPkt), |
| transmittedIgmpv3Reports[0] |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIgmpV3GeneralQueryWithRouterAlertOptionReplied() { |
| val apfFilter = getIgmpApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| // Using scapy to generate IGMPv3 general query packet with router alert option: |
| // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:00:00:01') |
| // ip = IP(src='10.0.0.2', dst='224.0.0.1', options=[IPOption_Router_Alert()]) |
| // igmp = IGMPv3(type=0x11)/IGMPv3mq() |
| // pkt = ether/ip/igmp |
| val pkt = """ |
| 01005e000001001122334455080046c000240001000001023a100a000002e0000001940400001114eeeb0 |
| 000000000000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(pkt), |
| DROPPED_IGMP_V3_GENERAL_QUERY_REPLIED |
| ) |
| |
| val transmittedIgmpv3Reports = apfTestHelpers.consumeTransmittedPackets(1) |
| |
| // ###[ Ethernet ]### |
| // dst = 01:00:5e:00:00:16 |
| // src = 02:03:04:05:06:07 |
| // type = IPv4 |
| // ###[ IP ]### |
| // version = 4 |
| // ihl = 6 |
| // tos = 0xc0 |
| // len = 56 |
| // id = 0 |
| // flags = DF |
| // frag = 0 |
| // ttl = 1 |
| // proto = igmp |
| // chksum = 0xf9e8 |
| // src = 10.0.0.1 |
| // dst = 224.0.0.22 |
| // \options \ |
| // |###[ IP Option Router Alert ]### |
| // | copy_flag = 1 |
| // | optclass = control |
| // | option = router_alert |
| // | length = 4 |
| // | alert = router_shall_examine_packet |
| // ###[ IGMPv3 ]### |
| // type = Version 3 Membership Report |
| // mrcode = 0 |
| // chksum = 0xaf4 |
| // ###[ IGMPv3mr ]### |
| // res2 = 0x0 |
| // numgrp = 3 |
| // \records \ |
| // |###[ IGMPv3gr ]### |
| // | rtype = Mode Is Exclude |
| // | auxdlen = 0 |
| // | numsrc = 0 |
| // | maddr = 239.0.0.1 |
| // | srcaddrs = [] |
| // |###[ IGMPv3gr ]### |
| // | rtype = Mode Is Exclude |
| // | auxdlen = 0 |
| // | numsrc = 0 |
| // | maddr = 239.0.0.2 |
| // | srcaddrs = [] |
| // |###[ IGMPv3gr ]### |
| // | rtype = Mode Is Exclude |
| // | auxdlen = 0 |
| // | numsrc = 0 |
| // | maddr = 239.0.0.3 |
| // | srcaddrs = [] |
| val igmpv3ReportPkt = """ |
| 01005e000016020304050607080046c00038000040000102f9e80a000001e00000169404000022000af40 |
| 000000302000000ef00000102000000ef00000202000000ef000003 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| assertContentEquals( |
| HexDump.hexStringToByteArray(igmpv3ReportPkt), |
| transmittedIgmpv3Reports[0] |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIgmpV3GroupSpecificQueryPassed() { |
| val apfFilter = getIgmpApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| // Using scapy to generate IGMPv3 group specific query packet |
| // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:7f:00:01') |
| // ip = IP(src='10.0.0.2', dst='239.0.0.1') |
| // igmp = IGMPv3(type=0x11)/IGMPv3mq(gaddr='239.0.0.1') |
| // pkt = ether/ip/igmp |
| val pkt = """ |
| 01005e7f0001001122334455080045c00020000100000102c0180a000002ef0000011114ffe9ef000001 |
| 00000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(pkt), |
| PASSED_IPV4 |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIgmpV3GroupAndSourceSpecificQueryPassed() { |
| val apfFilter = getIgmpApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| // Using scapy to generate IGMPv3 group and source specific query packet |
| // ether = Ether(src='00:11:22:33:44:55', dst='01:00:5e:7f:00:01') |
| // ip = IP(src='10.0.0.2', dst='239.0.0.1') |
| // igmp = IGMPv3(type=0x11)/IGMPv3mq(gaddr='239.0.0.1', numsrc=1, srcaddrs=['10.0.0.1']) |
| // pkt = ether/ip/igmp |
| val pkt = """ |
| 01005e7f0001001122334455080045c00024000100000102c0140a000002ef0000011114f5e7ef0000010 |
| 00000010a000001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(pkt), |
| PASSED_IPV4 |
| ) |
| } |
| |
| private fun getMldApfFilter(): ApfFilter { |
| val mcastAddrs = listOf( |
| InetAddress.getByName("ff12::1:1111:1111") as Inet6Address, |
| InetAddress.getByName("ff12::1:2222:2222") as Inet6Address, |
| InetAddress.getByName("ff12::1:3333:3333") as Inet6Address, |
| ) |
| val apfConfig = getDefaultConfig() |
| apfConfig.handleMldOffload = true |
| |
| // mock IPv6 multicast address from /proc/net/igmp6 |
| doReturn(mcastAddrs).`when`(dependencies).getIPv6MulticastAddresses(any()) |
| val apfFilter = getApfFilter(apfConfig) |
| val ipv6LinkAddress = LinkAddress(hostLinkLocalIpv6Address, 64) |
| val lp = LinkProperties() |
| lp.addLinkAddress(ipv6LinkAddress) |
| apfFilter.setLinkProperties(lp) |
| return apfFilter |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIPv6PacketWithNonMldHopByHopPassed() { |
| val apfFilter = getMldApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| // Using scapy to generate MLDv1 general query with different HOPOPTS |
| // ether = Ether(src='00:11:22:33:44:55', dst='33:33:11:11:11:11') |
| // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:3712', dst='ff02::1:1111:1111', hlim=1) |
| // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=3)]) |
| // mld = ICMPv6MLQuery() |
| // pkt = ether/ipv6/hopOpts/mld |
| var invalidHopOptPkt = """ |
| 33331111111100112233445586dd6000000000200001fe80000000000000fc0183fffea63712ff020000 |
| 0000000000000001111111113a000302000001008200813b271000000000000000000000000000000000 |
| 0000 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(invalidHopOptPkt), |
| PASSED_IPV6_NON_ICMP |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testInvalidMldPacketDropped() { |
| val apfFilter = getMldApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| // Using scapy to generate MLDv1 general query with invalid source addr |
| // ether = Ether(src='00:11:22:33:44:55', dst='33:33:11:11:11:11') |
| // ipv6 = IPv6(src='ff02::1:4444:4444', dst='ff02::1:1111:1111', hlim=1) |
| // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)]) |
| // mld = ICMPv6MLQuery() |
| // pkt = ether/ipv6/hopOpts/mld |
| var invalidSrcIpPkt = """ |
| 33331111111100112233445586dd6000000000200001ff020000000000000000000144444444ff02000 |
| 00000000000000001111111113a000502000001008200adea2710000000000000000000000000000000 |
| 000000 |
| """.replace("\\s+".toRegex(), "").trim().uppercase() |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(invalidSrcIpPkt), |
| DROPPED_IPV6_MLD_INVALID |
| ) |
| |
| // Using scapy to generate MLDv1 general query with invalid hoplimit |
| // ether = Ether(src='00:11:22:33:44:55', dst='33:33:11:11:11:11') |
| // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:3712', dst='ff02::1:1111:1111', hlim=5) |
| // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)]) |
| // mld = ICMPv6MLQuery() |
| // pkt = ether/ipv6/hopOpts/mld |
| var invalidHopLimitPkt = """ |
| 33331111111100112233445586dd6000000000200005fe80000000000000fc0183fffea63712ff02000 |
| 00000000000000001111111113a000502000001008200813b2710000000000000000000000000000000 |
| 000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(invalidHopLimitPkt), |
| DROPPED_IPV6_MLD_INVALID |
| ) |
| |
| // Using scapy to generate MLDv1 general query packet with invalid destination address |
| // ether = Ether(src='00:11:22:33:44:55', dst='33:33:00:00:00:01') |
| // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:3712', dst='ff03::1', hlim=1) |
| // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)]) |
| // mld = ICMPv6MLQuery() |
| // pkt = ether/ipv6/hopOpts/mld |
| var pkt = """ |
| 33330000000100112233445586dd6000000000200001fe80000000000000fc0183fffea63712ff03000 |
| 00000000000000000000000013a000502000001008200a35c2710000000000000000000000000000000 |
| 000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(pkt), |
| DROPPED_IPV6_MLD_INVALID |
| ) |
| |
| // Using scapy to generate MLD message with invalid payload length 27 |
| // ether = Ether(src='00:11:22:33:44:55', dst='33:33:00:00:00:01') |
| // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:3712', dst='ff03::1', hlim=1) |
| // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)]) |
| // mld = ICMPv6MLQuery() |
| // pkt = ether/ipv6/hopOpts/mld (and drop last byte) |
| var invalidPayloadLength27Pkt = """ |
| 33330000000100112233445586dd6000000000240001fe80000000000000fc0183fffea63712ff0200000 |
| 000000000000000000000013a000502000001008200a35927100000000000000000000000000000000000 |
| 00000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(invalidPayloadLength27Pkt), |
| DROPPED_IPV6_MLD_INVALID |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testMldV1ReportDropped() { |
| val apfFilter = getMldApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| // Using scapy to generate MLDv1 report |
| // ether = Ether(src='00:11:22:33:44:55', dst='33:33:11:11:11:11') |
| // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:3712', dst='ff12::1:1111:1111', hlim=1) |
| // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)]) |
| // mld = ICMPv6MLReport(mladdr='ff12::1:1111:1111') |
| // pkt = ether/ipv6/hopOpts/mld |
| var pkt = """ |
| 33331111111100112233445586dd6000000000200001fe80000000000000fc0183fffea63712ff12000 |
| 00000000000000001111111113a000502000001008300860500000000ff120000000000000000000111 |
| 111111 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(pkt), |
| DROPPED_IPV6_MLD_REPORT |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testMldV1DoneDropped() { |
| val apfFilter = getMldApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| // Using scapy to generate MLDv1 done |
| // ether = Ether(src='00:11:22:33:44:55', dst='33:33:00:00:00:02') |
| // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:3712', dst='ff02::2', hlim=1) |
| // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)]) |
| // mld = ICMPv6MLDone(mladdr='ff12::1:1111:1111') |
| // pkt = ether/ipv6/hopOpts/mld |
| var pkt = """ |
| 33330000000200112233445586dd6000000000200001fe80000000000000fc0183fffea63712ff020000 |
| 0000000000000000000000023a000502000001008400a73600000000ff12000000000000000000011111 |
| 1111 |
| """.replace("\\s+".toRegex(), "").trim() |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(pkt), |
| DROPPED_IPV6_MLD_REPORT |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testMldV2ReportDropped() { |
| val apfFilter = getMldApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| // Using scapy to generate MLDv2 report |
| // ether = Ether(src='00:11:22:33:44:55', dst='33:33:00:00:00:16') |
| // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:3712', dst='ff02::16', hlim=1) |
| // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)]) |
| // mld = ICMPv6MLReport2(records=[ICMPv6MLDMultAddrRec(dst='ff02::1:1111:1111')]) |
| // pkt = ether/ipv6/hopOpts/mld |
| var pkt = """ |
| 33330000001600112233445586dd6000000000240001fe80000000000000fc0183fffea63712ff020000 |
| 0000000000000000000000163a000502000001008f00982d0000000104000000ff020000000000000000 |
| 000111111111 |
| """.replace("\\s+".toRegex(), "").trim() |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(pkt), |
| DROPPED_IPV6_MLD_REPORT |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testMldV1GeneralQueryReplied() { |
| val apfFilter = getMldApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| // Using scapy to generate MLDv1 general query |
| // ether = Ether(src='00:11:22:33:44:55', dst='33:33:00:00:00:01') |
| // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:3712', dst='ff02::1', hlim=1) |
| // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)]) |
| // mld = ICMPv6MLQuery() |
| // pkt = ether/ipv6/hopOpts/mld |
| var pkt = """ |
| 33330000000100112233445586dd6000000000200001fe80000000000000fc0183fffea63712ff02000 |
| 00000000000000000000000013a000502000001008200a35d2710000000000000000000000000000000 |
| 000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(pkt), |
| DROPPED_IPV6_MLD_V1_GENERAL_QUERY_REPLIED |
| ) |
| |
| val mldV1ReportPkts = setOf( |
| // ###[ Ethernet ]### |
| // dst = 33:33:11:11:11:11 |
| // src = 02:03:04:05:06:07 |
| // type = IPv6 |
| // ###[ IPv6 ]### |
| // version = 6 |
| // tc = 0 |
| // fl = 0 |
| // plen = None |
| // nh = Hop-by-Hop Option Header |
| // hlim = 1 |
| // src = fe80::3 |
| // dst = ff12::1:1111:1111 |
| // ###[ IPv6 Extension Header - Hop-by-Hop Options Header ]### |
| // nh = ICMPv6 |
| // len = None |
| // autopad = On |
| // \options \ |
| // |###[ Router Alert ]### |
| // | otype = Router Alert [00: skip, 0: Don't change en-route] |
| // | optlen = 2 |
| // | value = None |
| // ###[ MLD - Multicast Listener Report ]### |
| // type = MLD Report |
| // code = 0 |
| // cksum = None |
| // mrd = 0 |
| // reserved = 0 |
| // mladdr = ff12::1:1111:1111 |
| """ |
| 33331111111102030405060786dd6000000000200001fe800000000000000000000000000003ff120000 |
| 0000000000000001111111113a0005020000010083003bbd00000000ff12000000000000000000011111 |
| 1111 |
| """.replace("\\s+".toRegex(), "").trim().uppercase(), |
| // ###[ Ethernet ]### |
| // dst = 33:33:22:22:22:22 |
| // src = 02:03:04:05:06:07 |
| // type = IPv6 |
| // ###[ IPv6 ]### |
| // version = 6 |
| // tc = 0 |
| // fl = 0 |
| // plen = None |
| // nh = Hop-by-Hop Option Header |
| // hlim = 1 |
| // src = fe80::3 |
| // dst = ff12::1:2222:2222 |
| // ###[ IPv6 Extension Header - Hop-by-Hop Options Header ]### |
| // nh = ICMPv6 |
| // len = None |
| // autopad = On |
| // \options \ |
| // |###[ Router Alert ]### |
| // | otype = Router Alert [00: skip, 0: Don't change en-route] |
| // | optlen = 2 |
| // | value = None |
| // ###[ MLD - Multicast Listener Report ]### |
| // type = MLD Report |
| // code = 0 |
| // cksum = None |
| // mrd = 0 |
| // reserved = 0 |
| // mladdr = ff12::1:2222:2222 |
| """ |
| 33332222222202030405060786dd6000000000200001fe800000000000000000000000000003ff120000 |
| 0000000000000001222222223a000502000001008300f77800000000ff12000000000000000000012222 |
| 2222 |
| """.replace("\\s+".toRegex(), "").trim().uppercase(), |
| // ###[ Ethernet ]### |
| // dst = 33:33:33:33:33:33 |
| // src = 02:03:04:05:06:07 |
| // type = IPv6 |
| // ###[ IPv6 ]### |
| // version = 6 |
| // tc = 0 |
| // fl = 0 |
| // plen = None |
| // nh = Hop-by-Hop Option Header |
| // hlim = 1 |
| // src = fe80::3 |
| // dst = ff12::1:3333:3333 |
| // ###[ IPv6 Extension Header - Hop-by-Hop Options Header ]### |
| // nh = ICMPv6 |
| // len = None |
| // autopad = On |
| // \options \ |
| // |###[ Router Alert ]### |
| // | otype = Router Alert [00: skip, 0: Don't change en-route] |
| // | optlen = 2 |
| // | value = None |
| // ###[ MLD - Multicast Listener Report ]### |
| // type = MLD Report |
| // code = 0 |
| // cksum = None |
| // mrd = 0 |
| // reserved = 0 |
| // mladdr = ff12::1:3333:3333 |
| """ |
| 33333333333302030405060786dd6000000000200001fe800000000000000000000000000003ff120000 |
| 0000000000000001333333333a000502000001008300b33400000000ff12000000000000000000013333 |
| 3333 |
| """.replace("\\s+".toRegex(), "").trim().uppercase() |
| ) |
| |
| val transmitPackets = apfTestHelpers.getAllTransmittedPackets() |
| .map { HexDump.toHexString(it).uppercase() }.toSet() |
| assertEquals(mldV1ReportPkts, transmitPackets) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testMldV2GeneralQueryReplied() { |
| val apfFilter = getMldApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| // Using scapy to generate MLDv2 general query |
| // ether = Ether(src='00:11:22:33:44:55', dst='33:33:00:00:00:01') |
| // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:3712', dst='ff02::1', hlim=1) |
| // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)]) |
| // mld = ICMPv6MLQuery2() |
| // pkt = ether/ipv6/hopOpts/mld |
| var pkt = """ |
| 33330000000100112233445586dd6000000000240001fe80000000000000fc0183fffea63712ff02000 |
| 00000000000000000000000013a000502000001008200a3592710000000000000000000000000000000 |
| 00000000000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(pkt), |
| DROPPED_IPV6_MLD_V2_GENERAL_QUERY_REPLIED |
| ) |
| |
| val transmittedMldV2Reports = apfTestHelpers.consumeTransmittedPackets(1) |
| // ###[ Ethernet ]### |
| // dst = 33:33:00:00:00:16 |
| // src = 02:03:04:05:06:07 |
| // type = IPv6 |
| // ###[ IPv6 ]### |
| // version = 6 |
| // tc = 0 |
| // fl = 0 |
| // plen = None |
| // nh = Hop-by-Hop Option Header |
| // hlim = 1 |
| // src = fe80::3 |
| // dst = ff02::16 |
| // ###[ IPv6 Extension Header - Hop-by-Hop Options Header ]### |
| // nh = ICMPv6 |
| // len = None |
| // autopad = On |
| // \options \ |
| // |###[ Router Alert ]### |
| // | otype = Router Alert [00: skip, 0: Don't change en-route] |
| // | optlen = 2 |
| // | value = None |
| // ###[ MLDv2 - Multicast Listener Report ]### |
| // type = MLD Report Version 2 |
| // res = 0 |
| // cksum = None |
| // reserved = 0 |
| // records_number= None |
| // \records \ |
| // |###[ ICMPv6 MLDv2 - Multicast Address Record ]### |
| // | rtype = 2 |
| // | auxdata_len= None |
| // | sources_number= None |
| // | dst = ff12::1:1111:1111 |
| // | sources = [ ] |
| // | auxdata = b'' |
| // |###[ ICMPv6 MLDv2 - Multicast Address Record ]### |
| // | rtype = 2 |
| // | auxdata_len= None |
| // | sources_number= None |
| // | dst = ff12::1:2222:2222 |
| // | sources = [ ] |
| // | auxdata = b'' |
| // |###[ ICMPv6 MLDv2 - Multicast Address Record ]### |
| // | rtype = 2 |
| // | auxdata_len= None |
| // | sources_number= None |
| // | dst = ff12::1:3333:3333 |
| // | sources = [ ] |
| // | auxdata = b'' |
| val mldV2ReportPkt = """ |
| 33330000001602030405060786dd60000000004c0001fe800000000000000000000000000003ff020000 |
| 0000000000000000000000163a000502000001008f00a2d80000000302000000ff120000000000000000 |
| 00011111111102000000ff12000000000000000000012222222202000000ff1200000000000000000001 |
| 33333333 |
| """.replace("\\s+".toRegex(), "").trim() |
| assertContentEquals( |
| HexDump.hexStringToByteArray(mldV2ReportPkt), |
| transmittedMldV2Reports[0] |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testMldV1GroupSpecificQueryPassed() { |
| val apfFilter = getMldApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| // Using scapy to generate MLDv1 group specific query |
| // ether = Ether(src='00:11:22:33:44:55', dst='33:33:00:00:00:01') |
| // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:3712', dst='ff02::1:1111:1111', hlim=1) |
| // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)]) |
| // mld = ICMPv6MLQuery(mladdr='ff02::1:1111:1111') |
| // pkt = ether/ipv6/hopOpts/mld |
| var pkt = """ |
| 33330000000100112233445586dd6000000000200001fe80000000000000fc0183fffea63712ff020000 |
| 0000000000000001111111113a000502000001008200601527100000ff02000000000000000000011111 |
| 1111 |
| """.replace("\\s+".toRegex(), "").trim() |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(pkt), |
| PASSED_IPV6_ICMP |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testMldV2GroupSpecificQueryPassed() { |
| val apfFilter = getMldApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| // Using scapy to generate MLDv2 group specific query |
| // ether = Ether(src='00:11:22:33:44:55', dst='33:33:00:00:00:01') |
| // ipv6 = IPv6(src='fe80::fc01:83ff:fea6:3712', dst='ff02::1:1111:1111', hlim=1) |
| // hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)]) |
| // mld = ICMPv6MLQuery2(mladdr='ff02::1:1111:1111') |
| // pkt = ether/ipv6/hopOpts/mld |
| var pkt = """ |
| 33330000000100112233445586dd6000000000240001fe80000000000000fc0183fffea63712ff020000 |
| 0000000000000001111111113a000502000001008200601127100000ff02000000000000000000011111 |
| 111100000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(pkt), |
| PASSED_IPV6_ICMP |
| ) |
| } |
| |
| @Test |
| fun testIPv4MulticastPacketFilter() { |
| val apfConfig = getDefaultConfig() |
| apfConfig.multicastFilter = true |
| val apfFilter = getApfFilter(apfConfig) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) |
| val lp = LinkProperties() |
| lp.addLinkAddress(linkAddress) |
| apfFilter.setLinkProperties(lp) |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, 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() |
| apfTestHelpers.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() |
| apfTestHelpers.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() |
| apfTestHelpers.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() |
| apfTestHelpers.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() |
| apfTestHelpers.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() |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(nonDhcpUcastL2BcastPkt), |
| DROPPED_IPV4_L2_BROADCAST |
| ) |
| } |
| |
| @Test |
| fun testArpFilterDropPktsOnV6OnlyNetwork() { |
| val apfFilter = getApfFilter() |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| apfFilter.updateClatInterfaceState(true) |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| 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) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| apfFilter.addTcpKeepalivePacketFilter(1, parcel) |
| var program = apfTestHelpers.consumeInstalledProgram(apfController, 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| program, |
| HexDump.hexStringToByteArray(otherSrcKeepaliveAck), |
| PASSED_IPV4_UNICAST |
| ) |
| |
| // test IPv4 packets when TCP keepalive filter is removed |
| apfFilter.removeKeepalivePacketFilter(1) |
| program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| program, |
| HexDump.hexStringToByteArray(keepaliveAckPkt), |
| PASSED_IPV4_UNICAST |
| ) |
| |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| 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) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| apfFilter.addNattKeepalivePacketFilter(1, parcel) |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| program, |
| HexDump.hexStringToByteArray(otherSrcNonNattPkt), |
| PASSED_IPV4_UNICAST |
| ) |
| } |
| |
| @Test |
| fun testIPv4TcpPort7Filter() { |
| val apfFilter = getApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| program, |
| HexDump.hexStringToByteArray(fragmentTcpPkt), |
| PASSED_IPV4 |
| ) |
| } |
| |
| @Test |
| fun testIPv6MulticastPacketFilterInDozeMode() { |
| val apfConfig = getDefaultConfig() |
| apfConfig.multicastFilter = true |
| val apfFilter = getApfFilter(apfConfig) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| val lp = LinkProperties() |
| for (addr in hostIpv6Addresses) { |
| lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) |
| } |
| apfFilter.setLinkProperties(lp) |
| apfFilter.setDozeMode(true) |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| program, |
| HexDump.hexStringToByteArray(icmpv6EchoPkt), |
| DROPPED_IPV6_NON_ICMP_MULTICAST |
| ) |
| } |
| |
| @Test |
| fun testIPv6PacketFilter() { |
| val apfFilter = getApfFilter() |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| val lp = LinkProperties() |
| for (addr in hostIpv6Addresses) { |
| lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) |
| } |
| apfFilter.setLinkProperties(lp) |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| program, |
| HexDump.hexStringToByteArray(ipv6WithHopByHopOptionPkt), |
| PASSED_IPV6_HOPOPTS |
| ) |
| } |
| |
| @Test |
| fun testRaFilterIgnoreReservedFieldInRdnssOption() { |
| val apfFilter = getApfFilter() |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| val lp = LinkProperties() |
| for (addr in hostIpv6Addresses) { |
| lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) |
| } |
| apfFilter.setLinkProperties(lp) |
| var program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| val ra1 = """ |
| 33330000000100c0babecafe86dd6e00000000783afffe800000000000002a0079e12e003f01ff0 |
| 200000000000000000000000000018600571140000e100000000000000000010100c0babecafe05 |
| 010000000023ee2602fff80064ff9b0000000000000000190500000012750020014860486000000 |
| 00000000000006420014860486000000000000000006464030440c000002a3000001c2000000000 |
| 2a0079e12e003f010000000000000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| val ra1Bytes = HexDump.hexStringToByteArray(ra1) |
| Os.write(raWriterSocket, ra1Bytes, 0, ra1Bytes.size) |
| |
| program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| program, |
| ra1Bytes, |
| DROPPED_RA |
| ) |
| |
| val ra2 = """ |
| 33330000000100c0babecafe86dd6e00000000783afffe800000000000002a0079e12e003f01ff0 |
| 200000000000000000000000000018600dd3040000e100000000000000000010100c0babecafe05 |
| 010000000023ee2602fff80064ff9b0000000000000000190579e00012750020014860486000000 |
| 00000000000006420014860486000000000000000006464030440c000002a3000001c2000000000 |
| 2a0079e12e003f010000000000000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| program, |
| HexDump.hexStringToByteArray(ra2), |
| DROPPED_RA |
| ) |
| } |
| |
| @Test |
| fun testArpFilterDropPktsNoIPv4() { |
| val apfFilter = getApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| program, |
| HexDump.hexStringToByteArray(garpReplyPkt), |
| DROPPED_GARP_REPLY |
| ) |
| } |
| |
| @Test |
| fun testArpFilterPassPktsNoIPv4() { |
| val apfFilter = getApfFilter() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| program, |
| HexDump.hexStringToByteArray(arpRequestPkt), |
| PASSED_ARP_REQUEST |
| ) |
| } |
| |
| @Test |
| fun testArpFilterDropPktsWithIPv4() { |
| val apfFilter = getApfFilter() |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) |
| val lp = LinkProperties() |
| lp.addLinkAddress(linkAddress) |
| apfFilter.setLinkProperties(lp) |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| program, |
| HexDump.hexStringToByteArray(otherHostArpRequestPkt), |
| DROPPED_ARP_OTHER_HOST |
| ) |
| } |
| |
| @Test |
| fun testArpFilterPassPktsWithIPv4() { |
| val apfFilter = getApfFilter() |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) |
| val lp = LinkProperties() |
| lp.addLinkAddress(linkAddress) |
| apfFilter.setLinkProperties(lp) |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, 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() |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| 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() |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) |
| val lp = LinkProperties() |
| lp.addLinkAddress(linkAddress) |
| apfFilter.setLinkProperties(lp) |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, 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) |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| receivedArpPacket, |
| DROPPED_ARP_REQUEST_REPLIED |
| ) |
| |
| val transmittedPackets = apfTestHelpers.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.handleArpOffload = false |
| val apfFilter = getApfFilter(apfConfig) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) |
| val lp = LinkProperties() |
| lp.addLinkAddress(linkAddress) |
| apfFilter.setLinkProperties(lp) |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, 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) |
| apfTestHelpers.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 = apfTestHelpers.consumeInstalledProgram(apfController, 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 |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(nsPkt), |
| PASSED_IPV6_ICMP |
| ) |
| } |
| |
| @Test |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| fun testNsFilter() { |
| val apfFilter = getApfFilter() |
| apfTestHelpers.consumeInstalledProgram(apfController, 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) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| apfFilter.updateClatInterfaceState(true) |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, 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 |
| apfTestHelpers.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 |
| apfTestHelpers.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 |
| apfTestHelpers.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 |
| apfTestHelpers.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 |
| apfTestHelpers.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 |
| apfTestHelpers.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 |
| apfTestHelpers.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 |
| apfTestHelpers.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 |
| apfTestHelpers.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 |
| apfTestHelpers.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 |
| apfTestHelpers.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 |
| apfTestHelpers.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 |
| apfTestHelpers.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 |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(tentativeTargetIpNsPkt), |
| PASSED_IPV6_ICMP |
| ) |
| |
| // 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 |
| apfTestHelpers.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 |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(dadNsPkt), |
| PASSED_IPV6_ICMP |
| ) |
| |
| // 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 |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(noOptionNsPkt), |
| PASSED_IPV6_ICMP |
| ) |
| |
| // 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 |
| apfTestHelpers.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 |
| apfTestHelpers.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 |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(sllaNotFirstOptionNsPkt), |
| PASSED_IPV6_ICMP |
| ) |
| |
| // 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 |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(noSllaOptionNsPkt), |
| PASSED_IPV6_ICMP |
| ) |
| |
| // 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 |
| apfTestHelpers.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 = apfTestHelpers.consumeInstalledProgram(apfController, 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 |
| ) |
| |
| apfTestHelpers.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 |
| ) |
| |
| apfTestHelpers.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 = apfTestHelpers.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 = apfTestHelpers.consumeInstalledProgram(apfController, 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() |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(hostMcastDstIpNsPkt), |
| DROPPED_IPV6_NS_REPLIED_NON_DAD |
| ) |
| |
| val transmitPkts = apfTestHelpers.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.handleNdOffload = false |
| val apfFilter = getApfFilter(apfConfig) |
| val lp = LinkProperties() |
| for (addr in hostIpv6Addresses) { |
| lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) |
| } |
| |
| apfFilter.setLinkProperties(lp) |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| val validIpv6Addresses = hostIpv6Addresses + hostAnycast6Addresses |
| for (addr in validIpv6Addresses) { |
| // unicast solicited NS request |
| val receivedUcastNsPacket = generateNsPacket( |
| senderMacAddress, |
| apfFilter.mHardwareAddress, |
| senderIpv6Address, |
| addr, |
| addr |
| ) |
| |
| apfTestHelpers.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 |
| ) |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| receivedMcastNsPacket, |
| PASSED_IPV6_ICMP |
| ) |
| } |
| } |
| |
| private fun getApfWithIpv6PingOffloadEnabled( |
| enableMultiCastFilter: Boolean = true, |
| inDozeMode: Boolean = false |
| ): Pair<ApfFilter, ByteArray> { |
| val apfConfig = getDefaultConfig() |
| apfConfig.multicastFilter = enableMultiCastFilter |
| apfConfig.handleIpv6PingOffload = true |
| val apfFilter = getApfFilter(apfConfig) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| if (inDozeMode) { |
| apfFilter.setDozeMode(inDozeMode) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| } |
| val lp = LinkProperties() |
| lp.addLinkAddress(LinkAddress(hostLinkLocalIpv6Address, 64)) |
| apfFilter.setLinkProperties(lp) |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| return Pair(apfFilter, program) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIpv6EchoRequestReplied() { |
| doReturn(64).`when`(dependencies).getIpv6DefaultHopLimit(ifParams.name) |
| val (apfFilter, program) = getApfWithIpv6PingOffloadEnabled() |
| // Using scapy to generate IPv6 echo request packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="02:03:04:05:06:07") |
| // ip = IPv6(src="fe80::1", dst="fe80::03") |
| // icmp = ICMPv6EchoRequest(id=1, seq=123) |
| // pkt = eth/ip/icmp/b"hello" |
| val ipv6EchoRequestPkt = """ |
| 02030405060701020304050686dd60000000000d3a40fe80000000000000000 |
| 0000000000001fe80000000000000000000000000000380003e640001007b68 |
| 656c6c6f |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(ipv6EchoRequestPkt), |
| DROPPED_IPV6_ICMP6_ECHO_REQUEST_REPLIED |
| ) |
| val transmitPkt = apfTestHelpers.consumeTransmittedPackets(1)[0] |
| |
| // ###[ Ethernet ]### |
| // dst = 01:02:03:04:05:06 |
| // src = 02:03:04:05:06:07 |
| // type = IPv6 |
| // ###[ IPv6 ]### |
| // version = 6 |
| // tc = 0 |
| // fl = 0 |
| // plen = 13 |
| // nh = ICMPv6 |
| // hlim = 64 |
| // src = fe80::3 |
| // dst = fe80::1 |
| // ###[ ICMPv6 Echo Reply ]### |
| // type = Echo Reply |
| // code = 0 |
| // cksum = 0x3d64 |
| // id = 0x1 |
| // seq = 0x7b |
| // data = b'hello' |
| val expectedReply = """ |
| 01020304050602030405060786DD60000000000D3A40FE80000000000000000 |
| 0000000000003FE80000000000000000000000000000181003D640001007B68 |
| 656C6C6F |
| """.replace("\\s+".toRegex(), "").trim() |
| assertContentEquals( |
| HexDump.hexStringToByteArray(expectedReply), |
| transmitPkt |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIpv6EchoRequestRepliedInDozeMode() { |
| doReturn(64).`when`(dependencies).getIpv6DefaultHopLimit(ifParams.name) |
| val (apfFilter, program) = getApfWithIpv6PingOffloadEnabled(inDozeMode = true) |
| // Using scapy to generate IPv6 echo request packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="02:03:04:05:06:07") |
| // ip = IPv6(src="fe80::1", dst="fe80::03") |
| // icmp = ICMPv6EchoRequest(id=1, seq=123) |
| // pkt = eth/ip/icmp/b"hello" |
| val ipv6EchoRequestPkt = """ |
| 02030405060701020304050686dd60000000000d3a40fe80000000000000000 |
| 0000000000001fe80000000000000000000000000000380003e640001007b68 |
| 656c6c6f |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(ipv6EchoRequestPkt), |
| DROPPED_IPV6_ICMP6_ECHO_REQUEST_REPLIED |
| ) |
| val transmitPkt = apfTestHelpers.consumeTransmittedPackets(1)[0] |
| |
| // ###[ Ethernet ]### |
| // dst = 01:02:03:04:05:06 |
| // src = 02:03:04:05:06:07 |
| // type = IPv6 |
| // ###[ IPv6 ]### |
| // version = 6 |
| // tc = 0 |
| // fl = 0 |
| // plen = 13 |
| // nh = ICMPv6 |
| // hlim = 64 |
| // src = fe80::3 |
| // dst = fe80::1 |
| // ###[ ICMPv6 Echo Reply ]### |
| // type = Echo Reply |
| // code = 0 |
| // cksum = 0x3d64 |
| // id = 0x1 |
| // seq = 0x7b |
| // data = b'hello' |
| val expectedReply = """ |
| 01020304050602030405060786DD60000000000D3A40FE80000000000000000 |
| 0000000000003FE80000000000000000000000000000181003D640001007B68 |
| 656C6C6F |
| """.replace("\\s+".toRegex(), "").trim() |
| assertContentEquals( |
| HexDump.hexStringToByteArray(expectedReply), |
| transmitPkt |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testCorruptedIpv6IcmpPacketDropped() { |
| val (apfFilter, program) = getApfWithIpv6PingOffloadEnabled() |
| // Using scapy to generate corrupted IPv6 ping packet |
| // eth = Ether(src="01:02:03:04:05:06", dst="02:03:04:05:06:07") |
| // ip = IPv6(src="fe80::1", dst="ff02::fb") |
| // icmp = ICMPv6EchoRequest(id=1, seq=123) |
| // pkt = eth/ip/icmp |
| // (drop the last byte in the packet) |
| val ipv6EchoRequestPkt = """ |
| 02030405060701020304050686dd6000000000083a40fe80000000000000000 |
| 0000000000001fe8000000000000000000000000000038000823b000100 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(ipv6EchoRequestPkt), |
| DROPPED_IPV6_ICMP6_ECHO_REQUEST_INVALID |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIpv6EchoRequestToOtherHostPassed() { |
| val (apfFilter, program) = getApfWithIpv4PingOffloadEnabled() |
| // Using scapy to generate IPv6 echo request packet to other host: |
| // eth = Ether(src="01:02:03:04:05:06", dst="02:03:04:05:06:07") |
| // ip = IPv6(src="fe80::1", dst="fe80::02") |
| // icmp = ICMPv6EchoRequest(id=1, seq=123) |
| // pkt = eth/ip/icmp/b"hello" |
| val ipv6EchoRequestPkt = """ |
| 02030405060701020304050686dd60000000000d3a40fe80000000000000000 |
| 0000000000001fe80000000000000000000000000000280003e650001007b68 |
| 656c6c6f |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(ipv6EchoRequestPkt), |
| PASSED_IPV6_ICMP |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIpv6EchoReplyPassed() { |
| val (apfFilter, program) = getApfWithIpv4PingOffloadEnabled() |
| // Using scapy to generate IPv6 echo reply packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="02:03:04:05:06:07") |
| // ip = IPv6(src="fe80::1", dst="fe80::03") |
| // icmp = ICMPv6EchoReply(id=1, seq=123) |
| // pkt = eth/ip/icmp |
| val ipv6EchoReplyPkt = """ |
| 02030405060701020304050686dd6000000000083a40fe80000000000000000 |
| 0000000000001fe8000000000000000000000000000038100813b0001007b |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(ipv6EchoReplyPkt), |
| PASSED_IPV6_ICMP |
| ) |
| } |
| |
| private fun getApfWithIpv4PingOffloadEnabled( |
| enableMultiCastFilter: Boolean = true |
| ): Pair<ApfFilter, ByteArray> { |
| val apfConfig = getDefaultConfig() |
| apfConfig.multicastFilter = enableMultiCastFilter |
| apfConfig.handleIpv4PingOffload = true |
| val apfFilter = getApfFilter(apfConfig) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) |
| val lp = LinkProperties() |
| lp.addLinkAddress(linkAddress) |
| apfFilter.setLinkProperties(lp) |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| return Pair(apfFilter, program) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIpv4EchoRequestReplied() { |
| doReturn(64).`when`(dependencies).ipv4DefaultTtl |
| val (apfFilter, program) = getApfWithIpv4PingOffloadEnabled() |
| // Using scapy to generate IPv4 echo request packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="02:03:04:05:06:07") |
| // ip = IP(src="10.0.0.2", dst="10.0.0.1") |
| // icmp = ICMP(id=1, seq=123) |
| // pkt = eth/ip/icmp/b"hello" |
| val ipv4EchoRequestPkt = """ |
| 02030405060701020304050608004500002100010000400166d90a0000020a0 |
| 000010800b3b10001007b68656c6c6f |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(ipv4EchoRequestPkt), |
| DROPPED_IPV4_PING_REQUEST_REPLIED |
| ) |
| |
| val transmitPkt = apfTestHelpers.consumeTransmittedPackets(1)[0] |
| |
| // ###[ Ethernet ]### |
| // dst = 01:02:03:04:05:06 |
| // src = 02:03:04:05:06:07 |
| // type = IPv4 |
| // ###[ IP ]### |
| // version = 4 |
| // ihl = 5 |
| // tos = 0x0 |
| // len = 33 |
| // id = 1 |
| // flags = |
| // frag = 0 |
| // ttl = 64 |
| // proto = icmp |
| // chksum = 0x66d9 |
| // src = 10.0.0.1 |
| // dst = 10.0.0.2 |
| // \options \ |
| // ###[ ICMP ]### |
| // type = echo-reply |
| // code = 0 |
| // chksum = 0xbbb1 |
| // id = 0x1 |
| // seq = 0x7b |
| // unused = b'' |
| // ###[ Raw ]### |
| // load = b'hello' |
| val expectedReply = """ |
| 01020304050602030405060708004500002100010000400166D90A0000010A0 |
| 000020000BBB10001007B68656C6C6F |
| """.replace("\\s+".toRegex(), "").trim() |
| assertContentEquals( |
| HexDump.hexStringToByteArray(expectedReply), |
| transmitPkt |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testCorruptedIpv4IcmpPacketDropped() { |
| val (apfFilter, program) = getApfWithIpv4PingOffloadEnabled() |
| // Using scapy to generate corrupted icmp packet |
| // eth = Ether(src="01:02:03:04:05:06", dst="02:03:04:05:06:07") |
| // ip = IP(proto=1, src="10.0.0.2", dst="10.0.0.1") |
| // pkt = eth/ip/b"hello" |
| val ipv4EchoRequestPkt = """ |
| 02030405060701020304050608004500001900010000400166e10a0000020a0 |
| 0000168656c6c6f |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(ipv4EchoRequestPkt), |
| DROPPED_IPV4_ICMP_INVALID |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIpv4EchoRequestWithOptionPassed() { |
| val (apfFilter, program) = getApfWithIpv4PingOffloadEnabled() |
| // Using scapy to generate IPv4 echo request packet with option: |
| // eth = Ether(src="01:02:03:04:05:06", dst="02:03:04:05:06:07") |
| // ip = IP(src="10.0.0.2", dst="10.0.0.1", options=IPOption(b'\x94\x04\x00\x00')) |
| // icmp = ICMP(id=1, seq=123) |
| // pkt = eth/ip/icmp/b"hello" |
| val ipv4EchoRequestPkt = """ |
| 020304050607010203040506080046000025000100004001d1d00a0000020a0 |
| 00001940400000800b3b10001007b68656c6c6f |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(ipv4EchoRequestPkt), |
| PASSED_IPV4_UNICAST |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIpv4EchoRequestToOtherHostPassed() { |
| val (apfFilter, program) = getApfWithIpv4PingOffloadEnabled() |
| // Using scapy to generate IPv4 echo request packet to other host: |
| // eth = Ether(src="01:02:03:04:05:06", dst="02:03:04:05:06:07") |
| // ip = IP(src="10.0.0.2", dst="10.0.0.111") |
| // icmp = ICMP(id=1, seq=123) |
| // pkt = eth/ip/icmp/b"hello" |
| val ipv4EchoRequestPkt = """ |
| 020304050607010203040506080045000021000100004001666b0a0000020a0 |
| 0006f0800b3b10001007b68656c6c6f |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(ipv4EchoRequestPkt), |
| PASSED_IPV4_UNICAST |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testBroadcastIpv4EchoRequestPassed() { |
| val (apfFilter, program) = getApfWithIpv4PingOffloadEnabled(enableMultiCastFilter = false) |
| // Using scapy to generate broadcast IPv4 echo request packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="ff:ff:ff:ff:ff:ff") |
| // ip = IP(src="10.0.0.2", dst="10.0.0.255") |
| // icmp = ICMP(id=1, seq=123) |
| // pkt = eth/ip/icmp/b"hello" |
| val ipv4EchoRequestPkt = """ |
| ffffffffffff01020304050608004500002100010000400165db0a0000020a0 |
| 000ff0800b3b10001007b68656c6c6f |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(ipv4EchoRequestPkt), |
| PASSED_IPV4 |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIpv4EchoReplyPassed() { |
| val (apfFilter, program) = getApfWithIpv4PingOffloadEnabled() |
| // Using scapy to generate IPv4 echo reply packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="02:03:04:05:06:07") |
| // ip = IP(src="10.0.0.2", dst="10.0.0.1") |
| // icmp = ICMP(type=0, id=1, seq=123) |
| // pkt = eth/ip/icmp/b"hello" |
| val ipv4EchoReplyPkt = """ |
| 02030405060701020304050608004500002100010000400166d90a0000020a0 |
| 000010000bbb10001007b68656c6c6f |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(ipv4EchoReplyPkt), |
| PASSED_IPV4_UNICAST |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testOffloadServiceInfoUpdateTriggersProgramInstall() { |
| val apfConfig = getDefaultConfig() |
| apfConfig.handleMdnsOffload = true |
| val apfFilter = getApfFilter(apfConfig) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| val captor = ArgumentCaptor.forClass(OffloadEngine::class.java) |
| verify(nsdManager).registerOffloadEngine( |
| eq(ifParams.name), |
| anyLong(), |
| anyLong(), |
| any(), |
| captor.capture() |
| ) |
| val offloadEngine = captor.value |
| visibleOnHandlerThread(handler) { |
| offloadEngine.onOffloadServiceUpdated(castOffloadInfo.value) |
| } |
| |
| verify(apfController).installPacketFilter(any(), any()) |
| |
| visibleOnHandlerThread(handler) { apfFilter.shutdown() } |
| verify(nsdManager).unregisterOffloadEngine(eq(offloadEngine)) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testCorruptedOffloadServiceInfoUpdateNotTriggerNewProgramInstall() { |
| val apfConfig = getDefaultConfig() |
| apfConfig.handleMdnsOffload = true |
| val apfFilter = getApfFilter(apfConfig) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| val captor = ArgumentCaptor.forClass(OffloadEngine::class.java) |
| verify(nsdManager).registerOffloadEngine( |
| eq(ifParams.name), |
| anyLong(), |
| anyLong(), |
| any(), |
| captor.capture() |
| ) |
| val offloadEngine = captor.value |
| visibleOnHandlerThread(handler) { |
| offloadEngine.onOffloadServiceUpdated(castOffloadInfo.value) |
| } |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| val corruptedOffloadInfo = OffloadServiceInfo( |
| OffloadServiceInfo.Key("gambit", "_${"a".repeat(63)}._tcp"), |
| listOf(), |
| "Android_f47ac10b58cc4b88bc3f5e7a81e59872.local", |
| byteArrayOf(0x01, 0x02, 0x03, 0x04), |
| 0, |
| OffloadEngine.OFFLOAD_TYPE_REPLY.toLong() |
| ) |
| visibleOnHandlerThread(handler) { |
| offloadEngine.onOffloadServiceUpdated(corruptedOffloadInfo) |
| } |
| verify(apfController, never()).installPacketFilter(any(), any()) |
| } |
| |
| private fun getApfWithMdnsOffloadEnabled( |
| apfRam: Int = 4096, |
| mcFilter: Boolean = true, |
| v6Only: Boolean = false, |
| addedOffloadInfos: List<FromU<OffloadServiceInfo>> = listOf( |
| castOffloadInfo, |
| tvRemoteOffloadInfo, |
| manySubtypeOffloadInfo, |
| manySubtypeOffloadInfo |
| ), |
| removedOffloadInfos: List<FromU<OffloadServiceInfo>> = listOf(), |
| raReaderSocket: FileDescriptor = raReadSocket |
| ): Pair<ApfFilter, ByteArray> { |
| val localNsdManager = mock(NsdManager::class.java) |
| doReturn(localNsdManager).`when`(context).getSystemService(NsdManager::class.java) |
| doReturn(raReaderSocket).`when`(dependencies).createPacketReaderSocket(anyInt()) |
| val apfConfig = getDefaultConfig() |
| apfConfig.apfRamSize = apfRam |
| apfConfig.handleMdnsOffload = true |
| if (mcFilter) { |
| apfConfig.multicastFilter = true |
| } |
| val apfFilter = getApfFilter(apfConfig) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| val captor = ArgumentCaptor.forClass(OffloadEngine::class.java) |
| verify(localNsdManager).registerOffloadEngine( |
| eq(ifParams.name), |
| anyLong(), |
| anyLong(), |
| any(), |
| captor.capture() |
| ) |
| val offloadEngine = captor.value |
| val lp = LinkProperties() |
| if (v6Only) { |
| apfFilter.updateClatInterfaceState(true) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| } else { |
| val ipv4LinkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) |
| lp.addLinkAddress(ipv4LinkAddress) |
| } |
| val ipv6LinkAddress = LinkAddress(hostLinkLocalIpv6Address, 64) |
| lp.addLinkAddress(ipv6LinkAddress) |
| apfFilter.setLinkProperties(lp) |
| var program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| |
| if (addedOffloadInfos.isNotEmpty()) { |
| visibleOnHandlerThread(handler) { |
| addedOffloadInfos.forEach { offloadEngine.onOffloadServiceUpdated(it.value) } |
| } |
| program = apfTestHelpers.consumeInstalledProgram( |
| apfController, |
| installCnt = addedOffloadInfos.size |
| ) |
| } |
| if (removedOffloadInfos.isNotEmpty()) { |
| visibleOnHandlerThread(handler) { |
| removedOffloadInfos.forEach { offloadEngine.onOffloadServiceRemoved(it.value) } |
| } |
| program = apfTestHelpers.consumeInstalledProgram( |
| apfController, |
| installCnt = removedOffloadInfos.size |
| ) |
| } |
| return Pair(apfFilter, program) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIPv4MdnsQueryReplied() { |
| val (apfFilter, program) = getApfWithMdnsOffloadEnabled(mcFilter = false) |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb") |
| // ip = IP(src="10.0.0.3", dst="224.0.0.251") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qd=DNSQR(qname="_googlecast._tcp.local", qtype="PTR")) |
| // pkt = eth/ip/udp/dns |
| val castIPv4MdnsPtrQuery = """ |
| 01005e0000fb0102030405060800450000440001000040118faa0a000003e00 |
| 000fb14e914e900309fa50000010000010000000000000b5f676f6f676c6563 |
| 617374045f746370056c6f63616c00000c0001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(castIPv4MdnsPtrQuery), |
| DROPPED_MDNS_REPLIED |
| ) |
| |
| var transmitPkt = apfTestHelpers.consumeTransmittedPackets(1)[0] |
| |
| // ###[ Ethernet ]### |
| // dst = 01:00:5e:00:00:fb |
| // src = 02:03:04:05:06:07 |
| // type = IPv4 |
| // ###[ IP ]### |
| // version = 4 |
| // ihl = 5 |
| // tos = 0x0 |
| // len = 514 |
| // id = 0 |
| // flags = DF |
| // frag = 0 |
| // ttl = 255 |
| // proto = udp |
| // chksum = 0x8eee |
| // src = 10.0.0.1 |
| // dst = 224.0.0.251 |
| // \options \ |
| // ###[ UDP ]### |
| // sport = mdns |
| // dport = mdns |
| // len = 494 |
| // chksum = 0x2f0d |
| // ###[ DNS ]### |
| // id = 0 |
| // qr = 1 |
| // opcode = QUERY |
| // aa = 1 |
| // tc = 0 |
| // rd = 0 |
| // ra = 0 |
| // z = 0 |
| // ad = 0 |
| // cd = 0 |
| // rcode = ok |
| // qdcount = 0 |
| // ancount = 7 |
| // nscount = 0 |
| // arcount = 0 |
| // \qd \ |
| // \an \ |
| // |###[ DNS Resource Record ]### |
| // | rrname = b'_googlecast._tcp.local.' |
| // | type = PTR |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = b'gambit-3cb56c6253638b3641e3d289013cc0ae._googlecast._tcp.local.' |
| // |###[ DNS SRV Resource Record ]### |
| // | rrname = b'\xc0.' |
| // | type = SRV |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | priority = 12320 |
| // | weight = 12320 |
| // | port = 14384 |
| // | target = b'9 3cb56c62-5363-8b36-41e3-d289013cc0ae.local..' |
| // |###[ DNS Resource Record ]### |
| // | rrname = b'\xc0.' |
| // | type = TXT |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = [b' "id=3cb56c6253638b3641e3d289013cc0ae cd=8ECC37F6755390D005DFC02F8EC0D4FA rm=4ABD579644ACFCCF ve=05 md=gambit ic=/setup/icon.png fn=gambit a=264709 st=0 bs=FA8FFD2242A7 nf=1 rs= '] |
| // |###[ DNS Resource Record ]### |
| // | rrname = b'Android_f47ac10b58cc4b88bc3f5e7a81e59872.local.' |
| // | type = A |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = 100.89.85.228 |
| // |###[ DNS Resource Record ]### |
| // | rrname = b' (Android_f47ac10b58cc4b88bc3f5e7a81e59872\xc0\x1d\x00\x01\x00\x01\x00\x00\x00x\x00\x04dYU\xe4\xc1W\x00.\x00\x01\x00\x00\x00x\x00\x10\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xc1W\x00\x1c.' |
| // | type = AAAA |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = fe80::3 |
| // |###[ DNS Resource Record ]### |
| // | rrname = b' (Android_f47ac10b58cc4b88bc3f5e7a81e59872\xc0\x1d\x00\x01\x00\x01\x00\x00\x00x\x00\x04dYU\xe4\xc1W\x00.\x00\x01\x00\x00\x00x\x00\x10\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xc1W\x00\x1c.' |
| // | type = AAAA |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = 200a::3 |
| // |###[ DNS Resource Record ]### |
| // | rrname = b' (Android_f47ac10b58cc4b88bc3f5e7a81e59872\xc0\x1d\x00\x01\x00\x01\x00\x00\x00x\x00\x04dYU\xe4\xc1W\x00.\x00\x01\x00\x00\x00x\x00\x10\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xc1W\x00\x1c.' |
| // | type = AAAA |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = 200b::3 |
| // \ns \ |
| // \ar \ |
| val expectedIPv4CastMdnsReply = """ |
| 01005E0000FB02030405060708004500020200004000FF118EEE0A000001E00 |
| 000FB14E914E901EE2F0D0000840000000007000000000B5F676F6F676C6563 |
| 617374045F746370056C6F63616C00000C000100000078002A2767616D62697 |
| 42D336362353663363235333633386233363431653364323839303133636330 |
| 6165C00C01C0000021000100000078003430203020383030392033636235366 |
| 336322D353336332D386233362D343165332D6432383930313363633061652E |
| 6C6F63616C2E01C000001000010000007800B3B2202269643D3363623536633 |
| 6323533363338623336343165336432383930313363633061652063643D3845 |
| 434333374636373535333930443030354446433032463845433044344641207 |
| 26D3D344142443537393634344143464343462076653D3035206D643D67616D |
| 6269742069633D2F73657475702F69636F6E2E706E6720666E3D67616D62697 |
| 420613D3236343730392073743D302062733D46413846464432323432413720 |
| 6E663D312072733D2028416E64726F69645F663437616331306235386363346 |
| 2383862633366356537613831653539383732C01D0001000100000078000464 |
| 5955E4C157001C0001000000780010FE800000000000000000000000000003C |
| 157001C0001000000780010200A0000000000000000000000000003C157001C |
| 0001000000780010200B0000000000000000000000000003 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| assertContentEquals( |
| HexDump.hexStringToByteArray(expectedIPv4CastMdnsReply), |
| transmitPkt |
| ) |
| |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb") |
| // ip = IP(src="10.0.0.3", dst="224.0.0.251") |
| // udp = UDP(dport=5353, sport=5353) |
| // questions = [ |
| // DNSQR(qname="_airplay._tcp.local", qtype="PTR"), |
| // DNSQR(qname="gambit-3cb56c6253638b3641e3d289013cc0ae._googlecast._tcp.local", qtype="TXT") |
| // ] |
| // dns = dns_compress(DNS(qd=questions)) |
| // pkt = eth/ip/udp/dns |
| val castIPv4MdnsTxtQuery = """ |
| 01005e0000fb01020304050608004500007b0001000040118f730a000003e00 |
| 000fb14e914e900675712000001000002000000000000085f616972706c6179 |
| 045f746370056c6f63616c00000c00012767616d6269742d336362353663363 |
| 23533363338623336343165336432383930313363633061650b5f676f6f676c |
| 6563617374c01500100001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(castIPv4MdnsTxtQuery), |
| DROPPED_MDNS_REPLIED |
| ) |
| |
| transmitPkt = apfTestHelpers.consumeTransmittedPackets(1)[0] |
| |
| assertContentEquals( |
| HexDump.hexStringToByteArray(expectedIPv4CastMdnsReply), |
| transmitPkt |
| ) |
| |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb") |
| // ip = IP(src="10.0.0.3", dst="224.0.0.251") |
| // udp = UDP(dport=5353, sport=5353) |
| // questions = [ |
| // DNSQR(qname="_airplay._tcp.local", qtype="PTR"), |
| // DNSQR(qname="gambit-3cb56c6253638b3641e3d289013cc0ae._googlecast._tcp.local", qtype="SRV") |
| // ] |
| // dns = dns_compress(DNS(qd=questions)) |
| // pkt = eth/ip/udp/dns |
| val castIPv4MdnsSRVQuery = """ |
| 01005e0000fb01020304050608004500007b0001000040118f730a000003e00 |
| 000fb14e914e900674612000001000002000000000000085f616972706c6179 |
| 045f746370056c6f63616c00000c00012767616d6269742d336362353663363 |
| 23533363338623336343165336432383930313363633061650b5f676f6f676c |
| 6563617374c01500210001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(castIPv4MdnsSRVQuery), |
| DROPPED_MDNS_REPLIED |
| ) |
| |
| transmitPkt = apfTestHelpers.consumeTransmittedPackets(1)[0] |
| |
| assertContentEquals( |
| HexDump.hexStringToByteArray(expectedIPv4CastMdnsReply), |
| transmitPkt |
| ) |
| |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb") |
| // ip = IP(src="10.0.0.3", dst="224.0.0.251") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qd=DNSQR(qname="_androidtvremote2._tcp.local", qtype="PTR")) |
| // pkt = eth/ip/udp/dns |
| val tvRemoteIPv4MdnsPtrQuery = """ |
| 01005e0000fb01020304050608004500004a0001000040118fa40a000003e00 |
| 000fb14e914e900366966000001000001000000000000115f616e64726f6964 |
| 747672656d6f746532045f746370056c6f63616c00000c0001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(tvRemoteIPv4MdnsPtrQuery), |
| DROPPED_MDNS_REPLIED |
| ) |
| |
| transmitPkt = apfTestHelpers.consumeTransmittedPackets(1)[0] |
| |
| // ###[ Ethernet ]### |
| // dst = 01:00:5e:00:00:fb |
| // src = 02:03:04:05:06:07 |
| // type = IPv4 |
| // ###[ IP ]### |
| // version = 4 |
| // ihl = 5 |
| // tos = 0x0 |
| // len = 332 |
| // id = 0 |
| // flags = DF |
| // frag = 0 |
| // ttl = 255 |
| // proto = udp |
| // chksum = 0x8fa4 |
| // src = 10.0.0.1 |
| // dst = 224.0.0.251 |
| // \options \ |
| // ###[ UDP ]### |
| // sport = mdns |
| // dport = mdns |
| // len = 312 |
| // chksum = 0xf867 |
| // ###[ DNS ]### |
| // id = 0 |
| // qr = 1 |
| // opcode = QUERY |
| // aa = 1 |
| // tc = 0 |
| // rd = 0 |
| // ra = 0 |
| // z = 0 |
| // ad = 0 |
| // cd = 0 |
| // rcode = ok |
| // qdcount = 0 |
| // ancount = 7 |
| // nscount = 0 |
| // arcount = 0 |
| // \qd \ |
| // \an \ |
| // |###[ DNS Resource Record ]### |
| // | rrname = b'_androidtvremote2._tcp.local.' |
| // | type = PTR |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = b'gambit._androidtvremote2._tcp.local.' |
| // |###[ DNS SRV Resource Record ]### |
| // | rrname = b'gambit._androidtvremote2._tcp.local.' |
| // | type = SRV |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | priority = 12320 |
| // | weight = 12320 |
| // | port = 13876 |
| // | target = b'6 Android_2570595cc11d4af4a4b7146b946eeb9e.local.' |
| // |###[ DNS Resource Record ]### |
| // | rrname = b'gambit._androidtvremote2._tcp.local.' |
| // | type = TXT |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = [b'"bt=3C:4E:56:76:1E:E9"'] |
| // |###[ DNS Resource Record ]### |
| // | rrname = b'Android_f47ac10b58cc4b88bc3f5e7a81e59872.local.' |
| // | type = A |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = 100.89.85.228 |
| // |###[ DNS Resource Record ]### |
| // | rrname = b'Android_f47ac10b58cc4b88bc3f5e7a81e59872.local.' |
| // | type = AAAA |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = fe80::3 |
| // |###[ DNS Resource Record ]### |
| // | rrname = b'Android_f47ac10b58cc4b88bc3f5e7a81e59872.local.' |
| // | type = AAAA |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = 200a::3 |
| // |###[ DNS Resource Record ]### |
| // | rrname = b'Android_f47ac10b58cc4b88bc3f5e7a81e59872.local.' |
| // | type = AAAA |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = 200b::3 |
| // \ns \ |
| // \ar \ |
| val expectedIPv4tvRemoteMdnsReply = """ |
| 01005E0000FB02030405060708004500014C00004000FF118FA40A000001E00 |
| 000FB14E914E90138F867000084000000000700000000115F616E64726F6964 |
| 747672656D6F746532045F746370056C6F63616C00000C00010000007800090 |
| 667616D626974C00CC03400210001000000780037302030203634363620416E |
| 64726F69645F323537303539356363313164346166346134623731343662393 |
| 43665656239652E6C6F63616CC03400100001000000780017162262743D3343 |
| 3A34453A35363A37363A31453A45392228416E64726F69645F6634376163313 |
| 062353863633462383862633366356537613831653539383732C02300010001 |
| 000000780004645955E4C0A3001C0001000000780010FE80000000000000000 |
| 0000000000003C0A3001C0001000000780010200A0000000000000000000000 |
| 000003C0A3001C0001000000780010200B0000000000000000000000000003 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| assertContentEquals( |
| HexDump.hexStringToByteArray(expectedIPv4tvRemoteMdnsReply), |
| transmitPkt |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIPv4MdnsQueryDropped() { |
| val (apfFilter, program) = getApfWithMdnsOffloadEnabled( |
| removedOffloadInfos = listOf(tvRemoteOffloadInfo) |
| ) |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb") |
| // ip = IP(src="10.0.0.3", dst="224.0.0.251") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qd=DNSQR(qname="_airplay._tcp.local", qtype="PTR")) |
| // pkt = eth/ip/udp/dns |
| val airplayIPv4MdnsPtrQuery = """ |
| 01005e0000fb0102030405060800450000410001000040118fad0a000003e00 |
| 000fb14e914e9002d8203000001000001000000000000085f616972706c6179 |
| 045f746370056c6f63616c00000c0001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(airplayIPv4MdnsPtrQuery), |
| DROPPED_MDNS |
| ) |
| |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb") |
| // ip = IP(src="10.0.0.3", dst="224.0.0.251") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qd=DNSQR(qname="_androidtvremote2._tcp.local", qtype="PTR")) |
| // pkt = eth/ip/udp/dns |
| val tvRemoteIPv4MdnsPtrQuery = """ |
| 01005e0000fb01020304050608004500004a0001000040118fa40a000003e00 |
| 000fb14e914e900366966000001000001000000000000115f616e64726f6964 |
| 747672656d6f746532045f746370056c6f63616c00000c0001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(tvRemoteIPv4MdnsPtrQuery), |
| DROPPED_MDNS |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIPv4MdnsQueryWithOptionPassed() { |
| val (apfFilter, program) = getApfWithMdnsOffloadEnabled(mcFilter = false) |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb") |
| // ip = IP(src="10.0.0.3", dst="224.0.0.251", options=IPOption(b'\x94\x04\x00\x00')) |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qd=DNSQR(qname="_googlecast._tcp.local", qtype="PTR")) |
| // pkt = eth/ip/udp/dns |
| val castIPv4MdnsPtrQueryWithOption = """ |
| 01005e0000fb010203040506080046000048000100004011faa10a000003e00 |
| 000fb9404000014e914e900309fa50000010000010000000000000b5f676f6f |
| 676c6563617374045f746370056c6f63616c00000c0001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(castIPv4MdnsPtrQueryWithOption), |
| PASSED_IPV4 |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIPv4MdnsQueryDroppedOnV6OnlyNetwork() { |
| val (apfFilter, program) = getApfWithMdnsOffloadEnabled(mcFilter = false, v6Only = true) |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb") |
| // ip = IP(src="10.0.0.3", dst="224.0.0.251") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qd=DNSQR(qname="_googlecast._tcp.local", qtype="PTR")) |
| // pkt = eth/ip/udp/dns |
| val castIPv4MdnsPtrQuery = """ |
| 01005e0000fb0102030405060800450000440001000040118faa0a000003e00 |
| 000fb14e914e900309fa50000010000010000000000000b5f676f6f676c6563 |
| 617374045f746370056c6f63616c00000c0001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(castIPv4MdnsPtrQuery), |
| DROPPED_IPV4_NON_DHCP4 |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIPv4MdnsReplyPassed() { |
| val (apfFilter, program) = getApfWithMdnsOffloadEnabled(mcFilter = false) |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb") |
| // ip = IP(src="10.0.0.3", dst="224.0.0.251") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qr=1, aa=1, rd=0, qd=None, an=DNSRR(rrname="_androidtvremote2._tcp.local", type="PTR", rdata="gambit._androidtvremote2._tcp.local", ttl=120)) |
| // pkt = eth/ip/udp/dns |
| val tvRemoteIPv4MdnsPtrAnswer = """ |
| 01005e0000fb0102030405060800450000750001000040118f790a000003e00 |
| 000fb14e914e9006169b4000084000000000100000000115f616e64726f6964 |
| 747672656d6f746532045f746370056c6f63616c00000c00010000007800250 |
| 667616d626974115f616e64726f6964747672656d6f746532045f746370056c |
| 6f63616c00 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(tvRemoteIPv4MdnsPtrAnswer), |
| PASSED_MDNS |
| ) |
| } |
| |
| fun testIPv6MdnsQueryReplied() { |
| val (apfFilter, program) = getApfWithMdnsOffloadEnabled(mcFilter = false) |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="33:33:00:00:00:FB") |
| // ip = IPv6(src="fe80::1", dst="ff02::fb") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qd=DNSQR(qname="_googlecast._tcp.local", qtype="PTR")) |
| // pkt = eth/ip/udp/dns |
| val castIPv6MdnsPtrQuery = """ |
| 3333000000fb01020304050686dd6000000000301140fe80000000000000000 |
| 0000000000001ff0200000000000000000000000000fb14e914e900308c2400 |
| 00010000010000000000000b5f676f6f676c6563617374045f746370056c6f6 |
| 3616c00000c0001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(castIPv6MdnsPtrQuery), |
| DROPPED_MDNS_REPLIED |
| ) |
| |
| var transmitPkt = apfTestHelpers.consumeTransmittedPackets(1)[0] |
| |
| // ###[ Ethernet ]### |
| // dst = 33:33:00:00:00:fb |
| // src = 02:03:04:05:06:07 |
| // type = IPv6 |
| // ###[ IPv6 ]### |
| // version = 6 |
| // tc = 0 |
| // fl = 0 |
| // plen = 494 |
| // nh = UDP |
| // hlim = 255 |
| // src = fe80::3 |
| // dst = ff02::fb |
| // ###[ UDP ]### |
| // sport = mdns |
| // dport = mdns |
| // len = 494 |
| // chksum = 0x1b88 |
| // ###[ DNS ]### |
| // id = 0 |
| // qr = 1 |
| // opcode = QUERY |
| // aa = 1 |
| // tc = 0 |
| // rd = 0 |
| // ra = 0 |
| // z = 0 |
| // ad = 0 |
| // cd = 0 |
| // rcode = ok |
| // qdcount = 0 |
| // ancount = 7 |
| // nscount = 0 |
| // arcount = 0 |
| // \qd \ |
| // \an \ |
| // |###[ DNS Resource Record ]### |
| // | rrname = b'_googlecast._tcp.local.' |
| // | type = PTR |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = b'gambit-3cb56c6253638b3641e3d289013cc0ae._googlecast._tcp.local.' |
| // |###[ DNS SRV Resource Record ]### |
| // | rrname = b'\xc0.' |
| // | type = SRV |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | priority = 12320 |
| // | weight = 12320 |
| // | port = 14384 |
| // | target = b'9 3cb56c62-5363-8b36-41e3-d289013cc0ae.local..' |
| // |###[ DNS Resource Record ]### |
| // | rrname = b'\xc0.' |
| // | type = TXT |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = [b' "id=3cb56c6253638b3641e3d289013cc0ae cd=8ECC37F6755390D005DFC02F8EC0D4FA rm=4ABD579644ACFCCF ve=05 md=gambit ic=/setup/icon.png fn=gambit a=264709 st=0 bs=FA8FFD2242A7 nf=1 rs= '] |
| // |###[ DNS Resource Record ]### |
| // | rrname = b'Android_f47ac10b58cc4b88bc3f5e7a81e59872.local.' |
| // | type = A |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = 100.89.85.228 |
| // |###[ DNS Resource Record ]### |
| // | rrname = b' (Android_f47ac10b58cc4b88bc3f5e7a81e59872\xc0\x1d\x00\x01\x00\x01\x00\x00\x00x\x00\x04dYU\xe4\xc1W\x00.\x00\x01\x00\x00\x00x\x00\x10\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xc1W\x00\x1c.' |
| // | type = AAAA |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = fe80::3 |
| // |###[ DNS Resource Record ]### |
| // | rrname = b' (Android_f47ac10b58cc4b88bc3f5e7a81e59872\xc0\x1d\x00\x01\x00\x01\x00\x00\x00x\x00\x04dYU\xe4\xc1W\x00.\x00\x01\x00\x00\x00x\x00\x10\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xc1W\x00\x1c.' |
| // | type = AAAA |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = 200a::3 |
| // |###[ DNS Resource Record ]### |
| // | rrname = b' (Android_f47ac10b58cc4b88bc3f5e7a81e59872\xc0\x1d\x00\x01\x00\x01\x00\x00\x00x\x00\x04dYU\xe4\xc1W\x00.\x00\x01\x00\x00\x00x\x00\x10\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xc1W\x00\x1c.' |
| // | type = AAAA |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = 200b::3 |
| // \ns \ |
| // \ar \ |
| val expectedIPv6CastMdnsReply = """ |
| 3333000000FB02030405060786DD6000000001EE11FFFE80000000000000000 |
| 0000000000003FF0200000000000000000000000000FB14E914E901EE1B8800 |
| 00840000000007000000000B5F676F6F676C6563617374045F746370056C6F6 |
| 3616C00000C000100000078002A2767616D6269742D33636235366336323533 |
| 36333862333634316533643238393031336363306165C00C01C000002100010 |
| 0000078003430203020383030392033636235366336322D353336332D386233 |
| 362D343165332D6432383930313363633061652E6C6F63616C2E01C00000100 |
| 0010000007800B3B2202269643D336362353663363235333633386233363431 |
| 65336432383930313363633061652063643D384543433337463637353533393 |
| 044303035444643303246384543304434464120726D3D344142443537393634 |
| 344143464343462076653D3035206D643D67616D6269742069633D2F7365747 |
| 5702F69636F6E2E706E6720666E3D67616D62697420613D3236343730392073 |
| 743D302062733D464138464644323234324137206E663D312072733D2028416 |
| E64726F69645F66343761633130623538636334623838626333663565376138 |
| 31653539383732C01D00010001000000780004645955E4C157001C000100000 |
| 0780010FE800000000000000000000000000003C157001C0001000000780010 |
| 200A0000000000000000000000000003C157001C0001000000780010200B000 |
| 0000000000000000000000003 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| assertContentEquals( |
| HexDump.hexStringToByteArray(expectedIPv6CastMdnsReply), |
| transmitPkt |
| ) |
| |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="33:33:00:00:00:FB") |
| // ip = IPv6(src="fe80::1", dst="ff02::fb") |
| // udp = UDP(dport=5353, sport=5353) |
| // questions = [ |
| // DNSQR(qname="_airplay._tcp.local", qtype="PTR"), |
| // DNSQR(qname="gambit-3cb56c6253638b3641e3d289013cc0ae._googlecast._tcp.local", qtype="TXT") |
| // ] |
| // dns = dns_compress(DNS(qd=questions)) |
| // pkt = eth/ip/udp/dns |
| val castIPv6MdnsTxtQuery = """ |
| 3333000000fb01020304050686dd6000000000671140fe80000000000000000 |
| 0000000000001ff0200000000000000000000000000fb14e914e90067439100 |
| 0001000002000000000000085f616972706c6179045f746370056c6f63616c0 |
| 0000c00012767616d6269742d33636235366336323533363338623336343165 |
| 336432383930313363633061650b5f676f6f676c6563617374c01500100001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(castIPv6MdnsTxtQuery), |
| DROPPED_MDNS_REPLIED |
| ) |
| |
| transmitPkt = apfTestHelpers.consumeTransmittedPackets(1)[0] |
| |
| assertContentEquals( |
| HexDump.hexStringToByteArray(expectedIPv6CastMdnsReply), |
| transmitPkt |
| ) |
| |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="33:33:00:00:00:FB") |
| // ip = IPv6(src="fe80::1", dst="ff02::fb") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qd=DNSQR(qname="_androidtvremote2._tcp.local", qtype="PTR")) |
| // pkt = eth/ip/udp/dns |
| val tvRemoteIPv6MdnsPtrQuery = """ |
| 3333000000fb01020304050686dd6000000000361140fe80000000000000000 |
| 0000000000001ff0200000000000000000000000000fb14e914e9003655e500 |
| 0001000001000000000000115f616e64726f6964747672656d6f746532045f7 |
| 46370056c6f63616c00000c0001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(tvRemoteIPv6MdnsPtrQuery), |
| DROPPED_MDNS_REPLIED |
| ) |
| |
| transmitPkt = apfTestHelpers.consumeTransmittedPackets(1)[0] |
| |
| // ###[ Ethernet ]### |
| // dst = 33:33:00:00:00:fb |
| // src = 02:03:04:05:06:07 |
| // type = IPv6 |
| // ###[ IPv6 ]### |
| // version = 6 |
| // tc = 0 |
| // fl = 0 |
| // plen = 312 |
| // nh = UDP |
| // hlim = 255 |
| // src = fe80::3 |
| // dst = ff02::fb |
| // ###[ UDP ]### |
| // sport = mdns |
| // dport = mdns |
| // len = 312 |
| // chksum = 0xf867 |
| // ###[ DNS ]### |
| // id = 0 |
| // qr = 1 |
| // opcode = QUERY |
| // aa = 1 |
| // tc = 0 |
| // rd = 0 |
| // ra = 0 |
| // z = 0 |
| // ad = 0 |
| // cd = 0 |
| // rcode = ok |
| // qdcount = 0 |
| // ancount = 7 |
| // nscount = 0 |
| // arcount = 0 |
| // \qd \ |
| // \an \ |
| // |###[ DNS Resource Record ]### |
| // | rrname = b'_androidtvremote2._tcp.local.' |
| // | type = PTR |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = b'gambit._androidtvremote2._tcp.local.' |
| // |###[ DNS SRV Resource Record ]### |
| // | rrname = b'gambit._androidtvremote2._tcp.local.' |
| // | type = SRV |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | priority = 12320 |
| // | weight = 12320 |
| // | port = 13876 |
| // | target = b'6 Android_2570595cc11d4af4a4b7146b946eeb9e.local.' |
| // |###[ DNS Resource Record ]### |
| // | rrname = b'gambit._androidtvremote2._tcp.local.' |
| // | type = TXT |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = [b'"bt=3C:4E:56:76:1E:E9"'] |
| // |###[ DNS Resource Record ]### |
| // | rrname = b'Android_f47ac10b58cc4b88bc3f5e7a81e59872.local.' |
| // | type = A |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = 100.89.85.228 |
| // |###[ DNS Resource Record ]### |
| // | rrname = b'Android_f47ac10b58cc4b88bc3f5e7a81e59872.local.' |
| // | type = AAAA |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = fe80::3 |
| // |###[ DNS Resource Record ]### |
| // | rrname = b'Android_f47ac10b58cc4b88bc3f5e7a81e59872.local.' |
| // | type = AAAA |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = 200a::3 |
| // |###[ DNS Resource Record ]### |
| // | rrname = b'Android_f47ac10b58cc4b88bc3f5e7a81e59872.local.' |
| // | type = AAAA |
| // | cacheflush= 0 |
| // | rclass = IN |
| // | ttl = 120 |
| // | rdlen = None |
| // | rdata = 200b::3 |
| // \ns \ |
| // \ar \ |
| val expectedIPv6tvRemoteMdnsReply = """ |
| 3333000000FB02030405060786DD60000000013811FFFE80000000000000000 |
| 0000000000003FF0200000000000000000000000000FB14E914E90138E4E200 |
| 0084000000000700000000115F616E64726F6964747672656D6F746532045F7 |
| 46370056C6F63616C00000C00010000007800090667616D626974C00CC03400 |
| 210001000000780037302030203634363620416E64726F69645F32353730353 |
| 935636331316434616634613462373134366239343665656239652E6C6F6361 |
| 6CC03400100001000000780017162262743D33433A34453A35363A37363A314 |
| 53A45392228416E64726F69645F663437616331306235386363346238386263 |
| 3366356537613831653539383732C02300010001000000780004645955E4C0A |
| 3001C0001000000780010FE800000000000000000000000000003C0A3001C00 |
| 01000000780010200A0000000000000000000000000003C0A3001C000100000 |
| 0780010200B0000000000000000000000000003 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| assertContentEquals( |
| HexDump.hexStringToByteArray(expectedIPv6tvRemoteMdnsReply), |
| transmitPkt |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIPv6MdnsQueryDropped() { |
| val (apfFilter, program) = getApfWithMdnsOffloadEnabled( |
| removedOffloadInfos = listOf(tvRemoteOffloadInfo) |
| ) |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="33:33:00:00:00:FB") |
| // ip = IPv6(src="fe80::1", dst="ff02::fb") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qd=DNSQR(qname="_airplay._tcp.local", qtype="PTR")) |
| // pkt = eth/ip/udp/dns |
| val airplayIPv6MdnsPtrQuery = """ |
| 3333000000fb01020304050686dd60000000002d1140fe80000000000000000 |
| 0000000000001ff0200000000000000000000000000fb14e914e9002d6e8200 |
| 0001000001000000000000085f616972706c6179045f746370056c6f63616c0 |
| 0000c0001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(airplayIPv6MdnsPtrQuery), |
| DROPPED_MDNS |
| ) |
| |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="33:33:00:00:00:FB") |
| // ip = IPv6(src="fe80::1", dst="ff02::fb") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qd=DNSQR(qname="_androidtvremote2._tcp.local", qtype="PTR")) |
| // pkt = eth/ip/udp/dns |
| val tvRemoteIPv6MdnsPtrQuery = """ |
| 3333000000fb01020304050686dd6000000000361140fe80000000000000000 |
| 0000000000001ff0200000000000000000000000000fb14e914e9003655e500 |
| 0001000001000000000000115f616e64726f6964747672656d6f746532045f7 |
| 46370056c6f63616c00000c0001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(tvRemoteIPv6MdnsPtrQuery), |
| DROPPED_MDNS |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testIPv6MdnsReplyPassed() { |
| val (apfFilter, program) = getApfWithMdnsOffloadEnabled(mcFilter = false) |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="33:33:00:00:00:FB") |
| // ip = IPv6(src="fe80::1", dst="ff02::fb") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qr=1, aa=1, rd=0, qd=None, an=DNSRR(rrname="_androidtvremote2._tcp.local", type="PTR", rdata="gambit._androidtvremote2._tcp.local", ttl=120)) |
| // pkt = eth/ip/udp/dns |
| val tvRemoteIPv6MdnsPtrAnswer = """ |
| 3333000000fb01020304050686dd6000000000611140fe80000000000000000 |
| 0000000000001ff0200000000000000000000000000fb14e914e90061563300 |
| 0084000000000100000000115f616e64726f6964747672656d6f746532045f7 |
| 46370056c6f63616c00000c00010000007800250667616d626974115f616e64 |
| 726f6964747672656d6f746532045f746370056c6f63616c00 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(tvRemoteIPv6MdnsPtrAnswer), |
| PASSED_MDNS |
| ) |
| } |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testRaFilterWorksWhenMdnsOffloadEnabled() { |
| var (apfFilter, program) = getApfWithMdnsOffloadEnabled() |
| // ###[ Ethernet ]### |
| // dst = 33:33:00:00:00:01 |
| // src = f4:34:f0:64:52:fe |
| // type = IPv6 |
| // ###[ IPv6 ]### |
| // version = 6 |
| // tc = 0 |
| // fl = 68608 |
| // plen = 80 |
| // nh = ICMPv6 |
| // hlim = 255 |
| // src = fe80::1cb6:b5bc:353b:7cfd |
| // dst = ff02::1 |
| // ###[ ICMPv6 Neighbor Discovery - Router Advertisement ]### |
| // type = Router Advertisement |
| // code = 0 |
| // cksum = 0xfab |
| // chlim = 0 |
| // M = 0 |
| // O = 0 |
| // H = 0 |
| // prf = Medium (default) |
| // P = 0 |
| // res = 0 |
| // routerlifetime= 0 |
| // reachabletime= 0 |
| // retranstimer= 0 |
| // ###[ ICMPv6 Neighbor Discovery Option - Prefix Information ]### |
| // type = 3 |
| // len = 4 |
| // prefixlen = 64 |
| // L = 1 |
| // A = 1 |
| // R = 0 |
| // res1 = 0 |
| // validlifetime= 0x708 |
| // preferredlifetime= 0x708 |
| // res2 = 0x0 |
| // prefix = fdee:d0c4:7546:5344:: |
| // ###[ ICMPv6 Neighbor Discovery Option - Route Information Option ]### |
| // type = 24 |
| // len = 2 |
| // plen = 64 |
| // res1 = 0 |
| // prf = Medium (default) |
| // res2 = 0 |
| // rtlifetime= 1800 |
| // prefix = fd0c:8be6:43ee:: |
| // ###[ ICMPv6 Neighbor Discovery Option - Expanded Flags Option ]### |
| // type = 26 |
| // len = 1 |
| // res = 140737488355328 |
| // ###[ ICMPv6 Neighbor Discovery Option - Source Link-Layer Address ]### |
| // type = 1 |
| // len = 1 |
| // lladdr = f4:34:f0:64:52:fe |
| val ra = """ |
| 333300000001f434f06452fe86dd60010c0000503afffe800000000000001cb6b5bc353b7cfdff0 |
| 2000000000000000000000000000186000fab000000000000000000000000030440c00000070800 |
| 00070800000000fdeed0c47546534400000000000000001802400000000708fd0c8be643ee00001 |
| a018000000000000101f434f06452fe |
| """.replace("\\s+".toRegex(), "").trim() |
| val raBytes = HexDump.hexStringToByteArray(ra) |
| Os.write(raWriterSocket, raBytes, 0, raBytes.size) |
| |
| program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| raBytes, |
| DROPPED_RA |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testMdnsOffloadFailOpenForTooManySubtype() { |
| val (apfFilter, program) = getApfWithMdnsOffloadEnabled() |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb") |
| // ip = IP(src="10.0.0.3", dst="224.0.0.251") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qd=DNSQR(qname="_testsubtype._tcp.local", qtype="PTR")) |
| // pkt = eth/ip/udp/dns |
| val typePtrQuery = """ |
| 01005e0000fb0102030405060800450000450001000040118fa90a000003e00 |
| 000fb14e914e900319b020000010000010000000000000c5f74657374737562 |
| 74797065045f746370056c6f63616c00000c0001 |
| """.replace("\\s+".toRegex(), "").trim() |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(typePtrQuery), |
| PASSED_MDNS |
| ) |
| |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb") |
| // ip = IP(src="10.0.0.3", dst="224.0.0.251") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qd=DNSQR(qname="sub1._sub._testsubtype._tcp.local", qtype="PTR")) |
| // pkt = eth/ip/udp/dns |
| val subTypePtrQuery = """ |
| 01005e0000fb01020304050608004500004f0001000040118f9f0a000003e00 |
| 000fb14e914e9003b1b3f0000010000010000000000000473756231045f7375 |
| 620c5f7465737473756274797065045f746370056c6f63616c00000c0001 |
| """.replace("\\s+".toRegex(), "").trim() |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(subTypePtrQuery), |
| PASSED_MDNS |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testMdnsOffloadRulePrioritizedOverRaFilter() { |
| val (apfFilterForEstimation, _) = getApfWithMdnsOffloadEnabled( |
| apfRam = 4096, |
| addedOffloadInfos = listOf( |
| castOffloadInfo, |
| tvRemoteOffloadInfo, |
| manySubtypeOffloadInfo |
| ), |
| raReaderSocket = FileDescriptor(), |
| ) |
| |
| val apfRam = apfFilterForEstimation.overEstimatedProgramSize + counterTotalSize |
| |
| val (apfFilter, _) = getApfWithMdnsOffloadEnabled( |
| apfRam = apfRam, |
| addedOffloadInfos = listOf( |
| castOffloadInfo, |
| tvRemoteOffloadInfo, |
| manySubtypeOffloadInfo |
| ), |
| ) |
| // ###[ Ethernet ]### |
| // dst = 33:33:00:00:00:01 |
| // src = f4:34:f0:64:52:fe |
| // type = IPv6 |
| // ###[ IPv6 ]### |
| // version = 6 |
| // tc = 0 |
| // fl = 68608 |
| // plen = 80 |
| // nh = ICMPv6 |
| // hlim = 255 |
| // src = fe80::1cb6:b5bc:353b:7cfd |
| // dst = ff02::1 |
| // ###[ ICMPv6 Neighbor Discovery - Router Advertisement ]### |
| // type = Router Advertisement |
| // code = 0 |
| // cksum = 0xfab |
| // chlim = 0 |
| // M = 0 |
| // O = 0 |
| // H = 0 |
| // prf = Medium (default) |
| // P = 0 |
| // res = 0 |
| // routerlifetime= 0 |
| // reachabletime= 0 |
| // retranstimer= 0 |
| // ###[ ICMPv6 Neighbor Discovery Option - Prefix Information ]### |
| // type = 3 |
| // len = 4 |
| // prefixlen = 64 |
| // L = 1 |
| // A = 1 |
| // R = 0 |
| // res1 = 0 |
| // validlifetime= 0x708 |
| // preferredlifetime= 0x708 |
| // res2 = 0x0 |
| // prefix = fdee:d0c4:7546:5344:: |
| // ###[ ICMPv6 Neighbor Discovery Option - Route Information Option ]### |
| // type = 24 |
| // len = 2 |
| // plen = 64 |
| // res1 = 0 |
| // prf = Medium (default) |
| // res2 = 0 |
| // rtlifetime= 1800 |
| // prefix = fd0c:8be6:43ee:: |
| // ###[ ICMPv6 Neighbor Discovery Option - Expanded Flags Option ]### |
| // type = 26 |
| // len = 1 |
| // res = 140737488355328 |
| // ###[ ICMPv6 Neighbor Discovery Option - Source Link-Layer Address ]### |
| // type = 1 |
| // len = 1 |
| // lladdr = f4:34:f0:64:52:fe |
| val ra = """ |
| 333300000001f434f06452fe86dd60010c0000503afffe800000000000001cb6b5bc353b7cfdff0 |
| 2000000000000000000000000000186000fab000000000000000000000000030440c00000070800 |
| 00070800000000fdeed0c47546534400000000000000001802400000000708fd0c8be643ee00001 |
| a018000000000000101f434f06452fe |
| """.replace("\\s+".toRegex(), "").trim() |
| val raBytes = HexDump.hexStringToByteArray(ra) |
| Os.write(raWriterSocket, raBytes, 0, raBytes.size) |
| |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| assertThat(program.size).isLessThan(apfRam + 1) |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| raBytes, |
| PASSED_IPV6_ICMP |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testMdnsOffloadRulePrioritizationAllRulesOffloaded() { |
| val (apfFilter, program) = getApfWithMdnsOffloadEnabled( |
| apfRam = 4096, |
| addedOffloadInfos = listOf( |
| castOffloadInfo, |
| tvRemoteOffloadInfo, |
| manySubtypeOffloadInfo |
| ), |
| ) |
| assertThat(program.size).isLessThan(4097) |
| |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb") |
| // ip = IP(src="10.0.0.3", dst="224.0.0.251") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qd=DNSQR(qname="_googlecast._tcp.local", qtype="PTR")) |
| // pkt = eth/ip/udp/dns |
| val castIPv4MdnsPtrQueryForOffload = """ |
| 01005e0000fb0102030405060800450000440001000040118faa0a000003e00 |
| 000fb14e914e900309fa50000010000010000000000000b5f676f6f676c6563 |
| 617374045f746370056c6f63616c00000c0001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(castIPv4MdnsPtrQueryForOffload), |
| DROPPED_MDNS_REPLIED |
| ) |
| |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb") |
| // ip = IP(src="10.0.0.3", dst="224.0.0.251") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qd=DNSQR(qname="_androidtvremote2._tcp.local", qtype="PTR")) |
| // pkt = eth/ip/udp/dns |
| val tvRemoteIPv4MdnsPtrQueryForOffload = """ |
| 01005e0000fb01020304050608004500004a0001000040118fa40a000003e00 |
| 000fb14e914e900366966000001000001000000000000115f616e64726f6964 |
| 747672656d6f746532045f746370056c6f63616c00000c0001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(tvRemoteIPv4MdnsPtrQueryForOffload), |
| DROPPED_MDNS_REPLIED |
| ) |
| |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb") |
| // ip = IP(src="10.0.0.3", dst="224.0.0.251") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qd=DNSQR(qname="sub1._sub._testsubtype._tcp.local", qtype="PTR")) |
| // pkt = eth/ip/udp/dns |
| val subTypePtrQueryForPassthrough = """ |
| 01005e0000fb01020304050608004500004f0001000040118f9f0a000003e00 |
| 000fb14e914e9003b1b3f0000010000010000000000000473756231045f7375 |
| 620c5f7465737473756274797065045f746370056c6f63616c00000c0001 |
| """.replace("\\s+".toRegex(), "").trim() |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(subTypePtrQueryForPassthrough), |
| PASSED_MDNS |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testMdnsOffloadRulePrioritizationSomeRulesFailOpened() { |
| val (apfFilterForEstimation, _) = getApfWithMdnsOffloadEnabled( |
| apfRam = 4096, |
| addedOffloadInfos = listOf( |
| castOffloadInfo, |
| tvRemoteOffloadInfo, |
| manySubtypeOffloadInfo |
| ), |
| ) |
| |
| val apfRam = apfFilterForEstimation.overEstimatedProgramSize + counterTotalSize - 1 |
| |
| val (apfFilter, program) = getApfWithMdnsOffloadEnabled( |
| apfRam = apfRam, |
| addedOffloadInfos = listOf( |
| castOffloadInfo, |
| tvRemoteOffloadInfo, |
| manySubtypeOffloadInfo |
| ), |
| ) |
| assertThat(program.size).isLessThan(apfRam + 1) |
| |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb") |
| // ip = IP(src="10.0.0.3", dst="224.0.0.251") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qd=DNSQR(qname="_googlecast._tcp.local", qtype="PTR")) |
| // pkt = eth/ip/udp/dns |
| val castIPv4MdnsPtrQueryForOffload = """ |
| 01005e0000fb0102030405060800450000440001000040118faa0a000003e00 |
| 000fb14e914e900309fa50000010000010000000000000b5f676f6f676c6563 |
| 617374045f746370056c6f63616c00000c0001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(castIPv4MdnsPtrQueryForOffload), |
| DROPPED_MDNS_REPLIED |
| ) |
| |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb") |
| // ip = IP(src="10.0.0.3", dst="224.0.0.251") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qd=DNSQR(qname="_androidtvremote2._tcp.local", qtype="PTR")) |
| // pkt = eth/ip/udp/dns |
| val tvRemoteIPv4MdnsPtrQueryForPassthrough = """ |
| 01005e0000fb01020304050608004500004a0001000040118fa40a000003e00 |
| 000fb14e914e900366966000001000001000000000000115f616e64726f6964 |
| 747672656d6f746532045f746370056c6f63616c00000c0001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(tvRemoteIPv4MdnsPtrQueryForPassthrough), |
| PASSED_MDNS |
| ) |
| |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb") |
| // ip = IP(src="10.0.0.3", dst="224.0.0.251") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qd=DNSQR(qname="sub1._sub._testsubtype._tcp.local", qtype="PTR")) |
| // pkt = eth/ip/udp/dns |
| val subTypePtrQueryForPassthrough = """ |
| 01005e0000fb01020304050608004500004f0001000040118f9f0a000003e00 |
| 000fb14e914e9003b1b3f0000010000010000000000000473756231045f7375 |
| 620c5f7465737473756274797065045f746370056c6f63616c00000c0001 |
| """.replace("\\s+".toRegex(), "").trim() |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(subTypePtrQueryForPassthrough), |
| PASSED_MDNS |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testMdnsOffloadRulePrioritizationAllRulesFailOpened() { |
| val (apfFilterForEstimation, _) = getApfWithMdnsOffloadEnabled( |
| apfRam = 4096, |
| addedOffloadInfos = listOf(passthroughCastOffloadInfo), |
| ) |
| |
| val apfRam = apfFilterForEstimation.overEstimatedProgramSize + counterTotalSize |
| val (apfFilter, program) = getApfWithMdnsOffloadEnabled( |
| apfRam = apfRam, |
| addedOffloadInfos = listOf( |
| castOffloadInfo, |
| tvRemoteOffloadInfo, |
| manySubtypeOffloadInfo |
| ), |
| ) |
| assertThat(program.size).isLessThan(apfRam + 1) |
| |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb") |
| // ip = IP(src="10.0.0.3", dst="224.0.0.251") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qd=DNSQR(qname="_googlecast._tcp.local", qtype="PTR")) |
| // pkt = eth/ip/udp/dns |
| val castIPv4MdnsPtrQueryForPassthrough = """ |
| 01005e0000fb0102030405060800450000440001000040118faa0a000003e00 |
| 000fb14e914e900309fa50000010000010000000000000b5f676f6f676c6563 |
| 617374045f746370056c6f63616c00000c0001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(castIPv4MdnsPtrQueryForPassthrough), |
| PASSED_MDNS |
| ) |
| |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb") |
| // ip = IP(src="10.0.0.3", dst="224.0.0.251") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qd=DNSQR(qname="_androidtvremote2._tcp.local", qtype="PTR")) |
| // pkt = eth/ip/udp/dns |
| val tvRemoteIPv4MdnsPtrQueryForPassthrough = """ |
| 01005e0000fb01020304050608004500004a0001000040118fa40a000003e00 |
| 000fb14e914e900366966000001000001000000000000115f616e64726f6964 |
| 747672656d6f746532045f746370056c6f63616c00000c0001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(tvRemoteIPv4MdnsPtrQueryForPassthrough), |
| PASSED_MDNS |
| ) |
| |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb") |
| // ip = IP(src="10.0.0.3", dst="224.0.0.251") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qd=DNSQR(qname="sub1._sub._testsubtype._tcp.local", qtype="PTR")) |
| // pkt = eth/ip/udp/dns |
| val subTypePtrQueryForPassthrough = """ |
| 01005e0000fb01020304050608004500004f0001000040118f9f0a000003e00 |
| 000fb14e914e9003b1b3f0000010000010000000000000473756231045f7375 |
| 620c5f7465737473756274797065045f746370056c6f63616c00000c0001 |
| """.replace("\\s+".toRegex(), "").trim() |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(subTypePtrQueryForPassthrough), |
| PASSED_MDNS |
| ) |
| |
| // Using scapy to generate packet: |
| // eth = Ether(src="01:02:03:04:05:06", dst="01:00:5e:00:00:fb") |
| // ip = IP(src="10.0.0.3", dst="224.0.0.251") |
| // udp = UDP(dport=5353, sport=5353) |
| // dns = DNS(qd=DNSQR(qname="_airplay._tcp.local", qtype="PTR")) |
| // pkt = eth/ip/udp/dns |
| val airplayIPv4MdnsPtrQueryForPassthrough = """ |
| 01005e0000fb0102030405060800450000410001000040118fad0a000003e00 |
| 000fb14e914e9002d8203000001000001000000000000085f616972706c6179 |
| 045f746370056c6f63616c00000c0001 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(airplayIPv4MdnsPtrQueryForPassthrough), |
| PASSED_MDNS |
| ) |
| } |
| |
| @Test |
| fun testApfProgramUpdate() { |
| val apfFilter = getApfFilter() |
| apfTestHelpers.consumeInstalledProgram(apfController, 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) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| |
| // add the same IPv4 address, expect to have no apf program update |
| apfFilter.setLinkProperties(lp) |
| verify(apfController, never()).installPacketFilter(any(), any()) |
| |
| // add IPv6 addresses, expect to have apf program update |
| for (addr in hostIpv6Addresses) { |
| lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) |
| } |
| |
| apfFilter.setLinkProperties(lp) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| |
| // add the same IPv6 addresses, expect to have no apf program update |
| apfFilter.setLinkProperties(lp) |
| verify(apfController, never()).installPacketFilter(any(), 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) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| |
| // add the same IPv6 addresses, expect to have no apf program update |
| apfFilter.setLinkProperties(lp) |
| verify(apfController, never()).installPacketFilter(any(), any()) |
| } |
| |
| // The APFv6 code path is only turned on in V+ |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testApfProgramUpdateWithMulticastAddressChange() { |
| val mcastAddrs = mutableListOf( |
| InetAddress.getByName("224.0.0.1") as Inet4Address |
| ) |
| doReturn(mcastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any()) |
| val apfConfig = getDefaultConfig() |
| apfConfig.handleIgmpOffload = true |
| val apfFilter = getApfFilter(apfConfig) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| val addr = InetAddress.getByName("239.0.0.1") as Inet4Address |
| mcastAddrs.add(addr) |
| doReturn(mcastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any()) |
| val testPacket = HexDump.hexStringToByteArray("000000") |
| Os.write(mcastWriteSocket, testPacket, 0, testPacket.size) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| |
| Os.write(mcastWriteSocket, testPacket, 0, testPacket.size) |
| Thread.sleep(NO_CALLBACK_TIMEOUT_MS) |
| verify(apfController, never()).installPacketFilter(any(), any()) |
| |
| mcastAddrs.remove(addr) |
| doReturn(mcastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any()) |
| Os.write(mcastWriteSocket, testPacket, 0, testPacket.size) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testApfProgramUpdateWithIPv6MulticastAddressChange() { |
| val mcastAddrs = mutableListOf( |
| IPV6_ADDR_ALL_NODES_MULTICAST, |
| IPV6_ADDR_NODE_LOCAL_ALL_NODES_MULTICAST |
| ) |
| doReturn(mcastAddrs).`when`(dependencies).getIPv6MulticastAddresses(any()) |
| val apfConfig = getDefaultConfig() |
| apfConfig.handleMldOffload = true |
| val apfFilter = getApfFilter(apfConfig) |
| val ipv6LinkAddress = LinkAddress(hostLinkLocalIpv6Address, 64) |
| val lp = LinkProperties() |
| lp.addLinkAddress(ipv6LinkAddress) |
| apfFilter.setLinkProperties(lp) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 3) |
| val addr = InetAddress.getByName("ff0e::1") as Inet6Address |
| mcastAddrs.add(addr) |
| updateIPv6MulticastAddrs(apfFilter, mcastAddrs) |
| val testPacket = HexDump.hexStringToByteArray("000000") |
| Os.write(mcastWriteSocket, testPacket, 0, testPacket.size) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| |
| var solicitedNodeMcastAddr = InetAddress.getByName("ff02::1:ff12:3456") as Inet6Address |
| mcastAddrs.add(solicitedNodeMcastAddr) |
| Os.write(mcastWriteSocket, testPacket, 0, testPacket.size) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| |
| Os.write(mcastWriteSocket, testPacket, 0, testPacket.size) |
| Thread.sleep(NO_CALLBACK_TIMEOUT_MS) |
| verify(apfController, never()).installPacketFilter(any(), any()) |
| |
| mcastAddrs.remove(addr) |
| updateIPv6MulticastAddrs(apfFilter, mcastAddrs) |
| Os.write(mcastWriteSocket, testPacket, 0, testPacket.size) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| } |
| |
| @Test |
| fun testApfFilterInitializationCleanUpTheApfMemoryRegion() { |
| val apfFilter = getApfFilter() |
| val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java) |
| verify(apfController, times(2)) |
| .installPacketFilter(programCaptor.capture(), any()) |
| val program = programCaptor.allValues.first() |
| assertContentEquals(ByteArray(4096) { 0 }, program) |
| } |
| |
| @Test |
| fun testApfFilterResumeWillCleanUpTheApfMemoryRegion() { |
| val apfFilter = getApfFilter() |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| apfFilter.resume() |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| assertContentEquals(ByteArray(4096) { 0 }, program) |
| } |
| |
| @Test |
| fun testApfIPv4MulticastAddrsUpdate() { |
| // mock IPv4 multicast address from /proc/net/igmp |
| val mcastAddrs = mutableListOf( |
| InetAddress.getByName("224.0.0.1") as Inet4Address, |
| InetAddress.getByName("239.0.0.1") as Inet4Address |
| ) |
| val mcastAddrsExcludeAllHost = mutableListOf( |
| InetAddress.getByName("239.0.0.1") as Inet4Address |
| ) |
| doReturn(mcastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any()) |
| val apfFilter = getApfFilter() |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| assertEquals(mcastAddrs.toSet(), apfFilter.mIPv4MulticastAddresses) |
| assertEquals(mcastAddrsExcludeAllHost.toSet(), apfFilter.mIPv4McastAddrsExcludeAllHost) |
| |
| val addr = InetAddress.getByName("239.0.0.2") as Inet4Address |
| mcastAddrs.add(addr) |
| mcastAddrsExcludeAllHost.add(addr) |
| updateIPv4MulticastAddrs(apfFilter, mcastAddrs) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| assertEquals(mcastAddrs.toSet(), apfFilter.mIPv4MulticastAddresses) |
| assertEquals(mcastAddrsExcludeAllHost.toSet(), apfFilter.mIPv4McastAddrsExcludeAllHost) |
| |
| updateIPv4MulticastAddrs(apfFilter, mcastAddrs) |
| verify(apfController, never()).installPacketFilter(any(), any()) |
| } |
| |
| @Test |
| fun testApfFailOpenOnLimitedRAM() { |
| val apfConfig = getDefaultConfig() |
| apfConfig.apfRamSize = 512 |
| val apfFilter = getApfFilter(apfConfig) |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| assertContentEquals( |
| ByteArray(apfConfig.apfRamSize - ApfCounterTracker.Counter.totalSize()) { 0 }, |
| program |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testCreateEgressReportReaderSocket() { |
| var apfFilter = getApfFilter() |
| verify(dependencies, never()).createEgressIgmpReportsReaderSocket(anyInt()) |
| verify(dependencies, never()).createEgressMulticastReportsReaderSocket(anyInt()) |
| clearInvocations(dependencies) |
| |
| val apfConfig = getDefaultConfig() |
| apfConfig.handleMldOffload = true |
| apfFilter = getApfFilter(apfConfig) |
| |
| verify(dependencies, never()).createEgressIgmpReportsReaderSocket(anyInt()) |
| verify(dependencies, times(1)).createEgressMulticastReportsReaderSocket(anyInt()) |
| clearInvocations(dependencies) |
| |
| apfConfig.handleIgmpOffload = true |
| apfConfig.handleMldOffload = false |
| apfFilter = getApfFilter(apfConfig) |
| |
| verify(dependencies, never()).createEgressMulticastReportsReaderSocket(anyInt()) |
| verify(dependencies, times(1)).createEgressIgmpReportsReaderSocket(anyInt()) |
| clearInvocations(dependencies) |
| |
| apfConfig.handleIgmpOffload = true |
| apfConfig.handleMldOffload = true |
| apfFilter = getApfFilter(apfConfig) |
| verify(dependencies, never()).createEgressIgmpReportsReaderSocket(anyInt()) |
| verify(dependencies, times(1)).createEgressMulticastReportsReaderSocket(anyInt()) |
| } |
| |
| fun getProgramWithAllFeatureEnabled( |
| apfRamSize: Int = 8192, |
| apfVersion: Int = apfInterpreterVersion |
| ): Pair<ByteArray, Long> { |
| val localNsdManager = mock(NsdManager::class.java) |
| doReturn(localNsdManager).`when`(context).getSystemService(NsdManager::class.java) |
| val localRaWriterSocket = FileDescriptor() |
| val localRaReaderSocket = FileDescriptor() |
| Os.socketpair(AF_UNIX, SOCK_STREAM, 0, localRaWriterSocket, localRaReaderSocket) |
| doReturn(localRaReaderSocket).`when`(dependencies).createPacketReaderSocket(anyInt()) |
| var program = byteArrayOf(0) |
| var generationTime = 0L |
| val ipv4McastAddrs = listOf( |
| InetAddress.getByName("224.0.0.1") as Inet4Address, |
| InetAddress.getByName("224.0.0.251") as Inet4Address, |
| InetAddress.getByName("239.255.255.250") as Inet4Address |
| ) |
| doReturn(ipv4McastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any()) |
| val ipv6McastAddrs = listOf( |
| InetAddress.getByName("ff02::1:ff11:33e1") as Inet6Address, |
| InetAddress.getByName("ff02::1:ff11:33e2") as Inet6Address, |
| InetAddress.getByName("ff02::fb") as Inet6Address, |
| InetAddress.getByName("ff02::c") as Inet6Address, |
| InetAddress.getByName("ff05::c") as Inet6Address, |
| InetAddress.getByName("ff02::1") as Inet6Address, |
| InetAddress.getByName("ff01::1") as Inet6Address, |
| ) |
| // mock IPv6 multicast address from /proc/net/igmp6 |
| doReturn(ipv6McastAddrs).`when`(dependencies).getIPv6MulticastAddresses(any()) |
| tryTest { |
| val apfConfig = getDefaultConfig() |
| apfConfig.apfRamSize = apfRamSize |
| apfConfig.apfVersionSupported = apfVersion |
| apfConfig.multicastFilter = true |
| apfConfig.handleArpOffload = true |
| apfConfig.handleNdOffload = true |
| apfConfig.handleIgmpOffload = true |
| apfConfig.handleMldOffload = true |
| apfConfig.handleIpv4PingOffload = true |
| apfConfig.handleIpv6PingOffload = true |
| apfConfig.handleMdnsOffload = true |
| val apfFilter = getApfFilter(apfConfig) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| |
| val srcAddr = byteArrayOf(10, 0, 0, 5) |
| val dstAddr = byteArrayOf(10, 0, 0, 6) |
| val srcPort = 1024 |
| val dstPort = 4500 |
| val parcel = NattKeepalivePacketDataParcelable() |
| parcel.srcAddress = InetAddress.getByAddress(srcAddr).address |
| parcel.srcPort = srcPort |
| parcel.dstAddress = InetAddress.getByAddress(dstAddr).address |
| parcel.dstPort = dstPort |
| apfFilter.addNattKeepalivePacketFilter(1, parcel) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| |
| val captor = ArgumentCaptor.forClass(OffloadEngine::class.java) |
| verify(localNsdManager).registerOffloadEngine( |
| eq(ifParams.name), |
| anyLong(), |
| anyLong(), |
| any(), |
| captor.capture() |
| ) |
| val offloadEngine = captor.value |
| |
| val lp = LinkProperties() |
| val ipv4LinkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) |
| lp.addLinkAddress(ipv4LinkAddress) |
| val ipv6LinkAddress = LinkAddress(hostLinkLocalIpv6Address, 64) |
| lp.addLinkAddress(ipv6LinkAddress) |
| for (addr in hostIpv6Addresses) { |
| lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) |
| } |
| apfFilter.setLinkProperties(lp) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| |
| visibleOnHandlerThread(handler) { |
| offloadEngine.onOffloadServiceUpdated(castOffloadInfo.value) |
| offloadEngine.onOffloadServiceUpdated(tvRemoteOffloadInfo.value) |
| offloadEngine.onOffloadServiceUpdated(airplayOffloadInfo.value) |
| offloadEngine.onOffloadServiceUpdated(raopOffloadInfo.value) |
| } |
| |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 4) |
| |
| val ra1 = """ |
| 333300000001f434f06452fe86dd60010c0000503afffe800000000000001cb6b5bc353b7cfdff0 |
| 2000000000000000000000000000186000fab000000000000000000000000030440c00000070800 |
| 00070800000000fdeed0c47546534400000000000000001802400000000708fd0c8be643ee00001 |
| a018000000000000101f434f06452fe |
| """.replace("\\s+".toRegex(), "").trim() |
| val ra1Bytes = HexDump.hexStringToByteArray(ra1) |
| Os.write(localRaWriterSocket, ra1Bytes, 0, ra1Bytes.size) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| |
| // Using scapy to generate packet: |
| // eth = Ether(src="E8:9F:80:66:60:BC", dst="f2:9c:70:2c:39:5a") |
| // ip6 = IPv6(src="fe80::2", dst="ff02::1") |
| // icmpra = ICMPv6ND_RA(routerlifetime=360, retranstimer=360) |
| // pio1 = ICMPv6NDOptPrefixInfo(prefixlen=64, prefix="2002:db8::") |
| // rio = ICMPv6NDOptRouteInfo(prefix="2002:db8:cafe::") |
| // ra = eth/ip6/icmpra/pio1/rio |
| val ra2 = """ |
| f29c702c395ae89f806660bc86dd6000000000483afffe800000000000000000000000000002ff0 |
| 200000000000000000000000000018600f6e3000801680000000000000168030440c0ffffffffff |
| ffffff0000000020020db800000000000000000000000018030000ffffffff20020db8cafe00000 |
| 000000000000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| val ra2Bytes = HexDump.hexStringToByteArray(ra2) |
| val beforeNs = SystemClock.elapsedRealtimeNanos() |
| Os.write(localRaWriterSocket, ra2Bytes, 0, ra2Bytes.size) |
| program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| val afterNs = SystemClock.elapsedRealtimeNanos() |
| generationTime = (afterNs - beforeNs) / 1000000 |
| } cleanup { |
| IoUtils.closeQuietly(localRaWriterSocket) |
| } |
| return Pair(program, generationTime) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testAllOffloadFeatureEnabled() { |
| val (program, generationTimeMs) = getProgramWithAllFeatureEnabled() |
| val programSize = program.size |
| val counterSize = ApfCounterTracker.Counter.totalSize() |
| val totalSize = programSize + counterSize |
| Log.i( |
| TAG, |
| "all feature on, program size: $programSize, counter size: $counterSize," + |
| " total size:$totalSize, program:" |
| ) |
| val programChunk = program.toList().chunked(2000) |
| programChunk.forEach { |
| Log.i(TAG, HexDump.toHexString(it.toByteArray())) |
| } |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testAllOffloadFeatureEnabledPerformanceEstimation() { |
| val (program, generationTimeMs) = getProgramWithAllFeatureEnabled() |
| // Ignore the first iteration as it may take longer time for JVM warm up |
| if (apfInterpreterVersion == ApfJniUtils.APF_INTERPRETER_VERSION_NEXT) { |
| Log.i( |
| TAG, |
| "all offload on: program size ${program.size}, " + |
| "generation time: $generationTimeMs ms" |
| ) |
| } |
| } |
| |
| fun getProgramWithAllFeatureOff( |
| apfRamSize: Int = 8192, |
| apfVersion: Int = apfInterpreterVersion |
| ): Pair<ByteArray, Long> { |
| val localRaWriterSocket = FileDescriptor() |
| val localRaReaderSocket = FileDescriptor() |
| Os.socketpair(AF_UNIX, SOCK_STREAM, 0, localRaWriterSocket, localRaReaderSocket) |
| doReturn(localRaReaderSocket).`when`(dependencies).createPacketReaderSocket(anyInt()) |
| var program = byteArrayOf(0) |
| var generationTime = 0L |
| tryTest { |
| val ipv4McastAddrs = listOf( |
| InetAddress.getByName("224.0.0.1") as Inet4Address, |
| InetAddress.getByName("224.0.0.251") as Inet4Address, |
| InetAddress.getByName("239.255.255.250") as Inet4Address |
| ) |
| doReturn(ipv4McastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any()) |
| val ipv6McastAddrs = listOf( |
| InetAddress.getByName("ff02::1:ff11:33e1") as Inet6Address, |
| InetAddress.getByName("ff02::1:ff11:33e2") as Inet6Address, |
| InetAddress.getByName("ff02::fb") as Inet6Address, |
| InetAddress.getByName("ff02::c") as Inet6Address, |
| InetAddress.getByName("ff05::c") as Inet6Address, |
| InetAddress.getByName("ff02::1") as Inet6Address, |
| InetAddress.getByName("ff01::1") as Inet6Address, |
| ) |
| // mock IPv6 multicast address from /proc/net/igmp6 |
| doReturn(ipv6McastAddrs).`when`(dependencies).getIPv6MulticastAddresses(any()) |
| val apfConfig = getDefaultConfig() |
| apfConfig.apfRamSize = apfRamSize |
| apfConfig.apfVersionSupported = apfVersion |
| apfConfig.multicastFilter = true |
| apfConfig.handleArpOffload = false |
| apfConfig.handleNdOffload = false |
| apfConfig.handleIgmpOffload = false |
| apfConfig.handleMldOffload = false |
| apfConfig.handleIpv4PingOffload = false |
| apfConfig.handleIpv6PingOffload = false |
| apfConfig.handleMdnsOffload = false |
| val apfFilter = getApfFilter(apfConfig) |
| if (apfVersion > 2) { |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| } else { |
| // If the APF version is less than 3, only one program will be installed because |
| // APFv2 lacks counter support, and therefore, counter region cleanup is unnecessary |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| } |
| |
| val lp = LinkProperties() |
| val ipv4LinkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) |
| lp.addLinkAddress(ipv4LinkAddress) |
| val ipv6LinkAddress = LinkAddress(hostLinkLocalIpv6Address, 64) |
| lp.addLinkAddress(ipv6LinkAddress) |
| for (addr in hostIpv6Addresses) { |
| lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) |
| } |
| apfFilter.setLinkProperties(lp) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| |
| val ra1 = """ |
| 333300000001f434f06452fe86dd60010c0000503afffe800000000000001cb6b5bc353b7cfdff0 |
| 2000000000000000000000000000186000fab000000000000000000000000030440c00000070800 |
| 00070800000000fdeed0c47546534400000000000000001802400000000708fd0c8be643ee00001 |
| a018000000000000101f434f06452fe |
| """.replace("\\s+".toRegex(), "").trim() |
| val ra1Bytes = HexDump.hexStringToByteArray(ra1) |
| Os.write(localRaWriterSocket, ra1Bytes, 0, ra1Bytes.size) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| |
| // Using scapy to generate packet: |
| // eth = Ether(src="E8:9F:80:66:60:BC", dst="f2:9c:70:2c:39:5a") |
| // ip6 = IPv6(src="fe80::2", dst="ff02::1") |
| // icmpra = ICMPv6ND_RA(routerlifetime=360, retranstimer=360) |
| // pio1 = ICMPv6NDOptPrefixInfo(prefixlen=64, prefix="2002:db8::") |
| // rio = ICMPv6NDOptRouteInfo(prefix="2002:db8:cafe::") |
| // ra = eth/ip6/icmpra/pio1/rio |
| val ra2 = """ |
| f29c702c395ae89f806660bc86dd6000000000483afffe800000000000000000000000000002ff0 |
| 200000000000000000000000000018600f6e3000801680000000000000168030440c0ffffffffff |
| ffffff0000000020020db800000000000000000000000018030000ffffffff20020db8cafe00000 |
| 000000000000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| val ra2Bytes = HexDump.hexStringToByteArray(ra2) |
| val beforeNs = SystemClock.elapsedRealtimeNanos() |
| Os.write(localRaWriterSocket, ra2Bytes, 0, ra2Bytes.size) |
| program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| val afterNs = SystemClock.elapsedRealtimeNanos() |
| generationTime = (afterNs - beforeNs) / 1000000 |
| } cleanup { |
| IoUtils.closeQuietly(localRaWriterSocket) |
| } |
| return Pair(program, generationTime) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testAllOffloadFeatureDisabledPerformanceEstimation() { |
| val (program, generationTimeMs) = getProgramWithAllFeatureOff() |
| // Ignore the first iteration as it may take longer time for JVM warm up |
| if (apfInterpreterVersion == ApfJniUtils.APF_INTERPRETER_VERSION_NEXT) { |
| Log.i( |
| TAG, |
| "all offload off: program size ${program.size}, " + |
| "generation time: $generationTimeMs ms" |
| ) |
| } |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testAPFv2GenerateValidProgram() { |
| assumeTrue(apfInterpreterVersion == ApfJniUtils.APF_INTERPRETER_VERSION_NEXT) |
| var apfRamSize = 600 |
| val maxApfRamSize = 2048 |
| |
| while (apfRamSize <= maxApfRamSize) { |
| val (program, _) = getProgramWithAllFeatureOff( |
| apfRamSize = apfRamSize, |
| apfVersion = 2 |
| ) |
| assertThat(program.size).isLessThan(apfRamSize + 1) |
| assertThat(program).isNotEqualTo(ByteArray(apfRamSize) { 0 }) |
| // TODO: reduce after fixing 'Failed to receive adb shell test output within 66000 ms' |
| val step = Random.nextInt(1, 64) |
| apfRamSize += step |
| } |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testAPFv4GenerateValidProgram() { |
| assumeTrue(apfInterpreterVersion == ApfJniUtils.APF_INTERPRETER_VERSION_NEXT) |
| var apfRamSize = 1024 |
| val maxApfRamSize = 4096 |
| |
| while (apfRamSize <= maxApfRamSize) { |
| val (program, _) = getProgramWithAllFeatureOff( |
| apfRamSize = apfRamSize, |
| apfVersion = 4 |
| ) |
| val availableRam = apfRamSize - ApfCounterTracker.Counter.totalSize() |
| assertThat(program.size).isLessThan(availableRam + 1) |
| assertThat(program).isNotEqualTo(ByteArray(availableRam) { 0 }) |
| // TODO: reduce after fixing 'Failed to receive adb shell test output within 66000 ms' |
| val step = Random.nextInt(1, 64) |
| apfRamSize += step |
| } |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testAPFv6GenerateValidProgram() { |
| var apfRamSize = 3000 |
| val maxApfRamSize = 6000 |
| |
| while (apfRamSize <= maxApfRamSize) { |
| val (program, _) = getProgramWithAllFeatureEnabled( |
| apfRamSize = apfRamSize, |
| apfVersion = apfInterpreterVersion |
| ) |
| val availableRam = apfRamSize - ApfCounterTracker.Counter.totalSize() |
| assertThat(program.size).isLessThan(availableRam + 1) |
| assertThat(program).isNotEqualTo(ByteArray(availableRam) { 0 }) |
| // TODO: reduce after fixing 'Failed to receive adb shell test output within 66000 ms' |
| val step = Random.nextInt(1, 64) |
| apfRamSize += step |
| } |
| } |
| |
| private fun getProgramForRaSizeEstimation( |
| apfRamSize: Int, |
| ): Pair<Int, ByteArray> { |
| val localRaWriterSocket = FileDescriptor() |
| val localRaReaderSocket = FileDescriptor() |
| Os.socketpair(AF_UNIX, SOCK_STREAM, 0, localRaWriterSocket, localRaReaderSocket) |
| doReturn(localRaReaderSocket).`when`(dependencies).createPacketReaderSocket(anyInt()) |
| var overEstimatedProgramSize = 0 |
| var program = byteArrayOf(0) |
| tryTest { |
| val apfConfig = getDefaultConfig() |
| apfConfig.apfRamSize = apfRamSize |
| val apfFilter = getApfFilter(apfConfig) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| |
| val lp = LinkProperties() |
| val ipv4LinkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24) |
| lp.addLinkAddress(ipv4LinkAddress) |
| val ipv6LinkAddress = LinkAddress(hostLinkLocalIpv6Address, 64) |
| lp.addLinkAddress(ipv6LinkAddress) |
| for (addr in hostIpv6Addresses) { |
| lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64)) |
| } |
| apfFilter.setLinkProperties(lp) |
| program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| |
| val ra1 = """ |
| 333300000001f434f06452fe86dd60010c0000503afffe800000000000001cb6b5bc353b7cfdff0 |
| 2000000000000000000000000000186000fab000000000000000000000000030440c00000070800 |
| 00070800000000fdeed0c47546534400000000000000001802400000000708fd0c8be643ee00001 |
| a018000000000000101f434f06452fe |
| """.replace("\\s+".toRegex(), "").trim() |
| val ra1Bytes = HexDump.hexStringToByteArray(ra1) |
| Os.write(localRaWriterSocket, ra1Bytes, 0, ra1Bytes.size) |
| apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| |
| // Using scapy to generate packet: |
| // eth = Ether(src="E8:9F:80:66:60:BC", dst="f2:9c:70:2c:39:5a") |
| // ip6 = IPv6(src="fe80::2", dst="ff02::1") |
| // icmpra = ICMPv6ND_RA(routerlifetime=360, retranstimer=360) |
| // pio1 = ICMPv6NDOptPrefixInfo(prefixlen=64, prefix="2002:db8::") |
| // rio = ICMPv6NDOptRouteInfo(prefix="2002:db8:cafe::") |
| // ra = eth/ip6/icmpra/pio1/rio |
| val ra2 = """ |
| f29c702c395ae89f806660bc86dd6000000000483afffe800000000000000000000000000002ff0 |
| 200000000000000000000000000018600f6e3000801680000000000000168030440c0ffffffffff |
| ffffff0000000020020db800000000000000000000000018030000ffffffff20020db8cafe00000 |
| 000000000000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| val ra2Bytes = HexDump.hexStringToByteArray(ra2) |
| Os.write(localRaWriterSocket, ra2Bytes, 0, ra2Bytes.size) |
| program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 1) |
| overEstimatedProgramSize = apfFilter.overEstimatedProgramSize |
| } cleanup { |
| IoUtils.closeQuietly(localRaWriterSocket) |
| } |
| return Pair(overEstimatedProgramSize, program) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testRaFilterSizeEstimation() { |
| val (overEstimatedProgramSize, _) = getProgramForRaSizeEstimation(apfRamSize = 8096) |
| val apfRam = overEstimatedProgramSize - 1 |
| val (_, program) = getProgramForRaSizeEstimation(apfRamSize = apfRam) |
| |
| val ra1 = """ |
| 333300000001f434f06452fe86dd60010c0000503afffe800000000000001cb6b5bc353b7cfdff0 |
| 2000000000000000000000000000186000fab000000000000000000000000030440c00000070800 |
| 00070800000000fdeed0c47546534400000000000000001802400000000708fd0c8be643ee00001 |
| a018000000000000101f434f06452fe |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| val ra2 = """ |
| f29c702c395ae89f806660bc86dd6000000000483afffe800000000000000000000000000002ff0 |
| 200000000000000000000000000018600f6e3000801680000000000000168030440c0ffffffffff |
| ffffff0000000020020db800000000000000000000000018030000ffffffff20020db8cafe00000 |
| 000000000000000 |
| """.replace("\\s+".toRegex(), "").trim() |
| |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| program, |
| HexDump.hexStringToByteArray(ra2), |
| DROPPED_RA |
| ) |
| apfTestHelpers.verifyProgramRun( |
| apfInterpreterVersion, |
| program, |
| HexDump.hexStringToByteArray(ra1), |
| PASSED_IPV6_ICMP |
| ) |
| } |
| |
| @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) |
| @Test |
| fun testFilteringNonUnicastTDLSPacket() { |
| val apfConfig = getDefaultConfig() |
| apfConfig.apfRamSize = 1500 |
| val apfFilter = getApfFilter(apfConfig) |
| val program = apfTestHelpers.consumeInstalledProgram(apfController, installCnt = 2) |
| // Using scapy to generate packet: |
| // pkt = Ether(dst="ff:ff:ff:ff:ff:ff", type=0x890D)/Raw(load="01") |
| val bcastTDLSPkt = "ffffffffffff000000000000890d3031" |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(bcastTDLSPkt), |
| DROPPED_NON_UNICAST_TDLS |
| ) |
| |
| // Using scapy to generate packet: |
| // pkt = Ether(dst="02:03:04:05:06:07", type=0x890D)/Raw(load="01") |
| val ucastTDLSPkt = "020304050607000000000000890d3031" |
| apfTestHelpers.verifyProgramRun( |
| apfFilter.mApfVersionSupported, |
| program, |
| HexDump.hexStringToByteArray(ucastTDLSPkt), |
| PASSED_NON_IP_UNICAST |
| ) |
| } |
| } |