Snap for 11976889 from ff4e5a46c9e715c702120df4a6c5e9624c88b219 to mainline-art-release

Change-Id: I0a61016d56777986438b0a0eab3a70d60934c007
diff --git a/src/android/net/apf/AndroidPacketFilter.java b/src/android/net/apf/AndroidPacketFilter.java
index 2a7165b..8c7ff05 100644
--- a/src/android/net/apf/AndroidPacketFilter.java
+++ b/src/android/net/apf/AndroidPacketFilter.java
@@ -44,9 +44,9 @@
     void setMulticastFilter(boolean isEnabled);
 
     /**
-     * Set the APF data snapshot.
+     * Set the APF data snapshot and return the latest counter snapshot as a String.
      */
-    void setDataSnapshot(byte[] data);
+    String setDataSnapshot(byte[] data);
 
     /**
      * Add TCP keepalive ack packet filter.
diff --git a/src/android/net/apf/ApfCounterTracker.java b/src/android/net/apf/ApfCounterTracker.java
index e35f011..e86aab1 100644
--- a/src/android/net/apf/ApfCounterTracker.java
+++ b/src/android/net/apf/ApfCounterTracker.java
@@ -64,6 +64,7 @@
         PASSED_IPV6_ICMP,
         PASSED_IPV6_NON_ICMP,
         PASSED_IPV6_NS_MULTIPLE_OPTIONS,
+        PASSED_IPV6_NS_NO_ADDRESS,
         PASSED_IPV6_UNICAST_NON_ICMP,
         PASSED_NON_IP_UNICAST,
         PASSED_MDNS,
@@ -81,7 +82,6 @@
         DROPPED_IPV6_MULTICAST_PING,
         DROPPED_IPV6_NON_ICMP_MULTICAST,
         DROPPED_IPV6_NS_INVALID,
-        DROPPED_IPV6_NS_NO_ADDRESS,
         DROPPED_IPV6_NS_OTHER_HOST,
         DROPPED_802_3_FRAME,
         DROPPED_ETHERTYPE_NOT_ALLOWED,
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java
index c705928..3aee15e 100644
--- a/src/android/net/apf/ApfFilter.java
+++ b/src/android/net/apf/ApfFilter.java
@@ -36,7 +36,9 @@
 import static android.net.apf.ApfConstants.ETH_TYPE_MAX;
 import static android.net.apf.ApfConstants.ETH_TYPE_MIN;
 import static android.net.apf.ApfConstants.FIXED_ARP_REPLY_HEADER;
+import static android.net.apf.ApfConstants.ICMP6_CHECKSUM_OFFSET;
 import static android.net.apf.ApfConstants.ICMP6_CODE_OFFSET;
+import static android.net.apf.ApfConstants.ICMP6_NS_OPTION_TYPE_OFFSET;
 import static android.net.apf.ApfConstants.ICMP6_NS_TARGET_IP_OFFSET;
 import static android.net.apf.ApfConstants.ICMP6_TYPE_OFFSET;
 import static android.net.apf.ApfConstants.IPPROTO_HOPOPTS;
@@ -55,17 +57,17 @@
 import static android.net.apf.ApfConstants.IPV6_HEADER_LEN;
 import static android.net.apf.ApfConstants.IPV6_HOP_LIMIT_OFFSET;
 import static android.net.apf.ApfConstants.IPV6_NEXT_HEADER_OFFSET;
-import static android.net.apf.ApfConstants.IPV6_SOLICITED_NODES_PREFIX;
 import static android.net.apf.ApfConstants.IPV6_PAYLOAD_LEN_OFFSET;
+import static android.net.apf.ApfConstants.IPV6_SOLICITED_NODES_PREFIX;
 import static android.net.apf.ApfConstants.IPV6_SRC_ADDR_OFFSET;
 import static android.net.apf.ApfConstants.MDNS_PORT;
 import static android.net.apf.ApfConstants.TCP_HEADER_SIZE_OFFSET;
 import static android.net.apf.ApfConstants.TCP_UDP_DESTINATION_PORT_OFFSET;
 import static android.net.apf.ApfConstants.TCP_UDP_SOURCE_PORT_OFFSET;
 import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_INVALID;
-import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_NO_ADDRESS;
 import static android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_OTHER_HOST;
 import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_MULTIPLE_OPTIONS;
+import static android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_ADDRESS;
 import static android.net.apf.BaseApfGenerator.MemorySlot;
 import static android.net.apf.BaseApfGenerator.Register.R0;
 import static android.net.apf.BaseApfGenerator.Register.R1;
@@ -86,8 +88,12 @@
 
 import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
 import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN;
 import static com.android.net.module.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET;
 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NA_HEADER_LEN;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA_LEN;
 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION;
 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
@@ -184,6 +190,7 @@
         public boolean shouldHandleLightDoze;
         public long minMetricsSessionDurationMs;
         public boolean hasClatInterface;
+        public boolean shouldHandleArpOffload;
     }
 
     /** A wrapper class of {@link SystemClock} to be mocked in unit tests. */
@@ -281,6 +288,7 @@
     // and PIO valid lifetimes.
     private final int mAcceptRaMinLft;
     private final boolean mShouldHandleLightDoze;
+    private final boolean mShouldHandleArpOffload;
 
     private final NetworkQuirkMetrics mNetworkQuirkMetrics;
     private final IpClientRaInfoMetrics mIpClientRaInfoMetrics;
@@ -384,6 +392,7 @@
         mMinRdnssLifetimeSec = config.minRdnssLifetimeSec;
         mAcceptRaMinLft = config.acceptRaMinLft;
         mShouldHandleLightDoze = config.shouldHandleLightDoze;
+        mShouldHandleArpOffload = config.shouldHandleArpOffload;
         mDependencies = dependencies;
         mNetworkQuirkMetrics = networkQuirkMetrics;
         mIpClientRaInfoMetrics = dependencies.getIpClientRaInfoMetrics();
@@ -514,11 +523,13 @@
         }
     }
 
-    public synchronized void setDataSnapshot(byte[] data) {
+    @Override
+    public synchronized String setDataSnapshot(byte[] data) {
         mDataSnapshot = data;
         if (mIsRunning) {
             mApfCounterTracker.updateCountersFromData(data);
         }
+        return mApfCounterTracker.getCounters().toString();
     }
 
     private void log(String s) {
@@ -1625,7 +1636,7 @@
                     Counter.DROPPED_ARP_OTHER_HOST);
 
             ApfV6Generator v6Gen = tryToConvertToApfV6Generator(gen);
-            if (v6Gen != null) {
+            if (v6Gen != null && mShouldHandleArpOffload) {
                 // Ethernet requires that all packets be at least 60 bytes long
                 v6Gen.addAllocate(60)
                         .addPacketCopy(ETHER_SRC_ADDR_OFFSET, ETHER_ADDR_LEN)
@@ -1829,6 +1840,54 @@
         return addresses;
     }
 
+    /**
+     * Generate allocate and transmit code to send ICMPv6 non-DAD NA packets.
+     */
+    @GuardedBy("this")
+    private void generateNonDadNaTransmitLocked(ApfV6GeneratorBase<?> gen)
+            throws IllegalInstructionException {
+        final int ipv6PayloadLen = ICMPV6_NA_HEADER_LEN + ICMPV6_ND_OPTION_TLLA_LEN;
+        final int pktLen = ETH_HEADER_LEN + IPV6_HEADER_LEN + ipv6PayloadLen;
+
+        gen.addAllocate(pktLen);
+
+        // Ethernet Header
+        gen.addPacketCopy(ICMP6_NS_OPTION_TYPE_OFFSET + 2, ETHER_ADDR_LEN)  // dst MAC address
+                .addDataCopy(mHardwareAddress)  // src MAC address
+                .addWriteU16(ETH_P_IPV6);  // IPv6 type
+
+        int tclass = mDependencies.getNdTrafficClass(mInterfaceParams.name);
+        int vtf = (0x60000000 | (tclass << 20));
+        // IPv6 header
+        gen.addWrite32(vtf)  // IPv6 Header: version, traffic class, flowlabel
+                // payload length (2 bytes) | next header: ICMPv6 (1 byte) | hop limit (1 byte)
+                .addWrite32((ipv6PayloadLen << 16) | ((IPPROTO_ICMPV6 << 8) | 255))
+                // target ip is guaranteed to be non-tentative as we already check before
+                // we call transmit, but the link local ip can potentially be tentative.
+                .addPacketCopy(ICMP6_NS_TARGET_IP_OFFSET, IPV6_ADDR_LEN)  // src ip
+                .addPacketCopy(IPV6_SRC_ADDR_OFFSET, IPV6_ADDR_LEN);  // dst ip
+
+        // ICMPv6 header and payload
+        // ICMPv6 type: NA (1 byte) | code: 0 (1 byte) | checksum: set to payload size (2 bytes)
+        gen.addWrite32((ICMPV6_NEIGHBOR_ADVERTISEMENT << 24) | ipv6PayloadLen)
+                // Always set Router flag to prevent host deleting routes point at the router
+                // Always set Override flag to update neighbor's cache
+                // Solicited flag set to 1 if non DAD, refer to RFC4861#7.2.4
+                .addWrite32(0xe0000000) // flags: R=1, S=1, O=1
+                .addPacketCopy(ICMP6_NS_TARGET_IP_OFFSET, IPV6_ADDR_LEN) // target address
+                // lla option: type (1 byte) | lla option: length (1 byte)
+                .addWriteU16((ICMPV6_ND_OPTION_TLLA << 8) | 1)
+                .addDataCopy(mHardwareAddress);  // lla option: link layer address
+
+        gen.addTransmitL4(
+                ETHER_HEADER_LEN,   // ip_ofs
+                ICMP6_CHECKSUM_OFFSET,  // csum_ofs
+                IPV6_SRC_ADDR_OFFSET,   // csum_start
+                IPPROTO_ICMPV6, // partial_sum
+                false   // udp
+        );
+    }
+
     @GuardedBy("this")
     private void generateNsFilterLocked(ApfV6Generator v6Gen)
             throws IllegalInstructionException {
@@ -1837,8 +1896,9 @@
                 true /* includeTentative */,
                 true /* includeAnycast */);
         if (allIPv6Addrs.isEmpty()) {
-            // There is no IPv6 link local address.
-            v6Gen.addCountAndDrop(DROPPED_IPV6_NS_NO_ADDRESS);
+            // If there is no IPv6 link local address, allow all NS packets to avoid racing
+            // against RS.
+            v6Gen.addCountAndPass(PASSED_IPV6_NS_NO_ADDRESS);
             return;
         }
 
@@ -1912,7 +1972,7 @@
         //     pass
         // (APFv6+ specific logic) if it's ICMPv6 NS:
         //   if there are no IPv6 addresses (including link local address) on the interface:
-        //     drop
+        //     pass
         //   if MAC dst is none of known {unicast, multicast, broadcast} MAC addresses
         //     drop
         //   if IPv6 dst prefix is "ff02::1:ff00:0/104" but is none of solicited-node multicast
diff --git a/src/android/net/apf/ApfV4Generator.java b/src/android/net/apf/ApfV4Generator.java
index 5b19d35..a41f033 100644
--- a/src/android/net/apf/ApfV4Generator.java
+++ b/src/android/net/apf/ApfV4Generator.java
@@ -202,6 +202,20 @@
     }
 
     @Override
+    public ApfV4Generator addCountAndDropIfBytesAtR0Equal(byte[] bytes,
+            ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+        final String tgt = getUniqueLabel();
+        return addJumpIfBytesAtR0NotEqual(bytes, tgt).addCountAndDrop(cnt).defineLabel(tgt);
+    }
+
+    @Override
+    public ApfV4Generator addCountAndPassIfBytesAtR0Equal(byte[] bytes,
+            ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+        final String tgt = getUniqueLabel();
+        return addJumpIfBytesAtR0NotEqual(bytes, tgt).addCountAndPass(cnt).defineLabel(tgt);
+    }
+
+    @Override
     public ApfV4Generator addCountAndPassIfR0IsOneOf(@NonNull Set<Long> values,
             ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
         if (values.isEmpty()) {
diff --git a/src/android/net/apf/ApfV4GeneratorBase.java b/src/android/net/apf/ApfV4GeneratorBase.java
index 860a21a..ced1d68 100644
--- a/src/android/net/apf/ApfV4GeneratorBase.java
+++ b/src/android/net/apf/ApfV4GeneratorBase.java
@@ -500,6 +500,22 @@
             ApfCounterTracker.Counter cnt) throws IllegalInstructionException;
 
     /**
+     * Add instructions to the end of the program to increase counter and drop packet if the
+     * bytes of the packet at an offset specified by register0 match {@code bytes}.
+     * WARNING: may modify R1
+     */
+    public abstract Type addCountAndDropIfBytesAtR0Equal(byte[] bytes,
+            ApfCounterTracker.Counter cnt) throws IllegalInstructionException;
+
+    /**
+     * Add instructions to the end of the program to increase counter and pass packet if the
+     * bytes of the packet at an offset specified by register0 match {@code bytes}.
+     * WARNING: may modify R1
+     */
+    public abstract Type addCountAndPassIfBytesAtR0Equal(byte[] bytes,
+            ApfCounterTracker.Counter cnt) throws IllegalInstructionException;
+
+    /**
      * Add instructions to the end of the program to increase counter and pass packet if the
      * value in register0 is one of {@code values}.
      * WARNING: may modify R1
diff --git a/src/android/net/apf/ApfV6GeneratorBase.java b/src/android/net/apf/ApfV6GeneratorBase.java
index 6ec4eda..a9abed6 100644
--- a/src/android/net/apf/ApfV6GeneratorBase.java
+++ b/src/android/net/apf/ApfV6GeneratorBase.java
@@ -712,6 +712,20 @@
     }
 
     @Override
+    public final Type addCountAndDropIfBytesAtR0Equal(byte[] bytes,
+            ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+        final String tgt = getUniqueLabel();
+        return addJumpIfBytesAtR0NotEqual(bytes, tgt).addCountAndDrop(cnt).defineLabel(tgt);
+    }
+
+    @Override
+    public final Type addCountAndPassIfBytesAtR0Equal(byte[] bytes,
+            ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
+        final String tgt = getUniqueLabel();
+        return addJumpIfBytesAtR0NotEqual(bytes, tgt).addCountAndPass(cnt).defineLabel(tgt);
+    }
+
+    @Override
     public Type addCountAndPassIfR0IsOneOf(@NonNull Set<Long> values,
             ApfCounterTracker.Counter cnt) throws IllegalInstructionException {
         if (values.isEmpty()) {
diff --git a/src/android/net/apf/LegacyApfFilter.java b/src/android/net/apf/LegacyApfFilter.java
index 99aab80..e4f709b 100644
--- a/src/android/net/apf/LegacyApfFilter.java
+++ b/src/android/net/apf/LegacyApfFilter.java
@@ -435,11 +435,13 @@
         mDependencies.onThreadCreated(mReceiveThread);
     }
 
-    public synchronized void setDataSnapshot(byte[] data) {
+    @Override
+    public synchronized String setDataSnapshot(byte[] data) {
         mDataSnapshot = data;
         if (mIsRunning) {
             mApfCounterTracker.updateCountersFromData(data);
         }
+        return mApfCounterTracker.getCounters().toString();
     }
 
     private void log(String s) {
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index 304b038..deaabac 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -46,6 +46,7 @@
 import static com.android.net.module.util.NetworkStackConstants.VENDOR_SPECIFIC_IE_ID;
 import static com.android.networkstack.apishim.ConstantsShim.IFA_F_MANAGETEMPADDR;
 import static com.android.networkstack.apishim.ConstantsShim.IFA_F_NOPREFIXROUTE;
+import static com.android.networkstack.util.NetworkStackUtils.APF_HANDLE_ARP_OFFLOAD_FORCE_DISABLE;
 import static com.android.networkstack.util.NetworkStackUtils.APF_HANDLE_LIGHT_DOZE_FORCE_DISABLE;
 import static com.android.networkstack.util.NetworkStackUtils.APF_NEW_RA_FILTER_VERSION;
 import static com.android.networkstack.util.NetworkStackUtils.APF_POLLING_COUNTERS_VERSION;
@@ -199,15 +200,19 @@
 public class IpClient extends StateMachine {
     private static final String TAG = IpClient.class.getSimpleName();
     private static final boolean DBG = false;
+    private final boolean mApfDebug;
 
     // For message logging.
     private static final Class[] sMessageClasses = { IpClient.class, DhcpClient.class };
     private static final SparseArray<String> sWhatToString =
             MessageUtils.findMessageNames(sMessageClasses);
-    // Two static concurrent hashmaps of interface name to logging classes.
-    // One holds StateMachine logs and the other connectivity packet logs.
+    // Static concurrent hashmaps of interface name to logging classes.
+    // This map holds StateMachine logs.
     private static final ConcurrentHashMap<String, SharedLog> sSmLogs = new ConcurrentHashMap<>();
+    // This map holds connectivity packet logs.
     private static final ConcurrentHashMap<String, LocalLog> sPktLogs = new ConcurrentHashMap<>();
+    // This map holds Apf logs.
+    private static final ConcurrentHashMap<String, SharedLog> sApfLogs = new ConcurrentHashMap<>();
     private final NetworkStackIpMemoryStore mIpMemoryStore;
     private final NetworkInformationShim mShim = NetworkInformationShimImpl.newInstance();
     private final IpProvisioningMetrics mIpProvisioningMetrics = new IpProvisioningMetrics();
@@ -225,6 +230,12 @@
 
             writer.println(String.format("--- BEGIN %s ---", ifname));
 
+            final SharedLog apfLog = sApfLogs.get(ifname);
+            if (apfLog != null) {
+                writer.println("APF log:");
+                apfLog.dump(null, writer, null);
+            }
+
             final SharedLog smLog = sSmLogs.get(ifname);
             if (smLog != null) {
                 writer.println("State machine log:");
@@ -266,16 +277,24 @@
     public static class IpClientCallbacksWrapper {
         private static final String PREFIX = "INVOKE ";
         private final IIpClientCallbacks mCallback;
+        @NonNull
         private final SharedLog mLog;
         @NonNull
+        private final SharedLog mApfLog;
+        @NonNull
         private final NetworkInformationShim mShim;
 
+        private final boolean mApfDebug;
+
         @VisibleForTesting
-        protected IpClientCallbacksWrapper(IIpClientCallbacks callback, SharedLog log,
-                @NonNull NetworkInformationShim shim) {
+        protected IpClientCallbacksWrapper(IIpClientCallbacks callback, @NonNull SharedLog log,
+                @NonNull SharedLog apfLog, @NonNull NetworkInformationShim shim,
+                boolean apfDebug) {
             mCallback = callback;
             mLog = log;
+            mApfLog = apfLog;
             mShim = shim;
+            mApfDebug = apfDebug;
         }
 
         private void log(String msg) {
@@ -396,6 +415,9 @@
         public boolean installPacketFilter(byte[] filter) {
             log("installPacketFilter(byte[" + filter.length + "])");
             try {
+                if (mApfDebug) {
+                    mApfLog.log("updated APF program: " + HexDump.toHexString(filter));
+                }
                 mCallback.installPacketFilter(filter);
             } catch (RemoteException e) {
                 log("Failed to call installPacketFilter", e);
@@ -682,6 +704,7 @@
     private final WakeupMessage mProvisioningTimeoutAlarm;
     private final WakeupMessage mDhcpActionTimeoutAlarm;
     private final SharedLog mLog;
+    private final SharedLog mApfLog;
     private final LocalLog mConnectivityPacketLog;
     private final MessageHandlingLogger mMsgStateLogger;
     private final IpConnectivityLog mMetricsLog;
@@ -711,6 +734,7 @@
     private final boolean mApfShouldHandleLightDoze;
     private final boolean mEnableApfPollingCounters;
     private final boolean mPopulateLinkAddressLifetime;
+    private final boolean mApfShouldHandleArpOffload;
 
     private InterfaceParams mInterfaceParams;
 
@@ -929,8 +953,11 @@
         mLog = sSmLogs.get(mInterfaceName);
         sPktLogs.putIfAbsent(mInterfaceName, new LocalLog(MAX_PACKET_RECORDS));
         mConnectivityPacketLog = sPktLogs.get(mInterfaceName);
+        sApfLogs.putIfAbsent(mInterfaceName, new SharedLog(10 /* maxRecords */, mTag));
+        mApfLog = sApfLogs.get(mInterfaceName);
+        mApfDebug = Log.isLoggable(ApfFilter.class.getSimpleName(), Log.DEBUG);
         mMsgStateLogger = new MessageHandlingLogger();
-        mCallback = new IpClientCallbacksWrapper(callback, mLog, mShim);
+        mCallback = new IpClientCallbacksWrapper(callback, mLog, mApfLog, mShim, mApfDebug);
 
         // TODO: Consider creating, constructing, and passing in some kind of
         // InterfaceController.Dependencies class.
@@ -957,6 +984,8 @@
         // Light doze mode status checking API is only available at T or later releases.
         mApfShouldHandleLightDoze = SdkLevel.isAtLeastT() && mDependencies.isFeatureNotChickenedOut(
                 mContext, APF_HANDLE_LIGHT_DOZE_FORCE_DISABLE);
+        mApfShouldHandleArpOffload = mDependencies.isFeatureNotChickenedOut(
+                mContext, APF_HANDLE_ARP_OFFLOAD_FORCE_DISABLE);
         mPopulateLinkAddressLifetime = mDependencies.isFeatureEnabled(context,
                 IPCLIENT_POPULATE_LINK_ADDRESS_LIFETIME_VERSION);
 
@@ -1370,7 +1399,9 @@
                 }
             }
             apfFilter.dump(pw);
-
+            pw.println("APF log:");
+            pw.println("mApfDebug: " + mApfDebug);
+            mApfLog.dump(fd, pw, args);
         } else {
             pw.print("No active ApfFilter; ");
             if (provisioningConfig == null) {
@@ -2563,6 +2594,7 @@
             apfConfig.acceptRaMinLft = 0;
         }
         apfConfig.shouldHandleLightDoze = mApfShouldHandleLightDoze;
+        apfConfig.shouldHandleArpOffload = mApfShouldHandleArpOffload;
         apfConfig.minMetricsSessionDurationMs = mApfCounterPollingIntervalMs;
         apfConfig.hasClatInterface = mHasSeenClatInterface;
         return mDependencies.maybeCreateApfFilter(mContext, apfConfig, mInterfaceParams,
@@ -3337,7 +3369,8 @@
 
                 case EVENT_READ_PACKET_FILTER_COMPLETE: {
                     if (mApfFilter != null) {
-                        mApfFilter.setDataSnapshot((byte[]) msg.obj);
+                        String snapShotStr = mApfFilter.setDataSnapshot((byte[]) msg.obj);
+                        mLog.log("readPacketFilterComplete, ApfCounters: " + snapShotStr);
                     }
                     mApfDataSnapshotComplete.open();
                     break;
diff --git a/src/com/android/networkstack/metrics/IpProvisioningMetrics.java b/src/com/android/networkstack/metrics/IpProvisioningMetrics.java
index 5ca996e..daaf207 100644
--- a/src/com/android/networkstack/metrics/IpProvisioningMetrics.java
+++ b/src/com/android/networkstack/metrics/IpProvisioningMetrics.java
@@ -16,11 +16,14 @@
 
 package com.android.networkstack.metrics;
 
+import static android.stats.connectivity.Ipv6ProvisioningMode.IPV6_PROV_MODE_UNKNOWN;
+
 import android.net.util.Stopwatch;
 import android.stats.connectivity.DhcpErrorCode;
 import android.stats.connectivity.DhcpFeature;
 import android.stats.connectivity.DisconnectCode;
 import android.stats.connectivity.HostnameTransResult;
+import android.stats.connectivity.Ipv6ProvisioningMode;
 
 import com.android.net.module.util.ConnectivityUtils;
 
@@ -147,6 +150,15 @@
     }
 
     /**
+     * Write the IPv6 provisioning mode proto into mStatsBuilder. This API should be called only
+     * once during the provisioning lifetime cycle.
+     */
+    public void setIpv6ProvisioningMode(final Ipv6ProvisioningMode mode) {
+        if (mode == IPV6_PROV_MODE_UNKNOWN || mStatsBuilder.hasIpv6ProvisioningMode()) return;
+        mStatsBuilder.setIpv6ProvisioningMode(mode);
+    }
+
+    /**
      * Write the NetworkIpProvisioningReported proto into statsd.
      */
     public NetworkIpProvisioningReported statsWrite() {
@@ -166,7 +178,8 @@
                 stats.getProvisioningDurationMicros(),
                 stats.getDisconnectCode().getNumber(),
                 DhcpSession,
-                stats.getRandomNumber());
+                stats.getRandomNumber(),
+                stats.getIpv6ProvisioningMode().getNumber());
         mWatch.reset();
         return stats;
     }
diff --git a/src/com/android/networkstack/metrics/stats.proto b/src/com/android/networkstack/metrics/stats.proto
index 06419f9..43bc414 100644
--- a/src/com/android/networkstack/metrics/stats.proto
+++ b/src/com/android/networkstack/metrics/stats.proto
@@ -107,6 +107,9 @@
 
     // The random number between 0 ~ 999 for sampling
     optional int32 random_number = 7;
+
+    // Check which IPv6 provisioning mode is used for current connection
+    optional .android.stats.connectivity.Ipv6ProvisioningMode ipv6_provisioning_mode = 8;
 }
 
 /**
diff --git a/src/com/android/networkstack/util/NetworkStackUtils.java b/src/com/android/networkstack/util/NetworkStackUtils.java
index 5446119..ac2832b 100755
--- a/src/com/android/networkstack/util/NetworkStackUtils.java
+++ b/src/com/android/networkstack/util/NetworkStackUtils.java
@@ -300,6 +300,12 @@
     public static final String IGNORE_TCP_INFO_FOR_BLOCKED_UIDS =
             "ignore_tcp_info_for_blocked_uids";
 
+    /**
+     * Kill switch flag to disable the feature of handle arp offload in Apf.
+     */
+    public static final String APF_HANDLE_ARP_OFFLOAD_FORCE_DISABLE =
+            "apf_handle_arp_offload_force_disable";
+
     static {
         System.loadLibrary("networkstackutilsjni");
     }
diff --git a/tests/unit/src/android/net/apf/ApfNewTest.kt b/tests/unit/src/android/net/apf/ApfNewTest.kt
index d271b2e..6863fb9 100644
--- a/tests/unit/src/android/net/apf/ApfNewTest.kt
+++ b/tests/unit/src/android/net/apf/ApfNewTest.kt
@@ -28,14 +28,15 @@
 import android.net.apf.ApfCounterTracker.Counter.DROPPED_ETH_BROADCAST
 import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV4_NON_DHCP4
 import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_INVALID
-import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_NO_ADDRESS
 import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_NS_OTHER_HOST
 import android.net.apf.ApfCounterTracker.Counter.PASSED_ALLOCATE_FAILURE
 import android.net.apf.ApfCounterTracker.Counter.PASSED_ARP
+import android.net.apf.ApfCounterTracker.Counter.PASSED_ARP_REQUEST
 import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4
 import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV4_FROM_DHCPV4_SERVER
 import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_ICMP
 import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_MULTIPLE_OPTIONS
+import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_NS_NO_ADDRESS
 import android.net.apf.ApfCounterTracker.Counter.PASSED_TRANSMIT_FAILURE
 import android.net.apf.ApfCounterTracker.Counter.TOTAL_PACKETS
 import android.net.apf.ApfFilter.Dependencies
@@ -375,6 +376,12 @@
             gen.addCountAndDropIfBytesAtR0NotEqual(byteArrayOf(1), PASSED_ARP)
         }
         assertFailsWith<IllegalArgumentException> {
+            gen.addCountAndDropIfBytesAtR0Equal(byteArrayOf(1), PASSED_ARP)
+        }
+        assertFailsWith<IllegalArgumentException> {
+            gen.addCountAndPassIfBytesAtR0Equal(byteArrayOf(1), DROPPED_ETH_BROADCAST)
+        }
+        assertFailsWith<IllegalArgumentException> {
             gen.addCountAndDropIfR0AnyBitsSet(3, PASSED_ARP)
         }
         assertFailsWith<IllegalArgumentException> {
@@ -448,6 +455,12 @@
             v4gen.addCountAndPassIfR0NotEquals(3, DROPPED_ETH_BROADCAST)
         }
         assertFailsWith<IllegalArgumentException> {
+            v4gen.addCountAndDropIfBytesAtR0Equal(byteArrayOf(1), PASSED_ARP)
+        }
+        assertFailsWith<IllegalArgumentException> {
+            v4gen.addCountAndPassIfBytesAtR0Equal(byteArrayOf(1), DROPPED_ETH_BROADCAST)
+        }
+        assertFailsWith<IllegalArgumentException> {
             v4gen.addCountAndDropIfR0LessThan(3, PASSED_ARP)
         }
         assertFailsWith<IllegalArgumentException> {
@@ -1302,6 +1315,30 @@
                 .addCountTrampoline()
                 .generate()
         verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal)
+
+        program = getGenerator()
+                .addLoadImmediate(R0, 1)
+                .addCountAndDropIfBytesAtR0Equal(
+                        byteArrayOf(2, 3), DROPPED_ETH_BROADCAST)
+                .addPass()
+                .addCountTrampoline()
+                .generate()
+        verifyProgramRun(
+                APF_VERSION_6,
+                program,
+                testPacket,
+                DROPPED_ETH_BROADCAST,
+                incTotal = incTotal
+        )
+
+        program = getGenerator()
+                .addLoadImmediate(R0, 1)
+                .addCountAndPassIfBytesAtR0Equal(
+                        byteArrayOf(2, 3), PASSED_ARP)
+                .addPass()
+                .addCountTrampoline()
+                .generate()
+        verifyProgramRun(APF_VERSION_6, program, testPacket, PASSED_ARP, incTotal = incTotal)
     }
 
     private fun doTestEtherTypeAllowListFilter(apfVersion: Int) {
@@ -1880,6 +1917,41 @@
     }
 
     @Test
+    fun testArpOffloadDisabled() {
+        val apfConfig = getDefaultConfig()
+        apfConfig.shouldHandleArpOffload = false
+        val apfFilter =
+            ApfFilter(
+                context,
+                apfConfig,
+                ifParams,
+                ipClientCallback,
+                metrics,
+                dependencies
+            )
+        verify(ipClientCallback, times(2)).installPacketFilter(any())
+        val linkAddress = LinkAddress(InetAddress.getByAddress(hostIpv4Address), 24)
+        val lp = LinkProperties()
+        lp.addLinkAddress(linkAddress)
+        apfFilter.setLinkProperties(lp)
+        val programCaptor = ArgumentCaptor.forClass(ByteArray::class.java)
+        verify(ipClientCallback, times(3)).installPacketFilter(programCaptor.capture())
+        val program = programCaptor.value
+        val receivedArpPacketBuf = ArpPacket.buildArpPacket(
+            arpBroadcastMacAddress,
+            senderMacAddress,
+            hostIpv4Address,
+            HexDump.hexStringToByteArray("000000000000"),
+            senderIpv4Address,
+            ARP_REQUEST.toShort()
+        )
+        val receivedArpPacket = ByteArray(ARP_ETHER_IPV4_LEN)
+        receivedArpPacketBuf.get(receivedArpPacket)
+        verifyProgramRun(APF_VERSION_6, program, receivedArpPacket, PASSED_ARP_REQUEST)
+        apfFilter.shutdown()
+    }
+
+    @Test
     @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     fun testNsFilterNoIPv6() {
         `when`(dependencies.getAnycast6Addresses(any())).thenReturn(listOf())
@@ -1905,12 +1977,12 @@
         val nsPkt = "01020304050600010203040586DD6000000000183AFF200100000000000" +
                     "00200001A1122334420010000000000000200001A334411228700452900" +
                     "00000020010000000000000200001A33441122"
-        // when there is no IPv6 addresses -> drop NS packet
+        // when there is no IPv6 addresses -> pass NS packet
         verifyProgramRun(
                 APF_VERSION_6,
                 program,
                 HexDump.hexStringToByteArray(nsPkt),
-                DROPPED_IPV6_NS_NO_ADDRESS
+                PASSED_IPV6_NS_NO_ADDRESS
         )
 
         apfFilter.shutdown()
@@ -2296,6 +2368,7 @@
         config.multicastFilter = false
         config.ieee802_3Filter = false
         config.ethTypeBlackList = IntArray(0)
+        config.shouldHandleArpOffload = true
         return config
     }
 }
diff --git a/tests/unit/src/android/net/apf/ApfTestUtils.java b/tests/unit/src/android/net/apf/ApfTestUtils.java
index d1f2a23..0b3ea65 100644
--- a/tests/unit/src/android/net/apf/ApfTestUtils.java
+++ b/tests/unit/src/android/net/apf/ApfTestUtils.java
@@ -247,13 +247,13 @@
         private boolean mInstallPacketFilterReturn = true;
 
         MockIpClientCallback() {
-            super(mock(IIpClientCallbacks.class), mock(SharedLog.class),
-                    NetworkInformationShimImpl.newInstance());
+            super(mock(IIpClientCallbacks.class), mock(SharedLog.class), mock(SharedLog.class),
+                    NetworkInformationShimImpl.newInstance(), false);
         }
 
         MockIpClientCallback(boolean installPacketFilterReturn) {
-            super(mock(IIpClientCallbacks.class), mock(SharedLog.class),
-                    NetworkInformationShimImpl.newInstance());
+            super(mock(IIpClientCallbacks.class), mock(SharedLog.class), mock(SharedLog.class),
+                    NetworkInformationShimImpl.newInstance(), false);
             mInstallPacketFilterReturn = installPacketFilterReturn;
         }
 
diff --git a/tests/unit/src/com/android/networkstack/metrics/NetworkIpProvisioningMetricsTest.java b/tests/unit/src/com/android/networkstack/metrics/NetworkIpProvisioningMetricsTest.java
index 01d94e2..4479e9a 100644
--- a/tests/unit/src/com/android/networkstack/metrics/NetworkIpProvisioningMetricsTest.java
+++ b/tests/unit/src/com/android/networkstack/metrics/NetworkIpProvisioningMetricsTest.java
@@ -16,15 +16,17 @@
 
 package com.android.networkstack.metrics;
 
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import android.net.dhcp.DhcpPacket;
 import android.net.metrics.DhcpErrorEvent;
 import android.stats.connectivity.DhcpErrorCode;
 import android.stats.connectivity.DhcpFeature;
 import android.stats.connectivity.DisconnectCode;
 import android.stats.connectivity.HostnameTransResult;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import android.stats.connectivity.Ipv6ProvisioningMode;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -86,6 +88,7 @@
         // The maximum number of DHCP error code counts is MAX_DHCP_ERROR_COUNT
         assertEquals(mMetrics.MAX_DHCP_ERROR_COUNT, mStats.getDhcpSession().getErrorCodeCount());
     }
+
     @Test
     public void testIpProvisioningMetrics_CollectMetrics() throws Exception {
         final NetworkIpProvisioningReported mStats;
@@ -158,4 +161,24 @@
             assertTrue(mStats.getProvisioningDurationMicros() >= 1000);
         }
     }
+
+    @Test
+    public void testIpProvisioningMetrics_setIpv6ProvisioningMode() throws Exception {
+        final IpProvisioningMetrics metrics = new IpProvisioningMetrics();
+        metrics.reset();
+
+        metrics.setIpv6ProvisioningMode(Ipv6ProvisioningMode.IPV6_PROV_MODE_SLAAC);
+        metrics.setIPv6ProvisionedLatencyOnFirstTime(true);
+        metrics.setDisconnectCode(DisconnectCode.DC_NORMAL_TERMINATION);
+
+        // don't allow to override the previous metircs.
+        metrics.setIpv6ProvisioningMode(Ipv6ProvisioningMode.IPV6_PROV_MODE_DHCP6_PD_HEURISTIC);
+
+        final NetworkIpProvisioningReported stats = metrics.statsWrite();
+
+        assertTrue(stats.getIpv4LatencyMicros() == 0);
+        assertTrue(stats.getIpv6LatencyMicros() > 0);
+        assertEquals(Ipv6ProvisioningMode.IPV6_PROV_MODE_SLAAC, stats.getIpv6ProvisioningMode());
+        assertEquals(DisconnectCode.DC_NORMAL_TERMINATION, stats.getDisconnectCode());
+    }
 }