Snap for 10620181 from 4c0e9c0cf7dd067497cfec2f9774b4cfa5e67e81 to mainline-art-release

Change-Id: If2f5de8cc4464056dcd6a955344719fc790e8f8a
diff --git a/Android.bp b/Android.bp
index 40bf3ce..15b4235 100644
--- a/Android.bp
+++ b/Android.bp
@@ -506,6 +506,9 @@
     srcs: [
         "jni/network_stack_utils_jni.cpp"
     ],
+    header_libs: [
+        "bpf_headers",
+    ],
     sdk_version: "30",
     min_sdk_version: "30",
     shared_libs: [
diff --git a/OWNERS b/OWNERS
index 62c5737..c24680e 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,2 +1,2 @@
 set noparent
-file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
+file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 3869fc9..46ade58 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -7,7 +7,12 @@
       "name": "NetworkStackNextTests"
     },
     {
-      "name": "NetworkStackIntegrationTests"
+      "name": "NetworkStackIntegrationTests",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        }
+      ]
     },
     {
       "name": "NetworkStackRootTests"
@@ -32,6 +37,11 @@
       "name": "NetworkStackRootTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk]"
     }
   ],
+  "postsubmit": [
+    {
+      "name": "NetworkStackIntegrationTests"
+    }
+  ],
   "imports": [
     {
       "path": "packages/modules/Connectivity"
diff --git a/jni/network_stack_utils_jni.cpp b/jni/network_stack_utils_jni.cpp
index 5d84f57..b5d97be 100644
--- a/jni/network_stack_utils_jni.cpp
+++ b/jni/network_stack_utils_jni.cpp
@@ -36,6 +36,7 @@
 #include <netjniutils/netjniutils.h>
 
 #include <android/log.h>
+#include <bpf/BpfClassic.h>
 
 namespace android {
 constexpr const char NETWORKSTACKUTILS_PKG_NAME[] =
@@ -102,25 +103,22 @@
 static void network_stack_utils_attachDhcpFilter(JNIEnv *env, jclass clazz, jobject javaFd) {
     static sock_filter filter_code[] = {
         // Check the protocol is UDP.
-        BPF_STMT(BPF_LD  | BPF_B    | BPF_ABS, kIPv4Protocol),
-        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   IPPROTO_UDP, 0, 6),
+        BPF_LOAD_IPV4_U8(protocol),
+        BPF2_REJECT_IF_NOT_EQUAL(IPPROTO_UDP),
 
         // Check this is not a fragment.
-        BPF_STMT(BPF_LD  | BPF_H    | BPF_ABS, kIPv4FlagsOffset),
-        BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K,   IP_MF | IP_OFFMASK, 4, 0),
+        BPF_LOAD_IPV4_BE16(frag_off),
+        BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K,   IP_MF | IP_OFFMASK, 0, 1),
+        BPF_REJECT,
 
         // Get the IP header length.
         BPF_STMT(BPF_LDX | BPF_B    | BPF_MSH, kEtherHeaderLen),
 
         // Check the destination port.
         BPF_STMT(BPF_LD  | BPF_H    | BPF_IND, kUDPDstPortIndirectOffset),
-        BPF_JUMP(BPF_JMP | BPF_JEQ  | BPF_K,   kDhcpClientPort, 0, 1),
+        BPF2_REJECT_IF_NOT_EQUAL(kDhcpClientPort),
 
-        // Accept.
-        BPF_STMT(BPF_RET | BPF_K,              0xffff),
-
-        // Reject.
-        BPF_STMT(BPF_RET | BPF_K,              0)
+        BPF_ACCEPT,
     };
     const sock_fprog filter = {
         sizeof(filter_code) / sizeof(filter_code[0]),
@@ -133,28 +131,17 @@
     }
 }
 
-static void network_stack_utils_attachRaFilter(JNIEnv *env, jclass clazz, jobject javaFd,
-        jint hardwareAddressType) {
-    if (hardwareAddressType != ARPHRD_ETHER) {
-        jniThrowExceptionFmt(env, "java/net/SocketException",
-                "attachRaFilter only supports ARPHRD_ETHER");
-        return;
-    }
-
+static void network_stack_utils_attachRaFilter(JNIEnv *env, jclass clazz, jobject javaFd) {
     static sock_filter filter_code[] = {
         // Check IPv6 Next Header is ICMPv6.
-        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  kIPv6NextHeader),
-        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    IPPROTO_ICMPV6, 0, 3),
+        BPF_LOAD_IPV6_U8(nexthdr),
+        BPF2_REJECT_IF_NOT_EQUAL(IPPROTO_ICMPV6),
 
         // Check ICMPv6 type is Router Advertisement.
-        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  kICMPv6TypeOffset),
-        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    ND_ROUTER_ADVERT, 0, 1),
+        BPF_LOAD_NET_RELATIVE_U8(sizeof(ipv6hdr) + offsetof(icmp6_hdr, icmp6_type)),
+        BPF2_REJECT_IF_NOT_EQUAL(ND_ROUTER_ADVERT),
 
-        // Accept.
-        BPF_STMT(BPF_RET | BPF_K,              0xffff),
-
-        // Reject.
-        BPF_STMT(BPF_RET | BPF_K,              0)
+        BPF_ACCEPT,
     };
     static const sock_fprog filter = {
         sizeof(filter_code) / sizeof(filter_code[0]),
@@ -227,11 +214,9 @@
         BPF_JUMP(BPF_JMP | BPF_JGE  | BPF_K,   ND_ROUTER_SOLICIT, 0, 2),
         BPF_JUMP(BPF_JMP | BPF_JGT  | BPF_K,   ND_NEIGHBOR_ADVERT, 1, 0),
 
-        // Accept.
-        BPF_STMT(BPF_RET | BPF_K,              0xffff),
+        BPF_ACCEPT,
 
-        // Reject.
-        BPF_STMT(BPF_RET | BPF_K,              0)
+        BPF_REJECT,
     };
     static const sock_fprog filter = {
         sizeof(filter_code) / sizeof(filter_code[0]),
@@ -252,7 +237,7 @@
     /* name, signature, funcPtr */
     { "addArpEntry", "([B[BLjava/lang/String;Ljava/io/FileDescriptor;)V", (void*) network_stack_utils_addArpEntry },
     { "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) network_stack_utils_attachDhcpFilter },
-    { "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) network_stack_utils_attachRaFilter },
+    { "attachRaFilter", "(Ljava/io/FileDescriptor;)V", (void*) network_stack_utils_attachRaFilter },
     { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) network_stack_utils_attachControlPacketFilter },
 };
 
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java
index 1bc3b41..5c8ff49 100644
--- a/src/android/net/apf/ApfFilter.java
+++ b/src/android/net/apf/ApfFilter.java
@@ -537,7 +537,7 @@
             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, mApfCapabilities.apfPacketFormat);
+            NetworkStackUtils.attachRaFilter(socket);
         } catch(SocketException|ErrnoException e) {
             Log.e(TAG, "Error starting filter", e);
             return;
diff --git a/src/android/net/dhcp6/Dhcp6AdvertisePacket.java b/src/android/net/dhcp6/Dhcp6AdvertisePacket.java
index a9fac83..263ab5f 100644
--- a/src/android/net/dhcp6/Dhcp6AdvertisePacket.java
+++ b/src/android/net/dhcp6/Dhcp6AdvertisePacket.java
@@ -34,7 +34,7 @@
      */
     Dhcp6AdvertisePacket(int transId, @NonNull final byte[] clientDuid,
             @NonNull final byte[] serverDuid, final byte[] iapd) {
-        super(transId, (short) 0 /* secs */, clientDuid, serverDuid, iapd);
+        super(transId, 0 /* elapsedTime */, clientDuid, serverDuid, iapd);
     }
 
     /**
diff --git a/src/android/net/dhcp6/Dhcp6Client.java b/src/android/net/dhcp6/Dhcp6Client.java
index 975c2c5..d4b4a21 100644
--- a/src/android/net/dhcp6/Dhcp6Client.java
+++ b/src/android/net/dhcp6/Dhcp6Client.java
@@ -292,37 +292,35 @@
         mTransStartMillis = SystemClock.elapsedRealtime();
     }
 
-    private short getHundredthsOfSec() {
-        return (short) ((SystemClock.elapsedRealtime() - mTransStartMillis) / 10);
+    private long getElapsedTimeMs() {
+        return SystemClock.elapsedRealtime() - mTransStartMillis;
     }
 
     @SuppressWarnings("ByteBufferBackingArray")
     private boolean sendSolicitPacket(final ByteBuffer iapd) {
         final ByteBuffer packet = Dhcp6Packet.buildSolicitPacket(mTransId,
-                getHundredthsOfSec() /* elapsed time */, iapd.array(), mClientDuid,
-                true /* rapidCommit */);
+                getElapsedTimeMs(), iapd.array(), mClientDuid, true /* rapidCommit */);
         return transmitPacket(packet, "solicit");
     }
 
     @SuppressWarnings("ByteBufferBackingArray")
     private boolean sendRequestPacket(final ByteBuffer iapd) {
-        final ByteBuffer packet = Dhcp6Packet.buildRequestPacket(mTransId,
-                getHundredthsOfSec() /* elapsed time */, iapd.array(), mClientDuid,
-                mServerDuid);
+        final ByteBuffer packet = Dhcp6Packet.buildRequestPacket(mTransId, getElapsedTimeMs(),
+                iapd.array(), mClientDuid, mServerDuid);
         return transmitPacket(packet, "request");
     }
 
     @SuppressWarnings("ByteBufferBackingArray")
     private boolean sendRenewPacket(final ByteBuffer iapd) {
-        final ByteBuffer packet = Dhcp6Packet.buildRenewPacket(mTransId,
-                getHundredthsOfSec() /* elapsed time*/, iapd.array(), mClientDuid, mServerDuid);
+        final ByteBuffer packet = Dhcp6Packet.buildRenewPacket(mTransId, getElapsedTimeMs(),
+                iapd.array(), mClientDuid, mServerDuid);
         return transmitPacket(packet, "renew");
     }
 
     @SuppressWarnings("ByteBufferBackingArray")
     private boolean sendRebindPacket(final ByteBuffer iapd) {
-        final ByteBuffer packet = Dhcp6Packet.buildRebindPacket(mTransId,
-                getHundredthsOfSec() /* elapsed time */, iapd.array(), mClientDuid);
+        final ByteBuffer packet = Dhcp6Packet.buildRebindPacket(mTransId, getElapsedTimeMs(),
+                iapd.array(), mClientDuid);
         return transmitPacket(packet, "rebind");
     }
 
@@ -632,7 +630,7 @@
         @Override
         protected void handlePacket(byte[] recvbuf, int length) {
             try {
-                final Dhcp6Packet packet = Dhcp6Packet.decodePacket(recvbuf, length);
+                final Dhcp6Packet packet = Dhcp6Packet.decode(recvbuf, length);
                 if (DBG) Log.d(TAG, "Received packet: " + packet);
                 sendMessage(CMD_RECEIVED_PACKET, packet);
             } catch (Dhcp6Packet.ParseException e) {
diff --git a/src/android/net/dhcp6/Dhcp6Packet.java b/src/android/net/dhcp6/Dhcp6Packet.java
index 9682556..8a11547 100644
--- a/src/android/net/dhcp6/Dhcp6Packet.java
+++ b/src/android/net/dhcp6/Dhcp6Packet.java
@@ -80,7 +80,7 @@
      * This time is expressed in hundredths of a second.
      */
     public static final byte DHCP6_ELAPSED_TIME = 8;
-    protected final short mSecs;
+    protected final int mElapsedTime;
 
     /**
      * DHCPv6 Optional Type: Status Code.
@@ -123,10 +123,10 @@
      */
     protected int mIaId;
 
-    Dhcp6Packet(int transId, short secs, @NonNull final byte[] clientDuid, final byte[] serverDuid,
-            @NonNull final byte[] iapd) {
+    Dhcp6Packet(int transId, int elapsedTime, @NonNull final byte[] clientDuid,
+            final byte[] serverDuid, @NonNull final byte[] iapd) {
         mTransId = transId;
-        mSecs = secs;
+        mElapsedTime = elapsedTime;
         mClientDuid = clientDuid;
         mServerDuid = serverDuid;
         mIaPd = iapd;
@@ -249,8 +249,8 @@
      * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      */
     @VisibleForTesting
-    static Dhcp6Packet decodePacket(@NonNull final ByteBuffer packet) throws ParseException {
-        short secs = 0;
+    static Dhcp6Packet decode(@NonNull final ByteBuffer packet) throws ParseException {
+        int elapsedTime = 0;
         byte[] iapd = null;
         byte[] serverDuid = null;
         byte[] clientDuid = null;
@@ -308,7 +308,7 @@
                         break;
                     case DHCP6_ELAPSED_TIME:
                         expectedLen = 2;
-                        secs = packet.getShort();
+                        elapsedTime = (int) (packet.getShort() & 0xFFFF);
                         break;
                     case DHCP6_STATUS_CODE:
                         expectedLen = optionLen;
@@ -335,23 +335,26 @@
 
         switch(messageType) {
             case DHCP6_MESSAGE_TYPE_SOLICIT:
-                newPacket = new Dhcp6SolicitPacket(transId, secs, clientDuid, iapd, rapidCommit);
+                newPacket = new Dhcp6SolicitPacket(transId, elapsedTime, clientDuid, iapd,
+                        rapidCommit);
                 break;
             case DHCP6_MESSAGE_TYPE_ADVERTISE:
                 newPacket = new Dhcp6AdvertisePacket(transId, clientDuid, serverDuid, iapd);
                 break;
             case DHCP6_MESSAGE_TYPE_REQUEST:
-                newPacket = new Dhcp6RequestPacket(transId, secs, clientDuid, serverDuid, iapd);
+                newPacket = new Dhcp6RequestPacket(transId, elapsedTime, clientDuid, serverDuid,
+                        iapd);
                 break;
             case DHCP6_MESSAGE_TYPE_REPLY:
                 newPacket = new Dhcp6ReplyPacket(transId, clientDuid, serverDuid, iapd,
                         rapidCommit);
                 break;
             case DHCP6_MESSAGE_TYPE_RENEW:
-                newPacket = new Dhcp6RenewPacket(transId, secs, clientDuid, serverDuid, iapd);
+                newPacket = new Dhcp6RenewPacket(transId, elapsedTime, clientDuid, serverDuid,
+                        iapd);
                 break;
             case DHCP6_MESSAGE_TYPE_REBIND:
-                newPacket = new Dhcp6RebindPacket(transId, secs, clientDuid, iapd);
+                newPacket = new Dhcp6RebindPacket(transId, elapsedTime, clientDuid, iapd);
                 break;
             default:
                 throw new ParseException("Unimplemented DHCP6 message type %d" + messageType);
@@ -376,10 +379,10 @@
     /**
      * Parse a packet from an array of bytes, stopping at the given length.
      */
-    public static Dhcp6Packet decodePacket(@NonNull final byte[] packet, int length)
+    public static Dhcp6Packet decode(@NonNull final byte[] packet, int length)
             throws ParseException {
         final ByteBuffer buffer = ByteBuffer.wrap(packet, 0, length).order(ByteOrder.BIG_ENDIAN);
-        return decodePacket(buffer);
+        return decode(buffer);
     }
 
     /**
@@ -524,10 +527,11 @@
     /**
      * Builds a DHCPv6 SOLICIT packet from the required specified parameters.
      */
-    public static ByteBuffer buildSolicitPacket(int transId, short secs, @NonNull final byte[] iapd,
-            @NonNull final byte[] clientDuid, boolean rapidCommit) {
+    public static ByteBuffer buildSolicitPacket(int transId, long millisecs,
+            @NonNull final byte[] iapd, @NonNull final byte[] clientDuid, boolean rapidCommit) {
         final Dhcp6SolicitPacket pkt =
-                new Dhcp6SolicitPacket(transId, secs, clientDuid, iapd, rapidCommit);
+                new Dhcp6SolicitPacket(transId, (int) (millisecs / 10) /* elapsed time */,
+                        clientDuid, iapd, rapidCommit);
         return pkt.buildPacket();
     }
 
@@ -555,29 +559,34 @@
     /**
      * Builds a DHCPv6 REQUEST packet from the required specified parameters.
      */
-    public static ByteBuffer buildRequestPacket(int transId, short secs, @NonNull final byte[] iapd,
-            @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid) {
+    public static ByteBuffer buildRequestPacket(int transId, long millisecs,
+            @NonNull final byte[] iapd, @NonNull final byte[] clientDuid,
+            @NonNull final byte[] serverDuid) {
         final Dhcp6RequestPacket pkt =
-                new Dhcp6RequestPacket(transId, secs, clientDuid, serverDuid, iapd);
+                new Dhcp6RequestPacket(transId, (int) (millisecs / 10) /* elapsed time */,
+                        clientDuid, serverDuid, iapd);
         return pkt.buildPacket();
     }
 
     /**
      * Builds a DHCPv6 RENEW packet from the required specified parameters.
      */
-    public static ByteBuffer buildRenewPacket(int transId, short secs, @NonNull final byte[] iapd,
-            @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid) {
+    public static ByteBuffer buildRenewPacket(int transId, long millisecs,
+            @NonNull final byte[] iapd, @NonNull final byte[] clientDuid,
+            @NonNull final byte[] serverDuid) {
         final Dhcp6RenewPacket pkt =
-                new Dhcp6RenewPacket(transId, secs, clientDuid, serverDuid, iapd);
+                new Dhcp6RenewPacket(transId, (int) (millisecs / 10) /* elapsed time */, clientDuid,
+                        serverDuid, iapd);
         return pkt.buildPacket();
     }
 
     /**
      * Builds a DHCPv6 REBIND packet from the required specified parameters.
      */
-    public static ByteBuffer buildRebindPacket(int transId, short secs, @NonNull final byte[] iapd,
-            @NonNull final byte[] clientDuid) {
-        final Dhcp6RebindPacket pkt = new Dhcp6RebindPacket(transId, secs, clientDuid, iapd);
+    public static ByteBuffer buildRebindPacket(int transId, long millisecs,
+            @NonNull final byte[] iapd, @NonNull final byte[] clientDuid) {
+        final Dhcp6RebindPacket pkt = new Dhcp6RebindPacket(transId,
+                (int) (millisecs / 10) /* elapsed time */, clientDuid, iapd);
         return pkt.buildPacket();
     }
 }
diff --git a/src/android/net/dhcp6/Dhcp6RebindPacket.java b/src/android/net/dhcp6/Dhcp6RebindPacket.java
index 33a9fc1..87f2f45 100644
--- a/src/android/net/dhcp6/Dhcp6RebindPacket.java
+++ b/src/android/net/dhcp6/Dhcp6RebindPacket.java
@@ -33,9 +33,9 @@
     /**
      * Generates a rebind packet with the specified parameters.
      */
-    Dhcp6RebindPacket(int transId, short secs, @NonNull final byte[] clientDuid,
+    Dhcp6RebindPacket(int transId, int elapsedTime, @NonNull final byte[] clientDuid,
             @NonNull final byte[] iapd) {
-        super(transId, secs, clientDuid, null /* serverDuid */, iapd);
+        super(transId, elapsedTime, clientDuid, null /* serverDuid */, iapd);
     }
 
     /**
@@ -47,7 +47,7 @@
         packet.putInt(msgTypeAndTransId);
 
         addTlv(packet, DHCP6_CLIENT_IDENTIFIER, getClientDuid());
-        addTlv(packet, DHCP6_ELAPSED_TIME, mSecs);
+        addTlv(packet, DHCP6_ELAPSED_TIME, (short) (mElapsedTime & 0xFFFF));
         addTlv(packet, DHCP6_IA_PD, mIaPd);
 
         packet.flip();
diff --git a/src/android/net/dhcp6/Dhcp6RenewPacket.java b/src/android/net/dhcp6/Dhcp6RenewPacket.java
index 3de73df..8c6686c 100644
--- a/src/android/net/dhcp6/Dhcp6RenewPacket.java
+++ b/src/android/net/dhcp6/Dhcp6RenewPacket.java
@@ -33,9 +33,9 @@
     /**
      * Generates a renew packet with the specified parameters.
      */
-    Dhcp6RenewPacket(int transId, short secs, @NonNull final byte[] clientDuid,
+    Dhcp6RenewPacket(int transId, int elapsedTime, @NonNull final byte[] clientDuid,
             @NonNull final byte[] serverDuid, final byte[] iapd) {
-        super(transId, secs, clientDuid, serverDuid, iapd);
+        super(transId, elapsedTime, clientDuid, serverDuid, iapd);
     }
 
     /**
@@ -48,7 +48,7 @@
 
         addTlv(packet, DHCP6_SERVER_IDENTIFIER, mServerDuid);
         addTlv(packet, DHCP6_CLIENT_IDENTIFIER, mClientDuid);
-        addTlv(packet, DHCP6_ELAPSED_TIME, mSecs);
+        addTlv(packet, DHCP6_ELAPSED_TIME, (short) (mElapsedTime & 0xFFFF));
         addTlv(packet, DHCP6_IA_PD, mIaPd);
 
         packet.flip();
diff --git a/src/android/net/dhcp6/Dhcp6ReplyPacket.java b/src/android/net/dhcp6/Dhcp6ReplyPacket.java
index 15f748b..d68fbdb 100644
--- a/src/android/net/dhcp6/Dhcp6ReplyPacket.java
+++ b/src/android/net/dhcp6/Dhcp6ReplyPacket.java
@@ -35,7 +35,7 @@
      */
     Dhcp6ReplyPacket(int transId, @NonNull final byte[] clientDuid,
             @NonNull final byte[] serverDuid, final byte[] iapd, boolean rapidCommit) {
-        super(transId, (short) 0 /* secs */, clientDuid, serverDuid, iapd);
+        super(transId, 0 /* elapsedTime */, clientDuid, serverDuid, iapd);
         mRapidCommit = rapidCommit;
     }
 
diff --git a/src/android/net/dhcp6/Dhcp6RequestPacket.java b/src/android/net/dhcp6/Dhcp6RequestPacket.java
index 5671502..0c65f17 100644
--- a/src/android/net/dhcp6/Dhcp6RequestPacket.java
+++ b/src/android/net/dhcp6/Dhcp6RequestPacket.java
@@ -32,9 +32,9 @@
     /**
      * Generates a request packet with the specified parameters.
      */
-    Dhcp6RequestPacket(int transId, short secs, @NonNull final byte[] clientDuid,
+    Dhcp6RequestPacket(int transId, int elapsedTime, @NonNull final byte[] clientDuid,
             @NonNull final byte[] serverDuid, final byte[] iapd) {
-        super(transId, secs, clientDuid, serverDuid, iapd);
+        super(transId, elapsedTime, clientDuid, serverDuid, iapd);
     }
 
     /**
@@ -47,7 +47,7 @@
 
         addTlv(packet, DHCP6_SERVER_IDENTIFIER, mServerDuid);
         addTlv(packet, DHCP6_CLIENT_IDENTIFIER, mClientDuid);
-        addTlv(packet, DHCP6_ELAPSED_TIME, mSecs);
+        addTlv(packet, DHCP6_ELAPSED_TIME, (short) (mElapsedTime & 0xFFFF));
         addTlv(packet, DHCP6_IA_PD, mIaPd);
 
         packet.flip();
diff --git a/src/android/net/dhcp6/Dhcp6SolicitPacket.java b/src/android/net/dhcp6/Dhcp6SolicitPacket.java
index 69dc81e..4916c1e 100644
--- a/src/android/net/dhcp6/Dhcp6SolicitPacket.java
+++ b/src/android/net/dhcp6/Dhcp6SolicitPacket.java
@@ -31,9 +31,9 @@
     /**
      * Generates a solicit packet with the specified parameters.
      */
-    Dhcp6SolicitPacket(int transId, short secs, @NonNull final byte[] clientDuid,
+    Dhcp6SolicitPacket(int transId, int elapsedTime, @NonNull final byte[] clientDuid,
             final byte[] iapd, boolean rapidCommit) {
-        super(transId, secs, clientDuid, null /* serverDuid */, iapd);
+        super(transId, elapsedTime, clientDuid, null /* serverDuid */, iapd);
         mRapidCommit = rapidCommit;
     }
 
@@ -45,7 +45,7 @@
         final int msgTypeAndTransId = (DHCP6_MESSAGE_TYPE_SOLICIT << 24) | mTransId;
         packet.putInt(msgTypeAndTransId);
 
-        addTlv(packet, DHCP6_ELAPSED_TIME, mSecs);
+        addTlv(packet, DHCP6_ELAPSED_TIME, (short) (mElapsedTime & 0xFFFF));
         addTlv(packet, DHCP6_CLIENT_IDENTIFIER, mClientDuid);
         addTlv(packet, DHCP6_IA_PD, mIaPd);
         if (mRapidCommit) {
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index b3a0652..c169ad5 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -2814,11 +2814,10 @@
 
             // Delete the global IPv6 address based on delegated prefix from interface.
             for (LinkAddress la : mLinkProperties.getLinkAddresses()) {
-                if (!la.isIpv6()) continue;
-                final Inet6Address address = (Inet6Address) la.getAddress();
-                if (la.isIpv6() && prefix.contains(address)) {
-                    NetlinkUtils.sendRtmDelAddressRequest(mInterfaceParams.index, address,
-                            (short) la.getPrefixLength());
+                final InetAddress address = la.getAddress();
+                if (prefix.contains(address)) {
+                    NetlinkUtils.sendRtmDelAddressRequest(mInterfaceParams.index,
+                            (Inet6Address) address, (short) la.getPrefixLength());
                 }
             }
         }
diff --git a/src/android/net/ip/IpReachabilityMonitor.java b/src/android/net/ip/IpReachabilityMonitor.java
index ff6d65e..4d40c4f 100644
--- a/src/android/net/ip/IpReachabilityMonitor.java
+++ b/src/android/net/ip/IpReachabilityMonitor.java
@@ -262,7 +262,7 @@
                 IP_REACHABILITY_MCAST_RESOLICIT_VERSION, true /* defaultEnabled */);
         mIgnoreIncompleteIpv6DnsServerEnabled = dependencies.isFeatureEnabled(context,
                 IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION,
-                false /* defaultEnabled */);
+                true /* defaultEnabled */);
         mIgnoreIncompleteIpv6DefaultRouterEnabled = dependencies.isFeatureEnabled(context,
                 IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION,
                 false /* defaultEnabled */);
diff --git a/src/com/android/networkstack/util/NetworkStackUtils.java b/src/com/android/networkstack/util/NetworkStackUtils.java
index 8559a26..8fb157c 100755
--- a/src/com/android/networkstack/util/NetworkStackUtils.java
+++ b/src/com/android/networkstack/util/NetworkStackUtils.java
@@ -385,9 +385,8 @@
     /**
      * Attaches a socket filter that accepts ICMPv6 router advertisements to the given socket.
      * @param fd the socket's {@link FileDescriptor}.
-     * @param packetType the hardware address type, one of ARPHRD_*.
      */
-    public static native void attachRaFilter(FileDescriptor fd, int packetType)
+    public static native void attachRaFilter(FileDescriptor fd)
             throws SocketException;
 
     /**
diff --git a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
index dfc8c1a..6f30d4c 100644
--- a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
@@ -218,6 +218,7 @@
 import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.HandlerUtils;
+import com.android.testutils.SkipPresubmit;
 import com.android.testutils.TapPacketReader;
 import com.android.testutils.TestableNetworkAgent;
 import com.android.testutils.TestableNetworkCallback;
@@ -1289,7 +1290,7 @@
             // Strip the Ethernet/IPv6/UDP headers, only keep DHCPv6 message payload for decode.
             final byte[] payload =
                     Arrays.copyOfRange(packet, DHCP6_HEADER_OFFSET, packet.length);
-            final Dhcp6Packet dhcp6Packet = Dhcp6Packet.decodePacket(payload, payload.length);
+            final Dhcp6Packet dhcp6Packet = Dhcp6Packet.decode(payload, payload.length);
             if (dhcp6Packet != null) return dhcp6Packet;
         }
         return null;
@@ -4851,6 +4852,8 @@
                 x -> x.isIpv6Provisioned()
                         && hasIpv6AddressPrefixedWith(x, prefix)
                         && hasRouteTo(x, "2001:db8:1::/64", RTN_UNREACHABLE)
+                        // IPv4 address, IPv6 link-local, two global delegated IPv6 addresses
+                        && x.getLinkAddresses().size() == 4
         ));
     }
 
@@ -4886,8 +4889,9 @@
         assertTrue(packet instanceof Dhcp6RebindPacket);
     }
 
-    @Test
     @SignatureRequiredTest(reason = "Need to mock the DHCP6 renew/rebind alarms")
+    @SkipPresubmit(reason = "Out of SLO flakiness")
+    @Test
     public void testDhcp6Pd_prefixMismatchOnRenew() throws Exception {
         prepareDhcp6PdRenewTest();
 
diff --git a/tests/integration/signature/android/net/NetworkStatsIntegrationTest.kt b/tests/integration/signature/android/net/NetworkStatsIntegrationTest.kt
index dcc0b1f..0f860a8 100644
--- a/tests/integration/signature/android/net/NetworkStatsIntegrationTest.kt
+++ b/tests/integration/signature/android/net/NetworkStatsIntegrationTest.kt
@@ -34,6 +34,7 @@
 import com.android.testutils.DevSdkIgnoreRunner
 import com.android.testutils.PacketBridge
 import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
+import com.android.testutils.SkipPresubmit
 import com.android.testutils.TestDnsServer
 import com.android.testutils.TestHttpServer
 import com.android.testutils.TestableNetworkCallback
@@ -178,6 +179,7 @@
      * While the packets are being forwarded to the external interface, the servers will see
      * the packets originated from the mocked v6 address, and destined to a local v6 address.
      */
+    @SkipPresubmit(reason = "Out of SLO flakiness")
     @Test
     fun test464XlatTcpStats() {
         // Wait for 464Xlat to be ready.
diff --git a/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt b/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt
index cf514e9..2377ffa 100644
--- a/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt
+++ b/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt
@@ -176,7 +176,7 @@
         echo.rewind()
         assertNextPacketEquals(socket, echo.readAsArray(), "ICMPv6 echo")
 
-        NetworkStackUtils.attachRaFilter(socket, ARPHRD_ETHER)
+        NetworkStackUtils.attachRaFilter(socket)
         // Send another echo, then an RA. After setting the filter expect only the RA.
         echo.rewind()
         reader.sendResponse(echo)
diff --git a/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt b/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt
index 2d093f7..de97ed4 100644
--- a/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt
+++ b/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt
@@ -42,7 +42,7 @@
                 // IA prefix option(option_len=25)
                 "001A001900000000000000004000000000000000000000000000000000"
         val bytes = HexDump.hexStringToByteArray(solicitHex)
-        val packet = Dhcp6Packet.decodePacket(ByteBuffer.wrap(bytes))
+        val packet = Dhcp6Packet.decode(ByteBuffer.wrap(bytes))
         assertTrue(packet is Dhcp6SolicitPacket)
     }
 
@@ -61,7 +61,7 @@
                 "001A001900000000000000004000000000000000000000000000000000"
         val bytes = HexDump.hexStringToByteArray(solicitHex)
         assertThrows(Dhcp6Packet.ParseException::class.java) {
-                Dhcp6Packet.decodePacket(ByteBuffer.wrap(bytes))
+                Dhcp6Packet.decode(ByteBuffer.wrap(bytes))
         }
     }
 
@@ -80,7 +80,7 @@
                 "001A0019000000000000000040000000000000000000000000000000"
         val bytes = HexDump.hexStringToByteArray(solicitHex)
         assertThrows(Dhcp6Packet.ParseException::class.java) {
-                Dhcp6Packet.decodePacket(ByteBuffer.wrap(bytes))
+                Dhcp6Packet.decode(ByteBuffer.wrap(bytes))
         }
     }
 
@@ -99,7 +99,7 @@
                 "001A001900000000000000004000000000000000000000000000000000"
         val bytes = HexDump.hexStringToByteArray(solicitHex)
         assertThrows(Dhcp6Packet.ParseException::class.java) {
-                Dhcp6Packet.decodePacket(ByteBuffer.wrap(bytes))
+                Dhcp6Packet.decode(ByteBuffer.wrap(bytes))
         }
     }
 
@@ -119,7 +119,7 @@
                 // IA prefix option(option_len=25, prefix="fdfd:9ed6:7950:2::/64")
                 "001A00190000019F0000A8C040FDFD9ED6795000010000000000000000"
         val bytes = HexDump.hexStringToByteArray(advertiseHex)
-        val packet = Dhcp6Packet.decodePacket(ByteBuffer.wrap(bytes))
+        val packet = Dhcp6Packet.decode(ByteBuffer.wrap(bytes))
         assertTrue(packet is Dhcp6AdvertisePacket)
     }
 
@@ -142,7 +142,7 @@
                 "001A00190000019F0000A8C040FDFD9ED6795000010000000000000000"
         val bytes = HexDump.hexStringToByteArray(advertiseHex)
         // The unsupported option will be skipped normally and won't throw ParseException.
-        val packet = Dhcp6Packet.decodePacket(ByteBuffer.wrap(bytes))
+        val packet = Dhcp6Packet.decode(ByteBuffer.wrap(bytes))
         assertTrue(packet is Dhcp6AdvertisePacket)
     }
 }
diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
index 19c9e5e..4cf6f34 100644
--- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -90,7 +90,6 @@
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
@@ -578,7 +577,7 @@
             return null;
         }).when(mContext).unregisterReceiver(any());
 
-        resetCallbacks();
+        initCallbacks(11 /* interfaceVersion */);
 
         setMinDataStallEvaluateInterval(TEST_MIN_STALL_EVALUATE_INTERVAL_MS);
         setDataStallEvaluationType(DATA_STALL_EVALUATION_TYPE_DNS);
@@ -609,12 +608,7 @@
         }
     }
 
-    private void resetCallbacks() throws Exception {
-        resetCallbacks(11);
-    }
-
-    private void resetCallbacks(int interfaceVersion) throws Exception {
-        reset(mCallbacks);
+    private void initCallbacks(int interfaceVersion) throws Exception {
         try {
             doReturn(interfaceVersion).when(mCallbacks).getInterfaceVersion();
         } catch (RemoteException e) {
@@ -1025,7 +1019,6 @@
         verify(mContext, times(1)).registerReceiver(receiverCaptor.capture(),
                 argThat(receiver -> ACTION_CONFIGURATION_CHANGED.equals(receiver.getAction(0))));
 
-        resetCallbacks();
         // New URLs with partial connectivity
         doReturn(TEST_HTTPS_OTHER_URL1).when(mResources).getString(
                 R.string.config_captive_portal_https_url);
@@ -1039,7 +1032,8 @@
 
         HandlerUtils.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS);
         verifyNetworkTested(NETWORK_VALIDATION_RESULT_PARTIAL,
-                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP);
+                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP,
+                1 /* interactions */);
         verify(mOtherHttpsConnection1, times(1)).getResponseCode();
         verify(mOtherHttpConnection1, times(1)).getResponseCode();
     }
@@ -1311,7 +1305,7 @@
         assertTrue(INITIAL_REEVALUATE_DELAY_MS < 2000);
         verify(mOtherFallbackConnection, timeout(INITIAL_REEVALUATE_DELAY_MS + HANDLER_TIMEOUT_MS))
                 .getResponseCode();
-        verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, TEST_LOGIN_URL);
+        verifyNetworkTestedPortal(TEST_LOGIN_URL, 1 /* interactions */);
     }
 
     @Test
@@ -1403,8 +1397,7 @@
                 + "'user-portal-url': '" + TEST_LOGIN_URL + "'}");
         nm.notifyLinkPropertiesChanged(makeCapportLPs());
 
-        verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */,
-                TEST_LOGIN_URL);
+        verifyNetworkTestedPortal(TEST_LOGIN_URL, 1 /* interactions */);
         final ArgumentCaptor<CaptivePortalData> capportCaptor = ArgumentCaptor.forClass(
                 CaptivePortalData.class);
         verify(mCallbacks).notifyCaptivePortalDataChanged(capportCaptor.capture());
@@ -1435,7 +1428,7 @@
 
         // After notifyNetworkConnected, validation uses the capport API contents
         notifyNetworkConnected(nm, lp, CELL_METERED_CAPABILITIES);
-        verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, TEST_LOGIN_URL);
+        verifyNetworkTestedPortal(TEST_LOGIN_URL, 1 /* interactions */);
 
         verify(mHttpConnection, never()).getResponseCode();
         verify(mCapportApiConnection).getResponseCode();
@@ -1555,25 +1548,19 @@
                 NETWORK_VALIDATION_RESULT_VALID,
                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS, null);
 
-        resetCallbacks();
         // Underlying network changed.
         notifyUnderlyingNetworkChange(nm, nc , List.of(new Network(TEST_NETID)));
         // The underlying network change should cause a re-validation
-        verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID,
-                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS);
+        verifyNetworkTestedValidFromHttps(1 /* interactions */);
 
-        resetCallbacks();
         notifyUnderlyingNetworkChange(nm, nc , List.of(new Network(TEST_NETID)));
-        // Identical networks should not cause revalidation.
-        verify(mCallbacks, never()).notifyNetworkTestedWithExtras(matchNetworkTestResultParcelable(
-                NETWORK_VALIDATION_RESULT_VALID,
-                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS));
+        // Identical networks should not cause revalidation. The interaction stays in 1 time which
+        // is verified in runNetworkTest.
+        verifyNetworkTestedValidFromHttps(1 /* interactions */);
 
-        resetCallbacks();
         // Change to another network
         notifyUnderlyingNetworkChange(nm, nc , List.of(new Network(TEST_NETID2)));
-        verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID,
-                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS);
+        verifyNetworkTestedValidFromHttps(2 /* interactions */);
     }
 
     private void notifyUnderlyingNetworkChange(NetworkMonitor nm, NetworkCapabilities nc,
@@ -1974,7 +1961,7 @@
     @Test
     public void testNoInternetCapabilityValidated_OlderPlatform() throws Exception {
         // Before callbacks version 11, NETWORK_VALIDATION_RESULT_SKIPPED is not sent
-        resetCallbacks(10);
+        initCallbacks(10);
         doValidationSkippedTest(CELL_NO_INTERNET_CAPABILITIES, NETWORK_VALIDATION_RESULT_VALID);
     }
 
@@ -2127,8 +2114,6 @@
         // Portal URL should be detection URL.
         final String redirectUrl = bundle.getString(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL);
         assertEquals(expectedUrl, redirectUrl);
-
-        resetCallbacks();
     }
 
 
@@ -2206,29 +2191,25 @@
         wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns6.google",
                 new InetAddress[0]));
         notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES);
-        verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, PROBES_PRIVDNS_VALID);
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyProbeStatusChanged(
-                eq(PROBES_PRIVDNS_VALID), eq(PROBES_PRIVDNS_VALID));
+        verifyNetworkTestedValidFromPrivateDns(1 /* interactions */);
+        verifyProbeStatusChangedPrivateDnsCompleteAndSucceeded(1 /* interaction */);
 
         // Verify dns query only get v4 address.
-        resetCallbacks();
         mFakeDns.setAnswer("dns4.google", new String[]{"192.0.2.1"}, TYPE_A);
         wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns4.google",
                 new InetAddress[0]));
-        verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, PROBES_PRIVDNS_VALID);
+        verifyNetworkTestedValidFromPrivateDns(2 /* interactions */);
         // NetworkMonitor will check if the probes has changed or not, if the probes has not
-        // changed, the callback won't be fired.
-        verify(mCallbacks, never()).notifyProbeStatusChanged(
-                eq(PROBES_PRIVDNS_VALID), eq(PROBES_PRIVDNS_VALID));
+        // changed, the callback won't be fired. The interaction stays in 1 time.
+        verifyProbeStatusChangedPrivateDnsCompleteAndSucceeded(1 /* interaction */);
 
         // Verify dns query get both v4 and v6 address.
-        resetCallbacks();
         mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::54"}, TYPE_AAAA);
         mFakeDns.setAnswer("dns.google", new String[]{"192.0.2.3"}, TYPE_A);
         wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
-        verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, PROBES_PRIVDNS_VALID);
-        verify(mCallbacks, never()).notifyProbeStatusChanged(
-                eq(PROBES_PRIVDNS_VALID), eq(PROBES_PRIVDNS_VALID));
+        verifyNetworkTestedValidFromPrivateDns(3 /* interactions */);
+        // Verify no further interaction.
+        verifyProbeStatusChangedPrivateDnsCompleteAndSucceeded(1 /* interaction */);
     }
 
     @Test
@@ -2240,22 +2221,18 @@
         WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
         wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
         notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES);
-        verifyNetworkTested(VALIDATION_RESULT_INVALID,
-                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS);
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS)).notifyProbeStatusChanged(
-                eq(PROBES_PRIVDNS_VALID), eq(NETWORK_VALIDATION_PROBE_DNS
-                | NETWORK_VALIDATION_PROBE_HTTPS));
+        verifyNetworkTestedInvalidFromHttps(1 /* interactions */);
+        verifyProbeStatusChangedPrivateDnsCompleteAndHttpsSucceeded(1 /* interaction */);
+
         // Fix DNS and retry, expect validation to succeed.
-        resetCallbacks();
         mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::1"}, TYPE_AAAA);
 
         wnm.forceReevaluation(Process.myUid());
         // ProbeCompleted should be reset to 0
         HandlerUtils.waitForIdle(wnm.getHandler(), HANDLER_TIMEOUT_MS);
         assertEquals(wnm.getEvaluationState().getProbeCompletedResult(), 0);
-        verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, PROBES_PRIVDNS_VALID);
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS)).notifyProbeStatusChanged(
-                eq(PROBES_PRIVDNS_VALID), eq(PROBES_PRIVDNS_VALID));
+        verifyNetworkTestedValidFromPrivateDns(1 /* interactions */);
+        verifyProbeStatusChangedPrivateDnsCompleteAndSucceeded(1 /* interaction */);
     }
 
     @Test
@@ -2267,57 +2244,40 @@
         WrappedNetworkMonitor wnm = makeCellNotMeteredNetworkMonitor();
         wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
         notifyNetworkConnected(wnm, CELL_NOT_METERED_CAPABILITIES);
-        verifyNetworkTested(VALIDATION_RESULT_INVALID,
-                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS);
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyProbeStatusChanged(
-                eq(PROBES_PRIVDNS_VALID), eq(NETWORK_VALIDATION_PROBE_DNS
-                | NETWORK_VALIDATION_PROBE_HTTPS));
+        verifyNetworkTestedInvalidFromHttps(1 /* interactions */);
+        verifyProbeStatusChangedPrivateDnsCompleteAndHttpsSucceeded(1 /* interactions */);
 
         // Fix DNS and retry, expect validation to succeed.
-        resetCallbacks();
         mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::1"}, TYPE_AAAA);
 
         wnm.forceReevaluation(Process.myUid());
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce())
-                .notifyNetworkTestedWithExtras(matchNetworkTestResultParcelable(
-                        NETWORK_VALIDATION_RESULT_VALID, PROBES_PRIVDNS_VALID));
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyProbeStatusChanged(
-                eq(PROBES_PRIVDNS_VALID), eq(PROBES_PRIVDNS_VALID));
+        verifyNetworkTestedValidFromPrivateDns(1 /* interactions */);
+        verifyProbeStatusChangedPrivateDnsCompleteAndSucceeded(1 /* interaction */);
 
         // Change configuration to an invalid DNS name, expect validation to fail.
-        resetCallbacks();
         mFakeDns.setAnswer("dns.bad", new String[0], TYPE_A);
         mFakeDns.setAnswer("dns.bad", new String[0], TYPE_AAAA);
         wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.bad", new InetAddress[0]));
         // Strict mode hostname resolve fail. Expect only notification for evaluation fail. No probe
         // notification.
-        verifyNetworkTested(VALIDATION_RESULT_INVALID,
-                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS);
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyProbeStatusChanged(
-                eq(PROBES_PRIVDNS_VALID), eq(NETWORK_VALIDATION_PROBE_DNS
-                | NETWORK_VALIDATION_PROBE_HTTPS));
+        verifyNetworkTestedInvalidFromHttps(2 /* interactions */);
+        verifyProbeStatusChangedPrivateDnsCompleteAndHttpsSucceeded(2 /* interaction */);
 
         // Change configuration back to working again, but make private DNS not work.
         // Expect validation to fail.
-        resetCallbacks();
         mFakeDns.setNonBypassPrivateDnsWorking(false);
         wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google",
                 new InetAddress[0]));
-        verifyNetworkTested(VALIDATION_RESULT_INVALID,
-                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS);
+        verifyNetworkTestedInvalidFromHttps(3 /* interactions */);
         // NetworkMonitor will check if the probes has changed or not, if the probes has not
-        // changed, the callback won't be fired.
-        verify(mCallbacks, never()).notifyProbeStatusChanged(
-                eq(PROBES_PRIVDNS_VALID), eq(NETWORK_VALIDATION_PROBE_DNS
-                | NETWORK_VALIDATION_PROBE_HTTPS));
+        // changed, the callback won't be fired. No further interaction.
+        verifyProbeStatusChangedPrivateDnsCompleteAndHttpsSucceeded(2 /* interaction */);
 
         // Make private DNS work again. Expect validation to succeed.
-        resetCallbacks();
         mFakeDns.setNonBypassPrivateDnsWorking(true);
         wnm.forceReevaluation(Process.myUid());
-        verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, PROBES_PRIVDNS_VALID);
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyProbeStatusChanged(
-                eq(PROBES_PRIVDNS_VALID), eq(PROBES_PRIVDNS_VALID));
+        verifyNetworkTestedValidFromPrivateDns(1 /* interactions */);
+        verifyProbeStatusChangedPrivateDnsCompleteAndSucceeded(1 /* interaction */);
     }
 
     @Test
@@ -2423,8 +2383,7 @@
             fail("Undefined transport type");
         }
         notifyNetworkConnected(nm, nc);
-        verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID,
-                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS);
+        verifyNetworkTestedValidFromHttps(1 /* interactions */);
         nm.setLastProbeTime(SystemClock.elapsedRealtime() - STALL_EXPECTED_LAST_PROBE_TIME_MS);
         return nm;
     }
@@ -2633,11 +2592,11 @@
                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP,
                 null /* redirectUrl */);
 
-        resetCallbacks();
         nm.setAcceptPartialConnectivity();
         // Expect to update evaluation result notifications to CS.
         verifyNetworkTested(NETWORK_VALIDATION_RESULT_PARTIAL | NETWORK_VALIDATION_RESULT_VALID,
-                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP);
+                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP,
+                1 /* interactions */);
     }
 
     @Test
@@ -2710,7 +2669,6 @@
         final NetworkMonitor nm = runValidatedNetworkTest();
         // Verify forceReevaluation will not reset the validation result but only probe result until
         // getting the validation result.
-        resetCallbacks();
         setSslException(mHttpsConnection);
         setStatus(mHttpConnection, 500);
         setStatus(mFallbackConnection, 204);
@@ -2718,7 +2676,7 @@
         // Expect to send HTTP, HTTPs, FALLBACK and evaluation results.
         verifyNetworkTested(VALIDATION_RESULT_INVALID,
                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_FALLBACK,
-                null /* redirectUrl */);
+                1 /* interactions */);
         HandlerUtils.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS);
     }
 
@@ -2726,7 +2684,7 @@
     public void testNotifyNetwork_NotifyNetworkTestedOldInterfaceVersion() throws Exception {
         // Use old interface version so notifyNetworkTested is used over
         // notifyNetworkTestedWithExtras
-        resetCallbacks(4);
+        initCallbacks(4);
 
         // Trigger Network validation
         setStatus(mHttpsConnection, 204);
@@ -2813,8 +2771,6 @@
         setValidProbes();
         final NetworkMonitor nm = runValidatedNetworkTest();
 
-        resetCallbacks();
-
         nm.reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP,
                 CaptivePortalProbeResult.success(1 << PROBE_HTTP));
         // Verify result should be appended and notifyNetworkTestedWithExtras callback is triggered
@@ -2842,20 +2798,22 @@
         nm.getEvaluationState().reportEvaluationResult(NETWORK_VALIDATION_RESULT_VALID,
                 null /* redirectUrl */);
         verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID,
-                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP);
+                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP,
+                1 /* interactions */);
 
         nm.getEvaluationState().reportEvaluationResult(
                 NETWORK_VALIDATION_RESULT_VALID | NETWORK_VALIDATION_RESULT_PARTIAL,
                 null /* redirectUrl */);
         verifyNetworkTested(
                 NETWORK_VALIDATION_RESULT_VALID | NETWORK_VALIDATION_RESULT_PARTIAL,
-                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP);
+                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP,
+                1 /* interactions */);
 
         nm.getEvaluationState().reportEvaluationResult(VALIDATION_RESULT_INVALID,
                 TEST_REDIRECT_URL);
         verifyNetworkTested(VALIDATION_RESULT_INVALID,
                 NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP,
-                TEST_REDIRECT_URL);
+                TEST_REDIRECT_URL, 1 /* interactions */);
     }
 
     @Test
@@ -2997,14 +2955,13 @@
         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
                 .showProvisioningNotification(any(), any());
         assertCaptivePortalAppReceiverRegistered(true /* isPortal */);
-        verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, TEST_LOGIN_URL);
+        verifyNetworkTestedPortal(TEST_LOGIN_URL, 1 /* interactions */);
 
         // Force reevaluation and confirm that the network is still captive
         HandlerUtils.waitForIdle(monitor.getHandler(), HANDLER_TIMEOUT_MS);
-        resetCallbacks();
         monitor.forceReevaluation(Process.myUid());
         assertEquals(monitor.getEvaluationState().getProbeCompletedResult(), 0);
-        verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, TEST_LOGIN_URL);
+        verifyNetworkTestedPortal(TEST_LOGIN_URL, 2 /* interactions */);
 
         // Check that startCaptivePortalApp sends the expected intent.
         monitor.launchCaptivePortalApp();
@@ -3197,21 +3154,59 @@
             int testResult, int probesSucceeded, String redirectUrl) throws Exception {
         final NetworkMonitor monitor = makeMonitor(nc);
         notifyNetworkConnected(monitor, config, lp, nc);
-        verifyNetworkTested(testResult, probesSucceeded, redirectUrl);
+        verifyNetworkTested(testResult, probesSucceeded, redirectUrl, 1 /* interactions */);
         HandlerUtils.waitForIdle(monitor.getHandler(), HANDLER_TIMEOUT_MS);
 
         return monitor;
     }
 
-    private void verifyNetworkTested(int testResult, int probesSucceeded) throws Exception {
-        verifyNetworkTested(testResult, probesSucceeded, null /* redirectUrl */);
+    private void verifyProbeStatusChangedPrivateDnsCompleteAndSucceeded(int interactions)
+            throws Exception {
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(interactions))
+                .notifyProbeStatusChanged(eq(PROBES_PRIVDNS_VALID), eq(PROBES_PRIVDNS_VALID));
     }
 
-    private void verifyNetworkTested(int testResult, int probesSucceeded, String redirectUrl)
-            throws RemoteException {
+    private void verifyProbeStatusChangedPrivateDnsCompleteAndHttpsSucceeded(int interactions)
+            throws Exception {
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(interactions))
+                .notifyProbeStatusChanged(
+                        eq(PROBES_PRIVDNS_VALID),
+                        eq(NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS));
+    }
+
+    private void verifyNetworkTestedInvalidFromHttps(int interactions) throws Exception {
+        verifyNetworkTested(VALIDATION_RESULT_INVALID,
+                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS,
+                interactions);
+    }
+
+    private void verifyNetworkTestedPortal(String redirectUrl, int interactions) throws Exception {
+        verifyNetworkTested(VALIDATION_RESULT_PORTAL, 0 /* probesSucceeded */, redirectUrl,
+                interactions);
+    }
+
+    private void verifyNetworkTestedValidFromHttps(int interactions) throws Exception {
+        verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID,
+                NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS,
+                interactions);
+    }
+
+    private void verifyNetworkTestedValidFromPrivateDns(int interactions) throws Exception {
+        verifyNetworkTested(NETWORK_VALIDATION_RESULT_VALID, PROBES_PRIVDNS_VALID, interactions);
+    }
+
+    private void verifyNetworkTested(int testResult, int probesSucceeded, int interactions)
+            throws Exception {
+        verifyNetworkTested(testResult, probesSucceeded, null /* redirectUrl */, interactions);
+    }
+
+    private void verifyNetworkTested(int testResult, int probesSucceeded, String redirectUrl,
+            int interactions) throws RemoteException {
         try {
-            verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS)).notifyNetworkTestedWithExtras(
-                    matchNetworkTestResultParcelable(testResult, probesSucceeded, redirectUrl));
+            verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(interactions))
+                    .notifyNetworkTestedWithExtras(
+                            matchNetworkTestResultParcelable(
+                                    testResult, probesSucceeded, redirectUrl));
         } catch (AssertionFailedError e) {
             // Capture the callbacks up to now to give a better error message
             final ArgumentCaptor<NetworkTestResultParcelable> captor =
@@ -3221,14 +3216,15 @@
             // call which failed, but this time use a captor to log the exact parcel sent by
             // NetworkMonitor.
             // This assertion will fail if notifyNetworkTested was not called at all.
-            verify(mCallbacks).notifyNetworkTestedWithExtras(captor.capture());
+            verify(mCallbacks, times(interactions)).notifyNetworkTestedWithExtras(captor.capture());
 
-            final NetworkTestResultParcelable lastResult = captor.getValue();
-            fail(String.format("notifyNetworkTestedWithExtras was not called with the "
+            final List<NetworkTestResultParcelable> results = captor.getAllValues();
+            final NetworkTestResultParcelable lastResult = results.get(results.size() - 1);
+            fail(String.format("notifyNetworkTestedWithExtras was not called %d times with the "
                     + "expected result within timeout. "
                     + "Expected result %d, probes succeeded %d, redirect URL %s, "
                     + "last result was (%d, %d, %s).",
-                    testResult, probesSucceeded, redirectUrl,
+                    interactions, testResult, probesSucceeded, redirectUrl,
                     lastResult.result, lastResult.probesSucceeded, lastResult.redirectUrl));
         }
     }