Snap for 12792407 from 72262523b931e2434d691ced3d93497fd6fb5106 to mainline-appsearch-release

Change-Id: I8da83f57d1b37745a1f117ad7238df7e44c1272c
diff --git a/src/android/net/apf/AndroidPacketFilter.java b/src/android/net/apf/AndroidPacketFilter.java
index c88587b..c9f8aba 100644
--- a/src/android/net/apf/AndroidPacketFilter.java
+++ b/src/android/net/apf/AndroidPacketFilter.java
@@ -120,4 +120,9 @@
     default boolean shouldEnableMdnsOffload() {
         return false;
     }
+
+    /**
+     * Update the multicast addresses that will be used by APF.
+     */
+    default void updateIPv4MulticastAddrs() {}
 }
diff --git a/src/android/net/apf/ApfConstants.java b/src/android/net/apf/ApfConstants.java
index a23e970..5aa8a6a 100644
--- a/src/android/net/apf/ApfConstants.java
+++ b/src/android/net/apf/ApfConstants.java
@@ -15,6 +15,8 @@
  */
 package android.net.apf;
 
+import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
+
 /**
  * The class which declares constants used in ApfFilter and unit tests.
  */
@@ -60,6 +62,10 @@
     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 ICMP4_TYPE_OFFSET = ETH_HEADER_LEN + IPV4_HEADER_MIN_LEN;
+    public static final int ICMP4_CHECKSUM_OFFSET = ETH_HEADER_LEN + IPV4_HEADER_MIN_LEN + 2;
+    public static final int ICMP4_CONTENT_OFFSET = ETH_HEADER_LEN + IPV4_HEADER_MIN_LEN + 4;
+
     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;
diff --git a/src/android/net/apf/ApfCounterTracker.java b/src/android/net/apf/ApfCounterTracker.java
index 5bd9877..c2c47fa 100644
--- a/src/android/net/apf/ApfCounterTracker.java
+++ b/src/android/net/apf/ApfCounterTracker.java
@@ -26,7 +26,7 @@
 import java.util.Map;
 
 /**
- * Common counter class for {@code ApfFilter} and {@code LegacyApfFilter}.
+ * Counter class for {@code ApfFilter}.
  *
  * @hide
  */
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java
index 08a370d..68d2136 100644
--- a/src/android/net/apf/ApfFilter.java
+++ b/src/android/net/apf/ApfFilter.java
@@ -350,6 +350,10 @@
     // Our tentative IPv6 addresses
     private Set<Inet6Address> mIPv6TentativeAddresses = new ArraySet<>();
 
+    // Our joined IPv4 multicast addresses
+    @VisibleForTesting
+    public Set<Inet4Address> mIPv4MulticastAddresses = new ArraySet<>();
+
     // Whether CLAT is enabled.
     private boolean mHasClat;
 
@@ -603,6 +607,14 @@
         public int getNdTrafficClass(@NonNull String ifname) {
             return ProcfsParsingUtils.getNdTrafficClass(ifname);
         }
+
+        /**
+         * Loads the existing IPv4 multicast addresses from the file
+         * `/proc/net/igmp`.
+         */
+        public List<Inet4Address> getIPv4MulticastAddresses(@NonNull String ifname) {
+            return ProcfsParsingUtils.getIPv4MulticastAddresses(ifname);
+        }
     }
 
     @Override
@@ -1321,7 +1333,7 @@
 
         NattKeepaliveResponse(final NattKeepalivePacketDataParcelable sentKeepalivePacket) {
             mPacket = new NattKeepaliveResponseData(sentKeepalivePacket);
-            mSrcDstAddr = concatArrays(mPacket.srcAddress, mPacket.dstAddress);
+            mSrcDstAddr = CollectionUtils.concatArrays(mPacket.srcAddress, mPacket.dstAddress);
             mPortFingerprint = generatePortFingerprint(mPacket.srcPort, mPacket.dstPort);
         }
 
@@ -1445,7 +1457,8 @@
             this(new TcpKeepaliveAckData(sentKeepalivePacket));
         }
         TcpKeepaliveAckV4(final TcpKeepaliveAckData packet) {
-            super(packet, concatArrays(packet.srcAddress, packet.dstAddress) /* srcDstAddr */);
+            super(packet, CollectionUtils.concatArrays(packet.srcAddress,
+                    packet.dstAddress) /* srcDstAddr */);
         }
 
         @Override
@@ -1487,7 +1500,8 @@
             this(new TcpKeepaliveAckData(sentKeepalivePacket));
         }
         TcpKeepaliveAckV6(final TcpKeepaliveAckData packet) {
-            super(packet, concatArrays(packet.srcAddress, packet.dstAddress) /* srcDstAddr */);
+            super(packet, CollectionUtils.concatArrays(packet.srcAddress,
+                    packet.dstAddress) /* srcDstAddr */);
         }
 
         @Override
@@ -2681,6 +2695,17 @@
     }
 
     @Override
+    public void updateIPv4MulticastAddrs() {
+        final Set<Inet4Address> mcastAddrs =
+                new ArraySet<>(mDependencies.getIPv4MulticastAddresses(mInterfaceParams.name));
+
+        if (!mIPv4MulticastAddresses.equals(mcastAddrs)) {
+            mIPv4MulticastAddresses = mcastAddrs;
+            installNewProgram();
+        }
+    }
+
+    @Override
     public boolean supportNdOffload() {
         return shouldUseApfV6Generator() && mShouldHandleNdOffload;
     }
@@ -3010,20 +3035,6 @@
                 + (uint8(bytes[3]));
     }
 
-    private static byte[] concatArrays(final byte[]... arr) {
-        int size = 0;
-        for (byte[] a : arr) {
-            size += a.length;
-        }
-        final byte[] result = new byte[size];
-        int offset = 0;
-        for (byte[] a : arr) {
-            System.arraycopy(a, 0, result, offset, a.length);
-            offset += a.length;
-        }
-        return result;
-    }
-
     private void sendNetworkQuirkMetrics(final NetworkQuirkEvent event) {
         if (mNetworkQuirkMetrics == null) return;
         mNetworkQuirkMetrics.setEvent(event);
diff --git a/src/android/net/apf/ApfMdnsUtils.java b/src/android/net/apf/ApfMdnsUtils.java
index 7666864..b2695c0 100644
--- a/src/android/net/apf/ApfMdnsUtils.java
+++ b/src/android/net/apf/ApfMdnsUtils.java
@@ -27,6 +27,7 @@
 import android.os.Build;
 import android.util.ArraySet;
 
+import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.DnsUtils;
 
 import java.io.ByteArrayOutputStream;
@@ -56,14 +57,6 @@
         allMatchers.add(matcher);
     }
 
-    private static String[] prepend(String[] suffix, String... prefixes) {
-        String[] result = new String[prefixes.length + suffix.length];
-        System.arraycopy(prefixes, 0, result, 0, prefixes.length);
-        System.arraycopy(suffix, 0, result, prefixes.length, suffix.length);
-        return result;
-    }
-
-
     /**
      * Extract the offload rules from the list of offloadServiceInfos. The rules are returned in
      * priority order (most important first). If there are too many rules, APF could decide only
@@ -91,7 +84,8 @@
             List<MdnsOffloadRule.Matcher> matcherGroup = new ArrayList<>();
             final OffloadServiceInfo.Key key = info.getKey();
             final String[] serviceTypeLabels = key.getServiceType().split("\\.", 0);
-            final String[] fullQualifiedName = prepend(serviceTypeLabels, key.getServiceName());
+            final String[] fullQualifiedName = CollectionUtils.prependArray(String.class,
+                    serviceTypeLabels, key.getServiceName());
             final byte[] replyPayload = info.getOffloadPayload();
             final byte[] encodedServiceType = encodeQname(serviceTypeLabels);
            // If (QTYPE == PTR) and (QNAME == mServiceName + mServiceType), then reply.
@@ -106,7 +100,8 @@
             boolean tooManySubtypes = subTypes.size() > MAX_SUPPORTED_SUBTYPES;
             if (tooManySubtypes) {
                 // If (QTYPE == PTR) and (QNAME == wildcard + _sub + mServiceType), then fail open.
-                final String[] serviceTypeSuffix = prepend(serviceTypeLabels, "_sub");
+                final String[] serviceTypeSuffix = CollectionUtils.prependArray(String.class,
+                        serviceTypeLabels, "_sub");
                 final ByteArrayOutputStream buf = new ByteArrayOutputStream();
                 // byte = 0xff is used as a wildcard.
                 buf.write(-1);
@@ -117,7 +112,8 @@
             } else {
                 // If (QTYPE == PTR) and (QNAME == subType + _sub + mServiceType), then reply.
                 for (String subType : subTypes) {
-                    final String[] fullServiceType = prepend(serviceTypeLabels, subType, "_sub");
+                    final String[] fullServiceType = CollectionUtils.prependArray(String.class,
+                            serviceTypeLabels, subType, "_sub");
                     final byte[] encodedFullServiceType = encodeQname(fullServiceType);
                     // If (QTYPE == PTR) and (QNAME == subType + "_sub" + mServiceType), then reply.
                     final MdnsOffloadRule.Matcher subtypePtrMatcher = new MdnsOffloadRule.Matcher(
diff --git a/src/android/net/apf/LegacyApfFilter.java b/src/android/net/apf/LegacyApfFilter.java
deleted file mode 100644
index 2cd0eec..0000000
--- a/src/android/net/apf/LegacyApfFilter.java
+++ /dev/null
@@ -1,2439 +0,0 @@
-/*
- * Copyright (C) 2016 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.BaseApfGenerator.MemorySlot;
-import static android.net.apf.BaseApfGenerator.Register.R0;
-import static android.net.apf.BaseApfGenerator.Register.R1;
-import static android.net.util.SocketUtils.makePacketSocketAddress;
-import static android.system.OsConstants.AF_PACKET;
-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 android.system.OsConstants.SOCK_RAW;
-
-import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
-import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN;
-
-import android.annotation.Nullable;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-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.BaseApfGenerator.IllegalInstructionException;
-import android.net.ip.IpClient.IpClientCallbacksWrapper;
-import android.net.metrics.ApfProgramEvent;
-import android.net.metrics.ApfStats;
-import android.net.metrics.IpConnectivityLog;
-import android.net.metrics.RaEvent;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.stats.connectivity.NetworkQuirkEvent;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.HexDump;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.net.module.util.CollectionUtils;
-import com.android.net.module.util.ConnectivityUtils;
-import com.android.net.module.util.InterfaceParams;
-import com.android.net.module.util.SocketUtils;
-import com.android.networkstack.metrics.ApfSessionInfoMetrics;
-import com.android.networkstack.metrics.IpClientRaInfoMetrics;
-import com.android.networkstack.metrics.NetworkQuirkMetrics;
-import com.android.networkstack.util.NetworkStackUtils;
-
-import java.io.ByteArrayOutputStream;
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.SocketAddress;
-import java.net.SocketException;
-import java.net.UnknownHostException;
-import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-/**
- * For networks that support packet filtering via APF programs, {@code ApfFilter}
- * listens for IPv6 ICMPv6 router advertisements (RAs) and generates APF programs to
- * filter out redundant duplicate ones.
- *
- * Threading model:
- * A collection of RAs we've received is kept in mRas. Generating APF programs uses mRas to
- * know what RAs to filter for, thus generating APF programs is dependent on mRas.
- * mRas can be accessed by multiple threads:
- * - ReceiveThread, which listens for RAs and adds them to mRas, and generates APF programs.
- * - callers of:
- *    - setMulticastFilter(), which can cause an APF program to be generated.
- *    - dump(), which dumps mRas among other things.
- *    - shutdown(), which clears mRas.
- * So access to mRas is synchronized.
- *
- * @hide
- */
-public class LegacyApfFilter implements AndroidPacketFilter {
-
-    // Enums describing the outcome of receiving an RA packet.
-    private static enum ProcessRaResult {
-        MATCH,          // Received RA matched a known RA
-        DROPPED,        // Received RA ignored due to MAX_RAS
-        PARSE_ERROR,    // Received RA could not be parsed
-        ZERO_LIFETIME,  // Received RA had 0 lifetime
-        UPDATE_NEW_RA,  // APF program updated for new RA
-        UPDATE_EXPIRY   // APF program updated for expiry
-    }
-
-    /**
-     * When APFv4 is supported, loads R1 with the offset of the specified counter.
-     */
-    private void maybeSetupCounter(ApfV4Generator gen, Counter c) {
-        if (hasDataAccess(mApfVersionSupported)) {
-            gen.addLoadImmediate(R1, c.offset());
-        }
-    }
-
-    // When APFv4 is supported, these point to the trampolines generated by emitEpilogue().
-    // Otherwise, they're just aliases for PASS_LABEL and DROP_LABEL.
-    private final String mCountAndPassLabel;
-    private final String mCountAndDropLabel;
-
-    /** A wrapper class of {@link SystemClock} to be mocked in unit tests. */
-    public static class Clock {
-        /**
-         * @see SystemClock#elapsedRealtime
-         */
-        public long elapsedRealtime() {
-            return SystemClock.elapsedRealtime();
-        }
-    }
-
-    // Thread to listen for RAs.
-    @VisibleForTesting
-    public class ReceiveThread extends Thread {
-        private final byte[] mPacket = new byte[1514];
-        private final FileDescriptor mSocket;
-        private final long mStart = mClock.elapsedRealtime();
-
-        private int mReceivedRas = 0;
-        private int mMatchingRas = 0;
-        private int mDroppedRas = 0;
-        private int mParseErrors = 0;
-        private int mZeroLifetimeRas = 0;
-        private int mProgramUpdates = 0;
-
-        private volatile boolean mStopped;
-
-        public ReceiveThread(FileDescriptor socket) {
-            mSocket = socket;
-        }
-
-        public void halt() {
-            mStopped = true;
-            // Interrupts the read() call the thread is blocked in.
-            SocketUtils.closeSocketQuietly(mSocket);
-        }
-
-        @Override
-        public void run() {
-            log("begin monitoring");
-            while (!mStopped) {
-                try {
-                    int length = Os.read(mSocket, mPacket, 0, mPacket.length);
-                    updateStats(processRa(mPacket, length));
-                } catch (IOException|ErrnoException e) {
-                    if (!mStopped) {
-                        Log.e(TAG, "Read error", e);
-                    }
-                }
-            }
-            logStats();
-        }
-
-        private void updateStats(ProcessRaResult result) {
-            mReceivedRas++;
-            switch(result) {
-                case MATCH:
-                    mMatchingRas++;
-                    return;
-                case DROPPED:
-                    mDroppedRas++;
-                    return;
-                case PARSE_ERROR:
-                    mParseErrors++;
-                    return;
-                case ZERO_LIFETIME:
-                    mZeroLifetimeRas++;
-                    return;
-                case UPDATE_EXPIRY:
-                    mMatchingRas++;
-                    mProgramUpdates++;
-                    return;
-                case UPDATE_NEW_RA:
-                    mProgramUpdates++;
-                    return;
-            }
-        }
-
-        private void logStats() {
-            final long nowMs = mClock.elapsedRealtime();
-            synchronized (LegacyApfFilter.this) {
-                final ApfStats stats = new ApfStats.Builder()
-                        .setReceivedRas(mReceivedRas)
-                        .setMatchingRas(mMatchingRas)
-                        .setDroppedRas(mDroppedRas)
-                        .setParseErrors(mParseErrors)
-                        .setZeroLifetimeRas(mZeroLifetimeRas)
-                        .setProgramUpdates(mProgramUpdates)
-                        .setDurationMs(nowMs - mStart)
-                        .setMaxProgramSize(mMaximumApfProgramSize)
-                        .setProgramUpdatesAll(mNumProgramUpdates)
-                        .setProgramUpdatesAllowingMulticast(mNumProgramUpdatesAllowingMulticast)
-                        .build();
-                mMetricsLog.log(stats);
-                logApfProgramEventLocked(nowMs / DateUtils.SECOND_IN_MILLIS);
-            }
-        }
-    }
-
-    private static final String TAG = "ApfFilter";
-    private static final boolean DBG = true;
-    private static final boolean VDBG = false;
-
-    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 int ETH_TYPE_MIN = 0x0600;
-    private static final int ETH_TYPE_MAX = 0xFFFF;
-    // TODO: Make these offsets relative to end of link-layer header; don't include ETH_HEADER_LEN.
-    private static final int IPV4_TOTAL_LENGTH_OFFSET = ETH_HEADER_LEN + 2;
-    private static final int IPV4_FRAGMENT_OFFSET_OFFSET = ETH_HEADER_LEN + 6;
-    // Endianness is not an issue for this constant because the APF interpreter always operates in
-    // network byte order.
-    private static final int IPV4_FRAGMENT_OFFSET_MASK = 0x1fff;
-    private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9;
-    private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16;
-    private static final int IPV4_ANY_HOST_ADDRESS = 0;
-    private static final int IPV4_BROADCAST_ADDRESS = -1; // 255.255.255.255
-    private static final int IPV4_HEADER_LEN = 20; // Without options
-
-    // Traffic class and Flow label are not byte aligned. Luckily we
-    // don't care about either value so we'll consider bytes 1-3 of the
-    // IPv6 header as don't care.
-    private static final int IPV6_FLOW_LABEL_OFFSET = ETH_HEADER_LEN + 1;
-    private static final int IPV6_FLOW_LABEL_LEN = 3;
-    private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6;
-    private static final int IPV6_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 8;
-    private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24;
-    private static final int IPV6_HEADER_LEN = 40;
-    // 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 int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
-
-    private static final int IPPROTO_HOPOPTS = 0;
-
-    // NOTE: this must be added to the IPv4 header length in MemorySlot.IPV4_HEADER_SIZE
-    private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 2;
-    private static final int UDP_HEADER_LEN = 8;
-
-    private static final int TCP_HEADER_SIZE_OFFSET = 12;
-
-    private static final int DHCP_CLIENT_PORT = 68;
-    // NOTE: this must be added to the IPv4 header length in MemorySlot.IPV4_HEADER_SIZE
-    private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 28;
-
-    private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN;
-    private static final byte[] ARP_IPV4_HEADER = {
-            0, 1, // Hardware type: Ethernet (1)
-            8, 0, // Protocol type: IP (0x0800)
-            6,    // Hardware size: 6
-            4,    // Protocol size: 4
-    };
-    private static final int ARP_OPCODE_OFFSET = ARP_HEADER_OFFSET + 6;
-    // Opcode: ARP request (0x0001), ARP reply (0x0002)
-    private static final short ARP_OPCODE_REQUEST = 1;
-    private static final short ARP_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;
-    // Do not log ApfProgramEvents whose actual lifetimes was less than this.
-    private static final int APF_PROGRAM_EVENT_LIFETIME_THRESHOLD = 2;
-    // Limit on the Black List size to cap on program usage for this
-    // TODO: Select a proper max length
-    private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20;
-
-    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 MDNS_PORT = 5353;
-    private static final int DNS_HEADER_LEN = 12;
-    private static final int DNS_QDCOUNT_OFFSET = 4;
-    // NOTE: this must be added to the IPv4 header length in MemorySlot.IPV4_HEADER_SIZE, or the
-    // IPv6 header length.
-    private static final int MDNS_QDCOUNT_OFFSET =
-            ETH_HEADER_LEN + UDP_HEADER_LEN + DNS_QDCOUNT_OFFSET;
-    private static final int MDNS_QNAME_OFFSET =
-            ETH_HEADER_LEN + UDP_HEADER_LEN + DNS_HEADER_LEN;
-
-
-    public final int mApfVersionSupported;
-    public final int mMaximumApfProgramSize;
-    private final IpClientCallbacksWrapper mIpClientCallback;
-    private final InterfaceParams mInterfaceParams;
-    private final IpConnectivityLog mMetricsLog;
-
-    @VisibleForTesting
-    public byte[] mHardwareAddress;
-    @VisibleForTesting
-    public ReceiveThread mReceiveThread;
-    @GuardedBy("this")
-    private long mUniqueCounter;
-    @GuardedBy("this")
-    private boolean mMulticastFilter;
-    @GuardedBy("this")
-    private boolean mInDozeMode;
-    private final boolean mDrop802_3Frames;
-    private final int[] mEthTypeBlackList;
-
-    private final ApfCounterTracker mApfCounterTracker = new ApfCounterTracker();
-    @GuardedBy("this")
-    private long mSessionStartMs = 0;
-    @GuardedBy("this")
-    private int mNumParseErrorRas = 0;
-    @GuardedBy("this")
-    private int mNumZeroLifetimeRas = 0;
-    @GuardedBy("this")
-    private int mLowestRouterLifetimeSeconds = Integer.MAX_VALUE;
-    @GuardedBy("this")
-    private long mLowestPioValidLifetimeSeconds = Long.MAX_VALUE;
-    @GuardedBy("this")
-    private long mLowestRioRouteLifetimeSeconds = Long.MAX_VALUE;
-    @GuardedBy("this")
-    private long mLowestRdnssLifetimeSeconds = Long.MAX_VALUE;
-
-    // Ignore non-zero RDNSS lifetimes below this value.
-    private final int mMinRdnssLifetimeSec;
-
-    // Minimum session time for metrics, duration less than this time will not be logged.
-    private final long mMinMetricsSessionDurationMs;
-
-    private final Clock mClock;
-    private final NetworkQuirkMetrics mNetworkQuirkMetrics;
-    private final IpClientRaInfoMetrics mIpClientRaInfoMetrics;
-    private final ApfSessionInfoMetrics mApfSessionInfoMetrics;
-
-    // Detects doze mode state transitions.
-    private final BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action.equals(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)) {
-                PowerManager powerManager =
-                        (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-                final boolean deviceIdle = powerManager.isDeviceIdleMode();
-                setDozeMode(deviceIdle);
-            }
-        }
-    };
-    private final Context mContext;
-
-    // Our IPv4 address, if we have just one, otherwise null.
-    @GuardedBy("this")
-    private byte[] mIPv4Address;
-    // The subnet prefix length of our IPv4 network. Only valid if mIPv4Address is not null.
-    @GuardedBy("this")
-    private int mIPv4PrefixLength;
-
-    // mIsRunning is reflects the state of the LegacyApfFilter during integration tests.
-    // LegacyApfFilter can be paused using "adb shell cmd apf <iface> <cmd>" commands. A paused
-    // LegacyApfFilter will not install any new programs, but otherwise operates normally.
-    private volatile boolean mIsRunning = true;
-
-    private final ApfFilter.Dependencies mDependencies;
-
-    @VisibleForTesting
-    public LegacyApfFilter(Context context, ApfFilter.ApfConfiguration config,
-            InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback,
-            IpConnectivityLog log, NetworkQuirkMetrics networkQuirkMetrics) {
-        this(context, config, ifParams, ipClientCallback, log, networkQuirkMetrics,
-                new ApfFilter.Dependencies(context), new Clock());
-    }
-
-    @VisibleForTesting
-    public LegacyApfFilter(Context context, ApfFilter.ApfConfiguration config,
-            InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback,
-            IpConnectivityLog log, NetworkQuirkMetrics networkQuirkMetrics,
-            ApfFilter.Dependencies dependencies, Clock clock) {
-        mApfVersionSupported = config.apfVersionSupported;
-        mMaximumApfProgramSize = config.apfRamSize;
-        mIpClientCallback = ipClientCallback;
-        mInterfaceParams = ifParams;
-        mMulticastFilter = config.multicastFilter;
-        mDrop802_3Frames = config.ieee802_3Filter;
-        mMinRdnssLifetimeSec = config.minRdnssLifetimeSec;
-        mContext = context;
-        mClock = clock;
-        mDependencies = dependencies;
-        mNetworkQuirkMetrics = networkQuirkMetrics;
-        mIpClientRaInfoMetrics = dependencies.getIpClientRaInfoMetrics();
-        mApfSessionInfoMetrics = dependencies.getApfSessionInfoMetrics();
-        mSessionStartMs = mClock.elapsedRealtime();
-        mMinMetricsSessionDurationMs = config.minMetricsSessionDurationMs;
-
-        if (hasDataAccess(mApfVersionSupported)) {
-            mCountAndPassLabel = "countAndPass";
-            mCountAndDropLabel = "countAndDrop";
-        } else {
-            // APFv4 unsupported: turn jumps to the counter trampolines to immediately PASS or DROP,
-            // preserving the original pre-APFv4 behavior.
-            mCountAndPassLabel = ApfV4Generator.PASS_LABEL;
-            mCountAndDropLabel = ApfV4Generator.DROP_LABEL;
-        }
-
-        // Now fill the black list from the passed array
-        mEthTypeBlackList = filterEthTypeBlackList(config.ethTypeBlackList);
-
-        mMetricsLog = log;
-
-        // TODO: ApfFilter should not generate programs until IpClient sends provisioning success.
-        maybeStartFilter();
-
-        // Listen for doze-mode transition changes to enable/disable the IPv6 multicast filter.
-        mContext.registerReceiver(mDeviceIdleReceiver,
-                new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED));
-
-        mDependencies.onApfFilterCreated(this);
-        // mReceiveThread is created in maybeStartFilter() and halted in shutdown().
-        mDependencies.onThreadCreated(mReceiveThread);
-    }
-
-    @Override
-    public synchronized String setDataSnapshot(byte[] data) {
-        mDataSnapshot = data;
-        if (mIsRunning) {
-            mApfCounterTracker.updateCountersFromData(data);
-        }
-        return mApfCounterTracker.getCounters().toString();
-    }
-
-    private void log(String s) {
-        Log.d(TAG, "(" + mInterfaceParams.name + "): " + s);
-    }
-
-    @GuardedBy("this")
-    private long getUniqueNumberLocked() {
-        return mUniqueCounter++;
-    }
-
-    private static int[] filterEthTypeBlackList(int[] ethTypeBlackList) {
-        ArrayList<Integer> bl = new ArrayList<Integer>();
-
-        for (int p : ethTypeBlackList) {
-            // Check if the protocol is a valid ether type
-            if ((p < ETH_TYPE_MIN) || (p > ETH_TYPE_MAX)) {
-                continue;
-            }
-
-            // Check if the protocol is not repeated in the passed array
-            if (bl.contains(p)) {
-                continue;
-            }
-
-            // Check if list reach its max size
-            if (bl.size() == APF_MAX_ETH_TYPE_BLACK_LIST_LEN) {
-                Log.w(TAG, "Passed EthType Black List size too large (" + bl.size() +
-                        ") using top " + APF_MAX_ETH_TYPE_BLACK_LIST_LEN + " protocols");
-                break;
-            }
-
-            // Now add the protocol to the list
-            bl.add(p);
-        }
-
-        return bl.stream().mapToInt(Integer::intValue).toArray();
-    }
-
-    /**
-     * Attempt to start listening for RAs and, if RAs are received, generating and installing
-     * filters to ignore useless RAs.
-     */
-    @VisibleForTesting
-    public void maybeStartFilter() {
-        FileDescriptor socket;
-        try {
-            mHardwareAddress = mInterfaceParams.macAddr.toByteArray();
-            synchronized(this) {
-                // 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 && hasDataAccess(mApfVersionSupported)) {
-                    byte[] zeroes = new byte[mMaximumApfProgramSize];
-                    if (!mIpClientCallback.installPacketFilter(zeroes)) {
-                        sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE);
-                    }
-                }
-
-                // Install basic filters
-                installNewProgramLocked();
-            }
-            socket = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IPV6);
-            SocketAddress addr = makePacketSocketAddress(ETH_P_IPV6, mInterfaceParams.index);
-            Os.bind(socket, addr);
-            NetworkStackUtils.attachRaFilter(socket);
-        } catch(SocketException|ErrnoException e) {
-            Log.e(TAG, "Error starting filter", e);
-            return;
-        }
-        mReceiveThread = new ReceiveThread(socket);
-        mReceiveThread.start();
-    }
-
-    // Returns seconds since device boot.
-    @VisibleForTesting
-    protected long currentTimeSeconds() {
-        return mClock.elapsedRealtime() / DateUtils.SECOND_IN_MILLIS;
-    }
-
-    public static class InvalidRaException extends Exception {
-        public InvalidRaException(String m) {
-            super(m);
-        }
-    }
-
-    /**
-     *  Class to keep track of a section in a packet.
-     */
-    private static class PacketSection {
-        public enum Type {
-            MATCH,     // A field that should be matched (e.g., the router IP address).
-            IGNORE,    // An ignored field such as the checksum of the flow label. Not matched.
-            LIFETIME,  // A lifetime. Not matched, and generally counts toward minimum RA lifetime.
-        }
-
-        /** The type of section. */
-        public final Type type;
-        /** Offset into the packet at which this section begins. */
-        public final int start;
-        /** Length of this section in bytes. */
-        public final int length;
-        /** If this is a lifetime, the ICMP option that defined it. 0 for router lifetime. */
-        public final int option;
-        /** If this is a lifetime, the lifetime value. */
-        public final long lifetime;
-
-        PacketSection(int start, int length, Type type, int option, long lifetime) {
-            this.start = start;
-            this.length = length;
-            this.type = type;
-            this.option = option;
-            this.lifetime = lifetime;
-        }
-
-        public String toString() {
-            if (type == Type.LIFETIME) {
-                return String.format("%s: (%d, %d) %d %d", type, start, length, option, lifetime);
-            } else {
-                return String.format("%s: (%d, %d)", type, start, length);
-            }
-        }
-    }
-
-    // A class to hold information about an RA.
-    @VisibleForTesting
-    public class Ra {
-        // From RFC4861:
-        private static final int ICMP6_RA_HEADER_LEN = 16;
-        private static final int ICMP6_RA_CHECKSUM_OFFSET =
-                ETH_HEADER_LEN + IPV6_HEADER_LEN + 2;
-        private static final int ICMP6_RA_CHECKSUM_LEN = 2;
-        private static final int ICMP6_RA_OPTION_OFFSET =
-                ETH_HEADER_LEN + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN;
-        private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET =
-                ETH_HEADER_LEN + IPV6_HEADER_LEN + 6;
-        private static final int ICMP6_RA_ROUTER_LIFETIME_LEN = 2;
-        // Prefix information option.
-        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_VALID_LIFETIME_LEN = 4;
-        private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8;
-        private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN = 4;
-
-        // 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_LIFETIME_OFFSET = 4;
-        private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4;
-
-        // Note: mPacket's position() cannot be assumed to be reset.
-        private final ByteBuffer mPacket;
-
-        // List of sections in the packet.
-        private final ArrayList<PacketSection> mPacketSections = new ArrayList<>();
-
-        // Router lifetime in packet
-        private final int mRouterLifetime;
-        // Minimum valid lifetime of PIOs in packet, Long.MAX_VALUE means not seen.
-        private long mMinPioValidLifetime = Long.MAX_VALUE;
-        // Minimum route lifetime of RIOs in packet, Long.MAX_VALUE means not seen.
-        private long mMinRioRouteLifetime = Long.MAX_VALUE;
-        // Minimum lifetime of RDNSSs in packet, Long.MAX_VALUE means not seen.
-        private long mMinRdnssLifetime = Long.MAX_VALUE;
-        // Minimum lifetime in packet
-        long mMinLifetime;
-        // When the packet was last captured, in seconds since Unix Epoch
-        long mLastSeen;
-
-        // For debugging only. Offsets into the packet where PIOs are.
-        private final ArrayList<Integer> mPrefixOptionOffsets = new ArrayList<>();
-
-        // For debugging only. Offsets into the packet where RDNSS options are.
-        private final ArrayList<Integer> mRdnssOptionOffsets = new ArrayList<>();
-
-        // For debugging only. Offsets into the packet where RIO options are.
-        private final ArrayList<Integer> mRioOptionOffsets = new ArrayList<>();
-
-        // For debugging only. How many times this RA was seen.
-        int seenCount = 0;
-
-        // For debugging only. Returns the hex representation of the last matching packet.
-        String getLastMatchingPacket() {
-            return HexDump.toHexString(mPacket.array(), 0, mPacket.capacity(),
-                    false /* lowercase */);
-        }
-
-        // For debugging only. Returns the string representation of the IPv6 address starting at
-        // position pos in the packet.
-        private String IPv6AddresstoString(int pos) {
-            try {
-                byte[] array = mPacket.array();
-                // Can't just call copyOfRange() and see if it throws, because if it reads past the
-                // end it pads with zeros instead of throwing.
-                if (pos < 0 || pos + 16 > array.length || pos + 16 < pos) {
-                    return "???";
-                }
-                byte[] addressBytes = Arrays.copyOfRange(array, pos, pos + 16);
-                InetAddress address = (Inet6Address) InetAddress.getByAddress(addressBytes);
-                return address.getHostAddress();
-            } catch (UnsupportedOperationException e) {
-                // array() failed. Cannot happen, mPacket is array-backed and read-write.
-                return "???";
-            } catch (ClassCastException|UnknownHostException e) {
-                // Cannot happen.
-                return "???";
-            }
-        }
-
-        // Can't be static because it's in a non-static inner class.
-        // TODO: Make this static once RA is its own class.
-        private void prefixOptionToString(StringBuffer sb, int offset) {
-            String prefix = IPv6AddresstoString(offset + 16);
-            int length = getUint8(mPacket, offset + 2);
-            long valid = getUint32(mPacket, offset + 4);
-            long preferred = getUint32(mPacket, offset + 8);
-            sb.append(String.format("%s/%d %ds/%ds ", prefix, length, valid, preferred));
-        }
-
-        private void rdnssOptionToString(StringBuffer sb, int offset) {
-            int optLen = getUint8(mPacket, offset + 1) * 8;
-            if (optLen < 24) return;  // Malformed or empty.
-            long lifetime = getUint32(mPacket, offset + 4);
-            int numServers = (optLen - 8) / 16;
-            sb.append("DNS ").append(lifetime).append("s");
-            for (int server = 0; server < numServers; server++) {
-                sb.append(" ").append(IPv6AddresstoString(offset + 8 + 16 * server));
-            }
-            sb.append(" ");
-        }
-
-        private void rioOptionToString(StringBuffer sb, int offset) {
-            int optLen = getUint8(mPacket, offset + 1) * 8;
-            if (optLen < 8 || optLen > 24) return;  // Malformed or empty.
-            int prefixLen = getUint8(mPacket, offset + 2);
-            long lifetime = getUint32(mPacket, offset + 4);
-
-            // This read is variable length because the prefix can be 0, 8 or 16 bytes long.
-            // We can't use any of the ByteBuffer#get methods here because they all start reading
-            // from the buffer's current position.
-            byte[] prefix = new byte[IPV6_ADDR_LEN];
-            System.arraycopy(mPacket.array(), offset + 8, prefix, 0, optLen - 8);
-            sb.append("RIO ").append(lifetime).append("s ");
-            try {
-                InetAddress address = (Inet6Address) InetAddress.getByAddress(prefix);
-                sb.append(address.getHostAddress());
-            } catch (UnknownHostException impossible) {
-                sb.append("???");
-            }
-            sb.append("/").append(prefixLen).append(" ");
-        }
-
-        public String toString() {
-            try {
-                StringBuffer sb = new StringBuffer();
-                sb.append(String.format("RA %s -> %s %ds ",
-                        IPv6AddresstoString(IPV6_SRC_ADDR_OFFSET),
-                        IPv6AddresstoString(IPV6_DEST_ADDR_OFFSET),
-                        getUint16(mPacket, ICMP6_RA_ROUTER_LIFETIME_OFFSET)));
-                for (int i: mPrefixOptionOffsets) {
-                    prefixOptionToString(sb, i);
-                }
-                for (int i: mRdnssOptionOffsets) {
-                    rdnssOptionToString(sb, i);
-                }
-                for (int i: mRioOptionOffsets) {
-                    rioOptionToString(sb, i);
-                }
-                return sb.toString();
-            } catch (BufferUnderflowException|IndexOutOfBoundsException e) {
-                return "<Malformed RA>";
-            }
-        }
-
-        /**
-         * Add a packet section that should be matched, starting from the current position.
-         * @param length the length of the section
-         */
-        private void addMatchSection(int length) {
-            // Don't generate JNEBS instruction for 0 bytes as they will fail the
-            // ASSERT_FORWARD_IN_PROGRAM(pc + cmp_imm - 1) check (where cmp_imm is
-            // the number of bytes to compare) and immediately pass the packet.
-            // The code does not attempt to generate such matches, but add a safety
-            // check to prevent doing so in the presence of bugs or malformed or
-            // truncated packets.
-            if (length == 0) return;
-            mPacketSections.add(
-                    new PacketSection(mPacket.position(), length, PacketSection.Type.MATCH, 0, 0));
-            mPacket.position(mPacket.position() + length);
-        }
-
-        /**
-         * Add a packet section that should be matched, starting from the current position.
-         * @param end the offset in the packet before which the section ends
-         */
-        private void addMatchUntil(int end) {
-            addMatchSection(end - mPacket.position());
-        }
-
-        /**
-         * Add a packet section that should be ignored, starting from the current position.
-         * @param length the length of the section in bytes
-         */
-        private void addIgnoreSection(int length) {
-            mPacketSections.add(
-                    new PacketSection(mPacket.position(), length, PacketSection.Type.IGNORE, 0, 0));
-            mPacket.position(mPacket.position() + length);
-        }
-
-        /**
-         * Add a packet section that represents a lifetime, starting from the current position.
-         * @param length the length of the section in bytes
-         * @param optionType the RA option containing this lifetime, or 0 for router lifetime
-         * @param lifetime the lifetime
-         */
-        private void addLifetimeSection(int length, int optionType, long lifetime) {
-            mPacketSections.add(
-                    new PacketSection(mPacket.position(), length, PacketSection.Type.LIFETIME,
-                            optionType, lifetime));
-            mPacket.position(mPacket.position() + length);
-        }
-
-        /**
-         * Adds packet sections for an RA option with a 4-byte lifetime 4 bytes into the option
-         * @param optionType the RA option that is being added
-         * @param optionLength the length of the option in bytes
-         */
-        private long add4ByteLifetimeOption(int optionType, int optionLength) {
-            addMatchSection(ICMP6_4_BYTE_LIFETIME_OFFSET);
-            final long lifetime = getUint32(mPacket, mPacket.position());
-            addLifetimeSection(ICMP6_4_BYTE_LIFETIME_LEN, optionType, lifetime);
-            addMatchSection(optionLength - ICMP6_4_BYTE_LIFETIME_OFFSET
-                    - ICMP6_4_BYTE_LIFETIME_LEN);
-            return lifetime;
-        }
-
-        /**
-         * Return the router lifetime of the RA
-         */
-        public int routerLifetime() {
-            return mRouterLifetime;
-        }
-
-        /**
-         * Return the minimum valid lifetime in PIOs
-         */
-        public long minPioValidLifetime() {
-            return mMinPioValidLifetime;
-        }
-
-        /**
-         * Return the minimum route lifetime in RIOs
-         */
-        public long minRioRouteLifetime() {
-            return mMinRioRouteLifetime;
-        }
-
-        /**
-         * Return the minimum lifetime in RDNSSs
-         */
-        public long minRdnssLifetime() {
-            return mMinRdnssLifetime;
-        }
-
-        // http://b/66928272 http://b/65056012
-        // DnsServerRepository ignores RDNSS servers with lifetimes that are too low. Ignore these
-        // lifetimes for the purpose of filter lifetime calculations.
-        private boolean shouldIgnoreLifetime(int optionType, long lifetime) {
-            return optionType == ICMP6_RDNSS_OPTION_TYPE
-                    && lifetime != 0 && lifetime < mMinRdnssLifetimeSec;
-        }
-
-        private boolean isRelevantLifetime(PacketSection section) {
-            return section.type == PacketSection.Type.LIFETIME
-                    && !shouldIgnoreLifetime(section.option, section.lifetime);
-        }
-
-        // Note that this parses RA and may throw InvalidRaException (from
-        // Buffer.position(int) or due to an invalid-length option) or IndexOutOfBoundsException
-        // (from ByteBuffer.get(int) ) if parsing encounters something non-compliant with
-        // specifications.
-        @VisibleForTesting
-        public Ra(byte[] packet, int length) throws InvalidRaException {
-            if (length < ICMP6_RA_OPTION_OFFSET) {
-                throw new InvalidRaException("Not an ICMP6 router advertisement: too short");
-            }
-
-            mPacket = ByteBuffer.wrap(Arrays.copyOf(packet, length));
-            mLastSeen = currentTimeSeconds();
-
-            // Check packet in case a packet arrives before we attach RA filter
-            // to our packet socket. b/29586253
-            if (getUint16(mPacket, ETH_ETHERTYPE_OFFSET) != ETH_P_IPV6 ||
-                    getUint8(mPacket, IPV6_NEXT_HEADER_OFFSET) != IPPROTO_ICMPV6 ||
-                    getUint8(mPacket, ICMP6_TYPE_OFFSET) != ICMPV6_ROUTER_ADVERTISEMENT) {
-                throw new InvalidRaException("Not an ICMP6 router advertisement");
-            }
-
-
-            RaEvent.Builder builder = new RaEvent.Builder();
-
-            // Ignore the flow label and low 4 bits of traffic class.
-            addMatchUntil(IPV6_FLOW_LABEL_OFFSET);
-            addIgnoreSection(IPV6_FLOW_LABEL_LEN);
-
-            // Ignore checksum.
-            addMatchUntil(ICMP6_RA_CHECKSUM_OFFSET);
-            addIgnoreSection(ICMP6_RA_CHECKSUM_LEN);
-
-            // Parse router lifetime
-            addMatchUntil(ICMP6_RA_ROUTER_LIFETIME_OFFSET);
-            mRouterLifetime = getUint16(mPacket, ICMP6_RA_ROUTER_LIFETIME_OFFSET);
-            addLifetimeSection(ICMP6_RA_ROUTER_LIFETIME_LEN, 0, mRouterLifetime);
-            builder.updateRouterLifetime(mRouterLifetime);
-
-            // Add remaining fields (reachable time and retransmission timer) to match section.
-            addMatchUntil(ICMP6_RA_OPTION_OFFSET);
-
-            while (mPacket.hasRemaining()) {
-                final int position = mPacket.position();
-                final int optionType = getUint8(mPacket, position);
-                final int optionLength = getUint8(mPacket, position + 1) * 8;
-                long lifetime;
-                switch (optionType) {
-                    case ICMP6_PREFIX_OPTION_TYPE:
-                        mPrefixOptionOffsets.add(position);
-
-                        // Parse valid lifetime
-                        addMatchSection(ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET);
-                        lifetime = getUint32(mPacket, mPacket.position());
-                        addLifetimeSection(ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN,
-                                ICMP6_PREFIX_OPTION_TYPE, lifetime);
-                        builder.updatePrefixValidLifetime(lifetime);
-                        mMinPioValidLifetime = getMinForPositiveValue(
-                                mMinPioValidLifetime, lifetime);
-
-                        // Parse preferred lifetime
-                        lifetime = getUint32(mPacket, mPacket.position());
-                        addLifetimeSection(ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN,
-                                ICMP6_PREFIX_OPTION_TYPE, lifetime);
-                        builder.updatePrefixPreferredLifetime(lifetime);
-
-                        addMatchSection(4);       // Reserved bytes
-                        addMatchSection(IPV6_ADDR_LEN);  // The prefix itself
-                        break;
-                    // These three options have the same lifetime offset and size, and
-                    // are processed with the same specialized add4ByteLifetimeOption:
-                    case ICMP6_RDNSS_OPTION_TYPE:
-                        mRdnssOptionOffsets.add(position);
-                        lifetime = add4ByteLifetimeOption(optionType, optionLength);
-                        builder.updateRdnssLifetime(lifetime);
-                        mMinRdnssLifetime = getMinForPositiveValue(mMinRdnssLifetime, lifetime);
-                        break;
-                    case ICMP6_ROUTE_INFO_OPTION_TYPE:
-                        mRioOptionOffsets.add(position);
-                        lifetime = add4ByteLifetimeOption(optionType, optionLength);
-                        builder.updateRouteInfoLifetime(lifetime);
-                        mMinRioRouteLifetime = getMinForPositiveValue(
-                                mMinRioRouteLifetime, lifetime);
-                        break;
-                    case ICMP6_DNSSL_OPTION_TYPE:
-                        lifetime = add4ByteLifetimeOption(optionType, optionLength);
-                        builder.updateDnsslLifetime(lifetime);
-                        break;
-                    default:
-                        // RFC4861 section 4.2 dictates we ignore unknown options for forwards
-                        // compatibility.
-                        mPacket.position(position + optionLength);
-                        break;
-                }
-                if (optionLength <= 0) {
-                    throw new InvalidRaException(String.format(
-                        "Invalid option length opt=%d len=%d", optionType, optionLength));
-                }
-            }
-            mMinLifetime = minLifetime();
-            mMetricsLog.log(builder.build());
-        }
-
-        // Considering only the MATCH sections, does {@code packet} match this RA?
-        boolean matches(byte[] packet, int length) {
-            if (length != mPacket.capacity()) return false;
-            byte[] referencePacket = mPacket.array();
-            for (PacketSection section : mPacketSections) {
-                if (section.type != PacketSection.Type.MATCH) continue;
-                for (int i = section.start; i < (section.start + section.length); i++) {
-                    if (packet[i] != referencePacket[i]) return false;
-                }
-            }
-            return true;
-        }
-
-        // What is the minimum of all lifetimes within {@code packet} in seconds?
-        // Precondition: matches(packet, length) already returned true.
-        long minLifetime() {
-            long minLifetime = Long.MAX_VALUE;
-            for (PacketSection section : mPacketSections) {
-                if (isRelevantLifetime(section)) {
-                    minLifetime = Math.min(minLifetime, section.lifetime);
-                }
-            }
-            return minLifetime;
-        }
-
-        // How many seconds does this RA's have to live, taking into account the fact
-        // that we might have seen it a while ago.
-        long currentLifetime() {
-            return mMinLifetime - (currentTimeSeconds() - mLastSeen);
-        }
-
-        boolean isExpired() {
-            // TODO: We may want to handle 0 lifetime RAs differently, if they are common. We'll
-            // have to calculate the filter lifetime specially as a fraction of 0 is still 0.
-            return currentLifetime() <= 0;
-        }
-
-        // Filter for a fraction of the lifetime and adjust for the age of the RA.
-        @GuardedBy("LegacyApfFilter.this")
-        int filterLifetime() {
-            return (int) (mMinLifetime / FRACTION_OF_LIFETIME_TO_FILTER)
-                    - (int) (mProgramBaseTime - mLastSeen);
-        }
-
-        @GuardedBy("LegacyApfFilter.this")
-        boolean shouldFilter() {
-            return filterLifetime() > 0;
-        }
-
-        // Append a filter for this RA to {@code gen}. Jump to DROP_LABEL if it should be dropped.
-        // Jump to the next filter if packet doesn't match this RA.
-        // Return Long.MAX_VALUE if we don't install any filter program for this RA. As the return
-        // value of this function is used to calculate the program min lifetime (which corresponds
-        // to the smallest generated filter lifetime). Returning Long.MAX_VALUE in the case no
-        // filter gets generated makes sure the program lifetime stays unaffected.
-        @GuardedBy("LegacyApfFilter.this")
-        long generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException {
-            String nextFilterLabel = "Ra" + getUniqueNumberLocked();
-            // Skip if packet is not the right size
-            gen.addLoadFromMemory(R0, MemorySlot.PACKET_SIZE);
-            gen.addJumpIfR0NotEquals(mPacket.capacity(), nextFilterLabel);
-            // Skip filter if expired
-            gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS);
-            gen.addJumpIfR0GreaterThan(filterLifetime(), nextFilterLabel);
-            for (PacketSection section : mPacketSections) {
-                // Generate code to match the packet bytes.
-                if (section.type == PacketSection.Type.MATCH) {
-                    gen.addLoadImmediate(R0, section.start);
-                    gen.addJumpIfBytesAtR0NotEqual(
-                            Arrays.copyOfRange(mPacket.array(), section.start,
-                                    section.start + section.length),
-                            nextFilterLabel);
-                }
-
-                // Generate code to test the lifetimes haven't gone down too far.
-                // The packet is accepted if any non-ignored lifetime is lower than filterLifetime.
-                if (isRelevantLifetime(section)) {
-                    switch (section.length) {
-                        case 4: gen.addLoad32(R0, section.start); break;
-                        case 2: gen.addLoad16(R0, section.start); break;
-                        default:
-                            throw new IllegalStateException(
-                                    "bogus lifetime size " + section.length);
-                    }
-                    gen.addJumpIfR0LessThan(filterLifetime(), nextFilterLabel);
-                }
-            }
-            maybeSetupCounter(gen, Counter.DROPPED_RA);
-            gen.addJump(mCountAndDropLabel);
-            gen.defineLabel(nextFilterLabel);
-            return filterLifetime();
-        }
-    }
-
-    // TODO: Refactor these subclasses to avoid so much repetition.
-    private abstract static class KeepalivePacket {
-        // Note that the offset starts from IP header.
-        // These must be added ether header length when generating program.
-        static final int IP_HEADER_OFFSET = 0;
-        static final int IPV4_SRC_ADDR_OFFSET = IP_HEADER_OFFSET + 12;
-
-        // Append a filter for this keepalive ack to {@code gen}.
-        // Jump to drop if it matches the keepalive ack.
-        // Jump to the next filter if packet doesn't match the keepalive ack.
-        abstract void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException;
-    }
-
-    // A class to hold NAT-T keepalive ack information.
-    private class NattKeepaliveResponse extends KeepalivePacket {
-        static final int UDP_LENGTH_OFFSET = 4;
-        static final int UDP_HEADER_LEN = 8;
-
-        protected class NattKeepaliveResponseData {
-            public final byte[] srcAddress;
-            public final int srcPort;
-            public final byte[] dstAddress;
-            public final int dstPort;
-
-            NattKeepaliveResponseData(final NattKeepalivePacketDataParcelable sentKeepalivePacket) {
-                srcAddress = sentKeepalivePacket.dstAddress;
-                srcPort = sentKeepalivePacket.dstPort;
-                dstAddress = sentKeepalivePacket.srcAddress;
-                dstPort = sentKeepalivePacket.srcPort;
-            }
-        }
-
-        protected final NattKeepaliveResponseData mPacket;
-        protected final byte[] mSrcDstAddr;
-        protected final byte[] mPortFingerprint;
-        // NAT-T keepalive packet
-        protected final byte[] mPayload = {(byte) 0xff};
-
-        NattKeepaliveResponse(final NattKeepalivePacketDataParcelable sentKeepalivePacket) {
-            mPacket = new NattKeepaliveResponseData(sentKeepalivePacket);
-            mSrcDstAddr = concatArrays(mPacket.srcAddress, mPacket.dstAddress);
-            mPortFingerprint = generatePortFingerprint(mPacket.srcPort, mPacket.dstPort);
-        }
-
-        byte[] generatePortFingerprint(int srcPort, int dstPort) {
-            final ByteBuffer fp = ByteBuffer.allocate(4);
-            fp.order(ByteOrder.BIG_ENDIAN);
-            fp.putShort((short) srcPort);
-            fp.putShort((short) dstPort);
-            return fp.array();
-        }
-
-        @Override
-        @GuardedBy("LegacyApfFilter.this")
-        void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException {
-            final String nextFilterLabel = "natt_keepalive_filter" + getUniqueNumberLocked();
-
-            gen.addLoadImmediate(R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
-            gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel);
-
-            // A NAT-T keepalive packet contains 1 byte payload with the value 0xff
-            // Check payload length is 1
-            gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE);
-            gen.addAdd(UDP_HEADER_LEN);
-            gen.addSwap();
-            gen.addLoad16(R0, IPV4_TOTAL_LENGTH_OFFSET);
-            gen.addNeg(R1);
-            gen.addAddR1ToR0();
-            gen.addJumpIfR0NotEquals(1, nextFilterLabel);
-
-            // Check that the ports match
-            gen.addLoadFromMemory(R0, MemorySlot.IPV4_HEADER_SIZE);
-            gen.addAdd(ETH_HEADER_LEN);
-            gen.addJumpIfBytesAtR0NotEqual(mPortFingerprint, nextFilterLabel);
-
-            // Payload offset = R0 + UDP header length
-            gen.addAdd(UDP_HEADER_LEN);
-            gen.addJumpIfBytesAtR0NotEqual(mPayload, nextFilterLabel);
-
-            maybeSetupCounter(gen, Counter.DROPPED_IPV4_NATT_KEEPALIVE);
-            gen.addJump(mCountAndDropLabel);
-            gen.defineLabel(nextFilterLabel);
-        }
-
-        public String toString() {
-            try {
-                return String.format("%s -> %s",
-                        ConnectivityUtils.addressAndPortToString(
-                                InetAddress.getByAddress(mPacket.srcAddress), mPacket.srcPort),
-                        ConnectivityUtils.addressAndPortToString(
-                                InetAddress.getByAddress(mPacket.dstAddress), mPacket.dstPort));
-            } catch (UnknownHostException e) {
-                return "Unknown host";
-            }
-        }
-    }
-
-    // A class to hold TCP keepalive ack information.
-    private abstract static class TcpKeepaliveAck extends KeepalivePacket {
-        protected static class TcpKeepaliveAckData {
-            public final byte[] srcAddress;
-            public final int srcPort;
-            public final byte[] dstAddress;
-            public final int dstPort;
-            public final int seq;
-            public final int ack;
-
-            // Create the characteristics of the ack packet from the sent keepalive packet.
-            TcpKeepaliveAckData(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) {
-                srcAddress = sentKeepalivePacket.dstAddress;
-                srcPort = sentKeepalivePacket.dstPort;
-                dstAddress = sentKeepalivePacket.srcAddress;
-                dstPort = sentKeepalivePacket.srcPort;
-                seq = sentKeepalivePacket.ack;
-                ack = sentKeepalivePacket.seq + 1;
-            }
-        }
-
-        protected final TcpKeepaliveAckData mPacket;
-        protected final byte[] mSrcDstAddr;
-        protected final byte[] mPortSeqAckFingerprint;
-
-        TcpKeepaliveAck(final TcpKeepaliveAckData packet, final byte[] srcDstAddr) {
-            mPacket = packet;
-            mSrcDstAddr = srcDstAddr;
-            mPortSeqAckFingerprint = generatePortSeqAckFingerprint(mPacket.srcPort,
-                    mPacket.dstPort, mPacket.seq, mPacket.ack);
-        }
-
-        static byte[] generatePortSeqAckFingerprint(int srcPort, int dstPort, int seq, int ack) {
-            final ByteBuffer fp = ByteBuffer.allocate(12);
-            fp.order(ByteOrder.BIG_ENDIAN);
-            fp.putShort((short) srcPort);
-            fp.putShort((short) dstPort);
-            fp.putInt(seq);
-            fp.putInt(ack);
-            return fp.array();
-        }
-
-        public String toString() {
-            try {
-                return String.format("%s -> %s , seq=%d, ack=%d",
-                        ConnectivityUtils.addressAndPortToString(
-                                InetAddress.getByAddress(mPacket.srcAddress), mPacket.srcPort),
-                        ConnectivityUtils.addressAndPortToString(
-                                InetAddress.getByAddress(mPacket.dstAddress), mPacket.dstPort),
-                        Integer.toUnsignedLong(mPacket.seq),
-                        Integer.toUnsignedLong(mPacket.ack));
-            } catch (UnknownHostException e) {
-                return "Unknown host";
-            }
-        }
-
-        // Append a filter for this keepalive ack to {@code gen}.
-        // Jump to drop if it matches the keepalive ack.
-        // Jump to the next filter if packet doesn't match the keepalive ack.
-        abstract void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException;
-    }
-
-    private class TcpKeepaliveAckV4 extends TcpKeepaliveAck {
-
-        TcpKeepaliveAckV4(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) {
-            this(new TcpKeepaliveAckData(sentKeepalivePacket));
-        }
-        TcpKeepaliveAckV4(final TcpKeepaliveAckData packet) {
-            super(packet, concatArrays(packet.srcAddress, packet.dstAddress) /* srcDstAddr */);
-        }
-
-        @Override
-        @GuardedBy("LegacyApfFilter.this")
-        void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException {
-            final String nextFilterLabel = "keepalive_ack" + getUniqueNumberLocked();
-
-            gen.addLoadImmediate(R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
-            gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel);
-
-            // Skip to the next filter if it's not zero-sized :
-            // TCP_HEADER_SIZE + IPV4_HEADER_SIZE - ipv4_total_length == 0
-            // Load the IP header size into R1
-            gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE);
-            // Load the TCP header size into R0 (it's indexed by R1)
-            gen.addLoad8Indexed(R0, ETH_HEADER_LEN + TCP_HEADER_SIZE_OFFSET);
-            // Size offset is in the top nibble, but it must be multiplied by 4, and the two
-            // top bits of the low nibble are guaranteed to be zeroes. Right-shift R0 by 2.
-            gen.addRightShift(2);
-            // R0 += R1 -> R0 contains TCP + IP headers length
-            gen.addAddR1ToR0();
-            // Load IPv4 total length
-            gen.addLoad16(R1, IPV4_TOTAL_LENGTH_OFFSET);
-            gen.addNeg(R0);
-            gen.addAddR1ToR0();
-            gen.addJumpIfR0NotEquals(0, nextFilterLabel);
-            // Add IPv4 header length
-            gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE);
-            gen.addLoadImmediate(R0, ETH_HEADER_LEN);
-            gen.addAddR1ToR0();
-            gen.addJumpIfBytesAtR0NotEqual(mPortSeqAckFingerprint, nextFilterLabel);
-
-            maybeSetupCounter(gen, Counter.DROPPED_IPV4_KEEPALIVE_ACK);
-            gen.addJump(mCountAndDropLabel);
-            gen.defineLabel(nextFilterLabel);
-        }
-    }
-
-    private class TcpKeepaliveAckV6 extends TcpKeepaliveAck {
-        TcpKeepaliveAckV6(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) {
-            this(new TcpKeepaliveAckData(sentKeepalivePacket));
-        }
-        TcpKeepaliveAckV6(final TcpKeepaliveAckData packet) {
-            super(packet, concatArrays(packet.srcAddress, packet.dstAddress) /* srcDstAddr */);
-        }
-
-        @Override
-        void generateFilterLocked(ApfV4Generator gen) throws IllegalInstructionException {
-            throw new UnsupportedOperationException("IPv6 TCP Keepalive is not supported yet");
-        }
-    }
-
-    // Maximum number of RAs to filter for.
-    private static final int MAX_RAS = 10;
-
-    @GuardedBy("this")
-    private ArrayList<Ra> mRas = new ArrayList<>();
-    @GuardedBy("this")
-    private SparseArray<KeepalivePacket> mKeepalivePackets = new SparseArray<>();
-    @GuardedBy("this")
-    private final List<String[]> mMdnsAllowList = new ArrayList<>();
-
-    // There is always some marginal benefit to updating the installed APF program when an RA is
-    // seen because we can extend the program's lifetime slightly, but there is some cost to
-    // updating the program, so don't bother unless the program is going to expire soon. This
-    // constant defines "soon" in seconds.
-    private static final long MAX_PROGRAM_LIFETIME_WORTH_REFRESHING = 30;
-    // We don't want to filter an RA for it's whole lifetime as it'll be expired by the time we ever
-    // see a refresh.  Using half the lifetime might be a good idea except for the fact that
-    // packets may be dropped, so let's use 6.
-    private static final int FRACTION_OF_LIFETIME_TO_FILTER = 6;
-
-    // The base time for this filter program. In seconds since Unix Epoch.
-    // This is the time when the APF program was generated. All filters in the program should use
-    // this base time as their current time for consistency purposes.
-    @GuardedBy("this")
-    private long mProgramBaseTime;
-    // When did we last install a filter program? In seconds since Unix Epoch.
-    @GuardedBy("this")
-    private long mLastTimeInstalledProgram;
-    // How long should the last installed filter program live for? In seconds.
-    @GuardedBy("this")
-    private long mLastInstalledProgramMinLifetime;
-    @GuardedBy("this")
-    private ApfProgramEvent.Builder mLastInstallEvent;
-
-    // For debugging only. The last program installed.
-    @GuardedBy("this")
-    private byte[] mLastInstalledProgram;
-
-    /**
-     * For debugging only. Contains the latest APF buffer snapshot captured from the firmware.
-     *
-     * A typical size for this buffer is 4KB. It is present only if the WiFi HAL supports
-     * IWifiStaIface#readApfPacketFilterData(), and the APF interpreter advertised support for
-     * the opcodes to access the data buffer (LDDW and STDW).
-     */
-    @GuardedBy("this") @Nullable
-    private byte[] mDataSnapshot;
-
-    // How many times the program was updated since we started.
-    @GuardedBy("this")
-    private int mNumProgramUpdates = 0;
-    // The maximum program size that updated since we started.
-    @GuardedBy("this")
-    private int mMaxProgramSize = 0;
-    // The maximum number of distinct RAs
-    @GuardedBy("this")
-    private int mMaxDistinctRas = 0;
-    // How many times the program was updated since we started for allowing multicast traffic.
-    @GuardedBy("this")
-    private int mNumProgramUpdatesAllowingMulticast = 0;
-
-    /**
-     * Generate filter code to process ARP packets. Execution of this code ends in either the
-     * DROP_LABEL or PASS_LABEL and does not fall off the end.
-     * Preconditions:
-     *  - Packet being filtered is ARP
-     */
-    @GuardedBy("this")
-    private void generateArpFilterLocked(ApfV4Generator gen) throws IllegalInstructionException {
-        // Here's a basic summary of what the ARP filter program does:
-        //
-        // if not ARP IPv4
-        //   pass
-        // if not ARP IPv4 reply or request
-        //   pass
-        // if ARP reply source ip is 0.0.0.0
-        //   drop
-        // if unicast ARP reply
-        //   pass
-        // if interface has no IPv4 address
-        //   if target ip is 0.0.0.0
-        //      drop
-        // else
-        //   if target ip is not the interface ip
-        //      drop
-        // pass
-
-        final String checkTargetIPv4 = "checkTargetIPv4";
-
-        // Pass if not ARP IPv4.
-        gen.addLoadImmediate(R0, ARP_HEADER_OFFSET);
-        maybeSetupCounter(gen, Counter.PASSED_ARP_NON_IPV4);
-        gen.addJumpIfBytesAtR0NotEqual(ARP_IPV4_HEADER, mCountAndPassLabel);
-
-        // Pass if unknown ARP opcode.
-        gen.addLoad16(R0, ARP_OPCODE_OFFSET);
-        gen.addJumpIfR0Equals(ARP_OPCODE_REQUEST, checkTargetIPv4); // Skip to unicast check
-        maybeSetupCounter(gen, Counter.PASSED_ARP_UNKNOWN);
-        gen.addJumpIfR0NotEquals(ARP_OPCODE_REPLY, mCountAndPassLabel);
-
-        // Drop if ARP reply source IP is 0.0.0.0
-        gen.addLoad32(R0, ARP_SOURCE_IP_ADDRESS_OFFSET);
-        maybeSetupCounter(gen, Counter.DROPPED_ARP_REPLY_SPA_NO_HOST);
-        gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel);
-
-        // Pass if unicast reply.
-        gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
-        maybeSetupCounter(gen, Counter.PASSED_ARP_UNICAST_REPLY);
-        gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
-
-        // Either a unicast request, a unicast reply, or a broadcast reply.
-        gen.defineLabel(checkTargetIPv4);
-        if (mIPv4Address == null) {
-            // When there is no IPv4 address, drop GARP replies (b/29404209).
-            gen.addLoad32(R0, ARP_TARGET_IP_ADDRESS_OFFSET);
-            maybeSetupCounter(gen, Counter.DROPPED_GARP_REPLY);
-            gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel);
-        } else {
-            // When there is an IPv4 address, drop unicast/broadcast requests
-            // and broadcast replies with a different target IPv4 address.
-            gen.addLoadImmediate(R0, ARP_TARGET_IP_ADDRESS_OFFSET);
-            maybeSetupCounter(gen, Counter.DROPPED_ARP_OTHER_HOST);
-            gen.addJumpIfBytesAtR0NotEqual(mIPv4Address, mCountAndDropLabel);
-        }
-
-        maybeSetupCounter(gen, Counter.PASSED_ARP);
-        gen.addJump(mCountAndPassLabel);
-    }
-
-    /**
-     * Generate filter code to process IPv4 packets. Execution of this code ends in either the
-     * DROP_LABEL or PASS_LABEL and does not fall off the end.
-     * Preconditions:
-     *  - Packet being filtered is IPv4
-     */
-    @GuardedBy("this")
-    private void generateIPv4FilterLocked(ApfV4Generator gen) throws IllegalInstructionException {
-        // Here's a basic summary of what the IPv4 filter program does:
-        //
-        // if filtering multicast (i.e. multicast lock not held):
-        //   if it's DHCP destined to our MAC:
-        //     pass
-        //   if it's L2 broadcast:
-        //     drop
-        //   if it's IPv4 multicast:
-        //     drop
-        //   if it's IPv4 broadcast:
-        //     drop
-        // if keepalive ack
-        //   drop
-        // pass
-
-        if (mMulticastFilter) {
-            final String skipDhcpv4Filter = "skip_dhcp_v4_filter";
-
-            // Pass DHCP addressed to us.
-            // Check it's UDP.
-            gen.addLoad8(R0, IPV4_PROTOCOL_OFFSET);
-            gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipDhcpv4Filter);
-            // Check it's not a fragment. This matches the BPF filter installed by the DHCP client.
-            gen.addLoad16(R0, IPV4_FRAGMENT_OFFSET_OFFSET);
-            gen.addJumpIfR0AnyBitsSet(IPV4_FRAGMENT_OFFSET_MASK, skipDhcpv4Filter);
-            // Check it's addressed to DHCP client port.
-            gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE);
-            gen.addLoad16Indexed(R0, UDP_DESTINATION_PORT_OFFSET);
-            gen.addJumpIfR0NotEquals(DHCP_CLIENT_PORT, skipDhcpv4Filter);
-            // Check it's DHCP to our MAC address.
-            gen.addLoadImmediate(R0, DHCP_CLIENT_MAC_OFFSET);
-            // NOTE: Relies on R1 containing IPv4 header offset.
-            gen.addAddR1ToR0();
-            gen.addJumpIfBytesAtR0NotEqual(mHardwareAddress, skipDhcpv4Filter);
-            maybeSetupCounter(gen, Counter.PASSED_DHCP);
-            gen.addJump(mCountAndPassLabel);
-
-            // Drop all multicasts/broadcasts.
-            gen.defineLabel(skipDhcpv4Filter);
-
-            // If IPv4 destination address is in multicast range, drop.
-            gen.addLoad8(R0, IPV4_DEST_ADDR_OFFSET);
-            gen.addAnd(0xf0);
-            maybeSetupCounter(gen, Counter.DROPPED_IPV4_MULTICAST);
-            gen.addJumpIfR0Equals(0xe0, mCountAndDropLabel);
-
-            // If IPv4 broadcast packet, drop regardless of L2 (b/30231088).
-            maybeSetupCounter(gen, Counter.DROPPED_IPV4_BROADCAST_ADDR);
-            gen.addLoad32(R0, IPV4_DEST_ADDR_OFFSET);
-            gen.addJumpIfR0Equals(IPV4_BROADCAST_ADDRESS, mCountAndDropLabel);
-            if (mIPv4Address != null && mIPv4PrefixLength < 31) {
-                maybeSetupCounter(gen, Counter.DROPPED_IPV4_BROADCAST_NET);
-                int broadcastAddr = ipv4BroadcastAddress(mIPv4Address, mIPv4PrefixLength);
-                gen.addJumpIfR0Equals(broadcastAddr, mCountAndDropLabel);
-            }
-
-            // If any TCP keepalive filter matches, drop
-            generateV4KeepaliveFilters(gen);
-
-            // If any NAT-T keepalive filter matches, drop
-            generateV4NattKeepaliveFilters(gen);
-
-            // Otherwise, this is an IPv4 unicast, pass
-            // If L2 broadcast packet, drop.
-            // TODO: can we invert this condition to fall through to the common pass case below?
-            maybeSetupCounter(gen, Counter.PASSED_IPV4_UNICAST);
-            gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
-            gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
-            maybeSetupCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST);
-            gen.addJump(mCountAndDropLabel);
-        } else {
-            generateV4KeepaliveFilters(gen);
-            generateV4NattKeepaliveFilters(gen);
-        }
-
-        // Otherwise, pass
-        maybeSetupCounter(gen, Counter.PASSED_IPV4);
-        gen.addJump(mCountAndPassLabel);
-    }
-
-    @GuardedBy("this")
-    private void generateKeepaliveFilters(ApfV4Generator gen, Class<?> filterType, int proto,
-            int offset, String label) throws IllegalInstructionException {
-        final boolean haveKeepaliveResponses = CollectionUtils.any(mKeepalivePackets,
-                ack -> filterType.isInstance(ack));
-
-        // If no keepalive packets of this type
-        if (!haveKeepaliveResponses) return;
-
-        // If not the right proto, skip keepalive filters
-        gen.addLoad8(R0, offset);
-        gen.addJumpIfR0NotEquals(proto, label);
-
-        // Drop Keepalive responses
-        for (int i = 0; i < mKeepalivePackets.size(); ++i) {
-            final KeepalivePacket response = mKeepalivePackets.valueAt(i);
-            if (filterType.isInstance(response)) response.generateFilterLocked(gen);
-        }
-
-        gen.defineLabel(label);
-    }
-
-    @GuardedBy("this")
-    private void generateV4KeepaliveFilters(ApfV4Generator gen) throws IllegalInstructionException {
-        generateKeepaliveFilters(gen, TcpKeepaliveAckV4.class, IPPROTO_TCP, IPV4_PROTOCOL_OFFSET,
-                "skip_v4_keepalive_filter");
-    }
-
-    @GuardedBy("this")
-    private void generateV4NattKeepaliveFilters(ApfV4Generator gen)
-            throws IllegalInstructionException {
-        generateKeepaliveFilters(gen, NattKeepaliveResponse.class,
-                IPPROTO_UDP, IPV4_PROTOCOL_OFFSET, "skip_v4_nattkeepalive_filter");
-    }
-
-    /**
-     * Generate filter code to process IPv6 packets. Execution of this code ends in either the
-     * DROP_LABEL or PASS_LABEL, or falls off the end for ICMPv6 packets.
-     * Preconditions:
-     *  - Packet being filtered is IPv6
-     */
-    @GuardedBy("this")
-    private void generateIPv6FilterLocked(ApfV4Generator gen) throws IllegalInstructionException {
-        // Here's a basic summary of what the IPv6 filter program does:
-        //
-        // if there is a hop-by-hop option present (e.g. MLD query)
-        //   pass
-        // if we're dropping multicast
-        //   if it's not IPCMv6 or it's ICMPv6 but we're in doze mode:
-        //     if it's multicast:
-        //       drop
-        //     pass
-        // if it's ICMPv6 RS to any:
-        //   drop
-        // if it's ICMPv6 NA to anything in ff02::/120
-        //   drop
-        // if keepalive ack
-        //   drop
-
-        gen.addLoad8(R0, IPV6_NEXT_HEADER_OFFSET);
-
-        // MLD packets set the router-alert hop-by-hop option.
-        // TODO: be smarter about not blindly passing every packet with HBH options.
-        maybeSetupCounter(gen, Counter.PASSED_MLD);
-        gen.addJumpIfR0Equals(IPPROTO_HOPOPTS, mCountAndPassLabel);
-
-        // Drop multicast if the multicast filter is enabled.
-        if (mMulticastFilter) {
-            final String skipIPv6MulticastFilterLabel = "skipIPv6MulticastFilter";
-            final String dropAllIPv6MulticastsLabel = "dropAllIPv6Multicast";
-
-            // While in doze mode, drop ICMPv6 multicast pings, let the others pass.
-            // While awake, let all ICMPv6 multicasts through.
-            if (mInDozeMode) {
-                // Not ICMPv6? -> Proceed to multicast filtering
-                gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, dropAllIPv6MulticastsLabel);
-
-                // ICMPv6 but not ECHO? -> Skip the multicast filter.
-                // (ICMPv6 ECHO requests will go through the multicast filter below).
-                gen.addLoad8(R0, ICMP6_TYPE_OFFSET);
-                gen.addJumpIfR0NotEquals(ICMPV6_ECHO_REQUEST_TYPE, skipIPv6MulticastFilterLabel);
-            } else {
-                gen.addJumpIfR0Equals(IPPROTO_ICMPV6, skipIPv6MulticastFilterLabel);
-            }
-
-            // Drop all other packets sent to ff00::/8 (multicast prefix).
-            gen.defineLabel(dropAllIPv6MulticastsLabel);
-            maybeSetupCounter(gen, Counter.DROPPED_IPV6_NON_ICMP_MULTICAST);
-            gen.addLoad8(R0, IPV6_DEST_ADDR_OFFSET);
-            gen.addJumpIfR0Equals(0xff, mCountAndDropLabel);
-            // If any keepalive filter matches, drop
-            generateV6KeepaliveFilters(gen);
-            // Not multicast. Pass.
-            maybeSetupCounter(gen, Counter.PASSED_IPV6_UNICAST_NON_ICMP);
-            gen.addJump(mCountAndPassLabel);
-            gen.defineLabel(skipIPv6MulticastFilterLabel);
-        } else {
-            generateV6KeepaliveFilters(gen);
-            // If not ICMPv6, pass.
-            maybeSetupCounter(gen, Counter.PASSED_IPV6_NON_ICMP);
-            gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, mCountAndPassLabel);
-        }
-
-        // If we got this far, the packet is ICMPv6.  Drop some specific types.
-
-        // Add unsolicited multicast neighbor announcements filter
-        String skipUnsolicitedMulticastNALabel = "skipUnsolicitedMulticastNA";
-        gen.addLoad8(R0, ICMP6_TYPE_OFFSET);
-        // Drop all router solicitations (b/32833400)
-        maybeSetupCounter(gen, Counter.DROPPED_IPV6_ROUTER_SOLICITATION);
-        gen.addJumpIfR0Equals(ICMPV6_ROUTER_SOLICITATION, mCountAndDropLabel);
-        // If not neighbor announcements, skip filter.
-        gen.addJumpIfR0NotEquals(ICMPV6_NEIGHBOR_ADVERTISEMENT, skipUnsolicitedMulticastNALabel);
-        // Drop all multicast NA to ff02::/120.
-        // This is a way to cover ff02::1 and ff02::2 with a single JNEBS.
-        // TODO: Drop only if they don't contain the address of on-link neighbours.
-        final byte[] unsolicitedNaDropPrefix = Arrays.copyOf(IPV6_ALL_NODES_ADDRESS, 15);
-        gen.addLoadImmediate(R0, IPV6_DEST_ADDR_OFFSET);
-        gen.addJumpIfBytesAtR0NotEqual(unsolicitedNaDropPrefix, skipUnsolicitedMulticastNALabel);
-
-        maybeSetupCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA);
-        gen.addJump(mCountAndDropLabel);
-        gen.defineLabel(skipUnsolicitedMulticastNALabel);
-    }
-
-    /** Encodes qname in TLV pattern. */
-    @VisibleForTesting
-    public static byte[] encodeQname(String[] labels) {
-        final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        for (String label : labels) {
-            byte[] labelBytes = label.getBytes(StandardCharsets.UTF_8);
-            out.write(labelBytes.length);
-            out.write(labelBytes, 0, labelBytes.length);
-        }
-        out.write(0);
-        return out.toByteArray();
-    }
-
-    /**
-     * Generate filter code to process mDNS packets. Execution of this code ends in * DROP_LABEL
-     * or PASS_LABEL if the packet is mDNS packets. Otherwise, skip this check.
-     */
-    @GuardedBy("this")
-    private void generateMdnsFilterLocked(ApfV4Generator gen)
-            throws IllegalInstructionException {
-        final String skipMdnsv4Filter = "skip_mdns_v4_filter";
-        final String skipMdnsFilter = "skip_mdns_filter";
-        final String checkMdnsUdpPort = "check_mdns_udp_port";
-        final String mDnsAcceptPacket = "mdns_accept_packet";
-        final String mDnsDropPacket = "mdns_drop_packet";
-
-        // Only turn on the filter if multicast filter is on and the qname allowlist is non-empty.
-        if (!mMulticastFilter || mMdnsAllowList.isEmpty()) {
-            return;
-        }
-
-        // Here's a basic summary of what the mDNS filter program does:
-        //
-        // if it is a multicast mDNS packet
-        //    if QDCOUNT != 1
-        //       pass
-        //    else if the QNAME is in the allowlist
-        //       pass
-        //    else:
-        //       drop
-        //
-        // A packet is considered as a multicast mDNS packet if it matches all the following
-        // conditions
-        //   1. its destination MAC address matches 01:00:5E:00:00:FB or 33:33:00:00:00:FB, for
-        //   v4 and v6 respectively.
-        //   2. it is an IPv4/IPv6 packet
-        //   3. it is a UDP packet with port 5353
-
-        // Check it's L2 mDNS multicast address.
-        gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
-        gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V4_MAC_ADDRESS,
-                skipMdnsv4Filter);
-
-        // Checks it's IPv4.
-        gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET);
-        gen.addJumpIfR0NotEquals(ETH_P_IP, skipMdnsFilter);
-
-        // Checks it's UDP.
-        gen.addLoad8(R0, IPV4_PROTOCOL_OFFSET);
-        gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipMdnsFilter);
-        // Set R1 to IPv4 header.
-        gen.addLoadFromMemory(R1, MemorySlot.IPV4_HEADER_SIZE);
-        gen.addJump(checkMdnsUdpPort);
-
-        gen.defineLabel(skipMdnsv4Filter);
-
-        // Checks it's L2 mDNS multicast address.
-        // Relies on R0 containing the ethernet destination mac address offset.
-        gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V6_MAC_ADDRESS, skipMdnsFilter);
-
-        // Checks it's IPv6.
-        gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET);
-        gen.addJumpIfR0NotEquals(ETH_P_IPV6, skipMdnsFilter);
-
-        // Checks it's UDP.
-        gen.addLoad8(R0, IPV6_NEXT_HEADER_OFFSET);
-        gen.addJumpIfR0NotEquals(IPPROTO_UDP, skipMdnsFilter);
-
-        // Set R1 to IPv6 header.
-        gen.addLoadImmediate(R1, IPV6_HEADER_LEN);
-
-        // Checks it's mDNS UDP port
-        gen.defineLabel(checkMdnsUdpPort);
-        gen.addLoad16Indexed(R0, UDP_DESTINATION_PORT_OFFSET);
-        gen.addJumpIfR0NotEquals(MDNS_PORT, skipMdnsFilter);
-
-        gen.addLoad16Indexed(R0, MDNS_QDCOUNT_OFFSET);
-        // If QDCOUNT != 1, pass the packet
-        gen.addJumpIfR0NotEquals(1, mDnsAcceptPacket);
-
-        // If QDCOUNT == 1, matches the QNAME with allowlist.
-        // Load offset for the first QNAME.
-        gen.addLoadImmediate(R0, MDNS_QNAME_OFFSET);
-        gen.addAddR1ToR0();
-
-        // Check first QNAME against allowlist
-        for (int i = 0; i < mMdnsAllowList.size(); ++i) {
-            final String mDnsNextAllowedQnameCheck = "mdns_next_allowed_qname_check" + i;
-            final byte[] encodedQname = encodeQname(mMdnsAllowList.get(i));
-            gen.addJumpIfBytesAtR0NotEqual(encodedQname, mDnsNextAllowedQnameCheck);
-            // QNAME matched
-            gen.addJump(mDnsAcceptPacket);
-            // QNAME not matched
-            gen.defineLabel(mDnsNextAllowedQnameCheck);
-        }
-        // If QNAME doesn't match any entries in allowlist, drop the packet.
-        gen.defineLabel(mDnsDropPacket);
-        maybeSetupCounter(gen, Counter.DROPPED_MDNS);
-        gen.addJump(mCountAndDropLabel);
-
-        gen.defineLabel(mDnsAcceptPacket);
-        maybeSetupCounter(gen, Counter.PASSED_MDNS);
-        gen.addJump(mCountAndPassLabel);
-
-
-        gen.defineLabel(skipMdnsFilter);
-    }
-
-    @GuardedBy("this")
-    private void generateV6KeepaliveFilters(ApfV4Generator gen) throws IllegalInstructionException {
-        generateKeepaliveFilters(gen, TcpKeepaliveAckV6.class, IPPROTO_TCP, IPV6_NEXT_HEADER_OFFSET,
-                "skip_v6_keepalive_filter");
-    }
-
-    /**
-     * Begin generating an APF program to:
-     * <ul>
-     * <li>Drop/Pass 802.3 frames (based on policy)
-     * <li>Drop packets with EtherType within the Black List
-     * <li>Drop ARP requests not for us, if mIPv4Address is set,
-     * <li>Drop IPv4 broadcast packets, except DHCP destined to our MAC,
-     * <li>Drop IPv4 multicast packets, if mMulticastFilter,
-     * <li>Pass all other IPv4 packets,
-     * <li>Drop all broadcast non-IP non-ARP packets.
-     * <li>Pass all non-ICMPv6 IPv6 packets,
-     * <li>Pass all non-IPv4 and non-IPv6 packets,
-     * <li>Drop IPv6 ICMPv6 NAs to anything in ff02::/120.
-     * <li>Drop IPv6 ICMPv6 RSs.
-     * <li>Filter IPv4 packets (see generateIPv4FilterLocked())
-     * <li>Filter IPv6 packets (see generateIPv6FilterLocked())
-     * <li>Let execution continue off the end of the program for IPv6 ICMPv6 packets. This allows
-     *     insertion of RA filters here, or if there aren't any, just passes the packets.
-     * </ul>
-     */
-    @GuardedBy("this")
-    protected ApfV4Generator emitPrologueLocked() throws IllegalInstructionException {
-        // This is guaranteed to succeed because of the check in maybeCreate.
-        ApfV4Generator gen = new ApfV4Generator(mApfVersionSupported, mMaximumApfProgramSize,
-                mMaximumApfProgramSize);
-
-        if (hasDataAccess(mApfVersionSupported)) {
-            // Increment TOTAL_PACKETS
-            maybeSetupCounter(gen, Counter.TOTAL_PACKETS);
-            gen.addLoadData(R0, 0);  // load counter
-            gen.addAdd(1);
-            gen.addStoreData(R0, 0);  // write-back counter
-
-            maybeSetupCounter(gen, Counter.FILTER_AGE_SECONDS);
-            gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS);
-            gen.addStoreData(R0, 0);  // store 'counter'
-
-            // requires a new enough APFv5+ interpreter, otherwise will be 0
-            maybeSetupCounter(gen, Counter.FILTER_AGE_16384THS);
-            gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_16384THS);
-            gen.addStoreData(R0, 0);  // store 'counter'
-
-            // requires a new enough APFv5+ interpreter, otherwise will be 0
-            maybeSetupCounter(gen, Counter.APF_VERSION);
-            gen.addLoadFromMemory(R0, MemorySlot.APF_VERSION);
-            gen.addStoreData(R0, 0);  // store 'counter'
-
-            // store this program's sequential id, for later comparison
-            maybeSetupCounter(gen, Counter.APF_PROGRAM_ID);
-            gen.addLoadImmediate(R0, mNumProgramUpdates);
-            gen.addStoreData(R0, 0);  // store 'counter'
-        }
-
-        // Here's a basic summary of what the initial program does:
-        //
-        // if it's a 802.3 Frame (ethtype < 0x0600):
-        //    drop or pass based on configurations
-        // if it has a ether-type that belongs to the black list
-        //    drop
-        // if it's ARP:
-        //   insert ARP filter to drop or pass these appropriately
-        // if it's IPv4:
-        //   insert IPv4 filter to drop or pass these appropriately
-        // if it's not IPv6:
-        //   if it's broadcast:
-        //     drop
-        //   pass
-        // insert IPv6 filter to drop, pass, or fall off the end for ICMPv6 packets
-
-        gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET);
-
-        if (mDrop802_3Frames) {
-            // drop 802.3 frames (ethtype < 0x0600)
-            maybeSetupCounter(gen, Counter.DROPPED_802_3_FRAME);
-            gen.addJumpIfR0LessThan(ETH_TYPE_MIN, mCountAndDropLabel);
-        }
-
-        // Handle ether-type black list
-        maybeSetupCounter(gen, Counter.DROPPED_ETHERTYPE_NOT_ALLOWED);
-        for (int p : mEthTypeBlackList) {
-            gen.addJumpIfR0Equals(p, mCountAndDropLabel);
-        }
-
-        // Add ARP filters:
-        String skipArpFiltersLabel = "skipArpFilters";
-        gen.addJumpIfR0NotEquals(ETH_P_ARP, skipArpFiltersLabel);
-        generateArpFilterLocked(gen);
-        gen.defineLabel(skipArpFiltersLabel);
-
-        // Add mDNS filter:
-        generateMdnsFilterLocked(gen);
-        gen.addLoad16(R0, ETH_ETHERTYPE_OFFSET);
-
-        // Add IPv4 filters:
-        String skipIPv4FiltersLabel = "skipIPv4Filters";
-        gen.addJumpIfR0NotEquals(ETH_P_IP, skipIPv4FiltersLabel);
-        generateIPv4FilterLocked(gen);
-        gen.defineLabel(skipIPv4FiltersLabel);
-
-        // Check for IPv6:
-        // NOTE: Relies on R0 containing ethertype. This is safe because if we got here, we did
-        // not execute the IPv4 filter, since this filter do not fall through, but either drop or
-        // pass.
-        String ipv6FilterLabel = "IPv6Filters";
-        gen.addJumpIfR0Equals(ETH_P_IPV6, ipv6FilterLabel);
-
-        // Drop non-IP non-ARP broadcasts, pass the rest
-        gen.addLoadImmediate(R0, ETH_DEST_ADDR_OFFSET);
-        maybeSetupCounter(gen, Counter.PASSED_NON_IP_UNICAST);
-        gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
-        maybeSetupCounter(gen, Counter.DROPPED_ETH_BROADCAST);
-        gen.addJump(mCountAndDropLabel);
-
-        // Add IPv6 filters:
-        gen.defineLabel(ipv6FilterLabel);
-        generateIPv6FilterLocked(gen);
-        return gen;
-    }
-
-    /**
-     * Append packet counting epilogue to the APF program.
-     *
-     * Currently, the epilogue consists of two trampolines which count passed and dropped packets
-     * before jumping to the actual PASS and DROP labels.
-     */
-    @GuardedBy("this")
-    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 (!hasDataAccess(mApfVersionSupported)) return;
-
-        // Execution will reach the bottom of the program if none of the filters match,
-        // which will pass the packet to the application processor.
-        maybeSetupCounter(gen, Counter.PASSED_IPV6_ICMP);
-
-        // Append the count & pass trampoline, which increments the counter at the data address
-        // pointed to by R1, then jumps to the pass label. This saves a few bytes over inserting
-        // the entire sequence inline for every counter.
-        gen.defineLabel(mCountAndPassLabel);
-        gen.addLoadData(R0, 0);   // R0 = *(R1 + 0)
-        gen.addAdd(1);                     // R0++
-        gen.addStoreData(R0, 0);  // *(R1 + 0) = R0
-        gen.addJump(gen.PASS_LABEL);
-
-        // Same as above for the count & drop trampoline.
-        gen.defineLabel(mCountAndDropLabel);
-        gen.addLoadData(R0, 0);   // R0 = *(R1 + 0)
-        gen.addAdd(1);                     // R0++
-        gen.addStoreData(R0, 0);  // *(R1 + 0) = R0
-        gen.addJump(gen.DROP_LABEL);
-    }
-
-    /**
-     * Generate and install a new filter program.
-     */
-    @GuardedBy("this")
-    // errorprone false positive on ra#shouldFilter and ra#generateFilterLocked
-    @SuppressWarnings("GuardedBy")
-    @VisibleForTesting
-    public void installNewProgramLocked() {
-        purgeExpiredRasLocked();
-        ArrayList<Ra> rasToFilter = new ArrayList<>();
-        final byte[] program;
-        long programMinLifetime = Long.MAX_VALUE;
-        long maximumApfProgramSize = mMaximumApfProgramSize;
-        if (hasDataAccess(mApfVersionSupported)) {
-            // Reserve space for the counters.
-            maximumApfProgramSize -= Counter.totalSize();
-        }
-
-        mProgramBaseTime = currentTimeSeconds();
-        try {
-            // Step 1: Determine how many RA filters we can fit in the program.
-            ApfV4Generator gen = emitPrologueLocked();
-
-            // The epilogue normally goes after the RA filters, but add it early to include its
-            // length when estimating the total.
-            emitEpilogue(gen);
-
-            // Can't fit the program even without any RA filters?
-            if (gen.programLengthOverEstimate() > maximumApfProgramSize) {
-                Log.e(TAG, "Program exceeds maximum size " + maximumApfProgramSize);
-                sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE);
-                return;
-            }
-
-            for (Ra ra : mRas) {
-                if (!ra.shouldFilter()) continue;
-                ra.generateFilterLocked(gen);
-                // Stop if we get too big.
-                if (gen.programLengthOverEstimate() > maximumApfProgramSize) {
-                    if (VDBG) Log.d(TAG, "Past maximum program size, skipping RAs");
-                    sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_OVER_SIZE_FAILURE);
-                    break;
-                }
-
-                rasToFilter.add(ra);
-            }
-
-            // Step 2: Actually generate the program
-            gen = emitPrologueLocked();
-            for (Ra ra : rasToFilter) {
-                programMinLifetime = Math.min(programMinLifetime, ra.generateFilterLocked(gen));
-            }
-            emitEpilogue(gen);
-            program = gen.generate();
-        } catch (IllegalInstructionException|IllegalStateException e) {
-            Log.e(TAG, "Failed to generate APF program.", e);
-            sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_GENERATE_FILTER_EXCEPTION);
-            return;
-        }
-        if (mIsRunning && !mIpClientCallback.installPacketFilter(program)) {
-            sendNetworkQuirkMetrics(NetworkQuirkEvent.QE_APF_INSTALL_FAILURE);
-        }
-        mLastTimeInstalledProgram = mProgramBaseTime;
-        mLastInstalledProgramMinLifetime = programMinLifetime;
-        mLastInstalledProgram = program;
-        mNumProgramUpdates++;
-        mMaxProgramSize = Math.max(mMaxProgramSize, program.length);
-
-        if (VDBG) {
-            hexDump("Installing filter: ", program, program.length);
-        }
-        logApfProgramEventLocked(mProgramBaseTime);
-        mLastInstallEvent = new ApfProgramEvent.Builder()
-                .setLifetime(programMinLifetime)
-                .setFilteredRas(rasToFilter.size())
-                .setCurrentRas(mRas.size())
-                .setProgramLength(program.length)
-                .setFlags(mIPv4Address != null, mMulticastFilter);
-    }
-
-    @GuardedBy("this")
-    private void logApfProgramEventLocked(long now) {
-        if (mLastInstallEvent == null) {
-            return;
-        }
-        ApfProgramEvent.Builder ev = mLastInstallEvent;
-        mLastInstallEvent = null;
-        final long actualLifetime = now - mLastTimeInstalledProgram;
-        ev.setActualLifetime(actualLifetime);
-        if (actualLifetime < APF_PROGRAM_EVENT_LIFETIME_THRESHOLD) {
-            return;
-        }
-        mMetricsLog.log(ev.build());
-    }
-
-    /**
-     * Returns {@code true} if a new program should be installed because the current one dies soon.
-     */
-    @GuardedBy("this")
-    private boolean shouldInstallnewProgram() {
-        long expiry = mLastTimeInstalledProgram + mLastInstalledProgramMinLifetime;
-        return expiry < currentTimeSeconds() + MAX_PROGRAM_LIFETIME_WORTH_REFRESHING;
-    }
-
-    private void hexDump(String msg, byte[] packet, int length) {
-        log(msg + HexDump.toHexString(packet, 0, length, false /* lowercase */));
-    }
-
-    @GuardedBy("this")
-    private void purgeExpiredRasLocked() {
-        for (int i = 0; i < mRas.size();) {
-            if (mRas.get(i).isExpired()) {
-                log("Expiring " + mRas.get(i));
-                mRas.remove(i);
-            } else {
-                i++;
-            }
-        }
-    }
-
-    // Get the minimum value excludes zero. This is used for calculating the lowest lifetime values
-    // in RA packets. Zero lifetimes are excluded because we want to detect whether there is any
-    // unusually small lifetimes but zero lifetime is actually valid (cease to be a default router
-    // or the option is no longer be used). Number of zero lifetime RAs is collected in a different
-    // Metrics.
-    private long getMinForPositiveValue(long oldMinValue, long value) {
-        if (value < 1) return oldMinValue;
-        return Math.min(oldMinValue, value);
-    }
-
-    private int getMinForPositiveValue(int oldMinValue, int value) {
-        return (int) getMinForPositiveValue((long) oldMinValue, (long) value);
-    }
-
-    /**
-     * Process an RA packet, updating the list of known RAs and installing a new APF program
-     * if the current APF program should be updated.
-     * @return a ProcessRaResult enum describing what action was performed.
-     */
-    @VisibleForTesting
-    public synchronized ProcessRaResult processRa(byte[] packet, int length) {
-        if (VDBG) hexDump("Read packet = ", packet, length);
-
-        // Have we seen this RA before?
-        for (int i = 0; i < mRas.size(); i++) {
-            Ra ra = mRas.get(i);
-            if (ra.matches(packet, length)) {
-                if (VDBG) log("matched RA " + ra);
-                // Update lifetimes.
-                ra.mLastSeen = currentTimeSeconds();
-                ra.seenCount++;
-
-                // Keep mRas in LRU order so as to prioritize generating filters for recently seen
-                // RAs. LRU prioritizes this because RA filters are generated in order from mRas
-                // until the filter program exceeds the maximum filter program size allowed by the
-                // chipset, so RAs appearing earlier in mRas are more likely to make it into the
-                // filter program.
-                // TODO: consider sorting the RAs in order of increasing expiry time as well.
-                // Swap to front of array.
-                mRas.add(0, mRas.remove(i));
-
-                // If the current program doesn't expire for a while, don't update.
-                if (shouldInstallnewProgram()) {
-                    installNewProgramLocked();
-                    return ProcessRaResult.UPDATE_EXPIRY;
-                }
-                return ProcessRaResult.MATCH;
-            }
-        }
-        purgeExpiredRasLocked();
-
-        mMaxDistinctRas = Math.max(mMaxDistinctRas, mRas.size() + 1);
-
-        // TODO: figure out how to proceed when we've received more than MAX_RAS RAs.
-        if (mRas.size() >= MAX_RAS) {
-            return ProcessRaResult.DROPPED;
-        }
-        final Ra ra;
-        try {
-            ra = new Ra(packet, length);
-        } catch (Exception e) {
-            Log.e(TAG, "Error parsing RA", e);
-            mNumParseErrorRas++;
-            return ProcessRaResult.PARSE_ERROR;
-        }
-
-        // Update info for Metrics
-        mLowestRouterLifetimeSeconds = getMinForPositiveValue(
-                mLowestRouterLifetimeSeconds, ra.routerLifetime());
-        mLowestPioValidLifetimeSeconds = getMinForPositiveValue(
-                mLowestPioValidLifetimeSeconds, ra.minPioValidLifetime());
-        mLowestRioRouteLifetimeSeconds = getMinForPositiveValue(
-                mLowestRioRouteLifetimeSeconds, ra.minRioRouteLifetime());
-        mLowestRdnssLifetimeSeconds = getMinForPositiveValue(
-                mLowestRdnssLifetimeSeconds, ra.minRdnssLifetime());
-
-        // Ignore 0 lifetime RAs.
-        if (ra.isExpired()) {
-            mNumZeroLifetimeRas++;
-            return ProcessRaResult.ZERO_LIFETIME;
-        }
-        log("Adding " + ra);
-        mRas.add(ra);
-        installNewProgramLocked();
-        return ProcessRaResult.UPDATE_NEW_RA;
-    }
-
-    /**
-     * Create an {@link LegacyApfFilter} if {@code apfCapabilities} indicates support for packet
-     * filtering using APF programs.
-     */
-    public static LegacyApfFilter maybeCreate(Context context, ApfFilter.ApfConfiguration config,
-            InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback,
-            NetworkQuirkMetrics networkQuirkMetrics) {
-        if (context == null || config == null || ifParams == null) return null;
-        if (!ApfV4Generator.supportsVersion(config.apfVersionSupported)) {
-            return null;
-        }
-        if (config.apfRamSize < 512) {
-            Log.e(TAG, "Unacceptably small APF limit: " + config.apfRamSize);
-            return null;
-        }
-
-        return new LegacyApfFilter(context, config, ifParams, ipClientCallback,
-                new IpConnectivityLog(), networkQuirkMetrics);
-    }
-
-    private synchronized void collectAndSendMetrics() {
-        if (mIpClientRaInfoMetrics == null || mApfSessionInfoMetrics == null) return;
-        final long sessionDurationMs = mClock.elapsedRealtime() - mSessionStartMs;
-        if (sessionDurationMs < mMinMetricsSessionDurationMs) return;
-
-        // Collect and send IpClientRaInfoMetrics.
-        mIpClientRaInfoMetrics.setMaxNumberOfDistinctRas(mMaxDistinctRas);
-        mIpClientRaInfoMetrics.setNumberOfZeroLifetimeRas(mNumZeroLifetimeRas);
-        mIpClientRaInfoMetrics.setNumberOfParsingErrorRas(mNumParseErrorRas);
-        mIpClientRaInfoMetrics.setLowestRouterLifetimeSeconds(mLowestRouterLifetimeSeconds);
-        mIpClientRaInfoMetrics.setLowestPioValidLifetimeSeconds(mLowestPioValidLifetimeSeconds);
-        mIpClientRaInfoMetrics.setLowestRioRouteLifetimeSeconds(mLowestRioRouteLifetimeSeconds);
-        mIpClientRaInfoMetrics.setLowestRdnssLifetimeSeconds(mLowestRdnssLifetimeSeconds);
-        mIpClientRaInfoMetrics.statsWrite();
-
-        // Collect and send ApfSessionInfoMetrics.
-        mApfSessionInfoMetrics.setVersion(mApfVersionSupported);
-        mApfSessionInfoMetrics.setMemorySize(mMaximumApfProgramSize);
-        mApfSessionInfoMetrics.setApfSessionDurationSeconds(
-                (int) (sessionDurationMs / DateUtils.SECOND_IN_MILLIS));
-        mApfSessionInfoMetrics.setNumOfTimesApfProgramUpdated(mNumProgramUpdates);
-        mApfSessionInfoMetrics.setMaxProgramSize(mMaxProgramSize);
-        for (Map.Entry<Counter, Long> entry : mApfCounterTracker.getCounters().entrySet()) {
-            if (entry.getValue() > 0) {
-                mApfSessionInfoMetrics.addApfCounter(entry.getKey(), entry.getValue());
-            }
-        }
-        mApfSessionInfoMetrics.statsWrite();
-    }
-
-    public synchronized void shutdown() {
-        collectAndSendMetrics();
-        if (mReceiveThread != null) {
-            log("shutting down");
-            mReceiveThread.halt();  // Also closes socket.
-            mReceiveThread = null;
-        }
-        mRas.clear();
-        mContext.unregisterReceiver(mDeviceIdleReceiver);
-    }
-
-    public synchronized void setMulticastFilter(boolean isEnabled) {
-        if (mMulticastFilter == isEnabled) return;
-        mMulticastFilter = isEnabled;
-        if (!isEnabled) {
-            mNumProgramUpdatesAllowingMulticast++;
-        }
-        installNewProgramLocked();
-    }
-
-    /** Adds qname to the mDNS allowlist */
-    public synchronized void addToMdnsAllowList(String[] labels) {
-        mMdnsAllowList.add(labels);
-        if (mMulticastFilter) {
-            installNewProgramLocked();
-        }
-    }
-
-    /** Removes qname from the mDNS allowlist */
-    public synchronized void removeFromAllowList(String[] labels) {
-        mMdnsAllowList.removeIf(e -> Arrays.equals(labels, e));
-        if (mMulticastFilter) {
-            installNewProgramLocked();
-        }
-    }
-
-    @VisibleForTesting
-    public synchronized void setDozeMode(boolean isEnabled) {
-        if (mInDozeMode == isEnabled) return;
-        mInDozeMode = isEnabled;
-        installNewProgramLocked();
-    }
-
-    /** Find the single IPv4 LinkAddress if there is one, otherwise return null. */
-    private static LinkAddress findIPv4LinkAddress(LinkProperties lp) {
-        LinkAddress ipv4Address = null;
-        for (LinkAddress address : lp.getLinkAddresses()) {
-            if (!(address.getAddress() instanceof Inet4Address)) {
-                continue;
-            }
-            if (ipv4Address != null && !ipv4Address.isSameAddressAs(address)) {
-                // More than one IPv4 address, abort.
-                return null;
-            }
-            ipv4Address = address;
-        }
-        return ipv4Address;
-    }
-
-    public synchronized void setLinkProperties(LinkProperties lp) {
-        // NOTE: Do not keep a copy of LinkProperties as it would further duplicate state.
-        final LinkAddress ipv4Address = findIPv4LinkAddress(lp);
-        final byte[] addr = (ipv4Address != null) ? ipv4Address.getAddress().getAddress() : null;
-        final int prefix = (ipv4Address != null) ? ipv4Address.getPrefixLength() : 0;
-        if ((prefix == mIPv4PrefixLength) && Arrays.equals(addr, mIPv4Address)) {
-            return;
-        }
-        mIPv4Address = addr;
-        mIPv4PrefixLength = prefix;
-        installNewProgramLocked();
-    }
-
-    /**
-     * Add TCP keepalive ack packet filter.
-     * This will add a filter to drop acks to the keepalive packet passed as an argument.
-     *
-     * @param slot The index used to access the filter.
-     * @param sentKeepalivePacket The attributes of the sent keepalive packet.
-     */
-    public synchronized void addTcpKeepalivePacketFilter(final int slot,
-            final TcpKeepalivePacketDataParcelable sentKeepalivePacket) {
-        log("Adding keepalive ack(" + slot + ")");
-        if (null != mKeepalivePackets.get(slot)) {
-            throw new IllegalArgumentException("Keepalive slot " + slot + " is occupied");
-        }
-        final int ipVersion = sentKeepalivePacket.srcAddress.length == 4 ? 4 : 6;
-        mKeepalivePackets.put(slot, (ipVersion == 4)
-                ? new TcpKeepaliveAckV4(sentKeepalivePacket)
-                : new TcpKeepaliveAckV6(sentKeepalivePacket));
-        installNewProgramLocked();
-    }
-
-    /**
-     * Add NAT-T keepalive packet filter.
-     * This will add a filter to drop NAT-T keepalive packet which is passed as an argument.
-     *
-     * @param slot The index used to access the filter.
-     * @param sentKeepalivePacket The attributes of the sent keepalive packet.
-     */
-    public synchronized void addNattKeepalivePacketFilter(final int slot,
-            final NattKeepalivePacketDataParcelable sentKeepalivePacket) {
-        log("Adding NAT-T keepalive packet(" + slot + ")");
-        if (null != mKeepalivePackets.get(slot)) {
-            throw new IllegalArgumentException("NAT-T Keepalive slot " + slot + " is occupied");
-        }
-
-        // TODO : update ApfFilter to support dropping v6 keepalives
-        if (sentKeepalivePacket.srcAddress.length != 4) {
-            return;
-        }
-
-        mKeepalivePackets.put(slot, new NattKeepaliveResponse(sentKeepalivePacket));
-        installNewProgramLocked();
-    }
-
-    /**
-     * Remove keepalive packet filter.
-     *
-     * @param slot The index used to access the filter.
-     */
-    public synchronized void removeKeepalivePacketFilter(int slot) {
-        log("Removing keepalive packet(" + slot + ")");
-        mKeepalivePackets.remove(slot);
-        installNewProgramLocked();
-    }
-
-    public synchronized void dump(IndentingPrintWriter pw) {
-        pw.println(String.format(
-                "Capabilities: { apfVersionSupported: %d, maximumApfProgramSize: %d }",
-                mApfVersionSupported, mMaximumApfProgramSize));
-        pw.println("Filter update status: " + (mIsRunning ? "RUNNING" : "PAUSED"));
-        pw.println("Receive thread: " + (mReceiveThread != null ? "RUNNING" : "STOPPED"));
-        pw.println("Multicast: " + (mMulticastFilter ? "DROP" : "ALLOW"));
-        pw.println("Minimum RDNSS lifetime: " + mMinRdnssLifetimeSec);
-        try {
-            pw.println("IPv4 address: " + InetAddress.getByAddress(mIPv4Address).getHostAddress());
-        } catch (UnknownHostException|NullPointerException e) {}
-
-        if (mLastTimeInstalledProgram == 0) {
-            pw.println("No program installed.");
-            return;
-        }
-        pw.println("Program updates: " + mNumProgramUpdates);
-        pw.println(String.format(
-                "Last program length %d, installed %ds ago, lifetime %ds",
-                mLastInstalledProgram.length, currentTimeSeconds() - mLastTimeInstalledProgram,
-                mLastInstalledProgramMinLifetime));
-
-        pw.print("Denylisted Ethertypes:");
-        for (int p : mEthTypeBlackList) {
-            pw.print(String.format(" %04x", p));
-        }
-        pw.println();
-        pw.println("RA filters:");
-        pw.increaseIndent();
-        for (Ra ra: mRas) {
-            pw.println(ra);
-            pw.increaseIndent();
-            pw.println(String.format(
-                    "Seen: %d, last %ds ago", ra.seenCount, currentTimeSeconds() - ra.mLastSeen));
-            if (DBG) {
-                pw.println("Last match:");
-                pw.increaseIndent();
-                pw.println(ra.getLastMatchingPacket());
-                pw.decreaseIndent();
-            }
-            pw.decreaseIndent();
-        }
-        pw.decreaseIndent();
-
-        pw.println("TCP Keepalive filters:");
-        pw.increaseIndent();
-        for (int i = 0; i < mKeepalivePackets.size(); ++i) {
-            final KeepalivePacket keepalivePacket = mKeepalivePackets.valueAt(i);
-            if (keepalivePacket instanceof TcpKeepaliveAck) {
-                pw.print("Slot ");
-                pw.print(mKeepalivePackets.keyAt(i));
-                pw.print(": ");
-                pw.println(keepalivePacket);
-            }
-        }
-        pw.decreaseIndent();
-
-        pw.println("NAT-T Keepalive filters:");
-        pw.increaseIndent();
-        for (int i = 0; i < mKeepalivePackets.size(); ++i) {
-            final KeepalivePacket keepalivePacket = mKeepalivePackets.valueAt(i);
-            if (keepalivePacket instanceof NattKeepaliveResponse) {
-                pw.print("Slot ");
-                pw.print(mKeepalivePackets.keyAt(i));
-                pw.print(": ");
-                pw.println(keepalivePacket);
-            }
-        }
-        pw.decreaseIndent();
-
-        if (DBG) {
-            pw.println("Last program:");
-            pw.increaseIndent();
-            pw.println(HexDump.toHexString(mLastInstalledProgram, false /* lowercase */));
-            pw.decreaseIndent();
-        }
-
-        pw.println("APF packet counters: ");
-        pw.increaseIndent();
-        if (!hasDataAccess(mApfVersionSupported)) {
-            pw.println("APF counters not supported");
-        } else if (mDataSnapshot == null) {
-            pw.println("No last snapshot.");
-        } else {
-            try {
-                Counter[] counters = Counter.class.getEnumConstants();
-                for (Counter c : Arrays.asList(counters).subList(1, counters.length)) {
-                    long value = ApfCounterTracker.getCounterValue(mDataSnapshot, c);
-                    // Only print non-zero counters
-                    if (value != 0) {
-                        pw.println(c.toString() + ": " + value);
-                    }
-
-                    // If the counter's value decreases, it may have been cleaned up or there may be
-                    // a bug.
-                    if (value < mApfCounterTracker.getCounters().getOrDefault(c, 0L)) {
-                        Log.e(TAG, "Error: Counter value unexpectedly decreased.");
-                    }
-                }
-            } catch (ArrayIndexOutOfBoundsException e) {
-                pw.println("Uh-oh: " + e);
-            }
-            if (VDBG) {
-                pw.println("Raw data dump: ");
-                pw.println(HexDump.dumpHexString(mDataSnapshot));
-            }
-        }
-        pw.decreaseIndent();
-    }
-
-    // TODO: move to android.net.NetworkUtils
-    @VisibleForTesting
-    public static int ipv4BroadcastAddress(byte[] addrBytes, int prefixLength) {
-        return bytesToBEInt(addrBytes) | (int) (Integer.toUnsignedLong(-1) >>> prefixLength);
-    }
-
-    private static int uint8(byte b) {
-        return b & 0xff;
-    }
-
-    private static int getUint16(ByteBuffer buffer, int position) {
-        return buffer.getShort(position) & 0xffff;
-    }
-
-    private static long getUint32(ByteBuffer buffer, int position) {
-        return Integer.toUnsignedLong(buffer.getInt(position));
-    }
-
-    private static int getUint8(ByteBuffer buffer, int position) {
-        return uint8(buffer.get(position));
-    }
-
-    private static int bytesToBEInt(byte[] bytes) {
-        return (uint8(bytes[0]) << 24)
-                + (uint8(bytes[1]) << 16)
-                + (uint8(bytes[2]) << 8)
-                + (uint8(bytes[3]));
-    }
-
-    private static byte[] concatArrays(final byte[]... arr) {
-        int size = 0;
-        for (byte[] a : arr) {
-            size += a.length;
-        }
-        final byte[] result = new byte[size];
-        int offset = 0;
-        for (byte[] a : arr) {
-            System.arraycopy(a, 0, result, offset, a.length);
-            offset += a.length;
-        }
-        return result;
-    }
-
-    private void sendNetworkQuirkMetrics(final NetworkQuirkEvent event) {
-        if (mNetworkQuirkMetrics == null) return;
-        mNetworkQuirkMetrics.setEvent(event);
-        mNetworkQuirkMetrics.statsWrite();
-    }
-
-    /**
-     * Indicates whether the ApfFilter is currently running / paused for test and debugging
-     * purposes.
-     */
-    public boolean isRunning() {
-        return mIsRunning;
-    }
-
-    /** Pause ApfFilter updates for testing purposes. */
-    public void pause() {
-        mIsRunning = false;
-    }
-
-    /** Resume ApfFilter updates for testing purposes. */
-    public void resume() {
-        mIsRunning = true;
-    }
-
-    /** Return hex string of current APF snapshot for testing purposes. */
-    public synchronized @Nullable String getDataSnapshotHexString() {
-        if (mDataSnapshot == null) {
-            return null;
-        }
-        return HexDump.toHexString(mDataSnapshot, 0, mDataSnapshot.length, false /* lowercase */);
-    }
-}
diff --git a/src/android/net/apf/ProcfsParsingUtils.java b/src/android/net/apf/ProcfsParsingUtils.java
index 4bac0f8..0d931a7 100644
--- a/src/android/net/apf/ProcfsParsingUtils.java
+++ b/src/android/net/apf/ProcfsParsingUtils.java
@@ -15,18 +15,23 @@
  */
 package android.net.apf;
 
+import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ALL_HOST_MULTICAST;
+
 import android.annotation.NonNull;
 import android.net.MacAddress;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.HexDump;
+import com.android.net.module.util.HexDump;
 
 import java.io.BufferedReader;
 import java.io.IOException;
+import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Paths;
@@ -39,6 +44,7 @@
     private static final String IPV6_CONF_PATH = "/proc/sys/net/ipv6/conf/";
     private static final String IPV6_ANYCAST_PATH = "/proc/net/anycast6";
     private static final String ETHER_MCAST_PATH = "/proc/net/dev_mcast";
+    private static final String IPV4_MCAST_PATH = "/proc/net/igmp";
     private static final String IPV6_MCAST_PATH = "/proc/net/igmp6";
 
     private ProcfsParsingUtils() {
@@ -172,6 +178,75 @@
 
         return addresses;
     }
+
+    /**
+     * Parses IPv4 multicast addresses associated with a specific interface from a list of strings.
+     *
+     * @param lines A list of strings, each containing interface and IPv4 address information.
+     * @param ifname The name of the network interface for which to extract multicast addresses.
+     * @param endian The byte order of the address, almost always use native order.
+     * @return A list of Inet4Address objects representing the parsed IPv4 multicast addresses.
+     *         If an error occurs during parsing,
+     *         a list contains IPv4 all host (224.0.0.1) is returned.
+     */
+    @VisibleForTesting
+    public static List<Inet4Address> parseIPv4MulticastAddresses(
+            @NonNull List<String> lines, @NonNull String ifname, @NonNull ByteOrder endian) {
+        final List<Inet4Address> ipAddresses = new ArrayList<>();
+
+        try {
+            String name = "";
+            // parse output similar to `ip maddr` command (iproute2/ip/ipmaddr.c#read_igmp())
+            for (String line : lines) {
+                final String[] parts = line.trim().split("\\s+");
+                if (!line.startsWith("\t")) {
+                    name = parts[1];
+                    if (name.endsWith(":")) {
+                        name = name.substring(0, name.length() - 1);
+                    }
+                    continue;
+                }
+
+                if (!name.equals(ifname)) {
+                    continue;
+                }
+
+                final String hexIp = parts[0];
+                final byte[] ipArray = HexDump.hexStringToByteArray(hexIp);
+                final byte[] convertArray =
+                    (endian == ByteOrder.LITTLE_ENDIAN)
+                        ? convertIPv4BytesToBigEndian(ipArray) : ipArray;
+                final Inet4Address ipv4Address =
+                        (Inet4Address) InetAddress.getByAddress(convertArray);
+
+                ipAddresses.add(ipv4Address);
+            }
+        } catch (UnknownHostException | IllegalArgumentException e) {
+            Log.wtf(TAG, "failed to convert to Inet4Address.", e);
+            // always return IPv4 all host address (224.0.0.1) if any error during parsing.
+            // this aligns with kernel behavior, it will join 224.0.0.1 when the interface is up.
+            ipAddresses.clear();
+            ipAddresses.add(IPV4_ADDR_ALL_HOST_MULTICAST);
+        }
+
+        return ipAddresses;
+    }
+
+    /**
+     * Converts an IPv4 address from little-endian byte order to big-endian byte order.
+     *
+     * @param bytes The IPv4 address in little-endian byte order.
+     * @return The IPv4 address in big-endian byte order.
+     */
+    private static byte[] convertIPv4BytesToBigEndian(byte[] bytes) {
+        final ByteBuffer buffer = ByteBuffer.wrap(bytes);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+        final ByteBuffer bigEndianBuffer = ByteBuffer.allocate(4);
+        bigEndianBuffer.order(ByteOrder.BIG_ENDIAN);
+        bigEndianBuffer.putInt(buffer.getInt());
+        return bigEndianBuffer.array();
+    }
+
     /**
      * Returns the traffic class for the specified interface.
      * The function loads the existing traffic class from the file
@@ -228,4 +303,19 @@
         final List<String> lines = readFile(IPV6_MCAST_PATH);
         return parseIPv6MulticastAddresses(lines, ifname);
     }
+
+    /**
+     * The function loads the existing IPv4 multicast addresses from the file `/proc/net/igmp6`.
+     * If the file does not exist or the interface is not found, the function returns empty list.
+     *
+     * @param ifname The name of the network interface to query.
+     * @return A list of Inet4Address objects representing the IPv4 multicast addresses
+     *         found for the interface.
+     *         If the file cannot be read or there are no addresses, an empty list is returned.
+     */
+    public static List<Inet4Address> getIPv4MulticastAddresses(@NonNull String ifname) {
+        final List<String> lines = readFile(IPV4_MCAST_PATH);
+        // follow the same pattern as NetlinkMonitor#handlePacket() for device's endian order
+        return parseIPv4MulticastAddresses(lines, ifname, ByteOrder.nativeOrder());
+    }
 }
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index 7447fc5..0efe7b2 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -120,7 +120,6 @@
 import android.net.apf.AndroidPacketFilter;
 import android.net.apf.ApfCapabilities;
 import android.net.apf.ApfFilter;
-import android.net.apf.LegacyApfFilter;
 import android.net.dhcp.DhcpClient;
 import android.net.dhcp.DhcpPacket;
 import android.net.dhcp6.Dhcp6Client;
@@ -805,7 +804,6 @@
 
     // Experiment flag read from device config.
     private final boolean mDhcp6PrefixDelegationEnabled;
-    private final boolean mUseNewApfFilter;
     private final boolean mIsAcceptRaMinLftEnabled;
     private final boolean mEnableApfPollingCounters;
     private final boolean mPopulateLinkAddressLifetime;
@@ -986,15 +984,9 @@
          */
         public AndroidPacketFilter maybeCreateApfFilter(Handler handler, Context context,
                 ApfFilter.ApfConfiguration config, InterfaceParams ifParams,
-                IpClientCallbacksWrapper cb, NetworkQuirkMetrics networkQuirkMetrics,
-                boolean useNewApfFilter) {
-            if (useNewApfFilter) {
-                return ApfFilter.maybeCreate(handler, context, config, ifParams, cb,
-                        networkQuirkMetrics);
-            } else {
-                return LegacyApfFilter.maybeCreate(context, config, ifParams, cb,
-                        networkQuirkMetrics);
-            }
+                IpClientCallbacksWrapper cb, NetworkQuirkMetrics networkQuirkMetrics) {
+            return ApfFilter.maybeCreate(handler, context, config, ifParams, cb,
+                    networkQuirkMetrics);
         }
 
         /**
@@ -1070,8 +1062,6 @@
         mApfCounterPollingIntervalMs = mDependencies.getDeviceConfigPropertyInt(
                 CONFIG_APF_COUNTER_POLLING_INTERVAL_SECS,
                 DEFAULT_APF_COUNTER_POLLING_INTERVAL_SECS) * DateUtils.SECOND_IN_MILLIS;
-        mUseNewApfFilter = SdkLevel.isAtLeastV() || mDependencies.isFeatureNotChickenedOut(context,
-                APF_NEW_RA_FILTER_VERSION);
         mEnableApfPollingCounters = mDependencies.isFeatureEnabled(context,
                 APF_POLLING_COUNTERS_VERSION);
         mIsAcceptRaMinLftEnabled =
@@ -2643,7 +2633,7 @@
         setIpv6Sysctl(ACCEPT_RA, 2);
         setIpv6Sysctl(ACCEPT_RA_DEFRTR, 1);
         maybeRestoreDadTransmits();
-        if (mUseNewApfFilter && mIsAcceptRaMinLftEnabled
+        if (mIsAcceptRaMinLftEnabled
                 && mDependencies.hasIpv6Sysctl(mInterfaceName, ACCEPT_RA_MIN_LFT)) {
             setIpv6Sysctl(ACCEPT_RA_MIN_LFT, 0 /* sysctl default */);
         }
@@ -2774,7 +2764,7 @@
         // Check the feature flag first before reading IPv6 sysctl, which can prevent from
         // triggering a potential kernel bug about the sysctl.
         // TODO: add unit test to check if the setIpv6Sysctl() is called or not.
-        if (mIsAcceptRaMinLftEnabled && mUseNewApfFilter
+        if (mIsAcceptRaMinLftEnabled
                 && mDependencies.hasIpv6Sysctl(mInterfaceName, ACCEPT_RA_MIN_LFT)) {
             setIpv6Sysctl(ACCEPT_RA_MIN_LFT, mAcceptRaMinLft);
             final Integer acceptRaMinLft = getIpv6Sysctl(ACCEPT_RA_MIN_LFT);
@@ -2788,7 +2778,7 @@
         apfConfig.minMetricsSessionDurationMs = mApfCounterPollingIntervalMs;
         apfConfig.hasClatInterface = mHasSeenClatInterface;
         return mDependencies.maybeCreateApfFilter(getHandler(), mContext, apfConfig,
-                mInterfaceParams, mCallback, mNetworkQuirkMetrics, mUseNewApfFilter);
+                mInterfaceParams, mCallback, mNetworkQuirkMetrics);
     }
 
     private boolean handleUpdateApfCapabilities(@NonNull final ApfCapabilities apfCapabilities) {
diff --git a/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java b/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java
index c2c51f6..3611c26 100644
--- a/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java
+++ b/src/com/android/networkstack/metrics/ApfSessionInfoMetrics.java
@@ -48,10 +48,8 @@
 import static android.net.apf.ApfCounterTracker.Counter.DROPPED_RA;
 import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP;
 import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_BROADCAST_REPLY;
-import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_NON_IPV4;
 import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_REQUEST;
 import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_UNICAST_REPLY;
-import static android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_UNKNOWN;
 import static android.net.apf.ApfCounterTracker.Counter.PASSED_DHCP;
 import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4;
 import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4_FROM_DHCPV4_SERVER;
@@ -85,6 +83,7 @@
 import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_MULTICAST;
 import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_NATT_KEEPALIVE;
 import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_NON_DHCP4;
+import static android.stats.connectivity.CounterName.CN_DROPPED_IPV4_TCP_PORT7_UNICAST;
 import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_KEEPALIVE_ACK;
 import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_MULTICAST;
 import static android.stats.connectivity.CounterName.CN_DROPPED_IPV6_MULTICAST_NA;
@@ -141,12 +140,8 @@
                 // The counter sequence should be keep the same in ApfCounterTracker.java
                 Map.entry(PASSED_ARP, CN_PASSED_ARP),
                 Map.entry(PASSED_ARP_BROADCAST_REPLY, CN_PASSED_ARP_BROADCAST_REPLY),
-                // deprecated in ApfFilter, PASSED_ARP_NON_IPV4 ==> DROPPED_ARP_NON_IPV4
-                Map.entry(PASSED_ARP_NON_IPV4, CN_UNKNOWN),
                 Map.entry(PASSED_ARP_REQUEST, CN_PASSED_ARP_REQUEST),
                 Map.entry(PASSED_ARP_UNICAST_REPLY, CN_PASSED_ARP_UNICAST_REPLY),
-                // deprecated in ApfFilter, PASSED_ARP_UNKNOWN  ==> DROPPED_ARP_UNKNOWN
-                Map.entry(PASSED_ARP_UNKNOWN, CN_UNKNOWN),
                 Map.entry(PASSED_DHCP, CN_PASSED_DHCP),
                 Map.entry(PASSED_IPV4, CN_PASSED_IPV4),
                 Map.entry(PASSED_IPV4_FROM_DHCPV4_SERVER, CN_PASSED_IPV4_FROM_DHCPV4_SERVER),
@@ -182,8 +177,7 @@
                 Map.entry(DROPPED_IPV6_KEEPALIVE_ACK, CN_DROPPED_IPV6_KEEPALIVE_ACK),
                 Map.entry(DROPPED_IPV4_NATT_KEEPALIVE, CN_DROPPED_IPV4_NATT_KEEPALIVE),
                 Map.entry(DROPPED_MDNS, CN_DROPPED_MDNS),
-                // TODO: Not supported yet in the metrics backend.
-                Map.entry(DROPPED_IPV4_TCP_PORT7_UNICAST, CN_UNKNOWN),
+                Map.entry(DROPPED_IPV4_TCP_PORT7_UNICAST, CN_DROPPED_IPV4_TCP_PORT7_UNICAST),
                 Map.entry(DROPPED_ARP_NON_IPV4, CN_DROPPED_ARP_NON_IPV4),
                 Map.entry(DROPPED_ARP_OTHER_HOST, CN_DROPPED_ARP_OTHER_HOST),
                 Map.entry(DROPPED_ARP_REPLY_SPA_NO_HOST, CN_DROPPED_ARP_REPLY_SPA_NO_HOST),
diff --git a/src/com/android/networkstack/metrics/stats.proto b/src/com/android/networkstack/metrics/stats.proto
index 43bc414..972e1f1 100644
--- a/src/com/android/networkstack/metrics/stats.proto
+++ b/src/com/android/networkstack/metrics/stats.proto
@@ -239,8 +239,7 @@
 /**
  * Logs APF session information event.
  * Logged from:
- * packages/modules/NetworkStack/src/android/net/apf/ApfFilter.java or
- * packages/modules/NetworkStack/src/android/net/apf/LegacyApfFilter.java
+ * packages/modules/NetworkStack/src/android/net/apf/ApfFilter.java
  */
 message ApfSessionInfoReported {
     // The version of APF, where version = -1 equals APF disable.
diff --git a/src/com/android/networkstack/util/NetworkStackUtils.java b/src/com/android/networkstack/util/NetworkStackUtils.java
index fce06e4..30d1228 100755
--- a/src/com/android/networkstack/util/NetworkStackUtils.java
+++ b/src/com/android/networkstack/util/NetworkStackUtils.java
@@ -323,6 +323,21 @@
     }
 
     /**
+     * Convert IPv4 multicast address to ethernet multicast address in network order.
+     */
+    public static MacAddress ipv4MulticastToEthernetMulticast(@NonNull final Inet4Address addr) {
+        final byte[] etherMulticast = new byte[6];
+        final byte[] ipv4Multicast = addr.getAddress();
+        etherMulticast[0] = (byte) 0x01;
+        etherMulticast[1] = (byte) 0x00;
+        etherMulticast[2] = (byte) 0x5e;
+        etherMulticast[3] = (byte) (ipv4Multicast[1] & 0x7f);
+        etherMulticast[4] = ipv4Multicast[2];
+        etherMulticast[5] = ipv4Multicast[3];
+        return MacAddress.fromBytes(etherMulticast);
+    }
+
+    /**
      * Convert IPv6 multicast address to ethernet multicast address in network order.
      */
     public static MacAddress ipv6MulticastToEthernetMulticast(@NonNull final Inet6Address addr) {
diff --git a/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt b/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt
index 29e6237..fc0bb34 100644
--- a/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt
+++ b/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt
@@ -62,6 +62,7 @@
 import java.io.FileDescriptor
 import java.net.Inet4Address
 import java.net.Inet6Address
+import java.net.InetAddress
 import java.nio.ByteBuffer
 import java.util.Arrays
 import kotlin.reflect.KClass
@@ -346,6 +347,30 @@
     fun testGenericDhcpResponseWithMfBitDropped() {
         doTestDhcpResponseWithMfBitDropped(true)
     }
+
+    @Test
+    fun testConvertIpv4AddressToEthernetMulticast() {
+        var mcastAddrs = listOf(
+            // ipv4 multicast address, multicast ethernet address
+            Pair(
+                InetAddress.getByName("224.0.0.1") as Inet4Address,
+                MacAddress.fromString("01:00:5e:00:00:01")
+            ),
+            Pair(
+                InetAddress.getByName("239.128.1.1") as Inet4Address,
+                MacAddress.fromString("01:00:5e:00:01:01")
+            ),
+            Pair(
+                InetAddress.getByName("239.255.255.255") as Inet4Address,
+                MacAddress.fromString("01:00:5e:7f:ff:ff")
+            )
+        )
+
+        for ((addr, expectAddr) in mcastAddrs) {
+            val ether = NetworkStackUtils.ipv4MulticastToEthernetMulticast(addr)
+            assertEquals(expectAddr, ether)
+        }
+    }
 }
 
 private fun ByteBuffer.readAsArray(): ByteArray {
diff --git a/tests/unit/src/android/net/apf/ApfFilterTest.kt b/tests/unit/src/android/net/apf/ApfFilterTest.kt
index 1d821ef..0645cdd 100644
--- a/tests/unit/src/android/net/apf/ApfFilterTest.kt
+++ b/tests/unit/src/android/net/apf/ApfFilterTest.kt
@@ -98,6 +98,7 @@
 import com.android.testutils.quitResources
 import com.android.testutils.waitForIdle
 import java.io.FileDescriptor
+import java.net.Inet4Address
 import java.net.Inet6Address
 import java.net.InetAddress
 import kotlin.test.assertContentEquals
@@ -2140,4 +2141,29 @@
         val program = consumeInstalledProgram(ipClientCallback, installCnt = 1)
         assertContentEquals(ByteArray(4096) { 0 }, program)
     }
+
+    @Test
+    fun testApfIPv4MulticastAddrsUpdate() {
+        val apfFilter = getApfFilter()
+        // mock IPv4 multicast address from /proc/net/igmp
+        val mcastAddrs = mutableListOf(
+            InetAddress.getByName("224.0.0.1") as Inet4Address
+        )
+        consumeInstalledProgram(ipClientCallback, installCnt = 2)
+
+        doReturn(mcastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any())
+        apfFilter.updateIPv4MulticastAddrs()
+        consumeInstalledProgram(ipClientCallback, installCnt = 1)
+        assertEquals(mcastAddrs.toSet(), apfFilter.mIPv4MulticastAddresses)
+
+        val addr = InetAddress.getByName("239.0.0.1") as Inet4Address
+        mcastAddrs.add(addr)
+        doReturn(mcastAddrs).`when`(dependencies).getIPv4MulticastAddresses(any())
+        apfFilter.updateIPv4MulticastAddrs()
+        consumeInstalledProgram(ipClientCallback, installCnt = 1)
+        assertEquals(mcastAddrs.toSet(), apfFilter.mIPv4MulticastAddresses)
+
+        apfFilter.updateIPv4MulticastAddrs()
+        verify(ipClientCallback, never()).installPacketFilter(any())
+    }
 }
diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java
index 9a4a224..ac5b6b2 100644
--- a/tests/unit/src/android/net/apf/ApfTest.java
+++ b/tests/unit/src/android/net/apf/ApfTest.java
@@ -57,7 +57,6 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
@@ -2833,21 +2832,6 @@
     }
 
     @Test
-    public void testGenerateApfProgramException() {
-        final ApfConfiguration config = getDefaultConfig();
-        ApfFilter apfFilter = getApfFilter(config);
-        // Simulate exception during installNewProgram() by mocking
-        // mDependencies.elapsedRealtime() to throw an exception (this method doesn't throw in
-        // real-world scenarios).
-        doThrow(new IllegalStateException("test exception")).when(mDependencies).elapsedRealtime();
-        synchronized (apfFilter) {
-            apfFilter.installNewProgram();
-        }
-        verify(mNetworkQuirkMetrics).setEvent(NetworkQuirkEvent.QE_APF_GENERATE_FILTER_EXCEPTION);
-        verify(mNetworkQuirkMetrics).statsWrite();
-    }
-
-    @Test
     public void testApfSessionInfoMetrics() throws Exception {
         final ApfConfiguration config = getDefaultConfig();
         config.apfVersionSupported = 4;
diff --git a/tests/unit/src/android/net/apf/LegacyApfTest.java b/tests/unit/src/android/net/apf/LegacyApfTest.java
deleted file mode 100644
index 319a997..0000000
--- a/tests/unit/src/android/net/apf/LegacyApfTest.java
+++ /dev/null
@@ -1,2045 +0,0 @@
-/*
- * 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.ApfTestHelpers.TIMEOUT_MS;
-import static android.system.OsConstants.AF_UNIX;
-import static android.net.apf.ApfTestHelpers.DROP;
-import static android.net.apf.ApfTestHelpers.PASS;
-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 android.system.OsConstants.SOCK_STREAM;
-
-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.junit.Assert.fail;
-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.Context;
-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.ip.IIpClientCallbacks;
-import android.net.ip.IpClient;
-import android.net.metrics.IpConnectivityLog;
-import android.os.Build;
-import android.os.ConditionVariable;
-import android.os.PowerManager;
-import android.stats.connectivity.NetworkQuirkEvent;
-import android.system.ErrnoException;
-import android.system.Os;
-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.net.module.util.InterfaceParams;
-import com.android.net.module.util.NetworkStackConstants;
-import com.android.net.module.util.SharedLog;
-import com.android.networkstack.apishim.NetworkInformationShimImpl;
-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.IoUtils;
-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.FileDescriptor;
-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 LegacyApfFilter.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";
-
-    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.apfVersionSupported = 2;
-        config.apfRamSize = 4096;
-        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 {
-        ApfTestHelpers.assertPass(mApfVersion, gen);
-    }
-
-    private void assertDrop(ApfV4Generator gen) throws ApfV4Generator.IllegalInstructionException {
-        ApfTestHelpers.assertDrop(mApfVersion, gen);
-    }
-
-    private void assertPass(byte[] program, byte[] packet) {
-        ApfTestHelpers.assertPass(mApfVersion, program, packet);
-    }
-
-    private void assertDrop(byte[] program, byte[] packet) {
-        ApfTestHelpers.assertDrop(mApfVersion, program, packet);
-    }
-
-    private void assertPass(byte[] program, byte[] packet, int filterAge) {
-        ApfTestHelpers.assertPass(mApfVersion, program, packet, filterAge);
-    }
-
-    private void assertDrop(byte[] program, byte[] packet, int filterAge) {
-        ApfTestHelpers.assertDrop(mApfVersion, program, packet, filterAge);
-    }
-
-    private void assertPass(ApfV4Generator gen, byte[] packet, int filterAge)
-            throws ApfV4Generator.IllegalInstructionException {
-        ApfTestHelpers.assertPass(mApfVersion, gen, packet, filterAge);
-    }
-
-    private void assertDrop(ApfV4Generator gen, byte[] packet, int filterAge)
-            throws ApfV4Generator.IllegalInstructionException {
-        ApfTestHelpers.assertDrop(mApfVersion, gen, packet, filterAge);
-    }
-
-    private void assertDataMemoryContents(int expected, byte[] program, byte[] packet,
-            byte[] data, byte[] expectedData) throws Exception {
-        ApfTestHelpers.assertDataMemoryContents(mApfVersion, expected, program, packet, data,
-                expectedData, false /* ignoreInterpreterVersion */);
-    }
-
-    private void assertDataMemoryContentsIgnoreVersion(int expected, byte[] program,
-            byte[] packet, byte[] data, byte[] expectedData) throws Exception {
-        ApfTestHelpers.assertDataMemoryContents(mApfVersion, expected, program, packet, data,
-                expectedData, true /* ignoreInterpreterVersion */);
-    }
-
-    private void assertVerdict(String msg, int expected, byte[] program,
-            byte[] packet, int filterAge) {
-        ApfTestHelpers.assertVerdict(mApfVersion, msg, expected, program, packet, filterAge);
-    }
-
-    private void assertVerdict(int expected, byte[] program, byte[] packet) {
-        ApfTestHelpers.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();
-        config.apfVersionSupported = 4;
-        config.apfRamSize = 1700;
-        config.multicastFilter = DROP_MULTICAST;
-        config.ieee802_3Filter = DROP_802_3_FRAMES;
-        TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback,
-                mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
-        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;
-        TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback,
-                mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
-        apfFilter.setLinkProperties(lp);
-
-        byte[] program = ipClientCallback.assertProgramUpdateAndGet();
-
-        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
-        // 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, TestLegacyApfFilter.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, TestLegacyApfFilter.MOCK_MAC_ADDR);
-        assertPass(program, packet.array());
-
-        // Verify unicast IPv4 DHCP to us is passed
-        put(packet, ETH_DEST_ADDR_OFFSET, TestLegacyApfFilter.MOCK_MAC_ADDR);
-        assertPass(program, packet.array());
-    }
-
-    @Test
-    public void testApfFilterIPv6() throws Exception {
-        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
-        ApfConfiguration config = getDefaultConfig();
-        TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback,
-                mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
-        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;
-        TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback,
-                mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
-        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(TestLegacyApfFilter.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 TestLegacyApfFilter(mContext, config, ipClientCallback,
-                mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
-        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 {
-        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
-        final ApfConfiguration configuration = getDefaultConfig();
-        final LegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, configuration,
-                ipClientCallback, mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
-
-        // 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};
-        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());
-
-        // ...and even while dozing...
-        apfFilter.setDozeMode(true);
-        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).
-        apfFilter.setDozeMode(false);
-        assertPass(ipClientCallback.assertProgramUpdateAndGet(), packet.array());
-
-        apfFilter.shutdown();
-    }
-
-    @Test
-    @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-    public void testApfFilter802_3() throws Exception {
-        MockIpClientCallback ipClientCallback = new MockIpClientCallback();
-        ApfConfiguration config = getDefaultConfig();
-        TestLegacyApfFilter apfFilter =  new TestLegacyApfFilter(mContext, config, ipClientCallback,
-                mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
-        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 =  new TestLegacyApfFilter(mContext, config, ipClientCallback,
-                mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
-        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();
-        TestLegacyApfFilter apfFilter =  new TestLegacyApfFilter(mContext, config, ipClientCallback,
-                mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
-        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 =  new TestLegacyApfFilter(mContext, config, ipClientCallback,
-                mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
-        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 =  new TestLegacyApfFilter(mContext, config, ipClientCallback,
-                mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
-        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, TestLegacyApfFilter 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(DROP, 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;
-        TestLegacyApfFilter apfFilter =  new TestLegacyApfFilter(mContext, config, ipClientCallback,
-                mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
-
-        // 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;
-        TestLegacyApfFilter apfFilter =  new TestLegacyApfFilter(mContext, config, cb,
-                mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
-        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;
-        TestLegacyApfFilter apfFilter =  new TestLegacyApfFilter(mContext, config, cb,
-                mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
-        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(TestLegacyApfFilter 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(TestLegacyApfFilter 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;
-        TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback,
-                mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
-        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 TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config,
-                ipClientCallback, mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
-        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);
-    }
-
-    /**
-     * 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;
-        final TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config,
-                cb, mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
-        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 (LegacyApfFilter.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;
-        final TestLegacyApfFilter apfFilter = new TestLegacyApfFilter(mContext, config,
-                cb, mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
-        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 testProcessRaWithInfiniteLifeTimeWithoutCrash() throws Exception {
-        final MockIpClientCallback ipClientCallback = new MockIpClientCallback();
-        // configure accept_ra_min_lft
-        final ApfConfiguration config = getDefaultConfig();
-        config.acceptRaMinLft = 180;
-        TestLegacyApfFilter 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", "00000001", "00001B58");
-        for (String lifetime : lifetimes) {
-            apfFilter = new TestLegacyApfFilter(mContext, config, ipClientCallback,
-                    mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, mClock);
-            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();
-        }
-    }
-
-    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();
-        config.apfVersionSupported = 2;
-        config.apfRamSize = 512;
-        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();
-        config.apfVersionSupported = 4;
-        config.apfRamSize = 4096;
-        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.
-        LegacyApfFilter.Clock clock = mock(LegacyApfFilter.Clock.class);
-        doReturn(startTimeMs).when(clock).elapsedRealtime();
-        final TestAndroidPacketFilter apfFilter2 = new TestLegacyApfFilter(mContext, config,
-                ipClientCallback, mIpConnectivityLog, mNetworkQuirkMetrics, mDependencies, clock);
-        doReturn(startTimeMs + durationTimeMs).when(clock).elapsedRealtime();
-        apfFilter2.shutdown();
-        verify(mApfSessionInfoMetrics).statsWrite();
-        verify(mIpClientRaInfoMetrics).statsWrite();
-    }
-
-    /**
-     * The Mock ip client callback class.
-     */
-    private static class MockIpClientCallback extends IpClient.IpClientCallbacksWrapper {
-        private final ConditionVariable mGotApfProgram = new ConditionVariable();
-        private byte[] mLastApfProgram;
-        private boolean mInstallPacketFilterReturn = true;
-
-        MockIpClientCallback() {
-            super(mock(IIpClientCallbacks.class), mock(SharedLog.class), mock(SharedLog.class),
-                    NetworkInformationShimImpl.newInstance(), false);
-        }
-
-        MockIpClientCallback(boolean installPacketFilterReturn) {
-            super(mock(IIpClientCallbacks.class), mock(SharedLog.class), mock(SharedLog.class),
-                    NetworkInformationShimImpl.newInstance(), false);
-            mInstallPacketFilterReturn = installPacketFilterReturn;
-        }
-
-        @Override
-        public boolean installPacketFilter(byte[] filter) {
-            mLastApfProgram = filter;
-            mGotApfProgram.open();
-            return mInstallPacketFilterReturn;
-        }
-
-        /**
-         * Reset the apf program and wait for the next update.
-         */
-        public void resetApfProgramWait() {
-            mGotApfProgram.close();
-        }
-
-        /**
-         * Assert the program is update within TIMEOUT_MS and return the program.
-         */
-        public byte[] assertProgramUpdateAndGet() {
-            assertTrue(mGotApfProgram.block(TIMEOUT_MS));
-            return mLastApfProgram;
-        }
-
-        /**
-         * Assert the program is not update within TIMEOUT_MS.
-         */
-        public void assertNoProgramUpdate() {
-            assertFalse(mGotApfProgram.block(TIMEOUT_MS));
-        }
-    }
-
-    /**
-     * The test legacy apf filter class.
-     */
-    private static class TestLegacyApfFilter extends LegacyApfFilter
-            implements TestAndroidPacketFilter {
-        public static final byte[] MOCK_MAC_ADDR = {1, 2, 3, 4, 5, 6};
-        private static final byte[] MOCK_IPV4_ADDR = {10, 0, 0, 1};
-
-        private FileDescriptor mWriteSocket;
-        private final MockIpClientCallback mMockIpClientCb;
-        private final boolean mThrowsExceptionWhenGeneratesProgram;
-
-        TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config,
-                MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog,
-                NetworkQuirkMetrics networkQuirkMetrics) throws Exception {
-            this(context, config, ipClientCallback, ipConnectivityLog, networkQuirkMetrics,
-                    new ApfFilter.Dependencies(context),
-                    false /* throwsExceptionWhenGeneratesProgram */, new Clock());
-        }
-
-        TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config,
-                MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog,
-                NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies,
-                boolean throwsExceptionWhenGeneratesProgram) throws Exception {
-            this(context, config, ipClientCallback, ipConnectivityLog, networkQuirkMetrics,
-                    dependencies, throwsExceptionWhenGeneratesProgram, new Clock());
-        }
-
-        TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config,
-                MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog,
-                NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies,
-                Clock clock) throws Exception {
-            this(context, config, ipClientCallback, ipConnectivityLog, networkQuirkMetrics,
-                    dependencies, false /* throwsExceptionWhenGeneratesProgram */, clock);
-        }
-
-        TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config,
-                MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog,
-                NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies,
-                boolean throwsExceptionWhenGeneratesProgram, Clock clock)
-                throws Exception {
-            super(context, config, InterfaceParams.getByName("lo"), ipClientCallback,
-                    ipConnectivityLog, networkQuirkMetrics, dependencies, clock);
-            mMockIpClientCb = ipClientCallback;
-            mThrowsExceptionWhenGeneratesProgram = throwsExceptionWhenGeneratesProgram;
-        }
-
-        /**
-         * Pretend an RA packet has been received and show it to LegacyApfFilter.
-         */
-        public void pretendPacketReceived(byte[] packet) throws IOException, ErrnoException {
-            mMockIpClientCb.resetApfProgramWait();
-            // ApfFilter's ReceiveThread will be waiting to read this.
-            Os.write(mWriteSocket, packet, 0, packet.length);
-        }
-
-        @Override
-        public synchronized void maybeStartFilter() {
-            mHardwareAddress = MOCK_MAC_ADDR;
-            installNewProgramLocked();
-
-            // Create two sockets, "readSocket" and "mWriteSocket" and connect them together.
-            FileDescriptor readSocket = new FileDescriptor();
-            mWriteSocket = new FileDescriptor();
-            try {
-                Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mWriteSocket, readSocket);
-            } catch (ErrnoException e) {
-                fail();
-                return;
-            }
-            // Now pass readSocket to ReceiveThread as if it was setup to read raw RAs.
-            // This allows us to pretend RA packets have been received via pretendPacketReceived().
-            mReceiveThread = new ReceiveThread(readSocket);
-            mReceiveThread.start();
-        }
-
-        @Override
-        public synchronized void shutdown() {
-            super.shutdown();
-            if (mReceiveThread != null) {
-                mReceiveThread.halt();
-                mReceiveThread = null;
-            }
-            IoUtils.closeQuietly(mWriteSocket);
-        }
-
-        @Override
-        @GuardedBy("this")
-        protected ApfV4Generator emitPrologueLocked() throws
-                BaseApfGenerator.IllegalInstructionException {
-            if (mThrowsExceptionWhenGeneratesProgram) {
-                throw new IllegalStateException();
-            }
-            return super.emitPrologueLocked();
-        }
-    }
-}
diff --git a/tests/unit/src/android/net/ip/IpClientTest.java b/tests/unit/src/android/net/ip/IpClientTest.java
index 2fb0a7f..b01e7ea 100644
--- a/tests/unit/src/android/net/ip/IpClientTest.java
+++ b/tests/unit/src/android/net/ip/IpClientTest.java
@@ -851,10 +851,10 @@
                 ApfConfiguration.class);
         if (isApfSupported) {
             verify(mDependencies, timeout(TEST_TIMEOUT_MS)).maybeCreateApfFilter(
-                    any(), any(), configCaptor.capture(), any(), any(), any(), anyBoolean());
+                    any(), any(), configCaptor.capture(), any(), any(), any());
         } else {
             verify(mDependencies, never()).maybeCreateApfFilter(
-                    any(), any(), configCaptor.capture(), any(), any(), any(), anyBoolean());
+                    any(), any(), configCaptor.capture(), any(), any(), any());
         }
 
         return isApfSupported ? configCaptor.getValue() : null;
@@ -923,7 +923,7 @@
         final ArgumentCaptor<ApfConfiguration> configCaptor = ArgumentCaptor.forClass(
                 ApfConfiguration.class);
         verify(mDependencies, timeout(TEST_TIMEOUT_MS)).maybeCreateApfFilter(
-                any(), any(), configCaptor.capture(), any(), any(), any(), anyBoolean());
+                any(), any(), configCaptor.capture(), any(), any(), any());
         final ApfConfiguration actual = configCaptor.getValue();
         assertNotNull(actual);
         assertEquals(SdkLevel.isAtLeastS() ? 4 : 3, actual.apfVersionSupported);
@@ -958,7 +958,7 @@
         ipc.updateApfCapabilities(newApfCapabilities);
         HandlerUtils.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
         verify(mDependencies, never()).maybeCreateApfFilter(any(), any(), any(), any(), any(),
-                any(), anyBoolean());
+                any());
         verifyShutdown(ipc);
     }
 
@@ -974,15 +974,15 @@
         ipc.updateApfCapabilities(null /* apfCapabilities */);
         HandlerUtils.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
         verify(mDependencies, never()).maybeCreateApfFilter(any(), any(), any(), any(), any(),
-                any(), anyBoolean());
+                any());
         verifyShutdown(ipc);
     }
 
     @Test
     @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     public void testVendorNdOffloadDisabledWhenApfV6Supported() throws Exception {
-        when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), any(),
-                anyBoolean())).thenReturn(mApfFilter);
+        when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(),
+                any())).thenReturn(mApfFilter);
         when(mApfFilter.supportNdOffload()).thenReturn(true);
         final IpClient ipc = makeIpClient(TEST_IFNAME);
         ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
@@ -1007,8 +1007,8 @@
     @Test
     @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     public void testVendorNdOffloadEnabledWhenApfV6NotSupported() throws Exception {
-        when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), any(),
-                anyBoolean())).thenReturn(mApfFilter);
+        when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(),
+                any())).thenReturn(mApfFilter);
         when(mApfFilter.supportNdOffload()).thenReturn(false);
         final IpClient ipc = makeIpClient(TEST_IFNAME);
         ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
@@ -1031,8 +1031,8 @@
     @Test
     @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     public void testVendorNdOffloadDisabledWhenApfCapabilitiesUpdated() throws Exception {
-        when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), any(),
-                anyBoolean())).thenReturn(mApfFilter);
+        when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(),
+                any())).thenReturn(mApfFilter);
         when(mApfFilter.supportNdOffload()).thenReturn(true);
         final IpClient ipc = makeIpClient(TEST_IFNAME);
         ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
@@ -1054,8 +1054,8 @@
 
     @Test
     public void testLinkPropertiesUpdate_callSetLinkPropertiesOnApfFilter() throws Exception {
-        when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(), any(),
-                anyBoolean())).thenReturn(mApfFilter);
+        when(mDependencies.maybeCreateApfFilter(any(), any(), any(), any(), any(),
+                any())).thenReturn(mApfFilter);
         final IpClient ipc = makeIpClient(TEST_IFNAME);
         verifyApfFilterCreatedOnStart(ipc, true /* isApfSupported */);
         onInterfaceAddressUpdated(
diff --git a/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java b/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java
index 69464cf..b8a0407 100644
--- a/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java
+++ b/tests/unit/src/com/android/networkstack/metrics/ApfSessionInfoMetricsTest.java
@@ -102,8 +102,6 @@
         verifyCounterName(Counter.PASSED_IPV6_ICMP, CounterName.CN_PASSED_IPV6_ICMP);
         verifyCounterName(Counter.PASSED_IPV6_UNICAST_NON_ICMP,
                 CounterName.CN_PASSED_IPV6_UNICAST_NON_ICMP);
-        verifyCounterName(Counter.PASSED_ARP_NON_IPV4, CounterName.CN_UNKNOWN);
-        verifyCounterName(Counter.PASSED_ARP_UNKNOWN, CounterName.CN_UNKNOWN);
         verifyCounterName(Counter.PASSED_ARP_UNICAST_REPLY,
                 CounterName.CN_PASSED_ARP_UNICAST_REPLY);
         verifyCounterName(Counter.PASSED_NON_IP_UNICAST, CounterName.CN_PASSED_NON_IP_UNICAST);
@@ -140,7 +138,8 @@
         verifyCounterName(Counter.DROPPED_IPV4_NATT_KEEPALIVE,
                 CounterName.CN_DROPPED_IPV4_NATT_KEEPALIVE);
         verifyCounterName(Counter.DROPPED_MDNS, CounterName.CN_DROPPED_MDNS);
-        verifyCounterName(Counter.DROPPED_IPV4_TCP_PORT7_UNICAST, CounterName.CN_UNKNOWN);
+        verifyCounterName(Counter.DROPPED_IPV4_TCP_PORT7_UNICAST,
+                CounterName.CN_DROPPED_IPV4_TCP_PORT7_UNICAST);
         verifyCounterName(Counter.DROPPED_ARP_NON_IPV4, CounterName.CN_DROPPED_ARP_NON_IPV4);
         verifyCounterName(Counter.DROPPED_ARP_UNKNOWN, CounterName.CN_DROPPED_ARP_UNKNOWN);
         verifyCounterName(Counter.PASSED_ARP_BROADCAST_REPLY,
diff --git a/tests/unit/src/com/android/networkstack/util/ProcfsParsingUtilsTest.kt b/tests/unit/src/com/android/networkstack/util/ProcfsParsingUtilsTest.kt
index 7f8cacb..f23f7f6 100644
--- a/tests/unit/src/com/android/networkstack/util/ProcfsParsingUtilsTest.kt
+++ b/tests/unit/src/com/android/networkstack/util/ProcfsParsingUtilsTest.kt
@@ -18,9 +18,11 @@
 import android.net.MacAddress
 import android.net.apf.ProcfsParsingUtils
 import androidx.test.filters.SmallTest
-import com.android.internal.util.HexDump
+import com.android.net.module.util.HexDump
+import java.net.Inet4Address
 import java.net.Inet6Address
 import java.net.InetAddress
+import java.nio.ByteOrder
 import kotlin.test.assertEquals
 import org.junit.Test
 
@@ -135,4 +137,149 @@
             ProcfsParsingUtils.parseIPv6MulticastAddresses(inputString, "wlan0")
         )
     }
+
+    @Test
+    fun testParseIpv4MulticastAddressLittleEndian() {
+        val order = ByteOrder.LITTLE_ENDIAN
+
+        // the format refer to net/ipv4/igmp.c#igmp_mc_seq_show
+        val inputString = listOf(
+            "Idx\tDevice    : Count Querier\tGroup    Users Timer\tReporter",
+            "1\tlo        :     1      V3",
+            "\t\t\t\t010000E0     1 0:00000000\t\t0",
+            "2\tdummy0    :     1      V3",
+            "\t\t\t\t010000E0     1 0:00000000\t\t0",
+            "47\twlan0     :     1      V3",
+            "\t\t\t\t020000EF     1 0:00000000\t\t0",
+            "\t\t\t\t010000EF     1 0:00000000\t\t0",
+            "\t\t\t\t010000E0     1 0:00000000\t\t0",
+            "51\tv4-wlan0  :     1      V3",
+            "\t\t\t\t010000E0     1 0:00000000\t\t0"
+        )
+
+        val expectedResult = listOf(
+            InetAddress.getByAddress(
+                HexDump.hexStringToByteArray("EF000002")
+            ) as Inet4Address,
+            InetAddress.getByAddress(
+                HexDump.hexStringToByteArray("EF000001")
+            ) as Inet4Address,
+            InetAddress.getByAddress(
+                HexDump.hexStringToByteArray("E0000001")
+            ) as Inet4Address,
+        )
+
+        assertEquals(
+            expectedResult,
+            ProcfsParsingUtils.parseIPv4MulticastAddresses(
+                inputString, "wlan0", order)
+        )
+
+        assertEquals(
+            emptyList<Inet4Address>(),
+            ProcfsParsingUtils.parseIPv4MulticastAddresses(
+                inputString, "eth0", order)
+        )
+
+        assertEquals(
+            emptyList<Inet4Address>(),
+            ProcfsParsingUtils.parseIPv4MulticastAddresses(
+                emptyList<String>(), "eth0", order)
+        )
+    }
+
+    @Test
+    fun testParseIpv4MulticastAddressBigEndian() {
+        val order = ByteOrder.BIG_ENDIAN
+
+        // the format refer to net/ipv4/igmp.c#igmp_mc_seq_show
+        val inputString = listOf(
+            "Idx\tDevice    : Count Querier\tGroup    Users Timer\tReporter",
+            "1\tlo        :     1      V3",
+            "\t\t\t\tE0000001     1 0:00000000\t\t0",
+            "2\tdummy0    :     1      V3",
+            "\t\t\t\tE0000001     1 0:00000000\t\t0",
+            "47\twlan0     :     1      V3",
+            "\t\t\t\tEF000002     1 0:00000000\t\t0",
+            "\t\t\t\tEF000001     1 0:00000000\t\t0",
+            "\t\t\t\tE0000001     1 0:00000000\t\t0",
+            "51\tv4-wlan0  :     1      V3",
+            "\t\t\t\tE0000001     1 0:00000000\t\t0"
+        )
+
+        val expectedResult = listOf(
+            InetAddress.getByAddress(
+                HexDump.hexStringToByteArray("EF000002")
+            ) as Inet4Address,
+            InetAddress.getByAddress(
+                HexDump.hexStringToByteArray("EF000001")
+            ) as Inet4Address,
+            InetAddress.getByAddress(
+                HexDump.hexStringToByteArray("E0000001")
+            ) as Inet4Address,
+        )
+
+        assertEquals(
+            expectedResult,
+            ProcfsParsingUtils.parseIPv4MulticastAddresses(
+                inputString, "wlan0", order)
+        )
+
+        assertEquals(
+            emptyList<Inet4Address>(),
+            ProcfsParsingUtils.parseIPv4MulticastAddresses(
+                inputString, "eth0", order)
+        )
+
+        assertEquals(
+            emptyList<Inet4Address>(),
+            ProcfsParsingUtils.parseIPv4MulticastAddresses(
+                emptyList<String>(), "eth0", order)
+        )
+    }
+
+    @Test
+    fun testParseIpv4MulticastAddressError() {
+        val order = ByteOrder.LITTLE_ENDIAN
+
+        // the format refer to net/ipv4/igmp.c#igmp_mc_seq_show
+        // wlan0 addresses contain invalid char 'X'
+        val inputString = listOf(
+            "Idx\tDevice    : Count Querier\tGroup    Users Timer\tReporter",
+            "1\tlo        :     1      V3",
+            "\t\t\t\t010000E0     1 0:00000000\t\t0",
+            "2\tdummy0    :     1      V3",
+            "\t\t\t\t010000E0     1 0:00000000\t\t0",
+            "47\twlan0     :     1      V3",
+            "\t\t\t\t02XXXXEF     1 0:00000000\t\t0",
+            "\t\t\t\t01XXXXEF     1 0:00000000\t\t0",
+            "\t\t\t\t01XXXXE0     1 0:00000000\t\t0",
+            "51\tv4-wlan0  :     1      V3",
+            "\t\t\t\t010000E0     1 0:00000000\t\t0"
+        )
+
+        val expectedResult = listOf(
+            InetAddress.getByAddress(
+                HexDump.hexStringToByteArray("E0000001")
+            ) as Inet4Address
+        )
+
+        assertEquals(
+            expectedResult,
+            ProcfsParsingUtils.parseIPv4MulticastAddresses(
+                inputString, "wlan0", order)
+        )
+
+        assertEquals(
+            emptyList<Inet4Address>(),
+            ProcfsParsingUtils.parseIPv4MulticastAddresses(
+                inputString, "eth0", order)
+        )
+
+        assertEquals(
+            emptyList<Inet4Address>(),
+            ProcfsParsingUtils.parseIPv4MulticastAddresses(
+                emptyList<String>(), "eth0", order)
+        )
+    }
 }