Merge "Use ArraySet instead of HashSet in IpReachabilityMonitor." into main
diff --git a/res/values/config.xml b/res/values/config.xml
index aed375c..16364d6 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -108,12 +108,14 @@
<!-- Whether the APF Filter in the device should filter out IEEE 802.3 Frames
Those frames are identified by the field Eth-type having values
- less than 0x600 -->
+ less than 0x600.
+ This configuration has been deprecated and is not functional in Android V+. -->
<bool name="config_apfDrop802_3Frames">true</bool>
<!-- An array of Denylisted EtherType, packets with EtherTypes within this array
- will be dropped
- TODO: are these proper values? -->
+ will be dropped.
+ TODO: are these proper values?
+ This configuration has been deprecated and is not functional in Android V+. -->
<integer-array name="config_apfEthTypeDenyList">
<item>0x88A2</item>
<item>0x88A4</item>
diff --git a/res/values/overlayable.xml b/res/values/overlayable.xml
index 9c2172a..08a2778 100644
--- a/res/values/overlayable.xml
+++ b/res/values/overlayable.xml
@@ -79,10 +79,12 @@
<item type="integer" name="config_evaluating_bandwidth_max_retry_timer_ms"/>
<!-- Whether the APF Filter in the device should filter out IEEE 802.3 Frames
- Those frames are identified by the field Eth-type having values less than 0x600 -->
+ Those frames are identified by the field Eth-type having values less than 0x600.
+ This configuration has been deprecated and is not functional in Android V+ -->
<item type="bool" name="config_apfDrop802_3Frames"/>
<!-- An array of Denylisted EtherType, packets with EtherTypes within this array
- will be dropped -->
+ will be dropped.
+ This configuration has been deprecated and is not functional in Android V+ -->
<item type="array" name="config_apfEthTypeDenyList"/>
<!-- Icon used for connected captive portal notifications on wifi networks,
notifying that internet access is now available or that a venue info page is
diff --git a/src/android/net/apf/AndroidPacketFilter.java b/src/android/net/apf/AndroidPacketFilter.java
index 1a34ec6..2a7165b 100644
--- a/src/android/net/apf/AndroidPacketFilter.java
+++ b/src/android/net/apf/AndroidPacketFilter.java
@@ -15,6 +15,7 @@
*/
package android.net.apf;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.LinkProperties;
import android.net.NattKeepalivePacketDataParcelable;
@@ -98,4 +99,12 @@
/** Return hex string of current APF snapshot for testing purposes. */
@Nullable String getDataSnapshotHexString();
+
+ /**
+ * Determines whether the APF interpreter advertises support for the data buffer access
+ * opcodes LDDW (LoaD Data Word) and STDW (STore Data Word).
+ */
+ default boolean hasDataAccess(@NonNull ApfCapabilities capabilities) {
+ return capabilities.apfVersionSupported > 2;
+ }
}
diff --git a/src/android/net/apf/ApfConstants.java b/src/android/net/apf/ApfConstants.java
index a4b38a3..fe2cfd8 100644
--- a/src/android/net/apf/ApfConstants.java
+++ b/src/android/net/apf/ApfConstants.java
@@ -53,13 +53,18 @@
// The IPv6 all nodes address ff02::1
public static final byte[] IPV6_ALL_NODES_ADDRESS =
{ (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
+ // The IPv6 unspecified address ::
+ public static final byte[] IPV6_UNSPECIFIED_ADDRESS =
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
// The IPv6 solicited nodes multicast address prefix ff02::1:ffXX:X/104
public static final byte[] IPV6_SOLICITED_NODES_PREFIX =
{ (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, (byte) 0xff};
public static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
public static final int ICMP6_CODE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN + 1;
+ public static final int ICMP6_CHECKSUM_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN + 2;
public static final int ICMP6_NS_TARGET_IP_OFFSET = ICMP6_TYPE_OFFSET + 8;
+ public static final int ICMP6_NS_OPTION_TYPE_OFFSET = ICMP6_NS_TARGET_IP_OFFSET + 16;
public static final int IPPROTO_HOPOPTS = 0;
@@ -90,6 +95,11 @@
// TODO: Select a proper max length
public static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20;
+ // The ethernet solicited nodes multicast address prefix 33:33:FF:xx:xx:xx
+ public static final byte[] ETH_SOLICITED_NODES_PREFIX =
+ {(byte) 0x33, (byte) 0x33, (byte) 0xff};
+ public static final byte[] ETH_MULTICAST_IPV6_ALL_NODES_MAC_ADDRESS =
+ { (byte) 0x33, (byte) 0x33, 0, 0, 0, 1};
public static final byte[] ETH_MULTICAST_MDNS_V4_MAC_ADDRESS =
{(byte) 0x01, (byte) 0x00, (byte) 0x5e, (byte) 0x00, (byte) 0x00, (byte) 0xfb};
public static final byte[] ETH_MULTICAST_MDNS_V6_MAC_ADDRESS =
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java
index 8f221af..c705928 100644
--- a/src/android/net/apf/ApfFilter.java
+++ b/src/android/net/apf/ApfFilter.java
@@ -77,6 +77,7 @@
import static android.system.OsConstants.ETH_P_ARP;
import static android.system.OsConstants.ETH_P_IP;
import static android.system.OsConstants.ETH_P_IPV6;
+import static android.system.OsConstants.IFA_F_TENTATIVE;
import static android.system.OsConstants.IPPROTO_ICMPV6;
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
@@ -116,6 +117,7 @@
import android.text.format.DateUtils;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -336,9 +338,13 @@
@GuardedBy("this")
private int mIPv4PrefixLength;
- // Our IPv6 addresses
+ // Our IPv6 non-tentative addresses
@GuardedBy("this")
- private Set<Inet6Address> mIPv6Addresses = new ArraySet<>();
+ private Set<Inet6Address> mIPv6NonTentativeAddresses = new ArraySet<>();
+
+ // Our tentative IPv6 addresses
+ @GuardedBy("this")
+ private Set<Inet6Address> mIPv6TentativeAddresses = new ArraySet<>();
// Whether CLAT is enabled.
@GuardedBy("this")
@@ -495,6 +501,17 @@
return addresses;
}
+
+ /**
+ * Loads the existing ND traffic class for the specific interface from the file
+ * /proc/sys/net/ipv6/conf/{ifname}/ndisc_tclass.
+ *
+ * If the file does not exist or the interface is not found,
+ * the function returns 0..255, 0 as default ND traffic class.
+ */
+ public int getNdTrafficClass(@NonNull String ifname) {
+ return ProcfsParsingUtils.getNdTrafficClass(ifname);
+ }
}
public synchronized void setDataSnapshot(byte[] data) {
@@ -554,7 +571,7 @@
// Clear the APF memory to reset all counters upon connecting to the first AP
// in an SSID. This is limited to APFv4 devices because this large write triggers
// a crash on some older devices (b/78905546).
- if (mIsRunning && mApfCapabilities.hasDataAccess()) {
+ if (mIsRunning && hasDataAccess(mApfCapabilities)) {
byte[] zeroes = new byte[mApfCapabilities.maximumApfProgramSize];
if (!mIpClientCallback.installPacketFilter(zeroes)) {
sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE);
@@ -1782,13 +1799,24 @@
}
@GuardedBy("this")
- private List<byte[]> getUnicastIpv6Addresses() {
+ private List<byte[]> getIpv6Addresses(
+ boolean includeNonTentative, boolean includeTentative, boolean includeAnycast) {
final List<byte[]> addresses = new ArrayList<>();
- for (Inet6Address addr : mIPv6Addresses) {
- addresses.add(addr.getAddress());
+ if (includeNonTentative) {
+ for (Inet6Address addr : mIPv6NonTentativeAddresses) {
+ addresses.add(addr.getAddress());
+ }
}
- addresses.addAll(mDependencies.getAnycast6Addresses(mInterfaceParams.name));
+ if (includeTentative) {
+ for (Inet6Address addr : mIPv6TentativeAddresses) {
+ addresses.add(addr.getAddress());
+ }
+ }
+
+ if (includeAnycast) {
+ addresses.addAll(mDependencies.getAnycast6Addresses(mInterfaceParams.name));
+ }
return addresses;
}
@@ -1804,7 +1832,10 @@
@GuardedBy("this")
private void generateNsFilterLocked(ApfV6Generator v6Gen)
throws IllegalInstructionException {
- final List<byte[]> allIPv6Addrs = getUnicastIpv6Addresses();
+ final List<byte[]> allIPv6Addrs = getIpv6Addresses(
+ true /* includeNonTentative */,
+ true /* includeTentative */,
+ true /* includeAnycast */);
if (allIPv6Addrs.isEmpty()) {
// There is no IPv6 link local address.
v6Gen.addCountAndDrop(DROPPED_IPV6_NS_NO_ADDRESS);
@@ -1857,6 +1888,8 @@
// (8 bytes ICMP6 header + 16 bytes target address + 8 bytes option) -> pass
v6Gen.addLoad16(R0, IPV6_PAYLOAD_LEN_OFFSET)
.addCountAndPassIfR0GreaterThan(32, PASSED_IPV6_NS_MULTIPLE_OPTIONS);
+
+ v6Gen.addCountAndPass(Counter.PASSED_IPV6_ICMP);
}
/**
@@ -2119,7 +2152,7 @@
gen = new ApfV4Generator(mApfCapabilities.apfVersionSupported);
}
- if (mApfCapabilities.hasDataAccess()) {
+ if (hasDataAccess(mApfCapabilities)) {
if (gen instanceof ApfV4Generator) {
// Increment TOTAL_PACKETS.
// Only needed in APFv4.
@@ -2241,7 +2274,7 @@
final byte[] program;
int programMinLft = Integer.MAX_VALUE;
int maximumApfProgramSize = mApfCapabilities.maximumApfProgramSize;
- if (mApfCapabilities.hasDataAccess()) {
+ if (hasDataAccess(mApfCapabilities)) {
// Reserve space for the counters.
maximumApfProgramSize -= Counter.totalSize();
}
@@ -2509,19 +2542,28 @@
return ipv4Address;
}
- /** Retrieve the IPv6 LinkAddress list, otherwise return empty list. */
- private static Set<Inet6Address> retrieveIPv6LinkAddress(LinkProperties lp) {
- final Set<Inet6Address> ipv6Addresses = new ArraySet<>();
-
+ /** Retrieve the pair of IPv6 Inet6Address set, otherwise return pair with two empty set.
+ * The first element is a set containing tentative IPv6 addresses,
+ * the second element is a set containing non-tentative IPv6 addresses
+ * */
+ private static Pair<Set<Inet6Address>, Set<Inet6Address>>
+ retrieveIPv6LinkAddress(LinkProperties lp) {
+ final Set<Inet6Address> tentativeAddrs = new ArraySet<>();
+ final Set<Inet6Address> nonTentativeAddrs = new ArraySet<>();
for (LinkAddress address : lp.getLinkAddresses()) {
if (!(address.getAddress() instanceof Inet6Address)) {
continue;
}
- ipv6Addresses.add((Inet6Address) address.getAddress());
+ if ((address.getFlags() & IFA_F_TENTATIVE) == IFA_F_TENTATIVE) {
+ tentativeAddrs.add((Inet6Address) address.getAddress());
+ } else {
+ nonTentativeAddrs.add((Inet6Address) address.getAddress());
+ }
}
- return ipv6Addresses;
+
+ return new Pair<>(tentativeAddrs, nonTentativeAddrs);
}
public synchronized void setLinkProperties(LinkProperties lp) {
@@ -2529,17 +2571,20 @@
final LinkAddress ipv4Address = retrieveIPv4LinkAddress(lp);
final byte[] addr = (ipv4Address != null) ? ipv4Address.getAddress().getAddress() : null;
final int prefix = (ipv4Address != null) ? ipv4Address.getPrefixLength() : 0;
- final Set<Inet6Address> ipv6Addresses = retrieveIPv6LinkAddress(lp);
+ final Pair<Set<Inet6Address>, Set<Inet6Address>>
+ ipv6Addresses = retrieveIPv6LinkAddress(lp);
if ((prefix == mIPv4PrefixLength)
&& Arrays.equals(addr, mIPv4Address)
- && ipv6Addresses.equals(mIPv6Addresses)
+ && ipv6Addresses.first.equals(mIPv6TentativeAddresses)
+ && ipv6Addresses.second.equals(mIPv6NonTentativeAddresses)
) {
return;
}
mIPv4Address = addr;
mIPv4PrefixLength = prefix;
- mIPv6Addresses = ipv6Addresses;
+ mIPv6TentativeAddresses = ipv6Addresses.first;
+ mIPv6NonTentativeAddresses = ipv6Addresses.second;
installNewProgramLocked();
}
@@ -2618,7 +2663,7 @@
pw.println("IPv4 address: " + InetAddress.getByAddress(mIPv4Address).getHostAddress());
pw.println("IPv6 addresses: ");
pw.increaseIndent();
- for (Inet6Address addr: mIPv6Addresses) {
+ for (Inet6Address addr: mIPv6NonTentativeAddresses) {
pw.println(addr.getHostAddress());
}
pw.decreaseIndent();
@@ -2691,7 +2736,7 @@
pw.println("APF packet counters: ");
pw.increaseIndent();
- if (!mApfCapabilities.hasDataAccess()) {
+ if (!hasDataAccess(mApfCapabilities)) {
pw.println("APF counters not supported");
} else if (mDataSnapshot == null) {
pw.println("No last snapshot.");
diff --git a/src/android/net/apf/LegacyApfFilter.java b/src/android/net/apf/LegacyApfFilter.java
index caa3b25..99aab80 100644
--- a/src/android/net/apf/LegacyApfFilter.java
+++ b/src/android/net/apf/LegacyApfFilter.java
@@ -126,7 +126,7 @@
* When APFv4 is supported, loads R1 with the offset of the specified counter.
*/
private void maybeSetupCounter(ApfV4Generator gen, Counter c) {
- if (mApfCapabilities.hasDataAccess()) {
+ if (hasDataAccess(mApfCapabilities)) {
gen.addLoadImmediate(R1, c.offset());
}
}
@@ -408,7 +408,7 @@
mSessionStartMs = mClock.elapsedRealtime();
mMinMetricsSessionDurationMs = config.minMetricsSessionDurationMs;
- if (mApfCapabilities.hasDataAccess()) {
+ if (hasDataAccess(mApfCapabilities)) {
mCountAndPassLabel = "countAndPass";
mCountAndDropLabel = "countAndDrop";
} else {
@@ -492,7 +492,7 @@
// Clear the APF memory to reset all counters upon connecting to the first AP
// in an SSID. This is limited to APFv4 devices because this large write triggers
// a crash on some older devices (b/78905546).
- if (mIsRunning && mApfCapabilities.hasDataAccess()) {
+ if (mIsRunning && hasDataAccess(mApfCapabilities)) {
byte[] zeroes = new byte[mApfCapabilities.maximumApfProgramSize];
if (!mIpClientCallback.installPacketFilter(zeroes)) {
sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE);
@@ -1728,7 +1728,7 @@
// This is guaranteed to succeed because of the check in maybeCreate.
ApfV4Generator gen = new ApfV4Generator(mApfCapabilities.apfVersionSupported);
- if (mApfCapabilities.hasDataAccess()) {
+ if (hasDataAccess(mApfCapabilities)) {
// Increment TOTAL_PACKETS
maybeSetupCounter(gen, Counter.TOTAL_PACKETS);
gen.addLoadData(R0, 0); // load counter
@@ -1831,7 +1831,7 @@
private void emitEpilogue(ApfV4Generator gen) throws IllegalInstructionException {
// If APFv4 is unsupported, no epilogue is necessary: if execution reached this far, it
// will just fall-through to the PASS label.
- if (!mApfCapabilities.hasDataAccess()) return;
+ if (!hasDataAccess(mApfCapabilities)) return;
// Execution will reach the bottom of the program if none of the filters match,
// which will pass the packet to the application processor.
@@ -1867,7 +1867,7 @@
final byte[] program;
long programMinLifetime = Long.MAX_VALUE;
long maximumApfProgramSize = mApfCapabilities.maximumApfProgramSize;
- if (mApfCapabilities.hasDataAccess()) {
+ if (hasDataAccess(mApfCapabilities)) {
// Reserve space for the counters.
maximumApfProgramSize -= Counter.totalSize();
}
@@ -2323,7 +2323,7 @@
pw.println("APF packet counters: ");
pw.increaseIndent();
- if (!mApfCapabilities.hasDataAccess()) {
+ if (!hasDataAccess(mApfCapabilities)) {
pw.println("APF counters not supported");
} else if (mDataSnapshot == null) {
pw.println("No last snapshot.");
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index a69505c..304b038 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -1355,15 +1355,13 @@
// Thread-unsafe access to mApfFilter but just used for debugging.
final AndroidPacketFilter apfFilter = mApfFilter;
final android.net.shared.ProvisioningConfiguration provisioningConfig = mConfiguration;
- final ApfCapabilities apfCapabilities = (provisioningConfig != null)
- ? provisioningConfig.mApfCapabilities : null;
+ final ApfCapabilities apfCapabilities = mCurrentApfCapabilities;
IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
pw.println(mTag + " APF dump:");
pw.increaseIndent();
- if (apfFilter != null && apfCapabilities != null
- && apfCapabilities.apfVersionSupported > 0) {
- if (apfCapabilities.hasDataAccess()) {
+ if (apfFilter != null) {
+ if (apfCapabilities != null && apfFilter.hasDataAccess(apfCapabilities)) {
// Request a new snapshot, then wait for it.
mApfDataSnapshotComplete.close();
mCallback.startReadPacketFilter("dumpsys");
@@ -2525,11 +2523,19 @@
}
@Nullable
- private AndroidPacketFilter maybeCreateApfFilter(final ApfCapabilities apfCapabilities) {
+ private AndroidPacketFilter maybeCreateApfFilter(final ApfCapabilities apfCaps) {
ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration();
- apfConfig.apfCapabilities = apfCapabilities;
- if (apfCapabilities != null && !SdkLevel.isAtLeastV()
- && apfCapabilities.apfVersionSupported <= 4) {
+ apfConfig.apfCapabilities = apfCaps;
+ if (apfCaps != null && !SdkLevel.isAtLeastS()) {
+ // Due to potential OEM modifications in Android R, reconfigure
+ // apfVersionSupported using apfCapabilities.hasDataAccess() to ensure safe data
+ // region access within ApfFilter.
+ int apfVersionSupported = apfCaps.hasDataAccess() ? 3 : 2;
+ apfConfig.apfCapabilities = new ApfCapabilities(apfVersionSupported,
+ apfCaps.maximumApfProgramSize, apfCaps.apfPacketFormat);
+ }
+ if (apfConfig.apfCapabilities != null && !SdkLevel.isAtLeastV()
+ && apfConfig.apfCapabilities.apfVersionSupported <= 4) {
apfConfig.installableProgramSizeClamp = 1024;
}
apfConfig.multicastFilter = mMulticastFiltering;
diff --git a/tests/unit/src/android/net/apf/ApfNewTest.kt b/tests/unit/src/android/net/apf/ApfNewTest.kt
index 73c9d79..d271b2e 100644
--- a/tests/unit/src/android/net/apf/ApfNewTest.kt
+++ b/tests/unit/src/android/net/apf/ApfNewTest.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.net.LinkAddress
import android.net.LinkProperties
+import android.net.MacAddress
import android.net.apf.ApfCounterTracker.Counter
import android.net.apf.ApfCounterTracker.Counter.APF_PROGRAM_ID
import android.net.apf.ApfCounterTracker.Counter.APF_VERSION
@@ -41,7 +42,6 @@
import android.net.apf.ApfTestUtils.DROP
import android.net.apf.ApfTestUtils.MIN_PKT_SIZE
import android.net.apf.ApfTestUtils.PASS
-import android.net.apf.ApfTestUtils.TestApfFilter
import android.net.apf.ApfTestUtils.assertDrop
import android.net.apf.ApfTestUtils.assertPass
import android.net.apf.ApfTestUtils.assertVerdict
@@ -54,10 +54,13 @@
import android.net.apf.BaseApfGenerator.PASS_LABEL
import android.net.apf.BaseApfGenerator.Register.R0
import android.net.apf.BaseApfGenerator.Register.R1
+import android.net.ip.IpClient.IpClientCallbacksWrapper
import android.os.Build
import android.system.OsConstants.ARPHRD_ETHER
+import android.system.OsConstants.IFA_F_TENTATIVE
import androidx.test.filters.SmallTest
import com.android.net.module.util.HexDump
+import com.android.net.module.util.InterfaceParams
import com.android.net.module.util.NetworkStackConstants.ARP_ETHER_IPV4_LEN
import com.android.net.module.util.NetworkStackConstants.ARP_REPLY
import com.android.net.module.util.NetworkStackConstants.ARP_REQUEST
@@ -80,9 +83,12 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@@ -97,22 +103,29 @@
@SmallTest
class ApfNewTest {
- @get:Rule
- val ignoreRule = DevSdkIgnoreRule()
+ @get:Rule val ignoreRule = DevSdkIgnoreRule()
- @Mock
- private lateinit var context: Context
+ @Mock private lateinit var context: Context
- @Mock
- private lateinit var metrics: NetworkQuirkMetrics
+ @Mock private lateinit var metrics: NetworkQuirkMetrics
- @Mock
- private lateinit var dependencies: Dependencies
+ @Mock private lateinit var dependencies: Dependencies
+
+ @Mock private lateinit var ipClientCallback: IpClientCallbacksWrapper
private val defaultMaximumApfProgramSize = 2048
- private val testPacket = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8,
- 9, 10, 11, 12, 13, 14, 15, 16)
+ private val loInterfaceParams = InterfaceParams.getByName("lo")
+
+ private val ifParams =
+ InterfaceParams(
+ "lo",
+ loInterfaceParams.index,
+ MacAddress.fromBytes(byteArrayOf(2, 3, 4, 5, 6, 7)),
+ loInterfaceParams.defaultMtu
+ )
+
+ private val testPacket = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
private val hostIpv4Address = byteArrayOf(10, 0, 0, 1)
private val senderIpv4Address = byteArrayOf(10, 0, 0, 2)
private val arpBroadcastMacAddress = intArrayOf(0xff, 0xff, 0xff, 0xff, 0xff, 0xff)
@@ -127,6 +140,14 @@
intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x01, 0, 0, 0x1b, 0x44, 0x55, 0x66, 0x77)
.map{ it.toByte() }.toByteArray()
)
+ private val hostIpv6TentativeAddresses = listOf(
+ // 2001::200:1a:1234:5678
+ intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x02, 0, 0, 0x1a, 0x12, 0x34, 0x56, 0x78)
+ .map{ it.toByte() }.toByteArray(),
+ // 2001::100:1b:1234:5678
+ intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x01, 0, 0, 0x1b, 0x12, 0x34, 0x56, 0x78)
+ .map{ it.toByte() }.toByteArray()
+ )
private val hostAnycast6Addresses = listOf(
// 2001::100:1b:aabb:ccdd
intArrayOf(0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x01, 0, 0, 0x1b, 0xaa, 0xbb, 0xcc, 0xdd)
@@ -1284,16 +1305,18 @@
}
private fun doTestEtherTypeAllowListFilter(apfVersion: Int) {
- val ipClientCallback = ApfTestUtils.MockIpClientCallback()
- val apfFilter = TestApfFilter(
+ val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java)
+ val apfFilter =
+ ApfFilter(
context,
getDefaultConfig(apfVersion),
+ ifParams,
ipClientCallback,
metrics,
dependencies
- )
-
- val program = ipClientCallback.assertProgramUpdateAndGet()
+ )
+ verify(ipClientCallback, times(2)).installPacketFilter(programCaptor.capture())
+ val program = programCaptor.allValues.last()
// Using scapy to generate IPv4 mDNS packet:
// eth = Ether(src="E8:9F:80:66:60:BB", dst="01:00:5E:00:00:FB")
@@ -1724,16 +1747,19 @@
@Test
fun testIPv4PacketFilterOnV6OnlyNetwork() {
- val ipClientCallback = ApfTestUtils.MockIpClientCallback()
- val apfFilter = TestApfFilter(
+ val apfFilter =
+ ApfFilter(
context,
getDefaultConfig(),
+ ifParams,
ipClientCallback,
metrics,
dependencies
)
apfFilter.updateClatInterfaceState(true)
- val program = ipClientCallback.assertProgramUpdateAndGet()
+ val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java)
+ verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture())
+ val program = programCaptor.allValues.last()
// Using scapy to generate IPv4 mDNS packet:
// eth = Ether(src="E8:9F:80:66:60:BB", dst="01:00:5E:00:00:FB")
@@ -1806,20 +1832,23 @@
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Test
fun testArpTransmit() {
- val ipClientCallback = ApfTestUtils.MockIpClientCallback()
- val apfFilter = TestApfFilter(
+ val apfFilter =
+ ApfFilter(
context,
getDefaultConfig(),
+ ifParams,
ipClientCallback,
metrics,
dependencies
)
+ verify(ipClientCallback, times(2)).installPacketFilter(any())
val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24)
val lp = LinkProperties()
lp.addLinkAddress(linkAddress)
- ipClientCallback.resetApfProgramWait()
apfFilter.setLinkProperties(lp)
- val program = ipClientCallback.assertProgramUpdateAndGet()
+ val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java)
+ verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture())
+ val program = programCaptor.value
val receivedArpPacketBuf = ArpPacket.buildArpPacket(
arpBroadcastMacAddress,
senderMacAddress,
@@ -1854,18 +1883,20 @@
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
fun testNsFilterNoIPv6() {
`when`(dependencies.getAnycast6Addresses(any())).thenReturn(listOf())
- val ipClientCallback = ApfTestUtils.MockIpClientCallback()
- val apfFilter = TestApfFilter(
+ val apfFilter =
+ ApfFilter(
context,
getDefaultConfig(),
+ ifParams,
ipClientCallback,
metrics,
dependencies
)
// validate NS packet check when there is no IPv6 address
-
- var program = ipClientCallback.assertProgramUpdateAndGet()
+ val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java)
+ verify(ipClientCallback, times(2)).installPacketFilter(programCaptor.capture())
+ val program = programCaptor.allValues.last()
// Using scapy to generate IPv6 NS packet:
// eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06")
// ip6 = IPv6(src="2001::200:1a:1122:3344", dst="2001::200:1a:3344:1122", hlim=255)
@@ -1888,26 +1919,30 @@
@Test
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
fun testNsFilter() {
- val ipClientCallback = ApfTestUtils.MockIpClientCallback()
- val apfFilter = TestApfFilter(
- context,
- getDefaultConfig(),
- ipClientCallback,
- metrics,
- dependencies
+ val apfFilter =
+ ApfFilter(
+ context,
+ getDefaultConfig(),
+ ifParams,
+ ipClientCallback,
+ metrics,
+ dependencies
)
+ verify(ipClientCallback, times(2)).installPacketFilter(any())
// validate Ethernet dst address check
val lp = LinkProperties()
- ipClientCallback.resetApfProgramWait()
for (addr in hostIpv6Addresses) {
lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64))
}
apfFilter.setLinkProperties(lp)
+ verify(ipClientCallback, times(3)).installPacketFilter(any())
apfFilter.updateClatInterfaceState(true)
- val program = ipClientCallback.assertProgramUpdateAndGet()
+ val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java)
+ verify(ipClientCallback, times(4)).installPacketFilter(programCaptor.capture())
+ val program = programCaptor.value
// Using scapy to generate IPv6 NS packet:
// eth = Ether(src="00:01:02:03:04:05", dst="00:05:04:03:02:01")
@@ -2148,42 +2183,52 @@
@Test
fun testApfProgramUpdate() {
- val ipClientCallback = ApfTestUtils.MockIpClientCallback()
- val apfFilter = TestApfFilter(
- context,
- getDefaultConfig(),
- ipClientCallback,
- metrics,
- dependencies
+ val apfFilter =
+ ApfFilter(
+ context,
+ getDefaultConfig(),
+ ifParams,
+ ipClientCallback,
+ metrics,
+ dependencies
)
- val lp = LinkProperties()
-
+ verify(ipClientCallback, times(2)).installPacketFilter(any())
// add IPv4 address, expect to have apf program update
- ipClientCallback.resetApfProgramWait()
+ val lp = LinkProperties()
val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24)
lp.addLinkAddress(linkAddress)
apfFilter.setLinkProperties(lp)
- ipClientCallback.assertProgramUpdateAndGet()
+ verify(ipClientCallback, times(3)).installPacketFilter(any())
// add the same IPv4 address, expect to have no apf program update
- ipClientCallback.resetApfProgramWait()
apfFilter.setLinkProperties(lp)
- ipClientCallback.assertNoProgramUpdate()
+ verify(ipClientCallback, times(3)).installPacketFilter(any())
// add IPv6 addresses, expect to have apf program update
- ipClientCallback.resetApfProgramWait()
for (addr in hostIpv6Addresses) {
lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64))
}
apfFilter.setLinkProperties(lp)
- ipClientCallback.assertProgramUpdateAndGet()
+ verify(ipClientCallback, times(4)).installPacketFilter(any())
// add the same IPv6 addresses, expect to have no apf program update
- ipClientCallback.resetApfProgramWait()
apfFilter.setLinkProperties(lp)
- ipClientCallback.assertNoProgramUpdate()
+ verify(ipClientCallback, times(4)).installPacketFilter(any())
+
+ // add more tentative IPv6 addresses, expect to have apf program update
+ for (addr in hostIpv6TentativeAddresses) {
+ lp.addLinkAddress(LinkAddress(InetAddress.getByAddress(addr), 64, IFA_F_TENTATIVE, 0))
+ }
+
+ apfFilter.setLinkProperties(lp)
+ verify(ipClientCallback, times(5)).installPacketFilter(any())
+
+ // add the same IPv6 addresses, expect to have no apf program update
+ apfFilter.setLinkProperties(lp)
+ verify(ipClientCallback, times(5)).installPacketFilter(any())
+ apfFilter.shutdown()
}
private fun verifyProgramRun(
diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java
index 33c89a7..05e2e39 100644
--- a/tests/unit/src/android/net/apf/ApfTest.java
+++ b/tests/unit/src/android/net/apf/ApfTest.java
@@ -73,7 +73,6 @@
import android.net.apf.ApfFilter.ApfConfiguration;
import android.net.apf.ApfTestUtils.MockIpClientCallback;
import android.net.apf.ApfTestUtils.TestApfFilter;
-import android.net.apf.ApfTestUtils.TestLegacyApfFilter;
import android.net.apf.BaseApfGenerator.IllegalInstructionException;
import android.net.metrics.IpConnectivityLog;
import android.os.Build;
@@ -3135,33 +3134,17 @@
}
private TestAndroidPacketFilter makeTestApfFilter(ApfConfiguration config,
- MockIpClientCallback ipClientCallback, boolean isLegacy) throws Exception {
- final TestAndroidPacketFilter apfFilter;
- if (isLegacy) {
- apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback,
- mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
- } else {
- apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics,
+ MockIpClientCallback ipClientCallback) throws Exception {
+ return new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics,
mDependencies, mClock);
- }
- return apfFilter;
}
- // LegacyApfFilter ignores zero lifetime RAs (doesn't update program) but ApfFilter won't.
- private void verifyUpdateProgramForZeroLifetimeRa(MockIpClientCallback ipClientCallback,
- boolean isLegacy) {
- if (isLegacy) {
- ipClientCallback.assertNoProgramUpdate();
- } else {
- ipClientCallback.assertProgramUpdateAndGet();
- }
- }
- private void verifyInstallPacketFilterFailure(boolean isLegacy) throws Exception {
+ @Test
+ public void testInstallPacketFilterFailure() throws Exception {
final MockIpClientCallback ipClientCallback = new MockIpClientCallback(false);
final ApfConfiguration config = getDefaultConfig();
- final TestAndroidPacketFilter apfFilter =
- makeTestApfFilter(config, ipClientCallback, isLegacy);
+ final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback);
verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE);
verify(mNetworkQuirkMetrics).statsWrite();
reset(mNetworkQuirkMetrics);
@@ -3172,23 +3155,14 @@
verify(mNetworkQuirkMetrics).statsWrite();
}
- @Test
- public void testInstallPacketFilterFailure() throws Exception {
- verifyInstallPacketFilterFailure(false /* isLegacy */);
- }
@Test
- public void testInstallPacketFilterFailure_LegacyApfFilter() throws Exception {
- verifyInstallPacketFilterFailure(true /* isLegacy */);
- }
-
- private void verifyApfProgramOverSize(boolean isLegacy) throws Exception {
+ public void testApfProgramOverSize() throws Exception {
final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
final ApfConfiguration config = getDefaultConfig();
final ApfCapabilities capabilities = new ApfCapabilities(2, 512, ARPHRD_ETHER);
config.apfCapabilities = capabilities;
- final TestAndroidPacketFilter apfFilter =
- makeTestApfFilter(config, ipClientCallback, isLegacy);
+ final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback);
byte[] program = ipClientCallback.assertProgramUpdateAndGet();
final byte[] ra = buildLargeRa();
apfFilter.pretendPacketReceived(ra);
@@ -3199,27 +3173,12 @@
}
@Test
- public void testApfProgramOverSize() throws Exception {
- verifyApfProgramOverSize(false /* isLegacy */);
- }
-
- @Test
- public void testApfProgramOverSize_LegacyApfFilter() throws Exception {
- verifyApfProgramOverSize(true /* isLegacy */);
- }
-
- private void verifyGenerateApfProgramException(boolean isLegacy) throws Exception {
+ public void testGenerateApfProgramException() throws Exception {
final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
final ApfConfiguration config = getDefaultConfig();
final TestAndroidPacketFilter apfFilter;
- if (isLegacy) {
- apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback,
- mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies,
- true /* throwsExceptionWhenGeneratesProgram */);
- } else {
- apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics,
- mDependencies, true /* throwsExceptionWhenGeneratesProgram */);
- }
+ apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics,
+ mDependencies, true /* throwsExceptionWhenGeneratesProgram */);
synchronized (apfFilter) {
apfFilter.installNewProgramLocked();
}
@@ -3228,16 +3187,7 @@
}
@Test
- public void testGenerateApfProgramException() throws Exception {
- verifyGenerateApfProgramException(false /* isLegacy */);
- }
-
- @Test
- public void testGenerateApfProgramException_LegacyApfFilter() throws Exception {
- verifyGenerateApfProgramException(true /* isLegacy */);
- }
-
- private void verifyApfSessionInfoMetrics(boolean isLegacy) throws Exception {
+ public void testApfSessionInfoMetrics() throws Exception {
final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
final ApfConfiguration config = getDefaultConfig();
final ApfCapabilities capabilities = new ApfCapabilities(4, 4096, ARPHRD_ETHER);
@@ -3245,8 +3195,7 @@
final long startTimeMs = 12345;
final long durationTimeMs = config.minMetricsSessionDurationMs;
doReturn(startTimeMs).when(mClock).elapsedRealtime();
- final TestAndroidPacketFilter apfFilter =
- makeTestApfFilter(config, ipClientCallback, isLegacy);
+ final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback);
int maxProgramSize = 0;
int numProgramUpdated = 0;
byte[] program = ipClientCallback.assertProgramUpdateAndGet();
@@ -3318,23 +3267,13 @@
}
@Test
- public void testApfSessionInfoMetrics() throws Exception {
- verifyApfSessionInfoMetrics(false /* isLegacy */);
- }
-
- @Test
- public void testApfSessionInfoMetrics_LegacyApfFilter() throws Exception {
- verifyApfSessionInfoMetrics(true /* isLegacy */);
- }
-
- private void verifyIpClientRaInfoMetrics(boolean isLegacy) throws Exception {
+ public void testIpClientRaInfoMetrics() throws Exception {
final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
final ApfConfiguration config = getDefaultConfig();
final long startTimeMs = 12345;
final long durationTimeMs = config.minMetricsSessionDurationMs;
doReturn(startTimeMs).when(mClock).elapsedRealtime();
- final TestAndroidPacketFilter apfFilter =
- makeTestApfFilter(config, ipClientCallback, isLegacy);
+ final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback);
byte[] program = ipClientCallback.assertProgramUpdateAndGet();
final int routerLifetime = 1000;
@@ -3379,27 +3318,20 @@
apfFilter.pretendPacketReceived(raInvalid.build());
ipClientCallback.assertNoProgramUpdate();
apfFilter.pretendPacketReceived(raZeroRouterLifetime.build());
- verifyUpdateProgramForZeroLifetimeRa(ipClientCallback, isLegacy);
+ ipClientCallback.assertProgramUpdateAndGet();
apfFilter.pretendPacketReceived(raZeroPioValidLifetime.build());
- verifyUpdateProgramForZeroLifetimeRa(ipClientCallback, isLegacy);
+ ipClientCallback.assertProgramUpdateAndGet();
apfFilter.pretendPacketReceived(raZeroRdnssLifetime.build());
- verifyUpdateProgramForZeroLifetimeRa(ipClientCallback, isLegacy);
+ ipClientCallback.assertProgramUpdateAndGet();
apfFilter.pretendPacketReceived(raZeroRioRouteLifetime.build());
- verifyUpdateProgramForZeroLifetimeRa(ipClientCallback, isLegacy);
+ ipClientCallback.assertProgramUpdateAndGet();
// Write metrics data to statsd pipeline when shutdown.
doReturn(startTimeMs + durationTimeMs).when(mClock).elapsedRealtime();
apfFilter.shutdown();
// Verify each metric fields in IpClientRaInfoMetrics.
- if (isLegacy) {
- // LegacyApfFilter will purge expired RAs before adding new RA. Every time a new zero
- // lifetime RA is received, zero lifetime RAs except the newly added one will be
- // cleared, so the number of distinct RAs is 3 (ra1, ra2 and the newly added RA).
- verify(mIpClientRaInfoMetrics).setMaxNumberOfDistinctRas(3);
- } else {
- verify(mIpClientRaInfoMetrics).setMaxNumberOfDistinctRas(6);
- }
+ verify(mIpClientRaInfoMetrics).setMaxNumberOfDistinctRas(6);
verify(mIpClientRaInfoMetrics).setNumberOfZeroLifetimeRas(4);
verify(mIpClientRaInfoMetrics).setNumberOfParsingErrorRas(1);
verify(mIpClientRaInfoMetrics).setLowestRouterLifetimeSeconds(routerLifetime);
@@ -3409,16 +3341,6 @@
verify(mIpClientRaInfoMetrics).statsWrite();
}
- @Test
- public void testIpClientRaInfoMetrics() throws Exception {
- verifyIpClientRaInfoMetrics(false /* isLegacy */);
- }
-
- @Test
- public void testIpClientRaInfoMetrics_LegacyApfFilter() throws Exception {
- verifyIpClientRaInfoMetrics(true /* isLegacy */);
- }
-
private void verifyNoMetricsWrittenForShortDuration(boolean isLegacy) throws Exception {
final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
final ApfConfiguration config = getDefaultConfig();
@@ -3427,8 +3349,7 @@
// Verify no metrics data written to statsd for duration less than durationTimeMs.
doReturn(startTimeMs).when(mClock).elapsedRealtime();
- final TestAndroidPacketFilter apfFilter =
- makeTestApfFilter(config, ipClientCallback, isLegacy);
+ final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback);
doReturn(startTimeMs + durationTimeMs - 1).when(mClock).elapsedRealtime();
apfFilter.shutdown();
verify(mApfSessionInfoMetrics, never()).statsWrite();
diff --git a/tests/unit/src/android/net/apf/LegacyApfTest.java b/tests/unit/src/android/net/apf/LegacyApfTest.java
new file mode 100644
index 0000000..f51ecdd
--- /dev/null
+++ b/tests/unit/src/android/net/apf/LegacyApfTest.java
@@ -0,0 +1,2267 @@
+/*
+ * Copyright (C) 2012 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 static android.net.apf.ApfJniUtils.dropsAllPackets;
+import static android.net.apf.ApfTestUtils.DROP;
+import static android.net.apf.ApfTestUtils.PASS;
+import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
+import static android.os.PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED;
+import static android.system.OsConstants.ARPHRD_ETHER;
+import static android.system.OsConstants.ETH_P_ARP;
+import static android.system.OsConstants.ETH_P_IP;
+import static android.system.OsConstants.ETH_P_IPV6;
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import static com.android.net.module.util.HexDump.hexStringToByteArray;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.NattKeepalivePacketDataParcelable;
+import android.net.TcpKeepalivePacketDataParcelable;
+import android.net.apf.ApfCounterTracker.Counter;
+import android.net.apf.ApfFilter.ApfConfiguration;
+import android.net.apf.ApfTestUtils.MockIpClientCallback;
+import android.net.apf.ApfTestUtils.TestApfFilter;
+import android.net.apf.ApfTestUtils.TestLegacyApfFilter;
+import android.net.metrics.IpConnectivityLog;
+import android.os.Build;
+import android.os.PowerManager;
+import android.stats.connectivity.NetworkQuirkEvent;
+import android.system.ErrnoException;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.HexDump;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.NetworkStackConstants;
+import com.android.networkstack.metrics.ApfSessionInfoMetrics;
+import com.android.networkstack.metrics.IpClientRaInfoMetrics;
+import com.android.networkstack.metrics.NetworkQuirkMetrics;
+import com.android.server.networkstack.tests.R;
+import com.android.testutils.ConcurrentUtils;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import libcore.io.Streams;
+
+import org.junit.After;
+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.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * Tests for APF program generator and interpreter.
+ *
+ * The test cases will be executed by both APFv4 and APFv6 interpreter.
+ */
+@DevSdkIgnoreRunner.MonitorThreadLeak
+@RunWith(DevSdkIgnoreRunner.class)
+@SmallTest
+public class LegacyApfTest {
+ private static final int APF_VERSION_2 = 2;
+
+ @Rule
+ public DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule();
+ // Indicates which apf interpreter to run.
+ @Parameterized.Parameter()
+ public int mApfVersion;
+
+ @Parameterized.Parameters
+ public static Iterable<? extends Object> data() {
+ return Arrays.asList(4, 6);
+ }
+
+ @Mock private Context mContext;
+ @Mock
+ private ApfFilter.Dependencies mDependencies;
+ @Mock private PowerManager mPowerManager;
+ @Mock private IpConnectivityLog mIpConnectivityLog;
+ @Mock private NetworkQuirkMetrics mNetworkQuirkMetrics;
+ @Mock private ApfSessionInfoMetrics mApfSessionInfoMetrics;
+ @Mock private IpClientRaInfoMetrics mIpClientRaInfoMetrics;
+ @Mock private ApfFilter.Clock mClock;
+ @GuardedBy("mApfFilterCreated")
+ private final ArrayList<AndroidPacketFilter> mApfFilterCreated = new ArrayList<>();
+ @GuardedBy("mThreadsToBeCleared")
+ private final ArrayList<Thread> mThreadsToBeCleared = new ArrayList<>();
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
+ doReturn(mApfSessionInfoMetrics).when(mDependencies).getApfSessionInfoMetrics();
+ doReturn(mIpClientRaInfoMetrics).when(mDependencies).getIpClientRaInfoMetrics();
+ doAnswer((invocation) -> {
+ synchronized (mApfFilterCreated) {
+ mApfFilterCreated.add(invocation.getArgument(0));
+ }
+ return null;
+ }).when(mDependencies).onApfFilterCreated(any());
+ doAnswer((invocation) -> {
+ synchronized (mThreadsToBeCleared) {
+ mThreadsToBeCleared.add(invocation.getArgument(0));
+ }
+ return null;
+ }).when(mDependencies).onThreadCreated(any());
+ }
+
+ private void quitThreads() throws Exception {
+ ConcurrentUtils.quitThreads(
+ THREAD_QUIT_MAX_RETRY_COUNT,
+ false /* interrupt */,
+ HANDLER_TIMEOUT_MS,
+ () -> {
+ synchronized (mThreadsToBeCleared) {
+ final ArrayList<Thread> ret = new ArrayList<>(mThreadsToBeCleared);
+ mThreadsToBeCleared.clear();
+ return ret;
+ }
+ });
+ }
+
+ private void shutdownApfFilters() throws Exception {
+ ConcurrentUtils.quitResources(THREAD_QUIT_MAX_RETRY_COUNT, () -> {
+ synchronized (mApfFilterCreated) {
+ final ArrayList<AndroidPacketFilter> ret =
+ new ArrayList<>(mApfFilterCreated);
+ mApfFilterCreated.clear();
+ return ret;
+ }
+ }, (apf) -> {
+ apf.shutdown();
+ });
+ synchronized (mApfFilterCreated) {
+ assertEquals("ApfFilters did not fully shutdown.",
+ 0, mApfFilterCreated.size());
+ }
+ // It's necessary to wait until all ReceiveThreads have finished running because
+ // clearInlineMocks clears all Mock objects, including some privilege frameworks
+ // required by logStats, at the end of ReceiveThread#run.
+ quitThreads();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ shutdownApfFilters();
+ // Clear mocks to prevent from stubs holding instances and cause memory leaks.
+ Mockito.framework().clearInlineMocks();
+ }
+
+ private static final String TAG = "ApfTest";
+ // Expected return codes from APF interpreter.
+ private static final ApfCapabilities MOCK_APF_CAPABILITIES =
+ new ApfCapabilities(2, 4096, ARPHRD_ETHER);
+
+ private static final boolean DROP_MULTICAST = true;
+ private static final boolean ALLOW_MULTICAST = false;
+
+ private static final boolean DROP_802_3_FRAMES = true;
+ private static final boolean ALLOW_802_3_FRAMES = false;
+
+ private static final int MIN_RDNSS_LIFETIME_SEC = 0;
+ private static final int MIN_METRICS_SESSION_DURATIONS_MS = 300_000;
+
+ private static final int HANDLER_TIMEOUT_MS = 1000;
+ private static final int THREAD_QUIT_MAX_RETRY_COUNT = 3;
+
+ // Constants for opcode encoding
+ private static final byte LI_OP = (byte)(13 << 3);
+ private static final byte LDDW_OP = (byte)(22 << 3);
+ private static final byte STDW_OP = (byte)(23 << 3);
+ private static final byte SIZE0 = (byte)(0 << 1);
+ private static final byte SIZE8 = (byte)(1 << 1);
+ private static final byte SIZE16 = (byte)(2 << 1);
+ private static final byte SIZE32 = (byte)(3 << 1);
+ private static final byte R1_REG = 1;
+
+ private static ApfConfiguration getDefaultConfig() {
+ ApfFilter.ApfConfiguration config = new ApfConfiguration();
+ config.apfCapabilities = MOCK_APF_CAPABILITIES;
+ config.multicastFilter = ALLOW_MULTICAST;
+ config.ieee802_3Filter = ALLOW_802_3_FRAMES;
+ config.ethTypeBlackList = new int[0];
+ config.minRdnssLifetimeSec = MIN_RDNSS_LIFETIME_SEC;
+ config.minRdnssLifetimeSec = 67;
+ config.minMetricsSessionDurationMs = MIN_METRICS_SESSION_DURATIONS_MS;
+ return config;
+ }
+
+ private void assertPass(ApfV4Generator gen) throws ApfV4Generator.IllegalInstructionException {
+ ApfTestUtils.assertPass(mApfVersion, gen);
+ }
+
+ private void assertDrop(ApfV4Generator gen) throws ApfV4Generator.IllegalInstructionException {
+ ApfTestUtils.assertDrop(mApfVersion, gen);
+ }
+
+ private void assertPass(byte[] program, byte[] packet) {
+ ApfTestUtils.assertPass(mApfVersion, program, packet);
+ }
+
+ private void assertDrop(byte[] program, byte[] packet) {
+ ApfTestUtils.assertDrop(mApfVersion, program, packet);
+ }
+
+ private void assertPass(byte[] program, byte[] packet, int filterAge) {
+ ApfTestUtils.assertPass(mApfVersion, program, packet, filterAge);
+ }
+
+ private void assertDrop(byte[] program, byte[] packet, int filterAge) {
+ ApfTestUtils.assertDrop(mApfVersion, program, packet, filterAge);
+ }
+
+ private void assertPass(ApfV4Generator gen, byte[] packet, int filterAge)
+ throws ApfV4Generator.IllegalInstructionException {
+ ApfTestUtils.assertPass(mApfVersion, gen, packet, filterAge);
+ }
+
+ private void assertDrop(ApfV4Generator gen, byte[] packet, int filterAge)
+ throws ApfV4Generator.IllegalInstructionException {
+ ApfTestUtils.assertDrop(mApfVersion, gen, packet, filterAge);
+ }
+
+ private void assertDataMemoryContents(int expected, byte[] program, byte[] packet,
+ byte[] data, byte[] expectedData) throws Exception {
+ ApfTestUtils.assertDataMemoryContents(mApfVersion, expected, program, packet, data,
+ expectedData, false /* ignoreInterpreterVersion */);
+ }
+
+ private void assertDataMemoryContentsIgnoreVersion(int expected, byte[] program,
+ byte[] packet, byte[] data, byte[] expectedData) throws Exception {
+ ApfTestUtils.assertDataMemoryContents(mApfVersion, expected, program, packet, data,
+ expectedData, true /* ignoreInterpreterVersion */);
+ }
+
+ private void assertVerdict(String msg, int expected, byte[] program,
+ byte[] packet, int filterAge) {
+ ApfTestUtils.assertVerdict(mApfVersion, msg, expected, program, packet, filterAge);
+ }
+
+ private void assertVerdict(int expected, byte[] program, byte[] packet) {
+ ApfTestUtils.assertVerdict(mApfVersion, expected, program, packet);
+ }
+
+ /**
+ * Generate APF program, run pcap file though APF filter, then check all the packets in the file
+ * should be dropped.
+ */
+ @Test
+ public void testApfFilterPcapFile() throws Exception {
+ final byte[] MOCK_PCAP_IPV4_ADDR = {(byte) 172, 16, 7, (byte) 151};
+ String pcapFilename = stageFile(R.raw.apfPcap);
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_PCAP_IPV4_ADDR), 16);
+ LinkProperties lp = new LinkProperties();
+ lp.addLinkAddress(link);
+
+ ApfConfiguration config = getDefaultConfig();
+ ApfCapabilities MOCK_APF_PCAP_CAPABILITIES = new ApfCapabilities(4, 1700, ARPHRD_ETHER);
+ config.apfCapabilities = MOCK_APF_PCAP_CAPABILITIES;
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+ mNetworkQuirkMetrics, mDependencies);
+ apfFilter.setLinkProperties(lp);
+ byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+ byte[] data = new byte[Counter.totalSize()];
+ final boolean result;
+
+ result = dropsAllPackets(mApfVersion, program, data, pcapFilename);
+ Log.i(TAG, "testApfFilterPcapFile(): Data counters: " + HexDump.toHexString(data, false));
+
+ assertTrue("Failed to drop all packets by filter. \nAPF counters:" +
+ HexDump.toHexString(data, false), result);
+ }
+
+ private static final int ETH_HEADER_LEN = 14;
+ private static final int ETH_DEST_ADDR_OFFSET = 0;
+ private static final int ETH_ETHERTYPE_OFFSET = 12;
+ private static final byte[] ETH_BROADCAST_MAC_ADDRESS =
+ {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
+ private static final byte[] ETH_MULTICAST_MDNS_v4_MAC_ADDRESS =
+ {(byte) 0x01, (byte) 0x00, (byte) 0x5e, (byte) 0x00, (byte) 0x00, (byte) 0xfb};
+ private static final byte[] ETH_MULTICAST_MDNS_V6_MAC_ADDRESS =
+ {(byte) 0x33, (byte) 0x33, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xfb};
+
+ private static final int IP_HEADER_OFFSET = ETH_HEADER_LEN;
+
+ private static final int IPV4_HEADER_LEN = 20;
+ private static final int IPV4_TOTAL_LENGTH_OFFSET = IP_HEADER_OFFSET + 2;
+ private static final int IPV4_PROTOCOL_OFFSET = IP_HEADER_OFFSET + 9;
+ private static final int IPV4_SRC_ADDR_OFFSET = IP_HEADER_OFFSET + 12;
+ private static final int IPV4_DEST_ADDR_OFFSET = IP_HEADER_OFFSET + 16;
+
+ private static final int IPV4_TCP_HEADER_LEN = 20;
+ private static final int IPV4_TCP_HEADER_OFFSET = IP_HEADER_OFFSET + IPV4_HEADER_LEN;
+ private static final int IPV4_TCP_SRC_PORT_OFFSET = IPV4_TCP_HEADER_OFFSET + 0;
+ private static final int IPV4_TCP_DEST_PORT_OFFSET = IPV4_TCP_HEADER_OFFSET + 2;
+ private static final int IPV4_TCP_SEQ_NUM_OFFSET = IPV4_TCP_HEADER_OFFSET + 4;
+ private static final int IPV4_TCP_ACK_NUM_OFFSET = IPV4_TCP_HEADER_OFFSET + 8;
+ private static final int IPV4_TCP_HEADER_LENGTH_OFFSET = IPV4_TCP_HEADER_OFFSET + 12;
+ private static final int IPV4_TCP_HEADER_FLAG_OFFSET = IPV4_TCP_HEADER_OFFSET + 13;
+
+ private static final int IPV4_UDP_HEADER_OFFSET = IP_HEADER_OFFSET + IPV4_HEADER_LEN;
+ private static final int IPV4_UDP_SRC_PORT_OFFSET = IPV4_UDP_HEADER_OFFSET + 0;
+ private static final int IPV4_UDP_DEST_PORT_OFFSET = IPV4_UDP_HEADER_OFFSET + 2;
+ private static final int IPV4_UDP_LENGTH_OFFSET = IPV4_UDP_HEADER_OFFSET + 4;
+ private static final int IPV4_UDP_PAYLOAD_OFFSET = IPV4_UDP_HEADER_OFFSET + 8;
+ private static final byte[] IPV4_BROADCAST_ADDRESS =
+ {(byte) 255, (byte) 255, (byte) 255, (byte) 255};
+
+ private static final int IPV6_HEADER_LEN = 40;
+ private static final int IPV6_PAYLOAD_LENGTH_OFFSET = IP_HEADER_OFFSET + 4;
+ private static final int IPV6_NEXT_HEADER_OFFSET = IP_HEADER_OFFSET + 6;
+ private static final int IPV6_SRC_ADDR_OFFSET = IP_HEADER_OFFSET + 8;
+ private static final int IPV6_DEST_ADDR_OFFSET = IP_HEADER_OFFSET + 24;
+ private static final int IPV6_PAYLOAD_OFFSET = IP_HEADER_OFFSET + IPV6_HEADER_LEN;
+ private static final int IPV6_TCP_SRC_PORT_OFFSET = IPV6_PAYLOAD_OFFSET + 0;
+ private static final int IPV6_TCP_DEST_PORT_OFFSET = IPV6_PAYLOAD_OFFSET + 2;
+ private static final int IPV6_TCP_SEQ_NUM_OFFSET = IPV6_PAYLOAD_OFFSET + 4;
+ private static final int IPV6_TCP_ACK_NUM_OFFSET = IPV6_PAYLOAD_OFFSET + 8;
+ // The IPv6 all nodes address ff02::1
+ private static final byte[] IPV6_ALL_NODES_ADDRESS =
+ { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
+ private static final byte[] IPV6_ALL_ROUTERS_ADDRESS =
+ { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 };
+ private static final byte[] IPV6_SOLICITED_NODE_MULTICAST_ADDRESS = {
+ (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ (byte) 0xff, (byte) 0xab, (byte) 0xcd, (byte) 0xef,
+ };
+
+ private static final int ICMP6_TYPE_OFFSET = IP_HEADER_OFFSET + IPV6_HEADER_LEN;
+ private static final int ICMP6_ROUTER_SOLICITATION = 133;
+ private static final int ICMP6_ROUTER_ADVERTISEMENT = 134;
+ private static final int ICMP6_NEIGHBOR_SOLICITATION = 135;
+ private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136;
+
+ private static final int ICMP6_RA_HEADER_LEN = 16;
+ private static final int ICMP6_RA_CHECKSUM_OFFSET =
+ IP_HEADER_OFFSET + IPV6_HEADER_LEN + 2;
+ private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET =
+ IP_HEADER_OFFSET + IPV6_HEADER_LEN + 6;
+ private static final int ICMP6_RA_REACHABLE_TIME_OFFSET =
+ IP_HEADER_OFFSET + IPV6_HEADER_LEN + 8;
+ private static final int ICMP6_RA_RETRANSMISSION_TIMER_OFFSET =
+ IP_HEADER_OFFSET + IPV6_HEADER_LEN + 12;
+ private static final int ICMP6_RA_OPTION_OFFSET =
+ IP_HEADER_OFFSET + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN;
+
+ private static final int ICMP6_PREFIX_OPTION_TYPE = 3;
+ private static final int ICMP6_PREFIX_OPTION_LEN = 32;
+ private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4;
+ private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8;
+
+ // From RFC6106: Recursive DNS Server option
+ private static final int ICMP6_RDNSS_OPTION_TYPE = 25;
+ // From RFC6106: DNS Search List option
+ private static final int ICMP6_DNSSL_OPTION_TYPE = 31;
+
+ // From RFC4191: Route Information option
+ private static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24;
+ // Above three options all have the same format:
+ private static final int ICMP6_4_BYTE_OPTION_LEN = 8;
+ private static final int ICMP6_4_BYTE_LIFETIME_OFFSET = 4;
+ private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4;
+
+ private static final int UDP_HEADER_LEN = 8;
+ private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 22;
+
+ private static final int DHCP_CLIENT_PORT = 68;
+ private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 48;
+
+ private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN;
+ private static final byte[] ARP_IPV4_REQUEST_HEADER = {
+ 0, 1, // Hardware type: Ethernet (1)
+ 8, 0, // Protocol type: IP (0x0800)
+ 6, // Hardware size: 6
+ 4, // Protocol size: 4
+ 0, 1 // Opcode: request (1)
+ };
+ private static final byte[] ARP_IPV4_REPLY_HEADER = {
+ 0, 1, // Hardware type: Ethernet (1)
+ 8, 0, // Protocol type: IP (0x0800)
+ 6, // Hardware size: 6
+ 4, // Protocol size: 4
+ 0, 2 // Opcode: reply (2)
+ };
+ private static final int ARP_SOURCE_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 14;
+ private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ARP_HEADER_OFFSET + 24;
+
+ private static final byte[] MOCK_IPV4_ADDR = {10, 0, 0, 1};
+ private static final byte[] MOCK_BROADCAST_IPV4_ADDR = {10, 0, 31, (byte) 255}; // prefix = 19
+ private static final byte[] MOCK_MULTICAST_IPV4_ADDR = {(byte) 224, 0, 0, 1};
+ private static final byte[] ANOTHER_IPV4_ADDR = {10, 0, 0, 2};
+ private static final byte[] IPV4_SOURCE_ADDR = {10, 0, 0, 3};
+ private static final byte[] ANOTHER_IPV4_SOURCE_ADDR = {(byte) 192, 0, 2, 1};
+ private static final byte[] BUG_PROBE_SOURCE_ADDR1 = {0, 0, 1, 2};
+ private static final byte[] BUG_PROBE_SOURCE_ADDR2 = {3, 4, 0, 0};
+ private static final byte[] IPV4_ANY_HOST_ADDR = {0, 0, 0, 0};
+ private static final byte[] IPV4_MDNS_MULTICAST_ADDR = {(byte) 224, 0, 0, (byte) 251};
+ private static final byte[] IPV6_MDNS_MULTICAST_ADDR =
+ {(byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfb};
+ private static final int IPV6_UDP_DEST_PORT_OFFSET = IPV6_PAYLOAD_OFFSET + 2;
+ private static final int MDNS_UDP_PORT = 5353;
+
+ private static void setIpv4VersionFields(ByteBuffer packet) {
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IP);
+ packet.put(IP_HEADER_OFFSET, (byte) 0x45);
+ }
+
+ private static void setIpv6VersionFields(ByteBuffer packet) {
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IPV6);
+ packet.put(IP_HEADER_OFFSET, (byte) 0x60);
+ }
+
+ private static ByteBuffer makeIpv4Packet(int proto) {
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ setIpv4VersionFields(packet);
+ packet.put(IPV4_PROTOCOL_OFFSET, (byte) proto);
+ return packet;
+ }
+
+ private static ByteBuffer makeIpv6Packet(int nextHeader) {
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ setIpv6VersionFields(packet);
+ packet.put(IPV6_NEXT_HEADER_OFFSET, (byte) nextHeader);
+ return packet;
+ }
+
+ @Test
+ public void testApfFilterIPv4() throws Exception {
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
+ LinkProperties lp = new LinkProperties();
+ lp.addLinkAddress(link);
+
+ ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+ mNetworkQuirkMetrics, mDependencies);
+ apfFilter.setLinkProperties(lp);
+
+ byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ if (SdkLevel.isAtLeastV()) {
+ // Verify empty packet of 100 zero bytes is dropped
+ assertDrop(program, packet.array());
+ } else {
+ // Verify empty packet of 100 zero bytes is passed
+ assertPass(program, packet.array());
+ }
+
+ // Verify unicast IPv4 packet is passed
+ put(packet, ETH_DEST_ADDR_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_IPV4_ADDR);
+ assertPass(program, packet.array());
+
+ // Verify L2 unicast to IPv4 broadcast addresses is dropped (b/30231088)
+ put(packet, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
+ assertDrop(program, packet.array());
+ put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_BROADCAST_IPV4_ADDR);
+ assertDrop(program, packet.array());
+
+ // Verify multicast/broadcast IPv4, not DHCP to us, is dropped
+ put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
+ assertDrop(program, packet.array());
+ packet.put(IP_HEADER_OFFSET, (byte) 0x45);
+ assertDrop(program, packet.array());
+ packet.put(IPV4_PROTOCOL_OFFSET, (byte)IPPROTO_UDP);
+ assertDrop(program, packet.array());
+ packet.putShort(UDP_DESTINATION_PORT_OFFSET, (short)DHCP_CLIENT_PORT);
+ assertDrop(program, packet.array());
+ put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_MULTICAST_IPV4_ADDR);
+ assertDrop(program, packet.array());
+ put(packet, IPV4_DEST_ADDR_OFFSET, MOCK_BROADCAST_IPV4_ADDR);
+ assertDrop(program, packet.array());
+ put(packet, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
+ assertDrop(program, packet.array());
+
+ // Verify broadcast IPv4 DHCP to us is passed
+ put(packet, DHCP_CLIENT_MAC_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
+ assertPass(program, packet.array());
+
+ // Verify unicast IPv4 DHCP to us is passed
+ put(packet, ETH_DEST_ADDR_OFFSET, TestApfFilter.MOCK_MAC_ADDR);
+ assertPass(program, packet.array());
+ }
+
+ @Test
+ public void testApfFilterIPv6() throws Exception {
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ ApfConfiguration config = getDefaultConfig();
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+ mNetworkQuirkMetrics, mDependencies);
+ byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+
+ // Verify empty IPv6 packet is passed
+ ByteBuffer packet = makeIpv6Packet(IPPROTO_UDP);
+ assertPass(program, packet.array());
+
+ // Verify empty ICMPv6 packet is passed
+ packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
+ assertPass(program, packet.array());
+
+ // Verify empty ICMPv6 NA packet is passed
+ packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_NEIGHBOR_ANNOUNCEMENT);
+ assertPass(program, packet.array());
+
+ // Verify ICMPv6 NA to ff02::1 is dropped
+ put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_NODES_ADDRESS);
+ assertDrop(program, packet.array());
+
+ // Verify ICMPv6 NA to ff02::2 is dropped
+ put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_ROUTERS_ADDRESS);
+ assertDrop(program, packet.array());
+
+ // Verify ICMPv6 NA to Solicited-Node Multicast is passed
+ put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_SOLICITED_NODE_MULTICAST_ADDRESS);
+ assertPass(program, packet.array());
+
+ // Verify ICMPv6 RS to any is dropped
+ packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_SOLICITATION);
+ assertDrop(program, packet.array());
+ put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_ROUTERS_ADDRESS);
+ assertDrop(program, packet.array());
+ }
+
+ @Test
+ public void testApfFilterMulticast() throws Exception {
+ final byte[] unicastIpv4Addr = {(byte)192,0,2,63};
+ final byte[] broadcastIpv4Addr = {(byte)192,0,2,(byte)255};
+ final byte[] multicastIpv4Addr = {(byte)224,0,0,1};
+ final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb};
+
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ LinkAddress link = new LinkAddress(InetAddress.getByAddress(unicastIpv4Addr), 24);
+ LinkProperties lp = new LinkProperties();
+ lp.addLinkAddress(link);
+
+ ApfConfiguration config = getDefaultConfig();
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+ mNetworkQuirkMetrics, mDependencies);
+ apfFilter.setLinkProperties(lp);
+
+ byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+
+ // Construct IPv4 and IPv6 multicast packets.
+ ByteBuffer mcastv4packet = makeIpv4Packet(IPPROTO_UDP);
+ put(mcastv4packet, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr);
+
+ ByteBuffer mcastv6packet = makeIpv6Packet(IPPROTO_UDP);
+ put(mcastv6packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr);
+
+ // Construct IPv4 broadcast packet.
+ ByteBuffer bcastv4packet1 = makeIpv4Packet(IPPROTO_UDP);
+ bcastv4packet1.put(ETH_BROADCAST_MAC_ADDRESS);
+ bcastv4packet1.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ put(bcastv4packet1, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr);
+
+ ByteBuffer bcastv4packet2 = makeIpv4Packet(IPPROTO_UDP);
+ bcastv4packet2.put(ETH_BROADCAST_MAC_ADDRESS);
+ bcastv4packet2.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ put(bcastv4packet2, IPV4_DEST_ADDR_OFFSET, IPV4_BROADCAST_ADDRESS);
+
+ // Construct IPv4 broadcast with L2 unicast address packet (b/30231088).
+ ByteBuffer bcastv4unicastl2packet = makeIpv4Packet(IPPROTO_UDP);
+ bcastv4unicastl2packet.put(TestApfFilter.MOCK_MAC_ADDR);
+ bcastv4unicastl2packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ put(bcastv4unicastl2packet, IPV4_DEST_ADDR_OFFSET, broadcastIpv4Addr);
+
+ // Verify initially disabled multicast filter is off
+ assertPass(program, mcastv4packet.array());
+ assertPass(program, mcastv6packet.array());
+ assertPass(program, bcastv4packet1.array());
+ assertPass(program, bcastv4packet2.array());
+ assertPass(program, bcastv4unicastl2packet.array());
+
+ // Turn on multicast filter and verify it works
+ ipClientCallback.resetApfProgramWait();
+ apfFilter.setMulticastFilter(true);
+ program = ipClientCallback.assertProgramUpdateAndGet();
+ assertDrop(program, mcastv4packet.array());
+ assertDrop(program, mcastv6packet.array());
+ assertDrop(program, bcastv4packet1.array());
+ assertDrop(program, bcastv4packet2.array());
+ assertDrop(program, bcastv4unicastl2packet.array());
+
+ // Turn off multicast filter and verify it's off
+ ipClientCallback.resetApfProgramWait();
+ apfFilter.setMulticastFilter(false);
+ program = ipClientCallback.assertProgramUpdateAndGet();
+ assertPass(program, mcastv4packet.array());
+ assertPass(program, mcastv6packet.array());
+ assertPass(program, bcastv4packet1.array());
+ assertPass(program, bcastv4packet2.array());
+ assertPass(program, bcastv4unicastl2packet.array());
+
+ // Verify it can be initialized to on
+ ipClientCallback.resetApfProgramWait();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics,
+ mDependencies);
+ apfFilter.setLinkProperties(lp);
+ program = ipClientCallback.assertProgramUpdateAndGet();
+ assertDrop(program, mcastv4packet.array());
+ assertDrop(program, mcastv6packet.array());
+ assertDrop(program, bcastv4packet1.array());
+ assertDrop(program, bcastv4unicastl2packet.array());
+
+ // Verify that ICMPv6 multicast is not dropped.
+ mcastv6packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
+ assertPass(program, mcastv6packet.array());
+ }
+
+ @Test
+ public void testApfFilterMulticastPingWhileDozing() throws Exception {
+ doTestApfFilterMulticastPingWhileDozing(false /* isLightDozing */);
+ }
+
+ @Test
+ @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void testApfFilterMulticastPingWhileLightDozing() throws Exception {
+ doTestApfFilterMulticastPingWhileDozing(true /* isLightDozing */);
+ }
+
+ @Test
+ @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void testShouldHandleLightDozeKillSwitch() throws Exception {
+ final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ final ApfConfiguration configuration = getDefaultConfig();
+ configuration.shouldHandleLightDoze = false;
+ final ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback,
+ configuration, mNetworkQuirkMetrics, mDependencies);
+ final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture(), anyBoolean());
+ final BroadcastReceiver receiver = receiverCaptor.getValue();
+ doReturn(true).when(mPowerManager).isDeviceLightIdleMode();
+ receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
+ assertFalse(apfFilter.isInDozeMode());
+ }
+
+ private void doTestApfFilterMulticastPingWhileDozing(boolean isLightDozing) throws Exception {
+ final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ final ApfConfiguration configuration = getDefaultConfig();
+ configuration.shouldHandleLightDoze = true;
+ final ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback,
+ configuration, mNetworkQuirkMetrics, mDependencies);
+ final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ verify(mDependencies).addDeviceIdleReceiver(receiverCaptor.capture(), anyBoolean());
+ final BroadcastReceiver receiver = receiverCaptor.getValue();
+
+ // Construct a multicast ICMPv6 ECHO request.
+ final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb};
+ final ByteBuffer packet = makeIpv6Packet(IPPROTO_ICMPV6);
+ packet.put(ICMP6_TYPE_OFFSET, (byte)ICMPV6_ECHO_REQUEST_TYPE);
+ put(packet, IPV6_DEST_ADDR_OFFSET, multicastIpv6Addr);
+
+ // Normally, we let multicast pings alone...
+ assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array());
+
+ if (isLightDozing) {
+ doReturn(true).when(mPowerManager).isDeviceLightIdleMode();
+ receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
+ } else {
+ doReturn(true).when(mPowerManager).isDeviceIdleMode();
+ receiver.onReceive(mContext, new Intent(ACTION_DEVICE_IDLE_MODE_CHANGED));
+ }
+ // ...and even while dozing...
+ assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array());
+
+ // ...but when the multicast filter is also enabled, drop the multicast pings to save power.
+ apfFilter.setMulticastFilter(true);
+ assertDrop(ipClientCallback.assertProgramUpdateAndGet(), packet.array());
+
+ // However, we should still let through all other ICMPv6 types.
+ ByteBuffer raPacket = ByteBuffer.wrap(packet.array().clone());
+ setIpv6VersionFields(packet);
+ packet.put(IPV6_NEXT_HEADER_OFFSET, (byte) IPPROTO_ICMPV6);
+ raPacket.put(ICMP6_TYPE_OFFSET, (byte) NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT);
+ assertPass(ipClientCallback.assertProgramUpdateAndGet(), raPacket.array());
+
+ // Now wake up from doze mode to ensure that we no longer drop the packets.
+ // (The multicast filter is still enabled at this point).
+ if (isLightDozing) {
+ doReturn(false).when(mPowerManager).isDeviceLightIdleMode();
+ receiver.onReceive(mContext, new Intent(ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
+ } else {
+ doReturn(false).when(mPowerManager).isDeviceIdleMode();
+ receiver.onReceive(mContext, new Intent(ACTION_DEVICE_IDLE_MODE_CHANGED));
+ }
+ assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array());
+ }
+
+ @Test
+ @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public void testApfFilter802_3() throws Exception {
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ ApfConfiguration config = getDefaultConfig();
+ ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config,
+ mNetworkQuirkMetrics, mDependencies);
+ byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+
+ // Verify empty packet of 100 zero bytes is passed
+ // Note that eth-type = 0 makes it an IEEE802.3 frame
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ assertPass(program, packet.array());
+
+ // Verify empty packet with IPv4 is passed
+ setIpv4VersionFields(packet);
+ assertPass(program, packet.array());
+
+ // Verify empty IPv6 packet is passed
+ setIpv6VersionFields(packet);
+ assertPass(program, packet.array());
+
+ // Now turn on the filter
+ ipClientCallback.resetApfProgramWait();
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config,
+ mNetworkQuirkMetrics, mDependencies);
+ program = ipClientCallback.assertProgramUpdateAndGet();
+
+ // Verify that IEEE802.3 frame is dropped
+ // In this case ethtype is used for payload length
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)(100 - 14));
+ assertDrop(program, packet.array());
+
+ // Verify that IPv4 (as example of Ethernet II) frame will pass
+ setIpv4VersionFields(packet);
+ assertPass(program, packet.array());
+
+ // Verify that IPv6 (as example of Ethernet II) frame will pass
+ setIpv6VersionFields(packet);
+ assertPass(program, packet.array());
+ }
+
+ @Test
+ @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public void testApfFilterEthTypeBL() throws Exception {
+ final int[] emptyBlackList = {};
+ final int[] ipv4BlackList = {ETH_P_IP};
+ final int[] ipv4Ipv6BlackList = {ETH_P_IP, ETH_P_IPV6};
+
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ ApfConfiguration config = getDefaultConfig();
+ ApfFilter apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config,
+ mNetworkQuirkMetrics, mDependencies);
+ byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+
+ // Verify empty packet of 100 zero bytes is passed
+ // Note that eth-type = 0 makes it an IEEE802.3 frame
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ assertPass(program, packet.array());
+
+ // Verify empty packet with IPv4 is passed
+ setIpv4VersionFields(packet);
+ assertPass(program, packet.array());
+
+ // Verify empty IPv6 packet is passed
+ setIpv6VersionFields(packet);
+ assertPass(program, packet.array());
+
+ // Now add IPv4 to the black list
+ ipClientCallback.resetApfProgramWait();
+ config.ethTypeBlackList = ipv4BlackList;
+ apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config,
+ mNetworkQuirkMetrics, mDependencies);
+ program = ipClientCallback.assertProgramUpdateAndGet();
+
+ // Verify that IPv4 frame will be dropped
+ setIpv4VersionFields(packet);
+ assertDrop(program, packet.array());
+
+ // Verify that IPv6 frame will pass
+ setIpv6VersionFields(packet);
+ assertPass(program, packet.array());
+
+ // Now let us have both IPv4 and IPv6 in the black list
+ ipClientCallback.resetApfProgramWait();
+ config.ethTypeBlackList = ipv4Ipv6BlackList;
+ apfFilter = TestApfFilter.createTestApfFilter(mContext, ipClientCallback, config,
+ mNetworkQuirkMetrics, mDependencies);
+ program = ipClientCallback.assertProgramUpdateAndGet();
+
+ // Verify that IPv4 frame will be dropped
+ setIpv4VersionFields(packet);
+ assertDrop(program, packet.array());
+
+ // Verify that IPv6 frame will be dropped
+ setIpv6VersionFields(packet);
+ assertDrop(program, packet.array());
+ }
+
+ private byte[] getProgram(MockIpClientCallback cb, ApfFilter filter, LinkProperties lp) {
+ cb.resetApfProgramWait();
+ filter.setLinkProperties(lp);
+ return cb.assertProgramUpdateAndGet();
+ }
+
+ private void verifyArpFilter(byte[] program, int filterResult) {
+ // Verify ARP request packet
+ assertPass(program, arpRequestBroadcast(MOCK_IPV4_ADDR));
+ assertVerdict(filterResult, program, arpRequestBroadcast(ANOTHER_IPV4_ADDR));
+ assertVerdict(filterResult, program, arpRequestBroadcast(IPV4_ANY_HOST_ADDR));
+
+ // Verify ARP reply packets from different source ip
+ assertDrop(program, arpReply(IPV4_ANY_HOST_ADDR, IPV4_ANY_HOST_ADDR));
+ assertPass(program, arpReply(ANOTHER_IPV4_SOURCE_ADDR, IPV4_ANY_HOST_ADDR));
+ assertPass(program, arpReply(BUG_PROBE_SOURCE_ADDR1, IPV4_ANY_HOST_ADDR));
+ assertPass(program, arpReply(BUG_PROBE_SOURCE_ADDR2, IPV4_ANY_HOST_ADDR));
+
+ // Verify unicast ARP reply packet is always accepted.
+ assertPass(program, arpReply(IPV4_SOURCE_ADDR, MOCK_IPV4_ADDR));
+ assertPass(program, arpReply(IPV4_SOURCE_ADDR, ANOTHER_IPV4_ADDR));
+ assertPass(program, arpReply(IPV4_SOURCE_ADDR, IPV4_ANY_HOST_ADDR));
+
+ // Verify GARP reply packets are always filtered
+ assertDrop(program, garpReply());
+ }
+
+ @Test
+ public void testApfFilterArp() throws Exception {
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+ mNetworkQuirkMetrics, mDependencies);
+
+ // Verify initially ARP request filter is off, and GARP filter is on.
+ verifyArpFilter(ipClientCallback.assertProgramUpdateAndGet(), PASS);
+
+ // Inform ApfFilter of our address and verify ARP filtering is on
+ LinkAddress linkAddress = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 24);
+ LinkProperties lp = new LinkProperties();
+ assertTrue(lp.addLinkAddress(linkAddress));
+ verifyArpFilter(getProgram(ipClientCallback, apfFilter, lp), DROP);
+
+ // Inform ApfFilter of loss of IP and verify ARP filtering is off
+ verifyArpFilter(getProgram(ipClientCallback, apfFilter, new LinkProperties()), PASS);
+ }
+
+ private static byte[] arpReply(byte[] sip, byte[] tip) {
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
+ put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REPLY_HEADER);
+ put(packet, ARP_SOURCE_IP_ADDRESS_OFFSET, sip);
+ put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, tip);
+ return packet.array();
+ }
+
+ private static byte[] arpRequestBroadcast(byte[] tip) {
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
+ put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
+ put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REQUEST_HEADER);
+ put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, tip);
+ return packet.array();
+ }
+
+ private static byte[] garpReply() {
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
+ put(packet, ETH_DEST_ADDR_OFFSET, ETH_BROADCAST_MAC_ADDRESS);
+ put(packet, ARP_HEADER_OFFSET, ARP_IPV4_REPLY_HEADER);
+ put(packet, ARP_TARGET_IP_ADDRESS_OFFSET, IPV4_ANY_HOST_ADDR);
+ return packet.array();
+ }
+
+ private static final byte[] IPV4_KEEPALIVE_SRC_ADDR = {10, 0, 0, 5};
+ private static final byte[] IPV4_KEEPALIVE_DST_ADDR = {10, 0, 0, 6};
+ private static final byte[] IPV4_ANOTHER_ADDR = {10, 0 , 0, 7};
+ private static final byte[] IPV6_KEEPALIVE_SRC_ADDR =
+ {(byte) 0x24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfa, (byte) 0xf1};
+ private static final byte[] IPV6_KEEPALIVE_DST_ADDR =
+ {(byte) 0x24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfa, (byte) 0xf2};
+ private static final byte[] IPV6_ANOTHER_ADDR =
+ {(byte) 0x24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfa, (byte) 0xf5};
+
+ @Test
+ public void testApfFilterKeepaliveAck() throws Exception {
+ final MockIpClientCallback cb = new MockIpClientCallback();
+ final ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ final TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb,
+ mNetworkQuirkMetrics, mDependencies);
+ byte[] program;
+ final int srcPort = 12345;
+ final int dstPort = 54321;
+ final int seqNum = 2123456789;
+ final int ackNum = 1234567890;
+ final int anotherSrcPort = 23456;
+ final int anotherDstPort = 65432;
+ final int anotherSeqNum = 2123456780;
+ final int anotherAckNum = 1123456789;
+ final int slot1 = 1;
+ final int slot2 = 2;
+ final int window = 14480;
+ final int windowScale = 4;
+
+ // src: 10.0.0.5, port: 12345
+ // dst: 10.0.0.6, port: 54321
+ InetAddress srcAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_SRC_ADDR);
+ InetAddress dstAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_DST_ADDR);
+
+ final TcpKeepalivePacketDataParcelable parcel = new TcpKeepalivePacketDataParcelable();
+ parcel.srcAddress = srcAddr.getAddress();
+ parcel.srcPort = srcPort;
+ parcel.dstAddress = dstAddr.getAddress();
+ parcel.dstPort = dstPort;
+ parcel.seq = seqNum;
+ parcel.ack = ackNum;
+
+ apfFilter.addTcpKeepalivePacketFilter(slot1, parcel);
+ program = cb.assertProgramUpdateAndGet();
+
+ // Verify IPv4 keepalive ack packet is dropped
+ // src: 10.0.0.6, port: 54321
+ // dst: 10.0.0.5, port: 12345
+ assertDrop(program,
+ ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, ackNum, seqNum + 1, 0 /* dataLength */));
+ // Verify IPv4 non-keepalive ack packet from the same source address is passed
+ assertPass(program,
+ ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, ackNum + 100, seqNum, 0 /* dataLength */));
+ assertPass(program,
+ ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, ackNum, seqNum + 1, 10 /* dataLength */));
+ // Verify IPv4 packet from another address is passed
+ assertPass(program,
+ ipv4TcpPacket(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, anotherSrcPort,
+ anotherDstPort, anotherSeqNum, anotherAckNum, 0 /* dataLength */));
+
+ // Remove IPv4 keepalive filter
+ apfFilter.removeKeepalivePacketFilter(slot1);
+
+ try {
+ // src: 2404:0:0:0:0:0:faf1, port: 12345
+ // dst: 2404:0:0:0:0:0:faf2, port: 54321
+ srcAddr = InetAddress.getByAddress(IPV6_KEEPALIVE_SRC_ADDR);
+ dstAddr = InetAddress.getByAddress(IPV6_KEEPALIVE_DST_ADDR);
+
+ final TcpKeepalivePacketDataParcelable ipv6Parcel =
+ new TcpKeepalivePacketDataParcelable();
+ ipv6Parcel.srcAddress = srcAddr.getAddress();
+ ipv6Parcel.srcPort = srcPort;
+ ipv6Parcel.dstAddress = dstAddr.getAddress();
+ ipv6Parcel.dstPort = dstPort;
+ ipv6Parcel.seq = seqNum;
+ ipv6Parcel.ack = ackNum;
+
+ apfFilter.addTcpKeepalivePacketFilter(slot1, ipv6Parcel);
+ program = cb.assertProgramUpdateAndGet();
+
+ // Verify IPv6 keepalive ack packet is dropped
+ // src: 2404:0:0:0:0:0:faf2, port: 54321
+ // dst: 2404:0:0:0:0:0:faf1, port: 12345
+ assertDrop(program,
+ ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, ackNum, seqNum + 1));
+ // Verify IPv6 non-keepalive ack packet from the same source address is passed
+ assertPass(program,
+ ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, ackNum + 100, seqNum));
+ // Verify IPv6 packet from another address is passed
+ assertPass(program,
+ ipv6TcpPacket(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, anotherSrcPort,
+ anotherDstPort, anotherSeqNum, anotherAckNum));
+
+ // Remove IPv6 keepalive filter
+ apfFilter.removeKeepalivePacketFilter(slot1);
+
+ // Verify multiple filters
+ apfFilter.addTcpKeepalivePacketFilter(slot1, parcel);
+ apfFilter.addTcpKeepalivePacketFilter(slot2, ipv6Parcel);
+ program = cb.assertProgramUpdateAndGet();
+
+ // Verify IPv4 keepalive ack packet is dropped
+ // src: 10.0.0.6, port: 54321
+ // dst: 10.0.0.5, port: 12345
+ assertDrop(program,
+ ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, ackNum, seqNum + 1, 0 /* dataLength */));
+ // Verify IPv4 non-keepalive ack packet from the same source address is passed
+ assertPass(program,
+ ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, ackNum + 100, seqNum, 0 /* dataLength */));
+ // Verify IPv4 packet from another address is passed
+ assertPass(program,
+ ipv4TcpPacket(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, anotherSrcPort,
+ anotherDstPort, anotherSeqNum, anotherAckNum, 0 /* dataLength */));
+
+ // Verify IPv6 keepalive ack packet is dropped
+ // src: 2404:0:0:0:0:0:faf2, port: 54321
+ // dst: 2404:0:0:0:0:0:faf1, port: 12345
+ assertDrop(program,
+ ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, ackNum, seqNum + 1));
+ // Verify IPv6 non-keepalive ack packet from the same source address is passed
+ assertPass(program,
+ ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, ackNum + 100, seqNum));
+ // Verify IPv6 packet from another address is passed
+ assertPass(program,
+ ipv6TcpPacket(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, anotherSrcPort,
+ anotherDstPort, anotherSeqNum, anotherAckNum));
+
+ // Remove keepalive filters
+ apfFilter.removeKeepalivePacketFilter(slot1);
+ apfFilter.removeKeepalivePacketFilter(slot2);
+ } catch (UnsupportedOperationException e) {
+ // TODO: support V6 packets
+ }
+
+ program = cb.assertProgramUpdateAndGet();
+
+ // Verify IPv4, IPv6 packets are passed
+ assertPass(program,
+ ipv4TcpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, ackNum, seqNum + 1, 0 /* dataLength */));
+ assertPass(program,
+ ipv6TcpPacket(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, ackNum, seqNum + 1));
+ assertPass(program,
+ ipv4TcpPacket(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, srcPort,
+ dstPort, anotherSeqNum, anotherAckNum, 0 /* dataLength */));
+ assertPass(program,
+ ipv6TcpPacket(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, srcPort,
+ dstPort, anotherSeqNum, anotherAckNum));
+ }
+
+ private static byte[] ipv4TcpPacket(byte[] sip, byte[] dip, int sport,
+ int dport, int seq, int ack, int dataLength) {
+ final int totalLength = dataLength + IPV4_HEADER_LEN + IPV4_TCP_HEADER_LEN;
+
+ ByteBuffer packet = ByteBuffer.wrap(new byte[totalLength + ETH_HEADER_LEN]);
+
+ // Ethertype and IPv4 header
+ setIpv4VersionFields(packet);
+ packet.putShort(IPV4_TOTAL_LENGTH_OFFSET, (short) totalLength);
+ packet.put(IPV4_PROTOCOL_OFFSET, (byte) IPPROTO_TCP);
+ put(packet, IPV4_SRC_ADDR_OFFSET, sip);
+ put(packet, IPV4_DEST_ADDR_OFFSET, dip);
+ packet.putShort(IPV4_TCP_SRC_PORT_OFFSET, (short) sport);
+ packet.putShort(IPV4_TCP_DEST_PORT_OFFSET, (short) dport);
+ packet.putInt(IPV4_TCP_SEQ_NUM_OFFSET, seq);
+ packet.putInt(IPV4_TCP_ACK_NUM_OFFSET, ack);
+
+ // TCP header length 5(20 bytes), reserved 3 bits, NS=0
+ packet.put(IPV4_TCP_HEADER_LENGTH_OFFSET, (byte) 0x50);
+ // TCP flags: ACK set
+ packet.put(IPV4_TCP_HEADER_FLAG_OFFSET, (byte) 0x10);
+ return packet.array();
+ }
+
+ private static byte[] ipv6TcpPacket(byte[] sip, byte[] tip, int sport,
+ int dport, int seq, int ack) {
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ setIpv6VersionFields(packet);
+ packet.put(IPV6_NEXT_HEADER_OFFSET, (byte) IPPROTO_TCP);
+ put(packet, IPV6_SRC_ADDR_OFFSET, sip);
+ put(packet, IPV6_DEST_ADDR_OFFSET, tip);
+ packet.putShort(IPV6_TCP_SRC_PORT_OFFSET, (short) sport);
+ packet.putShort(IPV6_TCP_DEST_PORT_OFFSET, (short) dport);
+ packet.putInt(IPV6_TCP_SEQ_NUM_OFFSET, seq);
+ packet.putInt(IPV6_TCP_ACK_NUM_OFFSET, ack);
+ return packet.array();
+ }
+
+ @Test
+ public void testApfFilterNattKeepalivePacket() throws Exception {
+ final MockIpClientCallback cb = new MockIpClientCallback();
+ final ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ final TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb,
+ mNetworkQuirkMetrics, mDependencies);
+ byte[] program;
+ final int srcPort = 1024;
+ final int dstPort = 4500;
+ final int slot1 = 1;
+ // NAT-T keepalive
+ final byte[] kaPayload = {(byte) 0xff};
+ final byte[] nonKaPayload = {(byte) 0xfe};
+
+ // src: 10.0.0.5, port: 1024
+ // dst: 10.0.0.6, port: 4500
+ InetAddress srcAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_SRC_ADDR);
+ InetAddress dstAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_DST_ADDR);
+
+ final NattKeepalivePacketDataParcelable parcel = new NattKeepalivePacketDataParcelable();
+ parcel.srcAddress = srcAddr.getAddress();
+ parcel.srcPort = srcPort;
+ parcel.dstAddress = dstAddr.getAddress();
+ parcel.dstPort = dstPort;
+
+ apfFilter.addNattKeepalivePacketFilter(slot1, parcel);
+ program = cb.assertProgramUpdateAndGet();
+
+ // Verify IPv4 keepalive packet is dropped
+ // src: 10.0.0.6, port: 4500
+ // dst: 10.0.0.5, port: 1024
+ byte[] pkt = ipv4UdpPacket(IPV4_KEEPALIVE_DST_ADDR,
+ IPV4_KEEPALIVE_SRC_ADDR, dstPort, srcPort, 1 /* dataLength */);
+ System.arraycopy(kaPayload, 0, pkt, IPV4_UDP_PAYLOAD_OFFSET, kaPayload.length);
+ assertDrop(program, pkt);
+
+ // Verify a packet with payload length 1 byte but it is not 0xff will pass the filter.
+ System.arraycopy(nonKaPayload, 0, pkt, IPV4_UDP_PAYLOAD_OFFSET, nonKaPayload.length);
+ assertPass(program, pkt);
+
+ // Verify IPv4 non-keepalive response packet from the same source address is passed
+ assertPass(program,
+ ipv4UdpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, 10 /* dataLength */));
+
+ // Verify IPv4 non-keepalive response packet from other source address is passed
+ assertPass(program,
+ ipv4UdpPacket(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, 10 /* dataLength */));
+
+ apfFilter.removeKeepalivePacketFilter(slot1);
+ }
+
+ private static byte[] ipv4UdpPacket(byte[] sip, byte[] dip, int sport,
+ int dport, int dataLength) {
+ final int totalLength = dataLength + IPV4_HEADER_LEN + UDP_HEADER_LEN;
+ final int udpLength = UDP_HEADER_LEN + dataLength;
+ ByteBuffer packet = ByteBuffer.wrap(new byte[totalLength + ETH_HEADER_LEN]);
+
+ // Ethertype and IPv4 header
+ setIpv4VersionFields(packet);
+ packet.putShort(IPV4_TOTAL_LENGTH_OFFSET, (short) totalLength);
+ packet.put(IPV4_PROTOCOL_OFFSET, (byte) IPPROTO_UDP);
+ put(packet, IPV4_SRC_ADDR_OFFSET, sip);
+ put(packet, IPV4_DEST_ADDR_OFFSET, dip);
+ packet.putShort(IPV4_UDP_SRC_PORT_OFFSET, (short) sport);
+ packet.putShort(IPV4_UDP_DEST_PORT_OFFSET, (short) dport);
+ packet.putShort(IPV4_UDP_LENGTH_OFFSET, (short) udpLength);
+
+ return packet.array();
+ }
+
+ private static class RaPacketBuilder {
+ final ByteArrayOutputStream mPacket = new ByteArrayOutputStream();
+ int mFlowLabel = 0x12345;
+ int mReachableTime = 30_000;
+ int mRetransmissionTimer = 1000;
+
+ public RaPacketBuilder(int routerLft) throws Exception {
+ InetAddress src = InetAddress.getByName("fe80::1234:abcd");
+ ByteBuffer buffer = ByteBuffer.allocate(ICMP6_RA_OPTION_OFFSET);
+
+ buffer.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IPV6);
+ buffer.position(ETH_HEADER_LEN);
+
+ // skip version, tclass, flowlabel; set in build()
+ buffer.position(buffer.position() + 4);
+
+ buffer.putShort((short) 0); // Payload length; updated later
+ buffer.put((byte) IPPROTO_ICMPV6); // Next header
+ buffer.put((byte) 0xff); // Hop limit
+ buffer.put(src.getAddress()); // Source address
+ buffer.put(IPV6_ALL_NODES_ADDRESS); // Destination address
+
+ buffer.put((byte) ICMP6_ROUTER_ADVERTISEMENT); // Type
+ buffer.put((byte) 0); // Code (0)
+ buffer.putShort((short) 0); // Checksum (ignored)
+ buffer.put((byte) 64); // Hop limit
+ buffer.put((byte) 0); // M/O, reserved
+ buffer.putShort((short) routerLft); // Router lifetime
+ // skip reachable time; set in build()
+ // skip retransmission timer; set in build();
+
+ mPacket.write(buffer.array(), 0, buffer.capacity());
+ }
+
+ public RaPacketBuilder setFlowLabel(int flowLabel) {
+ mFlowLabel = flowLabel;
+ return this;
+ }
+
+ public RaPacketBuilder setReachableTime(int reachable) {
+ mReachableTime = reachable;
+ return this;
+ }
+
+ public RaPacketBuilder setRetransmissionTimer(int retrans) {
+ mRetransmissionTimer = retrans;
+ return this;
+ }
+
+ public RaPacketBuilder addPioOption(int valid, int preferred, String prefixString)
+ throws Exception {
+ ByteBuffer buffer = ByteBuffer.allocate(ICMP6_PREFIX_OPTION_LEN);
+
+ IpPrefix prefix = new IpPrefix(prefixString);
+ buffer.put((byte) ICMP6_PREFIX_OPTION_TYPE); // Type
+ buffer.put((byte) 4); // Length in 8-byte units
+ buffer.put((byte) prefix.getPrefixLength()); // Prefix length
+ buffer.put((byte) 0b11000000); // L = 1, A = 1
+ buffer.putInt(valid);
+ buffer.putInt(preferred);
+ buffer.putInt(0); // Reserved
+ buffer.put(prefix.getRawAddress());
+
+ mPacket.write(buffer.array(), 0, buffer.capacity());
+ return this;
+ }
+
+ public RaPacketBuilder addRioOption(int lifetime, String prefixString) throws Exception {
+ IpPrefix prefix = new IpPrefix(prefixString);
+
+ int optionLength;
+ if (prefix.getPrefixLength() == 0) {
+ optionLength = 1;
+ } else if (prefix.getPrefixLength() <= 64) {
+ optionLength = 2;
+ } else {
+ optionLength = 3;
+ }
+
+ ByteBuffer buffer = ByteBuffer.allocate(optionLength * 8);
+
+ buffer.put((byte) ICMP6_ROUTE_INFO_OPTION_TYPE); // Type
+ buffer.put((byte) optionLength); // Length in 8-byte units
+ buffer.put((byte) prefix.getPrefixLength()); // Prefix length
+ buffer.put((byte) 0b00011000); // Pref = high
+ buffer.putInt(lifetime); // Lifetime
+
+ byte[] prefixBytes = prefix.getRawAddress();
+ buffer.put(prefixBytes, 0, (optionLength - 1) * 8);
+
+ mPacket.write(buffer.array(), 0, buffer.capacity());
+ return this;
+ }
+
+ public RaPacketBuilder addDnsslOption(int lifetime, String... domains) {
+ ByteArrayOutputStream dnssl = new ByteArrayOutputStream();
+ for (String domain : domains) {
+ for (String label : domain.split("\\.")) {
+ final byte[] bytes = label.getBytes(StandardCharsets.UTF_8);
+ dnssl.write((byte) bytes.length);
+ dnssl.write(bytes, 0, bytes.length);
+ }
+ dnssl.write((byte) 0);
+ }
+
+ // Extend with 0s to make it 8-byte aligned.
+ while (dnssl.size() % 8 != 0) {
+ dnssl.write((byte) 0);
+ }
+
+ final int length = ICMP6_4_BYTE_OPTION_LEN + dnssl.size();
+ ByteBuffer buffer = ByteBuffer.allocate(length);
+
+ buffer.put((byte) ICMP6_DNSSL_OPTION_TYPE); // Type
+ buffer.put((byte) (length / 8)); // Length
+ // skip past reserved bytes
+ buffer.position(buffer.position() + 2);
+ buffer.putInt(lifetime); // Lifetime
+ buffer.put(dnssl.toByteArray()); // Domain names
+
+ mPacket.write(buffer.array(), 0, buffer.capacity());
+ return this;
+ }
+
+ public RaPacketBuilder addRdnssOption(int lifetime, String... servers) throws Exception {
+ int optionLength = 1 + 2 * servers.length; // In 8-byte units
+ ByteBuffer buffer = ByteBuffer.allocate(optionLength * 8);
+
+ buffer.put((byte) ICMP6_RDNSS_OPTION_TYPE); // Type
+ buffer.put((byte) optionLength); // Length
+ buffer.putShort((short) 0); // Reserved
+ buffer.putInt(lifetime); // Lifetime
+ for (String server : servers) {
+ buffer.put(InetAddress.getByName(server).getAddress());
+ }
+
+ mPacket.write(buffer.array(), 0, buffer.capacity());
+ return this;
+ }
+
+ public RaPacketBuilder addZeroLengthOption() throws Exception {
+ ByteBuffer buffer = ByteBuffer.allocate(ICMP6_4_BYTE_OPTION_LEN);
+ buffer.put((byte) ICMP6_PREFIX_OPTION_TYPE);
+ buffer.put((byte) 0);
+
+ mPacket.write(buffer.array(), 0, buffer.capacity());
+ return this;
+ }
+
+ public byte[] build() {
+ ByteBuffer buffer = ByteBuffer.wrap(mPacket.toByteArray());
+ // IPv6, traffic class = 0, flow label = mFlowLabel
+ buffer.putInt(IP_HEADER_OFFSET, 0x60000000 | (0xFFFFF & mFlowLabel));
+ buffer.putShort(IPV6_PAYLOAD_LENGTH_OFFSET, (short) buffer.capacity());
+
+ buffer.position(ICMP6_RA_REACHABLE_TIME_OFFSET);
+ buffer.putInt(mReachableTime);
+ buffer.putInt(mRetransmissionTimer);
+
+ return buffer.array();
+ }
+ }
+
+ private byte[] buildLargeRa() throws Exception {
+ RaPacketBuilder builder = new RaPacketBuilder(1800 /* router lft */);
+
+ builder.addRioOption(1200, "64:ff9b::/96");
+ builder.addRdnssOption(7200, "2001:db8:1::1", "2001:db8:1::2");
+ builder.addRioOption(2100, "2000::/3");
+ builder.addRioOption(2400, "::/0");
+ builder.addPioOption(600, 300, "2001:db8:a::/64");
+ builder.addRioOption(1500, "2001:db8:c:d::/64");
+ builder.addPioOption(86400, 43200, "fd95:d1e:12::/64");
+
+ return builder.build();
+ }
+
+ // Verify that the last program pushed to the IpClient.Callback properly filters the
+ // given packet for the given lifetime.
+ private void verifyRaLifetime(byte[] program, ByteBuffer packet, int lifetime) {
+ verifyRaLifetime(program, packet, lifetime, 0);
+ }
+
+ // Verify that the last program pushed to the IpClient.Callback properly filters the
+ // given packet for the given lifetime and programInstallTime. programInstallTime is
+ // the time difference between when RA is last seen and the program is installed.
+ private void verifyRaLifetime(byte[] program, ByteBuffer packet, int lifetime,
+ int programInstallTime) {
+ final int FRACTION_OF_LIFETIME = 6;
+ final int ageLimit = lifetime / FRACTION_OF_LIFETIME - programInstallTime;
+
+ // Verify new program should drop RA for 1/6th its lifetime and pass afterwards.
+ assertDrop(program, packet.array());
+ assertDrop(program, packet.array(), ageLimit);
+ assertPass(program, packet.array(), ageLimit + 1);
+ assertPass(program, packet.array(), lifetime);
+ // Verify RA checksum is ignored
+ final short originalChecksum = packet.getShort(ICMP6_RA_CHECKSUM_OFFSET);
+ packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)12345);
+ assertDrop(program, packet.array());
+ packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)-12345);
+ assertDrop(program, packet.array());
+ packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, originalChecksum);
+
+ // Verify other changes to RA (e.g., a change in the source address) make it not match.
+ final int offset = IPV6_SRC_ADDR_OFFSET + 5;
+ final byte originalByte = packet.get(offset);
+ packet.put(offset, (byte) (~originalByte));
+ assertPass(program, packet.array());
+ packet.put(offset, originalByte);
+ assertDrop(program, packet.array());
+ }
+
+ // Test that when ApfFilter is shown the given packet, it generates a program to filter it
+ // for the given lifetime.
+ private void verifyRaLifetime(TestApfFilter apfFilter, MockIpClientCallback ipClientCallback,
+ ByteBuffer packet, int lifetime) throws IOException, ErrnoException {
+ // Verify new program generated if ApfFilter witnesses RA
+ apfFilter.pretendPacketReceived(packet.array());
+ byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+ verifyRaLifetime(program, packet, lifetime);
+ }
+
+ private void assertInvalidRa(TestApfFilter apfFilter, MockIpClientCallback ipClientCallback,
+ ByteBuffer packet) throws IOException, ErrnoException {
+ apfFilter.pretendPacketReceived(packet.array());
+ ipClientCallback.assertNoProgramUpdate();
+ }
+
+ @Test
+ public void testApfFilterRa() throws Exception {
+ MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+ mNetworkQuirkMetrics, mDependencies);
+ byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+
+ final int ROUTER_LIFETIME = 1000;
+ final int PREFIX_VALID_LIFETIME = 200;
+ final int PREFIX_PREFERRED_LIFETIME = 100;
+ final int RDNSS_LIFETIME = 300;
+ final int ROUTE_LIFETIME = 400;
+ // Note that lifetime of 2000 will be ignored in favor of shorter route lifetime of 1000.
+ final int DNSSL_LIFETIME = 2000;
+
+ // Verify RA is passed the first time
+ RaPacketBuilder ra = new RaPacketBuilder(ROUTER_LIFETIME);
+ ByteBuffer basePacket = ByteBuffer.wrap(ra.build());
+ assertPass(program, basePacket.array());
+
+ verifyRaLifetime(apfFilter, ipClientCallback, basePacket, ROUTER_LIFETIME);
+
+ ra = new RaPacketBuilder(ROUTER_LIFETIME);
+ // Check that changes are ignored in every byte of the flow label.
+ ra.setFlowLabel(0x56789);
+ ByteBuffer newFlowLabelPacket = ByteBuffer.wrap(ra.build());
+
+ // Ensure zero-length options cause the packet to be silently skipped.
+ // Do this before we test other packets. http://b/29586253
+ ra = new RaPacketBuilder(ROUTER_LIFETIME);
+ ra.addZeroLengthOption();
+ ByteBuffer zeroLengthOptionPacket = ByteBuffer.wrap(ra.build());
+ assertInvalidRa(apfFilter, ipClientCallback, zeroLengthOptionPacket);
+
+ // Generate several RAs with different options and lifetimes, and verify when
+ // ApfFilter is shown these packets, it generates programs to filter them for the
+ // appropriate lifetime.
+ ra = new RaPacketBuilder(ROUTER_LIFETIME);
+ ra.addPioOption(PREFIX_VALID_LIFETIME, PREFIX_PREFERRED_LIFETIME, "2001:db8::/64");
+ ByteBuffer prefixOptionPacket = ByteBuffer.wrap(ra.build());
+ verifyRaLifetime(
+ apfFilter, ipClientCallback, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
+
+ ra = new RaPacketBuilder(ROUTER_LIFETIME);
+ ra.addRdnssOption(RDNSS_LIFETIME, "2001:4860:4860::8888", "2001:4860:4860::8844");
+ ByteBuffer rdnssOptionPacket = ByteBuffer.wrap(ra.build());
+ verifyRaLifetime(apfFilter, ipClientCallback, rdnssOptionPacket, RDNSS_LIFETIME);
+
+ final int lowLifetime = 60;
+ ra = new RaPacketBuilder(ROUTER_LIFETIME);
+ ra.addRdnssOption(lowLifetime, "2620:fe::9");
+ ByteBuffer lowLifetimeRdnssOptionPacket = ByteBuffer.wrap(ra.build());
+ verifyRaLifetime(apfFilter, ipClientCallback, lowLifetimeRdnssOptionPacket,
+ ROUTER_LIFETIME);
+
+ ra = new RaPacketBuilder(ROUTER_LIFETIME);
+ ra.addRioOption(ROUTE_LIFETIME, "64:ff9b::/96");
+ ByteBuffer routeInfoOptionPacket = ByteBuffer.wrap(ra.build());
+ verifyRaLifetime(apfFilter, ipClientCallback, routeInfoOptionPacket, ROUTE_LIFETIME);
+
+ // Check that RIOs differing only in the first 4 bytes are different.
+ ra = new RaPacketBuilder(ROUTER_LIFETIME);
+ ra.addRioOption(ROUTE_LIFETIME, "64:ff9b::/64");
+ // Packet should be passed because it is different.
+ program = ipClientCallback.assertProgramUpdateAndGet();
+ assertPass(program, ra.build());
+
+ ra = new RaPacketBuilder(ROUTER_LIFETIME);
+ ra.addDnsslOption(DNSSL_LIFETIME, "test.example.com", "one.more.example.com");
+ ByteBuffer dnsslOptionPacket = ByteBuffer.wrap(ra.build());
+ verifyRaLifetime(apfFilter, ipClientCallback, dnsslOptionPacket, ROUTER_LIFETIME);
+
+ ByteBuffer largeRaPacket = ByteBuffer.wrap(buildLargeRa());
+ verifyRaLifetime(apfFilter, ipClientCallback, largeRaPacket, 300);
+
+ // Verify that current program filters all the RAs (note: ApfFilter.MAX_RAS == 10).
+ program = ipClientCallback.assertProgramUpdateAndGet();
+ verifyRaLifetime(program, basePacket, ROUTER_LIFETIME);
+ verifyRaLifetime(program, newFlowLabelPacket, ROUTER_LIFETIME);
+ verifyRaLifetime(program, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
+ verifyRaLifetime(program, rdnssOptionPacket, RDNSS_LIFETIME);
+ verifyRaLifetime(program, lowLifetimeRdnssOptionPacket, ROUTER_LIFETIME);
+ verifyRaLifetime(program, routeInfoOptionPacket, ROUTE_LIFETIME);
+ verifyRaLifetime(program, dnsslOptionPacket, ROUTER_LIFETIME);
+ verifyRaLifetime(program, largeRaPacket, 300);
+ }
+
+ @Test
+ public void testRaWithDifferentReachableTimeAndRetransTimer() throws Exception {
+ final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ final ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+ mNetworkQuirkMetrics, mDependencies);
+ byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+ final int RA_REACHABLE_TIME = 1800;
+ final int RA_RETRANSMISSION_TIMER = 1234;
+
+ // Create an Ra packet without options
+ // Reachable time = 1800, retransmission timer = 1234
+ RaPacketBuilder ra = new RaPacketBuilder(1800 /* router lft */);
+ ra.setReachableTime(RA_REACHABLE_TIME);
+ ra.setRetransmissionTimer(RA_RETRANSMISSION_TIMER);
+ byte[] raPacket = ra.build();
+ // First RA passes filter
+ assertPass(program, raPacket);
+
+ // Assume apf is shown the given RA, it generates program to filter it.
+ apfFilter.pretendPacketReceived(raPacket);
+ program = ipClientCallback.assertProgramUpdateAndGet();
+ assertDrop(program, raPacket);
+
+ // A packet with different reachable time should be passed.
+ // Reachable time = 2300, retransmission timer = 1234
+ ra.setReachableTime(RA_REACHABLE_TIME + 500);
+ raPacket = ra.build();
+ assertPass(program, raPacket);
+
+ // A packet with different retransmission timer should be passed.
+ // Reachable time = 1800, retransmission timer = 2234
+ ra.setReachableTime(RA_REACHABLE_TIME);
+ ra.setRetransmissionTimer(RA_RETRANSMISSION_TIMER + 1000);
+ raPacket = ra.build();
+ assertPass(program, raPacket);
+ }
+
+ // The ByteBuffer is always created by ByteBuffer#wrap in the helper functions
+ @SuppressWarnings("ByteBufferBackingArray")
+ @Test
+ public void testRaWithProgramInstalledSomeTimeAfterLastSeen() throws Exception {
+ final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ final ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+ mNetworkQuirkMetrics, mDependencies);
+ byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+
+ final int routerLifetime = 1000;
+ final int timePassedSeconds = 12;
+
+ // Verify that when the program is generated and installed some time after RA is last seen
+ // it should be installed with the correct remaining lifetime.
+ ByteBuffer basePacket = ByteBuffer.wrap(new RaPacketBuilder(routerLifetime).build());
+ verifyRaLifetime(apfFilter, ipClientCallback, basePacket, routerLifetime);
+ apfFilter.increaseCurrentTimeSeconds(timePassedSeconds);
+ synchronized (apfFilter) {
+ apfFilter.installNewProgramLocked();
+ }
+ program = ipClientCallback.assertProgramUpdateAndGet();
+ verifyRaLifetime(program, basePacket, routerLifetime, timePassedSeconds);
+
+ // Packet should be passed if the program is installed after 1/6 * lifetime from last seen
+ apfFilter.increaseCurrentTimeSeconds((int) (routerLifetime / 6) - timePassedSeconds - 1);
+ synchronized (apfFilter) {
+ apfFilter.installNewProgramLocked();
+ }
+ program = ipClientCallback.assertProgramUpdateAndGet();
+ assertDrop(program, basePacket.array());
+ apfFilter.increaseCurrentTimeSeconds(1);
+ synchronized (apfFilter) {
+ apfFilter.installNewProgramLocked();
+ }
+ program = ipClientCallback.assertProgramUpdateAndGet();
+ assertPass(program, basePacket.array());
+ }
+
+ /**
+ * Stage a file for testing, i.e. make it native accessible. Given a resource ID,
+ * copy that resource into the app's data directory and return the path to it.
+ */
+ private String stageFile(int rawId) throws Exception {
+ File file = new File(InstrumentationRegistry.getContext().getFilesDir(), "staged_file");
+ new File(file.getParent()).mkdirs();
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ in = InstrumentationRegistry.getContext().getResources().openRawResource(rawId);
+ out = new FileOutputStream(file);
+ Streams.copy(in, out);
+ } finally {
+ if (in != null) in.close();
+ if (out != null) out.close();
+ }
+ return file.getAbsolutePath();
+ }
+
+ private static void put(ByteBuffer buffer, int position, byte[] bytes) {
+ final int original = buffer.position();
+ buffer.position(position);
+ buffer.put(bytes);
+ buffer.position(original);
+ }
+
+ @Test
+ public void testRaParsing() throws Exception {
+ final int maxRandomPacketSize = 512;
+ final Random r = new Random();
+ MockIpClientCallback cb = new MockIpClientCallback();
+ ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mNetworkQuirkMetrics,
+ mDependencies);
+ for (int i = 0; i < 1000; i++) {
+ byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
+ r.nextBytes(packet);
+ try {
+ apfFilter.new Ra(packet, packet.length);
+ } catch (ApfFilter.InvalidRaException e) {
+ } catch (Exception e) {
+ throw new Exception("bad packet: " + HexDump.toHexString(packet), e);
+ }
+ }
+ }
+
+ @Test
+ public void testRaProcessing() throws Exception {
+ final int maxRandomPacketSize = 512;
+ final Random r = new Random();
+ MockIpClientCallback cb = new MockIpClientCallback();
+ ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mNetworkQuirkMetrics,
+ mDependencies);
+ for (int i = 0; i < 1000; i++) {
+ byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
+ r.nextBytes(packet);
+ try {
+ apfFilter.processRa(packet, packet.length);
+ } catch (Exception e) {
+ throw new Exception("bad packet: " + HexDump.toHexString(packet), e);
+ }
+ }
+ }
+
+ @Test
+ public void testMatchedRaUpdatesLifetime() throws Exception {
+ final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ final TestApfFilter apfFilter = new TestApfFilter(mContext, getDefaultConfig(),
+ ipClientCallback, mNetworkQuirkMetrics, mDependencies);
+
+ // Create an RA and build an APF program
+ byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build();
+ apfFilter.pretendPacketReceived(ra);
+ byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+
+ // lifetime dropped significantly, assert pass
+ ra = new RaPacketBuilder(200 /* router lifetime */).build();
+ assertPass(program, ra);
+
+ // update program with the new RA
+ apfFilter.pretendPacketReceived(ra);
+ program = ipClientCallback.assertProgramUpdateAndGet();
+
+ // assert program was updated and new lifetimes were taken into account.
+ assertDrop(program, ra);
+ }
+
+ @Test
+ public void testProcessRaWithInfiniteLifeTimeWithoutCrash() throws Exception {
+ final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ // configure accept_ra_min_lft
+ final ApfConfiguration config = getDefaultConfig();
+ config.acceptRaMinLft = 180;
+ TestApfFilter apfFilter;
+ // Template packet:
+ // Frame 1: 150 bytes on wire (1200 bits), 150 bytes captured (1200 bits)
+ // Ethernet II, Src: Netgear_23:67:2c (28:c6:8e:23:67:2c), Dst: IPv6mcast_01 (33:33:00:00:00:01)
+ // Internet Protocol Version 6, Src: fe80::2ac6:8eff:fe23:672c, Dst: ff02::1
+ // Internet Control Message Protocol v6
+ // Type: Router Advertisement (134)
+ // Code: 0
+ // Checksum: 0x0acd [correct]
+ // Checksum Status: Good
+ // Cur hop limit: 64
+ // Flags: 0xc0, Managed address configuration, Other configuration, Prf (Default Router Preference): Medium
+ // Router lifetime (s): 7000
+ // Reachable time (ms): 0
+ // Retrans timer (ms): 0
+ // ICMPv6 Option (Source link-layer address : 28:c6:8e:23:67:2c)
+ // Type: Source link-layer address (1)
+ // Length: 1 (8 bytes)
+ // Link-layer address: Netgear_23:67:2c (28:c6:8e:23:67:2c)
+ // Source Link-layer address: Netgear_23:67:2c (28:c6:8e:23:67:2c)
+ // ICMPv6 Option (MTU : 1500)
+ // Type: MTU (5)
+ // Length: 1 (8 bytes)
+ // Reserved
+ // MTU: 1500
+ // ICMPv6 Option (Prefix information : 2401:fa00:480:f000::/64)
+ // Type: Prefix information (3)
+ // Length: 4 (32 bytes)
+ // Prefix Length: 64
+ // Flag: 0xc0, On-link flag(L), Autonomous address-configuration flag(A)
+ // Valid Lifetime: Infinity (4294967295)
+ // Preferred Lifetime: Infinity (4294967295)
+ // Reserved
+ // Prefix: 2401:fa00:480:f000::
+ // ICMPv6 Option (Recursive DNS Server 2401:fa00:480:f000::1)
+ // Type: Recursive DNS Server (25)
+ // Length: 3 (24 bytes)
+ // Reserved
+ // Lifetime: 7000
+ // Recursive DNS Servers: 2401:fa00:480:f000::1
+ // ICMPv6 Option (Advertisement Interval : 600000)
+ // Type: Advertisement Interval (7)
+ // Length: 1 (8 bytes)
+ // Reserved
+ // Advertisement Interval: 600000
+ final String packetStringFmt = "33330000000128C68E23672C86DD60054C6B00603AFFFE800000000000002AC68EFFFE23672CFF02000000000000000000000000000186000ACD40C01B580000000000000000010128C68E23672C05010000000005DC030440C0%s000000002401FA000480F00000000000000000001903000000001B582401FA000480F000000000000000000107010000000927C0";
+ final List<String> lifetimes = List.of("FFFFFFFF", "00000000", "00000001", "00001B58");
+ for (String lifetime : lifetimes) {
+ apfFilter = new TestApfFilter(mContext, config, ipClientCallback, mNetworkQuirkMetrics,
+ mDependencies);
+ final byte[] ra = hexStringToByteArray(
+ String.format(packetStringFmt, lifetime + lifetime));
+ // feed the RA into APF and generate the filter, the filter shouldn't crash.
+ apfFilter.pretendPacketReceived(ra);
+ ipClientCallback.assertProgramUpdateAndGet();
+ }
+ }
+
+ // Test for go/apf-ra-filter Case 1a.
+ // Old lifetime is 0
+ @Test
+ public void testAcceptRaMinLftCase1a() throws Exception {
+ final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ // configure accept_ra_min_lft
+ final ApfConfiguration config = getDefaultConfig();
+ config.acceptRaMinLft = 180;
+ final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+ mNetworkQuirkMetrics, mDependencies);
+
+ // Create an initial RA and build an APF program
+ byte[] ra = new RaPacketBuilder(1800 /* router lifetime */)
+ .addPioOption(1800 /*valid*/, 0 /*preferred*/, "2001:db8::/64")
+ .build();
+
+ apfFilter.pretendPacketReceived(ra);
+ byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+
+ // repeated RA is dropped
+ assertDrop(program, ra);
+
+ // PIO preferred lifetime increases
+ ra = new RaPacketBuilder(1800 /* router lifetime */)
+ .addPioOption(1800 /*valid*/, 1 /*preferred*/, "2001:db8::/64")
+ .build();
+ assertPass(program, ra);
+ }
+
+ // Test for go/apf-ra-filter Case 2a.
+ // Old lifetime is > 0
+ @Test
+ public void testAcceptRaMinLftCase2a() throws Exception {
+ final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ // configure accept_ra_min_lft
+ final ApfConfiguration config = getDefaultConfig();
+ config.acceptRaMinLft = 180;
+ final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+ mNetworkQuirkMetrics, mDependencies);
+
+ // Create an initial RA and build an APF program
+ byte[] ra = new RaPacketBuilder(1800 /* router lifetime */)
+ .addPioOption(1800 /*valid*/, 100 /*preferred*/, "2001:db8::/64")
+ .build();
+
+ apfFilter.pretendPacketReceived(ra);
+ byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+
+ // repeated RA is dropped
+ assertDrop(program, ra);
+
+ // PIO preferred lifetime increases
+ ra = new RaPacketBuilder(1800 /* router lifetime */)
+ .addPioOption(1800 /*valid*/, 101 /*preferred*/, "2001:db8::/64")
+ .build();
+ assertPass(program, ra);
+
+ // PIO preferred lifetime decreases significantly
+ ra = new RaPacketBuilder(1800 /* router lifetime */)
+ .addPioOption(1800 /*valid*/, 33 /*preferred*/, "2001:db8::/64")
+ .build();
+ assertPass(program, ra);
+ }
+
+
+ // Test for go/apf-ra-filter Case 1b.
+ // Old lifetime is 0
+ @Test
+ public void testAcceptRaMinLftCase1b() throws Exception {
+ final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ // configure accept_ra_min_lft
+ final ApfConfiguration config = getDefaultConfig();
+ config.acceptRaMinLft = 180;
+ final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+ mNetworkQuirkMetrics, mDependencies);
+
+ // Create an initial RA and build an APF program
+ byte[] ra = new RaPacketBuilder(0 /* router lifetime */).build();
+
+ apfFilter.pretendPacketReceived(ra);
+ byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+
+ // repeated RA is dropped
+ assertDrop(program, ra);
+
+ // lifetime increases below accept_ra_min_lft
+ ra = new RaPacketBuilder(179 /* router lifetime */).build();
+ assertDrop(program, ra);
+
+ // lifetime increases to accept_ra_min_lft
+ ra = new RaPacketBuilder(180 /* router lifetime */).build();
+ assertPass(program, ra);
+ }
+
+
+ // Test for go/apf-ra-filter Case 2b.
+ // Old lifetime is < accept_ra_min_lft (but not 0).
+ @Test
+ public void testAcceptRaMinLftCase2b() throws Exception {
+ final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ // configure accept_ra_min_lft
+ final ApfConfiguration config = getDefaultConfig();
+ config.acceptRaMinLft = 180;
+ final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+ mNetworkQuirkMetrics, mDependencies);
+
+ // Create an initial RA and build an APF program
+ byte[] ra = new RaPacketBuilder(100 /* router lifetime */).build();
+
+ apfFilter.pretendPacketReceived(ra);
+ byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+
+ // repeated RA is dropped
+ assertDrop(program, ra);
+
+ // lifetime increases
+ ra = new RaPacketBuilder(101 /* router lifetime */).build();
+ assertDrop(program, ra);
+
+ // lifetime decreases significantly
+ ra = new RaPacketBuilder(1 /* router lifetime */).build();
+ assertDrop(program, ra);
+
+ // equals accept_ra_min_lft
+ ra = new RaPacketBuilder(180 /* router lifetime */).build();
+ assertPass(program, ra);
+
+ // lifetime is 0
+ ra = new RaPacketBuilder(0 /* router lifetime */).build();
+ assertPass(program, ra);
+ }
+
+ // Test for go/apf-ra-filter Case 3b.
+ // Old lifetime is >= accept_ra_min_lft and <= 3 * accept_ra_min_lft
+ @Test
+ public void testAcceptRaMinLftCase3b() throws Exception {
+ final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ // configure accept_ra_min_lft
+ final ApfConfiguration config = getDefaultConfig();
+ config.acceptRaMinLft = 180;
+ final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+ mNetworkQuirkMetrics, mDependencies);
+
+ // Create an initial RA and build an APF program
+ byte[] ra = new RaPacketBuilder(200 /* router lifetime */).build();
+
+ apfFilter.pretendPacketReceived(ra);
+ byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+
+ // repeated RA is dropped
+ assertDrop(program, ra);
+
+ // lifetime increases
+ ra = new RaPacketBuilder(201 /* router lifetime */).build();
+ assertPass(program, ra);
+
+ // lifetime is below accept_ra_min_lft (but not 0)
+ ra = new RaPacketBuilder(1 /* router lifetime */).build();
+ assertDrop(program, ra);
+
+ // lifetime is 0
+ ra = new RaPacketBuilder(0 /* router lifetime */).build();
+ assertPass(program, ra);
+ }
+
+ // Test for go/apf-ra-filter Case 4b.
+ // Old lifetime is > 3 * accept_ra_min_lft
+ @Test
+ public void testAcceptRaMinLftCase4b() throws Exception {
+ final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ // configure accept_ra_min_lft
+ final ApfConfiguration config = getDefaultConfig();
+ config.acceptRaMinLft = 180;
+ final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+ mNetworkQuirkMetrics, mDependencies);
+
+ // Create an initial RA and build an APF program
+ byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build();
+
+ apfFilter.pretendPacketReceived(ra);
+ byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+
+ // repeated RA is dropped
+ assertDrop(program, ra);
+
+ // lifetime increases
+ ra = new RaPacketBuilder(1801 /* router lifetime */).build();
+ assertPass(program, ra);
+
+ // lifetime is 1/3 of old lft
+ ra = new RaPacketBuilder(600 /* router lifetime */).build();
+ assertDrop(program, ra);
+
+ // lifetime is below 1/3 of old lft
+ ra = new RaPacketBuilder(599 /* router lifetime */).build();
+ assertPass(program, ra);
+
+ // lifetime is below accept_ra_min_lft (but not 0)
+ ra = new RaPacketBuilder(1 /* router lifetime */).build();
+ assertDrop(program, ra);
+
+ // lifetime is 0
+ ra = new RaPacketBuilder(0 /* router lifetime */).build();
+ assertPass(program, ra);
+ }
+
+ @Test
+ public void testRaFilterIsUpdated() throws Exception {
+ final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ // configure accept_ra_min_lft
+ final ApfConfiguration config = getDefaultConfig();
+ config.acceptRaMinLft = 180;
+ final TestApfFilter apfFilter = new TestApfFilter(mContext, config, ipClientCallback,
+ mNetworkQuirkMetrics, mDependencies);
+
+ // Create an initial RA and build an APF program
+ byte[] ra = new RaPacketBuilder(1800 /* router lifetime */).build();
+ apfFilter.pretendPacketReceived(ra);
+ byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+
+ // repeated RA is dropped.
+ assertDrop(program, ra);
+
+ // updated RA is passed, repeated RA is dropped after program update.
+ ra = new RaPacketBuilder(599 /* router lifetime */).build();
+ assertPass(program, ra);
+ apfFilter.pretendPacketReceived(ra);
+ program = ipClientCallback.assertProgramUpdateAndGet();
+ assertDrop(program, ra);
+
+ ra = new RaPacketBuilder(180 /* router lifetime */).build();
+ assertPass(program, ra);
+ apfFilter.pretendPacketReceived(ra);
+ program = ipClientCallback.assertProgramUpdateAndGet();
+ assertDrop(program, ra);
+
+ ra = new RaPacketBuilder(0 /* router lifetime */).build();
+ assertPass(program, ra);
+ apfFilter.pretendPacketReceived(ra);
+ program = ipClientCallback.assertProgramUpdateAndGet();
+ assertDrop(program, ra);
+
+ ra = new RaPacketBuilder(180 /* router lifetime */).build();
+ assertPass(program, ra);
+ apfFilter.pretendPacketReceived(ra);
+ program = ipClientCallback.assertProgramUpdateAndGet();
+ assertDrop(program, ra);
+
+ ra = new RaPacketBuilder(599 /* router lifetime */).build();
+ assertPass(program, ra);
+ apfFilter.pretendPacketReceived(ra);
+ program = ipClientCallback.assertProgramUpdateAndGet();
+ assertDrop(program, ra);
+
+ ra = new RaPacketBuilder(1800 /* router lifetime */).build();
+ assertPass(program, ra);
+ apfFilter.pretendPacketReceived(ra);
+ program = ipClientCallback.assertProgramUpdateAndGet();
+ assertDrop(program, ra);
+ }
+
+ private TestAndroidPacketFilter makeTestApfFilter(ApfConfiguration config,
+ MockIpClientCallback ipClientCallback) throws Exception {
+ return new TestLegacyApfFilter(mContext, config, ipClientCallback, mIpConnectivityLog,
+ mNetworkQuirkMetrics, mDependencies, mClock);
+ }
+
+
+ @Test
+ public void testInstallPacketFilterFailure_LegacyApfFilter() throws Exception {
+ final MockIpClientCallback ipClientCallback = new MockIpClientCallback(false);
+ final ApfConfiguration config = getDefaultConfig();
+ final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback);
+ verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE);
+ verify(mNetworkQuirkMetrics).statsWrite();
+ reset(mNetworkQuirkMetrics);
+ synchronized (apfFilter) {
+ apfFilter.installNewProgramLocked();
+ }
+ verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE);
+ verify(mNetworkQuirkMetrics).statsWrite();
+ }
+
+ @Test
+ public void testApfProgramOverSize_LegacyApfFilter() throws Exception {
+ final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ final ApfConfiguration config = getDefaultConfig();
+ final ApfCapabilities capabilities = new ApfCapabilities(2, 512, ARPHRD_ETHER);
+ config.apfCapabilities = capabilities;
+ final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback);
+ byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+ final byte[] ra = buildLargeRa();
+ apfFilter.pretendPacketReceived(ra);
+ // The generated program size will be 529, which is larger than 512
+ program = ipClientCallback.assertProgramUpdateAndGet();
+ verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE);
+ verify(mNetworkQuirkMetrics).statsWrite();
+ }
+
+ @Test
+ public void testGenerateApfProgramException_LegacyApfFilter() throws Exception {
+ final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ final ApfConfiguration config = getDefaultConfig();
+ final TestAndroidPacketFilter apfFilter;
+ apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback, mIpConnectivityLog,
+ mNetworkQuirkMetrics, mDependencies,
+ true /* throwsExceptionWhenGeneratesProgram */);
+ synchronized (apfFilter) {
+ apfFilter.installNewProgramLocked();
+ }
+ verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_GENERATE_FILTER_EXCEPTION);
+ verify(mNetworkQuirkMetrics).statsWrite();
+ }
+
+ @Test
+ public void testApfSessionInfoMetrics_LegacyApfFilter() throws Exception {
+ final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ final ApfConfiguration config = getDefaultConfig();
+ final ApfCapabilities capabilities = new ApfCapabilities(4, 4096, ARPHRD_ETHER);
+ config.apfCapabilities = capabilities;
+ final long startTimeMs = 12345;
+ final long durationTimeMs = config.minMetricsSessionDurationMs;
+ doReturn(startTimeMs).when(mClock).elapsedRealtime();
+ final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback);
+ int maxProgramSize = 0;
+ int numProgramUpdated = 0;
+ byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+ maxProgramSize = Math.max(maxProgramSize, program.length);
+ numProgramUpdated++;
+
+ final byte[] data = new byte[Counter.totalSize()];
+ final byte[] expectedData = data.clone();
+ final int totalPacketsCounterIdx = Counter.totalSize() + Counter.TOTAL_PACKETS.offset();
+ final int passedIpv6IcmpCounterIdx =
+ Counter.totalSize() + Counter.PASSED_IPV6_ICMP.offset();
+ final int droppedIpv4MulticastIdx =
+ Counter.totalSize() + Counter.DROPPED_IPV4_MULTICAST.offset();
+
+ // Receive an RA packet (passed).
+ final byte[] ra = buildLargeRa();
+ expectedData[totalPacketsCounterIdx + 3] += 1;
+ expectedData[passedIpv6IcmpCounterIdx + 3] += 1;
+ assertDataMemoryContentsIgnoreVersion(PASS, program, ra, data, expectedData);
+ apfFilter.pretendPacketReceived(ra);
+ program = ipClientCallback.assertProgramUpdateAndGet();
+ maxProgramSize = Math.max(maxProgramSize, program.length);
+ numProgramUpdated++;
+
+ apfFilter.setMulticastFilter(true);
+ // setMulticastFilter will trigger program installation.
+ program = ipClientCallback.assertProgramUpdateAndGet();
+ maxProgramSize = Math.max(maxProgramSize, program.length);
+ numProgramUpdated++;
+
+ // Receive IPv4 multicast packet (dropped).
+ final byte[] multicastIpv4Addr = {(byte) 224, 0, 0, 1};
+ ByteBuffer mcastv4packet = makeIpv4Packet(IPPROTO_UDP);
+ put(mcastv4packet, IPV4_DEST_ADDR_OFFSET, multicastIpv4Addr);
+ expectedData[totalPacketsCounterIdx + 3] += 1;
+ expectedData[droppedIpv4MulticastIdx + 3] += 1;
+ assertDataMemoryContentsIgnoreVersion(DROP, program, mcastv4packet.array(), data,
+ expectedData);
+
+ // Set data snapshot and update counters.
+ apfFilter.setDataSnapshot(data);
+
+ // Write metrics data to statsd pipeline when shutdown.
+ doReturn(startTimeMs + durationTimeMs).when(mClock).elapsedRealtime();
+ apfFilter.shutdown();
+ verify(mApfSessionInfoMetrics).setVersion(4);
+ verify(mApfSessionInfoMetrics).setMemorySize(4096);
+
+ // Verify Counters
+ final Map<Counter, Long> expectedCounters = Map.of(Counter.TOTAL_PACKETS, 2L,
+ Counter.PASSED_IPV6_ICMP, 1L, Counter.DROPPED_IPV4_MULTICAST, 1L);
+ final ArgumentCaptor<Counter> counterCaptor = ArgumentCaptor.forClass(Counter.class);
+ final ArgumentCaptor<Long> valueCaptor = ArgumentCaptor.forClass(Long.class);
+ verify(mApfSessionInfoMetrics, times(expectedCounters.size())).addApfCounter(
+ counterCaptor.capture(), valueCaptor.capture());
+ final List<Counter> counters = counterCaptor.getAllValues();
+ final List<Long> values = valueCaptor.getAllValues();
+ final ArrayMap<Counter, Long> capturedCounters = new ArrayMap<>();
+ for (int i = 0; i < counters.size(); i++) {
+ capturedCounters.put(counters.get(i), values.get(i));
+ }
+ assertEquals(expectedCounters, capturedCounters);
+
+ verify(mApfSessionInfoMetrics).setApfSessionDurationSeconds(
+ (int) (durationTimeMs / DateUtils.SECOND_IN_MILLIS));
+ verify(mApfSessionInfoMetrics).setNumOfTimesApfProgramUpdated(numProgramUpdated);
+ verify(mApfSessionInfoMetrics).setMaxProgramSize(maxProgramSize);
+ verify(mApfSessionInfoMetrics).statsWrite();
+ }
+
+ @Test
+ public void testIpClientRaInfoMetrics_LegacyApfFilter() throws Exception {
+ final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ final ApfConfiguration config = getDefaultConfig();
+ final long startTimeMs = 12345;
+ final long durationTimeMs = config.minMetricsSessionDurationMs;
+ doReturn(startTimeMs).when(mClock).elapsedRealtime();
+ final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback);
+ byte[] program = ipClientCallback.assertProgramUpdateAndGet();
+
+ final int routerLifetime = 1000;
+ final int prefixValidLifetime = 200;
+ final int prefixPreferredLifetime = 100;
+ final int rdnssLifetime = 300;
+ final int routeLifetime = 400;
+
+ // Construct 2 RAs with partial lifetimes larger than predefined constants
+ final RaPacketBuilder ra1 = new RaPacketBuilder(routerLifetime);
+ ra1.addPioOption(prefixValidLifetime + 123, prefixPreferredLifetime, "2001:db8::/64");
+ ra1.addRdnssOption(rdnssLifetime, "2001:4860:4860::8888", "2001:4860:4860::8844");
+ ra1.addRioOption(routeLifetime + 456, "64:ff9b::/96");
+ final RaPacketBuilder ra2 = new RaPacketBuilder(routerLifetime + 123);
+ ra2.addPioOption(prefixValidLifetime, prefixPreferredLifetime, "2001:db9::/64");
+ ra2.addRdnssOption(rdnssLifetime + 456, "2001:4860:4860::8888", "2001:4860:4860::8844");
+ ra2.addRioOption(routeLifetime, "64:ff9b::/96");
+
+ // Construct an invalid RA packet
+ final RaPacketBuilder raInvalid = new RaPacketBuilder(routerLifetime);
+ raInvalid.addZeroLengthOption();
+
+ // Construct 4 different kinds of zero lifetime RAs
+ final RaPacketBuilder raZeroRouterLifetime = new RaPacketBuilder(0 /* routerLft */);
+ final RaPacketBuilder raZeroPioValidLifetime = new RaPacketBuilder(routerLifetime);
+ raZeroPioValidLifetime.addPioOption(0, prefixPreferredLifetime, "2001:db10::/64");
+ final RaPacketBuilder raZeroRdnssLifetime = new RaPacketBuilder(routerLifetime);
+ raZeroRdnssLifetime.addPioOption(
+ prefixValidLifetime, prefixPreferredLifetime, "2001:db11::/64");
+ raZeroRdnssLifetime.addRdnssOption(0, "2001:4860:4860::8888", "2001:4860:4860::8844");
+ final RaPacketBuilder raZeroRioRouteLifetime = new RaPacketBuilder(routerLifetime);
+ raZeroRioRouteLifetime.addPioOption(
+ prefixValidLifetime, prefixPreferredLifetime, "2001:db12::/64");
+ raZeroRioRouteLifetime.addRioOption(0, "64:ff9b::/96");
+
+ // Inject RA packets. Calling assertProgramUpdateAndGet()/assertNoProgramUpdate() is to make
+ // sure that the RA packet has been processed.
+ apfFilter.pretendPacketReceived(ra1.build());
+ program = ipClientCallback.assertProgramUpdateAndGet();
+ apfFilter.pretendPacketReceived(ra2.build());
+ program = ipClientCallback.assertProgramUpdateAndGet();
+ apfFilter.pretendPacketReceived(raInvalid.build());
+ ipClientCallback.assertNoProgramUpdate();
+ apfFilter.pretendPacketReceived(raZeroRouterLifetime.build());
+ ipClientCallback.assertNoProgramUpdate();
+ apfFilter.pretendPacketReceived(raZeroPioValidLifetime.build());
+ ipClientCallback.assertNoProgramUpdate();
+ apfFilter.pretendPacketReceived(raZeroRdnssLifetime.build());
+ ipClientCallback.assertNoProgramUpdate();
+ apfFilter.pretendPacketReceived(raZeroRioRouteLifetime.build());
+ ipClientCallback.assertNoProgramUpdate();
+
+ // Write metrics data to statsd pipeline when shutdown.
+ doReturn(startTimeMs + durationTimeMs).when(mClock).elapsedRealtime();
+ apfFilter.shutdown();
+
+ // Verify each metric fields in IpClientRaInfoMetrics.
+ // LegacyApfFilter will purge expired RAs before adding new RA. Every time a new zero
+ // lifetime RA is received, zero lifetime RAs except the newly added one will be
+ // cleared, so the number of distinct RAs is 3 (ra1, ra2 and the newly added RA).
+ verify(mIpClientRaInfoMetrics).setMaxNumberOfDistinctRas(3);
+ verify(mIpClientRaInfoMetrics).setNumberOfZeroLifetimeRas(4);
+ verify(mIpClientRaInfoMetrics).setNumberOfParsingErrorRas(1);
+ verify(mIpClientRaInfoMetrics).setLowestRouterLifetimeSeconds(routerLifetime);
+ verify(mIpClientRaInfoMetrics).setLowestPioValidLifetimeSeconds(prefixValidLifetime);
+ verify(mIpClientRaInfoMetrics).setLowestRioRouteLifetimeSeconds(routeLifetime);
+ verify(mIpClientRaInfoMetrics).setLowestRdnssLifetimeSeconds(rdnssLifetime);
+ verify(mIpClientRaInfoMetrics).statsWrite();
+ }
+
+ @Test
+ public void testNoMetricsWrittenForShortDuration_LegacyApfFilter() throws Exception {
+ final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
+ final ApfConfiguration config = getDefaultConfig();
+ final long startTimeMs = 12345;
+ final long durationTimeMs = config.minMetricsSessionDurationMs;
+
+ // Verify no metrics data written to statsd for duration less than durationTimeMs.
+ doReturn(startTimeMs).when(mClock).elapsedRealtime();
+ final TestAndroidPacketFilter apfFilter = makeTestApfFilter(config, ipClientCallback);
+ doReturn(startTimeMs + durationTimeMs - 1).when(mClock).elapsedRealtime();
+ apfFilter.shutdown();
+ verify(mApfSessionInfoMetrics, never()).statsWrite();
+ verify(mIpClientRaInfoMetrics, never()).statsWrite();
+
+ // Verify metrics data written to statsd for duration greater than or equal to
+ // durationTimeMs.
+ ApfFilter.Clock clock = mock(ApfFilter.Clock.class);
+ doReturn(startTimeMs).when(clock).elapsedRealtime();
+ final TestAndroidPacketFilter apfFilter2 = new TestApfFilter(mContext, config,
+ ipClientCallback, mNetworkQuirkMetrics, mDependencies, clock);
+ doReturn(startTimeMs + durationTimeMs).when(clock).elapsedRealtime();
+ apfFilter2.shutdown();
+ verify(mApfSessionInfoMetrics).statsWrite();
+ verify(mIpClientRaInfoMetrics).statsWrite();
+ }
+}
diff --git a/tests/unit/src/android/net/ip/IpClientTest.java b/tests/unit/src/android/net/ip/IpClientTest.java
index 8d99b11..00982c7 100644
--- a/tests/unit/src/android/net/ip/IpClientTest.java
+++ b/tests/unit/src/android/net/ip/IpClientTest.java
@@ -83,6 +83,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.netlink.NduseroptMessage;
import com.android.net.module.util.netlink.RtNetlinkAddressMessage;
@@ -884,7 +885,8 @@
any(), configCaptor.capture(), any(), any(), any(), anyBoolean());
final ApfConfiguration actual = configCaptor.getValue();
assertNotNull(actual);
- assertEquals(4, actual.apfCapabilities.apfVersionSupported);
+ int expectedApfVersion = SdkLevel.isAtLeastS() ? 4 : 3;
+ assertEquals(expectedApfVersion, actual.apfCapabilities.apfVersionSupported);
assertEquals(4096, actual.apfCapabilities.maximumApfProgramSize);
assertEquals(4, actual.apfCapabilities.apfPacketFormat);