Add integration test to verify the incomplete IPv6 neighbors.

aosp/3013680 fixes the ignoring incomplete IPv6 neighbors logic, which
ingored the NUD failure event from unreachable IPv6 neighbor but didn't
guarantee the previous NUD probe result is null. Add integration test
to verify the behavior, i.e. only ignore the IPv6 neighbor which never
enters the reachable state, and we MUST NOT ignore the NUD failure if
the NUD probe result changes from probe to failed.

Bug: 335575053
Test: atest NetworkStackRootTests
Change-Id: I40463de9d165d66e282ae8286ca19f9974979539
diff --git a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
index af2fc41..b09cecf 100644
--- a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
@@ -4293,9 +4293,8 @@
         socket.send(pkt);
     }
 
-    private void runIpReachabilityMonitorAddressResolutionTest(final String dnsServer,
-            final Inet6Address targetIp,
-            final boolean expectNeighborLost) throws Exception {
+    private void prepareIpReachabilityMonitorAddressResolutionTest(final String dnsServer,
+            final Inet6Address targetIp) throws Exception {
         mNetworkAgentThread =
                 new HandlerThread(IpClientIntegrationTestCommon.class.getSimpleName());
         mNetworkAgentThread.start();
@@ -4303,14 +4302,14 @@
         setDhcpFeatures(true /* isRapidCommitEnabled */,
                 false /* isDhcpIpConflictDetectEnabled */);
         final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
-                 // We've found that mCm.shouldAvoidBadWifi() has a flaky behavior in the root test,
-                 // probably due to the sim card in the DUT. it doesn't occur in the siganture test
-                 // since we mock the return value directly. As a result, sometimes
-                 // IpReachabilityMonitor#avoidingBadLinks() returns false, it caused the expected
-                 // onReachabilityFailure callback wasn't triggered on the test. In order to make
-                 // the root test more stable, do not use MultinetworkPolicyTracker only for IPv6
-                 // neighbor reachability checking relevant test cases, that guarantees
-                 // avoidingBadLinks() always returns true which is expected.
+                // We've found that mCm.shouldAvoidBadWifi() has a flaky behavior in the root test,
+                // probably due to the sim card in the DUT. it doesn't occur in the siganture test
+                // since we mock the return value directly. As a result, sometimes
+                // IpReachabilityMonitor#avoidingBadLinks() returns false, it caused the expected
+                // onReachabilityFailure callback wasn't triggered on the test. In order to make
+                // the root test more stable, do not use MultinetworkPolicyTracker only for IPv6
+                // neighbor reachability checking relevant test cases, that guarantees
+                // avoidingBadLinks() always returns true which is expected.
                 .withoutMultinetworkPolicyTracker()
                 .build();
         startIpClientProvisioning(config);
@@ -4336,6 +4335,12 @@
         final byte[] data = new byte[100];
         random.nextBytes(data);
         sendUdpPacketToNetwork(mNetworkAgent.getNetwork(), dnsServerIp, 1234 /* port */, data);
+    }
+
+    private void runIpReachabilityMonitorAddressResolutionTest(final String dnsServer,
+            final Inet6Address targetIp,
+            final boolean expectNeighborLost) throws Exception {
+        prepareIpReachabilityMonitorAddressResolutionTest(dnsServer, targetIp);
 
         // Wait for the multicast NSes but never respond to them, that results in the on-link
         // DNS gets lost and onReachabilityLost callback will be invoked.
@@ -4447,6 +4452,65 @@
                 true /* expectNeighborLost */);
     }
 
+    private void runIpReachabilityMonitorIncompleteIpv6NeighborTest(final String dnsServer,
+            final Inet6Address targetIp) throws Exception {
+        prepareIpReachabilityMonitorAddressResolutionTest(dnsServer, targetIp);
+
+        // Simulate the default router was reachable by responding to multicast NS(not for DAD).
+        NeighborSolicitation ns;
+        while ((ns = getNextNeighborSolicitation()) != null) {
+            if (ns.ipv6Hdr.dstIp.isMulticastAddress() // Solicited-node multicast address
+                    && ns.nsHdr.target.equals(targetIp)) {
+                final ByteBuffer na = NeighborAdvertisement.build(ROUTER_MAC /* srcMac */,
+                        ns.ethHdr.srcMac /* dstMac */, ROUTER_LINK_LOCAL /* srcIp */,
+                        ns.ipv6Hdr.srcIp /* dstIp */,
+                        NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER | NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED,
+                        targetIp);
+                mPacketReader.sendResponse(na);
+                break;
+            }
+        }
+
+        // Trigger the NUD probe manually by sending CMD_CONFIRM command, this will force to start
+        // probing for all neighbors in the watchlist including default router and on-link DNS.
+        mIIpClient.confirmConfiguration();
+
+        // Wait for the next unicast NS probes, but don't respond to them, which should trigger
+        // reachability failure callback because the probe status is from probed to failed, rather
+        // than incomplete to failed.
+        while ((ns = getNextNeighborSolicitation()) != null) {
+            // Respond to NS for default router, it's used to avoid triggering multiple
+            // onReachabilityFailure callbacks.
+            if (!targetIp.equals(ROUTER_LINK_LOCAL)) {
+                final ByteBuffer na = NeighborAdvertisement.build(ROUTER_MAC /* srcMac */,
+                        ns.ethHdr.srcMac /* dstMac */, ROUTER_LINK_LOCAL /* srcIp */,
+                        ns.ipv6Hdr.srcIp /* dstIp */,
+                        NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER | NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED,
+                        ROUTER_LINK_LOCAL);
+                mPacketReader.sendResponse(na);
+            }
+        }
+        assertNotifyNeighborLost(targetIp, NudEventType.NUD_CONFIRM_FAILED_CRITICAL);
+    }
+
+    @Test
+    @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION, enabled = false)
+    @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION, enabled = true)
+    @Flag(name = IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, enabled = false)
+    public void testIpReachabilityMonitor_ignoreIpv6DefaultRouter_everReachable() throws Exception {
+        runIpReachabilityMonitorIncompleteIpv6NeighborTest(IPV6_OFF_LINK_DNS_SERVER,
+                ROUTER_LINK_LOCAL /* targetIp */);
+    }
+
+    @Test
+    @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION, enabled = true)
+    @Flag(name = IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION, enabled = false)
+    @Flag(name = IP_REACHABILITY_IGNORE_ORGANIC_NUD_FAILURE_VERSION, enabled = false)
+    public void testIpReachabilityMonitor_ignoreIpv6Dns_everReachable() throws Exception {
+        runIpReachabilityMonitorIncompleteIpv6NeighborTest(IPV6_ON_LINK_DNS_SERVER,
+                ipv6Addr(IPV6_ON_LINK_DNS_SERVER) /* targetIp */);
+    }
+
     @Test
     public void testIPv6LinkLocalOnly() throws Exception {
         ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()