Snap for 9679998 from 0bd4a252cd038d9c0e4adc4fd2f4785543cb76ff to sdk-release

Change-Id: Ifdc48dc9e8956a6a7b67cec0ea7b4adc6659d16a
diff --git a/apishim/30/com/android/networkstack/apishim/api30/ConstantsShim.java b/apishim/30/com/android/networkstack/apishim/api30/ConstantsShim.java
index 19ff9d3..b80cc32 100644
--- a/apishim/30/com/android/networkstack/apishim/api30/ConstantsShim.java
+++ b/apishim/30/com/android/networkstack/apishim/api30/ConstantsShim.java
@@ -46,4 +46,8 @@
     public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28;
     public static final int NET_CAPABILITY_ENTERPRISE = 29;
     public static final int TRANSPORT_TEST = 7;
+
+    // Constants defined in android.content.pm.PackageManager
+    public static final String PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES =
+            "android.net.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES";
 }
diff --git a/apishim/34/com/android/networkstack/apishim/ConstantsShim.java b/apishim/34/com/android/networkstack/apishim/ConstantsShim.java
index 9df84d5..c946544 100644
--- a/apishim/34/com/android/networkstack/apishim/ConstantsShim.java
+++ b/apishim/34/com/android/networkstack/apishim/ConstantsShim.java
@@ -16,6 +16,7 @@
 
 package com.android.networkstack.apishim;
 
+import android.content.pm.PackageManager;
 import android.os.Build;
 
 import androidx.annotation.RequiresApi;
@@ -35,4 +36,8 @@
      */
     @VisibleForTesting
     public static final int VERSION = 34;
+
+    // Constants defined in android.content.pm.PackageManager
+    public static final String PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES =
+            PackageManager.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES;
 }
diff --git a/src/android/net/ip/IpReachabilityMonitor.java b/src/android/net/ip/IpReachabilityMonitor.java
index d16840d..b7cb9ce 100644
--- a/src/android/net/ip/IpReachabilityMonitor.java
+++ b/src/android/net/ip/IpReachabilityMonitor.java
@@ -22,6 +22,8 @@
 import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST_ORGANIC;
 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 
+import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION;
+import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION;
 import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION;
 
 import android.content.Context;
@@ -233,6 +235,8 @@
     @NonNull
     private final Callback mCallback;
     private final boolean mMulticastResolicitEnabled;
+    private final boolean mIgnoreIncompleteIpv6DnsServerEnabled;
+    private final boolean mIgnoreIncompleteIpv6DefaultRouterEnabled;
 
     public IpReachabilityMonitor(
             Context context, InterfaceParams ifParams, Handler h, SharedLog log, Callback callback,
@@ -256,6 +260,12 @@
         mDependencies = dependencies;
         mMulticastResolicitEnabled = dependencies.isFeatureEnabled(context,
                 IP_REACHABILITY_MCAST_RESOLICIT_VERSION, false /* defaultEnabled */);
+        mIgnoreIncompleteIpv6DnsServerEnabled = dependencies.isFeatureEnabled(context,
+                IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION,
+                false /* defaultEnabled */);
+        mIgnoreIncompleteIpv6DefaultRouterEnabled = dependencies.isFeatureEnabled(context,
+                IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION,
+                false /* defaultEnabled */);
         mMetricsLog = metricsLog;
         mNetd = netd;
         Preconditions.checkNotNull(mNetd);
@@ -286,7 +296,7 @@
                         // After both unicast probe and multicast probe(if mcast_resolicit is not 0)
                         // attempts fail, trigger the neighbor lost event and disconnect.
                         mLog.w("ALERT neighbor went from: " + prev + " to: " + event);
-                        handleNeighborLost(event);
+                        handleNeighborLost(prev, event);
                     } else if (event.nudState == StructNdMsg.NUD_REACHABLE) {
                         handleNeighborReachable(prev, event);
                     }
@@ -423,7 +433,22 @@
         maybeRestoreNeighborParameters();
     }
 
-    private void handleNeighborLost(NeighborEvent event) {
+    private boolean shouldIgnoreIncompleteIpv6Neighbor(@Nullable final NeighborEvent prev,
+            @NonNull final NeighborEvent event) {
+        // If it isn't IPv6 neighbor, return false.
+        if (!(event.ip instanceof Inet6Address)) return false;
+
+        // If neighbor isn't in the watch list, return false.
+        if (!mNeighborWatchList.containsKey(event.ip)) return false;
+
+        // For on-link IPv6 DNS server or default router that never ever responds to address
+        // resolution, kernel will send RTM_NEWNEIGH with NUD_FAILED to user space directly,
+        // and there is no netlink neighbor events related to this neighbor received before.
+        return (prev == null || event.nudState == StructNdMsg.NUD_FAILED);
+    }
+
+    private void handleNeighborLost(@Nullable final NeighborEvent prev,
+            @NonNull final NeighborEvent event) {
         final LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
 
         InetAddress ip = null;
@@ -453,9 +478,39 @@
             }
         }
 
+        final boolean ignoreIncompleteIpv6DnsServer =
+                mIgnoreIncompleteIpv6DnsServerEnabled
+                        && isNeighborDnsServer(event)
+                        && shouldIgnoreIncompleteIpv6Neighbor(prev, event);
+
+        // Generally Router Advertisement should take SLLA option, then device won't do address
+        // resolution for default router's IPv6 link-local address automatically. But sometimes
+        // it may miss SLLA option, also add a flag to check these cases.
+        final boolean ignoreIncompleteIpv6DefaultRouter =
+                mIgnoreIncompleteIpv6DefaultRouterEnabled
+                        && isNeighborDefaultRouter(event)
+                        && shouldIgnoreIncompleteIpv6Neighbor(prev, event);
+
+        // Only ignore the incomplete IPv6 neighbor iff IPv4 is still provisioned. For IPv6-only
+        // networks, we MUST not ignore any incomplete IPv6 neighbor.
+        final boolean ignoreIncompleteIpv6Neighbor =
+                (ignoreIncompleteIpv6DnsServer || ignoreIncompleteIpv6DefaultRouter)
+                        && whatIfLp.isIpv4Provisioned();
+
+        // It's better to remove the incompleted on-link IPv6 DNS server or default router from
+        // watch list, otherwise, when wifi invokes probeAll later (e.g. post roam) to send probe
+        // to an incompleted on-link DNS server or default router, it should fail to send netlink
+        // message to kernel as there is no neighbor cache entry for it at all.
+        if (ignoreIncompleteIpv6Neighbor) {
+            Log.d(TAG, "remove incomplete IPv6 neighbor " + event.ip
+                    + " which fails to respond to address resolution from watch list.");
+            mNeighborWatchList.remove(event.ip);
+        }
+
         final boolean lostProvisioning =
                 (mLinkProperties.isIpv4Provisioned() && !whatIfLp.isIpv4Provisioned())
-                || (mLinkProperties.isIpv6Provisioned() && !whatIfLp.isIpv6Provisioned());
+                        || (mLinkProperties.isIpv6Provisioned() && !whatIfLp.isIpv6Provisioned()
+                                && !ignoreIncompleteIpv6Neighbor);
         final NudEventType type = getNudFailureEventType(isFromProbe(),
                 isNudFailureDueToRoam(), lostProvisioning);
 
diff --git a/src/com/android/networkstack/netlink/TcpSocketTracker.java b/src/com/android/networkstack/netlink/TcpSocketTracker.java
index a3a58e6..60ebf78 100644
--- a/src/com/android/networkstack/netlink/TcpSocketTracker.java
+++ b/src/com/android/networkstack/netlink/TcpSocketTracker.java
@@ -240,7 +240,7 @@
 
     // Return true if there are more pending messages to read
     private boolean parseMessage(ByteBuffer bytes, int family, TcpStat stat, long time) {
-        if (!enoughBytesRemainForValidNlMsg(bytes)) {
+        if (!NetlinkUtils.enoughBytesRemainForValidNlMsg(bytes)) {
             // This is unlikely to happen in real cases. Check this first for testing.
             Log.e(TAG, "Size is less than header size. Ignored.");
             return false;
@@ -283,7 +283,7 @@
                             calculateLatestPacketsStat(info, mSocketInfos.get(cookie)));
                     mSocketInfos.put(cookie, info);
                 }
-            } while (enoughBytesRemainForValidNlMsg(bytes));
+            } while (NetlinkUtils.enoughBytesRemainForValidNlMsg(bytes));
         } catch (IllegalArgumentException | BufferUnderflowException e) {
             Log.wtf(TAG, "Unexpected socket info parsing, family " + family
                     + " buffer:" + bytes + " "
@@ -435,12 +435,6 @@
         return mLatestReceivedCount;
     }
 
-    /** Check if the length and position of the given ByteBuffer is valid for a nlmsghdr message. */
-    @VisibleForTesting
-    static boolean enoughBytesRemainForValidNlMsg(@NonNull final ByteBuffer bytes) {
-        return bytes.remaining() >= StructNlMsgHdr.STRUCT_SIZE;
-    }
-
     private static boolean isValidInetDiagMsgSize(final int nlMsgLen) {
         return nlMsgLen >= SOCKDIAG_MSG_HEADER_SIZE;
     }
diff --git a/src/com/android/networkstack/util/NetworkStackUtils.java b/src/com/android/networkstack/util/NetworkStackUtils.java
index 5b329dd..0ae9589 100755
--- a/src/com/android/networkstack/util/NetworkStackUtils.java
+++ b/src/com/android/networkstack/util/NetworkStackUtils.java
@@ -255,6 +255,20 @@
     public static final String IP_REACHABILITY_MCAST_RESOLICIT_VERSION =
             "ip_reachability_mcast_resolicit_version";
 
+    /**
+     * Experiment flag to attempt to ignore the on-link IPv6 DNS server which fails to respond to
+     * address resolution.
+     */
+    public static final String IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION =
+            "ip_reachability_ignore_incompleted_ipv6_dns_server_version";
+
+    /**
+     * Experiment flag to attempt to ignore the IPv6 default router which fails to respond to
+     * address resolution.
+     */
+    public static final String IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION =
+            "ip_reachability_ignore_incompleted_ipv6_default_router_version";
+
     static {
         System.loadLibrary("networkstackutilsjni");
     }
diff --git a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
index 3cb651e..e36af57 100644
--- a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
@@ -36,7 +37,6 @@
 import static android.net.dhcp.DhcpResultsParcelableUtil.fromStableParcelable;
 import static android.net.ip.IpClientLinkObserver.CLAT_PREFIX;
 import static android.net.ip.IpClientLinkObserver.CONFIG_SOCKET_RECV_BUFSIZE;
-import static android.net.ip.IpReachabilityMonitor.MIN_NUD_SOLICIT_NUM;
 import static android.net.ip.IpReachabilityMonitor.NUD_MCAST_RESOLICIT_NUM;
 import static android.net.ip.IpReachabilityMonitor.nudEventTypeToInt;
 import static android.net.ipmemorystore.Status.SUCCESS;
@@ -121,6 +121,7 @@
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.MacAddress;
+import android.net.Network;
 import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
@@ -223,6 +224,8 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import java.lang.reflect.Method;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
@@ -384,6 +387,8 @@
     private static final String IPV4_TEST_SUBNET_PREFIX = "192.168.1.0/24";
     private static final String IPV4_ANY_ADDRESS_PREFIX = "0.0.0.0/0";
     private static final String HOSTNAME = "testhostname";
+    private static final String IPV6_OFF_LINK_DNS_SERVER = "2001:4860:4860::64";
+    private static final String IPV6_ON_LINK_DNS_SERVER = "2001:db8:1::64";
     private static final int TEST_DEFAULT_MTU = 1500;
     private static final int TEST_MIN_MTU = 1280;
     private static final MacAddress ROUTER_MAC = MacAddress.fromString("00:1A:11:22:33:44");
@@ -571,6 +576,10 @@
 
     protected abstract void assertIpMemoryNeverStoreNetworkAttributes(String l2Key, long timeout);
 
+    protected abstract int readNudSolicitNumInSteadyStateFromResource();
+
+    protected abstract int readNudSolicitNumPostRoamingFromResource();
+
     protected final boolean testSkipped() {
         if (!useNetworkStackSignature() && !TestNetworkStackServiceClient.isSupported()) {
             fail("Device running root tests doesn't support TestNetworkStackServiceClient.");
@@ -678,6 +687,7 @@
         }
         if (mNetworkAgentThread != null) {
             mNetworkAgentThread.quitSafely();
+            mNetworkAgentThread.join();
         }
         teardownTapInterface();
         mIIpClient.shutdown();
@@ -719,13 +729,14 @@
         return iface;
     }
 
-    private void teardownTapInterface() {
+    private void teardownTapInterface() throws Exception {
         if (mPacketReader != null) {
             mHandler.post(() -> mPacketReader.stop());  // Also closes the socket
             mTapFd = null;
         }
         if (mPacketReaderThread != null) {
             mPacketReaderThread.quitSafely();
+            mPacketReaderThread.join();
         }
     }
 
@@ -1585,6 +1596,7 @@
                         .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
                         .addCapability(NET_CAPABILITY_NOT_ROAMING)
                         .addCapability(NET_CAPABILITY_NOT_VPN)
+                        .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
                         .addTransportType(TRANSPORT_TEST)
                         .setNetworkSpecifier(testNetworkSpecifier)
                         .build(),
@@ -1818,9 +1830,8 @@
 
     private void sendRouterAdvertisement(boolean waitForRs, short lifetime, int valid,
             int preferred) throws Exception {
-        final String dnsServer = "2001:4860:4860::64";
         final ByteBuffer pio = buildPioOption(valid, preferred, "2001:db8:1::/64");
-        final ByteBuffer rdnss = buildRdnssOption(3600, dnsServer);
+        final ByteBuffer rdnss = buildRdnssOption(3600, IPV6_OFF_LINK_DNS_SERVER);
         sendRouterAdvertisement(waitForRs, lifetime, pio, rdnss);
     }
 
@@ -1908,9 +1919,8 @@
 
     private LinkProperties doIpv6OnlyProvisioning() throws Exception {
         final InOrder inOrder = inOrder(mCb);
-        final String dnsServer = "2001:4860:4860::64";
         final ByteBuffer pio = buildPioOption(3600, 1800, "2001:db8:1::/64");
-        final ByteBuffer rdnss = buildRdnssOption(3600, dnsServer);
+        final ByteBuffer rdnss = buildRdnssOption(3600, IPV6_OFF_LINK_DNS_SERVER);
         final ByteBuffer slla = buildSllaOption();
         final ByteBuffer ra = buildRaPacket(pio, rdnss, slla);
 
@@ -2022,12 +2032,11 @@
                 .build();
         startIpClientProvisioning(config);
 
-        final String dnsServer = "2001:4860:4860::64";
         final IpPrefix prefix = new IpPrefix("64:ff9b::/96");
         final IpPrefix otherPrefix = new IpPrefix("2001:db8:64::/96");
 
         final ByteBuffer pio = buildPioOption(600, 300, "2001:db8:1::/64");
-        ByteBuffer rdnss = buildRdnssOption(600, dnsServer);
+        ByteBuffer rdnss = buildRdnssOption(600, IPV6_OFF_LINK_DNS_SERVER);
         ByteBuffer pref64 = new StructNdOptPref64(prefix, 600).toByteBuffer();
         ByteBuffer ra = buildRaPacket(pio, rdnss, pref64);
 
@@ -2827,15 +2836,22 @@
                 true /* shouldReplyNakOnRoam */);
     }
 
-    private void performDualStackProvisioning() throws Exception {
-        final InOrder inOrder = inOrder(mCb);
-        final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>();
-        final String dnsServer = "2001:4860:4860::64";
+    private LinkProperties performDualStackProvisioning() throws Exception {
+        final Inet6Address dnsServer =
+                (Inet6Address) InetAddresses.parseNumericAddress(IPV6_OFF_LINK_DNS_SERVER);
         final ByteBuffer pio = buildPioOption(3600, 1800, "2001:db8:1::/64");
-        final ByteBuffer rdnss = buildRdnssOption(3600, dnsServer);
+        final ByteBuffer rdnss = buildRdnssOption(3600, IPV6_OFF_LINK_DNS_SERVER);
         final ByteBuffer slla = buildSllaOption();
         final ByteBuffer ra = buildRaPacket(pio, rdnss, slla);
 
+        return performDualStackProvisioning(ra, dnsServer);
+    }
+
+    private LinkProperties performDualStackProvisioning(final ByteBuffer ra,
+            final InetAddress dnsServer) throws Exception {
+        final InOrder inOrder = inOrder(mCb);
+        final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>();
+
         doIpv6OnlyProvisioning(inOrder, ra);
 
         // Start IPv4 provisioning and wait until entire provisioning completes.
@@ -2849,10 +2865,10 @@
 
         final LinkProperties lp = lpFuture.get(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
         assertNotNull(lp);
-        assertTrue(lp.getDnsServers().contains(InetAddress.getByName(dnsServer)));
+        assertTrue(lp.getDnsServers().contains(dnsServer));
         assertTrue(lp.getDnsServers().contains(SERVER_ADDR));
 
-        clearInvocations(mCb);
+        return lp;
     }
 
     private void doDualStackProvisioning(boolean shouldDisableAcceptRa) throws Exception {
@@ -3166,6 +3182,7 @@
     }
 
     private void shutdownAndRecreateIpClient() throws Exception {
+        clearInvocations(mCb);
         mIpc.shutdown();
         awaitIpClientShutdown();
         mIpc = makeIpClient();
@@ -3792,7 +3809,8 @@
         prepareIpReachabilityMonitorTest();
 
         final List<NeighborSolicitation> nsList = waitForMultipleNeighborSolicitations();
-        assertEquals(MIN_NUD_SOLICIT_NUM, nsList.size());
+        final int expectedNudSolicitNum = readNudSolicitNumPostRoamingFromResource();
+        assertEquals(expectedNudSolicitNum, nsList.size());
         for (NeighborSolicitation ns : nsList) {
             assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */,
                     ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
@@ -3835,13 +3853,14 @@
         prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */);
 
         final List<NeighborSolicitation> nsList = waitForMultipleNeighborSolicitations();
-        int expectedSize = MIN_NUD_SOLICIT_NUM + NUD_MCAST_RESOLICIT_NUM;
-        assertEquals(expectedSize, nsList.size()); // 5 unicast NSes + 3 multicast NSes
-        for (NeighborSolicitation ns : nsList.subList(0, MIN_NUD_SOLICIT_NUM)) {
+        final int expectedNudSolicitNum = readNudSolicitNumPostRoamingFromResource();
+        int expectedSize = expectedNudSolicitNum + NUD_MCAST_RESOLICIT_NUM;
+        assertEquals(expectedSize, nsList.size());
+        for (NeighborSolicitation ns : nsList.subList(0, expectedNudSolicitNum)) {
             assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */,
                     ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
         }
-        for (NeighborSolicitation ns : nsList.subList(MIN_NUD_SOLICIT_NUM, nsList.size())) {
+        for (NeighborSolicitation ns : nsList.subList(expectedNudSolicitNum, nsList.size())) {
             assertMulticastNeighborSolicitation(ns, ROUTER_LINK_LOCAL /* targetIp */);
         }
     }
@@ -3904,6 +3923,132 @@
                 NudEventType.NUD_POST_ROAMING_MAC_ADDRESS_CHANGED);
     }
 
+    private void sendUdpPacketToNetwork(final Network network, final Inet6Address remoteIp,
+            int port, final byte[] data) throws Exception {
+        final DatagramSocket socket = new DatagramSocket(0, (InetAddress) Inet6Address.ANY);
+        final DatagramPacket pkt = new DatagramPacket(data, data.length, remoteIp, port);
+        network.bindSocket(socket);
+        socket.send(pkt);
+    }
+
+    private void runIpReachabilityMonitorAddressResolutionTest(final String dnsServer,
+            final Inet6Address targetIp,
+            final boolean isIgnoreIncompleteIpv6DnsServerEnabled,
+            final boolean isIgnoreIncompleteIpv6DefaultRouterEnabled,
+            final boolean expectNeighborLost) throws Exception {
+        // This mock is required, otherwise, IpReachabilityMonitor#avoidingBadLinks() will always
+        // return false, that results in the target tested IPv6 off-link DNS server won't be removed
+        // from LP and notifyLost won't be invoked.
+        when(mCm.shouldAvoidBadWifi()).thenReturn(true);
+
+        mNetworkAgentThread =
+                new HandlerThread(IpClientIntegrationTestCommon.class.getSimpleName());
+        mNetworkAgentThread.start();
+
+        setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */,
+                false /* isDhcpIpConflictDetectEnabled */,
+                false /* isIPv6OnlyPreferredEnabled */);
+        setFeatureEnabled(
+                NetworkStackUtils.IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION,
+                isIgnoreIncompleteIpv6DnsServerEnabled);
+        setFeatureEnabled(
+                NetworkStackUtils.IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION,
+                isIgnoreIncompleteIpv6DefaultRouterEnabled);
+        final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+                .build();
+        startIpClientProvisioning(config);
+        verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(false);
+
+        final List<ByteBuffer> options = new ArrayList<ByteBuffer>();
+        options.add(buildPioOption(3600, 1800, "2001:db8:1::/64")); // PIO
+        options.add(buildRdnssOption(3600, dnsServer));             // RDNSS
+        // If target IP of address resolution is default router's IPv6 link-local address,
+        // then we should not take SLLA option in RA.
+        if (!targetIp.equals(ROUTER_LINK_LOCAL)) {
+            options.add(buildSllaOption());                         // SLLA
+        }
+        final ByteBuffer ra = buildRaPacket(options.toArray(new ByteBuffer[options.size()]));
+        final Inet6Address dnsServerIp =
+                (Inet6Address) InetAddresses.parseNumericAddress(dnsServer);
+        final LinkProperties lp = performDualStackProvisioning(ra, dnsServerIp);
+        runAsShell(MANAGE_TEST_NETWORKS, () -> createTestNetworkAgentAndRegister(lp));
+
+        // Send a UDP packet to IPv6 DNS server to trigger address resolution process for IPv6
+        // on-link DNS server or default router(if the target is default router, we should pass
+        // in an IPv6 off-link DNS server such as 2001:db8:4860:4860::64).
+        final Random random = new Random();
+        final byte[] data = new byte[100];
+        random.nextBytes(data);
+        sendUdpPacketToNetwork(mNetworkAgent.getNetwork(), dnsServerIp, 1234 /* port */, data);
+
+        // 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.
+        final List<NeighborSolicitation> nsList = new ArrayList<NeighborSolicitation>();
+        NeighborSolicitation ns;
+        while ((ns = getNextNeighborSolicitation()) != null) {
+            // multicast NS for address resolution, IPv6 dst address in that NS is solicited-node
+            // multicast address based on the target IP, the target IP is either on-link IPv6 DNS
+            // server address or IPv6 link-local address of default gateway.
+            final LinkAddress actual = new LinkAddress(ns.nsHdr.target, 64);
+            final LinkAddress target = new LinkAddress(targetIp, 64);
+            if (actual.equals(target) && ns.ipv6Hdr.dstIp.isMulticastAddress()) {
+                nsList.add(ns);
+            }
+        }
+        assertFalse(nsList.isEmpty());
+
+        if (expectNeighborLost) {
+            assertNotifyNeighborLost(targetIp, NudEventType.NUD_ORGANIC_FAILED_CRITICAL);
+        } else {
+            assertNeverNotifyNeighborLost();
+        }
+    }
+
+    @Test
+    @SignatureRequiredTest(reason = "Need to mock NetworkAgent")
+    public void testIpReachabilityMonitor_incompleteIpv6DnsServerInDualStack() throws Exception {
+        final Inet6Address targetIp =
+                (Inet6Address) InetAddresses.parseNumericAddress(IPV6_ON_LINK_DNS_SERVER);
+        runIpReachabilityMonitorAddressResolutionTest(IPV6_ON_LINK_DNS_SERVER, targetIp,
+                true /* isIgnoreIncompleteIpv6DnsServerEnabled */,
+                false /* isIgnoreIncompleteIpv6DefaultRouterEnabled */,
+                false /* expectNeighborLost */);
+    }
+
+    @Test
+    @SignatureRequiredTest(reason = "Need to mock NetworkAgent")
+    public void testIpReachabilityMonitor_incompleteIpv6DnsServerInDualStack_flagoff()
+            throws Exception {
+        final Inet6Address targetIp =
+                (Inet6Address) InetAddresses.parseNumericAddress(IPV6_ON_LINK_DNS_SERVER);
+        runIpReachabilityMonitorAddressResolutionTest(IPV6_ON_LINK_DNS_SERVER, targetIp,
+                false /* isIgnoreIncompleteIpv6DnsServerEnabled */,
+                false /* isIgnoreIncompleteIpv6DefaultRouterEnabled */,
+                true /* expectNeighborLost */);
+    }
+
+    @Test
+    @SignatureRequiredTest(reason = "Need to mock the NetworkAgent")
+    public void testIpReachabilityMonitor_incompleteIpv6DefaultRouterInDualStack()
+            throws Exception {
+        runIpReachabilityMonitorAddressResolutionTest(IPV6_OFF_LINK_DNS_SERVER,
+                ROUTER_LINK_LOCAL /* targetIp */,
+                false /* isIgnoreIncompleteIpv6DnsServerEnabled */,
+                true /* isIgnoreIncompleteIpv6DefaultRouterEnabled */,
+                false /* expectNeighborLost */);
+    }
+
+    @Test
+    @SignatureRequiredTest(reason = "Need to mock the NetworkAgent")
+    public void testIpReachabilityMonitor_incompleteIpv6DefaultRouterInDualStack_flagoff()
+            throws Exception {
+        runIpReachabilityMonitorAddressResolutionTest(IPV6_OFF_LINK_DNS_SERVER,
+                ROUTER_LINK_LOCAL /* targetIp */,
+                false /* isIgnoreIncompleteIpv6DnsServerEnabled */,
+                false /* isIgnoreIncompleteIpv6DefaultRouterEnabled */,
+                true /* expectNeighborLost */);
+    }
+
     @Test
     public void testIPv6LinkLocalOnly() throws Exception {
         ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
@@ -3920,12 +4065,8 @@
         assertEquals(0, lp.getDnsServers().size());
         final List<LinkAddress> addresses = lp.getLinkAddresses();
         assertEquals(1, addresses.size());
-        assertTrue(addresses.get(0).getAddress().isLinkLocalAddress());
-        assertEquals(1, lp.getRoutes().size());
-        final RouteInfo route = lp.getRoutes().get(0);
-        assertNotNull(route);
-        assertTrue(route.getDestination().equals(new IpPrefix("fe80::/64")));
-        assertTrue(route.getGateway().isAnyLocalAddress());
+        assertTrue(addresses.get(0).getAddress().isLinkLocalAddress()); // only IPv6 link-local
+        assertTrue(hasRouteTo(lp, IPV6_LINK_LOCAL_PREFIX)); // fe80::/64 -> :: iface mtu 0
 
         // Check that if an RA is received, no IP addresses, routes, or DNS servers are configured.
         // Instead of waiting some period of time for the RA to be received and checking the
@@ -4077,16 +4218,15 @@
         });
 
         // Send large amount of RAs to overflow the netlink socket receive buffer.
-        for (int i = 0; i < 100; i++) {
+        for (int i = 0; i < 200; i++) {
             sendBasicRouterAdvertisement(false /* waitRs */);
         }
 
         // Send another RA with a different IPv6 global prefix. This PIO option should be dropped
         // due to the ENOBUFS happens, it means IpClient shouldn't see the new IPv6 global prefix.
-        final String dnsServer = "2001:4860:4860::64";
         final String prefix = "2001:db8:dead:beef::/64";
         final ByteBuffer pio = buildPioOption(3600, 1800, prefix);
-        ByteBuffer rdnss = buildRdnssOption(3600, dnsServer);
+        ByteBuffer rdnss = buildRdnssOption(3600, IPV6_OFF_LINK_DNS_SERVER);
         sendRouterAdvertisement(false /* waitForRs */, (short) 1800, pio, rdnss);
 
         // Unblock the IpClient handler and ENOBUFS should happen then.
diff --git a/tests/integration/common/android/net/networkstack/TestNetworkStackServiceClient.kt b/tests/integration/common/android/net/networkstack/TestNetworkStackServiceClient.kt
index 481bfdc..bff1088 100644
--- a/tests/integration/common/android/net/networkstack/TestNetworkStackServiceClient.kt
+++ b/tests/integration/common/android/net/networkstack/TestNetworkStackServiceClient.kt
@@ -35,8 +35,8 @@
         private val TAG = "TestNetworkStackServiceClient"
         private val testNetworkStackServiceAction = "android.net.INetworkStackConnector.Test"
         private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
+        private val component = getNetworkStackComponent(testNetworkStackServiceAction)
         private val networkStackVersion by lazy {
-            val component = getNetworkStackComponent(testNetworkStackServiceAction)
             val info = context.packageManager.getPackageInfo(component.packageName, 0 /* flags */)
             info.longVersionCode
         }
@@ -88,4 +88,8 @@
     fun disconnect() {
         InstrumentationRegistry.getInstrumentation().context.unbindService(serviceConnection)
     }
+
+    fun getNetworkStackPackageName(): String {
+        return component.packageName
+    }
 }
diff --git a/tests/integration/root/android/net/ip/IpClientRootTest.kt b/tests/integration/root/android/net/ip/IpClientRootTest.kt
index 7359e05..f1fa1c7 100644
--- a/tests/integration/root/android/net/ip/IpClientRootTest.kt
+++ b/tests/integration/root/android/net/ip/IpClientRootTest.kt
@@ -22,8 +22,8 @@
 import android.net.IIpMemoryStore
 import android.net.IIpMemoryStoreCallbacks
 import android.net.NetworkStackIpMemoryStore
-import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener
 import android.net.ipmemorystore.NetworkAttributes
+import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener
 import android.net.ipmemorystore.Status
 import android.net.networkstack.TestNetworkStackServiceClient
 import android.os.Process
@@ -265,4 +265,19 @@
     override fun storeNetworkAttributes(l2Key: String, na: NetworkAttributes) {
         mStore.storeNetworkAttributes(l2Key, na, null /* listener */)
     }
+
+    private fun readNudSolicitNumFromResource(name: String): Int {
+        val packageName = nsClient.getNetworkStackPackageName()
+        val resource = mContext.createPackageContext(packageName, 0).getResources()
+        val id = resource.getIdentifier(name, "integer", packageName)
+        return resource.getInteger(id)
+    }
+
+    override fun readNudSolicitNumInSteadyStateFromResource(): Int {
+        return readNudSolicitNumFromResource("config_nud_steadystate_solicit_num")
+    }
+
+    override fun readNudSolicitNumPostRoamingFromResource(): Int {
+        return readNudSolicitNumFromResource("config_nud_postroaming_solicit_num")
+    }
 }
diff --git a/tests/integration/signature/android/net/ip/IpClientSignatureTest.kt b/tests/integration/signature/android/net/ip/IpClientSignatureTest.kt
index c9e33b5..26938b6 100644
--- a/tests/integration/signature/android/net/ip/IpClientSignatureTest.kt
+++ b/tests/integration/signature/android/net/ip/IpClientSignatureTest.kt
@@ -21,9 +21,9 @@
 import android.net.ipmemorystore.Status
 import android.net.ipmemorystore.Status.SUCCESS
 import android.util.ArrayMap
+import org.mockito.ArgumentCaptor
 import org.mockito.Mockito.any
 import org.mockito.Mockito.doAnswer
-import org.mockito.ArgumentCaptor
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.never
 import org.mockito.Mockito.timeout
@@ -33,6 +33,9 @@
  * Tests for IpClient, run with signature permissions.
  */
 class IpClientSignatureTest : IpClientIntegrationTestCommon() {
+    private val DEFAULT_NUD_SOLICIT_NUM_POST_ROAM = 5
+    private val DEFAULT_NUD_SOLICIT_NUM_STEADY_STATE = 10
+
     private val mEnabledFeatures = ArrayMap<String, Boolean>()
 
     override fun makeIIpClient(ifaceName: String, cb: IIpClientCallbacks): IIpClient {
@@ -68,4 +71,12 @@
             true
         }.`when`(mIpMemoryStore).retrieveNetworkAttributes(eq(l2Key), any())
     }
+
+    override fun readNudSolicitNumInSteadyStateFromResource(): Int {
+        return DEFAULT_NUD_SOLICIT_NUM_STEADY_STATE
+    }
+
+    override fun readNudSolicitNumPostRoamingFromResource(): Int {
+        return DEFAULT_NUD_SOLICIT_NUM_POST_ROAM
+    }
 }
diff --git a/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java b/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java
index 85d3842..968acee 100644
--- a/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java
+++ b/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java
@@ -54,6 +54,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.netlink.NetlinkUtils;
 import com.android.net.module.util.netlink.StructNlMsgHdr;
 import com.android.networkstack.apishim.ConstantsShim;
 import com.android.testutils.DevSdkIgnoreRule;
@@ -189,13 +190,13 @@
         final ByteBuffer buffer = ByteBuffer.allocate(TEST_BUFFER_SIZE);
 
         buffer.position(TEST_BUFFER_SIZE - StructNlMsgHdr.STRUCT_SIZE);
-        assertTrue(TcpSocketTracker.enoughBytesRemainForValidNlMsg(buffer));
+        assertTrue(NetlinkUtils.enoughBytesRemainForValidNlMsg(buffer));
         // Remaining buffer size is less than a valid StructNlMsgHdr size.
         buffer.position(TEST_BUFFER_SIZE - StructNlMsgHdr.STRUCT_SIZE + 1);
-        assertFalse(TcpSocketTracker.enoughBytesRemainForValidNlMsg(buffer));
+        assertFalse(NetlinkUtils.enoughBytesRemainForValidNlMsg(buffer));
 
         buffer.position(TEST_BUFFER_SIZE);
-        assertFalse(TcpSocketTracker.enoughBytesRemainForValidNlMsg(buffer));
+        assertFalse(NetlinkUtils.enoughBytesRemainForValidNlMsg(buffer));
     }
 
     @Test @IgnoreUpTo(Build.VERSION_CODES.Q) // TCP info parsing is not supported on Q